From 56f83cb75b155d0de081a21619cdcabad4da65c5 Mon Sep 17 00:00:00 2001 From: hns Date: Tue, 8 Aug 2006 15:37:09 +0000 Subject: [PATCH] * From Manfred's last patch for bug 468: - Factor out repetitive SQL query building tasks into DbMapping.appendCondition() - Implement automatic extended prototype filter for collections - add prototype ids, but with simplified implementation (be agnostic about numeric ids vs. prototype names) * Rewrite relational node insertion code * Make better use of DbColumn class wherever possible * Minor code improvements throughout the place --- src/helma/objectmodel/db/DbColumn.java | 53 +++-- src/helma/objectmodel/db/DbMapping.java | 228 ++++++++++++++++------ src/helma/objectmodel/db/NodeManager.java | 204 ++++++------------- src/helma/objectmodel/db/Relation.java | 87 +++------ 4 files changed, 297 insertions(+), 275 deletions(-) diff --git a/src/helma/objectmodel/db/DbColumn.java b/src/helma/objectmodel/db/DbColumn.java index f2ce4dfc..5c101cb8 100644 --- a/src/helma/objectmodel/db/DbColumn.java +++ b/src/helma/objectmodel/db/DbColumn.java @@ -16,24 +16,23 @@ package helma.objectmodel.db; +import java.sql.Types; /** - * A class that encapsulates the Column name and data type of a - * column in a relational table. + * A class that encapsulates the Column name and data type of a column in a + * relational table. */ public final class DbColumn { private final String name; private final int type; private final Relation relation; - + private final boolean isId; private final boolean isPrototype; private final boolean isName; - private final boolean isMapped; - /** - * Constructor + * Constructor */ public DbColumn(String name, int type, Relation rel, DbMapping dbmap) { this.name = name; @@ -47,47 +46,45 @@ public final class DbColumn { isId = name.equalsIgnoreCase(dbmap.getIDField()); isPrototype = name.equalsIgnoreCase(dbmap.getPrototypeField()); isName = name.equalsIgnoreCase(dbmap.getNameField()); - - isMapped = relation != null || isId || isPrototype || isName; } /** - * Get the column name. + * Get the column name. */ public String getName() { return name; } /** - * Get this columns SQL data type. + * Get this columns SQL data type. */ public int getType() { return type; } /** - * Return the relation associated with this column. May be null. + * Return the relation associated with this column. May be null. */ public Relation getRelation() { return relation; } /** - * Returns true if this column serves as ID field for the prototype. + * Returns true if this column serves as ID field for the prototype. */ public boolean isIdField() { return isId; } /** - * Returns true if this column serves as prototype field for the prototype. + * Returns true if this column serves as prototype field for the prototype. */ public boolean isPrototypeField() { return isPrototype; } /** - * Returns true if this column serves as name field for the prototype. + * Returns true if this column serves as name field for the prototype. */ public boolean isNameField() { return isName; @@ -97,7 +94,33 @@ public final class DbColumn { * Returns true if this field is mapped by the prototype's db mapping. */ public boolean isMapped() { - return isMapped; + // Note: not sure if check for primitive or reference relation is really + // needed, but we did it before, so we leave it in for safety. + return isId || isPrototype || isName || + (relation != null && (relation.isPrimitive() || relation.isReference())); + } + + /** + * Checks whether values for this column need to be quoted in insert/update + * stmts + * + * @return true if values need to be wrapped in quotes + */ + public boolean needsQuotes() { + switch (type) { + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + return true; + default: + return false; + } } } diff --git a/src/helma/objectmodel/db/DbMapping.java b/src/helma/objectmodel/db/DbMapping.java index 4294e79d..0224adf5 100644 --- a/src/helma/objectmodel/db/DbMapping.java +++ b/src/helma/objectmodel/db/DbMapping.java @@ -65,7 +65,7 @@ public final class DbMapping { // Case insensitive, keys are stored in upper case so // lookups must do a toUpperCase(). private HashMap db2prop; - + // list of columns to fetch from db private DbColumn[] columns = null; @@ -89,8 +89,13 @@ public final class DbMapping { // db field used to identify name of prototype to use for object instantiation private String protoField; - // name of parent prototype, if any - private String extendsProto; + // Used to map prototype ids to prototype names for + // prototypes which extend the prototype represented by + // this DbMapping. + private ResourceProperties extensionMap; + + // a numeric or literal id used to represent this type in db + private String extensionId; // dbmapping of parent prototype, if any private DbMapping parentMapping; @@ -221,7 +226,7 @@ public final class DbMapping { lastTypeChange = props.lastModified(); // see if this prototype extends (inherits from) any other prototype - extendsProto = props.getProperty("_extends"); + String extendsProto = props.getProperty("_extends"); if (extendsProto != null) { parentMapping = app.getDbMapping(extendsProto); @@ -245,6 +250,10 @@ public final class DbMapping { parentMapping = null; } + // check if there is an extension-id specified inside the type.properties + extensionId = props.getProperty("_extensionId", typename); + registerExtension(extensionId, typename); + // set the parent prototype in the corresponding Prototype object! // this was previously done by TypeManager, but we need to do it // ourself because DbMapping.update() may be called by other code than @@ -361,6 +370,57 @@ public final class DbMapping { } } + /** + * Add the given extensionId and the coresponding prototypename + * to extensionMap for later lookup. + * @param extID the id mapping to the prototypename recogniced by helma + * @param extName the name of the extending prototype + */ + private void registerExtension(String extID, String extName) { + // lazy initialization of extensionMap + if (extensionMap == null) { + extensionMap = new ResourceProperties(); + extensionMap.setIgnoreCase(true); + } else if (extensionMap.containsValue(extName)) { + // remove any preexisting mapping for the given childmapping + extensionMap.values().remove(extName); + } + extensionMap.setProperty(extID, extName); + if (inheritsStorage()) { + parentMapping.registerExtension(extID, extName); + } + } + + /** + * Returns the Set of Prototypes extending this prototype + * @return the Set of Prototypes extending this prototype + */ + public String[] getExtensions() { + return extensionMap == null + ? new String[] { extensionId } + : (String[]) extensionMap.keySet().toArray(new String[0]); + } + + /** + * Looks up the prototype-name identified by the given integer value + * @param id the id specified for the prototype + * @return the name of the extending prototype + */ + public String getPrototypeName(String id) { + if (inheritsStorage()) { + return parentMapping.getPrototypeName(id); + } + // fallback to base-prototype if the proto isn't recogniced + return extensionMap.getProperty(id, typename); + } + + /** + * get the id-value of this extension + */ + public String getExtensionId() { + return extensionId; + } + /** * Method in interface Updatable. */ @@ -459,7 +519,7 @@ public final class DbMapping { * Get the name of this type's parent type, if any. */ public String getExtends() { - return extendsProto; + return parentMapping == null ? null : parentMapping.getTypeName(); } /** @@ -937,27 +997,20 @@ public final class DbMapping { */ public DbColumn getColumn(String columnName) throws ClassNotFoundException, SQLException { - DbColumn col = (DbColumn) columnMap.get(columnName); - if (col == null) { DbColumn[] cols = columns; - if (cols == null) { cols = getColumns(); } - for (int i = 0; i < cols.length; i++) { if (columnName.equalsIgnoreCase(cols[i].getName())) { col = cols[i]; - break; } } - columnMap.put(columnName, col); } - return col; } @@ -1057,24 +1110,22 @@ public final class DbMapping { } StringBuffer b1 = new StringBuffer("INSERT INTO "); + StringBuffer b2 = new StringBuffer(" ) VALUES ( "); b1.append(getTableName()); b1.append(" ( "); - b1.append(getIDField()); - - StringBuffer b2 = new StringBuffer(" ) VALUES ( ?"); DbColumn[] cols = getColumns(); - + boolean needsComma = false; + for (int i = 0; i < cols.length; i++) { - Relation rel = cols[i].getRelation(); - String name = cols[i].getName(); - - if (((rel != null) && (rel.isPrimitive() || - rel.isReference())) || - name.equalsIgnoreCase(getNameField()) || - name.equalsIgnoreCase(getPrototypeField())) { - b1.append(", ").append(cols[i].getName()); - b2.append(", ?"); + if (cols[i].isMapped()) { + if (needsComma) { + b1.append(", "); + b2.append(", "); + } + b1.append(cols[i].getName()); + b2.append("?"); + needsComma = true; } } @@ -1115,36 +1166,16 @@ public final class DbMapping { * Return true if values for the column identified by the parameter need * to be quoted in SQL queries. */ - public boolean needsQuotes(String columnName) throws SQLException { + public boolean needsQuotes(String columnName) throws SQLException, ClassNotFoundException { if ((tableName == null) && (parentMapping != null)) { return parentMapping.needsQuotes(columnName); } - - try { - DbColumn col = getColumn(columnName); - - // This is not a mapped column. In case of doubt, add quotes. - if (col == null) { - return true; - } - - switch (col.getType()) { - case Types.CHAR: - case Types.VARCHAR: - case Types.LONGVARCHAR: - case Types.BINARY: - case Types.VARBINARY: - case Types.LONGVARBINARY: - case Types.DATE: - case Types.TIME: - case Types.TIMESTAMP: - return true; - - default: - return false; - } - } catch (Exception x) { - throw new SQLException(x.getMessage()); + DbColumn col = getColumn(columnName); + // This is not a mapped column. In case of doubt, add quotes. + if (col == null) { + return true; + } else { + return col.needsQuotes(); } } @@ -1367,10 +1398,7 @@ public final class DbMapping { // note: tableName and dbSourceName are nulled out in update() if they // are inherited from the parent mapping. This way we know that // storage is not inherited if either of them is not null. - if (parentMapping == null || tableName != null || dbSourceName != null) { - return false; - } - return true; + return parentMapping != null && tableName == null && dbSourceName == null; } /** @@ -1451,4 +1479,90 @@ public final class DbMapping { protected void addDependency(DbMapping dbmap) { this.dependentMappings.add(dbmap); } -} + + /** + * Append a sql-condition for the given column which must have + * one of the values contained inside the given Set to the given + * StringBuffer. + * @param q the StringBuffer to append to + * @param column the column which must match one of the values + * @param values the list of values + * @throws SQLException + */ + protected void appendCondition(StringBuffer q, String column, String[] values) + throws SQLException, ClassNotFoundException { + if (values.length == 1) { + appendCondition(q, column, values[0]); + return; + } + if (column.indexOf('(') == -1 && column.indexOf('.') == -1) { + q.append(getTableName()).append("."); + } + q.append(column).append(" in ("); + + if (needsQuotes(column)) { + for (int i = 0; i < values.length; i++) { + if (i > 0) + q.append(", "); + q.append("'").append(escape(values[i])).append("'"); + } + } else { + for (int i = 0; i < values.length; i++) { + if (i > 0) + q.append(", "); + q.append(values[i]); + } + } + q.append(")"); + } + + /** + * Append a sql-condition for the given column which must have + * the value given to the given StringBuffer. + * @param q the StringBuffer to append to + * @param column the column which must match one of the values + * @param val the value + * @throws SQLException + */ + protected void appendCondition(StringBuffer q, String column, String val) + throws SQLException, ClassNotFoundException { + if (column.indexOf('(') == -1 && column.indexOf('.') == -1) { + q.append(getTableName()).append("."); + } + q.append(column).append(" = "); + + if (needsQuotes(column)) { + q.append("'").append(escape(val)).append("'"); + } else { + q.append(val); + } + } + + /** + * a utility method to escape single quotes used for inserting + * string-values into relational databases. + * Searches for "'" characters and escapes them by duplicating them (= "''") + * @param str the string to escape + * @return the escaped string + */ + static String escape(String str) { + if (str == null) { + return null; + } else if (str.indexOf("'") < 0) { + return str; + } + + int l = str.length(); + StringBuffer sbuf = new StringBuffer(l + 10); + + for (int i = 0; i < l; i++) { + char c = str.charAt(i); + + if (c == '\'') { + sbuf.append('\''); + } + sbuf.append(c); + } + return sbuf.toString(); + } +} \ No newline at end of file diff --git a/src/helma/objectmodel/db/NodeManager.java b/src/helma/objectmodel/db/NodeManager.java index d444b2ef..24fba252 100644 --- a/src/helma/objectmodel/db/NodeManager.java +++ b/src/helma/objectmodel/db/NodeManager.java @@ -475,50 +475,36 @@ public final class NodeManager { String insertString = dbm.getInsert(); PreparedStatement stmt = con.prepareStatement(insertString); - // app.logEvent ("inserting relational node: "+node.getID ()); + // app.logEvent ("inserting relational node: " + node.getID ()); DbColumn[] columns = dbm.getColumns(); - String nameField = dbm.getNameField(); - String prototypeField = dbm.getPrototypeField(); - long logTimeStart = logSql ? System.currentTimeMillis() : 0; try { int stmtNumber = 1; - // first column of insert statement is always the primary key - stmt.setString(stmtNumber, node.getID()); - - Hashtable propMap = node.getPropMap(); - for (int i = 0; i < columns.length; i++) { - Relation rel = columns[i].getRelation(); - Property p = null; - - if (rel != null && propMap != null && (rel.isPrimitive() || rel.isReference())) { - p = (Property) propMap.get(rel.getPropName()); - } - - String name = columns[i].getName(); - - if (!((rel != null) && (rel.isPrimitive() || rel.isReference())) && - !name.equalsIgnoreCase(nameField) && - !name.equalsIgnoreCase(prototypeField)) { - continue; - } - - stmtNumber++; - if (p!=null) { - this.setStatementValues (stmt, stmtNumber, p, columns[i].getType()); - } else if (name.equalsIgnoreCase(nameField)) { - stmt.setString(stmtNumber, node.getName()); - } else if (name.equalsIgnoreCase(prototypeField)) { - stmt.setString(stmtNumber, node.getPrototype()); + DbColumn col = columns[i]; + if (!col.isMapped()) + continue; + if (col.isIdField()) { + setStatementValue(stmt, stmtNumber, node.getID(), col); + } else if (col.isPrototypeField()) { + setStatementValue(stmt, stmtNumber, dbm.getExtensionId(), col); } else { - stmt.setNull(stmtNumber, columns[i].getType()); + Relation rel = col.getRelation(); + Property p = rel == null ? null : node.getProperty(rel.getPropName()); + + if (p != null) { + setStatementValue(stmt, stmtNumber, p, col.getType()); + } else if (col.isNameField()) { + stmt.setString(stmtNumber, node.getName()); + } else { + stmt.setNull(stmtNumber, col.getType()); + } } + stmtNumber += 1; } - stmt.executeUpdate(); } finally { @@ -533,7 +519,6 @@ public final class NodeManager { } catch (Exception ignore) {} } } - } /** @@ -607,16 +592,7 @@ public final class NodeManager { } b.append(" WHERE "); - b.append(dbm.getIDField()); - b.append(" = "); - - if (dbm.needsQuotes(dbm.getIDField())) { - b.append("'"); - b.append(escape(node.getID())); - b.append("'"); - } else { - b.append(node.getID()); - } + dbm.appendCondition(b, dbm.getIDField(), node.getID()); Connection con = dbm.getConnection(); // set connection to write mode @@ -637,7 +613,7 @@ public final class NodeManager { Relation rel = dbm.propertyToRelation(p.getName()); stmtNumber++; - this.setStatementValues (stmt, stmtNumber, p, rel.getColumnType()); + setStatementValue(stmt, stmtNumber, p, rel.getColumnType()); p.dirty = false; @@ -1233,10 +1209,8 @@ public final class NodeManager { throws Exception { DbMapping dbm = rel.otherType; - if ((dbm == null) || !dbm.isRelational()) { - // this does nothing for objects in the embedded database - return; - } else { + // this does nothing for objects in the embedded database + if (dbm != null && dbm.isRelational()) { int missing = cache.containsKeys(keys); if (missing > 0) { @@ -1251,39 +1225,17 @@ public final class NodeManager { long logTimeStart = logSql ? System.currentTimeMillis() : 0; try { - StringBuffer b = dbm.getSelect(null); - + StringBuffer b = dbm.getSelect(null).append(" WHERE "); String idfield = (rel.groupby != null) ? rel.groupby : dbm.getIDField(); - boolean needsQuotes = dbm.needsQuotes(idfield); - b.append(" WHERE "); - b.append(dbm.getTableName()); - b.append("."); - b.append(idfield); - b.append(" IN ("); - - boolean first = true; - - for (int i = 0; i < keys.length; i++) { - if (keys[i] != null) { - if (!first) { - b.append(','); - } else { - first = false; - } - - if (needsQuotes) { - b.append("'"); - b.append(escape(keys[i].getID())); - b.append("'"); - } else { - b.append(keys[i].getID()); - } - } + String[] ids = new String[missing]; + int j = 0; + for (int k = 0; k < keys.length; k++) { + if (keys[k] != null) + ids[j++] = keys[k].getID(); } - b.append(") "); - + dbm.appendCondition(b, idfield, ids); dbm.addJoinConstraints(b, " AND "); if (rel.groupby != null) { @@ -1388,7 +1340,7 @@ public final class NodeManager { } } } catch (Exception x) { - System.err.println ("Error in prefetchNodes(): "+x); + app.logError("Error in prefetchNodes()", x); } finally { if (logSql) { long logTimeStop = System.currentTimeMillis(); @@ -1591,22 +1543,10 @@ public final class NodeManager { DbColumn[] columns = dbm.getColumns(); Relation[] joins = dbm.getJoins(); - StringBuffer b = dbm.getSelect(null).append("WHERE ") - .append(dbm.getTableName()) - .append(".") - .append(idfield) - .append(" = "); - - if (dbm.needsQuotes(idfield)) { - b.append("'"); - b.append(escape(kstr)); - b.append("'"); - } else { - b.append(kstr); - } - + + StringBuffer b = dbm.getSelect(null).append("WHERE "); + dbm.appendCondition(b, idfield, kstr); dbm.addJoinConstraints(b, " AND "); - query = b.toString(); ResultSet rs = stmt.executeQuery(query); @@ -1689,14 +1629,7 @@ public final class NodeManager { if (home.getSubnodeRelation() != null && !rel.isComplexReference()) { // combine our key with the constraints in the manually set subnode relation b.append(" WHERE "); - if (rel.accessName.indexOf('(') == -1 && rel.accessName.indexOf('.') == -1) { - b.append(dbm.getTableName()); - b.append("."); - } - b.append(rel.accessName); - b.append(" = '"); - b.append(escape(kstr)); - b.append("'"); + dbm.appendCondition(b, rel.accessName, kstr); // add join contraints in case this is an old oracle style join dbm.addJoinConstraints(b, " AND "); // add potential constraints from manually set subnodeRelation @@ -1778,7 +1711,8 @@ public final class NodeManager { // set prototype? if (columns[i].isPrototypeField()) { - protoName = rs.getString(i+1+offset); + String protoId = rs.getString(i + 1 + offset); + protoName = dbm.getPrototypeName(protoId); if (protoName != null) { dbmap = getDbMapping(protoName); @@ -1795,7 +1729,7 @@ public final class NodeManager { // set id? if (columns[i].isIdField()) { - id = rs.getString(i+1+offset); + id = rs.getString(i + 1 + offset); // if id == null, the object doesn't actually exist - return null if (id == null) { return null; @@ -1804,14 +1738,14 @@ public final class NodeManager { // set name? if (columns[i].isNameField()) { - name = rs.getString(i+1+offset); + name = rs.getString(i + 1 + offset); } Property newprop = new Property(node); switch (columns[i].getType()) { case Types.BIT: - newprop.setBooleanValue(rs.getBoolean(i+1+offset)); + newprop.setBooleanValue(rs.getBoolean(i + 1 + offset)); break; @@ -1819,21 +1753,21 @@ public final class NodeManager { case Types.BIGINT: case Types.SMALLINT: case Types.INTEGER: - newprop.setIntegerValue(rs.getLong(i+1+offset)); + newprop.setIntegerValue(rs.getLong(i + 1 + offset)); break; case Types.REAL: case Types.FLOAT: case Types.DOUBLE: - newprop.setFloatValue(rs.getDouble(i+1+offset)); + newprop.setFloatValue(rs.getDouble(i + 1 + offset)); break; case Types.DECIMAL: case Types.NUMERIC: - BigDecimal num = rs.getBigDecimal(i+1+offset); + BigDecimal num = rs.getBigDecimal(i + 1 + offset); if (num == null) { break; @@ -1849,16 +1783,16 @@ public final class NodeManager { case Types.VARBINARY: case Types.BINARY: - newprop.setStringValue(rs.getString(i+1+offset)); + newprop.setStringValue(rs.getString(i + 1 + offset)); break; case Types.LONGVARBINARY: case Types.LONGVARCHAR: try { - newprop.setStringValue(rs.getString(i+1+offset)); + newprop.setStringValue(rs.getString(i + 1 + offset)); } catch (SQLException x) { - Reader in = rs.getCharacterStream(i+1+offset); + Reader in = rs.getCharacterStream(i + 1 + offset); char[] buffer = new char[2048]; int read = 0; int r; @@ -1883,14 +1817,14 @@ public final class NodeManager { case Types.CHAR: case Types.VARCHAR: case Types.OTHER: - newprop.setStringValue(rs.getString(i+1+offset)); + newprop.setStringValue(rs.getString(i + 1 + offset)); break; case Types.DATE: case Types.TIME: case Types.TIMESTAMP: - newprop.setDateValue(rs.getTimestamp(i+1+offset)); + newprop.setDateValue(rs.getTimestamp(i + 1 + offset)); break; @@ -1912,7 +1846,7 @@ public final class NodeManager { break; default: - newprop.setStringValue(rs.getString(i+1+offset)); + newprop.setStringValue(rs.getString(i + 1 + offset)); break; } @@ -1996,32 +1930,6 @@ public final class NodeManager { return app.getDbMapping(protoname); } - // a utility method to escape single quotes - private String escape(String str) { - if (str == null) { - return null; - } - - if (str.indexOf("'") < 0) { - return str; - } - - int l = str.length(); - StringBuffer sbuf = new StringBuffer(l + 10); - - for (int i = 0; i < l; i++) { - char c = str.charAt(i); - - if (c == '\'') { - sbuf.append('\''); - } - - sbuf.append(c); - } - - return sbuf.toString(); - } - /** * Get an array of the the keys currently held in the object cache */ @@ -2144,7 +2052,19 @@ public final class NodeManager { } } - private void setStatementValues (PreparedStatement stmt, int stmtNumber, Property p, int columnType) throws SQLException { + private void setStatementValue(PreparedStatement stmt, int stmtNumber, String value, DbColumn col) + throws SQLException { + if (value == null) { + stmt.setNull(stmtNumber, col.getType()); + } else if (col.needsQuotes()) { + stmt.setString(stmtNumber, value); + } else { + stmt.setLong(stmtNumber, Long.parseLong(value)); + } + } + + private void setStatementValue(PreparedStatement stmt, int stmtNumber, Property p, int columnType) + throws SQLException { if (p.getValue() == null) { stmt.setNull(stmtNumber, columnType); } else { diff --git a/src/helma/objectmodel/db/Relation.java b/src/helma/objectmodel/db/Relation.java index ad721185..20571212 100644 --- a/src/helma/objectmodel/db/Relation.java +++ b/src/helma/objectmodel/db/Relation.java @@ -813,30 +813,17 @@ public final class Relation { * and a local object. */ public String buildQuery(INode home, INode nonvirtual, String kstr, String pre, - boolean useOrder) throws SQLException { + boolean useOrder) + throws SQLException, ClassNotFoundException { StringBuffer q = new StringBuffer(); String prefix = pre; if (kstr != null && !isComplexReference()) { q.append(prefix); - String accessColumn = (accessName == null) ? otherType.getIDField() : accessName; - - if (accessColumn.indexOf('(') == -1 && accessColumn.indexOf('.') == -1) { - q.append(otherType.getTableName()); - q.append("."); - } - q.append(accessColumn); - q.append(" = "); - - // check if column is string type and value needs to be quoted - if (otherType.needsQuotes(accessColumn)) { - q.append("'"); - q.append(escape(kstr)); - q.append("'"); - } else { - q.append(escape(kstr)); - } + String accessColumn = (accessName == null) ? + otherType.getIDField() : accessName; + otherType.appendCondition(q, accessColumn, kstr); prefix = " AND "; } @@ -906,7 +893,7 @@ public final class Relation { } // end column version if (value != null) { - q.append(escape(value.toString())); + q.append(DbMapping.escape(value.toString())); } else { q.append("NULL"); } @@ -930,7 +917,7 @@ public final class Relation { */ public void renderConstraints(StringBuffer q, INode home, INode nonvirtual, String prefix) - throws SQLException { + throws SQLException, ClassNotFoundException { if (constraints.length > 1 && logicalOperator != AND) { q.append(prefix); @@ -948,6 +935,23 @@ public final class Relation { q.append(")"); prefix = " AND "; } + + // also take the prototype into consideration if someone + // specifies an extension of an prototype inside the brakets of + // a type.properties's collection, only nodes having this proto + // sould appear inside the collection + if (otherType.inheritsStorage()) { + String protoField = otherType.getPrototypeField(); + String[] extensions = otherType.getExtensions(); + + // extensions should never be null for extension- and + // extended prototypes. nevertheless we check it here + if (extensions != null) { + q.append(prefix); + otherType.appendCondition(q, protoField, extensions); + prefix = " AND "; + } + } if (filter != null) { appendFilter(q, nonvirtual, prefix); @@ -1214,32 +1218,6 @@ public final class Relation { return map; } - // a utility method to escape single quotes - String escape(String str) { - if (str == null) { - return null; - } - - if (str.indexOf("'") < 0) { - return str; - } - - int l = str.length(); - StringBuffer sbuf = new StringBuffer(l + 10); - - for (int i = 0; i < l; i++) { - char c = str.charAt(i); - - if (c == '\'') { - sbuf.append('\''); - } - - sbuf.append(c); - } - - return sbuf.toString(); - } - /** * * @@ -1279,7 +1257,7 @@ public final class Relation { } public void addToQuery(StringBuffer q, INode home, INode nonvirtual) - throws SQLException { + throws SQLException, ClassNotFoundException { String local = null; INode ref = isGroupby ? home : nonvirtual; @@ -1292,20 +1270,7 @@ public final class Relation { local = ref.getString(homeprop); } - if (foreignName.indexOf('(') == -1 && foreignName.indexOf('.') == -1) { - q.append(otherType.getTableName()); - q.append("."); - } - q.append(foreignName); - q.append(" = "); - - if (otherType.needsQuotes(foreignName)) { - q.append("'"); - q.append(escape(local)); - q.append("'"); - } else { - q.append(escape(local)); - } + otherType.appendCondition(q, foreignName, local); } public boolean foreignKeyIsPrimary() {