@@ -742,26 +742,32 @@ describe('table row writes against real PostgreSQL', () => {
742742 )
743743
744744 /**
745- * Index and sequential scans this transaction ran on `user_table_rows`, by relation, read from
746- * the transaction's own statistics so concurrent suites cannot skew them.
745+ * Index and sequential scans this backend has counted on `user_table_rows`, by relation. The
746+ * counters are the backend's unflushed statistics, which can still hold scans from earlier
747+ * transactions on the same pooled connection, so callers diff two readings.
747748 */
748- async function rowScans ( trx : DbTransaction ) : Promise < Record < string , number > > {
749+ async function rowScans ( trx : DbTransaction ) : Promise < Map < string , number > > {
749750 const scans = await trx . execute < { relname : string ; scans : number } > ( sql `
750751 SELECT c.relname, pg_stat_get_xact_numscans(c.oid)::int AS scans
751752 FROM pg_class c
752753 WHERE c.oid = 'user_table_rows'::regclass
753754 OR c.oid IN (SELECT indexrelid FROM pg_index WHERE indrelid = 'user_table_rows'::regclass)` )
754- return Object . fromEntries (
755- scans . filter ( ( row ) => row . scans > 0 ) . map ( ( row ) => [ row . relname , row . scans ] )
756- )
755+ return new Map ( scans . map ( ( row ) => [ row . relname , row . scans ] ) )
757756 }
758757
758+ /** Runs the check in a transaction and returns its result with the scans the check alone ran. */
759759 const checkWithScans = ( table : TableDefinition , rows : RowData [ ] ) =>
760760 db . transaction ( async ( trx ) => {
761761 // A parallel worker's scans are not counted in the leader's transaction statistics.
762762 await trx . execute ( sql `SET LOCAL max_parallel_workers_per_gather = 0` )
763+ const before = await rowScans ( trx )
763764 const result = await checkBatchUniqueConstraintsDb ( table . id , rows , table . schema , trx )
764- return { result, scans : await rowScans ( trx ) }
765+ const scans : Record < string , number > = { }
766+ for ( const [ relname , count ] of await rowScans ( trx ) ) {
767+ const ran = count - ( before . get ( relname ) ?? 0 )
768+ if ( ran > 0 ) scans [ relname ] = ran
769+ }
770+ return { result, scans }
765771 } )
766772
767773 it ( 'reports stored and in-batch conflicts across every probe group' , async ( ) => {
@@ -862,6 +868,9 @@ describe('table row writes against real PostgreSQL', () => {
862868 SELECT ${ table . id } ::text || '-' || i, ${ table . id } , ${ workspaceId } ,
863869 jsonb_build_object('email', 'stored-' || i || '@example.test'), i, 'a' || i
864870 FROM generate_series(0, 39999) AS i`
871+ // A bulk insert leaves its entries in the GIN pending list, which a manual ANALYZE does not
872+ // merge and which the planner charges to every probe, so flush it for a deterministic plan.
873+ await control `SELECT gin_clean_pending_list('user_table_rows_tenant_data_gin_idx'::regclass)`
865874 await control `ANALYZE user_table_rows`
866875 const rows : RowData [ ] = Array . from ( { length : 260 } , ( _ , i ) => ( {
867876 email : i % 10 === 0 ? `stored-${ i * 100 } @example.test` : `new-${ i } @example.test` ,
0 commit comments