Code Monkey home page Code Monkey logo

pql's Introduction

=== PQL

PQL stands for Python-Query-Language. PQL translates python expressions to MongoDB queries.

PQL uses the builtin python ast module for parsing and analysis of python expressions.

PQL is resilient to code injections as it doesn't evaluate the code.

Installation

pip install pql

Follow @alonhorev on twitter for updates. Source located at: http://github.com/alonho/pql

Find Queries

Schema-Free Example

The schema-free parser converts python expressions to mongodb queries with no schema enforcement:

>>> import pql
>>> pql.find("a > 1 and b == 'foo' or not c.d == False")
{'$or': [{'$and': [{'a': {'$gt': 1}}, {'b': 'foo'}]}, {'$not': {'c.d': False}}]}

Schema-Aware Example

The schema-aware parser validates fields exist:

>>> import pql
>>> pql.find('b == 1', schema={'a': pql.DateTimeField()}) 
Traceback (most recent call last):
	...
pql.ParseError: Field not found: b. options: ['a']

Validates values are of the correct type:

>>> pql.find('a == 1', schema={'a': pql.DateTimeField()})
Traceback (most recent call last):
	...
pql.ParseError: Unsupported syntax (Num).

Validates functions are called against the appropriate types:

>>> pql.find('a == regex("foo")', schema={'a': pql.DateTimeField()})
Traceback (most recent call last):
	...
pql.ParseError: Unsupported function (regex). options: ['date', 'exists', 'type']

Referencing Fields

pql mongo
a a
a.b.c a.b.c
"a-b" a-b

Data Types

pql mongo
a == 1 {'a': 1}
a == "foo" {'a': 'foo'}
a == None {'a': None}
a == True {'a': True}
a == False {'a': False}
a == [1, 2, 3] {'a': [1, 2, 3]}
a == {"foo": 1} {'a': {'foo': 1}}
a == date("2012-3-4") {'a': datetime.datetime(2012, 3, 4, 0, 0)}
a == date("2012-3-4 12:34:56") {'a': datetime.datetime(2012, 3, 4, 12, 34, 56)}
a == date("2012-3-4 12:34:56.123") {'a': datetime.datetime(2012, 3, 4, 12, 34, 56, 123000)}
id == id("abcdeabcdeabcdeabcdeabcd") {'id': bson.ObjectId("abcdeabcdeabcdeabcdeabcd")}

Operators

pql mongo
a != 1 {'a': {'$ne': 1}}
a > 1 {'a': {'$gt': 1}}
a >= 1 {'a': {'$gte': 1}}
a < 1 {'a': {'$lt': 1}}
a <= 1 {'a': {'$lte': 1}}
a in [1, 2, 3] {'a': {'$in': [1, 2, 3]}}
a not in [1, 2, 3] {'a': {'$nin': [1, 2, 3]}}

Boolean Logic

pql mongo
not a == 1 {'$not': {'a': 1}}
a == 1 or b == 2 {'$or': [{'a': 1}, {'b': 2}]}
a == 1 and b == 2 {'$and': [{'a': 1}, {'b': 2}]}

Functions

pql mongo
a == all([1, 2, 3]) {'a': {'$all': [1, 2, 3]}}
a == exists(True) {'a': {'$exists': True}}
a == match({"foo": "bar"}) {'a': {'$elemMatch': {'foo': 'bar'}}}
a == mod(10, 3) {'a': {'$mod': [10, 3]}}
a == regex("foo") {'a': {'$regex': 'foo'}}
a == regex("foo", "i") {'a': {'$options': 'i', '$regex': 'foo'}}
a == size(4) {'a': {'$size': 4}}
a == type(3) {'a': {'$type': 3}}

Geo Queries

pql mongo
location == geoWithin(center([1, 2], 3)) {'location': {'$geoWithin': {'$center': [[1, 2], 3]}}}
location == geoWithin(centerSphere([1, 2], 3)) {'location': {'$geoWithin': {'$centerSphere': [[1, 2], 3]}}}
location == geoIntersects(LineString([[1, 2], [3, 4]])) {'location': {'$geoIntersects': {'$geometry': {'type': 'LineString', 'coordinates': [[1, 2], [3, 4]]}}}}
location == geoWithin(Polygon([[[1, 2], [3, 4], [5, 6]], [[1, 2], [3, 4], [5, 6]]])) {'location': {'$geoWithin': {'$geometry': {'type': 'Polygon', 'coordinates': [[[1, 2], [3, 4], [5, 6]], [[1, 2], [3, 4], [5, 6]]]}}}}
location == near([1, 2], 10) {'location': {'$maxDistance': 10, '$near': [1, 2]}}
location == near(Point(1, 2), 10) {'location': {'$near': {'$geometry': {'type': 'Point', 'coordinates': [1, 2]}, '$maxDistance': 10}}}
location == nearSphere(Point(1, 2)) {'location': {'$nearSphere': {'$geometry': {'type': 'Point', 'coordinates': [1, 2]}}}}
location == geoWithin(box([[1, 2], [3, 4], [5, 6]])) {'location': {'$geoWithin': {'$box': [[1, 2], [3, 4], [5, 6]]}}}
location == geoWithin(polygon([[1, 2], [3, 4], [5, 6]])) {'location': {'$geoWithin': {'$polygon': [[1, 2], [3, 4], [5, 6]]}}}

Aggregation Queries

Example

Lets say you have a collection of car listings:

>>> list(db.cars.find())
[{'_id': ObjectId('51794ce58c998f1e2b654b50'),
  'made_on': datetime.datetime(1971, 4, 7, 0, 0),
  'model': 'fiat',
  'price': 3},
 {'_id': ObjectId('51794cea8c998f1e2b654b51'),
  'made_on': datetime.datetime(1980, 10, 19, 0, 0),
  'model': 'subaru',
  'price': 5},
 {'_id': ObjectId('51794cf08c998f1e2b654b52'),
  'made_on': datetime.datetime(1983, 2, 27, 0, 0),
  'model': 'kia',
  'price': 4},
 {'_id': ObjectId('51794d3c8c998f1e2b654b53'),
  'made_on': datetime.datetime(1988, 1, 23, 0, 0),
  'model': 'kia',
  'price': 7}]

How do you get the number of cars and the sum of their prices per model per decade:

>>> collection.aggregate(project(model='model', made_on='year(made_on)', price='price * 3.7') | 
		         match('made_on > 1975 and made_on < 1990') | 
			 group(_id=project(model='model', decade='made_on - (made_on % 10)'), 
					   count='sum(1)', total='sum(price)'))
{'ok': 1.0,
 'result': [{'_id': {'decade': 1980, 'model': 'subaru'}, 'count': 1,'total': 18.5},
		    {'_id': {'decade': 1980, 'model': 'kia'}, 'count': 2, 'total': 40.7}]}

How would it look using the raw syntax:

[{'$project': {'made_on': {'$year': '$made_on'},
               'model': '$model',
               'price': {'$multiply': ['$price', 3.7]}}},
 {'$match': {'$and': [{'made_on': {'$gt': 1975}},
                      {'made_on': {'$lt': 1990}}]}},
 {'$group': {'_id': {'decade': {'$subtract': ['$made_on',
                                              {'$mod': ['$made_on', 10]}]},
                     'model': '$model'},
  'count': {'$sum': 1},
  'total': {'$sum': '$price'}}}]

Referencing Fields

pql mongo
a $a
a.b.c $a.b.c

Arithmetic Operators

pql mongo
a + 1 {'$add': ['$a', 1]}
a / 1 {'$divide': ['$a', 1]}
a % 1 {'$mod': ['$a', 1]}
a * 1 {'$multiply': ['$a', 1]}
a - 1 {'$subtract': ['$a', 1]}
a > 0 {'$gt': ['$a', 0]}
a >= 0 {'$gte': ['$a', 0]}
a < 0 {'$lt': ['$a', 0]}
a <= 0 {'$lte': ['$a', 0]}

Logical Operators

pql mongo
a == 0 {'$eq': ['$a', 0]}
a != 0 {'$ne': ['$a', 0]}
cmp(a, "bar") {'$cmp': ['$a', 'bar']}
a and b {'$and': ['$a', '$b']}
not a {'$not': '$a'}
a or b {'$or': ['$a', '$b']}
a if b > 3 else c {'$cond': [{'$gt': ['$b', 3]}, '$a', '$c']}
ifnull(a + b, 100) {'$ifnull': [{'$add': ['$a', '$b']}, 100]}

Date Operators

pql mongo
dayOfYear(a) {'$dayOfYear': '$a'}
dayOfMonth(a) {'$dayOfMonth': '$a'}
dayOfWeek(a) {'$dayOfWeek': '$a'}
year(a) {'$year': '$a'}
month(a) {'$month': '$a'}
week(a) {'$week': '$a'}
hour(a) {'$hour': '$a'}
minute(a) {'$minute': '$a'}
second(a) {'$second': '$a'}
millisecond(a) {'$millisecond': '$a'}

String Operators

pql mongo
concat("foo", "bar", b) {'$concat': ['foo', 'bar', '$b']}
strcasecmp("foo", b) {'$strcasecmp': ['foo', '$b']}
substr("foo", 1, 2) {'$substr': ['foo', 1, 2]}
toLower(a) {'$toLower': '$a'}
toUpper(a) {'$toUpper': '$a'}

TODO

  1. Generate a schema from a mongoengine or mongokit class.
  2. Add a declarative schema generation syntax.
  3. Add support for $where.

pql's People

Contributors

alonho avatar daevaorn avatar gianpaj avatar kirantambe avatar ov700 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pql's Issues

some date related tests are failing

cward@cward pql [master] $ python -m unittest find_tests

...........F.F...................

FAIL: test_date (find_tests.PqlSchemaLessTestCase)

Traceback (most recent call last):
File "find_tests.py", line 92, in test_date
self.compare('a == date(10)', {'a': datetime(1969, 12, 31, 19, 0, 10)})
File "find_tests.py", line 10, in compare
self.assertEqual(pql.find(string), expected)
AssertionError: {'a': datetime.datetime(1970, 1, 1, 1, 0, 10)} != {'a': datetime.datetime(1969, 12, 31, 19, 0, 10)}

  • {'a': datetime.datetime(1970, 1, 1, 1, 0, 10)}
  • {'a': datetime.datetime(1969, 12, 31, 19, 0, 10)}

FAIL: test_epoch (find_tests.PqlSchemaLessTestCase)

Traceback (most recent call last):
File "find_tests.py", line 99, in test_epoch
self.compare('a == epoch("2012")', {'a': 1340164800})
File "find_tests.py", line 10, in compare
self.assertEqual(pql.find(string), expected)
AssertionError: {'a': 1345068000.0} != {'a': 1340164800}

  • {'a': 1345068000.0}
    ? - ---
  • {'a': 1340164800}
    ? + +

Ran 33 tests in 0.007s

FAILED (failures=2)

Error in exception handler

Running 0.4.2, I found an interesting unhandled exception triggered by a bad expression syntax. Using the expression:

a == "b" ()

I get an exception 'Str' object has no attribute 'id'

AttributeError: 'Str' object has no attribute 'id'
...
[
    ".../lib/python2.7/site-packages/pql/matching.py", 
    171, 
    "handle", 
    "options=self.get_options())"
]

How to convert mongo query back to sql ?

Hello,

I am writing a restful sevice which act a storage layer to mongodb/mysql/... . mongodb query format is quite good for restful , but the problem is how to convert mongodb query to construct plain sql ( I use sqlalchemy ). Then I found pql , but looks like it doesn't support mongo query back to sql ?

Support True, False and None in codition as python

Because pql simulate the python language so it may be very helpful if it support the same way with python as below
query = '{var1} and {var2}'.format(var1=var1 or True, var2=var2)
in case var1 = None, True, False
Expect: it is still valid
Actual: throw exception

Bug on $near query

There is an error while creating query for GeoQueries, especially near. Your example location == near([1, 2], 10) generates {'location': SON([('$near', [1, 2]), ('$maxDistance', 10)])}, where SON([( is some random characters (possibly missing { and } ). It is replicated for location == near(Point(1, 2), 10) and location == nearSphere(Point(1, 2)) . Thanks.

Compatibility with Python 3.8

The Python ast module, which this library depends on has been changed in more recent versions of Python. For sure 3.8, and possibly 3.7 or earlier. The ast parser assigns a value of "Constant" to quoted strings and constant numeric values to the right of the comparison expression. When this is handled by the resolve() method, the string "Constant" is appended to "handle_" and then the getattr() method fails because none of the field handler functions have a handle_Constant() function. Hoping that this issue can be fixed.

Add support for hyphenated attributes

PQL should have the ability to support queries against attributes that contain a dash in the name.

Current Behavior:

> import pql
> q1 = 'fieldname=="test"'
> pql.find(q1)
>> {'fieldname': 'test'}
> q2 = 'field-name=="test"'
> pql.find(q2)
>> ParseError: Unsupported syntax (BinOp). options: ['Attribute', 'Name']

Expected Behavior:

> import pql
> q1 = 'fieldname=="test"'
> pql.find(q1)
>> {'fieldname': 'test'}
> q2 = 'field-name=="test"'
> pql.find(q2)
>> {'field-name': 'test'}

parse date("...") as timestamp

Parsing a query using the date() function returns a spec clause which using datetime object. I need to use a timestamp (epoc seconds) in place of a datetime.

IS:
date_field >= date("1998-01-01") == {'date_field': {'$gte': datetime.datetime(1998, 1, 1, 0, 0)}}

WAS:
date_field >= date("1998-01-01") == {'date_field': {'$gte': 883612800}}

eg::
from datetime import datetime
from calendar import timegm
dt = datetime(1998, 1, 1, 0, 0)
ts = timegm(dt.timetuple())

Perhaps an argument could be set when calling parse, where by default the parse would return datetime objects, or epoch timestamp if epoch=True? Or configurable option set somewhere? at parser init?

pql_parser = pql.SchemaFreeParser()
spec = pql_parser.parse(query, epoch=True)

$gt and $lt on shared term

Is it possible to use pql to generate multiple conditions on the same term? For example:

{'$and': [{'a': {'$gt': 1, '$lt': 10}]}

As opposed to a series of separate conditions:

{'$and': [{'a': {'$gt': 1}}, {'a': {'$lt': 10}]}

First example does not work

When I ran your first very simple example I got an error

pql.find("a > 1 and b == 'foo' or not c.d == False")
ParseError: Unsupported syntax (NameConstant). options: ['Call', 'Dict', 'List', 'Name', 'Num', 'Str', 'operator_and_right']

I think you need to replace False with null.

pql.find("a > 1 and b == 'foo' or not c.d == null")
Out[24]: {'$or': [{'a': 1}, {'$and': [{'b': '2'}, {'c.d': None}]}]}

I used Python 3.5.

Cheers

H

Extensible Functions

Hello,

First off I want to say great stuff! This library has really helped enhance an internal tool here at Jawbone where we used to require mongo query language.

Perhaps I missed something but I couldn't figure out how to add generic functions or types short of using a schema or monkey patching. The find method instantiates either SchemaFreeParser or the SchemaParser so there is no way to extend those (without patching). Perhaps find could take a parser as a parameter instead of instantiating one? Also, the issue I had with using the scheme is I don't have a complete schema so I only wanted to define certain fields with a specific type. Perhaps SchemaParser could fall back to GenericField/Type when not found in the map?

We store our timestamps in unix epoch. So I wanted a function to allow a user to compare a timestamp to a human readable date. Here's how I ended up adding EpochFunc to pql:

class EpochFunc(pql.matching.Func):
    def handle_epoch(self, node):
        return self.parse_arg(node, 0, EpochField())


class EpochField(pql.matching.AlgebricField):
    def handle_Str(self, node):
        dt = dateutil.parser.parse(node.s)
        return float(dt.strftime('%s.%f'))

    def handle_Num(self, node):
        return node.n

    def handle_Call(self, node):
        return EpochFunc().handle(node)


class GenericFunc(pql.matching.StringFunc, pql.matching.IntFunc, pql.matching.ListFunc, pql.matching.DateTimeFunc, pql.matching.IdFunc, EpochFunc):
    pass


# Monkey Patch GenericFunc with our GenericFunc which includes EpochFunc
pql.matching.GenericFunc = GenericFunc

Possible Asks:

  1. Allow a parser to be passed to find.
  2. Have SchemaParser fall back to Generic when field map misses.
  3. Have a way to extend generic functions.

Cheers,
Lew

Want PQL fromdate and todate structure.

I want $gte,$lte for same field in PQL, so how can i do this.

mongo query:
{'updated_on': {'$gte': datetime.datetime(2012, 2, 4, 0, 0),'$lte': datetime.datetime(2012, 3, 4, 0, 0)}}

I want PQL string structure for this mongo query.

Thanking you.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.