/
usage-guide.txt
3780 lines (2880 loc) · 146 KB
/
usage-guide.txt
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
===========
Usage Guide
===========
.. currentModule:: fdb
Driver structure
================
Source code is currently divided into next submodules:
* :mod:`~fdb.ibase` - Python :ref:`ctypes <python:module-ctypes>` interface to Firebird client library.
* :mod:`~fdb.fbcore` - Main driver source code.
* :mod:`~fdb.services` - Driver code to work with Firebird Services.
* :mod:`~fdb.schema` - Driver code to work with Firebird database schema (metadata).
* :mod:`~fdb.utils` - Various classes and functions used by driver that are generally useful.
All important data, functions, classes and constants are available directly in fdb namespace,
so there is not need to import or use :mod:`~fdb.fbcore` and :mod:`~fdb.ibase` submodules directly.
Exception is the :mod:`fdb.services` submodule that contains functions and classes for work with
Firebird Services. Because Services are optional, not so frequently used Firebird facility, all
service-related code was isolated in separate submodule rather than exposed directly through
main module namespace. Because :mod:`~fdb.services` submodule contains names also used by main
driver (:func:`~fdb.services.connect`, :class:`~fdb.services.Connection`), it's advised to use
fully qualified names when refering to them instead importing them via `from fdb.services import ...`.
.. index:: Database
Databases
=========
Access to the database is made available through :class:`Connection` objects. FDB provides two
constructors for these:
* :func:`connect` - Returns `Connection` to database that already exists.
* :func:`create_database` - Returns `Connection` to newly created database.
.. index::
pair: Database; connect
Using `connect`
---------------
This constructor has number of keyword parameters that could be divided into several groups:
* Database specification (parameters `dsn`, `host`, `database` and `port`)
* User specification (parameters `user`, `password` and `role`)
* Connection options (parameters `sql_dialect`, `charset`, `isolation_level`, `buffers`,
`force_writes`, `no_reserve` and `db_key_scope`)
To establish a connection to database, you always must specify the database, either as `connection
string` parameter `dsn`, or as required combination of parameters `host`, `database` and `port`.
.. important::
Current driver version ignores the value of `port` parameter. If you need to specify the port
number, you have to use `dsn` parameter instead.
Although specification of `user` and `password` parameters is optional (if environment variables
`ISC_USER` and `ISC_PASSWORD` are set, their values are used if these parameters are ommited),
it's recommended practice to use them. Parameter `role` is needed only when you use Firebird roles.
Connection options are optional (see `Firebird Documentation`_ for details). However you may often
want to specify `charset`, as it directs automatic conversions of string data between client and
server, and automatic conversions from/to unicode performed by FDB driver (see `Data handling
and conversions`_ for details).
**Examples:**
.. code-block:: python
# Connecting via 'dsn'
#
# Local database (local protocol, if supported)
con = fdb.connect(dsn='/path/database.fdb', user='sysdba', password='pass')
# Local database (TCP/IP)
con = fdb.connect(dsn='localhost:/path/database.fdb', user='sysdba', password='pass')
# Local database (TCP/IP with port specification)
con = fdb.connect(dsn='localhost/3050:/path/database.fdb', user='sysdba', password='pass')
# Remote database
con = fdb.connect(dsn='host:/path/database.db', user='sysdba', password='pass')
# Remote database with port specification
con = fdb.connect(dsn='host/3050:/path/database.db', user='sysdba', password='pass')
#
# Connecting via 'database', 'host' and 'port'
#
# Local database (local protocol, if supported)
con = fdb.connect(database='/path/database.db', user='sysdba', password='pass')
# Local database (TCP/IP)
con = fdb.connect(host='localhost', database='/path/database.db', user='sysdba', password='pass')
# Local database (TCP/IP with port specification)
con = fdb.connect(host='localhost', port=3050, database='/path/database.db', user='sysdba', password='pass')
# Remote database
con = fdb.connect(host='myhost', database='/path/database.db', user='sysdba', password='pass')
Since version 1.2 FDB supports additional `Connection` class(es) that extend :class:`Connection` functionality
in optional (opt-in) way. For example :class:`ConnectionWithSchema` extends Connection interface with methods
and attributes provided by :class:`~fdb.schema.Schema`. New `connection_class` parameter was introduced to
`connect` and `create_database` to connect to/create database using different class than descends from
`Connection`.
**Example:**
.. code-block:: python
# Connecting through ConnectionWithSchema
#
con = fdb.connect(dsn='/path/database.fdb', user='sysdba', password='pass',
connection_class=fdb.ConnectionWithSchema)
.. index::
pair: Database; create
Using `create_database`
-----------------------
The Firebird engine supports dynamic database creation via the SQL statement `CREATE DATABASE`.
FDB wraps it into :func:`create_database`, that returns `Connection` instance attached to newly
created database.
**Example:**
.. code-block:: python
con = fdb.create_database("create database 'host:/temp/db.db' user 'sysdba' password 'pass'")
.. note::
Since version 1.2 FDB supports additional method for database creation. Instead `CREATE DATABASE`
SQL statement you can use number of optional keyword parameters introduced to :func:`create_database`.
**Example:**
.. code-block:: python
con = fdb.create_database(dsn='/temp/db.fdb',user='sysdba',password='pass',page_size=8192)
.. index::
pair: Database; delete
Deleting databases
------------------
The Firebird engine also supports dropping (deleting) databases dynamically, but dropping is a more
complicated operation than creating, for several reasons: an existing database may be in use by users
other than the one who requests the deletion, it may have supporting objects such as temporary sort
files, and it may even have dependent shadow databases. Although the database engine recognizes a
`DROP DATABASE` SQL statement, support for that statement is limited to the `isql` command-line
administration utility. However, the engine supports the deletion of databases via an API call, which
FDB exposes as :meth:`~Connection.drop_database` method in :class:`Connection` class. So, to drop
a database you need to connect to it first.
**Examples:**
.. code-block:: python
import fdb
con = fdb.create_database("create database '/temp/db.db' user 'sysdba' password 'pass'")
con.drop_database()
con = fdb.connect(dsn='/path/database.fdb', user='sysdba', password='pass')
con.drop_database()
.. index::
pair: Connection; usage
Connection object
-----------------
:class:`Connection` object represents a direct link to database, and works as gateway for next operations
with it:
* `Executing SQL Statements`_: methods :meth:`~Connection.execute_immediate` and :meth:`~Connection.cursor`.
* Dropping database: method :meth:`~Connection.drop_database`.
* `Transanction management`_: methods :meth:`~Connection.begin`, :meth:`~Connection.commit`,
:meth:`~Connection.rollback`, :meth:`~Connection.savepoint`, :meth:`~Connection.trans`,
:meth:`~Connection.trans_info` and :meth:`~Connection.transaction_info`, and attributes
:attr:`~Connection.main_transaction`, :attr:`~Connection.transactions`, :attr:`~Connection.default_tpb`
and :attr:`~Connection.group`.
* Work with `Database Events`_: method :meth:`~Connection.event_conduit`.
* `Getting information about Firebird version`_: attributes :attr:`~Connection.server_version`,
:attr:`~Connection.firebird_version`, :attr:`~Connection.version` and :attr:`~Connection.engine_version`.
* `Getting information about database`_: methods :meth:`~Connection.db_info` and
:meth:`~Connection.database_info`.
* :ref:`Getting information about database metadata <working-with-schema>`: attribute :attr:`~Connection.schema` and :attr:`~Connection.ods`.
.. index::
pair: Firebird; information about
Getting information about Firebird version
------------------------------------------
Because functionality and some features depends on actual Firebird version, it could be important for FDB
users to check it. This (otherwise) simple task could be confusing for new Firebird users, because Firebird
uses two different version lineages. This abomination was introduced to Firebird thanks to its InterBase legacy
(Firebird 1.0 is a fork of InterBase 6.0), as applications designed to work with InterBase can often work
with Firebird without problems (and vice versa). However, legacy applications designed to work with InterBase
may stop working properly if they would detect unexpectedly low server version, so default version number
returned by Firebird (and FDB) is based on InterBase version number. For example this version for Firebird
2.5.2 is 6.3.2, so condition for legacy applications that require at least IB 6.0 is met.
FDB provides these version strings as two `Connection` properties:
- :attr:`~Connection.server_version` - Legacy InterBase-friendly version string.
- :attr:`~Connection.firebird_version` - Firebird's own version string.
However, this version string contains more information than version number. For example for Linux Firebird
2.5.2 it's 'LI-V2.5.2.26540 Firebird 2.5'. So FDB provides two more properties for your convenience:
- :attr:`~Connection.version` - Only Firebird version number. It's a string with format:
major.minor.subrelease.build
- :attr:`~Connection.engine_version` - Engine (major.minor) version as (float) number.
FDB also provides convenient constants for supported engine versions: `ODS_FB_20`,`ODS_FB_21` and `ODS_FB_25`.
Database On-Disk Structure
--------------------------
Particular Firebird features may also depend on specific support in database (for example monitoring
tables introduced in Firebird 2.1). These required structures are present automatically when database
is created by particular engine verison that needs them, but Firebird engine may typically work with
databases created by older versions and thus with older structure, so it could be necessary to consult
also On-Disk Structure (ODS for short) version. FDB provides this number as :attr:`Connection.ods` (float)
property.
**Example**:
.. code-block:: python
con = fdb.connect(dsn='/path/database.fdb', user='sysdba', password='pass')
print 'Firebird version:',con.version
print 'ODS version:',con.ods
::
Firebird version: 2.5.2.26540
ODS version: 11.1
In abover example although connected Firebird engine is version 2.5, connected database has ODS 11.1 which
came with Firebird 2.1, and some Firebird 2.5 features will not be available on this database.
.. index::
pair: Database; information about
Getting information about database
----------------------------------
Firebird provides various informations about server and connected database via `database_info` API call.
FDB surfaces this API through methods :meth:`~Connection.db_info` and :meth:`~Connection.database_info`
on Connection object.
:meth:`Connection.database_info` is a `very thin` wrapper around function `isc_database_info()`.
This method does not attempt to interpret its results except with regard to whether they are a string
or an integer. For example, requesting `isc_info_user_names` with the call::
con.database_info(fdb.isc_info_user_names, 's')
will return a binary string containing a raw succession of length-name pairs.
**Example program:**
.. code-block:: python
import fdb
con = fdb.connect(dsn='localhost:/temp/test.db', user='sysdba', password='pass')
# Retrieving an integer info item is quite simple.
bytesInUse = con.database_info(fdb.isc_info_current_memory, 'i')
print 'The server is currently using %d bytes of memory.' % bytesInUse
# Retrieving a string info item is somewhat more involved, because the
# information is returned in a raw binary buffer that must be parsed
# according to the rules defined in the Interbase® 6 API Guide section
# entitled "Requesting buffer items and result buffer values" (page 51).
#
# Often, the buffer contains a succession of length-string pairs
# (one byte telling the length of s, followed by s itself).
# Function fdb.ibase.ord2 is provided to convert a raw
# byte to a Python integer (see examples below).
buf = con.database_info(fdb.isc_info_db_id, 's')
# Parse the filename from the buffer.
beginningOfFilename = 2
# The second byte in the buffer contains the size of the database filename
# in bytes.
lengthOfFilename = fdb.ibase.ord2(buf[1])
filename = buf[beginningOfFilename:beginningOfFilename + lengthOfFilename]
# Parse the host name from the buffer.
beginningOfHostName = (beginningOfFilename + lengthOfFilename) + 1
# The first byte after the end of the database filename contains the size
# of the host name in bytes.
lengthOfHostName = fdb.ibase.ord2(buf[beginningOfHostName - 1])
host = buf[beginningOfHostName:beginningOfHostName + lengthOfHostName]
print 'We are connected to the database at %s on host %s.' % (filename, host)
Sample output::
The server is currently using 8931328 bytes of memory.
We are connected to the database at C:\TEMP\TEST.DB on host WEASEL.
A more convenient way to access the same functionality is via the :meth:`~Connection.db_info` method,
which is high-level convenience wrapper around the `database_info()` method that parses the output of
database_info into Python-friendly objects instead of returning raw binary buffers in the case of complex
result types. For example, requesting `isc_info_user_names` with the call::
con.db_info(fdb.isc_info_user_names)
returns a dictionary that maps (username -> number of open connections). If SYSDBA has one open connection
to the database to which `con` is connected, and TEST_USER_1 has three open connections to that same database,
the return value would be::
{‘SYSDBA’: 1, ‘TEST_USER_1’: 3}
**Example program:**
.. code-block:: python
import fdb
import os.path
###############################################################################
# Querying an isc_info_* item that has a complex result:
###############################################################################
# Establish three connections to the test database as TEST_USER_1, and one
# connection as SYSDBA. Then use the Connection.db_info method to query the
# number of attachments by each user to the test database.
testUserCons = []
for i in range(3):
tcon = fdb.connect(dsn='localhost:/temp/test.db', user='TEST_USER_1', password='pass')
testUserCons.append(tcon)
con = fdb.connect(dsn='localhost:/temp/test.db', user='sysdba', password='pass')
print 'Open connections to this database:'
print con.db_info(fdb.isc_info_user_names)
###############################################################################
# Querying multiple isc_info_* items at once:
###############################################################################
# Request multiple db_info items at once, specifically the page size of the
# database and the number of pages currently allocated. Compare the size
# computed by that method with the size reported by the file system.
# The advantages of using db_info instead of the file system to compute
# database size are:
# - db_info works seamlessly on connections to remote databases that reside
# in file systems to which the client program lacks access.
# - If the database is split across multiple files, db_info includes all of
# them.
res = con.db_info([fdb.isc_info_page_size, fdb.isc_info_allocation])
pagesAllocated = res[fdb.isc_info_allocation]
pageSize = res[fdb.isc_info_page_size]
print '\ndb_info indicates database size is', pageSize * pagesAllocated, 'bytes'
print 'os.path.getsize indicates size is ', os.path.getsize(DB_FILENAME), 'bytes'
Sample output::
Open connections to this database:
{'SYSDBA': 1, 'TEST_USER_1': 3}
db_info indicates database size is 20684800 bytes
os.path.getsize indicates size is 20684800 bytes
.. index:: SQL Statement
Executing SQL Statements
========================
FDB implements two ways for execution of SQL commands against connected database:
* :meth:`~Connection.execute_immediate` - for execution of SQL commands that don't
return any result.
* :class:`Cursor` objects that offer rich interface for execution of SQL commands and
fetching their results.
.. index::
pair: Cursor; usage
Cursor object
-------------
Because `Cursor` objects always operate in context of single :class:`Connection` (and :class:`Transaction`),
`Cursor` instances are not created directly, but by constructor method. Python DB API 2.0 assume that
if database engine supports transactions, it supports only one transaction per connection, hence it
defines constructor method :meth:`~Connection.cursor` (and other transaction-related methods) as part
of :class:`Connection` interface. However, Firebird supports multiple independent transactions per
connection. To conform to Python DB API, FDB uses concept of internal :attr:`~Connection.main_transaction`
and secondary :attr:`~Connection.transactions`. Cursor constructor is primarily defined by
:class:`Transaction`, and Cursor constructor on `Connection` is therefore a shortcut for `main_transaction.cursor()`.
`Cursor` objects are used for next operations:
* Execution of SQL Statemets: methods :meth:`~Cursor.execute`, :meth:`~Cursor.executemany` and
:meth:`~Cursor.callproc`.
* Creating :class:`PreparedStatement` objects for efficient repeated execution of SQL statements, and
to obtain additional information about SQL statements (like execution :attr:`~PreparedStatement.plan`):
method :meth:`~Cursor.prep`.
* `Fetching results <Fetching data from server>`_: methods :meth:`~Cursor.fetchone`, :meth:`~Cursor.fetchmany`, :meth:`~Cursor.fetchall`,
:meth:`~Cursor.fetchonemap`, :meth:`~Cursor.fetchmanymap`, :meth:`~Cursor.fetchallmap`,
:meth:`~Cursor.iter`, :meth:`~Cursor.itermap` and :meth:`~Cursor.next`.
.. index::
pair: SQL Statement; execution
SQL Execution Basics
--------------------
There are three methods how to execute SQL commands:
* :meth:`Connection.execute_immediate` or :meth:`Transaction.execute_immediate` for SQL commands that
don't return any result, and are not executed frequently. This method also **doesn't** support either
`parametrized statements`_ or `prepared statements`_.
.. tip::
This method is efficient for `administrative` and `DDL`_ SQL commands, like `DROP`, `CREATE` or `ALTER`
commands, `SET STATISTICS` etc.
* :meth:`Cursor.execute` or :meth:`Cursor.executemany` for commands that return result sets, i.e. sequence
of `rows` of the same structure, and sequence has unknown number of `rows` (including zero).
.. tip::
This method is preferred for all `SELECT` and other `DML`_ statements, or any statement that is executed
frequently, either `as is` or in `parametrized` form.
* :meth:`Cursor.callproc` for execution of `Stored procedures` that always return exactly one set of values.
.. note::
This method of SP invocation is equivalent to `"EXECUTE PROCEDURE ..."` SQL statement.
.. index::
pair: SQL Statement; parametrized
.. _parametrized-statements:
Parametrized statements
-----------------------
When SQL command you want to execute contains data `values`, you can either:
* Embed them `directly` or via `string formatting` into command `string`, e.g.:
.. code-block:: python
cur.execute("insert into the_table (a,b,c) values ('aardvark', 1, 0.1)")
# or
cur.execute("select * from the_table where col == 'aardvark'")
# or
cur.execute("insert into the_table (a,b,c) values ('%s', %i, %f)" % ('aardvark',1,0.1))
# or
cur.execute("select * from the_table where col == '%s'" % 'aardvark')
* Use parameter marker (`?`) in command `string` in the slots where values are expected, then supply
those values as Python list or tuple:
.. code-block:: python
cur.execute("insert into the_table (a,b,c) values (?,?,?)", ('aardvark', 1, 0.1))
# or
cur.execute("select * from the_table where col == ?",('aardvark',))
While both methods have the same results, the second one (called `parametrized`) has several important
advantages:
* You don't need to handle conversions from Python data types to strings.
* FDB will handle all data type conversions (if necessary) from Python data types to Firebird ones,
including `None/NULL` conversion and conversion from `unicode` to `byte strings` in encoding expected
by server.
* You may pass BLOB values as open `file-like` objects, and FDB will handle the transfer of BLOB value.
* If you'll pass exactly the same command `string` again to particular :class:`Cursor` instance,
it will be executed more efficiently (see section about `Prepared Statements`_ for details).
Parametrized statemets also have some limitations. Currently:
* `DATE`, `TIME` and `DATETIME` values must be relevant :mod:`datetime` objects.
* `NUMERIC` and `DECIMAL` values must be :mod:`decimal` objects.
.. index::
pair: Cursor; fetching data
Fetching data from server
-------------------------
Result of SQL statement execution consists from sequence of zero to unknown number of `rows`, where each
`row` is a set of exactly the same number of values. :class:`Cursor` object offer number of different
methods for fetching these `rows`, that should satisfy all your specific needs:
* :meth:`~Cursor.fetchone` - Returns the next row of a query result set, or `None` when no more data is
available.
.. tip::
Cursor supports the :ref:`iterator protocol <python:typeiter>`, yielding tuples of values like
:meth:`~Cursor.fetchone`.
* :meth:`~Cursor.fetchmany` - Returns the next set of rows of a query result, returning a sequence of
sequences (e.g. a list of tuples). An empty sequence is returned when no more rows are available.
The number of rows to fetch per call is specified by the parameter. If it is not given, the cursor’s
:attr:`~Cursor.arraysize` determines the number of rows to be fetched. The method does try to fetch
as many rows as indicated by the size parameter. If this is not possible due to the specified number
of rows not being available, fewer rows may be returned.
.. note::
The default value of :attr:`~Cursor.arraysize` is `1`, so without paremeter it's equivalent to
:meth:`~Cursor.fetchone`, but returns list of `rows`, instead actual `row` directly.
* :meth:`~Cursor.fetchall` - Returns all (remaining) rows of a query result as list of tuples, where
each tuple is one row of returned values.
.. tip::
This method can potentially return huge amount of data, that may exhaust available memory.
If you need just `iteration` over potentially big result set, use loops with :meth:`~Cursor.fetchone`,
Cursor's built-in support for :ref:`iterator protocol <python:typeiter>` or call to :meth:`~Cursor.iter`
instead this method.
* :meth:`~Cursor.fetchonemap` - Returns the next row like :meth:`~Cursor.fetchone`, but returns a mapping
of `field name` to `field value`, rather than a tuple.
* :meth:`~Cursor.fetchmanymap` - Returns the next set of rows of a query result like :meth:`~Cursor.fetchmany`,
but returns a list of mapping of `field name` to `field value`, rather than a tuple.
* :meth:`~Cursor.fetchallmap` - Returns all (remaining) rows of a query result like :meth:`~Cursor.fetchall`,
returns a list of mappings of `field name` to `field value`, rather than a tuple.
.. tip::
This method can potentially return huge amount of data, that may exhaust available memory.
If you need just `iteration` over potentially big result set with mapping support, use
:meth:`~Cursor.itermap` instead this method.
* :meth:`~Cursor.iter` - Equivalent to the :meth:`~Cursor.fetchall`, except that it returns :ref:`iterator
<python:typeiter>` rather than materialized list.
* :meth:`~Cursor.itermap` - Equivalent to the :meth:`~Cursor.fetchallmap`, except that it returns
:ref:`iterator <python:typeiter>` rather than materialized list.
* Call to :meth:`~Cursor.execute` returns self (Cursor instance) that itself supports
the :ref:`iterator protocol <python:typeiter>`, yielding tuples of values like :meth:`~Cursor.fetchone`.
.. important::
FDB makes absolutely no guarantees about the return value of the `fetchone` / `fetchmany` / `fetchall`
methods except that it is a sequence indexed by field position. FDB makes absolutely no guarantees about
the return value of the `fetchonemap` / `fetchmanymap` / `fetchallmap` methods except that it is a mapping
of field name to field value. Therefore, client programmers should not rely on the return value being
an instance of a particular class or type.
**Examples:**
.. code-block:: python
import fdb
con = fdb.connect(dsn='/temp/test.db', user='sysdba', password='masterkey')
cur = con.cursor()
SELECT = "select name, year_released from languages order by year_released"
# 1. Using built-in support for iteration protocol to iterate over the rows available from the cursor,
# unpacking the resulting sequences to yield their elements (name, year_released):
cur.execute(SELECT)
for (name, year_released) in cur:
print '%s has been publicly available since %d.' % (name, year_released)
# or alternatively you can take an advantage of cur.execute returning self.
for (name, year_released) in cur.execute(SELECT):
print '%s has been publicly available since %d.' % (name, year_released)
# 2. Equivalently using fetchall():
# This is potentially dangerous if result set is huge, as the whole result set is first materialized
# as list and then used for iteration.
cur.execute(SELECT)
for row in cur.fetchall():
print '%s has been publicly available since %d.' % (row[0], row[1])
# 3. Using mapping-iteration rather than sequence-iteration:
cur.execute(SELECT)
for row in cur.itermap():
print '%(name)s has been publicly available since %(year_released)d.' % row
.. tip::
:meth:`Cursor.execute` and :meth:`Cursor.executemany` return self, so you can use calls to them
as iterators (see example above).
.. index::
pair: SQL Statement; prepared
.. _prepared-statements:
Prepared Statements
-------------------
Execution of any SQL statement has three phases:
* *Preparation*: command is analyzed, validated, execution plan is determined by optimizer and all
necessary data structures (for example for input and output parameters) are initialized.
* *Execution*: input parameters (if any) are passed to server and previously prepared statement is
actually executed by database engine.
* *Fetching*: result of execution and data (if any) are transfered from server to client, and
allocated resources are then released.
The preparation phase consumes some amount of server resources (memory and CPU). Although preparation
and release of resources typically takes only small amount of CPU time, it builds up as number
of executed statements grows. Firebird (like most database engines) allows to spare this time for
subsequent execution if particular statement should be executed repeatedly - by reusing once prepared
statement for repeated execution. This may save significant amount of server processing time, and result
in better overall performance.
FDB builds on this by encapsulating all statement-related code into separate :class:`PreparedStatement`
class, and implementing :class:`Cursor` class as a wrapper around it.
.. warning::
FDB's implementation of Cursor somewhat violates the Python DB API 2.0, which requires that cursor
will be unusable after call to `close`; and an Error (or subclass) exception should be raised if
any operation is attempted with the cursor.
If you'll take advantage of this `anomaly`, your code would be less portable to other Python DB
API 2.0 compliant drivers.
Beside SQL command string, `Cursor` also allows to aquire and use `PreparedStatement` instances explicitly.
`PreparedStatement` are aquired by calling :meth:`~Cursor.prep` method could be then passed to :meth:`~Cursor.execute`
or :meth:`~Cursor.executemany` instead `command string`.
**Example:**
.. code-block:: python
insertStatement = cur.prep("insert into the_table (a,b,c) values (?,?,?)")
inputRows = [
('aardvark', 1, 0.1),
('zymurgy', 2147483647, 99999.999),
('foobar', 2000, 9.9)
]
for row in inputRows:
cur.execute(insertStatement,row)
#
# or you can use executemany
#
cur.executemany(insertStatement, inputRows)
Prepared statements are bound to `Cursor` instance that created them, and can't be used with any other
`Cursor` instance. Beside repeated execution they are also useful to get information about statement (like
its output :attr:`~PreparedStatement.description`, execution :attr:`~PreparedStatement.plan` or
:attr:`~PreparedStatement.statement_type`) before its execution.
**Example Program:**
The following program demonstrates the explicit use of `PreparedStatements`. It also benchmarks explicit
`PreparedStatement` reuse against normal execution that prepares statements on each execution.
.. code-block:: python
import time
import fdb
con = fdb.connect(dsn='localhost:employee',
user='sysdba', password='masterkey'
)
cur = con.cursor()
# Create supporting database entities:
cur.execute("recreate table t (a int, b varchar(50))")
con.commit()
cur.execute("create unique index unique_t_a on t(a)")
con.commit()
# Explicitly prepare the insert statement:
psIns = cur.prep("insert into t (a,b) values (?,?)")
print 'psIns.sql: "%s"' % psIns.sql
print 'psIns.statement_type == fdb.isc_info_sql_stmt_insert:', (
psIns.statement_type == fdb.isc_info_sql_stmt_insert
)
print 'psIns.n_input_params: %d' % psIns.n_input_params
print 'psIns.n_output_params: %d' % psIns.n_output_params
print 'psIns.plan: %s' % psIns.plan
print
N = 50000
iStart = 0
# The client programmer uses a PreparedStatement explicitly:
startTime = time.time()
for i in xrange(iStart, iStart + N):
cur.execute(psIns, (i, str(i)))
print (
'With explicit prepared statement, performed'
'\n %0.2f insertions per second.' % (N / (time.time() - startTime))
)
con.commit()
iStart += N
# A new SQL string containing the inputs is submitted every time. Also, in a
# more complicated scenario where the end user supplied the string input
# values, the program would risk SQL injection attacks:
startTime = time.time()
for i in xrange(iStart, iStart + N):
cur.execute("insert into t (a,b) values (%d,'%s')" % (i, str(i)))
print (
'When unable to reuse prepared statement, performed'
'\n %0.2f insertions per second.' % (N / (time.time() - startTime))
)
con.commit()
# Prepare a SELECT statement and examine its properties. The optimizer's plan
# should use the unique index that we created at the beginning of this program.
print
psSel = cur.prep("select * from t where a = ?")
print 'psSel.sql: "%s"' % psSel.sql
print 'psSel.statement_type == fdb.isc_info_sql_stmt_select:', (
psSel.statement_type == fdb.isc_info_sql_stmt_select
)
print 'psSel.n_input_params: %d' % psSel.n_input_params
print 'psSel.n_output_params: %d' % psSel.n_output_params
print 'psSel.plan: %s' % psSel.plan
# The current implementation does not allow PreparedStatements to be prepared
# on one Cursor and executed on another:
print
print 'Note that PreparedStatements are not transferrable from one cursor to another:'
cur2 = con.cursor()
cur2.execute(psSel)
Sample output::
psIns.sql: "insert into t (a,b) values (?,?)"
psIns.statement_type == fdb.isc_info_sql_stmt_insert: True
psIns.n_input_params: 2
psIns.n_output_params: 0
psIns.plan: None
With explicit prepared statement, performed
4276.00 insertions per second.
When unable to reuse prepared statement, performed
2037.70 insertions per second.
psSel.sql: "select * from t where a = ?"
psSel.statement_type == fdb.isc_info_sql_stmt_select: True
psSel.n_input_params: 1
psSel.n_output_params: 2
psSel.plan: PLAN (T INDEX (UNIQUE_T_A))
Note that PreparedStatements are not transferrable from one cursor to another:
Traceback (most recent call last):
File "pstest.py", line 85, in <module>
cur2.execute(psSel)
File "/home/job/python/envs/pyfirebird/fdb/fdb/fbcore.py", line 2623, in execute
raise ValueError("PreparedStatement was created by different Cursor.")
ValueError: PreparedStatement was created by different Cursor.
As you can see, the version that prevents the reuse of prepared statements is about two times slower
– *for a trivial statement*. In a real application, SQL statements are likely to be far more complicated,
so the speed advantage of using prepared statements would only increase.
.. index::
pair: Cursor; named
Named Cursors
-------------
To allow the Python programmer to perform scrolling `UPDATE` or `DELETE` via the `“SELECT ... FOR UPDATE”`
syntax, FDB provides the read/write property :attr:`Cursor.name`.
**Example Program:**
.. code-block:: python
import fdb
con = fdb.connect(dsn='localhost:/temp/test.db', user='sysdba', password='pass')
curScroll = con.cursor()
curUpdate = con.cursor()
curScroll.execute("select city from addresses for update")
curScroll.name = 'city_scroller'
update = "update addresses set city=? where current of " + curScroll.name
for (city,) in curScroll:
city = ... # make some changes to city
curUpdate.execute( update, (city,) )
con.commit()
.. index::
pair: Stored procedure; execution
Working with stored procedures
------------------------------
Firebird stored procedures can have `input` parameters and/or `output` parameters. Some databases support
`input/output` parameters, where the same parameter is used for both input and output; Firebird does not
support this.
It is important to distinguish between procedures that `return a result set` and procedures that `populate
and return their output parameters` exactly once. Conceptually, the latter “return their output parameters”
like a Python function, whereas the former “yield result rows” like a Python generator.
Firebird’s `server-side` procedural SQL syntax makes no such distinction, but client-side SQL code (and C
API code) must. A result set is retrieved from a stored procedure by `SELECT'ing from the procedure,
whereas output parameters are retrieved with an 'EXECUTE PROCEDURE' statement`.
To **retrieve a result set** from a stored procedure with FDB, use code such as this:
.. code-block:: python
cur.execute("select output1, output2 from the_proc(?, ?)", (input1, input2))
# Ordinary fetch code here, such as:
for row in cur:
... # process row
con.commit() # If the procedure had any side effects, commit them.
To **execute** a stored procedure and **access its output parameters**, use code such as this:
.. code-block:: python
cur.callproc("the_proc", (input1, input2))
# If there are output parameters, retrieve them as though they were the
# first row of a result set. For example:
outputParams = cur.fetchone()
con.commit() # If the procedure had any side effects, commit them.
This latter is not very elegant; it would be preferable to access the procedure’s output parameters as
the return value of :meth:`Cursor.callproc`. The Python DB API specification requires the current behavior,
however.
.. index::
pair: Data; conversion
Data handling and conversions
=============================
.. index::
pair: parameter; conversion
Implicit Conversion of Input Parameters from Strings
----------------------------------------------------
The database engine treats most SQL data types in a weakly typed fashion: the engine may attempt to convert
the raw value to a different type, as appropriate for the current context. For instance, the SQL expressions
`123` (integer) and `‘123’` (string) are treated equivalently when the value is to be inserted into
an `integer` field; the same applies when `‘123’` and `123` are to be inserted into a `varchar` field.
This weak typing model is quite unlike Python’s dynamic yet strong typing. Although weak typing is regarded
with suspicion by most experienced Python programmers, the database engine is in certain situations so
aggressive about its typing model that KInterbasDB must compromise in order to remain an elegant means
of programming the database engine.
An example is the handling of “magic values” for date and time fields. The database engine interprets certain
string values such as `‘yesterday’` and `‘now’` as having special meaning in a date/time context. If FDB did
not accept strings as the values of parameters destined for storage in date/time fields, the resulting code
would be awkward. Consider the difference between the two Python snippets below, which insert a row containing
an integer and a timestamp into a table defined with the following DDL statement:
.. code-block:: sql
create table test_table (i integer, t timestamp)
.. code-block:: python
i = 1
t = 'now'
sqlWithMagicValues = "insert into test_table (i, t) values (?, '%s')" % t
cur.execute( sqlWithMagicValues, (i,) )
.. code-block:: python
i = 1
t = 'now'
cur.execute( "insert into test_table (i, t) values (?, ?)", (i, t) )
If FDB did not support weak parameter typing, string parameters that the database engine is to interpret
as “magic values” would have to be rolled into the SQL statement in a separate operation from the binding
of the rest of the parameters, as in the first Python snippet above. Implicit conversion of parameter values
from strings allows the consistency evident in the second snippet, which is both more readable and more
general.
It should be noted that FDB does not perform the conversion from string itself. Instead, it passes that
responsibility to the database engine by changing the parameter metadata structure dynamically at the last
moment, then restoring the original state of the metadata structure after the database engine has performed
the conversion.
A secondary benefit is that when one uses FDB to import large amounts of data from flat files into
the database, the incoming values need not necessarily be converted to their proper Python types before
being passed to the database engine. Eliminating this intermediate step may accelerate the import process
considerably, although other factors such as the chosen connection protocol and the deactivation of indexes
during the import are more consequential. For bulk import tasks, the database engine’s external tables also
deserve consideration. External tables can be used to suck semi-structured data from flat files directly
into the relational database without the intervention of an ad hoc conversion program.
.. index::
pair: unicode; conversion
Automatic conversion from/to unicode
------------------------------------
In Firebird, every `CHAR`, `VARCHAR` or textual `BLOB` field can (or, better: must) have a `character set`
assigned. While it's possible to define single character set for whole database, it's also possible to
define different character set for each textual field. This information is used to correctly store the bytes
that make up the character string, and together with collation information (that defines the sort ordering
and uppercase conversions for a string) is vital for correct data manupulation, including automatic
transliteration between character sets when necessary.
.. important::
Because data also flow between server and client application, it's vital that client will send data
encoded only in character set(s) that server expects. While it's possible to leave this responsibility
completely on client application, it's better when client and server settle on single character set
they would use for communication, especially when database operates with multiple character sets, or
uses character set that is not `native` for client application.
Character set for communication is specified using `charset` parameter in :func:`connection <connect>` call.
When `connection charset` is defined, all textual data returned from server are encoded in this charset,
and client application must ensure that all textual data sent to server are encoded only in this charset
as well.
FDB helps with client side of this character set bargain by automatically converting `unicode` strings into
`bytes/strings` encoded in connection character set, and vice versa. However, developers are still
responsible that `non-unicode` strings passed to server are in correct encoding (because FDB makes no
assuption about encoding of non-unicode strings, so it can't recode them to connection charset).
.. important::
In case that `connection charset` is NOT defined at all, or `NONE` charset is specified, FDB uses
:func:`locale.getpreferredencoding` to determine encoding for conversions from/to `unicode`.
.. important::
There is one exception to automatic conversion: when character set OCTETS is defined for data column.
Values assigned to OCTETS columns are always passed `as is`, because they're basically binary streams.
This has specific implications regarding Python version you use. Python 2.x `native strings` are `bytes`,
suitable for such binary streams, but Python 3 native strings are `unicode`, and you would probably
want to use `bytes` type instead. However, FDB in this case doesn't check the value type at all, so
you'll not be warned if you'll make a mistake and pass `unicode` to OCTETS column (unless you'll pass
more bytes than column may hold, or you intend to store unicode that way).
Rules for automatic conversion depend on Python version you use:
* Native Python 2.x `strings` are passed to server as is, and developers must explicitly use `unicode`
strings to take advantage of automatic conversion. String values coming from server are converted to
`unicode` **only**:
* for data stored in database (i.e. not for string values returned by Firebird Service and `info`
calls etc.).
* when `connection charset` is specified.
* Native Python 3 strings are `unicode`, so conversion is fully automatic in both directions for all
textual data, i.e. including for string values returned by Firebird Service and `info` calls etc.
When `connection charset` is not specified, FDB uses :func:`locale.getpreferredencoding` to determine
encoding for conversions from/to `unicode`.
.. tip::
Except for legacy databases that doesn't have `character set` defined, **always** define character
set for your databases and specify `connection charset`. It will make your life much easier.
.. index:: BLOB
pair: Data; BLOB
.. _working_with_blobs:
Working with BLOBs
------------------
FDB uses two types of BLOB values:
* **Materialized** BLOB values are Python strings. This is the **default** type.