Typed atomic expressions¶
Version availability
These APIs are implemented on the Core development branch for the next 3.2 release; 3.1.0 does not include them.
Expr constructs a closed, typed SQL expression tree. The SQL adapters validate field names against ORM mappings, bind every scalar value, and emit MySQL- or PostgreSQL-specific expressions.
Atomic counters and compare-and-swap¶
use Small\SwooleEntityManager\QueryBuilder\RelationalQueryBuilder\Expression\Expr;
use Small\SwooleEntityManager\QueryBuilder\RelationalQueryBuilder\Enum\ConditionOperatorType;
$update = $jobManager->createUpdateBuilder()
->setExpression(
'revision',
Expr::add(Expr::field('revision'), Expr::value(1)),
)
->setExpression('leaseUntil', Expr::addSeconds(Expr::utcNow(), Expr::value(30)));
$update->where()
->firstCondition(
$update->getFieldForCondition('id'),
ConditionOperatorType::equal,
':id',
)
->andCondition(
$update->getFieldForCondition('revision'),
ConditionOperatorType::equal,
':expectedRevision',
);
$update->setParameter('id', $jobUuid);
$update->setParameter('expectedRevision', $expectedRevision);
$affected = $jobManager->executeUpdateAffectedRows($update, $pdo);
if ($affected !== 1) {
throw new LogicException('Optimistic concurrency conflict.');
}
This executes as one conditional database statement. It does not read the current revision into PHP and then write back a stale value. The native $pdo must be the same connection used by other operations in the workflow transaction.
Expression catalog¶
| Expression | Purpose |
|---|---|
Expr::field('revision') |
Validate and reference a mapped field |
Expr::value(5) |
Bind a scalar value |
Expr::add(...) / subtract(...) |
Arithmetic without a read/modify/write race |
Expr::multiply(...) / divide(...) |
Arithmetic expressions |
Expr::utcNow() |
UTC time using the database clock |
Expr::addSeconds(...) / subtractSeconds(...) |
Generate lease deadlines |
Expr::coalesce(...) / greatest(...) |
Null fallback and maximum |
Expr::equal(...) / greater(...) / less(...) |
Typed conditions |
Expr::and(...) / or(...) / isNull(...) |
Boolean combinations |
Expr::caseWhen(...) |
Conditional values |
Expr::countIf(...) / sumIf(...) |
Expressions for conditional aggregates |
The query compiler uses dialect-specific SQL for the time functions. Never pass SQL fragments to Expr::value(); they are treated as data, not executable SQL. A field outside the mapped entity causes an exception.
Conditional metrics without PHP iteration¶
$stats = $jobManager->createStatsQueryBuilder('job')
->countIf(
Expr::equal(Expr::field('status'), Expr::value('pending')),
'ready',
)
->sumIf(
Expr::greater(Expr::field('attempts'), Expr::value(0)),
Expr::field('attempts'),
'totalAttempts',
);
$raw = $jobManager->getRawResult($stats, $pdo);
The SQL is generated using conditional SUM(CASE WHEN ... THEN ... ELSE ... END) constructs, with COALESCE returning zero when there is no match. This can replace multi-pass PHP counting for recovery dashboards.
Transactional and concurrency constraints¶
- To update only one revision, always include an expected revision in
WHEREand checkexecuteUpdateAffectedRows(). - For a multi-statement workflow, keep the same native connection through the entire transaction.
- Use
forUpdate(skipLocked: true)when claiming concurrent work. This is an existing 3.1 feature. - After a communication failure, do not silently retry a statement whose commit outcome is ambiguous.
setExpression()is for structured expressions, not arbitrary SQL text. PreviousaddBoundFieldToUpdate()support is unchanged.
Related: atomic upserts, update builder, statistics, transactions.