Skip to content

Fixed bug in Identifier and fixed crash when no WHERE clause is present #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 14 additions & 26 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,20 @@
#!/usr/bin/env python
from setuptools import setup

import sqlparse
from setuptools import setup, find_packages

setup(
name='sqlparse',
version=sqlparse.__version__,

author='Chris Lyon',
author_email='[email protected]',

description='SQL parser and query builder',
long_description=open('README.md').read(),

url='https://github.com/Flushot/sqlparse',

license='Apache License 2.0',
name="sqlparse",
version=0.1,
author="Chris Lyon",
author_email="[email protected]",
description="SQL parser and query builder",
long_description=open("README.md").read(),
url="https://github.com/Flushot/sqlparse",
packages=find_packages(include=["sqlparse", "sqlparse.*"]),
license="Apache License 2.0",
classifiers=[
'Intended Audience :: Developers'
'Development Status :: 2 - Pre-Alpha',
'License :: OSI Approved :: Apache Software License',
],

install_requires=[
'pyparsing',
'sqlalchemy',
'pymongo'
"Intended Audience :: Developers" "Development Status :: 2 - Pre-Alpha",
"License :: OSI Approved :: Apache Software License",
],

test_suite='sqlparse'
install_requires=["pyparsing", "sqlalchemy", "pymongo"],
test_suite="sqlparse",
)
83 changes: 38 additions & 45 deletions sqlparse/builders/mongo_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,22 @@ class MongoQueryVisitor(IdentifierAndValueVisitor):
# Map of SQL operators to MongoDB equivalents
# TODO: Create node classes for these operators, rather than relying on operator.name
OPERATORS = {
'not': '$nor',
'!': '$nor',
'!=': '$ne',
'<>': '$ne',
'<': '$lt',
'<=': '$lte',
'>': '$gt',
'>=': '$gte',
'and': '$and',
'&&': '$and',
'or': '$or',
'||': '$or',
'in': '$in',
'mod': '$mod',
'%': '$mod',
'like': '$regex',
"not": "$nor",
"!": "$nor",
"!=": "$ne",
"<>": "$ne",
"<": "$lt",
"<=": "$lte",
">": "$gt",
">=": "$gte",
"and": "$and",
"&&": "$and",
"or": "$or",
"||": "$or",
"in": "$in",
"mod": "$mod",
"%": "$mod",
"like": "$regex",
# Mongo doesn't support: + - * / ** << >>
}

Expand All @@ -39,39 +39,28 @@ def visit_UnaryOperator(self, node):

rhs_node = self.visit(node.rhs)
if not isinstance(rhs_node, nodes.ListValue):
rhs_node = [ rhs_node ]
rhs_node = [rhs_node]

return { op_name: rhs_node }
return {op_name: rhs_node}

def visit_BinaryOperator(self, node):
lhs_node = self.visit(node.lhs)
rhs_node = self.visit(node.rhs)

if node.name == '=':
if node.name == "=":
# Mongo treats equality struct different from other binary operators
if isinstance(lhs_node, str):
return {lhs_node: rhs_node}
else:
raise ValueError('lhs is an expression: %s' % lhs_node)
raise ValueError("lhs is an expression: %s" % lhs_node)

elif node.name in ('xor', '^'):
elif node.name in ("xor", "^"):
# Mongo lacks an XOR operator
return {
'$and': [
{'$or': [lhs_node, rhs_node]},
{'$and': [
{'$nor': [lhs_node]},
{'$nor': [rhs_node]}
]}
]}

elif node.name == 'between':
return {"$and": [{"$or": [lhs_node, rhs_node]}, {"$and": [{"$nor": [lhs_node]}, {"$nor": [rhs_node]}]}]}

elif node.name == "between":
# Mongo lacks a BETWEEN operator
return {
'$and': [
{lhs_node: {'$gte': rhs_node.begin}},
{lhs_node: {'$lte': rhs_node.end}}
]}
return {"$and": [{lhs_node: {"$gte": rhs_node.begin}}, {lhs_node: {"$lte": rhs_node.end}}]}

# Standard binary operator
else:
Expand All @@ -80,19 +69,20 @@ def visit_BinaryOperator(self, node):
raise ValueError('Mongo visitor does not implement "%s" binary operator' % node.name)

# AND and OR have list operands
if op_name in ('$and', '$or'):
if op_name in ("$and", "$or"):
return {op_name: [lhs_node, rhs_node]}
# Everything else contains a { prop: expr } operand
elif isinstance(lhs_node, str):
return {lhs_node: {op_name: rhs_node}}
else:
raise ValueError('lhs is an expression: %s' % lhs_node)
raise ValueError("lhs is an expression: %s" % lhs_node)


class MongoQueryBuilder(QueryBuilder):
"""
Builds a MongoDB query from a SQL query
"""

def parse_and_build(self, query_string):
parse_tree = sqlparse.parse_string(query_string)
filter_options = {}
Expand All @@ -105,15 +95,18 @@ def parse_and_build(self, query_string):
filter_fields = self._get_fields_option(parse_tree)
self.fields = list(filter_fields.keys())
if filter_fields:
filter_options['fields'] = filter_fields
filter_options["fields"] = filter_fields

return self._get_filter_criteria(parse_tree), filter_options

def _get_filter_criteria(self, parse_tree):
"""
Filter criteria specified in WHERE
"""
filter_criteria = MongoQueryVisitor().visit(parse_tree.where[0])
try:
filter_criteria = MongoQueryVisitor().visit(parse_tree.where[0])
except IndexError:
return {}
# print('WHERE: {}', json.dumps(filter_criteria, indent=4))
return filter_criteria

Expand All @@ -123,17 +116,17 @@ def _get_collection_name(self, parse_tree):
"""
collections = [str(table.name) for table in parse_tree.tables.values]
if len(collections) == 0:
raise ValueError('Collection name required in FROM clause')
raise ValueError("Collection name required in FROM clause")

collection = collections[0]
# print('FROM: {}', collection)

# TODO: parse this as an Identifier instead of a str
if not isinstance(collection, str):
raise ValueError('collection name must be a string')
raise ValueError("collection name must be a string")

if len(collections) > 1:
raise ValueError('Mongo query requires single collection in FROM clause')
raise ValueError("Mongo query requires single collection in FROM clause")

return collection

Expand All @@ -144,11 +137,11 @@ def _get_fields_option(self, parse_tree):
fields = IdentifierAndValueVisitor().visit(parse_tree.columns)
# print('SELECT: {}', fields)
if not isinstance(fields, list):
raise ValueError('SELECT must be a list')
raise ValueError("SELECT must be a list")

filter_fields = {}
for field in fields:
if field == '*':
if field == "*":
return {}

filter_fields[field.name] = 1
Expand Down
Loading