| @@ -9,6 +9,8 @@ import mmh3 | |||||
| import oursql | import oursql | ||||
| from .migration import VERSION, MIGRATIONS | from .migration import VERSION, MIGRATIONS | ||||
| from ..query.nodes import (String, Regex, Text, Language, Author, Date, Symbol, | |||||
| BinaryOp, UnaryOp) | |||||
| __all__ = ["Database"] | __all__ = ["Database"] | ||||
| @@ -51,23 +53,64 @@ class Database(object): | |||||
| "Run `python -m bitshift.database.migration`." | "Run `python -m bitshift.database.migration`." | ||||
| raise RuntimeError(err) | 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 | The returned data is a 2-tuple of (list of codelet IDs, estimated | ||||
| number of total results). | 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 | return ids, num_results | ||||
| def _get_codelets_from_ids(self, cursor, ids): | def _get_codelets_from_ids(self, cursor, ids): | ||||
| """Return a list of Codelet objects given a list of codelet 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): | def _decompose_url(self, cursor, url): | ||||
| """Break up a URL into an origin (with a URL base) and a suffix.""" | """Break up a URL into an origin (with a URL base) and a suffix.""" | ||||