~ubuntu-branches/debian/jessie/web2py/jessie

« back to all changes in this revision

Viewing changes to gluon/dal.py

  • Committer: Package Import Robot
  • Author(s): José L. Redrejo Rodríguez
  • Date: 2011-11-04 10:12:01 UTC
  • mfrom: (1.1.10)
  • Revision ID: package-import@ubuntu.com-20111104101201-ym8q3030ik8sc10u
Tags: 1.99.2.1-1
* Updated upstream sources with real 1.99.2 version
* Ensure python-gtk2 is not needed to run web2py, fixing 
  debian/patches/gtk_gui (Closes: #646931)
* Refreshed debian/patches/avoid_updating patch

Show diffs side-by-side

added added

removed removed

Lines of Context:
107
107
'informixu://user:password@server:3050/database' # unicode informix
108
108
'google:datastore' # for google app engine datastore
109
109
'google:sql' # for google app engine with sql (mysql compatible)
110
 
'teradata://DSN=dsn;UID=user;PWD=pass' # experimental 
 
110
'teradata://DSN=dsn;UID=user;PWD=pass' # experimental
111
111
 
112
112
For more info:
113
113
help(DAL)
119
119
###################################################################################
120
120
 
121
121
__all__ = ['DAL', 'Field']
122
 
MAXCHARLENGTH = 512
123
 
INFINITY = 2**15 # not quite but reasonable default max char length
 
122
 
 
123
MAXCHARLENGTH = 2**15 # not quite but reasonable default max char length
 
124
DEFAULTLENGTH = {'string':512,
 
125
                 'password':512,
 
126
                 'upload':512,
 
127
                 'text':2**15,
 
128
                 'blob':2**31}
124
129
 
125
130
import re
126
131
import sys
188
193
#  <table>.<field>, tables and fields may only be [a-zA-Z0-0_]
189
194
 
190
195
regex_dbname = re.compile('^(\w+)(\:\w+)*')
191
 
table_field = re.compile('^[\w_]+\.[\w_]+$')
 
196
table_field = re.compile('^([\w_]+)\.([\w_]+)$')
192
197
regex_content = re.compile('(?P<table>[\w\-]+)\.(?P<field>[\w\-]+)\.(?P<uuidkey>[\w\-]+)\.(?P<name>\w+)\.\w+$')
193
198
regex_cleanup_fn = re.compile('[\'"\s;]+')
194
199
string_unpack=re.compile('(?<!\|)\|(?!\|)')
230
235
 
231
236
    try:
232
237
        import psycopg2
 
238
        from psycopg2.extensions import adapt as psycopg2_adapt
233
239
        drivers.append('PostgreSQL')
234
240
    except ImportError:
235
241
        logger.debug('no psycopg2 driver')
264
270
        logger.warning('Informix support is experimental')
265
271
    except ImportError:
266
272
        logger.debug('no informixdb driver')
267
 
        
 
273
 
268
274
    try:
269
275
        import sapdb
270
276
        drivers.append('SAPDB')
310
316
    except:
311
317
        logger.debug('no mongoDB driver')
312
318
 
 
319
def OR(a,b):
 
320
    return a|b
 
321
 
 
322
def AND(a,b):
 
323
    return a&b
313
324
 
314
325
if 'google' in drivers:
315
326
 
356
367
class ConnectionPool(object):
357
368
 
358
369
    pools = {}
 
370
    check_active_connection = True
359
371
 
360
372
    @staticmethod
361
373
    def set_folder(folder):
395
407
        if False and self.folder and not os.path.exists(self.folder):
396
408
            os.mkdir(self.folder)
397
409
 
398
 
    def pool_connection(self, f):
 
410
    def pool_connection(self, f, cursor=True):
 
411
        """
 
412
        this function defines: self.connection and self.cursor (iff cursor is True)
 
413
        if self.pool_size>0 it will try pull the connection from the pool
 
414
        if the connection is not active (closed by db server) it will loop
 
415
        if not self.pool_size or no active connections in pool makes a new one
 
416
        """
399
417
        if not self.pool_size:
400
418
            self.connection = f()
 
419
            self.cursor = cursor and self.connection.cursor()
401
420
        else:
402
421
            uri = self.uri
403
 
            sql_locker.acquire()
404
 
            if not uri in ConnectionPool.pools:
405
 
                ConnectionPool.pools[uri] = []
406
 
            if ConnectionPool.pools[uri]:
407
 
                self.connection = ConnectionPool.pools[uri].pop()
408
 
                sql_locker.release()
409
 
            else:
410
 
                sql_locker.release()
411
 
                self.connection = f()
 
422
            while True:
 
423
                sql_locker.acquire()
 
424
                if not uri in ConnectionPool.pools:
 
425
                    ConnectionPool.pools[uri] = []                    
 
426
                if ConnectionPool.pools[uri]:
 
427
                    self.connection = ConnectionPool.pools[uri].pop()
 
428
                    sql_locker.release()
 
429
                    self.cursor = cursor and self.connection.cursor()
 
430
                    try:
 
431
                        if self.cursor and self.check_active_connection:
 
432
                            self.execute('SELECT 1;')
 
433
                        break
 
434
                    except:
 
435
                        pass
 
436
                else:
 
437
                    sql_locker.release()
 
438
                    self.connection = f()
 
439
                    self.cursor = cursor and self.connection.cursor()
 
440
                    break
412
441
        if not hasattr(thread,'instances'):
413
442
            thread.instances = []
414
443
        thread.instances.append(self)
421
450
class BaseAdapter(ConnectionPool):
422
451
 
423
452
    driver = None
424
 
    maxcharlength = INFINITY
 
453
    maxcharlength = MAXCHARLENGTH
425
454
    commit_on_alter_table = False
426
455
    support_distributed_transaction = False
427
456
    uploads_in_blob = False
445
474
        'list:reference': 'TEXT',
446
475
        }
447
476
 
 
477
    def adapt(self,obj):
 
478
        return "'%s'" % obj.replace("'", "''")
 
479
 
448
480
    def integrity_error(self):
449
481
        return self.driver.IntegrityError
450
482
 
 
483
    def operational_error(self):
 
484
        return self.driver.OperationalError
 
485
 
451
486
    def file_exists(self, filename):
452
487
        """
453
488
        to be used ONLY for files that on GAE may not be on filesystem
798
833
    def NOT_NULL(self,default,field_type):
799
834
        return 'NOT NULL DEFAULT %s' % self.represent(default,field_type)
800
835
 
 
836
    def COALESCE(self,first,second):
 
837
        expressions = [self.expand(first)]+[self.expand(e) for e in second]
 
838
        return 'COALESCE(%s)' % ','.join(expressions)
 
839
 
801
840
    def COALESCE_ZERO(self,first):
802
841
        return 'COALESCE(%s,0)' % self.expand(first)
803
842
 
 
843
    def RAW(self,first):
 
844
        return first
 
845
 
804
846
    def ALLOW_NULL(self):
805
847
        return ''
806
848
 
868
910
        if isinstance(second,str):
869
911
            return '(%s IN (%s))' % (self.expand(first),second[:-1])
870
912
        elif second==[] or second==():
871
 
            return '(0)'
 
913
            return '(1=0)'
872
914
        items =','.join(self.expand(item,first.type) for item in second)
873
915
        return '(%s IN (%s))' % (self.expand(first),items)
874
916
 
945
987
                return expression.op(expression.first, expression.second)
946
988
            elif not expression.first is None:
947
989
                return expression.op(expression.first)
 
990
            elif not isinstance(expression.op,str):
 
991
                return expression.op()
948
992
            else:
949
 
                return expression.op()
 
993
                return '(%s)' % expression.op
950
994
        elif field_type:
951
995
            return self.represent(expression,field_type)
952
996
        elif isinstance(expression,(list,tuple)):
998
1042
            logfile.close()
999
1043
 
1000
1044
    def _update(self,tablename,query,fields):
 
1045
        query = self.filter_tenant(query,[tablename])
1001
1046
        if query:
1002
1047
            sql_w = ' WHERE ' + self.expand(query)
1003
1048
        else:
1014
1059
            return None
1015
1060
 
1016
1061
    def _delete(self,tablename, query):
 
1062
        query = self.filter_tenant(query,[tablename])
1017
1063
        if query:
1018
1064
            sql_w = ' WHERE ' + self.expand(query)
1019
1065
        else:
1193
1239
 
1194
1240
    def _count(self,query,distinct=None):
1195
1241
        tablenames = self.tables(query)
 
1242
        query = self.filter_tenant(query,tablenames)
1196
1243
        if query:
1197
1244
            sql_w = ' WHERE ' + self.expand(query)
1198
1245
        else:
1202
1249
            if isinstance(distinct,(list,tuple)):
1203
1250
                distinct = xorify(distinct)
1204
1251
            sql_d = self.expand(distinct)
1205
 
            return 'SELECT count(DISTINCT %s) FROM %s%s' % (sql_d, sql_t, sql_w)
1206
 
        return 'SELECT count(*) FROM %s%s' % (sql_t, sql_w)
 
1252
            return 'SELECT count(DISTINCT %s) FROM %s%s;' % (sql_d, sql_t, sql_w)
 
1253
        return 'SELECT count(*) FROM %s%s;' % (sql_t, sql_w)
1207
1254
 
1208
1255
    def count(self,query,distinct=None):
1209
1256
        self.execute(self._count(query,distinct))
1215
1262
        if isinstance(query, Field):
1216
1263
            tables.add(query.tablename)
1217
1264
        elif isinstance(query, (Expression, Query)):
1218
 
            if query.first!=None:
 
1265
            if not query.first is None:
1219
1266
                tables = tables.union(self.tables(query.first))
1220
 
            if query.second!=None:
 
1267
            if not query.second is None:
1221
1268
                tables = tables.union(self.tables(query.second))
1222
1269
        return list(tables)
1223
1270
 
1280
1327
        if obj == '' and not fieldtype[:2] in ['st', 'te', 'pa', 'up']:
1281
1328
            return 'NULL'
1282
1329
        r = self.represent_exceptions(obj,fieldtype)
1283
 
        if r != None:
 
1330
        if not r is None:
1284
1331
            return r
1285
1332
        if fieldtype == 'boolean':
1286
1333
            if obj and not str(obj)[:1].upper() in ['F', '0']:
1326
1373
            obj.decode(self.db_codec)
1327
1374
        except:
1328
1375
            obj = obj.decode('latin1').encode(self.db_codec)
1329
 
        return "'%s'" % obj.replace("'", "''")
 
1376
        return self.adapt(obj)
1330
1377
 
1331
1378
    def represent_exceptions(self, obj, fieldtype):
1332
1379
        return None
1450
1497
                    for (referee_table, referee_name) in \
1451
1498
                            table._referenced_by:
1452
1499
                        s = db[referee_table][referee_name]
1453
 
                        if not referee_table in colset:
1454
 
                            # for backward compatibility
1455
 
                            colset[referee_table] = Set(db, s == id)
1456
 
                        ### add new feature?
1457
 
                        ### colset[referee_table+'_by_'+refree_name] = Set(db, s == id)
 
1500
                        referee_link = db._referee_name and \
 
1501
                            db._referee_name % dict(table=referee_table,field=referee_name)
 
1502
                        if referee_link and not referee_link in colset:
 
1503
                            colset[referee_link] = Set(db, s == id)
1458
1504
                    colset['id'] = id
1459
1505
            new_rows.append(new_row)
 
1506
 
1460
1507
        rowsobj = Rows(db, new_rows, colnames, rawrows=rows)
 
1508
 
1461
1509
        for tablename in virtualtables:
1462
 
            for item in db[tablename].virtualfields:
 
1510
            ### new style virtual fields
 
1511
            table = db[tablename]
 
1512
            fields_virtual = [(f,v) for (f,v) in table.items() if isinstance(v,FieldVirtual)]
 
1513
            fields_lazy = [(f,v) for (f,v) in table.items() if isinstance(v,FieldLazy)]
 
1514
            if fields_virtual or fields_lazy:
 
1515
                for row in rowsobj.records:
 
1516
                    box = row[tablename]
 
1517
                    for f,v in fields_virtual:
 
1518
                        box[f] = v.f(row)
 
1519
                    for f,v in fields_lazy:
 
1520
                        box[f] = (v.handler or VirtualCommand)(v.f,row)
 
1521
 
 
1522
            ### old style virtual fields
 
1523
            for item in table.virtualfields:
1463
1524
                try:
1464
1525
                    rowsobj = rowsobj.setvirtualfields(**{tablename:item})
1465
1526
                except KeyError:
1473
1534
            table = self.db[tablename]
1474
1535
            if fieldname in table:
1475
1536
                default = table[fieldname].default
1476
 
                if default!=None:
1477
 
                    query = query&(table[fieldname]==default)
 
1537
                if not default is None:
 
1538
                    newquery = table[fieldname]==default
 
1539
                    if query is None:
 
1540
                        query = newquery
 
1541
                    else:
 
1542
                        query = query&newquery
1478
1543
        return query
1479
1544
 
1480
1545
###################################################################################
1506
1571
 
1507
1572
    def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
1508
1573
                 credential_decoder=lambda x:x, driver_args={},
1509
 
                    adapter_args={}):
 
1574
                 adapter_args={}):
1510
1575
        self.db = db
1511
1576
        self.dbengine = "sqlite"
1512
1577
        self.uri = uri
1523
1588
                dbpath = os.path.join(self.folder.decode(path_encoding).encode('utf8'),dbpath)
1524
1589
        if not 'check_same_thread' in driver_args:
1525
1590
            driver_args['check_same_thread'] = False
 
1591
        if not 'detect_types' in driver_args:
 
1592
            driver_args['detect_types'] = self.driver.PARSE_DECLTYPES
1526
1593
        def connect(dbpath=dbpath, driver_args=driver_args):
1527
1594
            return self.driver.Connection(dbpath, **driver_args)
1528
1595
        self.pool_connection(connect)
1529
 
        self.cursor = self.connection.cursor()
1530
1596
        self.connection.create_function('web2py_extract', 2, SQLiteAdapter.web2py_extract)
1531
1597
 
1532
1598
    def _truncate(self,table,mode = ''):
1544
1610
 
1545
1611
    def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
1546
1612
                 credential_decoder=lambda x:x, driver_args={},
1547
 
                    adapter_args={}):
 
1613
                 adapter_args={}):
1548
1614
        self.db = db
1549
1615
        self.dbengine = "sqlite"
1550
1616
        self.uri = uri
1561
1627
                dbpath = os.path.join(self.folder.decode(path_encoding).encode('utf8'),dbpath)
1562
1628
        def connect(dbpath=dbpath,driver_args=driver_args):
1563
1629
            return self.driver.connect(java.sql.DriverManager.getConnection('jdbc:sqlite:'+dbpath),**driver_args)
1564
 
        self.pool_connection(connect)
1565
 
        self.cursor = self.connection.cursor()
1566
1630
        # FIXME http://www.zentus.com/sqlitejdbc/custom_functions.html for UDFs
1567
1631
        # self.connection.create_function('web2py_extract', 2, SQLiteAdapter.web2py_extract)
1568
1632
 
1569
 
    def execute(self,a):
1570
 
        return self.log_execute(a[:-1])
 
1633
    def execute(self,a):        
 
1634
        return self.log_execute(a)
1571
1635
 
1572
1636
 
1573
1637
class MySQLAdapter(BaseAdapter):
1624
1688
 
1625
1689
    def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
1626
1690
                 credential_decoder=lambda x:x, driver_args={},
1627
 
                    adapter_args={}):
 
1691
                 adapter_args={}):
1628
1692
        self.db = db
1629
1693
        self.dbengine = "mysql"
1630
1694
        self.uri = uri
1660
1724
        def connect(driver_args=driver_args):
1661
1725
            return self.driver.connect(**driver_args)
1662
1726
        self.pool_connection(connect)
1663
 
        self.cursor = self.connection.cursor()
1664
1727
        self.execute('SET FOREIGN_KEY_CHECKS=1;')
1665
1728
        self.execute("SET sql_mode='NO_BACKSLASH_ESCAPES';")
1666
1729
 
1668
1731
        self.execute('select last_insert_id();')
1669
1732
        return int(self.cursor.fetchone()[0])
1670
1733
 
1671
 
 
1672
1734
class PostgreSQLAdapter(BaseAdapter):
1673
1735
 
1674
1736
    driver = globals().get('psycopg2',None)
1694
1756
        'list:reference': 'TEXT',
1695
1757
        }
1696
1758
 
 
1759
    def adapt(self,obj):
 
1760
        return psycopg2_adapt(obj).getquoted()
 
1761
 
1697
1762
    def sequence_name(self,table):
1698
1763
        return '%s_id_Seq' % table
1699
1764
 
1721
1786
 
1722
1787
    def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
1723
1788
                 credential_decoder=lambda x:x, driver_args={},
1724
 
                    adapter_args={}):
 
1789
                 adapter_args={}):
1725
1790
        self.db = db
1726
1791
        self.dbengine = "postgres"
1727
1792
        self.uri = uri
1759
1824
            return self.driver.connect(msg,**driver_args)
1760
1825
        self.pool_connection(connect)
1761
1826
        self.connection.set_client_encoding('UTF8')
1762
 
        self.cursor = self.connection.cursor()
1763
 
        self.execute('BEGIN;')
1764
 
        self.execute("SET CLIENT_ENCODING TO 'UNICODE';")
1765
1827
        self.execute("SET standard_conforming_strings=on;")
1766
1828
 
1767
1829
    def lastrowid(self,table):
1788
1850
 
1789
1851
    def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
1790
1852
                 credential_decoder=lambda x:x, driver_args={},
1791
 
                    adapter_args={}):
 
1853
                 adapter_args={}):
1792
1854
        self.db = db
1793
1855
        self.dbengine = "postgres"
1794
1856
        self.uri = uri
1818
1880
            return self.driver.connect(*msg,**driver_args)
1819
1881
        self.pool_connection(connect)
1820
1882
        self.connection.set_client_encoding('UTF8')
1821
 
        self.cursor = self.connection.cursor()
1822
1883
        self.execute('BEGIN;')
1823
1884
        self.execute("SET CLIENT_ENCODING TO 'UNICODE';")
1824
1885
 
1905
1966
 
1906
1967
    def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
1907
1968
                 credential_decoder=lambda x:x, driver_args={},
1908
 
                    adapter_args={}):
 
1969
                 adapter_args={}):
1909
1970
        self.db = db
1910
1971
        self.dbengine = "oracle"
1911
1972
        self.uri = uri
1919
1980
        def connect(uri=uri,driver_args=driver_args):
1920
1981
            return self.driver.connect(uri,**driver_args)
1921
1982
        self.pool_connection(connect)
1922
 
        self.cursor = self.connection.cursor()
1923
1983
        self.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';")
1924
1984
        self.execute("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS';")
1925
1985
    oracle_fix = re.compile("[^']*('[^']*'[^']*)*\:(?P<clob>CLOB\('([^']+|'')*'\))")
1934
1994
            command = command[:m.start('clob')] + str(i) + command[m.end('clob'):]
1935
1995
            args.append(m.group('clob')[6:-2].replace("''", "'"))
1936
1996
            i += 1
1937
 
        return self.log_execute(command[:-1], args)
 
1997
        if command[-1:]==';':
 
1998
            command = command[:-1]
 
1999
        return self.log_execute(command, args)
1938
2000
 
1939
2001
    def create_sequence_and_triggers(self, query, table, **args):
1940
2002
        tablename = table._tablename
2066
2128
            return self.driver.connect(cnxn,**driver_args)
2067
2129
        if not fake_connect:
2068
2130
            self.pool_connection(connect)
2069
 
            self.cursor = self.connection.cursor()
2070
2131
 
2071
2132
    def lastrowid(self,table):
2072
2133
        #self.execute('SELECT @@IDENTITY;')
2172
2233
 
2173
2234
    def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
2174
2235
                 credential_decoder=lambda x:x, driver_args={},
2175
 
                    adapter_args={}):
 
2236
                 adapter_args={}):
2176
2237
        self.db = db
2177
2238
        self.dbengine = "firebird"
2178
2239
        self.uri = uri
2208
2269
            elif adapter_args['driver_name'] == 'firebirdsql':
2209
2270
                self.driver = firebirdsql
2210
2271
        else:
2211
 
            self.driver = kinterbasdb        
 
2272
            self.driver = kinterbasdb
2212
2273
        def connect(driver_args=driver_args):
2213
2274
            return self.driver.connect(**driver_args)
2214
2275
        self.pool_connection(connect)
2215
 
        self.cursor = self.connection.cursor()
2216
2276
 
2217
2277
    def create_sequence_and_triggers(self, query, table, **args):
2218
2278
        tablename = table._tablename
2233
2293
 
2234
2294
    def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
2235
2295
                 credential_decoder=lambda x:x, driver_args={},
2236
 
                    adapter_args={}):
 
2296
                 adapter_args={}):
2237
2297
        self.db = db
2238
2298
        self.dbengine = "firebird"
2239
2299
        self.uri = uri
2272
2332
            elif adapter_args['driver_name'] == 'firebirdsql':
2273
2333
                self.driver = firebirdsql
2274
2334
        else:
2275
 
            self.driver = kinterbasdb        
 
2335
            self.driver = kinterbasdb
2276
2336
        def connect(driver_args=driver_args):
2277
2337
            return self.driver.connect(**driver_args)
2278
2338
        self.pool_connection(connect)
2279
 
        self.cursor = self.connection.cursor()
2280
2339
 
2281
2340
 
2282
2341
class InformixAdapter(BaseAdapter):
2343
2402
 
2344
2403
    def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
2345
2404
                 credential_decoder=lambda x:x, driver_args={},
2346
 
                    adapter_args={}):
 
2405
                 adapter_args={}):
2347
2406
        self.db = db
2348
2407
        self.dbengine = "informix"
2349
2408
        self.uri = uri
2375
2434
        def connect(dsn=dsn,driver_args=driver_args):
2376
2435
            return self.driver.connect(dsn,**driver_args)
2377
2436
        self.pool_connection(connect)
2378
 
        self.cursor = self.connection.cursor()
2379
2437
 
2380
2438
    def execute(self,command):
2381
2439
        if command[-1:]==';':
2441
2499
 
2442
2500
    def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
2443
2501
                 credential_decoder=lambda x:x, driver_args={},
2444
 
                    adapter_args={}):
 
2502
                 adapter_args={}):
2445
2503
        self.db = db
2446
2504
        self.dbengine = "db2"
2447
2505
        self.uri = uri
2453
2511
        def connect(cnxn=cnxn,driver_args=driver_args):
2454
2512
            return self.driver.connect(cnxn,**driver_args)
2455
2513
        self.pool_connection(connect)
2456
 
        self.cursor = self.connection.cursor()
2457
2514
 
2458
2515
    def execute(self,command):
2459
2516
        if command[-1:]==';':
2499
2556
 
2500
2557
    def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
2501
2558
                 credential_decoder=lambda x:x, driver_args={},
2502
 
                    adapter_args={}):
 
2559
                 adapter_args={}):
2503
2560
        self.db = db
2504
2561
        self.dbengine = "teradata"
2505
2562
        self.uri = uri
2511
2568
        def connect(cnxn=cnxn,driver_args=driver_args):
2512
2569
            return self.driver.connect(cnxn,**driver_args)
2513
2570
        self.pool_connection(connect)
2514
 
        self.cursor = self.connection.cursor()
2515
2571
 
2516
2572
 
2517
2573
INGRES_SEQNAME='ii***lineitemsequence' # NOTE invalid database object name
2563
2619
 
2564
2620
    def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
2565
2621
                 credential_decoder=lambda x:x, driver_args={},
2566
 
                    adapter_args={}):
 
2622
                 adapter_args={}):
2567
2623
        self.db = db
2568
2624
        self.dbengine = "ingres"
2569
2625
        self.uri = uri
2587
2643
        def connect(driver_args=driver_args):
2588
2644
            return self.driver.connect(**driver_args)
2589
2645
        self.pool_connection(connect)
2590
 
        self.cursor = self.connection.cursor()
2591
2646
 
2592
2647
    def create_sequence_and_triggers(self, query, table, **args):
2593
2648
        # post create table auto inc code (if needed)
2684
2739
 
2685
2740
    def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
2686
2741
                 credential_decoder=lambda x:x, driver_args={},
2687
 
                    adapter_args={}):
 
2742
                 adapter_args={}):
2688
2743
        self.db = db
2689
2744
        self.dbengine = "sapdb"
2690
2745
        self.uri = uri
2713
2768
            return self.driver.Connection(user,password,database,
2714
2769
                                          host,**driver_args)
2715
2770
        self.pool_connection(connect)
2716
 
        # self.connection.set_client_encoding('UTF8')
2717
 
        self.cursor = self.connection.cursor()
2718
2771
 
2719
2772
    def lastrowid(self,table):
2720
2773
        self.execute("select %s.NEXTVAL from dual" % table._sequence_name)
2758
2811
        def connect(host,port,db,user,passwd,driver_args=driver_args):
2759
2812
            return self.driver.connect(host,port,db,user,passwd,**driver_args)
2760
2813
        self.pool_connection(connect)
2761
 
        self.cursor = self.connection.cursor()
2762
2814
        self.execute('SET FOREIGN_KEY_CHECKS=1;')
2763
2815
        self.execute("SET sql_mode='NO_BACKSLASH_ESCAPES';")
2764
2816
 
2769
2821
 
2770
2822
    web2py_filesystem = False
2771
2823
 
 
2824
    def escape(self,obj):
 
2825
        return self.db._adapter.esacpe(obj)    
 
2826
 
2772
2827
    def __init__(self,db,filename,mode):
2773
2828
        if db._adapter.dbengine != 'mysql':
2774
2829
            raise RuntimeError, "only MySQL can store metadata .table files in database for now"
2781
2836
        self.p=0
2782
2837
        self.data = ''
2783
2838
        if mode in ('r','rw','a'):
2784
 
            query = "SELECT content FROM web2py_filesystem WHERE path='%s'" % filename
 
2839
            query = "SELECT content FROM web2py_filesystem WHERE path='%s'" \
 
2840
                % filename
2785
2841
            rows = self.db.executesql(query)
2786
2842
            if rows:
2787
2843
                self.data = rows[0][0]
2811
2867
        self.data += data
2812
2868
 
2813
2869
    def close(self):
2814
 
        self.db.executesql("DELETE FROM web2py_filesystem WHERE path='%s'" % self.filename)
2815
 
        query = "INSERT INTO web2py_filesystem(path,content) VALUES ('%s','%s')" % \
2816
 
            (self.filename, self.data.replace("'","''"))
 
2870
        self.db.executesql("DELETE FROM web2py_filesystem WHERE path=%s" \
 
2871
                               % self.adapt(self.filename))
 
2872
        query = "INSERT INTO web2py_filesystem(path,content) VALUES (%s,%s)"\
 
2873
            % (self.adapt(self.filename), self.adapt(self.data))
2817
2874
        self.db.executesql(query)
2818
2875
        self.db.commit()
2819
2876
 
2821
2878
    def exists(db,filename):
2822
2879
        if os.path.exists(filename):
2823
2880
            return True
2824
 
        query = "SELECT path FROM web2py_filesystem WHERE path='%s'" % filename
 
2881
        query = "SELECT path FROM web2py_filesystem WHERE path=%s" \
 
2882
            % self.adapt(filename)
2825
2883
        if db.executesql(query):
2826
2884
            return True
2827
2885
        return False
2845
2903
 
2846
2904
class GoogleSQLAdapter(UseDatabaseStoredFile,MySQLAdapter):
2847
2905
 
2848
 
    def __init__(self, db, uri='google:sql://realm:domain/database', pool_size=0,
2849
 
                 folder=None, db_codec='UTF-8', check_reserved=None,
2850
 
                 migrate=True, fake_migrate=False,
 
2906
    def __init__(self, db, uri='google:sql://realm:domain/database',
 
2907
                 pool_size=0, folder=None, db_codec='UTF-8',
2851
2908
                 credential_decoder = lambda x:x, driver_args={},
2852
 
                    adapter_args={}):
 
2909
                 adapter_args={}):
2853
2910
 
2854
2911
        self.db = db
2855
2912
        self.dbengine = "mysql"
2865
2922
        instance = credential_decoder(m.group('instance'))
2866
2923
        db = credential_decoder(m.group('db'))
2867
2924
        driver_args['instance'] = instance
2868
 
        if not migrate:
 
2925
        createdb = adapter_args.get('createdb',True)
 
2926
        if not createdb:
2869
2927
            driver_args['database'] = db
2870
2928
        def connect(driver_args=driver_args):
2871
2929
            return rdbms.connect(**driver_args)
2872
2930
        self.pool_connection(connect)
2873
 
        self.cursor = self.connection.cursor()
2874
 
        if migrate:
 
2931
        if createdb:
2875
2932
            # self.execute('DROP DATABASE %s' % db)
2876
2933
            self.execute('CREATE DATABASE IF NOT EXISTS %s' % db)
2877
2934
            self.execute('USE %s' % db)
2905
2962
                obj = [obj]
2906
2963
        if obj == '' and  not fieldtype[:2] in ['st','te','pa','up']:
2907
2964
            return None
2908
 
        if obj != None:
 
2965
        if not obj is None:
2909
2966
            if isinstance(obj, list) and not fieldtype.startswith('list'):
2910
2967
                obj = [self.represent(o, fieldtype) for o in obj]
2911
2968
            elif fieldtype in ('integer','id'):
3046
3103
 
3047
3104
    def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
3048
3105
                 credential_decoder=lambda x:x, driver_args={},
3049
 
                    adapter_args={}):
 
3106
                 adapter_args={}):
3050
3107
        self.types.update({
3051
3108
                'boolean': gae.BooleanProperty,
3052
3109
                'string': (lambda: gae.StringProperty(multiline=True)),
3228
3285
    def truncate(self,table,mode):
3229
3286
        self.db(table._id > 0).delete()
3230
3287
 
3231
 
    def select_raw(self,query,fields=[],attributes={}):
 
3288
    def select_raw(self,query,fields=None,attributes=None):
 
3289
        fields = fields or []
 
3290
        attributes = attributes or {}
3232
3291
        new_fields = []
3233
3292
        for item in fields:
3234
3293
            if isinstance(item,SQLALL):
3422
3481
    def __init__(self,db,uri='couchdb://127.0.0.1:5984',
3423
3482
                 pool_size=0,folder=None,db_codec ='UTF-8',
3424
3483
                 credential_decoder=lambda x:x, driver_args={},
3425
 
                    adapter_args={}):
 
3484
                 adapter_args={}):
3426
3485
        self.db = db
3427
3486
        self.uri = uri
3428
3487
        self.dbengine = 'couchdb'
3434
3493
        url='http://'+uri[10:]
3435
3494
        def connect(url=url,driver_args=driver_args):
3436
3495
            return couchdb.Server(url,**driver_args)
3437
 
        self.pool_connection(connect)
 
3496
        self.pool_connection(connect,cursor=False)
3438
3497
 
3439
3498
    def create_table(self, table, migrate=True, fake_migrate=False, polymodel=None):
3440
3499
        if migrate:
3582
3641
    def __init__(self,db,uri='mongodb://127.0.0.1:5984/db',
3583
3642
                 pool_size=0,folder=None,db_codec ='UTF-8',
3584
3643
                 credential_decoder=lambda x:x, driver_args={},
3585
 
                    adapter_args={}):
 
3644
                 adapter_args={}):
3586
3645
        self.db = db
3587
3646
        self.uri = uri
3588
3647
        self.dbengine = 'mongodb'
3591
3650
        self.db_codec = 'UTF-8'
3592
3651
        self.pool_size = pool_size
3593
3652
 
3594
 
        m = re.compile('^(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>.+)$').match(self._uri[10:])
 
3653
        m = re.compile('^(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>.+)$').match(self.uri[10:])
3595
3654
        if not m:
3596
 
            raise SyntaxError, "Invalid URI string in DAL: %s" % self._uri
 
3655
            raise SyntaxError, "Invalid URI string in DAL: %s" % self.uri
3597
3656
        host = m.group('host')
3598
3657
        if not host:
3599
3658
            raise SyntaxError, 'mongodb: host name required'
3604
3663
        driver_args.update(dict(host=host,port=port))
3605
3664
        def connect(dbname=dbname,driver_args=driver_args):
3606
3665
            return pymongo.Connection(**driver_args)[dbname]
3607
 
        self.pool_connection(connect)
 
3666
        self.pool_connection(connect,cursor=False)
3608
3667
 
3609
3668
    def insert(self,table,fields):
3610
3669
        ctable = self.connection[table._tablename]
3708
3767
            field_type.find('.') < 0 and \
3709
3768
            field_type[10:] in field.db.tables:
3710
3769
        referenced = field.db[field_type[10:]]
3711
 
        def repr_ref(id, r=referenced, f=ff): return f(r, id)
 
3770
        def repr_ref(id, row=None, r=referenced, f=ff): return f(r, id)
3712
3771
        field.represent = field.represent or repr_ref
3713
3772
        if hasattr(referenced, '_format') and referenced._format:
3714
3773
            requires = validators.IS_IN_DB(field.db,referenced._id,
3722
3781
            field_type.find('.') < 0 and \
3723
3782
            field_type[15:] in field.db.tables:
3724
3783
        referenced = field.db[field_type[15:]]
3725
 
        def list_ref_repr(ids, r=referenced, f=ff):
 
3784
        def list_ref_repr(ids, row=None, r=referenced, f=ff):
3726
3785
            if not ids:
3727
3786
                return None
3728
3787
            refs = r._db(r._id.belongs(ids)).select(r._id)
3738
3797
            requires._and = validators.IS_NOT_IN_DB(field.db,field)
3739
3798
        return requires
3740
3799
    elif field_type.startswith('list:'):
3741
 
        def repr_list(values): return', '.join(str(v) for v in (values or []))
 
3800
        def repr_list(values,row=None): return', '.join(str(v) for v in (values or []))
3742
3801
        field.represent = field.represent or repr_list
3743
3802
    if field.unique:
3744
3803
        requires.insert(0,validators.IS_NOT_IN_DB(field.db,field))
3772
3831
 
3773
3832
    def __getitem__(self, key):
3774
3833
        key=str(key)
 
3834
        m = table_field.match(key)
3775
3835
        if key in self.get('_extra',{}):
3776
3836
            return self._extra[key]
 
3837
        elif m:
 
3838
            try:
 
3839
                return dict.__getitem__(self, m.group(1))[m.group(2)]
 
3840
            except (KeyError,TypeError):
 
3841
                key = m.group(2)
3777
3842
        return dict.__getitem__(self, key)
3778
3843
 
3779
3844
    def __call__(self,key):
3845
3910
    def __call__(self):
3846
3911
        return copy.copy(self)
3847
3912
 
 
3913
def smart_query(fields,text):
 
3914
    if not isinstance(fields,(list,tuple)):
 
3915
        fields = [fields]
 
3916
    new_fields = []
 
3917
    for field in fields:
 
3918
        if isinstance(field,Field):
 
3919
            new_fields.append(field)
 
3920
        elif isinstance(field,Table):
 
3921
            for ofield in field:
 
3922
                new_fields.append(ofield)
 
3923
        else:
 
3924
            raise RuntimeError, "fields must be a list of fields"
 
3925
    field_map = {}
 
3926
    for field in fields:
 
3927
        n = field.name.lower()
 
3928
        if not n in field_map: 
 
3929
            field_map[n] = field
 
3930
        n = str(field).lower()
 
3931
        if not n in field_map:
 
3932
            field_map[n] = field
 
3933
    re_constants = re.compile('(\"[^\"]*?\")|(\'[^\']*?\')')
 
3934
    constants = {}
 
3935
    i = 0 
 
3936
    while True:
 
3937
        m = re_constants.search(text)
 
3938
        if not m: break
 
3939
        text = text[:m.start()]+('#%i' % i)+text[m.end():] 
 
3940
        constants[str(i)] = m.group()[1:-1]
 
3941
        i+=1
 
3942
    text = re.sub('\s+',' ',text).lower()
 
3943
    for a,b in [('&','and'),
 
3944
                ('|','or'),
 
3945
                ('~','not'),
 
3946
                ('==','=='),
 
3947
                ('<','<'),
 
3948
                ('>','>'),
 
3949
                ('<=','<='),
 
3950
                ('>=','>='),
 
3951
                ('<>','!='),
 
3952
                ('=<','<='),
 
3953
                ('=>','>='),
 
3954
                ('=','=='),
 
3955
                (' less or equal than ','<='),
 
3956
                (' greater or equal than ','>='),
 
3957
                (' equal or less than ','<='),
 
3958
                (' equal or greater than ','>='),
 
3959
                (' less or equal ','<='),
 
3960
                (' greater or equal ','>='),
 
3961
                (' equal or less ','<='),
 
3962
                (' equal or greater ','>='),
 
3963
                (' not equal to ','!='),
 
3964
                (' not equal ','!='),
 
3965
                (' equal to ','=='),
 
3966
                (' equal ','=='),
 
3967
                (' equals ','!='),
 
3968
                (' less than ','<'),
 
3969
                (' greater than ','>'),
 
3970
                (' starts with ','startswith'),
 
3971
                (' ends with ','endswith'),
 
3972
                (' is ','==')]:            
 
3973
        if a[0]==' ':
 
3974
            text = text.replace(' is'+a,' %s ' % b)
 
3975
        text = text.replace(a,' %s ' % b)
 
3976
    text = re.sub('\s+',' ',text).lower()
 
3977
    query = field = neg = op = logic = None
 
3978
    for item in text.split():
 
3979
        if field is None:
 
3980
            if item == 'not':
 
3981
                neg = True
 
3982
            elif not neg and not logic and item in ('and','or'):
 
3983
                logic = item
 
3984
            elif item in field_map:
 
3985
                field = field_map[item]                
 
3986
            else:
 
3987
                raise RuntimeError, "Invalid syntax"
 
3988
        elif not field is None and op is None:
 
3989
            op = item
 
3990
        elif not op is None:
 
3991
            if item.startswith('#'):
 
3992
                if not item[1:] in constants:
 
3993
                    raise RuntimeError, "Invalid syntax"
 
3994
                value = constants[item[1:]]
 
3995
            else:
 
3996
                value = item
 
3997
                if op == '==': op = 'like'
 
3998
            if op == '==': new_query = field==value
 
3999
            elif op == '<': new_query = field<value
 
4000
            elif op == '>': new_query = field>value                
 
4001
            elif op == '<=': new_query = field<=value
 
4002
            elif op == '>=': new_query = field>=value                
 
4003
            elif op == 'contains': new_query = field.contains(value)
 
4004
            elif op == 'like': new_query = field.like(value)
 
4005
            elif op == 'startswith': new_query = field.startswith(value)
 
4006
            elif op == 'endswith': new_query = field.endswith(value)
 
4007
            else: raise RuntimeError, "Invalid operation"
 
4008
            if neg: new_query = ~new_query                
 
4009
            if query is None:
 
4010
                query = new_query
 
4011
            elif logic == 'and':
 
4012
                query &= new_query
 
4013
            elif logic == 'or':
 
4014
                query |= new_query                
 
4015
            field = op = neg = logic = None
 
4016
    return query
 
4017
 
3848
4018
 
3849
4019
class DAL(dict):
3850
4020
 
3904
4074
        return
3905
4075
 
3906
4076
 
3907
 
    def __init__(self, uri='sqlite://dummy.db', pool_size=0, folder=None,
 
4077
    def __init__(self, uri='sqlite://dummy.db',
 
4078
                 pool_size=0, folder=None,
3908
4079
                 db_codec='UTF-8', check_reserved=None,
3909
4080
                 migrate=True, fake_migrate=False,
3910
4081
                 migrate_enabled=True, fake_migrate_all=False,
3911
4082
                 decode_credentials=False, driver_args=None,
3912
 
                 adapter_args={}, attempts=5, auto_import=False):
 
4083
                 adapter_args=None, attempts=5, auto_import=False):
3913
4084
        """
3914
4085
        Creates a new Database Abstraction Layer instance.
3915
4086
 
3950
4121
        self._pending_references = {}
3951
4122
        self._request_tenant = 'request_tenant'
3952
4123
        self._common_fields = []
 
4124
        self._referee_name = '%(table)s'
3953
4125
        if not str(attempts).isdigit() or attempts < 0:
3954
4126
            attempts = 5
3955
4127
        if uri:
3964
4136
                        self._dbname = regex_dbname.match(uri).group()
3965
4137
                        if not self._dbname in ADAPTERS:
3966
4138
                            raise SyntaxError, "Error in URI '%s' or database not supported" % self._dbname
3967
 
                        # notice that driver args or {} else driver_args defaults to {} global, not correct
3968
 
                        args = (self,uri,pool_size,folder,db_codec,credential_decoder,driver_args or {}, adapter_args)
 
4139
                        # notice that driver args or {} else driver_args
 
4140
                        # defaults to {} global, not correct
 
4141
                        args = (self,uri,pool_size,folder,
 
4142
                                db_codec, credential_decoder,
 
4143
                                driver_args or {}, adapter_args or {})
3969
4144
                        self._adapter = ADAPTERS[self._dbname](*args)
3970
4145
                        connected = True
3971
4146
                        break
4268
4443
                    'format',
4269
4444
                    'trigger_name',
4270
4445
                    'sequence_name',
4271
 
                    'polymodel']:
4272
 
                raise SyntaxError, 'invalid table "%s" attribute: %s' % (tablename, key)
4273
 
        migrate = self._migrate_enabled and args.get('migrate',self._migrate)
4274
 
        fake_migrate = self._fake_migrate_all or args.get('fake_migrate',self._fake_migrate)
 
4446
                    'polymodel',
 
4447
                    'table_class']:
 
4448
                raise SyntaxError, 'invalid table "%s" attribute: %s' \
 
4449
                    % (tablename, key)
 
4450
        migrate = self._migrate_enabled and args.get('migrate',
 
4451
                                                     self._migrate)
 
4452
        fake_migrate = self._fake_migrate_all or args.get('fake_migrate',
 
4453
                                                          self._fake_migrate)
 
4454
        table_class = args.get('table_class',Table)
4275
4455
        format = args.get('format',None)
4276
4456
        trigger_name = args.get('trigger_name', None)
4277
4457
        sequence_name = args.get('sequence_name', None)
4293
4473
        if self._common_fields:
4294
4474
            fields = [f for f in fields] + [f for f in self._common_fields]
4295
4475
 
4296
 
        t = self[tablename] = Table(self, tablename, *fields,
4297
 
                                    **dict(primarykey=primarykey,
4298
 
                                    trigger_name=trigger_name,
4299
 
                                    sequence_name=sequence_name))
 
4476
        t = self[tablename] = table_class(self, tablename, *fields,
 
4477
                                          **dict(primarykey=primarykey,
 
4478
                                                 trigger_name=trigger_name,
 
4479
                                                 sequence_name=sequence_name))
4300
4480
        # db magic
4301
4481
        if self._uri in (None,'None'):
4302
4482
            return t
4339
4519
    def __repr__(self):
4340
4520
        return '<DAL ' + dict.__repr__(self) + '>'
4341
4521
 
 
4522
    def smart_query(self,fields,text):
 
4523
        return Set(self, smart_query(fields,text))
 
4524
 
4342
4525
    def __call__(self, query=None):
4343
4526
        if isinstance(query,Table):
4344
4527
            query = query._id>0
4409
4592
            ofile.write('\r\n\r\n')
4410
4593
        ofile.write('END')
4411
4594
 
4412
 
    def import_from_csv_file(self, ifile, id_map={}, null='<NULL>',
 
4595
    def import_from_csv_file(self, ifile, id_map=None, null='<NULL>',
4413
4596
                             unique='uuid', *args, **kwargs):
 
4597
        if id_map is None: id_map={}
4414
4598
        for line in ifile:
4415
4599
            line = line.strip()
4416
4600
            if not line:
4454
4638
        self.__allocate()
4455
4639
        return self._record.get(key, None)
4456
4640
 
 
4641
    def get(self, key):
 
4642
        return self.__getattr__(key)
 
4643
 
4457
4644
    def __setattr__(self, key, value):
4458
4645
        if key.startswith('_'):
4459
4646
            int.__setattr__(self, key, value)
4528
4715
                raise SyntaxError, \
4529
4716
                    "primarykey must be a list of fields from table '%s'" \
4530
4717
                    % tablename
4531
 
            self._primarykey = primarykey            
 
4718
            self._primarykey = primarykey
4532
4719
        elif not [f for f in fields if isinstance(f,Field) and f.type=='id']:
4533
4720
            field = Field('id', 'id')
4534
4721
            newfields.append(field)
4569
4756
                    fields.append(self._db.Field(tmp, 'blob', default=''))
4570
4757
 
4571
4758
        lower_fieldnames = set()
4572
 
        reserved = dir(Table) + ['fields'] 
 
4759
        reserved = dir(Table) + ['fields']
4573
4760
        for field in fields:
4574
4761
            if db and db.check_reserved:
4575
4762
                db.check_reserved_keyword(field.name)
4589
4776
            field.tablename = field._tablename = tablename
4590
4777
            field.table = field._table = self
4591
4778
            field.db = field._db = self._db
4592
 
            if self._db and field.type!='text' and \
 
4779
            if self._db and not field.type in ('text','blob') and \
4593
4780
                    self._db._adapter.maxcharlength < field.length:
4594
4781
                field.length = self._db._adapter.maxcharlength
4595
4782
            if field.requires == DEFAULT:
4604
4791
                else:
4605
4792
                    self[k].notnull = True
4606
4793
 
 
4794
    def update(self,*args,**kwargs):
 
4795
        raise RuntimeError, "Syntax Not Supported"
 
4796
 
4607
4797
    def _validate(self,**vars):
4608
4798
        errors = Row()
4609
4799
        for key,value in vars.items():
4765
4955
                new_fields_names.append(name)
4766
4956
        for ofield in self:
4767
4957
            if not ofield.name in new_fields_names:
4768
 
                if not update and ofield.default!=None:
 
4958
                if not update and not ofield.default is None:
4769
4959
                    new_fields.append((ofield,ofield.default))
4770
 
                elif update and ofield.update!=None:
 
4960
                elif update and not ofield.update is None:
4771
4961
                    new_fields.append((ofield,ofield.update))
4772
4962
        for ofield in self:
4773
4963
            if not ofield.name in new_fields_names and ofield.compute:
4847
5037
            id_map_self = id_map[self._tablename]
4848
5038
 
4849
5039
        def fix(field, value, id_map):
 
5040
            list_reference_s='list:reference'
4850
5041
            if value == null:
4851
5042
                value = None
4852
5043
            elif field.type=='blob':
4863
5054
                    value = int(value)
4864
5055
            elif field.type.startswith('list:string'):
4865
5056
                value = bar_decode_string(value)
4866
 
            elif field.type.startswith('list:reference'):
4867
 
                ref_table = field.type[10:].strip()
 
5057
            elif field.type.startswith(list_reference_s):
 
5058
                ref_table = field.type[len(list_reference_s):].strip()
4868
5059
                value = [id_map[ref_table][int(v)] \
4869
5060
                             for v in bar_decode_string(value)]
4870
5061
            elif field.type.startswith('list:'):
4912
5103
                    else:
4913
5104
                        new_id = self.insert(**dict(items))
4914
5105
                if id_map and cid != []:
4915
 
                    id_map_self[line[cid]] = new_id
 
5106
                    id_map_self[int(line[cid])] = new_id
4916
5107
 
4917
5108
    def with_alias(self, alias):
4918
5109
        return self._db._adapter.alias(self,alias)
4976
5167
    def minutes(self):
4977
5168
        return Expression(self.db, self.db._adapter.EXTRACT, self, 'minute', 'integer')
4978
5169
 
 
5170
    def coalesce(self,*others):
 
5171
        return Expression(self.db, self.db._adapter.COALESCE, self, others, self.type)
 
5172
 
4979
5173
    def coalesce_zero(self):
4980
 
            return Expression(self.db, self.db._adapter.COALESCE_ZERO, self, None, self.type)
 
5174
        return Expression(self.db, self.db._adapter.COALESCE_ZERO, self, None, self.type)
4981
5175
 
4982
5176
    def seconds(self):
4983
5177
        return Expression(self.db, self.db._adapter.EXTRACT, self, 'second', 'integer')
5066
5260
            raise SyntaxError, "endswith used with incompatible field type"
5067
5261
        return Query(self.db, self.db._adapter.ENDSWITH, self, value)
5068
5262
 
5069
 
    def contains(self, value):
 
5263
    def contains(self, value, all=False):
 
5264
        if isinstance(value,(list,tuple)):
 
5265
            subqueries = [self.contains(str(v).strip()) for v in value if str(v).strip()]
 
5266
            return reduce(all and AND or OR, subqueries)
5070
5267
        if not self.type in ('string', 'text') and not self.type.startswith('list:'):
5071
5268
            raise SyntaxError, "contains used with incompatible field type"
5072
5269
        return Query(self.db, self.db._adapter.CONTAINS, self, value)
5132
5329
    def __str__(self):
5133
5330
        return self._class
5134
5331
 
 
5332
class FieldVirtual(object):
 
5333
    def __init__(self,f):
 
5334
        self.f = f
 
5335
 
 
5336
class FieldLazy(object):
 
5337
    def __init__(self,f,handler=None):
 
5338
        self.f = f
 
5339
        self.handler = handler
 
5340
 
5135
5341
 
5136
5342
class Field(Expression):
5137
5343
 
 
5344
    Virtual = FieldVirtual
 
5345
    Lazy = FieldLazy
 
5346
 
5138
5347
    """
5139
5348
    an instance of this class represents a database field
5140
5349
 
5181
5390
        unique=False,
5182
5391
        uploadfield=True,
5183
5392
        widget=None,
5184
 
        label=None,
 
5393
        label=DEFAULT,
5185
5394
        comment=None,
5186
5395
        writable=True,
5187
5396
        readable=True,
5215
5424
        if isinstance(type, Table):
5216
5425
            type = 'reference ' + type._tablename
5217
5426
        self.type = type  # 'string', 'integer'
5218
 
        self.length = (length is None) and MAXCHARLENGTH or length
 
5427
        self.length = (length is None) and DEFAULTLENGTH.get(type,512) or length
5219
5428
        if default==DEFAULT:
5220
5429
            self.default = update or None
5221
5430
        else:
5228
5437
        self.uploadfolder = uploadfolder
5229
5438
        self.uploadseparate = uploadseparate
5230
5439
        self.widget = widget
5231
 
        self.label = label or ' '.join(item.capitalize() for item in fieldname.split('_'))
 
5440
        if label == DEFAULT:
 
5441
            self.label = ' '.join(i.capitalize() for i in fieldname.split('_'))
 
5442
        else:
 
5443
            self.label = label or ''
5232
5444
        self.comment = comment
5233
5445
        self.writable = writable
5234
5446
        self.readable = readable
5236
5448
        self.authorize = authorize
5237
5449
        self.autodelete = autodelete
5238
5450
        if not represent and type in ('list:integer','list:string'):
5239
 
            represent=lambda x: ', '.join(str(y) for y in x or [])
 
5451
            represent=lambda x,r=None: ', '.join(str(y) for y in x or [])
5240
5452
        self.represent = represent
5241
5453
        self.compute = compute
5242
5454
        self.isattachment = True
5371
5583
            return '<no table>.%s' % self.name
5372
5584
 
5373
5585
 
 
5586
def raw(s): return Expression(None,s)
 
5587
 
5374
5588
class Query(object):
5375
5589
 
5376
5590
    """
5392
5606
        first=None,
5393
5607
        second=None,
5394
5608
        ):
5395
 
        self.db = db
 
5609
        self.db = self._db = db
5396
5610
        self.op = op
5397
5611
        self.first = first
5398
5612
        self.second = second
5449
5663
    def __call__(self, query):
5450
5664
        if isinstance(query,Table):
5451
5665
            query = query._id>0
 
5666
        elif isinstance(query,str):
 
5667
            query = raw(query)
5452
5668
        elif isinstance(query,Field):
5453
5669
            query = query!=None
5454
5670
        if self.query:
5492
5708
            raise SyntaxError, "No fields to update"
5493
5709
        self.delete_uploaded_files(update_fields)
5494
5710
        return self.db._adapter.update(tablename,self.query,fields)
5495
 
    
 
5711
 
5496
5712
    def validate_and_update(self, **update_fields):
5497
5713
        tablename = self.db._adapter.get_table(self.query)
5498
5714
        response = Row()
5499
 
        response.errors = self.db[tablename]._validate(**update_fields) 
 
5715
        response.errors = self.db[tablename]._validate(**update_fields)
5500
5716
        fields = self.db[tablename]._listify(update_fields,update=True)
5501
5717
        if not fields:
5502
5718
            raise SyntaxError, "No fields to update"
5537
5753
                        items = oldname.split('.')
5538
5754
                        uploadfolder = os.path.join(uploadfolder,
5539
5755
                                                    "%s.%s" % (items[0], items[1]),
5540
 
                                                    items[2][:2])                
 
5756
                                                    items[2][:2])
5541
5757
                    oldpath = os.path.join(uploadfolder, oldname)
5542
5758
                    if os.path.exists(oldpath):
5543
5759
                        os.unlink(oldpath)
5544
5760
 
5545
 
def update_record(pack, a={}):
 
5761
def update_record(pack, a=None):
5546
5762
    (colset, table, id) = pack
5547
5763
    b = a or dict(colset)
5548
5764
    c = dict([(k,v) for (k,v) in b.items() if k in table.fields and table[k].type!='id'])
5550
5766
    for (k, v) in c.items():
5551
5767
        colset[k] = v
5552
5768
 
 
5769
class VirtualCommand(object):
 
5770
    def __init__(self,method,row):
 
5771
        self.method=method
 
5772
        #self.instance=instance
 
5773
        self.row=row
 
5774
    def __call__(self,*args,**kwargs):
 
5775
        return self.method(self.row,*args,**kwargs)
 
5776
 
 
5777
def lazy_virtualfield(f):
 
5778
    f.__lazy__ = True
 
5779
    return f
5553
5780
 
5554
5781
class Rows(object):
5555
5782
 
5575
5802
        self.response = rawrows
5576
5803
 
5577
5804
    def setvirtualfields(self,**keyed_virtualfields):
 
5805
        """
 
5806
        db.define_table('x',Field('number','integer'))
 
5807
        if db(db.x).isempty(): [db.x.insert(number=i) for i in range(10)]
 
5808
 
 
5809
        from gluon.dal import lazy_virtualfield
 
5810
 
 
5811
        class MyVirtualFields(object):
 
5812
            # normal virtual field (backward compatible, discouraged)
 
5813
            def normal_shift(self): return self.x.number+1
 
5814
            # lazy virtual field (because of @staticmethod)
 
5815
            @lazy_virtualfield
 
5816
            def lazy_shift(instance,row,delta=4): return row.x.number+delta
 
5817
        db.x.virtualfields.append(MyVirtualFields())
 
5818
 
 
5819
        for row in db(db.x).select():
 
5820
            print row.number, row.normal_shift, row.lazy_shift(delta=7)
 
5821
        """
5578
5822
        if not keyed_virtualfields:
5579
5823
            return self
5580
5824
        for row in self.records:
5581
5825
            for (tablename,virtualfields) in keyed_virtualfields.items():
5582
5826
                attributes = dir(virtualfields)
5583
 
                virtualfields.__dict__.update(row)
5584
5827
                if not tablename in row:
5585
5828
                    box = row[tablename] = Row()
5586
5829
                else:
5587
5830
                    box = row[tablename]
 
5831
                updated = False
5588
5832
                for attribute in attributes:
5589
5833
                    if attribute[0] != '_':
5590
5834
                        method = getattr(virtualfields,attribute)
5591
 
                        if hasattr(method,'im_func') and method.im_func.func_code.co_argcount:
 
5835
                        if hasattr(method,'__lazy__'):
 
5836
                            box[attribute]=VirtualCommand(method,row)
 
5837
                        elif type(method)==types.MethodType:
 
5838
                            if not updated:
 
5839
                                virtualfields.__dict__.update(row)
 
5840
                                updated = True
5592
5841
                            box[attribute]=method()
5593
5842
        return self
5594
5843
 
5781
6030
                        value = record[t][f]
5782
6031
                    else:
5783
6032
                        value = record[f]
5784
 
                    if field.type=='blob' and value!=None:
 
6033
                    if field.type=='blob' and not value is None:
5785
6034
                        value = base64.b64encode(value)
5786
6035
                    elif represent and field.represent:
5787
6036
                        value = field.represent(value)
5808
6057
            (t, f) = col.split('.')
5809
6058
            res = None
5810
6059
            if not table_field.match(col):
 
6060
                key = col
5811
6061
                res = record._extra[col]
5812
6062
            else:
 
6063
                key = f
5813
6064
                if isinstance(record.get(t, None), Row):
5814
6065
                    res = record[t][f]
5815
6066
                else:
5816
6067
                    res = record[f]
5817
6068
            if mode == 'object':
5818
 
                return (f, res)
 
6069
                return (key, res)
5819
6070
            else:
5820
6071
                return res
5821
6072
 
6061
6312
 
6062
6313
 
6063
6314
 
 
6315
 
 
6316