Skip to content

Commit

Permalink
Added countOwn, countShared and countRelated.
Browse files Browse the repository at this point in the history
  • Loading branch information
= committed Jun 23, 2013
1 parent b58c3ac commit dbad985
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 13 deletions.
28 changes: 28 additions & 0 deletions RedBean/AssociationManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,34 @@ protected function associateBeans(RedBean_OODBBean $bean1, RedBean_OODBBean $bea
}
return $results;
}

/**
* Counts the number of related beans in an N-M relation.
*
* @param RedBean_OODBBean|array $bean a bean object or an array of beans
* @param string $type type of bean you're interested in
* @param string $sql SQL snippet (optional)
* @param array $values bindings for your SQL string
*
* @return integer
* @throws RedBean_Exception_Security
*/
public function relatedCount($bean, $type, $sql = null, $values = array()) {
if (!($bean instanceof RedBean_OODBBean)) throw new RedBean_Exception_Security('Expected array or RedBean_OODBBean but got:'.gettype($bean));
if (!$bean->id) return 0;
$beanType = $bean->getMeta('type');
$table = $this->getTable(array($beanType, $type));
try {
return $this->writer->queryRecordCountRelated($beanType, $type, $table, $bean->id, $sql, $values);
} catch(RedBean_Exception_SQL $e) {
if (!$this->writer->sqlStateIn($e->getSQLState(),
array(
RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_TABLE)
)) throw $e;
return 0;
}
}
/**
* Returns all ids of beans of type $type that are related to $bean. If the
* $getLinks parameter is set to boolean TRUE this method will return the ids
Expand Down
13 changes: 13 additions & 0 deletions RedBean/Facade.php
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,19 @@ public static function unassociate($beans1, $beans2, $fast = false) {
public static function related($bean, $type, $sql = null, $values = array()) {
return self::$associationManager->relatedSimple($bean, $type, $sql, $values);
}

/**
* Counts the number of related beans in an N-M relation.
*
* @param RedBean_OODBBean $bean
* @param string $type
* @param string $sql
* @param array $values
*/
public static function relatedCount($bean, $type, $sql = null, $values = array()) {
return self::$associationManager->relatedCount($bean, $type, $sql, $values);
}

/**
* Returns only single associated bean.
*
Expand Down
68 changes: 67 additions & 1 deletion RedBean/OODBBean.php
Original file line number Diff line number Diff line change
Expand Up @@ -866,5 +866,71 @@ public function fresh() {
public function via($via) {
$this->via = $via;
return $this;
}
}

/**
* Counts all own beans of type $type.
* Also works with alias(), with() and withCondition().
*
* @param string $type the type of bean you want to count
*
* @return integer
*/
public function countOwn($type) {
if (self::$flagUseBeautyfulColumnnames) {
$type = $this->beau($type);
}
if ($this->aliasName) {
$parentField = $this->aliasName;
$myFieldLink = $this->aliasName.'_id';
$this->aliasName = null;
} else {
$myFieldLink = $this->__info['type'].'_id';
$parentField = $this->__info['type'];
}
$count = 0;
if ($this->getID()>0) {
$params = array_merge(array($this->getID()), $this->withParams);
$count = $this->beanHelper->getToolbox()->getWriter()->queryRecordCount($type, array(), array(" $myFieldLink = ? ".$this->withSql, $params));
}
$this->withSql = '';
$this->withParams = array();
return $count;
}

/**
* Counts all shared beans of type $type.
* Also works with via(), with() and withCondition().
*
* @param string $type type of bean you wish to count
*
* @return integer
*/
public function countShared($type) {
$toolbox = $this->beanHelper->getToolbox();
$redbean = $toolbox->getRedBean();
$writer = $toolbox->getWriter();
if ($this->via) {
$oldName = $writer->getAssocTable(array($this->__info['type'],$type));
if ($oldName !== $this->via) {
//set the new renaming rule
$writer->renameAssocTable($oldName, $this->via);
$this->via = null;
}
}
if (self::$flagUseBeautyfulColumnnames ) {
$type = $this->beau($type);
}
$count = 0;
if ($this->getID()>0) {
if (trim($this->withSql) !== '') {
$count = $redbean->getAssociationManager()->relatedCount($this, $type, $this->withSql, $this->withParams, true);
} else {
$count = $redbean->getAssociationManager()->relatedCount($this, $type);
}
}
$this->withSql = '';
$this->withParams = array();
return $count;
}
}
23 changes: 22 additions & 1 deletion RedBean/QueryWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ interface RedBean_QueryWriter {
const C_SQLSTATE_NO_SUCH_TABLE = 1;
const C_SQLSTATE_NO_SUCH_COLUMN = 2;
const C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION = 3;

/**
* Returns the tables that are in the database.
*
Expand Down Expand Up @@ -131,6 +131,27 @@ public function selectRecord($type, $conditions, $addSql = null, $delete = false
* @param boolean $all if FALSE and $addSQL is SET prefixes $addSQL with ' WHERE ' or ' AND '
*/
public function queryRecord($type, $conditions, $addSql = null, $all = false);
/**
* Selects records from the database.
*
* @param string $type name of the table you want to query
* @param array $conditions criteria ( $column => array( $values ) )
* @param string|array $allSql additional SQL snippet, either a string or: array($SQL, $bindings)
*/
public function queryRecordCount($type, $conditions, $addSql = null);
/**
* Returns the number of records linked through $linkType and satisfying the SQL in $addSQL/$params.
*
* @param string $sourceType source type
* @param string $targetType the thing you want to count
* @param string $linkType the type used to link those two above
* @param mixed $linkID the of the source type
* @param string $addSQL additional SQL snippet
* @param array $params bindings for SQL snippet
*
* @return integer
*/
public function queryRecordCountRelated($sourceType, $targetType, $linkType, $linkID, $addSQL = '', $params = array());
/**
* Deletes records from the database.
* @note $addSql is always prefixed with ' WHERE ' or ' AND .'
Expand Down
69 changes: 58 additions & 11 deletions RedBean/QueryWriter/AQueryWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@
* with this source code in the file license.txt.
*/
abstract class RedBean_QueryWriter_AQueryWriter {
/**
* Query writer mode
*/
const C_MODE_SELECT = 0;
const C_MODE_DELETE = 1;
const C_MODE_COUNT = 2;

/**
* @var array
*/
Expand Down Expand Up @@ -163,41 +170,49 @@ protected function insertRecord($table, $insertcolumns, $insertvalues) {
/**
* @see RedBean_QueryWriter::queryRecord
*/
public function queryRecord($type, $conditions, $addSql = null, $all = false) {
return $this->writeStandardQuery($type, $conditions, $addSql, false, false, $all);
public function queryRecord($type, $conditions, $addSql = null, $all = false) {
return $this->writeStandardQuery($type, $conditions, $addSql, self::C_MODE_SELECT, false, $all);
}
/**
* @see RedBean_QueryWriter::queryRecordCount
*/
public function queryRecordCount($type, $conditions, $addSql = null) {
$rows = $this->writeStandardQuery($type, $conditions, $addSql, self::C_MODE_COUNT, false, false);
if (is_array($rows) && is_array($rows[0])) return (integer) reset($rows[0]);
return 0;
}
/**
* @see RedBean_QueryWriter::deleteRecord
*/
public function deleteRecord($type, $conditions, $addSql = null) {
return $this->writeStandardQuery($type, $conditions, $addSql, true, false, false);
return $this->writeStandardQuery($type, $conditions, $addSql, self::C_MODE_DELETE, false, false);
}
/**
* @see RedBean_QueryWriter::queryRecordInverse
*/
public function queryRecordInverse($type, $conditions, $addSql = null) {
return $this->writeStandardQuery($type, $conditions, $addSql, false, true, false);
return $this->writeStandardQuery($type, $conditions, $addSql, self::C_MODE_SELECT, true, false);
}
/**
* @deprecated
* @see RedBean_QueryWriter::selectRecord
*/
public function selectRecord($type, $conditions, $addSql = null, $delete = null, $inverse = false, $all = false) {
return $this->writeStandardQuery($type, $conditions, $addSql, $delete, $inverse, $all);
return $this->writeStandardQuery($type, $conditions, $addSql, ($delete) ? self::C_MODE_DELETE : self::C_MODE_SELECT, $inverse, $all);
}
/**
* Internal method to build query.
*
* @param string $type name of the table you want to query
* @param array $conditions criteria ( $column => array( $values ) )
* @param string|array $allSql additional SQL snippet, either a string or: array($SQL, $bindings)
* @param boolean $delete selects query mode: TRUE is DELETE, FALSE is SELECT
* @param boolean $mode selects query mode: 1 is DELETE, 0 is SELECT, 2 is COUNT(*)
* @param boolean $inverse if TRUE uses 'NOT IN'-clause for conditions
* @param boolean $all if FALSE and $addSQL is SET prefixes $addSQL with ' WHERE ' or ' AND '
*/
private function writeStandardQuery($type, $conditions, $addSql = null, $delete = null, $inverse = false, $all = false) {
private function writeStandardQuery($type, $conditions, $addSql = null, $mode = null, $inverse = false, $all = false) {
if (!is_array($conditions)) throw new Exception('Conditions must be an array');
if (!$delete && $this->flagUseCache) {
if (!($mode===self::C_MODE_DELETE) && $this->flagUseCache) {
$key = serialize(array($type, $conditions, $addSql, $inverse, $all));
$sql = $this->adapter->getSQL();
if (strpos($sql, '-- keep-cache') !== strlen($sql)-13) {
Expand Down Expand Up @@ -254,9 +269,12 @@ private function writeStandardQuery($type, $conditions, $addSql = null, $delete
$sql = " WHERE $addSql ";
}
}
$sql = (($delete) ? 'DELETE FROM ' : 'SELECT * FROM ').$table.$sql;
$rows = $this->adapter->get($sql.(($delete) ? '' : ' -- keep-cache'), $bindings);
if (!$delete && $this->flagUseCache) {
if ($mode === self::C_MODE_DELETE) $sqlBegin = 'DELETE FROM ';
elseif($mode === self::C_MODE_COUNT) $sqlBegin = 'SELECT COUNT(*) FROM ';
else $sqlBegin = 'SELECT * FROM ';
$sql = $sqlBegin . $table . $sql;
$rows = $this->adapter->get($sql.(($mode === 1) ? '' : ' -- keep-cache'), $bindings);
if (!($mode === 1) && $this->flagUseCache) {
$this->cache[$key] = $rows;
}
return $rows;
Expand Down Expand Up @@ -286,6 +304,35 @@ public function count($beanType, $addSQL = '', $params = array()) {
if ($addSQL != '') $addSQL = ' WHERE '.$addSQL;
return (int) $this->adapter->getCell($sql.$addSQL, $params);
}
/**
* @see RedBean_QueryWriter::queryRecordCountRelated
*/
public function queryRecordCountRelated($sourceType, $targetType, $linkType, $linkID, $addSQL = '', $params = array()) {
$sourceTable = $this->esc($sourceType);
$targetTable = $this->esc($targetType);
$sourceColumn = $this->esc($sourceType.'_id');
$targetColumn = $this->esc($targetType.'_id');
$linkTable = $this->esc($linkType);
$sql = "SELECT count(*) FROM {$targetTable} WHERE id IN (
SELECT {$targetColumn} FROM {$linkTable} WHERE {$sourceColumn} = ? ";
array_unshift($params, $linkID);
if ($sourceColumn === $targetColumn) {
$crossColumn = $this->esc($sourceType.'2_id');
$sql .= " OR {$crossColumn} = ? ";
array_unshift($params, $linkID);
}
$sql .= ") ";
if ($addSQL === '' || $addSQL === null || $addSQL === false) {
//do nothing
} else {
if (strpos(ltrim($addSQL), 'AND')===0) {
$sql .= $addSQL;
} else {
$sql .= ' AND '.$addSQL;
}
}
return (int) $this->adapter->getCell($sql, $params);
}
/**
* Checks whether a number can be treated like an int.
*
Expand Down
48 changes: 48 additions & 0 deletions testing/RedUNIT/Base/Relations.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,55 @@ class RedUNIT_Base_Relations extends RedUNIT_Base {
* @return void
*/
public function run() {

testpack('Test relatedCount()');
R::nuke();
list($d, $d2) = R::dispense('document', 2);
list($p, $p2, $p3) = R::dispense('page', 3);
$d->sharedPage = array($p, $p3);
$d2->sharedPage = array($p, $p2, $p3);
R::storeAll(array($d, $d2));
asrt(R::relatedCount($d, 'page', ' id = ? ', array($p->id)), 1);
asrt(R::relatedCount($d, 'page'), 2);
asrt(R::relatedCount($d2, 'page'), 3);
//now using bean methods..
asrt($d2->countShared('page'), 3);
asrt($d->countShared('page'), 2);
//n-1 counts
$author = R::dispense('author');
$author->ownDocument = array($d, $d2);
R::store($author);

R::nuke();
$book = R::dispense('book');
$book->ownPage = R::dispense('page',10);
$book2 = R::dispense('book');
$book2->ownPage = R::dispense('page', 4);
list($Bill, $James, $Andy) = R::dispense('person', 3);
$book->author = $Bill;
$book->coAuthor = $James;
$book2->author = $Bill;
$book2->coAuthor = $Andy;
$book->price = 25;
$book2->price = 50;
$notes = R::dispense('note',10);
$book->sharedNote = array($notes[0], $notes[1], $notes[2]);
$book2->sharedNote = array($notes[3], $notes[4], $notes[1], $notes[0]);
$books = R::dispense('book', 5);
$books[2]->title = 'boe';
$book->sharedBook = array($books[0], $books[1]);
$book2->sharedBook = array($books[0], $books[2], $books[4]);
R::storeAll(array($book, $book2));
asrt($book->countOwn('page'),10);
asrt($book->withCondition(' id < 5 ')->countOwn('page'), 4);
asrt($Bill->alias('author')->countOwn('book'), 2);
asrt($Andy->alias('coAuthor')->countOwn('book'), 1);
asrt($James->alias('coAuthor')->countOwn('book'), 1);
asrt($Bill->alias('author')->countOwn('book'), 2);
asrt($book->countShared('note'), 3);
asrt($book2->countShared('note'), 4);
asrt($book2->countShared('book'), 3);
asrt($book2->withCondition(' title = ? ',array('boe'))->countShared('book'), 1);
testpack('Test via()');
R::nuke();
$d = R::dispense('doctor')->setAttr('name', 'd1');
Expand Down

0 comments on commit dbad985

Please sign in to comment.