@@ -1268,6 +1268,7 @@ async def simple_upsert_many(
1268
1268
value_names : Collection [str ],
1269
1269
value_values : Collection [Collection [Any ]],
1270
1270
desc : str ,
1271
+ lock : bool = True ,
1271
1272
) -> None :
1272
1273
"""
1273
1274
Upsert, many times.
@@ -1279,21 +1280,24 @@ async def simple_upsert_many(
1279
1280
value_names: The value column names
1280
1281
value_values: A list of each row's value column values.
1281
1282
Ignored if value_names is empty.
1283
+ lock: True to lock the table when doing the upsert. Unused if the database engine
1284
+ supports native upserts.
1282
1285
"""
1283
1286
1284
1287
# We can autocommit if we are going to use native upserts
1285
1288
autocommit = (
1286
1289
self .engine .can_native_upsert and table not in self ._unsafe_to_upsert_tables
1287
1290
)
1288
1291
1289
- return await self .runInteraction (
1292
+ await self .runInteraction (
1290
1293
desc ,
1291
1294
self .simple_upsert_many_txn ,
1292
1295
table ,
1293
1296
key_names ,
1294
1297
key_values ,
1295
1298
value_names ,
1296
1299
value_values ,
1300
+ lock = lock ,
1297
1301
db_autocommit = autocommit ,
1298
1302
)
1299
1303
@@ -1305,6 +1309,7 @@ def simple_upsert_many_txn(
1305
1309
key_values : Collection [Iterable [Any ]],
1306
1310
value_names : Collection [str ],
1307
1311
value_values : Iterable [Iterable [Any ]],
1312
+ lock : bool = True ,
1308
1313
) -> None :
1309
1314
"""
1310
1315
Upsert, many times.
@@ -1316,14 +1321,16 @@ def simple_upsert_many_txn(
1316
1321
value_names: The value column names
1317
1322
value_values: A list of each row's value column values.
1318
1323
Ignored if value_names is empty.
1324
+ lock: True to lock the table when doing the upsert. Unused if the database engine
1325
+ supports native upserts.
1319
1326
"""
1320
1327
if self .engine .can_native_upsert and table not in self ._unsafe_to_upsert_tables :
1321
1328
return self .simple_upsert_many_txn_native_upsert (
1322
1329
txn , table , key_names , key_values , value_names , value_values
1323
1330
)
1324
1331
else :
1325
1332
return self .simple_upsert_many_txn_emulated (
1326
- txn , table , key_names , key_values , value_names , value_values
1333
+ txn , table , key_names , key_values , value_names , value_values , lock = lock
1327
1334
)
1328
1335
1329
1336
def simple_upsert_many_txn_emulated (
@@ -1334,6 +1341,7 @@ def simple_upsert_many_txn_emulated(
1334
1341
key_values : Collection [Iterable [Any ]],
1335
1342
value_names : Collection [str ],
1336
1343
value_values : Iterable [Iterable [Any ]],
1344
+ lock : bool = True ,
1337
1345
) -> None :
1338
1346
"""
1339
1347
Upsert, many times, but without native UPSERT support or batching.
@@ -1345,17 +1353,24 @@ def simple_upsert_many_txn_emulated(
1345
1353
value_names: The value column names
1346
1354
value_values: A list of each row's value column values.
1347
1355
Ignored if value_names is empty.
1356
+ lock: True to lock the table when doing the upsert.
1348
1357
"""
1349
1358
# No value columns, therefore make a blank list so that the following
1350
1359
# zip() works correctly.
1351
1360
if not value_names :
1352
1361
value_values = [() for x in range (len (key_values ))]
1353
1362
1363
+ if lock :
1364
+ # Lock the table just once, to prevent it being done once per row.
1365
+ # Note that, according to Postgres' documentation, once obtained,
1366
+ # the lock is held for the remainder of the current transaction.
1367
+ self .engine .lock_table (txn , "user_ips" )
1368
+
1354
1369
for keyv , valv in zip (key_values , value_values ):
1355
1370
_keys = {x : y for x , y in zip (key_names , keyv )}
1356
1371
_vals = {x : y for x , y in zip (value_names , valv )}
1357
1372
1358
- self .simple_upsert_txn_emulated (txn , table , _keys , _vals )
1373
+ self .simple_upsert_txn_emulated (txn , table , _keys , _vals , lock = False )
1359
1374
1360
1375
def simple_upsert_many_txn_native_upsert (
1361
1376
self ,
@@ -1792,6 +1807,86 @@ def simple_update_txn(
1792
1807
1793
1808
return txn .rowcount
1794
1809
1810
+ async def simple_update_many (
1811
+ self ,
1812
+ table : str ,
1813
+ key_names : Collection [str ],
1814
+ key_values : Collection [Iterable [Any ]],
1815
+ value_names : Collection [str ],
1816
+ value_values : Iterable [Iterable [Any ]],
1817
+ desc : str ,
1818
+ ) -> None :
1819
+ """
1820
+ Update, many times, using batching where possible.
1821
+ If the keys don't match anything, nothing will be updated.
1822
+
1823
+ Args:
1824
+ table: The table to update
1825
+ key_names: The key column names.
1826
+ key_values: A list of each row's key column values.
1827
+ value_names: The names of value columns to update.
1828
+ value_values: A list of each row's value column values.
1829
+ """
1830
+
1831
+ await self .runInteraction (
1832
+ desc ,
1833
+ self .simple_update_many_txn ,
1834
+ table ,
1835
+ key_names ,
1836
+ key_values ,
1837
+ value_names ,
1838
+ value_values ,
1839
+ )
1840
+
1841
+ @staticmethod
1842
+ def simple_update_many_txn (
1843
+ txn : LoggingTransaction ,
1844
+ table : str ,
1845
+ key_names : Collection [str ],
1846
+ key_values : Collection [Iterable [Any ]],
1847
+ value_names : Collection [str ],
1848
+ value_values : Collection [Iterable [Any ]],
1849
+ ) -> None :
1850
+ """
1851
+ Update, many times, using batching where possible.
1852
+ If the keys don't match anything, nothing will be updated.
1853
+
1854
+ Args:
1855
+ table: The table to update
1856
+ key_names: The key column names.
1857
+ key_values: A list of each row's key column values.
1858
+ value_names: The names of value columns to update.
1859
+ value_values: A list of each row's value column values.
1860
+ """
1861
+
1862
+ if len (value_values ) != len (key_values ):
1863
+ raise ValueError (
1864
+ f"{ len (key_values )} key rows and { len (value_values )} value rows: should be the same number."
1865
+ )
1866
+
1867
+ # List of tuples of (value values, then key values)
1868
+ # (This matches the order needed for the query)
1869
+ args = [tuple (x ) + tuple (y ) for x , y in zip (value_values , key_values )]
1870
+
1871
+ for ks , vs in zip (key_values , value_values ):
1872
+ args .append (tuple (vs ) + tuple (ks ))
1873
+
1874
+ # 'col1 = ?, col2 = ?, ...'
1875
+ set_clause = ", " .join (f"{ n } = ?" for n in value_names )
1876
+
1877
+ if key_names :
1878
+ # 'WHERE col3 = ? AND col4 = ? AND col5 = ?'
1879
+ where_clause = "WHERE " + (" AND " .join (f"{ n } = ?" for n in key_names ))
1880
+ else :
1881
+ where_clause = ""
1882
+
1883
+ # UPDATE mytable SET col1 = ?, col2 = ? WHERE col3 = ? AND col4 = ?
1884
+ sql = f"""
1885
+ UPDATE { table } SET { set_clause } { where_clause }
1886
+ """
1887
+
1888
+ txn .execute_batch (sql , args )
1889
+
1795
1890
async def simple_update_one (
1796
1891
self ,
1797
1892
table : str ,
0 commit comments