support in db for constraints that reference the same column by appending a # followed by some unique string

This commit is contained in:
Lex Berezhny 2019-05-06 17:23:33 -04:00
parent ba8870823c
commit 31983fbb8e
2 changed files with 32 additions and 25 deletions

View file

@ -56,7 +56,11 @@ class TestQueryBuilder(unittest.TestCase):
def test_dot(self): def test_dot(self):
self.assertEqual( self.assertEqual(
constraints_to_sql({'txo.position': 18}), 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): def test_any(self):
@ -67,25 +71,25 @@ class TestQueryBuilder(unittest.TestCase):
'txo.age__lt': 38 'txo.age__lt': 38
} }
}), }),
('(txo.age > :ages__any_txo_age__gt OR txo.age < :ages__any_txo_age__lt)', { ('(txo.age > :ages__any0_txo_age__gt0 OR txo.age < :ages__any0_txo_age__lt0)', {
'ages__any_txo_age__gt': 18, 'ages__any0_txo_age__gt0': 18,
'ages__any_txo_age__lt': 38 'ages__any0_txo_age__lt0': 38
}) })
) )
def test_in(self): def test_in(self):
self.assertEqual( self.assertEqual(
constraints_to_sql({'txo.age__in': [18, 38]}), constraints_to_sql({'txo.age__in#2': [18, 38]}),
('txo.age IN (:txo_age__in0, :txo_age__in1)', { ('txo.age IN (:txo_age__in2_0, :txo_age__in2_1)', {
'txo_age__in0': 18, 'txo_age__in2_0': 18,
'txo_age__in1': 38 'txo_age__in2_1': 38
}) })
) )
self.assertEqual( self.assertEqual(
constraints_to_sql({'txo.name__in': ('abc123', 'def456')}), constraints_to_sql({'txo.name__in': ('abc123', 'def456')}),
('txo.name IN (:txo_name__in0, :txo_name__in1)', { ('txo.name IN (:txo_name__in0_0, :txo_name__in0_1)', {
'txo_name__in0': 'abc123', 'txo_name__in0_0': 'abc123',
'txo_name__in1': 'def456' 'txo_name__in0_1': 'def456'
}) })
) )
self.assertEqual( self.assertEqual(
@ -96,16 +100,16 @@ class TestQueryBuilder(unittest.TestCase):
def test_not_in(self): def test_not_in(self):
self.assertEqual( self.assertEqual(
constraints_to_sql({'txo.age__not_in': [18, 38]}), constraints_to_sql({'txo.age__not_in': [18, 38]}),
('txo.age NOT IN (:txo_age__not_in0, :txo_age__not_in1)', { ('txo.age NOT IN (:txo_age__not_in0_0, :txo_age__not_in0_1)', {
'txo_age__not_in0': 18, 'txo_age__not_in0_0': 18,
'txo_age__not_in1': 38 'txo_age__not_in0_1': 38
}) })
) )
self.assertEqual( self.assertEqual(
constraints_to_sql({'txo.name__not_in': ('abc123', 'def456')}), constraints_to_sql({'txo.name__not_in': ('abc123', 'def456')}),
('txo.name NOT IN (:txo_name__not_in0, :txo_name__not_in1)', { ('txo.name NOT IN (:txo_name__not_in0_0, :txo_name__not_in0_1)', {
'txo_name__not_in0': 'abc123', 'txo_name__not_in0_0': 'abc123',
'txo_name__not_in1': 'def456' 'txo_name__not_in0_1': 'def456'
}) })
) )
self.assertEqual( self.assertEqual(
@ -128,10 +132,10 @@ class TestQueryBuilder(unittest.TestCase):
a__not='b', b__in='select * from blah where c=:$c', 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}), 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 " "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", "(one LIKE :d__any0_one__like0 OR two = :d__any0_two0) ORDER BY b LIMIT 10",
{'a__not': 'b', 'd__any_one__like': 'o', 'd__any_two': 2, '$c': 3} {'a__not0': 'b', 'd__any0_one__like0': 'o', 'd__any0_two0': 2, '$c': 3}
) )
) )

View file

@ -87,6 +87,9 @@ class AIOSQLite:
def constraints_to_sql(constraints, joiner=' AND ', prepend_key=''): def constraints_to_sql(constraints, joiner=' AND ', prepend_key=''):
sql, values = [], {} sql, values = [], {}
for key, constraint in constraints.items(): 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('.', '_') col, op, key = key, '=', key.replace('.', '_')
if key.startswith('$'): if key.startswith('$'):
values[key] = constraint values[key] = constraint
@ -120,8 +123,8 @@ def constraints_to_sql(constraints, joiner=' AND ', prepend_key=''):
if isinstance(constraint, (list, set, tuple)): if isinstance(constraint, (list, set, tuple)):
keys = [] keys = []
for i, val in enumerate(constraint): for i, val in enumerate(constraint):
keys.append(f':{key}{i}') keys.append(f':{key}{tag}_{i}')
values[f'{key}{i}'] = val values[f'{key}{tag}_{i}'] = val
sql.append(f'{col} {op} ({", ".join(keys)})') sql.append(f'{col} {op} ({", ".join(keys)})')
elif isinstance(constraint, str): elif isinstance(constraint, str):
sql.append(f'{col} {op} ({constraint})') 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.") raise ValueError(f"{col} requires a list, set or string as constraint value.")
continue continue
elif key.endswith('__any'): 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})') sql.append(f'({where})')
values.update(subvalues) values.update(subvalues)
continue continue
sql.append(f'{col} {op} :{prepend_key}{key}') sql.append(f'{col} {op} :{prepend_key}{key}{tag}')
values[prepend_key+key] = constraint values[prepend_key+key+tag] = constraint
return joiner.join(sql) if sql else '', values return joiner.join(sql) if sql else '', values