diff --git a/src/helma/objectmodel/db/Node.java b/src/helma/objectmodel/db/Node.java index a0fe2ea2..0837d40f 100644 --- a/src/helma/objectmodel/db/Node.java +++ b/src/helma/objectmodel/db/Node.java @@ -246,8 +246,11 @@ public final class Node implements INode, Serializable { this.subnodes = subnodes; } - protected synchronized void checkWriteLock() { - // System.err.println ("registering writelock for "+this.getName ()+" ("+lock+") to "+Thread.currentThread ()); + /** + * Get the write lock on this node, throwing a ConcurrencyException if the + * lock is already held by another thread. + */ + synchronized void checkWriteLock() { if (state == TRANSIENT) { return; // no need to lock transient node } @@ -275,11 +278,17 @@ public final class Node implements INode, Serializable { lock = current; } - protected synchronized void clearWriteLock() { + /** + * Clear the write lock on this node. + */ + synchronized void clearWriteLock() { lock = null; } - protected void markAs(int s) { + /** + * Set this node's state, registering it with the transactor if necessary. + */ + void markAs(int s) { if ((state == INVALID) || (state == VIRTUAL) || (state == TRANSIENT)) { return; } @@ -304,18 +313,37 @@ public final class Node implements INode, Serializable { } /** + * Register this node as parent node with the transactor so that + * setLastSubnodeChange is called when the transaction completes. + */ + void registerSubnodeChange() { + if (Thread.currentThread() instanceof Transactor) { + Transactor tx = (Transactor) Thread.currentThread(); + tx.visitParentNode(this); + } + } + + /** + * Called by the transactor on registered parent nodes to mark the + * child index as changed + */ + void setLastSubnodeChange(long t) { + lastSubnodeChange = t; + } + + /** + * Gets this node's stateas defined in the INode interface * - * - * @return ... + * @return this node's state */ public int getState() { return state; } /** + * Sets this node's state as defined in the INode interface * - * - * @param s ... + * @param s this node's new state */ public void setState(int s) { this.state = s; @@ -793,8 +821,6 @@ public final class Node implements INode, Serializable { node.makePersistable(); } - // if (n.indexOf('/') > -1) - // throw new RuntimeException ("\"/\" found in Node name."); // only mark this node as modified if subnodes are not in relational db // pointing to this node. if (!ignoreSubnodeChange() && ((state == CLEAN) || (state == DELETED))) { @@ -830,7 +856,6 @@ public final class Node implements INode, Serializable { } catch (Exception x) { System.err.println("Error adding groupby: " + x); - // x.printStackTrace (); return null; } } @@ -851,7 +876,6 @@ public final class Node implements INode, Serializable { if (subnodes == null) { subnodes = new ExternalizableVector(); } - subnodes.add(where, nhandle); // check if properties are subnodes (_properties.aresubnodes=true) @@ -861,7 +885,8 @@ public final class Node implements INode, Serializable { if ((prel != null) && (prel.accessName != null)) { Relation localrel = node.dbmap.columnNameToRelation(prel.accessName); - // if no relation from db column to prop name is found, assume that both are equal + // if no relation from db column to prop name is found, + // assume that both are equal String propname = (localrel == null) ? prel.accessName : localrel.propName; String prop = node.getString(propname); @@ -880,29 +905,29 @@ public final class Node implements INode, Serializable { } if (!"root".equalsIgnoreCase(node.getPrototype())) { - // avoid calling getParent() because it would return bogus results for the not-anymore transient node + // avoid calling getParent() because it would return bogus results + // for the not-anymore transient node Node nparent = (node.parentHandle == null) ? null : node.parentHandle.getNode(nmgr); - // if the node doesn't have a parent yet, or it has one but it's transient while we are - // persistent, make this the nodes new parent. + // if the node doesn't have a parent yet, or it has one but it's + // transient while we are persistent, make this the nodes new parent. if ((nparent == null) || ((state != TRANSIENT) && (nparent.getState() == TRANSIENT))) { node.setParent(this); node.anonymous = true; } else if ((nparent != null) && ((nparent != this) || !node.anonymous)) { - // this makes the additional job of addLink, registering that we have a link to a node in our - // subnodes that actually sits somewhere else. This means that addLink and addNode - // are actually the same now. + // this makes the additional job of addLink, registering that we have + // a link to a node in our subnodes that actually sits somewhere else. + // This means that addLink and addNode are actually the same now. node.registerLinkFrom(this); } } } lastmodified = System.currentTimeMillis(); - lastSubnodeChange = lastmodified; + registerSubnodeChange(); - // Server.throwNodeEvent (new NodeEvent (this, NodeEvent.SUBNODE_ADDED, node)); return node; } @@ -1237,15 +1262,7 @@ public final class Node implements INode, Serializable { subnodes.remove(node.getHandle()); } - lastSubnodeChange = System.currentTimeMillis(); - - // check if the subnode is in relational db and has a link back to this - // which needs to be unset - /* - if (dbmap != null) { - Relation srel = dbmap.getSubnodeRelation(); - } - */ + registerSubnodeChange(); // check if subnodes are also accessed as properties. If so, also unset the property if ((dbmap != null) && (node.dbmap != null)) { @@ -1269,8 +1286,6 @@ public final class Node implements INode, Serializable { return; } - // Server.throwNodeEvent (new NodeEvent (node, NodeEvent.NODE_REMOVED)); - // Server.throwNodeEvent (new NodeEvent (this, NodeEvent.SUBNODE_REMOVED, node)); lastmodified = System.currentTimeMillis(); // nmgr.logEvent ("released node "+node +" from "+this+" oldobj = "+what); @@ -1354,7 +1369,8 @@ public final class Node implements INode, Serializable { return -1; } - // if the node contains relational groupby subnodes, the subnodes vector contains the names instead of ids. + // if the node contains relational groupby subnodes, the subnodes vector + // contains the names instead of ids. if (!(n instanceof Node)) { return -1; } @@ -1399,7 +1415,6 @@ public final class Node implements INode, Serializable { subnodeCount = nmgr.countNodes(this, subRel); lastSubnodeCount = System.currentTimeMillis(); } - return subnodeCount; } } @@ -1599,7 +1614,8 @@ public final class Node implements INode, Serializable { DbMapping pmap = (dbmap == null) ? null : dbmap.getExactPropertyMapping(propname); if ((pmap != null) && (prop != null) && (prop.getType() != IProperty.NODE)) { - // this is a relational node stored by id but we still think it's just a string. Fix it + // this is a relational node stored by id but we still think it's just a string. + // Fix it. prop.convertToNodeReference(pmap); } @@ -1619,11 +1635,13 @@ public final class Node implements INode, Serializable { } // so if we have a property relation and it does in fact link to another object... - if ((propRel != null) && (propRel.isCollection() || propRel.isComplexReference())) { - // in some cases we just want to create and set a generic node without consulting - // the NodeManager if it exists: When we get a collection (aka virtual node) - // from a transient node for the first time, or when we get a collection whose - // content objects are stored in the embedded XML data storage. + if ((propRel != null) && (propRel.isCollection() || + propRel.isComplexReference())) { + // in some cases we just want to create and set a generic node without + // consulting the NodeManager if it exists: When we get a collection + // (aka virtual node) from a transient node for the first time, or when + // we get a collection whose content objects are stored in the embedded + // XML data storage. if ((state == TRANSIENT) && propRel.virtual) { Node pn = new Node(propname, propRel.getPrototype(), nmgr); @@ -1827,7 +1845,6 @@ public final class Node implements INode, Serializable { propMap.put(p2, prop); } - // Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED)); lastmodified = System.currentTimeMillis(); if (state == CLEAN) { @@ -1922,8 +1939,9 @@ public final class Node implements INode, Serializable { if (((oldStorage == null) && (newStorage == null)) || ((oldStorage != null) && oldStorage.equals(newStorage))) { - dbmap.notifyDataChange(); - newmap.notifyDataChange(); + long now = System.currentTimeMillis(); + dbmap.setLastDataChange(now); + newmap.setLastDataChange(now); this.dbmap = newmap; this.prototype = value; } @@ -1931,7 +1949,6 @@ public final class Node implements INode, Serializable { } } - // Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED)); lastmodified = System.currentTimeMillis(); if (state == CLEAN) { @@ -1967,7 +1984,6 @@ public final class Node implements INode, Serializable { propMap.put(p2, prop); } - // Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED)); lastmodified = System.currentTimeMillis(); if (state == CLEAN) { @@ -2003,7 +2019,6 @@ public final class Node implements INode, Serializable { propMap.put(p2, prop); } - // Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED)); lastmodified = System.currentTimeMillis(); if (state == CLEAN) { @@ -2039,7 +2054,6 @@ public final class Node implements INode, Serializable { propMap.put(p2, prop); } - // Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED)); lastmodified = System.currentTimeMillis(); if (state == CLEAN) { @@ -2075,7 +2089,6 @@ public final class Node implements INode, Serializable { propMap.put(p2, prop); } - // Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED)); lastmodified = System.currentTimeMillis(); if (state == CLEAN) { @@ -2111,7 +2124,6 @@ public final class Node implements INode, Serializable { propMap.put(p2, prop); } - // Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED)); lastmodified = System.currentTimeMillis(); if (state == CLEAN) { @@ -2288,7 +2300,6 @@ public final class Node implements INode, Serializable { p.setStringValue(null); } - // Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED)); lastmodified = System.currentTimeMillis(); if (state == CLEAN) { @@ -2488,4 +2499,5 @@ public final class Node implements INode, Serializable { System.err.println("properties: " + propMap); System.err.println("links: " + links); } + } diff --git a/src/helma/objectmodel/db/NodeManager.java b/src/helma/objectmodel/db/NodeManager.java index 1ba86361..0949a806 100644 --- a/src/helma/objectmodel/db/NodeManager.java +++ b/src/helma/objectmodel/db/NodeManager.java @@ -151,7 +151,7 @@ public final class NodeManager { db.commitTransaction(txn); } catch (Exception x) { - System.err.println(">> " + x); + System.err.println(x); x.printStackTrace(); try { @@ -214,8 +214,9 @@ public final class NodeManager { node = (Node) cache.get(key); if ((node == null) || (node.getState() == Node.INVALID)) { - // The requested node isn't in the shared cache. Synchronize with key to make sure only one - // version is fetched from the database. + // The requested node isn't in the shared cache. + // Synchronize with key to make sure only one version is + // fetched from the database. if (key instanceof SyntheticKey) { Node parent = getNode(key.getParentKey()); Relation rel = parent.dbmap.getPropertyRelation(key.getID()); @@ -249,7 +250,6 @@ public final class NodeManager { tx.visitCleanNode(key, node); } - // tx.timer.endEvent ("getNode "+kstr); return node; } @@ -289,10 +289,11 @@ public final class NodeManager { Node node = tx.getVisitedNode(key); if ((node != null) && (node.getState() != Node.INVALID)) { - // we used to refresh the node in the main cache here to avoid the primary key entry being - // flushed from cache before the secondary one (risking duplicate nodes in cache) but - // we don't need to since we fetched the node from the threadlocal transactor cache and - // didn't refresh it in the main cache. + // we used to refresh the node in the main cache here to avoid the primary key + // entry being flushed from cache before the secondary one + // (risking duplicate nodes in cache) but we don't need to since we fetched + // the node from the threadlocal transactor cache and didn't refresh it in the + // main cache. return node; } @@ -326,8 +327,9 @@ public final class NodeManager { } if ((node == null) || (node.getState() == Node.INVALID)) { - // The requested node isn't in the shared cache. Synchronize with key to make sure only one - // version is fetched from the database. + // The requested node isn't in the shared cache. + // Synchronize with key to make sure only one version is fetched + // from the database. node = getNodeByRelation(tx.txn, home, kstr, rel); if (node != null) { @@ -459,10 +461,7 @@ public final class NodeManager { db.saveNode(txn, node.getID(), node); } else { insertRelationalNode(node, dbm, dbm.getConnection()); - dbm.notifyDataChange(); } - - // tx.timer.endEvent ("insertNode "+node); } /** @@ -645,8 +644,11 @@ public final class NodeManager { /** * Updates a modified node in the embedded db or an external relational database, depending * on its database mapping. + * + * @return true if the DbMapping of the updated Node is to be marked as updated via + * DbMapping.setLastDataChange */ - public void updateNode(IDatabase db, ITransaction txn, Node node) + public boolean updateNode(IDatabase db, ITransaction txn, Node node) throws IOException, SQLException, ClassNotFoundException { // Transactor tx = (Transactor) Thread.currentThread (); // tx.timer.beginEvent ("updateNode "+node); @@ -666,6 +668,8 @@ public final class NodeManager { StringBuffer b = dbm.getUpdate(); + // comma flag set after the first dirty column, also tells as + // if there are dirty columns at all boolean comma = false; for (int i = 0; i < props.length; i++) { @@ -699,9 +703,9 @@ public final class NodeManager { b.append(" = ?"); } - // if no columns were updated, return + // if no columns were updated, return false if (!comma) { - return; + return false; } b.append(" WHERE "); @@ -822,10 +826,6 @@ public final class NodeManager { } } } - - if (markMappingAsUpdated) { - dbm.notifyDataChange(); - } } // update may cause changes in the node's parent subnode array @@ -837,16 +837,15 @@ public final class NodeManager { } } - // tx.timer.endEvent ("updateNode "+node); + return markMappingAsUpdated; } /** - * Performs the actual deletion of a node from either the embedded or an external SQL database. + * Performs the actual deletion of a node from either the embedded or an external + * SQL database. */ public void deleteNode(IDatabase db, ITransaction txn, Node node) throws Exception { - // Transactor tx = (Transactor) Thread.currentThread (); - // tx.timer.beginEvent ("deleteNode "+node); DbMapping dbm = node.getDbMapping(); if ((dbm == null) || !dbm.isRelational()) { @@ -877,14 +876,10 @@ public final class NodeManager { } } } - - dbm.notifyDataChange(); } // node may still be cached via non-primary keys. mark as invalid node.setState(Node.INVALID); - - // tx.timer.endEvent ("deleteNode "+node); } /** @@ -994,23 +989,20 @@ public final class NodeManager { try { String q = null; + StringBuffer b = new StringBuffer("SELECT ").append(table).append('.') + .append(idfield).append(" FROM ") + .append(table); if (home.getSubnodeRelation() != null) { // subnode relation was explicitly set - q = new StringBuffer("SELECT ").append(table).append('.') - .append(idfield).append(" FROM ") - .append(table).append(" ") - .append(home.getSubnodeRelation()) - .toString(); + q = b.append(" ").append(home.getSubnodeRelation()).toString(); } else { // let relation object build the query - q = new StringBuffer("SELECT ").append(idfield).append(" FROM ") - .append(table) - .append(rel.buildQuery(home, - home.getNonVirtualParent(), - null, - " WHERE ", true)) - .toString(); + q = b.append(rel.buildQuery(home, + home.getNonVirtualParent(), + null, + " WHERE ", + true)).toString(); } if (logSql) { @@ -1099,8 +1091,11 @@ public final class NodeManager { q.append(home.getSubnodeRelation()); } else { // let relation object build the query - q.append(rel.buildQuery(home, home.getNonVirtualParent(), null, - " WHERE ", true)); + q.append(rel.buildQuery(home, + home.getNonVirtualParent(), + null, + " WHERE ", + true)); } if (logSql) { @@ -1341,21 +1336,18 @@ public final class NodeManager { try { String q = null; + StringBuffer b = new StringBuffer("SELECT count(*) FROM ").append(table); if (home.getSubnodeRelation() != null) { // use the manually set subnoderelation of the home node - q = new StringBuffer("SELECT count(*) FROM ").append(table).append(" ") - .append(home.getSubnodeRelation()) - .toString(); + q = b.append(" ").append(home.getSubnodeRelation()).toString(); } else { // let relation object build the query - q = new StringBuffer("SELECT count(*) FROM ").append(table) - .append(rel.buildQuery(home, - home.getNonVirtualParent(), - null, - " WHERE ", - false)) - .toString(); + q = b.append(rel.buildQuery(home, + home.getNonVirtualParent(), + null, + " WHERE ", + false)).toString(); } if (logSql) { @@ -1408,15 +1400,19 @@ public final class NodeManager { Statement stmt = null; try { - StringBuffer q = new StringBuffer("SELECT ").append(namefield).append(" FROM ") - .append(table); + StringBuffer q = new StringBuffer("SELECT ").append(namefield) + .append(" FROM ") + .append(table); if (home.getSubnodeRelation() != null) { q.append(" ").append(home.getSubnodeRelation()); } else { // let relation object build the query - q.append(rel.buildQuery(home, home.getNonVirtualParent(), null, - " WHERE ", true)); + q.append(rel.buildQuery(home, + home.getNonVirtualParent(), + null, + " WHERE ", + true)); } stmt = con.createStatement(); @@ -1477,13 +1473,11 @@ public final class NodeManager { DbColumn[] columns = dbm.getColumns(); Relation[] joins = dbm.getJoins(); - StringBuffer q = dbm.getSelect(); - - q.append("WHERE "); - q.append(dbm.getTableName()); - q.append("."); - q.append(idfield); - q.append(" = "); + StringBuffer q = dbm.getSelect().append("WHERE ") + .append(dbm.getTableName()) + .append(".") + .append(idfield) + .append(" = "); if (dbm.needsQuotes(idfield)) { q.append("'"); @@ -1578,8 +1572,11 @@ public final class NodeManager { q.append(home.getSubnodeRelation().trim().substring(5)); q.append(")"); } else { - q.append(rel.buildQuery(home, home.getNonVirtualParent(), kstr, - "WHERE ", false)); + q.append(rel.buildQuery(home, + home.getNonVirtualParent(), + kstr, + "WHERE ", + false)); } if (logSql) { @@ -1916,12 +1913,14 @@ public final class NodeManager { } synchronized (cache) { + long now = System.currentTimeMillis(); + for (Enumeration en = add.elements(); en.hasMoreElements();) { Node n = (Node) en.nextElement(); DbMapping dbm = app.getDbMapping(n.getPrototype()); if (dbm != null) { - dbm.notifyDataChange(); + dbm.setLastDataChange(now); } n.lastParentSet = -1; @@ -1937,7 +1936,7 @@ public final class NodeManager { DbMapping dbm = app.getDbMapping(n.getPrototype()); if (dbm != null) { - dbm.notifyDataChange(); + dbm.setLastDataChange(now); } n.setDbMapping(dbm); diff --git a/src/helma/objectmodel/db/Relation.java b/src/helma/objectmodel/db/Relation.java index 93218c39..a50245e9 100644 --- a/src/helma/objectmodel/db/Relation.java +++ b/src/helma/objectmodel/db/Relation.java @@ -48,13 +48,17 @@ public final class Relation { public final static int COMPLEX_REFERENCE = 3; // constraints linked together by OR or AND if applicable? - private int constraintsLogic; - public final static int CONSTRAINTS_AND = 0; - public final static int CONSTRAINTS_OR = 1; - public final static int CONSTRAINTS_XOR = 2; - public final String[] logicalOperators = {" AND ", " OR ", " XOR "}; - - // direct mapping is a very powerful feature: objects of some types can be directly accessed + public final static String AND = " AND "; + public final static String OR = " OR "; + public final static String XOR = " XOR "; + private String logicalOperator = AND; + + // prefix to use for symbolic names of joined tables. The name is composed + // from this prefix and the name of the property we're doing the join for + final static String JOIN_PREFIX = "_JOIN_"; + + // direct mapping is a very powerful feature: + // objects of some types can be directly accessed // by one of their properties/db fields. // public final static int DIRECT = 3; // the DbMapping of the type we come from @@ -85,22 +89,31 @@ public final class Relation { String prototype; String groupbyPrototype; String filter; + String additionalTables; int maxSize = 0; /** * This constructor makes a copy of an existing relation. Not all fields are copied, just those * which are needed in groupby- and virtual nodes defined by this relation. */ - public Relation(Relation rel) { - this.ownType = rel.ownType; - this.otherType = rel.otherType; - this.propName = rel.propName; - this.columnName = rel.columnName; - this.reftype = rel.reftype; - this.constraints = rel.constraints; - this.accessName = rel.accessName; - this.maxSize = rel.maxSize; - this.constraintsLogic = rel.constraintsLogic; + private Relation(Relation rel) { + // Note: prototype, groupby, groupbyPrototype and groupbyOrder aren't copied here. + // these are set by the individual get*Relation() methods as appropriate. + this.ownType = rel.ownType; + this.otherType = rel.otherType; + this.propName = rel.propName; + this.columnName = rel.columnName; + this.reftype = rel.reftype; + this.order = rel.order; + this.filter = rel.filter; + this.additionalTables = rel.additionalTables; + this.maxSize = rel.maxSize; + this.constraints = rel.constraints; + this.accessName = rel.accessName; + this.maxSize = rel.maxSize; + this.logicalOperator = rel.logicalOperator; + this.aggressiveLoading = rel.aggressiveLoading; + this.aggressiveCaching = rel.aggressiveCaching; } /** @@ -254,6 +267,14 @@ public final class Relation { } } + // get additional tables + additionalTables = props.getProperty(propName + ".filter.additionalTables"); + + if (additionalTables != null) { + if (additionalTables.trim().length() == 0) + additionalTables = null; + } + // get max size of collection String max = props.getProperty(propName + ".maxSize"); @@ -315,30 +336,24 @@ public final class Relation { // parse constraints logic if (cnst.size() > 1) { - String logic = props.getProperty(propName + ".logic"); - if ("and".equalsIgnoreCase(logic)) - constraintsLogic = CONSTRAINTS_AND; - else if ("or".equalsIgnoreCase(logic)) - constraintsLogic = CONSTRAINTS_OR; - else if ("xor".equalsIgnoreCase(logic)) - constraintsLogic = CONSTRAINTS_XOR; - else - throw new RuntimeException("Unrecognized logical operator: "+logic); - } else - constraintsLogic = CONSTRAINTS_AND; + String logic = props.getProperty(propName + ".logicalOperator"); + if ("and".equalsIgnoreCase(logic)) { + logicalOperator = AND; + } else if ("or".equalsIgnoreCase(logic)) { + logicalOperator = OR; + } else if ("xor".equalsIgnoreCase(logic)) { + logicalOperator = XOR; + } else { + logicalOperator = AND; + } + } else { + logicalOperator = AND; + } } /////////////////////////////////////////////////////////////////////////////////////////// - /** - * Constraints linked by AND or OR? - */ - - public String getConstraintsOperator() { - return logicalOperators[constraintsLogic]; - } - /** * Does this relation describe a virtual (collection) node? */ @@ -579,12 +594,6 @@ public final class Relation { vr.groupby = groupby; vr.groupbyOrder = groupbyOrder; vr.groupbyPrototype = groupbyPrototype; - vr.order = order; - vr.filter = filter; - vr.maxSize = maxSize; - vr.constraints = constraints; - vr.aggressiveLoading = aggressiveLoading; - vr.aggressiveCaching = aggressiveCaching; return vr; } @@ -602,10 +611,6 @@ public final class Relation { vr.groupby = groupby; vr.groupbyOrder = groupbyOrder; vr.groupbyPrototype = groupbyPrototype; - vr.order = order; - vr.filter = filter; - vr.maxSize = maxSize; - vr.constraints = constraints; return vr; } @@ -620,13 +625,8 @@ public final class Relation { Relation vr = new Relation(this); - vr.order = order; vr.prototype = groupbyPrototype; - vr.filter = filter; - vr.constraints = constraints; vr.addConstraint(new Constraint(null, groupby, true)); - vr.aggressiveLoading = aggressiveLoading; - vr.aggressiveCaching = aggressiveCaching; return vr; } @@ -641,10 +641,7 @@ public final class Relation { Relation vr = new Relation(this); - vr.order = order; vr.prototype = groupbyPrototype; - vr.filter = filter; - vr.constraints = constraints; vr.addConstraint(new Constraint(null, groupby, true)); return vr; @@ -681,7 +678,7 @@ public final class Relation { prefix = " AND "; } - if (constraints.length > 1 && constraintsLogic != CONSTRAINTS_AND) { + if (constraints.length > 1 && logicalOperator != AND) { q.append(prefix); q.append("("); prefix = ""; @@ -690,10 +687,10 @@ public final class Relation { for (int i = 0; i < constraints.length; i++) { q.append(prefix); constraints[i].addToQuery(q, home, nonvirtual); - prefix = getConstraintsOperator(); + prefix = logicalOperator; } - if (constraints.length > 1 && constraintsLogic != CONSTRAINTS_AND) { + if (constraints.length > 1 && logicalOperator != AND) { q.append(")"); prefix = " AND "; } @@ -744,12 +741,17 @@ public final class Relation { return q.toString(); } + /** + * Render the constraints for this relation for use within + * a left outer join select statement for the base object. + */ public void renderJoinConstraints(StringBuffer select) { for (int i = 0; i < constraints.length; i++) { select.append(ownType.getTableName()); select.append("."); select.append(constraints[i].localName); - select.append(" = _HLM_"); + select.append(" = "); + select.append(JOIN_PREFIX); select.append(propName); select.append("."); select.append(constraints[i].foreignName);