added spatialite support, thanks Denes
This commit is contained in:
@@ -1 +1 @@
|
|||||||
Version 1.99.7 (2012-03-29 09:59:32) dev
|
Version 1.99.7 (2012-04-01 11:14:07) dev
|
||||||
|
|||||||
+113
-12
@@ -17,7 +17,7 @@ Thanks to
|
|||||||
|
|
||||||
This file contains the DAL support for many relational databases,
|
This file contains the DAL support for many relational databases,
|
||||||
including:
|
including:
|
||||||
- SQLite
|
- SQLite & SpatiaLite
|
||||||
- MySQL
|
- MySQL
|
||||||
- Postgres
|
- Postgres
|
||||||
- Oracle
|
- Oracle
|
||||||
@@ -93,7 +93,9 @@ id string text boolean integer double decimal password upload blob time date dat
|
|||||||
|
|
||||||
Supported DAL URI strings:
|
Supported DAL URI strings:
|
||||||
'sqlite://test.db'
|
'sqlite://test.db'
|
||||||
|
'spatialite://test.db'
|
||||||
'sqlite:memory'
|
'sqlite:memory'
|
||||||
|
'spatialite:memory'
|
||||||
'jdbc:sqlite://test.db'
|
'jdbc:sqlite://test.db'
|
||||||
'mysql://root:none@localhost/test'
|
'mysql://root:none@localhost/test'
|
||||||
'postgres://mdipierro:password@localhost/test'
|
'postgres://mdipierro:password@localhost/test'
|
||||||
@@ -721,7 +723,7 @@ class BaseAdapter(ConnectionPool):
|
|||||||
query = '''CREATE TABLE %s(\n %s\n)%s''' % \
|
query = '''CREATE TABLE %s(\n %s\n)%s''' % \
|
||||||
(tablename, fields, other)
|
(tablename, fields, other)
|
||||||
|
|
||||||
if self.uri.startswith('sqlite:///'):
|
if self.uri.startswith('sqlite:///') or self.uri.startswith('spatialite:///'):
|
||||||
path_encoding = sys.getfilesystemencoding() or locale.getdefaultlocale()[1] or 'utf8'
|
path_encoding = sys.getfilesystemencoding() or locale.getdefaultlocale()[1] or 'utf8'
|
||||||
dbpath = self.uri[9:self.uri.rfind('/')].decode('utf8').encode(path_encoding)
|
dbpath = self.uri[9:self.uri.rfind('/')].decode('utf8').encode(path_encoding)
|
||||||
else:
|
else:
|
||||||
@@ -729,7 +731,7 @@ class BaseAdapter(ConnectionPool):
|
|||||||
|
|
||||||
if not migrate:
|
if not migrate:
|
||||||
return query
|
return query
|
||||||
elif self.uri.startswith('sqlite:memory'):
|
elif self.uri.startswith('sqlite:memory') or self.uri.startswith('spatialite:memory'):
|
||||||
table._dbt = None
|
table._dbt = None
|
||||||
elif isinstance(migrate, str):
|
elif isinstance(migrate, str):
|
||||||
table._dbt = os.path.join(dbpath, migrate)
|
table._dbt = os.path.join(dbpath, migrate)
|
||||||
@@ -822,7 +824,7 @@ class BaseAdapter(ConnectionPool):
|
|||||||
(tablename, key,
|
(tablename, key,
|
||||||
sql_fields_aux[key]['sql'].replace(', ', new_add))]
|
sql_fields_aux[key]['sql'].replace(', ', new_add))]
|
||||||
metadata_change = True
|
metadata_change = True
|
||||||
elif self.dbengine == 'sqlite':
|
elif self.dbengine in ('sqlite', 'spatialite'):
|
||||||
if key in sql_fields:
|
if key in sql_fields:
|
||||||
sql_fields_current[key] = sql_fields[key]
|
sql_fields_current[key] = sql_fields[key]
|
||||||
metadata_change = True
|
metadata_change = True
|
||||||
@@ -1176,24 +1178,24 @@ class BaseAdapter(ConnectionPool):
|
|||||||
|
|
||||||
def delete(self, tablename, query):
|
def delete(self, tablename, query):
|
||||||
sql = self._delete(tablename, query)
|
sql = self._delete(tablename, query)
|
||||||
### special code to handle CASCADE in SQLite
|
### special code to handle CASCADE in SQLite & SpatiaLite
|
||||||
db = self.db
|
db = self.db
|
||||||
table = db[tablename]
|
table = db[tablename]
|
||||||
if self.dbengine=='sqlite' and table._referenced_by:
|
if self.dbengine in ('sqlite', 'spatialite') and table._referenced_by:
|
||||||
deleted = [x[table._id.name] for x in db(query).select(table._id)]
|
deleted = [x[table._id.name] for x in db(query).select(table._id)]
|
||||||
### end special code to handle CASCADE in SQLite
|
### end special code to handle CASCADE in SQLite & SpatiaLite
|
||||||
self.execute(sql)
|
self.execute(sql)
|
||||||
try:
|
try:
|
||||||
counter = self.cursor.rowcount
|
counter = self.cursor.rowcount
|
||||||
except:
|
except:
|
||||||
counter = None
|
counter = None
|
||||||
### special code to handle CASCADE in SQLite
|
### special code to handle CASCADE in SQLite & SpatiaLite
|
||||||
if self.dbengine=='sqlite' and counter:
|
if self.dbengine in ('sqlite', 'spatialite') and counter:
|
||||||
for tablename,fieldname in table._referenced_by:
|
for tablename,fieldname in table._referenced_by:
|
||||||
f = db[tablename][fieldname]
|
f = db[tablename][fieldname]
|
||||||
if f.type=='reference '+table._tablename and f.ondelete=='CASCADE':
|
if f.type=='reference '+table._tablename and f.ondelete=='CASCADE':
|
||||||
db(db[tablename][fieldname].belongs(deleted)).delete()
|
db(db[tablename][fieldname].belongs(deleted)).delete()
|
||||||
### end special code to handle CASCADE in SQLite
|
### end special code to handle CASCADE in SQLite & SpatiaLite
|
||||||
return counter
|
return counter
|
||||||
|
|
||||||
def get_table(self, query):
|
def get_table(self, query):
|
||||||
@@ -1586,7 +1588,7 @@ class BaseAdapter(ConnectionPool):
|
|||||||
|
|
||||||
def parse_decimal(self, value, field_type):
|
def parse_decimal(self, value, field_type):
|
||||||
decimals = int(field_type[8:-1].split(',')[-1])
|
decimals = int(field_type[8:-1].split(',')[-1])
|
||||||
if self.dbengine == 'sqlite':
|
if self.dbengine in ('sqlite', 'spatialite'):
|
||||||
value = ('%.' + str(decimals) + 'f') % value
|
value = ('%.' + str(decimals) + 'f') % value
|
||||||
if not isinstance(value, decimal.Decimal):
|
if not isinstance(value, decimal.Decimal):
|
||||||
value = decimal.Decimal(str(value))
|
value = decimal.Decimal(str(value))
|
||||||
@@ -1808,6 +1810,100 @@ class SQLiteAdapter(BaseAdapter):
|
|||||||
return sql
|
return sql
|
||||||
|
|
||||||
|
|
||||||
|
class SpatiaLiteAdapter(SQLiteAdapter):
|
||||||
|
|
||||||
|
import copy
|
||||||
|
types = copy.copy(BaseAdapter.types)
|
||||||
|
types.update({'geometry': 'GEOMETRY'})
|
||||||
|
|
||||||
|
def __init__(self, db, uri, pool_size=0, folder=None, db_codec ='UTF-8',
|
||||||
|
credential_decoder=lambda x:x, driver_args={},
|
||||||
|
adapter_args={}, srid=4326):
|
||||||
|
if not self.driver:
|
||||||
|
raise RuntimeError, "Unable to import driver"
|
||||||
|
self.db = db
|
||||||
|
self.dbengine = "spatialite"
|
||||||
|
self.uri = uri
|
||||||
|
self.pool_size = 0
|
||||||
|
self.folder = folder
|
||||||
|
self.db_codec = db_codec
|
||||||
|
self.find_or_make_work_folder()
|
||||||
|
self.srid = srid
|
||||||
|
path_encoding = sys.getfilesystemencoding() or locale.getdefaultlocale()[1] or 'utf8'
|
||||||
|
if uri.startswith('spatialite:memory'):
|
||||||
|
dbpath = ':memory:'
|
||||||
|
else:
|
||||||
|
dbpath = uri.split('://')[1]
|
||||||
|
if dbpath[0] != '/':
|
||||||
|
dbpath = os.path.join(self.folder.decode(path_encoding).encode('utf8'), dbpath)
|
||||||
|
if not 'check_same_thread' in driver_args:
|
||||||
|
driver_args['check_same_thread'] = False
|
||||||
|
if not 'detect_types' in driver_args:
|
||||||
|
driver_args['detect_types'] = self.driver.PARSE_DECLTYPES
|
||||||
|
def connect(dbpath=dbpath, driver_args=driver_args):
|
||||||
|
return self.driver.Connection(dbpath, **driver_args)
|
||||||
|
self.pool_connection(connect)
|
||||||
|
self.connection.enable_load_extension(True)
|
||||||
|
# for Windows, rename libspatialite-2.dll as libspatialite.dll
|
||||||
|
# Linux uses libspatialite.so
|
||||||
|
# Mac OS X uses libspatialite.dylib
|
||||||
|
self.execute(r'SELECT load_extension("libspatialite");')
|
||||||
|
|
||||||
|
self.connection.create_function('web2py_extract', 2,
|
||||||
|
SQLiteAdapter.web2py_extract)
|
||||||
|
self.connection.create_function("REGEXP", 2,
|
||||||
|
SQLiteAdapter.web2py_regexp)
|
||||||
|
|
||||||
|
# GIS functions
|
||||||
|
|
||||||
|
def ST_ASGEOJSON(self, first, second):
|
||||||
|
return 'AsGeoJSON(%s,%s,%s)' %(self.expand(first),
|
||||||
|
second['precision'], second['options'])
|
||||||
|
|
||||||
|
def ST_ASTEXT(self, first):
|
||||||
|
return 'AsText(%s)' %(self.expand(first))
|
||||||
|
|
||||||
|
def ST_CONTAINS(self, first, second):
|
||||||
|
return 'Contains(%s,%s)' %(self.expand(first), self.expand(second, first.type))
|
||||||
|
|
||||||
|
def ST_DISTANCE(self, first, second):
|
||||||
|
return 'Distance(%s,%s)' %(self.expand(first), self.expand(second, first.type))
|
||||||
|
|
||||||
|
def ST_EQUALS(self, first, second):
|
||||||
|
return 'Equals(%s,%s)' %(self.expand(first), self.expand(second, first.type))
|
||||||
|
|
||||||
|
def ST_INTERSECTS(self, first, second):
|
||||||
|
return 'Intersects(%s,%s)' %(self.expand(first), self.expand(second, first.type))
|
||||||
|
|
||||||
|
def ST_OVERLAPS(self, first, second):
|
||||||
|
return 'Overlaps(%s,%s)' %(self.expand(first), self.expand(second, first.type))
|
||||||
|
|
||||||
|
def ST_SIMPLIFY(self, first, second):
|
||||||
|
return 'Simplify(%s,%s)' %(self.expand(first), self.expand(second, 'double'))
|
||||||
|
|
||||||
|
def ST_TOUCHES(self, first, second):
|
||||||
|
return 'Touches(%s,%s)' %(self.expand(first), self.expand(second, first.type))
|
||||||
|
|
||||||
|
def ST_WITHIN(self, first, second):
|
||||||
|
return 'Within(%s,%s)' %(self.expand(first), self.expand(second, first.type))
|
||||||
|
|
||||||
|
def represent(self, obj, fieldtype):
|
||||||
|
if fieldtype.startswith('geo'):
|
||||||
|
srid = 4326 # Spatialite default srid for geometry
|
||||||
|
geotype, parms = fieldtype[:-1].split('(')
|
||||||
|
parms = parms.split(',')
|
||||||
|
if len(parms) >= 2:
|
||||||
|
schema, srid = parms[:2]
|
||||||
|
# if fieldtype.startswith('geometry'):
|
||||||
|
value = "ST_GeomFromText('%s',%s)" %(obj, srid)
|
||||||
|
# elif fieldtype.startswith('geography'):
|
||||||
|
# value = "ST_GeogFromText('SRID=%s;%s')" %(srid, obj)
|
||||||
|
# else:
|
||||||
|
# raise SyntaxError, 'Invalid field type %s' %fieldtype
|
||||||
|
return value
|
||||||
|
return BaseAdapter.represent(self, obj, fieldtype)
|
||||||
|
|
||||||
|
|
||||||
class JDBCSQLiteAdapter(SQLiteAdapter):
|
class JDBCSQLiteAdapter(SQLiteAdapter):
|
||||||
|
|
||||||
driver = globals().get('zxJDBC', None)
|
driver = globals().get('zxJDBC', None)
|
||||||
@@ -2737,6 +2833,9 @@ class FireBirdEmbeddedAdapter(FireBirdAdapter):
|
|||||||
user=credential_decoder(user),
|
user=credential_decoder(user),
|
||||||
password=credential_decoder(password),
|
password=credential_decoder(password),
|
||||||
charset=charset))
|
charset=charset))
|
||||||
|
#def connect(driver_args=driver_args):
|
||||||
|
# return kinterbasdb.connect(**driver_args)
|
||||||
|
|
||||||
def connect(driver_args=driver_args):
|
def connect(driver_args=driver_args):
|
||||||
return self.driver.connect(**driver_args)
|
return self.driver.connect(**driver_args)
|
||||||
self.pool_connection(connect)
|
self.pool_connection(connect)
|
||||||
@@ -5686,7 +5785,9 @@ class IMAPAdapter(NoSQLAdapter):
|
|||||||
|
|
||||||
ADAPTERS = {
|
ADAPTERS = {
|
||||||
'sqlite': SQLiteAdapter,
|
'sqlite': SQLiteAdapter,
|
||||||
|
'spatialite': SpatiaLiteAdapter,
|
||||||
'sqlite:memory': SQLiteAdapter,
|
'sqlite:memory': SQLiteAdapter,
|
||||||
|
'spatialite:memory': SpatiaLiteAdapter,
|
||||||
'mysql': MySQLAdapter,
|
'mysql': MySQLAdapter,
|
||||||
'postgres': PostgreSQLAdapter,
|
'postgres': PostgreSQLAdapter,
|
||||||
'postgres:psycopg2': PostgreSQLAdapter,
|
'postgres:psycopg2': PostgreSQLAdapter,
|
||||||
@@ -7668,7 +7769,7 @@ class Field(Expression):
|
|||||||
u = m.group('uuidkey')
|
u = m.group('uuidkey')
|
||||||
path = os.path.join(path,"%s.%s" % (t,f),u[:2])
|
path = os.path.join(path,"%s.%s" % (t,f),u[:2])
|
||||||
return dict(path=path,filename=filename)
|
return dict(path=path,filename=filename)
|
||||||
|
|
||||||
|
|
||||||
def formatter(self, value):
|
def formatter(self, value):
|
||||||
if value is None or not self.requires:
|
if value is None or not self.requires:
|
||||||
|
|||||||
Reference in New Issue
Block a user