-
Notifications
You must be signed in to change notification settings - Fork 152
/
06.markmin
3328 lines (2546 loc) · 126 KB
/
06.markmin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
## The database abstraction layer
``DAL``:inxx
### Dependencies
web2py comes with a Database Abstraction Layer (DAL), an API that maps Python objects into database objects such as queries, tables, and records. The DAL dynamically generates the SQL in real time using the specified dialect for the database back end, so that you do not have to write SQL code or learn different SQL dialects (the term SQL is used generically), and the application will be portable among different types of databases. A partial list of supported databases is show in the table below. Please check on the web2py web site and mailing list for more recent adapters. Google NoSQL is treated as a particular case in Chapter 13.
The Windows binary distribution works out of the box with SQLite and MySQL. The Mac binary distribution works out of the box with SQLite.
To use any other database back-end, run from the source distribution and install the appropriate driver for the required back end.
``database drivers``:inxx
Once the proper driver is installed, start web2py from source, and it will find the driver. Here is a list of drivers:
``DAL``:inxx ``SQLite``:inxx ``MySQL``:inxx ``PostgresSQL``:inxx ``Oracle``:inxx ``MSSQL``:inxx ``FireBird``:inxx ``DB2``:inxx ``Informix``:inxx ``Sybase``:inxx ``Teradata``:inxx ``MongoDB``:inxx ``CouchDB``:inxx ``SAPDB``:inxx ``Cubrid``:inxx
----------
database | drivers (source)
SQLite | sqlite3 or pysqlite2 or zxJDBC ``zxjdbc``:cite (on Jython)
PostgreSQL | psycopg2 ``psycopg2``:cite or pg8000 ``pg8000``:cite or zxJDBC ``zxjdbc``:cite (on Jython)
MySQL | pymysql ``pymysql``:cite or MySQLdb ``mysqldb``:cite
Oracle | cx_Oracle ``cxoracle``:cite
MSSQL | pyodbc ``pyodbc``:cite
FireBird | kinterbasdb ``kinterbasdb``:cite or fdb or pyodbc
DB2 | pyodbc ``pyodbc``:cite
Informix | informixdb ``informixdb``:cite
Ingres | ingresdbi ``ingresdbi``:cite
Cubrid | cubriddb ``cubridb``:cite ``cubridb``:cite
Sybase | Sybase ``Sybase``:cite
Teradata | pyodbc ``Teradata``:cite
SAPDB | sapdb ``SAPDB``:cite
MongoDB | pymongo ``pymongo``:cite
IMAP | imaplib ``IMAP``:cite
---------
``sqlite3``, ``pymysql``, ``pg8000``, and ``imaplib`` ship with web2py. Support of MongoDB is experimental. The IMAP option allows to use DAL to access IMAP.
web2py defines the following classes that make up the DAL:
The **DAL** object represents a database connection. For example:
``sqlite``:inxx
``
db = DAL('sqlite://storage.db')
``:code
``define_table``:inxx
**Table** represents a database table. You do not directly instantiate Table; instead, ``DAL.define_table`` instantiates it.
``
db.define_table('mytable', Field('myfield'))
``:code
The most important methods of a Table are:
``insert``:inxx
``truncate``:inxx
``drop``:inxx
``import_from_csv_file``:inxx
``count``:inxx
``.insert``, ``.truncate``, ``.drop``, and ``.import_from_csv_file``.
``Field``:inxx
**Field** represents a database field. It can be instantiated and passed as an argument to ``DAL.define_table``.
``Rows``:inxx
**DAL Rows** ``Row``:inxx is the object returned by a database select. It can be thought of as a list of ``Row`` rows:
``
rows = db(db.mytable.myfield!=None).select()
``:code
``Row``:inxx
**Row** contains field values.
``
for row in rows:
print row.myfield
``:code
``Query``:inxx
**Query** is an object that represents a SQL "where" clause:
``
myquery = (db.mytable.myfield != None) | (db.mytable.myfield > 'A')
``:code
``Set``:inxx
**Set** is an object that represents a set of records. Its most important methods are ``count``, ``select``, ``update``, and ``delete``. For example:
``
myset = db(myquery)
rows = myset.select()
myset.update(myfield='somevalue')
myset.delete()
``:code
``Expression``:inxx
**Expression** is something like an ``orderby`` or ``groupby`` expression. The Field class is derived from the Expression. Here is an example.
``
myorder = db.mytable.myfield.upper() | db.mytable.id
db().select(db.table.ALL, orderby=myorder)
``:code
### Connection strings
``connection strings``:inxx
A connection with the database is established by creating an instance of the DAL object:
``
>>> db = DAL('sqlite://storage.db', pool_size=0)
``:code
``db`` is not a keyword; it is a local variable that stores the connection object ``DAL``. You are free to give it a different name. The constructor of ``DAL`` requires a single argument, the connection string. The connection string is the only web2py code that depends on a specific back-end database. Here are examples of connection strings for specific types of supported back-end databases (in all cases, we assume the database is running from localhost on its default port and is named "test"):
-------------
**SQLite** | ``sqlite://storage.db``
**MySQL** | ``mysql://username:password@localhost/test``
**PostgreSQL** | ``postgres://username:password@localhost/test``
**MSSQL** | ``mssql://username:password@localhost/test``
**FireBird** | ``firebird://username:password@localhost/test``
**Oracle** | ``oracle://username/password@test``
**DB2** | ``db2://username:password@test``
**Ingres** | ``ingres://username:password@localhost/test``
**Sybase** | ``sybase://username:password@localhost/test``
**Informix** | ``informix://username:password@test``
**Teradata** | ``teradata://DSN=dsn;UID=user;PWD=pass;DATABASE=name``
**Cubrid** | ``cubrid://username:password@localhost/test``
**SAPDB** | ``sapdb://username:password@localhost/test``
**IMAP** | ``imap://user:password@server:port``
**MongoDB** | ``mongodb://username:password@localhost/test``
**Google/SQL** | ``google:sql``
**Google/NoSQL** | ``google:datastore``
-------------
Notice that in SQLite the database consists of a single file. If it does not exist, it is created. This file is locked every time it is accessed. In the case of MySQL, PostgreSQL, MSSQL, FireBird, Oracle, DB2, Ingres and Informix the database "test" must be created outside web2py. Once the connection is established, web2py will create, alter, and drop tables appropriately.
It is also possible to set the connection string to ``None``. In this case DAL will not connect to any back-end database, but the API can still be accessed for testing. Examples of this will be discussed in Chapter 7.
Some times you may need to generate SQL as if you had a connection but without actually connecting to the database. This can be done with
``
db = DAL('...', do_connect=False)
``:code
In this case you will be able to call ``_select``, ``_insert``, ``_update``, and ``_delete`` to generate SQL but not call ``select``, ``insert``, ``update``, and ``delete``. In most of the cases you can use ``do_connect=False`` even without having the required database drivers.
Notice that by default web2py uses utf8 character encoding for databases. If you work with existing databases that behave differently, you have to change it with the optional parameter ``db_codec`` like
``
db = DAL('...', db_codec='latin1')
``:code
otherwise you'll get UnicodeDecodeErrors tickets.
#### Connection pooling
``connection pooling``:inxx
The second argument of the DAL constructor is the ``pool_size``; it defaults to zero.
As it is rather slow to establish a new database connection for each request, web2py implements a mechanism for connection pooling. Once a connection is established and the page has been served and the transaction completed, the connection is not closed but goes into a pool. When the next http request arrives, web2py tries to recycle a connection from the pool and use that for the new transaction. If there are no available connections in the pool, a new connection is established.
When web2py starts, the pool is always empty. The pool grows up to the minimum between the value of ``pool_size`` and the max number of concurrent requests. This means that if ``pool_size=10`` but our server never receives more than 5 concurrent requests, then the actual pool size will only grow to 5. If ``pool_size=0`` then connection pooling is not used.
Connections in the pools are shared sequentially among threads, in the sense that they may be used by two different but not simultaneous threads. There is only one pool for each web2py process.
The ``pool_size`` parameter is ignored by SQLite and Google App Engine.
Connection pooling is ignored for SQLite, since it would not yield any benefit.
#### Connection failures
If web2py fails to connect to the database it waits 1 seconds and tries again up to 5 times before declaring a failure. In case of connection pooling it is possible that a pooled connection that stays open but unused for some time is closed by the database end. Thanks to the retry feature web2py tries to re-establish these dropped connections.
#### Replicated databases
The first argument of ``DAL(...)`` can be a list of URIs. In this case web2py tries to connect to each of them. The main purpose for this is to deal with multiple database servers and distribute the workload among them). Here is a typical use case:
``
db = DAL(['mysql://...1','mysql://...2','mysql://...3'])
``:code
In this case the DAL tries to connect to the first and, on failure, it
will try the second and the third. This can also be used to distribute load
in a database master-slave configuration. We will talk more about this
in Chapter 13 in the context of scalability.
### Reserved keywords
``reserved Keywords``:inxx
``check_reserved`` is yet another argument that can be passed to the DAL constructor. It tells it to check table names and column names against reserved SQL keywords in target back-end databases.
This argument is ``check_reserved`` and it defaults to None.
This is a list of strings that contain the database back-end adapter names.
The adapter name is the same as used in the DAL connection string. So if you want to check against PostgreSQL and MSSQL then your connection string would look as follows:
``
db = DAL('sqlite://storage.db',
check_reserved=['postgres', 'mssql'])
``:code
The DAL will scan the keywords in the same order as of the list.
There are two extra options "all" and "common". If you specify all, it will check against all known SQL keywords. If you specify common, it will only check against common SQL keywords such as ``SELECT``, ``INSERT``, ``UPDATE``, etc.
For supported back-ends you may also specify if you would like to check against the non-reserved SQL keywords as well. In this case you would append ``_nonreserved`` to the name. For example:
``
check_reserved=['postgres', 'postgres_nonreserved']
``:code
The following database backends support reserved words checking.
-----
**PostgreSQL** | ``postgres(_nonreserved)``
**MySQL** | ``mysql``
**FireBird** | ``firebird(_nonreserved)``
**MSSQL** | ``mssql``
**Oracle** | ``oracle``
-----
### ``DAL``, ``Table``, ``Field``
You can experiment with the DAL API using the web2py shell.
Start by creating a connection. For the sake of example, you can use SQLite. Nothing in this discussion changes when you change the back-end engine.
``
>>> db = DAL('sqlite://storage.db')
``:code
The database is now connected and the connection is stored in the global variable ``db``.
At any time you can retrieve the connection string.
``_uri``:inxx
``
>>> print db._uri
sqlite://storage.db
``:code
and the database name
``_dbname``:inxx
``
>>> print db._dbname
sqlite
``:code
The connection string is called a ``_uri`` because it is an instance of a Uniform Resource Identifier.
The DAL allows multiple connections with the same database or with different databases, even databases of different types. For now, we will assume the presence of a single database since this is the most common situation.
``define_table``:inxx ``Field``:inxx
``type``:inxx ``length``:inxx ``default``:inxx ``requires``:inxx ``required``:inxx ``unique``:inxx
``notnull``:inxx ``ondelete``:inxx ``uploadfield``:inxx ``uploadseparate``:inxx ``migrate``:inxx ``sql.log``:inxx
The most important method of a DAL is ``define_table``:
``
>>> db.define_table('person', Field('name'))
``:code
It defines, stores and returns a ``Table`` object called "person" containing a field (column) "name". This object can also be accessed via ``db.person``, so you do not need to catch the return value.
Do not declare a field called "id", because one is created by web2py anyway. Every table has a field called "id" by default. It is an auto-increment integer field (starting at 1) used for cross-reference and for making every record unique, so "id" is a primary key. (Note: the id's starting at 1 is back-end specific. For example, this does not apply to the Google App Engine NoSQL.)
``named id field``:inxx
Optionally you can define a field of ``type='id'`` and web2py will use this field as auto-increment id field. This is not recommended except when accessing legacy database tables. With some limitation, you can also use different primary keys and this is discussed in the section on "Legacy databases and keyed tables".
Tables can be defined only once but you can force web2py to redefine an existing table:
``
db.define_table('person', Field('name'))
db.define_table('person', Field('name'), redefine=True)
``:code
The redefinition may trigger a migration if field content is different.
----------
Because usually in web2py models are executed before controllers, it is possible that some table are defined even if not needed. It is therefore necessary to speed up the code by making table definitions lazy. This is done by setting the ``DAL(...,lazy_tables=True)`` attributes. Tables will be actually created only when accessed.
----------
### Record representation
It is optional but recommended to specify a format representation for records:
``
>>> db.define_table('person', Field('name'), format='%(name)s')
``:code
or
``
>>> db.define_table('person', Field('name'), format='%(name)s %(id)s')
``:code
or even more complex ones using a function:
``
>>> db.define_table('person', Field('name'),
format=lambda r: r.name or 'anonymous')
``:code
The format attribute will be used for two purposes:
- To represent referenced records in select/option drop-downs.
- To set the ``db.othertable.person.represent`` attribute for all fields referencing this table. This means that SQLTABLE will not show references by id but will use the format preferred representation instead.
``Field constructor``:inxx
These are the default values of a Field constructor:
``
Field(name, 'string', length=None, default=None,
required=False, requires='<default>',
ondelete='CASCADE', notnull=False, unique=False,
uploadfield=True, widget=None, label=None, comment=None,
writable=True, readable=True, update=None, authorize=None,
autodelete=False, represent=None, compute=None,
uploadfolder=os.path.join(request.folder,'uploads'),
uploadseparate=None,uploadfs=None)
``:code
Not all of them are relevant for every field. "length" is relevant only for fields of type "string". "uploadfield" and "authorize" are relevant only for fields of type "upload". "ondelete" is relevant only for fields of type "reference" and "upload".
- ``length`` sets the maximum length of a "string", "password" or "upload" field. If ``length`` is not specified a default value is used but the default value is not guaranteed to be backward compatible. ''To avoid unwanted migrations on upgrades, we recommend that you always specify the length for string, password and upload fields.''
- ``default`` sets the default value for the field. The default value is used when performing an insert if a value is not explicitly specified. It is also used to pre-populate forms built from the table using SQLFORM. Note, rather than being a fixed value, the default can instead be a function (including a lambda function) that returns a value of the appropriate type for the field. In that case, the function is called once for each record inserted, even when multiple records are inserted in a single transaction.
- ``required`` tells the DAL that no insert should be allowed on this table if a value for this field is not explicitly specified.
- ``requires`` is a validator or a list of validators. This is not used by the DAL, but it is used by SQLFORM. The default validators for the given types are shown in the following table:
----------
**field type** | **default field validators**
``string`` | ``IS_LENGTH(length)`` default length is 512
``text`` | ``IS_LENGTH(65536)``
``blob`` | ``None``
``boolean`` | ``None``
``integer`` | ``IS_INT_IN_RANGE(-1e100, 1e100)``
``double`` | ``IS_FLOAT_IN_RANGE(-1e100, 1e100)``
``decimal(n,m)`` | ``IS_DECIMAL_IN_RANGE(-1e100, 1e100)``
``date`` | ``IS_DATE()``
``time`` | ``IS_TIME()``
``datetime`` | ``IS_DATETIME()``
``password`` | ``None``
``upload`` | ``None``
``reference <table>`` | ``IS_IN_DB(db,table.field,format)``
``list:string`` | ``None``
``list:integer`` | ``None``
``list:reference <table>`` | ``IS_IN_DB(db,table.field,format,multiple=True)``
``json`` | ``IS_JSON()``
``bigint`` | ``None``
``big-id`` | ``None``
``big-reference`` | ``None``
---------
Decimal requires and returns values as ``Decimal`` objects, as defined in the Python ``decimal`` module. SQLite does not handle the ``decimal`` type so internally we treat it as a ``double``. The (n,m) are the number of digits in total and the number of digits after the decimal point respectively.
The ``big-id`` and, ``big-reference`` are only supported by some of the database engines and are experimental. They are not normally used as field types unless for legacy tables, however, the DAL constructor has a ``bigint_id`` argument that when set to ``True`` makes the ``id`` fields and ``reference`` fields ``big-id`` and ``big-reference`` respectively.
The ``list:<type>`` fields are special because they are designed to take advantage of certain denormalization features on NoSQL (in the case of Google App Engine NoSQL, the field types ``ListProperty`` and ``StringListProperty``) and back-port them all the other supported relational databases. On relational databases lists are stored as a ``text`` field. The items are separated by a ``|`` and each ``|`` in string item is escaped as a ``||``. They are discussed in their own section.
The ``json`` field type is pretty much explanatory. It can store any json serializable object. It is designed to work specifically for MongoDB and backported to the other database adapters for portability.
-------
Notice that ``requires=...`` is enforced at the level of forms, ``required=True`` is enforced at the level of the DAL (insert), while ``notnull``, ``unique`` and ``ondelete`` are enforced at the level of the database. While they sometimes may seem redundant, it is important to maintain the distinction when programming with the DAL.
-------
``ondelete``:inxx
- ``ondelete`` translates into the "ON DELETE" SQL statement. By default it is set to "CASCADE". This tells the database that when it deletes a record, it should also delete all records that refer to it. To disable this feature, set ``ondelete`` to "NO ACTION" or "SET NULL".
- ``notnull=True`` translates into the "NOT NULL" SQL statement. It prevents the database from inserting null values for the field.
- ``unique=True`` translates into the "UNIQUE" SQL statement and it makes sure that values of this field are unique within the table. It is enforced at the database level.
- ``uploadfield`` applies only to fields of type "upload". A field of type "upload" stores the name of a file saved somewhere else, by default on the filesystem under the application "uploads/" folder. If ``uploadfield`` is set, then the file is stored in a blob field within the same table and the value of ``uploadfield`` is the name of the blob field. This will be discussed in more detail later in the context of SQLFORM.
- ``uploadfolder`` defaults to the application's "uploads/" folder. If set to a different path, files will uploaded to a different folder. For example,
``
Field(...,uploadfolder=os.path.join(request.folder,'static/temp'))
``:code
will upload files to the "web2py/applications/myapp/static/temp" folder.
- ``uploadseparate`` if set to True will upload files under different subfolders of the ''uploadfolder'' folder. This is optimized to avoid too many files under the same folder/subfolder. ATTENTION: You cannot change the value of ``uploadseparate`` from True to False without breaking links to existing uploads. web2py either uses the separate subfolders or it does not. Changing the behavior after files have been uploaded will prevent web2py from being able to retrieve those files. If this happens it is possible to move files and fix the problem but this is not described here.
- ``uploadfs`` allows you specify a different file system where to upload files, including an Amazon S3 storage or a remote SFTP storage. This option requires PyFileSystem installed. ``uploadfs`` must point to ``PyFileSystem``. ``PyFileSystem``:inxx ``uploadfs``:idxx
- ``widget`` must be one of the available widget objects, including custom widgets, for example: ``SQLFORM.widgets.string.widget``. A list of available widgets will be discussed later. Each field type has a default widget.
- ``label`` is a string (or a helper or something that can be serialized to a string) that contains the label to be used for this field in auto-generated forms.
- ``comment`` is a string (or a helper or something that can be serialized to a string) that contains a comment associated with this field, and will be displayed to the right of the input field in the autogenerated forms.
- ``writable`` declares whether a field is writable in forms.
- ``readable`` declares whether a field is readable in forms. If a field is neither readable nor writable, it will not be displayed in create and update forms.
- ``update`` contains the default value for this field when the record is updated.
- ``compute`` is an optional function. If a record is inserted or updated, the compute function will be executed and the field will be populated with the function result. The record is passed to the compute function as a ``dict``, and the dict will not include the current value of that, or any other compute field.
- ``authorize`` can be used to require access control on the corresponding field, for "upload" fields only. It will be discussed more in detail in the context of Authentication and Authorization.
- ``autodelete`` determines if the corresponding uploaded file should be deleted when the record referencing the file is deleted. For "upload" fields only.
- ``represent`` can be None or can point to a function that takes a field value and returns an alternate representation for the field value. Examples:
``
db.mytable.name.represent = lambda name,row: name.capitalize()
db.mytable.other_id.represent = lambda id,row: row.myfield
db.mytable.some_uploadfield.represent = lambda value,row: \
A('get it', _href=URL('download', args=value))
``:code
``blob``:inxx
"blob" fields are also special. By default, binary data is encoded in base64 before being stored into the actual database field, and it is decoded when extracted. This has the negative effect of using 25% more storage space than necessary in blob fields, but has two advantages. On average it reduces the amount of data communicated between web2py and the database server, and it makes the communication independent of back-end-specific escaping conventions.
Most attributes of fields and tables can be modified after they are defined:
``
db.define_table('person',Field('name',default=''),format='%(name)s')
db.person._format = '%(name)s/%(id)s'
db.person.name.default = 'anonymous'
``
(notice that attributes of tables are usually prefixed by an underscore to avoid conflict with possible field names).
You can list the tables that have been defined for a given database connection:
``tables``:inxx
``
>>> print db.tables
['person']
``:code
You can also list the fields that have been defined for a given table:
``fields``:inxx
``
>>> print db.person.fields
['id', 'name']
``:code
You can query for the type of a table:
``Table``:inxx
``
>>> print type(db.person)
<class 'gluon.sql.Table'>
``:code
and you can access a table from the DAL connection using:
``
>>> print type(db['person'])
<class 'gluon.sql.Table'>
``:code
Similarly you can access fields from their name in multiple equivalent ways:
``
>>> print type(db.person.name)
<class 'gluon.sql.Field'>
>>> print type(db.person['name'])
<class 'gluon.sql.Field'>
>>> print type(db['person']['name'])
<class 'gluon.sql.Field'>
``:code
Given a field, you can access the attributes set in its definition:
``
>>> print db.person.name.type
string
>>> print db.person.name.unique
False
>>> print db.person.name.notnull
False
>>> print db.person.name.length
32
``:code
including its parent table, tablename, and parent connection:
``
>>> db.person.name._table == db.person
True
>>> db.person.name._tablename == 'person'
True
>>> db.person.name._db == db
True
``:code
A field also has methods. Some of them are used to build queries and we will see them later.
A special method of the field object is ``validate`` and it calls the validators for the field.
``
print db.person.name.validate('John')
``
which returns a tuple ``(value, error)``. ``error`` is ``None`` if the input passes validation.
### Migrations
``migrations``:inxx
``define_table`` checks whether or not the corresponding table exists. If it does not, it generates the SQL to create it and executes the SQL. If the table does exist but differs from the one being defined, it generates the SQL to alter the table and executes it. If a field has changed type but not name, it will try to convert the data (If you do not want this, you need to redefine the table twice, the first time, letting web2py drop the field by removing it, and the second time adding the newly defined field so that web2py can create it.). If the table exists and matches the current definition, it will leave it alone. In all cases it will create the ``db.person`` object that represents the table.
We refer to this behavior as a "migration". web2py logs all migrations and migration attempts in the file "databases/sql.log".
The first argument of ``define_table`` is always the table name. The other unnamed arguments are the fields (Field). The function also takes an optional last argument called "migrate" which must be referred to explicitly by name as in:
``
>>> db.define_table('person', Field('name'), migrate='person.table')
``:code
The value of migrate is the filename (in the "databases" folder for the application) where web2py stores internal migration information for this table.
These files are very important and should never be removed while the corresponding tables exist. In cases where a table has been dropped and the corresponding file still exist, it can be removed manually. By default, migrate is set to True. This causes web2py to generate the filename from a hash of the connection string. If migrate is set to False, the migration is not performed, and web2py assumes that the table exists in the datastore and it contains (at least) the fields listed in ``define_table``.
The best practice is to give an explicit name to the migrate table.
There may not be two tables in the same application with the same migrate filename.
The DAL class also takes a "migrate" argument, which determines the default value of migrate for calls to ``define_table``. For example,
``
>>> db = DAL('sqlite://storage.db', migrate=False)
``:code
will set the default value of migrate to False whenever ``db.define_table`` is called without a migrate argument.
------
Notice that web2py only migrates new columns, removed columns, and changes in column type (except in sqlite). web2py does not migrate changes in attributes such as changes in the values of ``default``, ``unique``, ``notnull``, and ``ondelete``.
------
Migrations can be disabled for all tables at once:
``
db = DAL(...,migrate_enabled=False)
``
This is the recommended behavior when two apps share the same database. Only one of the two apps should perform migrations, the other should disabled them.
### Fixing broken migrations
``fake_migrate``:inxx
There are two common problems with migrations and there are ways to recover from them.
One problem is specific with SQLite. SQLite does not enforce column types and cannot drop columns. This means that if you have a column of type string and you remove it, it is not really removed. If you add the column again with a different type (for example datetime) you end up with a datetime column that contains strings (junk for practical purposes). web2py does not complain about this because it does not know what is in the database, until it tries to retrieve records and fails.
If web2py returns an error in the gluon.sql.parse function when selecting records, this is the problem: corrupted data in a column because of the above issue.
The solution consists in updating all records of the table and updating the values in the column in question with None.
The other problem is more generic but typical with MySQL. MySQL does not allow more than one ALTER TABLE in a transaction. This means that web2py must break complex transactions into smaller ones (one ALTER TABLE at the time) and commit one piece at the time. It is therefore possible that part of a complex transaction gets committed and one part fails, leaving web2py in a corrupted state. Why would part of a transaction fail? Because, for example, it involves altering a table and converting a string column into a datetime column, web2py tries to convert the data, but the data cannot be converted. What happens to web2py? It gets confused about what exactly is the table structure actually stored in the database.
The solution consists of disabling migrations for all tables and enabling fake migrations:
``
db.define_table(....,migrate=False,fake_migrate=True)
``:code
This will rebuild web2py metadata about the table according to the table definition. Try multiple table definitions to see which one works (the one before the failed migration and the one after the failed migration). Once successful remove the ``fake_migrate=True`` attribute.
Before attempting to fix migration problems it is prudent to make a copy of "applications/yourapp/databases/*.table" files.
Migration problems can also be fixed for all tables at once:
``
db = DAL(...,fake_migrate_all=True)
``:code
This also fails if the model describes tables that do not exist in the database,
but it can help narrowing down the problem.
### ``insert``
Given a table, you can insert records
``insert``:inxx
``
>>> db.person.insert(name="Alex")
1
>>> db.person.insert(name="Bob")
2
``:code
Insert returns the unique "id" value of each record inserted.
You can truncate the table, i.e., delete all records and reset the counter of the id.
``truncate``:inxx
``
>>> db.person.truncate()
``:code
Now, if you insert a record again, the counter starts again at 1 (this is back-end specific and does not apply to Google NoSQL):
``
>>> db.person.insert(name="Alex")
1
``:code
Notice you can pass parameters to ``truncate``, for example you can tell SQLITE to restart the id counter.
``
db.person.truncate('RESTART IDENTITY CASCADE')
``:code
The argument is in raw SQL and therefore engine specific.
``bulk_insert``:inxx
web2py also provides a bulk_insert method
``
>>> db.person.bulk_insert([{'name':'Alex'}, {'name':'John'}, {'name':'Tim'}])
[3,4,5]
``:code
It takes a list of dictionaries of fields to be inserted and performs multiple inserts at once. It returns the IDs of the inserted records. On the supported relational databases there is no advantage in using this function as opposed to looping and performing individual inserts but on Google App Engine NoSQL, there is a major speed advantage.
### ``commit`` and ``rollback``
No create, drop, insert, truncate, delete, or update operation is actually committed until you issue the commit command
``commit``:inxx
``
>>> db.commit()
``:code
To check it let's insert a new record:
``
>>> db.person.insert(name="Bob")
2
``:code
and roll back, i.e., ignore all operations since the last commit:
``rollback``:inxx
``
>>> db.rollback()
``:code
If you now insert again, the counter will again be set to 2, since the previous insert was rolled back.
``
>>> db.person.insert(name="Bob")
2
``:code
Code in models, views and controllers is enclosed in web2py code that looks like this:
``
try:
execute models, controller function and view
except:
rollback all connections
log the traceback
send a ticket to the visitor
else:
commit all connections
save cookies, sessions and return the page
``:code
There is no need to ever call ``commit`` or ``rollback`` explicitly in web2py unless one needs more granular control.
### Raw SQL
#### Timing queries
All queries are automatically timed by web2py. The variable ``db._timings`` is a list of tuples. Each tuple contains the raw SQL query as passed to the database driver and the time it took to execute in seconds. This variable can be displayed in views using the toolbar:
``
{{=response.toolbar()}}
``
#### ``executesql``
The DAL allows you to explicitly issue SQL statements.
``executesql``:inxx
``
>>> print db.executesql('SELECT * FROM person;')
[(1, u'Massimo'), (2, u'Massimo')]
``:code
In this case, the return values are not parsed or transformed by the DAL, and the format depends on the specific database driver. This usage with selects is normally not needed, but it is more common with indexes.
``executesql`` takes four optional arguments: ``placeholders``, ``as_dict``, ``fields`` and ``colnames``.
``placeholders`` is an optional
sequence of values to be substituted in
or, if supported by the DB driver, a dictionary with keys
matching named placeholders in your SQL.
If ``as_dict`` is set to True,
and the results cursor returned by the DB driver will be
converted to a sequence of dictionaries keyed with the db
field names. Results returned with ``as_dict = True ``are
the same as those returned when applying **.as_list()** to a normal select.
``
[{field1: value1, field2: value2}, {field1: value1b, field2: value2b}]
``:code
The ``fields`` argument is a list of DAL Field objects that match the
fields returned from the DB. The Field objects should be part of one or
more Table objects defined on the DAL object. The ``fields`` list can
include one or more DAL Table objects in addition to or instead of
including Field objects, or it can be just a single table (not in a
list). In that case, the Field objects will be extracted from the
table(s).
Instead of specifying the ``fields`` argument, the ``colnames`` argument
can be specified as a list of field names in tablename.fieldname format.
Again, these should represent tables and fields defined on the DAL
object.
It is also possible to specify both ``fields`` and the associated
``colnames``. In that case, ``fields`` can also include DAL Expression
objects in addition to Field objects. For Field objects in "fields",
the associated ``colnames`` must still be in tablename.fieldname format.
For Expression objects in ``fields``, the associated ``colnames`` can
be any arbitrary labels.
Notice, the DAL Table objects referred to by ``fields`` or ``colnames`` can
be dummy tables and do not have to represent any real tables in the
database. Also, note that the ``fields`` and ``colnames`` must be in the
same order as the fields in the results cursor returned from the DB.
#### ``_lastsql``
Whether SQL was executed manually using executesql or was SQL generated by the DAL, you can always find the SQL code in ``db._lastsql``. This is useful for debugging purposes:
``_lastdb``:inxx
``
>>> rows = db().select(db.person.ALL)
>>> print db._lastsql
SELECT person.id, person.name FROM person;
``:code
-------
web2py never generates queries using the "*" operator. web2py is always explicit when selecting fields.
-------
### ``drop``
Finally, you can drop tables and all data will be lost:
``drop``:inxx
``
>>> db.person.drop()
``:code
### Indexes
Currently the DAL API does not provide a command to create indexes on tables, but this can be done using the ``executesql`` command. This is because the existence of indexes can make migrations complex, and it is better to deal with them explicitly. Indexes may be needed for those fields that are used in recurrent queries.
Here is an example of how to [[create an index using SQL in SQLite http://www.sqlite.org/lang_createindex.html]]:
``
>>> db = DAL('sqlite://storage.db')
>>> db.define_table('person', Field('name'))
>>> db.executesql('CREATE INDEX IF NOT EXISTS myidx ON person (name);')
``:code
Other database dialects have very similar syntaxes but may not support the optional "IF NOT EXISTS" directive.
### Legacy databases and keyed tables
web2py can connect to legacy databases under some conditions.
The easiest way is when these conditions are met:
- Each table must have a unique auto-increment integer field called "id"
- Records must be referenced exclusively using the "id" field.
When accessing an existing table, i.e., a table not created by web2py in the current application, always set ``migrate=False``.
If the legacy table has an auto-increment integer field but it is not called "id", web2py can still access it but the table definition must contain explicitly as ``Field('....','id')`` where ... is the name of the auto-increment integer field.
``keyed table``:inxx
Finally if the legacy table uses a primary key that is not an auto-increment id field it is possible to use a "keyed table", for example:
``
db.define_table('account',
Field('accnum','integer'),
Field('acctype'),
Field('accdesc'),
primarykey=['accnum','acctype'],
migrate=False)
``:code
- ``primarykey`` is a list of the field names that make up the primary key.
- All primarykey fields have a ``NOT NULL`` set even if not specified.
- Keyed tables can only reference other keyed tables.
- Referencing fields must use the ``reference tablename.fieldname`` format.
- The ``update_record`` function is not available for Rows of keyed tables.
-------
Currently keyed tables are only supported for DB2, MS-SQL, Ingres and Informix, but others engines will be added.
-------
At the time of writing, we cannot guarantee that the ``primarykey`` attribute works with every existing legacy table and every supported database backend.
For simplicity, we recommend, if possible, creating a database view that has an auto-increment id field.
### Distributed transaction
``distributed transactions``:inxx
------
At the time of writing this feature is only supported
by PostgreSQL, MySQL and Firebird, since they expose API for two-phase commits.
------
Assuming you have two (or more) connections to distinct PostgreSQL databases, for example:
``
db_a = DAL('postgres://...')
db_b = DAL('postgres://...')
``:code
In your models or controllers, you can commit them concurrently with:
``
DAL.distributed_transaction_commit(db_a, db_b)
``:code
On failure, this function rolls back and raises an ``Exception``.
In controllers, when one action returns, if you have two distinct connections and you do not call the above function, web2py commits them separately. This means there is a possibility that one of the commits succeeds and one fails. The distributed transaction prevents this from happening.
### More on uploads
Consider the following model:
``
>>> db.define_table('myfile',
Field('image', 'upload', default='path/'))
``:code
In the case of an 'upload' field, the default value can optionally be set to a path (an absolute path or a path relative to the current app folder) and the default image will be set to a copy of the file at the path. A new copy is made for each new record that does not specify an image.
Normally an insert is handled automatically via a SQLFORM or a crud form (which is a SQLFORM) but occasionally you already have the file on the filesystem and want to upload it programmatically. This can be done in this way:
``
>>> stream = open(filename, 'rb')
>>> db.myfile.insert(image=db.myfile.image.store(stream, filename))
``:code
It is also possible to insert a file in a simpler way and have the insert method call store automatically:
``
>>> stream = open(filename, 'rb')
>>> db.myfile.insert(image=stream)
``:code
In this case the filename is obtained from the stream object if available.
The ``store`` method of the upload field object takes a file stream and a filename. It uses the filename to determine the extension (type) of the file, creates a new temp name for the file (according to web2py upload mechanism) and loads the file content in this new temp file (under the uploads folder unless specified otherwise). It returns the new temp name, which is then stored in the ``image`` field of the ``db.myfile`` table.
Note, if the file is to be stored in an associated blob field rather than the file system, the ``store()`` method will not insert the file in the blob field (because ``store()`` is called before the insert), so the file must be explicitly inserted into the blob field:
``
>>> db.define_table('myfile',
Field('image', 'upload', uploadfield='image_file'),
Field('image_file', 'blob'))
>>> stream = open(filename, 'rb')
>>> db.myfile.insert(image=db.myfile.image.store(stream, filename),
image_file=stream.read())
``:code
The opposite of ``.store`` is ``.retrieve``:
``
>>> row = db(db.myfile).select().first()
>>> (filename, stream) = db.myfile.image.retrieve(row.image)
>>> import shutil
>>> shutil.copyfileobj(stream,open(filename,'wb'))
``
### ``Query``, ``Set``, ``Rows``
Let's consider again the table defined (and dropped) previously and insert three records:
``
>>> db.define_table('person', Field('name'))
>>> db.person.insert(name="Alex")
1
>>> db.person.insert(name="Bob")
2
>>> db.person.insert(name="Carl")
3
``:code
You can store the table in a variable. For example, with variable ``person``, you could do:
``Table``:inxx
``
>>> person = db.person
``:code
You can also store a field in a variable such as ``name``. For example, you could also do:
``Field``:inxx
``
>>> name = person.name
``:code
You can even build a query (using operators like ==, !=, <, >, <=, >=, like, belongs) and store the query in a variable ``q`` such as in:
``Query``:inxx
``
>>> q = name=='Alex'
``:code
When you call ``db`` with a query, you define a set of records. You can store it in a variable ``s`` and write:
``Set``:inxx
``
>>> s = db(q)
``:code
Notice that no database query has been performed so far. DAL + Query simply define a set of records in this db that match the query.
web2py determines from the query which table (or tables) are involved and, in fact, there is no need to specify that.
### ``select``
Given a Set, ``s``, you can fetch the records with the command ``select``:
``Rows``:inxx ``select``:inxx
``
>>> rows = s.select()
``:code
``Row``:inxx
It returns an iterable object of class ``gluon.sql.Rows`` whose elements are Row objects. ``gluon.sql.Row`` objects act like dictionaries, but their elements can also be accessed as attributes, like ``gluon.storage.Storage``.The former differ from the latter because its values are read-only.
The Rows object allows looping over the result of the select and printing the selected field values for each row:
``
>>> for row in rows:
print row.id, row.name
1 Alex
``:code
You can do all the steps in one statement:
``
>>> for row in db(db.person.name=='Alex').select():
print row.name
Alex
``:code
``ALL``:inxx
The select command can take arguments. All unnamed arguments are interpreted as the names of the fields that you want to fetch. For example, you can be explicit on fetching field "id" and field "name":
``
>>> for row in db().select(db.person.id, db.person.name):
print row.name
Alex
Bob
Carl
``:code
The table attribute ALL allows you to specify all fields:
``
>>> for row in db().select(db.person.ALL):
print row.name
Alex
Bob
Carl
``:code
Notice that there is no query string passed to db. web2py understands that if you want all fields of the table person without additional information then you want all records of the table person.
An equivalent alternative syntax is the following:
``
>>> for row in db(db.person.id > 0).select():
print row.name
Alex
Bob
Carl
``:code
and web2py understands that if you ask for all records of the table person (id > 0) without additional information, then you want all the fields of table person.
Given one row
``
row = rows[0]
``
you can extract its values using multiple equivalent expressions:
``
>>> row.name
Alex
>>> row['name']
Alex
>>> row('person.name')
Alex
``
The latter syntax is particularly handy when selecting en expression instead of a column. We will show this later.
You can also do
``
rows.compact = False
``
to disable the notation
``
row[i].name
``
and enable, instead, the less compact notation:
``
row[i].person.name
``
Yes this is unusual and rarely needed.
#### Shortcuts
``DAL shortcuts``:inxx
The DAL supports various code-simplifying shortcuts.
In particular:
``
myrecord = db.mytable[id]
``:code
returns the record with the given ``id`` if it exists. If the ``id`` does not exist, it returns ``None``. The above statement is equivalent to
``
myrecord = db(db.mytable.id==id).select().first()
``:code
You can delete records by id:
``
del db.mytable[id]
``:code
and this is equivalent to
``
db(db.mytable.id==id).delete()
``:code
and deletes the record with the given ``id``, if it exists.
You can insert records:
``
db.mytable[0] = dict(myfield='somevalue')
``:code
It is equivalent to
``
db.mytable.insert(myfield='somevalue')