Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Walkable queries #81

Closed
wants to merge 13 commits into from
Closed

Conversation

bdunogier
Copy link

@bdunogier bdunogier commented Oct 29, 2016

Status: fresh, working prototype

This adds support for a query migration type, that walks over results from a query, and apply another migration to them.

This allows operations such as multi-criteria selection of a set of items with another operation on each result. The Expression Language is used to map parameters from the query to migration parameters, with a collection_item function that has each current item of each iteration.

Usage example: for a set of content items, it adds locations to them below items from a relation list field (uses a custom function), and delete the former main location:

# Find all "Businesses" items
-
    mode: content
    type: query
    match:
        query_type: businesses
    collection: businesses
    walk:
        # For each business content item, find the locations of related content items from the "sector" field
        -
            mode: location
            type: query
            match:
                query_type: business_sectors
                parameters:
                    business: '@=relatedContentIds(collection_item("businesses"), "sector")'
            collection: sectors
            walk:
                # Add a location to each business under each sector
                -
                    mode: create
                    type: location
                    match:
                        content_id: "@=collection_item('businesses').id"
                    parent_location_id: "@=collection_item('sectors').id"

        # Delete the former main location
        -
            mode: delete
            type: location
            match:
                location_id: "@=collection_item('businesses').contentInfo.mainLocationId"

Custom expression language functions, such as relatedContentIds, can be registered with a service tag.

TODO

  •  Handle the QueryType problem. The feature did not exist in older kernel versions. Needs to be handled cleanly (this is why Travis fails)
  • Cleanup copyright headers ;)
  • Add the new requirements (expression language ? anything else ?)
  •  Add an example of walk to the DSL docs

Bertrand Dunogier added 6 commits October 29, 2016 17:08
Added phpdoc, or fixed obvious errors.
Allows stateless executors (almost), thus nested executors.
Allows storage of non string references.
Allows to walk query results, and run a step for each result:

```
-
  mode: location
  type: query
  match:
    query_type: some_query_type
  collection: my_collection
  walk:
    # run another query on each location
    -
      mode: location
      type: query
      match:
        query_type: get_x_children
        parameters:
          location_id: "@=collection_item(my_collection).id"
      collection: some_other_collection
      walk:
        - ...

    # Do something to the location
    -
      mode: update
      type: location
      match:
        location_id: "@=collection_item('my_collection')"
      # ..
```
@crevillo
Copy link
Contributor

very interesting!!

@@ -100,7 +100,7 @@ public function execute(MigrationStep $step)

$previousUserId = $this->loginUser(self::ADMIN_USER_ID);
try {
$output = $this->$action();
$output = $this->$action($step->dsl);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmm, ce n'est pas dejà dispo qq part ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is available, but we can't use a stateful variable (explained that in the commit message). Since all executors have the DSL stored as a property, changes by one affect the DSL read by the other layers. And bam.

It is kind of a big change, but I don't see big counterparts.

@bdunogier
Copy link
Author

bdunogier commented Oct 31, 2016

I doubt that this failure is on me: https://travis-ci.org/kaliop-uk/ezmigrationbundle/jobs/171643923 :-)

But a little problem I have here: this implementation requires QueryTypes. And those were added in eZ Platform, meaning kernel >= 6.0. I guess I must disable the feature for earlier versions somehow ?

@gggeek
Copy link
Member

gggeek commented Oct 31, 2016

Yeah, the Travis failure on eZ6 is weird, I pin it down to Travis caching some bits of Sf somehow... it makes little sense at first sight, as in theory it fails with an SF3 error, but we are running that test case on SF2.8... So I just tagged testing on eZ6 as 'allow fail' for now, while I carve out time for a deeper investigation.

As for the need to be able to run the test suite on eZP versions starting 2014.3 yes, please :-)

@gggeek
Copy link
Member

gggeek commented Oct 31, 2016

ps: as a side note: it was my original intention to allow the existing 'matcher' classes to be able to work on an array of conditions passed in, not just a single one (in short: turn matchers into query builders). I never came around to implementing that. At first sight, it seems that your code does something similar but in a different way... did you take a look at extending the matchers ?

@bdunogier
Copy link
Author

bdunogier commented Oct 31, 2016

I first started by implementing queries on matchers, and it turned out to be tedious and repetitive. It requiredwhich seem far from efficient. Rather than heavily changing the existing, this implementations sits around it.

I found out that there are a few advantages to this structure:

  • it allows nested calls, something you could not do by using the matchers
  • it makes iteration over collections more natural, as you only work on one content item at a time

The nesting part is really important with the use-case i'm working with, and I think that it allows for complex migrations with the comfort and ease of use of yaml.

@gggeek
Copy link
Member

gggeek commented Oct 31, 2016

I see what you mean.
But is it compatible at all with eZP 5 / 2014.11 ?
(also: in terms of efficiency, does is still build an efficient solr/db filter when nesting filters ?)

@bdunogier
Copy link
Author

QueryTypes: no, they're not. That's why I was hinting about earlier :)

@gggeek
Copy link
Member

gggeek commented Oct 31, 2016

ps: could you add an example DSL in the doc folder ?

@lolautruche
Copy link
Contributor

Hi

As for QueryTypes, they could be backported somewhere like EzCoreExtraBundle. I intended to do so sooner or later.

@lolautruche
Copy link
Contributor

Sorry for my ignorance, but what is collection: businesses? Is this a way to label collections ? Or something else that I missed?

@bdunogier
Copy link
Author

bdunogier commented Oct 31, 2016

It is how collections are set as references, to be used later on with collection_item().

public function addExecutor(ExecutorInterface $executor)
{
foreach($executor->supportedTypes() as $type) {
$this->executors[$type] = $executor;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be $this->executors[$type][]?

* @param $searchResult
* @return boolean
*/
protected function setReferences($args)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not using proper arguments here?

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

class QueryManagerCompilerPass implements CompilerPassInterface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please briefly explain what this compiler pass is meant for


ez_migration_bundle.expression_language:
class: Symfony\Component\ExpressionLanguage\ExpressionLanguage
lazy: true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why lazy?

*/
public function process(ContainerBuilder $container)
{
if ($container->has('ez_migration_bundle.executor.query_manager') && $container->has('ez_migration_bundle.migration_service')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better using return asap pattern here :-)

function() {
throw new \Exception('ref function does not support compilation');
},
function($foo, $identifier) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$foo represents the variables from the expression context. This is actually a hash, identical to what we pass to a twig view when rendering it.
So we'd better name it $variables and type hint it as array 😉


protected function getSearchService()
{
return $this->repository->getSearchService();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really needed?

{
foreach ($this->matchers as $matcher) {
$result = $matcher->match($dsl['match']);
if (count($result) == 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if count is > 1?

$this->referenceResolver->addReference('collection_' . $collection, $value, true);
}

protected function getQuery($dsl)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHPDoc please 😃

use Symfony\Component\ExpressionLanguage\ExpressionFunction;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

class QueryManager extends RepositoryExecutor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about QueryExecutor instead?
Besides, class name isn't quite self-explanatory. Maybe a phpdoc comment is needed 😉

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK forget it, it's perfectly inline with other managers 😊

gggeek pushed a commit that referenced this pull request Nov 20, 2016
@gggeek
Copy link
Member

gggeek commented Feb 6, 2017

About the QueryType class not existing in older kernels: what if we split off this PR into its own bundle & repo, and only try to push into the migrationbundle the minimal changes required to support it?

Apart from solving the requirements problem, I think that there might be other, smaller benefits to that approach:

  • as much as I like the flexibility given by your solution, I am also loath to add such a huge chunk of code (at least until I feel comfortable enough to efficiently maintain it);
  • I also fear that the apparent complexity of the walkable-queries approach and example might put off some less-technical users of the bundle, or give them bad experience by leading them into using a tool which will shoot them in the foot (of course I might be completely wrong on this point).

@gggeek
Copy link
Member

gggeek commented Oct 29, 2020

Status update:

  • the 'walkable collections' part has been implemented, imho, via the generic loop migration step. This part can probably be removed from the PR

  • adding support for 'QueryType' seems like a worthy addition... maybe it could be implemented simply via a couple of new methods in the existing ContentMatcher/LocationMatcher? (I confess to never having used QueryTypes, I assume they can be used to fetch either Nodes or Locations only?)

  • I am still undecided about having new resolvers enabled by default (such as fe. for container parameters or env vars). On one hand they seem useful, on the other hand I don't want to break existing migrations which have in their strings/contents anything which matches a new introduced prefix... (of course we could introduce them in code but leave their services inactive. I think that this solution would not make them very discoverable by end users though...)

  • ...speaking about which: I am thinking to massively revamp the whole 'resolver' concept in the next major release (v6) by making each string in a migration element resolvable as if it was a twig template. This would give us access to the whole symfony expression language syntax, and would make it easier to add resolvers without risking breaking existing migrations

  • anything else I am missing?

@gggeek
Copy link
Member

gggeek commented Nov 16, 2020

Status update: matching based on QueryType has been implemented, and will be in 5.14.
No feedback was given for more than 2 weeks since my last comment, which probably means that either everything is fine, or the original dev lost interest in this ;-)
Closing.

@gggeek gggeek closed this Nov 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants