db.t.f.epoch(), thanks Niphlod

This commit is contained in:
mdipierro
2012-09-14 15:51:58 -05:00
parent 47bcab6b26
commit 5f503d0427
3 changed files with 78 additions and 58 deletions
+1 -1
View File
@@ -1 +1 @@
Version 2.0.9 (2012-09-14 09:42:11) stable
Version 2.0.9 (2012-09-14 15:51:53) stable
+69 -50
View File
@@ -40,7 +40,7 @@ Example of usage:
>>> # from dal import DAL, Field
### create DAL connection (and create DB if it doesn't exist)
>>> db = DAL(('sqlite://storage.sqlite','mysql://a:b@localhost/x'),
>>> db = DAL(('sqlite://storage.sqlite','mysql://a:b@localhost/x'),
... folder=None)
### define a table 'person' (create/alter as necessary)
@@ -330,7 +330,7 @@ if not 'google' in DRIVERS:
DRIVERS.append('Sybase(Sybase)')
except ImportError:
logger.debug('no Sybase driver')
try:
import kinterbasdb
DRIVERS.append('Interbase(kinterbasdb)')
@@ -342,7 +342,7 @@ if not 'google' in DRIVERS:
import fdb
DRIVERS.append('Firbird(fdb)')
except ImportError:
logger.debug('no Firebird driver fdb')
logger.debug('no Firebird driver fdb')
#####
try:
import firebirdsql
@@ -660,7 +660,7 @@ class BaseAdapter(ConnectionPool):
def find_driver(self,adapter_args,uri=None):
if hasattr(self,'driver') and self.driver!=None:
return
return
drivers_available = [driver for driver in self.drivers
if driver in globals()]
if uri:
@@ -668,7 +668,7 @@ class BaseAdapter(ConnectionPool):
request_driver = items[1] if len(items)>1 else None
else:
request_driver = None
request_driver = request_driver or adapter_args.get('driver')
request_driver = request_driver or adapter_args.get('driver')
if request_driver:
if request_driver in drivers_available:
self.driver_name = request_driver
@@ -680,7 +680,7 @@ class BaseAdapter(ConnectionPool):
self.driver = globals().get(self.driver_name)
else:
raise RuntimeError, "no driver available %s", self.drivers
def __init__(self, db,uri,pool_size=0, folder=None, db_codec='UTF-8',
credential_decoder=IDENTITY, driver_args={},
@@ -799,7 +799,7 @@ class BaseAdapter(ConnectionPool):
else:
schema = parms[0]
ftype = "SELECT AddGeometryColumn ('%%(schema)s', '%%(tablename)s', '%%(fieldname)s', %%(srid)s, '%s', %%(dimension)s);" % types[geotype]
ftype = ftype % dict(schema=schema,
ftype = ftype % dict(schema=schema,
tablename=tablename,
fieldname=field_name, srid=srid,
dimension=dimension)
@@ -890,7 +890,7 @@ class BaseAdapter(ConnectionPool):
else:
table._dbt = pjoin(
dbpath, '%s_%s.table' % (table._db._uri_hash, tablename))
if table._dbt:
table._loggername = pjoin(dbpath, 'sql.log')
logfile = self.file_open(table._loggername, 'a')
@@ -1002,7 +1002,7 @@ class BaseAdapter(ConnectionPool):
query = ['ALTER TABLE %s DROP %s;' % (tablename, key)]
metadata_change = True
elif sql_fields[key]['sql'] != sql_fields_old[key]['sql'] \
and not (key in table.fields and
and not (key in table.fields and
isinstance(table[key].type, SQLCustomType)) \
and not sql_fields[key]['type'].startswith('reference')\
and not sql_fields[key]['type'].startswith('double')\
@@ -1073,6 +1073,9 @@ class BaseAdapter(ConnectionPool):
def EXTRACT(self, first, what):
return "EXTRACT(%s FROM %s)" % (what, self.expand(first))
def EPOCH(self, first):
return self.EXTRACT(first, 'epoch')
def AGGREGATE(self, first, what):
return "%s(%s)" % (what, self.expand(first))
@@ -1273,7 +1276,7 @@ class BaseAdapter(ConnectionPool):
first = expression.first
second = expression.second
op = expression.op
if not second is None:
if not second is None:
return op(first, second)
elif not first is None:
return op(first)
@@ -1580,14 +1583,14 @@ class BaseAdapter(ConnectionPool):
"""
sql = self._select(query, fields, attributes)
cache = attributes.get('cache', None)
if cache and attributes.get('cacheable',False):
if cache and attributes.get('cacheable',False):
del attributes['cache']
(cache_model, time_expire) = cache
key = self.uri + '/' + sql
if len(key)>200: key = hashlib.md5(key).hexdigest()
args = (sql,fields,attributes)
return cache_model(
key,
key,
lambda self=self,args=args:self._select_aux(*args),
time_expire)
else:
@@ -1906,7 +1909,7 @@ class BaseAdapter(ConnectionPool):
for (i,row) in enumerate(rows):
new_row = Row()
for (j,colname) in enumerate(colnames):
value = row[j]
value = row[j]
tmp = tmps[j]
if tmp:
(tablename,fieldname,table,field,ft) = tmp
@@ -1920,14 +1923,14 @@ class BaseAdapter(ConnectionPool):
if field.filter_out:
value = field.filter_out(value)
colset[fieldname] = value
# for backward compatibility
if ft=='id' and fieldname!='id' and \
not 'id' in table.fields:
colset['id'] = value
if ft == 'id' and not cacheable:
# temporary hack to deal with
# temporary hack to deal with
# GoogleDatastoreAdapter
# references
if isinstance(self, GoogleDatastoreAdapter):
@@ -2027,8 +2030,11 @@ class SQLiteAdapter(BaseAdapter):
'second': (17, 19),
}
try:
(i, j) = table[lookup]
return int(s[i:j])
if lookup != 'epoch':
(i, j) = table[lookup]
return int(s[i:j])
else:
return time.mktime(datetime.datetime.strptime(s, '%Y-%m-%d %H:%M:%S').timetuple())
except:
return None
@@ -2280,6 +2286,9 @@ class MySQLAdapter(BaseAdapter):
return 'SUBSTRING(%s,%s,%s)' % (self.expand(field),
parameters[0], parameters[1])
def EPOCH(self, first):
return "UNIX_TIMESTAMP(%s)" % self.expand(first)
def _drop(self,table,mode):
# breaks db integrity but without this mysql does not drop table
return ['SET FOREIGN_KEY_CHECKS=0;','DROP TABLE %s;' % table,
@@ -2984,7 +2993,7 @@ class MSSQLAdapter(BaseAdapter):
# (in the form of arg1=value1&arg2=value2&...)
# Default values (drivers like FreeTDS insist on uppercase parameter keys)
argsdict = { 'DRIVER':'{SQL Server}' }
urlargs = m.group('urlargs') or ''
urlargs = m.group('urlargs') or ''
for argmatch in self.REGEX_ARGPATTERN.finditer(urlargs):
argsdict[str(argmatch.group('argkey')).upper()] = argmatch.group('argvalue')
urlargs = ';'.join(['%s=%s' % (ak, av) for (ak, av) in argsdict.iteritems()])
@@ -3009,6 +3018,9 @@ class MSSQLAdapter(BaseAdapter):
return rows[minimum:]
return rows[minimum:maximum]
def EPOCH(self, first):
return "DATEDIFF(second, '1970-01-01 00:00:00', %s)" % self.expand(first)
# GIS functions
# No STAsGeoJSON in MSSQL
@@ -3156,7 +3168,7 @@ class SybaseAdapter(MSSQLAdapter):
logger.error('NdGpatch error')
raise e
else:
m = self.REGEX_URI.match(uri)
m = self.REGEX_URI.match(uri)
if not m:
raise SyntaxError, \
"Invalid URI string in DAL: %s" % self.uri
@@ -3228,6 +3240,9 @@ class FireBirdAdapter(BaseAdapter):
def RANDOM(self):
return 'RAND()'
def EPOCH(self, first):
return "DATEDIFF(second, '1970-01-01 00:00:00', %s)" % self.expand(first)
def NOT_NULL(self,default,field_type):
return 'DEFAULT %s NOT NULL' % self.represent(default,field_type)
@@ -3252,7 +3267,7 @@ class FireBirdAdapter(BaseAdapter):
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}):
adapter_args={}):
self.db = db
self.dbengine = "firebird"
self.uri = uri
@@ -3283,7 +3298,7 @@ class FireBirdAdapter(BaseAdapter):
user = credential_decoder(user),
password = credential_decoder(password),
charset = charset)
def connect(driver_args=driver_args):
return self.driver.connect(**driver_args)
self.pool_connection(connect)
@@ -3343,7 +3358,7 @@ class FireBirdEmbeddedAdapter(FireBirdAdapter):
user=credential_decoder(user),
password=credential_decoder(password),
charset=charset)
def connect(driver_args=driver_args):
return self.driver.connect(**driver_args)
self.pool_connection(connect)
@@ -4416,7 +4431,7 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
else:
projection.append(f.name)
# projection's can't include 'id'.
# projection's can't include 'id'.
# it will be added to the result later
query_projection = [
p for p in projection if \
@@ -4912,7 +4927,7 @@ class MongoDBAdapter(NoSQLAdapter):
print "in expand and this is a query"
# any query using 'id':=
# set name as _id (as per pymongo/mongodb primary key)
# convert second arg to an objectid field
# convert second arg to an objectid field
# (if its not already)
# if second arg is 0 convert to objectid
if isinstance(expression.first,Field) and \
@@ -4922,9 +4937,9 @@ class MongoDBAdapter(NoSQLAdapter):
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
# 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:
@@ -5212,27 +5227,27 @@ class MongoDBAdapter(NoSQLAdapter):
return result
def ADD(self, first, second):
raise NotImplementedError, "This must yet be replaced with javescript in order to accomplish this. Sorry"
raise NotImplementedError, "This must yet be replaced with javascript in order to accomplish this. Sorry"
return '%s + %s' % (self.expand(first), self.expand(second, first.type))
def SUB(self, first, second):
raise NotImplementedError, "This must yet be replaced with javescript in order to accomplish this. Sorry"
raise NotImplementedError, "This must yet be replaced with javascript in order to accomplish this. Sorry"
return '(%s - %s)' % (self.expand(first), self.expand(second, first.type))
def MUL(self, first, second):
raise NotImplementedError, "This must yet be replaced with javescript in order to accomplish this. Sorry"
raise NotImplementedError, "This must yet be replaced with javascript in order to accomplish this. Sorry"
return '(%s * %s)' % (self.expand(first), self.expand(second, first.type))
def DIV(self, first, second):
raise NotImplementedError, "This must yet be replaced with javescript in order to accomplish this. Sorry"
raise NotImplementedError, "This must yet be replaced with javascript in order to accomplish this. Sorry"
return '(%s / %s)' % (self.expand(first), self.expand(second, first.type))
def MOD(self, first, second):
raise NotImplementedError, "This must yet be replaced with javescript in order to accomplish this. Sorry"
raise NotImplementedError, "This must yet be replaced with javascript in order to accomplish this. Sorry"
return '(%s %% %s)' % (self.expand(first), self.expand(second, first.type))
def AS(self, first, second):
raise NotImplementedError, "This must yet be replaced with javescript in order to accomplish this. Sorry"
raise NotImplementedError, "This must yet be replaced with javascript in order to accomplish this. Sorry"
return '%s AS %s' % (self.expand(first), second)
#We could implement an option that simulates a full featured SQL database. But I think the option should be set explicit or implemented as another library.
@@ -5344,31 +5359,31 @@ class MongoDBAdapter(NoSQLAdapter):
#TODO javascript has math
def ADD(self, first, second):
raise NotImplementedError, "This must yet be replaced with javescript in order to accomplish this. Sorry"
raise NotImplementedError, "This must yet be replaced with javascript in order to accomplish this. Sorry"
return '%s + %s' % (self.expand(first), self.expand(second, first.type))
#TODO javascript has math
def SUB(self, first, second):
raise NotImplementedError, "This must yet be replaced with javescript in order to accomplish this. Sorry"
raise NotImplementedError, "This must yet be replaced with javascript in order to accomplish this. Sorry"
return '(%s - %s)' % (self.expand(first), self.expand(second, first.type))
#TODO javascript has math
def MUL(self, first, second):
raise NotImplementedError, "This must yet be replaced with javescript in order to accomplish this. Sorry"
raise NotImplementedError, "This must yet be replaced with javascript in order to accomplish this. Sorry"
return '(%s * %s)' % (self.expand(first), self.expand(second, first.type))
#TODO javascript has math
def DIV(self, first, second):
raise NotImplementedError, "This must yet be replaced with javescript in order to accomplish this. Sorry"
raise NotImplementedError, "This must yet be replaced with javascript in order to accomplish this. Sorry"
return '(%s / %s)' % (self.expand(first), self.expand(second, first.type))
#TODO javascript has math
def MOD(self, first, second):
raise NotImplementedError, "This must yet be replaced with javescript in order to accomplish this. Sorry"
raise NotImplementedError, "This must yet be replaced with javascript in order to accomplish this. Sorry"
return '(%s %% %s)' % (self.expand(first), self.expand(second, first.type))
#TODO javascript can do this
def AS(self, first, second):
raise NotImplementedError, "This must yet be replaced with javescript in order to accomplish this. Sorry"
raise NotImplementedError, "This must yet be replaced with javascript in order to accomplish this. Sorry"
return '%s AS %s' % (self.expand(first), second)
#We could implement an option that simulates a full featured SQL database. But I think the option should be set explicit or implemented as another library.
@@ -6455,7 +6470,7 @@ class Row(object):
def values(self):
return self.__dict__.values()
def __iter__(self):
return self.__dict__.__iter__()
@@ -6648,7 +6663,7 @@ class DAL(object):
db.define_table('tablename', Field('fieldname1'),
Field('fieldname2'))
"""
@staticmethod
def set_folder(folder):
"""
@@ -7211,7 +7226,7 @@ def index():
Added 2012-08-24 "fields" and "colnames" optional arguments. If either
is provided, the results cursor returned by the DB driver will be
converted to a DAL Rows object using the db._adapter.parse() method.
The "fields" argument is a list of DAL Field objects that match the
fields returned from the DB. The Field objects should be part of one or
more Table objects defined on the DAL object. The "fields" list can
@@ -7224,14 +7239,14 @@ def index():
can be specified as a list of field names in tablename.fieldname format.
Again, these should represent tables and fields defined on the DAL
object.
It is also possible to specify both "fields" and the associated
"colnames". In that case, "fields" can also include DAL Expression
objects in addition to Field objects. For Field objects in "fields",
the associated "colnames" must still be in tablename.fieldname format.
For Expression objects in "fields", the associated "colnames" can
be any arbitrary labels.
Note, the DAL Table objects referred to by "fields" or "colnames" can
be dummy tables and do not have to represent any real tables in the
database. Also, note that the "fields" and "colnames" must be in the
@@ -7390,7 +7405,7 @@ class Table(object):
db.users.insert(name='me') # print db.users._insert(...) to see SQL
db.users.drop()
"""
def __init__(
self,
db,
@@ -8075,6 +8090,10 @@ class Expression(object):
db = self.db
return Expression(db, db._adapter.EXTRACT, self, 'second', 'integer')
def epoch(self):
db = self.db
return Expression(db, db._adapter.EPOCH, self, None, 'integer')
def __getslice__(self, start, stop):
db = self.db
if start < 0:
@@ -8215,7 +8234,7 @@ class Expression(object):
def st_asgeojson(self, precision=15, options=0, version=1):
return Expression(self.db, self.db._adapter.ST_ASGEOJSON, self,
dict(precision=precision, options=options,
dict(precision=precision, options=options,
version=version), 'dict')
def st_astext(self):
@@ -8464,7 +8483,7 @@ class Field(Expression):
elif not filename:
filename = file.name
filename = os.path.basename(filename.replace('/', os.sep)\
.replace('\\', os.sep))
.replace('\\', os.sep))
m = REGEX_STORE_PATTERN.search(filename)
extension = m and m.group('e') or 'txt'
uuid_key = web2py_uuid().replace('-', '')[-16:]
@@ -8752,7 +8771,7 @@ class Set(object):
key,
(lambda self=self,distinct=distinct: \
db._adapter.count(self.query,distinct)),
time_expire)
time_expire)
return db._adapter.count(self.query,distinct)
def select(self, *fields, **attributes):
@@ -8870,7 +8889,7 @@ class RecordUpdater(object):
self.colset, self.table, self.id = colset, table, id
def __call__(self, **fields):
colset, table, id = self.colset, self.table, self.id
colset, table, id = self.colset, self.table, self.id
newfields = fields or dict(colset)
for fieldname in newfields.keys():
if not fieldname in table.fields or table[fieldname].type=='id':
@@ -9192,7 +9211,7 @@ class Rows(object):
def xml(self,strict=False,row_name='row',rows_name='rows'):
"""
serializes the table using sqlhtml.SQLTABLE (if present)
"""
"""
if strict:
ncols = len(self.colnames)
def f(row,field,indent=' '):
+8 -7
View File
@@ -311,12 +311,13 @@ class TestDatetime(unittest.TestCase):
9, 30)), 3)
self.assertEqual(len(db(db.t.a == datetime.datetime(1971, 12,
21, 11, 30)).select()), 1)
self.assertEqual(len(db(db.t.a.year() == 1971).select()), 2)
self.assertEqual(len(db(db.t.a.month() == 12).select()), 2)
self.assertEqual(len(db(db.t.a.day() == 21).select()), 3)
self.assertEqual(len(db(db.t.a.hour() == 11).select()), 1)
self.assertEqual(len(db(db.t.a.minutes() == 30).select()), 3)
self.assertEqual(len(db(db.t.a.seconds() == 0).select()), 3)
self.assertEqual(db(db.t.a.year() == 1971).count(), 2)
self.assertEqual(db(db.t.a.month() == 12).count(), 2)
self.assertEqual(db(db.t.a.day() == 21).count(), 3)
self.assertEqual(db(db.t.a.hour() == 11).count(), 1)
self.assertEqual(db(db.t.a.minutes() == 30).count(), 3)
self.assertEqual(db(db.t.a.seconds() == 0).count(), 3)
self.assertEqual(db(db.t.a.epoch()<365*24*3600).count(),1)
db.t.drop()
@@ -329,7 +330,7 @@ class TestExpressions(unittest.TestCase):
self.assertEqual(db.t.insert(a=2), 2)
self.assertEqual(db.t.insert(a=3), 3)
self.assertEqual(db(db.t.a == 3).update(a=db.t.a + 1), 1)
self.assertEqual(len(db(db.t.a == 4).select()), 1)
self.assertEqual(db(db.t.a == 4).count(), 1)
db.t.drop()