This repository has been archived by the owner on Jun 24, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathcount.py
116 lines (92 loc) · 4.34 KB
/
count.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
"""
### Count Operation
Slicing corresponds to the `SELECT COUNT(*)` part of an SQL query.
Simply, return the number of items, without returning the items themselves. Just a number. That's it.
Example:
```javascript
$.get('/api/user?query=' + JSON.stringify({
count: 1,
}))
```
The `1` is the *on* switch. Replace it with `0` to stop counting.
NOTE: In MongoSQL 2.0, there is a way to get both the list of items, *and* their count *simultaneously*.
This would have way better performance than two separate queries.
Please have a look: [CountingQuery](#countingqueryquery) and [MongoQuery.end_count()](#mongoqueryend_count---countingquery).
"""
from sqlalchemy import func
from sqlalchemy import exc as sa_exc
from .base import MongoQueryHandlerBase
from ..exc import InvalidQueryError, InvalidColumnError, InvalidRelationError
class MongoCount(MongoQueryHandlerBase):
""" MongoDB count query
Just give it:
* count=True
"""
query_object_section_name = 'count'
def __init__(self, model, bags):
""" Init a count
:param model: Sqlalchemy model to work with
:param bags: Model bags
"""
super(MongoCount, self).__init__(model, bags)
# On input
self.count = None
def input_prepare_query_object(self, query_object):
# When we count, we don't care about certain things
if query_object.get('count', False):
# Performance: do not sort when counting
query_object.pop('sort', None)
# We don't care about projections either
query_object.pop('project', None)
# Also, remove all skips & limits
query_object.pop('skip', None)
query_object.pop('limit', None)
# Remove all join, but not joinf (as it may filter)
query_object.pop('join', None)
# Finally, when we count, we have to remove `max_items` setting from MongoLimit.
# Only MongoLimit can do it, and it will do it for us.
# See: MongoLimit.input_prepare_query_object
return query_object
def input(self, count=None):
super(MongoCount, self).input(count)
if not isinstance(count, (int, bool, NoneType)):
raise InvalidQueryError('Count must be either true or false. Or at least a 1, or a 0')
# Done
self.count = count
return self
def _get_supported_bags(self):
return None # not used by this class
# Not Implemented for this Query Object handler
compile_columns = NotImplemented
compile_options = NotImplemented
compile_statement = NotImplemented
compile_statements = NotImplemented
def alter_query(self, query, as_relation=None):
""" Apply offset() and limit() to the query """
if self.count:
# Previously, we used to do counts like this:
# >>> query = query.with_entities(func.count())
# However, when there's no WHERE clause set on a Query, it's left without any reference to the target table.
# In this case, SqlAlchemy will actually generate a query without a FROM clause, which gives a wrong count!
# Therefore, we have to make sure that there will always be a FROM clause.
#
# Normally, we just do the following:
# >>> query = query.select_from(self.model)
# This is supposed to indicate which table to select from.
# However, it can only be applied when there's no FROM nor ORDER BY clauses present.
#
# But wait a second... didn't we just assume that there would be no FROM clause?
# Have a look at this ugly duckling:
# >>> Query(User).filter_by().select_from(User)
# This filter_by() would actually create an EMPTY condition, which will break select_from()'s assertions!
# This is reported to SqlAlchemy:
# https://github.com/sqlalchemy/sqlalchemy/issues/4606
# And (is fixed in version x.x.x | is not going to be fixed)
#
# Therefore, we'll try to do it the nice way ; and if it fails, we'll have to do something else.
try:
query = query.with_entities(func.count()).select_from(self.model)
except sa_exc.InvalidRequestError:
query = query.from_self(func.count())
return query
NoneType = type(None)