openmedialibrary/oml/queryparser.py

235 lines
7.2 KiB
Python
Raw Normal View History

2014-05-04 17:26:43 +00:00
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from datetime import datetime
import unicodedata
from sqlalchemy.sql import operators
2016-01-07 04:08:00 +00:00
from sqlalchemy.orm import load_only
2014-05-04 17:26:43 +00:00
import utils
import settings
2014-05-18 23:24:04 +00:00
import logging
2015-11-29 14:56:38 +00:00
logger = logging.getLogger(__name__)
2014-05-18 23:24:04 +00:00
2014-05-04 17:26:43 +00:00
def get_operator(op, type='str'):
return {
'str': {
2014-05-26 16:02:41 +00:00
'==': operators.eq,
2014-05-04 17:26:43 +00:00
'>': operators.gt,
'>=': operators.ge,
'<': operators.lt,
'<=': operators.le,
'^': operators.startswith_op,
'$': operators.endswith_op,
},
'int': {
'==': operators.eq,
'>': operators.gt,
'>=': operators.ge,
'<': operators.lt,
'<=': operators.le,
}
}[type].get(op, {
'str': operators.contains_op,
'int': operators.eq
}[type])
class Parser(object):
def __init__(self, model, user_items):
2014-05-04 17:26:43 +00:00
self._model = model
self._find = model.find.mapper.class_
2014-05-27 18:10:55 +00:00
self._sort = model.sort.mapper.class_
2014-05-04 17:26:43 +00:00
self._user = model.users.mapper.class_
self._user_items = user_items
2014-05-04 17:26:43 +00:00
self._list = model.lists.mapper.class_
self.item_keys = model.item_keys
self.filter_keys = model.filter_keys
def parse_condition(self, condition):
'''
condition: {
value: "war"
}
or
condition: {
key: "year",
value: [1970, 1980],
operator: "="
}
...
'''
2014-05-19 01:36:37 +00:00
#logger.debug('parse_condition %s', condition)
2014-05-18 23:24:04 +00:00
if not 'value' in condition:
return None
2014-05-04 17:26:43 +00:00
k = condition.get('key', '*')
if not k:
k = '*'
v = condition['value']
op = condition.get('operator')
if not op:
op = '='
if op.startswith('!'):
op = op[1:]
exclude = True
else:
exclude = False
key_type = (utils.get_by_id(self.item_keys, k) or {'type': 'string'}).get('type')
if isinstance(key_type, list):
key_type = key_type[0]
if k == 'list':
key_type = ''
if (not exclude and op == '=' or op in ('$', '^')) and v == '':
return None
elif k == 'resolution':
q = self.parse_condition({'key': 'width', 'value': v[0], 'operator': op}) \
& self.parse_condition({'key': 'height', 'value': v[1], 'operator': op})
if exclude:
q = ~q
return q
elif isinstance(v, list) and len(v) == 2 and op == '=':
q = self.parse_condition({'key': k, 'value': v[0], 'operator': '>='}) \
& self.parse_condition({'key': k, 'value': v[1], 'operator': '<'})
if exclude:
q = ~q
return q
elif key_type == 'boolean':
q = getattr(self._model, 'find_%s' % k) == v
if exclude:
q = ~q
return q
elif key_type in ("string", "text"):
2014-09-02 22:32:44 +00:00
if isinstance(v, str):
2014-05-04 17:26:43 +00:00
v = unicodedata.normalize('NFKD', v).lower()
2014-05-26 16:02:41 +00:00
else:
v = v.lower()
q = get_operator(op)(self._find.findvalue, v)
2014-05-04 17:26:43 +00:00
if k != '*':
q &= (self._find.key == k)
2014-05-26 23:45:29 +00:00
self._joins.append(self._find)
2014-05-04 17:26:43 +00:00
if exclude:
q = ~q
return q
elif k == 'list':
2014-05-18 23:24:04 +00:00
nickname, name = v.split(':', 1)
2014-05-04 17:26:43 +00:00
if nickname:
u = self._user.query.filter_by(nickname=nickname).one()
2014-05-04 17:26:43 +00:00
else:
u = self._user.query.filter_by(id=settings.USER_ID).one()
l = self._list.query.filter_by(user_id=u.id, name=name).one()
if exclude:
2016-01-07 04:08:00 +00:00
ids = l.user.items.filter(self._list.id==l.id).options(load_only('id'))
q = operators.notin_op(self._model.id, ids)
2014-05-04 17:26:43 +00:00
else:
if l.type == 'smart':
data = l._query
q = self.parse_conditions(data.get('conditions', []),
data.get('operator', '&'))
2014-05-26 23:45:29 +00:00
else:
q = (self._list.id == l.id)
self._joins.append(self._list.items)
2014-05-04 17:26:43 +00:00
return q
elif key_type == 'date':
def parse_date(d):
while len(d) < 3:
d.append(1)
return datetime(*[int(i) for i in d])
#using sort here since find only contains strings
v = parse_date(v.split('-'))
2014-05-27 18:10:55 +00:00
vk = getattr(self._sort, k)
2014-05-04 17:26:43 +00:00
q = get_operator(op, 'int')(vk, v)
2014-05-27 18:10:55 +00:00
self._joins.append(self._sort)
2014-05-04 17:26:43 +00:00
if exclude:
q = ~q
return q
else: #integer, float, time
2014-05-27 18:10:55 +00:00
q = get_operator(op, 'int')(getattr(self._sort, k), v)
self._joins.append(self._sort)
2014-05-04 17:26:43 +00:00
if exclude:
q = ~q
return q
def parse_conditions(self, conditions, operator):
'''
conditions: [
{
value: "war"
}
{
key: "year",
value: "1970-1980,
operator: "!="
},
{
key: "country",
value: "f",
operator: "^"
}
],
operator: "&"
'''
conn = []
for condition in conditions:
if 'conditions' in condition:
q = self.parse_conditions(condition['conditions'],
condition.get('operator', '&'))
else:
q = self.parse_condition(condition)
if isinstance(q, list):
conn += q
else:
conn.append(q)
conn = [q for q in conn if not isinstance(q, None.__class__)]
if conn:
if operator == '|':
q = conn[0]
for c in conn[1:]:
q = q | c
q = [q]
else:
q = conn
return q
return []
def find(self, data):
'''
query: {
conditions: [
{
value: "war"
}
{
key: "year",
value: "1970-1980,
operator: "!="
},
{
key: "country",
value: "f",
operator: "^"
}
],
operator: "&"
}
'''
#join query with operator
qs = self._model.query
#only include items that have hard metadata
2014-05-26 23:45:29 +00:00
self._joins = []
2014-05-04 17:26:43 +00:00
conditions = self.parse_conditions(data.get('query', {}).get('conditions', []),
data.get('query', {}).get('operator', '&'))
for c in conditions:
2014-05-26 23:45:29 +00:00
if self._joins:
qs = qs.join(self._joins.pop(0))
qs = qs.filter(c)
2016-01-04 10:33:37 +00:00
# FIXME: group_by needed here to avoid
# duplicates due to joins.
qs = qs.group_by(self._model.id)
#print(qs)
2014-05-04 17:26:43 +00:00
return qs