from ..languages import LANGS __all__ = ["String", "Regex", "Text", "Language", "Author", "Date", "Symbol", "BinaryOp", "UnaryOp"] class _Node(object): """Represents a single node in a query tree. Generally speaking, a node is a constraint applied to the database. Thus, a :py:class:`~.Language` node represents a constraint where only codelets of a specific language are selected. """ def _null_regex(self, expr): """Implements a regex search with support for a null expression.""" return "IF(ISNULL(%s), 0, %s REGEXP ?)" % (expr, expr) def sortkey(self): """Return a string sort key for the node.""" return "" def parameterize(self, tables): """Parameterize the node. Returns a 4-tuple of (conditional string, parameter list, rank list, should-we-rank boolean). If the rank list is empty, then it is assumed to contain the conditional string. """ return "", [], [], False class _Literal(object): """Represents a literal component of a search query, present at the leaves. A literal might be a string or a regular expression. """ pass class String(_Literal): """Represents a string literal.""" def __init__(self, string): """ :type string: unicode """ self.string = string def __repr__(self): return "String({0!r})".format(self.string) def sortkey(self): return self.string class Regex(_Literal): """Represents a regular expression literal.""" def __init__(self, regex): """ :type string: unicode """ self.regex = regex def __repr__(self): return "Regex({0!r})".format(self.regex) def sortkey(self): return self.regex class Text(_Node): """Represents a text node. Searches in codelet names (full-text search), symbols (equality), and source code (full-text search). """ def __init__(self, text): """ :type text: :py:class:`._Literal` """ self.text = text def __repr__(self): return "Text({0})".format(self.text) def sortkey(self): return self.text.sortkey() def parameterize(self, tables): tables |= {"code", "symbols"} if isinstance(self.text, Regex): ranks = ["(codelet_name REGEXP ?)", "(code_code REGEXP ?)", self._null_regex("symbol_name")] text = self.text.regex else: ranks = ["(MATCH(codelet_name) AGAINST (? IN BOOLEAN MODE))", "(MATCH(code_code) AGAINST (? IN BOOLEAN MODE))", "(symbol_name <=> ?)"] text = self.text.string cond = "(" + " OR ".join(ranks) + ")" return cond, [text] * 3, ranks, True class Language(_Node): """Represents a language node. Searches in the code_lang field. """ def __init__(self, lang): """ :type lang: int """ self.lang = lang def __repr__(self): return "Language({0})".format(LANGS[self.lang]) def sortkey(self): return LANGS[self.lang] def parameterize(self, tables): tables |= {"code"} return "(code_lang <=> ?)", [self.lang], [], False class Author(_Node): """Represents a author node. Searches in the author_name field (full-text search). """ def __init__(self, name): """ :type name: :py:class:`_Literal` """ self.name = name def __repr__(self): return "Author({0})".format(self.name) def sortkey(self): return self.name.sortkey() def parameterize(self, tables): tables |= {"authors"} if isinstance(self.name, Regex): cond = self._null_regex("author_name") return cond, [self.name.regex], [], False cond = "(MATCH(author_name) AGAINST (? IN BOOLEAN MODE))" return cond, [self.name.string], [], True class Date(_Node): """Represents a date node. Searches in the codelet_date_created or codelet_date_modified fields. """ CREATE = 1 MODIFY = 2 BEFORE = 1 AFTER = 2 def __init__(self, type_, relation, date): """ :type type_: int (``CREATE`` or ``MODIFY``) :type relation: int (``BEFORE``, ``AFTER``) :type date: datetime.datetime """ self.type = type_ self.relation = relation self.date = date def __repr__(self): types = {self.CREATE: "CREATE", self.MODIFY: "MODIFY"} relations = {self.BEFORE: "BEFORE", self.AFTER: "AFTER"} tm = "Date({0}, {1}, {2})" return tm.format(types[self.type], relations[self.relation], self.date) def sortkey(self): return self.date.strftime("%Y%m%d%H%M%S") def parameterize(self, tables): column = {self.CREATE: "codelet_date_created", self.MODIFY: "codelet_date_modified"}[self.type] op = {self.BEFORE: "<=", self.AFTER: ">="}[self.relation] cond = "IF(ISNULL(%s), 0, %s %s ?)" % (column, column, op) return cond, [self.date], [], False class Symbol(_Node): """Represents a symbol node. Searches in symbol_type and symbol_name. """ ALL = -1 DEFINE = 0 USE = 1 FUNCTION = 0 CLASS = 1 VARIABLE = 2 NAMESPACE = 3 INTERFACE = 4 IMPORT = 5 TYPES = ["functions", "classes", "vars", "namespaces", "interfaces", "imports"] TYPE_REPR = ["FUNCTION", "CLASS", "VARIABLE", "NAMESPACE", "INTERFACE", "IMPORT"] def __init__(self, context, type_, name): """ :type context: int (``DEFINE`` or ``USE``) :type type_: int (``ALL``, ``FUNCTION``, ``CLASS``, etc.) :type name: :py:class:`._Literal` """ self.context = context self.type = type_ self.name = name def __repr__(self): context = ["DEFINE", "USE", "ALL"][self.context] type_ = self.TYPE_REPR[self.type] if self.type >= 0 else "ALL" return "Symbol({0}, {1}, {2})".format(context, type_, self.name) def sortkey(self): return self.name.sortkey() def parameterize(self, tables): tables |= {"code", "symbols"} if isinstance(self.name, Regex): cond, name = self._null_regex("symbol_name"), self.name.regex else: cond, name = "symbol_name <=> ?", self.name.string if self.type == self.ALL: types = ", ".join(str(typ) for typ in xrange(len(self.TYPES))) part = " AND IF(ISNULL(symbol_type), 0, symbol_type IN (%s))" cond += part % types if self.type != self.ALL: cond += " AND symbol_type <=> %d" % self.type if self.context != self.ALL: tables |= {"symbol_locations"} cond += " AND sloc_type <=> %d" % self.context return "(" + cond + ")", [name], [], False class BinaryOp(_Node): """Represents a relationship between two nodes: ``and``, ``or``.""" AND = object() OR = object() OPS = {AND: "AND", OR: "OR"} def __init__(self, left, op, right): self.left = left self.op = op self.right = right def __repr__(self): tmpl = "BinaryOp({0}, {1}, {2})" return tmpl.format(self.left, self.OPS[self.op], self.right) def sortkey(self): return self.left.sortkey() + self.right.sortkey() def parameterize(self, tables): lcond, largs, lranks, need_lranks = self.left.parameterize(tables) rcond, rargs, rranks, need_rranks = self.right.parameterize(tables) lranks, rranks = lranks or [lcond], rranks or [rcond] op = self.OPS[self.op] cond = "(" + lcond + " " + op + " " + rcond + ")" need_ranks = need_lranks or need_rranks or self.op == self.OR return cond, largs + rargs, lranks + rranks, need_ranks class UnaryOp(_Node): """Represents a transformation applied to one node: ``not``.""" NOT = object() OPS = {NOT: "NOT"} def __init__(self, op, node): self.op = op self.node = node def __repr__(self): return "UnaryOp({0}, {1})".format(self.OPS[self.op], self.node) def sortkey(self): return self.node.sortkey() def parameterize(self, tables): cond, args, ranks, need_ranks = self.node.parameterize(tables) new_cond = "(" + self.OPS[self.op] + " " + cond + ")" ranks = ranks or [cond] return new_cond, args, ranks, need_ranks