diff --git a/tests/client_tests/unit/test_database.py b/tests/client_tests/unit/test_database.py index d722f62db..268917695 100644 --- a/tests/client_tests/unit/test_database.py +++ b/tests/client_tests/unit/test_database.py @@ -56,7 +56,11 @@ class TestQueryBuilder(unittest.TestCase): def test_dot(self): self.assertEqual( constraints_to_sql({'txo.position': 18}), - ('txo.position = :txo_position', {'txo_position': 18}) + ('txo.position = :txo_position0', {'txo_position0': 18}) + ) + self.assertEqual( + constraints_to_sql({'txo.position#6': 18}), + ('txo.position = :txo_position6', {'txo_position6': 18}) ) def test_any(self): @@ -67,25 +71,25 @@ class TestQueryBuilder(unittest.TestCase): 'txo.age__lt': 38 } }), - ('(txo.age > :ages__any_txo_age__gt OR txo.age < :ages__any_txo_age__lt)', { - 'ages__any_txo_age__gt': 18, - 'ages__any_txo_age__lt': 38 + ('(txo.age > :ages__any0_txo_age__gt0 OR txo.age < :ages__any0_txo_age__lt0)', { + 'ages__any0_txo_age__gt0': 18, + 'ages__any0_txo_age__lt0': 38 }) ) def test_in(self): self.assertEqual( - constraints_to_sql({'txo.age__in': [18, 38]}), - ('txo.age IN (:txo_age__in0, :txo_age__in1)', { - 'txo_age__in0': 18, - 'txo_age__in1': 38 + constraints_to_sql({'txo.age__in#2': [18, 38]}), + ('txo.age IN (:txo_age__in2_0, :txo_age__in2_1)', { + 'txo_age__in2_0': 18, + 'txo_age__in2_1': 38 }) ) self.assertEqual( constraints_to_sql({'txo.name__in': ('abc123', 'def456')}), - ('txo.name IN (:txo_name__in0, :txo_name__in1)', { - 'txo_name__in0': 'abc123', - 'txo_name__in1': 'def456' + ('txo.name IN (:txo_name__in0_0, :txo_name__in0_1)', { + 'txo_name__in0_0': 'abc123', + 'txo_name__in0_1': 'def456' }) ) self.assertEqual( @@ -96,16 +100,16 @@ class TestQueryBuilder(unittest.TestCase): def test_not_in(self): self.assertEqual( constraints_to_sql({'txo.age__not_in': [18, 38]}), - ('txo.age NOT IN (:txo_age__not_in0, :txo_age__not_in1)', { - 'txo_age__not_in0': 18, - 'txo_age__not_in1': 38 + ('txo.age NOT IN (:txo_age__not_in0_0, :txo_age__not_in0_1)', { + 'txo_age__not_in0_0': 18, + 'txo_age__not_in0_1': 38 }) ) self.assertEqual( constraints_to_sql({'txo.name__not_in': ('abc123', 'def456')}), - ('txo.name NOT IN (:txo_name__not_in0, :txo_name__not_in1)', { - 'txo_name__not_in0': 'abc123', - 'txo_name__not_in1': 'def456' + ('txo.name NOT IN (:txo_name__not_in0_0, :txo_name__not_in0_1)', { + 'txo_name__not_in0_0': 'abc123', + 'txo_name__not_in0_1': 'def456' }) ) self.assertEqual( @@ -128,10 +132,10 @@ class TestQueryBuilder(unittest.TestCase): a__not='b', b__in='select * from blah where c=:$c', d__any={'one__like': 'o', 'two': 2}, limit=10, order_by='b', **{'$c': 3}), ( - "select * from foo WHERE a != :a__not AND " + "select * from foo WHERE a != :a__not0 AND " "b IN (select * from blah where c=:$c) AND " - "(one LIKE :d__any_one__like OR two = :d__any_two) ORDER BY b LIMIT 10", - {'a__not': 'b', 'd__any_one__like': 'o', 'd__any_two': 2, '$c': 3} + "(one LIKE :d__any0_one__like0 OR two = :d__any0_two0) ORDER BY b LIMIT 10", + {'a__not0': 'b', 'd__any0_one__like0': 'o', 'd__any0_two0': 2, '$c': 3} ) ) diff --git a/torba/client/basedatabase.py b/torba/client/basedatabase.py index 8e4b9ccdc..e5c28988d 100644 --- a/torba/client/basedatabase.py +++ b/torba/client/basedatabase.py @@ -87,6 +87,9 @@ class AIOSQLite: def constraints_to_sql(constraints, joiner=' AND ', prepend_key=''): sql, values = [], {} for key, constraint in constraints.items(): + tag = '0' + if '#' in key: + key, tag = key[:key.index('#')], key[key.index('#')+1:] col, op, key = key, '=', key.replace('.', '_') if key.startswith('$'): values[key] = constraint @@ -120,8 +123,8 @@ def constraints_to_sql(constraints, joiner=' AND ', prepend_key=''): if isinstance(constraint, (list, set, tuple)): keys = [] for i, val in enumerate(constraint): - keys.append(f':{key}{i}') - values[f'{key}{i}'] = val + keys.append(f':{key}{tag}_{i}') + values[f'{key}{tag}_{i}'] = val sql.append(f'{col} {op} ({", ".join(keys)})') elif isinstance(constraint, str): sql.append(f'{col} {op} ({constraint})') @@ -129,12 +132,12 @@ def constraints_to_sql(constraints, joiner=' AND ', prepend_key=''): raise ValueError(f"{col} requires a list, set or string as constraint value.") continue elif key.endswith('__any'): - where, subvalues = constraints_to_sql(constraint, ' OR ', key+'_') + where, subvalues = constraints_to_sql(constraint, ' OR ', key+tag+'_') sql.append(f'({where})') values.update(subvalues) continue - sql.append(f'{col} {op} :{prepend_key}{key}') - values[prepend_key+key] = constraint + sql.append(f'{col} {op} :{prepend_key}{key}{tag}') + values[prepend_key+key+tag] = constraint return joiner.join(sql) if sql else '', values