diff --git a/bitshift/database/__init__.py b/bitshift/database/__init__.py index 07f46c7..08d7f08 100644 --- a/bitshift/database/__init__.py +++ b/bitshift/database/__init__.py @@ -9,6 +9,8 @@ import mmh3 import oursql from .migration import VERSION, MIGRATIONS +from ..query.nodes import (String, Regex, Text, Language, Author, Date, Symbol, + BinaryOp, UnaryOp) __all__ = ["Database"] @@ -51,23 +53,64 @@ class Database(object): "Run `python -m bitshift.database.migration`." raise RuntimeError(err) - def _search_with_query(self, cursor, query): - """Convert a query tree into SQL SELECTs, execute, and return results. + def _explode_query_tree(self, tree): + """Convert a query tree into components of an SQL SELECT statement.""" + def _parse_node(node): + if isinstance(node, Text): + tables |= {"code", "symbols"} + # (FTS: codelet_name, =: symbol_name, FTS: code_code) vs. node.text (_Literal) + pass + elif isinstance(node, Language): + tables |= {"code"} + return "(code_lang = ?)", [node.lang] + elif isinstance(node, Author): + tables |= {"authors"} + # (FTS: author_name) vs. node.name (_Literal) + pass + elif isinstance(node, Date): + # read node.type, node.relation + # (>=/<=: codelet_date_created / codelet_date_modified) vs. node.date (datetime.datetime) + pass + elif isinstance(node, Symbol): + tables |= {"symbols"} + # (symbol_type, symbol_name) vs. (node.type, node.name) + pass + elif isinstance(node, BinaryOp): + left_cond, left_args = _parse_node(node.left) + right_cond, right_args = _parse_node(node.right) + op = node.OPS[node.op] + cond = "(" + left_cond + " " + op + " " + right_cond + ")" + return cond, left_args + right_args + elif isinstance(node, UnaryOp): + cond, args = _parse_node(node.node) + return "(" + node.OPS[node.op] + " " + cond + ")", args + + tables = set() + conditional, arglist = _parse_node(tree.root) + # joins = " ".join(tables) + + return conditional, joins, tuple(arglist) + + def _search_with_query(self, cursor, query, page): + """Execute an SQL query based on a query tree, and return results. The returned data is a 2-tuple of (list of codelet IDs, estimated number of total results). """ - raise NotImplementedError() ## TODO - - results = cursor.fetchall() - ids = NotImplemented ## TODO: extract ids from results - num_results = NotImplemented ## TODO: num if results else 0 - + conditional, joins, args = self._explode_query_tree(query) + base = "SELECT codelet_id FROM codelets %s WHERE %s LIMIT 10" + qstring = base % (joins, conditional) + if page > 1: + qstring += " OFFSET %d" % ((page - 1) * 10) + + cursor.execute(qstring, args) + ids = [id for id, in cursor.fetchall()] + num_results = 0 # TODO: NotImplemented return ids, num_results def _get_codelets_from_ids(self, cursor, ids): """Return a list of Codelet objects given a list of codelet IDs.""" - raise NotImplementedError() ## TODO + raise NotImplementedError() # TODO def _decompose_url(self, cursor, url): """Break up a URL into an origin (with a URL base) and a suffix."""