In #1658712: Refactor test_entity schema to be multilingual we introduced the capability of having multilingual properties attached to a single test entity. Before extending this approach to the real core entities, we need to make multilingual properties queryable through EntityFieldQuery. Since one of the plans of #1346214: [meta] Unified Entity Field API is merging properties and fields, we might end up not needing to migrate all core entities to the new schema supporting multilingual properties. Nonetheless we will still need to ensure (and ratify through tests) that EFQ is fully functional with multilingual properties.

#1184272: Remove deprecated $conditions support from entity controller already needs EFQ to have multilingual property conditions to make EntityTest work correctly. By removing the custom solution introduced there in EntityTestStorageController::loadByProperties() and making the already existing tests pass, we would ensure that we already have pretty solid multilingual capablities for property conditions.

Comments

plach’s picture

Title: Make EFQ work with multilingual properties » Make EntityFieldQuery work with multilingual properties
Status: Active » Postponed
Issue tags: +D8MI, +language-content
gábor hojtsy’s picture

Issue tags: +sprint

Put on sprint in anticipation of that one landing soon :)

fago’s picture

Component: comment.module » entity system

I do think EFQ should be aligned, such that it works for fields and non-fields the same way, i.e. there is just a single addCondition() method. That doesn't necessarily need to part of that issue though.

plach’s picture

Totally agreed. Let's see if we end up needing to address the unification here or if it's easier to defer it to another issue.

plach’s picture

Assigned: Unassigned » plach
Status: Postponed » Active

Working on this.

plach’s picture

Issue summary: View changes

Updated issue summary

plach’s picture

Status: Active » Needs review
StatusFileSize
new7.8 KB

Here is a first attempt: the attached patch allows to perform EFQs on multilingual properties, but does not support property language meta conditions yet. After mulling on this and experimenting for a while, I think that for now it does not make sense to try and provide a unique method to apply language meta conditions to both properties and fields, as currently there is no common code. IMO we should wait to be able to define a unified property/field condition, then it should pretty straightforward to provide also the unfied property/field meta condition.

The attached patch introduces two new entity conditions (langcode and UUID), which are both applied to the base table. The former allows to filter on the entity original language. For now the property language is specified through regular proprty conditions. This approach works for simple queries but won't allow to query on different properties with different languages ("Give me all the test entities having english name Frank and italian uid Zappa"). For this we'll have to introduce property language meta conditions and grouping.

However I tested the attached code by applying #1184272: Remove deprecated $conditions support from entity controller (which implements entity load conditions through EFQ) plus some adjustments to the EntityTestStorageController (see the topic branch), and all the entity translation tests are green. Also the EFQ tests pass, which means that the attached patch is already basically working :)

Let's see what the bot thinks about this.

chx’s picture

Status: Needs review » Needs work

First, thanks for the patch.

This whole data table business needs comments. The data_table variable needs to change to data_table_alias because it's not a table, after all -- there is no table called 'data'. There's $table = isset($data_table_schema['fields'][$column]) ? $data_table : $base_table;. I would be happier with initializing $data_table_alias to FALSE and make this line $table = data_table_alias ? $data_table_alias : $base_table;. If that's not viable we need a comment on what sort of propertyCondition necessitates a column-by-column choice. Also, if we do not per-column then this choice might be moved out of the loop and only did once. Perhaps insttead of data_table_alias we could use $property_table = $base_table; and then override it with the 'data table' key if there is one.

$sql_field = !empty($entity_info['entity keys'][$key]) ? $entity_info['entity keys'][$key] : $key;. Really?? We expect people to put uuid and langcode into sql fields not called uuid and langcode?

Also, I do not see tests.

berdir’s picture

We should be able to simple remove the overriden loadByProperties() method in EntityTestStorageController in #1184272: Remove deprecated $conditions support from entity controller then this should already add basic test coverage and serve as a proof that this works. Might need more specific tests, though.

plach’s picture

Yes, for multilingual entity load conditions we already have test coverage. I'll provide some additional test for more advanced use cases...

klonos’s picture

...coming from #1188394-49: Make title behave as a configurable, translatable field (again)

It would be great if the issue summary had some basic information of what needs testing here. I really want to help with this and testing is as far as I can go. Let me know.

tim.plunkett’s picture

Tagging.

berdir’s picture

Status: Needs work » Needs review
StatusFileSize
new10.19 KB

Re-roll of the original patch with the overridden loadByProperties() method in EntityTestStorageController which means this does now actually test that part of EFQ. I do get two failures locally, so there seems to be an issue...

Status: Needs review » Needs work

The last submitted patch, efq-ml-1691952-12.patch, failed testing.

plach’s picture

Yes, the test storage class needs some tweak. I'm providing a new a patch soon...

plach’s picture

Status: Needs work » Needs review
StatusFileSize
new11.08 KB
new5.19 KB

Here is a reroll. Actually I found out that checking the table per column is not needed in the EFQ context so I've gone the full way proposed by @chx. The EntityTestStorageController still needs a couple of tweaks to keep working with ml properties, as mentioned above, but very small. Now we have basic test coverage in the Entity Translation test.

@klonos:

I don't think this is ready for full-testing yet, since we need to add property language conditions, which I will try to do this afternoon. Moving to 'needs review' for the bot.

plach’s picture

Status: Needs review » Needs work
+++ b/core/modules/entity/lib/Drupal/entity/EntityFieldQuery.php
@@ -868,7 +895,9 @@ class EntityFieldQuery {
+        $table = isset($data_table_schema['fields'][$specifier]) ? $data_table : $base_table;

Forgot to fix sorting.

plach’s picture

Status: Needs work » Needs review
StatusFileSize
new11 KB
new709 bytes

New patch

Status: Needs review » Needs work

The last submitted patch, ml_efq-1691952-14.patch, failed testing.

plach’s picture

Status: Needs work » Needs review
StatusFileSize
new11.53 KB
new2.01 KB

It looks like uuid is used in entity load conditions, hence we need to determine if the property lives in the base table or in the data table (I guess this will need to be fixed while overhauling EFQs). For now I added a comment explaining why we need this.

$sql_field = !empty($entity_info['entity keys'][$key]) ? $entity_info['entity keys'][$key] : $key;. Really?? We expect people to put uuid and langcode into sql fields not called uuid and langcode?

I know it's crazy but we have entity keys for UUIDs, hence we need to support them. Hopefully they'll go away sooner or later.

plach’s picture

The attached patch provides support for multiple property language conditions. Field SQL storage support still missing but we are close. We should have a decent test coverage now.

fago’s picture

I'm no efq expert, but from a overall stance this looks good. The introduction of the "data table" concept though is something storage controller specific, so it should also live in its storage specific query implementation.
We don't need to do that now, but I think we really want to move efq-query execution to a storage-query controller/plugin/manager.

plach’s picture

Totally agreed on moving storage-specific stuff on the storage controller. The current implementation sort of mixes that as only fields are storage-independent but we should totally move those when refactoring EFQ to support storage-independent entities.

The attached patch extends ML property support to the Field SQL storage implementation and provides test coverage for that.

attiks’s picture

I'm not an expert, but this looks solid

+++ b/core/modules/entity/lib/Drupal/entity/EntityFieldQuery.phpundefined
@@ -961,4 +1013,114 @@ class EntityFieldQuery {
+      // Every properties needs a new join on the data table.

properties -> property

schnitzel’s picture

I'm not so used to the EFQ, so would definitely be a candidate which has to learn the EFQ with the new parameters.

When I got this right, I would need this:

$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'node');
$query->propertyCondition('title', 'english_title', NULL, 'original');
$query->propertyLanguageCondition('en', NULL, 'original');
$query->propertyCondition('title', 'german_title', NULL, 'translation');
$query->propertyLanguageCondition('de', NULL, 'translation');
$result = $query->execute();

but why we don't use:

$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'node');
$query->propertyCondition('title', 'english_title', NULL, 'en');
$query->propertyCondition('title', 'german_title', NULL, 'de');
$result = $query->execute();

because this cannot be possible?:

$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'node');
$query->propertyCondition('title', 'english_title', NULL, 'original');
$query->propertyCondition('title', 'german_title', NULL, 'original');
$query->propertyLanguageCondition('en', NULL, 'original');
$result = $query->execute();

so in my understanding the reason why we need the 'propertyLanguageCondition' is to group the different languages, but then we could directly use the langcode as group?
or is there a case which I'm missing?

geek-merlin’s picture

Status: Needs review » Needs work

Looks solid to me too. minor thing before i look deeper:

+++ b/core/modules/entity/lib/Drupal/entity/EntityFieldQuery.phpundefined
@@ -420,19 +427,48 @@ class EntityFieldQuery {
+  public function propertyCondition($column, $value, $operator = NULL, $langcode_group = NULL) {
+    $group = !empty($langcode_group) ? $langcode_group : 0;

simpler: $langcode_group = 0
(also in some other places)

geek-merlin’s picture

Status: Needs work » Needs review

not meant to change status

gábor hojtsy’s picture

Issue tags: +VDC

Tagging for VDC too.

gábor hojtsy’s picture

Tagging for VDC too.

plach’s picture

Issue tags: -VDC
StatusFileSize
new26.24 KB
new2.8 KB

After talking with @Schnitzel we agreed that for consistency we want to keep the current approach, but experiment with his proposal when refactoring EFQs to make them support storage-independent entities.

plach’s picture

Oops, crosspost.

fago’s picture

Status: Needs review » Needs work
Issue tags: +VDC
+  // The entity base keys require joining the entity base table.
+  $entity_base_keys = array('langcode', 'uuid');

So this hardcodes that base table sql fields. I think this is already denormalized to hook_entity_info, so it should be read from there.

-    '#submit' => array('language_admin_add_form_submit'),
+    '#submit' => array('language_admin_add_custom_form_submit'),
   );

Unrelated.

+    list($data_table, $data_table_schema) = $this->getPropertyDataSchema($entity_type);
+    $specifier = $order['specifier'];
+    $table = !empty($data_table_schema['fields'][$specifier]) ? $data_table  . '_' . $order['langcode_group'] : 

Does this ensure we have joined to the table already? I guess we should have something like views' $this->ensureTable(). Also, as we've already the base table schema fields, why not just assume it's in the data table if it' not in the base table and not a field?

+  public function propertyLanguageCondition($langcode = NULL, $operator = NULL, $langcode_group = 0) {
+    // We have a separate method here to ensure there is a distinction at API
+    // level between properties and metadata, even if from the implementation
+    // perspective both conditions are implemented the same way. However this
+    // might not be the case in other storages.
+    // @todo Actually we could also implement the same functionality and keep
+    // this distinction by using language codes as group identifiers:
+    //
+    // $query->propertyCondition('title', 'english_title', NULL, 'en');
+    // $query->propertyCondition('uid', 1, NULL, 'en');
+    // $query->propertyCondition('title', 'german_title', NULL, 'de');
+    //
+    // We probably want to move to this approach when refactoring EFQ to work
+    // with storage-independent entities. For now we are keeping the current
+    // approach for consistency with field meta conditions.
+    return $this->propertyCondition('langcode', $langcode, $operator, $langcode_group);

hm, that makes me feel I cannot query for the entity language any more? Can I?

plach’s picture

Status: Needs work » Needs review
StatusFileSize
new22.84 KB
new8.88 KB

@fago:

Thanks for the review!

Also, as we've already the base table schema fields, why not just assume it's in the data table if it' not in the base table and not a field?

Because we have the langcode column that is present in both tables and we need to join on the data table in that case.

hm, that makes me feel I cannot query for the entity language any more? Can I?

Sure, through an entity condition. Added a couple of them to the tests to prove they are working. They should not be affected by the change proposed in the comment, since they are applied to the base table.

plach’s picture

gábor hojtsy’s picture

Any remaining concerns or should this go for RTBC finally? :)

gábor hojtsy’s picture

Status: Needs review » Reviewed & tested by the community

All right, this has been reviewed by various people from various angles. All concerns are now resolved, there are plenty of docs, so let's get it in!

fago’s picture

I must say I find it quite confusing that $this->propertyCondition('langcode') does something else than $this->entityCondition('langcode'). Then as pointed out in #24 the whole API is not very nice to use, but yep - this patch just follows what we already have for fields. So I guess it's best to move on with this now, but work on improving the API in a follow-up.

We already have #1754174: Add entity_query() wrapper for EntityfieldQuery to simplify common usage as a start.

gábor hojtsy’s picture

Issue tags: +Avoid commit conflicts

This deals with the rapidly changing entity landscape and is at a definite risk of commit conflicts.

gábor hojtsy’s picture

#33: ml_efq-1691952-33.patch queued for re-testing.

catch’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/entity/lib/Drupal/entity/EntityFieldQuery.phpundefined
@@ -420,19 +427,61 @@ class EntityFieldQuery {
+   *   An arbitrary identifier: conditions in the same group must have the same

Is it really arbitrary? Presumably you'd want to put a langcode in here most of the time? What's the difference between a 'group' and a 'langcode group'?

+++ b/core/modules/entity/lib/Drupal/entity/EntityFieldQuery.phpundefined
@@ -420,19 +427,61 @@ class EntityFieldQuery {
+  /**
+   * Adds a condition on the property language.
+   *
+   * @param $langcode
+   *   The language code that the properties belonging to the language group
+   *   should match.
+   * @param $operator
+   *   The operator to be used to test the given value.
+   * @param $langcode_group
+   *   An arbitrary identifier: conditions in the same group must have the same
...
+    // We have a separate method here to ensure there is a distinction at API
+    // level between properties and metadata, even if from the implementation
+    // perspective both conditions are implemented the same way. However this
+    // might not be the case in other storages.
+    // @todo Actually we could also implement the same functionality and keep

OK reading this it starts to make more sense. So $langcode is the language code. Additionally we're allowing you to pass a langcode group in. No... I still don't get it just reading the comments at all.

+++ b/core/modules/entity/lib/Drupal/entity/EntityFieldQuery.phpundefined
@@ -420,19 +427,61 @@ class EntityFieldQuery {
+    // @todo Actually we could also implement the same functionality and keep
+    // this distinction by using language codes as group identifiers:
+    //
+    // $query->propertyCondition('title', 'english_title', NULL, 'en');
+    // $query->propertyCondition('uid', 1, NULL, 'en');

This looks a bit saner - why's it a @todo?

If there really is a good reason to not use langcodes for langcode groups (then presumably we could set the default to NULL and default to the langcode passed in if it's not set explicitly too?) then this could do with much better comments about what you'd need to pass in and why. Maybe query examples in @code tags?

None of the rest of this merits a needs work, just noting:

+++ b/core/modules/entity/lib/Drupal/entity/EntityFieldQuery.phpundefined
@@ -824,7 +883,7 @@ class EntityFieldQuery {
-        $this->addCondition($select_query, $base_table . '.' . $sql_field, $this->entityConditions['revision_id']);

These don't look related to this patch at all, but it's more readable this way so I don't mind them slipping in.

+++ b/core/modules/entity/lib/Drupal/entity/EntityFieldQuery.phpundefined
@@ -858,6 +919,15 @@ class EntityFieldQuery {
+    foreach (array('uuid', 'langcode') as $key) {
+      if (isset($this->entityConditions[$key])) {
+        $sql_field = !empty($entity_info['entity keys'][$key]) ? $entity_info['entity keys'][$key] : $key;
+        if (isset($base_table_schema[$sql_field])) {
+          $this->addCondition($select_query, "$base_table.$sql_field", $this->entityConditions[$key]);
+        }
+      }

Ouch that's nasty. So we're loading the schema just to check if the uuid or langcode keys are defined. Is there an actual issue open to require a naming convention on the table instead of the entity keys stuff?

+++ b/core/modules/entity/lib/Drupal/entity/EntityFieldQuery.phpundefined
@@ -961,4 +1031,104 @@ class EntityFieldQuery {
+  public function addPropertyOrderBy(Select $select_query, $entity_type, array $order) {
+    $entity_info = entity_get_info($entity_type);
+    list($data_table, $data_table_schema) = $this->getPropertyDataSchema($entity_type);
+    $specifier = $order['specifier'];
+    $table = !empty($data_table_schema['fields'][$specifier]) ? $data_table  . '_' . $order['langcode_group'] : $entity_info['base table'];
+    $select_query->orderBy("$table.$specifier", $order['direction']);

This is going to be nasty for performance, but I assume it will only affect queries that are actually multilingual?

fago’s picture

As mentioned above, we already cache the schema fields of the base and revision table in hook-entity-info, couldn't we just assume a property is in the data table if it's not in the main table or in the revision table?

plach’s picture

Status: Needs work » Reviewed & tested by the community

Is it really arbitrary? Presumably you'd want to put a langcode in here most of the time? What's the difference between a 'group' and a 'langcode group'?
[...]
This looks a bit saner - why's it a @todo?
[...]
Maybe query examples in @code tags?

As I was saying above the main reason for all of this is consistency: field conditions have delta gropus and language groups, properties do not have deltas so they get only the language group. I totally agree that this is not the best DX we could aim for and looking forward to refactoring all of of this when making EFQ fully storage-independent. This is just a stop-gap solution to make working on the meaty stuff possible while we get there. We already have plenty of documentation in the patch, I don't think adding some examples of a functionality that just mimicks an existing one would be that useful, given that we would be throwing it away later on.

Ouch that's nasty. So we're loading the schema just to check if the uuid or langcode keys are defined. Is there an actual issue open to require a naming convention on the table instead of the entity keys stuff?

No issue that I'm aware of, however on one hand I'd be tempted to say that UUID and langcode should be fixed by convention and be done with it. OTOH remotely-stored/read-only/legacy entities might have different column names for those, so having the prossibility to provide a mapping would be useful. That said we are loading the data table schema only temporarily: I guess property definitions should allow us to avoid that.

This is going to be nasty for performance, but I assume it will only affect queries that are actually multilingual?

Nope, all the property values are in the data table, so multilingual does not matter here. However we could make EFQ smart enough to use the data table as the query base table in case of property queries, pretty much like it already does with field ones. Mixed ones might end up being slow, but that's just how SQL work in the end.

Tentatively setting back to RTBC, since I don't think there's much room for improvement here. @catch?

plach’s picture

StatusFileSize
new22.72 KB

Well, surely one possible improvement is making the patch apply to the 8.x branch :)

catch’s picture

Status: Reviewed & tested by the community » Needs work

Nah I 'd really like to see the docs improvements here, because I genuinely can't understand what's in there at the moment, and I'd like to not commit new features that I don't understand the API docs. $delta is a lot easier to grok than $langcode vs. $langcode_group.

plach’s picture

Ok, so should we improve also the field condition docs? Because I really feel property conditions should not be any different from field conditions. Also, I cannot see how delta groups are easier to grok than langcode ones, would you please elaborate on this? Sorry, but having written most of the code here (I am responsible for pairing delta groups with langcode groups way back then) it's difficult for me to spot what's easy to understand and what is not.

gábor hojtsy’s picture

I don't think we have any direct need for this support right now in EFQ, so if the refactoring is happening elsewhere, then it might not make sense to keep this among the top current issues, and we can possibly return here later. I don't think Views is planning to use EFQ, they were not much interested in this issue even though asks and tagging for VDC and there is minimal use of EFQ in core so far. If/once people want to make EFQ use spread, they would hit this issue anyway and we would get more eyeballs.

We have little more than 10 weeks to go, so time is becoming very precious.

plach’s picture

I am not surely blocked on this issue and if all @catch needs before letting it in is some doc clean-up I will be happy to do it. I think it would be a great waste of time to throw this away now that we are so close to being done with it.

catch’s picture

I think the difference is that $delta_group doesn't sound like anything, whereas $langcode_group does a bit.

Really the docs improvement might only need to be a couple of example queries in @code tags.

plach’s picture

Status: Needs work » Reviewed & tested by the community
StatusFileSize
new24.74 KB
new4.11 KB

Here are some more docs. Setting back to RTBC to ensure @catch does not lose track of this.

catch’s picture

Title: Make EntityFieldQuery work with multilingual properties » Change notice for: Make EntityFieldQuery work with multilingual properties
Status: Reviewed & tested by the community » Active

OK thanks for the code example. I'm still not comfortable with the terminology but don't have a better suggestion so committed/pushed to 8.x.

We'll need a change notice for the API addition.

plach’s picture

Status: Active » Needs work

I'm still not comfortable with the terminology but don't have a better suggestion so committed/pushed to 8.x.

Thanks, I'll make sure to revisit that when refactoring EFQs. Working on a change notice.

plach’s picture

Status: Needs work » Active
plach’s picture

Status: Active » Needs review
gábor hojtsy’s picture

Title: Change notice for: Make EntityFieldQuery work with multilingual properties » Make EntityFieldQuery work with multilingual properties
Status: Needs review » Fixed
Issue tags: -sprint

Looks understandable to me. Good code example, IMHO.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

chx’s picture

Issue tags: -Avoid commit conflicts

Removing Avoid commit conflicts tag

chx’s picture

Issue summary: View changes

Updated issue summary.