Skip to end of metadata
Go to start of metadata

<h3>General Goals</h3>

<ul>
<li>SHOULD do less in each sub-component</li>
<li>SHOULD have a more clear and concise API in each sub-componet</li>
<li>SHOULD create a set of sub-components that have a small core feature-set, with many opt-in injectable features</li>
<li>SHOULD have more reuse of base ZF components (Zend\Registry, Zend\Cache, Zend\SignalSlot to name a few)</li>
<li>SHOULD create an API that is open to extension at all times</li>
<li>SHOULD favor composition over inheritance for better flexibility and dependency injection</li>
<li>SHOULD attempt to keep each components API as minimal as possible to allow for growth over the 2.0 time period</li>
<li>SHOULD NOT be dependent on any 3rd party code</li>
<li>SHOULD create a set of interfaces and best practices to facilitate plug-ability in all Zend\DB core components</li>
<li>SHOULD NOT include features in core components that can be solved by the Plug-in architecture</li>
<li>SHOULD NOT attempt to solve problems in the Model domain:
<ul>
<li>like mapping of column names in various components</li>
</ul>
</li>
</ul>

<h4>Motivation & Discussion</h4>

<ul>
<li>Plugability is a major step forward for a database abstraction layer. Over the past few years, as the current iteration has become more popular, more features are requested. Each new feature request generally comes with it's own concerns that apply to the project as a whole: "How useful is the feature?", "How does this feature impact performance?", "How wide spread is the need for this feature?". Generally, features are added to the core component bloating the core component and adding a new set of code that has to be maintained. Plugins allow us to keep the core feature set and code base very small, but also allow us to provide new <strong>opt-in</strong> features. This shifts the onus onto the developer to decide with features they want to use, but also understand the performance impact they impart. Ultimately, this is a good thing since we can offer the feature, but the consumer can have full control over the inclusion of that feature into their project.</li>
</ul>

<h3>MUST have a Zend\Db\Adapter component</h3>

<ul>
<li>MUST create a distinct abstraction for interfacing with various PHP db drivers
<ul>
<li>MUST support drivers:
<ul>
<li>PDO/mysql</li>
<li>PDO/oracle</li>
<li>PDO/db2</li>
<li>PDO/postgres</li>
<li>PDO/sqlite</li>
<li>PDO/sqlserver</li>
<li>ext/mysqli</li>
<li>ext/ibmdb2</li>
<li>ext/oci8</li>
<li>ext/sqlsrv from MS</li>
<li>ext/postgres</li>
</ul>
</li>
<li>SHOULD support drivers
<ul>
<li>PDO/firebird</li>
<li>ext/mysql</li>
<li>ext/sqlite3</li>
</ul>
</li>
</ul>
</li>
<li>MUST contain a registry component to allow for storage of named connections</li>
<li>MUST support plug-ablity via callbacks (Zend\SignalSlot) at various times during runtime:
<ul>
<li>pre-prepare & pre-execute</li>
<li>post-prepare & post-execute</li>
<li>pre-connect & post-connect</li>
<li>pre-connect-close & post-connect-close</li>
</ul>
</li>
<li>MUST NOT contain any SQL</li>
<li>MUST be able to report to consumers the capabilities concerning statement execution and ability to prepare statements</li>
<li>MUST be able to run non-prepared queries</li>
<li>MUST provide an API for quoting various elements</li>
<li>MUST provide an API for retrieving server version information</li>
<li>SHOULD be able to successfully wrap ANY driver (past, present or future) regardless of its ability to parse and execute parameritized queries</li>
</ul>

<h4>Motivation & Discussion</h4>

<ul>
<li>The main motivation between the role of this component and the one below, the Query component, is to separate the the API's responsible for "how" consumers speak to the database (Adapter) and "what" they speak to the database (Query). This solves the current real world problem of modeling an adapter after two drivers that connect to the same vendor platform. For example ext/mysqli and PDOMySQL. Both have separate database interaction APIS, yet they both share the MySQL dialect. This separation will provide us with the appropriate amount of abstract to grow our adapters when new drivers are created in PHP that use the same SQL dialects.</li>
<li>There are some queries that current drivers simply cannot prepare; they must just be executed. That said, each adapter must be able to run prepared and straight queries and return a resultset for each. Being able to run non-prepared statements is a feature request that comes with many votes that has yet to be implemented in the current Zend_Db_Adapter implementation.</li>
<li>See main goals for notes on plugability.</li>
<li>The registry is an important sub-component of Zend\Db\Adapter since it allows you to store a collection of adapters & connections. IN some situations, it makes a lot of sense to have multiple connections open so that you can create a strategy around how to use them. For example, one might be able to create two connections to different database, and name them "read" and "writer". This then allows them to create a strategy in their appliction as to which one to use when talking to the data store.</li>
</ul>

<h3>MUST have a Zend\Db\Query component</h3>

<ul>
<li>MUST create a distinct abstraction layer for creating DML (Data Manipulation Language) for:
<ul>
<li>MUST support SELECT</li>
<li>MUST support INSERT</li>
<li>MUST support UPDATE</li>
<li>MUST support DELETE</li>
</ul>
</li>
<li>MUST create a distinct abstraction layer for creating DDL (Data Definition Language) for:
<ul>
<li>MUST support CREATE</li>
<li>MUST support ALTER</li>
<li>MUST support DROP</li>
<li>MUST support TRUNCATE</li>
<li>SHOULD support RENAME</li>
<li>SHOULD support COMMENT</li>
</ul>
</li>
<li>MUST support a distinct abstraction layer for creating TCL (Transaction Control Language) for:
<ul>
<li>MUST support COMMIT</li>
<li>MUST support ROLLBACK</li>
<li>SHOULD support SAVEPOINT</li>
<li>SHOULD support SET TRANSACTION</li>
</ul>
</li>
<li>MUST support Vendor specific variances when generating SQL</li>
<li>SHOULD support Vendor specific features for query generation via Vendor object API</li>
<li>MUST support the following Vendor specific Dialects:
<ul>
<li>MUST support vendor neutral SQL92 dialect</li>
<li>MUST support MySQL dialect</li>
<li>MUST support Postgres dialect</li>
<li>MUST support SQLite dialect</li>
<li>MUST support Oracle dialect</li>
<li>MUST support SQLServer dialect</li>
<li>MUST support DB2 on i5 dialect</li>
<li>MUST supoort DB2 (general) dialect</li>
<li>SHOULD support Firebird and Informix variant dialect</li>
</ul>
</li>
<li>MUST support the ability to serialize queries</li>
<li>MUST be able to produce full bound statements (bound in userland) as well as parameritized statements</li>
<li>MUST be able to interrogate the Adapter for parameritization capabilities and provide statements capable of being executed</li>
</ul>

<h4>Motivation & Discussion</h4>

<ul>
<li>See above in Zend\Db\Adapter for the motivation in Adapter/Query separation.</li>
<li>It is important that we are able to create, for as much as possible, a DDL query generation component. This will set the framework such that we can utilize this component during unit testing time (see more notes in the ZendTest\Db section). This also fulfills the long-standing request for a schema management component.</li>
</ul>

<h3>MUST have a Zend\Db\ResultSet component</h3>

<ul>
<li>MUST model a database result set in a vendor and driver neutral way</li>
<li>SHOULD be capable of modeling rows as arrays or row objects</li>
<li>MUST support positional keys as well as column name keys in rows</li>
<li>MUST be pluggable at various points in runtime:
<ul>
<li>pre-populate and post-populate</li>
</ul>
</li>
<li>MUST be serializable and cacheable</li>
<li>SHOULD utilize all facilities of PHP to be able to always get row objects as array</li>
<li>SHOULD be capable of creating Row objects from arrays of data</li>
</ul>

<h4>Motivation & Discussion</h4>

<ul>
<li>This resultSet component should be capable of modeling the results as they come back from various databases. This allow consumers a flexible and standard way of storing their result sets. By having this component, consumers will be able to apply array operations to their collection. They will also be able to choose the type of the rows inside of the collect: they can be plain old PHP arrays, or they can be a particular object</li>
</ul>

<h3>MUST have a Zend\Db\Metadata component</h3>

<ul>
<li>MUST create a component that is capable of interrogating a database for schema information</li>
<li>MUST be able to describe schema to consumers in a vendor neutral way</li>
<li>MUST contain the various Vendor specific queries (Created by Zend\Db\Query) for interrogating database</li>
<li>MUST be cacheable via Zend\Cache</li>
<li>MUST be serializable</li>
<li>MUST be able to describe the capabilities</li>
</ul>

<h4>Motivation & Discussion</h4>

<ul>
<li>The main motivation here is to be able to capture information about the structure of the database and present it in a standardized API for other components and consumers to be able to interrogate. Currently, we do this to some extent in the describeTable() calls of Zend_Db_Adapter. By having this standard API, developers will be able to harness this to be able to create more interesting ORM or simply better Modeling solutions.</li>
</ul>

<h3>MUST have a Zend\Db\TableGateway component</h3>

<ul>
<li>MUST implement a component that subscribes to the Table Gateway pattern described in PoEAA
<ul>
<li>API: insert(), update(), delete(), select()</li>
</ul>
</li>
<li>MUST accept Zend\Db\Query objects</li>
<li>MUST be vendor and driver neutral</li>
<li>SHOULD be pluggable at various points in the runtime:
<ul>
<li>pre-insert & post-insert</li>
<li>pre-update & post-update</li>
<li>pre-delete & post-delete</li>
<li>pre-select & post-select</li>
</ul>
</li>
<li>MUST return Zend\Db\ResultSet object</li>
</ul>

<ul>
<li>Currently, we have table like operations located in two places, both the Zend_Db_Adapter as well as the Zend_Db_Table. The benefit of having them in the adapter is that you do not have to subscribe to Zend_Db_Table, which has its performance implications as well as its hard coupling with Zend_Db_Table_Row & Rowset. By creating a more lightweight Table Gateway component, the "table API" can be contained in one place and it will be a more attractive API to adopt for those "table only" operations. Furthermore, the "table API" is out of place currently in Zend_Db_Adapter.</li>
</ul>

<h3>MUST have a Zend\Db\RowGateway component</h3>

<ul>
<li>MUST implement a component that subscribes to the Row Gateway pattern described in PoEAA</li>
<li>MUST be able to accept Zend\Db\Metadata to understand the database schema in order to process row operations</li>
<li>SHOULD be injectable into any Zend\Db\ResultSet such that any row can become a Row Gateway</li>
<li>SHOULD be pluggable at various point in the runtime:
<ul>
<li>pre-save & post-save</li>
<li>pre-delete & post-delete</li>
<li>pre-populate & post-populate</li>
</ul>
</li>
</ul>

<h4>Motivation & Discussion</h4>

<ul>
<li>Currently, the Row Gateway is hard coupled to the Zend_Db_Table component. If Row were decoupled from TableGateway, that would mean it can be used in any situation where a developer has retrieved a row from the database.. even in situations where they have issued SQL directly as a statement or to the adapter (prepared or not). This would allow the Zend\Db\ResultSet component to cast any row to a RowGateway object that supports the RowGateway pattern thus giving the developer the opportunity to have row objects delete() and save() themselves to/in persistent storage.</li>
<li>Moreover, the RowGateway should also have the ability to be pluggable. This allows developers the maximum amount of flexibility in cases where they would like to use these row objects as the model or the subject of a model, OR the target for a mapper.</li>
</ul>

<h3>MUST have a ZendTest\Db component</h3>

<ul>
<li>MUST create a base schema that the majority of unit tests can share</li>
<li>MUST have cleanup mechanism so that schema's can return to their original state without having to "rebuild" entities</li>
<li>MUST have the capability to dynamically create schema when base schema does not model a particular problem</li>
<li>SHOULD create better facilities for mock adapters to be used by components that are driver and dialect agnostic</li>
</ul>

<h4>Motivation & Discussion</h4>

<ul>
<li>Currently, on some systems, it takes upwards of 4 hours to run the full test suite. Other systems might be quicker, but slowness is a noticeable quality across all major databases. This is primarily due to the fact that DDL operations, specifically creating and dropping schemas is extremely expensive. That said, it is important that we have a base schema that most unit tests can share, and a schema we can get back to a "clean" state. Furthermore, for tests that the base schema does not satisfy, there will be operations they can use to create a temporary schema that will be destroyed during cleanup of that particular test.</li>
<li>By creating a query and adatper abstraction layer that is well tested, and by creating components that are both driver/adapter neutral and vendor query neutral, we can avoid having to create mutliple tests for each flavor of database we intend to test on. For example, currently Zend_Db_Table has an implementation of all its tests for each database platform that Zend_Db supports. This in and of itself creates a maintenance nightmare as for each new unit test that is created, it actually creates n tests where n is the number of currently supported platforms. In most cases, developers do not have the means to be able to test some platforms, thus they can never be sure if their solution is completely safe across all platoforms. Since we are creating Zend\Db\TableGateway and Zend\Db\RowGateway with standard API's that are vendor neutral, this will get rid of the need for developers to have to test against various plaforms in their consuming components (like Zend\Paginator).</li>
</ul>

<h3>MUST have a Zend\Db\Plugin\Profiler component plugin</h3>

<ul>
<li>MUST create a plugin that is capable of providing introspection into the "running" of a query</li>
<li>MUST be able to determine how long a query took to run</li>
<li>MUST be able to demonstrate the full state of the query: query and bound parameters if necessary</li>
</ul>

<h4>Motivation & Discussion</h4>

<ul>
<li>We currently have a profiler which is very good. There are a few BC issues with the current one with regards to returning bound parameters that we will attempt to fix in this plugin.</li>
<li>Since this is a plugin, it will be opt in in a way that does not bloat code.</li>
</ul>

<h3>SHOULD have a Zend\Db\Plugin\TypeCaster component plugin</h3>

<ul>
<li>MUST create a plugin that is capable of casting the values of a row result from the database type (represented as a string in PHP) to a PHP type</li>
<li>MUST be able to have an internal pre-defined casting rules</li>
<li>SHOULD be vendor datatype aware for most common types</li>
<li>MUST be able to accept a user definition for casting</li>
</ul>

<h4>Motivation & Discussion</h4>

<ul>
<li>This is a highly requested feature in Zend_Db but due to BC, it is very hard to implement. Moreover, this kind of solution can be considered very heavy in terms of CPU and performance costs, so making it a plugin allows developers to opt-in to this feature why the need these capabilities in their application.</li>
</ul>

<h3>COULD have a Zend\Db\ActiveRecord component</h3>

<ul>
<li>COULD create a component that subscribes and implements the ActiveRecord pattern as described in PoEAA</li>
</ul>

<h4>Motivation & Discussion</h4>

<ul>
<li>Not a requirement, but now that we have late static binding, and separate API's for table operations and row operations, it would be trivial to wrap those components into a class that could satisfy the ActiveRecord pattern. This would allow developers a one-stop-shop when it comes to the quick solution to their table and row needs in one class.</li>
</ul>

Labels:
None
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Jul 29, 2010

    <p>This new draft is a great improvement over the earlier draft. Kudos for being specific and focusing more on use cases instead of implementation goals. On the other hand, it's curious the the words "database" or "SQL" do not appear in the General Goals section! Perhaps there needs to be a sort of Mission Statement that comes before the General Goals? For example:</p>

    <blockquote>
    <p>Zend\Db provides a generic and extensible interface for SQL databases.  Developers can  submit DML and DDL to a variety of RDBMS services, and process the results in PHP.  Both query interface and result interface is abstracted so developers can use any RDBMS similarly.  Zend\Db core functionality handles the 80% most common tasks, and provides developers the means to plug in their own custom code for the remaining 20%.</p></blockquote>
    <p>Suggestions:</p>
    <ul>
    <li>Flesh out the requirements for the Zend\Db\Registry.  This is potentially powerful, because you could make it a Composite pattern to allow developers to map SQL queries to multiple back-end servers.  E.g. write to master, read from slave(s); load balancing; failover.  Provide defaults for common use cases, and plugin hooks so developers can customize how queries are balanced among the servers.</li>
    <li>Define the priority of the Db adapters.  Each one is going to be quite a few weeks of work, so I suggest being clear up front that you're going to implement them in some order.  Or else define one (make it MySQL) as the baseline or reference implementation.</li>
    <li>Extend the profiler plugin concept to support logging as well.  Leverage Zend\Log and Zend\Log\Filter for this.</li>
    <li>Zend\Db\Query should be able to report the type of statement, e.g. SELECT, INSERT, UPDATE, DELETE, CREATE, etc.  The author of a Composite plugin can use this, and also the Zend\Log\Filter can use this.  Also you could provide plugin hooks for each respective type of statement.</li>
    <li>Zend\Db\Query must be able to accept literal SQL fragments or whole statements in the vendor's SQL dialect.  Don't try to develop an abstract interface for all of the SQL supported by even one vendor, let alone all of them.</li>
    <li>Zend\Db\Metadata: Note that an RDBMS has server metadata (versions, SQL modes, config options), each table has table metadata (per column as well as table options), queries have parameter metadata (data types, nullable, etc.), and result sets have metadata (per column, row count, etc.).  Which of these do you plan to support?</li>
    <li>ZendTest\Db: You should provide a mock adapter to support testing w/o a live database.  This would make tests run much faster, simplify setup, and it would support the spirit of unit testing better.</li>
    <li>Any intention to support calling stored procedures?</li>
    <li>I'll repeat my urging to remove support for cascading updates and deletes in PHP-space.  I've described the reasons elsewhere.</li>
    <li>Don't support ext/mysql or other extensions that don't support transactions or prepared statements.</li>
    <li>Don't try to show "the real query" by combining SQL with its parameters.  The whole point of parameters is that they're kept separate from the SQL string.  The SQL string is sent to the RDBMS at prepare time, the parameters are sent at execute time.  Showing the combined query perpetuates the myth that "parameters do the quoting for you" which is not true.  Also it's harder than it seems to combine the parameters into a SQL string; you basically have to write a full SQL parser to do it correctly.</li>
    <li>Zend\Db\Plugin\TypeCaster:  I wouldn't bother.  Zend\Db\RowGateway can be a collection of user-defined objects instead of a collection of scalars, but the objects should correspond to logical columns, not data types.</li>
    </ul>

  2. Dec 02, 2010

    <p>I forked off at DB1 in my personal library with some similarities to this proposal. Just sharing my own agenda if that's okay?</p>

    <p>I wanted metadata functionality centralised and subordinate by default to the DB's metadata which is authorative really. The aim being to achieve instant CRUD for any DB connected to, and even navigate it based on constraints detected. Faster scaffolding in effect. I wanted to avoid the complexity/issues of XML and code generation to acheive it.</p>

    <p>My library pretty much does does this. Unfortunately, its PHP 5.2 based, no documentation, no test units, and only implemented for PDO MySQL as it's what I use. My hack really, but I'd like to see similar features in DB2 if possible. </p>

    <p>Here's a few suggestions/examples:</p>

    <p>1) In metadata please also grab table constraints. I had to make my library call SHOW CREATE TABLE to obtain this (using MySQL).</p>

    <p>2) Glad to see SQL strings eliminated in the proposal. I did this for DML. My usage looks like:</p>

    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    $update = $this->mmTest->getUpdate();
    $e = $this->mmTest->getExprFactory();
    $update->table('Images')
    ->set(array('name' => 'hello'))
    >where($e>eq('imageId', 4))
    ->order('imageSetId')
    ->limit(3);
    echo $update->render()."\n";
    $update->execute();
    $update->reset();

    $update->table('Images')
    ->set(array('name' => '?'))
    >where($e>eq('imageId', '?'))
    ->order('imageSetId')
    ->limit(3)
    ->bind(array('goodbye', 4));
    echo $update->render()."\n";
    $update->execute();

    $select = $this->mmTest->getSelect();
    $e = $this->mmTest->getExprFactory();
    $procedure = $e->procedure(
    '#CASE WHEN',
    $e->eq('pwd', ':pwd'),
    '#THEN 1 ELSE 0 END'
    );
    $select->from('Members', array(Mm_Rdb::SQL_WILDCARD, 'credentialMatch' => $procedure))
    >where($e>eq('username', ':username'))
    ->bind(array('username' => 'kat', 'pwd' => 'mysecretpwd'));
    echo $select->render()."\n";
    $rowset = $select->execute();
    ]]></ac:plain-text-body></ac:macro>

    <p>3) Could SQL rendering/assembly options be maximised? I've implimented: autoQuoteIdentifiers, autoCreateTableAliases, autoRenderTableNames, autoRenderSchemaNames. For example, here's just a couple of ways my DB can render the same query for me:</p>

    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    SELECT M.memberId
    FROM Members AS M
    LEFT JOIN Profiles AS P ON M.memberId=P.memberId
    WHERE M.username='mim'
    ORDER BY M.memberId DESC
    LIMIT 5

    SELECT `MmTest`.`Members`.`memberId` FROM `MmTest`.`Members`
    LEFT JOIN `MmTest`.`Profiles` ON `MmTest`.`Members`.`memberId`=`MmTest`.`Profiles`.`memberId`
    WHERE `MmTest`.`Members`.`username`='mim'
    ORDER BY `MmTest`.`Members`.`memberId` DESC
    LIMIT 5
    ]]></ac:plain-text-body></ac:macro>

    <p>4) Should data objects and models really be in the DB component?</p>

    <p>I suspect this one being a fundamental BC break will not be acceptable for starters, but just in case, I thought I might flesh it out a little.</p>

    <p>I reduced my forked DB to being a purely DB adapter as its role is not trivial and I think there is some justification for a low-level Model component - a sytematic way to bind metadata to the data we are working with. This then is modelled data where the storage be it to DB or session cache etc doesn't matter.</p>

    <p>I'm still developing this but it basically works.</p>

    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    interface Mm_Model_Interface
    {
    public function getFieldNames(); // In the DB abstract obtained from DB metadata
    public function getPrimaryFieldNames(); // Where meaningful eg. Rdb or some other indexed source
    public function getFieldAttribs($fieldName); // User definable eg. bitmaps for visibility/read/write perms
    public function getFieldLabels(); // In the DB abstract, deduced from DB metadata unless overridden
    public function getFieldLabel($fieldName); // Deduced from DB metadata unless overridden
    public function getFieldSummary($fieldName); // The abstract calls getFieldLabel() until you override it
    public function getFieldDescription($fieldName); // ditto to getFieldSummary()
    public function getFieldOptions($fieldName); // For MySQL style set data type
    public function getFieldOptionLabels($fieldName); // Abstracts call getFieldOptions() unless overidden
    public function getFieldDefault($fieldName); // Obtained from DB metadata unless overridden
    public function getFieldMaxLength($fieldName); // Obtained from DB metadata unless overridden
    public function getFieldMinVal($fieldName); // Obtained from DB metadata unless overridden
    public function getFieldMaxVal($fieldName); // Obtained from DB metadata unless overridden
    public function getFieldType($fieldName); // Obtained from DB metadata unless overridden
    public function getValidators($fieldName); // Obtained from DB metadata unless overridden
    public function getFilters($fieldName); // Obtained from DB metadata unless overridden
    -
    -
    -
    public function save(Mm_Model_Data_Row $dataRow); // Automatically works out insert/update
    public function load($primaryFields); // Load data (un-mixed model config)
    public function delete($primaryFields); // Delete data (un-mixed model config)
    public function renderField($fieldName, $value); Proxies to the model for basic html safe data rendering
    }
    ]]></ac:plain-text-body></ac:macro>
    <p>This is the model component dir tree I'm using which my DB makes use of, but does not rely on:</p>
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    Model
    ??? Address.php
    ??? Data
    ?   ??? Row.php
    ?   ??? Rowset.php
    ?   ??? Set.php
    ??? Exception.php
    ??? Form
    ?   ??? List.php
    ?   ??? SubForm.php
    ??? Interface.php
    ??? Pseudo
    ?   ??? Abstract.php
    ??? Rdb
    ?   ??? Abstract.php
    ??? Session
    ??? Abstract.php
    ]]></ac:plain-text-body></ac:macro>
    <p>Field names are internally indexed using schema, table name, and column name for uniqueness (eg. in joined table results) but you can access by simple field name/alias too. The rowset maintains a model for each data field (or one model for all). The model data row can be worked with as an array, and by being bound to models, a partial view script or a form builder can discover anything it needs to about the data it is processing. The Psuedo abstract model is where I record the SQL aliases I use, such as "total" for count() in a query. In this way I can have a model data row containing result data from joined tables and aliases and every column has a consistent model object to call getLabel() on etc. If I create a row directly I must pass the model(s) to it as it will wrap itself in a rowset automatically, although I would normally create the rowset and then add rows to it.</p>

    <p>I did also wonder if low-level data models ought to be static classes. Good for performance perhaps and stops them creeping into the role of general business logic models. I could not make static subclassing work usefully in PHP 5.2 but with late static binding in PHP 5.3 perhaps it might be worth revisiting.</p>

    <p>Once again, yes this last item is an aside, but offered here as an argument for stripping DB2 down to being a DB adapter only.</p>

  3. Dec 02, 2010

    <p>Since MariaDB is supported as of version 1.11, we may want to mention that as well in the reqs.</p>

    <p>Also, imho support for nested transactions should be included (see also <a class="external-link" href="http://framework.zend.com/issues/browse/ZF-10736">http://framework.zend.com/issues/browse/ZF-10736</a> ).</p>