Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit f2d2481

Browse files
author
David Robertson
authored
Require SQLite >= 3.27.0 (#13760)
1 parent 69fa297 commit f2d2481

File tree

9 files changed

+106
-208
lines changed

9 files changed

+106
-208
lines changed

changelog.d/13760.removal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Synapse will now refuse to start if configured to use SQLite < 3.27.

synapse/storage/database.py

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -533,15 +533,14 @@ def __init__(
533533
if isinstance(self.engine, Sqlite3Engine):
534534
self._unsafe_to_upsert_tables.add("user_directory_search")
535535

536-
if self.engine.can_native_upsert:
537-
# Check ASAP (and then later, every 1s) to see if we have finished
538-
# background updates of tables that aren't safe to update.
539-
self._clock.call_later(
540-
0.0,
541-
run_as_background_process,
542-
"upsert_safety_check",
543-
self._check_safe_to_upsert,
544-
)
536+
# Check ASAP (and then later, every 1s) to see if we have finished
537+
# background updates of tables that aren't safe to update.
538+
self._clock.call_later(
539+
0.0,
540+
run_as_background_process,
541+
"upsert_safety_check",
542+
self._check_safe_to_upsert,
543+
)
545544

546545
def name(self) -> str:
547546
"Return the name of this database"
@@ -1160,11 +1159,8 @@ async def simple_upsert(
11601159
attempts = 0
11611160
while True:
11621161
try:
1163-
# We can autocommit if we are going to use native upserts
1164-
autocommit = (
1165-
self.engine.can_native_upsert
1166-
and table not in self._unsafe_to_upsert_tables
1167-
)
1162+
# We can autocommit if it is safe to upsert
1163+
autocommit = table not in self._unsafe_to_upsert_tables
11681164

11691165
return await self.runInteraction(
11701166
desc,
@@ -1199,22 +1195,23 @@ def simple_upsert_txn(
11991195
) -> bool:
12001196
"""
12011197
Pick the UPSERT method which works best on the platform. Either the
1202-
native one (Pg9.5+, recent SQLites), or fall back to an emulated method.
1198+
native one (Pg9.5+, SQLite >= 3.24), or fall back to an emulated method.
12031199
12041200
Args:
12051201
txn: The transaction to use.
12061202
table: The table to upsert into
12071203
keyvalues: The unique key tables and their new values
12081204
values: The nonunique columns and their new values
12091205
insertion_values: additional key/values to use only when inserting
1210-
lock: True to lock the table when doing the upsert.
1206+
lock: True to lock the table when doing the upsert. Unused when performing
1207+
a native upsert.
12111208
Returns:
12121209
Returns True if a row was inserted or updated (i.e. if `values` is
12131210
not empty then this always returns True)
12141211
"""
12151212
insertion_values = insertion_values or {}
12161213

1217-
if self.engine.can_native_upsert and table not in self._unsafe_to_upsert_tables:
1214+
if table not in self._unsafe_to_upsert_tables:
12181215
return self.simple_upsert_txn_native_upsert(
12191216
txn, table, keyvalues, values, insertion_values=insertion_values
12201217
)
@@ -1365,14 +1362,12 @@ async def simple_upsert_many(
13651362
value_names: The value column names
13661363
value_values: A list of each row's value column values.
13671364
Ignored if value_names is empty.
1368-
lock: True to lock the table when doing the upsert. Unused if the database engine
1369-
supports native upserts.
1365+
lock: True to lock the table when doing the upsert. Unused when performing
1366+
a native upsert.
13701367
"""
13711368

1372-
# We can autocommit if we are going to use native upserts
1373-
autocommit = (
1374-
self.engine.can_native_upsert and table not in self._unsafe_to_upsert_tables
1375-
)
1369+
# We can autocommit if it safe to upsert
1370+
autocommit = table not in self._unsafe_to_upsert_tables
13761371

13771372
await self.runInteraction(
13781373
desc,
@@ -1406,10 +1401,10 @@ def simple_upsert_many_txn(
14061401
value_names: The value column names
14071402
value_values: A list of each row's value column values.
14081403
Ignored if value_names is empty.
1409-
lock: True to lock the table when doing the upsert. Unused if the database engine
1410-
supports native upserts.
1404+
lock: True to lock the table when doing the upsert. Unused when performing
1405+
a native upsert.
14111406
"""
1412-
if self.engine.can_native_upsert and table not in self._unsafe_to_upsert_tables:
1407+
if table not in self._unsafe_to_upsert_tables:
14131408
return self.simple_upsert_many_txn_native_upsert(
14141409
txn, table, key_names, key_values, value_names, value_values
14151410
)

synapse/storage/databases/main/lock.py

Lines changed: 39 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -129,91 +129,48 @@ async def _try_acquire_lock(
129129
now = self._clock.time_msec()
130130
token = random_string(6)
131131

132-
if self.db_pool.engine.can_native_upsert:
133-
134-
def _try_acquire_lock_txn(txn: LoggingTransaction) -> bool:
135-
# We take out the lock if either a) there is no row for the lock
136-
# already, b) the existing row has timed out, or c) the row is
137-
# for this instance (which means the process got killed and
138-
# restarted)
139-
sql = """
140-
INSERT INTO worker_locks (lock_name, lock_key, instance_name, token, last_renewed_ts)
141-
VALUES (?, ?, ?, ?, ?)
142-
ON CONFLICT (lock_name, lock_key)
143-
DO UPDATE
144-
SET
145-
token = EXCLUDED.token,
146-
instance_name = EXCLUDED.instance_name,
147-
last_renewed_ts = EXCLUDED.last_renewed_ts
148-
WHERE
149-
worker_locks.last_renewed_ts < ?
150-
OR worker_locks.instance_name = EXCLUDED.instance_name
151-
"""
152-
txn.execute(
153-
sql,
154-
(
155-
lock_name,
156-
lock_key,
157-
self._instance_name,
158-
token,
159-
now,
160-
now - _LOCK_TIMEOUT_MS,
161-
),
162-
)
163-
164-
# We only acquired the lock if we inserted or updated the table.
165-
return bool(txn.rowcount)
166-
167-
did_lock = await self.db_pool.runInteraction(
168-
"try_acquire_lock",
169-
_try_acquire_lock_txn,
170-
# We can autocommit here as we're executing a single query, this
171-
# will avoid serialization errors.
172-
db_autocommit=True,
132+
def _try_acquire_lock_txn(txn: LoggingTransaction) -> bool:
133+
# We take out the lock if either a) there is no row for the lock
134+
# already, b) the existing row has timed out, or c) the row is
135+
# for this instance (which means the process got killed and
136+
# restarted)
137+
sql = """
138+
INSERT INTO worker_locks (lock_name, lock_key, instance_name, token, last_renewed_ts)
139+
VALUES (?, ?, ?, ?, ?)
140+
ON CONFLICT (lock_name, lock_key)
141+
DO UPDATE
142+
SET
143+
token = EXCLUDED.token,
144+
instance_name = EXCLUDED.instance_name,
145+
last_renewed_ts = EXCLUDED.last_renewed_ts
146+
WHERE
147+
worker_locks.last_renewed_ts < ?
148+
OR worker_locks.instance_name = EXCLUDED.instance_name
149+
"""
150+
txn.execute(
151+
sql,
152+
(
153+
lock_name,
154+
lock_key,
155+
self._instance_name,
156+
token,
157+
now,
158+
now - _LOCK_TIMEOUT_MS,
159+
),
173160
)
174-
if not did_lock:
175-
return None
176-
177-
else:
178-
# If we're on an old SQLite we emulate the above logic by first
179-
# clearing out any existing stale locks and then upserting.
180-
181-
def _try_acquire_lock_emulated_txn(txn: LoggingTransaction) -> bool:
182-
sql = """
183-
DELETE FROM worker_locks
184-
WHERE
185-
lock_name = ?
186-
AND lock_key = ?
187-
AND (last_renewed_ts < ? OR instance_name = ?)
188-
"""
189-
txn.execute(
190-
sql,
191-
(lock_name, lock_key, now - _LOCK_TIMEOUT_MS, self._instance_name),
192-
)
193-
194-
inserted = self.db_pool.simple_upsert_txn_emulated(
195-
txn,
196-
table="worker_locks",
197-
keyvalues={
198-
"lock_name": lock_name,
199-
"lock_key": lock_key,
200-
},
201-
values={},
202-
insertion_values={
203-
"token": token,
204-
"last_renewed_ts": self._clock.time_msec(),
205-
"instance_name": self._instance_name,
206-
},
207-
)
208-
209-
return inserted
210161

211-
did_lock = await self.db_pool.runInteraction(
212-
"try_acquire_lock_emulated", _try_acquire_lock_emulated_txn
213-
)
162+
# We only acquired the lock if we inserted or updated the table.
163+
return bool(txn.rowcount)
214164

215-
if not did_lock:
216-
return None
165+
did_lock = await self.db_pool.runInteraction(
166+
"try_acquire_lock",
167+
_try_acquire_lock_txn,
168+
# We can autocommit here as we're executing a single query, this
169+
# will avoid serialization errors.
170+
db_autocommit=True,
171+
)
172+
if not did_lock:
173+
return None
217174

218175
lock = Lock(
219176
self._reactor,

synapse/storage/databases/main/stats.py

Lines changed: 34 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -446,59 +446,41 @@ def _upsert_with_additive_relatives_txn(
446446
absolutes: Absolute (set) fields
447447
additive_relatives: Fields that will be added onto if existing row present.
448448
"""
449-
if self.database_engine.can_native_upsert:
450-
absolute_updates = [
451-
"%(field)s = EXCLUDED.%(field)s" % {"field": field}
452-
for field in absolutes.keys()
453-
]
454-
455-
relative_updates = [
456-
"%(field)s = EXCLUDED.%(field)s + COALESCE(%(table)s.%(field)s, 0)"
457-
% {"table": table, "field": field}
458-
for field in additive_relatives.keys()
459-
]
460-
461-
insert_cols = []
462-
qargs = []
463-
464-
for (key, val) in chain(
465-
keyvalues.items(), absolutes.items(), additive_relatives.items()
466-
):
467-
insert_cols.append(key)
468-
qargs.append(val)
449+
absolute_updates = [
450+
"%(field)s = EXCLUDED.%(field)s" % {"field": field}
451+
for field in absolutes.keys()
452+
]
453+
454+
relative_updates = [
455+
"%(field)s = EXCLUDED.%(field)s + COALESCE(%(table)s.%(field)s, 0)"
456+
% {"table": table, "field": field}
457+
for field in additive_relatives.keys()
458+
]
459+
460+
insert_cols = []
461+
qargs = []
462+
463+
for (key, val) in chain(
464+
keyvalues.items(), absolutes.items(), additive_relatives.items()
465+
):
466+
insert_cols.append(key)
467+
qargs.append(val)
468+
469+
sql = """
470+
INSERT INTO %(table)s (%(insert_cols_cs)s)
471+
VALUES (%(insert_vals_qs)s)
472+
ON CONFLICT (%(key_columns)s) DO UPDATE SET %(updates)s
473+
""" % {
474+
"table": table,
475+
"insert_cols_cs": ", ".join(insert_cols),
476+
"insert_vals_qs": ", ".join(
477+
["?"] * (len(keyvalues) + len(absolutes) + len(additive_relatives))
478+
),
479+
"key_columns": ", ".join(keyvalues),
480+
"updates": ", ".join(chain(absolute_updates, relative_updates)),
481+
}
469482

470-
sql = """
471-
INSERT INTO %(table)s (%(insert_cols_cs)s)
472-
VALUES (%(insert_vals_qs)s)
473-
ON CONFLICT (%(key_columns)s) DO UPDATE SET %(updates)s
474-
""" % {
475-
"table": table,
476-
"insert_cols_cs": ", ".join(insert_cols),
477-
"insert_vals_qs": ", ".join(
478-
["?"] * (len(keyvalues) + len(absolutes) + len(additive_relatives))
479-
),
480-
"key_columns": ", ".join(keyvalues),
481-
"updates": ", ".join(chain(absolute_updates, relative_updates)),
482-
}
483-
484-
txn.execute(sql, qargs)
485-
else:
486-
self.database_engine.lock_table(txn, table)
487-
retcols = list(chain(absolutes.keys(), additive_relatives.keys()))
488-
current_row = self.db_pool.simple_select_one_txn(
489-
txn, table, keyvalues, retcols, allow_none=True
490-
)
491-
if current_row is None:
492-
merged_dict = {**keyvalues, **absolutes, **additive_relatives}
493-
self.db_pool.simple_insert_txn(txn, table, merged_dict)
494-
else:
495-
for (key, val) in additive_relatives.items():
496-
if current_row[key] is None:
497-
current_row[key] = val
498-
else:
499-
current_row[key] += val
500-
current_row.update(absolutes)
501-
self.db_pool.simple_update_one_txn(txn, table, keyvalues, current_row)
483+
txn.execute(sql, qargs)
502484

503485
async def _calculate_and_set_initial_state_for_room(self, room_id: str) -> None:
504486
"""Calculate and insert an entry into room_stats_current.

synapse/storage/databases/main/transactions.py

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -221,25 +221,15 @@ async def set_destination_retry_timings(
221221
retry_interval: how long until next retry in ms
222222
"""
223223

224-
if self.database_engine.can_native_upsert:
225-
await self.db_pool.runInteraction(
226-
"set_destination_retry_timings",
227-
self._set_destination_retry_timings_native,
228-
destination,
229-
failure_ts,
230-
retry_last_ts,
231-
retry_interval,
232-
db_autocommit=True, # Safe as its a single upsert
233-
)
234-
else:
235-
await self.db_pool.runInteraction(
236-
"set_destination_retry_timings",
237-
self._set_destination_retry_timings_emulated,
238-
destination,
239-
failure_ts,
240-
retry_last_ts,
241-
retry_interval,
242-
)
224+
await self.db_pool.runInteraction(
225+
"set_destination_retry_timings",
226+
self._set_destination_retry_timings_native,
227+
destination,
228+
failure_ts,
229+
retry_last_ts,
230+
retry_interval,
231+
db_autocommit=True, # Safe as it's a single upsert
232+
)
243233

244234
def _set_destination_retry_timings_native(
245235
self,
@@ -249,8 +239,6 @@ def _set_destination_retry_timings_native(
249239
retry_last_ts: int,
250240
retry_interval: int,
251241
) -> None:
252-
assert self.database_engine.can_native_upsert
253-
254242
# Upsert retry time interval if retry_interval is zero (i.e. we're
255243
# resetting it) or greater than the existing retry interval.
256244
#

synapse/storage/engines/_base.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,6 @@ def __init__(self, module: DBAPI2Module, config: Mapping[str, Any]):
4343
def single_threaded(self) -> bool:
4444
...
4545

46-
@property
47-
@abc.abstractmethod
48-
def can_native_upsert(self) -> bool:
49-
"""
50-
Do we support native UPSERTs?
51-
"""
52-
...
53-
5446
@property
5547
@abc.abstractmethod
5648
def supports_using_any_list(self) -> bool:

synapse/storage/engines/postgres.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -158,13 +158,6 @@ def on_new_connection(self, db_conn: "LoggingDatabaseConnection") -> None:
158158
cursor.close()
159159
db_conn.commit()
160160

161-
@property
162-
def can_native_upsert(self) -> bool:
163-
"""
164-
Can we use native UPSERTs?
165-
"""
166-
return True
167-
168161
@property
169162
def supports_using_any_list(self) -> bool:
170163
"""Do we support using `a = ANY(?)` and passing a list"""

0 commit comments

Comments
 (0)