1
2
3
4 """
5 This file is part of the web2py Web Framework
6 Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
7 License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
8
9 Thanks to
10 * Niall Sweeny <niall.sweeny@fonjax.com> for MS SQL support
11 * Marcel Leuthi <mluethi@mlsystems.ch> for Oracle support
12 * Denes
13 * Chris Clark
14 * clach05
15 * Denes Lengyel
16 * and many others who have contributed to current and previous versions
17
18 This file contains the DAL support for many relational databases,
19 including:
20 - SQLite & SpatiaLite
21 - MySQL
22 - Postgres
23 - Firebird
24 - Oracle
25 - MS SQL
26 - DB2
27 - Interbase
28 - Ingres
29 - Informix (9+ and SE)
30 - SapDB (experimental)
31 - Cubrid (experimental)
32 - CouchDB (experimental)
33 - MongoDB (in progress)
34 - Google:nosql
35 - Google:sql
36 - Teradata
37 - IMAP (experimental)
38
39 Example of usage:
40
41 >>> # from dal import DAL, Field
42
43 ### create DAL connection (and create DB if it doesn't exist)
44 >>> db = DAL(('sqlite://storage.sqlite','mysql://a:b@localhost/x'),
45 ... folder=None)
46
47 ### define a table 'person' (create/alter as necessary)
48 >>> person = db.define_table('person',Field('name','string'))
49
50 ### insert a record
51 >>> id = person.insert(name='James')
52
53 ### retrieve it by id
54 >>> james = person(id)
55
56 ### retrieve it by name
57 >>> james = person(name='James')
58
59 ### retrieve it by arbitrary query
60 >>> query = (person.name=='James') & (person.name.startswith('J'))
61 >>> james = db(query).select(person.ALL)[0]
62
63 ### update one record
64 >>> james.update_record(name='Jim')
65 <Row {'id': 1, 'name': 'Jim'}>
66
67 ### update multiple records by query
68 >>> db(person.name.like('J%')).update(name='James')
69 1
70
71 ### delete records by query
72 >>> db(person.name.lower() == 'jim').delete()
73 0
74
75 ### retrieve multiple records (rows)
76 >>> people = db(person).select(orderby=person.name,
77 ... groupby=person.name, limitby=(0,100))
78
79 ### further filter them
80 >>> james = people.find(lambda row: row.name == 'James').first()
81 >>> print james.id, james.name
82 1 James
83
84 ### check aggregates
85 >>> counter = person.id.count()
86 >>> print db(person).select(counter).first()(counter)
87 1
88
89 ### delete one record
90 >>> james.delete_record()
91 1
92
93 ### delete (drop) entire database table
94 >>> person.drop()
95
96 Supported field types:
97 id string text boolean integer double decimal password upload
98 blob time date datetime
99
100 Supported DAL URI strings:
101 'sqlite://test.db'
102 'spatialite://test.db'
103 'sqlite:memory'
104 'spatialite:memory'
105 'jdbc:sqlite://test.db'
106 'mysql://root:none@localhost/test'
107 'postgres://mdipierro:password@localhost/test'
108 'postgres:psycopg2://mdipierro:password@localhost/test'
109 'postgres:pg8000://mdipierro:password@localhost/test'
110 'jdbc:postgres://mdipierro:none@localhost/test'
111 'mssql://web2py:none@A64X2/web2py_test'
112 'mssql2://web2py:none@A64X2/web2py_test' # alternate mappings
113 'oracle://username:password@database'
114 'firebird://user:password@server:3050/database'
115 'db2://DSN=dsn;UID=user;PWD=pass'
116 'firebird://username:password@hostname/database'
117 'firebird_embedded://username:password@c://path'
118 'informix://user:password@server:3050/database'
119 'informixu://user:password@server:3050/database' # unicode informix
120 'google:datastore' # for google app engine datastore
121 'google:sql' # for google app engine with sql (mysql compatible)
122 'teradata://DSN=dsn;UID=user;PWD=pass; DATABASE=database' # experimental
123 'imap://user:password@server:port' # experimental
124
125 For more info:
126 help(DAL)
127 help(Field)
128 """
129
130
131
132
133
134 __all__ = ['DAL', 'Field']
135
136 MAXCHARLENGTH = 2**15
137 DEFAULTLENGTH = {'string':512,
138 'password':512,
139 'upload':512,
140 'text':2**15,
141 'blob':2**31}
142 TIMINGSSIZE = 100
143 SPATIALLIBS = {
144 'Windows':'libspatialite',
145 'Linux':'libspatialite.so',
146 'Darwin':'libspatialite.dylib'
147 }
148
149 import re
150 import sys
151 import locale
152 import os
153 import types
154 import datetime
155 import threading
156 import time
157 import csv
158 import cgi
159 import copy
160 import socket
161 import logging
162 import base64
163 import shutil
164 import marshal
165 import decimal
166 import struct
167 import urllib
168 import hashlib
169 import uuid
170 import glob
171 import traceback
172 import platform
173
174 PYTHON_VERSION = sys.version_info[0]
175 if PYTHON_VERSION == 2:
176 import cPickle as pickle
177 import cStringIO as StringIO
178 import copy_reg as copyreg
179 hashlib_md5 = hashlib.md5
180 bytes, unicode = str, unicode
181 else:
182 import pickle
183 from io import StringIO as StringIO
184 import copyreg
185 long = int
186 hashlib_md5 = lambda s: hashlib.md5(bytes(s,'utf8'))
187 bytes, unicode = bytes, str
188
189 CALLABLETYPES = (types.LambdaType, types.FunctionType,
190 types.BuiltinFunctionType,
191 types.MethodType, types.BuiltinMethodType)
192
193 TABLE_ARGS = set(
194 ('migrate','primarykey','fake_migrate','format','redefine',
195 'singular','plural','trigger_name','sequence_name',
196 'common_filter','polymodel','table_class','on_define',))
197
198 SELECT_ARGS = set(
199 ('orderby', 'groupby', 'limitby','required', 'cache', 'left',
200 'distinct', 'having', 'join','for_update', 'processor','cacheable'))
201
202 ogetattr = object.__getattribute__
203 osetattr = object.__setattr__
204 exists = os.path.exists
205 pjoin = os.path.join
206
207
208
209
210 try:
211 from utils import web2py_uuid
212 except (ImportError, SystemError):
213 import uuid
215
216 try:
217 import portalocker
218 have_portalocker = True
219 except ImportError:
220 have_portalocker = False
221
222 try:
223 import serializers
224 have_serializers = True
225 except ImportError:
226 have_serializers = False
227
228 try:
229 import validators
230 have_validators = True
231 except (ImportError, SyntaxError):
232 have_validators = False
233
234 LOGGER = logging.getLogger("web2py.dal")
235 DEFAULT = lambda:0
236
237 GLOBAL_LOCKER = threading.RLock()
238 THREAD_LOCAL = threading.local()
239
240
241
242
243 REGEX_TYPE = re.compile('^([\w\_\:]+)')
244 REGEX_DBNAME = re.compile('^(\w+)(\:\w+)*')
245 REGEX_W = re.compile('^\w+$')
246 REGEX_TABLE_DOT_FIELD = re.compile('^(\w+)\.(\w+)$')
247 REGEX_UPLOAD_PATTERN = re.compile('(?P<table>[\w\-]+)\.(?P<field>[\w\-]+)\.(?P<uuidkey>[\w\-]+)\.(?P<name>\w+)\.\w+$')
248 REGEX_CLEANUP_FN = re.compile('[\'"\s;]+')
249 REGEX_UNPACK = re.compile('(?<!\|)\|(?!\|)')
250 REGEX_PYTHON_KEYWORDS = re.compile('^(and|del|from|not|while|as|elif|global|or|with|assert|else|if|pass|yield|break|except|import|print|class|exec|in|raise|continue|finally|is|return|def|for|lambda|try)$')
251 REGEX_SELECT_AS_PARSER = re.compile("\s+AS\s+(\S+)")
252 REGEX_CONST_STRING = re.compile('(\"[^\"]*?\")|(\'[^\']*?\')')
253 REGEX_SEARCH_PATTERN = re.compile('^{[^\.]+\.[^\.]+(\.(lt|gt|le|ge|eq|ne|contains|startswith|year|month|day|hour|minute|second))?(\.not)?}$')
254 REGEX_SQUARE_BRACKETS = re.compile('^.+\[.+\]$')
255 REGEX_STORE_PATTERN = re.compile('\.(?P<e>\w{1,5})$')
256 REGEX_QUOTES = re.compile("'[^']*'")
257 REGEX_ALPHANUMERIC = re.compile('^[0-9a-zA-Z]\w*$')
258 REGEX_PASSWORD = re.compile('\://([^:@]*)\:')
259 REGEX_NOPASSWD = re.compile('(?<=\:)([^:@/]+)(?=@.+)')
260
261
262
263 DRIVERS = []
264
265 try:
266 from new import classobj
267 from google.appengine.ext import db as gae
268 from google.appengine.api import namespace_manager, rdbms
269 from google.appengine.api.datastore_types import Key
270 from google.appengine.ext.db.polymodel import PolyModel
271 DRIVERS.append('google')
272 except ImportError:
273 pass
274
275 if not 'google' in DRIVERS:
276
277 try:
278 from pysqlite2 import dbapi2 as sqlite2
279 DRIVERS.append('SQLite(sqlite2)')
280 except ImportError:
281 LOGGER.debug('no SQLite drivers pysqlite2.dbapi2')
282
283 try:
284 from sqlite3 import dbapi2 as sqlite3
285 DRIVERS.append('SQLite(sqlite3)')
286 except ImportError:
287 LOGGER.debug('no SQLite drivers sqlite3')
288
289 try:
290
291 try:
292 import contrib.pymysql as pymysql
293
294
295 pymysql.ESCAPE_REGEX = re.compile("'")
296 pymysql.ESCAPE_MAP = {"'": "''"}
297
298 except ImportError:
299 import pymysql
300 DRIVERS.append('MySQL(pymysql)')
301 except ImportError:
302 LOGGER.debug('no MySQL driver pymysql')
303
304 try:
305 import MySQLdb
306 DRIVERS.append('MySQL(MySQLdb)')
307 except ImportError:
308 LOGGER.debug('no MySQL driver MySQLDB')
309
310
311 try:
312 import psycopg2
313 from psycopg2.extensions import adapt as psycopg2_adapt
314 DRIVERS.append('PostgreSQL(psycopg2)')
315 except ImportError:
316 LOGGER.debug('no PostgreSQL driver psycopg2')
317
318 try:
319
320 try:
321 import contrib.pg8000.dbapi as pg8000
322 except ImportError:
323 import pg8000.dbapi as pg8000
324 DRIVERS.append('PostgreSQL(pg8000)')
325 except ImportError:
326 LOGGER.debug('no PostgreSQL driver pg8000')
327
328 try:
329 import cx_Oracle
330 DRIVERS.append('Oracle(cx_Oracle)')
331 except ImportError:
332 LOGGER.debug('no Oracle driver cx_Oracle')
333
334 try:
335 import pyodbc
336 DRIVERS.append('MSSQL(pyodbc)')
337 DRIVERS.append('DB2(pyodbc)')
338 DRIVERS.append('Teradata(pyodbc)')
339 except ImportError:
340 LOGGER.debug('no MSSQL/DB2/Teradata driver pyodbc')
341
342 try:
343 import Sybase
344 DRIVERS.append('Sybase(Sybase)')
345 except ImportError:
346 LOGGER.debug('no Sybase driver')
347
348 try:
349 import kinterbasdb
350 DRIVERS.append('Interbase(kinterbasdb)')
351 DRIVERS.append('Firebird(kinterbasdb)')
352 except ImportError:
353 LOGGER.debug('no Firebird/Interbase driver kinterbasdb')
354
355 try:
356 import fdb
357 DRIVERS.append('Firbird(fdb)')
358 except ImportError:
359 LOGGER.debug('no Firebird driver fdb')
360
361 try:
362 import firebirdsql
363 DRIVERS.append('Firebird(firebirdsql)')
364 except ImportError:
365 LOGGER.debug('no Firebird driver firebirdsql')
366
367 try:
368 import informixdb
369 DRIVERS.append('Informix(informixdb)')
370 LOGGER.warning('Informix support is experimental')
371 except ImportError:
372 LOGGER.debug('no Informix driver informixdb')
373
374 try:
375 import sapdb
376 DRIVERS.append('SQL(sapdb)')
377 LOGGER.warning('SAPDB support is experimental')
378 except ImportError:
379 LOGGER.debug('no SAP driver sapdb')
380
381 try:
382 import cubriddb
383 DRIVERS.append('Cubrid(cubriddb)')
384 LOGGER.warning('Cubrid support is experimental')
385 except ImportError:
386 LOGGER.debug('no Cubrid driver cubriddb')
387
388 try:
389 from com.ziclix.python.sql import zxJDBC
390 import java.sql
391
392 from org.sqlite import JDBC
393 zxJDBC_sqlite = java.sql.DriverManager
394 DRIVERS.append('PostgreSQL(zxJDBC)')
395 DRIVERS.append('SQLite(zxJDBC)')
396 LOGGER.warning('zxJDBC support is experimental')
397 is_jdbc = True
398 except ImportError:
399 LOGGER.debug('no SQLite/PostgreSQL driver zxJDBC')
400 is_jdbc = False
401
402 try:
403 import ingresdbi
404 DRIVERS.append('Ingres(ingresdbi)')
405 except ImportError:
406 LOGGER.debug('no Ingres driver ingresdbi')
407
408
409 try:
410 import couchdb
411 DRIVERS.append('CouchDB(couchdb)')
412 except ImportError:
413 LOGGER.debug('no Couchdb driver couchdb')
414
415 try:
416 import pymongo
417 DRIVERS.append('MongoDB(pymongo)')
418 except:
419 LOGGER.debug('no MongoDB driver pymongo')
420
421 try:
422 import imaplib
423 DRIVERS.append('IMAP(imaplib)')
424 except:
425 LOGGER.debug('no IMAP driver imaplib')
426
427 PLURALIZE_RULES = [
428 (re.compile('child$'), re.compile('child$'), 'children'),
429 (re.compile('oot$'), re.compile('oot$'), 'eet'),
430 (re.compile('ooth$'), re.compile('ooth$'), 'eeth'),
431 (re.compile('l[eo]af$'), re.compile('l([eo])af$'), 'l\\1aves'),
432 (re.compile('sis$'), re.compile('sis$'), 'ses'),
433 (re.compile('man$'), re.compile('man$'), 'men'),
434 (re.compile('ife$'), re.compile('ife$'), 'ives'),
435 (re.compile('eau$'), re.compile('eau$'), 'eaux'),
436 (re.compile('lf$'), re.compile('lf$'), 'lves'),
437 (re.compile('[sxz]$'), re.compile('$'), 'es'),
438 (re.compile('[^aeioudgkprt]h$'), re.compile('$'), 'es'),
439 (re.compile('(qu|[^aeiou])y$'), re.compile('y$'), 'ies'),
440 (re.compile('$'), re.compile('$'), 's'),
441 ]
442
448
451
454
457
459
462
463 if 'google' in DRIVERS:
464
465 is_jdbc = False
466
468 """
469 GAE decimal implementation
470 """
471 data_type = decimal.Decimal
472
473 - def __init__(self, precision, scale, **kwargs):
474 super(GAEDecimalProperty, self).__init__(self, **kwargs)
475 d = '1.'
476 for x in range(scale):
477 d += '0'
478 self.round = decimal.Decimal(d)
479
487
489 if value is None or value == '':
490 return None
491 else:
492 return decimal.Decimal(value).quantize(self.round)
493
495 value = super(GAEDecimalProperty, self).validate(value)
496 if value is None or isinstance(value, decimal.Decimal):
497 return value
498 elif isinstance(value, basestring):
499 return decimal.Decimal(value)
500 raise gae.BadValueError("Property %s must be a Decimal or string."\
501 % self.name)
502
503
504
505
506
508
509 POOLS = {}
510 check_active_connection = True
511
512 @staticmethod
515
516
517
518 - def close(self,action='commit',really=True):
535
536 @staticmethod
538 """ to close cleanly databases in a multithreaded environment """
539 dbs = getattr(THREAD_LOCAL,'db_instances',{}).items()
540 for db_uid, db_group in dbs:
541 for db in db_group:
542 if hasattr(db,'_adapter'):
543 db._adapter.close(action)
544 getattr(THREAD_LOCAL,'db_instances',{}).clear()
545 getattr(THREAD_LOCAL,'db_instances_zombie',{}).clear()
546 if callable(action):
547 action(None)
548 return
549
551 """ this actually does not make the folder. it has to be there """
552 self.folder = getattr(THREAD_LOCAL,'folder','')
553
554
555 if False and self.folder and not exists(self.folder):
556 os.mkdir(self.folder)
557
559 """ this it is suppoed to be overloaded by adtapters"""
560 pass
561
563 """
564 this function defines: self.connection and self.cursor
565 (iff cursor is True)
566 if self.pool_size>0 it will try pull the connection from the pool
567 if the connection is not active (closed by db server) it will loop
568 if not self.pool_size or no active connections in pool makes a new one
569 """
570 if getattr(self,'connection',None) != None:
571 return
572 if f is None:
573 f = self.connector
574
575 if not self.pool_size:
576 self.connection = f()
577 self.cursor = cursor and self.connection.cursor()
578 else:
579 uri = self.uri
580 POOLS = ConnectionPool.POOLS
581 while True:
582 GLOBAL_LOCKER.acquire()
583 if not uri in POOLS:
584 POOLS[uri] = []
585 if POOLS[uri]:
586 self.connection = POOLS[uri].pop()
587 GLOBAL_LOCKER.release()
588 self.cursor = cursor and self.connection.cursor()
589 try:
590 if self.cursor and self.check_active_connection:
591 self.execute('SELECT 1;')
592 break
593 except:
594 pass
595 else:
596 GLOBAL_LOCKER.release()
597 self.connection = f()
598 self.cursor = cursor and self.connection.cursor()
599 break
600 self.after_connection()
601
602
603
604
605
606
608 driver = None
609 driver_name = None
610 drivers = ()
611 connection = None
612 maxcharlength = MAXCHARLENGTH
613 commit_on_alter_table = False
614 support_distributed_transaction = False
615 uploads_in_blob = False
616 can_select_for_update = True
617
618 TRUE = 'T'
619 FALSE = 'F'
620 types = {
621 'boolean': 'CHAR(1)',
622 'string': 'CHAR(%(length)s)',
623 'text': 'TEXT',
624 'password': 'CHAR(%(length)s)',
625 'blob': 'BLOB',
626 'upload': 'CHAR(%(length)s)',
627 'integer': 'INTEGER',
628 'bigint': 'INTEGER',
629 'float':'DOUBLE',
630 'double': 'DOUBLE',
631 'decimal': 'DOUBLE',
632 'date': 'DATE',
633 'time': 'TIME',
634 'datetime': 'TIMESTAMP',
635 'id': 'INTEGER PRIMARY KEY AUTOINCREMENT',
636 'reference': 'INTEGER REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
637 'list:integer': 'TEXT',
638 'list:string': 'TEXT',
639 'list:reference': 'TEXT',
640
641 'big-id': 'BIGINT PRIMARY KEY AUTOINCREMENT',
642 'big-reference': 'BIGINT REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
643 }
644
646 return table._id != None
647
649 return "'%s'" % obj.replace("'", "''")
650
652 if isinstance(obj,(int,float)):
653 return str(obj)
654 return self.adapt(str(obj))
655
657 return self.driver.IntegrityError
658
660 return self.driver.OperationalError
661
663 """
664 to be used ONLY for files that on GAE may not be on filesystem
665 """
666 return exists(filename)
667
668 - def file_open(self, filename, mode='rb', lock=True):
669 """
670 to be used ONLY for files that on GAE may not be on filesystem
671 """
672 if have_portalocker and lock:
673 fileobj = portalocker.LockedFile(filename,mode)
674 else:
675 fileobj = open(filename,mode)
676 return fileobj
677
679 """
680 to be used ONLY for files that on GAE may not be on filesystem
681 """
682 if fileobj:
683 fileobj.close()
684
687
689 if getattr(self,'driver',None) != None:
690 return
691 drivers_available = [driver for driver in self.drivers
692 if driver in globals()]
693 if uri:
694 items = uri.split('://',1)[0].split(':')
695 request_driver = items[1] if len(items)>1 else None
696 else:
697 request_driver = None
698 request_driver = request_driver or adapter_args.get('driver')
699 if request_driver:
700 if request_driver in drivers_available:
701 self.driver_name = request_driver
702 self.driver = globals().get(request_driver)
703 else:
704 raise RuntimeError("driver %s not available" % request_driver)
705 elif drivers_available:
706 self.driver_name = drivers_available[0]
707 self.driver = globals().get(self.driver_name)
708 else:
709 raise RuntimeError("no driver available %s" % str(self.drivers))
710
711
712 - def __init__(self, db,uri,pool_size=0, folder=None, db_codec='UTF-8',
713 credential_decoder=IDENTITY, driver_args={},
714 adapter_args={},do_connect=True):
715 self.db = db
716 self.dbengine = "None"
717 self.uri = uri
718 self.pool_size = pool_size
719 self.folder = folder
720 self.db_codec = db_codec
721 class Dummy(object):
722 lastrowid = 1
723 def __getattr__(self, value):
724 return lambda *a, **b: []
725 self.connection = Dummy()
726 self.cursor = Dummy()
727
729 return '%s_sequence' % tablename
730
732 return '%s_sequence' % tablename
733
736
737 - def create_table(self, table,
738 migrate=True,
739 fake_migrate=False,
740 polymodel=None):
741 db = table._db
742 fields = []
743
744 postcreation_fields = []
745 sql_fields = {}
746 sql_fields_aux = {}
747 TFK = {}
748 tablename = table._tablename
749 sortable = 0
750 types = self.types
751 for field in table:
752 sortable += 1
753 field_name = field.name
754 field_type = field.type
755 if isinstance(field_type,SQLCustomType):
756 ftype = field_type.native or field_type.type
757 elif field_type.startswith('reference'):
758 referenced = field_type[10:].strip()
759 if referenced == '.':
760 referenced = tablename
761 constraint_name = self.constraint_name(tablename, field_name)
762 if not '.' in referenced \
763 and referenced != tablename \
764 and hasattr(table,'_primarykey'):
765 ftype = types['integer']
766 else:
767 if hasattr(table,'_primarykey'):
768 rtablename,rfieldname = referenced.split('.')
769 rtable = db[rtablename]
770 rfield = rtable[rfieldname]
771
772 if rfieldname in hasattr(rtable,'_primarykey') or \
773 rfield.unique:
774 ftype = types[rfield.type[:9]] % \
775 dict(length=rfield.length)
776
777 if not rfield.unique and len(rtable._primarykey)>1:
778
779 if rtablename not in TFK:
780 TFK[rtablename] = {}
781 TFK[rtablename][rfieldname] = field_name
782 else:
783 ftype = ftype + \
784 types['reference FK'] % dict(
785 constraint_name = constraint_name,
786 foreign_key = '%s (%s)' % (rtablename,
787 rfieldname),
788 on_delete_action=field.ondelete)
789 else:
790
791 if referenced in db:
792 id_fieldname = db[referenced]._id.name
793 elif referenced == tablename:
794 id_fieldname = table._id.name
795 else:
796 id_fieldname = 'id'
797 ftype = types[field_type[:9]] % dict(
798 index_name = field_name+'__idx',
799 field_name = field_name,
800 constraint_name = constraint_name,
801 foreign_key = '%s (%s)' % (referenced,
802 id_fieldname),
803 on_delete_action=field.ondelete)
804 elif field_type.startswith('list:reference'):
805 ftype = types[field_type[:14]]
806 elif field_type.startswith('decimal'):
807 precision, scale = map(int,field_type[8:-1].split(','))
808 ftype = types[field_type[:7]] % \
809 dict(precision=precision,scale=scale)
810 elif field_type.startswith('geo'):
811 srid = self.srid
812 geotype, parms = field_type[:-1].split('(')
813 if not geotype in types:
814 raise SyntaxError(
815 'Field: unknown field type: %s for %s' \
816 % (field_type, field_name))
817 ftype = types[geotype]
818 if self.dbengine == 'postgres' and geotype == 'geometry':
819
820 dimension = 2
821 parms = parms.split(',')
822 if len(parms) == 3:
823 schema, srid, dimension = parms
824 elif len(parms) == 2:
825 schema, srid = parms
826 else:
827 schema = parms[0]
828 ftype = "SELECT AddGeometryColumn ('%%(schema)s', '%%(tablename)s', '%%(fieldname)s', %%(srid)s, '%s', %%(dimension)s);" % types[geotype]
829 ftype = ftype % dict(schema=schema,
830 tablename=tablename,
831 fieldname=field_name, srid=srid,
832 dimension=dimension)
833 postcreation_fields.append(ftype)
834 elif not field_type in types:
835 raise SyntaxError('Field: unknown field type: %s for %s' % \
836 (field_type, field_name))
837 else:
838 ftype = types[field_type]\
839 % dict(length=field.length)
840 if not field_type.startswith('id') and \
841 not field_type.startswith('reference'):
842 if field.notnull:
843 ftype += ' NOT NULL'
844 else:
845 ftype += self.ALLOW_NULL()
846 if field.unique:
847 ftype += ' UNIQUE'
848 if field.custom_qualifier:
849 ftype += ' %s' % field.custom_qualifier
850
851
852 sql_fields[field_name] = dict(
853 length=field.length,
854 unique=field.unique,
855 notnull=field.notnull,
856 sortable=sortable,
857 type=str(field_type),
858 sql=ftype)
859
860 if isinstance(field.default,(str,int,float)):
861
862
863
864
865
866
867 not_null = self.NOT_NULL(field.default, field_type)
868 ftype = ftype.replace('NOT NULL', not_null)
869 sql_fields_aux[field_name] = dict(sql=ftype)
870
871
872 if not (self.dbengine == 'postgres' and \
873 field_type.startswith('geom')):
874 fields.append('%s %s' % (field_name, ftype))
875 other = ';'
876
877
878 if self.dbengine == 'mysql':
879 if not hasattr(table, "_primarykey"):
880 fields.append('PRIMARY KEY(%s)' % table._id.name)
881 other = ' ENGINE=InnoDB CHARACTER SET utf8;'
882
883 fields = ',\n '.join(fields)
884 for rtablename in TFK:
885 rfields = TFK[rtablename]
886 pkeys = db[rtablename]._primarykey
887 fkeys = [ rfields[k] for k in pkeys ]
888 fields = fields + ',\n ' + \
889 types['reference TFK'] % dict(
890 table_name = tablename,
891 field_name=', '.join(fkeys),
892 foreign_table = rtablename,
893 foreign_key = ', '.join(pkeys),
894 on_delete_action = field.ondelete)
895
896 if hasattr(table,'_primarykey'):
897 query = "CREATE TABLE %s(\n %s,\n %s) %s" % \
898 (tablename, fields,
899 self.PRIMARY_KEY(', '.join(table._primarykey)),other)
900 else:
901 query = "CREATE TABLE %s(\n %s\n)%s" % \
902 (tablename, fields, other)
903
904 if self.uri.startswith('sqlite:///') \
905 or self.uri.startswith('spatialite:///'):
906 path_encoding = sys.getfilesystemencoding() \
907 or locale.getdefaultlocale()[1] or 'utf8'
908 dbpath = self.uri[9:self.uri.rfind('/')]\
909 .decode('utf8').encode(path_encoding)
910 else:
911 dbpath = self.folder
912
913 if not migrate:
914 return query
915 elif self.uri.startswith('sqlite:memory')\
916 or self.uri.startswith('spatialite:memory'):
917 table._dbt = None
918 elif isinstance(migrate, str):
919 table._dbt = pjoin(dbpath, migrate)
920 else:
921 table._dbt = pjoin(
922 dbpath, '%s_%s.table' % (table._db._uri_hash, tablename))
923
924 if table._dbt:
925 table._loggername = pjoin(dbpath, 'sql.log')
926 logfile = self.file_open(table._loggername, 'a')
927 else:
928 logfile = None
929 if not table._dbt or not self.file_exists(table._dbt):
930 if table._dbt:
931 logfile.write('timestamp: %s\n'
932 % datetime.datetime.today().isoformat())
933 logfile.write(query + '\n')
934 if not fake_migrate:
935 self.create_sequence_and_triggers(query,table)
936 table._db.commit()
937
938
939 for query in postcreation_fields:
940 self.execute(query)
941 table._db.commit()
942 if table._dbt:
943 tfile = self.file_open(table._dbt, 'w')
944 pickle.dump(sql_fields, tfile)
945 self.file_close(tfile)
946 if fake_migrate:
947 logfile.write('faked!\n')
948 else:
949 logfile.write('success!\n')
950 else:
951 tfile = self.file_open(table._dbt, 'r')
952 try:
953 sql_fields_old = pickle.load(tfile)
954 except EOFError:
955 self.file_close(tfile)
956 self.file_close(logfile)
957 raise RuntimeError('File %s appears corrupted' % table._dbt)
958 self.file_close(tfile)
959 if sql_fields != sql_fields_old:
960 self.migrate_table(table,
961 sql_fields, sql_fields_old,
962 sql_fields_aux, logfile,
963 fake_migrate=fake_migrate)
964 self.file_close(logfile)
965 return query
966
967 - def migrate_table(
968 self,
969 table,
970 sql_fields,
971 sql_fields_old,
972 sql_fields_aux,
973 logfile,
974 fake_migrate=False,
975 ):
976 db = table._db
977 db._migrated.append(table._tablename)
978 tablename = table._tablename
979 def fix(item):
980 k,v=item
981 if not isinstance(v,dict):
982 v=dict(type='unkown',sql=v)
983 return k.lower(),v
984
985
986 sql_fields = dict(map(fix,sql_fields.iteritems()))
987 sql_fields_old = dict(map(fix,sql_fields_old.iteritems()))
988 sql_fields_aux = dict(map(fix,sql_fields_aux.iteritems()))
989 if db._debug:
990 logging.debug('migrating %s to %s' % (sql_fields_old,sql_fields))
991
992 keys = sql_fields.keys()
993 for key in sql_fields_old:
994 if not key in keys:
995 keys.append(key)
996 if self.dbengine == 'mssql':
997 new_add = '; ALTER TABLE %s ADD ' % tablename
998 else:
999 new_add = ', ADD '
1000
1001 metadata_change = False
1002 sql_fields_current = copy.copy(sql_fields_old)
1003 for key in keys:
1004 query = None
1005 if not key in sql_fields_old:
1006 sql_fields_current[key] = sql_fields[key]
1007 if self.dbengine in ('postgres',) and \
1008 sql_fields[key]['type'].startswith('geometry'):
1009
1010 query = [ sql_fields[key]['sql'] ]
1011 else:
1012 query = ['ALTER TABLE %s ADD %s %s;' % \
1013 (tablename, key,
1014 sql_fields_aux[key]['sql'].replace(', ', new_add))]
1015 metadata_change = True
1016 elif self.dbengine in ('sqlite', 'spatialite'):
1017 if key in sql_fields:
1018 sql_fields_current[key] = sql_fields[key]
1019 metadata_change = True
1020 elif not key in sql_fields:
1021 del sql_fields_current[key]
1022 ftype = sql_fields_old[key]['type']
1023 if self.dbengine in ('postgres',) \
1024 and ftype.startswith('geometry'):
1025 geotype, parms = ftype[:-1].split('(')
1026 schema = parms.split(',')[0]
1027 query = [ "SELECT DropGeometryColumn ('%(schema)s', '%(table)s', '%(field)s');" % dict(schema=schema, table=tablename, field=key,) ]
1028 elif not self.dbengine in ('firebird',):
1029 query = ['ALTER TABLE %s DROP COLUMN %s;'
1030 % (tablename, key)]
1031 else:
1032 query = ['ALTER TABLE %s DROP %s;' % (tablename, key)]
1033 metadata_change = True
1034 elif sql_fields[key]['sql'] != sql_fields_old[key]['sql'] \
1035 and not (key in table.fields and
1036 isinstance(table[key].type, SQLCustomType)) \
1037 and not sql_fields[key]['type'].startswith('reference')\
1038 and not sql_fields[key]['type'].startswith('double')\
1039 and not sql_fields[key]['type'].startswith('id'):
1040 sql_fields_current[key] = sql_fields[key]
1041 t = tablename
1042 tt = sql_fields_aux[key]['sql'].replace(', ', new_add)
1043 if not self.dbengine in ('firebird',):
1044 query = ['ALTER TABLE %s ADD %s__tmp %s;' % (t, key, tt),
1045 'UPDATE %s SET %s__tmp=%s;' % (t, key, key),
1046 'ALTER TABLE %s DROP COLUMN %s;' % (t, key),
1047 'ALTER TABLE %s ADD %s %s;' % (t, key, tt),
1048 'UPDATE %s SET %s=%s__tmp;' % (t, key, key),
1049 'ALTER TABLE %s DROP COLUMN %s__tmp;' % (t, key)]
1050 else:
1051 query = ['ALTER TABLE %s ADD %s__tmp %s;' % (t, key, tt),
1052 'UPDATE %s SET %s__tmp=%s;' % (t, key, key),
1053 'ALTER TABLE %s DROP %s;' % (t, key),
1054 'ALTER TABLE %s ADD %s %s;' % (t, key, tt),
1055 'UPDATE %s SET %s=%s__tmp;' % (t, key, key),
1056 'ALTER TABLE %s DROP %s__tmp;' % (t, key)]
1057 metadata_change = True
1058 elif sql_fields[key]['type'] != sql_fields_old[key]['type']:
1059 sql_fields_current[key] = sql_fields[key]
1060 metadata_change = True
1061
1062 if query:
1063 logfile.write('timestamp: %s\n'
1064 % datetime.datetime.today().isoformat())
1065 db['_lastsql'] = '\n'.join(query)
1066 for sub_query in query:
1067 logfile.write(sub_query + '\n')
1068 if not fake_migrate:
1069 self.execute(sub_query)
1070
1071
1072
1073 if db._adapter.commit_on_alter_table:
1074 db.commit()
1075 tfile = self.file_open(table._dbt, 'w')
1076 pickle.dump(sql_fields_current, tfile)
1077 self.file_close(tfile)
1078 logfile.write('success!\n')
1079 else:
1080 logfile.write('faked!\n')
1081 elif metadata_change:
1082 tfile = self.file_open(table._dbt, 'w')
1083 pickle.dump(sql_fields_current, tfile)
1084 self.file_close(tfile)
1085
1086 if metadata_change and \
1087 not (query and self.dbengine in ('mysql','oracle','firebird')):
1088 db.commit()
1089 tfile = self.file_open(table._dbt, 'w')
1090 pickle.dump(sql_fields_current, tfile)
1091 self.file_close(tfile)
1092
1093 - def LOWER(self, first):
1095
1096 - def UPPER(self, first):
1098
1099 - def COUNT(self, first, distinct=None):
1100 return ('COUNT(%s)' if not distinct else 'COUNT(DISTINCT %s)') \
1101 % self.expand(first)
1102
1104 return "EXTRACT(%s FROM %s)" % (what, self.expand(first))
1105
1106 - def EPOCH(self, first):
1108
1111
1114
1117
1120
1121 - def NOT_NULL(self, default, field_type):
1122 return 'NOT NULL DEFAULT %s' % self.represent(default,field_type)
1123
1125 expressions = [self.expand(first)]+[self.expand(e) for e in second]
1126 return 'COALESCE(%s)' % ','.join(expressions)
1127
1130
1131 - def RAW(self, first):
1133
1136
1138 return 'SUBSTR(%s,%s,%s)' % (self.expand(field), parameters[0], parameters[1])
1139
1141 return 'PRIMARY KEY(%s)' % key
1142
1143 - def _drop(self, table, mode):
1144 return ['DROP TABLE %s;' % table]
1145
1146 - def drop(self, table, mode=''):
1162
1163 - def _insert(self, table, fields):
1167
1168 - def insert(self, table, fields):
1169 query = self._insert(table,fields)
1170 try:
1171 self.execute(query)
1172 except Exception:
1173 e = sys.exc_info()[1]
1174 if isinstance(e,self.integrity_error_class()):
1175 return None
1176 raise e
1177 if hasattr(table,'_primarykey'):
1178 return dict([(k[0].name, k[1]) for k in fields \
1179 if k[0].name in table._primarykey])
1180 id = self.lastrowid(table)
1181 if not isinstance(id,int):
1182 return id
1183 rid = Reference(id)
1184 (rid._table, rid._record) = (table, None)
1185 return rid
1186
1188 return [self.insert(table,item) for item in items]
1189
1190 - def NOT(self, first):
1192
1193 - def AND(self, first, second):
1195
1196 - def OR(self, first, second):
1198
1199 - def BELONGS(self, first, second):
1200 if isinstance(second, str):
1201 return '(%s IN (%s))' % (self.expand(first), second[:-1])
1202 elif not second:
1203 return '(1=0)'
1204 items = ','.join(self.expand(item, first.type) for item in second)
1205 return '(%s IN (%s))' % (self.expand(first), items)
1206
1207 - def REGEXP(self, first, second):
1208 "regular expression operator"
1209 raise NotImplementedError
1210
1211 - def LIKE(self, first, second):
1212 "case sensitive like operator"
1213 raise NotImplementedError
1214
1215 - def ILIKE(self, first, second):
1216 "case in-sensitive like operator"
1217 return '(%s LIKE %s)' % (self.expand(first),
1218 self.expand(second, 'string'))
1219
1221 return '(%s LIKE %s)' % (self.expand(first),
1222 self.expand(second+'%', 'string'))
1223
1225 return '(%s LIKE %s)' % (self.expand(first),
1226 self.expand('%'+second, 'string'))
1227
1234
1235 - def EQ(self, first, second=None):
1240
1241 - def NE(self, first, second=None):
1246
1247 - def LT(self,first,second=None):
1248 if second is None:
1249 raise RuntimeError("Cannot compare %s < None" % first)
1250 return '(%s < %s)' % (self.expand(first),
1251 self.expand(second,first.type))
1252
1253 - def LE(self,first,second=None):
1254 if second is None:
1255 raise RuntimeError("Cannot compare %s <= None" % first)
1256 return '(%s <= %s)' % (self.expand(first),
1257 self.expand(second,first.type))
1258
1259 - def GT(self,first,second=None):
1260 if second is None:
1261 raise RuntimeError("Cannot compare %s > None" % first)
1262 return '(%s > %s)' % (self.expand(first),
1263 self.expand(second,first.type))
1264
1265 - def GE(self,first,second=None):
1266 if second is None:
1267 raise RuntimeError("Cannot compare %s >= None" % first)
1268 return '(%s >= %s)' % (self.expand(first),
1269 self.expand(second,first.type))
1270
1271 - def ADD(self, first, second):
1274
1275 - def SUB(self, first, second):
1278
1279 - def MUL(self, first, second):
1282
1283 - def DIV(self, first, second):
1286
1287 - def MOD(self, first, second):
1290
1291 - def AS(self, first, second):
1293
1294 - def ON(self, first, second):
1296
1299
1300 - def COMMA(self, first, second):
1302
1303 - def expand(self, expression, field_type=None):
1304 if isinstance(expression, Field):
1305 return '%s.%s' % (expression.tablename, expression.name)
1306 elif isinstance(expression, (Expression, Query)):
1307 first = expression.first
1308 second = expression.second
1309 op = expression.op
1310 if not second is None:
1311 return op(first, second)
1312 elif not first is None:
1313 return op(first)
1314 elif isinstance(op, str):
1315 if op.endswith(';'):
1316 op=op[:-1]
1317 return '(%s)' % op
1318 else:
1319 return op()
1320 elif field_type:
1321 return str(self.represent(expression,field_type))
1322 elif isinstance(expression,(list,tuple)):
1323 return ','.join(self.represent(item,field_type) \
1324 for item in expression)
1325 elif isinstance(expression, bool):
1326 return '1' if expression else '0'
1327 else:
1328 return str(expression)
1329
1330 - def alias(self, table, alias):
1331 """
1332 Given a table object, makes a new table object
1333 with alias name.
1334 """
1335 other = copy.copy(table)
1336 other['_ot'] = other._tablename
1337 other['ALL'] = SQLALL(other)
1338 other['_tablename'] = alias
1339 for fieldname in other.fields:
1340 other[fieldname] = copy.copy(other[fieldname])
1341 other[fieldname]._tablename = alias
1342 other[fieldname].tablename = alias
1343 other[fieldname].table = other
1344 table._db[alias] = other
1345 return other
1346
1348 tablename = table._tablename
1349 return ['TRUNCATE TABLE %s %s;' % (tablename, mode or '')]
1350
1352
1353 if table._dbt:
1354 logfile = self.file_open(table._loggername, 'a')
1355 else:
1356 class Logfile(object):
1357 def write(self, value):
1358 pass
1359 def close(self):
1360 pass
1361 logfile = Logfile()
1362
1363 try:
1364 queries = table._db._adapter._truncate(table, mode)
1365 for query in queries:
1366 logfile.write(query + '\n')
1367 self.execute(query)
1368 table._db.commit()
1369 logfile.write('success!\n')
1370 finally:
1371 logfile.close()
1372
1373 - def _update(self, tablename, query, fields):
1374 if query:
1375 if use_common_filters(query):
1376 query = self.common_filter(query, [tablename])
1377 sql_w = ' WHERE ' + self.expand(query)
1378 else:
1379 sql_w = ''
1380 sql_v = ','.join(['%s=%s' % (field.name,
1381 self.expand(value, field.type)) \
1382 for (field, value) in fields])
1383 return 'UPDATE %s SET %s%s;' % (tablename, sql_v, sql_w)
1384
1385 - def update(self, tablename, query, fields):
1386 sql = self._update(tablename, query, fields)
1387 self.execute(sql)
1388 try:
1389 return self.cursor.rowcount
1390 except:
1391 return None
1392
1393 - def _delete(self, tablename, query):
1394 if query:
1395 if use_common_filters(query):
1396 query = self.common_filter(query, [tablename])
1397 sql_w = ' WHERE ' + self.expand(query)
1398 else:
1399 sql_w = ''
1400 return 'DELETE FROM %s%s;' % (tablename, sql_w)
1401
1402 - def delete(self, tablename, query):
1403 sql = self._delete(tablename, query)
1404
1405 db = self.db
1406 table = db[tablename]
1407 if self.dbengine in ('sqlite', 'spatialite') and table._referenced_by:
1408 deleted = [x[table._id.name] for x in db(query).select(table._id)]
1409
1410 self.execute(sql)
1411 try:
1412 counter = self.cursor.rowcount
1413 except:
1414 counter = None
1415
1416 if self.dbengine in ('sqlite', 'spatialite') and counter:
1417 for field in table._referenced_by:
1418 if field.type=='reference '+table._tablename \
1419 and field.ondelete=='CASCADE':
1420 db(field.belongs(deleted)).delete()
1421
1422 return counter
1423
1425 tablenames = self.tables(query)
1426 if len(tablenames)==1:
1427 return tablenames[0]
1428 elif len(tablenames)<1:
1429 raise RuntimeError("No table selected")
1430 else:
1431 raise RuntimeError("Too many tables selected")
1432
1434 db = self.db
1435 new_fields = []
1436 append = new_fields.append
1437 for item in fields:
1438 if isinstance(item,SQLALL):
1439 new_fields += item._table
1440 elif isinstance(item,str):
1441 if REGEX_TABLE_DOT_FIELD.match(item):
1442 tablename,fieldname = item.split('.')
1443 append(db[tablename][fieldname])
1444 else:
1445 append(Expression(db,lambda:item))
1446 else:
1447 append(item)
1448
1449 if not new_fields:
1450 for table in tablenames:
1451 for field in db[table]:
1452 append(field)
1453 return new_fields
1454
1455 - def _select(self, query, fields, attributes):
1456 tables = self.tables
1457 for key in set(attributes.keys())-SELECT_ARGS:
1458 raise SyntaxError('invalid select attribute: %s' % key)
1459 args_get = attributes.get
1460 tablenames = tables(query)
1461 for field in fields:
1462 if isinstance(field, basestring) \
1463 and REGEX_TABLE_DOT_FIELD.match(field):
1464 tn,fn = field.split('.')
1465 field = self.db[tn][fn]
1466 for tablename in tables(field):
1467 if not tablename in tablenames:
1468 tablenames.append(tablename)
1469
1470 if use_common_filters(query):
1471 query = self.common_filter(query,tablenames)
1472
1473 if len(tablenames) < 1:
1474 raise SyntaxError('Set: no tables selected')
1475 sql_f = ', '.join(map(self.expand, fields))
1476 self._colnames = [c.strip() for c in sql_f.split(', ')]
1477 if query:
1478 sql_w = ' WHERE ' + self.expand(query)
1479 else:
1480 sql_w = ''
1481 sql_o = ''
1482 sql_s = ''
1483 left = args_get('left', False)
1484 inner_join = args_get('join', False)
1485 distinct = args_get('distinct', False)
1486 groupby = args_get('groupby', False)
1487 orderby = args_get('orderby', False)
1488 having = args_get('having', False)
1489 limitby = args_get('limitby', False)
1490 for_update = args_get('for_update', False)
1491 if self.can_select_for_update is False and for_update is True:
1492 raise SyntaxError('invalid select attribute: for_update')
1493 if distinct is True:
1494 sql_s += 'DISTINCT'
1495 elif distinct:
1496 sql_s += 'DISTINCT ON (%s)' % distinct
1497 if inner_join:
1498 icommand = self.JOIN()
1499 if not isinstance(inner_join, (tuple, list)):
1500 inner_join = [inner_join]
1501 ijoint = [t._tablename for t in inner_join
1502 if not isinstance(t,Expression)]
1503 ijoinon = [t for t in inner_join if isinstance(t, Expression)]
1504 itables_to_merge={}
1505 [itables_to_merge.update(
1506 dict.fromkeys(tables(t))) for t in ijoinon]
1507 ijoinont = [t.first._tablename for t in ijoinon]
1508 [itables_to_merge.pop(t) for t in ijoinont
1509 if t in itables_to_merge]
1510 iimportant_tablenames = ijoint + ijoinont + itables_to_merge.keys()
1511 iexcluded = [t for t in tablenames
1512 if not t in iimportant_tablenames]
1513 if left:
1514 join = attributes['left']
1515 command = self.LEFT_JOIN()
1516 if not isinstance(join, (tuple, list)):
1517 join = [join]
1518 joint = [t._tablename for t in join
1519 if not isinstance(t, Expression)]
1520 joinon = [t for t in join if isinstance(t, Expression)]
1521
1522 tables_to_merge={}
1523 [tables_to_merge.update(
1524 dict.fromkeys(tables(t))) for t in joinon]
1525 joinont = [t.first._tablename for t in joinon]
1526 [tables_to_merge.pop(t) for t in joinont if t in tables_to_merge]
1527 important_tablenames = joint + joinont + tables_to_merge.keys()
1528 excluded = [t for t in tablenames
1529 if not t in important_tablenames ]
1530 def alias(t):
1531 return str(self.db[t])
1532 if inner_join and not left:
1533 sql_t = ', '.join([alias(t) for t in iexcluded + \
1534 itables_to_merge.keys()])
1535 for t in ijoinon:
1536 sql_t += ' %s %s' % (icommand, str(t))
1537 elif not inner_join and left:
1538 sql_t = ', '.join([alias(t) for t in excluded + \
1539 tables_to_merge.keys()])
1540 if joint:
1541 sql_t += ' %s %s' % (command, ','.join([t for t in joint]))
1542 for t in joinon:
1543 sql_t += ' %s %s' % (command, str(t))
1544 elif inner_join and left:
1545 all_tables_in_query = set(important_tablenames + \
1546 iimportant_tablenames + \
1547 tablenames)
1548 tables_in_joinon = set(joinont + ijoinont)
1549 tables_not_in_joinon = \
1550 all_tables_in_query.difference(tables_in_joinon)
1551 sql_t = ','.join([alias(t) for t in tables_not_in_joinon])
1552 for t in ijoinon:
1553 sql_t += ' %s %s' % (icommand, str(t))
1554 if joint:
1555 sql_t += ' %s %s' % (command, ','.join([t for t in joint]))
1556 for t in joinon:
1557 sql_t += ' %s %s' % (command, str(t))
1558 else:
1559 sql_t = ', '.join(alias(t) for t in tablenames)
1560 if groupby:
1561 if isinstance(groupby, (list, tuple)):
1562 groupby = xorify(groupby)
1563 sql_o += ' GROUP BY %s' % self.expand(groupby)
1564 if having:
1565 sql_o += ' HAVING %s' % attributes['having']
1566 if orderby:
1567 if isinstance(orderby, (list, tuple)):
1568 orderby = xorify(orderby)
1569 if str(orderby) == '<random>':
1570 sql_o += ' ORDER BY %s' % self.RANDOM()
1571 else:
1572 sql_o += ' ORDER BY %s' % self.expand(orderby)
1573 if limitby:
1574 if not orderby and tablenames:
1575 sql_o += ' ORDER BY %s' % ', '.join(['%s.%s'%(t,x) for t in tablenames for x in (hasattr(self.db[t],'_primarykey') and self.db[t]._primarykey or [self.db[t]._id.name])])
1576
1577 sql = self.select_limitby(sql_s, sql_f, sql_t, sql_w, sql_o, limitby)
1578 if for_update and self.can_select_for_update is True:
1579 sql = sql.rstrip(';') + ' FOR UPDATE;'
1580 return sql
1581
1582 - def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
1583 if limitby:
1584 (lmin, lmax) = limitby
1585 sql_o += ' LIMIT %i OFFSET %i' % (lmax - lmin, lmin)
1586 return 'SELECT %s %s FROM %s%s%s;' % \
1587 (sql_s, sql_f, sql_t, sql_w, sql_o)
1588
1590 return self.cursor.fetchall()
1591
1593 args_get = attributes.get
1594 cache = args_get('cache',None)
1595 if not cache:
1596 self.execute(sql)
1597 rows = self._fetchall()
1598 else:
1599 (cache_model, time_expire) = cache
1600 key = self.uri + '/' + sql + '/rows'
1601 if len(key)>200: key = hashlib_md5(key).hexdigest()
1602 def _select_aux2():
1603 self.execute(sql)
1604 return self._fetchall()
1605 rows = cache_model(key,_select_aux2,time_expire)
1606 if isinstance(rows,tuple):
1607 rows = list(rows)
1608 limitby = args_get('limitby', None) or (0,)
1609 rows = self.rowslice(rows,limitby[0],None)
1610 processor = args_get('processor',self.parse)
1611 cacheable = args_get('cacheable',False)
1612 return processor(rows,fields,self._colnames,cacheable=cacheable)
1613
1614 - def select(self, query, fields, attributes):
1615 """
1616 Always returns a Rows object, possibly empty.
1617 """
1618 sql = self._select(query, fields, attributes)
1619 cache = attributes.get('cache', None)
1620 if cache and attributes.get('cacheable',False):
1621 del attributes['cache']
1622 (cache_model, time_expire) = cache
1623 key = self.uri + '/' + sql
1624 if len(key)>200: key = hashlib_md5(key).hexdigest()
1625 args = (sql,fields,attributes)
1626 return cache_model(
1627 key,
1628 lambda self=self,args=args:self._select_aux(*args),
1629 time_expire)
1630 else:
1631 return self._select_aux(sql,fields,attributes)
1632
1633 - def _count(self, query, distinct=None):
1634 tablenames = self.tables(query)
1635 if query:
1636 if use_common_filters(query):
1637 query = self.common_filter(query, tablenames)
1638 sql_w = ' WHERE ' + self.expand(query)
1639 else:
1640 sql_w = ''
1641 sql_t = ','.join(tablenames)
1642 if distinct:
1643 if isinstance(distinct,(list, tuple)):
1644 distinct = xorify(distinct)
1645 sql_d = self.expand(distinct)
1646 return 'SELECT count(DISTINCT %s) FROM %s%s;' % \
1647 (sql_d, sql_t, sql_w)
1648 return 'SELECT count(*) FROM %s%s;' % (sql_t, sql_w)
1649
1650 - def count(self, query, distinct=None):
1651 self.execute(self._count(query, distinct))
1652 return self.cursor.fetchone()[0]
1653
1665
1668
1671
1674
1677
1680
1683
1686
1689
1691 return '%s_%s__constraint' % (table,fieldname)
1692
1695
1697 if not self.connection: return None
1698 command = a[0]
1699 if self.db._debug:
1700 LOGGER.debug('SQL: %s' % command)
1701 self.db._lastsql = command
1702 t0 = time.time()
1703 ret = self.cursor.execute(*a, **b)
1704 self.db._timings.append((command,time.time()-t0))
1705 del self.db._timings[:-TIMINGSSIZE]
1706 return ret
1707
1710
1712 field_is_type = fieldtype.startswith
1713 if isinstance(obj, CALLABLETYPES):
1714 obj = obj()
1715 if isinstance(fieldtype, SQLCustomType):
1716 value = fieldtype.encoder(obj)
1717 if fieldtype.type in ('string','text'):
1718 return self.adapt(value)
1719 return value
1720 if isinstance(obj, (Expression, Field)):
1721 return str(obj)
1722 if field_is_type('list:'):
1723 if not obj:
1724 obj = []
1725 elif not isinstance(obj, (list, tuple)):
1726 obj = [obj]
1727 if field_is_type('list:string'):
1728 obj = map(str,obj)
1729 else:
1730 obj = map(int,obj)
1731 if isinstance(obj, (list, tuple)):
1732 obj = bar_encode(obj)
1733 if obj is None:
1734 return 'NULL'
1735 if obj == '' and not fieldtype[:2] in ['st', 'te', 'pa', 'up']:
1736 return 'NULL'
1737 r = self.represent_exceptions(obj, fieldtype)
1738 if not r is None:
1739 return r
1740 if fieldtype == 'boolean':
1741 if obj and not str(obj)[:1].upper() in '0F':
1742 return self.smart_adapt(self.TRUE)
1743 else:
1744 return self.smart_adapt(self.FALSE)
1745 if fieldtype == 'id' or fieldtype == 'integer':
1746 return str(int(obj))
1747 if field_is_type('decimal'):
1748 return str(obj)
1749 elif field_is_type('reference'):
1750 if fieldtype.find('.')>0:
1751 return repr(obj)
1752 elif isinstance(obj, (Row, Reference)):
1753 return str(obj['id'])
1754 return str(int(obj))
1755 elif fieldtype == 'double':
1756 return repr(float(obj))
1757 if isinstance(obj, unicode):
1758 obj = obj.encode(self.db_codec)
1759 if fieldtype == 'blob':
1760 obj = base64.b64encode(str(obj))
1761 elif fieldtype == 'date':
1762 if isinstance(obj, (datetime.date, datetime.datetime)):
1763 obj = obj.isoformat()[:10]
1764 else:
1765 obj = str(obj)
1766 elif fieldtype == 'datetime':
1767 if isinstance(obj, datetime.datetime):
1768 obj = obj.isoformat()[:19].replace('T',' ')
1769 elif isinstance(obj, datetime.date):
1770 obj = obj.isoformat()[:10]+' 00:00:00'
1771 else:
1772 obj = str(obj)
1773 elif fieldtype == 'time':
1774 if isinstance(obj, datetime.time):
1775 obj = obj.isoformat()[:10]
1776 else:
1777 obj = str(obj)
1778 if not isinstance(obj,bytes):
1779 obj = bytes(obj)
1780 try:
1781 obj.decode(self.db_codec)
1782 except:
1783 obj = obj.decode('latin1').encode(self.db_codec)
1784 return self.adapt(obj)
1785
1788
1791
1794
1795 - def rowslice(self, rows, minimum=0, maximum=None):
1796 """
1797 By default this function does nothing;
1798 overload when db does not do slicing.
1799 """
1800 return rows
1801
1802 - def parse_value(self, value, field_type, blob_decode=True):
1803 if field_type != 'blob' and isinstance(value, str):
1804 try:
1805 value = value.decode(self.db._db_codec)
1806 except Exception:
1807 pass
1808 if isinstance(value, unicode):
1809 value = value.encode('utf-8')
1810 if isinstance(field_type, SQLCustomType):
1811 value = field_type.decoder(value)
1812 if not isinstance(field_type, str) or value is None:
1813 return value
1814 elif field_type in ('string', 'text', 'password', 'upload', 'dict'):
1815 return value
1816 elif field_type.startswith('geo'):
1817 return value
1818 elif field_type == 'blob' and not blob_decode:
1819 return value
1820 else:
1821 key = REGEX_TYPE.match(field_type).group(0)
1822 return self.parsemap[key](value,field_type)
1823
1825 referee = field_type[10:].strip()
1826 if not '.' in referee:
1827 value = Reference(value)
1828 value._table, value._record = self.db[referee], None
1829 return value
1830
1832 return value == True or str(value)[:1].lower() == 't'
1833
1835 if not isinstance(value, (datetime.date,datetime.datetime)):
1836 (y, m, d) = map(int, str(value)[:10].strip().split('-'))
1837 value = datetime.date(y, m, d)
1838 return value
1839
1841 if not isinstance(value, datetime.time):
1842 time_items = map(int,str(value)[:8].strip().split(':')[:3])
1843 if len(time_items) == 3:
1844 (h, mi, s) = time_items
1845 else:
1846 (h, mi, s) = time_items + [0]
1847 value = datetime.time(h, mi, s)
1848 return value
1849
1851 if not isinstance(value, datetime.datetime):
1852 value = str(value)
1853 date_part,time_part,timezone = value[:10],value[11:19],value[19:]
1854 if '+' in timezone:
1855 ms,tz = timezone.split('+')
1856 h,m = tz.split(':')
1857 dt = datetime.timedelta(seconds=3600*int(h)+60*int(m))
1858 elif '-' in timezone:
1859 ms,tz = timezone.split('-')
1860 h,m = tz.split(':')
1861 dt = -datetime.timedelta(seconds=3600*int(h)+60*int(m))
1862 else:
1863 dt = None
1864 (y, m, d) = map(int,date_part.split('-'))
1865 time_parts = time_part and time_part.split(':')[:3] or (0,0,0)
1866 while len(time_parts)<3: time_parts.append(0)
1867 time_items = map(int,time_parts)
1868 (h, mi, s) = time_items
1869 value = datetime.datetime(y, m, d, h, mi, s)
1870 if dt:
1871 value = value + dt
1872 return value
1873
1875 return base64.b64decode(str(value))
1876
1878 decimals = int(field_type[8:-1].split(',')[-1])
1879 if self.dbengine in ('sqlite', 'spatialite'):
1880 value = ('%.' + str(decimals) + 'f') % value
1881 if not isinstance(value, decimal.Decimal):
1882 value = decimal.Decimal(str(value))
1883 return value
1884
1889
1894
1899
1900 - def parse_id(self, value, field_type):
1902
1905
1908
1910 self.parsemap = {
1911 'id':self.parse_id,
1912 'integer':self.parse_integer,
1913 'bigint':self.parse_integer,
1914 'float':self.parse_double,
1915 'double':self.parse_double,
1916 'reference':self.parse_reference,
1917 'boolean':self.parse_boolean,
1918 'date':self.parse_date,
1919 'time':self.parse_time,
1920 'datetime':self.parse_datetime,
1921 'blob':self.parse_blob,
1922 'decimal':self.parse_decimal,
1923 'list:integer':self.parse_list_integers,
1924 'list:reference':self.parse_list_references,
1925 'list:string':self.parse_list_strings,
1926 }
1927
1928 - def parse(self, rows, fields, colnames, blob_decode=True,
1929 cacheable = False):
1930 self.build_parsemap()
1931 db = self.db
1932 virtualtables = []
1933 new_rows = []
1934 tmps = []
1935 for colname in colnames:
1936 if not REGEX_TABLE_DOT_FIELD.match(colname):
1937 tmps.append(None)
1938 else:
1939 (tablename, fieldname) = colname.split('.')
1940 table = db[tablename]
1941 field = table[fieldname]
1942 ft = field.type
1943 tmps.append((tablename,fieldname,table,field,ft))
1944 for (i,row) in enumerate(rows):
1945 new_row = Row()
1946 for (j,colname) in enumerate(colnames):
1947 value = row[j]
1948 tmp = tmps[j]
1949 if tmp:
1950 (tablename,fieldname,table,field,ft) = tmp
1951 if tablename in new_row:
1952 colset = new_row[tablename]
1953 else:
1954 colset = new_row[tablename] = Row()
1955 if tablename not in virtualtables:
1956 virtualtables.append(tablename)
1957 value = self.parse_value(value,ft,blob_decode)
1958 if field.filter_out:
1959 value = field.filter_out(value)
1960 colset[fieldname] = value
1961
1962
1963 if ft=='id' and fieldname!='id' and \
1964 not 'id' in table.fields:
1965 colset['id'] = value
1966
1967 if ft == 'id' and not cacheable:
1968
1969
1970
1971 if isinstance(self, GoogleDatastoreAdapter):
1972 id = value.key().id_or_name()
1973 colset[fieldname] = id
1974 colset.gae_item = value
1975 else:
1976 id = value
1977 colset.update_record = RecordUpdater(colset,table,id)
1978 colset.delete_record = RecordDeleter(table,id)
1979 for rfield in table._referenced_by:
1980 referee_link = db._referee_name and \
1981 db._referee_name % dict(
1982 table=rfield.tablename,field=rfield.name)
1983 if referee_link and not referee_link in colset:
1984 colset[referee_link] = LazySet(rfield,id)
1985 else:
1986 if not '_extra' in new_row:
1987 new_row['_extra'] = Row()
1988 new_row['_extra'][colname] = \
1989 self.parse_value(value,
1990 fields[j].type,blob_decode)
1991 new_column_name = \
1992 REGEX_SELECT_AS_PARSER.search(colname)
1993 if not new_column_name is None:
1994 column_name = new_column_name.groups(0)
1995 setattr(new_row,column_name[0],value)
1996 new_rows.append(new_row)
1997 rowsobj = Rows(db, new_rows, colnames, rawrows=rows)
1998
1999 for tablename in virtualtables:
2000
2001 table = db[tablename]
2002 fields_virtual = [(f,v) for (f,v) in table.iteritems()
2003 if isinstance(v,FieldVirtual)]
2004 fields_lazy = [(f,v) for (f,v) in table.iteritems()
2005 if isinstance(v,FieldMethod)]
2006 if fields_virtual or fields_lazy:
2007 for row in rowsobj.records:
2008 box = row[tablename]
2009 for f,v in fields_virtual:
2010 box[f] = v.f(row)
2011 for f,v in fields_lazy:
2012 box[f] = (v.handler or VirtualCommand)(v.f,row)
2013
2014
2015 for item in table.virtualfields:
2016 try:
2017 rowsobj = rowsobj.setvirtualfields(**{tablename:item})
2018 except (KeyError, AttributeError):
2019
2020 pass
2021 return rowsobj
2022
2024 tenant_fieldname = self.db._request_tenant
2025
2026 for tablename in tablenames:
2027 table = self.db[tablename]
2028
2029
2030 if table._common_filter != None:
2031 query = query & table._common_filter(query)
2032
2033
2034 if tenant_fieldname in table:
2035 default = table[tenant_fieldname].default
2036 if not default is None:
2037 newquery = table[tenant_fieldname] == default
2038 if query is None:
2039 query = newquery
2040 else:
2041 query = query & newquery
2042 return query
2043
2044 - def CASE(self,query,t,f):
2045 def represent(x):
2046 types = {type(True):'boolean',type(0):'integer',type(1.0):'double'}
2047 if x is None: return 'NULL'
2048 elif isinstance(x,Expression): return str(x)
2049 else: return self.represent(x,types.get(type(x),'string'))
2050 return Expression(self.db,'CASE WHEN %s THEN %s ELSE %s END' % \
2051 (self.expand(query),represent(t),represent(f)))
2052
2053
2054
2055
2056
2058 drivers = ('sqlite2','sqlite3')
2059
2060 can_select_for_update = None
2061
2063 return "web2py_extract('%s',%s)" % (what, self.expand(field))
2064
2065 @staticmethod
2067 table = {
2068 'year': (0, 4),
2069 'month': (5, 7),
2070 'day': (8, 10),
2071 'hour': (11, 13),
2072 'minute': (14, 16),
2073 'second': (17, 19),
2074 }
2075 try:
2076 if lookup != 'epoch':
2077 (i, j) = table[lookup]
2078 return int(s[i:j])
2079 else:
2080 return time.mktime(datetime.datetime.strptime(s, '%Y-%m-%d %H:%M:%S').timetuple())
2081 except:
2082 return None
2083
2084 @staticmethod
2086 return re.compile(expression).search(item) is not None
2087
2088 - def __init__(self, db, uri, pool_size=0, folder=None, db_codec ='UTF-8',
2089 credential_decoder=IDENTITY, driver_args={},
2090 adapter_args={}, do_connect=True):
2091 self.db = db
2092 self.dbengine = "sqlite"
2093 self.uri = uri
2094 if do_connect: self.find_driver(adapter_args)
2095 self.pool_size = 0
2096 self.folder = folder
2097 self.db_codec = db_codec
2098 self.find_or_make_work_folder()
2099 path_encoding = sys.getfilesystemencoding() \
2100 or locale.getdefaultlocale()[1] or 'utf8'
2101 if uri.startswith('sqlite:memory'):
2102 dbpath = ':memory:'
2103 else:
2104 dbpath = uri.split('://',1)[1]
2105 if dbpath[0] != '/':
2106 if PYTHON_VERSION == 2:
2107 dbpath = pjoin(
2108 self.folder.decode(path_encoding).encode('utf8'), dbpath)
2109 else:
2110 dbpath = pjoin(self.folder, dbpath)
2111 if not 'check_same_thread' in driver_args:
2112 driver_args['check_same_thread'] = False
2113 if not 'detect_types' in driver_args:
2114 driver_args['detect_types'] = self.driver.PARSE_DECLTYPES
2115 def connector(dbpath=dbpath, driver_args=driver_args):
2116 return self.driver.Connection(dbpath, **driver_args)
2117 self.connector = connector
2118 if do_connect: self.reconnect()
2119
2125
2127 tablename = table._tablename
2128 return ['DELETE FROM %s;' % tablename,
2129 "DELETE FROM sqlite_sequence WHERE name='%s';" % tablename]
2130
2133
2134 - def REGEXP(self,first,second):
2135 return '(%s REGEXP %s)' % (self.expand(first),
2136 self.expand(second,'string'))
2137
2138 - def select(self, query, fields, attributes):
2139 """
2140 Simulate SELECT ... FOR UPDATE with BEGIN IMMEDIATE TRANSACTION.
2141 Note that the entire database, rather than one record, is locked
2142 (it will be locked eventually anyway by the following UPDATE).
2143 """
2144 if attributes.get('for_update', False) and not 'cache' in attributes:
2145 self.execute('BEGIN IMMEDIATE TRANSACTION;')
2146 return super(SQLiteAdapter, self).select(query, fields, attributes)
2147
2149 drivers = ('sqlite3','sqlite2')
2150
2151 types = copy.copy(BaseAdapter.types)
2152 types.update(geometry='GEOMETRY')
2153
2154 - def __init__(self, db, uri, pool_size=0, folder=None, db_codec ='UTF-8',
2155 credential_decoder=IDENTITY, driver_args={},
2156 adapter_args={}, do_connect=True, srid=4326):
2157 self.db = db
2158 self.dbengine = "spatialite"
2159 self.uri = uri
2160 if do_connect: self.find_driver(adapter_args)
2161 self.pool_size = 0
2162 self.folder = folder
2163 self.db_codec = db_codec
2164 self.find_or_make_work_folder()
2165 self.srid = srid
2166 path_encoding = sys.getfilesystemencoding() \
2167 or locale.getdefaultlocale()[1] or 'utf8'
2168 if uri.startswith('spatialite:memory'):
2169 dbpath = ':memory:'
2170 else:
2171 dbpath = uri.split('://',1)[1]
2172 if dbpath[0] != '/':
2173 dbpath = pjoin(
2174 self.folder.decode(path_encoding).encode('utf8'), dbpath)
2175 if not 'check_same_thread' in driver_args:
2176 driver_args['check_same_thread'] = False
2177 if not 'detect_types' in driver_args:
2178 driver_args['detect_types'] = self.driver.PARSE_DECLTYPES
2179 def connector(dbpath=dbpath, driver_args=driver_args):
2180 return self.driver.Connection(dbpath, **driver_args)
2181 self.connector = connector
2182 if do_connect: self.reconnect()
2183
2196
2197
2198
2200 return 'AsGeoJSON(%s,%s,%s)' %(self.expand(first),
2201 second['precision'], second['options'])
2202
2203 - def ST_ASTEXT(self, first):
2204 return 'AsText(%s)' %(self.expand(first))
2205
2209
2213
2217
2221
2225
2227 return 'Simplify(%s,%s)' %(self.expand(first),
2228 self.expand(second, 'double'))
2229
2233
2237
2239 field_is_type = fieldtype.startswith
2240 if field_is_type('geo'):
2241 srid = 4326
2242 geotype, parms = fieldtype[:-1].split('(')
2243 parms = parms.split(',')
2244 if len(parms) >= 2:
2245 schema, srid = parms[:2]
2246
2247 value = "ST_GeomFromText('%s',%s)" %(obj, srid)
2248
2249
2250
2251
2252 return value
2253 return BaseAdapter.represent(self, obj, fieldtype)
2254
2255
2257 drivers = ('zxJDBC_sqlite',)
2258
2259 - def __init__(self, db, uri, pool_size=0, folder=None, db_codec='UTF-8',
2260 credential_decoder=IDENTITY, driver_args={},
2261 adapter_args={}, do_connect=True):
2262 self.db = db
2263 self.dbengine = "sqlite"
2264 self.uri = uri
2265 if do_connect: self.find_driver(adapter_args)
2266 self.pool_size = pool_size
2267 self.folder = folder
2268 self.db_codec = db_codec
2269 self.find_or_make_work_folder()
2270 path_encoding = sys.getfilesystemencoding() \
2271 or locale.getdefaultlocale()[1] or 'utf8'
2272 if uri.startswith('sqlite:memory'):
2273 dbpath = ':memory:'
2274 else:
2275 dbpath = uri.split('://',1)[1]
2276 if dbpath[0] != '/':
2277 dbpath = pjoin(
2278 self.folder.decode(path_encoding).encode('utf8'), dbpath)
2279 def connector(dbpath=dbpath,driver_args=driver_args):
2280 return self.driver.connect(
2281 self.driver.getConnection('jdbc:sqlite:'+dbpath),
2282 **driver_args)
2283 self.connector = connector
2284 if do_connect: self.reconnect()
2285
2290
2293
2294
2296 drivers = ('MySQLdb','pymysql')
2297
2298 maxcharlength = 255
2299 commit_on_alter_table = True
2300 support_distributed_transaction = True
2301 types = {
2302 'boolean': 'CHAR(1)',
2303 'string': 'VARCHAR(%(length)s)',
2304 'text': 'LONGTEXT',
2305 'password': 'VARCHAR(%(length)s)',
2306 'blob': 'LONGBLOB',
2307 'upload': 'VARCHAR(%(length)s)',
2308 'integer': 'INT',
2309 'bigint': 'BIGINT',
2310 'float': 'FLOAT',
2311 'double': 'DOUBLE',
2312 'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
2313 'date': 'DATE',
2314 'time': 'TIME',
2315 'datetime': 'DATETIME',
2316 'id': 'INT AUTO_INCREMENT NOT NULL',
2317 'reference': 'INT, INDEX %(index_name)s (%(field_name)s), FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
2318 'list:integer': 'LONGTEXT',
2319 'list:string': 'LONGTEXT',
2320 'list:reference': 'LONGTEXT',
2321 'big-id': 'BIGINT AUTO_INCREMENT NOT NULL',
2322 'big-reference': 'BIGINT, INDEX %(index_name)s (%(field_name)s), FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
2323 }
2324
2327
2330
2332 return 'SUBSTRING(%s,%s,%s)' % (self.expand(field),
2333 parameters[0], parameters[1])
2334
2335 - def EPOCH(self, first):
2337
2338 - def _drop(self,table,mode):
2339
2340 return ['SET FOREIGN_KEY_CHECKS=0;','DROP TABLE %s;' % table,
2341 'SET FOREIGN_KEY_CHECKS=1;']
2342
2345
2349
2352
2355
2357 return '; ALTER TABLE %s ADD ' % table
2358
2359 REGEX_URI = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>[^?]+)(\?set_encoding=(?P<charset>\w+))?$')
2360
2361 - def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
2362 credential_decoder=IDENTITY, driver_args={},
2363 adapter_args={}, do_connect=True):
2364 self.db = db
2365 self.dbengine = "mysql"
2366 self.uri = uri
2367 if do_connect: self.find_driver(adapter_args,uri)
2368 self.pool_size = pool_size
2369 self.folder = folder
2370 self.db_codec = db_codec
2371 self.find_or_make_work_folder()
2372 ruri = uri.split('://',1)[1]
2373 m = self.REGEX_URI.match(ruri)
2374 if not m:
2375 raise SyntaxError(
2376 "Invalid URI string in DAL: %s" % self.uri)
2377 user = credential_decoder(m.group('user'))
2378 if not user:
2379 raise SyntaxError('User required')
2380 password = credential_decoder(m.group('password'))
2381 if not password:
2382 password = ''
2383 host = m.group('host')
2384 if not host:
2385 raise SyntaxError('Host name required')
2386 db = m.group('db')
2387 if not db:
2388 raise SyntaxError('Database name required')
2389 port = int(m.group('port') or '3306')
2390 charset = m.group('charset') or 'utf8'
2391 driver_args.update(db=db,
2392 user=credential_decoder(user),
2393 passwd=credential_decoder(password),
2394 host=host,
2395 port=port,
2396 charset=charset)
2397
2398
2399 def connector(driver_args=driver_args):
2400 return self.driver.connect(**driver_args)
2401 self.connector = connector
2402 if do_connect: self.reconnect()
2403
2405 self.execute('SET FOREIGN_KEY_CHECKS=1;')
2406 self.execute("SET sql_mode='NO_BACKSLASH_ESCAPES';")
2407
2409 self.execute('select last_insert_id();')
2410 return int(self.cursor.fetchone()[0])
2411
2412 -class PostgreSQLAdapter(BaseAdapter):