On Sun, Oct 15, 2017 at 10:46 AM, Steve Ebersole <steve(a)hibernate.org> wrote:
On Fri, Oct 13, 2017, 3:35 PM Gail Badner <gbadner(a)redhat.com>
wrote:
>
> Hi Steve,
>
> I'm circling back to this. Please see below...
>
> On Sat, Sep 2, 2017 at 8:47 AM, Steve Ebersole <steve(a)hibernate.org>
> wrote:
> > I don't have the original email to reply to. So I'll reply here.
> >
> > Overall, I had not really considered the primitive attribute case, but
> > yeah
> > that's clearly an issue. My mistake.
> >
> > A) I agree that the con here is huge. Not the best option
> >
> > B) Is close to better. We could certainly check this and throw an
> > error.
>
> Throwing an exception is option A.
Your (A) and (B) are the exact same thing (same underlying condition check)
except in (A) you throw an exception and in (B) you log a warning and
disregard an explicit user setting.
Correct.
WRT to the condition check, you suggest to "compare a primitive value in a
composite with the default for the primitive type (e.g, comparing an int
value with 0 instead of null)". That's not the best condition check. I was
pointing out that we already have better handling for this for ids and
versions and that we ought to follow that paradigm, mainly on startup we
would instantiate an instance of said component and check the initial values
for its attributes. E.g., consider:
@Embeddable
public class MyEmbeddable {
private int someInt;
private int anotherInt = -1;
}
Here, comparison of `#anotherInt` to 0 is not really correct. The proper
comparison is to -1. Again, this is exactly what we do for ids and
versions.
So because your (A) and (B) check the same condition I was pointing out that
they actually are based on the incorrect condition check (0 versus -1), so
we should correct that assumption. But even then we have and further that
we should update the check and that we still have the different outcome
between (A) and (B) to decide between.
I have already implemented something that does the check that you
suggested, and I decided against it for a couple of reasons:
1) Any initialized values can be "real" values.
2) Suppose an instantiated embedded value includes something like:
Date created = new Date();
double random = Math.random();
For 2), an embedded value would never be considered empty, and its
values would always be persisted instead of null.
I think that it should be up to the `EmptyCompositeStrategy` to decide
if a primitive that has the default value, or something like `created`
or `random` should be ignored when determining if an embedded value is
empty.
The downside I was trying to point out (and it is the same for issue for
some of the other parts of the discussion) is that we currently do not have
a singular place where we keep "embeddable" metadata - again, CompositeType
describes the embedded, not the embeddable. But maybe it is ok to duplicate
this "instantiate and check" for each CompositeType. A alternative is to
keep a mapping of this information as part of the bootstrap data structures
to help do that just once for every embedded of that embeddable.
Further, in discussing these various options it is important to make the
distinction between the best solution in terms of
short-term - how do we handle this for 5.x
longer-term - how do we address this in 6.0+ because here we have some more
flexibility.
Longer term, I ultimately think the following is the best solution:
correct the condition as discussed above
As I mention, I don't think correcting the condition is the right thing to do.
slightly modified version of (C) - see below
slightly modified version of (D) - see below
The change I'd make to `EmptyCompositeStrategy` is specifically the
signatures and (slightly) the intent to work with (D). Best case scenario,
again for 6.0+) I'd pass in
`org.hibernate.metamodel.model.domain.spi.EmbeddedTypeDescriptor` whose
intent is kind of, sort of the same as `ComponentMetamodel`[1]. Worst case
we'd pass in this `org.hibernate.metamodel.model.domain.NavigableRole` you
can see exposed here. NavigableRoles can be resolved to their Navigable via
the TypeConfiguration (the Navigable here would be the
EmbeddedTypeDescriptor). Additionally, these roles are nice in terms of
configuration as we will see below.
In terms of (D), I think configuration here is a good idea especially in
combination with (C). In fact I can see the following settings:
hibernate.empty_composite_creation
enable - enable creation & throw exceptions when we deem we cannot create
them (condition check discussion). This is basically (A)
warn - same as "enable", but warn when we cannot create them - basically (B)
disable (default) - "opt out"
strategy - consult the configured EmptyCompositeStrategy
hibernate.empty_composite_strategy - names a `EmptyCompositeStrategy` to use
I like what you describe above.
Currently the property is named `hibernate.create_empty_composites.enabled`.
Are you suggesting we rename the property in 6.0+?
For 5.x, these would be options for hibernate.create_empty_composites.enabled:
true - enable creation & throw exceptions when we deem we cannot
create them (condition check discussion). This is basically (A)
warn - same as "enable", but warn when we cannot create them - basically (B)
false - disable (default) - "opt out"
strategy - consult the configured EmptyCompositeStrategy
Additionally, I think it would be awesome to allow configuring (1) per role,
e.g. (assuming Person#name as a composite):
hibernate.empty_composite_creation = strategy
hibernate.empty_composite_creation.com.acme.Person.name = disable
So for all composites we will use the configured strategy, except for
Person#name, which we have explicitly disabled / opted-out-of.
I like this as well.
For 5.x, I guess we'd have something like this to opt out:
hibernate.create_empty_composites.enabled=strategy
hibernate.create_empty_composites.enabled.com.acme.Person.name=false
Should we also allow to disable in general, then opt-in for Person.name, as in:
hibernate.create_empty_composites.enabled=false
hibernate.create_empty_composites.enabled.com.acme.Person.name=true
(this would fail like case (A) if the newly instantiated value has
initialized values)
Also, allow a strategy to be used for the opted-in value:
hibernate.create_empty_composites.enabled=false
hibernate.create_empty_composites.enabled.com.acme.Person.name=strategy
>
> > A better logical option is to do similar to what we do for ids and
> > versions...
> > on startup instantiate one of these and grab/store its initial state
> > when
> > freshly instantiated. We can later use those values to perform the
> > empty
> > check. This is more logical, but not sure how "practical" it is
given
> > that
> > we do not really have a good place to keep this relative to an
> > embeddable,
> > nor relative to an embedded, aside from CompositeType, but that does not
> > feel right.
>
> Is this what you refer to as (B) (expanded)?
>
>
> My first email in the thread (which I told you to disregard) basically
> suggests doing this. I told you to disregard it because I realized
> that it would have serious issues.
I'm not seeing where that first email suggests checking based on the actual
initialized value rather than the "primitive type default". But if it did,
then yes that is a better check as discussed above.
> Please see an example of the consequences when the embedded represents
> an ID:
>
https://github.com/hibernate/hibernate-orm/pull/1993/commits/329354bfa4bd...
Ids should be excluded from this completely. Hibernate does not support any
part of a PK to be nullable - ergo this entire discussion is completely
irrelevant for composite PKs.
My example involves a nullable property that happens to be a composite ID.
Please take a look at the mapping in my example:
https://github.com/hibernate/hibernate-orm/pull/1993/commits/329354bfa4bd...
IMO, it's a really bad idea for Hibernate to assume, by default, that
initialized values in a newly instantiated embedded value would be
considered empty.
>
> If I'm understanding what you mean by (B) (expanded), then I don't
> think that's is a good idea.
As I said in my initial reply and above, I am not a fan of (B) - I think it
is, generally speaking, a bad option to ignore settings that the user has
explicitly set. But again, we need to keep short/long term in mind because
short-term I think every other option has some significant drawbacks.
And long-term it comes down to what we allow for configuration. E.g. if we
do not allow the full breath of config options I mentioned, then that
changes things.
> I think C is a good starting point in H5, preferably using role (if
> that can be done).
For 5.x, (C) may be the best option aside from the argument discussion.
Like I said, in terms of 6 the best argument to pass would be
EmbeddedTypeDescriptor. Or, depending on the timing (boot model versus
runtime model) `org.hibernate.boot.model.domain.EmbeddedMapping` /
`org.hibernate.boot.model.domain.EmbeddedValueMapping`.
We *could* pass in this role as a String, but I really hate passing Strings.
Another option would be to back-port NavigableRole and expose these from
EntityPersister/EntityType, CollectionPersister/CollectionType and
ComponentType.
I'll have to look at NavigableRole to see what you mean. We are hoping
to backport to 5.1. I don't think I can make changes to
EntityPersister/CollectionPersister in 5.1, since we can't use default
methods.
I'm trying to get something that will work in both 5 and 6 to minimize
changes for users as they migrate. Assuming we back-port NavigableRole,
something like:
public interface EmptyCompositeStrategy {
/**
* Should a composite/embeddable be instantiated all of
* its composed attribute values are null?
*/
boolean supportsEmptyComposite(NavigableRole compositeRole);
/**
* Is the given composite value considered "empty"?
*/
boolean isEmptyComposite(
NavigableRole compositeRole,
Object value,
Object component,
SessionFactoryImplementor factory);
}
I mentioned in my previous email that there was an extra argument and
corrected it there. With your change it should be:
boolean isEmptyComposite(
NavigableRole compositeRole,
Object value,
SessionFactoryImplementor factory);
Although, tbh I am not really understanding the intent of your second
method. Are you asking the strategy to check each individual sub-attribute
for emptiness? Also, are you thinking this gets called when reading the
values? Or when writing the values?
It is intended to determine if a particular composite value should be
considered empty. It will be needed by ComponentType#isEqual,
#compare, #isDirty, #isModified to determine if 2 values are both
considered empty.
In the case that an embeddable has an indeterminate sub-attribute, the
2 values could have a different sub-attribute value, but
EmptyCompositeStrategy#isEmptyComposite could ignore that
sub-attribute in its determination.