Parcourir la source

Move parameterization functions to Node classes.

tags/v1.0^2
Ben Kurtovic il y a 10 ans
Parent
révision
f49ad1d281
4 fichiers modifiés avec 62 ajouts et 47 suppressions
  1. +2
    -45
      bitshift/database/__init__.py
  2. +2
    -2
      bitshift/query/__init__.py
  3. +49
    -0
      bitshift/query/nodes.py
  4. +9
    -0
      bitshift/query/tree.py

+ 2
- 45
bitshift/database/__init__.py Voir le fichier

@@ -53,61 +53,18 @@ class Database(object):
"Run `python -m bitshift.database.migration`."
raise RuntimeError(err)

def _explode_query_tree(self, tree):
"""Convert a query tree into components of an SQL SELECT statement."""
def _parse_node(node, tables):
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 = ?)", tables, [node.lang]
elif isinstance(node, Author):
tables |= {"authors"}
if isinstance(node.name, Regex):
return "(author_name REGEXP ?)", [node.name.regex]
cond = "(MATCH(author_name) AGAINST (? IN BOOLEAN MODE))"
return cond, tables, [node.name.string]
elif isinstance(node, Date):
column = {node.CREATE: "codelet_date_created",
node.MODIFY: "codelet_date_modified"}[node.type]
op = {node.BEFORE: "<=", node.AFTER: ">="}[node.relation]
return "(" + column + " " + op + " ?)", tables, [node.date]
elif isinstance(node, Symbol):
tables |= {"symbols"}
cond_base = "(symbol_type = ? AND symbol_name = ?)"
if node.type != node.ALL:
return cond_base, tables, [node.type, node.name]
cond = "(" + " OR ".join([cond_base] * len(node.TYPES)) + ")"
args = zip(node.TYPES.keys(), [node.name] * len(node.TYPES))
return cond, tables, [arg for tup in args for arg in tup]
elif isinstance(node, BinaryOp):
left_cond, tbls, left_args = _parse_node(node.left, tables)
right_cond, tables, right_args = _parse_node(node.right, tbls)
op = node.OPS[node.op]
cond = "(" + left_cond + " " + op + " " + right_cond + ")"
return cond, tables, left_args + right_args
elif isinstance(node, UnaryOp):
cond, tables, args = _parse_node(node.node, tables)
return "(" + node.OPS[node.op] + " " + cond + ")", tables, args

conditional, tables, arglist = _parse_node(tree.root, set())
# 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).
"""
conditional, joins, args = self._explode_query_tree(query)
base = """SELECT codelet_id
FROM codelets %s
WHERE %s
ORDER BY codelet_rank LIMIT 10"""
conditional, tables, args = query.parameterize()
joins = " ".join(tables)
qstring = base % (joins, conditional)
if page > 1:
qstring += " OFFSET %d" % ((page - 1) * 10)


+ 2
- 2
bitshift/query/__init__.py Voir le fichier

@@ -139,7 +139,7 @@ class _QueryParser(object):

Returns a 2-tuple of (first_marker_found, marker_index).
"""
def _is_escaped(query, index):
def is_escaped(query, index):
"""Return whether a query marker is backslash-escaped."""
return (index > 0 and query[index - 1] == "\\" and
(index < 2 or query[index - 2] != "\\"))
@@ -147,7 +147,7 @@ class _QueryParser(object):
best_marker, best_index = None, maxsize
for marker in markers:
index = query.find(marker)
if _is_escaped(query, index):
if is_escaped(query, index):
_, new_index = self._scan_query(query[index + 1:], marker)
index += new_index + 1
if index >= 0 and index < best_index:


+ 49
- 0
bitshift/query/nodes.py Voir le fichier

@@ -15,6 +15,13 @@ class _Node(object):
"""Return a string sort key for the node."""
return ""

def parameterize(self, tables):
"""Parameterize the node.

Returns a 3-tuple of (query conditional string, table set, param list).
"""
return "", tables, []


class _Literal(object):
"""Represents a literal component of a search query, present at the leaves.
@@ -75,6 +82,11 @@ class Text(_Node):
def sortkey(self):
return self.text.sortkey()

def parameterize(self, tables):
tables |= {"code", "symbols"}
# (FTS: codelet_name, =: symbol_name, FTS: code_code) vs. node.text (_Literal)
pass


class Language(_Node):
"""Represents a language node.
@@ -94,6 +106,10 @@ class Language(_Node):
def sortkey(self):
return LANGS[self.lang]

def parameterize(self, tables):
tables |= {"code"}
return "(code_lang = ?)", tables, [self.lang]


class Author(_Node):
"""Represents a author node.
@@ -113,6 +129,13 @@ class Author(_Node):
def sortkey(self):
return self.name.sortkey()

def parameterize(self, tables):
tables |= {"authors"}
if isinstance(self.name, Regex):
return "(author_name REGEXP ?)", [self.name.regex]
cond = "(MATCH(author_name) AGAINST (? IN BOOLEAN MODE))"
return cond, tables, [self.name.string]


class Date(_Node):
"""Represents a date node.
@@ -144,6 +167,12 @@ class Date(_Node):
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]
return "(" + column + " " + op + " ?)", tables, [self.date]


class Symbol(_Node):
"""Represents a symbol node.
@@ -171,6 +200,15 @@ class Symbol(_Node):
def sortkey(self):
return self.name.sortkey()

def parameterize(self, tables):
tables |= {"symbols"}
cond_base = "(symbol_type = ? AND symbol_name = ?)"
if self.type != self.ALL:
return cond_base, tables, [self.type, self.name]
cond = "(" + " OR ".join([cond_base] * len(self.TYPES)) + ")"
args = zip(self.TYPES.keys(), [self.name] * len(self.TYPES))
return cond, tables, [arg for tup in args for arg in tup]


class BinaryOp(_Node):
"""Represents a relationship between two nodes: ``and``, ``or``."""
@@ -190,6 +228,13 @@ class BinaryOp(_Node):
def sortkey(self):
return self.left.sortkey() + self.right.sortkey()

def parameterize(self, tables):
left_cond, tables, left_args = self.left.parameterize(tables)
right_cond, tables, right_args = self.right.parameterize(tables)
op = self.OPS[self.op]
cond = "(" + left_cond + " " + op + " " + right_cond + ")"
return cond, tables, left_args + right_args


class UnaryOp(_Node):
"""Represents a transformation applied to one node: ``not``."""
@@ -205,3 +250,7 @@ class UnaryOp(_Node):

def sortkey(self):
return self.node.sortkey()

def parameterize(self, tables):
cond, tables, args = self.node.parameterize(tables)
return "(" + self.OPS[self.op] + " " + cond + ")", tables, args

+ 9
- 0
bitshift/query/tree.py Voir le fichier

@@ -25,3 +25,12 @@ class Tree(object):
:rtype: str
"""
return repr(self)

def parameterize(self):
"""Parameterize the query tree for an SQL SELECT statement.

:return: SQL query data.
:rtype: 3-tuple of (query conditional string, table set, param tuple)
"""
conditional, tables, arglist = self._root.parameterize(set())
return conditional, tables, tuple(arglist)

Chargement…
Annuler
Enregistrer