88use Utopia \Database \Document ;
99use Utopia \Database \Exception as DatabaseException ;
1010use Utopia \Database \Exception \Duplicate as DuplicateException ;
11+ use Utopia \Database \Exception \Limit as LimitException ;
1112use Utopia \Database \Exception \NotFound as NotFoundException ;
1213use Utopia \Database \Exception \Operator as OperatorException ;
1314use Utopia \Database \Operator ;
@@ -3467,12 +3468,13 @@ protected function applyOperator(mixed $current, Operator $operator): mixed
34673468 $ max = $ values [1 ] ?? null ;
34683469 $ base = \is_numeric ($ current ) ? $ current + 0 : 0 ;
34693470 if ($ max !== null ) {
3470- // Compare *remaining headroom* against $by so we never
3471- // overflow PHP's int range (which would silently demote
3472- // the result to float and corrupt downstream Range
3473- // validators).
3474- if ($ base >= $ max || ($ max - $ base ) <= $ by ) {
3475- return $ this ->preserveNumericType ($ base , $ max );
3471+ // Compare *remaining headroom* against $by so we never overflow PHP's int
3472+ // range. Guard: if the RESULT would exceed the max, leave it unchanged.
3473+ // Note: we must NOT short-circuit on `$base >= $max` — a negative $by moves
3474+ // the value down, so an already-over-max base can still land within bound
3475+ // (e.g. 52 + (-5) = 47 <= 50 must apply).
3476+ if (($ max - $ base ) < $ by ) {
3477+ return $ this ->preserveNumericType ($ base , $ base );
34763478 }
34773479 }
34783480
@@ -3483,8 +3485,10 @@ protected function applyOperator(mixed $current, Operator $operator): mixed
34833485 $ min = $ values [1 ] ?? null ;
34843486 $ base = \is_numeric ($ current ) ? $ current + 0 : 0 ;
34853487 if ($ min !== null ) {
3486- if ($ base <= $ min || ($ base - $ min ) <= $ by ) {
3487- return $ this ->preserveNumericType ($ base , $ min );
3488+ // Guard: leave unchanged only if the RESULT would go below min. Don't
3489+ // short-circuit on `$base <= $min` — a negative $by moves the value up.
3490+ if (($ base - $ min ) < $ by ) {
3491+ return $ this ->preserveNumericType ($ base , $ base );
34883492 }
34893493 }
34903494
@@ -3494,8 +3498,12 @@ protected function applyOperator(mixed $current, Operator $operator): mixed
34943498 $ by = $ values [0 ] ?? 1 ;
34953499 $ max = $ values [1 ] ?? null ;
34963500 $ base = \is_numeric ($ current ) ? $ current + 0 : 0 ;
3501+ $ result = $ base * $ by ;
3502+ if ($ max !== null && $ result > $ max ) {
3503+ return $ this ->preserveNumericType ($ base , $ base );
3504+ }
34973505
3498- return $ this ->applyNumericLimit ($ base * $ by , $ max , true );
3506+ return $ this ->preserveNumericType ($ base , $ result );
34993507
35003508 case Operator::TYPE_DIVIDE :
35013509 $ by = $ values [0 ] ?? 1 ;
@@ -3504,8 +3512,12 @@ protected function applyOperator(mixed $current, Operator $operator): mixed
35043512 return $ current ;
35053513 }
35063514 $ base = \is_numeric ($ current ) ? $ current + 0 : 0 ;
3515+ $ result = $ base / $ by ;
3516+ if ($ min !== null && $ result < $ min ) {
3517+ return $ this ->preserveNumericType ($ base , $ base );
3518+ }
35073519
3508- return $ this ->applyNumericLimit ($ base / $ by , $ min , false );
3520+ return $ this ->preserveNumericType ($ base , $ result );
35093521
35103522 case Operator::TYPE_MODULO :
35113523 $ by = $ values [0 ] ?? 1 ;
@@ -3520,8 +3532,29 @@ protected function applyOperator(mixed $current, Operator $operator): mixed
35203532 $ by = $ values [0 ] ?? 1 ;
35213533 $ max = $ values [1 ] ?? null ;
35223534 $ base = \is_numeric ($ current ) ? $ current + 0 : 0 ;
3535+ if ($ max !== null ) {
3536+ // Leave the value unchanged for undefined inputs (0 to a negative power, or a
3537+ // negative base to a fractional exponent) — they produce INF/NaN, not a number.
3538+ if (($ base == 0 && $ by < 0 ) || ($ base < 0 && \floor ($ by ) != $ by )) {
3539+ return $ this ->preserveNumericType ($ base , $ base );
3540+ }
3541+ $ result = $ base ** $ by ;
3542+ // A result that overflows (INF) or exceeds the max also leaves the value as-is.
3543+ if (!\is_finite ($ result ) || $ result > $ max ) {
3544+ return $ this ->preserveNumericType ($ base , $ base );
3545+ }
3546+
3547+ return $ this ->preserveNumericType ($ base , $ result );
3548+ }
3549+
3550+ // 0 to a negative power, or a negative base to a fractional exponent, is not a real
3551+ // number. Fail loudly with a clear exception rather than storing INF/NaN.
3552+ $ result = $ base ** $ by ;
3553+ if (!\is_finite ($ result )) {
3554+ throw new LimitException ('Value out of range ' );
3555+ }
35233556
3524- return $ this ->applyNumericLimit ($ base ** $ by , $ max , true );
3557+ return $ this ->preserveNumericType ($ base , $ result );
35253558
35263559 case Operator::TYPE_STRING_CONCAT :
35273560 return ((string ) ($ current ?? '' )).(string ) ($ values [0 ] ?? '' );
0 commit comments