Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added compound primary key support, docs and tests #161

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,13 @@ all tables. If your ID column is called ``primary_key``, use:
<?php
ORM::configure('id_column', 'primary_key');

You can specify a compound primary key using an array:

.. code-block:: php

<?php
ORM::configure('id_column', array('pk_1', 'pk_2'));

Setting: ``id_column_overrides``

This setting is used to specify the primary key column name for each
Expand All @@ -212,6 +219,9 @@ the table, you can use the following configuration:
'role' => 'role_id',
));

As with ``id_column`` setting, you can specify a compound primary key
using an array.

Limit clause style
^^^^^^^^^^^^^^^^^^

Expand Down
20 changes: 19 additions & 1 deletion docs/querying.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ To find a single record by ID, you can pass the ID directly to the
<?php
$person = ORM::for_table('person')->find_one(5);

This feature works only when compound primary keys are not used.

Multiple records
^^^^^^^^^^^^^^^^

Expand All @@ -111,6 +113,10 @@ To find all records where the ``gender`` is ``female``:
<?php
$females = ORM::for_table('person')->where('gender', 'female')->find_many();

Notice that it will return an associative array with the primary IDs as the keys,
but only if the primary key is not compund (in this case, a regular array will be
returned).

As a result set
'''''''''''''''

Expand Down Expand Up @@ -242,7 +248,8 @@ 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.
Respects the ID column specified in the config. It won't work when using
compound primary keys.

Less than / greater than: ``where_lt``, ``where_gt``, ``where_lte``, ``where_gte``
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Expand Down Expand Up @@ -667,3 +674,14 @@ 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.

Specifying a non-default ID column in a query
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Should you need to override the table default primary key column in
the current query, you can use the ``use_id_column`` method. It takes
the column name as the only parameter, but you can use an array of
column names if the primary key is compound.

Take note, though, that you can specify the default primary key for
a table for every query using the ``id_column_overrides`` setting.
98 changes: 75 additions & 23 deletions idiorm.php
Original file line number Diff line number Diff line change
Expand Up @@ -617,15 +617,20 @@ protected function _find_many() {
/**
* Create instances of each row in the result and map
* them to an associative array with the primary IDs as
* the array keys.
* the array keys if the primary key is not compound.
* @param array $rows
* @return array
*/
protected function _instances_with_id_as_key($rows) {
$instances = array();
foreach($rows as $row) {
$row = $this->_create_instance_from_row($row);
$instances[$row->id()] = $row;
if (is_array($row->id())) {
$instances[] = $row;
}
else {
$instances[$row->id()] = $row;
}
}
return $instances;
}
Expand Down Expand Up @@ -1082,7 +1087,8 @@ public function where_not_equal($column_name, $value) {
}

/**
* Special method to query the table by its primary key
* Special method to query the table by its primary key.
* Does not work with compounf primary keys.
*/
public function where_id_is($id) {
return $this->where($this->_get_id_column_name(), $id);
Expand Down Expand Up @@ -1263,7 +1269,8 @@ public function having_not_equal($column_name, $value) {
}

/**
* Special method to query the table by its primary key
* Special method to query the table by its primary key.
* Does not work with compound primary keys.
*/
public function having_id_is($id) {
return $this->having($this->_get_id_column_name(), $id);
Expand Down Expand Up @@ -1523,12 +1530,24 @@ protected function _join_if_not_empty($glue, $pieces) {
* (table names, column names etc). This method can
* also deal with dot-separated identifiers eg table.column
*/
protected function _quote_identifier($identifier) {
protected function _quote_one_identifier($identifier) {
$parts = explode('.', $identifier);
$parts = array_map(array($this, '_quote_identifier_part'), $parts);
return join('.', $parts);
}

/**
* Quote a string that is used as an identifier
* (table names, column names etc) or an array containing
* multiple identifiers. This method can also deal with
* dot-separated identifiers eg table.column
*/
protected function _quote_identifier($identifier) {
return is_array($identifier) ?
array_map(array($this, '_quote_one_identifier'), $identifier) :
$this->_quote_one_identifier($identifier);
}

/**
* This method performs the actual quoting of a single
* part of an identifier, using the identifier quote
Expand Down Expand Up @@ -1646,7 +1665,8 @@ public function get($key) {

/**
* Return the name of the column in the database table which contains
* the primary key ID of the row.
* the primary key ID of the row. It will return an array if a compound
* primary key is used.
*/
protected function _get_id_column_name() {
if (!is_null($this->_instance_id_column)) {
Expand All @@ -1659,10 +1679,20 @@ protected function _get_id_column_name() {
}

/**
* Get the primary key ID of this object.
* Get the primary key ID of this object. It will return an associative
* array if a compound primary key is used.
*/
public function id() {
return $this->get($this->_get_id_column_name());
if (is_array($this->_get_id_column_name())) {
$return = array();
foreach($this->_get_id_column_name() as $key) {
$return[$key] = $this->get($key);
}
return $return;
}
else {
return $this->get($this->_get_id_column_name());
}
}

/**
Expand Down Expand Up @@ -1743,17 +1773,23 @@ public function save() {
return true;
}
$query = $this->_build_update();
$values[] = $this->id();
if (is_array($this->_get_id_column_name())) {
$values = array_merge($values, $this->id());
}
else {
$values[] = $this->id();
}
} else { // INSERT
$query = $this->_build_insert();
}

$success = self::_execute($query, $values, $this->_connection_name);

// If we've just inserted a new record, set the ID of this object
// If we've just inserted a new record and the primary key is not compound,
// set the ID of this object
if ($this->_is_new) {
$this->_is_new = false;
if (is_null($this->id())) {
if (is_null($this->id()) && (!is_array($this->_get_id_column_name()))) {
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 {
Expand All @@ -1766,6 +1802,25 @@ public function save() {
return $success;
}

/**
* Add a WHERE clause for every column that composes the primary key
*/
public function _add_id_column_conditions(&$query) {
$query[] = "WHERE";
$keys = is_array($this->_get_id_column_name()) ? $this->_get_id_column_name() : array( $this->_get_id_column_name() );
$first = true;
foreach($keys as $key) {
if ($first) {
$first = false;
}
else {
$query[] = "AND";
}
$query[] = $this->_quote_identifier($key);
$query[] = "= ?";
}
}

/**
* Build an UPDATE query
*/
Expand All @@ -1781,9 +1836,7 @@ protected function _build_update() {
$field_list[] = "{$this->_quote_identifier($key)} = $value";
}
$query[] = join(", ", $field_list);
$query[] = "WHERE";
$query[] = $this->_quote_identifier($this->_get_id_column_name());
$query[] = "= ?";
$this->_add_id_column_conditions($query);
return join(" ", $query);
}

Expand All @@ -1800,7 +1853,9 @@ protected function _build_insert() {
$placeholders = $this->_create_placeholders($this->_dirty_fields);
$query[] = "({$placeholders})";

if (self::$_db[$this->_connection_name]->getAttribute(PDO::ATTR_DRIVER_NAME) == 'pgsql') {
// Only enable returning last insert ID on PostgreSQL when not using compound primary keys
if ((self::$_db[$this->_connection_name]->getAttribute(PDO::ATTR_DRIVER_NAME) == 'pgsql') &&
(!is_array($this->_get_id_column_name()))) {
$query[] = 'RETURNING ' . $this->_quote_identifier($this->_get_id_column_name());
}

Expand All @@ -1811,15 +1866,12 @@ protected function _build_insert() {
* Delete this record from the database
*/
public function delete() {
$query = join(" ", array(
$query = array(
"DELETE FROM",
$this->_quote_identifier($this->_table_name),
"WHERE",
$this->_quote_identifier($this->_get_id_column_name()),
"= ?",
));

return self::_execute($query, array($this->id()), $this->_connection_name);
$this->_quote_identifier($this->_table_name)
);
$this->_add_id_column_conditions($query);
return self::_execute(join(" ", $query), is_array($this->id()) ? $this->id() :array($this->id()), $this->_connection_name);
}

/**
Expand Down
15 changes: 15 additions & 0 deletions test/ORMTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,21 @@ public function testArrayAccess() {
$this->assertFalse(isset($model['test']));
}

public function testGetId() {
$value = 'test';
$record = ORM::for_table('test')->create();
$record->set('id', 10);
$this->assertEquals($record->id(), 10);
}

public function testGetIdWithCompoundPrimaryKeys() {
$value = 'test';
$record = ORM::for_table('test')->use_id_column(array('id1', 'id2'))->create();
$record->set('id1', 10);
$record->set('id2', 20);
$this->assertEquals($record->id(), array('id1' => 10, 'id2' => 20));
}

public function testFindResultSet() {
$result_set = ORM::for_table('test')->find_result_set();
$this->assertInstanceOf('IdiormResultSet', $result_set);
Expand Down
36 changes: 36 additions & 0 deletions test/QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,42 @@ public function testSum() {
$this->assertEquals($expected, ORM::get_last_query());
}

/**
* Compound primary key tests
*/
public function testInsertWithCompoundPrimaryKey() {
$record = ORM::for_table('widget')->use_id_column(array('id1', 'id2'))->create();
$record->set('id1', 10);
$record->set('id2', 20);
$record->set('name', 'Joe');
$record->save();
$expected = "INSERT INTO `widget` (`id1`, `id2`, `name`) VALUES ('10', '20', 'Joe')";
$this->assertEquals($expected, ORM::get_last_query());
}

public function testUpdateWithCompoundPrimaryKey() {
$record = ORM::for_table('widget')->use_id_column(array('id1', 'id2'))->create();
$record->set('id1', 10);
$record->set('id2', 20);
$record->set('name', 'Joe');
$record->save();
$record->set('name', 'John');
$record->save();
$expected = "UPDATE `widget` SET `name` = 'John' WHERE `id1` = '10' AND `id2` = '20'";
$this->assertEquals($expected, ORM::get_last_query());
}

public function testDeleteWithCompoundPrimaryKey() {
$record = ORM::for_table('widget')->use_id_column(array('id1', 'id2'))->create();
$record->set('id1', 10);
$record->set('id2', 20);
$record->set('name', 'Joe');
$record->save();
$record->delete();
$expected = "DELETE FROM `widget` WHERE `id1` = '10' AND `id2` = '20'";
$this->assertEquals($expected, ORM::get_last_query());
}

/**
* Regression tests
*/
Expand Down