phalcon/cphalcon · error · InvalidUnaryExpression

Invalid SQL-unary expression

Error message

Invalid SQL-unary expression

What it means

getSqlExpressionUnary() throws InvalidUnaryExpression when a unary-op expression has neither a 'left' nor a 'right' key. The resolver first tries expression['left'] (rendered as 'left OP'), then expression['right'] ('OP right'); if both are absent there is no operand for the operator (NOT, IS NULL, etc.) and the compiler throws.

Source

Thrown at phalcon/Db/Dialect.zep:1429

    final protected function getSqlExpressionUnaryOperations( array expression, string escapeChar = null,  array bindCounts = []) -> string
    {
        var left, right;

        /**
         * Some unary operators use the left operand...
         */
        if fetch left, expression["left"] {
            return this->getSqlExpression(left, escapeChar, bindCounts) . " " . expression["op"];
        }

        /**
         * ...Others use the right operand
         */
        if fetch right, expression["right"] {
            return expression["op"] . " " . this->getSqlExpression(right, escapeChar, bindCounts);
        }

        throw new InvalidUnaryExpression();
    }

    /**
     * Resolve a WHERE clause
     *
     * @param array|string expression
     * @param string|null escapeChar
     * @param array bindCounts
     *
     * @return string
     */
    final protected function getSqlExpressionWhere(var expression, string escapeChar = null,  array bindCounts = []) -> string
    {
        var whereSql;

        if typeof expression === "array" {
            let whereSql = this->getSqlExpression(expression, escapeChar, bindCounts);
        } else {

View on GitHub (pinned to b7419de9cd)

Solutions

  1. Attach the operand under 'left' or 'right': ['type' => 'unary-op', 'op' => 'NOT ', 'right' => ['type' => 'qualified', 'name' => 'active']]
  2. For NULL checks prefer Query Builder conditions like where('active IS NULL') which build the unary expression correctly
  3. Validate unary nodes in your expression factory: require isset($node['left']) XOR isset($node['right'])

Example fix

// before
$expr = ['type' => 'unary-op', 'op' => 'NOT '];

// after
$expr = [
    'type'  => 'unary-op',
    'op'    => 'NOT ',
    'right' => ['type' => 'qualified', 'name' => 'active'],
];
Defensive patterns

Strategy: validation

Validate before calling

if (($expr['type'] ?? null) === 'unary-op'
    && !isset($expr['left'])
    && !isset($expr['right'])
) {
    throw new InvalidArgumentException('Unary expression requires a left or right operand');
}

Type guard

function isCompleteUnaryExpression(array $expr): bool
{
    return ($expr['type'] ?? null) !== 'unary-op'
        || isset($expr['left'])
        || isset($expr['right']);
}

Try / catch

try {
    $sql = $dialect->select($definition);
} catch (\Phalcon\Db\Exceptions\InvalidUnaryExpression $e) {
    throw new InvalidArgumentException('Unary operator without operand', 0, $e);
}

Prevention

When it happens

Trigger: Passing ['type' => 'unary-op', 'op' => 'NOT'] with no operand; operand stored under a wrong key ('operand', 'value'); nested expression construction that attaches the operand to a copy of the array that is later discarded.

Common situations: Building IS NULL / NOT conditions programmatically where the operand branch is skipped for edge cases; copy-pasted expression templates missing the operand key.

Related errors


AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21). Data as JSON: /api/errors/d055b7d215dc6cbd. Report an issue: GitHub.