Hello Craig (et al.),

from what I understand of your proposals, you want to mandate integrity of bidirectional associations. Is that correct? That would be a great new feature!

Next to that, it seems to me that still you always want to silently remove objects from their relationships when those objects are deleted. In my opinion, this should only happen if we e.g. have delete-action="null" on the corresponding foreign key. As that can be declared only when foreign keys are declared for the association, maybe we should have something like "delete-action" on the field level, so the behaviour can easily be specified?

Apart from that, I'd have two questions. For "One-many relationships", you write:
If the "one" side of instance A removes instance X (which currently refers to A) from its collection, and instance X has not been added to a different collection and its reference has not been changed to another instance, the spec is silent. 
Why is the spec silent here?

My second question is: why do you want to prevent those "conflicting changes", or why do you see them conflicting at all? Why can't the second change simply override the first, in both cases?

Thanks for the proposals, regards,
Jörg

Craig L Russell schrieb:
Javadogs,

We have an issue with regard to the behavior of relationships mapped using mapped-by, where there is a bidirectional relationship instantiated by a single database artifact, either a foreign key or a row in a join table.

The spec currently defines a subset of behavior where relationships are updated by the program, and the memory model must be updated to reflect the change in the datastore after flush (including flush for commit).

The spec doesn't define the behavior when instances are deleted or primary keys are updated. 

There are interacting metadata tags that affect the way the database schema is defined. Column attribute allows-null specifies whether the column is nullable or not. Foreign-key attribute delete-action specifies how the foreign key is defined in the database. Field attribute mapped-by specifies the field on the other side that shares a mapping artifact in the database. Field attribute null-value specifies the behavior of null values at flush time. Field attribute dependent specifies that there is a dependency relationship (if the instance is deleted, the instance referred to by the dependent field is also deleted).

The proposals below do not add any more metadata concepts, but reuse existing concepts. The proposals are intended for incorporation into the JDO 2 maintenance release.

Many-many relationships:

These are implemented as join tables, in which each row in the join table corresponds to an element (or key or value) in a field on each of the two sides. The join table is typically mapped on one side, and the field on the other side refers to the field with a mapped-by attribute. But the behavior is the same regardless of which side defines the mapping.

If either side adds an instance to the map or collection, at flush time a new row is added to the join table. The specification requires that for consistency, the other side's collection be updated in memory.

If either side removes an instance from the map or collection, at flush time the corresponding row is removed from the join table. The specification requires that for consistency, the other side's collection be updated in memory to remove the corresponding element or entry from the collection or map.

If an instance participating in a relationship is deleted, and the field referring to the other side is defined as dependent, then all instances of the other side are deleted, and all rows in the join table corresponding to the deleted instances are deleted.

If an instance participating in a relationship is deleted, and the field referring to the other side is not defined as dependent, at flush time the corresponding rows are removed from the join table. I believe that the specification requires that for consistency, all of the other side's collections be updated in memory to remove the corresponding element or entry from the collection or map. But I think it would be a good idea to add this explicitly to the specification.

One-many relationships:

The foreign key is typically specified on the "many" side, which declares a reference type; and the "one" side declares an element, key, or value, and uses the mapped-by attribute. The specification doesn't require the mapped-by attribute on the "one" side; it's just less work to define it there.

These relationships might also be mapped using a join table in which the existence of a relationship is indicated by a row in the join table. The semantics and behavior are the same as if the column defining the relationship is in the "many" side's mapping.

If the "many" side reference in instance X is updated (from instance A to instance B) then the spec requires that the underlying database column(s) be updated from referring to A to referring to B. The memory model is made consistent, by removing X from A's collection and adding X to B's collection. 

If the "one" side of instance B adds instance X (which currently refers to A) to its collection, at flush time the specification requires that the underlying database column be updated in the row corresponding to instance X to refer to B. The memory model is made consistent, by removing X from A's collection and updating X to refer to B. A conflicting update is one in which some other instance of the "one" type also adds X to its collection, or instance X is changed so it refers to an instance of the "one" type other than B.

If the "one" side of instance A removes instance X (which currently refers to A) from its collection, and instance X has not been added to a different collection and its reference has not been changed to another instance, the spec is silent. 

<proposed>
If the field in X is defined as null-value="exception", then at flush time this is an error. If the column that defines the relationship is defined as allows-null="false", then this is an error, as there is no known value to which the column can be changed. If the column is defined as allows-null="true" and the field in X is defined as null-value="none", then the column is updated to null and the memory model is changed so that the reference in X is null.

Deleting an instance X on the "many" side results in removing the instance from the database and updating the memory model to be consistent. If the field of instance A that contains X is instantiated in memory, then the collection is updated to remove X.

Deleting an instance A on the "one" side that contains an instance X is ok if instance X is also deleted in the same transaction. There are two cases:

o If the collection field is defined as dependent-element="true" then instance X is also deleted by the jdo implementation. 

o If instance X is not also deleted by the program, and the collection field is defined with dependent-element="false" then instance X is not deleted. If the field in X is defined as null-value="exception", then at flush time this is an error, as the memory model cannot be made consistent. If the column that defines the relationship is defined as allows-null="false", then this is an error, as there is no known value to which the column can be changed. If the column is defined as allows-null="true", and the field in X is defined as null-value="none", then at flush time the column is updated to null and the memory model is changed so that the reference in X is null. NOTE: This behavior can be done automatically by the database if the foreign-key specifies delete-action="null".
</proposed>

One-one relationships:

These relationships are symmetric from the object model perspective, as currently the only metadata to describe whether one side or the other can exist independently is the null-value attribute of field. When mapped, typically the mapping is to a single unique foreign key column. This column is defined on one side of the relationship, and the mapping of the other side uses the mapped-by attribute. For the purposes of this discussion, I'll refer to the side containing the foreign key in its mapping as the mapped side, and the other as the mapped-by side.

If the mapped-by side reference of instance C is updated (from instance Y to instance Z) then at flush time the underlying database column(s) of Z is updated to refer to C. The specification requires that the memory model be made consistent, by changing Z to refer to C. Also, since there can only be one value in the database column containing C, the row corresponding to Y must be changed, but if Y has not also been updated to refer to another instance and no other instance has been updated to refer to Y, there is no value to which to set Y's reference except null.

<proposed>
If the field in Y is defined as null-value="exception", this is an error. If the field is defined as null-value="none", and the column defining the relationship is defined as allows-null="false", this is an error. If the field is defined as null-value="none", and the column defining the relationship is defined as allows-null="true", then the column is updated to null and the memory model is made consistent by setting the field in Y to null. 
</proposed>

If the mapped side of instance Y is set to instance D (and Y  currently refers to C), the spec requires that the underlying database column be updated in instance Y to refer to D. The memory model is made consistent, by setting the reference in D to Y and setting C's reference to null. 

<proposed>
If the field in C is defined as null-value="exception", this is an error. If the field is defined as null-value="none", the column in the row corresponding to instance Y is updated to refer to D and there is no longer any row with a reference to C. 
</proposed>

If the mapped-by side reference of instance C is deleted (and currently instance C refers to Y and vice versa) and the field in C is defined as dependent then at flush time both C and Y are deleted.

If the field in C is not defined as dependent and Y has not been changed to refer to another instance, then at flush time the reference in Y to C must be set to null.

<proposed>
If the field in Y is defined as null-value="exception", this is an error. If the field is defined as null-value="none", and the column defining the relationship is defined as allows-null="false", this is an error. If the field is defined as null-value="none", and the column defining the relationship is defined as allows-null="true", then the column in Y is updated to null and the memory model is made consistent by setting the field in Y to null. 
</proposed>

If the mapped side reference of instance Y is deleted (and currently instance Y refers to C and vice versa) and the field in Y that refers to C is defined as dependent, then at flush time the rows corresponding to Y and C are deleted.

If the field is not defined as dependent, then at flush time the field in C must be set to null.

<proposed>
If the field in C is defined as null-value="exception", this is an error. If the field is defined as null-value="none", the row corresponding to instance Y is deleted and there is no instance that refers to C. 
</proposed>

<spec>
The field on the other side of the relationship can be mapped by using the mapped-by at- 
tribute identifying the field on the side that defines the mapping. Regardless of which side 
changes the relationship, flush (whether done as part of commit or explicitly by the user) 
will modify the datastore to reflect the change and will update the memory model for con- 
sistency. There is no further behavior implied by having both sides of the relationship map 
to the same database column(s). In particular, making a change to one side of the relation- 
ship does not imply any runtime behavior by the JDO implementation to change the other 
side of the relationship in memory prior to flush, and there is no requirement to load fields 
affected by the change if they are not already loaded. This implies that if the RetainVal- 
ues flag or DetachAllOnCommit is set to true, and the relationship field is loaded, then 
the implementation will change the field on the other side so it is visible after transaction 
completion. 
Conflicting changes to relationships cause a JDOUserException to be thrown at flush 
time. Conflicting changes include: 
•adding a related instance with a single-valued mapped-by relationship field to 
more than one one-to-many collection relationship 
•setting both sides of a one-to-one relationship such that they do not refer to each 
other 
</spec>


Craig Russell

Architect, Sun Java Enterprise System http://java.sun.com/products/jdo

408 276-5638 mailto:Craig.Russell@sun.com

P.S. A good JDO? O, Gasp!