From 1091d34c77744352d062e83485c01ae3a4831b9c Mon Sep 17 00:00:00 2001 From: hns Date: Thu, 10 May 2007 15:13:44 +0000 Subject: [PATCH] * Implement bug #516 * Fix bug #515 * Some refactoring in helma.objectmodel.db --- src/helma/objectmodel/db/DbColumn.java | 2 +- src/helma/objectmodel/db/DbMapping.java | 65 ++---- src/helma/objectmodel/db/MultiKey.java | 24 +- src/helma/objectmodel/db/Node.java | 120 ++++++---- src/helma/objectmodel/db/NodeManager.java | 34 ++- src/helma/objectmodel/db/Relation.java | 257 +++++++++++++++------- src/helma/scripting/rhino/HopObject.java | 13 +- 7 files changed, 319 insertions(+), 196 deletions(-) diff --git a/src/helma/objectmodel/db/DbColumn.java b/src/helma/objectmodel/db/DbColumn.java index 5c101cb8..75e3dbd8 100644 --- a/src/helma/objectmodel/db/DbColumn.java +++ b/src/helma/objectmodel/db/DbColumn.java @@ -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()); } /** diff --git a/src/helma/objectmodel/db/DbMapping.java b/src/helma/objectmodel/db/DbMapping.java index dfbd363b..0be9a3e0 100644 --- a/src/helma/objectmodel/db/DbMapping.java +++ b/src/helma/objectmodel/db/DbMapping.java @@ -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; - } - /** * * diff --git a/src/helma/objectmodel/db/MultiKey.java b/src/helma/objectmodel/db/MultiKey.java index 1a20f6dc..77d36dca 100644 --- a/src/helma/objectmodel/db/MultiKey.java +++ b/src/helma/objectmodel/db/MultiKey.java @@ -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(); } /** diff --git a/src/helma/objectmodel/db/Node.java b/src/helma/objectmodel/db/Node.java index 033787aa..79be6b6c 100644 --- a/src/helma/objectmodel/db/Node.java +++ b/src/helma/objectmodel/db/Node.java @@ -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; + } } \ No newline at end of file diff --git a/src/helma/objectmodel/db/NodeManager.java b/src/helma/objectmodel/db/NodeManager.java index ada2b8e1..8fbc540b 100644 --- a/src/helma/objectmodel/db/NodeManager.java +++ b/src/helma/objectmodel/db/NodeManager.java @@ -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 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. * NOTE: 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