summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Holywell <[email protected]>2013-01-29 13:33:47 +0000
committerSimon Holywell <[email protected]>2013-01-29 13:33:47 +0000
commit53042c235fed60c707a67defaf65c7a470d0d8c0 (patch)
tree843549006247561c18b1e053627615cb08a3dd6f
parent23da4a02c6765c07a9a83a6146621b6d76c6dde4 (diff)
parent81600c0aea59ec64b77c3e54c660e92c0ef3d749 (diff)
Merge branch 'develop'
Conflicts: README.markdown
-rw-r--r--.gitignore2
-rw-r--r--README.markdown631
-rw-r--r--composer.json4
-rw-r--r--docs/Makefile153
-rw-r--r--docs/conf.py242
-rw-r--r--docs/configuration.rst246
-rw-r--r--docs/connections.rst77
-rw-r--r--docs/index.rst29
-rw-r--r--docs/installation.rst19
-rw-r--r--docs/make.bat190
-rw-r--r--docs/models.rst152
-rw-r--r--docs/philosophy.rst34
-rw-r--r--docs/querying.rst593
-rw-r--r--docs/transactions.rst20
-rw-r--r--idiorm.php738
-rw-r--r--phpunit.xml11
-rw-r--r--test/CacheTest.php45
-rw-r--r--test/ConfigTest.php100
-rw-r--r--test/IdiormResultSetTest.php81
-rw-r--r--test/MulitpleConnectionTest.php54
-rw-r--r--test/ORMTest.php87
-rw-r--r--test/QueryBuilderTest.php542
-rw-r--r--test/bootstrap.php61
-rw-r--r--test/test_classes.php121
-rw-r--r--test/test_queries.php354
25 files changed, 3437 insertions, 1149 deletions
diff --git a/.gitignore b/.gitignore
index 312eef1..29263bc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
*.swp
*.sqlite
+docs/_build
+/phpunit.phar
diff --git a/README.markdown b/README.markdown
index 8d2f1b2..5695bae 100644
--- a/README.markdown
+++ b/README.markdown
@@ -21,13 +21,70 @@ Features
* Requires no model classes, no XML configuration and no code generation: works out of the box, given only a connection string.
* Consists of just one class called `ORM`. Minimal global namespace pollution.
* Database agnostic. Currently supports SQLite and MySQL. May support others, please give it a try!
+* Supports collections of models with method chaining to filter or apply actions to multiple results at once.
+* Multiple connections supported
+
+Documentation
+-------------
+
+The documentation is hosted on Read the Docs: [idiorm.rtfd.org](http://idiorm.rtfd.org)
+
+### Building the Docs ###
+
+You will need to install [Sphinx](http://sphinx-doc.org/) and then in the docs folder run:
+
+ make html
+
+The documentation will now be in docs/_build/html/index.html
+
+Let's See Some Code
+-------------------
+
+ $user = ORM::for_table('user')
+ ->where_equal('username', 'j4mie')
+ ->find_one();
+
+ $user->first_name = 'Jamie';
+ $user->save();
+
+ $tweets = ORM::for_table('tweet')
+ ->select('tweet.*')
+ ->join('user', array(
+ 'user.id', '=', 'tweet.user_id'
+ ))
+ ->where_equal('user.username', 'j4mie')
+ ->find_many();
+
+ foreach ($tweets as $tweet) {
+ echo $tweet->text;
+ }
Changelog
---------
+#### 1.3.0 - release XXXX-XX-XX
+
+* Documentation moved to [idiorm.rtfd.org](http://idiorm.rtfd.org) and now built using [Sphinx](http://sphinx-doc.org/)
+* Add support for multiple database connections - closes [issue #15](https://github.com/j4mie/idiorm/issues/15) [[tag](https://github.com/tag)]
+* Add in raw_execute - closes [issue #40](https://github.com/j4mie/idiorm/issues/40) [[tag](https://github.com/tag)]
+* Add `get_last_statement()` - closes [issue #84](https://github.com/j4mie/idiorm/issues/84) [[tag](https://github.com/tag)]
+* Add HAVING clause functionality - closes [issue #50](https://github.com/j4mie/idiorm/issues/50)
+* Add `is_new` method - closes [issue #85](https://github.com/j4mie/idiorm/issues/85)
+* Add `ArrayAccess` support to the model instances allowing property access via `$model['field']` as well as `$model->field` - [issue #51](https://github.com/j4mie/idiorm/issues/51)
+* Add a result set object for collections of models that can support method chains to filter or apply actions to multiple results at once - issue [#51](https://github.com/j4mie/idiorm/issues/51) and [#22](https://github.com/j4mie/idiorm/issues/22)
+* Add support for [Firebird](http://www.firebirdsql.org) with `ROWS` and `TO` result set limiting and identifier quoting [[mapner](https://github.com/mapner)] - [issue #98](https://github.com/j4mie/idiorm/issues/98)
+* Fix last insert ID for PostgreSQL using RETURNING - closes issues [#62](https://github.com/j4mie/idiorm/issues/62) and [#89](https://github.com/j4mie/idiorm/issues/89) [[laacz](https://github.com/laacz)]
+* Reset Idiorm after performing a query to allow for calling `count()` and then `find_many()` [[fayland](https://github.com/fayland)] - [issue #97](https://github.com/j4mie/idiorm/issues/97)
+* Change Composer to use a classmap so that autoloading is better supported [[javierd](https://github.com/javiervd)] - [issue #96](https://github.com/j4mie/idiorm/issues/96)
+* Add query logging to `delete_many` [[tag](https://github.com/tag)]
+* Fix when using `set_expr` alone it doesn't trigger query creation - closes [issue #90](https://github.com/j4mie/idiorm/issues/90)
+* Escape quote symbols in "_quote_identifier_part" - close [issue #74](https://github.com/j4mie/idiorm/issues/74)
+* Fix issue with aggregate functions always returning `int` when is `float` sometimes required - closes [issue #92](https://github.com/j4mie/idiorm/issues/92)
+* Move testing into PHPUnit to unify method testing and query generation testing
+
#### 1.2.3 - release 2012-11-28
-* Fix issue #78 - remove use of PHP 5.3 static call
+* Fix [issue #78](https://github.com/j4mie/idiorm/issues/78) - remove use of PHP 5.3 static call
#### 1.2.2 - release 2012-11-15
@@ -44,16 +101,16 @@ Changelog
* Add support for raw queries without parameters argument [[sandermarechal](http://github.com/sandermarechal)]
* Add support to set multiple properties at once by passing an associative array to `set` method [[sandermarechal](http://github.com/sandermarechal)]
* Allow an associative array to be passed to `configure` method [[jordanlev](http://github.com/jordanlev)]
-* Patch to allow empty Paris models to be saved ([[j4mie/paris](http://github.com/j4mie/paris)]) issue #58
-* Add `select_many` and `select_many_expr` - closing issues #49 and #69
-* Add support for `MIN`, `AVG`, `MAX` and `SUM` - closes issue #16
-* Add `group_by_expr` - closes issue #24
-* Add `set_expr` to allow database expressions to be set as ORM properties - closes issues #59 and #43 [[brianherbert](https://github.com/brianherbert)]
-* Prevent ambiguous column names when joining tables - issue #66 [[hellogerard](https://github.com/hellogerard)]
+* Patch to allow empty Paris models to be saved ([[j4mie/paris](http://github.com/j4mie/paris)]) - [issue #58](https://github.com/j4mie/idiorm/issues/58)
+* Add `select_many` and `select_many_expr` - closing issues [#49](https://github.com/j4mie/idiorm/issues/49) and [#69](https://github.com/j4mie/idiorm/issues/69)
+* Add support for `MIN`, `AVG`, `MAX` and `SUM` - closes [issue #16](https://github.com/j4mie/idiorm/issues/16)
+* Add `group_by_expr` - closes [issue #24](https://github.com/j4mie/idiorm/issues/24)
+* Add `set_expr` to allow database expressions to be set as ORM properties - closes issues [#59](https://github.com/j4mie/idiorm/issues/59) and [#43](https://github.com/j4mie/idiorm/issues/43) [[brianherbert](https://github.com/brianherbert)]
+* Prevent ambiguous column names when joining tables - [issue #66](https://github.com/j4mie/idiorm/issues/66) [[hellogerard](https://github.com/hellogerard)]
* Add `delete_many` method [[CBeerta](https://github.com/CBeerta)]
* Allow unsetting of ORM parameters [[CBeerta](https://github.com/CBeerta)]
-* Add `find_array` to get the records as associative arrays [[Surt](https://github.com/Surt)] - closes issue #17
-* Fix bug in `_log_query` with `?` and `%` supplied in raw where statements etc. - closes issue #57 [[ridgerunner](https://github.com/ridgerunner)]
+* Add `find_array` to get the records as associative arrays [[Surt](https://github.com/Surt)] - closes [issue #17](https://github.com/j4mie/idiorm/issues/17)
+* Fix bug in `_log_query` with `?` and `%` supplied in raw where statements etc. - closes [issue #57](https://github.com/j4mie/idiorm/issues/57) [[ridgerunner](https://github.com/ridgerunner)]
#### 1.1.1 - release 2011-01-30
@@ -69,558 +126,4 @@ Changelog
#### 1.0.0 - released 2010-12-01
-* Initial release
-
-
-Philosophy
-----------
-
-The [Pareto Principle](http://en.wikipedia.org/wiki/Pareto_principle) states that *roughly 80% of the effects come from 20% of the causes.* In software development terms, this could be translated into something along the lines of *80% of the results come from 20% of the complexity*. In other words, you can get pretty far by being pretty stupid.
-
-**Idiorm is deliberately simple**. Where other ORMs consist of dozens of classes with complex inheritance hierarchies, Idiorm has only one class, `ORM`, which functions as both a fluent `SELECT` query API and a simple CRUD model class. If my hunch is correct, this should be quite enough for many real-world applications. Let's face it: most of us aren't building Facebook. We're working on small-to-medium-sized projects, where the emphasis is on simplicity and rapid development rather than infinite flexibility and features.
-
-You might think of **Idiorm** as a *micro-ORM*. It could, perhaps, be "the tie to go along with [Slim](http://github.com/codeguy/slim/)'s tux" (to borrow a turn of phrase from [DocumentCloud](http://github.com/documentcloud/underscore)). Or it could be an effective bit of spring cleaning for one of those horrendous SQL-littered legacy PHP apps you have to support.
-
-**Idiorm** might also provide a good base upon which to build higher-level, more complex database abstractions. For example, [Paris](http://github.com/j4mie/paris) is an implementation of the [Active Record pattern](http://martinfowler.com/eaaCatalog/activeRecord.html) built on top of Idiorm.
-
-Installation
-------------
-
-### Packagist ###
-
-This library is available through Packagist with the vendor and package identifier of `j4mie/idiorm`
-
-Please see the [Packagist documentation](http://packagist.org/) for further information.
-
-### Download ###
-
-You can clone the git repository, download idiorm.php or a release tag and then drop the idiorm.php file in the vendors/3rd party/libs directory of your project.
-
-Let's See Some Code
--------------------
-
-The first thing you need to know about Idiorm is that *you don't need to define any model classes to use it*. With almost every other ORM, the first thing to do is set up your models and map them to database tables (through configuration variables, XML files or similar). With Idiorm, you can start using the ORM straight away.
-
-### Setup ###
-
-First, `require` the Idiorm source file:
-
- require_once 'idiorm.php';
-
-Then, pass a *Data Source Name* connection string to the `configure` method of the ORM class. This is used by PDO to connect to your database. For more information, see the [PDO documentation](http://php.net/manual/en/pdo.construct.php).
-
- ORM::configure('sqlite:./example.db');
-
-You may also need to pass a username and password to your database driver, using the `username` and `password` configuration options. For example, if you are using MySQL:
-
- ORM::configure('mysql:host=localhost;dbname=my_database');
- ORM::configure('username', 'database_user');
- ORM::configure('password', 'top_secret');
-
-Also see "Configuration" section below.
-
-### Querying ###
-
-Idiorm provides a [*fluent interface*](http://en.wikipedia.org/wiki/Fluent_interface) to enable simple queries to be built without writing a single character of SQL. If you've used [jQuery](http://jquery.com) at all, you'll be familiar with the concept of a fluent interface. It just means that you can *chain* method calls together, one after another. This can make your code more readable, as the method calls strung together in order can start to look a bit like a sentence.
-
-All Idiorm queries start with a call to the `for_table` static method on the ORM class. This tells the ORM which table to use when making the query.
-
-*Note that this method **does not** escape its query parameter and so the table name should **not** be passed directly from user input.*
-
-Method calls which add filters and constraints to your query are then strung together. Finally, the chain is finished by calling either `find_one()` or `find_many()`, which executes the query and returns the result.
-
-Let's start with a simple example. Say we have a table called `person` which contains the columns `id` (the primary key of the record - Idiorm assumes the primary key column is called `id` but this is configurable, see below), `name`, `age` and `gender`.
-
-#### Single records ####
-
-Any method chain that ends in `find_one()` will return either a *single* instance of the ORM class representing the database row you requested, or `false` if no matching record was found.
-
-To find a single record where the `name` column has the value "Fred Bloggs":
-
- $person = ORM::for_table('person')->where('name', 'Fred Bloggs')->find_one();
-
-This roughly translates into the following SQL: `SELECT * FROM person WHERE name = "Fred Bloggs"`
-
-To find a single record by ID, you can pass the ID directly to the `find_one` method:
-
- $person = ORM::for_table('person')->find_one(5);
-
-#### Multiple records ####
-
-Any method chain that ends in `find_many()` will return an *array* of ORM class instances, one for each row matched by your query. If no rows were found, an empty array will be returned.
-
-To find all records in the table:
-
- $people = ORM::for_table('person')->find_many();
-
-To find all records where the `gender` is `female`:
-
- $females = ORM::for_table('person')->where('gender', 'female')->find_many();
-
-##### As an associative array #####
-
-You can also find many records as an associative array instead of Idiorm instances. To do this substitute any call to `find_many()` with `find_array()`.
-
- $females = ORM::for_table('person')->where('gender', 'female')->find_array();
-
-This is useful if you need to serialise the the query output into a format like JSON and you do not need the ability to update the returned records.
-
-#### Counting results ####
-
-To return a count of the number of rows that would be returned by a query, call the `count()` method.
-
- $number_of_people = ORM::for_table('person')->count();
-
-#### Filtering results ####
-
-Idiorm provides a family of methods to extract only records which satisfy some condition or conditions. These methods may be called multiple times to build up your query, and Idiorm's fluent interface allows method calls to be *chained* to create readable and simple-to-understand queries.
-
-##### *Caveats* #####
-
-Only a subset of the available conditions supported by SQL are available when using Idiorm. Additionally, all the `WHERE` clauses will be `AND`ed together when the query is run. Support for `OR`ing `WHERE` clauses is not currently present.
-
-These limits are deliberate: these are by far the most commonly used criteria, and by avoiding support for very complex queries, the Idiorm codebase can remain small and simple.
-
-Some support for more complex conditions and queries is provided by the `where_raw` and `raw_query` methods (see below). If you find yourself regularly requiring more functionality than Idiorm can provide, it may be time to consider using a more full-featured ORM.
-
-##### Equality: `where`, `where_equal`, `where_not_equal` #####
-
-By default, calling `where` with two parameters (the column name and the value) will combine them using an equals operator (`=`). For example, calling `where('name', 'Fred')` will result in the clause `WHERE name = "Fred"`.
-
-If your coding style favours clarity over brevity, you may prefer to use the `where_equal` method: this is identical to `where`.
-
-The `where_not_equal` method adds a `WHERE column != "value"` clause to your query.
-
-##### Shortcut: `where_id_is` #####
-
-This is a simple helper method to query the table by primary key. Respects the ID column specified in the config.
-
-##### Less than / greater than: `where_lt`, `where_gt`, `where_lte`, `where_gte` #####
-
-There are four methods available for inequalities:
-
-* Less than: `$people = ORM::for_table('person')->where_lt('age', 10)->find_many();`
-* Greater than: `$people = ORM::for_table('person')->where_gt('age', 5)->find_many();`
-* Less than or equal: `$people = ORM::for_table('person')->where_lte('age', 10)->find_many();`
-* Greater than or equal: `$people = ORM::for_table('person')->where_gte('age', 5)->find_many();`
-
-##### String comparision: `where_like` and `where_not_like` #####
-
-To add a `WHERE ... LIKE` clause, use:
-
- $people = ORM::for_table('person')->where_like('name', '%fred%')->find_many();
-
-Similarly, to add a `WHERE ... NOT LIKE` clause, use:
-
- $people = ORM::for_table('person')->where_not_like('name', '%bob%')->find_many();
-
-##### Set membership: `where_in` and `where_not_in` #####
-
-To add a `WHERE ... IN ()` or `WHERE ... NOT IN ()` clause, use the `where_in` and `where_not_in` methods respectively.
-
-Both methods accept two arguments. The first is the column name to compare against. The second is an *array* of possible values.
-
- $people = ORM::for_table('person')->where_in('name', array('Fred', 'Joe', 'John'))->find_many();
-
-##### Working with `NULL` values: `where_null` and `where_not_null` #####
-
-To add a `WHERE column IS NULL` or `WHERE column IS NOT NULL` clause, use the `where_null` and `where_not_null` methods respectively. Both methods accept a single parameter: the column name to test.
-
-##### Raw WHERE clauses #####
-
-If you require a more complex query, you can use the `where_raw` method to specify the SQL fragment for the WHERE clause exactly. This method takes two arguments: the string to add to the query, and an (optional) array of parameters which will be bound to the string. If parameters are supplied, the string should contain question mark characters (`?`) to represent the values to be bound, and the parameter array should contain the values to be substituted into the string in the correct order.
-
-This method may be used in a method chain alongside other `where_*` methods as well as methods such as `offset`, `limit` and `order_by_*`. The contents of the string you supply will be connected with preceding and following WHERE clauses with AND.
-
- $people = ORM::for_table('person')
- ->where('name', 'Fred')
- ->where_raw('(`age` = ? OR `age` = ?)', array(20, 25))
- ->order_by_asc('name')
- ->find_many();
-
- // Creates SQL:
- SELECT * FROM `person` WHERE `name` = "Fred" AND (`age` = 20 OR `age` = 25) ORDER BY `name` ASC;
-
-Note that this method only supports "question mark placeholder" syntax, and NOT "named placeholder" syntax. This is because PDO does not allow queries that contain a mixture of placeholder types. Also, you should ensure that the number of question mark placeholders in the string exactly matches the number of elements in the array.
-
-If you require yet more flexibility, you can manually specify the entire query. See *Raw queries* below.
-
-##### Limits and offsets #####
-
-*Note that these methods **do not** escape their query parameters and so these should **not** be passed directly from user input.*
-
-The `limit` and `offset` methods map pretty closely to their SQL equivalents.
-
- $people = ORM::for_table('person')->where('gender', 'female')->limit(5)->offset(10)->find_many();
-
-##### Ordering #####
-
-*Note that these methods **do not** escape their query parameters and so these should **not** be passed directly from user input.*
-
-Two methods are provided to add `ORDER BY` clauses to your query. These are `order_by_desc` and `order_by_asc`, each of which takes a column name to sort by. The column names will be quoted.
-
- $people = ORM::for_table('person')->order_by_asc('gender')->order_by_desc('name')->find_many();
-
-If you want to order by something other than a column name, then use the `order_by_expr` method to add an unquoted SQL expression as an `ORDER BY` clause.
-
- $people = ORM::for_table('person')->order_by_expr('SOUNDEX(`name`)')->find_many();
-
-#### Grouping ####
-
-*Note that this method **does not** escape it query parameter and so this should **not** by passed directly from user input.*
-
-To add a `GROUP BY` clause to your query, call the `group_by` method, passing in the column name. You can call this method multiple times to add further columns.
-
- $people = ORM::for_table('person')->where('gender', 'female')->group_by('name')->find_many();
-
-It is also possible to `GROUP BY` a database expression:
-
- $people = ORM::for_table('person')->where('gender', 'female')->group_by_expr("FROM_UNIXTIME(`time`, '%Y-%m')")->find_many();
-
-#### Result columns ####
-
-By default, all columns in the `SELECT` statement are returned from your query. That is, calling:
-
- $people = ORM::for_table('person')->find_many();
-
-Will result in the query:
-
- SELECT * FROM `person`;
-
-The `select` method gives you control over which columns are returned. Call `select` multiple times to specify columns to return or use [`select_many`](#shortcuts-for-specifying-many-columns) to specify many columns at once.
-
- $people = ORM::for_table('person')->select('name')->select('age')->find_many();
-
-Will result in the query:
-
- SELECT `name`, `age` FROM `person`;
-
-Optionally, you may also supply a second argument to `select` to specify an alias for the column:
-
- $people = ORM::for_table('person')->select('name', 'person_name')->find_many();
-
-Will result in the query:
-
- SELECT `name` AS `person_name` FROM `person`;
-
-Column names passed to `select` are quoted automatically, even if they contain `table.column`-style identifiers:
-
- $people = ORM::for_table('person')->select('person.name', 'person_name')->find_many();
-
-Will result in the query:
-
- SELECT `person`.`name` AS `person_name` FROM `person`;
-
-If you wish to override this behaviour (for example, to supply a database expression) you should instead use the `select_expr` method. Again, this takes the alias as an optional second argument. You can specify multiple expressions by calling `select_expr` multiple times or use [`select_many_expr`](#shortcuts-for-specifying-many-columns) to specify many expressions at once.
-
- // NOTE: For illustrative purposes only. To perform a count query, use the count() method.
- $people_count = ORM::for_table('person')->select_expr('COUNT(*)', 'count')->find_many();
-
-Will result in the query:
-
- SELECT COUNT(*) AS `count` FROM `person`;
-
-##### Shortcuts for specifying many columns #####
-
-`select_many` and `select_many_expr` are very similar, but they allow you to specify more than one column at once. For example:
-
- $people = ORM::for_table('person')->select_many('name', 'age')->find_many();
-
-Will result in the query:
-
- SELECT `name`, `age` FROM `person`;
-
-To specify aliases you need to pass in an array (aliases are set as the key in an associative array):
-
- $people = ORM::for_table('person')->select_many(array('first_name' => 'name', 'age'), 'height')->find_many();
-
-Will result in the query:
-
- SELECT `name` AS `first_name`, `age`, `height` FROM `person`;
-
-You can pass the the following styles into `select_many` and `select_many_expr` by mixing and matching arrays and parameters:
-
- select_many(array('alias' => 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5')
- select_many('column', 'column2', 'column3')
- select_many(array('column', 'column2', 'column3'), 'column4', 'column5')
-
-All the select methods can also be chained with each other so you could do the following to get a neat select query including an expression:
-
- $people = ORM::for_table('person')->select_many('name', 'age', 'height')->select_expr('NOW()', 'timestamp')->find_many();
-
-Will result in the query:
-
- SELECT `name`, `age`, `height`, NOW() AS `timestamp` FROM `person`;
-
-
-#### DISTINCT ####
-
-To add a `DISTINCT` keyword before the list of result columns in your query, add a call to `distinct()` to your query chain.
-
- $distinct_names = ORM::for_table('person')->distinct()->select('name')->find_many();
-
-This will result in the query:
-
- SELECT DISTINCT `name` FROM `person`;
-
-#### Joins ####
-
-Idiorm has a family of methods for adding different types of `JOIN`s to the queries it constructs:
-
-Methods: `join`, `inner_join`, `left_outer_join`, `right_outer_join`, `full_outer_join`.
-
-Each of these methods takes the same set of arguments. The following description will use the basic `join` method as an example, but the same applies to each method.
-
-The first two arguments are mandatory. The first is the name of the table to join, and the second supplies the conditions for the join. The recommended way to specify the conditions is as an *array* containing three components: the first column, the operator, and the second column. The table and column names will be automatically quoted. For example:
-
- $results = ORM::for_table('person')->join('person_profile', array('person.id', '=', 'person_profile.person_id'))->find_many();
-
-It is also possible to specify the condition as a string, which will be inserted as-is into the query. However, in this case the column names will **not** be escaped, and so this method should be used with caution.
-
- // Not recommended because the join condition will not be escaped.
- $results = ORM::for_table('person')->join('person_profile', 'person.id = person_profile.person_id')->find_many();
-
-The `join` methods also take an optional third parameter, which is an `alias` for the table in the query. This is useful if you wish to join the table to *itself* to create a hierarchical structure. In this case, it is best combined with the `table_alias` method, which will add an alias to the *main* table associated with the ORM, and the `select` method to control which columns get returned.
-
- $results = ORM::for_table('person')
- ->table_alias('p1')
- ->select('p1.*')
- ->select('p2.name', 'parent_name')
- ->join('person', array('p1.parent', '=', 'p2.id'), 'p2')
- ->find_many();
-
-#### Aggregate functions ####
-
-There is support for `MIN`, `AVG`, `MAX` and `SUM` in addition to `COUNT` (documented earlier).
-
-To return a minimum value of column, call the `min()` method.
-
- $min = ORM::for_table('person')->min('height');
-
-The other functions (`AVG`, `MAX` and `SUM`) work in exactly the same manner. Supply a column name to perform the aggregate function on and it will return an integer.
-
-#### Raw queries ####
-
-If you need to perform more complex queries, you can completely specify the query to execute by using the `raw_query` method. This method takes a string and optionally an array of parameters. The string can contain placeholders, either in question mark or named placeholder syntax, which will be used to bind the parameters to the query.
-
- $people = ORM::for_table('person')->raw_query('SELECT p.* FROM person p JOIN role r ON p.role_id = r.id WHERE r.name = :role', array('role' => 'janitor'))->find_many();
-
-The ORM class instance(s) returned will contain data for all the columns returned by the query. Note that you still must call `for_table` to bind the instances to a particular table, even though there is nothing to stop you from specifying a completely different table in the query. This is because if you wish to later called `save`, the ORM will need to know which table to update.
-
-Note that using `raw_query` is advanced and possibly dangerous, and Idiorm does not make any attempt to protect you from making errors when using this method. If you find yourself calling `raw_query` often, you may have misunderstood the purpose of using an ORM, or your application may be too complex for Idiorm. Consider using a more full-featured database abstraction system.
-
-### Getting data from objects ###
-
-Once you've got a set of records (objects) back from a query, you can access properties on those objects (the values stored in the columns in its corresponding table) in two ways: by using the `get` method, or simply by accessing the property on the object directly:
-
- $person = ORM::for_table('person')->find_one(5);
-
- // The following two forms are equivalent
- $name = $person->get('name');
- $name = $person->name;
-
-You can also get the all the data wrapped by an ORM instance using the `as_array` method. This will return an associative array mapping column names (keys) to their values.
-
-The `as_array` method takes column names as optional arguments. If one or more of these arguments is supplied, only matching column names will be returned.
-
- $person = ORM::for_table('person')->create();
-
- $person->first_name = 'Fred';
- $person->surname = 'Bloggs';
- $person->age = 50;
-
- // Returns array('first_name' => 'Fred', 'surname' => 'Bloggs', 'age' => 50)
- $data = $person->as_array();
-
- // Returns array('first_name' => 'Fred', 'age' => 50)
- $data = $person->as_array('first_name', 'age');
-
-### Updating records ###
-
-To update the database, change one or more of the properties of the object, then call the `save` method to commit the changes to the database. Again, you can change the values of the object's properties either by using the `set` method or by setting the value of the property directly. By using the `set` method it is also possible to update multiple properties at once, by passing in an associative array:
-
- $person = ORM::for_table('person')->find_one(5);
-
- // The following two forms are equivalent
- $person->set('name', 'Bob Smith');
- $person->age = 20;
-
- // This is equivalent to the above two assignments
- $person->set(array(
- 'name' => 'Bob Smith',
- 'age' => 20
- ));
-
- // Syncronise the object with the database
- $person->save();
-
-#### Properties containing expressions ####
-
-It is possible to set properties on the model that contain database expressions using the `set_expr` method.
-
- $person = ORM::for_table('person')->find_one(5);
- $person->set('name', 'Bob Smith');
- $person->age = 20;
- $person->set_expr('updated', 'NOW()');
- $person->save();
-
-The `updated` column's value will be inserted into query in its raw form therefore allowing the database to execute any functions referenced - such as `NOW()` in this case.
-
-### Creating new records ###
-
-To add a new record, you need to first create an "empty" object instance. You then set values on the object as normal, and save it.
-
- $person = ORM::for_table('person')->create();
-
- $person->name = 'Joe Bloggs';
- $person->age = 40;
-
- $person->save();
-
-After the object has been saved, you can call its `id()` method to find the autogenerated primary key value that the database assigned to it.
-
-#### Properties containing expressions ####
-
-It is possible to set properties on the model that contain database expressions using the `set_expr` method.
-
- $person = ORM::for_table('person')->create();
- $person->set('name', 'Bob Smith');
- $person->age = 20;
- $person->set_expr('added', 'NOW()');
- $person->save();
-
-The `added` column's value will be inserted into query in its raw form therefore allowing the database to execute any functions referenced - such as `NOW()` in this case.
-
-### Checking whether a property has been modified ###
-
-To check whether a property has been changed since the object was created (or last saved), call the `is_dirty` method:
-
- $name_has_changed = $person->is_dirty('name'); // Returns true or false
-
-### Deleting records ###
-
-To delete an object from the database, simply call its `delete` method.
-
- $person = ORM::for_table('person')->find_one(5);
- $person->delete();
-
-To delete more than one object from the database, build a query:
-
- $person = ORM::for_table('person')
- ->where_equal('zipcode', 55555)
- ->delete_many();
-
-### Transactions ###
-
-Idiorm doesn't supply any extra methods to deal with transactions, but it's very easy to use PDO's built-in methods:
-
- // Start a transaction
- ORM::get_db()->beginTransaction();
-
- // Commit a transaction
- ORM::get_db()->commit();
-
- // Roll back a transaction
- ORM::get_db()->rollBack();
-
-For more details, see [the PDO documentation on Transactions](http://www.php.net/manual/en/pdo.transactions.php).
-
-### Configuration ###
-
-Other than setting the DSN string for the database connection (see above), the `configure` method can be used to set some other simple options on the ORM class. Modifying settings involves passing a key/value pair to the `configure` method, representing the setting you wish to modify and the value you wish to set it to.
-
- ORM::configure('setting_name', 'value_for_setting');
-
-A shortcut is provided to allow passing multiple key/value pairs at once.
-
- ORM::configure(array(
- 'setting_name_1' => 'value_for_setting_1',
- 'setting_name_2' => 'value_for_setting_2',
- 'etc' => 'etc'
- ));
-
-#### Database authentication details ####
-
-Settings: `username` and `password`
-
-Some database adapters (such as MySQL) require a username and password to be supplied separately to the DSN string. These settings allow you to provide these values. A typical MySQL connection setup might look like this:
-
- ORM::configure('mysql:host=localhost;dbname=my_database');
- ORM::configure('username', 'database_user');
- ORM::configure('password', 'top_secret');
-
-Or you can combine the connection setup into a single line using the configuration array shortcut:
-
- ORM::configure(array(
- 'mysql:host=localhost;dbname=my_database',
- 'username' => 'database_user',
- 'password' => 'top_secret'
- ));
-
-#### PDO Driver Options ####
-
-Setting: `driver_options`
-
-Some database adapters require (or allow) an array of driver-specific configuration options. This setting allows you to pass these options through to the PDO constructor. For more information, see [the PDO documentation](http://www.php.net/manual/en/pdo.construct.php). For example, to force the MySQL driver to use UTF-8 for the connection:
-
- ORM::configure('driver_options', array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'));
-
-
-#### PDO Error Mode ####
-
-Setting: `error_mode`
-
-This can be used to set the `PDO::ATTR_ERRMODE` setting on the database connection class used by Idiorm. It should be passed one of the class constants defined by PDO. For example:
-
- ORM::configure('error_mode', PDO::ERRMODE_WARNING);
-
-The default setting is `PDO::ERRMODE_EXCEPTION`. For full details of the error modes available, see [the PDO documentation](http://uk2.php.net/manual/en/pdo.setattribute.php).
-
-#### Identifier quote character ####
-
-Setting: `identifier_quote_character`
-
-Set the character used to quote identifiers (eg table name, column name). If this is not set, it will be autodetected based on the database driver being used by PDO.
-
-#### ID Column ####
-
-By default, the ORM assumes that all your tables have a primary key column called `id`. There are two ways to override this: for all tables in the database, or on a per-table basis.
-
-Setting: `id_column`
-
-This setting is used to configure the name of the primary key column for all tables. If your ID column is called `primary_key`, use:
-
- ORM::configure('id_column', 'primary_key');
-
-Setting: `id_column_overrides`
-
-This setting is used to specify the primary key column name for each table separately. It takes an associative array mapping table names to column names. If, for example, your ID column names include the name of the table, you can use the following configuration:
-
- ORM::configure('id_column_overrides', array(
- 'person' => 'person_id',
- 'role' => 'role_id',
- ));
-
-#### Query logging ####
-
-Setting: `logging`
-
-Idiorm can log all queries it executes. To enable query logging, set the `logging` option to `true` (it is `false` by default).
-
-When query logging is enabled, you can use two static methods to access the log. `ORM::get_last_query()` returns the most recent query executed. `ORM::get_query_log()` returns an array of all queries executed.
-
-#### Query caching ####
-
-Setting: `caching`
-
-Idiorm can cache the queries it executes during a request. To enable query caching, set the `caching` option to `true` (it is `false` by default).
-
-When query caching is enabled, Idiorm will cache the results of every `SELECT` query it executes. If Idiorm encounters a query that has already been run, it will fetch the results directly from its cache and not perform a database query.
-
-##### Warnings and gotchas #####
-
-* Note that this is an in-memory cache that only persists data for the duration of a single request. This is *not* a replacement for a persistent cache such as [Memcached](http://www.memcached.org/).
-
-* Idiorm's cache is very simple, and does not attempt to invalidate itself when data changes. This means that if you run a query to retrieve some data, modify and save it, and then run the same query again, the results will be stale (ie, they will not reflect your modifications). This could potentially cause subtle bugs in your application. If you have caching enabled and you are experiencing odd behaviour, disable it and try again. If you do need to perform such operations but still wish to use the cache, you can call the `ORM::clear_cache()` to clear all existing cached queries.
-
-* Enabling the cache will increase the memory usage of your application, as all database rows that are fetched during each request are held in memory. If you are working with large quantities of data, you may wish to disable the cache.
+* Initial release \ No newline at end of file
diff --git a/composer.json b/composer.json
index 5cc3e15..2308873 100644
--- a/composer.json
+++ b/composer.json
@@ -37,6 +37,6 @@
"php": ">=5.2.0"
},
"autoload": {
- "files": ["idiorm.php"]
+ "classmap": ["idiorm.php"]
}
-} \ No newline at end of file
+}
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..4dbea87
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,153 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Idiorm.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Idiorm.qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/Idiorm"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Idiorm"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+info:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..85edfb3
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,242 @@
+# -*- coding: utf-8 -*-
+#
+# Idiorm documentation build configuration file, created by
+# sphinx-quickstart on Wed Nov 28 15:39:16 2012.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = []
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Idiorm'
+copyright = u'2012, Jamie Matthews, Simon Holywell, Durham Hale'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '1.2.3'
+# The full version, including alpha/beta/rc tags.
+release = '1.2.3'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'Idiormdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'Idiorm.tex', u'Idiorm Documentation',
+ u'Jamie Matthews, Simon Holywell, Durham Hale', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('index', 'idiorm', u'Idiorm Documentation',
+ [u'Jamie Matthews, Simon Holywell, Durham Hale'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ ('index', 'Idiorm', u'Idiorm Documentation',
+ u'Jamie Matthews, Simon Holywell, Durham Hale', 'Idiorm', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
diff --git a/docs/configuration.rst b/docs/configuration.rst
new file mode 100644
index 0000000..d464032
--- /dev/null
+++ b/docs/configuration.rst
@@ -0,0 +1,246 @@
+Configuration
+=============
+
+The first thing you need to know about Idiorm is that *you don’t need to
+define any model classes to use it*. With almost every other ORM, the
+first thing to do is set up your models and map them to database tables
+(through configuration variables, XML files or similar). With Idiorm,
+you can start using the ORM straight away.
+
+Setup
+~~~~~
+
+First, ``require`` the Idiorm source file:
+
+::
+
+ require_once 'idiorm.php';
+
+Then, pass a *Data Source Name* connection string to the ``configure``
+method of the ORM class. This is used by PDO to connect to your
+database. For more information, see the `PDO documentation`_.
+
+::
+
+ ORM::configure('sqlite:./example.db');
+
+You may also need to pass a username and password to your database
+driver, using the ``username`` and ``password`` configuration options.
+For example, if you are using MySQL:
+
+::
+
+ ORM::configure('mysql:host=localhost;dbname=my_database');
+ ORM::configure('username', 'database_user');
+ ORM::configure('password', 'top_secret');
+
+Also see “Configuration” section below.
+
+Configuration
+~~~~~~~~~~~~~
+
+Other than setting the DSN string for the database connection (see
+above), the ``configure`` method can be used to set some other simple
+options on the ORM class. Modifying settings involves passing a
+key/value pair to the ``configure`` method, representing the setting you
+wish to modify and the value you wish to set it to.
+
+::
+
+ ORM::configure('setting_name', 'value_for_setting');
+
+A shortcut is provided to allow passing multiple key/value pairs at
+once.
+
+::
+
+ ORM::configure(array(
+ 'setting_name_1' => 'value_for_setting_1',
+ 'setting_name_2' => 'value_for_setting_2',
+ 'etc' => 'etc'
+ ));
+
+Database authentication details
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Settings: ``username`` and ``password``
+
+Some database adapters (such as MySQL) require a username and password
+to be supplied separately to the DSN string. These settings allow you to
+provide these values. A typical MySQL connection setup might look like
+this:
+
+::
+
+ ORM::configure('mysql:host=localhost;dbname=my_database');
+ ORM::configure('username', 'database_user');
+ ORM::configure('password', 'top_secret');
+
+Or you can combine the connection setup into a single line using the
+configuration array shortcut:
+
+::
+
+ ORM::configure(array(
+ 'mysql:host=localhost;dbname=my_database',
+ 'username' => 'database_user',
+ 'password' => 'top_secret'
+ ));
+
+Result sets
+^^^^^^^^^^^
+
+Setting: ``return_result_sets``
+
+Collections of results can be returned as an array (default) or as a result set.
+See the `find_result_set()` documentation for more information.
+
+::
+
+ ORM::configure('return_result_sets', true); // returns result sets
+
+
+.. note::
+
+ It is recommended that you setup your projects to use result sets as they
+ are more flexible.
+
+PDO Driver Options
+^^^^^^^^^^^^^^^^^^
+
+Setting: ``driver_options``
+
+Some database adapters require (or allow) an array of driver-specific
+configuration options. This setting allows you to pass these options
+through to the PDO constructor. For more information, see `the PDO
+documentation`_. For example, to force the MySQL driver to use UTF-8 for
+the connection:
+
+::
+
+ ORM::configure('driver_options', array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'));
+
+PDO Error Mode
+^^^^^^^^^^^^^^
+
+Setting: ``error_mode``
+
+This can be used to set the ``PDO::ATTR_ERRMODE`` setting on the
+database connection class used by Idiorm. It should be passed one of the
+class constants defined by PDO. For example:
+
+::
+
+ ORM::configure('error_mode', PDO::ERRMODE_WARNING);
+
+The default setting is ``PDO::ERRMODE_EXCEPTION``. For full details of
+the error modes available, see `the PDO set attribute documentation`_.
+
+PDO object access
+^^^^^^^^^^^^^^^^^
+
+Should it ever be necessary, the PDO object used by Idiorm may be
+accessed directly through ``ORM::get_db()``, or set directly via
+``ORM::set_db()``. This should be an unusual occurance.
+
+After a statement has been executed by any means, such as ``::save()``
+or ``::raw_execute()``, the ``PDOStatement`` instance used may be
+accessed via ``ORM::get_last_statement()``. This may be useful in order
+to access ``PDOStatement::errorCode()``, if PDO exceptions are turned
+off, or to access the ``PDOStatement::rowCount()`` method, which returns
+differing results based on the underlying database. For more
+information, see the `PDOStatement documentation`_.
+
+Identifier quote character
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Setting: ``identifier_quote_character``
+
+Set the character used to quote identifiers (eg table name, column
+name). If this is not set, it will be autodetected based on the database
+driver being used by PDO.
+
+ID Column
+^^^^^^^^^
+
+By default, the ORM assumes that all your tables have a primary key
+column called ``id``. There are two ways to override this: for all
+tables in the database, or on a per-table basis.
+
+Setting: ``id_column``
+
+This setting is used to configure the name of the primary key column for
+all tables. If your ID column is called ``primary_key``, use:
+
+::
+
+ ORM::configure('id_column', 'primary_key');
+
+Setting: ``id_column_overrides``
+
+This setting is used to specify the primary key column name for each
+table separately. It takes an associative array mapping table names to
+column names. If, for example, your ID column names include the name of
+the table, you can use the following configuration:
+
+::
+
+ ORM::configure('id_column_overrides', array(
+ 'person' => 'person_id',
+ 'role' => 'role_id',
+ ));
+
+Query logging
+^^^^^^^^^^^^^
+
+Setting: ``logging``
+
+Idiorm can log all queries it executes. To enable query logging, set the
+``logging`` option to ``true`` (it is ``false`` by default).
+
+When query logging is enabled, you can use two static methods to access
+the log. ``ORM::get_last_query()`` returns the most recent query
+executed. ``ORM::get_query_log()`` returns an array of all queries
+executed.
+
+Query caching
+^^^^^^^^^^^^^
+
+Setting: ``caching``
+
+Idiorm can cache the queries it executes during a request. To enable
+query caching, set the ``caching`` option to ``true`` (it is ``false``
+by default).
+
+When query caching is enabled, Idiorm will cache the results of every
+``SELECT`` query it executes. If Idiorm encounters a query that has
+already been run, it will fetch the results directly from its cache and
+not perform a database query.
+
+Warnings and gotchas
+''''''''''''''''''''
+
+- Note that this is an in-memory cache that only persists data for the
+ duration of a single request. This is *not* a replacement for a
+ persistent cache such as `Memcached`_.
+
+- Idiorm’s cache is very simple, and does not attempt to invalidate
+ itself when data changes. This means that if you run a query to
+ retrieve some data, modify and save it, and then run the same query
+ again, the results will be stale (ie, they will not reflect your
+ modifications). This could potentially cause subtle bugs in your
+ application. If you have caching enabled and you are experiencing odd
+ behaviour, disable it and try again. If you do need to perform such
+ operations but still wish to use the cache, you can call the
+ ``ORM::clear_cache()`` to clear all existing cached queries.
+
+- Enabling the cache will increase the memory usage of your
+ application, as all database rows that are fetched during each
+ request are held in memory. If you are working with large quantities
+ of data, you may wish to disable the cache.
+
+.. _PDO documentation: http://php.net/manual/en/pdo.construct.php
+.. _the PDO documentation: http://www.php.net/manual/en/pdo.construct.php
+.. _the PDO set attribute documentation: http://uk2.php.net/manual/en/pdo.setattribute.php
+.. _PDOStatement documentation: http://www.php.net/manual/en/class.pdostatement.php
+.. _Memcached: http://www.memcached.org/ \ No newline at end of file
diff --git a/docs/connections.rst b/docs/connections.rst
new file mode 100644
index 0000000..d34d6a7
--- /dev/null
+++ b/docs/connections.rst
@@ -0,0 +1,77 @@
+Mulitple Connections
+====================
+Idiorm now works with multiple conections. Most of the static functions
+work with an optional connection name as an extra parameter. For the
+``ORM::configure`` method, this means that when passing connection
+strings for a new connection, the second parameter, which is typically
+omitted, should be ``null``. In all cases, if a connection name is not
+provided, it defaults to ``ORM::DEFAULT_CONNECTION``.
+
+When chaining, once ``for_table()`` has been used in the chain, remaining
+calls in the chain use the correct connection.
+
+::
+
+ // Default connection
+ ORM::configure('sqlite:./example.db');
+
+ // A named connection, where 'remote' is an arbitrary key name
+ ORM::configure('mysql:host=localhost;dbname=my_database', null, 'remote');
+ ORM::configure('username', 'database_user', 'remote');
+ ORM::configure('password', 'top_secret', 'remote');
+
+ // Using default connection
+ $person = ORM::for_table('person')->find_one(5);
+
+ // Using default connection, explicitly
+ $person = ORM::for_table('person', ORM::DEFAULT_CONNECTION)->find_one(5);
+
+ // Using named connection
+ $person = ORM::for_table('different_person', 'remote')->find_one(5);
+
+
+
+Supported Methods
+^^^^^^^^^^^^^^^^^
+In each of these cases, the ``$connection_name`` parameter is optional, and is
+an arbitrary key identifying the named connection.
+
+* ``ORM::configure($key, $value, $connection_name)``
+* ``ORM::for_table($table_name, $connection_name)``
+* ``ORM::set_db($pdo, $connection_name)``
+* ``ORM::get_db($connection_name)``
+* ``ORM::raw_execute($query, $parameters, $connection_name)``
+* ``ORM::get_last_query($connection_name)``
+* ``ORM::get_query_log($connection_name)``
+
+Of these methods, only ``ORM::get_last_query($connection_name)`` does *not*
+fallback to the default connection when no connection name is passed.
+Instead, passing no connection name (or ``null``) returns the most recent
+query on *any* connection.
+
+::
+
+ // Using default connection, explicitly
+ $person = ORM::for_table('person')->find_one(5);
+
+ // Using named connection
+ $person = ORM::for_table('different_person', 'remote')->find_one(5);
+
+ // Last query on *any* connection
+ ORM::get_last_query(); // returns query on 'different_person' using 'remote'
+
+ // returns query on 'person' using default by passing in the connection name
+ ORM::get_last_query(ORM::DEFAULT_CONNECTION);
+
+Notes
+~~~~~
+* **There is no support for joins across connections**
+* Multiple connections do not share configuration settings. This means if
+ one connection has logging set to ``true`` and the other does not, only
+ queries from the logged connection will be available via
+ ``ORM::get_last_query()`` and ``ORM::get_query_log()``.
+* A new method has been added, ``ORM::get_connection_names()``, which returns
+ an array of connection names.
+* Caching *should* work with multiple connections (remember to turn caching
+ on for each connection), but the unit tests are not robust. Please report
+ any errors. \ No newline at end of file
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..d4e4bb9
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,29 @@
+.. Idiorm documentation master file, created by
+ sphinx-quickstart on Wed Nov 28 15:39:16 2012.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+Welcome to Idiorm's documentation!
+==================================
+
+Contents:
+
+.. toctree::
+ :maxdepth: 2
+
+ philosophy
+ installation
+ configuration
+ querying
+ models
+ transactions
+ connections
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
diff --git a/docs/installation.rst b/docs/installation.rst
new file mode 100644
index 0000000..255ea62
--- /dev/null
+++ b/docs/installation.rst
@@ -0,0 +1,19 @@
+Installation
+============
+
+Packagist
+~~~~~~~~~
+
+This library is available through Packagist with the vendor and package
+identifier of ``j4mie/idiorm``
+
+Please see the `Packagist documentation`_ for further information.
+
+Download
+~~~~~~~~
+
+You can clone the git repository, download idiorm.php or a release tag
+and then drop the idiorm.php file in the vendors/3rd party/libs
+directory of your project.
+
+.. _Packagist documentation: http://packagist.org/ \ No newline at end of file
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000..c090735
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,190 @@
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+set I18NSPHINXOPTS=%SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+ set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+ set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+ :help
+ echo.Please use `make ^<target^>` where ^<target^> is one of
+ echo. html to make standalone HTML files
+ echo. dirhtml to make HTML files named index.html in directories
+ echo. singlehtml to make a single large HTML file
+ echo. pickle to make pickle files
+ echo. json to make JSON files
+ echo. htmlhelp to make HTML files and a HTML help project
+ echo. qthelp to make HTML files and a qthelp project
+ echo. devhelp to make HTML files and a Devhelp project
+ echo. epub to make an epub
+ echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+ echo. text to make text files
+ echo. man to make manual pages
+ echo. texinfo to make Texinfo files
+ echo. gettext to make PO message catalogs
+ echo. changes to make an overview over all changed/added/deprecated items
+ echo. linkcheck to check all external links for integrity
+ echo. doctest to run all doctests embedded in the documentation if enabled
+ goto end
+)
+
+if "%1" == "clean" (
+ for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+ del /q /s %BUILDDIR%\*
+ goto end
+)
+
+if "%1" == "html" (
+ %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+ goto end
+)
+
+if "%1" == "dirhtml" (
+ %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+ goto end
+)
+
+if "%1" == "singlehtml" (
+ %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+ goto end
+)
+
+if "%1" == "pickle" (
+ %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the pickle files.
+ goto end
+)
+
+if "%1" == "json" (
+ %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the JSON files.
+ goto end
+)
+
+if "%1" == "htmlhelp" (
+ %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+ goto end
+)
+
+if "%1" == "qthelp" (
+ %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+ echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Idiorm.qhcp
+ echo.To view the help file:
+ echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Idiorm.ghc
+ goto end
+)
+
+if "%1" == "devhelp" (
+ %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished.
+ goto end
+)
+
+if "%1" == "epub" (
+ %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The epub file is in %BUILDDIR%/epub.
+ goto end
+)
+
+if "%1" == "latex" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "text" (
+ %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The text files are in %BUILDDIR%/text.
+ goto end
+)
+
+if "%1" == "man" (
+ %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The manual pages are in %BUILDDIR%/man.
+ goto end
+)
+
+if "%1" == "texinfo" (
+ %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
+ goto end
+)
+
+if "%1" == "gettext" (
+ %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
+ goto end
+)
+
+if "%1" == "changes" (
+ %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.The overview file is in %BUILDDIR%/changes.
+ goto end
+)
+
+if "%1" == "linkcheck" (
+ %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+ goto end
+)
+
+if "%1" == "doctest" (
+ %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+ goto end
+)
+
+:end
diff --git a/docs/models.rst b/docs/models.rst
new file mode 100644
index 0000000..f8246fa
--- /dev/null
+++ b/docs/models.rst
@@ -0,0 +1,152 @@
+Models
+======
+
+Getting data from objects
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Once you've got a set of records (objects) back from a query, you can
+access properties on those objects (the values stored in the columns in
+its corresponding table) in two ways: by using the ``get`` method, or
+simply by accessing the property on the object directly:
+
+::
+
+ $person = ORM::for_table('person')->find_one(5);
+
+ // The following two forms are equivalent
+ $name = $person->get('name');
+ $name = $person->name;
+
+You can also get the all the data wrapped by an ORM instance using the
+``as_array`` method. This will return an associative array mapping
+column names (keys) to their values.
+
+The ``as_array`` method takes column names as optional arguments. If one
+or more of these arguments is supplied, only matching column names will
+be returned.
+
+::
+
+ $person = ORM::for_table('person')->create();
+
+ $person->first_name = 'Fred';
+ $person->surname = 'Bloggs';
+ $person->age = 50;
+
+ // Returns array('first_name' => 'Fred', 'surname' => 'Bloggs', 'age' => 50)
+ $data = $person->as_array();
+
+ // Returns array('first_name' => 'Fred', 'age' => 50)
+ $data = $person->as_array('first_name', 'age');
+
+Updating records
+~~~~~~~~~~~~~~~~
+
+To update the database, change one or more of the properties of the
+object, then call the ``save`` method to commit the changes to the
+database. Again, you can change the values of the object's properties
+either by using the ``set`` method or by setting the value of the
+property directly. By using the ``set`` method it is also possible to
+update multiple properties at once, by passing in an associative array:
+
+::
+
+ $person = ORM::for_table('person')->find_one(5);
+
+ // The following two forms are equivalent
+ $person->set('name', 'Bob Smith');
+ $person->age = 20;
+
+ // This is equivalent to the above two assignments
+ $person->set(array(
+ 'name' => 'Bob Smith',
+ 'age' => 20
+ ));
+
+ // Syncronise the object with the database
+ $person->save();
+
+Properties containing expressions
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+It is possible to set properties on the model that contain database
+expressions using the ``set_expr`` method.
+
+::
+
+ $person = ORM::for_table('person')->find_one(5);
+ $person->set('name', 'Bob Smith');
+ $person->age = 20;
+ $person->set_expr('updated', 'NOW()');
+ $person->save();
+
+The ``updated`` column's value will be inserted into query in its raw
+form therefore allowing the database to execute any functions referenced
+- such as ``NOW()`` in this case.
+
+Creating new records
+~~~~~~~~~~~~~~~~~~~~
+
+To add a new record, you need to first create an "empty" object
+instance. You then set values on the object as normal, and save it.
+
+::
+
+ $person = ORM::for_table('person')->create();
+
+ $person->name = 'Joe Bloggs';
+ $person->age = 40;
+
+ $person->save();
+
+After the object has been saved, you can call its ``id()`` method to
+find the autogenerated primary key value that the database assigned to
+it.
+
+Properties containing expressions
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+It is possible to set properties on the model that contain database
+expressions using the ``set_expr`` method.
+
+::
+
+ $person = ORM::for_table('person')->create();
+ $person->set('name', 'Bob Smith');
+ $person->age = 20;
+ $person->set_expr('added', 'NOW()');
+ $person->save();
+
+The ``added`` column's value will be inserted into query in its raw form
+therefore allowing the database to execute any functions referenced -
+such as ``NOW()`` in this case.
+
+Checking whether a property has been modified
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To check whether a property has been changed since the object was
+created (or last saved), call the ``is_dirty`` method:
+
+::
+
+ $name_has_changed = $person->is_dirty('name'); // Returns true or false
+
+Deleting records
+~~~~~~~~~~~~~~~~
+
+To delete an object from the database, simply call its ``delete``
+method.
+
+::
+
+ $person = ORM::for_table('person')->find_one(5);
+ $person->delete();
+
+To delete more than one object from the database, build a query:
+
+::
+
+ $person = ORM::for_table('person')
+ ->where_equal('zipcode', 55555)
+ ->delete_many();
+
diff --git a/docs/philosophy.rst b/docs/philosophy.rst
new file mode 100644
index 0000000..a11d421
--- /dev/null
+++ b/docs/philosophy.rst
@@ -0,0 +1,34 @@
+Philosophy
+==========
+
+The `Pareto Principle`_ states that *roughly 80% of the effects come
+from 20% of the causes.* In software development terms, this could be
+translated into something along the lines of *80% of the results come
+from 20% of the complexity*. In other words, you can get pretty far by
+being pretty stupid.
+
+**Idiorm is deliberately simple**. Where other ORMs consist of dozens of
+classes with complex inheritance hierarchies, Idiorm has only one class,
+``ORM``, which functions as both a fluent ``SELECT`` query API and a
+simple CRUD model class. If my hunch is correct, this should be quite
+enough for many real-world applications. Let’s face it: most of us
+aren’t building Facebook. We’re working on small-to-medium-sized
+projects, where the emphasis is on simplicity and rapid development
+rather than infinite flexibility and features.
+
+You might think of **Idiorm** as a *micro-ORM*. It could, perhaps, be
+“the tie to go along with `Slim`_\ ’s tux” (to borrow a turn of phrase
+from `DocumentCloud`_). Or it could be an effective bit of spring
+cleaning for one of those horrendous SQL-littered legacy PHP apps you
+have to support.
+
+**Idiorm** might also provide a good base upon which to build
+higher-level, more complex database abstractions. For example, `Paris`_
+is an implementation of the `Active Record pattern`_ built on top of
+Idiorm.
+
+.. _Pareto Principle: http://en.wikipedia.org/wiki/Pareto_principle
+.. _Slim: http://github.com/codeguy/slim/
+.. _DocumentCloud: http://github.com/documentcloud/underscore
+.. _Paris: http://github.com/j4mie/paris
+.. _Active Record pattern: http://martinfowler.com/eaaCatalog/activeRecord.html \ No newline at end of file
diff --git a/docs/querying.rst b/docs/querying.rst
new file mode 100644
index 0000000..41b6485
--- /dev/null
+++ b/docs/querying.rst
@@ -0,0 +1,593 @@
+Querying
+========
+
+Idiorm provides a `*fluent
+interface* <http://en.wikipedia.org/wiki/Fluent_interface>`_ to enable
+simple queries to be built without writing a single character of SQL. If
+you've used `jQuery <http://jquery.com>`_ at all, you'll be familiar
+with the concept of a fluent interface. It just means that you can
+*chain* method calls together, one after another. This can make your
+code more readable, as the method calls strung together in order can
+start to look a bit like a sentence.
+
+All Idiorm queries start with a call to the ``for_table`` static method
+on the ORM class. This tells the ORM which table to use when making the
+query.
+
+*Note that this method **does not** escape its query parameter and so
+the table name should **not** be passed directly from user input.*
+
+Method calls which add filters and constraints to your query are then
+strung together. Finally, the chain is finished by calling either
+``find_one()`` or ``find_many()``, which executes the query and returns
+the result.
+
+Let's start with a simple example. Say we have a table called ``person``
+which contains the columns ``id`` (the primary key of the record -
+Idiorm assumes the primary key column is called ``id`` but this is
+configurable, see below), ``name``, ``age`` and ``gender``.
+
+Single records
+^^^^^^^^^^^^^^
+
+Any method chain that ends in ``find_one()`` will return either a
+*single* instance of the ORM class representing the database row you
+requested, or ``false`` if no matching record was found.
+
+To find a single record where the ``name`` column has the value "Fred
+Bloggs":
+
+::
+
+ $person = ORM::for_table('person')->where('name', 'Fred Bloggs')->find_one();
+
+This roughly translates into the following SQL:
+``SELECT * FROM person WHERE name = "Fred Bloggs"``
+
+To find a single record by ID, you can pass the ID directly to the
+``find_one`` method:
+
+::
+
+ $person = ORM::for_table('person')->find_one(5);
+
+Multiple records
+^^^^^^^^^^^^^^^^
+
+.. note::
+
+ It is recommended that you use results sets over arrays - see `As a result set`
+ below.
+
+Any method chain that ends in ``find_many()`` will return an *array* of
+ORM class instances, one for each row matched by your query. If no rows
+were found, an empty array will be returned.
+
+To find all records in the table:
+
+::
+
+ $people = ORM::for_table('person')->find_many();
+
+To find all records where the ``gender`` is ``female``:
+
+::
+
+ $females = ORM::for_table('person')->where('gender', 'female')->find_many();
+
+As a result set
+'''''''''''''''
+
+.. note::
+
+ There is a configuration setting ``return_result_sets`` that will cause
+ ``find_many()`` to return result sets by default. It is recommended that you
+ turn this setting on:
+
+ ::
+
+ ORM::configure('return_result_sets', true);
+
+You can also find many records as a result set instead of an array of Idiorm
+instances. This gives you the advantage that you can run batch operations on a
+set of results.
+
+So for example instead of running this:
+
+::
+
+ $people = ORM::for_table('person')->find_many();
+ foreach ($people as $person) {
+ $person->age = 50;
+ $person->save();
+ }
+
+You can simple do this instead:
+
+::
+
+ ORM::for_table('person')->find_result_set()
+ ->set('age', 50)
+ ->save();
+
+To do this substitute any call to ``find_many()`` with
+``find_result_set()``.
+
+A result set will also behave like an array so you can `count()` it and `foreach`
+over it just like an array.
+
+::
+
+ foreach(ORM::for_table('person')->find_result_set() as $record) {
+ echo $person->name;
+ }
+
+::
+
+ echo count(ORM::for_table('person')->find_result_set());
+
+.. note::
+
+ For deleting many records it is recommended that you use `delete_many()` as it
+ is more efficient than calling `delete()` on a result set.
+
+As an associative array
+'''''''''''''''''''''''
+
+You can also find many records as an associative array instead of Idiorm
+instances. To do this substitute any call to ``find_many()`` with
+``find_array()``.
+
+::
+
+ $females = ORM::for_table('person')->where('gender', 'female')->find_array();
+
+This is useful if you need to serialise the the query output into a
+format like JSON and you do not need the ability to update the returned
+records.
+
+Counting results
+^^^^^^^^^^^^^^^^
+
+To return a count of the number of rows that would be returned by a
+query, call the ``count()`` method.
+
+::
+
+ $number_of_people = ORM::for_table('person')->count();
+
+Filtering results
+^^^^^^^^^^^^^^^^^
+
+Idiorm provides a family of methods to extract only records which
+satisfy some condition or conditions. These methods may be called
+multiple times to build up your query, and Idiorm's fluent interface
+allows method calls to be *chained* to create readable and
+simple-to-understand queries.
+
+*Caveats*
+'''''''''
+
+Only a subset of the available conditions supported by SQL are available
+when using Idiorm. Additionally, all the ``WHERE`` clauses will be
+``AND``\ ed together when the query is run. Support for ``OR``\ ing
+``WHERE`` clauses is not currently present.
+
+These limits are deliberate: these are by far the most commonly used
+criteria, and by avoiding support for very complex queries, the Idiorm
+codebase can remain small and simple.
+
+Some support for more complex conditions and queries is provided by the
+``where_raw`` and ``raw_query`` methods (see below). If you find
+yourself regularly requiring more functionality than Idiorm can provide,
+it may be time to consider using a more full-featured ORM.
+
+Equality: ``where``, ``where_equal``, ``where_not_equal``
+'''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+By default, calling ``where`` with two parameters (the column name and
+the value) will combine them using an equals operator (``=``). For
+example, calling ``where('name', 'Fred')`` will result in the clause
+``WHERE name = "Fred"``.
+
+If your coding style favours clarity over brevity, you may prefer to use
+the ``where_equal`` method: this is identical to ``where``.
+
+The ``where_not_equal`` method adds a ``WHERE column != "value"`` clause
+to your query.
+
+Shortcut: ``where_id_is``
+'''''''''''''''''''''''''
+
+This is a simple helper method to query the table by primary key.
+Respects the ID column specified in the config.
+
+Less than / greater than: ``where_lt``, ``where_gt``, ``where_lte``, ``where_gte``
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+There are four methods available for inequalities:
+
+- Less than:
+ ``$people = ORM::for_table('person')->where_lt('age', 10)->find_many();``
+- Greater than:
+ ``$people = ORM::for_table('person')->where_gt('age', 5)->find_many();``
+- Less than or equal:
+ ``$people = ORM::for_table('person')->where_lte('age', 10)->find_many();``
+- Greater than or equal:
+ ``$people = ORM::for_table('person')->where_gte('age', 5)->find_many();``
+
+String comparision: ``where_like`` and ``where_not_like``
+'''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+To add a ``WHERE ... LIKE`` clause, use:
+
+::
+
+ $people = ORM::for_table('person')->where_like('name', '%fred%')->find_many();
+
+Similarly, to add a ``WHERE ... NOT LIKE`` clause, use:
+
+::
+
+ $people = ORM::for_table('person')->where_not_like('name', '%bob%')->find_many();
+
+Set membership: ``where_in`` and ``where_not_in``
+'''''''''''''''''''''''''''''''''''''''''''''''''
+
+To add a ``WHERE ... IN ()`` or ``WHERE ... NOT IN ()`` clause, use the
+``where_in`` and ``where_not_in`` methods respectively.
+
+Both methods accept two arguments. The first is the column name to
+compare against. The second is an *array* of possible values.
+
+::
+
+ $people = ORM::for_table('person')->where_in('name', array('Fred', 'Joe', 'John'))->find_many();
+
+Working with ``NULL`` values: ``where_null`` and ``where_not_null``
+'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+To add a ``WHERE column IS NULL`` or ``WHERE column IS NOT NULL``
+clause, use the ``where_null`` and ``where_not_null`` methods
+respectively. Both methods accept a single parameter: the column name to
+test.
+
+Raw WHERE clauses
+'''''''''''''''''
+
+If you require a more complex query, you can use the ``where_raw``
+method to specify the SQL fragment for the WHERE clause exactly. This
+method takes two arguments: the string to add to the query, and an
+(optional) array of parameters which will be bound to the string. If
+parameters are supplied, the string should contain question mark
+characters (``?``) to represent the values to be bound, and the
+parameter array should contain the values to be substituted into the
+string in the correct order.
+
+This method may be used in a method chain alongside other ``where_*``
+methods as well as methods such as ``offset``, ``limit`` and
+``order_by_*``. The contents of the string you supply will be connected
+with preceding and following WHERE clauses with AND.
+
+::
+
+ $people = ORM::for_table('person')
+ ->where('name', 'Fred')
+ ->where_raw('(`age` = ? OR `age` = ?)', array(20, 25))
+ ->order_by_asc('name')
+ ->find_many();
+
+ // Creates SQL:
+ SELECT * FROM `person` WHERE `name` = "Fred" AND (`age` = 20 OR `age` = 25) ORDER BY `name` ASC;
+
+Note that this method only supports "question mark placeholder" syntax,
+and NOT "named placeholder" syntax. This is because PDO does not allow
+queries that contain a mixture of placeholder types. Also, you should
+ensure that the number of question mark placeholders in the string
+exactly matches the number of elements in the array.
+
+If you require yet more flexibility, you can manually specify the entire
+query. See *Raw queries* below.
+
+Limits and offsets
+''''''''''''''''''
+
+*Note that these methods **do not** escape their query parameters and so
+these should **not** be passed directly from user input.*
+
+The ``limit`` and ``offset`` methods map pretty closely to their SQL
+equivalents.
+
+::
+
+ $people = ORM::for_table('person')->where('gender', 'female')->limit(5)->offset(10)->find_many();
+
+Ordering
+''''''''
+
+*Note that these methods **do not** escape their query parameters and so
+these should **not** be passed directly from user input.*
+
+Two methods are provided to add ``ORDER BY`` clauses to your query.
+These are ``order_by_desc`` and ``order_by_asc``, each of which takes a
+column name to sort by. The column names will be quoted.
+
+::
+
+ $people = ORM::for_table('person')->order_by_asc('gender')->order_by_desc('name')->find_many();
+
+If you want to order by something other than a column name, then use the
+``order_by_expr`` method to add an unquoted SQL expression as an
+``ORDER BY`` clause.
+
+::
+
+ $people = ORM::for_table('person')->order_by_expr('SOUNDEX(`name`)')->find_many();
+
+Grouping
+^^^^^^^^
+
+*Note that this method **does not** escape it query parameter and so
+this should **not** by passed directly from user input.*
+
+To add a ``GROUP BY`` clause to your query, call the ``group_by``
+method, passing in the column name. You can call this method multiple
+times to add further columns.
+
+::
+
+ $people = ORM::for_table('person')->where('gender', 'female')->group_by('name')->find_many();
+
+It is also possible to ``GROUP BY`` a database expression:
+
+::
+
+ $people = ORM::for_table('person')->where('gender', 'female')->group_by_expr("FROM_UNIXTIME(`time`, '%Y-%m')")->find_many();
+
+Having
+^^^^^^
+
+When using aggregate functions in combination with a ``GROUP BY`` you can use
+``HAVING`` to filter based on those values.
+
+``HAVING`` works in exactly the same way as all of the ``where*`` functions in Idiorm.
+Substitute ``where_`` for ``having_`` to make use of these functions.
+
+For example:
+
+::
+
+ $people = ORM::for_table('person')->group_by('name')->having_not_like('name', '%bob%')->find_many();
+
+Result columns
+^^^^^^^^^^^^^^
+
+By default, all columns in the ``SELECT`` statement are returned from
+your query. That is, calling:
+
+::
+
+ $people = ORM::for_table('person')->find_many();
+
+Will result in the query:
+
+::
+
+ SELECT * FROM `person`;
+
+The ``select`` method gives you control over which columns are returned.
+Call ``select`` multiple times to specify columns to return or use
+```select_many`` <#shortcuts-for-specifying-many-columns>`_ to specify
+many columns at once.
+
+::
+
+ $people = ORM::for_table('person')->select('name')->select('age')->find_many();
+
+Will result in the query:
+
+::
+
+ SELECT `name`, `age` FROM `person`;
+
+Optionally, you may also supply a second argument to ``select`` to
+specify an alias for the column:
+
+::
+
+ $people = ORM::for_table('person')->select('name', 'person_name')->find_many();
+
+Will result in the query:
+
+::
+
+ SELECT `name` AS `person_name` FROM `person`;
+
+Column names passed to ``select`` are quoted automatically, even if they
+contain ``table.column``-style identifiers:
+
+::
+
+ $people = ORM::for_table('person')->select('person.name', 'person_name')->find_many();
+
+Will result in the query:
+
+::
+
+ SELECT `person`.`name` AS `person_name` FROM `person`;
+
+If you wish to override this behaviour (for example, to supply a
+database expression) you should instead use the ``select_expr`` method.
+Again, this takes the alias as an optional second argument. You can
+specify multiple expressions by calling ``select_expr`` multiple times
+or use ```select_many_expr`` <#shortcuts-for-specifying-many-columns>`_
+to specify many expressions at once.
+
+::
+
+ // NOTE: For illustrative purposes only. To perform a count query, use the count() method.
+ $people_count = ORM::for_table('person')->select_expr('COUNT(*)', 'count')->find_many();
+
+Will result in the query:
+
+::
+
+ SELECT COUNT(*) AS `count` FROM `person`;
+
+Shortcuts for specifying many columns
+'''''''''''''''''''''''''''''''''''''
+
+``select_many`` and ``select_many_expr`` are very similar, but they
+allow you to specify more than one column at once. For example:
+
+::
+
+ $people = ORM::for_table('person')->select_many('name', 'age')->find_many();
+
+Will result in the query:
+
+::
+
+ SELECT `name`, `age` FROM `person`;
+
+To specify aliases you need to pass in an array (aliases are set as the
+key in an associative array):
+
+::
+
+ $people = ORM::for_table('person')->select_many(array('first_name' => 'name', 'age'), 'height')->find_many();
+
+Will result in the query:
+
+::
+
+ SELECT `name` AS `first_name`, `age`, `height` FROM `person`;
+
+You can pass the the following styles into ``select_many`` and
+``select_many_expr`` by mixing and matching arrays and parameters:
+
+::
+
+ select_many(array('alias' => 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5')
+ select_many('column', 'column2', 'column3')
+ select_many(array('column', 'column2', 'column3'), 'column4', 'column5')
+
+All the select methods can also be chained with each other so you could
+do the following to get a neat select query including an expression:
+
+::
+
+ $people = ORM::for_table('person')->select_many('name', 'age', 'height')->select_expr('NOW()', 'timestamp')->find_many();
+
+Will result in the query:
+
+::
+
+ SELECT `name`, `age`, `height`, NOW() AS `timestamp` FROM `person`;
+
+DISTINCT
+^^^^^^^^
+
+To add a ``DISTINCT`` keyword before the list of result columns in your
+query, add a call to ``distinct()`` to your query chain.
+
+::
+
+ $distinct_names = ORM::for_table('person')->distinct()->select('name')->find_many();
+
+This will result in the query:
+
+::
+
+ SELECT DISTINCT `name` FROM `person`;
+
+Joins
+^^^^^
+
+Idiorm has a family of methods for adding different types of ``JOIN``\ s
+to the queries it constructs:
+
+Methods: ``join``, ``inner_join``, ``left_outer_join``,
+``right_outer_join``, ``full_outer_join``.
+
+Each of these methods takes the same set of arguments. The following
+description will use the basic ``join`` method as an example, but the
+same applies to each method.
+
+The first two arguments are mandatory. The first is the name of the
+table to join, and the second supplies the conditions for the join. The
+recommended way to specify the conditions is as an *array* containing
+three components: the first column, the operator, and the second column.
+The table and column names will be automatically quoted. For example:
+
+::
+
+ $results = ORM::for_table('person')->join('person_profile', array('person.id', '=', 'person_profile.person_id'))->find_many();
+
+It is also possible to specify the condition as a string, which will be
+inserted as-is into the query. However, in this case the column names
+will **not** be escaped, and so this method should be used with caution.
+
+::
+
+ // Not recommended because the join condition will not be escaped.
+ $results = ORM::for_table('person')->join('person_profile', 'person.id = person_profile.person_id')->find_many();
+
+The ``join`` methods also take an optional third parameter, which is an
+``alias`` for the table in the query. This is useful if you wish to join
+the table to *itself* to create a hierarchical structure. In this case,
+it is best combined with the ``table_alias`` method, which will add an
+alias to the *main* table associated with the ORM, and the ``select``
+method to control which columns get returned.
+
+::
+
+ $results = ORM::for_table('person')
+ ->table_alias('p1')
+ ->select('p1.*')
+ ->select('p2.name', 'parent_name')
+ ->join('person', array('p1.parent', '=', 'p2.id'), 'p2')
+ ->find_many();
+
+Aggregate functions
+^^^^^^^^^^^^^^^^^^^
+
+There is support for ``MIN``, ``AVG``, ``MAX`` and ``SUM`` in addition
+to ``COUNT`` (documented earlier).
+
+To return a minimum value of column, call the ``min()`` method.
+
+::
+
+ $min = ORM::for_table('person')->min('height');
+
+The other functions (``AVG``, ``MAX`` and ``SUM``) work in exactly the
+same manner. Supply a column name to perform the aggregate function on
+and it will return an integer.
+
+Raw queries
+^^^^^^^^^^^
+
+If you need to perform more complex queries, you can completely specify
+the query to execute by using the ``raw_query`` method. This method
+takes a string and optionally an array of parameters. The string can
+contain placeholders, either in question mark or named placeholder
+syntax, which will be used to bind the parameters to the query.
+
+::
+
+ $people = ORM::for_table('person')->raw_query('SELECT p.* FROM person p JOIN role r ON p.role_id = r.id WHERE r.name = :role', array('role' => 'janitor'))->find_many();
+
+The ORM class instance(s) returned will contain data for all the columns
+returned by the query. Note that you still must call ``for_table`` to
+bind the instances to a particular table, even though there is nothing
+to stop you from specifying a completely different table in the query.
+This is because if you wish to later called ``save``, the ORM will need
+to know which table to update.
+
+Note that using ``raw_query`` is advanced and possibly dangerous, and
+Idiorm does not make any attempt to protect you from making errors when
+using this method. If you find yourself calling ``raw_query`` often, you
+may have misunderstood the purpose of using an ORM, or your application
+may be too complex for Idiorm. Consider using a more full-featured
+database abstraction system.
diff --git a/docs/transactions.rst b/docs/transactions.rst
new file mode 100644
index 0000000..03a1e92
--- /dev/null
+++ b/docs/transactions.rst
@@ -0,0 +1,20 @@
+Transactions
+============
+
+Idiorm doesn’t supply any extra methods to deal with transactions, but
+it’s very easy to use PDO’s built-in methods:
+
+::
+
+ // Start a transaction
+ ORM::get_db()->beginTransaction();
+
+ // Commit a transaction
+ ORM::get_db()->commit();
+
+ // Roll back a transaction
+ ORM::get_db()->rollBack();
+
+For more details, see `the PDO documentation on Transactions`_.
+
+.. _the PDO documentation on Transactions: http://www.php.net/manual/en/pdo.transactions.php \ No newline at end of file
diff --git a/idiorm.php b/idiorm.php
index 9d1f8c4..66a243c 100644
--- a/idiorm.php
+++ b/idiorm.php
@@ -38,22 +38,24 @@
*
*/
- class ORM {
+ class ORM implements ArrayAccess {
// ----------------------- //
// --- CLASS CONSTANTS --- //
// ----------------------- //
- // Where condition array keys
- const WHERE_FRAGMENT = 0;
- const WHERE_VALUES = 1;
+ // WHERE and HAVING condition array keys
+ const CONDITION_FRAGMENT = 0;
+ const CONDITION_VALUES = 1;
+
+ const DEFAULT_CONNECTION = 'default';
// ------------------------ //
// --- CLASS PROPERTIES --- //
// ------------------------ //
// Class configuration
- protected static $_config = array(
+ protected static $_default_config = array(
'connection_string' => 'sqlite::memory:',
'id_column' => 'id',
'id_column_overrides' => array(),
@@ -64,24 +66,34 @@
'identifier_quote_character' => null, // if this is null, will be autodetected
'logging' => false,
'caching' => false,
+ 'return_result_sets' => false,
);
- // Database connection, instance of the PDO class
- protected static $_db;
+ // Map of configuration settings
+ protected static $_config = array();
+
+ // Map of database connections, instances of the PDO class
+ protected static $_db = array();
// Last query run, only populated if logging is enabled
protected static $_last_query;
- // Log of all queries run, only populated if logging is enabled
+ // Log of all queries run, mapped by connection key, only populated if logging is enabled
protected static $_query_log = array();
// Query cache, only used if query caching is enabled
protected static $_query_cache = array();
+ // Reference to previously used PDOStatement object to enable low-level access, if needed
+ protected static $_last_statement = null;
+
// --------------------------- //
// --- INSTANCE PROPERTIES --- //
// --------------------------- //
+ // Key name of the connections in self::$_db used by this instance
+ protected $_connection_name;
+
// The name of the table the current ORM instance is associated with
protected $_table_name;
@@ -127,6 +139,9 @@
// GROUP BY
protected $_group_by = array();
+ // HAVING
+ protected $_having_conditions = array();
+
// The data for a hydrated instance of the class
protected $_data = array();
@@ -157,13 +172,18 @@
* required to use Idiorm). If you have more than one setting
* you wish to configure, another shortcut is to pass an array
* of settings (and omit the second argument).
+ * @param string $key
+ * @param mixed $value
+ * @param string $connection_name Which connection to use
*/
- public static function configure($key, $value=null) {
+ public static function configure($key, $value = null, $connection_name = self::DEFAULT_CONNECTION) {
+ self::_setup_db_config($connection_name); //ensures at least default config is set
+
if (is_array($key)) {
// Shortcut: If only one array argument is passed,
// assume it's an array of configuration settings
foreach ($key as $conf_key => $conf_value) {
- self::configure($conf_key, $conf_value);
+ self::configure($conf_key, $conf_value, $connection_name);
}
} else {
if (is_null($value)) {
@@ -172,7 +192,7 @@
$value = $key;
$key = 'connection_string';
}
- self::$_config[$key] = $value;
+ self::$_config[$connection_name][$key] = $value;
}
}
@@ -182,35 +202,59 @@
* this way for the sake of a readable interface, ie
* ORM::for_table('table_name')->find_one()-> etc. As such,
* this will normally be the first method called in a chain.
+ * @param string $table_name
+ * @param string $connection_name Which connection to use
+ * @return ORM
*/
- public static function for_table($table_name) {
- self::_setup_db();
- return new self($table_name);
+ public static function for_table($table_name, $connection_name = self::DEFAULT_CONNECTION)
+ {
+ self::_setup_db($connection_name);
+ return new self($table_name, array(), $connection_name);
}
/**
- * Set up the database connection used by the class.
+ * Set up the database connection used by the class
+ * @param string $connection_name Which connection to use
*/
- protected static function _setup_db() {
- if (!is_object(self::$_db)) {
- $connection_string = self::$_config['connection_string'];
- $username = self::$_config['username'];
- $password = self::$_config['password'];
- $driver_options = self::$_config['driver_options'];
- $db = new PDO($connection_string, $username, $password, $driver_options);
- $db->setAttribute(PDO::ATTR_ERRMODE, self::$_config['error_mode']);
- self::set_db($db);
+ protected static function _setup_db($connection_name = self::DEFAULT_CONNECTION)
+ {
+ if (!is_object(self::$_db[$connection_name])) {
+ self::_setup_db_config($connection_name);
+
+ $db = new PDO(
+ self::$_config[$connection_name]['connection_string'],
+ self::$_config[$connection_name]['username'],
+ self::$_config[$connection_name]['password'],
+ self::$_config[$connection_name]['driver_options']
+ );
+
+ $db->setAttribute(PDO::ATTR_ERRMODE, self::$_config[$connection_name]['error_mode']);
+ self::set_db($db, $connection_name);
+ }
+ }
+
+ /**
+ * Ensures configuration (mulitple connections) is at least set to default.
+ * @param string $connection_name Which connection to use
+ */
+ protected static function _setup_db_config($connection_name) {
+ if (!array_key_exists($connection_name, self::$_config)) {
+ self::$_config[$connection_name] = self::$_default_config;
}
}
/**
* Set the PDO object used by Idiorm to communicate with the database.
* This is public in case the ORM should use a ready-instantiated
- * PDO object as its database connection.
+ * PDO object as its database connection. Accepts an optional string key
+ * to identify the connection if multiple connections are used.
+ * @param ORM $db
+ * @param string $connection_name Which connection to use
*/
- public static function set_db($db) {
- self::$_db = $db;
- self::_setup_identifier_quote_character();
+ public static function set_db($db, $connection_name = self::DEFAULT_CONNECTION) {
+ self::_setup_db_config($connection_name);
+ self::$_db[$connection_name] = $db;
+ self::_setup_identifier_quote_character($connection_name);
}
/**
@@ -218,24 +262,29 @@
* (table names, column names etc). If this has been specified
* manually using ORM::configure('identifier_quote_character', 'some-char'),
* this will do nothing.
+ * @param string $connection_name Which connection to use
*/
- public static function _setup_identifier_quote_character() {
- if (is_null(self::$_config['identifier_quote_character'])) {
- self::$_config['identifier_quote_character'] = self::_detect_identifier_quote_character();
+ protected static function _setup_identifier_quote_character($connection_name) {
+ if (is_null(self::$_config[$connection_name]['identifier_quote_character'])) {
+ self::$_config[$connection_name]['identifier_quote_character'] =
+ self::_detect_identifier_quote_character($connection_name);
}
}
/**
* Return the correct character used to quote identifiers (table
* names, column names etc) by looking at the driver being used by PDO.
+ * @param string $connection_name Which connection to use
+ * @return string
*/
- protected static function _detect_identifier_quote_character() {
- switch(self::$_db->getAttribute(PDO::ATTR_DRIVER_NAME)) {
+ protected static function _detect_identifier_quote_character($connection_name) {
+ switch(self::$_db[$connection_name]->getAttribute(PDO::ATTR_DRIVER_NAME)) {
case 'pgsql':
case 'sqlsrv':
case 'dblib':
case 'mssql':
case 'sybase':
+ case 'firebird':
return '"';
case 'mysql':
case 'sqlite':
@@ -248,11 +297,57 @@
/**
* Returns the PDO instance used by the the ORM to communicate with
* the database. This can be called if any low-level DB access is
- * required outside the class.
+ * required outside the class. If multiple connections are used,
+ * accepts an optional key name for the connection.
+ * @param string $connection_name Which connection to use
+ * @return ORM
*/
- public static function get_db() {
- self::_setup_db(); // required in case this is called before Idiorm is instantiated
- return self::$_db;
+ public static function get_db($connection_name = self::DEFAULT_CONNECTION) {
+ self::_setup_db($connection_name); // required in case this is called before Idiorm is instantiated
+ return self::$_db[$connection_name];
+ }
+
+ /**
+ * Executes a raw query as a wrapper for PDOStatement::execute.
+ * Useful for queries that can't be accomplished through Idiorm,
+ * particularly those using engine-specific features.
+ * @example raw_execute('SELECT `name`, AVG(`order`) FROM `customer` GROUP BY `name` HAVING AVG(`order`) > 10')
+ * @example raw_execute('INSERT OR REPLACE INTO `widget` (`id`, `name`) SELECT `id`, `name` FROM `other_table`')
+ * @param string $query The raw SQL query
+ * @param array $parameters Optional bound parameters
+ * @param string $connection_name Which connection to use
+ * @return bool Success
+ */
+ public static function raw_execute($query, $parameters = array(), $connection_name = self::DEFAULT_CONNECTION) {
+ self::_setup_db($connection_name);
+ return self::_execute($query, $parameters, $connection_name);
+ }
+
+ /**
+ * Returns the PDOStatement instance last used by any connection wrapped by the ORM.
+ * Useful for access to PDOStatement::rowCount() or error information
+ * @return PDOStatement
+ */
+ public static function get_last_statement() {
+ return self::$_last_statement;
+ }
+
+ /**
+ * Internal helper method for executing statments. Logs queries, and
+ * stores statement object in ::_last_statment, accessible publicly
+ * through ::get_last_statement()
+ * @param string $query
+ * @param array $parameters An array of parameters to be bound in to the query
+ * @param string $connection_name Which connection to use
+ * @return bool Response of PDOStatement::execute()
+ */
+ protected static function _execute($query, $parameters = array(), $connection_name = self::DEFAULT_CONNECTION) {
+ self::_log_query($query, $parameters, $connection_name);
+ $statement = self::$_db[$connection_name]->prepare($query);
+
+ self::$_last_statement = $statement;
+
+ return $statement->execute($parameters);
}
/**
@@ -263,16 +358,24 @@
* query isn't executed like this (PDO normally passes the query and
* parameters to the database which takes care of the binding) but
* doing it this way makes the logged queries more readable.
+ * @param string $query
+ * @param array $parameters An array of parameters to be bound in to the query
+ * @param string $connection_name Which connection to use
+ * @return bool
*/
- protected static function _log_query($query, $parameters) {
+ protected static function _log_query($query, $parameters, $connection_name) {
// If logging is not enabled, do nothing
- if (!self::$_config['logging']) {
+ if (!self::$_config[$connection_name]['logging']) {
return false;
}
+ if (!isset(self::$_query_log[$connection_name])) {
+ self::$_query_log[$connection_name] = array();
+ }
+
if (count($parameters) > 0) {
// Escape the parameters
- $parameters = array_map(array(self::$_db, 'quote'), $parameters);
+ $parameters = array_map(array(self::$_db[$connection_name], 'quote'), $parameters);
// Avoid %format collision for vsprintf
$query = str_replace("%", "%%", $query);
@@ -291,26 +394,50 @@
}
self::$_last_query = $bound_query;
- self::$_query_log[] = $bound_query;
+ self::$_query_log[$connection_name][] = $bound_query;
return true;
}
/**
* Get the last query executed. Only works if the
* 'logging' config option is set to true. Otherwise
- * this will return null.
+ * this will return null. Returns last query from all connections if
+ * no connection_name is specified
+ * @param null|string $connection_name Which connection to use
+ * @return string
+ */
+ public static function get_last_query($connection_name = null) {
+ if ($connection_name === null) {
+ return self::$_last_query;
+ }
+ if (!isset(self::$_query_log[$connection_name])) {
+ return '';
+ }
+
+ return implode('', array_slice(self::$_query_log[$connection_name], -1));
+ // Used implode(array_slice()) instead of end() to avoid resetting interal array pointer
+ }
+
+ /**
+ * Get an array containing all the queries run on a
+ * specified connection up to now.
+ * Only works if the 'logging' config option is
+ * set to true. Otherwise, returned array will be empty.
+ * @param string $connection_name Which connection to use
*/
- public static function get_last_query() {
- return self::$_last_query;
+ public static function get_query_log($connection_name = self::DEFAULT_CONNECTION) {
+ if (isset(self::$_query_log[$connection_name])) {
+ return self::$_query_log[$connection_name];
+ }
+ return array();
}
/**
- * Get an array containing all the queries run up to
- * now. Only works if the 'logging' config option is
- * set to true. Otherwise returned array will be empty.
+ * Get a list of the available connection names
+ * @return array
*/
- public static function get_query_log() {
- return self::$_query_log;
+ public static function get_connection_names() {
+ return array_keys(self::$_db);
}
// ------------------------ //
@@ -321,9 +448,12 @@
* "Private" constructor; shouldn't be called directly.
* Use the ORM::for_table factory method instead.
*/
- protected function __construct($table_name, $data=array()) {
+ protected function __construct($table_name, $data = array(), $connection_name = self::DEFAULT_CONNECTION) {
$this->_table_name = $table_name;
$this->_data = $data;
+
+ $this->_connection_name = $connection_name;
+ self::_setup_db_config($connection_name);
}
/**
@@ -360,7 +490,7 @@
* array of data fetched from the database)
*/
protected function _create_instance_from_row($row) {
- $instance = self::for_table($this->_table_name);
+ $instance = self::for_table($this->_table_name, $this->_connection_name);
$instance->use_id_column($this->_instance_id_column);
$instance->hydrate($row);
return $instance;
@@ -394,14 +524,39 @@
* from your query, and execute it. Will return an array
* of instances of the ORM class, or an empty array if
* no rows were returned.
+ * @return array|\IdiormResultSet
*/
public function find_many() {
+ if(self::$_config[$this->_connection_name]['return_result_sets']) {
+ return $this->find_result_set();
+ }
+ return $this->_find_many();
+ }
+
+ /**
+ * Tell the ORM that you are expecting multiple results
+ * from your query, and execute it. Will return an array
+ * of instances of the ORM class, or an empty array if
+ * no rows were returned.
+ * @return array
+ */
+ protected function _find_many() {
$rows = $this->_run();
return array_map(array($this, '_create_instance_from_row'), $rows);
}
/**
* Tell the ORM that you are expecting multiple results
+ * from your query, and execute it. Will return a result set object
+ * containing instances of the ORM class.
+ * @return \IdiormResultSet
+ */
+ public function find_result_set() {
+ return new IdiormResultSet($this->_find_many());
+ }
+
+ /**
+ * Tell the ORM that you are expecting multiple results
* from your query, and execute it. Will return an array,
* or an empty array if no rows were returned.
* @return array
@@ -465,7 +620,16 @@
}
$this->select_expr("$sql_function($column)", $alias);
$result = $this->find_one();
- return ($result !== false && isset($result->$alias)) ? (int) $result->$alias : 0;
+
+ $return_value = 0;
+ if($result !== false && isset($result->$alias)) {
+ if((int) $result->$alias == (float) $result->$alias) {
+ $return_value = (int) $result->$alias;
+ } else {
+ $return_value = (float) $result->$alias;
+ }
+ }
+ return $return_value;
}
/**
@@ -723,33 +887,62 @@
}
/**
+ * Internal method to add a HAVING condition to the query
+ */
+ protected function _add_having($fragment, $values=array()) {
+ return $this->_add_condition('having', $fragment, $values);
+ }
+
+ /**
+ * Internal method to add a HAVING condition to the query
+ */
+ protected function _add_simple_having($column_name, $separator, $value) {
+ return $this->_add_simple_condition('having', $column_name, $separator, $value);
+ }
+
+ /**
* Internal method to add a WHERE condition to the query
*/
protected function _add_where($fragment, $values=array()) {
+ return $this->_add_condition('where', $fragment, $values);
+ }
+
+ /**
+ * Internal method to add a WHERE condition to the query
+ */
+ protected function _add_simple_where($column_name, $separator, $value) {
+ return $this->_add_simple_condition('where', $column_name, $separator, $value);
+ }
+
+ /**
+ * Internal method to add a HAVING or WHERE condition to the query
+ */
+ protected function _add_condition($type, $fragment, $values=array()) {
+ $conditions_class_property_name = "_{$type}_conditions";
if (!is_array($values)) {
$values = array($values);
}
- $this->_where_conditions[] = array(
- self::WHERE_FRAGMENT => $fragment,
- self::WHERE_VALUES => $values,
- );
+ array_push($this->$conditions_class_property_name, array(
+ self::CONDITION_FRAGMENT => $fragment,
+ self::CONDITION_VALUES => $values,
+ ));
return $this;
}
- /**
+ /**
* Helper method to compile a simple COLUMN SEPARATOR VALUE
- * style WHERE condition into a string and value ready to
- * be passed to the _add_where method. Avoids duplication
+ * style HAVING or WHERE condition into a string and value ready to
+ * be passed to the _add_condition method. Avoids duplication
* of the call to _quote_identifier
*/
- protected function _add_simple_where($column_name, $separator, $value) {
+ protected function _add_simple_condition($type, $column_name, $separator, $value) {
// Add the table name in case of ambiguous columns
if (count($this->_join_sources) > 0 && strpos($column_name, '.') === false) {
$column_name = "{$this->_table_name}.{$column_name}";
}
$column_name = $this->_quote_identifier($column_name);
- return $this->_add_where("{$column_name} {$separator} ?", $value);
- }
+ return $this->_add_condition($type, "{$column_name} {$separator} ?", $value);
+ }
/**
* Return a string containing the given number of question marks,
@@ -952,6 +1145,123 @@
}
/**
+ * Add a HAVING column = value clause to your query. Each time
+ * this is called in the chain, an additional HAVING will be
+ * added, and these will be ANDed together when the final query
+ * is built.
+ */
+ public function having($column_name, $value) {
+ return $this->having_equal($column_name, $value);
+ }
+
+ /**
+ * More explicitly named version of for the having() method.
+ * Can be used if preferred.
+ */
+ public function having_equal($column_name, $value) {
+ return $this->_add_simple_having($column_name, '=', $value);
+ }
+
+ /**
+ * Add a HAVING column != value clause to your query.
+ */
+ public function having_not_equal($column_name, $value) {
+ return $this->_add_simple_having($column_name, '!=', $value);
+ }
+
+ /**
+ * Special method to query the table by its primary key
+ */
+ public function having_id_is($id) {
+ return $this->having($this->_get_id_column_name(), $id);
+ }
+
+ /**
+ * Add a HAVING ... LIKE clause to your query.
+ */
+ public function having_like($column_name, $value) {
+ return $this->_add_simple_having($column_name, 'LIKE', $value);
+ }
+
+ /**
+ * Add where HAVING ... NOT LIKE clause to your query.
+ */
+ public function having_not_like($column_name, $value) {
+ return $this->_add_simple_having($column_name, 'NOT LIKE', $value);
+ }
+
+ /**
+ * Add a HAVING ... > clause to your query
+ */
+ public function having_gt($column_name, $value) {
+ return $this->_add_simple_having($column_name, '>', $value);
+ }
+
+ /**
+ * Add a HAVING ... < clause to your query
+ */
+ public function having_lt($column_name, $value) {
+ return $this->_add_simple_having($column_name, '<', $value);
+ }
+
+ /**
+ * Add a HAVING ... >= clause to your query
+ */
+ public function having_gte($column_name, $value) {
+ return $this->_add_simple_having($column_name, '>=', $value);
+ }
+
+ /**
+ * Add a HAVING ... <= clause to your query
+ */
+ public function having_lte($column_name, $value) {
+ return $this->_add_simple_having($column_name, '<=', $value);
+ }
+
+ /**
+ * Add a HAVING ... IN clause to your query
+ */
+ public function having_in($column_name, $values) {
+ $column_name = $this->_quote_identifier($column_name);
+ $placeholders = $this->_create_placeholders($values);
+ return $this->_add_having("{$column_name} IN ({$placeholders})", $values);
+ }
+
+ /**
+ * Add a HAVING ... NOT IN clause to your query
+ */
+ public function having_not_in($column_name, $values) {
+ $column_name = $this->_quote_identifier($column_name);
+ $placeholders = $this->_create_placeholders($values);
+ return $this->_add_having("{$column_name} NOT IN ({$placeholders})", $values);
+ }
+
+ /**
+ * Add a HAVING column IS NULL clause to your query
+ */
+ public function having_null($column_name) {
+ $column_name = $this->_quote_identifier($column_name);
+ return $this->_add_having("{$column_name} IS NULL");
+ }
+
+ /**
+ * Add a HAVING column IS NOT NULL clause to your query
+ */
+ public function having_not_null($column_name) {
+ $column_name = $this->_quote_identifier($column_name);
+ return $this->_add_having("{$column_name} IS NOT NULL");
+ }
+
+ /**
+ * Add a raw HAVING clause to the query. The clause should
+ * contain question mark placeholders, which will be bound
+ * to the parameters supplied in the second argument.
+ */
+ public function having_raw($clause, $parameters=array()) {
+ return $this->_add_having($clause, $parameters);
+ }
+
+ /**
* Build a SELECT statement based on the clauses that have
* been passed to this instance by chaining method calls.
*/
@@ -970,6 +1280,7 @@
$this->_build_join(),
$this->_build_where(),
$this->_build_group_by(),
+ $this->_build_having(),
$this->_build_order_by(),
$this->_build_limit(),
$this->_build_offset(),
@@ -1009,18 +1320,14 @@
* Build the WHERE clause(s)
*/
protected function _build_where() {
- // If there are no WHERE clauses, return empty string
- if (count($this->_where_conditions) === 0) {
- return '';
- }
-
- $where_conditions = array();
- foreach ($this->_where_conditions as $condition) {
- $where_conditions[] = $condition[self::WHERE_FRAGMENT];
- $this->_values = array_merge($this->_values, $condition[self::WHERE_VALUES]);
- }
+ return $this->_build_conditions('where');
+ }
- return "WHERE " . join(" AND ", $where_conditions);
+ /**
+ * Build the HAVING clause(s)
+ */
+ protected function _build_having() {
+ return $this->_build_conditions('having');
}
/**
@@ -1034,6 +1341,27 @@
}
/**
+ * Build a WHERE or HAVING clause
+ * @param string $type
+ * @return string
+ */
+ protected function _build_conditions($type) {
+ $conditions_class_property_name = "_{$type}_conditions";
+ // If there are no clauses, return empty string
+ if (count($this->$conditions_class_property_name) === 0) {
+ return '';
+ }
+
+ $conditions = array();
+ foreach ($this->$conditions_class_property_name as $condition) {
+ $conditions[] = $condition[self::CONDITION_FRAGMENT];
+ $this->_values = array_merge($this->_values, $condition[self::CONDITION_VALUES]);
+ }
+
+ return strtoupper($type) . " " . join(" AND ", $conditions);
+ }
+
+ /**
* Build ORDER BY
*/
protected function _build_order_by() {
@@ -1048,7 +1376,11 @@
*/
protected function _build_limit() {
if (!is_null($this->_limit)) {
- return "LIMIT " . $this->_limit;
+ $clause = 'LIMIT';
+ if (self::$_db[$this->_connection_name]->getAttribute(PDO::ATTR_DRIVER_NAME) == 'firebird') {
+ $clause = 'ROWS';
+ }
+ return "$clause " . $this->_limit;
}
return '';
}
@@ -1058,7 +1390,11 @@
*/
protected function _build_offset() {
if (!is_null($this->_offset)) {
- return "OFFSET " . $this->_offset;
+ $clause = 'OFFSET';
+ if (self::$_db[$this->_connection_name]->getAttribute(PDO::ATTR_DRIVER_NAME) == 'firebird') {
+ $clause = 'TO';
+ }
+ return "$clause " . $this->_offset;
}
return '';
}
@@ -1100,8 +1436,14 @@
if ($part === '*') {
return $part;
}
- $quote_character = self::$_config['identifier_quote_character'];
- return $quote_character . $part . $quote_character;
+
+ $quote_character = self::$_config[$this->_connection_name]['identifier_quote_character'];
+ // double up any identifier quotes to escape them
+ return $quote_character .
+ str_replace($quote_character,
+ $quote_character . $quote_character,
+ $part
+ ) . $quote_character;
}
/**
@@ -1117,9 +1459,9 @@
* Check the query cache for the given cache key. If a value
* is cached for the key, return the value. Otherwise, return false.
*/
- protected static function _check_query_cache($cache_key) {
- if (isset(self::$_query_cache[$cache_key])) {
- return self::$_query_cache[$cache_key];
+ protected static function _check_query_cache($cache_key, $connection_name = self::DEFAULT_CONNECTION) {
+ if (isset(self::$_query_cache[$connection_name][$cache_key])) {
+ return self::$_query_cache[$connection_name][$cache_key];
}
return false;
}
@@ -1134,8 +1476,11 @@
/**
* Add the given value to the query cache.
*/
- protected static function _cache_query_result($cache_key, $value) {
- self::$_query_cache[$cache_key] = $value;
+ protected static function _cache_query_result($cache_key, $value, $connection_name = self::DEFAULT_CONNECTION) {
+ if (!isset(self::$_query_cache[$connection_name])) {
+ self::$_query_cache[$connection_name] = array();
+ }
+ self::$_query_cache[$connection_name][$cache_key] = $value;
}
/**
@@ -1144,20 +1489,19 @@
*/
protected function _run() {
$query = $this->_build_select();
- $caching_enabled = self::$_config['caching'];
+ $caching_enabled = self::$_config[$this->_connection_name]['caching'];
if ($caching_enabled) {
$cache_key = self::_create_cache_key($query, $this->_values);
- $cached_result = self::_check_query_cache($cache_key);
+ $cached_result = self::_check_query_cache($cache_key, $this->_connection_name);
if ($cached_result !== false) {
return $cached_result;
}
}
- self::_log_query($query, $this->_values);
- $statement = self::$_db->prepare($query);
- $statement->execute($this->_values);
+ self::_execute($query, $this->_values, $this->_connection_name);
+ $statement = self::get_last_statement();
$rows = array();
while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
@@ -1165,9 +1509,14 @@
}
if ($caching_enabled) {
- self::_cache_query_result($cache_key, $rows);
+ self::_cache_query_result($cache_key, $rows, $this->_connection_name);
}
+ // reset Idiorm after executing the query
+ $this->_values = array();
+ $this->_result_columns = array('*');
+ $this->_using_default_result_columns = true;
+
return $rows;
}
@@ -1201,11 +1550,10 @@
if (!is_null($this->_instance_id_column)) {
return $this->_instance_id_column;
}
- if (isset(self::$_config['id_column_overrides'][$this->_table_name])) {
- return self::$_config['id_column_overrides'][$this->_table_name];
- } else {
- return self::$_config['id_column'];
+ if (isset(self::$_config[$this->_connection_name]['id_column_overrides'][$this->_table_name])) {
+ return self::$_config[$this->_connection_name]['id_column_overrides'][$this->_table_name];
}
+ return self::$_config[$this->_connection_name]['id_column'];
}
/**
@@ -1226,6 +1574,15 @@
$this->_set_orm_property($key, $value);
}
+ /**
+ * Set a property to a particular value on this object.
+ * To set multiple properties at once, pass an associative array
+ * as the first parameter and leave out the second parameter.
+ * Flags the properties as 'dirty' so they will be saved to the
+ * database when save() is called.
+ * @param string|array $key
+ * @param string|null $value
+ */
public function set_expr($key, $value = null) {
$this->_set_orm_property($key, $value, true);
}
@@ -1260,6 +1617,14 @@
}
/**
+ * Check whether the model was the result of a call to create() or not
+ * @return bool
+ */
+ public function is_new() {
+ return $this->_is_new;
+ }
+
+ /**
* Save any fields which have been modified on this object
* to the database.
*/
@@ -1271,7 +1636,7 @@
if (!$this->_is_new) { // UPDATE
// If there are no dirty values, do nothing
- if (count($values) == 0) {
+ if (empty($values) && empty($this->_expr_fields)) {
return true;
}
$query = $this->_build_update();
@@ -1280,15 +1645,17 @@
$query = $this->_build_insert();
}
- self::_log_query($query, $values);
- $statement = self::$_db->prepare($query);
- $success = $statement->execute($values);
+ $success = self::_execute($query, $values, $this->_connection_name);
// If we've just inserted a new record, set the ID of this object
if ($this->_is_new) {
$this->_is_new = false;
if (is_null($this->id())) {
- $this->_data[$this->_get_id_column_name()] = self::$_db->lastInsertId();
+ if(self::$_db[$this->_connection_name]->getAttribute(PDO::ATTR_DRIVER_NAME) == 'pgsql') {
+ $this->_data[$this->_get_id_column_name()] = self::get_last_statement()->fetchColumn();
+ } else {
+ $this->_data[$this->_get_id_column_name()] = self::$_db[$this->_connection_name]->lastInsertId();
+ }
}
}
@@ -1329,6 +1696,11 @@
$placeholders = $this->_create_placeholders($this->_dirty_fields);
$query[] = "({$placeholders})";
+
+ if (self::$_db[$this->_connection_name]->getAttribute(PDO::ATTR_DRIVER_NAME) == 'pgsql') {
+ $query[] = 'RETURNING ' . $this->_quote_identifier($this->_get_id_column_name());
+ }
+
return join(" ", $query);
}
@@ -1343,10 +1715,8 @@
$this->_quote_identifier($this->_get_id_column_name()),
"= ?",
));
- $params = array($this->id());
- self::_log_query($query, $params);
- $statement = self::$_db->prepare($query);
- return $statement->execute($params);
+
+ return self::_execute($query, array($this->id()), $this->_connection_name);
}
/**
@@ -1360,29 +1730,52 @@
$this->_quote_identifier($this->_table_name),
$this->_build_where(),
));
- $statement = self::$_db->prepare($query);
- return $statement->execute($this->_values);
+
+ return self::_execute($query, $this->_values, $this->_connection_name);
}
// --------------------- //
- // --- MAGIC METHODS --- //
+ // --- ArrayAccess --- //
// --------------------- //
- public function __get($key) {
+
+ public function offsetExists($key) {
+ return isset($this->_data[$key]);
+ }
+
+ public function offsetGet($key) {
return $this->get($key);
}
- public function __set($key, $value) {
+ public function offsetSet($key, $value) {
+ if(is_null($key)) {
+ throw new InvalidArgumentException('You must specify a key/array index.');
+ }
$this->set($key, $value);
}
- public function __unset($key) {
+ public function offsetUnset($key) {
unset($this->_data[$key]);
unset($this->_dirty_fields[$key]);
}
+ // --------------------- //
+ // --- MAGIC METHODS --- //
+ // --------------------- //
+ public function __get($key) {
+ return $this->offsetGet($key);
+ }
+
+ public function __set($key, $value) {
+ $this->offsetSet($key, $value);
+ }
+
+ public function __unset($key) {
+ $this->offsetUnset($key);
+ }
+
public function __isset($key) {
- return isset($this->_data[$key]);
+ return $this->offsetExists($key);
}
}
@@ -1491,6 +1884,135 @@
}
/**
+ * A result set class for working with collections of model instances
+ * @author Simon Holywell <[email protected]>
+ */
+ class IdiormResultSet implements Countable, IteratorAggregate, ArrayAccess, Serializable {
+ /**
+ * The current result set as an array
+ * @var array
+ */
+ protected $_results = array();
+
+ /**
+ * Optionally set the contents of the result set by passing in array
+ * @param array $results
+ */
+ public function __construct(array $results = array()) {
+ $this->set_results($results);
+ }
+
+ /**
+ * Set the contents of the result set by passing in array
+ * @param array $results
+ */
+ public function set_results(array $results) {
+ $this->_results = $results;
+ }
+
+ /**
+ * Get the current result set as an array
+ * @return array
+ */
+ public function get_results() {
+ return $this->_results;
+ }
+
+ /**
+ * Get the current result set as an array
+ * @return array
+ */
+ public function as_array() {
+ return $this->get_results();
+ }
+
+ /**
+ * Get the number of records in the result set
+ * @return int
+ */
+ public function count() {
+ return count($this->_results);
+ }
+
+ /**
+ * Get an iterator for this object. In this case it supports foreaching
+ * over the result set.
+ * @return \ArrayIterator
+ */
+ public function getIterator() {
+ return new ArrayIterator($this->_results);
+ }
+
+ /**
+ * ArrayAccess
+ * @param int|string $offset
+ * @return bool
+ */
+ public function offsetExists($offset) {
+ return isset($this->_results[$offset]);
+ }
+
+ /**
+ * ArrayAccess
+ * @param int|string $offset
+ * @return mixed
+ */
+ public function offsetGet($offset) {
+ return $this->_results[$offset];
+ }
+
+ /**
+ * ArrayAccess
+ * @param int|string $offset
+ * @param mixed $value
+ */
+ public function offsetSet($offset, $value) {
+ $this->_results[$offset] = $value;
+ }
+
+ /**
+ * ArrayAccess
+ * @param int|string $offset
+ */
+ public function offsetUnset($offset) {
+ unset($this->_results[$offset]);
+ }
+
+ /**
+ * Serializable
+ * @return string
+ */
+ public function serialize() {
+ return serialize($this->_results);
+ }
+
+ /**
+ * Serializable
+ * @param string $serialized
+ * @return array
+ */
+ public function unserialize($serialized) {
+ return unserialize($serialized);
+ }
+
+ /**
+ * Call a method on all models in a result set. This allows for method
+ * chaining such as setting a property on all models in a result set or
+ * any other batch operation across models.
+ * @example ORM::for_table('Widget')->find_many()->set('field', 'value')->save();
+ * @param string $method
+ * @param array $params
+ * @return \IdiormResultSet
+ */
+ public function __call($method, $params = array()) {
+ foreach($this->_results as $model) {
+ call_user_func_array(array($model, $method), $params);
+ }
+ return $this;
+ }
+ }
+
+ /**
* A placeholder for exceptions eminating from the IdiormString class
*/
class IdiormStringException extends Exception {}
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..4718e62
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,11 @@
+<phpunit backupGlobals="true"
+ backupStaticAttributes="false"
+ bootstrap="test/bootstrap.php"
+ cacheTokens="false"
+ colors="true">
+ <testsuites>
+ <testsuite name="Idiorm Test Suite">
+ <directory suffix="Test.php">test</directory>
+ </testsuite>
+ </testsuites>
+</phpunit> \ No newline at end of file
diff --git a/test/CacheTest.php b/test/CacheTest.php
new file mode 100644
index 0000000..8479d13
--- /dev/null
+++ b/test/CacheTest.php
@@ -0,0 +1,45 @@
+<?php
+
+class CacheTest extends PHPUnit_Framework_TestCase {
+
+ const ALTERNATE = 'alternate'; // Used as name of alternate connection
+
+ public function setUp() {
+ // Set up the dummy database connections
+ ORM::set_db(new MockPDO('sqlite::memory:'));
+ ORM::set_db(new MockDifferentPDO('sqlite::memory:'), self::ALTERNATE);
+
+ // Enable logging
+ ORM::configure('logging', true);
+ ORM::configure('logging', true, self::ALTERNATE);
+ ORM::configure('caching', true);
+ ORM::configure('caching', true, self::ALTERNATE);
+ }
+
+ public function tearDown() {
+ ORM::configure('logging', false);
+ ORM::configure('logging', false, self::ALTERNATE);
+ ORM::configure('caching', false);
+ ORM::configure('caching', false, self::ALTERNATE);
+ ORM::set_db(null);
+ ORM::set_db(null, self::ALTERNATE);
+ }
+
+ // Test caching. This is a bit of a hack.
+ public function testQueryGenerationOnlyOccursOnce() {
+ ORM::for_table('widget')->where('name', 'Fred')->where('age', 17)->find_one();
+ ORM::for_table('widget')->where('name', 'Bob')->where('age', 42)->find_one();
+ $expected = ORM::get_last_query();
+ ORM::for_table('widget')->where('name', 'Fred')->where('age', 17)->find_one(); // this shouldn't run a query!
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testQueryGenerationOnlyOccursOnceWithMultipleConnections() {
+ // Test caching with multiple connections (also a bit of a hack)
+ ORM::for_table('widget', self::ALTERNATE)->where('name', 'Steve')->where('age', 80)->find_one();
+ ORM::for_table('widget', self::ALTERNATE)->where('name', 'Tom')->where('age', 120)->find_one();
+ $expected = ORM::get_last_query();
+ ORM::for_table('widget', self::ALTERNATE)->where('name', 'Steve')->where('age', 80)->find_one(); // this shouldn't run a query!
+ $this->assertEquals($expected, ORM::get_last_query(self::ALTERNATE));
+ }
+} \ No newline at end of file
diff --git a/test/ConfigTest.php b/test/ConfigTest.php
new file mode 100644
index 0000000..83194b4
--- /dev/null
+++ b/test/ConfigTest.php
@@ -0,0 +1,100 @@
+<?php
+
+class ConfigTest extends PHPUnit_Framework_TestCase {
+
+ public function setUp() {
+ // Enable logging
+ ORM::configure('logging', true);
+
+ // Set up the dummy database connection
+ $db = new MockPDO('sqlite::memory:');
+ ORM::set_db($db);
+
+ ORM::configure('id_column', 'primary_key');
+ }
+
+ public function tearDown() {
+ ORM::configure('logging', false);
+ ORM::set_db(null);
+
+ ORM::configure('id_column', 'id');
+ }
+
+ protected function setUpIdColumnOverrides() {
+ ORM::configure('id_column_overrides', array(
+ 'widget' => 'widget_id',
+ 'widget_handle' => 'widget_handle_id',
+ ));
+ }
+
+ protected function tearDownIdColumnOverrides() {
+ ORM::configure('id_column_overrides', array());
+ }
+
+ public function testSettingIdColumn() {
+ ORM::for_table('widget')->find_one(5);
+ $expected = "SELECT * FROM `widget` WHERE `primary_key` = '5' LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testSettingIdColumnOverridesOne() {
+ $this->setUpIdColumnOverrides();
+
+ ORM::for_table('widget')->find_one(5);
+ $expected = "SELECT * FROM `widget` WHERE `widget_id` = '5' LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+
+ $this->tearDownIdColumnOverrides();
+ }
+
+ public function testSettingIdColumnOverridesTwo() {
+ $this->setUpIdColumnOverrides();
+
+ ORM::for_table('widget_handle')->find_one(5);
+ $expected = "SELECT * FROM `widget_handle` WHERE `widget_handle_id` = '5' LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+
+ $this->tearDownIdColumnOverrides();
+ }
+
+ public function testSettingIdColumnOverridesThree() {
+ $this->setUpIdColumnOverrides();
+
+ ORM::for_table('widget_nozzle')->find_one(5);
+ $expected = "SELECT * FROM `widget_nozzle` WHERE `primary_key` = '5' LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+
+ $this->tearDownIdColumnOverrides();
+ }
+
+ public function testInstanceIdColumnOne() {
+ $this->setUpIdColumnOverrides();
+
+ ORM::for_table('widget')->use_id_column('new_id')->find_one(5);
+ $expected = "SELECT * FROM `widget` WHERE `new_id` = '5' LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+
+ $this->tearDownIdColumnOverrides();
+ }
+
+ public function testInstanceIdColumnTwo() {
+ $this->setUpIdColumnOverrides();
+
+ ORM::for_table('widget_handle')->use_id_column('new_id')->find_one(5);
+ $expected = "SELECT * FROM `widget_handle` WHERE `new_id` = '5' LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+
+ $this->tearDownIdColumnOverrides();
+ }
+
+ public function testInstanceIdColumnThree() {
+ $this->setUpIdColumnOverrides();
+
+ ORM::for_table('widget_nozzle')->use_id_column('new_id')->find_one(5);
+ $expected = "SELECT * FROM `widget_nozzle` WHERE `new_id` = '5' LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+
+ $this->tearDownIdColumnOverrides();
+ }
+
+} \ No newline at end of file
diff --git a/test/IdiormResultSetTest.php b/test/IdiormResultSetTest.php
new file mode 100644
index 0000000..f41205a
--- /dev/null
+++ b/test/IdiormResultSetTest.php
@@ -0,0 +1,81 @@
+<?php
+
+class IdiormResultSetTest extends PHPUnit_Framework_TestCase {
+
+ public function setUp() {
+ // Enable logging
+ ORM::configure('logging', true);
+
+ // Set up the dummy database connection
+ $db = new MockPDO('sqlite::memory:');
+ ORM::set_db($db);
+ }
+
+ public function tearDown() {
+ ORM::configure('logging', false);
+ ORM::set_db(null);
+ }
+
+ public function testGet() {
+ $IdiormResultSet = new IdiormResultSet();
+ $this->assertInternalType('array', $IdiormResultSet->get_results());
+ }
+
+ public function testConstructor() {
+ $result_set = array('item' => new stdClass);
+ $IdiormResultSet = new IdiormResultSet($result_set);
+ $this->assertSame($IdiormResultSet->get_results(), $result_set);
+ }
+
+ public function testSetResultsAndGetResults() {
+ $result_set = array('item' => new stdClass);
+ $IdiormResultSet = new IdiormResultSet();
+ $IdiormResultSet->set_results($result_set);
+ $this->assertSame($IdiormResultSet->get_results(), $result_set);
+ }
+
+ public function testAsArray() {
+ $result_set = array('item' => new stdClass);
+ $IdiormResultSet = new IdiormResultSet();
+ $IdiormResultSet->set_results($result_set);
+ $this->assertSame($IdiormResultSet->as_array(), $result_set);
+ }
+
+ public function testCount() {
+ $result_set = array('item' => new stdClass);
+ $IdiormResultSet = new IdiormResultSet($result_set);
+ $this->assertSame($IdiormResultSet->count(), 1);
+ $this->assertSame(count($IdiormResultSet), 1);
+ }
+
+ public function testGetIterator() {
+ $result_set = array('item' => new stdClass);
+ $IdiormResultSet = new IdiormResultSet($result_set);
+ $this->assertInstanceOf('ArrayIterator', $IdiormResultSet->getIterator());
+ }
+
+ public function testForeach() {
+ $result_set = array('item' => new stdClass);
+ $IdiormResultSet = new IdiormResultSet($result_set);
+ $return_array = array();
+ foreach($IdiormResultSet as $key => $record) {
+ $return_array[$key] = $record;
+ }
+ $this->assertSame($result_set, $return_array);
+ }
+
+ public function testCallingMethods() {
+ $result_set = array('item' => ORM::for_table('test'), 'item2' => ORM::for_table('test'));
+ $IdiormResultSet = new IdiormResultSet($result_set);
+ $IdiormResultSet->set('field', 'value')->set('field2', 'value');
+
+ foreach($IdiormResultSet as $record) {
+ $this->assertTrue(isset($record->field));
+ $this->assertSame($record->field, 'value');
+
+ $this->assertTrue(isset($record->field2));
+ $this->assertSame($record->field2, 'value');
+ }
+ }
+
+} \ No newline at end of file
diff --git a/test/MulitpleConnectionTest.php b/test/MulitpleConnectionTest.php
new file mode 100644
index 0000000..9df05d0
--- /dev/null
+++ b/test/MulitpleConnectionTest.php
@@ -0,0 +1,54 @@
+<?php
+
+class MultipleConnectionTest extends PHPUnit_Framework_TestCase {
+
+ const ALTERNATE = 'alternate'; // Used as name of alternate connection
+
+ public function setUp() {
+ // Set up the dummy database connections
+ ORM::set_db(new MockPDO('sqlite::memory:'));
+ ORM::set_db(new MockDifferentPDO('sqlite::memory:'), self::ALTERNATE);
+
+ // Enable logging
+ ORM::configure('logging', true);
+ ORM::configure('logging', true, self::ALTERNATE);
+ }
+
+ public function tearDown() {
+ ORM::configure('logging', false);
+ ORM::configure('logging', false, self::ALTERNATE);
+ ORM::set_db(null);
+ ORM::set_db(null, self::ALTERNATE);
+ }
+
+ public function testMultiplePdoConnections() {
+ $this->assertInstanceOf('MockPDO', ORM::get_db());
+ $this->assertInstanceOf('MockPDO', ORM::get_db(ORM::DEFAULT_CONNECTION));
+ $this->assertInstanceOf('MockDifferentPDO', ORM::get_db(self::ALTERNATE));
+ }
+
+ public function testRawExecuteOverAlternateConnection() {
+ $expected = "SELECT * FROM `foo`";
+ ORM::raw_execute("SELECT * FROM `foo`", array(), self::ALTERNATE);
+
+ $this->assertEquals($expected, ORM::get_last_query(self::ALTERNATE));
+ }
+
+ public function testFindOneOverDifferentConnections() {
+ ORM::for_table('widget')->find_one();
+ $statementOne = ORM::get_last_statement();
+ $this->assertInstanceOf('MockPDOStatement', $statementOne);
+
+ ORM::for_table('person', self::ALTERNATE)->find_one();
+ $statementOne = ORM::get_last_statement(); // get_statement is *not* per connection
+ $this->assertInstanceOf('MockDifferentPDOStatement', $statementOne);
+
+ $expected = "SELECT * FROM `widget` LIMIT 1";
+ $this->assertNotEquals($expected, ORM::get_last_query()); // Because get_last_query() is across *all* connections
+ $this->assertEquals($expected, ORM::get_last_query(ORM::DEFAULT_CONNECTION));
+
+ $expectedToo = "SELECT * FROM `person` LIMIT 1";
+ $this->assertEquals($expectedToo, ORM::get_last_query(self::ALTERNATE));
+ }
+
+} \ No newline at end of file
diff --git a/test/ORMTest.php b/test/ORMTest.php
new file mode 100644
index 0000000..ced3cbd
--- /dev/null
+++ b/test/ORMTest.php
@@ -0,0 +1,87 @@
+<?php
+
+class ORMTest extends PHPUnit_Framework_TestCase {
+
+ public function setUp() {
+ // Enable logging
+ ORM::configure('logging', true);
+
+ // Set up the dummy database connection
+ $db = new MockPDO('sqlite::memory:');
+ ORM::set_db($db);
+ }
+
+ public function tearDown() {
+ ORM::configure('logging', false);
+ ORM::set_db(null);
+ }
+
+ public function testStaticAtrributes() {
+ $this->assertEquals('0', ORM::CONDITION_FRAGMENT);
+ $this->assertEquals('1', ORM::CONDITION_VALUES);
+ }
+
+ public function testForTable() {
+ $result = ORM::for_table('test');
+ $this->assertInstanceOf('ORM', $result);
+ }
+
+ public function testCreate() {
+ $model = ORM::for_table('test')->create();
+ $this->assertInstanceOf('ORM', $model);
+ $this->assertTrue($model->is_new());
+ }
+
+ public function testIsNew() {
+ $model = ORM::for_table('test')->create();
+ $this->assertTrue($model->is_new());
+
+ $model = ORM::for_table('test')->create(array('test' => 'test'));
+ $this->assertTrue($model->is_new());
+ }
+
+ public function testIsDirty() {
+ $model = ORM::for_table('test')->create();
+ $this->assertFalse($model->is_dirty('test'));
+
+ $model = ORM::for_table('test')->create(array('test' => 'test'));
+ $this->assertTrue($model->is_dirty('test'));
+ }
+
+ public function testArrayAccess() {
+ $value = 'test';
+ $model = ORM::for_table('test')->create();
+ $model['test'] = $value;
+ $this->assertTrue(isset($model['test']));
+ $this->assertEquals($model['test'], $value);
+ unset($model['test']);
+ $this->assertFalse(isset($model['test']));
+ }
+
+ public function testFindResultSet() {
+ $result_set = ORM::for_table('test')->find_result_set();
+ $this->assertInstanceOf('IdiormResultSet', $result_set);
+ $this->assertSame(count($result_set), 5);
+ }
+
+ public function testFindResultSetByDefault() {
+ ORM::configure('return_result_sets', true);
+
+ $result_set = ORM::for_table('test')->find_many();
+ $this->assertInstanceOf('IdiormResultSet', $result_set);
+ $this->assertSame(count($result_set), 5);
+
+ ORM::configure('return_result_sets', false);
+
+ $result_set = ORM::for_table('test')->find_many();
+ $this->assertInternalType('array', $result_set);
+ $this->assertSame(count($result_set), 5);
+ }
+
+ public function testGetLastPdoStatement() {
+ ORM::for_table('widget')->where('name', 'Fred')->find_one();
+ $statement = ORM::get_last_statement();
+ $this->assertInstanceOf('MockPDOStatement', $statement);
+ }
+
+} \ No newline at end of file
diff --git a/test/QueryBuilderTest.php b/test/QueryBuilderTest.php
new file mode 100644
index 0000000..bd888c0
--- /dev/null
+++ b/test/QueryBuilderTest.php
@@ -0,0 +1,542 @@
+<?php
+
+class QueryBuilderTest extends PHPUnit_Framework_TestCase {
+
+ public function setUp() {
+ // Enable logging
+ ORM::configure('logging', true);
+
+ // Set up the dummy database connection
+ $db = new MockPDO('sqlite::memory:');
+ ORM::set_db($db);
+ }
+
+ public function tearDown() {
+ ORM::configure('logging', false);
+ ORM::set_db(null);
+ }
+
+ public function testFindMany() {
+ ORM::for_table('widget')->find_many();
+ $expected = "SELECT * FROM `widget`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testFindOne() {
+ ORM::for_table('widget')->find_one();
+ $expected = "SELECT * FROM `widget` LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testFindOneWithPrimaryKeyFilter() {
+ ORM::for_table('widget')->find_one(5);
+ $expected = "SELECT * FROM `widget` WHERE `id` = '5' LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testWhereIdIs() {
+ ORM::for_table('widget')->where_id_is(5)->find_one();
+ $expected = "SELECT * FROM `widget` WHERE `id` = '5' LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testSingleWhereClause() {
+ ORM::for_table('widget')->where('name', 'Fred')->find_one();
+ $expected = "SELECT * FROM `widget` WHERE `name` = 'Fred' LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testMultipleWhereClauses() {
+ ORM::for_table('widget')->where('name', 'Fred')->where('age', 10)->find_one();
+ $expected = "SELECT * FROM `widget` WHERE `name` = 'Fred' AND `age` = '10' LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testWhereNotEqual() {
+ ORM::for_table('widget')->where_not_equal('name', 'Fred')->find_many();
+ $expected = "SELECT * FROM `widget` WHERE `name` != 'Fred'";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testWhereLike() {
+ ORM::for_table('widget')->where_like('name', '%Fred%')->find_one();
+ $expected = "SELECT * FROM `widget` WHERE `name` LIKE '%Fred%' LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testWhereNotLike() {
+ ORM::for_table('widget')->where_not_like('name', '%Fred%')->find_one();
+ $expected = "SELECT * FROM `widget` WHERE `name` NOT LIKE '%Fred%' LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testWhereIn() {
+ ORM::for_table('widget')->where_in('name', array('Fred', 'Joe'))->find_many();
+ $expected = "SELECT * FROM `widget` WHERE `name` IN ('Fred', 'Joe')";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testWhereNotIn() {
+ ORM::for_table('widget')->where_not_in('name', array('Fred', 'Joe'))->find_many();
+ $expected = "SELECT * FROM `widget` WHERE `name` NOT IN ('Fred', 'Joe')";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testLimit() {
+ ORM::for_table('widget')->limit(5)->find_many();
+ $expected = "SELECT * FROM `widget` LIMIT 5";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testLimitAndOffset() {
+ ORM::for_table('widget')->limit(5)->offset(5)->find_many();
+ $expected = "SELECT * FROM `widget` LIMIT 5 OFFSET 5";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testOrderByDesc() {
+ ORM::for_table('widget')->order_by_desc('name')->find_one();
+ $expected = "SELECT * FROM `widget` ORDER BY `name` DESC LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testOrderByAsc() {
+ ORM::for_table('widget')->order_by_asc('name')->find_one();
+ $expected = "SELECT * FROM `widget` ORDER BY `name` ASC LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testOrderByExpression() {
+ ORM::for_table('widget')->order_by_expr('SOUNDEX(`name`)')->find_one();
+ $expected = "SELECT * FROM `widget` ORDER BY SOUNDEX(`name`) LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testMultipleOrderBy() {
+ ORM::for_table('widget')->order_by_asc('name')->order_by_desc('age')->find_one();
+ $expected = "SELECT * FROM `widget` ORDER BY `name` ASC, `age` DESC LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testGroupBy() {
+ ORM::for_table('widget')->group_by('name')->find_many();
+ $expected = "SELECT * FROM `widget` GROUP BY `name`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testMultipleGroupBy() {
+ ORM::for_table('widget')->group_by('name')->group_by('age')->find_many();
+ $expected = "SELECT * FROM `widget` GROUP BY `name`, `age`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testGroupByExpression() {
+ ORM::for_table('widget')->group_by_expr("FROM_UNIXTIME(`time`, '%Y-%m')")->find_many();
+ $expected = "SELECT * FROM `widget` GROUP BY FROM_UNIXTIME(`time`, '%Y-%m')";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testHaving() {
+ ORM::for_table('widget')->group_by('name')->having('name', 'Fred')->find_one();
+ $expected = "SELECT * FROM `widget` GROUP BY `name` HAVING `name` = 'Fred' LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testMultipleHaving() {
+ ORM::for_table('widget')->group_by('name')->having('name', 'Fred')->having('age', 10)->find_one();
+ $expected = "SELECT * FROM `widget` GROUP BY `name` HAVING `name` = 'Fred' AND `age` = '10' LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testHavingNotEqual() {
+ ORM::for_table('widget')->group_by('name')->having_not_equal('name', 'Fred')->find_many();
+ $expected = "SELECT * FROM `widget` GROUP BY `name` HAVING `name` != 'Fred'";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testHavingLike() {
+ ORM::for_table('widget')->group_by('name')->having_like('name', '%Fred%')->find_one();
+ $expected = "SELECT * FROM `widget` GROUP BY `name` HAVING `name` LIKE '%Fred%' LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testHavingNotLike() {
+ ORM::for_table('widget')->group_by('name')->having_not_like('name', '%Fred%')->find_one();
+ $expected = "SELECT * FROM `widget` GROUP BY `name` HAVING `name` NOT LIKE '%Fred%' LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testHavingIn() {
+ ORM::for_table('widget')->group_by('name')->having_in('name', array('Fred', 'Joe'))->find_many();
+ $expected = "SELECT * FROM `widget` GROUP BY `name` HAVING `name` IN ('Fred', 'Joe')";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testHavingNotIn() {
+ ORM::for_table('widget')->group_by('name')->having_not_in('name', array('Fred', 'Joe'))->find_many();
+ $expected = "SELECT * FROM `widget` GROUP BY `name` HAVING `name` NOT IN ('Fred', 'Joe')";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testHavingLessThan() {
+ ORM::for_table('widget')->group_by('name')->having_lt('age', 10)->having_gt('age', 5)->find_many();
+ $expected = "SELECT * FROM `widget` GROUP BY `name` HAVING `age` < '10' AND `age` > '5'";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testHavingLessThanOrEqualAndGreaterThanOrEqual() {
+ ORM::for_table('widget')->group_by('name')->having_lte('age', 10)->having_gte('age', 5)->find_many();
+ $expected = "SELECT * FROM `widget` GROUP BY `name` HAVING `age` <= '10' AND `age` >= '5'";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testHavingNull() {
+ ORM::for_table('widget')->group_by('name')->having_null('name')->find_many();
+ $expected = "SELECT * FROM `widget` GROUP BY `name` HAVING `name` IS NULL";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testHavingNotNull() {
+ ORM::for_table('widget')->group_by('name')->having_not_null('name')->find_many();
+ $expected = "SELECT * FROM `widget` GROUP BY `name` HAVING `name` IS NOT NULL";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testRawHaving() {
+ ORM::for_table('widget')->group_by('name')->having_raw('`name` = ? AND (`age` = ? OR `age` = ?)', array('Fred', 5, 10))->find_many();
+ $expected = "SELECT * FROM `widget` GROUP BY `name` HAVING `name` = 'Fred' AND (`age` = '5' OR `age` = '10')";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testComplexQuery() {
+ ORM::for_table('widget')->where('name', 'Fred')->limit(5)->offset(5)->order_by_asc('name')->find_many();
+ $expected = "SELECT * FROM `widget` WHERE `name` = 'Fred' ORDER BY `name` ASC LIMIT 5 OFFSET 5";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testWhereLessThanAndGreaterThan() {
+ ORM::for_table('widget')->where_lt('age', 10)->where_gt('age', 5)->find_many();
+ $expected = "SELECT * FROM `widget` WHERE `age` < '10' AND `age` > '5'";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testWhereLessThanAndEqualAndGreaterThanAndEqual() {
+ ORM::for_table('widget')->where_lte('age', 10)->where_gte('age', 5)->find_many();
+ $expected = "SELECT * FROM `widget` WHERE `age` <= '10' AND `age` >= '5'";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testWhereNull() {
+ ORM::for_table('widget')->where_null('name')->find_many();
+ $expected = "SELECT * FROM `widget` WHERE `name` IS NULL";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testWhereNotNull() {
+ ORM::for_table('widget')->where_not_null('name')->find_many();
+ $expected = "SELECT * FROM `widget` WHERE `name` IS NOT NULL";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testRawWhereClause() {
+ ORM::for_table('widget')->where_raw('`name` = ? AND (`age` = ? OR `age` = ?)', array('Fred', 5, 10))->find_many();
+ $expected = "SELECT * FROM `widget` WHERE `name` = 'Fred' AND (`age` = '5' OR `age` = '10')";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testRawWhereClauseWithPercentSign() {
+ ORM::for_table('widget')->where_raw('STRFTIME("%Y", "now") = ?', array(2012))->find_many();
+ $expected = "SELECT * FROM `widget` WHERE STRFTIME(\"%Y\", \"now\") = '2012'";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testRawWhereClauseWithNoParameters() {
+ ORM::for_table('widget')->where_raw('`name` = "Fred"')->find_many();
+ $expected = "SELECT * FROM `widget` WHERE `name` = \"Fred\"";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testRawWhereClauseInMethodChain() {
+ ORM::for_table('widget')->where('age', 18)->where_raw('(`name` = ? OR `name` = ?)', array('Fred', 'Bob'))->where('size', 'large')->find_many();
+ $expected = "SELECT * FROM `widget` WHERE `age` = '18' AND (`name` = 'Fred' OR `name` = 'Bob') AND `size` = 'large'";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testRawQuery() {
+ ORM::for_table('widget')->raw_query('SELECT `w`.* FROM `widget` w')->find_many();
+ $expected = "SELECT `w`.* FROM `widget` w";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testRawQueryWithParameters() {
+ ORM::for_table('widget')->raw_query('SELECT `w`.* FROM `widget` w WHERE `name` = ? AND `age` = ?', array('Fred', 5))->find_many();
+ $expected = "SELECT `w`.* FROM `widget` w WHERE `name` = 'Fred' AND `age` = '5'";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testSimpleResultColumn() {
+ ORM::for_table('widget')->select('name')->find_many();
+ $expected = "SELECT `name` FROM `widget`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testMultipleSimpleResultColumns() {
+ ORM::for_table('widget')->select('name')->select('age')->find_many();
+ $expected = "SELECT `name`, `age` FROM `widget`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testSpecifyTableNameAndColumnInResultColumns() {
+ ORM::for_table('widget')->select('widget.name')->find_many();
+ $expected = "SELECT `widget`.`name` FROM `widget`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testMainTableAlias() {
+ ORM::for_table('widget')->table_alias('w')->find_many();
+ $expected = "SELECT * FROM `widget` `w`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testAliasesInResultColumns() {
+ ORM::for_table('widget')->select('widget.name', 'widget_name')->find_many();
+ $expected = "SELECT `widget`.`name` AS `widget_name` FROM `widget`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testAliasesInSelectManyResults() {
+ ORM::for_table('widget')->select_many(array('widget_name' => 'widget.name'), 'widget_handle')->find_many();
+ $expected = "SELECT `widget`.`name` AS `widget_name`, `widget_handle` FROM `widget`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testLiteralExpressionInResultColumn() {
+ ORM::for_table('widget')->select_expr('COUNT(*)', 'count')->find_many();
+ $expected = "SELECT COUNT(*) AS `count` FROM `widget`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testLiteralExpressionInSelectManyResultColumns() {
+ ORM::for_table('widget')->select_many_expr(array('count' => 'COUNT(*)'), 'SUM(widget_order)')->find_many();
+ $expected = "SELECT COUNT(*) AS `count`, SUM(widget_order) FROM `widget`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testSimpleJoin() {
+ ORM::for_table('widget')->join('widget_handle', array('widget_handle.widget_id', '=', 'widget.id'))->find_many();
+ $expected = "SELECT * FROM `widget` JOIN `widget_handle` ON `widget_handle`.`widget_id` = `widget`.`id`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testSimpleJoinWithWhereIdIsMethod() {
+ ORM::for_table('widget')->join('widget_handle', array('widget_handle.widget_id', '=', 'widget.id'))->find_one(5);
+ $expected = "SELECT * FROM `widget` JOIN `widget_handle` ON `widget_handle`.`widget_id` = `widget`.`id` WHERE `widget`.`id` = '5' LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testInnerJoin() {
+ ORM::for_table('widget')->inner_join('widget_handle', array('widget_handle.widget_id', '=', 'widget.id'))->find_many();
+ $expected = "SELECT * FROM `widget` INNER JOIN `widget_handle` ON `widget_handle`.`widget_id` = `widget`.`id`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testLeftOuterJoin() {
+ ORM::for_table('widget')->left_outer_join('widget_handle', array('widget_handle.widget_id', '=', 'widget.id'))->find_many();
+ $expected = "SELECT * FROM `widget` LEFT OUTER JOIN `widget_handle` ON `widget_handle`.`widget_id` = `widget`.`id`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testRightOuterJoin() {
+ ORM::for_table('widget')->right_outer_join('widget_handle', array('widget_handle.widget_id', '=', 'widget.id'))->find_many();
+ $expected = "SELECT * FROM `widget` RIGHT OUTER JOIN `widget_handle` ON `widget_handle`.`widget_id` = `widget`.`id`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testFullOuterJoin() {
+ ORM::for_table('widget')->full_outer_join('widget_handle', array('widget_handle.widget_id', '=', 'widget.id'))->find_many();
+ $expected = "SELECT * FROM `widget` FULL OUTER JOIN `widget_handle` ON `widget_handle`.`widget_id` = `widget`.`id`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testMultipleJoinSources() {
+ ORM::for_table('widget')
+ ->join('widget_handle', array('widget_handle.widget_id', '=', 'widget.id'))
+ ->join('widget_nozzle', array('widget_nozzle.widget_id', '=', 'widget.id'))
+ ->find_many();
+ $expected = "SELECT * FROM `widget` JOIN `widget_handle` ON `widget_handle`.`widget_id` = `widget`.`id` JOIN `widget_nozzle` ON `widget_nozzle`.`widget_id` = `widget`.`id`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testJoinWithAliases() {
+ ORM::for_table('widget')->join('widget_handle', array('wh.widget_id', '=', 'widget.id'), 'wh')->find_many();
+ $expected = "SELECT * FROM `widget` JOIN `widget_handle` `wh` ON `wh`.`widget_id` = `widget`.`id`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testJoinWithStringConstraint() {
+ ORM::for_table('widget')->join('widget_handle', "widget_handle.widget_id = widget.id")->find_many();
+ $expected = "SELECT * FROM `widget` JOIN `widget_handle` ON widget_handle.widget_id = widget.id";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testSelectWithDistinct() {
+ ORM::for_table('widget')->distinct()->select('name')->find_many();
+ $expected = "SELECT DISTINCT `name` FROM `widget`";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testInsertData() {
+ $widget = ORM::for_table('widget')->create();
+ $widget->name = "Fred";
+ $widget->age = 10;
+ $widget->save();
+ $expected = "INSERT INTO `widget` (`name`, `age`) VALUES ('Fred', '10')";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testInsertDataContainingAnExpression() {
+ $widget = ORM::for_table('widget')->create();
+ $widget->name = "Fred";
+ $widget->age = 10;
+ $widget->set_expr('added', 'NOW()');
+ $widget->save();
+ $expected = "INSERT INTO `widget` (`name`, `age`, `added`) VALUES ('Fred', '10', NOW())";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testInsertDataUsingArrayAccess() {
+ $widget = ORM::for_table('widget')->create();
+ $widget['name'] = "Fred";
+ $widget['age'] = 10;
+ $widget->save();
+ $expected = "INSERT INTO `widget` (`name`, `age`) VALUES ('Fred', '10')";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testUpdateData() {
+ $widget = ORM::for_table('widget')->find_one(1);
+ $widget->name = "Fred";
+ $widget->age = 10;
+ $widget->save();
+ $expected = "UPDATE `widget` SET `name` = 'Fred', `age` = '10' WHERE `id` = '1'";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testUpdateDataContainingAnExpression() {
+ $widget = ORM::for_table('widget')->find_one(1);
+ $widget->name = "Fred";
+ $widget->age = 10;
+ $widget->set_expr('added', 'NOW()');
+ $widget->save();
+ $expected = "UPDATE `widget` SET `name` = 'Fred', `age` = '10', `added` = NOW() WHERE `id` = '1'";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testUpdateMultipleFields() {
+ $widget = ORM::for_table('widget')->find_one(1);
+ $widget->set(array("name" => "Fred", "age" => 10));
+ $widget->save();
+ $expected = "UPDATE `widget` SET `name` = 'Fred', `age` = '10' WHERE `id` = '1'";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testUpdateMultipleFieldsContainingAnExpression() {
+ $widget = ORM::for_table('widget')->find_one(1);
+ $widget->set(array("name" => "Fred", "age" => 10));
+ $widget->set_expr(array("added" => "NOW()", "lat_long" => "GeomFromText('POINT(1.2347 2.3436)')"));
+ $widget->save();
+ $expected = "UPDATE `widget` SET `name` = 'Fred', `age` = '10', `added` = NOW(), `lat_long` = GeomFromText('POINT(1.2347 2.3436)') WHERE `id` = '1'";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testUpdateMultipleFieldsContainingAnExpressionAndOverridePreviouslySetExpression() {
+ $widget = ORM::for_table('widget')->find_one(1);
+ $widget->set(array("name" => "Fred", "age" => 10));
+ $widget->set_expr(array("added" => "NOW()", "lat_long" => "GeomFromText('POINT(1.2347 2.3436)')"));
+ $widget->lat_long = 'unknown';
+ $widget->save();
+ $expected = "UPDATE `widget` SET `name` = 'Fred', `age` = '10', `added` = NOW(), `lat_long` = 'unknown' WHERE `id` = '1'";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testDeleteData() {
+ $widget = ORM::for_table('widget')->find_one(1);
+ $widget->delete();
+ $expected = "DELETE FROM `widget` WHERE `id` = '1'";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testDeleteMany() {
+ ORM::for_table('widget')->where_equal('age', 10)->delete_many();
+ $expected = "DELETE FROM `widget` WHERE `age` = '10'";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testCount() {
+ ORM::for_table('widget')->count();
+ $expected = "SELECT COUNT(*) AS `count` FROM `widget` LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testMax() {
+ ORM::for_table('person')->max('height');
+ $expected = "SELECT MAX(`height`) AS `max` FROM `person` LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testMin() {
+ ORM::for_table('person')->min('height');
+ $expected = "SELECT MIN(`height`) AS `min` FROM `person` LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testAvg() {
+ ORM::for_table('person')->avg('height');
+ $expected = "SELECT AVG(`height`) AS `avg` FROM `person` LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testSum() {
+ ORM::for_table('person')->sum('height');
+ $expected = "SELECT SUM(`height`) AS `sum` FROM `person` LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ /**
+ * Regression tests
+ */
+ public function testIssue12IncorrectQuotingOfColumnWildcard() {
+ ORM::for_table('widget')->select('widget.*')->find_one();
+ $expected = "SELECT `widget`.* FROM `widget` LIMIT 1";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testIssue57LogQueryRaisesWarningWhenPercentSymbolSupplied() {
+ ORM::for_table('widget')->where_raw('username LIKE "ben%"')->find_many();
+ $expected = 'SELECT * FROM `widget` WHERE username LIKE "ben%"';
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testIssue57LogQueryRaisesWarningWhenQuestionMarkSupplied() {
+ ORM::for_table('widget')->where_raw('comments LIKE "has been released?%"')->find_many();
+ $expected = 'SELECT * FROM `widget` WHERE comments LIKE "has been released?%"';
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testIssue74EscapingQuoteMarksIn_quote_identifier_part() {
+ $widget = ORM::for_table('widget')->find_one(1);
+ $widget->set('ad`ded', '2013-01-04');
+ $widget->save();
+ $expected = "UPDATE `widget` SET `ad``ded` = '2013-01-04' WHERE `id` = '1'";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+
+ public function testIssue90UsingSetExprAloneDoesTriggerQueryGeneration() {
+ $widget = ORM::for_table('widget')->find_one(1);
+ $widget->set_expr('added', 'NOW()');
+ $widget->save();
+ $expected = "UPDATE `widget` SET `added` = NOW() WHERE `id` = '1'";
+ $this->assertEquals($expected, ORM::get_last_query());
+ }
+}
+
diff --git a/test/bootstrap.php b/test/bootstrap.php
new file mode 100644
index 0000000..ec13cc7
--- /dev/null
+++ b/test/bootstrap.php
@@ -0,0 +1,61 @@
+<?php
+
+require_once dirname(__FILE__) . '/../idiorm.php';
+
+/**
+ *
+ * Mock version of the PDOStatement class.
+ *
+ */
+class MockPDOStatement extends PDOStatement {
+
+ private $current_row = 0;
+ /**
+ * Return some dummy data
+ */
+ public function fetch($fetch_style=PDO::FETCH_BOTH, $cursor_orientation=PDO::FETCH_ORI_NEXT, $cursor_offset=0) {
+ if ($this->current_row == 5) {
+ return false;
+ } else {
+ $this->current_row++;
+ return array('name' => 'Fred', 'age' => 10, 'id' => '1');
+ }
+ }
+}
+
+/**
+ * Another mock PDOStatement class, used for testing multiple connections
+ */
+class MockDifferentPDOStatement extends MockPDOStatement { }
+
+/**
+ *
+ * Mock database class implementing a subset
+ * of the PDO API.
+ *
+ */
+class MockPDO extends PDO {
+
+ /**
+ * Return a dummy PDO statement
+ */
+ public function prepare($statement, $driver_options=array()) {
+ $this->last_query = new MockPDOStatement($statement);
+ return $this->last_query;
+ }
+}
+
+/**
+ * A different mock database class, for testing multiple connections
+ * Mock database class implementing a subset of the PDO API.
+ */
+class MockDifferentPDO extends MockPDO {
+
+ /**
+ * Return a dummy PDO statement
+ */
+ public function prepare($statement, $driver_options = array()) {
+ $this->last_query = new MockDifferentPDOStatement($statement);
+ return $this->last_query;
+ }
+}
diff --git a/test/test_classes.php b/test/test_classes.php
deleted file mode 100644
index a948d01..0000000
--- a/test/test_classes.php
+++ /dev/null
@@ -1,121 +0,0 @@
-<?php
-
- /**
- *
- * Mock version of the PDOStatement class.
- *
- */
- class DummyPDOStatement extends PDOStatement {
-
- private $current_row = 0;
- /**
- * Return some dummy data
- */
- public function fetch($fetch_style=PDO::FETCH_BOTH, $cursor_orientation=PDO::FETCH_ORI_NEXT, $cursor_offset=0) {
- if ($this->current_row == 5) {
- return false;
- } else {
- $this->current_row++;
- return array('name' => 'Fred', 'age' => 10, 'id' => '1');
- }
- }
- }
-
- /**
- *
- * Mock database class implementing a subset
- * of the PDO API.
- *
- */
- class DummyPDO extends PDO {
-
- /**
- * Return a dummy PDO statement
- */
- public function prepare($statement, $driver_options=array()) {
- $this->last_query = new DummyPDOStatement($statement);
- return $this->last_query;
- }
- }
-
- /**
- *
- * Class to provide simple testing functionality
- *
- */
- class Tester {
-
- private static $passed_tests = array();
- private static $failed_tests = array();
- private static $db;
-
- private static $term_colours = array(
- 'BLACK' => "30",
- 'RED' => "31",
- 'GREEN' => "32",
- 'DEFAULT' => "00",
- );
-
- /**
- * Format a line for printing. Detects
- * if the script is being run from the command
- * line or from a browser.
- *
- * Colouring code loosely based on
- * http://www.zend.com//code/codex.php?ozid=1112&single=1
- */
- private static function format_line($line, $colour='DEFAULT') {
- if (isset($_SERVER['HTTP_USER_AGENT'])) {
- $colour = strtolower($colour);
- return "<p style=\"color: $colour;\">$line</p>\n";
- } else {
- $colour = self::$term_colours[$colour];
- return chr(27) . "[0;{$colour}m{$line}" . chr(27) . "[00m\n";
- }
- }
-
- /**
- * Report a passed test
- */
- private static function report_pass($test_name) {
- echo self::format_line("PASS: $test_name", 'GREEN');
- self::$passed_tests[] = $test_name;
- }
-
- /**
- * Report a failed test
- */
- private static function report_failure($test_name, $expected, $actual) {
- echo self::format_line("FAIL: $test_name", 'RED');
- echo self::format_line("Expected: $expected", 'RED');
- echo self::format_line("Actual: $actual", 'RED');
- self::$failed_tests[] = $test_name;
- }
-
- /**
- * Print a summary of passed and failed test counts
- */
- public static function report() {
- $passed_count = count(self::$passed_tests);
- $failed_count = count(self::$failed_tests);
- echo self::format_line('');
- echo self::format_line("$passed_count tests passed. $failed_count tests failed.");
-
- if ($failed_count != 0) {
- echo self::format_line("Failed tests: " . join(", ", self::$failed_tests));
- }
- }
-
- /**
- * Check the provided string is equal to the last
- * query generated by the dummy database class.
- */
- public static function check_equal($test_name, $query) {
- $last_query = ORM::get_last_query();
- if ($query === $last_query) {
- self::report_pass($test_name);
- } else {
- self::report_failure($test_name, $query, $last_query);
- }
- }
- }
diff --git a/test/test_queries.php b/test/test_queries.php
deleted file mode 100644
index bcd4c7e..0000000
--- a/test/test_queries.php
+++ /dev/null
@@ -1,354 +0,0 @@
-<?php
- /*
- * Basic testing for Idiorm
- *
- * Checks that the generated SQL is correct
- *
- */
-
- require_once dirname(__FILE__) . "/../idiorm.php";
- require_once dirname(__FILE__) . "/test_classes.php";
-
- // Enable logging
- ORM::configure('logging', true);
-
- // Set up the dummy database connection
- $db = new DummyPDO('sqlite::memory:');
- ORM::set_db($db);
-
- ORM::for_table('widget')->find_many();
- $expected = "SELECT * FROM `widget`";
- Tester::check_equal("Basic unfiltered find_many query", $expected);
-
- ORM::for_table('widget')->find_one();
- $expected = "SELECT * FROM `widget` LIMIT 1";
- Tester::check_equal("Basic unfiltered find_one query", $expected);
-
- ORM::for_table('widget')->where_id_is(5)->find_one();
- $expected = "SELECT * FROM `widget` WHERE `id` = '5' LIMIT 1";
- Tester::check_equal("where_id_is method", $expected);
-
- ORM::for_table('widget')->find_one(5);
- $expected = "SELECT * FROM `widget` WHERE `id` = '5' LIMIT 1";
- Tester::check_equal("Filtering on ID passed into find_one method", $expected);
-
- ORM::for_table('widget')->count();
- $expected = "SELECT COUNT(*) AS `count` FROM `widget` LIMIT 1";
- Tester::check_equal("COUNT query", $expected);
-
- ORM::for_table('person')->max('height');
- $expected = "SELECT MAX(`height`) AS `max` FROM `person` LIMIT 1";
- Tester::check_equal("MAX query", $expected);
-
- ORM::for_table('person')->min('height');
- $expected = "SELECT MIN(`height`) AS `min` FROM `person` LIMIT 1";
- Tester::check_equal("MIN query", $expected);
-
- ORM::for_table('person')->avg('height');
- $expected = "SELECT AVG(`height`) AS `avg` FROM `person` LIMIT 1";
- Tester::check_equal("AVG query", $expected);
-
- ORM::for_table('person')->sum('height');
- $expected = "SELECT SUM(`height`) AS `sum` FROM `person` LIMIT 1";
- Tester::check_equal("SUM query", $expected);
-
- ORM::for_table('widget')->where('name', 'Fred')->find_one();
- $expected = "SELECT * FROM `widget` WHERE `name` = 'Fred' LIMIT 1";
- Tester::check_equal("Single where clause", $expected);
-
- ORM::for_table('widget')->where('name', 'Fred')->where('age', 10)->find_one();
- $expected = "SELECT * FROM `widget` WHERE `name` = 'Fred' AND `age` = '10' LIMIT 1";
- Tester::check_equal("Multiple WHERE clauses", $expected);
-
- ORM::for_table('widget')->where_not_equal('name', 'Fred')->find_many();
- $expected = "SELECT * FROM `widget` WHERE `name` != 'Fred'";
- Tester::check_equal("where_not_equal method", $expected);
-
- ORM::for_table('widget')->where_like('name', '%Fred%')->find_one();
- $expected = "SELECT * FROM `widget` WHERE `name` LIKE '%Fred%' LIMIT 1";
- Tester::check_equal("where_like method", $expected);
-
- ORM::for_table('widget')->where_not_like('name', '%Fred%')->find_one();
- $expected = "SELECT * FROM `widget` WHERE `name` NOT LIKE '%Fred%' LIMIT 1";
- Tester::check_equal("where_not_like method", $expected);
-
- ORM::for_table('widget')->where_in('name', array('Fred', 'Joe'))->find_many();
- $expected = "SELECT * FROM `widget` WHERE `name` IN ('Fred', 'Joe')";
- Tester::check_equal("where_in method", $expected);
-
- ORM::for_table('widget')->where_not_in('name', array('Fred', 'Joe'))->find_many();
- $expected = "SELECT * FROM `widget` WHERE `name` NOT IN ('Fred', 'Joe')";
- Tester::check_equal("where_not_in method", $expected);
-
- ORM::for_table('widget')->limit(5)->find_many();
- $expected = "SELECT * FROM `widget` LIMIT 5";
- Tester::check_equal("LIMIT clause", $expected);
-
- ORM::for_table('widget')->limit(5)->offset(5)->find_many();
- $expected = "SELECT * FROM `widget` LIMIT 5 OFFSET 5";
- Tester::check_equal("LIMIT and OFFSET clause", $expected);
-
- ORM::for_table('widget')->order_by_desc('name')->find_one();
- $expected = "SELECT * FROM `widget` ORDER BY `name` DESC LIMIT 1";
- Tester::check_equal("ORDER BY DESC", $expected);
-
- ORM::for_table('widget')->order_by_asc('name')->find_one();
- $expected = "SELECT * FROM `widget` ORDER BY `name` ASC LIMIT 1";
- Tester::check_equal("ORDER BY ASC", $expected);
-
- ORM::for_table('widget')->order_by_expr('SOUNDEX(`name`)')->find_one();
- $expected = "SELECT * FROM `widget` ORDER BY SOUNDEX(`name`) LIMIT 1";
- Tester::check_equal("ORDER BY expression", $expected);
-
- ORM::for_table('widget')->order_by_asc('name')->order_by_desc('age')->find_one();
- $expected = "SELECT * FROM `widget` ORDER BY `name` ASC, `age` DESC LIMIT 1";
- Tester::check_equal("Multiple ORDER BY", $expected);
-
- ORM::for_table('widget')->group_by('name')->find_many();
- $expected = "SELECT * FROM `widget` GROUP BY `name`";
- Tester::check_equal("GROUP BY", $expected);
-
- ORM::for_table('widget')->group_by('name')->group_by('age')->find_many();
- $expected = "SELECT * FROM `widget` GROUP BY `name`, `age`";
- Tester::check_equal("Multiple GROUP BY", $expected);
-
- ORM::for_table('widget')->group_by_expr("FROM_UNIXTIME(`time`, '%Y-%m')")->find_many();
- $expected = "SELECT * FROM `widget` GROUP BY FROM_UNIXTIME(`time`, '%Y-%m')";
- Tester::check_equal("GROUP BY expression", $expected);
-
- ORM::for_table('widget')->where('name', 'Fred')->limit(5)->offset(5)->order_by_asc('name')->find_many();
- $expected = "SELECT * FROM `widget` WHERE `name` = 'Fred' ORDER BY `name` ASC LIMIT 5 OFFSET 5";
- Tester::check_equal("Complex query", $expected);
-
- ORM::for_table('widget')->where_lt('age', 10)->where_gt('age', 5)->find_many();
- $expected = "SELECT * FROM `widget` WHERE `age` < '10' AND `age` > '5'";
- Tester::check_equal("Less than and greater than", $expected);
-
- ORM::for_table('widget')->where_lte('age', 10)->where_gte('age', 5)->find_many();
- $expected = "SELECT * FROM `widget` WHERE `age` <= '10' AND `age` >= '5'";
- Tester::check_equal("Less than or equal and greater than or equal", $expected);
-
- ORM::for_table('widget')->where_null('name')->find_many();
- $expected = "SELECT * FROM `widget` WHERE `name` IS NULL";
- Tester::check_equal("where_null method", $expected);
-
- ORM::for_table('widget')->where_not_null('name')->find_many();
- $expected = "SELECT * FROM `widget` WHERE `name` IS NOT NULL";
- Tester::check_equal("where_not_null method", $expected);
-
- ORM::for_table('widget')->where_raw('`name` = ? AND (`age` = ? OR `age` = ?)', array('Fred', 5, 10))->find_many();
- $expected = "SELECT * FROM `widget` WHERE `name` = 'Fred' AND (`age` = '5' OR `age` = '10')";
- Tester::check_equal("Raw WHERE clause", $expected);
-
- ORM::for_table('widget')->where_raw('STRFTIME("%Y", "now") = ?', array(2012))->find_many();
- $expected = "SELECT * FROM `widget` WHERE STRFTIME(\"%Y\", \"now\") = '2012'";
- Tester::check_equal("Raw WHERE clause with '%'", $expected);
-
- ORM::for_table('widget')->where_raw('`name` = "Fred"')->find_many();
- $expected = "SELECT * FROM `widget` WHERE `name` = \"Fred\"";
- Tester::check_equal("Raw WHERE clause with no parameters", $expected);
-
- ORM::for_table('widget')->where('age', 18)->where_raw('(`name` = ? OR `name` = ?)', array('Fred', 'Bob'))->where('size', 'large')->find_many();
- $expected = "SELECT * FROM `widget` WHERE `age` = '18' AND (`name` = 'Fred' OR `name` = 'Bob') AND `size` = 'large'";
- Tester::check_equal("Raw WHERE clause in method chain", $expected);
-
- ORM::for_table('widget')->raw_query('SELECT `w`.* FROM `widget` w')->find_many();
- $expected = "SELECT `w`.* FROM `widget` w";
- Tester::check_equal("Raw query", $expected);
-
- ORM::for_table('widget')->raw_query('SELECT `w`.* FROM `widget` w WHERE `name` = ? AND `age` = ?', array('Fred', 5))->find_many();
- $expected = "SELECT `w`.* FROM `widget` w WHERE `name` = 'Fred' AND `age` = '5'";
- Tester::check_equal("Raw query with parameters", $expected);
-
- ORM::for_table('widget')->select('name')->find_many();
- $expected = "SELECT `name` FROM `widget`";
- Tester::check_equal("Simple result column", $expected);
-
- ORM::for_table('widget')->select('name')->select('age')->find_many();
- $expected = "SELECT `name`, `age` FROM `widget`";
- Tester::check_equal("Multiple simple result columns", $expected);
-
- ORM::for_table('widget')->select('widget.name')->find_many();
- $expected = "SELECT `widget`.`name` FROM `widget`";
- Tester::check_equal("Specify table name and column in result columns", $expected);
-
- ORM::for_table('widget')->select('widget.name', 'widget_name')->find_many();
- $expected = "SELECT `widget`.`name` AS `widget_name` FROM `widget`";
- Tester::check_equal("Aliases in result columns", $expected);
-
- ORM::for_table('widget')->select_expr('COUNT(*)', 'count')->find_many();
- $expected = "SELECT COUNT(*) AS `count` FROM `widget`";
- Tester::check_equal("Literal expression in result columns", $expected);
-
- ORM::for_table('widget')->select_many(array('widget_name' => 'widget.name'), 'widget_handle')->find_many();
- $expected = "SELECT `widget`.`name` AS `widget_name`, `widget_handle` FROM `widget`";
- Tester::check_equal("Aliases in select many result columns", $expected);
-
- ORM::for_table('widget')->select_many_expr(array('count' => 'COUNT(*)'), 'SUM(widget_order)')->find_many();
- $expected = "SELECT COUNT(*) AS `count`, SUM(widget_order) FROM `widget`";
- Tester::check_equal("Literal expression in select many result columns", $expected);
-
- ORM::for_table('widget')->join('widget_handle', array('widget_handle.widget_id', '=', 'widget.id'))->find_many();
- $expected = "SELECT * FROM `widget` JOIN `widget_handle` ON `widget_handle`.`widget_id` = `widget`.`id`";
- Tester::check_equal("Simple join", $expected);
-
- ORM::for_table('widget')->join('widget_handle', array('widget_handle.widget_id', '=', 'widget.id'))->find_one(5);
- $expected = "SELECT * FROM `widget` JOIN `widget_handle` ON `widget_handle`.`widget_id` = `widget`.`id` WHERE `widget`.`id` = '5' LIMIT 1";
- Tester::check_equal("Simple join with where_id_is method", $expected);
-
- ORM::for_table('widget')->inner_join('widget_handle', array('widget_handle.widget_id', '=', 'widget.id'))->find_many();
- $expected = "SELECT * FROM `widget` INNER JOIN `widget_handle` ON `widget_handle`.`widget_id` = `widget`.`id`";
- Tester::check_equal("Inner join", $expected);
-
- ORM::for_table('widget')->left_outer_join('widget_handle', array('widget_handle.widget_id', '=', 'widget.id'))->find_many();
- $expected = "SELECT * FROM `widget` LEFT OUTER JOIN `widget_handle` ON `widget_handle`.`widget_id` = `widget`.`id`";
- Tester::check_equal("Left outer join", $expected);
-
- ORM::for_table('widget')->right_outer_join('widget_handle', array('widget_handle.widget_id', '=', 'widget.id'))->find_many();
- $expected = "SELECT * FROM `widget` RIGHT OUTER JOIN `widget_handle` ON `widget_handle`.`widget_id` = `widget`.`id`";
- Tester::check_equal("Right outer join", $expected);
-
- ORM::for_table('widget')->full_outer_join('widget_handle', array('widget_handle.widget_id', '=', 'widget.id'))->find_many();
- $expected = "SELECT * FROM `widget` FULL OUTER JOIN `widget_handle` ON `widget_handle`.`widget_id` = `widget`.`id`";
- Tester::check_equal("Full outer join", $expected);
-
- ORM::for_table('widget')
- ->join('widget_handle', array('widget_handle.widget_id', '=', 'widget.id'))
- ->join('widget_nozzle', array('widget_nozzle.widget_id', '=', 'widget.id'))
- ->find_many();
- $expected = "SELECT * FROM `widget` JOIN `widget_handle` ON `widget_handle`.`widget_id` = `widget`.`id` JOIN `widget_nozzle` ON `widget_nozzle`.`widget_id` = `widget`.`id`";
- Tester::check_equal("Multiple join sources", $expected);
-
- ORM::for_table('widget')->table_alias('w')->find_many();
- $expected = "SELECT * FROM `widget` `w`";
- Tester::check_equal("Main table alias", $expected);
-
- ORM::for_table('widget')->join('widget_handle', array('wh.widget_id', '=', 'widget.id'), 'wh')->find_many();
- $expected = "SELECT * FROM `widget` JOIN `widget_handle` `wh` ON `wh`.`widget_id` = `widget`.`id`";
- Tester::check_equal("Join with alias", $expected);
-
- ORM::for_table('widget')->join('widget_handle', "widget_handle.widget_id = widget.id")->find_many();
- $expected = "SELECT * FROM `widget` JOIN `widget_handle` ON widget_handle.widget_id = widget.id";
- Tester::check_equal("Join with string constraint", $expected);
-
- ORM::for_table('widget')->distinct()->select('name')->find_many();
- $expected = "SELECT DISTINCT `name` FROM `widget`";
- Tester::check_equal("Select with DISTINCT", $expected);
-
- $widget = ORM::for_table('widget')->create();
- $widget->name = "Fred";
- $widget->age = 10;
- $widget->save();
- $expected = "INSERT INTO `widget` (`name`, `age`) VALUES ('Fred', '10')";
- Tester::check_equal("Insert data", $expected);
-
- $widget = ORM::for_table('widget')->create();
- $widget->name = "Fred";
- $widget->age = 10;
- $widget->set_expr('added', 'NOW()');
- $widget->save();
- $expected = "INSERT INTO `widget` (`name`, `age`, `added`) VALUES ('Fred', '10', NOW())";
- Tester::check_equal("Insert data containing an expression", $expected);
-
- $widget = ORM::for_table('widget')->find_one(1);
- $widget->name = "Fred";
- $widget->age = 10;
- $widget->save();
- $expected = "UPDATE `widget` SET `name` = 'Fred', `age` = '10' WHERE `id` = '1'";
- Tester::check_equal("Update data", $expected);
-
- $widget = ORM::for_table('widget')->find_one(1);
- $widget->name = "Fred";
- $widget->age = 10;
- $widget->set_expr('added', 'NOW()');
- $widget->save();
- $expected = "UPDATE `widget` SET `name` = 'Fred', `age` = '10', `added` = NOW() WHERE `id` = '1'";
- Tester::check_equal("Update data containing an expression", $expected);
-
- $widget = ORM::for_table('widget')->find_one(1);
- $widget->set(array("name" => "Fred", "age" => 10));
- $widget->save();
- $expected = "UPDATE `widget` SET `name` = 'Fred', `age` = '10' WHERE `id` = '1'";
- Tester::check_equal("Update multiple fields", $expected);
-
- $widget = ORM::for_table('widget')->find_one(1);
- $widget->set(array("name" => "Fred", "age" => 10));
- $widget->set_expr(array("added" => "NOW()", "lat_long" => "GeomFromText('POINT(1.2347 2.3436)')"));
- $widget->save();
- $expected = "UPDATE `widget` SET `name` = 'Fred', `age` = '10', `added` = NOW(), `lat_long` = GeomFromText('POINT(1.2347 2.3436)') WHERE `id` = '1'";
- Tester::check_equal("Update multiple fields containing an expression", $expected);
-
- $widget = ORM::for_table('widget')->find_one(1);
- $widget->set(array("name" => "Fred", "age" => 10));
- $widget->set_expr(array("added" => "NOW()", "lat_long" => "GeomFromText('POINT(1.2347 2.3436)')"));
- $widget->lat_long = 'unknown';
- $widget->save();
- $expected = "UPDATE `widget` SET `name` = 'Fred', `age` = '10', `added` = NOW(), `lat_long` = 'unknown' WHERE `id` = '1'";
- Tester::check_equal("Update multiple fields containing an expression (override previously set expression with plain value)", $expected);
-
- $widget = ORM::for_table('widget')->find_one(1);
- $widget->delete();
- $expected = "DELETE FROM `widget` WHERE `id` = '1'";
- Tester::check_equal("Delete data", $expected);
-
- // Regression tests
-
- $widget = ORM::for_table('widget')->select('widget.*')->find_one();
- $expected = "SELECT `widget`.* FROM `widget` LIMIT 1";
- Tester::check_equal("Issue #12 - incorrect quoting of column wildcard", $expected);
-
- $widget = ORM::for_table('widget')->where_raw('username LIKE "ben%"')->find_many();
- $expected = 'SELECT * FROM `widget` WHERE username LIKE "ben%"';
- Tester::check_equal('Issue #57 - _log_query method raises a warning when query contains "%"', $expected);
-
- $widget = ORM::for_table('widget')->where_raw('comments LIKE "has been released?%"')->find_many();
- $expected = 'SELECT * FROM `widget` WHERE comments LIKE "has been released?%"';
- Tester::check_equal('Issue #57 - _log_query method raises a warning when query contains "?"', $expected);
-
- // Tests that alter Idiorm's config are done last
-
- ORM::configure('id_column', 'primary_key');
- ORM::for_table('widget')->find_one(5);
- $expected = "SELECT * FROM `widget` WHERE `primary_key` = '5' LIMIT 1";
- Tester::check_equal("Setting: id_column", $expected);
-
- ORM::configure('id_column_overrides', array(
- 'widget' => 'widget_id',
- 'widget_handle' => 'widget_handle_id',
- ));
-
- ORM::for_table('widget')->find_one(5);
- $expected = "SELECT * FROM `widget` WHERE `widget_id` = '5' LIMIT 1";
- Tester::check_equal("Setting: id_column_overrides, first test", $expected);
-
- ORM::for_table('widget_handle')->find_one(5);
- $expected = "SELECT * FROM `widget_handle` WHERE `widget_handle_id` = '5' LIMIT 1";
- Tester::check_equal("Setting: id_column_overrides, second test", $expected);
-
- ORM::for_table('widget_nozzle')->find_one(5);
- $expected = "SELECT * FROM `widget_nozzle` WHERE `primary_key` = '5' LIMIT 1";
- Tester::check_equal("Setting: id_column_overrides, third test", $expected);
-
- ORM::for_table('widget')->use_id_column('new_id')->find_one(5);
- $expected = "SELECT * FROM `widget` WHERE `new_id` = '5' LIMIT 1";
- Tester::check_equal("Instance ID column, first test", $expected);
-
- ORM::for_table('widget_handle')->use_id_column('new_id')->find_one(5);
- $expected = "SELECT * FROM `widget_handle` WHERE `new_id` = '5' LIMIT 1";
- Tester::check_equal("Instance ID column, second test", $expected);
-
- ORM::for_table('widget_nozzle')->use_id_column('new_id')->find_one(5);
- $expected = "SELECT * FROM `widget_nozzle` WHERE `new_id` = '5' LIMIT 1";
- Tester::check_equal("Instance ID column, third test", $expected);
-
- // Test caching. This is a bit of a hack.
- ORM::configure('caching', true);
- ORM::for_table('widget')->where('name', 'Fred')->where('age', 17)->find_one();
- ORM::for_table('widget')->where('name', 'Bob')->where('age', 42)->find_one();
- $expected = ORM::get_last_query();
- ORM::for_table('widget')->where('name', 'Fred')->where('age', 17)->find_one(); // this shouldn't run a query!
- Tester::check_equal("Caching, same query not run twice", $expected);
-
-
- Tester::report();
-?>