diff --git a/gertty/db.py b/gertty/db.py index 332ed0e..335db9f 100644 --- a/gertty/db.py +++ b/gertty/db.py @@ -464,10 +464,11 @@ class DatabaseSession(object): def getChanges(self, query, unreviewed=False): self.database.log.debug("Search query: %s" % query) - search_filter = self.search.parse(query) - q = self.session().query(Change).filter(search_filter).order_by(change_table.c.number) + q = self.session().query(Change).filter(self.search.parse(query)) if unreviewed: q = q.filter(change_table.c.hidden==False, change_table.c.reviewed==False) + q = q.order_by(change_table.c.number) + self.database.log.debug("Search SQL: %s" % q) try: return q.all() except sqlalchemy.orm.exc.NoResultFound: diff --git a/gertty/search/__init__.py b/gertty/search/__init__.py index da024ab..5837a05 100644 --- a/gertty/search/__init__.py +++ b/gertty/search/__init__.py @@ -12,15 +12,22 @@ # License for the specific language governing permissions and limitations # under the License. +import sqlalchemy.sql.expression + from gertty.search import tokenizer, parser +import gertty.db + class SearchSyntaxError(Exception): pass + class SearchCompiler(object): def __init__(self, app): + self.app = app self.lexer = tokenizer.SearchTokenizer() self.parser = parser.SearchParser() def parse(self, data): + self.parser.username = self.app.config.username return self.parser.parse(data, lexer=self.lexer) diff --git a/gertty/search/parser.py b/gertty/search/parser.py index 363f40b..438a556 100644 --- a/gertty/search/parser.py +++ b/gertty/search/parser.py @@ -16,7 +16,7 @@ import datetime import re import ply.yacc as yacc -from sqlalchemy.sql.expression import and_, or_ +from sqlalchemy.sql.expression import and_, or_, not_, exists, select import gertty.db import gertty.search @@ -53,7 +53,7 @@ def SearchParser(): def p_negative_expr(p): '''negative_expr : NOT expression | NEG expression''' - p[0] = not p[1] + p[0] = not_(p[2]) def p_term(p): '''term : age_term @@ -120,15 +120,35 @@ def SearchParser(): def p_owner_term(p): '''owner_term : OP_OWNER string''' - p[0] = gertty.db.change_table.c.owner == p[2] + if p[2] == 'self': + username = p.parser.username + p[0] = gertty.db.account_table.c.username == username + else: + p[0] = or_(gertty.db.account_table.c.username == p[2], + gertty.db.account_table.c.email == p[2], + gertty.db.account_table.c.name == p[2]) def p_reviewer_term(p): '''reviewer_term : OP_REVIEWER string''' - p[0] = gertty.db.approval_table.c.name == p[2] + filters = [] + filters.append(gertty.db.approval_table.c.change_key == gertty.db.change_table.c.key) + if p[2] == 'self': + username = p.parser.username + filters.append(gertty.db.account_table.c.username == username) + else: + filters.append(or_(gertty.db.account_table.c.username == p[2], + gertty.db.account_table.c.email == p[2], + gertty.db.account_table.c.name == p[2])) + s = select([gertty.db.change_table.c.key], correlate=False).where(and_(*filters)) + p[0] = gertty.db.change_table.c.key.in_(s) def p_commit_term(p): '''commit_term : OP_COMMIT string''' - p[0] = gertty.db.revision_table.c.commit == p[2] + filters = [] + filters.append(gertty.db.revision_table.c.change_key == gertty.db.change_table.c.key) + filters.append(gertty.db.revision_table.c.commit == p[2]) + s = select([gertty.db.change_table.c.key], correlate=False).where(and_(*filters)) + p[0] = gertty.db.change_table.c.key.in_(s) def p_project_term(p): '''project_term : OP_PROJECT string''' @@ -167,6 +187,7 @@ def SearchParser(): user = args.group('user') filters = [] + filters.append(gertty.db.approval_table.c.change_key == gertty.db.change_table.c.key) filters.append(gertty.db.approval_table.c.category == label) if op == '=': filters.append(gertty.db.approval_table.c.value == value) @@ -175,31 +196,58 @@ def SearchParser(): elif op == '<=': filters.append(gertty.db.approval_table.c.value <= value) if user is not None: - filters.append(gertty.db.approval_table.c.name == user) - p[0] = and_(*filters) + filters.append( + or_(gertty.db.account_table.c.username == user, + gertty.db.account_table.c.email == user, + gertty.db.account_table.c.name == user)) + s = select([gertty.db.change_table.c.key], correlate=False).where(and_(*filters)) + p[0] = gertty.db.change_table.c.key.in_(s) def p_message_term(p): '''message_term : OP_MESSAGE string''' - p[0] = gertty.db.revision_table.c.message.like(p[1]) + filters = [] + filters.append(gertty.db.revision_table.c.change_key == gertty.db.change_table.c.key) + filters.append(gertty.db.revision_table.c.message == p[2]) + s = select([gertty.db.change_table.c.key], correlate=False).where(and_(*filters)) + p[0] = gertty.db.change_table.c.key.in_(s) def p_comment_term(p): '''comment_term : OP_COMMENT string''' - p[0] = and_(gertty.db.message_table.c.message.like(p[1]), - gertty.db.comment_table.c.message.like(p[1])) + filters = [] + filters.append(gertty.db.revision_table.c.change_key == gertty.db.change_table.c.key) + filters.append(gertty.db.revision_table.c.message == p[2]) + revision_select = select([gertty.db.change_table.c.key], correlate=False).where(and_(*filters)) + filters = [] + filters.append(gertty.db.revision_table.c.change_key == gertty.db.change_table.c.key) + filters.append(gertty.db.comment_table.c.revision_key == gertty.db.revision_table.c.key) + filters.append(gertty.db.comment_table.c.message == p[2]) + comment_select = select([gertty.db.change_table.c.key], correlate=False).where(and_(*filters)) + p[0] = or_(gertty.db.change_table.c.key.in_(comment_select), + gertty.db.change_table.c.key.in_(revision_select)) def p_has_term(p): '''has_term : OP_HAS string''' #TODO: implement star if p[2] == 'draft': - p[0] = gertty.db.message_table.c.pending == True + filters = [] + filters.append(gertty.db.revision_table.c.change_key == gertty.db.change_table.c.key) + filters.append(gertty.db.message_table.c.revision_key == gertty.db.revision_table.c.key) + filters.append(gertty.db.message_table.c.pending == True) + s = select([gertty.db.change_table.c.key], correlate=False).where(and_(*filters)) + p[0] = gertty.db.change_table.c.key.in_(s) else: raise gertty.search.SearchSyntaxError('Syntax error: has:%s is not supported' % p[2]) def p_is_term(p): '''is_term : OP_IS string''' #TODO: implement starred, watched, owner, reviewer, draft + username = p.parser.username if p[2] == 'reviewed': - p[0] = gertty.db.approval_table.c.value != 0 + filters = [] + filters.append(gertty.db.approval_table.c.change_key == gertty.db.change_table.c.key) + filters.append(gertty.db.approval_table.c.value != 0) + s = select([gertty.db.change_table.c.key], correlate=False).where(and_(*filters)) + p[0] = gertty.db.change_table.c.key.in_(s) elif p[2] == 'open': p[0] = gertty.db.change_table.c.status.notin_(['MERGED', 'ABANDONED']) elif p[2] == 'closed': @@ -210,6 +258,14 @@ def SearchParser(): p[0] = gertty.db.change_table.c.status == 'MERGED' elif p[2] == 'abandoned': p[0] = gertty.db.change_table.c.status == 'ABANDONED' + elif p[2] == 'owner': + p[0] = gertty.db.account_table.c.username == username + elif p[2] == 'reviewer': + filters = [] + filters.append(gertty.db.approval_table.c.change_key == gertty.db.change_table.c.key) + filters.append(gertty.db.account_table.c.username == username) + s = select([gertty.db.change_table.c.key], correlate=False).where(and_(*filters)) + p[0] = gertty.db.change_table.c.key.in_(s) else: raise gertty.search.SearchSyntaxError('Syntax error: has:%s is not supported' % p[2]) diff --git a/gertty/search/test.py b/gertty/search/test.py deleted file mode 100644 index 2e8cef0..0000000 --- a/gertty/search/test.py +++ /dev/null @@ -1,73 +0,0 @@ -import gertty.search -import re -import sys - -label_re = re.compile(r'(?P<label>[a-zA-Z0-9_-]+([a-zA-Z]|((?<![-+])[0-9])))' - r'(?P<operator>[<>]?=?)(?P<value>[-+]?[0-9]+)' - r'($|,user=(?P<user>\S+))') - -for a in [ - 'Code-Review=1', - 'Code-Review=+1', - 'Code-Review=-1', - 'Code-Review>=+1', - 'Code-Review<=-1', - 'Code-Review+1', - 'Code-Review-1', - ]: - for b in [ - '', - ',user=corvus', - ]: - data = a+b - print - print data - m = label_re.match(data) - print 'res', m and m.groups() - -#sys.exit(0) -parser = gertty.search.SearchCompiler(None) - -import tokenizer -lexer = tokenizer.SearchTokenizer() -lexer.input("project:foo/bar") - -# Tokenize -while True: - tok = lexer.token() - if not tok: break # No more input - print tok - -#TODO: unit test -for a in [ - 'label:Code-Review=1', - 'label:Code-Review=+1', - 'label:Code-Review=-1', - 'label:Code-Review>=+1', - 'label:Code-Review<=-1', - 'label:Code-Review+1', - 'label:Code-Review-1', - ]: - for b in [ - '', - ',user=corvus', - ]: - data = a+b - print - print data - result = parser.parse(data) - print 'res', str(result) - -for data in [ - '_project_key:18 status:open', - 'project:foo/bar status:open', - 'project:foo and status:open', - 'project:foo or status:open', - 'project:foo and (status:merged or status:new)', - 'project:foo or project:bar or project:baz', - 'project:foo project:bar project:baz', - ]: - print - print data - result = parser.parse(data) - print 'res', str(result)