Added support for complex object references as specified in
"New Format for Object-Relational Mapping (Version 2)"
This commit is contained in:
parent
20df514693
commit
0cbc25c04a
6 changed files with 281 additions and 60 deletions
|
@ -237,7 +237,8 @@ public final class TypeManager {
|
||||||
if ((dbmap != null) && dbmap.needsUpdate()) {
|
if ((dbmap != null) && dbmap.needsUpdate()) {
|
||||||
dbmap.update();
|
dbmap.update();
|
||||||
|
|
||||||
if ((proto != hopobjectProto) && (proto != globalProto)) {
|
// this is now done in dbmap.update()!!!
|
||||||
|
/*if ((proto != hopobjectProto) && (proto != globalProto)) {
|
||||||
// set parent prototype, in case it has changed.
|
// set parent prototype, in case it has changed.
|
||||||
String parentName = dbmap.getExtends();
|
String parentName = dbmap.getExtends();
|
||||||
|
|
||||||
|
@ -246,7 +247,7 @@ public final class TypeManager {
|
||||||
} else if (!app.isJavaPrototype(proto.getName())) {
|
} else if (!app.isJavaPrototype(proto.getName())) {
|
||||||
proto.setParentPrototype(hopobjectProto);
|
proto.setParentPrototype(hopobjectProto);
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package helma.objectmodel.db;
|
package helma.objectmodel.db;
|
||||||
|
|
||||||
import helma.framework.core.Application;
|
import helma.framework.core.Application;
|
||||||
|
import helma.framework.core.Prototype;
|
||||||
import helma.util.SystemProperties;
|
import helma.util.SystemProperties;
|
||||||
import helma.util.Updatable;
|
import helma.util.Updatable;
|
||||||
import java.sql.*;
|
import java.sql.*;
|
||||||
|
@ -159,6 +160,7 @@ public final class DbMapping implements Updatable {
|
||||||
return props.lastModified() != lastTypeChange;
|
return props.lastModified() != lastTypeChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the mapping from the Properties. Return true if the properties were changed.
|
* Read the mapping from the Properties. Return true if the properties were changed.
|
||||||
* The read is split in two, this method and the rewire method. The reason is that in order
|
* The read is split in two, this method and the rewire method. The reason is that in order
|
||||||
|
@ -173,9 +175,6 @@ public final class DbMapping implements Updatable {
|
||||||
// can be stored in this table
|
// can be stored in this table
|
||||||
prototypeField = props.getProperty("_prototypefield");
|
prototypeField = props.getProperty("_prototypefield");
|
||||||
|
|
||||||
// see if this prototype extends (inherits from) any other prototype
|
|
||||||
extendsProto = props.getProperty("_extends");
|
|
||||||
|
|
||||||
dbSourceName = props.getProperty("_db");
|
dbSourceName = props.getProperty("_db");
|
||||||
|
|
||||||
if (dbSourceName != null) {
|
if (dbSourceName != null) {
|
||||||
|
@ -220,17 +219,40 @@ public final class DbMapping implements Updatable {
|
||||||
|
|
||||||
lastTypeChange = props.lastModified();
|
lastTypeChange = props.lastModified();
|
||||||
|
|
||||||
|
// see if this prototype extends (inherits from) any other prototype
|
||||||
|
extendsProto = props.getProperty("_extends");
|
||||||
|
|
||||||
|
if (extendsProto != null) {
|
||||||
|
parentMapping = app.getDbMapping(extendsProto);
|
||||||
|
if (parentMapping != null && parentMapping.needsUpdate()) {
|
||||||
|
parentMapping.update();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parentMapping = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the parent prototype in the corresponding Prototype object!
|
||||||
|
// this was previously done by TypeManager, but we need to do it
|
||||||
|
// ourself because DbMapping.update() may be called by other code than
|
||||||
|
// the TypeManager.
|
||||||
|
if (typename != null &&
|
||||||
|
!"global".equalsIgnoreCase(typename) &&
|
||||||
|
!"hopobject".equalsIgnoreCase(typename)) {
|
||||||
|
Prototype proto = app.getPrototypeByName(typename);
|
||||||
|
if (proto != null) {
|
||||||
|
if (extendsProto != null) {
|
||||||
|
proto.setParentPrototype(app.getPrototypeByName(extendsProto));
|
||||||
|
} else if (!app.isJavaPrototype(typename)) {
|
||||||
|
proto.setParentPrototype(app.getPrototypeByName("hopobject"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// null the cached columns and select string
|
// null the cached columns and select string
|
||||||
columns = null;
|
columns = null;
|
||||||
columnMap.clear();
|
columnMap.clear();
|
||||||
selectString = insertString = updateString = null;
|
selectString = insertString = updateString = null;
|
||||||
|
|
||||||
if (extendsProto != null) {
|
|
||||||
parentMapping = app.getDbMapping(extendsProto);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (tableName != null && dbSource != null) {
|
|
||||||
// app.logEvent ("set data dbSource for "+typename+" to "+dbSource);
|
|
||||||
HashMap p2d = new HashMap();
|
HashMap p2d = new HashMap();
|
||||||
HashMap d2p = new HashMap();
|
HashMap d2p = new HashMap();
|
||||||
|
|
||||||
|
@ -259,7 +281,15 @@ public final class DbMapping implements Updatable {
|
||||||
if ((rel.columnName != null) &&
|
if ((rel.columnName != null) &&
|
||||||
((rel.reftype == Relation.PRIMITIVE) ||
|
((rel.reftype == Relation.PRIMITIVE) ||
|
||||||
(rel.reftype == Relation.REFERENCE))) {
|
(rel.reftype == Relation.REFERENCE))) {
|
||||||
d2p.put(rel.columnName.toUpperCase(), rel);
|
Relation old = (Relation) d2p.put(rel.columnName.toUpperCase(), 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 ("Mapping "+propName+" -> "+dbField);
|
// app.logEvent ("Mapping "+propName+" -> "+dbField);
|
||||||
|
|
124
src/helma/objectmodel/db/MultiKey.java
Normal file
124
src/helma/objectmodel/db/MultiKey.java
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* Helma License Notice
|
||||||
|
*
|
||||||
|
* The contents of this file are subject to the Helma License
|
||||||
|
* Version 2.0 (the "License"). You may not use this file except in
|
||||||
|
* compliance with the License. A copy of the License is available at
|
||||||
|
* http://adele.helma.org/download/helma/license.txt
|
||||||
|
*
|
||||||
|
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* $RCSfile$
|
||||||
|
* $Author$
|
||||||
|
* $Revision$
|
||||||
|
* $Date$
|
||||||
|
*/
|
||||||
|
|
||||||
|
package helma.objectmodel.db;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the internal representation of a database key with multiple
|
||||||
|
* columns. It is constructed from the logical table (type) name and the
|
||||||
|
* column name/column value pairs that identify the key's object
|
||||||
|
*
|
||||||
|
* NOTE: This class doesn't fully support the Key interface - getID always
|
||||||
|
* returns null since there is no unique key (at least we don't know about it).
|
||||||
|
*/
|
||||||
|
public final class MultiKey implements Key, Serializable {
|
||||||
|
// the name of the prototype which defines the storage of this object.
|
||||||
|
// this is the name of the object's prototype, or one of its ancestors.
|
||||||
|
// If null, the object is stored in the embedded db.
|
||||||
|
private final String storageName;
|
||||||
|
|
||||||
|
// the id that defines this key's object within the above storage space
|
||||||
|
private final Map parts;
|
||||||
|
|
||||||
|
// lazily initialized hashcode
|
||||||
|
private transient int hashcode = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param what the other key to be compared with this one
|
||||||
|
*
|
||||||
|
* @return true if both keys are identical
|
||||||
|
*/
|
||||||
|
public boolean equals(Object what) {
|
||||||
|
if (what == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(what instanceof MultiKey)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiKey k = (MultiKey) what;
|
||||||
|
|
||||||
|
// storageName is an interned string (by DbMapping, from where we got it)
|
||||||
|
// so we can compare by using == instead of the equals method.
|
||||||
|
return (storageName == k.storageName) &&
|
||||||
|
((parts == k.parts) || parts.equals(k.parts));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return this key's hash code
|
||||||
|
*/
|
||||||
|
public int hashCode() {
|
||||||
|
if (hashcode == 0) {
|
||||||
|
hashcode = (storageName == null) ? (17 + (37 * parts.hashCode()))
|
||||||
|
: (17 + (37 * storageName.hashCode()) +
|
||||||
|
(+37 * parts.hashCode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return the key of this key's object's parent object
|
||||||
|
*/
|
||||||
|
public Key getParentKey() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return the unique storage name for this key's object
|
||||||
|
*/
|
||||||
|
public String getStorageName() {
|
||||||
|
return storageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return this key's object's id
|
||||||
|
*/
|
||||||
|
public String getID() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return a string representation for this key
|
||||||
|
*/
|
||||||
|
public String toString() {
|
||||||
|
return (storageName == null) ? ("[" + parts + "]") : (storageName + "[" + parts + "]");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1821,7 +1821,7 @@ public final class Node implements INode, Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
// so if we have a property relation and it does in fact link to another object...
|
// so if we have a property relation and it does in fact link to another object...
|
||||||
if ((propRel != null) && propRel.isCollection()) {
|
if ((propRel != null) && (propRel.isCollection() || propRel.isComplexReference())) {
|
||||||
// in some cases we just want to create and set a generic node without consulting
|
// in some cases we just want to create and set a generic node without consulting
|
||||||
// the NodeManager if it exists: When we get a collection (aka virtual node)
|
// the NodeManager if it exists: When we get a collection (aka virtual node)
|
||||||
// from a transient node for the first time, or when we get a collection whose
|
// from a transient node for the first time, or when we get a collection whose
|
||||||
|
@ -1835,9 +1835,9 @@ public final class Node implements INode, Serializable {
|
||||||
}
|
}
|
||||||
// if this is from relational database only fetch if this node
|
// if this is from relational database only fetch if this node
|
||||||
// is itself persistent.
|
// is itself persistent.
|
||||||
else if ((state != TRANSIENT) && propRel.createPropertyOnDemand()) {
|
else if ((state != TRANSIENT) && propRel.createOnDemand()) {
|
||||||
// this may be a relational node stored by property name
|
// this may be a relational node stored by property name
|
||||||
try {
|
// try {
|
||||||
Node pn = nmgr.getNode(this, propname, propRel);
|
Node pn = nmgr.getNode(this, propname, propRel);
|
||||||
|
|
||||||
if (pn != null) {
|
if (pn != null) {
|
||||||
|
@ -1850,9 +1850,9 @@ public final class Node implements INode, Serializable {
|
||||||
|
|
||||||
prop = new Property(propname, this, pn);
|
prop = new Property(propname, this, pn);
|
||||||
}
|
}
|
||||||
} catch (RuntimeException nonode) {
|
// } catch (RuntimeException nonode) {
|
||||||
// wasn't a node after all
|
// wasn't a node after all
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2367,6 +2367,15 @@ 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 && rel.isComplexReference()) {
|
||||||
|
rel.setConstraints(this, n);
|
||||||
|
Key key = new MultiKey(n.getDbMapping(), rel.getKeyParts(this));
|
||||||
|
nmgr.nmgr.registerNode(n, key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Property prop = (propMap == null) ? null : (Property) propMap.get(p2);
|
Property prop = (propMap == null) ? null : (Property) propMap.get(p2);
|
||||||
|
|
||||||
if (prop != null) {
|
if (prop != null) {
|
||||||
|
@ -2389,8 +2398,6 @@ public final class Node implements INode, Serializable {
|
||||||
|
|
||||||
prop.setNodeValue(n);
|
prop.setNodeValue(n);
|
||||||
|
|
||||||
Relation rel = (dbmap == null) ? null : dbmap.getPropertyRelation(propname);
|
|
||||||
|
|
||||||
if ((rel == null) || (rel.reftype == Relation.REFERENCE) || rel.virtual ||
|
if ((rel == null) || (rel.reftype == Relation.REFERENCE) || rel.virtual ||
|
||||||
(rel.otherType == null) || !rel.otherType.isRelational()) {
|
(rel.otherType == null) || !rel.otherType.isRelational()) {
|
||||||
// the node must be stored as explicit property
|
// the node must be stored as explicit property
|
||||||
|
|
|
@ -270,7 +270,10 @@ public final class NodeManager {
|
||||||
Key key = null;
|
Key key = null;
|
||||||
|
|
||||||
// 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.virtual || (rel.groupby != null) || !rel.usesPrimaryKey()) {
|
if (rel.isComplexReference()) {
|
||||||
|
// a key for a complex reference
|
||||||
|
key = new MultiKey(rel.otherType, rel.getKeyParts(home));
|
||||||
|
} else if (rel.virtual || (rel.groupby != null) || !rel.usesPrimaryKey()) {
|
||||||
// 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, generated on the fly
|
// or a key for an object that represents subobjects grouped by some property, generated on the fly
|
||||||
key = new SyntheticKey(home.getKey(), kstr);
|
key = new SyntheticKey(home.getKey(), kstr);
|
||||||
|
@ -395,6 +398,14 @@ public final class NodeManager {
|
||||||
cache.put(node.getKey(), node);
|
cache.put(node.getKey(), node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a node in the node cache using the key argument.
|
||||||
|
*/
|
||||||
|
protected void registerNode(Node node, Key key) {
|
||||||
|
cache.put(key, node);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a node from the node cache. If at a later time it is accessed again,
|
* Remove a node from the node cache. If at a later time it is accessed again,
|
||||||
* it will be refetched from the database.
|
* it will be refetched from the database.
|
||||||
|
@ -1523,7 +1534,7 @@ public final class NodeManager {
|
||||||
DbColumn[] columns = dbm.getColumns();
|
DbColumn[] columns = dbm.getColumns();
|
||||||
StringBuffer q = dbm.getSelect();
|
StringBuffer q = dbm.getSelect();
|
||||||
|
|
||||||
if (home.getSubnodeRelation() != null) {
|
if (home.getSubnodeRelation() != null && !rel.isComplexReference()) {
|
||||||
// combine our key with the constraints in the manually set subnode relation
|
// combine our key with the constraints in the manually set subnode relation
|
||||||
q.append("WHERE ");
|
q.append("WHERE ");
|
||||||
q.append(rel.accessName);
|
q.append(rel.accessName);
|
||||||
|
|
|
@ -21,6 +21,8 @@ import helma.objectmodel.*;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This describes how a property of a persistent Object is stored in a
|
* This describes how a property of a persistent Object is stored in a
|
||||||
|
@ -41,6 +43,10 @@ public final class Relation {
|
||||||
// a 1-to-many relation, a field in another table points to objects of this type
|
// a 1-to-many relation, a field in another table points to objects of this type
|
||||||
public final static int COLLECTION = 2;
|
public final static int COLLECTION = 2;
|
||||||
|
|
||||||
|
// a 1-to-1 reference with multiple or otherwise not-trivial constraints
|
||||||
|
// this is managed differently than REFERENCE, hence the separate type.
|
||||||
|
public final static int COMPLEX_REFERENCE = 3;
|
||||||
|
|
||||||
// direct mapping is a very powerful feature: objects of some types can be directly accessed
|
// direct mapping is a very powerful feature: objects of some types can be directly accessed
|
||||||
// by one of their properties/db fields.
|
// by one of their properties/db fields.
|
||||||
// public final static int DIRECT = 3;
|
// public final static int DIRECT = 3;
|
||||||
|
@ -141,6 +147,12 @@ public final class Relation {
|
||||||
throw new RuntimeException("DbMapping for " + proto +
|
throw new RuntimeException("DbMapping for " + proto +
|
||||||
" not found from " + ownType.typename);
|
" not found from " + ownType.typename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make sure the type we're referring to is up to date!
|
||||||
|
if (otherType != null && otherType.needsUpdate()) {
|
||||||
|
otherType.update();
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
virtual = false;
|
virtual = false;
|
||||||
columnName = desc;
|
columnName = desc;
|
||||||
|
@ -148,13 +160,7 @@ public final class Relation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String rdonly = props.getProperty(propName + ".readonly");
|
readonly = "true".equalsIgnoreCase(props.getProperty(propName + ".readonly"));
|
||||||
|
|
||||||
if ((rdonly != null) && "true".equalsIgnoreCase(rdonly)) {
|
|
||||||
readonly = true;
|
|
||||||
} else {
|
|
||||||
readonly = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
isPrivate = "true".equalsIgnoreCase(props.getProperty(propName + ".private"));
|
isPrivate = "true".equalsIgnoreCase(props.getProperty(propName + ".private"));
|
||||||
|
|
||||||
|
@ -167,6 +173,15 @@ public final class Relation {
|
||||||
constraints = new Constraint[newConstraints.size()];
|
constraints = new Constraint[newConstraints.size()];
|
||||||
newConstraints.copyInto(constraints);
|
newConstraints.copyInto(constraints);
|
||||||
|
|
||||||
|
|
||||||
|
// check if this is a non-trivial reference
|
||||||
|
if (reftype == REFERENCE) {
|
||||||
|
if (constraints.length > 1 || !usesPrimaryKey()) {
|
||||||
|
reftype = COMPLEX_REFERENCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// if DbMapping for virtual nodes has already been created,
|
// if DbMapping for virtual nodes has already been created,
|
||||||
// update its subnode relation.
|
// update its subnode relation.
|
||||||
// FIXME: needs to be synchronized?
|
// FIXME: needs to be synchronized?
|
||||||
|
@ -248,21 +263,18 @@ public final class Relation {
|
||||||
String foreign = props.getProperty(propName + ".foreign");
|
String foreign = props.getProperty(propName + ".foreign");
|
||||||
|
|
||||||
if ((local != null) && (foreign != null)) {
|
if ((local != null) && (foreign != null)) {
|
||||||
cnst.addElement(new Constraint(local, otherType.getTableName(), foreign, false));
|
cnst.addElement(new Constraint(local, foreign, false));
|
||||||
columnName = local;
|
columnName = local;
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse additional contstraints
|
// parse additional contstraints from *.1 to *.9
|
||||||
for (int i=1; i<10; i++) {
|
for (int i=1; i<10; i++) {
|
||||||
local = props.getProperty(propName + ".local."+i);
|
local = props.getProperty(propName + ".local."+i);
|
||||||
foreign = props.getProperty(propName + ".foreign."+i);
|
foreign = props.getProperty(propName + ".foreign."+i);
|
||||||
|
|
||||||
if ((local != null) && (foreign != null)) {
|
if ((local != null) && (foreign != null)) {
|
||||||
cnst.addElement(new Constraint(local, otherType.getTableName(), foreign, false));
|
cnst.addElement(new Constraint(local, foreign, false));
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,6 +308,13 @@ public final class Relation {
|
||||||
return reftype == COLLECTION;
|
return reftype == COLLECTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this Relation describes a complex object reference property
|
||||||
|
*/
|
||||||
|
public boolean isComplexReference() {
|
||||||
|
return reftype == COMPLEX_REFERENCE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tell wether the property described by this relation is to be handled as private, i.e.
|
* Tell wether the property described by this relation is to be handled as private, i.e.
|
||||||
* a change on it should not result in any changed object/collection relations.
|
* a change on it should not result in any changed object/collection relations.
|
||||||
|
@ -310,8 +329,8 @@ public final class Relation {
|
||||||
* node. Virtual nodes are objects which are only generated on demand
|
* node. Virtual nodes are objects which are only generated on demand
|
||||||
* and never stored to a persistent storage.
|
* and never stored to a persistent storage.
|
||||||
*/
|
*/
|
||||||
public boolean createPropertyOnDemand() {
|
public boolean createOnDemand() {
|
||||||
return virtual || (accessName != null) || (groupby != null);
|
return virtual || (accessName != null) || (groupby != null) || isComplexReference();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -392,7 +411,9 @@ public final class Relation {
|
||||||
public boolean usesPrimaryKey() {
|
public boolean usesPrimaryKey() {
|
||||||
if (otherType != null) {
|
if (otherType != null) {
|
||||||
if (reftype == REFERENCE) {
|
if (reftype == REFERENCE) {
|
||||||
return (constraints.length == 1) && constraints[0].foreignKeyIsPrimary();
|
return constraints.length == 0 ||
|
||||||
|
((constraints.length == 1) &&
|
||||||
|
constraints[0].foreignKeyIsPrimary());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reftype == COLLECTION) {
|
if (reftype == COLLECTION) {
|
||||||
|
@ -523,7 +544,7 @@ public final class Relation {
|
||||||
vr.prototype = groupbyPrototype;
|
vr.prototype = groupbyPrototype;
|
||||||
vr.filter = filter;
|
vr.filter = filter;
|
||||||
vr.constraints = constraints;
|
vr.constraints = constraints;
|
||||||
vr.addConstraint(new Constraint(null, null, groupby, true));
|
vr.addConstraint(new Constraint(null, groupby, true));
|
||||||
vr.aggressiveLoading = aggressiveLoading;
|
vr.aggressiveLoading = aggressiveLoading;
|
||||||
vr.aggressiveCaching = aggressiveCaching;
|
vr.aggressiveCaching = aggressiveCaching;
|
||||||
|
|
||||||
|
@ -544,7 +565,7 @@ public final class Relation {
|
||||||
vr.prototype = groupbyPrototype;
|
vr.prototype = groupbyPrototype;
|
||||||
vr.filter = filter;
|
vr.filter = filter;
|
||||||
vr.constraints = constraints;
|
vr.constraints = constraints;
|
||||||
vr.addConstraint(new Constraint(null, null, groupby, true));
|
vr.addConstraint(new Constraint(null, groupby, true));
|
||||||
|
|
||||||
return vr;
|
return vr;
|
||||||
}
|
}
|
||||||
|
@ -558,7 +579,7 @@ public final class Relation {
|
||||||
StringBuffer q = new StringBuffer();
|
StringBuffer q = new StringBuffer();
|
||||||
String prefix = pre;
|
String prefix = pre;
|
||||||
|
|
||||||
if (kstr != null) {
|
if (kstr != null && !isComplexReference()) {
|
||||||
q.append(prefix);
|
q.append(prefix);
|
||||||
|
|
||||||
String accessColumn = (accessName == null) ? otherType.getIDField() : accessName;
|
String accessColumn = (accessName == null) ? otherType.getIDField() : accessName;
|
||||||
|
@ -669,16 +690,14 @@ public final class Relation {
|
||||||
if (propname != null) {
|
if (propname != null) {
|
||||||
INode home = constraints[i].isGroupby ? parent
|
INode home = constraints[i].isGroupby ? parent
|
||||||
: parent.getNonVirtualParent();
|
: parent.getNonVirtualParent();
|
||||||
String localName = constraints[i].localName;
|
|
||||||
String value = null;
|
String value = null;
|
||||||
|
|
||||||
if ((localName == null) ||
|
if (constraints[i].localKeyIsPrimary()) {
|
||||||
localName.equalsIgnoreCase(ownType.getIDField())) {
|
|
||||||
value = home.getID();
|
value = home.getID();
|
||||||
} else if (ownType.isRelational()) {
|
} else if (ownType.isRelational()) {
|
||||||
value = home.getString(ownType.columnNameToProperty(localName));
|
value = home.getString(constraints[i].localProperty());
|
||||||
} else {
|
} else {
|
||||||
value = home.getString(localName);
|
value = home.getString(constraints[i].localName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((value != null) && !value.equals(child.getString(propname))) {
|
if ((value != null) && !value.equals(child.getString(propname))) {
|
||||||
|
@ -704,14 +723,19 @@ public final class Relation {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Relation crel = otherType.columnNameToRelation(constraints[i].foreignName);
|
// 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()) {
|
||||||
|
parent.setString(constraints[i].localProperty(), child.getID());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Relation crel = otherType.columnNameToRelation(constraints[i].foreignName);
|
||||||
if (crel != null) {
|
if (crel != null) {
|
||||||
// INode home = constraints[i].isGroupby ? parent : nonVirtual;
|
// INode home = constraints[i].isGroupby ? parent : nonVirtual;
|
||||||
String localName = constraints[i].localName;
|
|
||||||
|
|
||||||
if ((localName == null) ||
|
if (constraints[i].localKeyIsPrimary()) {
|
||||||
localName.equalsIgnoreCase(ownType.getIDField())) {
|
|
||||||
// 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);
|
||||||
|
@ -733,10 +757,9 @@ public final class Relation {
|
||||||
Property prop = null;
|
Property prop = null;
|
||||||
|
|
||||||
if (ownType.isRelational()) {
|
if (ownType.isRelational()) {
|
||||||
prop = home.getProperty(ownType.columnNameToProperty(localName));
|
prop = home.getProperty(constraints[i].localProperty());
|
||||||
// value = home.getString(ownType.columnNameToProperty(localName));
|
|
||||||
} else {
|
} else {
|
||||||
prop = home.getProperty(localName);
|
prop = home.getProperty(constraints[i].localName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prop != null) {
|
if (prop != null) {
|
||||||
|
@ -747,6 +770,21 @@ public final class Relation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map containing the key/value pairs for a specific Node
|
||||||
|
*/
|
||||||
|
public Map getKeyParts(INode home) {
|
||||||
|
Map map = new HashMap();
|
||||||
|
for (int i=0; i<constraints.length; i++) {
|
||||||
|
if (ownType.getIDField().equals(constraints[i].localName)) {
|
||||||
|
map.put(constraints[i].foreignName, home.getID());
|
||||||
|
} else {
|
||||||
|
map.put(constraints[i].foreignName, home.getString(constraints[i].localProperty()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
// a utility method to escape single quotes
|
// a utility method to escape single quotes
|
||||||
String escape(String str) {
|
String escape(String str) {
|
||||||
if (str == null) {
|
if (str == null) {
|
||||||
|
@ -780,13 +818,20 @@ public final class Relation {
|
||||||
*/
|
*/
|
||||||
public String toString() {
|
public String toString() {
|
||||||
String c = "";
|
String c = "";
|
||||||
|
String spacer = "";
|
||||||
|
|
||||||
if (constraints != null) {
|
if (constraints != null) {
|
||||||
for (int i = 0; i < constraints.length; i++)
|
c = " constraints: ";
|
||||||
|
for (int i = 0; i < constraints.length; i++) {
|
||||||
|
c += spacer;
|
||||||
c += constraints[i].toString();
|
c += constraints[i].toString();
|
||||||
|
spacer = ", ";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Relation[" + ownType + "." + propName + ">" + otherType + "]" + c;
|
String target = otherType == null ? columnName : otherType.toString();
|
||||||
|
|
||||||
|
return "Relation " + ownType+"."+propName + " -> " + target + c;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -795,13 +840,11 @@ public final class Relation {
|
||||||
*/
|
*/
|
||||||
class Constraint {
|
class Constraint {
|
||||||
String localName;
|
String localName;
|
||||||
String tableName;
|
|
||||||
String foreignName;
|
String foreignName;
|
||||||
boolean isGroupby;
|
boolean isGroupby;
|
||||||
|
|
||||||
Constraint(String local, String table, String foreign, boolean groupby) {
|
Constraint(String local, String foreign, boolean groupby) {
|
||||||
localName = local;
|
localName = local;
|
||||||
tableName = table;
|
|
||||||
foreignName = foreign;
|
foreignName = foreign;
|
||||||
isGroupby = groupby;
|
isGroupby = groupby;
|
||||||
}
|
}
|
||||||
|
@ -837,6 +880,11 @@ public final class Relation {
|
||||||
foreignName.equalsIgnoreCase(otherType.getIDField());
|
foreignName.equalsIgnoreCase(otherType.getIDField());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean localKeyIsPrimary() {
|
||||||
|
return (localName == null) ||
|
||||||
|
localName.equalsIgnoreCase(ownType.getIDField());
|
||||||
|
}
|
||||||
|
|
||||||
public String foreignProperty() {
|
public String foreignProperty() {
|
||||||
return otherType.columnNameToProperty(foreignName);
|
return otherType.columnNameToProperty(foreignName);
|
||||||
}
|
}
|
||||||
|
@ -846,7 +894,7 @@ public final class Relation {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return ownType + "." + localName + "=" + tableName + "." + foreignName;
|
return localName + "=" + otherType.getTypeName() + "." + foreignName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue