Yoann Rodière (
https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=557058%...
) *updated* an issue
Hibernate Search (
https://hibernate.atlassian.net/browse/HSEARCH?atlOrigin=eyJpIjoiNTA4OWRj...
) / New Feature (
https://hibernate.atlassian.net/browse/HSEARCH-3903?atlOrigin=eyJpIjoiNTA...
) HSEARCH-3903 (
https://hibernate.atlassian.net/browse/HSEARCH-3903?atlOrigin=eyJpIjoiNTA...
) Filters based exclusively on mapper metadata for @IndexedEmbedded (
https://hibernate.atlassian.net/browse/HSEARCH-3903?atlOrigin=eyJpIjoiNTA...
)
Change By: Yoann Rodière (
https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=557058%...
)
The {{includePaths}} filter in {{@IndexedEmbedded}} refers to index field paths. This has
several drawbacks:
* Part of the implementation has to be in the backend, which feels quite dirty.
* This is not very consistent with the {{maxDepth}} filter, which applied to the
{{@IndexedEmbedded}} only (depth of fields created within an included bridge is
unlimited).
* The filters cannot easily be applied to dynamic fields, so dynamic fields are always
included as soon as their nearest static parent is included.
* The filter can end up including some fields declared by a custom field bridge, but not
others.
** This does not make sense performance-wise as the fields will still be populated by the
bridge, but ignored by the backend.
** Worse, when we introduce support for bridge-defined predicates (HSEARCH-3320), we may
end up with dysfunctional predicates because only some fields are present, while the
bridge expects all fields to be present.
* We are forced to use inference to detect which bridges should be included or excluded,
based on the fields they declared.
** This code is unnecessarily complex.
** This code does not work correctly with field templates, since we cannot know in advance
whether dynamic fields will be included. In particular:
*** Bridges that declare field templates, but only ever add dynamic fields that would not
match the {{includePaths}}, are included nonetheless.
*** Bridges that do not declare anything and rely on field templates declared by a parent
(which is legal) are excluded.
We could get rid of most of the complexity by implementing filters differently, based on
mapper metadata exclusively (mapping annotations and/or entity model).
h4. Solution 1: property paths
We could rely on property paths instead of field paths. Only bridges applied to included
properties are themselves included.
The major drawback is that there wouldn't be any way to filter out type bridges.
h4. Solution 2: groups /alternatives
We could rely on "groups", similarly to the {{@LazyGroup}} support in Hibernate
ORM, or to the group support in Hibernate Validator.
In our case I'd be tempted to call them "alternatives" since the problem is
rather similar to one solved by the {{AlternativeBinder}} (except that one solves the
problem dynamically).
One assigns groups alternatives to every {{@Field}}/{{@IndexedEmbedded}}, then references
the groups alternatives in {{@IncludedEmbedded( includeGroups includeAlternatives =
...)}}.
The main problem with this solution is its complexity; Validator is using groups and I
know they can be pretty complex to handle. We should definitely see what makes them so
complex in Validator to avoid the same problems in Search.
For example:
{code}
@Indexed
public class Level1Entity {
// Will include id only
@IndexedEmbedded
private Level2Entity level2_1;
// Will include id, name
@IndexedEmbedded( includeGroups includeAlternatives = { BuiltinGroups
BuiltinAlternatives.DEFAULT, "base"})
private Level2Entity level2_2;
// Will include id, name, category
@IndexedEmbedded( includeGroups includeAlternatives = { BuiltinGroups
BuiltinAlternatives.DEFAULT, "base", "advanced"})
private Level2Entity level2_3;
}
public class Level2Entity {
@GenericField // Default group
private String id;
@GenericField( groups alternatives = "base")
private String name;
@GenericField( groups alternatives = "advanced")
private String category;
}
{code}
h5. Going further: dynamic alternative selection
One could imagine to allow selecting alternatives dynamically. All the alternatives that
*can* be selected would be included in the index schema, and when indexing some fields
would get enabled or not based on the dynamic alternative selection.
This would provide a feature similar to the {{AlternativeBinder}}, but much more
powerful.
There is one unknown, though: how would {{(a)IndexedEmbedded.includeAlternatives}} interact
with the dynamic alternative selection? If dynamic alternative selection is overridden by
{{(a)IndexedEmbedded.includeAlternatives}}, it will likely not work as intended for the
"multi-language" use case of {{AlternativeBinder}}. If dynamic alternative
selection ignores {{(a)IndexedEmbedded.includeAlternatives}}, it will become impossible to
exclude dynamically enabled fields from {{@IndexedEmbedded}}.
Maybe we should separate the two concepts, e.g. {{@GenericField(alternatives = ...,
dynamicAlternatives = ...)}}?
Or maybe we should assign a static alternative to the "dynamic alternative
resolver": the resolver and all corresponding fields would be included in the schema
if the resolver's assigned static alternative is included, even if the field's
(dynamic) alternatives are not included. Then it would be the user's responsibility to
make sure static and dynamic alternatives use different names, so as not to include
dynamic alternatives statically by mistake.
h5. Variation: overriding {{includeAlternatives}}
It would prevent us from supporting the use case mentioned in HSEARCH-1112 directly, but I
believe the same effect could be achieved if we defined group alternative filters as
"overriding" instead of "composable": an {{@IndexedEmbedded(
includeGroups includeAlternatives = "a")}} that includes an {{@IndexedEmbedded(
includeGroups includeAlternatives = "b")}} would just act as if the contained
{{@IndexedEmbedded}} included group alternative "a", and only group alternative
"a".
For example:
{code}
@Indexed
public class Level1Entity {
// Will include level2.level3.a only
@IndexedEmbedded( includeGroups includeAlternatives = "a")
private Level2Entity level2;
}
public class Level2Entity {
@GenericField( groups alternatives = "b")
private String name;
@IndexedEmbedded( includeGroups includeAlternatives = "b") // includeGroups
includeAlternatives is overridden in Level1Entity
private String id;
}
public class Level3Entity {
@GenericField( group alternatives = "a")
private String a;
@GenericField( group alternatives = "b")
private String b;
}
{code}
There are pros and cons:
* Pro: Groups Alternatives would be much easier to implement and understand: the various
filters defined in indexed-embedded entities would no longer be relevant.
* Con: it would become harder to manage cycles through group filtering: you would no
longer be able to rely on indexed-embedded entities to filter out cycles through groups
alternatives (since their group alternative filters are ignored).
* Con: the behavior would not be consistent with that of {{maxDepth}}.
h4. Next
As a second step, we should probably deprecate {{includePaths}} and mark it for removal in
a later major version (7+).
(
https://hibernate.atlassian.net/browse/HSEARCH-3903#add-comment?atlOrigin...
) Add Comment (
https://hibernate.atlassian.net/browse/HSEARCH-3903#add-comment?atlOrigin...
)
Get Jira notifications on your phone! Download the Jira Cloud app for Android (
https://play.google.com/store/apps/details?id=com.atlassian.android.jira....
) or iOS (
https://itunes.apple.com/app/apple-store/id1006972087?pt=696495&ct=Em...
) This message was sent by Atlassian Jira (v1001.0.0-SNAPSHOT#100140- sha1:5d840a8 )