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

Fix adding new columns instead of updating them if one of the key values is a NULL in upserts. #4369

Merged
merged 6 commits into from
Jan 9, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/4369.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Prevent users with access tokens predating the introduction of device IDs from creating spurious entries in the user_ips table.
10 changes: 9 additions & 1 deletion synapse/storage/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,11 +547,19 @@ def _simple_upsert_txn(self, txn, table, keyvalues, values, insertion_values={},
if lock:
self.database_engine.lock_table(txn, table)

def _getwhere(val):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given that val is a key as opposed to a value, could it be called key ?

# If the value we're passing in is None (aka NULL), we need to use
# IS, not =, as NULL = NULL equals NULL (False).
if values.get(val, keyvalues.get(val)) is None:
hawkowl marked this conversation as resolved.
Show resolved Hide resolved
return "%s IS ?" % (val,)
else:
return "%s = ?" % (val,)

# First try to update.
sql = "UPDATE %s SET %s WHERE %s" % (
table,
", ".join("%s = ?" % (k,) for k in values),
hawkowl marked this conversation as resolved.
Show resolved Hide resolved
" AND ".join("%s = ?" % (k,) for k in keyvalues)
" AND ".join(_getwhere(k) for k in keyvalues)
)
sqlargs = list(values.values()) + list(keyvalues.values())

Expand Down
71 changes: 71 additions & 0 deletions tests/storage/test_client_ips.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,77 @@ def test_insert_new_client_ip(self):
r,
)

def test_insert_new_client_ip_none_device_id(self):
"""
An insert with a device ID of NULL will not create a new entry, but
update an existing entry in the user_ips table.
"""
self.reactor.advance(12345678)

user_id = "@user:id"

# Add & trigger the storage loop
self.get_success(
self.store.insert_client_ip(
user_id, "access_token", "ip", "user_agent", None
)
)
self.reactor.advance(200)
self.pump(0)

result = self.get_success(
self.store._simple_select_list(
table="user_ips",
keyvalues={"user_id": user_id},
retcols=["access_token", "ip", "user_agent", "device_id", "last_seen"],
desc="get_user_ip_and_agents",
)
)

self.assertEqual(
result,
[
{
'access_token': 'access_token',
'ip': 'ip',
'user_agent': 'user_agent',
'device_id': None,
'last_seen': 12345678000,
}
],
)

# Add another & trigger the storage loop
self.get_success(
self.store.insert_client_ip(
user_id, "access_token", "ip", "user_agent", None
)
)
self.reactor.advance(10)
self.pump(0)

result = self.get_success(
self.store._simple_select_list(
table="user_ips",
keyvalues={"user_id": user_id},
retcols=["access_token", "ip", "user_agent", "device_id", "last_seen"],
desc="get_user_ip_and_agents",
)
)
# Only one result, has been upserted.
self.assertEqual(
result,
[
{
'access_token': 'access_token',
'ip': 'ip',
'user_agent': 'user_agent',
'device_id': None,
'last_seen': 12345878000,
}
],
)

def test_disabled_monthly_active_user(self):
self.hs.config.limit_usage_by_mau = False
self.hs.config.max_mau_value = 50
Expand Down