* Implement mechanism to register parent nodes with changed child nodes with the transactor.

When finishing the transaction, the transactor will call setLastSubnodeChange() on 
the parent nodes. This is necessary because the lastSubnodeChange flag should only be 
set after child node changes have been committed to the database. 
(Fixes bug 285 http://helma.org/bugs/show_bug.cgi?id=285 )
* Changed the way select statements are built: Use tablename.* rather than * to prevent 
columns from additionalTables are fetched unnecessarily.
* Code cleanup everywhere, mostly in Transactor.java, Relation.java and NodeManager.java
This commit is contained in:
hns 2003-10-10 18:09:50 +00:00
parent 8774a52e44
commit 507949310c
3 changed files with 188 additions and 175 deletions

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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 "};
public final static String AND = " AND ";
public final static String OR = " OR ";
public final static String XOR = " XOR ";
private String logicalOperator = AND;
// direct mapping is a very powerful feature: objects of some types can be directly accessed
// 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);