forked from LBRYCommunity/lbry-sdk
sql constraints support table.column dot delimited column names
This commit is contained in:
parent
1f4a9cff26
commit
0960762694
3 changed files with 45 additions and 25 deletions
|
@ -11,53 +11,66 @@ from .test_transaction import get_output, NULL_HASH
|
|||
|
||||
class TestConstraintBuilder(unittest.TestCase):
|
||||
|
||||
def test_dot(self):
|
||||
constraints = {'txo.position': 18}
|
||||
self.assertEqual(
|
||||
constraints_to_sql(constraints, prepend_sql=''),
|
||||
'txo.position = :txo_position'
|
||||
)
|
||||
self.assertEqual(constraints, {'txo_position': 18})
|
||||
|
||||
def test_any(self):
|
||||
constraints = {
|
||||
'ages__any': {
|
||||
'age__gt': 18,
|
||||
'age__lt': 38
|
||||
'txo.age__gt': 18,
|
||||
'txo.age__lt': 38
|
||||
}
|
||||
}
|
||||
self.assertEqual(
|
||||
constraints_to_sql(constraints, prepend_sql=''),
|
||||
'(age > :ages__any_age__gt OR age < :ages__any_age__lt)'
|
||||
'(txo.age > :ages__any_txo_age__gt OR txo.age < :ages__any_txo_age__lt)'
|
||||
)
|
||||
self.assertEqual(
|
||||
constraints, {
|
||||
'ages__any_age__gt': 18,
|
||||
'ages__any_age__lt': 38
|
||||
'ages__any_txo_age__gt': 18,
|
||||
'ages__any_txo_age__lt': 38
|
||||
}
|
||||
)
|
||||
|
||||
def test_in_list(self):
|
||||
constraints = {'ages__in': [18, 38]}
|
||||
constraints = {'txo.age__in': [18, 38]}
|
||||
self.assertEqual(
|
||||
constraints_to_sql(constraints, prepend_sql=''),
|
||||
'ages IN (:ages_1, :ages_2)'
|
||||
'txo.age IN (:txo_age_1, :txo_age_2)'
|
||||
)
|
||||
self.assertEqual(
|
||||
constraints, {
|
||||
'ages_1': 18,
|
||||
'ages_2': 38
|
||||
'txo_age_1': 18,
|
||||
'txo_age_2': 38
|
||||
}
|
||||
)
|
||||
|
||||
def test_in_query(self):
|
||||
constraints = {'ages__in': 'SELECT age from ages_table'}
|
||||
constraints = {'txo.age__in': 'SELECT age from ages_table'}
|
||||
self.assertEqual(
|
||||
constraints_to_sql(constraints, prepend_sql=''),
|
||||
'ages IN (SELECT age from ages_table)'
|
||||
'txo.age IN (SELECT age from ages_table)'
|
||||
)
|
||||
self.assertEqual(constraints, {})
|
||||
|
||||
def test_not_in_query(self):
|
||||
constraints = {'ages__not_in': 'SELECT age from ages_table'}
|
||||
constraints = {'txo.age__not_in': 'SELECT age from ages_table'}
|
||||
self.assertEqual(
|
||||
constraints_to_sql(constraints, prepend_sql=''),
|
||||
'ages NOT IN (SELECT age from ages_table)'
|
||||
'txo.age NOT IN (SELECT age from ages_table)'
|
||||
)
|
||||
self.assertEqual(constraints, {})
|
||||
|
||||
def test_in_invalid(self):
|
||||
constraints = {'ages__in': 9}
|
||||
with self.assertRaisesRegex(ValueError, 'list or string'):
|
||||
constraints_to_sql(constraints, prepend_sql='')
|
||||
|
||||
|
||||
class TestQueries(unittest.TestCase):
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
__path__: str = __import__('pkgutil').extend_path(__path__, __name__)
|
||||
__version__ = '0.0.5'
|
||||
__version__ = '0.0.7'
|
||||
|
|
|
@ -11,12 +11,16 @@ from torba.basetransaction import BaseTransaction, TXORefResolvable
|
|||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def clean_arg_name(arg):
|
||||
return arg.replace('.', '_')
|
||||
|
||||
|
||||
def constraints_to_sql(constraints, joiner=' AND ', prepend_sql=' AND ', prepend_key=''):
|
||||
if not constraints:
|
||||
return ''
|
||||
extras = []
|
||||
for key in list(constraints):
|
||||
col, op = key, '='
|
||||
col, op, constraint = key, '=', constraints.pop(key)
|
||||
if key.endswith('__not'):
|
||||
col, op = key[:-len('__not')], '!='
|
||||
elif key.endswith('__lt'):
|
||||
|
@ -32,24 +36,27 @@ def constraints_to_sql(constraints, joiner=' AND ', prepend_sql=' AND ', prepend
|
|||
col, op = key[:-len('__in')], 'IN'
|
||||
else:
|
||||
col, op = key[:-len('__not_in')], 'NOT IN'
|
||||
items = constraints.pop(key)
|
||||
if isinstance(items, list):
|
||||
if isinstance(constraint, list):
|
||||
placeholders = []
|
||||
for item_no, item in enumerate(items, 1):
|
||||
constraints['{}_{}'.format(col, item_no)] = item
|
||||
placeholders.append(':{}_{}'.format(col, item_no))
|
||||
for item_no, item in enumerate(constraint, 1):
|
||||
constraints['{}_{}'.format(clean_arg_name(col), item_no)] = item
|
||||
placeholders.append(':{}_{}'.format(clean_arg_name(col), item_no))
|
||||
items = ', '.join(placeholders)
|
||||
elif isinstance(constraint, str):
|
||||
items = constraint
|
||||
else:
|
||||
raise ValueError("{} requires a list or string as constraint value.".format(key))
|
||||
extras.append('{} {} ({})'.format(col, op, items))
|
||||
continue
|
||||
elif key.endswith('__any'):
|
||||
subconstraints = constraints.pop(key)
|
||||
extras.append('({})'.format(
|
||||
constraints_to_sql(subconstraints, ' OR ', '', key+'_')
|
||||
constraints_to_sql(constraint, ' OR ', '', key+'_')
|
||||
))
|
||||
for subkey, val in subconstraints.items():
|
||||
constraints['{}_{}'.format(key, subkey)] = val
|
||||
for subkey, val in constraint.items():
|
||||
constraints['{}_{}'.format(clean_arg_name(key), clean_arg_name(subkey))] = val
|
||||
continue
|
||||
extras.append('{} {} :{}'.format(col, op, prepend_key+key))
|
||||
constraints[clean_arg_name(key)] = constraint
|
||||
extras.append('{} {} :{}'.format(col, op, prepend_key+clean_arg_name(key)))
|
||||
return prepend_sql + joiner.join(extras) if extras else ''
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue