* 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 // 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. // needed, but we did it before, so we leave it in for safety.
return isId || isPrototype || isName || 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; private HashMap prop2db;
// Map of db columns to Relations objects. // Map of db columns to Relations objects.
// Case insensitive, keys are stored in upper case so // Case insensitive, keys are stored in lower case so
// lookups must do a toUpperCase(). // lookups must do a toLowerCase().
private HashMap db2prop; private HashMap db2prop;
// list of columns to fetch from db // list of columns to fetch from db
@ -303,24 +303,21 @@ public final class DbMapping {
// (ResourceProperties now preserve key capitalization!) // (ResourceProperties now preserve key capitalization!)
p2d.put(propName.toLowerCase(), rel); p2d.put(propName.toLowerCase(), rel);
if ((rel.columnName != null) && if ((rel.columnName != null) && rel.isPrimitiveOrReference()) {
((rel.reftype == Relation.PRIMITIVE) || Relation old = (Relation) d2p.put(rel.columnName.toLowerCase(), rel);
(rel.reftype == Relation.REFERENCE))) {
Relation old = (Relation) d2p.put(rel.columnName.toUpperCase(), rel);
// check if we're overwriting another relation // check if we're overwriting another relation
// if so, primitive relations get precendence to references // if so, primitive relations get precendence to references
if (old != null) { if (old != null) {
app.logEvent("*** Duplicate mapping for "+typename+"."+rel.columnName); app.logEvent("*** Duplicate mapping for " + typename + "." + rel.columnName);
if (old.reftype == Relation.PRIMITIVE) { if (old.isPrimitive()) {
d2p.put(old.columnName.toUpperCase(), old); d2p.put(old.columnName.toLowerCase(), old);
} }
} }
} }
// check if a reference is aggressively fetched // check if a reference is aggressively fetched
if ((rel.reftype == Relation.REFERENCE || if (rel.aggressiveLoading &&
rel.reftype == Relation.COMPLEX_REFERENCE) && (rel.isReference() || rel.isComplexReference())) {
rel.aggressiveLoading) {
joinList.add(rel); joinList.add(rel);
} }
@ -413,6 +410,9 @@ public final class DbMapping {
return parentMapping.getPrototypeName(id); return parentMapping.getPrototypeName(id);
} }
// fallback to base-prototype if the proto isn't recogniced // fallback to base-prototype if the proto isn't recogniced
if (id == null) {
return typename;
}
return extensionMap.getProperty(id, typename); return extensionMap.getProperty(id, typename);
} }
@ -581,7 +581,7 @@ public final class DbMapping {
columnName = columnName.substring(open + 1, close); columnName = columnName.substring(open + 1, close);
} }
return _columnNameToProperty(columnName.toUpperCase()); return _columnNameToProperty(columnName.toLowerCase());
} }
private String _columnNameToProperty(final String columnName) { private String _columnNameToProperty(final String columnName) {
@ -591,9 +591,7 @@ public final class DbMapping {
return parentMapping._columnNameToProperty(columnName); return parentMapping._columnNameToProperty(columnName);
} }
if ((rel != null) && if ((rel != null) && rel.isPrimitiveOrReference()) {
((rel.reftype == Relation.PRIMITIVE) ||
(rel.reftype == Relation.REFERENCE))) {
return rel.propName; 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) { public String propertyToColumnName(String propName) {
if (propName == null) { if (propName == null) {
return null; return null;
} }
// FIXME: prop2db stores keys in lower case, because it gets them // prop2db stores keys in lower case
// from a SystemProperties object which converts keys to lower case.
return _propertyToColumnName(propName.toLowerCase()); return _propertyToColumnName(propName.toLowerCase());
} }
@ -620,9 +619,7 @@ public final class DbMapping {
return parentMapping._propertyToColumnName(propName); return parentMapping._propertyToColumnName(propName);
} }
if ((rel != null) && if ((rel != null) && (rel.isPrimitiveOrReference())) {
((rel.reftype == Relation.PRIMITIVE) ||
(rel.reftype == Relation.REFERENCE))) {
return rel.columnName; return rel.columnName;
} }
@ -637,7 +634,7 @@ public final class DbMapping {
return null; return null;
} }
return _columnNameToRelation(columnName.toUpperCase()); return _columnNameToRelation(columnName.toLowerCase());
} }
private Relation _columnNameToRelation(final String columnName) { private Relation _columnNameToRelation(final String columnName) {
@ -702,28 +699,6 @@ public final class DbMapping {
return null; 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) { public MultiKey(DbMapping dbmap, Map parts) {
this.parts = 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; package helma.objectmodel.db;
import helma.framework.IPathElement; import helma.framework.IPathElement;
import helma.framework.core.RequestEvaluator;
import helma.framework.core.Application;
import helma.objectmodel.ConcurrencyException; import helma.objectmodel.ConcurrencyException;
import helma.objectmodel.INode; import helma.objectmodel.INode;
import helma.objectmodel.IProperty; 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 // 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 // we only try to fetch a node if an explicit relation is specified for the prop name
Relation rel = dbmap.propertyToRelation(pinfo.propname); Relation rel = dbmap.propertyToRelation(pinfo.propname);
if ((rel != null) && (rel.isReference() || rel.isComplexReference())) {
if ((rel != null) && (rel.reftype == Relation.REFERENCE ||
rel.reftype == Relation.COMPLEX_REFERENCE)) {
pn = (Node) getNode(pinfo.propname); pn = (Node) getNode(pinfo.propname);
} }
@ -776,10 +776,17 @@ public final class Node implements INode, Serializable {
if (pn != null) { if (pn != null) {
// see if dbmapping specifies anonymity for this node // see if dbmapping specifies anonymity for this node
if (pinfo.virtualname != null) { if (pinfo.virtualname != null) {
pn = (Node) pn.getNode(pinfo.virtualname); Node pn2 = (Node) pn.getNode(pinfo.virtualname);
if (pn == null) if (pn2 == null) {
nmgr.nmgr.app.logError("Error: Can't retrieve parent node " + getApp().logError("Error: Can't retrieve parent node " +
pinfo + " for " + this); 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(); DbMapping dbm = (pn == null) ? null : pn.getDbMapping();
@ -798,7 +805,7 @@ public final class Node implements INode, Serializable {
return pn; return pn;
} }
} catch (Exception x) { } catch (Exception x) {
nmgr.nmgr.app.logError("Error retrieving parent node " + getApp().logError("Error retrieving parent node " +
pinfo + " for " + this, x); pinfo + " for " + this, x);
} }
} }
@ -810,8 +817,8 @@ public final class Node implements INode, Serializable {
} }
} }
if (parentHandle == null && !nmgr.isRootNode(this) && state != TRANSIENT) { if (parentHandle == null && !nmgr.isRootNode(this) && state != TRANSIENT) {
nmgr.nmgr.app.logEvent("*** Couldn't resolve parent for " + this); getApp().logEvent("*** Couldn't resolve parent for " + this);
nmgr.nmgr.app.logEvent("*** Please check _parent info in type.properties!"); 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. * "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) { protected void releaseNode(Node node) {
INode parent = node.getParent(); INode parent = node.getParent();
@ -1325,35 +1333,40 @@ public final class Node implements INode, Serializable {
checkWriteLock(); checkWriteLock();
node.checkWriteLock(); node.checkWriteLock();
boolean removed = false;
// load subnodes in case they haven't been loaded. // load subnodes in case they haven't been loaded.
// this is to prevent subsequent access to reload the // this is to prevent subsequent access to reload the
// index which would potentially still contain the removed child // index which would potentially still contain the removed child
loadNodes(); loadNodes();
if (subnodes != null) { if (subnodes != null) {
boolean removed = false;
synchronized (subnodes) { synchronized (subnodes) {
removed = subnodes.remove(node.getHandle()); 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 // check if subnodes are also accessed as properties. If so, also unset the property
if ((dbmap != null) && (node.dbmap != null)) { if ((dbmap != null) && (node.dbmap != null)) {
Relation prel = dbmap.getSubnodeRelation(); Relation prel = dbmap.getSubnodeRelation();
if ((prel != null) && (prel.accessName != null)) { if (prel != null) {
Relation localrel = node.dbmap.columnNameToRelation(prel.accessName); 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 // 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 propname = (localrel == null) ? prel.accessName : localrel.propName;
String prop = node.getString(propname); String prop = node.getString(propname);
if ((prop != null) && (getNode(prop) == node)) { if ((prop != null) && (getNode(prop) == node)) {
unset(prop); unset(prop);
}
}
if (prel.countConstraints() > 1) {
prel.unsetConstraints(this, node);
} }
} }
} }
@ -1370,7 +1383,6 @@ public final class Node implements INode, Serializable {
lastmodified = System.currentTimeMillis(); lastmodified = System.currentTimeMillis();
// nmgr.logEvent ("released node "+node +" from "+this+" oldobj = "+what);
if (state == CLEAN) { if (state == CLEAN) {
markAs(MODIFIED); markAs(MODIFIED);
} }
@ -2276,12 +2288,15 @@ public final class Node implements INode, Serializable {
public void setNode(String propname, INode value) { public void setNode(String propname, INode value) {
// nmgr.logEvent ("setting node prop"); // nmgr.logEvent ("setting node prop");
// check if types match, otherwise throw exception // 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 ((nmap != null) && (nmap != vmap)) {
if (value.getDbMapping() == null) { if (vmap == null) {
value.setDbMapping(nmap); value.setDbMapping(nmap);
} else if (!nmap.isStorageCompatible(value.getDbMapping())) { } else if (!nmap.isStorageCompatible(vmap) && !rel.isComplexReference()) {
throw new RuntimeException("Can't set " + propname + throw new RuntimeException("Can't set " + propname +
" to object with prototype " + " to object with prototype " +
value.getPrototype() + ", was expecting " + value.getPrototype() + ", was expecting " +
@ -2333,7 +2348,10 @@ public final class Node implements INode, Serializable {
String p2 = propname.toLowerCase(); 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())) { if (rel != null && (rel.countConstraints() > 1 || rel.isComplexReference())) {
rel.setConstraints(this, n); rel.setConstraints(this, n);
@ -2367,7 +2385,7 @@ public final class Node implements INode, Serializable {
prop.setNodeValue(n); prop.setNodeValue(n);
if ((rel == null) || if ((rel == null) ||
rel.reftype == Relation.REFERENCE || rel.isReference() ||
state == TRANSIENT || state == TRANSIENT ||
rel.otherType == null || rel.otherType == null ||
!rel.otherType.isRelational()) { !rel.otherType.isRelational()) {
@ -2417,20 +2435,19 @@ public final class Node implements INode, Serializable {
* specified via property relation. * specified via property relation.
*/ */
public void unset(String propname) { public void unset(String propname) {
if (propMap == null) {
return;
}
try { try {
// if node is relational, leave a null property so that it is // if node is relational, leave a null property so that it is
// updated in the DB. Otherwise, remove the property. // updated in the DB. Otherwise, remove the property.
Property p; Property p = null;
boolean relational = (dbmap != null) && dbmap.isRelational(); boolean relational = (dbmap != null) && dbmap.isRelational();
if (relational) { if (propMap != null) {
p = (Property) propMap.get(propname.toLowerCase()); if (relational) {
} else { p = (Property) propMap.get(propname.toLowerCase());
p = (Property) propMap.remove(propname.toLowerCase()); } else {
p = (Property) propMap.remove(propname.toLowerCase());
}
} }
if (p != null) { if (p != null) {
@ -2456,7 +2473,8 @@ public final class Node implements INode, Serializable {
rel.unsetConstraints(this, p.getNodeValue()); 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 a string representation for this node. This tries to call the
* * javascript implemented toString() if it is defined.
* @return ... * @return a string representing this node.
*/ */
public String toString() { 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; return "HopObject " + name;
} }
@ -2670,11 +2700,19 @@ public final class Node implements INode, Serializable {
// FIXME: what do we do if this.dbmap is null // FIXME: what do we do if this.dbmap is null
if (this.dbmap == null) { if (this.dbmap == null) {
throw new RuntimeException (this + " doesn't have a DbMapping"); throw new RuntimeException (this + " doesn't have a DbMapping");
} }
Relation rel = this.dbmap.getSubnodeRelation(); Relation rel = this.dbmap.getSubnodeRelation();
synchronized (this) { synchronized (this) {
lastSubnodeFetch = System.currentTimeMillis(); lastSubnodeFetch = System.currentTimeMillis();
return this.nmgr.updateSubnodeList(this, rel); 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. * Checks if the given node is the application's root node.
*/ */
public boolean isRootNode(Node 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()); DbMapping.areStorageCompatible(app.getRootMapping(), node.getDbMapping());
} }
@ -215,11 +215,12 @@ public final class NodeManager {
Transactor tx = (Transactor) Thread.currentThread(); Transactor tx = (Transactor) Thread.currentThread();
Key key; Key key;
DbMapping otherDbm = rel == null ? null : rel.otherType;
// check what kind of object we're looking for and make an apropriate key // check what kind of object we're looking for and make an apropriate key
if (rel.isComplexReference()) { if (rel.isComplexReference()) {
// a key for a complex reference // a key for a complex reference
key = new MultiKey(rel.otherType, rel.getKeyParts(home)); key = new MultiKey(rel.otherType, rel.getKeyParts(home));
otherDbm = app.getDbMapping(key.getStorageName());
} else if (rel.createOnDemand()) { } else if (rel.createOnDemand()) {
// a key for a virtually defined object that's never actually stored in the db // 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, // 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. // The requested node isn't in the shared cache.
// Synchronize with key to make sure only one version is fetched // Synchronize with key to make sure only one version is fetched
// from the database. // from the database.
node = getNodeByRelation(tx.txn, home, kstr, rel); node = getNodeByRelation(tx.txn, home, kstr, rel, otherDbm);
if (node != null) { if (node != null) {
Node newNode = node; Node newNode = node;
@ -433,7 +434,7 @@ public final class NodeManager {
* Insert a node into a different (relational) database than its default one. * Insert a node into a different (relational) database than its default one.
*/ */
public void exportNode(Node node, DbSource dbs) public void exportNode(Node node, DbSource dbs)
throws IOException, SQLException, ClassNotFoundException { throws SQLException, ClassNotFoundException {
if (node == null) { if (node == null) {
throw new IllegalArgumentException("Node can't be null in exportNode"); 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. * Insert a node into a different (relational) database than its default one.
*/ */
public void exportNode(Node node, DbMapping dbm) public void exportNode(Node node, DbMapping dbm)
throws IOException, SQLException, ClassNotFoundException { throws SQLException, ClassNotFoundException {
if (node == null) { if (node == null) {
throw new IllegalArgumentException("Node can't be null in exportNode"); throw new IllegalArgumentException("Node can't be null in exportNode");
} }
@ -593,7 +594,7 @@ public final class NodeManager {
// skip readonly, virtual and collection relations // skip readonly, virtual and collection relations
if ((rel == null) || rel.readonly || rel.virtual || if ((rel == null) || rel.readonly || rel.virtual ||
(!rel.isReference() && !rel.isPrimitive())) { (!rel.isPrimitiveOrReference())) {
// null out property so we don't consider it later // null out property so we don't consider it later
props[i] = null; props[i] = null;
continue; continue;
@ -1568,7 +1569,7 @@ public final class NodeManager {
return node; 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 { throws Exception {
Node node = null; Node node = null;
@ -1582,25 +1583,22 @@ public final class NodeManager {
// set prototype and dbmapping on the newly created virtual/collection node // set prototype and dbmapping on the newly created virtual/collection node
node.setPrototype(rel.prototype); node.setPrototype(rel.prototype);
node.setDbMapping(rel.getVirtualMapping()); node.setDbMapping(rel.getVirtualMapping());
} else if ((rel != null) && (rel.groupby != null)) { } else if (rel != null && rel.groupby != null) {
node = home.getGroupbySubnode(kstr, false); node = home.getGroupbySubnode(kstr, false);
if ((node == null) && if (node == null && (dbm == null || !dbm.isRelational())) {
((rel.otherType == null) || !rel.otherType.isRelational())) {
node = (Node) db.getNode(txn, kstr); node = (Node) db.getNode(txn, kstr);
node.nmgr = safe; node.nmgr = safe;
} }
return node; return node;
} else if ((rel == null) || (rel.otherType == null) || } else if (rel == null || dbm == null || !dbm.isRelational()) {
!rel.otherType.isRelational()) {
node = (Node) db.getNode(txn, kstr); node = (Node) db.getNode(txn, kstr);
node.nmgr = safe; node.nmgr = safe;
node.setDbMapping(rel.otherType); node.setDbMapping(dbm);
return node; return node;
} else { } else {
DbMapping dbm = rel.otherType;
Statement stmt = null; Statement stmt = null;
String query = null; String query = null;
long logTimeStart = logSql ? System.currentTimeMillis() : 0; long logTimeStart = logSql ? System.currentTimeMillis() : 0;
@ -1629,6 +1627,7 @@ public final class NodeManager {
} else { } else {
b.append(rel.buildQuery(home, b.append(rel.buildQuery(home,
home.getNonVirtualParent(), home.getNonVirtualParent(),
dbm,
kstr, kstr,
" WHERE ", " WHERE ",
false)); false));
@ -1644,7 +1643,7 @@ public final class NodeManager {
return null; return null;
} }
node = createNode(rel.otherType, rs, columns, 0); node = createNode(dbm, rs, columns, 0);
fetchJoinedNodes(rs, joins, columns.length); fetchJoinedNodes(rs, joins, columns.length);
@ -1855,8 +1854,7 @@ public final class NodeManager {
DbColumn[] columns2 = dbmap.getColumns(); DbColumn[] columns2 = dbmap.getColumns();
for (int i=0; i<columns2.length; i++) { for (int i=0; i<columns2.length; i++) {
Relation rel = columns2[i].getRelation(); Relation rel = columns2[i].getRelation();
if (rel != null && (rel.reftype == Relation.PRIMITIVE || if (rel != null && rel.isPrimitiveOrReference()) {
rel.reftype == Relation.REFERENCE)) {
Property prop = (Property) propBuffer.get(columns2[i].getName()); Property prop = (Property) propBuffer.get(columns2[i].getName());
if (prop == null) { if (prop == null) {
@ -1866,7 +1864,7 @@ public final class NodeManager {
prop.setName(rel.propName); prop.setName(rel.propName);
// if the property is a pointer to another node, change the property type to NODE // 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 // FIXME: References to anything other than the primary key are not supported
prop.convertToNodeReference(rel.otherType); prop.convertToNodeReference(rel.otherType);
} }

View file

@ -223,15 +223,16 @@ public final class Relation {
} else { } else {
boolean rprim = false; boolean rprim = false;
for (int i=0; i<constraints.length; i++) { for (int i=0; i<constraints.length; i++) {
if (constraints[0].foreignKeyIsPrimary()) { if (constraints[i].foreignKeyIsPrimary()) {
rprim = true; rprim = true;
break;
} }
} }
referencesPrimaryKey = rprim; referencesPrimaryKey = rprim;
} }
// check if this is a non-trivial reference // check if this is a non-trivial reference
if (constraints.length > 0 && !usesPrimaryKey()) { if (constraints.length > 1 || !usesPrimaryKey()) {
reftype = COMPLEX_REFERENCE; reftype = COMPLEX_REFERENCE;
} else { } else {
reftype = REFERENCE; reftype = REFERENCE;
@ -457,6 +458,14 @@ public final class Relation {
return reftype == REFERENCE; 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. * Returns true if this Relation describes a collection.
* <b>NOTE:</b> this will return true both for collection objects * <b>NOTE:</b> this will return true both for collection objects
@ -742,6 +751,18 @@ public final class Relation {
return virtualMapping; 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. * 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 * Build the second half of an SQL select statement according to this relation
* and a local object. * and a local object.
*/ */
public String buildQuery(INode home, INode nonvirtual, String kstr, String pre, public String buildQuery(INode home, INode nonvirtual,
boolean useOrder) 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 { throws SQLException, ClassNotFoundException {
StringBuffer q = new StringBuffer(); StringBuffer q = new StringBuffer();
String prefix = pre; String prefix = pre;
@ -822,14 +853,14 @@ public final class Relation {
q.append(prefix); q.append(prefix);
String accessColumn = (accessName == null) ? String accessColumn = (accessName == null) ?
otherType.getIDField() : accessName; otherDbm.getIDField() : accessName;
otherType.appendCondition(q, accessColumn, kstr); otherDbm.appendCondition(q, accessColumn, kstr);
prefix = " AND "; prefix = " AND ";
} }
// render the constraints and filter // render the constraints and filter
renderConstraints(q, home, nonvirtual, prefix); renderConstraints(q, home, nonvirtual, otherDbm, prefix);
// add joined fetch constraints // add joined fetch constraints
ownType.addJoinConstraints(q, prefix); 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 q the query string
* @param home our home node * @param home our home node
* @param nonvirtual our non-virtual home node * @param nonvirtual our non-virtual home node
* @param prefix the prefix to use to append to the existing query (e.g. " AND ") * @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, public void renderConstraints(StringBuffer q, INode home, INode nonvirtual,
INode nonvirtual, String prefix) 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 { throws SQLException, ClassNotFoundException {
if (constraints.length > 1 && logicalOperator != AND) { if (constraints.length > 1 && logicalOperator != AND) {
@ -926,8 +976,13 @@ public final class Relation {
} }
for (int i = 0; i < constraints.length; i++) { 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); q.append(prefix);
constraints[i].addToQuery(q, home, nonvirtual); constraints[i].addToQuery(q, home, nonvirtual, otherDbm);
prefix = logicalOperator; prefix = logicalOperator;
} }
@ -940,15 +995,15 @@ public final class Relation {
// specifies an extension of an prototype inside the brakets of // specifies an extension of an prototype inside the brakets of
// a type.properties's collection, only nodes having this proto // a type.properties's collection, only nodes having this proto
// sould appear inside the collection // sould appear inside the collection
if (otherType.inheritsStorage()) { if (otherDbm.inheritsStorage()) {
String protoField = otherType.getPrototypeField(); String protoField = otherDbm.getPrototypeField();
String[] extensions = otherType.getExtensions(); String[] extensions = otherDbm.getExtensions();
// extensions should never be null for extension- and // extensions should never be null for extension- and
// extended prototypes. nevertheless we check it here // extended prototypes. nevertheless we check it here
if (extensions != null && protoField != null) { if (extensions != null && protoField != null) {
q.append(prefix); q.append(prefix);
otherType.appendCondition(q, protoField, extensions); otherDbm.appendCondition(q, protoField, extensions);
prefix = " AND "; prefix = " AND ";
} }
} }
@ -969,12 +1024,12 @@ public final class Relation {
for (int i = 0; i < constraints.length; i++) { for (int i = 0; i < constraints.length; i++) {
select.append(ownType.getTableName()); select.append(ownType.getTableName());
select.append("."); select.append(".");
select.append(constraints[i].localName); select.append(constraints[i].localKey);
select.append(" = "); select.append(" = ");
select.append(JOIN_PREFIX); select.append(JOIN_PREFIX);
select.append(propName); select.append(propName);
select.append("."); select.append(".");
select.append(constraints[i].foreignName); select.append(constraints[i].foreignKey);
if (isOracle) { if (isOracle) {
// create old oracle style join - see // create old oracle style join - see
// http://www.praetoriate.com/oracle_tips_outer_joins.htm // http://www.praetoriate.com/oracle_tips_outer_joins.htm
@ -1034,21 +1089,28 @@ public final class Relation {
int satisfied = 0; int satisfied = 0;
INode nonvirtual = parent.getNonVirtualParent(); INode nonvirtual = parent.getNonVirtualParent();
DbMapping otherDbm = child.getDbMapping();
if (otherDbm == null) {
otherDbm = otherType;
}
for (int i = 0; i < constraints.length; i++) { 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) { if (propname != null) {
INode home = constraints[i].isGroupby ? parent INode home = cnst.isGroupby ? parent
: nonvirtual; : nonvirtual;
String value = null; String value = null;
if (constraints[i].localKeyIsPrimary(home.getDbMapping())) { if (cnst.localKeyIsPrimary(home.getDbMapping())) {
value = home.getID(); value = home.getID();
} else if (cnst.localKeyIsPrototype()) {
value = home.getDbMapping().getStorageTypeName();
} else if (ownType.isRelational()) { } else if (ownType.isRelational()) {
value = home.getString(constraints[i].localProperty()); value = home.getString(cnst.localProperty());
} else { } else {
value = home.getString(constraints[i].localName); value = home.getString(cnst.localKey);
} }
count++; count++;
@ -1084,32 +1146,40 @@ public final class Relation {
Node home = parent.getNonVirtualParent(); Node home = parent.getNonVirtualParent();
for (int i = 0; i < constraints.length; i++) { for (int i = 0; i < constraints.length; i++) {
Constraint cnst = constraints[i];
// don't set groupby constraints since we don't know if the // don't set groupby constraints since we don't know if the
// parent node is the base node or a group node // parent node is the base node or a group node
if (constraints[i].isGroupby) { if (cnst.isGroupby) {
continue; continue;
} }
// check if we update the local or the other object, depending on // check if we update the local or the other object, depending on
// whether the primary key of either side is used. // whether the primary key of either side is used.
boolean foreignIsPrimary = cnst.foreignKeyIsPrimary();
if (constraints[i].foreignKeyIsPrimary()) { if (foreignIsPrimary || cnst.foreignKeyIsPrototype()) {
String localProp = constraints[i].localProperty(); String localProp = cnst.localProperty();
if (localProp == null) { 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 "+ " must be mapped in order to be used as constraint in "+
Relation.this); Relation.this);
} else { } else {
home.setString(localProp, child.getID()); String value = foreignIsPrimary ?
child.getID() : child.getDbMapping().getStorageTypeName();
home.setString(localProp, value);
} }
continue; continue;
} }
Relation crel = otherType.columnNameToRelation(constraints[i].foreignName); DbMapping otherDbm = child.getDbMapping();
if (crel != null) { if (otherDbm == null) {
// INode home = constraints[i].isGroupby ? parent : nonVirtual; 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. // only set node if property in child object is defined as reference.
if (crel.reftype == REFERENCE) { if (crel.reftype == REFERENCE) {
INode currentValue = child.getNode(crel.propName); INode currentValue = child.getNode(crel.propName);
@ -1131,16 +1201,13 @@ public final class Relation {
child.setString(crel.propName, home.getID()); child.setString(crel.propName, home.getID());
} }
} else if (crel.reftype == PRIMITIVE) { } else if (crel.reftype == PRIMITIVE) {
Property prop = null; if (cnst.localKeyIsPrototype()) {
child.setString(crel.propName, home.getDbMapping().getStorageTypeName());
if (ownType.isRelational()) {
prop = home.getProperty(constraints[i].localProperty());
} else { } else {
prop = home.getProperty(constraints[i].localName); Property prop = home.getProperty(cnst.localProperty());
} if (prop != null) {
child.set(crel.propName, prop.getValue(), prop.getType());
if (prop != null) { }
child.set(crel.propName, prop.getValue(), prop.getType());
} }
} }
} }
@ -1154,28 +1221,33 @@ public final class Relation {
Node home = parent.getNonVirtualParent(); Node home = parent.getNonVirtualParent();
for (int i = 0; i < constraints.length; i++) { for (int i = 0; i < constraints.length; i++) {
Constraint cnst = constraints[i];
// don't set groupby constraints since we don't know if the // don't set groupby constraints since we don't know if the
// parent node is the base node or a group node // parent node is the base node or a group node
if (constraints[i].isGroupby) { if (cnst.isGroupby) {
continue; continue;
} }
// check if we update the local or the other object, depending on // check if we update the local or the other object, depending on
// whether the primary key of either side is used. // whether the primary key of either side is used.
if (constraints[i].foreignKeyIsPrimary()) { if (cnst.foreignKeyIsPrimary() || cnst.foreignKeyIsPrototype()) {
String localProp = constraints[i].localProperty(); String localProp = cnst.localProperty();
if (localProp != null) { if (localProp != null) {
home.setString(localProp, null); home.setString(localProp, null);
} }
continue; continue;
} }
Relation crel = otherType.columnNameToRelation(constraints[i].foreignName); DbMapping otherDbm = child.getDbMapping();
if (crel != null) { if (otherDbm == null) {
// INode home = constraints[i].isGroupby ? parent : nonVirtual; 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. // only set node if property in child object is defined as reference.
if (crel.reftype == REFERENCE) { if (crel.reftype == REFERENCE) {
INode currentValue = child.getNode(crel.propName); INode currentValue = child.getNode(crel.propName);
@ -1187,17 +1259,7 @@ public final class Relation {
child.setString(crel.propName, null); child.setString(crel.propName, null);
} }
} else if (crel.reftype == PRIMITIVE) { } else if (crel.reftype == PRIMITIVE) {
Property prop = null; child.setString(crel.propName, 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);
}
} }
} }
} }
@ -1209,10 +1271,13 @@ public final class Relation {
public Map getKeyParts(INode home) { public Map getKeyParts(INode home) {
Map map = new HashMap(); Map map = new HashMap();
for (int i=0; i<constraints.length; i++) { for (int i=0; i<constraints.length; i++) {
if (ownType.getIDField().equalsIgnoreCase(constraints[i].localName)) { Constraint cnst = constraints[i];
map.put(constraints[i].foreignName, home.getID()); if (cnst.localKeyIsPrimary(ownType)) {
map.put(cnst.foreignKey, home.getID());
} else if (cnst.localKeyIsPrototype()) {
map.put(cnst.foreignKey, home.getDbMapping().getStorageTypeName());
} else { } else {
map.put(constraints[i].foreignName, home.getString(constraints[i].localProperty())); map.put(cnst.foreignKey, home.getString(cnst.localProperty()));
} }
} }
return map; return map;
@ -1246,53 +1311,77 @@ public final class Relation {
* establish a relation between database mapped objects. * establish a relation between database mapped objects.
*/ */
class Constraint { class Constraint {
String localName; String localKey;
String foreignName; String foreignKey;
boolean isGroupby; boolean isGroupby;
Constraint(String local, String foreign, boolean groupby) { Constraint(String local, String foreign, boolean groupby) {
localName = local; localKey = local;
foreignName = foreign; foreignKey = foreign;
isGroupby = groupby; 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 { throws SQLException, ClassNotFoundException {
String local = null; String local;
INode ref = isGroupby ? home : nonvirtual; INode ref = isGroupby ? home : nonvirtual;
if ((localName == null) || if (localKeyIsPrimary(ref.getDbMapping())) {
localName.equalsIgnoreCase(ref.getDbMapping().getIDField())) {
local = ref.getID(); local = ref.getID();
} else if (localKeyIsPrototype()) {
local = ref.getDbMapping().getStorageTypeName();
} else { } 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); 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() { public boolean foreignKeyIsPrimary() {
return (foreignName == null) || return (foreignKey == null) ||
foreignName.equalsIgnoreCase(otherType.getIDField()); "$id".equalsIgnoreCase(foreignKey) ||
foreignKey.equalsIgnoreCase(otherType.getIDField());
}
public boolean foreignKeyIsPrototype() {
return "$prototype".equalsIgnoreCase(foreignKey);
} }
public boolean localKeyIsPrimary(DbMapping homeMapping) { public boolean localKeyIsPrimary(DbMapping homeMapping) {
return (homeMapping == null) || (localName == null) || return (homeMapping == null) || (localKey == null) ||
localName.equalsIgnoreCase(homeMapping.getIDField()); "$id".equalsIgnoreCase(localKey) ||
localKey.equalsIgnoreCase(homeMapping.getIDField());
} }
public String foreignProperty() { public boolean localKeyIsPrototype() {
return otherType.columnNameToProperty(foreignName); return "$prototype".equalsIgnoreCase(localKey);
}
public String foreignProperty(DbMapping otherDbm) {
if (otherDbm.isRelational())
return otherDbm.columnNameToProperty(foreignKey);
return foreignKey;
} }
public String localProperty() { public String localProperty() {
return ownType.columnNameToProperty(localName); if (ownType.isRelational())
return ownType.columnNameToProperty(localKey);
return localKey;
} }
public String toString() { 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 * @return the default value for the object
*/ */
public Object getDefaultValue(Class hint) { 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 a string representation of this HopObject.
* * @return a string representing this HopObject
* @return ...
*/ */
public String toString() { public String toString() {
return (className != null) ? ("[HopObject " + className + "]") : "[HopObject]"; if (node == null) {
return "[HopObject prototype " + className + "]";
} else {
return "[HopObject " + node.getName() + "]";
}
} }
/** /**