@@ -45,15 +45,16 @@ public function testRunNoRowsIsNoop(): void {
4545 $ this ->backfill ->run ($ this ->output );
4646 }
4747
48- public function testRunUpdatesTwoRows (): void {
48+ public function testRunGroupsRowsWithSameTimestampIntoOneUpdate (): void {
49+ // Two ACLs from the same board share the same timestamp → only 1 UPDATE
4950 $ rows = [
5051 ['acl_id ' => 1 , 'board_last_modified ' => 1000000 ],
51- ['acl_id ' => 2 , 'board_last_modified ' => 2000000 ],
52+ ['acl_id ' => 2 , 'board_last_modified ' => 1000000 ],
5253 ];
5354 [$ selectQb ] = $ this ->buildSelectQb ($ rows );
54- $ updateQb = $ this ->buildUpdateQb (2 );
55+ $ updateQb = $ this ->buildUpdateQb (1 ); // single IN-clause UPDATE for both IDs
5556
56- $ this ->db ->expects ($ this ->exactly (2 ))
57+ $ this ->db ->expects ($ this ->exactly (2 )) // 1 SELECT + 1 UPDATE
5758 ->method ('getQueryBuilder ' )
5859 ->willReturnOnConsecutiveCalls ($ selectQb , $ updateQb );
5960
@@ -64,13 +65,34 @@ public function testRunUpdatesTwoRows(): void {
6465 $ this ->backfill ->run ($ this ->output );
6566 }
6667
68+ public function testRunIssuesSeparateUpdatePerDistinctTimestamp (): void {
69+ // Two ACLs from different boards → two distinct timestamps → 2 UPDATEs
70+ $ rows = [
71+ ['acl_id ' => 1 , 'board_last_modified ' => 1000000 ],
72+ ['acl_id ' => 2 , 'board_last_modified ' => 2000000 ],
73+ ];
74+ [$ selectQb ] = $ this ->buildSelectQb ($ rows );
75+ $ updateQb1 = $ this ->buildUpdateQb (1 );
76+ $ updateQb2 = $ this ->buildUpdateQb (1 );
77+
78+ $ this ->db ->expects ($ this ->exactly (3 )) // 1 SELECT + 2 UPDATEs
79+ ->method ('getQueryBuilder ' )
80+ ->willReturnOnConsecutiveCalls ($ selectQb , $ updateQb1 , $ updateQb2 );
81+
82+ $ this ->output ->expects ($ this ->once ())
83+ ->method ('info ' )
84+ ->with ($ this ->stringContains ('2 ' ));
85+
86+ $ this ->backfill ->run ($ this ->output );
87+ }
88+
6789 public function testRunUsesBoardTimestampWhenAvailable (): void {
6890 $ rows = [['acl_id ' => 1 , 'board_last_modified ' => 1234567 ]];
6991 [$ selectQb ] = $ this ->buildSelectQb ($ rows );
7092
7193 $ capturedTs = null ;
72- $ updateQb = $ this ->buildUpdateQb (1 , function (string $ name , mixed $ value ) use (&$ capturedTs ): void {
73- if ($ name === ' ts ' ) {
94+ $ updateQb = $ this ->buildUpdateQb (1 , function (mixed $ value , int $ type ) use (&$ capturedTs ): void {
95+ if ($ type === IQueryBuilder:: PARAM_INT ) {
7496 $ capturedTs = $ value ;
7597 }
7698 });
@@ -91,8 +113,8 @@ public function testRunUsesCurrentTimeWhenBoardTimestampIsZero(): void {
91113
92114 $ capturedTs = null ;
93115 $ before = time ();
94- $ updateQb = $ this ->buildUpdateQb (1 , function (string $ name , mixed $ value ) use (&$ capturedTs ): void {
95- if ($ name === ' ts ' ) {
116+ $ updateQb = $ this ->buildUpdateQb (1 , function (mixed $ value , int $ type ) use (&$ capturedTs ): void {
117+ if ($ type === IQueryBuilder:: PARAM_INT ) {
96118 $ capturedTs = $ value ;
97119 }
98120 });
@@ -132,25 +154,26 @@ private function buildSelectQb(array $rows): array {
132154 return [$ qb ];
133155 }
134156
135- private function buildUpdateQb (int $ expectedExecutions , ?\Closure $ onSetParameter = null ): IQueryBuilder &MockObject {
157+ private function buildUpdateQb (int $ expectedExecutions , ?\Closure $ onCreateNamedParameter = null ): IQueryBuilder &MockObject {
136158 $ expr = $ this ->createMock (IExpressionBuilder::class);
137- $ expr ->method ('eq ' )->willReturn ('1=1 ' );
159+ $ expr ->method ('in ' )->willReturn ('1=1 ' );
138160
139161 $ qb = $ this ->createMock (IQueryBuilder::class);
140162 $ qb ->method ('update ' )->willReturnSelf ();
141163 $ qb ->method ('set ' )->willReturnSelf ();
142164 $ qb ->method ('where ' )->willReturnSelf ();
143- $ qb ->method ('createParameter ' )->willReturn ('? ' );
144165 $ qb ->method ('expr ' )->willReturn ($ expr );
145166 $ qb ->expects ($ this ->exactly ($ expectedExecutions ))->method ('executeStatement ' );
146167
147- if ($ onSetParameter !== null ) {
148- $ qb ->method ('setParameter ' )->willReturnCallback (function (string $ name , mixed $ value ) use ($ onSetParameter , $ qb ) {
149- $ onSetParameter ($ name , $ value );
150- return $ qb ;
151- });
168+ if ($ onCreateNamedParameter !== null ) {
169+ $ qb ->method ('createNamedParameter ' )->willReturnCallback (
170+ function (mixed $ value , int $ type ) use ($ onCreateNamedParameter ): string {
171+ $ onCreateNamedParameter ($ value , $ type );
172+ return '? ' ;
173+ }
174+ );
152175 } else {
153- $ qb ->method ('setParameter ' )->willReturnSelf ( );
176+ $ qb ->method ('createNamedParameter ' )->willReturn ( ' ? ' );
154177 }
155178
156179 return $ qb ;
0 commit comments