Some people have asked so here is a prototype of the new DAL. It is
far come complete. The tests at the end work. It cannot jet be
integrated in web2py because the part for auto-closing transactions if
missing.
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"web2py Web Framework" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/web2py?hl=en
-~----------~----~----~----~------~----~------~--~---
import os
import datetime
import logging
logging.level=0
drivers=[]
try:
from pysqlite2 import dbapi2 as sqlite3
drivers.append('pysqlite2')
except:
try:
import sqlite3
drivers.append('SQLite3')
except:
logging.debug('no sqlite3 or pysqlite2.dbapi2 driver')
class AdaptorSQLite:
name = 'sqlite'
def __init__(self,db,uri):
"""
connects to database and allocates
self.connection
self.cursor
"""
(self._db,self.uri)=(db,uri)
def execute(self,command):
logging.info('web2py.gluon.AdaptorSQLite:'+command)
if isinstance(command, str): self.cursor.execute(command)
else: raise SyntaxError, "invalid command:, ", repr(command)
def quote(self,value):
return "'%s'" % value.replace("'","''")
def r(self,value,type='s'):
if isinstance(value,Field):
return value._truefullname
if isinstance(value,(Query, Expression)):
return value.command
if value==None:
return 'NULL'
if isinstance(value, unicode):
value = value.encode('utf-8')
if type[0] == 'i':
return str(int(value))
if type[0] == 'd':
return str(float(value))
if type[0] == 'r':
return str(int(value))
if isinstance(value,(datetime.date,datetime.datetime,datetime.time)):
return "'%s'" % value.isoformat()
if type=='boolean':
if value and not str(value).upper()[0] in ('0','F'): return 'T'
return 'F'
if type=='blob':
value = base64.b64encode(str(value))
if not isinstance(value,str):
value=str(value)
return self.quote(value)
def migrate(self,table):
"""
creates or alters the table as requires
"""
self.execute(self.CREATE(table)) ### do migrations here
self.COMMIT()
def lastrowid(self,table):
return self.cursor.lastrowid
def rowcount(self):
return self.cursor.rowcount
def insert(self,table,**fields):
[self.execute(q) for q in self.INSERT(table,**fields)]
return self.lastrowid(table)
def drop(self,table):
[self.execute(q) for q in self.DROP(table)]
def truncate(self,table):
[self.execute(q) for q in self.TRUNCATE(table)]
def count(self,query):
[self.execute(q) for q in self.COUNT(query)]
return self.cursor.fetchone()[0]
def update(self,query,**fields):
[self.execute(q) for q in self.UPDATE(query,**fields)]
return self.rowcount()
def select(self,query,*fields,**args):
items=self.SELECT(query,*fields,**args)
[self.execute(q) for q in items[1:]]
response=self.convert(self.cursor.fetchall(),items[0])
return Rows(self.db,response,items[0])
def convert(self,items,columns):
nc=range(len(columns))
for i in range(len(items)):
item=items[i]
newitem=[]
for j in nc:
value=item[j]
column=columns[j]
### ADD MAMBO JUMBO
newitem.append(value)
items[i]=tuple(newitem)
return items
def BEGIN(self):
pass
def AND(self,first,second):
return '(%s) AND (%s)' % (self.r(first),self.r(second))
def OR(self,first,second):
return '(%s) OR (%s)' % (self.r(first),self.r(second))
def NOT(self,first,second):
return 'NOT (%s)' % (self.r(first))
def COMMA(self,first,second):
return '%s, %s' % (self.r(first),self.r(second))
def DESC(self,first):
return '%s DESC' % (self.r(first))
def EQ(self,first,second):
return '%s = %s' % (self.r(first),self.r(second,first.type))
def NEQ(self,first,second):
return '%s <> %s' % (self.r(first),self.r(second,first.type))
def LT(self,first,second):
return '%s < %s' % (self.r(first),self.r(second,first.type))
def GT(self,first,second):
return '%s > %s' % (self.r(first),self.r(second,first.type))
def LE(self,first,second):
return '%s <= %s' % (self.r(first),self.r(second,first.type))
def GE(self,first,second):
return '%s >= %s' % (self.r(first),self.r(second,first.type))
def ADD(self,first,second):
return '%s + %s' % (self.r(first),self.r(second,first.type))
def SUM(self,first,second):
return '%s - %s' % (self.r(first),self.r(second,first.type))
def MUL(self,first,second):
return '%s * %s' % (self.r(first),self.r(second,first.type))
def DIV(self,first,second):
return '%s / %s' % (self.r(first),self.r(second,first.type))
def LIKE(self,first,second):
return '%s LIKE %s' % (self.r(first),self.r(second))
def BELONGS(self,first,second):
return '%s IN %s' % (self.r(first),self.r(second))
def SUM(self,first):
return 'SUM(%s)' % (self.r(first))
def MIN(self,first):
return 'MIN(%s)' % (self.r(first))
def MAX(self,first):
return 'MAX(%s)' % (self.r(first))
def COUNT(self,first):
return 'COUNT(%s)' % (self.r(first))
def YEAR(self,first):
return "EXTRACT('year',%s)" % (self.r(first))
def MONTH(self,first):
return "EXTRACT('month',%s)" % (self.r(first))
def DAY(self,first):
return "EXTRACT('day',%s)" % (self.r(first))
def HOUR(self,first):
return "EXTRACT('hour',%s)" % (self.r(first))
def MINUTE(self,first):
return "EXTRACT('minute',%s)" % (self.r(first))
def SECOND(self,first):
return "EXTRACT('second',%s)" % (self.r(first))
def UPPER(self,first):
return 'UPPER(%s)' % (self.r(first))
def LOWER(self,first):
return 'LOWER(%s)' % (self.r(first))
def ORDERBY(self,query,first):
return '%s ORDER BY %s' % (query, self.r(first))
def GROUPBY(self,query,first):
return '%s GROUP BY %s' % (query, self.r(first))
def LIMITBY(self,query,mimimum,maximum):
return '%s LIMIT %s OFFSET %s' % (query, maximum-minimum,minimum+1)
def ISNULL(self):
return 'IS NULL'
def ISNOTNULL(self):
return 'IS NOT NULL'
def NOTNULL(self,default):
return 'NOT NULL DEFAULT %s' % self.r(default)
def UNIQUE(self):
return 'UNIQUE'
def SUBSTR(self,first,start,stop):
return 'SUBSTR(%s,%s,%s)' % (self.r(first),start+1,stop-start)
def RANDOM(self):
return 'RANDOM()'
def INSERT(self,table,**fields):
keys = ', '.join(fields.keys())
values = ', '.join([self.r(v,table[k].type) for (k,v) in fields.items()])
return ('INSERT INTO %s(%s) VALUES(%s);' % (table._tablename,keys,values),)
def DROP(self,table):
return ('DROP TABLE %s;' % table._tablename,)
def TRUNCATE(self,table,mode=None):
if self.name == 'sqlite':
return ('DELETE FROM %s;' % table._tablename,
"DELETE FROM sqlite_sequence WHERE name='%s';" \
% table._tablename)
return ('TRUNCATE %s%s;' % (table._tablename, (mode and ' '+mode) or ''),)
def COUNT(self,query):
tables=', '.join(query._tbs) ### WAY THSES ARE NOT REALNAMES
command = query.command and ' WHERE '+query.command or ''
return ('SELECT COUNT(*) FROM %s%s;' % (tables,command),)
def UPDATE(self,query,**fields):
if len(query._tbs)!=1: raise SyntaxError, "You cannot update a join"
s=', '.join(['%s=%s' % (k,self.r(v)) for k,v in fields.items()])
command = query.command and ' WHERE '+query.command or ''
return ('UPDATE %s SET %s%s;' % (query._tbs[0],s,command),)
def SELECT(self,query,*fields,**args):
### FIX THIS TO CONSIDER ALIAS AND TRUENAMES
if not fields: fields=[query._db[t].ALL for t in query._tbs]
newfields=[]
for field in fields:
if isinstance(field,list): newfields.extend(field)
elif isinstance(field,str): newfields.append(field)
else: newfields.append(self.r(field))
tables=[field.split('.')[0] for field in newfields]
fieldnames=', '.join(newfields)
if query._tbs: tables=list(set(tables).union(set(query._tbs)))
##### WAIT, NOT REAL NAMES
command = query.command and ' WHERE '+query.command or ''
return (newfields, 'SELECT %s FROM %s%s;' % (fieldnames,tables[0],command))
def CREATE(self,table):
items=[]
for fieldname in table.fields:
field=table[fieldname]
items.append('%s %s' % (field._truename,
self.FIELD_TYPES[field.type] % field.__dict__))
sql='CREATE TABLE %s (\n\t%s\n);' % (table._truename,',\n\t'.join(items))
return sql
def COMMIT(self):
self.connection.commit()
def ROLLBACK(self):
self.connection.rollback()
def CLOSE(self):
self.connection.close()
FIELD_TYPES = {
'boolean': 'CHAR(1)',
'string': 'CHAR(%(length)s)',
'text': 'TEXT',
'password': 'CHAR(%(length)s)',
'blob': 'BLOB',
'upload': 'CHAR(128)',
'integer': 'INTEGER',
'double': 'DOUBLE',
'date': 'DATE',
'time': 'TIME',
'datetime': 'TIMESTAMP',
'id': 'INTEGER PRIMARY KEY AUTOINCREMENT',
'reference': 'REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
}
@staticmethod
def extract(lookup, s):
table = {
'year': (0, 4), 'month': (5, 7), 'day': (8, 10),
'hour': (11, 13), 'minute': (14, 16), 'second': (17, 19),
}
try:
(i, j) = table[lookup]
return int(s[i:j])
except:
return None
def __init__(self,db,uri,pool_size=0,folder='./'):
self.db=db
self.uri = uri
self.pool_size = pool_size
self.folder = folder
path = uri[9:]
if path[0]!='/': path = os.path.join(folder,path)
#self.pool_connection(lambda : sqlite3.Connection(path,
# check_same_thread=False))
self.connection=sqlite3.Connection(path)
self.connection.create_function('EXTRACT', 2, self.extract)
self.cursor = self.connection.cursor()
#self.register_instance()
self.BEGIN()
class Storage(dict):
def __getattr__(self,key):
return self.get(key,None)
def __setattr__(self,key,value):
dict.__setattr__(self,key,value)
class DB(Storage):
adaptors={
'sqlite':AdaptorSQLite,
#'mysql':AdaptorMySQL,
#'postgres':AdaptorPostgres,
#'mssql':AdaptorMSSQL,
#'firebird':AdaptorFireBird,
#'oracle':AdaptorOracle,
#'informix':AdaptorInformix,
#'db2':AdaptorDB2,
}
def __init__(self, uri='sqlite://storage.sqlite'):
adaptor=DB.adaptors.get(uri.split('://')[0],None)
if not adaptor: raise SyntaxError, "database not supported"
self._adaptor=adaptor(self,uri)
self.tables=[]
def define_table(self,name,*fields,**args):
self[name]=table=Table(self,name,*fields,**args)
self._adaptor.migrate(table)
self.tables.append(table._tablename)
def __call__(self,query=None):
if not query: return Query(None,self,[])
return query
def commit(self):
self._adaptor.COMMIT()
def rollback(self):
self._adaptor.ROLLBACK()
def close(self):
self._adaptor.CLOSE()
def __str__(self):
return self._adaptor.uri
class Table(Storage):
def __init__(self,db,name,*fields,**args):
self._db=db
self._tablename=name
self._truename=args.get('real_name',name)
self._args=args
fields=[Field('id','id')]+list(fields)
self.fields=[field._name for field in fields]
self.ALL=[]
for field in fields:
self.__dict__[field._name]=field
field._tablename=self._tablename
field._tbs=[field._tablename]
field._fullname=self._tablename+'.'+field._name
field._truefullname=self._truename+'.'+field._truename
field._db=db
self.ALL.append(field._fullname)
def with_alias(self,name):
### this changes the name of tables as they apper in fields realnames
### and SELECT tables
pass
def _insert(self,**fields):
return self._db._adaptor.INSERT(self,**fields)
def insert(self,**fields):
return self._db._adaptor.insert(self,**fields)
def _drop(self):
return self._db._adaptor.DROP(self)
def drop(self):
return self._db._adaptor.drop(self)
def _truncate(self):
return self._db._adaptor.TRUNCATE(self)
def truncate(self):
return self._db._adaptor.truncate(self)
def __getitem__(self,id):
try:
id=int(id)
except:
return self.__dict__[id]
else:
rows=self._db(self.id==id).select()
if rows: return rows[0]
return None
def __setitem__(self,id,fields):
self._db(self.id==id).update(**fields)
def __delitem__(self,id):
self._db(self.id==id).delete()
def __str__(self):
return self._tablename
class Expression(object):
def __init__(self,command,db=None,tbs=[]):
(self.command,self._db,self._tbs)=(command,db,tbs)
def __or__(self,other):
return Expression(self._db._adaptor.COMMA(self,other),self._db,self._tbs)
def __invert__(self):
return Expression(self._db._adaptor.DESC(self),self._db,self._tbs)
def __eq__(self,other):
return Query(self._db._adaptor.EQ(self,other),self._db,self._tbs)
def __ne__(self,other):
return Query(self._db._adaptor.NEQ(self,other),self._db,self._tbs)
def __lt__(self,other):
return Query(self._db._adaptor.LT(self,other),self._db,self._tbs)
def __gt__(self,other):
return Query(self._db._adaptor.GT(self,other),self._db,self._tbs)
def __le__(self,other):
return Query(self._db._adaptor.LE(self,other),self._db,self._tbs)
def __gr__(self,other):
return Query(self._db._adaptor.GE(self,other),self._db,self._tbs)
def like(self,other):
return Query(self._db._adaptor.LIKE(self,other),self._db,self._tbs)
def belongs(self,other):
return Query(self._db._adaptor.BELONGS(self,other),self._db,self._tbs)
def __add__(self,other):
return Query(self._db._adaptor.ADD(self,other),self._db,self._tbs)
def __sub__(self,other):
return Query(self._db._adaptor.SUB(self,other),self._db,self._tbs)
def __mul__(self,other):
return Query(self._db._adaptor.MUL(self,other),self._db,self._tbs)
def __div__(self,other):
return Query(self._db._adaptor.DIV(self,other),self._db,self._tbs)
def __str__(self):
return self.command
class Query(object):
def __init__(self,command,db=None,tbs=[]):
(self.command,self._db,self._tbs)=(command,db,tbs)
def __and__(self,other):
return Query(self._db._adaptor.AND(self,other),self._db,self._tbs)
def __or__(self,other):
return Query(self._db._adaptor.OR(self,other),self._db,self._tbs)
def __invert__(self,other):
return Query(self._db._adaptor.NOT(self),self._db,self._tbs)
def __call__(self,other):
return Query(self._db._adaptor.AND(self,other),self._db,self._tbs)
def _count(self):
return self._db._adaptor.COUNT(self)
def count(self):
return self._db._adaptor.count(self)
def _update(self,**fields):
return self._db._adaptor.UPDATE(self,**fields)
def update(self,**fields):
return self._db._adaptor.update(self,**fields)
def _select(self,*fields,**args):
return self._db._adaptor.SELECT(self,*fields,**args)[1:]
def select(self,*fields,**args):
return self._db._adaptor.select(self,*fields,**args)
def __str__(self):
return self.command
class Field(Expression):
def __init__(self,name,type='string',**args):
self._db=None
self._name=name
self.type=type
self.args=args ### FIX THIS
self._truename=args.get('truename',name)
self.length=128
def sum(self):
return Expression(self._db._adaptor.SUM(self),self._db,self._tbs)
def count(self):
return Expression(self._db._adaptor.COUNT(self),self._db,self._tbs)
def max(self):
return Expression(self._db._adaptor.MAX(self),self._db,self._tbs)
def min(self):
return Expression(self._db._adaptor.MIN(self),self._db,self._tbs)
def year(self):
return Expression(self._db._adaptor.YEAR(self),self._db,self._tbs)
def month(self):
return Expression(self._db._adaptor.MONTH(self),self._db,self._tbs)
def day(self):
return Expression(self._db._adaptor.DAY(self),self._db,self._tbs)
def hour(self):
return Expression(self._db._adaptor.HOUR(self),self._db,self._tbs)
def minute(self):
return Expression(self._db._adaptor.MINUTE(self),self._db,self._tbs)
def second(self):
return Expression(self._db._adaptor.SECOND(self),self._db,self._tbs)
def upper(self):
return Expression(self._db._adaptor.UPPER(self),self._db,self._tbs)
def lower(self):
return Expression(self._db._adaptor.LOWER(self),self._db,self._tbs)
def __str__(self):
return self._fullname
class Row:
def __init__(self,items,columns):
self._items=items
self._columns=columns
#self._parents=parents
self._rn=range(len(columns))
self._extra=dict([(self._columns[j],self._items[j]) for j in self._rn])
def __getitem__(self,column):
return self._extra[str(column)]
def __str__(self):
return str(self._extra)
class Rows:
def __init__(self,db,response,columns):
self.db=db
self.response=response
self.columns=columns
#self.parents=list(set([x.split('.')[0] for x in columns]).intersection(db.tables))
def __getitem__(self,i):
return Row(self.response[i],self.columns)
def __iter__(self):
for i in range(len(self.response)):
yield self[i]
def __getslice__(self,i,j):
return Rows(self.response[i:j],self.columns)
def __nonzero__(self):
return self.response!=[]
def __len__(self):
return len(self.response)
def __str__(self):
## FIX THIS!
return ',\n'.join([str(line) for line in self])
#
# Compatibility Layer
#
SQLDB=DB
SQLField=Field
SQLTable=Table
#
# Tests
#
def test():
try: os.unlink('storage.sqlite')
except: pass
db=SQLDB('sqlite://storage.sqlite')
db.define_table('person',SQLField('name'),SQLField('age','integer'))
print db['person']
print db['person']['name']
print db.person.ALL
print db.person.fields
print db.person.insert(name='Massimo',age=37)
print db.person.insert(name='Claudia',age=36)
print db.person.insert(name='Marco',age=4)
print db.person.name.like('M%').count()
print db(db.person.id==1).update(age=db.person.age+1)
for row in db((db.person.id>0)&(db.person.id<10)).select():
print row, row['person.id']
print db((db.person.id>0)&(db.person.id<10)).select(db.person.ALL,
db.person.age.sum())
print db().select(db.person.ALL)
db.person.truncate()
db.person.drop()
db.commit()
db.close()
test()
"""
sqlite> pragma table_info(people);
0|first_name|varchar|0||0
1|last_name|varchar|0||0
2|email_address|varchar|0||0
mysql>SHOW COLUMNS FROM City;
postgresql> SELECT column_name FROM information_schema.columns WHERE table_name ='table';
oracle>SELECT * FROM tabs;
firebird>show table City;
reflection
new is the new description of table
old is the old description of table
table is the description of table as is exists in db
if not table:
if not old:
create table
create old
else:
for field in old not in new:
FYI: Some issues to note as you are rewriting the DAL:
1. For mssql connections, I want to write:
db = SQLDB('mssql://dlypka:p...@word1@localhost/TMoMilestones')
The '@' delimiter is a problem for me because '@' is a legal, often
used character in SQL Server passwords
So please at least change the regular expression in sql.py line 768 to
allow enclosing the password in perhaps square brackets
So for example, it would allow
db = SQLDB('mssql://dlypka: [p...@word1]@localhost/TMoMilestones')
The re at line 768 is
re.compile('^(?P<user>[^:@]+)(\:(?P<passwd>[...@]
*))?
@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>.+)$'
).match(self._uri[skip:])
2. Please change sql.py to use pymssql rather than pyodbc
pymssql Version 1.0.2 was released on 2009-04-28. It has major bug
fixes and works well now.
I got it from herehttp://pymssql.sourceforge.net/
I suggest changing sql.py line 791:
# self._pool_connection(lambda : pyodbc.connect(cnxn))
self._pool_connection(lambda : pymssql.connect(cnxn)
3. Please provide a way to specify options such as trusted=True
For example, I need to call it this way:
self._pool_connection(lambda : pymssql.connect
(host='',trusted=True,database=db))
Thank you.
- Dave Lypka.
"""