1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # mojo, a Python library for implementing document based databases
5 # Copyright (C) 2013-2014 by Javier Sancho Fernandez <jsf at jsancho dot org>
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
24 def __init__(self, collection, spec=None, fields=None, **kwargs):
25 if spec and not type(spec) is dict:
26 raise Exception("spec must be an instance of dict")
28 self.Query = collection.database.connection.Query
29 self.Field = collection.database.connection.Field
30 self.Table = collection.database.connection.Table
31 self.Constraint = collection.database.connection.Constraint
32 self.Literal = collection.database.connection.Literal
34 self.serializer = collection.database.connection.serializer
36 self.collection = collection
38 if self.collection.exists():
39 self.fields = self._get_fields(fields)
40 self.cursor = self._get_cursor()
48 def _get_fields(self, fields):
49 set_all_fields = set(self.collection._get_fields())
51 res_fields = list(set_all_fields)
52 elif type(fields) is dict:
53 fields_without_id = filter(lambda x: x[0] != '_id', fields.iteritems())
54 if fields_without_id[0][1]:
59 res_fields = set(set_all_fields)
60 for f in fields_without_id:
61 if f[1] and f[0] in set_all_fields:
65 raise Exception("You cannot currently mix including and excluding fields. Contact us if this is an issue.")
68 res_fields.discard(f[0])
70 raise Exception("You cannot currently mix including and excluding fields. Contact us if this is an issue.")
71 if '_id' in fields and not fields['_id']:
72 res_fields.discard('_id')
75 res_fields = list(res_fields)
77 set_fields = set(list(fields))
79 res_fields = list(set_all_fields.intersection(set_fields))
83 def _get_cursor(self):
84 table_id = self.Table(self.collection.database.db_name, '%s$_id' % self.collection.table_name)
86 fields = [self.Field(table_id, 'value')]
87 for f in filter(lambda x: x != '_id', self.fields):
88 fields.append(self._get_cursor_field(f))
92 constraints = [self.Constraint('=', self.Field(table_id, 'name'), self.Literal('_id'))]
94 for k, v in self.spec.iteritems():
95 constraints.append(self._get_cursor_constraint(k, v))
97 query = self.Query(fields, tables, constraints)
98 return self.collection.database.connection._get_cursor(query)
100 def _get_cursor_field(self, field_name):
101 table_id = self.Table(self.collection.database.db_name, '%s$_id' % self.collection.table_name)
102 table_field = self.Table(self.collection.database.db_name, '%s$%s' % (self.collection.table_name, field_name.split(".")[0]))
104 fields = [self.Field(table_field, 'value')]
105 tables = [table_field]
107 self.Constraint('=', self.Field(table_field, 'id'), self.Field(table_id, 'id')),
108 self.Constraint('=', self.Field(table_field, 'name'), self.Literal(field_name)),
110 return self.Query(fields, tables, constraints)
112 def _get_cursor_constraint(self, field_name, field_value):
113 table_id = self.Table(self.collection.database.db_name, '%s$_id' % self.collection.table_name)
114 table_field = self.Table(self.collection.database.db_name, '%s$%s' % (self.collection.table_name, field_name.split(".")[0]))
116 if type(field_value) in (int, float):
117 field_type = 'number'
120 field_value = self.serializer.dumps(field_value)
122 fields = [self.Field(table_field, 'id')]
123 tables = [table_field]
125 self.Constraint('or', self.Constraint('=', self.Field(table_field, 'name'), self.Literal(field_name)),
126 self.Constraint('starts', self.Field(table_field, 'name'), self.Literal('%s..' % field_name))),
127 self.Constraint('=', self.Field(table_field, field_type), self.Literal(field_value)),
130 return self.Constraint('in', self.Field(table_id, 'id'), self.Query(fields, tables, constraints))
133 if self.cursor is None:
137 res = self.collection.database.connection._next(self.cursor)
142 if '_id' in self.fields:
143 document['_id'] = self.serializer.loads(res[0])
144 fields_without_id = filter(lambda x: x != '_id', self.fields)
145 for i in xrange(len(fields_without_id)):
146 if not res[i + 1] is None:
147 document[fields_without_id[i]] = self.serializer.loads(res[i + 1])