phalcon/cphalcon · error · ReturningNotSupported

RETURNING clauses are not supported by this dialect

Error message

RETURNING clauses are not supported by this dialect

What it means

Dialect::returning() appends a `RETURNING` clause so INSERT/UPDATE/DELETE statements give back rows. It is a PostgreSQL and SQLite 3.35+ feature; the base dialect implements it as an unconditional throw, and the MySQL dialect inherits that because MySQL has no RETURNING construct. Use supportsReturning() (false on the base/MySQL dialects) to probe.

Source

Thrown at phalcon/Db/Dialect.zep:576

            let assignments[] = this->escape((string) col)
                . " = excluded." . this->escape((string) col);
        }

        return sqlQuery
            . " ON CONFLICT (" . this->getColumnList(conflictColumns) . ")"
            . " DO UPDATE SET " . implode(", ", assignments);
    }

    /**
     * Returns a SQL statement extended with a `RETURNING` clause so the
     * INSERT/UPDATE/DELETE returns rows. Supported by PostgreSQL and
     * SQLite 3.35+. Pass `["*"]` for `RETURNING *`, or a list of column
     * names. The base implementation throws - MySQL inherits it because
     * MySQL has no RETURNING construct.
     */
    public function returning( string sqlQuery,  array columns) -> string
    {
        throw new ReturningNotSupported();
    }

    /**
     * Generate SQL to release a savepoint
     */
    public function releaseSavepoint( string name) -> string
    {
        return "RELEASE SAVEPOINT " . name;
    }

    /**
     * Generate SQL to rollback a savepoint
     */
    public function rollbackSavepoint( string name) -> string
    {
        return "ROLLBACK TO SAVEPOINT " . name;
    }

View on GitHub (pinned to b7419de9cd)

Solutions

  1. Guard with if ($connection->getDialect()->supportsReturning()) and branch per adapter.
  2. On MySQL use $connection->lastInsertId() after plain INSERT instead of RETURNING.
  3. When portability matters, keep the RETURNING path but provide a MySQL fallback that re-selects by lastInsertId or by a client-generated unique key.

Example fix

// before
$sql = $connection->getDialect()->returning(
    "INSERT INTO users (email) VALUES ('a@b.c')",
    ['id']
); // throws ReturningNotSupported on MySQL

// after
if ($connection->getDialect()->supportsReturning()) {
    $sql = $connection->getDialect()->returning(
        "INSERT INTO users (email) VALUES ('a@b.c')",
        ['id']
    );
} else {
    $connection->execute("INSERT INTO users (email) VALUES ('a@b.c')");
    $id = $connection->lastInsertId();
}
Defensive patterns

Strategy: validation

Validate before calling

if ($connection->getDialect()->supportsReturning()) {
    $sql = $connection->getDialect()->returning($insert, ['id']);
} else {
    $connection->execute($insert);
    $id = $connection->lastInsertId();
}

Try / catch

use Phalcon\Db\Exceptions\ReturningNotSupported;

try {
    $sql = $connection->getDialect()->returning($insert, ['id']);
} catch (ReturningNotSupported $e) {
    // MySQL path: plain insert + lastInsertId
    $connection->execute($insert);
    $id = $connection->lastInsertId();
}

Prevention

When it happens

Trigger: Calling $dialect->returning($sql, ['id']) or an adapter helper built on it while connected through Pdo\Mysql; portable repository code that relies on RETURNING for last-insert-id and runs against a MySQL deployment.

Common situations: Code written and tested on PostgreSQL/SQLite then deployed on MySQL; multi-driver packages assuming RETURNING everywhere; migrating from Postgres to MySQL without auditing RETURNING usage.

Related errors


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