77use Utopia \Database \Adapter ;
88use Utopia \Database \Change ;
99use Utopia \Database \Database ;
10+ use Utopia \Database \DateTime ;
1011use Utopia \Database \Document ;
1112use Utopia \Database \Exception as DatabaseException ;
1213use Utopia \Database \Exception \Duplicate as DuplicateException ;
@@ -2020,7 +2021,6 @@ protected function bindOperatorParams(\PDOStatement $stmt, Operator $operator, i
20202021 $ condition = $ values [0 ] ?? 'equal ' ;
20212022 $ value = $ values [1 ] ?? null ;
20222023
2023- // SECURITY: Whitelist validation to prevent SQL injection in CASE statements
20242024 $ validConditions = [
20252025 'equal ' , 'notEqual ' , // Comparison
20262026 'greaterThan ' , 'greaterThanEqual ' , 'lessThan ' , 'lessThanEqual ' , // Numeric
@@ -2070,11 +2070,11 @@ protected function applyOperatorToValue(Operator $operator, mixed $value): mixed
20702070
20712071 case Operator::TYPE_DIVIDE :
20722072 $ divisor = $ values [0 ] ?? 1 ;
2073- return $ divisor != 0 ? ($ value ?? 0 ) / $ divisor : ($ value ?? 0 );
2073+ return ( float ) $ divisor !== 0. 0 ? ($ value ?? 0 ) / $ divisor : ($ value ?? 0 );
20742074
20752075 case Operator::TYPE_MODULO :
20762076 $ divisor = $ values [0 ] ?? 1 ;
2077- return $ divisor != 0 ? ($ value ?? 0 ) % $ divisor : ($ value ?? 0 );
2077+ return ( float ) $ divisor !== 0. 0 ? ($ value ?? 0 ) % $ divisor : ($ value ?? 0 );
20782078
20792079 case Operator::TYPE_POWER :
20802080 return pow ($ value ?? 0 , $ values [0 ] ?? 1 );
@@ -2133,7 +2133,7 @@ protected function applyOperatorToValue(Operator $operator, mixed $value): mixed
21332133 return $ value ;
21342134
21352135 case Operator::TYPE_DATE_SET_NOW :
2136- return \ Utopia \ Database \ DateTime::now ();
2136+ return DateTime::now ();
21372137
21382138 default :
21392139 return $ value ;
@@ -2560,7 +2560,6 @@ public function upsertDocuments(
25602560 try {
25612561 $ spatialAttributes = $ this ->getSpatialAttributes ($ collection );
25622562
2563- // Build attribute defaults map before we lose the collection document
25642563 $ attributeDefaults = [];
25652564 foreach ($ collection ->getAttribute ('attributes ' , []) as $ attr ) {
25662565 $ attributeDefaults [$ attr ['$id ' ]] = $ attr ['default ' ] ?? null ;
@@ -2569,7 +2568,6 @@ public function upsertDocuments(
25692568 $ collection = $ collection ->getId ();
25702569 $ name = $ this ->filter ($ collection );
25712570
2572- // Fast path: Check if ANY document has operators
25732571 $ hasOperators = false ;
25742572 $ firstChange = $ changes [0 ];
25752573 $ firstDoc = $ firstChange ->getNew ();
@@ -2578,7 +2576,6 @@ public function upsertDocuments(
25782576 if (!empty ($ firstExtracted ['operators ' ])) {
25792577 $ hasOperators = true ;
25802578 } else {
2581- // Check remaining documents
25822579 foreach ($ changes as $ change ) {
25832580 $ doc = $ change ->getNew ();
25842581 $ extracted = Operator::extractOperators ($ doc ->getAttributes ());
@@ -2589,9 +2586,7 @@ public function upsertDocuments(
25892586 }
25902587 }
25912588
2592- // Fast path for non-operator upserts - ZERO overhead
25932589 if (!$ hasOperators ) {
2594- // Traditional bulk upsert - original implementation
25952590 $ bindIndex = 0 ;
25962591 $ batchKeys = [];
25972592 $ bindValues = [];
@@ -2603,8 +2598,8 @@ public function upsertDocuments(
26032598 $ currentRegularAttributes = $ document ->getAttributes ();
26042599
26052600 $ currentRegularAttributes ['_uid ' ] = $ document ->getId ();
2606- $ currentRegularAttributes ['_createdAt ' ] = $ document ->getCreatedAt () ? \ Utopia \ Database \ DateTime::setTimezone ($ document ->getCreatedAt ()) : null ;
2607- $ currentRegularAttributes ['_updatedAt ' ] = $ document ->getUpdatedAt () ? \ Utopia \ Database \ DateTime::setTimezone ($ document ->getUpdatedAt ()) : null ;
2601+ $ currentRegularAttributes ['_createdAt ' ] = $ document ->getCreatedAt () ? DateTime::setTimezone ($ document ->getCreatedAt ()) : null ;
2602+ $ currentRegularAttributes ['_updatedAt ' ] = $ document ->getUpdatedAt () ? DateTime::setTimezone ($ document ->getUpdatedAt ()) : null ;
26082603 $ currentRegularAttributes ['_permissions ' ] = \json_encode ($ document ->getPermissions ());
26092604
26102605 if (!empty ($ document ->getSequence ())) {
@@ -2671,15 +2666,13 @@ public function upsertDocuments(
26712666 $ stmt ->execute ();
26722667 $ stmt ->closeCursor ();
26732668 } else {
2674- // Operator path: Group by operator signature and process in batches
26752669 $ groups = [];
26762670
26772671 foreach ($ changes as $ change ) {
26782672 $ document = $ change ->getNew ();
26792673 $ extracted = Operator::extractOperators ($ document ->getAttributes ());
26802674 $ operators = $ extracted ['operators ' ];
26812675
2682- // Create signature for grouping
26832676 if (empty ($ operators )) {
26842677 $ signature = 'no_ops ' ;
26852678 } else {
@@ -2701,7 +2694,6 @@ public function upsertDocuments(
27012694 $ groups [$ signature ]['documents ' ][] = $ change ;
27022695 }
27032696
2704- // Process each group
27052697 foreach ($ groups as $ group ) {
27062698 $ groupChanges = $ group ['documents ' ];
27072699 $ operators = $ group ['operators ' ];
@@ -2721,7 +2713,6 @@ public function upsertDocuments(
27212713 $ extractedOperators = $ extracted ['operators ' ];
27222714
27232715 // For new documents, apply operators to attribute defaults
2724- // This ensures operators work even when no regular attributes are present
27252716 if ($ change ->getOld ()->isEmpty () && !empty ($ extractedOperators )) {
27262717 foreach ($ extractedOperators as $ operatorKey => $ operator ) {
27272718 $ default = $ attributeDefaults [$ operatorKey ] ?? null ;
@@ -2730,8 +2721,8 @@ public function upsertDocuments(
27302721 }
27312722
27322723 $ currentRegularAttributes ['_uid ' ] = $ document ->getId ();
2733- $ currentRegularAttributes ['_createdAt ' ] = $ document ->getCreatedAt () ? \ Utopia \ Database \DateTime:: setTimezone ( $ document ->getCreatedAt () ) : null ;
2734- $ currentRegularAttributes ['_updatedAt ' ] = $ document ->getUpdatedAt () ? \ Utopia \ Database \DateTime:: setTimezone ( $ document ->getUpdatedAt () ) : null ;
2724+ $ currentRegularAttributes ['_createdAt ' ] = $ document ->getCreatedAt () ? $ document ->getCreatedAt () : null ;
2725+ $ currentRegularAttributes ['_updatedAt ' ] = $ document ->getUpdatedAt () ? $ document ->getUpdatedAt () : null ;
27352726 $ currentRegularAttributes ['_permissions ' ] = \json_encode ($ document ->getPermissions ());
27362727
27372728 if (!empty ($ document ->getSequence ())) {
@@ -2749,7 +2740,6 @@ public function upsertDocuments(
27492740 $ documentsData [] = ['regularAttributes ' => $ currentRegularAttributes ];
27502741 }
27512742
2752- // Add operator columns
27532743 foreach (\array_keys ($ operators ) as $ colName ) {
27542744 $ allColumnNames [$ colName ] = true ;
27552745 }
@@ -2799,13 +2789,21 @@ public function upsertDocuments(
27992789 $ regularAttributes [$ key ] = $ value ;
28002790 }
28012791
2802- $ stmt = $ this ->getUpsertStatement ($ name , $ columns , $ batchKeys , $ regularAttributes , $ bindValues , '' , $ operators );
2792+ $ stmt = $ this ->getUpsertStatement (
2793+ $ name ,
2794+ $ columns ,
2795+ $ batchKeys ,
2796+ $ regularAttributes ,
2797+ $ bindValues ,
2798+ '' ,
2799+ $ operators
2800+ );
2801+
28032802 $ stmt ->execute ();
28042803 $ stmt ->closeCursor ();
28052804 }
28062805 }
28072806
2808- // Handle permissions (unchanged)
28092807 $ removeQueries = [];
28102808 $ removeBindValues = [];
28112809 $ addQueries = [];
@@ -2889,7 +2887,6 @@ public function upsertDocuments(
28892887 return \array_map (fn ($ change ) => $ change ->getNew (), $ changes );
28902888 }
28912889
2892-
28932890 /**
28942891 * Build geometry WKT string from array input for spatial queries
28952892 *
0 commit comments