@@ -1275,3 +1275,111 @@ async def test_batch_update_exceeds_parameters():
1275
1275
assert "SET" in first_call .args [0 ]
1276
1276
assert "WHERE" in first_call .args [0 ]
1277
1277
assert '"id"' in first_call .args [0 ]
1278
+
1279
+
1280
+ @pytest .mark .asyncio
1281
+ async def test_batch_upsert_exceeds_parameters ():
1282
+ """
1283
+ Test that upsert() correctly batches operations when we exceed Postgres parameter limits.
1284
+ We'll create enough objects with enough fields that a single query would exceed PG_MAX_PARAMETERS.
1285
+ """
1286
+ assert assert_expected_user_fields (UserDemo )
1287
+
1288
+ # Calculate how many objects we need to exceed the parameter limit
1289
+ # Each object has 2 fields (name, email) in UserDemo
1290
+ # So each object uses 2 parameters
1291
+ objects_needed = (PG_MAX_PARAMETERS // 2 ) + 1
1292
+ users = [
1293
+ UserDemo (name = f"User { i } " , email = f"user{ i } @example.com" )
1294
+ for i in range (objects_needed )
1295
+ ]
1296
+
1297
+ # Mock the connection with dynamic results based on input
1298
+ mock_conn = AsyncMock ()
1299
+ mock_conn .fetchmany = AsyncMock (
1300
+ side_effect = lambda query , values_list : [
1301
+ {"id" : i , "name" : f"User { i } " , "email" : f"user{ i } @example.com" }
1302
+ for i in range (len (values_list ))
1303
+ ]
1304
+ )
1305
+ mock_conn .executemany = AsyncMock ()
1306
+ mock_conn .transaction = mock_transaction
1307
+
1308
+ db = DBConnection (mock_conn )
1309
+
1310
+ # Upsert the objects with all possible kwargs
1311
+ result = await db .upsert (
1312
+ users ,
1313
+ conflict_fields = (UserDemo .email ,),
1314
+ update_fields = (UserDemo .name ,),
1315
+ returning_fields = (UserDemo .id , UserDemo .name , UserDemo .email ),
1316
+ )
1317
+
1318
+ # We should have made at least 2 calls to fetchmany since we exceeded the parameter limit
1319
+ assert len (mock_conn .fetchmany .mock_calls ) >= 2
1320
+
1321
+ # Verify the structure of the first call
1322
+ first_call = mock_conn .fetchmany .mock_calls [0 ]
1323
+ assert "INSERT INTO" in first_call .args [0 ]
1324
+ assert "ON CONFLICT" in first_call .args [0 ]
1325
+ assert "DO UPDATE SET" in first_call .args [0 ]
1326
+ assert "RETURNING" in first_call .args [0 ]
1327
+
1328
+ # Verify we got back the expected number of results
1329
+ assert result is not None
1330
+ assert len (result ) == objects_needed
1331
+ assert all (len (r ) == 3 for r in result ) # Each result should have id, name, email
1332
+
1333
+
1334
+ @pytest .mark .asyncio
1335
+ async def test_batch_upsert_multiple_with_real_db (db_connection : DBConnection ):
1336
+ """
1337
+ Integration test for upserting multiple objects at once with a real database connection.
1338
+ Tests both insert and update scenarios in the same batch.
1339
+ """
1340
+ await db_connection .conn .execute (
1341
+ """
1342
+ ALTER TABLE userdemo
1343
+ ADD CONSTRAINT email_unique UNIQUE (email)
1344
+ """
1345
+ )
1346
+
1347
+ # Create initial set of users
1348
+ initial_users = [
1349
+ UserDemo (
name = "User 1" ,
email = "[email protected] " ),
1350
+ UserDemo (
name = "User 2" ,
email = "[email protected] " ),
1351
+ ]
1352
+ await db_connection .insert (initial_users )
1353
+
1354
+ # Create a mix of new and existing users for upsert
1355
+ users_to_upsert = [
1356
+ # These should update
1357
+ UserDemo (
name = "Updated User 1" ,
email = "[email protected] " ),
1358
+ UserDemo (
name = "Updated User 2" ,
email = "[email protected] " ),
1359
+ # These should insert
1360
+ UserDemo (
name = "User 3" ,
email = "[email protected] " ),
1361
+ UserDemo (
name = "User 4" ,
email = "[email protected] " ),
1362
+ ]
1363
+
1364
+ result = await db_connection .upsert (
1365
+ users_to_upsert ,
1366
+ conflict_fields = (UserDemo .email ,),
1367
+ update_fields = (UserDemo .name ,),
1368
+ returning_fields = (UserDemo .name , UserDemo .email ),
1369
+ )
1370
+
1371
+ # Verify we got all results back
1372
+ assert result is not None
1373
+ assert len (result ) == 4
1374
+
1375
+ # Verify the database state
1376
+ db_result = await db_connection .conn .fetch ("SELECT * FROM userdemo ORDER BY email" )
1377
+ assert len (db_result ) == 4
1378
+
1379
+ # Check that updates worked
1380
+ assert db_result [0 ]["name" ] == "Updated User 1"
1381
+ assert db_result [1 ]["name" ] == "Updated User 2"
1382
+
1383
+ # Check that inserts worked
1384
+ assert db_result [2 ]["name" ] == "User 3"
1385
+ assert db_result [3 ]["name" ] == "User 4"
0 commit comments