* Implement bug #516

* Fix bug #515
* Some refactoring in helma.objectmodel.db
This commit is contained in:
hns 2007-05-10 15:13:44 +00:00
parent d8f5446d01
commit 1091d34c77
7 changed files with 319 additions and 196 deletions

View file

@ -97,7 +97,7 @@ public final class DbColumn {
// 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()));
(relation != null && relation.isPrimitiveOrReference());
}
/**

View file

@ -62,8 +62,8 @@ public final class DbMapping {
private HashMap prop2db;
// Map of db columns to Relations objects.
// Case insensitive, keys are stored in upper case so
// lookups must do a toUpperCase().
// Case insensitive, keys are stored in lower case so
// lookups must do a toLowerCase().
private HashMap db2prop;
// list of columns to fetch from db
@ -303,24 +303,21 @@ public final class DbMapping {
// (ResourceProperties now preserve key capitalization!)
p2d.put(propName.toLowerCase(), rel);
if ((rel.columnName != null) &&
((rel.reftype == Relation.PRIMITIVE) ||
(rel.reftype == Relation.REFERENCE))) {
Relation old = (Relation) d2p.put(rel.columnName.toUpperCase(), rel);
if ((rel.columnName != null) && rel.isPrimitiveOrReference()) {
Relation old = (Relation) d2p.put(rel.columnName.toLowerCase(), rel);
// check if we're overwriting another relation
// if so, primitive relations get precendence to references
if (old != null) {
app.logEvent("*** Duplicate mapping for "+typename+"."+rel.columnName);
if (old.reftype == Relation.PRIMITIVE) {
d2p.put(old.columnName.toUpperCase(), old);
app.logEvent("*** Duplicate mapping for " + typename + "." + rel.columnName);
if (old.isPrimitive()) {
d2p.put(old.columnName.toLowerCase(), old);
}
}
}
// check if a reference is aggressively fetched
if ((rel.reftype == Relation.REFERENCE ||
rel.reftype == Relation.COMPLEX_REFERENCE) &&
rel.aggressiveLoading) {
if (rel.aggressiveLoading &&
(rel.isReference() || rel.isComplexReference())) {
joinList.add(rel);
}
@ -413,6 +410,9 @@ public final class DbMapping {
return parentMapping.getPrototypeName(id);
}
// fallback to base-prototype if the proto isn't recogniced
if (id == null) {
return typename;
}
return extensionMap.getProperty(id, typename);
}
@ -581,7 +581,7 @@ public final class DbMapping {
columnName = columnName.substring(open + 1, close);
}
return _columnNameToProperty(columnName.toUpperCase());
return _columnNameToProperty(columnName.toLowerCase());
}
private String _columnNameToProperty(final String columnName) {
@ -591,9 +591,7 @@ public final class DbMapping {
return parentMapping._columnNameToProperty(columnName);
}
if ((rel != null) &&
((rel.reftype == Relation.PRIMITIVE) ||
(rel.reftype == Relation.REFERENCE))) {
if ((rel != null) && rel.isPrimitiveOrReference()) {
return rel.propName;
}
@ -601,15 +599,16 @@ public final class DbMapping {
}
/**
* Translate an object property name to a database column name according to this mapping.
* Translate an object property name to a database column name according
* to this mapping. If no mapping is found, the property name is returned,
* assuming property and column names are equal.
*/
public String propertyToColumnName(String propName) {
if (propName == null) {
return null;
}
// FIXME: prop2db stores keys in lower case, because it gets them
// from a SystemProperties object which converts keys to lower case.
// prop2db stores keys in lower case
return _propertyToColumnName(propName.toLowerCase());
}
@ -620,9 +619,7 @@ public final class DbMapping {
return parentMapping._propertyToColumnName(propName);
}
if ((rel != null) &&
((rel.reftype == Relation.PRIMITIVE) ||
(rel.reftype == Relation.REFERENCE))) {
if ((rel != null) && (rel.isPrimitiveOrReference())) {
return rel.columnName;
}
@ -637,7 +634,7 @@ public final class DbMapping {
return null;
}
return _columnNameToRelation(columnName.toUpperCase());
return _columnNameToRelation(columnName.toLowerCase());
}
private Relation _columnNameToRelation(final String columnName) {
@ -702,28 +699,6 @@ public final class DbMapping {
return null;
}
/**
*
*
* @param propname ...
*
* @return ...
*/
public DbMapping getExactPropertyMapping(String propname) {
Relation rel = getExactPropertyRelation(propname);
if (rel != null) {
// if this is a virtual node, it doesn't have a dbmapping
if (rel.virtual && (rel.prototype == null)) {
return null;
} else {
return rel.otherType;
}
}
return null;
}
/**
*
*

View file

@ -46,11 +46,31 @@ public final class MultiKey implements Key, Serializable {
/**
* make a key for a persistent Object, describing its datasource and key parts.
* Make a key for a persistent Object, describing its datasource and key parts.
*/
public MultiKey(DbMapping dbmap, Map parts) {
this.parts = parts;
this.storageName = (dbmap == null) ? null : dbmap.getStorageTypeName();
this.storageName = getStorageNameFromParts(dbmap, parts);
}
/**
* Get the actual dbmapping prototype name out of the parts map if possible.
* This is necessary to implement references to unspecified prototype targets.
* @param dbmap the nominal/static dbmapping
* @param parts the parts map
* @return the actual dbmapping name
*/
private String getStorageNameFromParts(DbMapping dbmap, Map parts) {
if (dbmap == null)
return null;
String protoName = (String) parts.get("$prototype");
if (protoName != null) {
DbMapping dynamap = dbmap.app.getDbMapping(protoName);
if (dynamap != null) {
return (dynamap.getStorageTypeName());
}
}
return dbmap.getStorageTypeName();
}
/**

View file

@ -17,6 +17,8 @@
package helma.objectmodel.db;
import helma.framework.IPathElement;
import helma.framework.core.RequestEvaluator;
import helma.framework.core.Application;
import helma.objectmodel.ConcurrencyException;
import helma.objectmodel.INode;
import helma.objectmodel.IProperty;
@ -761,9 +763,7 @@ public final class Node implements INode, Serializable {
// see if there is an explicit relation defined for this parent info
// we only try to fetch a node if an explicit relation is specified for the prop name
Relation rel = dbmap.propertyToRelation(pinfo.propname);
if ((rel != null) && (rel.reftype == Relation.REFERENCE ||
rel.reftype == Relation.COMPLEX_REFERENCE)) {
if ((rel != null) && (rel.isReference() || rel.isComplexReference())) {
pn = (Node) getNode(pinfo.propname);
}
@ -776,10 +776,17 @@ public final class Node implements INode, Serializable {
if (pn != null) {
// see if dbmapping specifies anonymity for this node
if (pinfo.virtualname != null) {
pn = (Node) pn.getNode(pinfo.virtualname);
if (pn == null)
nmgr.nmgr.app.logError("Error: Can't retrieve parent node " +
Node pn2 = (Node) pn.getNode(pinfo.virtualname);
if (pn2 == null) {
getApp().logError("Error: Can't retrieve parent node " +
pinfo + " for " + this);
} else if (pn2.equals(this)) {
setParent(pn);
name = pinfo.virtualname;
anonymous = false;
return pn;
}
pn = pn2;
}
DbMapping dbm = (pn == null) ? null : pn.getDbMapping();
@ -798,7 +805,7 @@ public final class Node implements INode, Serializable {
return pn;
}
} catch (Exception x) {
nmgr.nmgr.app.logError("Error retrieving parent node " +
getApp().logError("Error retrieving parent node " +
pinfo + " for " + this, x);
}
}
@ -810,8 +817,8 @@ public final class Node implements INode, Serializable {
}
}
if (parentHandle == null && !nmgr.isRootNode(this) && state != TRANSIENT) {
nmgr.nmgr.app.logEvent("*** Couldn't resolve parent for " + this);
nmgr.nmgr.app.logEvent("*** Please check _parent info in type.properties!");
getApp().logEvent("*** Couldn't resolve parent for " + this);
getApp().logEvent("*** Please check _parent info in type.properties!");
}
}
@ -1317,7 +1324,8 @@ public final class Node implements INode, Serializable {
/**
* "Locally" remove a subnode from the subnodes table.
* The logical stuff necessary for keeping data consistent is done in removeNode().
* The logical stuff necessary for keeping data consistent is done in
* {@link #removeNode(INode)}.
*/
protected void releaseNode(Node node) {
INode parent = node.getParent();
@ -1325,35 +1333,40 @@ public final class Node implements INode, Serializable {
checkWriteLock();
node.checkWriteLock();
boolean removed = false;
// load subnodes in case they haven't been loaded.
// this is to prevent subsequent access to reload the
// index which would potentially still contain the removed child
loadNodes();
if (subnodes != null) {
boolean removed = false;
synchronized (subnodes) {
removed = subnodes.remove(node.getHandle());
}
if (removed) {
registerSubnodeChange();
}
}
if (removed)
registerSubnodeChange();
// check if subnodes are also accessed as properties. If so, also unset the property
if ((dbmap != null) && (node.dbmap != null)) {
Relation prel = dbmap.getSubnodeRelation();
if ((prel != null) && (prel.accessName != null)) {
Relation localrel = node.dbmap.columnNameToRelation(prel.accessName);
if (prel != null) {
if (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
String propname = (localrel == null) ? prel.accessName : localrel.propName;
String prop = node.getString(propname);
// 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);
if ((prop != null) && (getNode(prop) == node)) {
unset(prop);
if ((prop != null) && (getNode(prop) == node)) {
unset(prop);
}
}
if (prel.countConstraints() > 1) {
prel.unsetConstraints(this, node);
}
}
}
@ -1370,7 +1383,6 @@ public final class Node implements INode, Serializable {
lastmodified = System.currentTimeMillis();
// nmgr.logEvent ("released node "+node +" from "+this+" oldobj = "+what);
if (state == CLEAN) {
markAs(MODIFIED);
}
@ -2276,12 +2288,15 @@ public final class Node implements INode, Serializable {
public void setNode(String propname, INode value) {
// nmgr.logEvent ("setting node prop");
// check if types match, otherwise throw exception
DbMapping nmap = (dbmap == null) ? null : dbmap.getExactPropertyMapping(propname);
Relation rel = (dbmap == null) ?
null : dbmap.getExactPropertyRelation(propname);
DbMapping nmap = (rel == null) ? null : rel.getPropertyMapping();
DbMapping vmap = value.getDbMapping();
if ((nmap != null) && (nmap != value.getDbMapping())) {
if (value.getDbMapping() == null) {
if ((nmap != null) && (nmap != vmap)) {
if (vmap == null) {
value.setDbMapping(nmap);
} else if (!nmap.isStorageCompatible(value.getDbMapping())) {
} else if (!nmap.isStorageCompatible(vmap) && !rel.isComplexReference()) {
throw new RuntimeException("Can't set " + propname +
" to object with prototype " +
value.getPrototype() + ", was expecting " +
@ -2333,7 +2348,10 @@ public final class Node implements INode, Serializable {
String p2 = propname.toLowerCase();
Relation rel = (dbmap == null) ? null : dbmap.getPropertyRelation(propname);
if (rel == null && dbmap != null) {
// widen relation to non-exact (collection) mapping
rel = dbmap.getPropertyRelation(propname);
}
if (rel != null && (rel.countConstraints() > 1 || rel.isComplexReference())) {
rel.setConstraints(this, n);
@ -2367,7 +2385,7 @@ public final class Node implements INode, Serializable {
prop.setNodeValue(n);
if ((rel == null) ||
rel.reftype == Relation.REFERENCE ||
rel.isReference() ||
state == TRANSIENT ||
rel.otherType == null ||
!rel.otherType.isRelational()) {
@ -2417,20 +2435,19 @@ public final class Node implements INode, Serializable {
* specified via property relation.
*/
public void unset(String propname) {
if (propMap == null) {
return;
}
try {
// if node is relational, leave a null property so that it is
// updated in the DB. Otherwise, remove the property.
Property p;
Property p = null;
boolean relational = (dbmap != null) && dbmap.isRelational();
if (relational) {
p = (Property) propMap.get(propname.toLowerCase());
} else {
p = (Property) propMap.remove(propname.toLowerCase());
if (propMap != null) {
if (relational) {
p = (Property) propMap.get(propname.toLowerCase());
} else {
p = (Property) propMap.remove(propname.toLowerCase());
}
}
if (p != null) {
@ -2456,7 +2473,8 @@ public final class Node implements INode, Serializable {
rel.unsetConstraints(this, p.getNodeValue());
}
}
} catch (Exception ignore) {
} catch (Exception x) {
getApp().logError("Error unsetting property", x);
}
}
@ -2479,11 +2497,23 @@ public final class Node implements INode, Serializable {
}
/**
*
*
* @return ...
* Return a string representation for this node. This tries to call the
* javascript implemented toString() if it is defined.
* @return a string representing this node.
*/
public String toString() {
try {
// We need to reach deap into helma.framework.core to invoke onInit(),
// but the functionality is really worth it.
RequestEvaluator reval = getApp().getCurrentRequestEvaluator();
if (reval != null) {
Object str = reval.invokeDirectFunction(this, "toString", RequestEvaluator.EMPTY_ARGS);
if (str instanceof String)
return (String) str;
}
} catch (Exception x) {
// fall back to default representation
}
return "HopObject " + name;
}
@ -2670,11 +2700,19 @@ public final class Node implements INode, Serializable {
// FIXME: what do we do if this.dbmap is null
if (this.dbmap == null) {
throw new RuntimeException (this + " doesn't have a DbMapping");
}
}
Relation rel = this.dbmap.getSubnodeRelation();
synchronized (this) {
lastSubnodeFetch = System.currentTimeMillis();
return this.nmgr.updateSubnodeList(this, rel);
}
}
/**
* Get the application this node belongs to.
* @return the app we belong to
*/
private Application getApp() {
return nmgr.nmgr.app;
}
}

View file

@ -112,7 +112,7 @@ public final class NodeManager {
* Checks if the given node is the application's root node.
*/
public boolean isRootNode(Node node) {
return app.getRootId().equals(node.getID()) &&
return node.getState() != Node.TRANSIENT && app.getRootId().equals(node.getID()) &&
DbMapping.areStorageCompatible(app.getRootMapping(), node.getDbMapping());
}
@ -215,11 +215,12 @@ public final class NodeManager {
Transactor tx = (Transactor) Thread.currentThread();
Key key;
DbMapping otherDbm = rel == null ? null : rel.otherType;
// check what kind of object we're looking for and make an apropriate key
if (rel.isComplexReference()) {
// a key for a complex reference
key = new MultiKey(rel.otherType, rel.getKeyParts(home));
otherDbm = app.getDbMapping(key.getStorageName());
} else if (rel.createOnDemand()) {
// a key for a virtually defined object that's never actually stored in the db
// or a key for an object that represents subobjects grouped by some property,
@ -275,7 +276,7 @@ public final class NodeManager {
// 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);
node = getNodeByRelation(tx.txn, home, kstr, rel, otherDbm);
if (node != null) {
Node newNode = node;
@ -433,7 +434,7 @@ public final class NodeManager {
* Insert a node into a different (relational) database than its default one.
*/
public void exportNode(Node node, DbSource dbs)
throws IOException, SQLException, ClassNotFoundException {
throws SQLException, ClassNotFoundException {
if (node == null) {
throw new IllegalArgumentException("Node can't be null in exportNode");
}
@ -453,7 +454,7 @@ public final class NodeManager {
* Insert a node into a different (relational) database than its default one.
*/
public void exportNode(Node node, DbMapping dbm)
throws IOException, SQLException, ClassNotFoundException {
throws SQLException, ClassNotFoundException {
if (node == null) {
throw new IllegalArgumentException("Node can't be null in exportNode");
}
@ -593,7 +594,7 @@ public final class NodeManager {
// skip readonly, virtual and collection relations
if ((rel == null) || rel.readonly || rel.virtual ||
(!rel.isReference() && !rel.isPrimitive())) {
(!rel.isPrimitiveOrReference())) {
// null out property so we don't consider it later
props[i] = null;
continue;
@ -1568,7 +1569,7 @@ public final class NodeManager {
return node;
}
private Node getNodeByRelation(ITransaction txn, Node home, String kstr, Relation rel)
private Node getNodeByRelation(ITransaction txn, Node home, String kstr, Relation rel, DbMapping dbm)
throws Exception {
Node node = null;
@ -1582,25 +1583,22 @@ public final class NodeManager {
// set prototype and dbmapping on the newly created virtual/collection node
node.setPrototype(rel.prototype);
node.setDbMapping(rel.getVirtualMapping());
} else if ((rel != null) && (rel.groupby != null)) {
} else if (rel != null && rel.groupby != null) {
node = home.getGroupbySubnode(kstr, false);
if ((node == null) &&
((rel.otherType == null) || !rel.otherType.isRelational())) {
if (node == null && (dbm == null || !dbm.isRelational())) {
node = (Node) db.getNode(txn, kstr);
node.nmgr = safe;
}
return node;
} else if ((rel == null) || (rel.otherType == null) ||
!rel.otherType.isRelational()) {
} else if (rel == null || dbm == null || !dbm.isRelational()) {
node = (Node) db.getNode(txn, kstr);
node.nmgr = safe;
node.setDbMapping(rel.otherType);
node.setDbMapping(dbm);
return node;
} else {
DbMapping dbm = rel.otherType;
Statement stmt = null;
String query = null;
long logTimeStart = logSql ? System.currentTimeMillis() : 0;
@ -1629,6 +1627,7 @@ public final class NodeManager {
} else {
b.append(rel.buildQuery(home,
home.getNonVirtualParent(),
dbm,
kstr,
" WHERE ",
false));
@ -1644,7 +1643,7 @@ public final class NodeManager {
return null;
}
node = createNode(rel.otherType, rs, columns, 0);
node = createNode(dbm, rs, columns, 0);
fetchJoinedNodes(rs, joins, columns.length);
@ -1855,8 +1854,7 @@ public final class NodeManager {
DbColumn[] columns2 = dbmap.getColumns();
for (int i=0; i<columns2.length; i++) {
Relation rel = columns2[i].getRelation();
if (rel != null && (rel.reftype == Relation.PRIMITIVE ||
rel.reftype == Relation.REFERENCE)) {
if (rel != null && rel.isPrimitiveOrReference()) {
Property prop = (Property) propBuffer.get(columns2[i].getName());
if (prop == null) {
@ -1866,7 +1864,7 @@ public final class NodeManager {
prop.setName(rel.propName);
// if the property is a pointer to another node, change the property type to NODE
if ((rel.reftype == Relation.REFERENCE) && rel.usesPrimaryKey()) {
if (rel.isReference() && rel.usesPrimaryKey()) {
// FIXME: References to anything other than the primary key are not supported
prop.convertToNodeReference(rel.otherType);
}

View file

@ -223,15 +223,16 @@ public final class Relation {
} else {
boolean rprim = false;
for (int i=0; i<constraints.length; i++) {
if (constraints[0].foreignKeyIsPrimary()) {
if (constraints[i].foreignKeyIsPrimary()) {
rprim = true;
break;
}
}
referencesPrimaryKey = rprim;
}
// check if this is a non-trivial reference
if (constraints.length > 0 && !usesPrimaryKey()) {
if (constraints.length > 1 || !usesPrimaryKey()) {
reftype = COMPLEX_REFERENCE;
} else {
reftype = REFERENCE;
@ -457,6 +458,14 @@ public final class Relation {
return reftype == REFERENCE;
}
/**
* Returns true if this Relation describes either a primitive value
* or an object reference.
*/
public boolean isPrimitiveOrReference() {
return reftype == PRIMITIVE || reftype == REFERENCE;
}
/**
* Returns true if this Relation describes a collection.
* <b>NOTE:</b> this will return true both for collection objects
@ -742,6 +751,18 @@ public final class Relation {
return virtualMapping;
}
/**
* Return the db mapping for a propery relation.
* @return the target mapping of this property relation
*/
public DbMapping getPropertyMapping() {
// if this is an untyped virtual node, it doesn't have a dbmapping
if (!virtual || prototype != null) {
return otherType;
}
return null;
}
/**
* Return a Relation that defines the subnodes of a virtual node.
*/
@ -812,8 +833,18 @@ public final class Relation {
* Build the second half of an SQL select statement according to this relation
* and a local object.
*/
public String buildQuery(INode home, INode nonvirtual, String kstr, String pre,
boolean useOrder)
public String buildQuery(INode home, INode nonvirtual,
String kstr, String pre, boolean useOrder)
throws SQLException, ClassNotFoundException {
return buildQuery(home, nonvirtual, otherType, kstr, pre, useOrder);
}
/**
* Build the second half of an SQL select statement according to this relation
* and a local object.
*/
public String buildQuery(INode home, INode nonvirtual, DbMapping otherDbm,
String kstr, String pre, boolean useOrder)
throws SQLException, ClassNotFoundException {
StringBuffer q = new StringBuffer();
String prefix = pre;
@ -822,14 +853,14 @@ public final class Relation {
q.append(prefix);
String accessColumn = (accessName == null) ?
otherType.getIDField() : accessName;
otherType.appendCondition(q, accessColumn, kstr);
otherDbm.getIDField() : accessName;
otherDbm.appendCondition(q, accessColumn, kstr);
prefix = " AND ";
}
// render the constraints and filter
renderConstraints(q, home, nonvirtual, prefix);
renderConstraints(q, home, nonvirtual, otherDbm, prefix);
// add joined fetch constraints
ownType.addJoinConstraints(q, prefix);
@ -906,17 +937,36 @@ public final class Relation {
}
/**
* Render contraints and filter conditions to an SQL query string buffer
* Render contraints and filter conditions to an SQL query string buffer.
*
* @param q the query string
* @param home our home node
* @param nonvirtual our non-virtual home node
* @param prefix the prefix to use to append to the existing query (e.g. " AND ")
*
* @throws SQLException ...
* @throws SQLException sql related exception
* @throws ClassNotFoundException driver class not found
*/
public void renderConstraints(StringBuffer q, INode home,
INode nonvirtual, String prefix)
public void renderConstraints(StringBuffer q, INode home, INode nonvirtual,
String prefix)
throws SQLException, ClassNotFoundException {
renderConstraints(q, home, nonvirtual, otherType, prefix);
}
/**
* Render contraints and filter conditions to an SQL query string buffer.
*
* @param q the query string
* @param home our home node
* @param nonvirtual our non-virtual home nod
* @param otherDbm the DbMapping of the remote Node
* @param prefix the prefix to use to append to the existing query (e.g. " AND ")
*
* @throws SQLException sql related exception
* @throws ClassNotFoundException driver class not found
*/
public void renderConstraints(StringBuffer q, INode home, INode nonvirtual,
DbMapping otherDbm, String prefix)
throws SQLException, ClassNotFoundException {
if (constraints.length > 1 && logicalOperator != AND) {
@ -926,8 +976,13 @@ public final class Relation {
}
for (int i = 0; i < constraints.length; i++) {
if (constraints[i].foreignKeyIsPrototype()) {
// if foreign key is $prototype we already have this constraint
// covered by doing the select on the proper table
continue;
}
q.append(prefix);
constraints[i].addToQuery(q, home, nonvirtual);
constraints[i].addToQuery(q, home, nonvirtual, otherDbm);
prefix = logicalOperator;
}
@ -935,20 +990,20 @@ 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();
if (otherDbm.inheritsStorage()) {
String protoField = otherDbm.getPrototypeField();
String[] extensions = otherDbm.getExtensions();
// extensions should never be null for extension- and
// extended prototypes. nevertheless we check it here
if (extensions != null && protoField != null) {
q.append(prefix);
otherType.appendCondition(q, protoField, extensions);
otherDbm.appendCondition(q, protoField, extensions);
prefix = " AND ";
}
}
@ -969,12 +1024,12 @@ public final class Relation {
for (int i = 0; i < constraints.length; i++) {
select.append(ownType.getTableName());
select.append(".");
select.append(constraints[i].localName);
select.append(constraints[i].localKey);
select.append(" = ");
select.append(JOIN_PREFIX);
select.append(propName);
select.append(".");
select.append(constraints[i].foreignName);
select.append(constraints[i].foreignKey);
if (isOracle) {
// create old oracle style join - see
// http://www.praetoriate.com/oracle_tips_outer_joins.htm
@ -1034,21 +1089,28 @@ public final class Relation {
int satisfied = 0;
INode nonvirtual = parent.getNonVirtualParent();
DbMapping otherDbm = child.getDbMapping();
if (otherDbm == null) {
otherDbm = otherType;
}
for (int i = 0; i < constraints.length; i++) {
String propname = constraints[i].foreignProperty();
Constraint cnst = constraints[i];
String propname = cnst.foreignProperty(otherDbm);
if (propname != null) {
INode home = constraints[i].isGroupby ? parent
: nonvirtual;
INode home = cnst.isGroupby ? parent
: nonvirtual;
String value = null;
if (constraints[i].localKeyIsPrimary(home.getDbMapping())) {
if (cnst.localKeyIsPrimary(home.getDbMapping())) {
value = home.getID();
} else if (cnst.localKeyIsPrototype()) {
value = home.getDbMapping().getStorageTypeName();
} else if (ownType.isRelational()) {
value = home.getString(constraints[i].localProperty());
value = home.getString(cnst.localProperty());
} else {
value = home.getString(constraints[i].localName);
value = home.getString(cnst.localKey);
}
count++;
@ -1084,32 +1146,40 @@ public final class Relation {
Node home = parent.getNonVirtualParent();
for (int i = 0; i < constraints.length; i++) {
Constraint cnst = constraints[i];
// don't set groupby constraints since we don't know if the
// parent node is the base node or a group node
if (constraints[i].isGroupby) {
if (cnst.isGroupby) {
continue;
}
// check if we update the local or the other object, depending on
// whether the primary key of either side is used.
if (constraints[i].foreignKeyIsPrimary()) {
String localProp = constraints[i].localProperty();
boolean foreignIsPrimary = cnst.foreignKeyIsPrimary();
if (foreignIsPrimary || cnst.foreignKeyIsPrototype()) {
String localProp = cnst.localProperty();
if (localProp == null) {
System.err.println ("Error: column "+constraints[i].localName+
ownType.app.logError("Error: column " + cnst.localKey +
" must be mapped in order to be used as constraint in "+
Relation.this);
} else {
home.setString(localProp, child.getID());
String value = foreignIsPrimary ?
child.getID() : child.getDbMapping().getStorageTypeName();
home.setString(localProp, value);
}
continue;
}
Relation crel = otherType.columnNameToRelation(constraints[i].foreignName);
if (crel != null) {
// INode home = constraints[i].isGroupby ? parent : nonVirtual;
DbMapping otherDbm = child.getDbMapping();
if (otherDbm == null) {
otherDbm = otherType;
}
if (constraints[i].localKeyIsPrimary(home.getDbMapping())) {
Relation crel = otherDbm.columnNameToRelation(cnst.foreignKey);
if (crel != null) {
if (cnst.localKeyIsPrimary(home.getDbMapping())) {
// only set node if property in child object is defined as reference.
if (crel.reftype == REFERENCE) {
INode currentValue = child.getNode(crel.propName);
@ -1131,16 +1201,13 @@ public final class Relation {
child.setString(crel.propName, home.getID());
}
} else if (crel.reftype == PRIMITIVE) {
Property prop = null;
if (ownType.isRelational()) {
prop = home.getProperty(constraints[i].localProperty());
if (cnst.localKeyIsPrototype()) {
child.setString(crel.propName, home.getDbMapping().getStorageTypeName());
} else {
prop = home.getProperty(constraints[i].localName);
}
if (prop != null) {
child.set(crel.propName, prop.getValue(), prop.getType());
Property prop = home.getProperty(cnst.localProperty());
if (prop != null) {
child.set(crel.propName, prop.getValue(), prop.getType());
}
}
}
}
@ -1154,28 +1221,33 @@ public final class Relation {
Node home = parent.getNonVirtualParent();
for (int i = 0; i < constraints.length; i++) {
Constraint cnst = constraints[i];
// don't set groupby constraints since we don't know if the
// parent node is the base node or a group node
if (constraints[i].isGroupby) {
if (cnst.isGroupby) {
continue;
}
// check if we update the local or the other object, depending on
// whether the primary key of either side is used.
if (constraints[i].foreignKeyIsPrimary()) {
String localProp = constraints[i].localProperty();
if (cnst.foreignKeyIsPrimary() || cnst.foreignKeyIsPrototype()) {
String localProp = cnst.localProperty();
if (localProp != null) {
home.setString(localProp, null);
}
continue;
}
Relation crel = otherType.columnNameToRelation(constraints[i].foreignName);
if (crel != null) {
// INode home = constraints[i].isGroupby ? parent : nonVirtual;
DbMapping otherDbm = child.getDbMapping();
if (otherDbm == null) {
otherDbm = otherType;
}
if (constraints[i].localKeyIsPrimary(home.getDbMapping())) {
Relation crel = otherDbm.columnNameToRelation(cnst.foreignKey);
if (crel != null) {
if (cnst.localKeyIsPrimary(home.getDbMapping())) {
// only set node if property in child object is defined as reference.
if (crel.reftype == REFERENCE) {
INode currentValue = child.getNode(crel.propName);
@ -1187,17 +1259,7 @@ public final class Relation {
child.setString(crel.propName, null);
}
} else if (crel.reftype == PRIMITIVE) {
Property prop = null;
if (ownType.isRelational()) {
prop = home.getProperty(constraints[i].localProperty());
} else {
prop = home.getProperty(constraints[i].localName);
}
if (prop != null) {
child.setString(crel.propName, null);
}
child.setString(crel.propName, null);
}
}
}
@ -1209,10 +1271,13 @@ public final class Relation {
public Map getKeyParts(INode home) {
Map map = new HashMap();
for (int i=0; i<constraints.length; i++) {
if (ownType.getIDField().equalsIgnoreCase(constraints[i].localName)) {
map.put(constraints[i].foreignName, home.getID());
Constraint cnst = constraints[i];
if (cnst.localKeyIsPrimary(ownType)) {
map.put(cnst.foreignKey, home.getID());
} else if (cnst.localKeyIsPrototype()) {
map.put(cnst.foreignKey, home.getDbMapping().getStorageTypeName());
} else {
map.put(constraints[i].foreignName, home.getString(constraints[i].localProperty()));
map.put(cnst.foreignKey, home.getString(cnst.localProperty()));
}
}
return map;
@ -1246,53 +1311,77 @@ public final class Relation {
* establish a relation between database mapped objects.
*/
class Constraint {
String localName;
String foreignName;
String localKey;
String foreignKey;
boolean isGroupby;
Constraint(String local, String foreign, boolean groupby) {
localName = local;
foreignName = foreign;
localKey = local;
foreignKey = foreign;
isGroupby = groupby;
}
public void addToQuery(StringBuffer q, INode home, INode nonvirtual)
public void addToQuery(StringBuffer q, INode home, INode nonvirtual, DbMapping otherDbm)
throws SQLException, ClassNotFoundException {
String local = null;
String local;
INode ref = isGroupby ? home : nonvirtual;
if ((localName == null) ||
localName.equalsIgnoreCase(ref.getDbMapping().getIDField())) {
if (localKeyIsPrimary(ref.getDbMapping())) {
local = ref.getID();
} else if (localKeyIsPrototype()) {
local = ref.getDbMapping().getStorageTypeName();
} else {
String homeprop = ownType.columnNameToProperty(localName);
String homeprop = ownType.columnNameToProperty(localKey);
if (homeprop == null) {
throw new SQLException("Invalid local name '" + localKey +
"' on " + ownType);
}
local = ref.getString(homeprop);
}
otherType.appendCondition(q, foreignName, local);
String columnName;
if (foreignKeyIsPrimary()) {
columnName = otherDbm.getIDField();
} else {
columnName = foreignKey;
}
otherDbm.appendCondition(q, columnName, local);
}
public boolean foreignKeyIsPrimary() {
return (foreignName == null) ||
foreignName.equalsIgnoreCase(otherType.getIDField());
return (foreignKey == null) ||
"$id".equalsIgnoreCase(foreignKey) ||
foreignKey.equalsIgnoreCase(otherType.getIDField());
}
public boolean foreignKeyIsPrototype() {
return "$prototype".equalsIgnoreCase(foreignKey);
}
public boolean localKeyIsPrimary(DbMapping homeMapping) {
return (homeMapping == null) || (localName == null) ||
localName.equalsIgnoreCase(homeMapping.getIDField());
return (homeMapping == null) || (localKey == null) ||
"$id".equalsIgnoreCase(localKey) ||
localKey.equalsIgnoreCase(homeMapping.getIDField());
}
public String foreignProperty() {
return otherType.columnNameToProperty(foreignName);
public boolean localKeyIsPrototype() {
return "$prototype".equalsIgnoreCase(localKey);
}
public String foreignProperty(DbMapping otherDbm) {
if (otherDbm.isRelational())
return otherDbm.columnNameToProperty(foreignKey);
return foreignKey;
}
public String localProperty() {
return ownType.columnNameToProperty(localName);
if (ownType.isRelational())
return ownType.columnNameToProperty(localKey);
return localKey;
}
public String toString() {
return localName + "=" + otherType.getTypeName() + "." + foreignName;
return localKey + "=" + otherType.getTypeName() + "." + foreignKey;
}
}
}

View file

@ -118,7 +118,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
* @return the default value for the object
*/
public Object getDefaultValue(Class hint) {
return toString();
return node == null ? toString() : node.toString();
}
/**
@ -981,12 +981,15 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
}
/**
* Return a string representation of this object
*
* @return ...
* Return a string representation of this HopObject.
* @return a string representing this HopObject
*/
public String toString() {
return (className != null) ? ("[HopObject " + className + "]") : "[HopObject]";
if (node == null) {
return "[HopObject prototype " + className + "]";
} else {
return "[HopObject " + node.getName() + "]";
}
}
/**