Commit 99ebaeef authored by Benjamin Eberlei's avatar Benjamin Eberlei

Add chapter on database portability, added section to data retrieval and...

Add chapter on database portability, added section to data retrieval and manipulation on binding type handling and parameter lists.
parent 64ad7f02
...@@ -23,6 +23,7 @@ Contents: ...@@ -23,6 +23,7 @@ Contents:
reference/schema-representation reference/schema-representation
reference/events reference/events
reference/supporting-other-databases reference/supporting-other-databases
reference/portability
reference/known-vendor-issues reference/known-vendor-issues
Indices and tables Indices and tables
......
Data Retrieval And Manipulation Data Retrieval And Manipulation
=============================== ===============================
Doctrine DBAL follows the PDO API very closely. If you have worked with PDO
before you will get to know Doctrine DBAL very quickly. On top of API provided
by PDO there are tons of convenience functions in Doctrine DBAL.
Types
-----
Doctrine DBAL extends PDOs handling of binding types in prepared statement
considerably. Besides the well known ``\PDO::PARAM_*`` constants you
can make use of two very powerful additional features.
Doctrine\DBAL\Types Conversion
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you don't specify an integer (through a ``PDO::PARAM*`` constant) to
any of the parameter binding methods but a string, Doctrine DBAL will
ask the type abstraction layer to convert the passed value from
its PHP to a database representation. This way you can pass ``\DateTime``
instances to a prepared statement and have Doctrine convert them
to the apropriate vendors database format:
.. code-block:: php
<?php
$date = new \DateTime("2011-03-05 14:00:21");
$stmt = $conn->prepare("SELECT * FROM articles WHERE publish_date > ?");
$stmt->bindValue(1, $date, "datetime");
$stmt->execute();
If you take a look at ``Doctrine\DBAL\Types\DateTimeType`` you will see that
parts of the conversion is delegated to a method on the current database platform,
which means this code works independent of the database you are using.
.. note::
Be aware this type conversion only works with ``Statement#bindValue()``,
``Connection#executeQuery()`` and ``Connection#executeUpdate()``. It
is not supported to pass a doctrine type name to ``Statement#bindParam()``,
because this would not work with binding by reference.
List of Parameters Conversion
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. note::
This is a Doctrine 2.1 feature.
One rather annoying bit of missing functionality in SQL is the support for lists of parameters.
You cannot bind an array of values into a single prepared statement parameter. Consider
the following very common SQL statement:
.. code-block:: sql
SELECT * FROM articles WHERE id IN (?)
Since you are using an ``IN`` expression you would really like to use it in the following way
(and I guess everybody has tried to do this once in his life, before realizing it doesn't work):
.. code-block:: php
<?php
$stmt = $conn->prepare('SELECT * FROM articles WHERE id IN (?)');
// THIS WILL NOT WORK:
$stmt->bindValue(1, array(1, 2, 3, 4, 5, 6));
$stmt->execute();
Implementing a generic way to handle this kind of query is tedious work. This is why most
developers fallback to inserting the parameters directly into the query, which can open
SQL injection possibilities if not handled carefully.
Doctrine DBAL implements a very powerful parsing process that will make this kind of prepared
statement possible natively in the binding type system.
The parsing necessarily comes with a performance overhead, but only if you really use a list of parameters.
There are two special binding types that describe a list of integers or strings:
* \Doctrine\DBAL\Connection::PARAM_INT_ARRAY
* \Doctrine\DBAL\Connection::PARAM_STR_ARRAY
Using one of this constants as a type you can activate the SQLParser inside Doctrine that rewrites
the SQL and flattens the specified values into the set of parameters. Consider our previous example:
.. code-block:: php
<?php
$stmt = $conn->executeQuery('SELECT * FROM articles WHERE id IN (?)',
array(1 => array(1, 2, 3, 4, 5, 6)),
array(1 => \Doctrine\DBAL\Connection::PARAM_INT_ARRAY)
);
The sql statement passed to ``Connection#executeQuery`` is not the one actually passed to the
database. It is internally rewritten to look like the following explicit code that could
be specified aswell:
<?php
// Same SQL WITHOUT usage of Doctrine\DBAL\Connection::PARAM_INT_ARRAY
$stmt = $conn->executeQuery('SELECT * FROM articles WHERE id IN (?, ?, ?, ?, ?, ?)',
array(1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6),
array(
1 => \PDO::PARAM_INT, 2 => \PDO::PARAM_INT, 3 => \PDO::PARAM_INT,
4 => \PDO::PARAM_INT, 5 => \PDO::PARAM_INT, 6 => \PDO::PARAM_INT,
)
);
This is much more complicated and is ugly to write generically.
.. note::
The parameter list support only works with ``Doctrine\DBAL\Connection::executeQuery()``
and ``Doctrine\DBAL\Connection::executeUpdate()``, NOT with the binding methods of
a prepared statement.
API
---
The DBAL contains several methods for executing queries against The DBAL contains several methods for executing queries against
your configured database for data retrieval and manipulation. Below your configured database for data retrieval and manipulation. Below
we'll introduce these methods and provide some examples for each of we'll introduce these methods and provide some examples for each of
them. them.
prepare() prepare()
------------- ~~~~~~~~~
Prepare a given sql statement and return the Prepare a given sql statement and return the
``\Doctrine\DBAL\Driver\Statement`` instance: ``\Doctrine\DBAL\Driver\Statement`` instance:
...@@ -29,7 +143,7 @@ Prepare a given sql statement and return the ...@@ -29,7 +143,7 @@ Prepare a given sql statement and return the
*/ */
executeUpdate() executeUpdate()
-------------------------------------------------------------------- ~~~~~~~~~~~~~~~
Executes a prepared statement with the given sql and parameters and Executes a prepared statement with the given sql and parameters and
returns the affected rows count: returns the affected rows count:
...@@ -46,7 +160,7 @@ parameters and expected database values. See the ...@@ -46,7 +160,7 @@ parameters and expected database values. See the
`Types <./types#type-conversion>`_ section for more information. `Types <./types#type-conversion>`_ section for more information.
executeQuery() executeQuery()
------------------------------------------------------------------- ~~~~~~~~~~~~~~
Creates a prepared statement for the given sql and passes the Creates a prepared statement for the given sql and passes the
parameters to the execute method, then returning the statement: parameters to the execute method, then returning the statement:
...@@ -70,7 +184,7 @@ parameters and expected database values. See the ...@@ -70,7 +184,7 @@ parameters and expected database values. See the
`Types <./types#type-conversion>`_ section for more information. `Types <./types#type-conversion>`_ section for more information.
fetchAll() fetchAll()
----------------------------- ~~~~~~~~~~
Execute the query and fetch all results into an array: Execute the query and fetch all results into an array:
...@@ -89,7 +203,7 @@ Execute the query and fetch all results into an array: ...@@ -89,7 +203,7 @@ Execute the query and fetch all results into an array:
*/ */
fetchArray() fetchArray()
------------------------------- ~~~~~~~~~~~~
Numeric index retrieval of first result row of the given query: Numeric index retrieval of first result row of the given query:
...@@ -106,7 +220,7 @@ Numeric index retrieval of first result row of the given query: ...@@ -106,7 +220,7 @@ Numeric index retrieval of first result row of the given query:
*/ */
fetchColumn() fetchColumn()
----------------------------------------- ~~~~~~~~~~~~~
Retrieve only the given column of the first result row. Retrieve only the given column of the first result row.
...@@ -117,7 +231,7 @@ Retrieve only the given column of the first result row. ...@@ -117,7 +231,7 @@ Retrieve only the given column of the first result row.
echo $username; // jwage echo $username; // jwage
fetchAssoc() fetchAssoc()
------------------------------- ~~~~~~~~~~~~
Retrieve assoc row of the first result row. Retrieve assoc row of the first result row.
...@@ -135,7 +249,7 @@ Retrieve assoc row of the first result row. ...@@ -135,7 +249,7 @@ Retrieve assoc row of the first result row.
There are also convenience methods for data manipulation queries: There are also convenience methods for data manipulation queries:
delete() delete()
------------------------------------- ~~~~~~~~~
Delete all rows of a table matching the given identifier, where Delete all rows of a table matching the given identifier, where
keys are column names. keys are column names.
...@@ -147,7 +261,7 @@ keys are column names. ...@@ -147,7 +261,7 @@ keys are column names.
// DELETE FROM user WHERE id = ? (1) // DELETE FROM user WHERE id = ? (1)
insert() insert()
------------------------------- ~~~~~~~~~
Insert a row into the given table name using the key value pairs of Insert a row into the given table name using the key value pairs of
data. data.
...@@ -159,7 +273,7 @@ data. ...@@ -159,7 +273,7 @@ data.
// INSERT INTO user (username) VALUES (?) (jwage) // INSERT INTO user (username) VALUES (?) (jwage)
update() update()
-------------------------------------------------- ~~~~~~~~~
Update all rows for the matching key value identifiers with the Update all rows for the matching key value identifiers with the
given data. given data.
...@@ -178,7 +292,7 @@ the Doctrine DBAL as standalone, you have to take care of this ...@@ -178,7 +292,7 @@ the Doctrine DBAL as standalone, you have to take care of this
yourself. The following methods help you with it: yourself. The following methods help you with it:
quote() quote()
--------------------------- ~~~~~~~~~
Quote a value: Quote a value:
...@@ -189,7 +303,7 @@ Quote a value: ...@@ -189,7 +303,7 @@ Quote a value:
$quoted = $conn->quote('1234', \PDO::PARAM_INT); $quoted = $conn->quote('1234', \PDO::PARAM_INT);
quoteIdentifier() quoteIdentifier()
---------------------------- ~~~~~~~~~~~~~~~~~
Quote an identifier according to the platform details. Quote an identifier according to the platform details.
...@@ -198,4 +312,3 @@ Quote an identifier according to the platform details. ...@@ -198,4 +312,3 @@ Quote an identifier according to the platform details.
<?php <?php
$quoted = $conn->quoteIdentifier('id'); $quoted = $conn->quoteIdentifier('id');
Portability
===========
There are often cases when you need to write an application or library that is portable
across multiple different database vendors. The Doctrine ORM is one example of such
a library. It is an abstraction layer over all the currently supported vendors (MySQL, Oracle,
PostgreSQL, SQLite and MSSQL). If you want to use the DBAL to write a portable application
or library you have to follow lots of rules to make all the different vendors work the
same.
There are many different layers that you need to take care of, here is a quick list:
1. Returning of data is handled differently across vendors.
Oracle converts empty strings to NULL, which means a portable application
needs to convert all empty strings to null.
2. Additionally some vendors pad CHAR columns to their length, whereas others don't.
This means all strings returned from a database have to be passed through ``rtrim()``.
3. Case-sensitivity of column keys is handled differently in all databases, even depending
on identifier quoting or not. You either need to know all the rules or fix the cases
to lower/upper-case only.
4. ANSI-SQL is not implemented fully by the different vendors. You have to make
sure that the SQL you write is supported by all the vendors you are targeting.
5. Some vendors use sequences for identity generation, some auto-increment approaches.
Both are completely different (pre- and post-insert access) and therefore need
special handling.
6. Every vendor has a list of keywords that are not allowed inside SQL. Some even
allow a subset of their keywords, but not at every position.
7. Database types like dates, long text fields, booleans and many others are handled
very differently between the vendors.
8. There are differences with the regard to support of positional, named or both styles of parameters
in prepared statements between all vendors.
For each point in this list there are different abstraction layers in Doctrine DBAL that you
can use to write a portable application.
Connection Wrapper
------------------
This functionality is only implemented with Doctrine 2.1 upwards.
To handle all the points 1-3 you have to use a special wrapper around the database
connection. The handling and differences to tackle are all taken from the great
`PEAR MDB2 library <http://pear.php.net/package/MDB2/redirected>`_.
Using the following code block in your initialization will:
* ``rtrim()`` all strings if necessary
* Convert all empty strings to null
* Return all associative keys in lower-case, using PDO native functionality or implemented in PHP userland (OCI8).
.. code-block:: php
<?php
$params = array(
// vendor specific configuration
//...
'wrapperClass' => 'Doctrine\DBAL\Portability\Connection',
'portability' => \Doctrine\DBAL\Portability\Connection::PORTABILITY_ALL,
'fetch_case' => \PDO::CASE_LOWER,
);
This sort of portability handling is pretty expensive because all the result
rows and columns have to be looped inside PHP before being returned to you.
This is why by default Doctrine ORM does not use this compability wrapper but
implements another approach to handle assoc-key casing and ignores the other
two issues.
Database Platform
-----------------
Using the database platform you can generate bits of SQL for you, specifically
in the area of sql functions to achieve portability. You should have a look
at all the different methods that the platforms allow you to access.
Keyword Lists
-------------
This functionality is only implemented with Doctrine 2.1 upwards.
Doctrine ships with lists of keywords for every supported vendor. You
can access a keyword list through the schema manager of the vendor you
are currently using or just instantiating it from the ``Doctrine\DBAL\Platforms\Keywords``
namespace.
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment