laravel-nestedset icon indicating copy to clipboard operation
laravel-nestedset copied to clipboard

withDepth causes Cardinality violation mysql error

Open ludgerey opened this issue 4 years ago • 1 comments

Description

Using withDepth results in a mysql error caused by a superfluous * selector. Changing the library code like below solves the issue.

Versions:

kalnoy/nestedset: 6.0.0 laravel/framework: 8.48.1 php: 8.0

Reproducable using:

Unit::withDepth()->get();

Error:

Illuminate\Database\QueryException
SQLSTATE[21000]: Cardinality violation: 1241 Operand should contain 1 column(s) (SQL: select *, (select *, count(1) - 1 from `units` as `_d` where `units`.`deleted_at` is null and `units`.`_lft` between `_d`.`_lft` and `_d`.`_rgt`) as `depth` from `units` where `units`.`deleted_at` is null)

I isolated the problem to the subquery for the depth value. Laravel somehow adds a *-selector to the query. MySQL cannot match the subquery result to a single value in the main query. To get rid of the *-selector I tweaked the library code as stated below.

Malfunctioning library code

# vendor/kalnoy/nestedset/src/QueryBuilder.php
# line 411 and following
$query = $this->model
            ->newScopedQuery('_d')
            ->toBase()
            ->selectRaw('count(1) - 1') // <-- not working
            ->from($this->model->getTable().' as '.$alias)
            ->whereRaw("{$table}.{$lft} between {$wrappedAlias}.{$lft} and {$wrappedAlias}.{$rgt}");

Working library code

# vendor/kalnoy/nestedset/src/QueryBuilder.php
# line 411 and following
$query = $this->model
            ->newScopedQuery('_d')
            ->toBase()
            ->select(DB::raw('count(1) - 1')) // <-- works :)
            ->from($this->model->getTable().' as '.$alias)
            ->whereRaw("{$table}.{$lft} between {$wrappedAlias}.{$lft} and {$wrappedAlias}.{$rgt}");

ludgerey avatar Jun 25 '21 14:06 ludgerey

I found my error. I had some left-over code in my default Eloquent query, that led to the superfluous *.

After removing this, the library works as desired:

static::addGlobalScope('order', function (Builder $builder) {
    if(is_null($builder->getQuery()->columns)) {
        $builder->addSelect('*'); // <-- this
    }

    $builder->addSelect(
        DB::raw(/* some complex mysql terms */),
    )->orderBy('hierarchy');
});

I am leaving this here for others who encounter the same problem.

ludgerey avatar Jun 28 '21 08:06 ludgerey