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

Using subqueries #299

Closed
mishal opened this issue Jun 3, 2021 · 5 comments
Closed

Using subqueries #299

mishal opened this issue Jun 3, 2021 · 5 comments
Assignees
Labels

Comments

@mishal
Copy link

mishal commented Jun 3, 2021

I used to use something like:

// create subquery, all fields use 's' alias
$q = $repo->getQueryBuilder(
    Spec::orX(
        Spec::like('name', 'a', Like::CONTAINS, 's'),
        Spec::eq('name', 'a', 's')
    ),
    's'
)->getQuery();

$q = $repo->getQuery(Spec::andX(
    new InSubquery('id', $q, 's')
));

but it does not work with 2.0... (using happyr/doctrine-specification 2.x-dev dc6c2b5 )


class InSubquery implements Filter
{
    /**
     * @var string field
     */
    protected $field;

    /**
     * @var QueryBuilder value
     */
    protected $value;

    /**
     * @var string dqlAlias
     */
    protected $dqlAlias;

    /**
     * Make sure the $field has a value equals to $value.
     *
     * @param string $field
     * @param string $dqlAlias
     */
    public function __construct($field, Query $value, $dqlAlias = null)
    {
        $this->field = $field;
        $this->value = $value;
        $this->dqlAlias = $dqlAlias;
    }

    public function getFilter(QueryBuilder $qb, string $dqlAlias): string
    {
        if (null !== $this->dqlAlias) {
            $dqlAlias = $this->dqlAlias;
        }

        $parameters = $this->value->getParameters();

        /** @var Parameter $parameter */
        foreach ($parameters as $parameter) {
            $qb->setParameter($parameter->getName(), $parameter->getValue(), $parameter->getType());
        }

        return (string) $qb->expr()->in(
            \sprintf('%s.%s', $dqlAlias, $this->field),
            $this->value->getDQL()
        );
    }

    /**
     * Get a good unique parameter name.
     *
     * @return string
     */
    protected function getParameterName(QueryBuilder $qb)
    {
        return \sprintf('in_subquery_%d', $qb->getParameters()->count());
    }
}

The query tries to join s... and produces invalid DQL.
I tried to understand the new context from #273 but I do not know how to fix my code.... Can you please provide some hints?

Thanks

@mishal
Copy link
Author

mishal commented Jun 3, 2021

My workaround is to implement custom Operand something like:

$name = new class('name', 's') implements Operand {

    /**
     * @var string
     */
    private $fieldName;

    /**
     * @var string|null
     */
    private $context;

    /**
     * @param string      $fieldName
     * @param string|null $context
     */
    public function __construct(string $fieldName, ?string $context = null)
    {
        $this->fieldName = $fieldName;
        $this->context = $context;
    }

    public function transform(QueryBuilder $qb, string $context): string
    {
        if (null !== $this->context) {
            $context = $this->contex;
        }

        return sprintf('%s.%s', $context, $this->fieldName);
    }

    public function execute($candidate, ?string $context = null)
    {
        throw new \RuntimeException('Not supported');
    }

};

And than use:

// create subquery, all fields use 's' alias
$q = $repo->getQueryBuilder(
    Spec::orX(
        Spec::like($name, 'a', Like::CONTAINS),
        Spec::eq($name, 'a')
    ),
    's'
)->getQuery();

@peter-gribanov
Copy link
Member

peter-gribanov commented Jun 3, 2021

Perhaps you shouldn't explicitly specify the DQL alias?

final class InSubquery implements Filter
{
    private string $field;
    private QueryBuilder $subqueryBuilder;
    private ?string $dqlAlias;

    public function __construct(string $field, QueryBuilder $subqueryBuilder, ?string $dqlAlias = null)
    {
        $this->field = $field;
        $this->subqueryBuilder = $subqueryBuilder;
        $this->dqlAlias = $dqlAlias;
    }

    public function getFilter(QueryBuilder $qb, string $dqlAlias): string
    {
        if (null !== $this->dqlAlias) {
            $dqlAlias = $this->dqlAlias;
        }

        foreach ($this->subqueryBuilder->getParameters() as $parameter) {
            $qb->setParameter($parameter->getName(), $parameter->getValue(), $parameter->getType());
        }

        return (string) $qb->expr()->in(
            \sprintf('%s.%s', $dqlAlias, $this->field),
            $this->subqueryBuilder->getDQL(),
        );
    }
}

Usage

$subqueryBuilder = $repo->getQueryBuilder(Spec::orX(
    Spec::eq('name', 'a'),
    Spec::like('name', 'a', Like::CONTAINS),
));

$qb = $repo->getQueryBuilder(new InSubquery('id', $subqueryBuilder));

@peter-gribanov
Copy link
Member

Let me remind you that you can turn off context resolving for a specific query.

DQLContextResolver::disableDeadJoinsProtection();

$q = $repo->getQueryBuilder(
    Spec::orX(
        Spec::like('name', 'a', Like::CONTAINS, 's'),
        Spec::eq('name', 'a', 's')
    ),
    's'
)->getQuery();

$q = $repo->getQuery(Spec::andX(
    new InSubquery('id', $q, 's')
));

DQLContextResolver::enableDeadJoinsProtection();

Or you can disable auto joining

DQLContextResolver::disableAutoJoining();

// your code here

DQLContextResolver::enableAutoJoining();

@mishal
Copy link
Author

mishal commented Jun 4, 2021

Thanks Peter, I will try that out.

@mishal
Copy link
Author

mishal commented Jun 4, 2021

Turning off the context resolving works for me. Thanks a lot! Good work!

@mishal mishal closed this as completed Jun 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants