fixed issue 1251, imporved mongodb support, thanks Alan
This commit is contained in:
@@ -1 +1 @@
|
||||
Version 2.4.1-alpha.2+timestamp.2012.12.31.15.17.11
|
||||
Version 2.4.1-alpha.2+timestamp.2013.01.01.19.02.47
|
||||
|
||||
+128
-69
@@ -4913,6 +4913,7 @@ class MongoDBAdapter(NoSQLAdapter):
|
||||
'time': datetime.time,
|
||||
'datetime': datetime.datetime,
|
||||
'id': long,
|
||||
'mongo': unicode, # any Mongodb document (not implemented)
|
||||
'reference': long,
|
||||
'list:string': list,
|
||||
'list:integer': list,
|
||||
@@ -4928,6 +4929,21 @@ class MongoDBAdapter(NoSQLAdapter):
|
||||
if do_connect: self.find_driver(adapter_args)
|
||||
|
||||
m=None
|
||||
|
||||
import random
|
||||
try:
|
||||
from pymongo.objectid import ObjectId
|
||||
except ImportError:
|
||||
from bson.objectid import ObjectId
|
||||
try:
|
||||
from bson.son import SON
|
||||
except ImportError:
|
||||
from pymongo.son import SON
|
||||
|
||||
self.SON = SON
|
||||
self.ObjectId = ObjectId
|
||||
self.random = random
|
||||
|
||||
try:
|
||||
#Since version 2
|
||||
import pymongo.uri_parser
|
||||
@@ -4971,12 +4987,46 @@ class MongoDBAdapter(NoSQLAdapter):
|
||||
raise SyntaxError("This is not an official Mongodb uri (http://www.mongodb.org/display/DOCS/Connections) Error : %s" % inst)
|
||||
self.reconnect(connector,cursor=False)
|
||||
|
||||
def object_id(self, arg=None):
|
||||
""" Convert input to a valid Mongodb ObjectId instance
|
||||
|
||||
self.object_id("<random>") -> ObjectId (not unique) instance """
|
||||
if not arg:
|
||||
arg = 0
|
||||
if isinstance(arg, basestring):
|
||||
# we assume an integer as default input
|
||||
rawhex = len(arg.replace("0x", "").replace("L", "")) == 24
|
||||
if arg.isdigit() and (not rawhex):
|
||||
arg = int(arg)
|
||||
elif arg == "<random>":
|
||||
arg = int("0x%sL" % \
|
||||
str("".join([self.random.choice("0123456789abcdef") for x in \
|
||||
range(24)])), 0)
|
||||
elif arg.isalnum():
|
||||
if not arg.startswith("0x"):
|
||||
arg = "0x%s" % arg
|
||||
try:
|
||||
arg = int(arg, 0)
|
||||
except ValueError, e:
|
||||
raise ValueError("invalid objectid argument string: %s" % e)
|
||||
else:
|
||||
raise ValueError("invalid objectid argument string. requires an integer or base 16 value")
|
||||
elif isinstance(arg, self.ObjectId):
|
||||
return arg
|
||||
if not isinstance(arg, (int, long)):
|
||||
raise TypeError("object_id argument must be of type ObjectId or an objectid representable integer")
|
||||
if arg == 0:
|
||||
hexvalue = "".zfill(24)
|
||||
else:
|
||||
hexvalue = hex(arg)[2:].replace("L", "")
|
||||
return self.ObjectId(hexvalue)
|
||||
|
||||
def represent(self, obj, fieldtype):
|
||||
value = NoSQLAdapter.represent(self, obj, fieldtype)
|
||||
if fieldtype =='date':
|
||||
if value == None:
|
||||
return value
|
||||
t = datetime.time(0, 0, 0)#this piece of data can be stripped of based on the fieldtype
|
||||
t = datetime.time(0, 0, 0)#this piece of data can be stripped off based on the fieldtype
|
||||
return datetime.datetime.combine(value, t) #mongodb doesn't has a date object and so it must datetime, string or integer
|
||||
elif fieldtype == 'time':
|
||||
if value == None:
|
||||
@@ -4993,7 +5043,16 @@ class MongoDBAdapter(NoSQLAdapter):
|
||||
if safe==None:
|
||||
safe=self.safe
|
||||
ctable = self.connection[table._tablename]
|
||||
values = dict((k.name,self.represent(v,table[k.name].type)) for k,v in fields)
|
||||
values = dict()
|
||||
for k, v in fields:
|
||||
# avoid writing "id" name reserved form Mongodb
|
||||
if not k.name == "id":
|
||||
fieldname = k.name
|
||||
fieldtype = table[k.name].type
|
||||
if ("reference" in fieldtype) or (fieldtype=="id"):
|
||||
values[fieldname] = self.object_id(v)
|
||||
else:
|
||||
values[fieldname] = self.represent(v, fieldtype)
|
||||
ctable.insert(values,safe=safe)
|
||||
return int(str(values['_id']), 16)
|
||||
|
||||
@@ -5014,13 +5073,10 @@ class MongoDBAdapter(NoSQLAdapter):
|
||||
# therefor call __select() connection[table].find(query).count() Since this will probably reduce the return set?
|
||||
|
||||
def expand(self, expression, field_type=None):
|
||||
try:
|
||||
from pymongo.objectid import ObjectId
|
||||
except ImportError:
|
||||
from bson.objectid import ObjectId
|
||||
#if isinstance(expression,Field):
|
||||
# if expression.type=='id':
|
||||
# return {_id}"
|
||||
result = None
|
||||
if isinstance(expression, Query):
|
||||
# any query using 'id':=
|
||||
# set name as _id (as per pymongo/mongodb primary key)
|
||||
@@ -5028,61 +5084,42 @@ class MongoDBAdapter(NoSQLAdapter):
|
||||
# (if its not already)
|
||||
# if second arg is 0 convert to objectid
|
||||
if isinstance(expression.first,Field) and \
|
||||
expression.first.type == 'id':
|
||||
expression.first.name = '_id'
|
||||
if expression.second != 0 and \
|
||||
not isinstance(expression.second,ObjectId):
|
||||
if isinstance(expression.second,int):
|
||||
try:
|
||||
# Because the reference field is by default
|
||||
# an integer and therefore this must be an
|
||||
# integer to be able to work with other
|
||||
# databases
|
||||
expression.second = ObjectId(("%X" % expression.second))
|
||||
except:
|
||||
raise SyntaxError('The second argument must by an integer that can represent an objectid.')
|
||||
else:
|
||||
try:
|
||||
#But a direct id is also possible
|
||||
expression.second = ObjectId(expression.second)
|
||||
except:
|
||||
raise SyntaxError('second argument must be of type ObjectId or an objectid representable integer')
|
||||
elif expression.second == 0:
|
||||
expression.second = ObjectId('000000000000000000000000')
|
||||
return expression.op(expression.first, expression.second)
|
||||
((expression.first.type == 'id') or \
|
||||
("reference" in expression.first.type)):
|
||||
if expression.first.type == 'id':
|
||||
expression.first.name = '_id'
|
||||
# cast to Mongo ObjectId
|
||||
expression.second = self.object_id(expression.second)
|
||||
result = expression.op(expression.first, expression.second)
|
||||
if isinstance(expression, Field):
|
||||
if expression.type=='id':
|
||||
return "_id"
|
||||
result = "_id"
|
||||
else:
|
||||
return expression.name
|
||||
result = expression.name
|
||||
#return expression
|
||||
elif isinstance(expression, (Expression, Query)):
|
||||
if not expression.second is None:
|
||||
return expression.op(expression.first, expression.second)
|
||||
result = expression.op(expression.first, expression.second)
|
||||
elif not expression.first is None:
|
||||
return expression.op(expression.first)
|
||||
result = expression.op(expression.first)
|
||||
elif not isinstance(expression.op, str):
|
||||
return expression.op()
|
||||
result = expression.op()
|
||||
else:
|
||||
return expression.op
|
||||
result = expression.op
|
||||
elif field_type:
|
||||
return str(self.represent(expression,field_type))
|
||||
result = str(self.represent(expression,field_type))
|
||||
elif isinstance(expression,(list,tuple)):
|
||||
return ','.join(self.represent(item,field_type) for item in expression)
|
||||
result = ','.join(self.represent(item,field_type) for item in expression)
|
||||
else:
|
||||
return expression
|
||||
result = expression
|
||||
return result
|
||||
|
||||
def _select(self,query,fields,attributes):
|
||||
try:
|
||||
from bson.son import SON
|
||||
except ImportError:
|
||||
from pymongo.son import SON
|
||||
|
||||
if 'for_update' in attributes:
|
||||
logging.warn('mongodb does not support for_update')
|
||||
for key in set(attributes.keys())-set(('limitby','orderby','for_update')):
|
||||
if attributes[key]!=None:
|
||||
raise SyntaxError('invalid select attribute: %s' % key)
|
||||
logging.warn('select attribute not implemented: %s' % key)
|
||||
|
||||
new_fields=[]
|
||||
mongosort_list = []
|
||||
@@ -5108,7 +5145,7 @@ class MongoDBAdapter(NoSQLAdapter):
|
||||
else:
|
||||
limitby_skip = limitby_limit = 0
|
||||
|
||||
mongofields_dict = SON()
|
||||
mongofields_dict = self.SON()
|
||||
mongoqry_dict = {}
|
||||
for item in fields:
|
||||
if isinstance(item,SQLALL):
|
||||
@@ -5132,13 +5169,10 @@ class MongoDBAdapter(NoSQLAdapter):
|
||||
# need to define all the 'sql' methods gt,lt etc....
|
||||
|
||||
def select(self,query,fields,attributes,count=False,snapshot=False):
|
||||
try:
|
||||
from pymongo.objectid import ObjectId
|
||||
except ImportError:
|
||||
from bson.objectid import ObjectId
|
||||
tablename, mongoqry_dict, mongofields_dict, \
|
||||
mongosort_list, limitby_limit, limitby_skip = \
|
||||
self._select(query,fields,attributes)
|
||||
|
||||
ctable = self.connection[tablename]
|
||||
if count:
|
||||
return {'count' : ctable.find(
|
||||
@@ -5150,19 +5184,21 @@ class MongoDBAdapter(NoSQLAdapter):
|
||||
mongoqry_dict, mongofields_dict,
|
||||
skip=limitby_skip, limit=limitby_limit,
|
||||
sort=mongosort_list, snapshot=snapshot) # pymongo cursor object
|
||||
# DEBUG: print "mongo_list_dicts=%s" % mongo_list_dicts
|
||||
|
||||
rows = []
|
||||
### populate row in proper order
|
||||
colnames = [str(field) for field in fields]
|
||||
for k,record in enumerate(mongo_list_dicts):
|
||||
# for k,record in enumerate(mongo_list_dicts):
|
||||
for record in mongo_list_dicts:
|
||||
row=[]
|
||||
for fullcolname in colnames:
|
||||
colname = fullcolname.split('.')[1]
|
||||
column = '_id' if colname=='id' else colname
|
||||
if column in record:
|
||||
if column == '_id' and isinstance(
|
||||
record[column],ObjectId):
|
||||
value = int(str(record[column]),16)
|
||||
# if column in ('_id', "id") and isinstance(
|
||||
# record[column], self.ObjectId):
|
||||
if isinstance(record[column], self.ObjectId):
|
||||
value = int(str(record[column]), 16)
|
||||
elif column != '_id':
|
||||
value = record[column]
|
||||
else:
|
||||
@@ -5172,7 +5208,12 @@ class MongoDBAdapter(NoSQLAdapter):
|
||||
row.append(value)
|
||||
rows.append(row)
|
||||
processor = attributes.get('processor',self.parse)
|
||||
return processor(rows,fields,colnames,False)
|
||||
result = processor(rows,fields,colnames,False)
|
||||
# we need to point .id to ._id for scaffolding actions
|
||||
for row in result:
|
||||
if hasattr(row, "_id"):
|
||||
row.id = row._id
|
||||
return result
|
||||
|
||||
def INVERT(self,first):
|
||||
#print "in invert first=%s" % first
|
||||
@@ -5198,43 +5239,61 @@ class MongoDBAdapter(NoSQLAdapter):
|
||||
filter = self.expand(query)
|
||||
f_v = []
|
||||
|
||||
|
||||
modify = { '$set' : dict(((k.name,self.represent(v,k.type)) for k,v in fields)) }
|
||||
return modify,filter
|
||||
|
||||
#TODO implement update
|
||||
#TODO implement set operator
|
||||
#TODO implement find and modify
|
||||
#todo implement complex update
|
||||
# TODO implement set operator
|
||||
# TODO implement find and modify
|
||||
# TODO implement complex update
|
||||
|
||||
def update(self,tablename,query,fields,safe=None):
|
||||
if safe==None:
|
||||
safe=self.safe
|
||||
#return amount of adjusted rows or zero, but no exceptions related not finding the result
|
||||
#return amount of adjusted rows or zero, but no exceptions
|
||||
# @ related not finding the result
|
||||
if not isinstance(query,Query):
|
||||
raise RuntimeError("Not implemented")
|
||||
amount = self.count(query,False)
|
||||
modify,filter = self.oupdate(tablename,query,fields)
|
||||
try:
|
||||
result = self.connection[tablename].update(filter,
|
||||
modify, multi=True, safe=safe)
|
||||
if safe:
|
||||
return self.connection[tablename].update(filter,modify,multi=True,safe=safe).n
|
||||
try:
|
||||
# if result count is available fetch it
|
||||
return result["n"]
|
||||
except (KeyError, AttributeError, TypeError):
|
||||
return amount
|
||||
else:
|
||||
amount =self.count(query)
|
||||
self.connection[tablename].update(filter,modify,multi=True,safe=safe)
|
||||
return amount
|
||||
except:
|
||||
except Exception, e:
|
||||
#TODO Reverse update query to verifiy that the query succeded
|
||||
return 0
|
||||
raise RuntimeError("uncaught exception when updating rows: %s" % e)
|
||||
|
||||
"""
|
||||
(NOTE: missing method for this docstring)
|
||||
An special update operator that enables the update of specific field
|
||||
return a dict
|
||||
"""
|
||||
|
||||
|
||||
|
||||
#this function returns a dict with the where clause and update fields
|
||||
def _update(self,tablename,query,fields):
|
||||
return str(self.oupdate(tablename,query,fields))
|
||||
|
||||
def delete(self, tablename, query, safe=None):
|
||||
if safe is None:
|
||||
safe = self.safe
|
||||
amount = 0
|
||||
amount = self.count(query,False)
|
||||
if not isinstance(query, Query):
|
||||
raise RuntimeError("query type %s is not supported" % type(query))
|
||||
filter = self.expand(query)
|
||||
self._delete(tablename, filter, safe=safe)
|
||||
return amount
|
||||
|
||||
def _delete(self, tablename, filter, safe=None):
|
||||
return self.connection[tablename].remove(filter, safe=safe)
|
||||
|
||||
def bulk_insert(self, table, items):
|
||||
return [self.insert(table,item) for item in items]
|
||||
|
||||
@@ -7451,8 +7510,8 @@ class SQLALL(object):
|
||||
def __str__(self):
|
||||
return ', '.join([str(field) for field in self._table])
|
||||
|
||||
|
||||
class Reference(int):
|
||||
# class Reference(int):
|
||||
class Reference(long):
|
||||
|
||||
def __allocate(self):
|
||||
if not self._record:
|
||||
|
||||
Reference in New Issue
Block a user