Begin Refactor

This commit is contained in:
color.diff=auto
2021-03-21 13:47:29 -06:00
parent 7622dc0d02
commit 018931d7ae
197 changed files with 47799 additions and 6 deletions

View File

@@ -0,0 +1,55 @@
<?php
namespace RedUNIT;
/**
* Base
*
* This is the base class for all multi-driver Unit Tests.
* By default base class derived tests will offer 'test rounds' for
* all well known RedBeanPHP drivers: mysql (MySQL/MariaDB), pgsql (PostgreSQL),
* sqlite (SQLite3) and CUBRID (CUBRID).
*
* @file RedUNIT/Base.php
* @desc Base class for all drivers that support all database systems.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Base extends RedUNIT
{
/**
* List of DB drivers.
* Contains the list of database drivers as returned by getTargetDrivers().
*
* @var array
*/
protected static $driverList = array( 'mysql', 'pgsql', 'sqlite', 'CUBRID' );
/**
* Adds a driver to the list.
*
* @param string $driverID driver identifier.
*/
public static function addToDriverList( $driverID )
{
self::$driverList[] = $driverID;
}
/**
* Returns a list of drivers for which this driver supports
* 'test rounds'. This class only supports all base drivers.
*
* @see RedUNIT::getTargetDrivers() for details.
*
* @return array
*/
public function getTargetDrivers()
{
return self::$driverList;
}
}

View File

@@ -0,0 +1,387 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Aliasing
*
* Tests aliasing functionality, i.e. fetching beans as,
* inferring correct type and retrieving lists as alias.
*
* @file RedUNIT/Base/Aliasing.php
* @desc Tests for nested beans with aliases, i.e. teacher alias for person etc.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Aliasing extends Base
{
/**
* Test for aliasing issue for LTS version.
*
* @return void
*/
public function testIssueAliasingForLTSVersion() {
$person = R::dispense('person');
$pro = R::dispense('project');
$c = R::dispense('course');
$person->name = 'x';
$person->alias('teacher')->ownProject[] = $pro;
$person->alias('student')->ownCourse[] = $c;
R::store($person);
asrt($c->fresh()->fetchAs('person')->student->name, 'x');
asrt($pro->fresh()->fetchAs('person')->teacher->name, 'x');
$person = $person->fresh();
$person->alias('teacher')->ownProject = array();
$person->alias('student')->ownCourse = array();
R::store($person);
asrt($c->fresh()->fetchAs('person')->student, NULL);
asrt($pro->fresh()->fetchAs('person')->teacher, NULL);
}
/**
* Describing how clearing state of bean works.
* Every method returning somthing (except getID)
* clears prefix-method-state (anything set by withCond,with,alias,fetchAs).
*
* @return void
*/
public function clearStateAdditionalTests()
{
list( $project1, $project2 ) = R::dispense( 'project', 2 );
list( $irene, $ilse ) = R::dispense('person', 2);
$project1->developer = $ilse;
$project1->designer = $irene;
$ilse->name = 'Ilse';
$irene->name = 'Irene';
$project2->developer = $ilse;
R::storeAll( array( $project1, $project2 ) );
$ilse = R::load( 'person', $ilse->id );
asrt( count( $ilse->alias( 'developer' )->ownProject ), 2);
//cached - same list
asrt( count( $ilse->ownProject ), 2);
asrt( count( $ilse->alias( 'designer' )->ownProject ), 0);
//cached - same list
asrt( count( $ilse->ownProject ), 0);
//now test state
asrt( count( $ilse->setAttr( 'a', 'b' )->alias( 'developer' )->ownProject ), 2);
//now test state
$ilse = $ilse->fresh();
//attr clears state...
asrt( count( $ilse->alias( 'developer' )->setAttr( 'a', 'b' )->ownProject ), 0);
//but getID() does not!
$ilse = $ilse->fresh();
$ilse->alias('developer');
$ilse->getID();
asrt( count( $ilse->ownProject ), 2 );
}
/**
* Can switch fetchAs().
* Also checks shadow by storing.
*
* @return void
*/
public function canSwitchParentBean()
{
list( $project1, $project2 ) = R::dispense( 'project', 2 );
list( $irene, $ilse ) = R::dispense('person', 2);
$project1->developer = $ilse;
$project1->designer = $irene;
$ilse->name = 'Ilse';
$irene->name = 'Irene';
$project2->developer = $ilse;
R::storeAll( array( $project1, $project2 ) );
$project1 = R::load( 'project', $project1->id );
asrt( $project1->fetchAs('person')->developer->name, 'Ilse' );
asrt( $project1->fetchAs('person')->designer->name, 'Irene' );
R::store( $project1 );
$project1 = R::load( 'project', $project1->id );
asrt( $project1->fetchAs('person')->designer->name, 'Irene' );
asrt( $project1->fetchAs('person')->developer->name, 'Ilse' );
R::store( $project1 );
asrt( $project1->fetchAs('person')->developer->name, 'Ilse' );
asrt( $project1->fetchAs('person')->designer->name, 'Irene' );
asrt( $project1->fetchAs('person')->developer->name, 'Ilse' );
}
/**
* Switching aliases (->alias) should not change other list during
* storage.
*
* @return void
*/
public function testShadow()
{
list( $project1, $project2 ) = R::dispense( 'project', 2 );
list( $irene, $ilse ) = R::dispense('person', 2);
$project1->developer = $ilse;
$project1->designer = $irene;
$project2->developer = $ilse;
R::storeAll( array( $project1, $project2 ) );
$ilse = R::load( 'person', $ilse->id );
$irene = R::load( 'person', $irene->id );
asrt( count( $ilse->alias('developer')->ownProject ), 2 );
asrt( count( $ilse->alias('designer')->ownProject ), 0 );
R::store( $ilse );
$ilse = R::load( 'person', $ilse->id );
$irene = R::load( 'person', $irene->id );
asrt( count( $ilse->alias('designer')->ownProject ), 0 );
asrt( count( $ilse->alias('developer')->ownProject ), 2 );
R::storeAll( array( $ilse, $irene) );
$ilse = R::load( 'person', $ilse->id );
$irene = R::load( 'person', $irene->id );
asrt( count( $ilse->alias('designer')->ownProject ), 0 );
asrt( count( $ilse->alias('developer')->ownProject ), 2 );
asrt( count( $irene->alias('designer')->ownProject), 1 );
asrt( count( $irene->alias('developer')->ownProject), 0 );
R::storeAll( array( $ilse, $irene) );
$ilse = R::load( 'person', $ilse->id );
$irene = R::load( 'person', $irene->id );
asrt( count( $ilse->alias('designer')->ownProject ), 0 );
asrt( count( $ilse->alias('developer')->ownProject ), 2 );
asrt( count( $irene->alias('designer')->ownProject), 1 );
asrt( count( $irene->alias('developer')->ownProject), 0 );
}
/**
* Issue 291. State not cleared.
*
* @return void
*/
public function testFetchTypeConfusionIssue291()
{
list( $teacher, $student ) = R::dispense( 'person', 2 ) ;
$teacher->name = 'jimmy' ;
$student->name = 'jacko' ;
R::store( $teacher ) ;
R::store( $student ) ;
$client = R::dispense( 'client' ) ;
$client->firm = 'bean AG' ;
R::store( $client ) ;
$project = R::dispense( 'project' ) ;
$project->teacher = $teacher ;
$project->student = $student ;
$project->client = $client ;
R::store( $project ) ;
unset( $project->student ) ;
R::store( $project ) ;
$project = R::load( 'project', 1 ) ;
$teacher = $project->fetchAs( 'person' )->teacher ;
$student = $project->fetchAs( 'person' )->student ;
$client = $project->client ; // this will select from "person" instead of "client"
asrt( $client->firm, 'bean AG' );
}
/**
* Test switching alias (also issue #291).
*
* @return void
*/
public function testAliasSwitch()
{
$student = R::dispense( 'person' );
$project = R::dispense( 'project' );
$project->student = $student;
R::store( $project );
$person = R::load( 'person', $student->id );
asrt( count( $person->alias( 'student' )->ownProject ), 1);
asrt( count( $person->alias( 'teacher' )->ownProject ), 0);
}
/**
* Associating two beans, then loading the associated bean
*
* @return void
*/
public function testAssociated()
{
$person = R::dispense( 'person' );
$person->name = 'John';
R::store( $person );
$course = R::dispense( 'course' );
$course->name = 'Math';
R::store( $course );
$course->teacher = $person;
$id = R::store( $course );
$course = R::load( 'course', $id );
$teacher = $course->fetchAs( 'person' )->teacher;
asrt( $teacher->name, 'John' );
//Trying to load a property that has an invalid name
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$book->wrongProperty = array( $page );
try {
$book->wrongProperty[] = $page;
R::store( $book );
fail();
} catch ( RedException $e ) {
pass();
} catch ( \Exception $e ) {
fail();
}
}
/**
* Test for quick detect change.
*
* @return void
*/
public function basic()
{
$book = R::dispense( 'book' );
asrt( isset( $book->prop ), FALSE ); //not a very good test
asrt( in_array( 'prop', array_keys( $book->export() ) ), FALSE ); //better...
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$book->paper = $page;
$id = R::store( $book );
$book = R::load( 'book', $id );
asrt( FALSE, ( isset( $book->paper ) ) );
asrt( FALSE, ( isset( $book->page ) ) );
/**
* The following tests try to store various things that aren't
* beans (which is expected) with the own* and shared* properties
* which only accept beans as assignments, so they're expected to fail
*/
foreach ( array( 'a string', 1928, TRUE, NULL, array()) as $value ) {
try {
$book->ownPage[] = $value;
R::store( $book );
$book->sharedPage[] = $value;
R::store( $book );
fail();
} catch ( RedException $e ) {
pass();
} catch ( \Exception $e ) {
if (strpos($e->getMessage(),'Array to string conversion')===FALSE) {
fail();
}
}
}
}
/**
* Finding $person beans that have been aliased into various roles
*
* @return void
*/
public function testAliasedFinder()
{
$message = R::dispense( 'message' );
$message->subject = 'Roommate agreement';
list( $sender, $recipient ) = R::dispense( 'person', 2 );
$sender->name = 'Sheldon';
$recipient->name = 'Leonard';
$message->sender = $sender;
$message->recipient = $recipient;
$id = R::store( $message );
$message = R::load( 'message', $id );
asrt( $message->fetchAs( 'person' )->sender->name, 'Sheldon' );
asrt( $message->fetchAs( 'person' )->recipient->name, 'Leonard' );
$otherRecipient = R::dispense( 'person' );
$otherRecipient->name = 'Penny';
$message->recipient = $otherRecipient;
R::store( $message );
$message = R::load( 'message', $id );
asrt( $message->fetchAs( 'person' )->sender->name, 'Sheldon' );
asrt( $message->fetchAs( 'person' )->recipient->name, 'Penny' );
}
/**
* Test Basic Fetch AS functionality.
*/
public function testBasicFetchAs()
{
$project = R::dispense( 'project' );
$project->name = 'Mutant Project';
list( $teacher, $student ) = R::dispense( 'person', 2 );
$teacher->name = 'Charles Xavier';
$project->student = $student;
$project->student->name = 'Wolverine';
$project->teacher = $teacher;
$id = R::store( $project );
$project = R::load( 'project', $id );
asrt( $project->fetchAs( 'person' )->teacher->name, 'Charles Xavier' );
asrt( $project->fetchAs( 'person' )->student->name, 'Wolverine' );
}
/**
* Test Basic list variations.
*
* @return void
*/
public function testBasicListVariations()
{
$farm = R::dispense( 'building' );
$village = R::dispense( 'village' );
$farm->name = 'farm';
$village->name = 'Dusty Mountains';
$farm->village = $village;
$id = R::store( $farm );
$farm = R::load( 'building', $id );
asrt( $farm->name, 'farm' );
asrt( $farm->village->name, 'Dusty Mountains' );
$village = R::dispense( 'village' );
list( $mill, $tavern ) = R::dispense( 'building', 2 );
$mill->name = 'Mill';
$tavern->name = 'Tavern';
$village->ownBuilding = array( $mill, $tavern );
$id = R::store( $village );
$village = R::load( 'village', $id );
asrt( count( $village->ownBuilding ), 2 );
$village2 = R::dispense( 'village' );
$army = R::dispense( 'army' );
$village->sharedArmy[] = $army;
$village2->sharedArmy[] = $army;
$id1 = R::store( $village );
$id2 = R::store( $village2 );
$village1 = R::load( 'village', $id1 );
$village2 = R::load( 'village', $id2 );
asrt( count( $village1->sharedArmy ), 1 );
asrt( count( $village2->sharedArmy ), 1 );
asrt( count( $village1->ownArmy ), 0 );
asrt( count( $village2->ownArmy ), 0 );
}
/**
* Tests whether aliasing plays nice with beautification.
* Ensure that aliased column aren't beautified
*
* @return void
*/
public function testAliasWithBeautify()
{
$points = R::dispense( 'point', 2 );
$line = R::dispense( 'line' );
$line->pointA = $points[0];
$line->pointB = $points[1];
R::store( $line );
$line2 = R::dispense( 'line' );
$line2->pointA = $line->fetchAs('point')->pointA;
$line2->pointB = R::dispense( 'point' );
R::store( $line2 );
//now we have two points per line (1-to-x)
//I want to know which lines cross A:
$a = R::load( 'point', $line->pointA->id ); //reload A
$lines = $a->alias( 'pointA' )->ownLine;
asrt( count( $lines ), 2 );
}
}

View File

@@ -0,0 +1,273 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
/**
* Arrays
*
* Beans can also be treated like arrays, this test verifies
* whether the bean array interface works correctly in various
* scenarios (combination with lists and so on).
*
* @file RedUNIT/Base/Arrays.php
* @desc Tests the array interface of beans
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Arrays extends Base
{
/**
* Tests basic array access.
*
* @return void
*/
public function testArrayAccess()
{
$bean = R::dispense('bean');
$bean->name = 'bean';
$bean->taste = 'salty';
$properties = array();
foreach($bean as $key => $value) {
$properties[ $key ] = $value;
}
asrt( count( $bean ), 3 );
asrt( count( $properties ), 3 );
asrt( isset( $properties['id'] ), TRUE );
asrt( isset( $properties['name'] ), TRUE );
asrt( isset( $properties['taste'] ), TRUE );
$bean = R::dispense('bean');
$bean['name'] = 'bean';
$bean['taste'] = 'salty';
$properties = array();
foreach($bean as $key => $value) {
$properties[ $key ] = $value;
}
asrt( count( $bean ), 3 );
asrt( count( $properties ), 3 );
asrt( isset( $properties['id'] ), TRUE );
asrt( isset( $properties['name'] ), TRUE );
asrt( isset( $properties['taste'] ), TRUE );
}
/**
* Tests array access with lists.
* Tests whether list properties behave as arrays correctly.
*
* @return void
*/
public function testArrayAccessAndLists()
{
$book = R::dispense('book');
$book['title'] = 'My Book';
//Can we add a bean in list with array access?
$book['ownPage'][] = R::dispense('page');
$book['ownPage'][] = R::dispense('page');
asrt( count( $book ), 3 );
$properties = array();
foreach($book as $key => $value) {
$properties[ $key ] = $value;
}
asrt( count( $properties ), 3 );
//Dont reveal aliased x-own and -List in foreach-loop
asrt( isset( $properties['id'] ), TRUE );
asrt( isset( $properties['title'] ), TRUE );
asrt( isset( $properties['ownPage'] ), TRUE );
asrt( isset( $properties['ownPageList'] ), FALSE );
asrt( isset( $properties['xownPage'] ), FALSE );
asrt( isset( $properties['xownPageList'] ), FALSE );
//But keep them countable
asrt( count( $book['ownPage'] ), 2 );
asrt( count( $book['ownPageList'] ), 2 );
asrt( count( $book['xownPage'] ), 2 );
asrt( count( $book['xownPageList'] ), 2 );
//And reveal state of items with isset()
asrt( isset( $book['id'] ), TRUE );
asrt( isset( $book['title'] ), TRUE );
asrt( isset( $book['ownPage'] ), TRUE );
asrt( isset( $book['ownPageList'] ), TRUE );
asrt( isset( $book['xownPage'] ), TRUE );
asrt( isset( $book['xownPageList'] ), TRUE );
//Can we add using the List alias?
$book['ownPageList'][] = R::dispense('page');
asrt( count( $book['ownPage'] ), 3 );
asrt( count( $book['ownPageList'] ), 3 );
asrt( count( $book['xownPage'] ), 3 );
asrt( count( $book['xownPageList'] ), 3 );
//Can we add using the x-mode alias?
$book['ownPageList'][] = R::dispense('page');
asrt( count( $book['ownPage'] ), 4 );
asrt( count( $book['ownPageList'] ), 4 );
asrt( count( $book['xownPage'] ), 4 );
asrt( count( $book['xownPageList'] ), 4 );
//Can we unset using array access?
unset( $book['ownPage'] );
asrt( isset( $book['ownPage'] ), FALSE );
asrt( isset( $book['ownPageList'] ), FALSE );
asrt( isset( $book['xownPage'] ), FALSE );
asrt( isset( $book['xownPageList'] ), FALSE );
$book['ownPage'] = array( R::dispense('page') );
unset( $book['xownPage'] );
asrt( isset( $book['ownPage'] ), FALSE );
asrt( isset( $book['ownPageList'] ), FALSE );
asrt( isset( $book['xownPage'] ), FALSE );
asrt( isset( $book['xownPageList'] ), FALSE );
$book['ownPage'] = array( R::dispense('page') );
unset( $book['ownPageList'] );
asrt( isset( $book['ownPage'] ), FALSE );
asrt( isset( $book['ownPageList'] ), FALSE );
asrt( isset( $book['xownPage'] ), FALSE );
asrt( isset( $book['xownPageList'] ), FALSE );
$book['ownPage'] = array( R::dispense('page') );
unset( $book['xownPageList'] );
asrt( isset( $book['ownPage'] ), FALSE );
asrt( isset( $book['ownPageList'] ), FALSE );
asrt( isset( $book['xownPage'] ), FALSE );
asrt( isset( $book['xownPageList'] ), FALSE );
//does it work with shared lists as well?
$book['sharedCategory'] = array( R::dispense('category') );
asrt( count( $book ), 3 );
$properties = array();
foreach($book as $key => $value) {
$properties[ $key ] = $value;
}
asrt( isset( $properties['sharedCategory'] ), TRUE );
asrt( isset( $properties['sharedCategoryList'] ), FALSE );
asrt( isset( $book['sharedCategory'] ), TRUE );
asrt( isset( $book['sharedCategoryList'] ), TRUE );
asrt( count( $book['sharedCategory'] ), 1 );
asrt( count( $book['sharedCategoryList'] ), 1 );
$book['sharedCategory'][] = R::dispense( 'category' );
asrt( count( $book['sharedCategory'] ), 2 );
asrt( count( $book['sharedCategoryList'] ), 2 );
$book['sharedCategoryList'][] = R::dispense( 'category' );
asrt( count( $book['sharedCategory'] ), 3 );
asrt( count( $book['sharedCategoryList'] ), 3 );
}
/**
* Tests array access with parent beans.
*
* @return void
*/
public function testArrayAccessWithBeans()
{
$book = R::dispense( 'bean' );
$book['author'] = R::dispense( 'author' );
asrt( isset( $book['author'] ), TRUE );
asrt( count( $book ), 2 );
$book['author']['name'] = 'me';
asrt( $book['author']['name'], 'me' );
$book['author']['address'] = R::dispense( 'address' );
$book['author']['ownTagList'][] = R::dispense( 'tag' );
asrt( isset( $book['author']['address'] ), TRUE );
asrt( isset( $book['author']['ownTag'] ), TRUE );
asrt( count( $book['author']['ownTag'] ), 1 );
asrt( isset( $book['author']['xownTag'] ), TRUE );
asrt( count( $book['author']['xownTag'] ), 1 );
asrt( isset( $book['author']['ownTagList'] ), TRUE );
asrt( count( $book['author']['ownTagList'] ), 1 );
asrt( isset( $book['author']['xownTagList'] ), TRUE );
asrt( count( $book['author']['xownTagList'] ), 1 );
unset( $book['author'] );
asrt( isset( $book['author'] ), FALSE );
asrt( count( $book ), 1 );
}
/**
* Tests array access with CRUD operations.
*
* @return void
*/
public function testArrayAccessWithCRUD()
{
R::nuke();
$book = R::dispense( 'book' );
$book['ownPageList'] = R::dispense( 'page', 3 );
R::store( $book );
$book = $book->fresh();
//note that isset first returns FALSE, so you can check if a list is loaded
asrt( isset( $book['ownPage'] ), FALSE );
asrt( isset( $book['ownPageList'] ), FALSE );
asrt( isset( $book['xownPage'] ), FALSE );
asrt( isset( $book['xownPageList'] ), FALSE );
//count triggers load...
asrt( count( $book['ownPage'] ), 3 );
asrt( isset( $book['ownPage'] ), TRUE );
asrt( isset( $book['ownPageList'] ), TRUE );
asrt( isset( $book['xownPage'] ), TRUE );
asrt( isset( $book['xownPageList'] ), TRUE );
$book = $book->fresh();
asrt( count( $book['xownPage'] ), 3 );
$book = $book->fresh();
asrt( count( $book['ownPageList'] ), 3 );
$book = $book->fresh();
asrt( count( $book['xownPageList'] ), 3 );
$book['ownPage'][] = R::dispense( 'page' );
R::store( $book );
$book = $book->fresh();
asrt( count( $book['ownPage'] ), 4 );
$book = $book->fresh();
asrt( count( $book['xownPage'] ), 4 );
$book = $book->fresh();
asrt( count( $book['ownPageList'] ), 4 );
$book = $book->fresh();
asrt( count( $book['xownPageList'] ), 4 );
//does dependency still work?
$book['xownPageList'] = array();
R::store( $book );
$book = $book->fresh();
asrt( count( $book['ownPage'] ), 0 );
$book = $book->fresh();
asrt( count( $book['xownPage'] ), 0 );
$book = $book->fresh();
asrt( count( $book['ownPageList'] ), 0 );
$book = $book->fresh();
asrt( count( $book['xownPageList'] ), 0 );
//does shared list work as well?
$book['sharedTag'] = R::dispense( 'tag', 2 );
R::store( $book );
$book = $book->fresh();
//note that isset first returns FALSE, so you can check if a list is loaded
asrt( isset( $book['sharedTagList'] ), FALSE );
asrt( isset( $book['sharedTag'] ), FALSE );
//count triggers load...
asrt( count( $book['sharedTagList'] ), 2 );
asrt( count( $book['sharedTag'] ), 2 );
asrt( isset( $book['sharedTagList'] ), TRUE );
asrt( isset( $book['sharedTag'] ), TRUE );
$book['sharedTag'][] = R::dispense( 'tag' );
R::store( $book );
$book = $book->fresh();
asrt( count( $book['sharedTagList'] ), 3 );
asrt( count( $book['sharedTag'] ), 3 );
$book['sharedTagList'][] = R::dispense( 'tag' );
R::store( $book );
$book = $book->fresh();
asrt( count( $book['sharedTagList'] ), 4 );
asrt( count( $book['sharedTag'] ), 4 );
//does it also work with cross-shared
$book['sharedBookList'][] = R::dispense( 'book' );
R::store( $book );
$book = $book->fresh();
asrt( isset( $book['sharedBookList'] ), FALSE );
asrt( count( $book['sharedBookList'] ), 1 );
$first = reset( $book['sharedBookList'] );
$id = $first['id'];
asrt( count( $book['sharedBookList'][$id]['sharedBookList'] ), 1 );
$properties = array();
foreach($book as $key => $value) {
$properties[ $key ] = $value;
}
asrt( count( $properties ), 2 );
$keys = array_keys( $properties );
sort( $keys );
asrt( implode( ',', $keys ), 'id,sharedBook' );
}
}

View File

@@ -0,0 +1,296 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\ToolBox as ToolBox;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\AssociationManager as AssociationManager;
use RedBeanPHP\RedException\SQL as SQL;
/**
* Association
*
* Originally meant to test R::associate - which is no longer
* used, this class tests all kinds of relations from
* one-to-one to polymorph relations using the poly() method.
*
* @file RedUNIT/Base/Association.php
* @desc Tests Association API (N:N associations)
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Association extends Base
{
/**
* MySQL specific tests.
* Test MySQL specific issues with constraints.
*
* @return void
*/
public function testMySQL()
{
if ( $this->currentlyActiveDriverID !== 'mysql' ) {
return;
}
testpack( 'Throw exception in case of issue with assoc constraint' );
$bunny = R::dispense( 'bunny' );
$carrot = R::dispense( 'carrot' );
$faultyWriter = new \FaultyWriter( R::getToolBox()->getDatabaseAdapter() );
$faultyOODB = new OODB( $faultyWriter );
$faultyOODB->setBeanHelper( R::getRedBean()->getBeanHelper() );
$faultyToolbox = new ToolBox( $faultyOODB, R::getToolBox()->getDatabaseAdapter(), $faultyWriter );
$faultyAssociationManager = new AssociationManager( $faultyToolbox );
$faultyWriter->setSQLState( '23000' );
$faultyAssociationManager->associate( $bunny, $carrot );
pass();
$faultyWriter->setSQLState( '42S22' );
R::nuke();
try {
$faultyAssociationManager->associate( $bunny, $carrot );
fail();
} catch ( SQL $exception ) {
pass();
}
}
/**
* Test fast-track deletion, i.e. bypassing FUSE.
* For link beans.
*
* @return void
*/
public function testFastTrackDeletion()
{
testpack( 'Test fast-track deletion' );
$ghost = R::dispense( 'ghost' );
$house = R::dispense( 'house' );
$house->sharedGhost[] = $ghost;
R::store($house);
\Model_Ghost_House::$deleted = FALSE;
R::getRedBean()->getAssociationManager()->unassociate( $house, $ghost );
// No fast-track, assoc bean got trashed
asrt( \Model_Ghost_House::$deleted, TRUE );
\Model_Ghost_House::$deleted = FALSE;
$house->sharedGhost[] = $ghost;
R::store($house);
R::getRedBean()->getAssociationManager()->unassociate( $house, $ghost, TRUE );
// Fast-track, assoc bean got deleted right away
asrt( \Model_Ghost_House::$deleted, FALSE );
}
/**
* Test self-referential associations.
*
* @return void
*/
public function testCrossAssociation()
{
$ghost = R::dispense( 'ghost' );
$ghost2 = R::dispense( 'ghost' );
R::getRedBean()->getAssociationManager()->associate( $ghost, $ghost2 );
\Model_Ghost_Ghost::$deleted = FALSE;
R::getRedBean()->getAssociationManager()->unassociate( $ghost, $ghost2 );
// No fast-track, assoc bean got trashed
asrt( \Model_Ghost_Ghost::$deleted, TRUE );
\Model_Ghost_Ghost::$deleted = FALSE;
R::getRedBean()->getAssociationManager()->unassociate( $ghost, $ghost2, TRUE );
// Fast-track, assoc bean got deleted right away
asrt( \Model_Ghost_Ghost::$deleted, FALSE );
}
/**
* Test limited support for polymorph associations.
* RedBeanPHP does not really feature polymorph relations since
* they are not really compatible with traditional relational databases.
* However a light-weight, basic implementation has been added for
* those circumstances where you can't live without...
* i.e... possible legacy systems and so on.
*
* @return void
*/
public function testPoly()
{
testpack( 'Testing poly' );
$shoe = R::dispense( 'shoe' );
$lace = R::dispense( 'lace' );
$lace->color = 'white';
$id = R::store( $lace );
$shoe->itemType = 'lace';
$shoe->item = $lace;
$id = R::store( $shoe );
$shoe = R::load( 'shoe', $id );
$x = $shoe->poly( 'itemType' )->item;
asrt( $x->color, 'white' );
}
/**
* Test limited support for 1-to-1 associations.
* The rule is, one-to-ones are supposes to be in the same table,
* this is just for some legacy tables not designed to work
* with RedBeanPHP at all.
*
* @return void
*/
public function testOneToOne()
{
testpack( 'Testing one-to-ones' );
$author = R::dispense( 'author' )->setAttr( 'name', 'a' );;
$bio = R::dispense( 'bio' )->setAttr( 'name', 'a' );
R::storeAll( array( $author, $bio ) );
$id1 = $author->id;
$author = R::dispense( 'author' )->setAttr( 'name', 'b' );;
$bio = R::dispense( 'bio' )->setAttr( 'name', 'b' );
R::storeAll( array( $author, $bio ) );
$x = $author->one( 'bio' );
$y = $bio->one('author');
asrt( $x->name, $bio->name );
asrt( $y->name, $author->name );
asrt( $x->id, $bio->id );
asrt( $y->id, $author->id );
$id2 = $author->id;
list( $a, $b ) = R::loadMulti( 'author,bio', $id1 );
asrt( $a->name, $b->name );
asrt( $a->name, 'a' );
list( $a, $b ) = R::loadMulti( 'author,bio', $id2 );
asrt( $a->name, $b->name );
asrt( $a->name, 'b' );
list( $a, $b ) = R::loadMulti( array( 'author', 'bio' ), $id1 );
asrt( $a->name, $b->name );
asrt( $a->name, 'a' );
list( $a, $b ) = R::loadMulti( array( 'author', 'bio' ), $id2 );
asrt( $a->name, $b->name );
asrt( $a->name, 'b' );
asrt( is_array( R::loadMulti( NULL, 1 ) ), TRUE );
asrt( ( count( R::loadMulti( NULL, 1 ) ) === 0 ), TRUE );
}
/**
* Test single column bases unique constraints.
*
* @return void
*/
public function testSingleColUniqueConstraint()
{
testpack( 'Testing unique constraint on single column' );
$book = R::dispense( 'book' );
$book->title = 'bla';
$book->extra = 2;
$id = R::store( $book );
R::getWriter()->addUniqueIndex( 'book', array( 'title' ) );
$book = R::dispense( 'book' );
$book->title = 'bla';
$expected = NULL;
try {
R::store( $book );
fail();
} catch ( SQL $e ) {
$expected = $e;
}
asrt( ( $expected instanceof SQL ), TRUE );
asrt( R::count( 'book' ), 1 );
$book = R::load( 'book', $id );
// Causes failure, table will be rebuild
$book->extra = 'CHANGE';
$id2 = R::store( $book );
$book2 = R::load( 'book', $id2 );
$book = R::dispense( 'book' );
$book->title = 'bla';
try {
R::store( $book );
fail();
} catch ( SQL $e ) {
$expected = $e;
}
asrt( ( $expected instanceof SQL ), TRUE );
asrt( R::count( 'book' ), 1 );
}
/**
* Test multiple assiociation.
*
* @return void
*/
public function testMultiAssociationDissociation()
{
$wines = R::dispense( 'wine', 3 );
$cheese = R::dispense( 'cheese', 3 );
$olives = R::dispense( 'olive', 3 );
R::getRedBean()->getAssociationManager()->associate( $wines, array_merge( $cheese, $olives ) );
asrt( R::count( 'cheese' ), 3 );
asrt( R::count( 'olive' ), 3 );
asrt( R::count( 'wine' ), 3 );
asrt( count( $wines[0]->sharedCheese ), 3 );
asrt( count( $wines[0]->sharedOlive ), 3 );
asrt( count( $wines[1]->sharedCheese ), 3 );
asrt( count( $wines[1]->sharedOlive ), 3 );
asrt( count( $wines[2]->sharedCheese ), 3 );
asrt( count( $wines[2]->sharedOlive ), 3 );
R::getRedBean()->getAssociationManager()->unassociate( $wines, $olives );
asrt( count( $wines[0]->sharedCheese ), 3 );
asrt( count( $wines[0]->sharedOlive ), 0 );
asrt( count( $wines[1]->sharedCheese ), 3 );
asrt( count( $wines[1]->sharedOlive ), 0 );
asrt( count( $wines[2]->sharedCheese ), 3 );
asrt( count( $wines[2]->sharedOlive ), 0 );
R::getRedBean()->getAssociationManager()->unassociate( array( $wines[1] ), $cheese );
asrt( count( $wines[0]->sharedCheese ), 3 );
asrt( count( $wines[0]->sharedOlive ), 0 );
asrt( count( $wines[1]->sharedCheese ), 0 );
asrt( count( $wines[1]->sharedOlive ), 0 );
asrt( count( $wines[2]->sharedCheese ), 3 );
asrt( count( $wines[2]->sharedOlive ), 0 );
R::getRedBean()->getAssociationManager()->unassociate( array( $wines[2] ), $cheese );
asrt( count( $wines[0]->sharedCheese ), 3 );
asrt( count( $wines[0]->sharedOlive ), 0 );
asrt( count( $wines[1]->sharedCheese ), 0 );
asrt( count( $wines[1]->sharedOlive ), 0 );
asrt( count( $wines[2]->sharedCheese ), 0 );
asrt( count( $wines[2]->sharedOlive ), 0 );
}
/**
* Tests error handling related to association.
* On database systems providing informative SQL STATE error codes
* RedBeanPHP should not mind non-existing tables or columns in
* fluid mode.
*
* @return void
*/
public function testErrorHandling()
{
R::nuke();
list( $book, $page ) = R::dispenseAll( 'book,page' );
$book->sharedPage[] = $page;
R::store( $page );
$redbean = R::getRedBean();
$am = $redbean->getAssociationManager();
//SQLite and CUBRID do not comply with ANSI SQLState codes.
$catchAll = ( $this->currentlyActiveDriverID == 'sqlite' || $this->currentlyActiveDriverID === 'CUBRID' );
try {
$am->related( $book, 'page', 'invalid SQL' );
if ($catchAll) pass(); else fail();
} catch ( SQL $e ) {
if ($catchAll) fail(); else pass();
}
try {
$am->related( $book, 'cover');
pass();
} catch ( SQL $e ) {
fail();
}
try {
$am->related( R::dispense('cover'), 'book' );
pass();
} catch ( SQL $e ) {
fail();
}
}
}

View File

@@ -0,0 +1,151 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\RedException\SQL as SQL;
/**
* Batch
*
* Test whether we can load a group of beans in one go, i.e.
* the batch loading functionality of RedBeanPHP, also in combination
* with aliasing.
*
* @file RedUNIT/Base/Batch.php
* @desc Tests batch loading of beans, i.e. loading large collections of beans in optimized way.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Batch extends Base
{
/**
* Can we delete beans by find-query?
*
* @return void
*/
public function testHunt()
{
R::nuke();
$books = R::dispenseAll( 'book*3' );
R::storeAll( $books[0] );
pass();
asrt( ( R::count( 'book' ) === 3 ), TRUE );
$ids = R::getCol( 'SELECT id FROM book' );
R::hunt( 'book', ' id IN ( '. R::genSlots( $ids ) .' ) ', $ids );
asrt( ( R::count( 'book' ) === 0 ), TRUE );
}
/**
* Tests batch trashing. Can we trash beans using
* IDs only?
*
* @return void
*/
public function testBatchTrash()
{
R::nuke();
$books = R::dispenseAll( 'book*3' );
R::storeAll( $books[0] );
pass();
asrt( ( R::count( 'book' ) === 3 ), TRUE );
R::trashBatch( 'book', R::getCol( 'SELECT id FROM book' ) );
asrt( ( R::count( 'book' ) === 0 ), TRUE );
}
/**
* Begin testing.
* This method runs the actual test pack.
*
* @return void
*/
public function testBatch()
{
R::freeze( FALSE );
$toolbox = R::getToolBox();
$adapter = $toolbox->getDatabaseAdapter();
$writer = $toolbox->getWriter();
$redbean = $toolbox->getRedBean();
$pdo = $adapter->getDatabase();
$page = $redbean->dispense( "page" );
$page->name = "page no. 1";
$page->rating = 1;
$id1 = $redbean->store( $page );
$page = $redbean->dispense( "page" );
$page->name = "page no. 2";
$id2 = $redbean->store( $page );
$batch = $redbean->batch( "page", array( $id1, $id2 ) );
asrt( count( $batch ), 2 );
asrt( $batch[$id1]->getMeta( "type" ), "page" );
asrt( $batch[$id2]->getMeta( "type" ), "page" );
asrt( (int) $batch[$id1]->id, $id1 );
asrt( (int) $batch[$id2]->id, $id2 );
$book = $redbean->dispense( "book" );
$book->name = "book 1";
$redbean->store( $book );
$book = $redbean->dispense( "book" );
$book->name = "book 2";
$redbean->store( $book );
$book = $redbean->dispense( "book" );
$book->name = "book 3";
$redbean->store( $book );
$books = $redbean->batch( "book", $adapter->getCol( "SELECT id FROM book" ) );
asrt( count( $books ), 3 );
$a = $redbean->batch( 'book', 9919 );
asrt( is_array( $a ), TRUE );
asrt( count( $a ), 0 );
$a = $redbean->batch( 'triangle', 1 );
asrt( is_array( $a ), TRUE );
asrt( count( $a ), 0 );
R::freeze( TRUE );
$a = $redbean->batch( 'book', 9919 );
asrt( is_array( $a ), TRUE );
asrt( count( $a ), 0 );
try {
$a = $redbean->batch( 'triangle', 1 );
fail();
} catch(SQL $e) {
pass();
}
R::freeze( FALSE );
asrt( R::wipe( 'spaghettimonster' ), FALSE );
}
/**
* Test missing bean scenarios.
*
* @return void
*/
public function testMissingBeans()
{
testpack( 'deal with missing beans' );
$id = R::store( R::dispense( 'beer' ) );
$bottles = R::batch( 'beer', array( $id, $id + 1, $id + 2 ) );
asrt( count( $bottles ), 3 );
asrt( (int) $bottles[$id]->id, (int) $id );
asrt( (int) $bottles[$id + 1]->id, 0 );
asrt( (int) $bottles[$id + 2]->id, 0 );
}
/**
* Test batch alias loadAll.
*
* @return void
*/
public function testBatchAliasLoadAll()
{
$ids = R::storeAll( R::dispense( 'page', 2 ) );
$pages = R::loadAll( 'page', $ids );
asrt( is_array( $pages ), TRUE );
asrt( count( $pages ), 2 );
asrt( ( $pages[$ids[0]] instanceof OODBBean ), TRUE );
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\SimpleModel as SimpleModel;
/**
* Boxing
*
* Test boxing and unboxing of beans.
*
* @file RedUNIT/Base/Boxing.php
* @desc Tests bean boxing and unboxing functionality.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Boxing extends Base
{
/**
* Test boxing beans.
*
* @return void
*/
public function testBoxing()
{
R::nuke();
$bean = R::dispense( 'boxedbean' )->box();
R::trash( $bean );
pass();
$bean = R::dispense( 'boxedbean' );
$bean->sharedBoxbean = R::dispense( 'boxedbean' )->box();
R::store( $bean );
pass();
$bean = R::dispense( 'boxedbean' );
$bean->ownBoxedbean = R::dispense( 'boxedbean' )->box();
R::store( $bean );
pass();
$bean = R::dispense( 'boxedbean' );
$bean->other = R::dispense( 'boxedbean' )->box();
R::store( $bean );
pass();
$bean = R::dispense( 'boxedbean' );
$bean->title = 'MyBean';
$box = $bean->box();
asrt( ( $box instanceof \Model_Boxedbean ), TRUE );
R::store( $box );
}
/**
* Test fix for issue #512 - thanks for reporting Bernhard H.
* OODBBean::__toString() implementation only works with C_ERR_IGNORE
*
* @return void
*/
public function testToStringIssue512()
{
R::setErrorHandlingFUSE( \RedBeanPHP\OODBBean::C_ERR_FATAL );
$boxedBean = R::dispense( 'boxedbean' );
$str = (string) $boxedBean;
asrt( $str, '{"id":0}' ); //no fatal error
R::setErrorHandlingFUSE( FALSE );
}
}

View File

@@ -0,0 +1,135 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
/**
* Chill
*
* Tests 'chill' mode. In this mode some bean types are frozen,
* their schemas cannot be modified while others are fluid and
* can still be adjusted.
*
* @file RedUNIT/Base/Chill.php
* @desc Tests chill list functionality, i.e. freezing a subset of all types.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Chill extends Base
{
/**
* Test Chill mode.
*
* @return void
*/
public function testChill()
{
$bean = R::dispense( 'bean' );
$bean->col1 = '1';
$bean->col2 = '2';
R::store( $bean );
asrt( count( R::getWriter()->getColumns( 'bean' ) ), 3 );
$bean->col3 = '3';
R::store( $bean );
asrt( count( R::getWriter()->getColumns( 'bean' ) ), 4 );
R::freeze( array( 'umbrella' ) );
$bean->col4 = '4';
R::store( $bean );
asrt( count( R::getWriter()->getColumns( 'bean' ) ), 5 );
R::freeze( array( 'bean' ) );
$bean->col5 = '5';
try {
R::store( $bean );
fail();
} catch (\Exception $e ) {
pass();
}
asrt( count( R::getWriter()->getColumns( 'bean' ) ), 5 );
R::freeze( array() );
$bean->col5 = '5';
R::store( $bean );
asrt( count( R::getWriter()->getColumns( 'bean' ) ), 6 );
}
/**
* Test whether we cannot add unique constraints on chilled tables,
* otherwise you cannot avoid this from happening when adding beans to the
* shared list :) -- this is almost a theoretical issue however we want it
* to work according to specifications!
*
* @return void
*/
public function testDontAddUniqueConstraintForChilledBeanTypes()
{
R::nuke();
$person = R::dispense( 'person' );
$role = R::dispense( 'role' );
$person->sharedRole[] = $role;
R::store( $person );
$person->sharedRole[] = R::dispense( 'role' );
R::store( $person );
$bean = R::getRedBean()->dispense('person_role');
$bean->personId = $person->id;
$bean->roleId = $role->id;
try {
R::store( $bean );
fail();
} catch(\Exception $e) {
pass();
}
asrt(R::count('person_role'), 2);
R::nuke();
$link = R::getRedBean()->dispense('person_role');
$person = R::dispense( 'person' );
$role = R::dispense( 'role' );
$link->person = $person;
$link->role = $role;
R::store( $link );
R::freeze(array('person_role'));
$person->sharedRole[] = R::dispense( 'role' );
R::store( $person );
$bean = R::getRedBean()->dispense('person_role');
$bean->personId = $person->id;
$bean->roleId = $role->id;
try {
R::store( $bean );
pass();
} catch(\Exception $e) {
fail();
}
asrt(R::count('person_role'), 3);
R::freeze( array() ); //set freeze to FALSE and clear CHILL LIST!
}
/**
* Test whether we can set and reset the chill list and check the contents
* of the chill list.
*
* @return void
*/
public function testChillTest()
{
R::freeze( array( 'beer' ) );
$oodb = R::getRedBean();
asrt( $oodb->isChilled( 'beer' ), TRUE );
asrt( $oodb->isChilled( 'wine' ), FALSE );
R::freeze( FALSE );
$oodb = R::getRedBean();
asrt( $oodb->isChilled( 'beer' ), TRUE );
asrt( $oodb->isChilled( 'wine' ), FALSE );
R::freeze( TRUE );
$oodb = R::getRedBean();
asrt( $oodb->isChilled( 'beer' ), TRUE );
asrt( $oodb->isChilled( 'wine' ), FALSE );
R::freeze( array() );
$oodb = R::getRedBean();
asrt( $oodb->isChilled( 'beer' ), FALSE );
asrt( $oodb->isChilled( 'wine' ), FALSE );
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\ToolBox as ToolBox;
use RedBeanPHP\QueryWriter\SQLiteT as SQLiteT;
/**
* Close
*
* Tests whether we can close the database connection.
*
* @file RedUNIT/Base/Close.php
* @desc Tests database closing functionality.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Close extends Base
{
/**
* Test closing database connection.
*
* @return void
*/
public function testClose()
{
// Test whether we can select a specific feature set
R::useFeatureSet('novice/latest');
pass();
R::useFeatureSet('latest');
pass();
R::useFeatureSet('5.3');
pass();
R::useFeatureSet('novice/5.3');
pass();
try {
R::useFeatureSet('5.2');
fail();
} catch ( \Exception $e ) {
asrt( $e->getMessage(), 'Unknown feature set label.' );
}
try {
R::nuke();
fail();
} catch( \Exception $e ) {
asrt( $e->getMessage(), 'The nuke() command has been disabled using noNuke() or R::feature(novice/...).' );
}
R::useFeatureSet('latest');
//Close
R::getDatabaseAdapter()->setOption( 'setInitQuery', NULL );
asrt( R::getDatabaseAdapter()->getDatabase()->isConnected(), TRUE );
R::close();
asrt( R::getDatabaseAdapter()->getDatabase()->isConnected(), FALSE );
}
}

View File

@@ -0,0 +1,314 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\QueryWriter\AQueryWriter;
/**
* Concurrency
*
* Tests whether we can lock beans.
*
* @file RedUNIT/Base/Concurrency.php
* @desc Tests concurrency scenarios
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Concurrency extends Base
{
/**
* Returns the target drivers for this test.
* This test only works with Postgres and MySQL/MariaDB.
*
* @return array
*/
public function getTargetDrivers()
{
return array( 'pgsql','mysql' );
}
/**
* Prepares the database connection.
*
* @return void
*/
public function prepare()
{
try{
R::close();
} catch( \Exception $e ) {}
}
/**
* This test has to be run manually.
*
* @return void
*/
private function testLockException()
{
R::nuke();
$lock = R::dispense('lock');
$lock->value = 123;
R::store($lock);
$c = pcntl_fork();
if ($c == -1) exit(1);
if (!$c) {
R::selectDatabase($this->currentlyActiveDriverID . 'c2');
R::freeze(TRUE);
try { R::exec('SET SESSION innodb_lock_wait_timeout=5');}catch( \Exception $e ){}
try { R::exec('SET autocommit = 0'); }catch( \Exception $e ){}
R::begin();
$lock = R::loadForUpdate('lock', $lock->id);
$lock->value = 4;
sleep(10);
R::store($lock);
exit(0);
} else {
R::selectDatabase($this->currentlyActiveDriverID . 'c2');
sleep(1);
R::freeze(TRUE);
try{ R::exec('SET SESSION innodb_lock_wait_timeout=5');}catch( \Exception $e ){}
try{R::exec("SET lock_timeout = '1s';");}catch( \Exception $e ){}
try { R::exec('SET autocommit = 0'); }catch( \Exception $e ){}
R::begin();
$exception = NULL;
try {
$lock = R::loadForUpdate('lock', $lock->id);
} catch( \Exception $e ) {
$exception = $e;
}
if ( !$exception ) fail();
pass();
$details = $exception->getDriverDetails();
asrt( ($details[1]===1205 || $details[0]==='55P03'), TRUE );
var_dump($lock);
}
try { R::exec('SET autocommit = 1'); }catch( \Exception $e ){}
pcntl_wait($status);
try { R::exec('SET SESSION innodb_lock_wait_timeout=50');}catch( \Exception $e ){}
try{R::exec("SET lock_timeout = '50s';");}catch( \Exception $e ){}
}
/**
* Tests basic locking scenarios using fork().
*
* @return void
*/
public function testConcurrency()
{
$c = pcntl_fork();
if ($c == -1) exit(1);
if (!$c) {
R::selectDatabase($this->currentlyActiveDriverID . 'c');
try{ R::exec('SET SESSION innodb_lock_wait_timeout=51');}catch( \Exception $e ){}
try{R::exec("SET lock_timeout = '51s';");}catch( \Exception $e ){}
R::exec('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
sleep(2);
try { R::exec('SET autocommit = 0'); }catch( \Exception $e ){}
R::freeze(TRUE);
R::begin();
echo "CHILD: SUBTRACTING 2 START\n";
$i = R::loadForUpdate('inventory', 1);
$i->apples -= 2;
sleep(4);
R::store($i);
R::commit();
echo "CHILD: SUBTRACTING 2 DONE\n";
echo (R::load('inventory', 1));
echo "\n";
exit(0);
} else {
R::selectDatabase($this->currentlyActiveDriverID . 'c');
try{ R::exec('SET SESSION innodb_lock_wait_timeout=51');}catch( \Exception $e ){}
try{R::exec("SET lock_timeout = '51s';");}catch( \Exception $e ){}
echo "PARENT: PREP START\n";
R::nuke();
$i = R::dispense('inventory');
$i->apples = 10;
R::store($i);
R::exec('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
echo "PARENT: PREP DONE\n";
sleep(3);
echo "PARENT: ADDING 5 START\n";
try { R::exec('SET autocommit = 0'); }catch( \Exception $e ){}
R::freeze( TRUE );
R::begin();
$i = R::findForUpdate('inventory', ' id = ? ', array(1));
$i = reset( $i );
print_r($i);
$i->apples += 5;
R::store($i);
R::commit();
echo "PARENT ADDING 5 DONE\n";
$i = R::getAll('select * from inventory where id = 1');
print_r($i);
asrt((int)$i[0]['apples'], 13);
R::freeze( FALSE );
try { R::exec('SET autocommit = 1'); }catch( \Exception $e ){}
pcntl_wait($status);
}
}
/**
* Test whether we can use setSQLSnippet with find() and batch().
*
* @return void
*/
public function testBatchAndFind()
{
/* baseline */
R::nuke();
$beans = R::dispenseAll('bean*10');
R::storeAll($beans[0]);
$ids = array();
foreach($beans[0] as $bean) $ids[] = $bean->id;
R::debug( TRUE, 1 );
$logs = R::getDatabaseAdapter()
->getDatabase()
->getLogger();
$beans = R::batch( 'bean', $ids );
asrt( count( $beans ), 10 );
$entries = $logs->grep('for update');
asrt( count( $entries ), 0 );
$logs->clear();
/* findOneForUpdate */
R::nuke();
$beans = R::dispenseAll('bean*10');
R::storeAll($beans[0]);
$ids = array();
foreach($beans[0] as $bean) $ids[] = $bean->id;
R::debug( TRUE, 1 );
$logs = R::getDatabaseAdapter()
->getDatabase()
->getLogger();
$beans = R::findOneForUpdate( 'bean' );
asrt( count( $beans ), 1 );
$entries = $logs->grep('for update');
asrt( count( $entries ), 0 );
$logs->clear();
/* findForUpdate */
R::nuke();
$beans = R::dispenseAll('bean*10');
R::storeAll($beans[0]);
$ids = array();
foreach($beans[0] as $bean) $ids[] = $bean->id;
R::debug( TRUE, 1 );
$logs = R::getDatabaseAdapter()
->getDatabase()
->getLogger();
$beans = R::findForUpdate( 'bean' );
asrt( count( $beans ), 10 );
$entries = $logs->grep('for update');
asrt( count( $entries ), 0 );
$logs->clear();
/* batch + snippet */
R::nuke();
$beans = R::dispenseAll('bean*10');
R::storeAll($beans[0]);
$ids = array();
R::getWriter()->setSQLSelectSnippet('for update');
foreach($beans[0] as $bean) $ids[] = $bean->id;
R::debug( TRUE, 1 );
$logs = R::getDatabaseAdapter()
->getDatabase()
->getLogger();
$beans = R::batch( 'bean', $ids );
asrt( count( $beans ), 10 );
$entries = $logs->grep('for update');
asrt( count( $entries ), 1 );
print_r( $entries );
$logs->clear();
/* baseline */
R::nuke();
$beans = R::dispenseAll('bean*10');
R::storeAll($beans[0]);
$ids = array();
foreach($beans[0] as $bean) $ids[] = $bean->id;
R::debug( TRUE, 1 );
$logs = R::getDatabaseAdapter()
->getDatabase()
->getLogger();
$beans = R::batch( 'bean', $ids );
asrt( count( $beans ), 10 );
$entries = $logs->grep('for update');
asrt( count( $entries ), 0 );
$logs->clear();
/* find + snippet */
R::nuke();
$beans = R::dispenseAll('bean*10');
R::storeAll($beans[0]);
$ids = array();
R::getWriter()->setSQLSelectSnippet('for update');
foreach($beans[0] as $bean) $ids[] = $bean->id;
R::debug( TRUE, 1 );
$logs = R::getDatabaseAdapter()
->getDatabase()
->getLogger();
$beans = R::find( 'bean' );
asrt( count( $beans ), 10 );
$entries = $logs->grep('for update');
asrt( count( $entries ), 1 );
print_r( $entries );
$logs->clear();
/* baseline */
R::nuke();
$beans = R::dispenseAll('bean*10');
R::storeAll($beans[0]);
$ids = array();
foreach($beans[0] as $bean) $ids[] = $bean->id;
R::debug( TRUE, 1 );
$logs = R::getDatabaseAdapter()
->getDatabase()
->getLogger();
$beans = R::batch( 'bean', $ids );
asrt( count( $beans ), 10 );
$entries = $logs->grep('for update');
asrt( count( $entries ), 0 );
$logs->clear();
R::debug( FALSE );
}
/**
* loadForUpdate/findForUpdate should be applied even if caching is on.
* Caching may not interfere with locking beans.
*
* @return void
*/
public function testLockAndCache()
{
R::nuke();
$bean = R::dispense('lock');
$bean->title = 'lock';
$id = R::store( $bean );
R::getWriter()->setUseCache( TRUE );
$lock = R::loadForUpdate( 'lock', $id );
R::debug( TRUE, 1 );
$lock = R::loadForUpdate( 'lock', $id );
$logs = R::getDatabaseAdapter()->getDatabase()->getLogger()->grep( AQueryWriter::C_SELECT_SNIPPET_FOR_UPDATE );
asrt( count($logs), 1 ); //if no cache clear, then would have been 2
R::debug( FALSE );
$lock = R::findForUpdate( 'lock', 'id = ?', array( $id ) );
R::debug( TRUE, 1 );
$lock = R::findForUpdate( 'lock', 'id = ?', array( $id ) );
$logs = R::getDatabaseAdapter()->getDatabase()->getLogger()->grep( AQueryWriter::C_SELECT_SNIPPET_FOR_UPDATE );
asrt( count($logs), 1 ); //if no cache clear, then would have been 2
R::getWriter()->setUseCache( FALSE );
R::debug( FALSE );
R::nuke();
}
}

View File

@@ -0,0 +1,224 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Copy
*
* Tests whether we can make a copy or a deep copy of a bean
* and whether recursion is handled well. Also tests
* versioning: copying can be used to implement a versioning feature,
* some test cases will reflect this particular use case.
*
* @file RedUNIT/Base/Copy.php
* @desc Tests whether we can make a deep copy of a bean.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Copy extends Base
{
/**
* Test whether recursion happens
*/
public function testCopyRecursion()
{
$document = R::dispense( 'document' );
$id = R::store( $document );
$document->ownDocument[] = $document;
R::store( $document );
$duplicate = R::dup( $document );
pass(); //if RB cant handle this is will crash (nesting level error from PHP).
$id2 = R::store( $duplicate );
$duplicate = R::load( 'document', $id );
asrt( (int) $document->document_id, $id );
asrt( (int) $duplicate->document_id, $id2 );
// Export variant
$duplicate = R::exportAll( $document );
asrt( (int) $duplicate[0]['document_id'], $id );
}
/**
* Test real world scenario: Versioning
*/
public function testVersioning()
{
$document = R::dispense( 'document' );
$page = R::dispense( 'page' );
$document->title = 'test';
$page->content = 'lorem ipsum';
$user = R::dispense( 'user' );
$user->name = 'Leo';
$document->sharedUser[] = $user;
$document->ownPage[] = $page;
$document->starship_id = 3;
$document->planet = R::dispense( 'planet' );
R::store( $document );
$duplicate = R::dup( $document );
R::store( $duplicate );
$duplicate = R::dup( $document );
R::store( $duplicate );
asrt( R::count( 'planet' ), 1 );
asrt( R::count( 'user' ), 1 );
asrt( R::count( 'document' ), 3 );
asrt( R::count( 'page' ), 3 );
asrt( R::count( 'spaceship' ), 0 );
}
/**
* Same as above but now with intermediate save, counts must be same
*/
public function testVersioningIntermediateSaves()
{
$document = R::dispense( 'document' );
$page = R::dispense( 'page' );
$document->title = 'test';
$page->content = 'lorem ipsum';
$user = R::dispense( 'user' );
$user->name = 'Leo';
$document->sharedUser[] = $user;
$document->ownPage[] = $page;
$document->starship_id = 3;
$document->planet = R::dispense( 'planet' );
R::store( $document );
$duplicate = R::dup( $document );
R::store( $document );
R::store( $duplicate );
R::store( $document );
$duplicate = R::dup( $document );
R::store( $document );
R::store( $duplicate );
asrt( R::count( 'planet' ), 1 );
asrt( R::count( 'user' ), 1 );
asrt( R::count( 'document' ), 3 );
asrt( R::count( 'page' ), 3 );
asrt( R::count( 'spaceship' ), 0 );
// same, but now with intermediate save, counts must be same
R::freeze( TRUE );
$document = R::dispense( 'document' );
$page = R::dispense( 'page' );
$document->title = 'test';
$page->content = 'lorem ipsum';
$user = R::dispense( 'user' );
$user->name = 'Leo';
$document->sharedUser[] = $user;
$document->ownPage[] = $page;
$document->starship_id = 3;
$document->planet = R::dispense( 'planet' );
R::store( $document );
$duplicate = R::dup( $document );
R::store( $document );
R::store( $duplicate );
R::store( $document );
$duplicate = R::dup( $document );
R::store( $document );
R::store( $duplicate );
asrt( R::count( 'planet' ), 2 );
asrt( R::count( 'user' ), 2 );
asrt( R::count( 'document' ), 6 );
asrt( R::count( 'page' ), 6 );
try { asrt( R::count( 'spaceship' ), 0 ); }catch(\Exception $e){pass();}
R::freeze( FALSE );
}
/**
* Test Recursion
*/
public function testRecursion()
{
list( $d1, $d2 ) = R::dispense( 'document', 2 );
$page = R::dispense( 'page' );
list( $p1, $p2 ) = R::dispense( 'paragraph', 2 );
list( $e1, $e2 ) = R::dispense( 'excerpt', 2 );
$id2 = R::store( $d2 );
$p1->name = 'a';
$p2->name = 'b';
$page->title = 'my page';
$page->ownParagraph = array( $p1, $p2 );
$p1->ownExcerpt[] = $e1;
$p2->ownExcerpt[] = $e2;
$e1->ownDocument[] = $d2;
$e2->ownDocument[] = $d1;
$d1->ownPage[] = $page;
$id1 = R::store( $d1 );
$d1 = R::load( 'document', $id1 );
$d = R::dup( $d1 );
$ids = array();
asrt( ( $d instanceof OODBBean ), TRUE );
asrt( count( $d->ownPage ), 1 );
foreach ( end( $d->ownPage )->ownParagraph as $p ) {
foreach ( $p->ownExcerpt as $e ) {
$ids[] = end( $e->ownDocument )->id;
}
}
sort( $ids );
asrt( (int) $ids[0], 0 );
asrt( (int) $ids[1], $id1 );
R::store( $d );
pass();
$phillies = R::dispense( 'diner' );
list( $lonelyman, $man, $woman ) = R::dispense( 'guest', 3 );
$attendant = R::dispense( 'employee' );
$lonelyman->name = 'Bennie Moten';
$man->name = 'Daddy Stovepipe';
$woman->name = 'Mississippi Sarah';
$attendant->name = 'Gus Cannon';
$phillies->sharedGuest = array( $lonelyman, $man, $woman );
$phillies->ownEmployee[] = $attendant;
$props = R::dispense( 'prop', 2 );
$props[0]->kind = 'cigarette';
$props[1]->kind = 'coffee';
$thought = R::dispense( 'thought' );
$thought->content = 'Blues';
$thought2 = R::dispense( 'thought' );
$thought2->content = 'Jazz';
$woman->ownProp[] = $props[0];
$man->sharedProp[] = $props[1];
$attendant->ownThought = array( $thought, $thought2 );
R::store( $phillies );
$diner = R::findOne( 'diner' );
$diner2 = R::dup( $diner );
$id2 = R::store( $diner2 );
$diner2 = R::load( 'diner', $id2 );
asrt( count( $diner->ownEmployee ), 1 );
asrt( count( $diner2->ownEmployee ), 1 );
asrt( count( $diner->sharedGuest ), 3 );
asrt( count( $diner2->sharedGuest ), 3 );
$employee = reset( $diner->ownEmployee );
asrt( count( $employee->ownThought ), 2 );
$employee = reset( $diner2->ownEmployee );
asrt( count( $employee->ownThought ), 2 );
// Can we change something in the duplicate without changing the original?
$employee->name = 'Marvin';
$thought = R::dispense( 'thought' );
$thought->content = 'depression';
$employee->ownThought[] = $thought;
array_pop( $diner2->sharedGuest );
$guest = reset( $diner2->sharedGuest );
$guest->name = 'Arthur Dent';
$id2 = R::store( $diner2 );
$diner2 = R::load( 'diner', $id2 );
asrt( count( $diner->ownEmployee ), 1 );
asrt( count( $diner2->ownEmployee ), 1 );
asrt( count( $diner->sharedGuest ), 3 );
asrt( count( $diner2->sharedGuest ), 2 );
$employeeOld = reset( $diner->ownEmployee );
asrt( count( $employeeOld->ownThought ), 2 );
$employee = reset( $diner2->ownEmployee );
asrt( count( $employee->ownThought ), 3 );
asrt( $employee->name, 'Marvin' );
asrt( $employeeOld->name, 'Gus Cannon' );
// However the shared beans must not be copied
asrt( R::count( 'guest' ), 3 );
asrt( R::count( 'guest_prop' ), 1 );
$arthur = R::findOne( 'guest', ' ' . R::getWriter()->esc( 'name' ) . ' = ? ', array( 'Arthur Dent' ) );
asrt( $arthur->name, 'Arthur Dent' );
}
}

View File

@@ -0,0 +1,226 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\RedException\SQL as SQL;
use RedBeanPHP\OODBBean;
/**
* Count
*
* Tests whether we can count beans with or without
* additional conditions and whether we can count associated
* beans (relationCount).
*
* @file RedUNIT/Base/Count.php
* @desc Tests for simple bean counting.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Count extends Base
{
/**
* Tests type check and conversion in
* OODB for count().
*
* @return void
*/
public function testCountType()
{
R::nuke();
$book = R::dispense( 'book' );
$book->sharedPage = R::dispense( 'page', 10 );
R::store( $book );
asrt( R::count('bookPage'), 10 );
try {
R::count( 'WrongTypeName' );
fail();
} catch ( RedException $ex ) {
pass();
}
try {
R::count( 'wrong_type_name' );
fail();
} catch ( RedException $ex ) {
pass();
}
}
/**
* Test count and wipe.
*
* @return void
*/
public function testCountAndWipe()
{
testpack( "Test count and wipe" );
$page = R::dispense( "page" );
$page->name = "ABC";
R::store( $page );
$n1 = R::count( "page" );
$page = R::dispense( "page" );
$page->name = "DEF";
R::store( $page );
$n2 = R::count( "page" );
asrt( $n1 + 1, $n2 );
R::wipe( "page" );
asrt( R::count( "page" ), 0 );
asrt( R::getRedBean()->count( "page" ), 0 );
asrt( R::getRedBean()->count( "kazoo" ), 0 ); // non existing table
R::freeze( TRUE );
try {
asrt( R::getRedBean()->count( "kazoo" ), 0 ); // non existing table
fail();
} catch( \Exception $e ) {
pass();
}
R::freeze( FALSE );
$page = R::dispense( 'page' );
$page->name = 'foo';
R::store( $page );
$page = R::dispense( 'page' );
$page->name = 'bar';
R::store( $page );
asrt( R::count( 'page', ' name = ? ', array( 'foo' ) ), 1 );
// Now count something that does not exist, this should return 0. (just be polite)
asrt( R::count( 'teapot', ' name = ? ', array( 'flying' ) ), 0 );
asrt( R::count( 'teapot' ), 0 );
$currentDriver = $this->currentlyActiveDriverID;
// Some drivers don't support that many error codes.
if ( $currentDriver === 'mysql' || $currentDriver === 'postgres' ) {
try {
R::count( 'teaport', ' for tea ' );
fail();
} catch ( SQL $e ) {
pass();
}
}
}
/**
* Can we count the number of shared beans?
*
* @return void
*/
public function testCountShared()
{
R::nuke();
$book = R::dispense( 'book' );
$book->sharedPageList = R::dispense( 'page', 5 );
R::store( $book );
asrt( $book->countShared('page'), 5 );
asrt( $book->countShared('leaflet'), 0 );
asrt( R::dispense( 'book' )->countShared('page'), 0 );
$am = R::getRedBean()->getAssociationManager();
asrt( $am->relatedCount( R::dispense( 'book' ), 'page' ), 0);
try {
$am->relatedCount( 'not a bean', 'type' );
fail();
} catch( RedException $e ) {
pass();
}
R::getWriter()->setUseCache(TRUE);
asrt( $book->countShared('page'), 5 );
R::exec('DELETE FROM book_page WHERE book_id > 0 -- keep-cache');
asrt( $book->countShared('page'), 5 );
R::getWriter()->setUseCache(FALSE);
asrt( $book->countShared('page'), 0 );
}
/**
* Test $bean->countOwn($type);
*
* @return void
*/
public function testCountOwn()
{
R::nuke();
$book = R::dispense( 'book' );
$empty = R::dispense( 'book' );
$nothing = R::dispense( 'book' );
$page = R::dispense( 'page' );
$book->ownPageList[] = $page;
R::store( $book );
R::store( $empty );
OODBBean::useFluidCount( FALSE );
asrt( $book->countOwn('page'), 1 );
asrt( $empty->countOwn('page'), 0 );
asrt( $nothing->countOwn('page'), 0 );
$old = OODBBean::useFluidCount( TRUE );
asrt( $old, FALSE );
asrt( $book->countOwn('page'), 1 );
asrt( $empty->countOwn('page'), 0 );
asrt( $nothing->countOwn('page'), 0 );
R::freeze( TRUE );
asrt( $book->countOwn('page'), 1 );
asrt( $empty->countOwn('page'), 0 );
asrt( $nothing->countOwn('page'), 0 );
R::freeze( FALSE );
R::nuke();
asrt( $empty->countOwn('page'), 0 );
asrt( $nothing->countOwn('page'), 0 );
R::freeze( TRUE );
asrt( $nothing->countOwn('page'), 0 );
try { asrt( $empty->countOwn('page'), 0 ); fail(); } catch(\Exception $e) { pass(); }
try { asrt( $book->countOwn('page'), 0 ); fail(); } catch(\Exception $e) { pass(); }
R::freeze( FALSE );
OODBBean::useFluidCount( FALSE );
try { asrt( $empty->countOwn('page'), 0 ); fail(); } catch(\Exception $e) { pass(); }
try { asrt( $book->countOwn('page'), 0 ); fail(); } catch(\Exception $e) { pass(); }
OODBBean::useFluidCount( TRUE );
}
/**
* Test $bean->withCondition( ... )->countOwn( $type );
*
* @return void
*/
public function testCountWithCondition()
{
R::nuke();
$book = R::dispense( 'book' );
$book->ownPageList[] = R::dispense( 'page' );
R::store( $book );
OODBBean::useFluidCount( FALSE );
$count = $book
->withCondition(' id > :id ', array( ':id' => 0 ) )
->countOwn('page');
asrt( $count, 1 );
$count = $book
->withCondition(' id > ? ', array( 0 ) )
->countOwn('page');
asrt( $count, 1 );
$count = $book
->withCondition(' id < :id ', array( ':id' => 0 ) )
->countOwn('page');
asrt( $count, 0 );
$count = $book
->withCondition(' id < ? ', array( 0 ) )
->countOwn('page');
asrt( $count, 0 );
OODBBean::useFluidCount( TRUE );
$count = $book
->withCondition(' id > :id ', array( ':id' => 0 ) )
->countOwn('page');
asrt( $count, 1 );
$count = $book
->withCondition(' id > ? ', array( 0 ) )
->countOwn('page');
asrt( $count, 1 );
$count = $book
->withCondition(' id < :id ', array( ':id' => 0 ) )
->countOwn('page');
asrt( $count, 0 );
$count = $book
->withCondition(' id < ? ', array( 0 ) )
->countOwn('page');
asrt( $count, 0 );
}
}

View File

@@ -0,0 +1,474 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\AssociationManager as AssociationManager;
use RedBeanPHP\RedException\SQL as SQL;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Cross
*
* Tests self referential many-to-many relations, including the
* aggr feature.
*
* @file RedUNIT/Base/Cross.php
* @desc Tests associations within the same table (i.e. page_page2 alike)
* Cross tables, self referential many-to-many relations
* and aggregation techniques
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Cross extends Base
{
/**
* Test how well aggr handles fields with no
* values.
*
* @return void
*/
public function testAggrNullHandling()
{
R::nuke();
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$page->name = 'Page 3';
$book->xownPageList[] = $page;
R::store( $book );
$book = $book->fresh();
$texts = $book->aggr( 'ownPageList', 'text' );
pass();
asrt( count( $texts ), 0 );
asrt( is_array( $texts ), TRUE );
R::nuke();
$book = R::dispense( 'book' );
$page1 = R::dispense( 'page' );
$page1->name = 'Page 1';
$text1 = R::dispense('text');
$text1->content = 'Text 1';
$page1->text = $text1;
$book->xownPageList[] = $page1;
$page2 = R::dispense( 'page' );
$page2->name = 'Page 2';
$text2 = R::dispense( 'text' );
$text2->content = 'Text 2';
$page2->text = $text2;
$book->xownPageList[] = $page2;
$page3 = R::dispense( 'page' );
$page3->name = 'Page 3';
$book->xownPageList[] = $page3;
R::store( $book );
$book = $book->fresh();
$texts = $book->aggr( 'ownPageList', 'text' );
pass();
asrt( count( $texts ), 2 );
}
/**
* Test many different scenarios with self referential
* many-to-many relations.
*
* @return void
*/
public function testSelfReferentialCRUD()
{
R::nuke();
$buddies = R::dispense( 'buddy', 4 );
$buddies[0]->name = 'A';
$buddies[1]->name = 'B';
$buddies[2]->name = 'C';
$buddies[3]->name = 'D';
$buddies[0]->sharedBuddyList = array( $buddies[1], $buddies[2] );
$buddies[3]->sharedBuddyList = array( $buddies[2] );
R::storeAll( $buddies );
$buddies[0] = $buddies[0]->fresh();
asrt( count( $buddies[0]->sharedBuddyList ), 2 );
//does this yield valid combinations - cross relations / self ref n-m
//need to symmetric....
$names = R::gatherLabels( $buddies[0]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'B,C' );
unset( $buddies[0]->sharedBuddy );
R::storeAll( $buddies );
$buddies[0] = $buddies[0]->fresh();
asrt( count( $buddies[0]->sharedBuddyList ), 2 );
$buddies[3] = $buddies[3]->fresh();
asrt( count( $buddies[3]->sharedBuddyList ), 1 );
$names = R::gatherLabels( $buddies[3]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'C' );
$buddies[2] = $buddies[2]->fresh();
asrt( count( $buddies[2]->sharedBuddyList ), 2 );
$names = R::gatherLabels( $buddies[2]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'A,D' );
$buddies[1] = $buddies[1]->fresh();
asrt( count( $buddies[1]->sharedBuddyList ), 1 );
$names = R::gatherLabels( $buddies[1]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'A' );
//Can we add one?
$buddies[1]->sharedBuddyList[] = R::dispense('buddy')->setAttr('name', 'E');
R::store( $buddies[1] );
$buddies[0] = $buddies[0]->fresh();
asrt( count( $buddies[0]->sharedBuddyList ), 2 );
$names = R::gatherLabels( $buddies[0]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'B,C' );
$buddies[1] = $buddies[1]->fresh();
asrt( count( $buddies[1]->sharedBuddyList ), 2 );
$names = R::gatherLabels( $buddies[1]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'A,E' );
$all = R::find( 'buddy' );
asrt( count( $all ), 5 );
foreach( $buddies[1]->sharedBuddy as $buddy ) {
if ( $buddy->name === 'E' ) {
$buddyE = $buddy;
}
}
asrt( isset( $buddyE ), TRUE );
asrt( $buddyE->name, 'E' );
//can we update?
foreach( $buddies[0]->sharedBuddy as $buddy ) {
if ( $buddy->name === 'C' ) {
$buddy->name = 'C2';
}
}
R::store( $buddies[0] );
$buddies[0] = $buddies[0]->fresh();
asrt( count( $buddies[0]->sharedBuddyList ), 2 );
$names = R::gatherLabels( $buddies[0]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'B,C2' );
$buddies[2] = $buddies[2]->fresh();
asrt( count( $buddies[2]->sharedBuddyList ), 2 );
$names = R::gatherLabels( $buddies[2]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'A,D' );
//can we delete?
foreach( $buddies[0]->sharedBuddyList as $id => $buddy ) {
if ( $buddy->name === 'B' ) {
unset( $buddies[0]->sharedBuddyList[$id] );
}
}
R::store( $buddies[0] );
$buddies[0] = $buddies[0]->fresh();
asrt( count( $buddies[0]->sharedBuddyList ), 1 );
$names = R::gatherLabels( $buddies[0]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'C2' );
$buddies[1] = $buddies[1]->fresh();
asrt( count( $buddies[1]->sharedBuddyList ), 1 );
$names = R::gatherLabels( $buddies[1]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'E' );
asrt( R::count( 'buddy' ), 5 );
asrt( R::count( 'buddyBuddy' ), 3 );
$buddies[3] = $buddies[3]->fresh();
asrt( count( $buddies[3]->sharedBuddyList ), 1 );
$names = R::gatherLabels( $buddies[3]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'C2' );
$buddies[2] = $buddies[2]->fresh();
asrt( count( $buddies[2]->sharedBuddyList ), 2 );
$names = R::gatherLabels( $buddies[2]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'A,D' );
$buddyE = $buddyE->fresh();
asrt( count( $buddyE->sharedBuddyList ), 1 );
$names = R::gatherLabels( $buddyE->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'B' );
if ( $this->currentlyActiveDriverID === 'mysql' ) {
$buddyE = $buddyE->fresh();
asrt( count( $buddyE->with(' HAVING linked_by > 0 ')->sharedBuddyList ), 1 );
$buddyE = $buddyE->fresh();
asrt( count( $buddyE->with(' HAVING linked_by < 0 ')->sharedBuddyList ), 0 );
}
if ( $this->currentlyActiveDriverID === 'sqlite' ) {
$buddyE = $buddyE->fresh();
asrt( count( $buddyE->withCondition(' linked_by > 0 ')->sharedBuddyList ), 1 );
$buddyE = $buddyE->fresh();
asrt( count( $buddyE->withCondition(' linked_by < 0 ')->sharedBuddyList ), 0 );
}
$buddyE = $buddyE->fresh();
asrt( count( $buddyE->withCondition(' buddy_buddy.buddy_id > 0 AND buddy_buddy.buddy2_id > 0 ')->sharedBuddyList ), 1 );
$buddyE = $buddyE->fresh();
asrt( count( $buddyE->withCondition(' buddy_buddy.buddy_id < 0 AND buddy_buddy.buddy2_id < 0 ')->sharedBuddyList ), 0 );
}
/**
* Test self referential N-M relations (page_page).
*
* @return void
*/
public function testSelfReferential()
{
$page = R::dispense( 'page' )->setAttr( 'title', 'a' );
$page->sharedPage[] = R::dispense( 'page' )->setAttr( 'title', 'b' );
R::store( $page );
$page = $page->fresh();
$page = reset( $page->sharedPage );
asrt( $page->title, 'b' );
$tables = array_flip( R::inspect() );
asrt( isset( $tables['page_page'] ), TRUE );
$columns = R::inspect( 'page_page' );
asrt( isset( $columns['page2_id'] ), TRUE );
}
/**
* Test the unique constraint.
* Just want to make sure it is not too limiting and functions
* properly for typical RedBeanPHP usage.
*
* @return void
*/
public function testUnique()
{
R::nuke();
$page1 = R::dispense( 'page' );
$tag1 = R::dispense( 'tag' );
$page2 = R::dispense( 'page' );
$tag2 = R::dispense( 'tag' );
$page3 = R::dispense( 'page' );
$tag3 = R::dispense( 'tag' );
$page1->sharedTag[] = $tag1;
R::store( $page1 );
//can we save all combinations with unique?
asrt( R::count( 'pageTag' ), 1);
$page1->sharedTag[] = $tag2;
R::store( $page1 );
asrt( R::count( 'pageTag' ), 2 );
$page1->sharedTag[] = $tag3;
$page2->sharedTag[] = $tag1;
$page2->sharedTag[] = $tag2;
$page2->sharedTag[] = $tag3;
$page3->sharedTag[] = $tag1;
$page3->sharedTag[] = $tag2;
$page3->sharedTag[] = $tag3;
R::storeAll( array( $page1, $page2, $page3 ) );
asrt( R::count('pageTag'), 9 );
$page1 = $page1->fresh();
$page1->sharedTag[] = $tag3;
R::store( $page1 );
//cant add violates unique
asrt( R::count( 'pageTag' ), 9 );
}
/**
* Shared lists can only be formed using types.
* If you happen to have two beans of the same type you can still
* have a shared list but not with a sense of direction.
* I.e. quest->sharedQuest returns all the quests that follow the first one,
* but also the ones that are followed by the first one.
* If you want to have some sort of direction; i.e. one quest follows another one
* you'll have to use an alias: quest->target, but now you can't use the shared list
* anymore because it will start looking for a type named 'target' (which is just an alias)
* for quest, but it cant find that table and it's not possible to 'keep remembering'
* the alias throughout the system.
*
* The aggr() method solves this inconvenience.
* Aggr iterates through the list identified by its first argument ('target' -> ownQuestTargetList)
* and fetches every ID of the target (quest_target.target_id), loads these beans in batch for
* optimal performance, puts them back in the beans (questTarget->target) and returns the
* references.
*
* @return void
*/
public function testAggregationInSelfRefNM()
{
R::nuke();
$quest1 = R::dispense( 'quest' );
$quest1->name = 'Quest 1';
$quest2 = R::dispense( 'quest' );
$quest2->name = 'Quest 2';
$quest3 = R::dispense( 'quest' );
$quest3->name = 'Quest 3';
$quest4 = R::dispense( 'quest' );
$quest4->name = 'Quest 4';
$quest1->link( 'questTarget' )->target = $quest2;
$quest1->link( 'questTarget' )->target = $quest3;
$quest3->link( 'questTarget' )->target = $quest4;
$quest3->link( 'questTarget' )->target = $quest1;
R::storeAll( array( $quest1, $quest3 ) );
//There should be 4 links
asrt( (int) R::count('questTarget'), 4 );
$quest1 = $quest1->fresh();
$targets = $quest1->aggr( 'ownQuestTargetList', 'target', 'quest' );
//can we aggregate the targets over the link type?
asrt( count( $targets), 2 );
//are the all valid beans?
foreach( $targets as $target ) {
//are they beans?
asrt( ( $target instanceof OODBBean ), TRUE );
//are they fetched as quest?
asrt( ( $target->getMeta( 'type' ) ), 'quest' );
}
//list target should already have been loaded, nuke has no effect
R::nuke();
$links = $quest1->ownQuestTargetList;
//are the links still there, have they been set in the beans as well?
asrt( count( $links ), 2);
//are they references instead of copies, changes in the aggregation set should affect the beans in links!
foreach( $targets as $target ) {
$target->name .= 'b';
asrt( substr( $target->name, -1 ), 'b' );
}
//do the names end with a 'b' here as well ? i.e. have they been changed through references?
foreach( $links as $link ) {
asrt( substr( $target->name, -1 ), 'b' );
}
//now test the effect on existing shadow...
R::nuke();
$quest1 = R::dispense('quest');
$quest1->name = 'Quest 1';
$quest2 = R::dispense('quest');
$quest2->name = 'Quest 2';
$quest3 = R::dispense('quest');
$quest3->name = 'Quest 3';
$quest4 = R::dispense('quest');
$quest4->name = 'Quest 4';
$quest1->link( 'questTarget' )->target = $quest2;
$quest1->link( 'questTarget' )->target = $quest3;
R::store($quest1);
asrt( (int) R::count( 'questTarget' ), 2 );
//now lets first build a shadow
$quest1->link( 'questTarget' )->target = $quest4;
//$quest1 = $quest1->fresh();
$targets = $quest1->aggr( 'ownQuestTargetList', 'target', 'quest' );
//targets should not include the new bean...
asrt( count($targets), 2 );
//this should not overwrite the existing beans
R::store($quest1);
asrt( (int) R::count( 'questTarget' ), 3 );
}
/**
* Test aggr without the aliasing.
*
* @return void
*/
public function testAggrBasic()
{
R::nuke();
$book = R::dispense( 'book' );
$page1 = R::dispense( 'page' );
$page1->name = 'Page 1';
$text1 = R::dispense('text');
$text1->content = 'Text 1';
$page1->text = $text1;
$book->xownPageList[] = $page1;
$page2 = R::dispense( 'page' );
$page2->name = 'Page 2';
$text2 = R::dispense( 'text' );
$text2->content = 'Text 2';
$page2->text = $text2;
$book->xownPageList[] = $page2;
R::store( $book );
$book = $book->fresh();
$texts = $book->aggr( 'ownPageList', 'text' );
R::nuke();
asrt( count( $texts ), 2 );
foreach( $texts as $text ) {
asrt( ( $text instanceof OODBBean ), TRUE );
}
$pages = $book->ownPageList;
asrt( count( $pages ), 2 );
asrt( R::count( 'page' ), 0 );
foreach( $pages as $page ) {
asrt( ( $page instanceof OODBBean ), TRUE );
$text = $page->text;
asrt( ( $text instanceof OODBBean ), TRUE );
$text->content = 'CHANGED';
}
foreach( $texts as $text ) {
asrt( $text->content, 'CHANGED' );
}
}
/**
* Test aggr with basic aliasing.
*
* @return void
*/
public function testAggrWithOnlyAlias()
{
R::nuke();
$book = R::dispense( 'book' );
$page1 = R::dispense( 'page' );
$page1->name = 'Page 1';
$text1 = R::dispense( 'text' );
$text1->content = 'Text 1';
$page1->content = $text1;
$book->xownPageList[] = $page1;
$page2 = R::dispense( 'page' );
$page2->name = 'Page 2';
$text2 = R::dispense( 'text' );
$text2->content = 'Text 2';
$page2->content = $text2;
$book->xownPageList[] = $page2;
R::store( $book );
$book = $book->fresh();
$texts = $book->aggr( 'ownPageList', 'content', 'text' );
R::nuke();
asrt( count( $texts ), 2 );
foreach( $texts as $text ) {
asrt( ( $text instanceof OODBBean), TRUE );
}
$pages = $book->ownPageList;
asrt( count( $pages ), 2 );
asrt( R::count( 'page' ), 0 );
foreach( $pages as $page ) {
asrt( ( $page instanceof OODBBean ), TRUE );
$text = $page->content;
asrt( ( $text instanceof OODBBean ), TRUE );
$text->content = 'CHANGED';
}
foreach( $texts as $text ) {
asrt( $text->content, 'CHANGED' );
}
}
/**
* The aggr method can only be used with own-list.
*
* @return void
*/
public function testErrorHandlingAggr()
{
$wrongLists = array( 'not-an-own-list', 'OWNlist', 'Ownpage', 'ownbook', 'own book', '!', 'sharedBook' );
foreach( $wrongLists as $wrongList ) {
$bean = R::dispense( 'bean' );
try {
$bean->aggr( $wrongList, 'field' );
fail();
} catch ( \Exception $exception ) {
pass();
asrt( ( $exception instanceof RedException ), TRUE );
}
}
}
}

View File

@@ -0,0 +1,160 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\QueryWriter;
use RedBeanPHP\QueryWriter\AQueryWriter;
/**
* Cursors
*
* Tests whether RedBeanPHP can use cursors (using the
* findCollection method) to iterate over large data sets.
*
* @file RedUNIT/Base/Cursors.php
* @desc Tests whether we can use cursors
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Cursors extends Base
{
/**
* Test whether we can use cursors with raw SQL
* from the facade (#656).
*
* @return void
*/
public function testSQLCursors()
{
R::nuke();
for( $i=0; $i<20; $i++ ) {
$page = R::dispense( 'page' );
$page->number = $i;
$page->content = sha1( $i );
R::store( $page );
}
$cursor = R::getCursor( 'SELECT * FROM page ORDER BY page.number ASC' );
asrt( get_class( $cursor ), 'RedBeanPHP\Cursor\PDOCursor');
$i = 0;
$list = array();
while( $row = $cursor->getNextItem() ) {
asrt( is_array( $row ), TRUE );
asrt( (string) $row['number'], strval( $i ) );
asrt( $row['content'], sha1( $i ) );
$list[] = $row['content'];
$i ++;
}
}
/**
* Test basic cursor functionality.
*
* @return void
*/
public function testBasicCursors()
{
R::nuke();
for( $i=0; $i<20; $i++ ) {
$page = R::dispense( 'page' );
$page->number = $i;
$page->content = sha1( $i );
R::store( $page );
}
$collection = R::findCollection( 'page', 'ORDER BY page.number ASC' );
asrt( get_class( $collection ), 'RedBeanPHP\BeanCollection');
$i = 0;
$list = array();
while( $bean = $collection->next() ) {
asrt( ( $bean instanceof OODBBean ), TRUE );
asrt( (string) $bean->number, strval( $i ) );
asrt( $bean->content, sha1( $i ) );
$list[] = $bean->content;
$i ++;
}
$collection->reset();
$i = 0;
while( $bean = $collection->next() ) {
asrt( ( $bean instanceof OODBBean ), TRUE );
asrt( (string) $bean->number, strval( $i ) );
asrt( $bean->content, sha1( $i ) );
$i ++;
}
$collection = R::findCollection( 'page', ' ORDER BY content ASC ' );
sort( $list );
$i = 0;
while( $bean = $collection->next() ) {
asrt( $bean->content, $list[$i] );
$i ++;
}
$collection = R::findCollection( 'page', ' ORDER BY content ASC LIMIT 5 ' );
sort( $list );
$i = 0;
while( $bean = $collection->next() ) {
asrt( $bean->content, $list[$i] );
$i ++;
if ( $i > 5 ) break;
}
$key = array_rand( $list );
$content = $list[ $key ];
$collection = R::findCollection( 'page', ' content = ? ', array( $content ) );
$bean = $collection->next();
asrt( $bean->content, $content );
$collection->close();
}
/**
* Can we use a filtered cursor?
*
* @return void
*/
public function testCursorWithFilter()
{
R::nuke();
$book = R::dispense( 'book' );
$book->title = 'Title';
R::store( $book );
$filter = array(
QueryWriter::C_SQLFILTER_READ => array(
'book' => array('title' => ' LOWER(book.title) ' )
)
);
AQueryWriter::setSQLFilters( $filter );
$books = R::findCollection( 'book' );
$book = $books->next();
asrt( $book->title, 'title' );
AQueryWriter::setSQLFilters( array() );
$books = R::findCollection( 'book' );
$book = $books->next();
asrt( $book->title, 'Title' );
}
/**
* Test empty collections (NULLCursor).
*
* @return void
*/
public function testEmptyCollection()
{
R::nuke();
$page = R::dispense( 'page' );
$page->content = 'aaa';
R::store( $page );
$collection = R::findCollection( 'page' );
asrt( get_class( $collection ), 'RedBeanPHP\BeanCollection');
$collection = R::findCollection( 'page', ' content = ?', array( 'bbb' ) );
asrt( get_class( $collection ), 'RedBeanPHP\BeanCollection');
asrt( is_null( $collection->next() ), TRUE );
$collection = R::findCollection( 'something' );
asrt( get_class( $collection ), 'RedBeanPHP\BeanCollection');
asrt( is_null( $collection->next() ), TRUE );
asrt( is_null( $collection->reset() ), TRUE );
$collection->close();
}
}

View File

@@ -0,0 +1,461 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\QueryWriter\SQLiteT as SQLiteT;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\ToolBox as ToolBox;
use RedBeanPHP\AssociationManager as AssociationManager;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\RedException\SQL as SQL;
use RedBeanPHP\QueryWriter\MySQL as MySQL;
use RedBeanPHP\QueryWriter\PostgreSQL as PostgreSQL;
use RedBeanPHP\QueryWriter\CUBRID as CUBRID;
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
/**
* Database
*
* Tests basic RedBeanPHP database functionality.
*
* @file RedUNIT/Base/Database.php
* @desc Tests basic database behaviors
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Database extends Base
{
/**
* What drivers should be loaded for this test pack?
*/
public function getTargetDrivers()
{
return array( 'mysql', 'pgsql', 'sqlite', 'CUBRID' );
}
/**
* Can we use meta mask with find() ?
*
* @return void
*/
public function testSelectFindOne()
{
R::store(R::dispense('book'));
R::store(R::dispense('book'));
if ($this->currentlyActiveDriverID == 'pgsql') {
R::getWriter()->setSQLFilters(array('r'=>array('book'=>array('__meta_total'=>'COUNT(*) OVER()'))), FALSE);
} else {
R::getWriter()->setSQLFilters(array('r'=>array('book'=>array('__meta_total'=>'2'))), FALSE);
}
$books = R::find('book', 'LIMIT 1');
$book = reset($books);
$bundle = $book->getMeta('data.bundle');
asrt(intval($bundle['__meta_total']),2);
R::getWriter()->setSQLFilters(array(), FALSE);
}
/**
* Test whether we cannot just bind function names but
* also function templates, i.e. little SQL snippets.
*
* @return void
*/
public function testBindFuncFunctionTemplates()
{
R::bindFunc('read', 'xbean.lucky', '111 * %s', TRUE);
$bean = R::dispense('xbean');
$bean->lucky = 7;
$id = R::store( $bean );
$bean = R::load( 'xbean', $id );
asrt( intval($bean->lucky), 777 );
R::bindFunc('write', 'xbean.triple', '3 * %s', TRUE);
$bean->triple = 3;
R::store($bean);
$bean = $bean->fresh();
asrt( intval($bean->triple), 9);
R::bindFunc('read', 'xbean.lucky', NULL);
R::bindFunc('write', 'xbean.triple', NULL);
R::getRedBean()->clearAllFuncBindings();
}
/**
* Make ConvertToBean work together with getRow #759.
* When no results are found for getRow it returns []
* Then when you give that to convertToBean it wraps your
* single row into an array of multiple rows, so you get [[]].
* Then this loop has something to
* iterate on foreach ( $rows as $row ) { ...
* And then it crashes on: $id = $row['id'];
*/
public function testHarmonizeConvertToBeanAndGetRow()
{
R::nuke();
$book = R::convertToBean( 'book', R::getRow( 'SELECT * FROM book' ) );
asrt( is_null( $book ), TRUE );
$book = R::convertToBean( 'book', array() );
asrt( is_null( $book ), TRUE );
}
/**
* Test for bugfix:
* adhere to return type specification for R::getRow #728
* public static function getRow is documented as a function
* that returns an array. However, in a situation
* where the resultset is empty, this returns a boolean
* and causes an unexpected failure in
* code like this because it is expecting an array.
*/
public function testReturnTypeGetRow()
{
R::nuke();
$book = R::dispense( 'book' );
R::store( $book );
$row = R::getRow('SELECT * FROM book');
asrt( is_array( $row ), TRUE );
R::trash( $book );
$row = R::getRow('SELECT * FROM book');
asrt( is_array( $row ), TRUE );
R::nuke();
$row = R::getRow('SELECT * FROM book');
asrt( is_array( $row ), TRUE );
}
/**
* Test the (protected) database capability checker method
* of the RedBeanPHP PDO driver (RPDO).
*/
public function testDatabaseCapabilityChecker()
{
$capChecker = new \DatabaseCapabilityChecker( R::getDatabaseAdapter()->getDatabase()->getPDO() );
$result = $capChecker->checkCapability('creativity');
asrt( $result, FALSE ); /* nope, no strong AI yet.. */
}
/**
* Test whether we can obtain the PDO object from the
* database driver for custom database operations.
*
* @return void
*/
public function testGetPDO()
{
$driver = R::getDatabaseAdapter();
asrt( ( $driver instanceof DBAdapter), TRUE );
$pdo = $driver->getDatabase()->getPDO();
asrt( ( $pdo instanceof \PDO ), TRUE );
$pdo2 = R::getPDO();
asrt( ( $pdo2 instanceof \PDO ), TRUE );
asrt( ( $pdo === $pdo2 ), TRUE );
}
/**
* Test setter maximum integer bindings.
*
* @return void
*/
public function testSetMaxBind()
{
$driver = R::getDatabaseAdapter()->getDatabase();
$old = $driver->setMaxIntBind( 10 );
//use SQLite to confirm...
if ( $this->currentlyActiveDriverID === 'sqlite' ) {
$type = R::getCell( 'SELECT typeof( ? ) ', array( 11 ) );
asrt( $type, 'text' );
$type = R::getCell( 'SELECT typeof( ? ) ', array( 10 ) );
asrt( $type, 'integer' );
$type = R::getCell( 'SELECT typeof( ? ) ', array( 9 ) );
asrt( $type, 'integer' );
}
$new = $driver->setMaxIntBind( $old );
asrt( $new, 10 );
try {
$driver->setMaxIntBind( '10' );
fail();
} catch( RedException $e ) {
pass();
}
$new = $driver->setMaxIntBind( $old );
asrt( $new, $old );
$new = $driver->setMaxIntBind( $old );
asrt( $new, $old );
}
/**
* Can we use colons in SQL?
*
* @return void
*/
public function testColonsInSQL()
{
R::nuke();
$book = R::dispense( 'book' );
$book->title = 'About :';
R::store( $book );
pass();
$book = R::findOne( 'book', ' title LIKE :this ', array(
':this' => 'About :'
) );
asrt( ( $book instanceof OODBBean ), TRUE );
//without the colon?
$book = R::findOne( 'book', ' title LIKE :this ', array(
'this' => 'About :'
) );
asrt( ( $book instanceof OODBBean ), TRUE );
$book = R::findOne( 'book', ' title LIKE :this ', array(
':this' => '%:%'
) );
asrt( ( $book instanceof OODBBean ), TRUE );
$book = R::findOne( 'book', ' title LIKE :this OR title LIKE :that', array(
'this' => '%:%', ':that' => 'That'
) );
asrt( ( $book instanceof OODBBean ), TRUE );
$records = R::getAll('SELECT * FROM book WHERE title LIKE :this', array( ':this' => 'About :' ) );
asrt( count( $records ), 1 );
$records = R::getAll('SELECT * FROM book WHERE title LIKE :this', array( 'this' => 'About :' ) );
asrt( count( $records ), 1 );
$records = R::getAll('SELECT * FROM book WHERE title LIKE :this OR title LIKE :that', array( ':this' => 'About :', ':that' => 'That' ) );
asrt( count( $records ), 1 );
$records = R::getRow('SELECT * FROM book WHERE title LIKE :this', array( ':this' => 'About :' ) );
asrt( count( $records ), 2 );
$records = R::getRow('SELECT * FROM book WHERE title LIKE :this', array( 'this' => 'About :' ) );
asrt( count( $records ), 2 );
$records = R::getRow('SELECT * FROM book WHERE title LIKE :this OR title LIKE :that', array( ':this' => 'About :', ':that' => 'That' ) );
asrt( count( $records ), 2 );
}
/**
* Test setting direct PDO.
* Not much to test actually.
*
* @return void
*/
public function testDirectPDO()
{
$pdo = R::getDatabaseAdapter()->getDatabase()->getPDO();
R::getDatabaseAdapter()->getDatabase()->setPDO( $pdo );
pass();
}
/**
* Test for testConnection() method.
*
* @return void
*/
public function testConnectionTester()
{
asrt( R::testConnection(), TRUE );
}
/**
* Tests the various ways to fetch (select queries)
* data using adapter methods in the facade.
* Also tests the new R::getAssocRow() method,
* as requested in issue #324.
*/
public function testFetchTypes()
{
R::nuke();
$page = R::dispense( 'page' );
$page->a = 'a';
$page->b = 'b';
R::store( $page );
$page = R::dispense( 'page' );
$page->a = 'c';
$page->b = 'd';
R::store( $page );
$expect = '[{"id":"1","a":"a","b":"b"},{"id":"2","a":"c","b":"d"}]';
asrt( json_encode( R::getAll( 'SELECT * FROM page' ) ), $expect );
$expect = '{"1":"a","2":"c"}';
asrt( json_encode( R::getAssoc( 'SELECT id, a FROM page' ) ), $expect );
$expect = '{"1":{"a":"a","b":"b"},"2":{"a":"c","b":"d"}}';
asrt( json_encode( R::getAssoc( 'SELECT id, a, b FROM page' ) ), $expect );
$expect = '[{"id":"1","a":"a"},{"id":"2","a":"c"}]';
asrt( json_encode( R::getAssocRow( 'SELECT id, a FROM page' ) ), $expect );
$expect = '[{"id":"1","a":"a","b":"b"},{"id":"2","a":"c","b":"d"}]';
asrt( json_encode( R::getAssocRow( 'SELECT id, a, b FROM page' ) ), $expect );
$expect = '{"id":"1","a":"a","b":"b"}';
asrt( json_encode( R::getRow( 'SELECT * FROM page WHERE id = 1' ) ), $expect );
$expect = '"a"';
asrt( json_encode( R::getCell( 'SELECT a FROM page WHERE id = 1' ) ), $expect );
$expect = '"b"';
asrt( json_encode( R::getCell( 'SELECT b FROM page WHERE id = 1') ), $expect );
$expect = '"c"';
asrt( json_encode( R::getCell('SELECT a FROM page WHERE id = 2') ), $expect );
$expect = '["a","c"]';
asrt( json_encode( R::getCol( 'SELECT a FROM page' ) ), $expect );
$expect = '["b","d"]';
asrt( json_encode( R::getCol('SELECT b FROM page') ), $expect );
}
/**
* Tests whether we can store an empty bean.
* An empty bean has no properties, only ID. Normally we would
* skip the ID field in an INSERT, this test forces the driver
* to specify a value for the ID field. Different writers have to
* use different values: Mysql uses NULL to insert a new auto-generated ID,
* while Postgres has to use DEFAULT.
*/
public function testEmptyBean()
{
testpack( 'Test Empty Bean Storage.' );
R::nuke();
$bean = R::dispense( 'emptybean' );
$id = R::store( $bean );
asrt( ( $id > 0 ), TRUE );
asrt( R::count( 'emptybean' ), 1 );
$bean = R::dispense( 'emptybean' );
$id = R::store( $bean );
asrt( ( $id > 0 ), TRUE );
asrt( R::count( 'emptybean' ), 2 );
//also test in frozen mode
R::freeze( TRUE );
$bean = R::dispense( 'emptybean' );
$id = R::store( $bean );
asrt( ( $id > 0 ), TRUE );
asrt( R::count( 'emptybean' ), 3 );
R::freeze( FALSE );
}
/**
* Test the database driver and low level functions.
*
* @return void
*/
public function testDriver()
{
$currentDriver = $this->currentlyActiveDriverID;
R::store( R::dispense( 'justabean' ) );
$adapter = new TroubleDapter( R::getToolBox()->getDatabaseAdapter()->getDatabase() );
$adapter->setSQLState( 'HY000' );
$writer = new SQLiteT( $adapter );
$redbean = new OODB( $writer );
$toolbox = new ToolBox( $redbean, $adapter, $writer );
// We can only test this for a known driver...
if ( $currentDriver === 'sqlite' ) {
try {
$redbean->find( 'bean' );
pass();
} catch (\Exception $e ) {
var_dump( $e->getSQLState() );
fail();
}
}
$adapter->setSQLState( -999 );
try {
$redbean->find( 'bean' );
fail();
} catch (\Exception $e ) {
pass();
}
try {
$redbean->wipe( 'justabean' );
fail();
} catch (\Exception $e ) {
pass();
}
$toolbox = R::getToolBox();
$adapter = $toolbox->getDatabaseAdapter();
$writer = $toolbox->getWriter();
$redbean = $toolbox->getRedBean();
$pdo = $adapter->getDatabase();
$page = $redbean->dispense( "page" );
try {
$adapter->exec( "an invalid query" );
fail();
} catch ( SQL $e ) {
pass();
}
// Special data type description should result in magic number 99 (specified)
if ( $currentDriver == 'mysql' ) {
asrt( $writer->code( MySQL::C_DATATYPE_SPECIAL_DATE ), 99 );
}
if ( $currentDriver == 'pgsql' ) {
asrt( $writer->code( PostgreSQL::C_DATATYPE_SPECIAL_DATE ), 99 );
}
if ( $currentDriver == 'CUBRID' ) {
asrt( $writer->code( CUBRID::C_DATATYPE_SPECIAL_DATE ), 99 );
}
asrt( (int) $adapter->getCell( "SELECT 123" ), 123 );
$page->aname = "my page";
$id = (int) $redbean->store( $page );
asrt( (int) $page->id, 1 );
asrt( (int) $pdo->GetCell( "SELECT count(*) FROM page" ), 1 );
asrt( $pdo->GetCell( "SELECT aname FROM page LIMIT 1" ), "my page" );
asrt( (int) $id, 1 );
$page = $redbean->load( "page", 1 );
asrt( $page->aname, "my page" );
asrt( ( (bool) $page->getMeta( "type" ) ), TRUE );
asrt( isset( $page->id ), TRUE );
asrt( ( $page->getMeta( "type" ) ), "page" );
asrt( (int) $page->id, $id );
}
/**
* Test selecting.
*
* @return void
*/
public function testSelects()
{
$rooms = R::dispense( 'room', 2 );
$rooms[0]->kind = 'suite';
$rooms[1]->kind = 'classic';
$rooms[0]->number = 6;
$rooms[1]->number = 7;
R::store( $rooms[0] );
R::store( $rooms[1] );
$rooms = R::getAssoc('SELECT * FROM room WHERE id < -999');
asrt(is_array($rooms), TRUE);
asrt(count($rooms), 0);
$rooms = R::getAssoc( 'SELECT ' . R::getWriter()->esc( 'number' ) . ', kind FROM room ORDER BY kind ASC' );
foreach ( $rooms as $key => $room ) {
asrt( ( $key === 6 || $key === 7 ), TRUE );
asrt( ( $room == 'classic' || $room == 'suite' ), TRUE );
}
$rooms = R::getDatabaseAdapter()->getAssoc( 'SELECT kind FROM room' );
foreach ( $rooms as $key => $room ) {
asrt( ( $room == 'classic' || $room == 'suite' ), TRUE );
asrt( $room, $key );
}
$rooms = R::getAssoc( 'SELECT `number`, kind FROM rooms2 ORDER BY kind ASC' );
asrt( count( $rooms ), 0 );
asrt( is_array( $rooms ), TRUE );
// GetCell should return NULL in case of exception
asrt( NULL, R::getCell( 'SELECT dream FROM fantasy' ) );
}
}
/**
* Malfunctioning database adapter to test exceptions.
*/
class TroubleDapter extends DBAdapter
{
private $sqlState;
public function setSQLState( $sqlState )
{
$this->sqlState = $sqlState;
}
public function get( $sql, $values = array() )
{
$exception = new SQL( 'Just a trouble maker' );
$exception->setSQLState( $this->sqlState );
$exception->setDriverDetails( array(0,1,0) );
throw $exception;
}
public function getRow( $sql, $aValues = array() )
{
$this->get( $sql, $aValues );
}
public function exec( $sql, $aValues = array(), $noEvent = FALSE )
{
$this->get( $sql, $aValues );
}
}

View File

@@ -0,0 +1,182 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\Facade as Facade;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\Util\DispenseHelper as DispenseHelper;
/**
* Dispense
*
* Tests whether we can dispense beans and tests all
* features of the dispense/dispenseAll functions.
*
* @file RedUNIT/Base/Dispense.php
* @desc Tests bean dispensing functionality.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Dispense extends Base
{
/**
* Test whether findOrDispense and findOneOrDispense
* will trigger same validation Exception for invalid
* bean types as R::dispense(). Github issue #546.
*
* @return void
*/
public function testIssue546()
{
try {
R::findOrDispense( 'invalid_type' );
fail();
} catch ( RedException $exception ) {
pass();
}
try {
R::findOneOrDispense( 'invalid_type' );
fail();
} catch ( RedException $exception ) {
pass();
}
try {
DispenseHelper::checkType( 'invalid_type' );
fail();
} catch ( RedException $exception ) {
pass();
}
}
/**
* Test dispense.
*
* @return void
*/
public function testBasicsDispense()
{
$redbean = R::getRedBean();
// Can we dispense a bean?
$page = $redbean->dispense( "page" );
// Does it have a meta type?
asrt( ( (bool) $page->getMeta( "type" ) ), TRUE );
// Does it have an ID?
asrt( isset( $page->id ), TRUE );
// Type should be 'page'
asrt( ( $page->getMeta( "type" ) ), "page" );
// ID should be 0 because bean does not exist in database yet.
asrt( ( $page->id ), 0 );
// Try some faulty dispense actions.
foreach ( array( "", ".", "-") as $value ) {
try {
$redbean->dispense( $value );
fail();
} catch (RedException $e ) {
pass();
}
}
$bean = $redbean->dispense( "testbean" );
$bean["property"] = 123;
$bean["abc"] = "def";
asrt( $bean["property"], 123 );
asrt( $bean["abc"], "def" );
asrt( $bean->abc, "def" );
asrt( isset( $bean["abd"] ), FALSE );
asrt( isset( $bean["abc"] ), TRUE );
}
/**
* Tests the facade-only dispenseAll method.
*
* @return void
*/
public function testDispenseAll()
{
list( $book, $page ) = Facade::dispenseAll( 'book,page' );
asrt( ( $book instanceof OODBBean ), TRUE );
asrt( ( $page instanceof OODBBean ), TRUE );
asrt( $book->getMeta( 'type' ), 'book');
asrt( $page->getMeta( 'type' ), 'page');
list( $book, $page, $texts, $mark ) = R::dispenseAll( 'book,page,text*2,mark' );
asrt( ( $book instanceof OODBBean ), TRUE );
asrt( ( $page instanceof OODBBean ), TRUE );
asrt( is_array( $texts ), TRUE );
asrt( ( $mark instanceof OODBBean ), TRUE );
asrt( $book->getMeta( 'type'), 'book' );
asrt( $page->getMeta( 'type'), 'page' );
asrt( $mark->getMeta( 'type'), 'mark' );
asrt( $texts[0]->getMeta( 'type'), 'text' );
asrt( $texts[1]->getMeta( 'type'), 'text' );
list( $eggs, $milk, $butter ) = R::dispenseAll( 'eggs*3,milk*1,butter*9' );
asrt( count( $eggs ), 3 );
asrt( ( $milk instanceof OODBBean ), TRUE );
asrt( count( $butter ), 9 );
list( $eggs, $milk, $butter ) = R::dispenseAll( 'eggs*3,milk*1,butter*9', TRUE );
asrt( count( $eggs ), 3 );
asrt( count( $milk ), 1 );
asrt( count( $eggs ), 3 );
list( $beer ) = R::dispenseAll( 'beer*0', TRUE );
asrt( is_array( $beer ), TRUE );
asrt( count( $beer ), 0 );
list( $beer ) = R::dispenseAll( 'beer*0', FALSE );
asrt( is_array( $beer ), FALSE );
asrt( is_null( $beer ), TRUE );
}
/**
* Tests different return values of dispense().
*
* @return void
*/
public function testDispenseArray()
{
$oodb = R::getRedBean();
$array = $oodb->dispense( 'book', 0, TRUE );
asrt( is_array( $array ), TRUE );
$array = $oodb->dispense( 'book', 1, TRUE );
asrt( is_array( $array ), TRUE );
$array = $oodb->dispense( 'book', 2, TRUE );
asrt( is_array( $array ), TRUE );
$array = R::dispense( 'book', 0, TRUE );
asrt( is_array( $array ), TRUE );
$array = R::dispense( 'book', 1, TRUE );
asrt( is_array( $array ), TRUE );
$array = R::dispense( 'book', 2, TRUE );
asrt( is_array( $array ), TRUE );
$array = $oodb->dispense( 'book', 0, FALSE );
asrt( is_array( $array ), FALSE );
asrt( is_null( $array ), TRUE );
$array = $oodb->dispense( 'book', 1, FALSE );
asrt( is_array( $array ), FALSE );
asrt( ( $array instanceof OODBBean ), TRUE );
$array = $oodb->dispense( 'book', 2, FALSE );
asrt( is_array( $array ), TRUE );
$array = R::dispense( 'book', 0, FALSE );
asrt( is_array( $array ), FALSE );
$array = R::dispense( 'book', 1, FALSE );
asrt( is_array( $array ), FALSE );
$array = R::dispense( 'book', 2, FALSE );
asrt( is_array( $array ), TRUE );
$array = $oodb->dispense( 'book', 0 );
asrt( is_array( $array ), FALSE );
asrt( is_null( $array ), TRUE );
$array = $oodb->dispense( 'book', 1 );
asrt( is_array( $array ), FALSE );
asrt( ( $array instanceof OODBBean ), TRUE );
$array = $oodb->dispense( 'book', 2 );
asrt( is_array( $array ), TRUE );
$array = R::dispense( 'book', 0 );
asrt( is_array( $array ), FALSE );
$array = R::dispense( 'book', 1 );
asrt( is_array( $array ), FALSE );
$array = R::dispense( 'book', 2 );
asrt( is_array( $array ), TRUE );
}
}

View File

@@ -0,0 +1,647 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\DuplicationManager as DuplicationManager;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\RedException as RedException;
/**
* Dup
*
* Tests duplication. Like the 'copy' test suite but
* focuses on more complex scenarios.
*
* @file RedUNIT/Base/Dup.php
* @desc Intensive test for dup()
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Dup extends Base
{
/**
* Tests basic export functionality
*
* @return void
*/
public function testExport()
{
$bean = R::dispense('bean');
$bean->name = R::dispense('book');
$export = $bean->export( FALSE, FALSE, FALSE, array( 'book' ) );
asrt( isset( $export['name'] ), TRUE );
$export = $bean->export( FALSE, FALSE, FALSE, array( 'book2' ) );
asrt( isset( $export['name'] ), FALSE );
}
/**
* Tests whether the original ID is stored
* in meta data (quite handy for ID mappings).
*
* @return void
*/
public function testKeepOldID()
{
R::nuke();
$book = R::dispense( 'book' );
$book->xownPageList[] = R::dispense( 'page' );
R::store( $book );
$bookID = $book->id;
$page = reset( $book->xownPageList );
$pageID = $page->id;
$book = $book->fresh();
$copy = R::duplicate( $book );
asrt( $copy->getMeta( 'sys.dup-from-id' ), $bookID );
$copyPage = reset( $copy->xownPageList );
asrt( $copyPage->getMeta( 'sys.dup-from-id' ), $pageID );
}
/**
* Test export camelCase.
*
* @return void
*/
public function testExportCamelCase()
{
R::nuke();
$book = R::dispense( 'book' );
$book->isCheap = TRUE;
$book->hasISBNCode = FALSE;
$page = R::dispense('page');
$page->isWrittenWell = TRUE;
$page->containsInterestingText = TRUE;
$book->ownPageList[] = $page;
R::store( $book );
$book = $book->fresh();
$export = R::exportAll( $book );
asrt( isset( $export[0]['id'] ), TRUE );
asrt( isset( $export[0]['is_cheap'] ), TRUE );
asrt( isset( $export[0]['has_isbn_code'] ), TRUE );
asrt( isset( $export[0]['ownPage']['0']['id'] ), TRUE );
asrt( isset( $export[0]['ownPage']['0']['is_written_well'] ), TRUE );
asrt( isset( $export[0]['ownPage']['0']['contains_interesting_text'] ), TRUE );
asrt( isset( $export[0]['ownPage']['0']['book_id'] ), TRUE );
R::useExportCase( 'camel' );
$export = R::exportAll( $book );
asrt( isset( $export[0]['id'] ), TRUE );
asrt( isset( $export[0]['isCheap'] ), TRUE );
asrt( isset( $export[0]['hasIsbnCode'] ), TRUE );
asrt( isset( $export[0]['ownPage']['0']['id'] ), TRUE );
asrt( isset( $export[0]['ownPage']['0']['isWrittenWell'] ), TRUE );
asrt( isset( $export[0]['ownPage']['0']['containsInterestingText'] ), TRUE );
asrt( isset( $export[0]['ownPage']['0']['bookId'] ), TRUE );
R::useExportCase( 'dolphin' );
$export = R::exportAll( $book );
asrt( isset( $export[0]['id'] ), TRUE );
asrt( isset( $export[0]['isCheap'] ), TRUE );
asrt( isset( $export[0]['hasIsbnCode'] ), TRUE );
asrt( isset( $export[0]['ownPage']['0']['id'] ), TRUE );
asrt( isset( $export[0]['ownPage']['0']['isWrittenWell'] ), TRUE );
asrt( isset( $export[0]['ownPage']['0']['containsInterestingText'] ), TRUE );
asrt( isset( $export[0]['ownPage']['0']['bookID'] ), TRUE );
R::useExportCase( 'default' );
$export = R::exportAll( $book );
asrt( isset( $export[0]['id'] ), TRUE );
asrt( isset( $export[0]['is_cheap'] ), TRUE );
asrt( isset( $export[0]['has_isbn_code'] ), TRUE );
asrt( isset( $export[0]['ownPage']['0']['id'] ), TRUE );
asrt( isset( $export[0]['ownPage']['0']['is_written_well'] ), TRUE );
asrt( isset( $export[0]['ownPage']['0']['contains_interesting_text'] ), TRUE );
asrt( isset( $export[0]['ownPage']['0']['book_id'] ), TRUE );
try {
R::useExportCase( 'invalid' );
fail();
} catch ( RedException $exception ) {
pass();
}
}
/**
* Test whether we can duplicate part of a tree
* without infinite loops.
*
* @return void
*/
public function testDupPortionOfATree()
{
R::nuke();
$article = R::dispense( 'article' );
$article->name = 'article 1';
list( $article2, $article3 ) = R::dispense( 'article', 2 );
$article2->name = 'article 2';
$article3->name = 'article 3';
list( $article4, $article5 ) = R::dispense( 'article' , 2);
$article4->name = 'article 4';
$article5->name = 'article 5';
list( $article6, $article7 ) = R::dispense( 'article' , 2);
$article6->name = 'article 6';
$article7->name = 'article 7';
$article3->xownArticleList[] = $article7;
$article4->xownArticleList[] = $article6;
$article2->xownArticleList = array( $article5, $article4 );
$article->xownArticleList = array( $article2, $article3 );
R::store( $article );
asrt( R::count( 'article' ), 7 );
$article2 = $article2->fresh();
$dupArticle2 = R::duplicate( $article2 );
$dupArticle2->name = 'article 2b';
$dupBeans = $dupArticle2->xownArticleList;
foreach( $dupBeans as $dupBean ) {
$list[] = $dupBean->name;
}
sort( $list );
$listStr = implode( ',', $list );
asrt( $listStr, 'article 4,article 5' );
foreach( $dupBeans as $dupBean ) {
if ( $dupBean->name === 'article 4' ) {
$dup4 = $dupBean;
}
}
asrt( isset( $dup4 ), TRUE );
$dupBeans = $dup4->xownArticleList;
foreach( $dupBeans as $dupBean ) {
asrt( $dupBean->name, 'article 6' );
}
//so we have extracted part of the tree, can we store it?
$id = R::store( $dupArticle2 );
asrt( ( $id > 0 ), TRUE );
asrt( R::count( 'article' ), 11 );
$originalArticle = $article->fresh();
asrt( $originalArticle->name, 'article 1' );
$subArticles = $originalArticle->xownArticleList;
$list = array();
foreach( $subArticles as $subArticle ) {
$list[] = $subArticle->name;
}
sort( $list );
$listStr = implode( ',', $list );
asrt( $listStr, 'article 2,article 2b,article 3' );
foreach( $subArticles as $subArticle ) {
if ( $subArticle->name === 'article 2' ) {
$sub2 = $subArticle;
}
if ( $subArticle->name === 'article 3' ) {
$sub3 = $subArticle;
}
}
$subArticles = $sub2->xownArticleList;
$list = array();
foreach( $subArticles as $subArticle ) {
$list[] = $subArticle->name;
}
sort( $list );
$listStr = implode( ',', $list );
asrt( $listStr, 'article 4,article 5' );
$subArticles = $sub3->xownArticleList;
$list = array();
foreach( $subArticles as $subArticle ) {
$list[] = $subArticle->name;
}
sort( $list );
$listStr = implode( ',', $list );
asrt( $listStr, 'article 7' );
$subArticles = $sub2->xownArticleList;
foreach( $subArticles as $subArticle ) {
if ( $subArticle->name === 'article 4' ) {
$sub4 = $subArticle;
}
if ( $subArticle->name === 'article 5' ) {
$sub5 = $subArticle;
}
}
asrt( count( $sub4->xownArticleList ), 1 );
$subBeans = $sub4->xownArticleList;
$subBean = reset( $subBeans );
asrt( $subBean->name, 'article 6');
asrt( count( $sub5->xownArticleList ), 0 );
$dupArticle2 = $dupArticle2->fresh();
$subArticles = $dupArticle2->xownArticleList;
$list = array();
foreach( $subArticles as $subArticle ) {
$list[] = $subArticle->name;
}
sort( $list );
$listStr = implode( ',', $list );
asrt( $listStr, 'article 4,article 5' );
foreach( $subArticles as $subArticle ) {
if ( $subArticle->name === 'article 4' ) {
$sub4 = $subArticle;
}
if ( $subArticle->name === 'article 5' ) {
$sub5 = $subArticle;
}
}
asrt( count( $sub4->xownArticleList ), 1 );
$subBeans = $sub4->xownArticleList;
$subBean = reset( $subBeans );
asrt( $subBean->name, 'article 6');
asrt( count( $sub5->xownArticleList ), 0 );
}
/**
* Test exportAll and caching.
*
* @return void
*/
public function testExportAllAndCache()
{
testpack( 'exportAll() and Cache' );
$can = R::dispense( 'can' )->setAttr( 'size', 3 );
$can->ownCoffee[] = R::dispense( 'coffee' )->setAttr( 'color', 'black' );
$can->sharedTag[] = R::dispense( 'tag' )->setAttr( 'name', 'cool' );
$id = R::store( $can );
R::debug( TRUE );
ob_start();
$can = R::load( 'can', $id );
$cache = $this->getCache();
$data = R::exportAll( array( $can ), TRUE );
$queries = ob_get_contents();
R::debug( FALSE );
ob_end_clean();
$len1 = strlen( $queries );
$can = R::dispense( 'can' )->setAttr( 'size', 3 );
$can->ownCoffee[] = R::dispense( 'coffee' )->setAttr( 'color', 'black' );
$can->sharedTag[] = R::dispense( 'tag' )->setAttr( 'name', 'cool' );
$id = R::store( $can );
R::debug( TRUE );
ob_start();
$can = R::load( 'can', $id );
$cache = $this->getCache();
$data = R::exportAll( array( $can ), TRUE );
$queries = ob_get_contents();
R::debug( FALSE );
ob_end_clean();
$len2 = strlen( $queries );
asrt( ( $len1 ), ( $len2 ) );
$can = R::dispense( 'can' )->setAttr( 'size', 3 );
$can->ownCoffee[] = R::dispense( 'coffee' )->setAttr( 'color', 'black' );
$can->sharedTag[] = R::dispense( 'tag' )->setAttr( 'name', 'cool' );
$id = R::store( $can );
R::debug( TRUE );
ob_start();
$can = R::load( 'can', $id );
$cache = $this->getCache();
R::getDuplicationManager()->setTables( $cache );
$data = R::exportAll( array( $can ), TRUE );
$queries = ob_get_contents();
R::debug( FALSE );
ob_end_clean();
$len3 = strlen( $queries );
asrt( ( ( $len3 ) < ( $len2 ) ), TRUE );
asrt( count( $data ), 1 );
asrt( $data[0]['ownCoffee'][0]['color'], 'black' );
R::getDuplicationManager()->setCacheTables( FALSE );
}
/**
* Test duplication and caching.
*
* @return void
*/
public function DupAndCache()
{
testpack( 'Dup() and Cache' );
$can = R::dispense( 'can' )->setAttr( 'size', 3 );
$can->ownCoffee[] = R::dispense( 'coffee' )->setAttr( 'color', 'black' );
$can->sharedTag[] = R::dispense( 'tag' )->setAttr( 'name', 'cool' );
$can = R::load( 'can', R::store( $can ) );
$d = new DuplicationManager( R::getToolBox() );
$d->setCacheTables( TRUE );
ob_start();
R::debug( 1 );
$x = $d->dup( $can );
$queries = ob_get_contents();
R::debug( 0 );
ob_end_clean();
$len1 = strlen( $queries );
asrt( ( $len1 > 40 ), TRUE );
asrt( isset( $x->ownCoffee ), TRUE );
asrt( count( $x->ownCoffee ), 1 );
asrt( isset( $x->sharedTag ), TRUE );
asrt( count( $x->sharedTag ), 1 );
$cache = $d->getSchema();
R::nuke();
$can = R::dispense( 'can' )->setAttr( 'size', 3 );
$can->ownCoffee[] = R::dispense( 'coffee' )->setAttr( 'color', 'black' );
$can->sharedTag[] = R::dispense( 'tag' )->setAttr( 'name', 'cool' );
$can = R::load( 'can', R::store( $can ) );
$d = new DuplicationManager( R::getToolBox() );
/**
* $cache = '{"book": {
* "id": "INTEGER",
* "title": "TEXT"
* }, "bean": {
* "id": "INTEGER",
* "prop": "INTEGER"
* }, "pessoa": {
* "id": "INTEGER",
* "nome": "TEXT",
* "nome_meio": "TEXT",
* "sobrenome": "TEXT",
* "nascimento": "NUMERIC",
* "reg_owner": "TEXT"
* }, "documento": {
* "id": "INTEGER",
* "nome_documento": "TEXT",
* "numero_documento": "TEXT",
* "reg_owner": "TEXT",
* "ownPessoa_id": "INTEGER"
* }, "can": {
* "id": "INTEGER",
* "size": "INTEGER"
* }, "coffee": {
* "id": "INTEGER",
* "color": "TEXT",
* "can_id": "INTEGER"
* }, "tag": {
* "id": "INTEGER",
* "name": "TEXT"
* }, "can_tag": {
* "id": "INTEGER",
* "tag_id": "INTEGER",
* "can_id": "INTEGER"
* }}'
*/
$d->setTables( $cache );
ob_start();
R::debug( 1 );
$x = $d->dup( $can );
$queries = ob_get_contents();
ob_end_clean();
R::debug( 0 );
$len2 = strlen( $queries );
asrt( isset( $x->ownCoffee ), TRUE );
asrt( count( $x->ownCoffee ), 1 );
asrt( isset( $x->sharedTag ), TRUE );
asrt( count( $x->sharedTag ), 1 );
asrt( json_encode( $cache ), json_encode( $d->getSchema() ) );
asrt( ( $len1 > $len2 ), TRUE );
}
/**
* Test duplication and tainting.
*
* @return void
*/
public function testDupAndExportNonTainting()
{
testpack( 'Dup() and Export() should not taint beans' );
$p = R::dispense( 'page' );
$b = R::dispense( 'book' );
$b->ownPage[] = $p;
$b->title = 'a';
$id = R::store( $b );
$b = R::load( 'book', $id );
asrt( ( !$b->getMeta( 'tainted' ) ), TRUE );
R::exportAll( $b );
asrt( ( !$b->getMeta( 'tainted' ) ), TRUE );
R::dup( $b );
asrt( ( !$b->getMeta( 'tainted' ) ), TRUE );
testpack( 'Test issue with ownItems and stealing Ids.' );
R::nuke();
$bill = R::dispense( 'bill' );
$item = R::dispense( 'item' );
$element = R::dispense( 'element' );
$bill->ownItem[] = $item;
$bill->sharedElement[] = $element;
R::store( $bill );
$bill = R::load( 'bill', 1 );
$bill->ownItem;
$bill->sharedElement;
$copy = R::dup( $bill );
R::store( $copy );
$rows = ( R::getAll( 'select * from bill_element' ) );
asrt( count( $rows ), 2 );
$rows = ( R::getAll( 'select * from item' ) );
foreach ( $rows as $row ) {
asrt( ( $row['bill_id'] > 0 ), TRUE );
}
R::nuke();
$this->runOnce();
R::freeze( TRUE );
$this->runOnce( FALSE );
R::freeze( FALSE );
}
/**
* Test exporting with filters.
*
* @return void
*/
public function ExportWithFilters()
{
testpack( 'Export with filters' );
$book = R::dispense( 'book' );
$pages = R::dispense( 'page', 2 );
$texts = R::dispense( 'text', 2 );
$images = R::dispense( 'image', 2 );
$author = R::dispense( 'author' );
$pub = R::dispense( 'publisher' );
$bookmarks = R::dispense( 'bookmark', 2 );
$pages[0]->ownText = array( $texts[0] );
$pages[0]->ownImage = array( $images[0] );
$pages[1]->ownText = array( $texts[1] );
$pages[1]->ownImage = array( $images[1] );
$pages[0]->sharedBookmark[] = $bookmarks[0];
$pages[1]->sharedBookmark[] = $bookmarks[1];
$bookmarks[0]->ownNote[] = R::dispense( 'note' )->setAttr( 'text', 'a note' );
$bookmarks[1]->ownNote[] = R::dispense( 'note' )->setAttr( 'text', 'a note' );
$book->ownPage = $pages;
$book->author = $author;
$author->publisher = $pub;
$bookID = R::store( $book );
R::getDuplicationManager()->setTables( R::getWriter()->getTables() );
$objects = ( R::exportAll( array( $book ), TRUE, array() ) );
asrt( isset( $objects[0]['ownPage'] ), TRUE );
asrt( count( $objects[0]['ownPage'] ), 2 );
asrt( isset( $objects[0]['author'] ), TRUE );
asrt( isset( $objects[0]['ownPage'][0]['ownText'] ), TRUE );
asrt( count( $objects[0]['ownPage'][0]['ownText'] ), 1 );
asrt( isset( $objects[0]['ownPage'][0]['ownImage'] ), TRUE );
asrt( count( $objects[0]['ownPage'][0]['ownImage'] ), 1 );
$objects = ( R::exportAll( array( $book ), TRUE, array( 'page', 'author', 'text', 'image' ) ) );
asrt( isset( $objects[0]['ownPage'] ), TRUE );
asrt( count( $objects[0]['ownPage'] ), 2 );
asrt( isset( $objects[0]['author'] ), TRUE );
asrt( isset( $objects[0]['ownPage'][0]['ownText'] ), TRUE );
asrt( count( $objects[0]['ownPage'][0]['ownText'] ), 1 );
asrt( isset( $objects[0]['ownPage'][0]['ownImage'] ), TRUE );
asrt( count( $objects[0]['ownPage'][0]['ownImage'] ), 1 );
$objects = ( R::exportAll( array( $book ), TRUE, 'author' ) );
asrt( isset( $objects[0]['ownPage'] ), FALSE );
asrt( isset( $objects[0]['ownPage'][0]['ownText'] ), FALSE );
$objects = ( R::exportAll( array( $book ), TRUE, array( 'page' ) ) );
asrt( isset( $objects[0]['author'] ), FALSE );
asrt( isset( $objects[0]['ownPage'][0]['ownText'] ), FALSE );
$objects = ( R::exportAll( array( $book ), TRUE, array( 'page', 'text' ) ) );
asrt( isset( $objects[0]['author'] ), FALSE );
asrt( isset( $objects[0]['ownPage'] ), TRUE );
asrt( isset( $objects[0]['ownPage'][0]['ownText'] ), TRUE );
asrt( count( $objects[0]['ownPage'][0]['ownText'] ), 1 );
asrt( isset( $objects[0]['ownPage'][0]['ownImage'] ), FALSE );
$objects = ( R::exportAll( array( $book ), TRUE, array( 'none' ) ) );
asrt( isset( $objects[0]['author'] ), FALSE );
asrt( isset( $objects[0]['ownPage'] ), FALSE );
$texts = R::find( 'text' );
R::getDuplicationManager()->setCacheTables( FALSE );
testpack( 'Keyless export' );
$book = R::load( 'book', $bookID );
$book->ownPage;
$export = $book->export();
asrt( isset( $export['ownPage'][0] ), TRUE );
}
/**
* Helper function getCache().
*
* @return array
*/
private function getCache()
{
return array(
'coffee' => array(
'color' => 'color',
'id' => 'id',
'can_id' => 'can_id'
),
'can' => array(
'size' => 'size',
'id' => 'id'
),
'can_tag' => array(
'id' => 'id',
'can_id' => 'can_id',
'tag_id' => 'tag_id'
),
'tag' => array(
'id' => 'id',
'name' => 'name' )
);
}
/**
* Compares object with export
*
* @param type $object
* @param type $array
*
* @return void
*/
private function compare( $object, $array )
{
foreach ( $object as $property => $value ) {
if ( is_array( $value ) ) {
foreach ( $value as $index => $nestedObject ) {
if ( $nestedObject->id ) {
$foundMatch = FALSE;
//order might be different
foreach ( $array[$property] as $k => $a ) {
if ( $a['id'] == $nestedObject->id ) {
$foundMatch = TRUE;
$index = $k;
}
}
if ( !$foundMatch ) throw new\Exception( 'failed to find match for object ' . $nestedObject->id );
}
$this->compare( $nestedObject, $array[$property][$index] );
}
} elseif ( !is_object( $value ) ) {
asrt( strval( $array[$property] ), strval( $value ) );
}
}
}
/**
* Run tests
*/
private function runOnce( $n = TRUE )
{
$books = R::dispense( 'book', 10 );
$pages = R::dispense( 'page', 10 );
$readers = R::dispense( 'reader', 10 );
$texts = R::dispense( 'text', 10 );
$i = 0;
foreach ( $books as $book ) $book->name = 'book-' . ( $i++ );
$i = 0;
foreach ( $pages as $page ) $page->name = 'page-' . ( $i++ );
$i = 0;
foreach ( $readers as $reader ) $reader->name = 'reader-' . ( $i++ );
$i = 0;
foreach ( $texts as $text ) $text->content = 'lorem ipsum -' . ( $i++ );
foreach ( $texts as $text ) {
$pages[array_rand( $pages )]->ownText[] = $text;
}
foreach ( $pages as $page ) {
$books[array_rand( $books )]->ownPage[] = $page;
}
foreach ( $readers as $reader ) {
$books[array_rand( $books )]->sharedReader[] = $reader;
}
$i = $noOfReaders = $noOfPages = $noOfTexts = 0;
foreach ( $books as $key => $book ) {
$i++;
$noOfPages += count( $book->ownPage );
$noOfReaders += count( $book->sharedReader );
foreach ( $book->ownPage as $page ) $noOfTexts += count( $page->ownText );
$arr = R::exportAll( $book );
echo "\nIntermediate info: " . json_encode( $arr ) . ": Totals = $i,$noOfPages,$noOfReaders,$noOfTexts ";
$this->compare( $book, $arr[0] );
$copiedBook = R::dup( $book );
$copiedBookArray = R::exportAll( $copiedBook );
$this->compare( $book, $copiedBookArray[0] );
$copiedBookArrayII = $copiedBook->export();
$this->compare( $book, $copiedBookArrayII );
$copyFromCopy = R::dup( $copiedBook );
$copyFromCopyArray = R::exportAll( $copyFromCopy );
$this->compare( $book, $copyFromCopyArray[0] );
$copyFromCopyArrayII = $copyFromCopy->export();
$this->compare( $book, $copyFromCopyArrayII );
$id = R::store( $book );
$copiedBook = R::dup( $book );
R::store( $book ); //should not be damaged
$copiedBookArray = R::exportAll( $copiedBook );
$originalBookArray = R::exportAll( $book );
$this->compare( $copiedBook, $copiedBookArray[0] );
$this->compare( $book, $originalBookArray[0] );
$book = R::load( 'book', $id );
$this->compare( $book, $originalBookArray[0] );
$copiedBook = R::dup( $book );
$this->compare( $copiedBook, $copiedBook->export() );
R::store( $copiedBook );
$this->compare( $copiedBook, $copiedBook->export() );
$copyFromCopy = R::dup( $copiedBook );
$this->compare( $copyFromCopy, $copyFromCopy->export() );
R::store( $copyFromCopy );
$newPage = R::dispense( 'page' );
$newPage->name = 'new';
$copyFromCopy->ownPage[] = $newPage;
$modifiedCopy = R::dup( $copyFromCopy );
$exportMod = R::exportAll( $modifiedCopy );
$this->compare( $modifiedCopy, $exportMod[0] );
asrt( count( $modifiedCopy->ownPage ), count( $copiedBook->ownPage ) + 1 );
R::store( $modifiedCopy );
if ( $n ) {
asrt( (int) R::getCell( 'SELECT count(*) FROM book' ), $i * 4 );
asrt( (int) R::getCell( 'SELECT count(*) FROM page' ), ( $noOfPages * 4 ) + $i );
asrt( (int) R::getCell( 'SELECT count(*) FROM text' ), $noOfTexts * 4 );
asrt( (int) R::getCell( 'SELECT count(*) FROM book_reader' ), $noOfReaders * 4 );
asrt( (int) R::getCell( 'SELECT count(*) FROM reader' ), $noOfReaders );
}
}
if ( $n ) {
asrt( $noOfTexts, 10 );
asrt( $noOfReaders, 10 );
asrt( $noOfPages, 10 );
asrt( $i, 10 );
}
}
}

View File

@@ -0,0 +1,166 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\RedException\SQL as SQLException;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\Adapter\DBAdapter;
use RedBeanPHP\ToolBox;
use RedBeanPHP\QueryWriter\SQLiteT;
use RedBeanPHP\QueryWriter\AQueryWriter;
use RedBeanPHP\QueryWriter;
use RedBeanPHP\OODB;
use RedBeanPHP\Driver\RPDO;
/**
* Exceptions
*
* Tests exception handling in various scenarios as well
* as exception related functionalities.
*
* @file RedUNIT/Base/Exceptions.php
* @desc Tests exception handling
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Exceptions extends Base {
/**
* Tests load exceptions.
* Load Exceptions (version 5+)
*
* - If a bean does not exist,
* R::load() and R::loadForUpdate() will return an empty bean.
* - If there is an error because of a missing table or column,
* both methods will return an empty bean in fluid mode
* and throw an exception in frozen mode.
* - If something else happens (lock timeout for instance)
* both methods will always throw an exception, even in fluid mode.
*
* @return void
*/
public function testLoadExceptions()
{
/* bean does not exist, and table does not exist */
R::nuke();
$book = R::load( 'book', 1 );
pass();
asrt( $book->id, 0 );
R::freeze( TRUE );
$exception = NULL;
try {
$book = R::load( 'book', 1 );
} catch( RedException $exception ) {}
asrt( ( $exception instanceof RedException ), TRUE );
R::freeze( FALSE );
R::store( $book );
/* bean does not exist - table exists */
$book = R::load( 'book', 2 );
pass();
asrt( $book->id, 0 );
R::freeze( TRUE );
$book = R::load( 'book', 2 );
pass();
asrt( $book->id, 0 );
/* other error */
if ( !( R::getWriter() instanceof SQLiteT ) ) {
R::freeze( FALSE );
$exception = NULL;
try {
$book = R::load( 'book', 1, 'invalid sql' );
} catch( RedException $exception ) {}
//not supported for CUBRID
if ($this->currentlyActiveDriverID !== 'CUBRID') {
asrt( ( $exception instanceof RedException ), TRUE );
}
} else {
/* error handling in SQLite is suboptimal */
R::freeze( FALSE );
$book = R::load( 'book', 1, 'invalid sql' );
pass();
asrt( $book->id, 0 );
}
R::freeze( TRUE );
$exception = NULL;
try {
$book = R::load( 'book', 1, 'invalid sql' );
} catch( RedException $exception ) {}
asrt( ( $exception instanceof RedException ), TRUE );
R::freeze( FALSE );
R::nuke();
}
/**
* Test delete exceptions
*
* - in fluid mode no complaining about missing structures
*
* @return void
*/
public function testDeleteExceptions()
{
R::nuke();
$book = R::dispense( 'book' );
R::store( $book );
R::nuke();
R::trash( $book );
R::freeze( TRUE );
$exception = NULL;
try {
R::trash( $book );
} catch( RedException $exception ) {}
asrt( ( $exception instanceof RedException ), TRUE );
R::freeze( FALSE );
$adapter = R::getDatabaseAdapter();
R::nuke();
$book = R::dispense( 'book' );
R::store( $book );
$broken = new BrokenWriter( $adapter );
$redbean = R::getRedBean();
$oodb = new OODB( $broken, $redbean->isFrozen() );
R::setRedBean( $oodb );
$exception = NULL;
try {
R::trash( $book );
} catch( RedException $exception ) {}
asrt( ( $exception instanceof RedException ), TRUE );
R::freeze( TRUE );
$exception = NULL;
try {
R::trash( $book );
} catch( RedException $exception ) {}
asrt( ( $exception instanceof RedException ), TRUE );
R::setRedBean( $redbean );
}
/**
* Test chaining of exceptions.
*
* @return void
*/
public function testChainingExceptions()
{
R::freeze( TRUE );
$exception = NULL;
try {
$book = R::load( 'book', 1, 'invalid sql' );
} catch( RedException $exception ) {}
pass();
asrt( ( $exception instanceof RedException ), TRUE );
asrt( ( $exception->getPrevious() instanceof \Exception ), TRUE );
}
}
class BrokenWriter extends SQLiteT {
public function deleteRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
{
throw new SQLException('oops');
}
}

View File

@@ -0,0 +1,137 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\AssociationManager as AssociationManager;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\ToolBox as ToolBox;
use RedBeanPHP\Adapter as Adapter;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Facade
*
* Tests the facade. The facade is a single class (often aliased with R)
* that provides access to all (or most) RedBeanPHP functionality without
* the need to interact with all the objects involved. The facade has
* been designed to be as 'straightfoward' as possible, many methods
* of the facade class should be 'almost identical' to simple calls
* to objects behind the facade. This test focuses on basic usage
* scenarios. Complex scenarios are tested in other suites because
* we often test 'the complete route' as the facade code path always
* includes the non-facade code path as well (as proven by the coverage numbers).
*
* @file RedUNIT/Base/Facade.php
* @desc Tests basic functions through facade.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Facade extends Base
{
/**
* What drivers should be loaded for this test pack?
* This pack contains some SQL incomp. with OCI
*/
public function getTargetDrivers()
{
return array( 'mysql', 'pgsql', 'sqlite', 'CUBRID' );
}
/**
* Tests quick trash method: R::trash( type, id ).
*
* @return void
*/
public function testQuickTrash()
{
R::nuke();
$bean = R::dispense( 'bean' );
$id = R::store( $bean );
asrt( R::count( 'bean' ), 1 );
R::trash( 'bean', $id );
asrt( R::count( 'bean' ), 0 );
}
/**
* Test common Facade usage scenarios.
*
* @return void
*/
public function testCommonUsageFacade()
{
$toolbox = R::getToolBox();
$adapter = $toolbox->getDatabaseAdapter();
$writer = $toolbox->getWriter();
$redbean = $toolbox->getRedBean();
$pdo = $adapter->getDatabase();
$a = new AssociationManager( $toolbox );
asrt( R::getRedBean() instanceof OODB, TRUE );
asrt( R::getToolBox() instanceof ToolBox, TRUE );
asrt( R::getDatabaseAdapter() instanceof Adapter, TRUE );
asrt( R::getWriter() instanceof QueryWriter, TRUE );
$book = R::dispense( "book" );
asrt( $book instanceof OODBBean, TRUE );
$book->title = "a nice book";
$id = R::store( $book );
asrt( ( $id > 0 ), TRUE );
$book = R::load( "book", (int) $id );
asrt( $book->title, "a nice book" );
asrt( R::load( 'book', 999 )->title, NULL );
R::freeze( TRUE );
try {
R::load( 'bookies', 999 );
fail();
} catch (\Exception $e ) {
pass();
}
R::freeze( FALSE );
$author = R::dispense( "author" );
$author->name = "me";
R::store( $author );
$book9 = R::dispense( "book" );
$author9 = R::dispense( "author" );
$author9->name = "mr Nine";
$a9 = R::store( $author9 );
$book9->author_id = $a9;
$bk9 = R::store( $book9 );
$book9 = R::load( "book", $bk9 );
$author = R::load( "author", $book9->author_id );
asrt( $author->name, "mr Nine" );
R::trash( $author );
R::trash( $book9 );
pass();
$book2 = R::dispense( "book" );
$book2->title = "second";
R::store( $book2 );
$book3 = R::dispense( "book" );
$book3->title = "third";
R::store( $book3 );
asrt( count( R::find( "book" ) ), 3 );
asrt( count( R::findAll( "book" ) ), 3 );
asrt( count( R::findAll( "book", " LIMIT 2" ) ), 2 );
asrt( count( R::find( "book", " id=id " ) ), 3 );
asrt( count( R::find( "book", " title LIKE ?", array( "third" ) ) ), 1 );
asrt( count( R::find( "book", " title LIKE ?", array( "%d%" ) ) ), 2 );
// Find without where clause
asrt( count( R::findAll( 'book', ' order by id' ) ), 3 );
R::trash( $book3 );
R::trash( $book2 );
asrt( count( R::getAll( "SELECT * FROM book " ) ), 1 );
asrt( count( R::getCol( "SELECT title FROM book " ) ), 1 );
asrt( (int) R::getCell( "SELECT 123 " ), 123 );
$book = R::dispense( "book" );
$book->title = "not so original title";
$author = R::dispense( "author" );
$author->name = "Bobby";
R::store( $book );
$aid = R::store( $author );
$author = R::findOne( "author", " name = ? ", array( "Bobby" ) );
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,329 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\Observer as Observer;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\Adapter as Adapter;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
/**
* Foreignkeys
*
* Tests whether foreign keys are correctly generated and whether
* depending beans are correctly removed. Also tests auto resolving
* types inferred by inspecting foreign keys.
*
* @file RedUNIT/Base/Foreignkeys.php
* @desc Tests foreign key handling and dynamic foreign keys with
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Foreignkeys extends Base implements Observer
{
/**
* To log the queries
*
* @var array
*/
private $queries = array();
/**
* Test whether unique constraints are properly created using
* reflection.
*
* @return void
*/
public function testUniqueInspect()
{
$writer = R::getWriter();
R::nuke();
$book = R::dispense( 'book' );
$category = R::dispense( 'category' );
$book->sharedCategory[] = $category;
R::store( $book );
asrt( count( get_uniques_for_type('book_category') ), 1 );
asrt( are_cols_in_unique( 'book_category', array( 'book_id', 'category_id' ) ), TRUE );
R::nuke();
$book = R::dispense( 'book' );
$category = R::dispense( 'category' );
$book->via( 'library' )->sharedCategory[] = $category;
R::store( $book );
asrt( count( get_uniques_for_type('book_category') ), 0 );
asrt( are_cols_in_unique( 'book_category', array( 'book_id', 'category_id' ) ), FALSE );
asrt( count( get_uniques_for_type('library') ), 1 );
asrt( are_cols_in_unique( 'library', array( 'book_id', 'category_id' ) ), TRUE );
AQueryWriter::clearRenames();
R::nuke();
$book = R::dispense( 'book' );
$category = R::dispense( 'category' );
$book->sharedCategory[] = $category;
R::store( $book );
asrt( count( get_uniques_for_type('book_category') ), 1 );
asrt( are_cols_in_unique( 'book_category', array( 'book_id', 'category_id' ) ), TRUE );
asrt( count( get_uniques_for_type('library') ), 0 );
asrt( are_cols_in_unique( 'library', array( 'book_id', 'category_id' ) ), FALSE );
R::nuke();
$book = R::dispense( 'book' );
$book2 = R::dispense( 'book' );
$book->sharedBook[] = $book2;
R::store( $book );
asrt( count( get_uniques_for_type('book_book') ), 1 );
asrt( are_cols_in_unique( 'book_book', array( 'book_id', 'book2_id' ) ), TRUE );
try {
$result = R::getWriter()->addUniqueConstraint( 'nonexistant', array( 'a', 'b' ) );
} catch( \Exception $e ) {
print_r( $e ); exit;
}
pass(); //dont crash!
asrt( $result, FALSE );
}
/**
* Tests foreign keys but checks using ProxyWriter.
*
* @return void
*/
public function testFKInspect()
{
$faultyWriter = new \FaultyWriter( R::getDatabaseAdapter() );
try {
$null = \ProxyWriter::callMethod( $faultyWriter, 'getForeignKeyForTypeProperty', 'test', 'test' );
pass();
} catch( \Exception $e ) {
fail();
}
asrt( is_null( $null ), TRUE );
$writer = R::getWriter();
R::nuke();
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$book->xownPage[] = $page;
R::store( $book );
$keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'page', 'book_id' );
asrt( is_array( $keys ), TRUE );
asrt( $keys['on_delete'], 'CASCADE' );
$keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'page', 'id' );
asrt( is_null( $keys ), TRUE );
R::nuke();
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$book->ownPage[] = $page;
R::store( $book );
$keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'page', 'book_id' );
asrt( is_array( $keys ), TRUE );
asrt( $keys['on_delete'], 'SET NULL' );
$keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'page', 'id' );
asrt( is_null( $keys ), TRUE );
R::nuke();
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$book->alias('magazine')->xownPage[] = $page;
R::store( $book );
$keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'page', 'magazine_id' );
asrt( is_array( $keys ), TRUE );
asrt( $keys['on_delete'], 'CASCADE' );
$keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'page', 'book_id' );
asrt( is_null( $keys ), TRUE );
$keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'page', 'id' );
asrt( is_null( $keys ), TRUE );
R::nuke();
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$book->cover= $page;
R::store( $book );
$keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'book', 'cover_id' );
asrt( is_array( $keys ), TRUE );
asrt( $keys['on_delete'], 'SET NULL' );
$keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'book', 'page_id' );
asrt( is_null( $keys ), TRUE );
$keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'book', 'id' );
asrt( is_null( $keys ), TRUE );
R::nuke();
$book = R::dispense( 'book' );
$category = R::dispense( 'category' );
$book->sharedTag[] = $category;
R::store( $book );
$keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'book_category', 'book_id' );
asrt( is_array( $keys ), TRUE );
$keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'book_category', 'category_id' );
asrt( is_array( $keys ), TRUE );
$keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'book_category', 'id' );
asrt( is_null( $keys ), TRUE );
}
/**
* Test dependencies.
*
* @return void
*/
public function testDependency()
{
$can = $this->createBeanInCan( FALSE );
asrt( R::count( 'bean' ), 1 );
R::trash( $can );
// Bean stays
asrt( R::count( 'bean' ), 1 );
}
/**
* Test dependencies (variation).
*
* @return void
*/
public function testDependency2()
{
$can = $this->createBeanInCan( TRUE );
asrt( R::count( 'bean' ), 1 );
R::trash( $can );
// Bean gone
asrt( R::count( 'bean' ), 0 );
$can = $this->createBeanInCan( FALSE );
asrt( R::count( 'bean' ), 1 );
R::trash( $can );
// Bean stays, constraint removed
asrt( R::count( 'bean' ), 0 );
//need to recreate table to get rid of constraint!
R::nuke();
$can = $this->createBeanInCan( FALSE );
asrt( R::count( 'bean' ), 1 );
R::trash( $can );
// Bean stays, constraint removed
asrt( R::count( 'bean' ), 1 );
}
/**
* Tests dependencies (variation).
*
* @return void
*/
public function testDependency3()
{
R::nuke();
$can = $this->createCanForBean();
asrt( R::count( 'bean' ), 1 );
R::trash( $can );
asrt( R::count( 'bean' ), 1 );
}
/**
* Tests dependencies (variation).
*
* @return void
*/
public function testDependency4()
{
R::nuke();
$can = $this->createBeanInCan( TRUE );
R::store( $can );
R::trash( $can );
$can = $this->createCanForBean();
asrt( R::count( 'bean' ), 1 );
R::trash( $can );
asrt( R::count( 'bean' ), 0 );
$can = $this->createBeanInCan( TRUE );
R::store( $can );
R::trash( $can );
$can = $this->createCanForBean();
asrt( R::count( 'bean' ), 1 );
R::trash( $can );
asrt( R::count( 'bean' ), 0 );
}
/**
* Issue #171
* The index name argument is not unique in processEmbeddedBean etc.
*
* @return void
*/
public function testIssue171()
{
R::getDatabaseAdapter()->addEventListener( 'sql_exec', $this );
$account = R::dispense( 'account' );
$user = R::dispense( 'user' );
$player = R::dispense( 'player' );
$account->ownUser[] = $user;
R::store( $account );
asrt( strpos( implode( ',', $this->queries ), 'index_foreignkey_user_account' ) !== FALSE, TRUE );
$this->queries = array();
$account->ownPlayer[] = $player;
R::store( $account );
asrt( strpos( implode( ',', $this->queries ), 'index_foreignkey_player_accou' ) !== FALSE, TRUE );
}
/**
* Tests whether foreign keys are created correctly for certain
* relations.
*
* @return void
*/
public function testCreationOfForeignKeys()
{
$this->queries = array();
$account = R::dispense( 'account' );
$user = R::dispense( 'user' );
$player = R::dispense( 'player' );
$user->account = $account;
R::store( $user );
asrt( strpos( implode( ',', $this->queries ), 'index_foreignkey_user_account' ) !== FALSE, TRUE );
$this->queries = array();
$player->account = $account;
R::store( $player );
asrt( strpos( implode( ',', $this->queries ), 'index_foreignkey_player_accou' ) !== FALSE, TRUE );
}
/**
* Test helper method.
* Creates a bean in a can. The bean will get a reference
* to the can and can be made dependent.
*
* @return OODBBean $can
*/
private function createBeanInCan( $isExcl )
{
$can = R::dispense( 'can' );
$bean = R::dispense( 'bean' );
$can->name = 'bakedbeans';
$bean->taste = 'salty';
if ($isExcl) {
$can->xownBean[] = $bean;
} else {
$can->ownBean[] = $bean;
}
R::store( $can );
return $can;
}
/**
* Test helper method.
* Creates a bean in a can beginning with the bean. The bean will get a reference
* to the can and can be made dependent.
*
* @return OODBBean $can
*/
private function createCanForBean()
{
$can = R::dispense( 'can' );
$bean = R::dispense( 'bean' );
$bean->can = $can;
R::store( $bean );
return $can;
}
/**
* Log queries
*
* @param string $event
* @param Adapter $info
*/
public function onEvent( $event, $info )
{
$this->queries[] = $info->getSQL();
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Frozen
*
* Frozen mode tests
* When I split the repositories in frozen and fluid I discovered some missed
* code-paths in the tests.
* These tests are here to make sure the following scenarios work properly
* in frozen mode as well.
*
* @file RedUNIT/Base/Frozen.php
* @desc Test some scenarios we haven't covered for frozen mode.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Frozen extends Base
{
/**
* Tests the handling of trashed beans in frozen mode.
* Are the lists unset etc?
*
* @return void
*/
public function testTrash()
{
R::nuke();
$book = R::dispense( 'book' );
$book->xownPageList[] = R::dispense( 'page' );
$book->sharedTagList[] = R::dispense( 'tag' );
R::store( $book );
$book = $book->fresh();
R::freeze( TRUE );
$book->xownPageList = array();
R::store( $book );
$book = $book->fresh();
asrt( R::count('page'), 0 );
$book->xownPageList[] = R::dispense( 'page' );
R::store( $book );
$book = $book->fresh();
asrt( R::count('page'), 1 );
$book->xownPageList;
$book->sharedTagList;
R::trash( $book );
asrt( R::count('book'), 0 );
asrt( R::count('page'), 0 );
asrt( R::count('tag'), 1 );
asrt( R::count('book_tag'), 0 );
R::freeze( FALSE );
}
/**
* Tests whether invalid list checks are
* operational in frozen mode.
*
* @return void
*/
public function testInvalidList()
{
R::nuke();
$book = R::dispense( 'book' );
$book->xownPageList[] = R::dispense( 'page' );
$book->sharedTagList[] = R::dispense( 'tag' );
R::store( $book );
R::freeze( TRUE );
$book = R::dispense( 'book' );
$book->xownPageList[] = 'nonsense';
try {
R::store( $book );
fail();
} catch( \Exception $e ) {
pass();
}
R::freeze( FALSE );
}
/**
* Tests whether loading non-existant beans
* returns the same results in frozen mode.
*
* @return
*/
public function testLoadNonExistant()
{
R::nuke();
R::store( R::dispense( 'bean' ) );
R::freeze( TRUE );
$bean = R::load( 'bean', 123 );
R::freeze( FALSE );
asrt( ( $bean instanceof OODBBean ), TRUE );
asrt( $bean->id, 0 );
}
}

View File

@@ -0,0 +1,420 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\ModelHelper as ModelHelper;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\ToolBox as ToolBox;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\Adapter as Adapter;
use RedBeanPHP\BeanHelper\SimpleFacadeBeanHelper as SimpleFacadeBeanHelper;
/**
* FUSE
*
* Tests whether we can associate model logic on-the-fly
* by defining models extending from SimpleModel. Tests
* whether the calls to facade trigger the corresponding
* methods on the model.
*
* @file RedUNIT/Base/Fuse.php
* @desc Tests Fuse feature; coupling beans to models.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Fuse extends Base
{
/**
* Hook into the jsonSerialize function #651
* Allow models to provide a jsonSerialize return.
* This test uses the Coffee Helper Model to add
* a description to the JSON representation of the bean.
*
* @return void
*/
public function testFUSEJSonSerialize()
{
if ( phpversion() < 5.4 ) return;
$coffee = R::dispense( 'coffee' );
$coffee->variant = 'Tropical';
$coffee->strength = 4;
$json = json_encode( $coffee );
$array = json_decode( $json, TRUE );
asrt( isset( $array['description'] ), TRUE );
asrt( $array['description'], 'Tropical.4' );
}
/**
* Test whether we can override the getModelForBean() method
* of the BeanHelper and use a custom BeanHelper to attach a model
* based on type.
*
* @return void
*/
public function testCustomBeanHelper()
{
$customBeanHelper = new \SoupBeanHelper( R::getToolbox() );
$oldBeanHelper = R::getRedBean()->getBeanHelper();
asrt( ( $oldBeanHelper instanceof SimpleFacadeBeanHelper ), TRUE );
R::getRedBean()->setBeanHelper( $customBeanHelper );
$meal = R::dispense( 'meal' );
asrt( ( $meal->box() instanceof \Model_Soup ), TRUE );
$cake = R::dispense( 'cake' );
asrt( is_null( $cake->box() ), TRUE );
$bean = R::dispense( 'coffee' );
asrt( ( $bean->box() instanceof \Model_Coffee ), TRUE );
$meal->setFlavour( 'tomato' );
asrt( $meal->getFlavour(), 'tomato' );
$meal->rating = 5;
R::store( $meal );
asrt( $meal->getFlavour(), 'tomato' );
$meal = $meal->unbox();
asrt( $meal->getFlavour(), 'tomato' );
$meal = R::findOne( 'meal' );
asrt( ( $meal->box() instanceof \Model_Soup ), TRUE );
asrt( $meal->getFlavour(), '' );
$meal->setFlavour( 'tomato' );
asrt( $meal->getFlavour(), 'tomato' );
$meal = $meal->unbox();
asrt( $meal->getFlavour(), 'tomato' );
R::getRedBean()->setBeanHelper( $oldBeanHelper );
}
/**
* Test FUSE hooks (i.e. open, update, update_after etc..)
*
* @return void
*/
public function testHooks()
{
R::nuke();
$probe = R::dispense( 'probe' );
$probe->name = 'test';
asrt( $probe->getLogActionCount(), 1 );
asrt( $probe->getLogActionCount( 'dispense' ), 1 );
asrt( $probe->getLogActionCount( 'open' ), 0 );
asrt( $probe->getLogActionCount( 'update' ), 0 );
asrt( $probe->getLogActionCount( 'after_update' ), 0 );
asrt( $probe->getLogActionCount( 'delete' ), 0 );
asrt( $probe->getLogActionCount( 'after_delete' ), 0 );
asrt( ( $probe->getDataFromLog( 0, 'bean' ) === $probe ), TRUE );
R::store( $probe );
asrt( $probe->getLogActionCount(), 3 );
asrt( $probe->getLogActionCount( 'dispense' ), 1 );
asrt( $probe->getLogActionCount( 'open' ), 0 );
asrt( $probe->getLogActionCount( 'update' ), 1 );
asrt( $probe->getLogActionCount( 'after_update' ), 1 );
asrt( $probe->getLogActionCount( 'delete' ), 0 );
asrt( $probe->getLogActionCount( 'after_delete' ), 0 );
asrt( ( $probe->getDataFromLog( 2, 'bean' ) === $probe ), TRUE );
$probe = R::load( 'probe', $probe->id );
asrt( $probe->getLogActionCount(), 2 );
asrt( $probe->getLogActionCount( 'dispense' ), 1 );
asrt( $probe->getLogActionCount( 'open' ), 1 );
asrt( $probe->getLogActionCount( 'update' ), 0 );
asrt( $probe->getLogActionCount( 'after_update' ), 0 );
asrt( $probe->getLogActionCount( 'delete' ), 0 );
asrt( $probe->getLogActionCount( 'after_delete' ), 0 );
asrt( ( $probe->getDataFromLog( 0, 'bean' ) === $probe ), TRUE );
asrt( ( $probe->getDataFromLog( 1, 'id' ) === $probe->id ), TRUE );
$probe->clearLog();
R::trash( $probe );
asrt( $probe->getLogActionCount(), 2 );
asrt( $probe->getLogActionCount( 'dispense' ), 0 );
asrt( $probe->getLogActionCount( 'open' ), 0 );
asrt( $probe->getLogActionCount( 'update' ), 0 );
asrt( $probe->getLogActionCount( 'after_update' ), 0 );
asrt( $probe->getLogActionCount( 'delete' ), 1 );
asrt( $probe->getLogActionCount( 'after_delete' ), 1 );
asrt( ( $probe->getDataFromLog( 0, 'bean' ) === $probe ), TRUE );
asrt( ( $probe->getDataFromLog( 1, 'bean' ) === $probe ), TRUE );
//less 'normal scenarios'
$probe = R::dispense( 'probe' );
$probe->name = 'test';
asrt( $probe->getLogActionCount(), 1 );
asrt( $probe->getLogActionCount( 'dispense' ), 1 );
asrt( $probe->getLogActionCount( 'open' ), 0 );
asrt( $probe->getLogActionCount( 'update' ), 0 );
asrt( $probe->getLogActionCount( 'after_update' ), 0 );
asrt( $probe->getLogActionCount( 'delete' ), 0 );
asrt( $probe->getLogActionCount( 'after_delete' ), 0 );
asrt( ( $probe->getDataFromLog( 0, 'bean' ) === $probe ), TRUE );
R::store( $probe );
asrt( $probe->getLogActionCount(), 3 );
asrt( $probe->getLogActionCount( 'dispense' ), 1 );
asrt( $probe->getLogActionCount( 'open' ), 0 );
asrt( $probe->getLogActionCount( 'update' ), 1 );
asrt( $probe->getLogActionCount( 'after_update' ), 1 );
asrt( $probe->getLogActionCount( 'delete' ), 0 );
asrt( $probe->getLogActionCount( 'after_delete' ), 0 );
asrt( ( $probe->getDataFromLog( 2, 'bean' ) === $probe ), TRUE );
asrt( $probe->getMeta( 'tainted' ), FALSE );
asrt( $probe->getMeta( 'changed' ), FALSE );
R::store( $probe ); //not tainted, no FUSE save!
asrt( $probe->getLogActionCount(), 3 );
asrt( $probe->getLogActionCount( 'dispense' ), 1 );
asrt( $probe->getLogActionCount( 'open' ), 0 );
asrt( $probe->getLogActionCount( 'update' ), 1 );
asrt( $probe->getLogActionCount( 'after_update' ), 1 );
asrt( $probe->getLogActionCount( 'delete' ), 0 );
asrt( $probe->getLogActionCount( 'after_delete' ), 0 );
asrt( ( $probe->getDataFromLog( 2, 'bean' ) === $probe ), TRUE );
$probe->xownProbeList[] = R::dispense( 'probe' );
//tainted, not changed, triggers FUSE
asrt( $probe->getMeta( 'tainted' ), TRUE );
asrt( $probe->getMeta( 'changed' ), FALSE );
R::store( $probe );
asrt( $probe->getMeta( 'tainted' ), FALSE );
asrt( $probe->getMeta( 'changed' ), FALSE );
asrt( $probe->getLogActionCount(), 5 );
asrt( $probe->getLogActionCount( 'dispense' ), 1 );
asrt( $probe->getLogActionCount( 'open' ), 0 );
asrt( $probe->getLogActionCount( 'update' ), 2 );
asrt( $probe->getLogActionCount( 'after_update' ), 2 );
asrt( $probe->getLogActionCount( 'delete' ), 0 );
asrt( $probe->getLogActionCount( 'after_delete' ), 0 );
asrt( ( $probe->getDataFromLog( 2, 'bean' ) === $probe ), TRUE );
}
/**
* Tests the SimpleFacadeBeanHelper factory setter.
*
* @return void
*/
public function testFactory()
{
SimpleFacadeBeanHelper::setFactoryFunction( function( $name ) {
$model = new $name();
$model->setNote( 'injected', 'dependency' );
return $model;
} );
$bean = R::dispense( 'band' )->box();
asrt( ( $bean instanceof \Model_Band ), TRUE );
asrt( ( $bean->getNote('injected') ), 'dependency' );
SimpleFacadeBeanHelper::setFactoryFunction( NULL );
}
/**
* Make sure that beans of type book_page can be fused with
* models like BookPage (beautified) as well as Book_Page (non-beautified).
*/
public function testBeutificationOfLinkModel()
{
$page = R::dispense( 'page' );
$widget = R::dispense( 'widget' );
$page->sharedWidgetList[] = $widget;
R::store( $page );
$testReport = \Model_PageWidget::getTestReport();
asrt( $testReport, 'didSave' );
$page = R::dispense( 'page' );
$gadget = R::dispense( 'gadget' );
$page->sharedGadgetList[] = $gadget;
R::store( $page );
$testReport = \Model_Gadget_Page::getTestReport();
asrt( $testReport, 'didSave' );
}
/**
* Only theoretical.
*
* @return void
*/
public function testTheoreticalBeautifications()
{
$bean = R::dispense('bean');
$bean->setMeta('type', 'a_b_c');
R::store($bean);
$testReport = \Model_A_B_C::getTestReport();
asrt( $testReport, 'didSave' );
}
/**
* Test extraction of toolbox.
*
* @return void
*/
public function testGetExtractedToolBox()
{
$helper = new SimpleFacadeBeanHelper;
list( $redbean, $database, $writer, $toolbox ) = $helper->getExtractedToolbox();
asrt( ( $redbean instanceof OODB ), TRUE );
asrt( ( $database instanceof Adapter ), TRUE );
asrt( ( $writer instanceof QueryWriter ), TRUE );
asrt( ( $toolbox instanceof ToolBox ), TRUE );
}
/**
* Test FUSE and model formatting.
*
* @todo move tagging tests to tag tester.
*
* @return void
*/
public function testFUSE()
{
$toolbox = R::getToolBox();
$adapter = $toolbox->getDatabaseAdapter();
$blog = R::dispense( 'blog' );
$blog->title = 'testing';
$blog->blog = 'tesing';
R::store( $blog );
$blogpost = R::load( "blog", 1 );
$post = R::dispense( "post" );
$post->message = "hello";
$blog->sharedPost[] = $post;
R::store($blog);
$a = R::getAll( "select * from blog " );
R::tag( $post, "lousy,smart" );
asrt( implode( ',', R::tag( $post ) ), "lousy,smart" );
R::tag( $post, "clever,smart" );
$tagz = implode( ',', R::tag( $post ) );
asrt( ( $tagz == "smart,clever" || $tagz == "clever,smart" ), TRUE );
R::tag( $blog, array( "smart", "interesting" ) );
asrt( implode( ',', R::tag( $blog ) ), "smart,interesting" );
try {
R::tag( $blog, array( "smart", "interesting", "lousy!" ) );
pass();
} catch ( RedException $e ) {
fail();
}
asrt( implode( ',', R::tag( $blog ) ), "smart,interesting,lousy!" );
asrt( implode( ",", R::tag( $blog ) ), "smart,interesting,lousy!" );
R::untag( $blog, array( "smart", "interesting" ) );
asrt( implode( ",", R::tag( $blog ) ), "lousy!" );
asrt( R::hasTag( $blog, array( "lousy!" ) ), TRUE );
asrt( R::hasTag( $blog, array( "lousy!", "smart" ) ), TRUE );
asrt( R::hasTag( $blog, array( "lousy!", "smart" ), TRUE ), FALSE );
R::tag( $blog, FALSE );
asrt( count( R::tag( $blog ) ), 0 );
R::tag( $blog, array( "funny", "comic" ) );
asrt( count( R::tag( $blog ) ), 2 );
R::addTags( $blog, array( "halloween" ) );
asrt( count( R::tag( $blog ) ), 3 );
asrt( R::hasTag( $blog, array( "funny", "commic", "halloween" ), TRUE ), FALSE );
R::unTag( $blog, array( "funny" ) );
R::addTags( $blog, "horror" );
asrt( count( R::tag( $blog ) ), 3 );
asrt( R::hasTag( $blog, array( "horror", "commic", "halloween" ), TRUE ), FALSE );
// No double tags
R::addTags( $blog, "horror" );
asrt( R::hasTag( $blog, array( "horror", "commic", "halloween" ), TRUE ), FALSE );
asrt( count( R::tag( $blog ) ), 3 );
}
/**
* Test error handling options FUSE.
*/
public function testModelErrorHandling()
{
$test = R::dispense( 'feed' );
$test->nonExistantMethod();
pass();
$old = R::setErrorHandlingFUSE( OODBBean::C_ERR_LOG );
asrt( is_array( $old ), TRUE );
asrt( count( $old ), 2 );
asrt( $old[0], FALSE );
asrt( $old[1], NULL);
$test->nonExistantMethod(); //we cant really test this... :(
pass();
$old = R::setErrorHandlingFUSE( OODBBean::C_ERR_NOTICE );
asrt( is_array( $old ), TRUE );
asrt( count( $old ), 2 );
asrt( $old[0], OODBBean::C_ERR_LOG );
asrt( $old[1], NULL);
set_error_handler(function($error, $str) {
asrt( $str, 'FUSE: method does not exist in model: nonExistantMethod' );
}, E_USER_NOTICE);
$test->nonExistantMethod();
restore_error_handler();
$old = OODBBean::setErrorHandlingFUSE( OODBBean::C_ERR_WARN );
asrt( is_array( $old ), TRUE );
asrt( count( $old ), 2 );
asrt( $old[0], OODBBean::C_ERR_NOTICE );
asrt( $old[1], NULL);
set_error_handler(function($error, $str) {
asrt( $str, 'FUSE: method does not exist in model: nonExistantMethod' );
}, E_USER_WARNING);
$test->nonExistantMethod();
restore_error_handler();
$old = OODBBean::setErrorHandlingFUSE( OODBBean::C_ERR_FATAL );
asrt( is_array( $old ), TRUE );
asrt( count( $old ), 2 );
asrt( $old[0], OODBBean::C_ERR_WARN );
asrt( $old[1], NULL);
set_error_handler(function($error, $str) {
asrt( $str, 'FUSE: method does not exist in model: nonExistantMethod' );
}, E_USER_ERROR);
$test->nonExistantMethod();
restore_error_handler();
$old = OODBBean::setErrorHandlingFUSE( OODBBean::C_ERR_EXCEPTION );
asrt( is_array( $old ), TRUE );
asrt( count( $old ), 2 );
asrt( $old[0], OODBBean::C_ERR_FATAL );
asrt( $old[1], NULL);
try {
$test->nonExistantMethod();
fail();
} catch (\Exception $e) {
pass();
}
global $test_bean;
$test_bean = $test;
global $has_executed_error_func_fuse;
$has_executed_error_func_fuse = FALSE;
$old = OODBBean::setErrorHandlingFUSE( OODBBean::C_ERR_FUNC, function( $info ){
global $has_executed_error_func_fuse;
global $test_bean;
$has_executed_error_func_fuse = TRUE;
asrt( is_array( $info ), TRUE );
asrt( $info['method'], 'nonExistantMethod' );
asrt( json_encode( $info['bean']->export() ), json_encode( $test_bean->export() ) );
asrt( $info['message'], 'FUSE: method does not exist in model: nonExistantMethod' );
} );
asrt( is_array( $old ), TRUE );
asrt( count( $old ), 2 );
asrt( $old[0], OODBBean::C_ERR_EXCEPTION );
asrt( $old[1], NULL);
$test->nonExistantMethod();
asrt( $has_executed_error_func_fuse, TRUE );
$old = OODBBean::setErrorHandlingFUSE( OODBBean::C_ERR_IGNORE );
asrt( is_array( $old ), TRUE );
asrt( count( $old ), 2 );
asrt( $old[0], OODBBean::C_ERR_FUNC );
asrt( is_callable( $old[1] ), TRUE );
$old = OODBBean::setErrorHandlingFUSE( OODBBean::C_ERR_IGNORE );
asrt( is_array( $old ), TRUE );
asrt( count( $old ), 2 );
asrt( $old[0], OODBBean::C_ERR_IGNORE );
asrt( $old[1], NULL);
try {
OODBBean::setErrorHandlingFUSE( 900 );
fail();
} catch (\Exception $e) {
pass();
asrt( $e->getMessage(), 'Invalid error mode selected' );
}
try {
OODBBean::setErrorHandlingFUSE( OODBBean::C_ERR_FUNC, 'hello' );
fail();
} catch (\Exception $e) {
pass();
asrt( $e->getMessage(), 'Invalid error handler' );
}
OODBBean::setErrorHandlingFUSE( OODBBean::C_ERR_EXCEPTION );
//make sure ignore FUSE events
$test = R::dispense('feed');
R::store( $test );
$test = $test->fresh();
R::trash( $test );
pass();
OODBBean::setErrorHandlingFUSE( OODBBean::C_ERR_IGNORE );
}
}

View File

@@ -0,0 +1,175 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
/**
* Hybrid
*
* Test Hybrid mode where the database can be unfozen
* in case of an exception during storing.
*
* @file RedUNIT/Base/Hybrid.php
* @desc Tests hybrid mode
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Hybrid extends Base
{
/**
* Tests hybrid mode.
*
* @return void
*/
public function testHybrid()
{
R::nuke();
$book = R::dispense('book');
$book->pages = 123;
$id = R::store( $book );
R::freeze( TRUE );
R::setAllowHybridMode( FALSE );
$book->title = 'Tales of a misfit';
try {
R::store( $book, TRUE );
fail();
} catch(\Exception $e) {
pass();
}
try {
R::store( $book, FALSE );
fail();
} catch(\Exception $e) {
pass();
}
$book = $book->fresh();
asrt( is_null( $book->title ), TRUE );
R::setAllowHybridMode( TRUE );
$book->title = 'Tales of a misfit';
try {
R::store( $book );
fail();
} catch(\Exception $e) {
pass();
}
try {
R::store( $book, FALSE );
fail();
} catch(\Exception $e) {
pass();
}
try {
R::store( $book, TRUE );
pass();
} catch(\Exception $e) {
fail();
}
$book = $book->fresh();
asrt( $book->title, 'Tales of a misfit' );
R::setAllowHybridMode( FALSE );
R::freeze( FALSE );
}
/**
* Test whether we can use Hybrid mode to alter columns.
* This won't work for SQLite.
*/
public function testHybridDataType()
{
R::nuke();
if ($this->currentlyActiveDriverID == 'mysql') {
R::exec('SET @@SESSION.sql_mode=\'STRICT_TRANS_TABLES\';');
}
if ($this->currentlyActiveDriverID == 'sqlite') return;
$book = R::dispense('book');
$book->pages = 1;
$id = R::store( $book, TRUE );
R::freeze( TRUE );
asrt( R::getRedBean()->isFrozen(), TRUE );
R::setAllowHybridMode( FALSE );
$book->pages = 'too many';
try {
R::store( $book, TRUE );
fail();
} catch(\Exception $e) {
pass();
}
asrt( R::getRedBean()->isFrozen(), TRUE );
R::setAllowHybridMode( TRUE );
asrt( R::getRedBean()->isFrozen(), TRUE );
R::debug(1);
try {
R::store( $book, TRUE );
pass();
} catch(\Exception $e) {
fail();
}
asrt( R::getRedBean()->isFrozen(), TRUE );
$book = $book->fresh();
echo $book;
asrt( $book->pages, 'too many' );
R::setAllowHybridMode( FALSE );
R::freeze( FALSE );
if ($this->currentlyActiveDriverID == 'mysql') {
R::exec('SET @@SESSION.sql_mode=\'\';');
}
}
/**
* Other exceptions must fall through...
*/
public function testHybridNonSQLException()
{
R::nuke();
$toy = R::dispense('brokentoy');
R::freeze( TRUE );
R::setAllowHybridMode( TRUE );
try {
R::store( $toy, TRUE );
fail();
} catch(\Exception $e) {
pass();
}
R::setAllowHybridMode( FALSE );
R::nuke();
$toy = R::dispense('toy');
R::freeze( TRUE );
R::setAllowHybridMode( TRUE );
try {
R::store( $toy, TRUE );
pass();
} catch(\Exception $e) {
fail();
}
R::setAllowHybridMode( FALSE );
}
/**
* Test whether Hybrid mode is only activated
* for latest or 5.4 without novice and ensure
* maintaining backward compatibility by not setting
* Hybrid allowed for 5.3 and earlier.
*/
public function testVersions()
{
R::useFeatureSet('novice/latest');
asrt( R::setAllowHybridMode( FALSE ), FALSE );
R::useFeatureSet('latest');
asrt( R::setAllowHybridMode( FALSE ), TRUE );
R::useFeatureSet('novice/5.4');
asrt( R::setAllowHybridMode( FALSE ), FALSE );
R::useFeatureSet('5.4');
asrt( R::setAllowHybridMode( FALSE ), TRUE );
R::useFeatureSet('novice/5.3');
asrt( R::setAllowHybridMode( FALSE ), FALSE );
R::useFeatureSet('5.3');
asrt( R::setAllowHybridMode( FALSE ), FALSE );
}
}

View File

@@ -0,0 +1,136 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
/**
* Indexes
*
* Tests whether indexes are created properly and whether
* index creation errors are supressed (they are not important
* enough to cause exceptions during development phase).
*
* @file RedUNIT/Base/Indexes.php
* @desc Tests whether indexes are applied properly.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Indexes extends Base {
/**
* Tests whether a regular index is created properly.
*
* @return void
*/
public function testIndexCreation()
{
R::nuke();
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$book->ownPageList[] = $page;
R::store( $book );
$indexes = getIndexes( 'page' );
asrt( in_array( 'index_foreignkey_page_book', $indexes ), TRUE );
}
/**
* Tests indexes on parent beans.
*
* @return void
*/
public function testIndexCreationParentBean()
{
R::nuke();
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$page->book = $book;
R::store( $page );
$indexes = getIndexes( 'page' );
asrt( in_array( 'index_foreignkey_page_book', $indexes ), TRUE );
}
/**
* Tests indexes on link tables.
*
* @return void
*/
public function testIndexCreationMany2Many()
{
R::nuke();
$book = R::dispense( 'book' );
$category = R::dispense( 'category' );
$book->sharedCategoryList[] = $category;
R::store( $book );
$indexes = getIndexes( 'book_category' );
asrt( in_array( 'index_foreignkey_book_category_book', $indexes ), TRUE );
asrt( in_array( 'index_foreignkey_book_category_category', $indexes ), TRUE );
R::nuke();
R::nuke();
$book = R::dispense( 'book' );
$category = R::dispense( 'category' );
$category->sharedBookList[] = $book;
R::store( $category );
$indexes = getIndexes( 'book_category' );
asrt( in_array( 'index_foreignkey_book_category_book', $indexes ), TRUE );
asrt( in_array( 'index_foreignkey_book_category_category', $indexes ), TRUE );
}
/**
* Tests indexes on aliases.
*
* @return void
*/
public function testIndexCreationAlias()
{
R::nuke();
$book = R::dispense( 'book' );
$author = R::dispense( 'author' );
$book->coAuthor = $author;
R::store( $book );
$indexes = getIndexes( 'book' );
asrt( in_array( 'index_foreignkey_book_co_author', $indexes ), TRUE );
R::nuke();
$project = R::dispense( 'project' );
$person = R::dispense( 'person' );
$person->alias( 'teacher' )->ownProject[] = $project;
$person2 = R::dispense( 'person' );
$person2->alias( 'student' )->ownProject[] = $project;
R::store( $person );
$indexes = getIndexes( 'project' );
asrt( in_array( 'index_foreignkey_project_teacher', $indexes ), TRUE );
R::store( $person2 );
$indexes = getIndexes( 'project' );
asrt( in_array( 'index_foreignkey_project_student', $indexes ), TRUE );
}
/**
* Tests index fails.
*
* @return void
*/
public function testIndexCreationFail()
{
R::nuke();
$book = R::dispense( 'book' );
$book->author_id = '999';
R::store( $book );
$indexes = getIndexes( 'book' );
//should just work fine
asrt( in_array( 'index_foreignkey_book_author', $indexes ), TRUE );
//these should just pass, no indexes but no errors as well
R::getWriter()->addIndex( 'book', 'bla', 'nonexist' );
pass();
R::getWriter()->addIndex( 'book', '@#$', 'nonexist' );
pass();
R::getWriter()->addIndex( 'nonexist', 'bla', 'nonexist' );
pass();
$indexesAfter = getIndexes( 'book' );
asrt( count( $indexesAfter ), count( $indexes ) );
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\SimpleModel as SimpleModel;
/**
* Issue259
*
* Tests whether this specific issue on github has been resolved.
* Issue #259 - Stash Cache breaks model delegation in open().
*
* @file RedUNIT/Base/Issue259.php
* @desc Issue #259 - Stash cache breaks model delegation in open().
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Issue259 extends Base
{
/**
* Test to make sure stash cache works with recursively opening models
* with FUSE.
*
* @return void
*/
public function testIssue259()
{
testpack( 'Testing Issue #259 - Stash Cache breaks model delegation in open().' );
$mother = R::dispense( 'mother' );
$mother->desc = 'I am mother';
R::store( $mother );
$child = R::dispense( 'child' );
$child->mother = $mother;
$child->desc = 'I am child';
$id = R::store( $child );
R::findOne( 'child', ' id = ?', array( $id ) );
R::find( 'child', ' id = ? ', array( $id ) );
R::load( 'child', $id );
}
}
/**
* Mock Model.
*/
class Model_Mother extends SimpleModel
{
public function open()
{
$bean = $this->bean;
asrt( $this->bean->desc, 'I am mother' );
}
}
/**
* Mock Model.
*/
class Model_Child extends SimpleModel
{
public function open()
{
$this->bean->mother;
asrt( $this->bean->desc, 'I am child' );
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException as RedException;
/**
* Issue303
*
* Tests whether this specific issue on github has been resolved.
* Test whether we have two different exception messages for
* properties and values.
*
* @file RedUNIT/Base/Issue303.php
* @desc Issue #303 - Split bean property exception.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Issue303 extends Base
{
/**
* Test whether we have two different exception messages for
* properties and values.
*
* @return void
*/
public function testIssue303()
{
testpack( 'Testing Issue #303 - Test splitting bean exception property/value.' );
try {
R::store( R::dispense( 'invalidbean' )->setAttr( 'invalid.property', 'value' ) );
fail();
} catch (RedException $e ) {
asrt( $e->getMessage(), 'Invalid Bean property: property invalid.property' );
}
try {
R::store( R::dispense( 'invalidbean' )->setAttr( 'property', array() ) );
fail();
} catch (RedException $e ) {
asrt( $e->getMessage(), 'Invalid Bean value: property property' );
}
try {
R::store( R::dispense( 'invalidbean' )->setAttr( 'property', new \stdClass ) );
fail();
} catch (RedException $e ) {
asrt( $e->getMessage(), 'Invalid Bean value: property property' );
}
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
/**
* Issue 408
*
* Tests whether this specific issue on github has been resolved.
* Tests whether we can use export on beans having arrays in properties.
*
* @file RedUNIT/Mysql/Issue408.php
* @desc Test whether we can export beans with arrays in properties
* (deserialized/serialized on open/update).
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Issue408 extends Base
{
/**
* In the past it was not possible to export beans
* like 'feed' (Model_Feed).
*
* @return void
*/
public function testExportIssue()
{
R::nuke();
$feed = R::dispense( 'feed' );
$feed->post = array(
'first',
'second'
);
R::store( $feed );
$rows = R::getAll('SELECT * FROM feed');
asrt( $rows[0]['post'], '["first","second"]' );
$feed = $feed->fresh();
asrt( is_array( $feed->post ), TRUE );
asrt( $feed->post[0], 'first' );
asrt( $feed->post[1], 'second' );
R::store( $feed );
$rows = R::getAll('SELECT * FROM feed');
asrt( $rows[0]['post'], '["first","second"]' );
$feed = R::load( 'feed', $feed->id );
$feed->post[] = 'third';
R::store( $feed );
$rows = R::getAll('SELECT * FROM feed');
asrt( $rows[0]['post'], '["first","second","third"]' );
$feed = $feed->fresh();
asrt( is_array( $feed->post ), TRUE );
asrt( $feed->post[0], 'first' );
asrt( $feed->post[1], 'second' );
asrt( $feed->post[2], 'third' );
//now the catch: can we use export?
//PHP Fatal error: Call to a member function export() on a non-object
$feeds = R::exportAll( R::find( 'feed' ) );
asrt( is_array( $feeds ), TRUE );
$feed = reset( $feeds );
asrt( $feed['post'][0], 'first' );
asrt( $feed['post'][1], 'second' );
asrt( $feed['post'][2], 'third' );
//can we also dup()?
$feedOne = R::findOne( 'feed' );
R::store( R::dup( $feedOne ) );
asrt( R::count( 'feed' ), 2 );
//can we delete?
R::trash( $feedOne );
asrt( R::count( 'feed' ), 1 );
$feedTwo = R::findOne( 'feed' );
$feed = $feedTwo->export();
asrt( $feed['post'][0], 'first' );
asrt( $feed['post'][1], 'second' );
asrt( $feed['post'][2], 'third' );
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
/**
* Issue 841
*
* After bindFunc() Shared List cannot remove items.
*
* @file RedUNIT/Base/Issue841.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Issue841 extends Base
{
/**
* After bindFunc() Shared List cannot remove items.
*
* @return void
*/
public function testIssue841()
{
R::nuke();
R::bindFunc( 'read', 'record.point', 'abs' );
R::bindFunc( 'write', 'record.point', 'abs' );
for($i = 0;$i < 3;$i++){
$tag = R::dispense('tag');
$tag->name = 'TAG_'.$i;
R::store($tag);
}
$record = R::dispense('record');
$record->point = rand(-100,-1);
$record->sharedTagList[] = R::load('tag',2);
R::store($record);
asrt(count($record->sharedTagList),1);
$record = R::load('record',1);
$record->sharedTagList = array();
R::store($record);
$record = R::load('record',1);
asrt(count($record->sharedTagList),0);
R::bindFunc( 'read', 'record.point', null );
R::bindFunc( 'write', 'record.point', null );
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
/**
* Issue90
*
* Tests whether this specific issue on github has been resolved.
* Checking 'own' relationship, makes it impossible to trash a bean.
*
* @file RedUNIT/Base/Issue90.php
* @desc Issue #90 - cannot trash bean with ownproperty if checked in model.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Issue90 extends Base
{
/**
* Test for issue90.
* Checking 'own' relationship, makes it impossible to trash a bean.
*
* @return void
*/
public function testIssue90()
{
$s = R::dispense( 'box' );
$s->name = 'a';
$f = R::dispense( 'bottle' );
$s->ownBottle[] = $f;
R::store( $s );
$s2 = R::dispense( 'box' );
$s2->name = 'a';
R::store( $s2 );
R::trash( $s2 );
pass();
}
}

View File

@@ -0,0 +1,988 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Joins
*
* Tests the @joined keyword, this keyword in an SQL snippet
* allows you to join another table and use one or more of its columns
* in the query snippet, for instance for sorting or filtering.
*
* @file RedUNIT/Base/Joins.php
* @desc Tests joins in ownLists and trees.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Joins extends Base
{
private function quickTestBeans( $beans, $property ) {
$list = array();
foreach( $beans as $bean ) {
$list[] = $bean->{$property};
}
sort($list);
return implode(',', $list);
}
/**
* Test whether we can use [via] notation in parsed joins.
*
* @return void
*/
public function testParsedJoinsWithVia()
{
$project1 = R::dispense('project');
$project2 = R::dispense('project');
$project1->title = 'project1';
$project2->title = 'project2';
$employee1 = R::dispense('employee');
$employee2 = R::dispense('employee');
$employee1->name = 'a';
$employee2->name = 'b';
$participant1 = R::dispense('participant');
$participant2 = R::dispense('participant');
$participant3 = R::dispense('participant');
$participant1->employee = $employee1;
$participant2->employee = $employee1;
$participant3->employee = $employee2;
$participant1->project = $project1;
$participant2->project = $project2;
$participant3->project = $project2;
R::storeAll(array($participant1,$participant2,$participant3));
$projects = R::find('project', ' @shared.employee[via:participant].name LIKE ? ', array('a'));
asrt(count($projects),2);
$projects = R::find('project', ' @shared.employee[via:participant].name LIKE ? AND project.title = ? ', array('a','project1'));
asrt(count($projects),1);
$projects = R::find('project', ' @shared.employee[via:participant].name LIKE ? AND project.title = ? ', array('a','project2'));
asrt(count($projects),1);
$projects = R::find('project', ' @shared.employee[via:participant].name LIKE ? ', array('b'));
asrt(count($projects),1);
$projects = R::find('project', ' @shared.employee[via:participant].name LIKE ? AND project.title = ? ', array('b','project1'));
asrt(count($projects),0);
$projects = R::find('project', ' @shared.employee[via:participant].name LIKE ? AND project.title = ? ', array('b','project2'));
asrt(count($projects),1);
/* shouldnt work if no [via:...] and no global aliases */
$projects = R::find('project', ' @shared.participant.name LIKE ? ', array('a'));
asrt(count($projects),0);
$projects = R::find('project', ' @shared.participant.name LIKE ? AND project.title = ? ', array('a','project1'));
asrt(count($projects),0);
$projects = R::find('project', ' @shared.participant.name LIKE ? AND project.title = ? ', array('a','project2'));
asrt(count($projects),0);
$projects = R::find('project', ' @shared.participant.name LIKE ? ', array('b'));
asrt(count($projects),0);
$projects = R::find('project', ' @shared.participant.name LIKE ? AND project.title = ? ', array('b','project1'));
asrt(count($projects),0);
$projects = R::find('project', ' @shared.participant.name LIKE ? AND project.title = ? ', array('b','project2'));
asrt(count($projects),0);
R::aliases(array('work'=>'project'));
$company1 = R::dispense('company');
$company2 = R::dispense('company');
$company1->work = $project1;
$company2->work = $project2;
R::storeAll( array( $company1, $company2 ) );
$companies = R::find('company', ' @joined.work.shared.employee[via:participant].name LIKE ? ', array('a'));
asrt(count($companies),2);
$companies = R::find('company', ' @joined.work.shared.employee[via:participant].name LIKE ? AND @joined.work.title = ? ', array('a','project1'));
asrt(count($companies),1);
$companies = R::find('company', ' @joined.work.shared.employee[via:participant].name LIKE ? AND @joined.work.title = ? ', array('a','project2'));
asrt(count($companies),1);
$companies = R::find('company', ' @joined.work.shared.employee[via:participant].name LIKE ? ', array('b'));
asrt(count($companies),1);
$companies = R::find('company', ' @joined.work.shared.employee[via:participant].name LIKE ? AND @joined.work.title = ?', array('b','project1'));
asrt(count($companies),0);
$companies = R::find('company', ' @joined.work.shared.employee[via:participant].name LIKE ? AND @joined.work.title = ? ', array('b','project2'));
asrt(count($companies),1);
}
/**
* Can we use joins with aliases?
* Can we perform bean->alias->ownlist and bean->fetchas->other
* like operation in SQL as well?
*
* In this test:
* Albert: Almanac, Study Guide
* Bob: Poetry Bundle, Study Guide (coauthor)
*
* Texts:
* Example -> in Study Guide (as source)
* Essay -> in Study Guide (as book)
* Poem -> in Poetry Bundle (as book)
* Poem -> also in Almanac (as magazine)
*
* @return void
*/
public function testParsedJoinsWithAliasing()
{
R::nuke();
R::aliases(array());
$albert = R::dispense('person');
$bob = R::dispense('person');
$guide = R::dispense('book');
$almanac= R::dispense('book');
$poetry = R::dispense('book');
$albert->firstname = 'Albert';
$bob->firstname = 'Bob';
$guide->title = 'Study Guide';
$almanac->title = 'Almanac';
$poetry->title = 'Poems';
$guide->author = $albert;
$guide->coauthor = $bob;
$almanac->author = $albert;
$poetry->author = $bob;
$poem = R::dispense('text');
$essay = R::dispense('text');
$example = R::dispense('text');
$poem->content = 'poem';
$essay->content = 'essay';
$example->content = 'example';
$poem->magazine = $almanac;
$poem->book = $poetry;
$essay->book = $guide;
$example->source = $guide;
$fiction = R::dispense('tag');
$nonfiction = R::dispense('tag');
$fiction->description = 'fiction';
$nonfiction->description = 'non-fiction';
$example->sharedTag[] = $nonfiction;
$poem->sharedTag[] = $fiction;
$essay->sharedTag = array( $nonfiction, $fiction );
R::storeAll( array( $poem, $essay, $example ) );
$books = R::find('book', ' @joined.author.firstname = ? ', array('Bob'));
asrt(count($books), 0);
$books = R::find('book', ' @joined.author.firstname = ? ', array('Albert'));
asrt(count($books), 0);
$books = R::find('book', ' @joined.person[as:author].firstname = ? ', array('Bob'));
asrt(count($books), 1);
$books = R::find('book', ' @joined.person[as:author].firstname = ? ', array('Albert'));
asrt(count($books), 2);
R::freeze( TRUE );
try { $books = R::find('book', ' @joined.author.firstname = ? ', array('Bob')); fail(); } catch ( \Exception $e ) { pass(); }
try { $books = R::find('book', ' @joined.author.firstname = ? ', array('Albert')); fail(); } catch ( \Exception $e ) {pass();}
$books = R::find('book', ' @joined.person[as:author].firstname = ? ', array('Bob'));
asrt(count($books), 1);
$books = R::find('book', ' @joined.person[as:author].firstname = ? ', array('Albert'));
asrt(count($books), 2);
R::aliases(array('author' => 'person','coauthor' => 'person'));
$books = R::find('book', ' @joined.author.firstname = ? ', array('Bob'));
asrt(count($books), 1);
$books = R::find('book', ' @joined.author.firstname = ? ', array('Albert'));
asrt(count($books), 2);
$books = R::find('book', ' @joined.person[as:author].firstname = ? ', array('Bob'));
asrt(count($books), 1);
$books = R::find('book', ' @joined.person[as:author].firstname = ? ', array('Albert'));
asrt(count($books), 2);
R::freeze( FALSE );
R::aliases(array());
//If we want to find all the people who authored books like X
$authors = R::find( 'person', ' @own.book[author].title LIKE ? ', array( '%Study%' ) );
asrt( count($authors), 1 );
$authors = R::find( 'person', ' @own.book[alias:author].title LIKE ? ', array( '%Study%' ) );
asrt( count($authors), 1 );
//yields Almanac and Poems
$authors = R::find( 'person', ' @own.book[author].title LIKE ? ', array( '%m%' ) );
asrt( count($authors), 2 );
//yields Almanac and Poems
$authors = R::find( 'person', ' @own.book[alias:author].title LIKE ? ', array( '%m%' ) );
asrt( count($authors), 2 );
//yields nothing
$authors = R::find( 'person', ' @own.book[author].title LIKE ? ', array( '%x%' ) );
asrt( count($authors), 0 );
//yields nothing
$authors = R::find( 'person', ' @own.book[alias:author].title LIKE ? ', array( '%x%' ) );
asrt( count($authors), 0 );
//If we want to find all the people who authored books starting with X
$authors = R::find( 'person', ' @own.book[coauthor].title LIKE ? ', array( '%Study%' ) );
asrt( count($authors), 1 );
R::freeze( TRUE );
$authors = R::find( 'person', ' @own.book[author].title LIKE ? ', array( '%Study%' ) );
asrt( count($authors), 1 );
$authors = R::find( 'person', ' @own.book[alias:author].title LIKE ? ', array( '%Study%' ) );
asrt( count($authors), 1 );
//yields Almanac and Poems
$authors = R::find( 'person', ' @own.book[author].title LIKE ? ', array( '%m%' ) );
asrt( count($authors), 2 );
//yields Almanac and Poems
$authors = R::find( 'person', ' @own.book[alias:author].title LIKE ? ', array( '%m%' ) );
asrt( count($authors), 2 );
asrt( $this->quickTestBeans( $authors, 'firstname' ), 'Albert,Bob');
//yields nothing
$authors = R::find( 'person', ' @own.book[author].title LIKE ? ', array( '%x%' ) );
asrt( count($authors), 0 );
//yields nothing
$authors = R::find( 'person', ' @own.book[alias:author].title LIKE ? ', array( '%x%' ) );
asrt( count($authors), 0 );
$authors = R::find( 'person', ' @own.book[coauthor].title LIKE ? ', array( '%Study%' ) );
asrt( count($authors), 1 );
asrt( $this->quickTestBeans( $authors, 'firstname' ), 'Bob');
R::freeze(FALSE);
$books = R::find( 'book', ' @joined.person[author/coauthor].firstname = ?', array( 'Bob' ) );
asrt( count($books), 2 );
asrt( $this->quickTestBeans( $books, 'title' ), 'Poems,Study Guide');
//If we want all books where the author or the coauthor is named 'Bob':
$books = R::find( 'book', ' @joined.person[as:author/coauthor].firstname = ?', array( 'Bob' ) );
asrt( count($books), 2 );
asrt( $this->quickTestBeans( $books, 'title' ), 'Poems,Study Guide');
//If we want all books where the author or the coauthor is named 'Albert':
$books = R::find( 'book', ' @joined.person[as:author/].firstname = ?', array( 'Albert' ) );
asrt( count($books), 2 );
asrt( $this->quickTestBeans( $books, 'title' ), 'Almanac,Study Guide');
$books = R::find( 'book', ' @joined.person[as:coauthor/author].firstname = ?', array( 'Albert' ) );
asrt( count($books), 2 );
$authors = R::find( 'person', ' @own.book[alias:author/coauthor].title LIKE ?', array( '%Study%' ) );
asrt( count($authors), 2 );
$authors = R::find( 'person', ' @own.book[alias:author/coauthor].title LIKE ?', array( '%Poem%' ) );
asrt( count($authors), 1 );
asrt( $this->quickTestBeans( $authors, 'firstname' ), 'Bob');
$authors = R::find( 'person', ' @own.book[alias:author/coauthor].title LIKE ?', array( '%Alm%' ) );
asrt( $this->quickTestBeans( $authors, 'firstname' ), 'Albert');
asrt( count($authors), 1 );
R::freeze(TRUE);
$books = R::find( 'book', ' @joined.person[author/coauthor].firstname = ?', array( 'Bob' ) );
asrt( count($books), 2 );
$books = R::find( 'book', ' @joined.person[as:author/coauthor].firstname = ?', array( 'Bob' ) );
asrt( count($books), 2 );
$books = R::find( 'book', ' @joined.person[as:author/coauthor].firstname = ?', array( 'Albert' ) );
asrt( count($books), 2 );
$books = R::find( 'book', ' @joined.person[as:author/coauthor].firstname = ?', array( 'Albert' ) );
asrt( count($books), 2 );
$authors = R::find( 'person', ' @own.book[alias:author/coauthor].title LIKE ?', array( '%Study%' ) );
asrt( count($authors), 2 );
$authors = R::find( 'person', ' @own.book[alias:author/coauthor].title LIKE ?', array( '%Poem%' ) );
asrt( count($authors), 1 );
$authors = R::find( 'person', ' @own.book[alias:author/coauthor].title LIKE ?', array( '%Alm%' ) );
asrt( count($authors), 1 );
R::freeze(FALSE);
//2 people as author/coauthor have written a book (study guide) that contains the essay
$authors = R::find( 'person', ' @own.book[alias:author/coauthor].own.text.content = ?', array( 'essay' ) );
asrt( count($authors), 2 );
//2 people as author/coauthor have written a book as source that contains the example
$authors = R::find( 'person', ' @own.book[alias:author/coauthor].own.text[alias:source].content = ?', array( 'example' ) );
asrt( count($authors), 2 );
//1 person as author/coauthor has written a book as magazine/source that contains the poem
$authors = R::find( 'person', ' @own.book[alias:author/coauthor].own.text[alias:source/magazine].content = ?', array( 'poem' ) );
asrt( count($authors), 1 );
//If we include book, we get 2 authors because the poem is also in the poetry bundle (book)
$authors = R::find( 'person', ' @own.book[alias:author/coauthor].own.text[alias:book/source/magazine].content = ?', array( 'poem' ) );
asrt( count($authors), 2 );
R::freeze(TRUE);
//2 people as author/coauthor have written a book (study guide) that contains the essay
$authors = R::find( 'person', ' @own.book[alias:author/coauthor].own.text.content = ?', array( 'essay' ) );
asrt( count($authors), 2 );
//2 people as author/coauthor have written a book as source that contains the example
$authors = R::find( 'person', ' @own.book[alias:author/coauthor].own.text[alias:source].content = ?', array( 'example' ) );
asrt( count($authors), 2 );
//1 person as author/coauthor has written a book as magazine/source that contains the poem
$authors = R::find( 'person', ' @own.book[alias:author/coauthor].own.text[alias:source/magazine].content = ?', array( 'poem' ) );
asrt( count($authors), 1 );
//If we include book, we get 2 authors because the poem is also in the poetry bundle (book)
$authors = R::find( 'person', ' @own.book[alias:author/coauthor].own.text[alias:book/source/magazine].content = ?', array( 'poem' ) );
asrt( count($authors), 2 );
R::freeze(FALSE);
//Get all texts in books authored by Bob
$texts = R::find('text', ' @joined.book.joined.person[as:author].firstname = ? ',array('Bob'));
asrt( count($texts), 1 );
//Get all texts in books authored by Bob as author/coauthor
$texts = R::find('text', ' @joined.book.joined.person[as:author/coauthor].firstname = ? ',array('Bob'));
asrt( count($texts), 2 );
//Get all texts in books as magazines or sources authored by Bob as author/coauthor
$texts = R::find('text', ' @joined.book[as:magazine/source].joined.person[as:author/coauthor].firstname = ? ',array('Bob'));
asrt( count($texts), 1 );
//Get all texts in books as magazines or sources or books authored by Bob as author/coauthor
$texts = R::find('text', ' @joined.book[as:magazine/source/book].joined.person[as:author/coauthor].firstname = ? ',array('Bob'));
asrt( count($texts), 3 );
//Get all texts in books as magazines or sources or books authored by Albert as author/coauthor
$texts = R::find('text', ' @joined.book[as:magazine/source/book].joined.person[as:author/coauthor].firstname = ? ',array('Albert'));
asrt( count($texts), 3 );
R::freeze(TRUE);
//Get all texts in books authored by Bob
$texts = R::find('text', ' @joined.book.joined.person[as:author].firstname = ? ',array('Bob'));
asrt( count($texts), 1 );
//Get all texts in books authored by Bob as author/coauthor
$texts = R::find('text', ' @joined.book.joined.person[as:author/coauthor].firstname = ? ',array('Bob'));
asrt( count($texts), 2 );
//Get all texts in books as magazines or sources authored by Bob as author/coauthor
$texts = R::find('text', ' @joined.book[as:magazine/source].joined.person[as:author/coauthor].firstname = ? ',array('Bob'));
asrt( count($texts), 1 );
//Get all texts in books as magazines or sources or books authored by Bob as author/coauthor
$texts = R::find('text', ' @joined.book[as:magazine/source/book].joined.person[as:author/coauthor].firstname = ? ',array('Bob'));
asrt( count($texts), 3 );
//Get all texts in books as magazines or sources or books authored by Albert as author/coauthor
$texts = R::find('text', ' @joined.book[as:magazine/source/book].joined.person[as:author/coauthor].firstname = ? ',array('Albert'));
asrt( count($texts), 3 );
R::freeze(FALSE);
//Get all texts in books as magazines or sources or books authored by Albert as author/coauthor that have been tagged
//as non-fiction, i.e. the example text and the essay.
$texts = R::find('text', '
@joined.book[as:magazine/source/book].joined.person[as:author/coauthor].firstname = ?
AND
@shared.tag.description = ?
',array('Albert', 'non-fiction'));
asrt( count($texts), 2 );
//Get all texts in books or sources or books authored by Albert as author/coauthor that have been tagged
//as non-fiction, i.e. nothing, only the essay
$texts = R::find('text', '
@joined.book[as:source/book].joined.person[as:author/coauthor].firstname = ?
AND
@shared.tag.description = ?
',array('Albert', 'fiction'));
asrt( count($texts), 1 );
}
/**
* Non-join keywords starting with @ should be
* left untouched.
*/
public function testNonJoinsLeftUntouched()
{
$writer = R::getWriter();
$types = array( 'book', 'cafe', 'bean' );
$sqls = array(
'@version',
'@oined.satire',
'@oined.satire.laugh',
'@hared.lookalike.title',
'@powned.by.a.hacker',
'nothing here!',
'shared.person.name',
'owned.thing.name',
'joined.thing.name',
'shared.person.shared.tag.name',
'owned.thing.shared.tag.name',
'joined.thing.shared.tag.name',
'shared.person.joined.tag.name',
'owned.thing.joined.tag.name',
'joined.thing.joined.tag.name',
'shared.person.owned.tag.name',
'owned.thing.owned.tag.name',
'joined.thing.owned.tag.name'
);
$ctes = array( TRUE, FALSE );
foreach($types as $type) {
foreach($sqls as $sql) {
foreach($ctes as $cte) {
$same = $writer->parseJoin( $type, $sql, $cte );
asrt( trim($same), trim($sql) );
}
}
}
}
/**
* Can we join multiple tables with the same parent?
*
* @return void
*/
public function testMultipleJoinsSameParent()
{
R::nuke();
$TheHagueNorthEnd = R::dispense(array(
'_type' => 'painting',
'title' => 'Northend, The Hague',
'artist' => array(
'_type' => 'artist',
'name' => 'Isaac Israels',
'sharedCategory' => array(
array( '_type' => 'category', 'label' => 'Haagse School' )
)
),
'ownDetail' => array(
array(
'_type' => 'detail',
'description' => 'awnings',
'sharedCategory' => array(
array( '_type' => 'category', 'label' => 'object' )
)
),
array(
'_type' => 'detail',
'description' => 'sunlight',
'sharedCategory' => array(
array( '_type' => 'category', 'label' => 'daytime' )
)
),
)
));
$Nighthawks = R::dispense(array(
'_type' => 'painting',
'title' => 'Nighthawks',
'artist' => array(
'_type' => 'artist',
'name' => 'Hopper',
'sharedCategory' => array(
array( '_type' => 'category', 'label' => 'American Realism' )
)
),
'ownDetail' => array(
array(
'_type' => 'detail',
'description' => 'percolator',
'sharedCategory' => array(
array( '_type' => 'category', 'label' => 'object' )
)
),
array(
'_type' => 'detail',
'description' => 'cigarette',
'sharedCategory' => array(
array( '_type' => 'category', 'label' => 'object' )
)
),
array(
'_type' => 'detail',
'description' => 'night',
'sharedCategory' => array(
array( '_type' => 'category', 'label' => 'nocturnal' )
)
)
)
));
R::storeAll( array( $Nighthawks, $TheHagueNorthEnd ) );
$paintings = R::find('painting', '
@joined.artist.shared.category.label = ?
OR
@own.detail.shared.category.label= ?
', array('American Realism', 'nocturnal'));
asrt(count($paintings),1);
$painting = reset($paintings);
asrt($painting->title, 'Nighthawks');
$paintings = R::find('painting', '
@joined.artist.shared.category.label = ?
OR
@own.detail.shared.category.label= ?
', array('Haagse School', 'daytime'));
asrt(count($paintings),1);
$painting = reset($paintings);
asrt($painting->title, 'Northend, The Hague');
$paintings = R::find('painting', '
@own.detail.shared.category.label= ?
', array('object'));
asrt(count($paintings),2);
$paintings = R::find('painting', '
@own.detail.shared.category.label= ?
AND @own.detail.description= ?
', array('object', 'percolator'));
asrt(count($paintings),1);
$painting = reset($paintings);
asrt($painting->title, 'Nighthawks');
$paintings = R::find('painting', '
@own.detail.shared.category.label= ?
AND @own.detail.description= ?
', array('object', 'ashtray'));
asrt(count($paintings),0);
}
/**
* Complex tests for the parsed joins featured.
*
* @return void
*/
public function testComplexParsedJoins()
{
R::nuke();
$other = R::dispense('book');
R::store( $other );
$book = R::dispense('book');
$page = R::dispense('page');
$paragraph = R::dispense('paragraph');
$paragraph->title = 'hello';
$book->title = 'book';
$book->ownPage[] = $page;
$page->ownParagraph[] = $paragraph;
$figure = R::dispense('figure');
$chart = R::dispense('chart');
$chart->title = 'results';
$page->ownFigure[] = $figure;
$figure->ownChart[] = $chart;
R::store($book);
$books = R::find('book',' @own.page.own.paragraph.title = ? OR @own.page.own.figure.own.chart.title = ?', array('hello','results'));
asrt(count($books),1);
$book = reset($books);
asrt($book->title, 'book');
R::nuke();
R::aliases(array( 'author' => 'person' ));
$book = R::dispense('book');
$author = R::dispense('person');
$detail = R::dispense('detail');
$shop = R::dispense('shop');
$shop->name = 'Books4you';
$shop2 = R::dispense('shop');
$shop2->name = 'Readers Delight';
$author->name = 'Albert';
$detail->title = 'Book by Albert';
$book->ownDetailList[] = $detail;
$book->author = $author;
$shop->sharedBookList[] = $book;
$book2 = R::dispense('book');
$author2 = R::dispense('person');
$detail2 = R::dispense('detail');
$author2->name = 'Bert';
$detail2->title = 'Book by Bert';
$book2->ownDetailList[] = $detail2;
$book2->author = $author2;
$shop->sharedBookList[] = $book2;
$shop2->sharedBookList[] = $book2;
R::store($shop);
R::store($shop2);
//joined+own
$books = R::find('book', ' @joined.author.name LIKE ? AND @own.detail.title LIKE ? ', array('Albert', 'Book by Albert'));
asrt(count($books),1);
$books = R::find('book', ' @joined.author.name LIKE ? AND @own.detail.title LIKE ? ', array('%ert%', '%Book by%'));
asrt(count($books),2);
$books = R::find('book', ' @joined.author.name LIKE ? AND @own.detail.title LIKE ? ', array('%ert%', '%'));
asrt(count($books),2);
$books = R::find('book', ' @joined.author.name LIKE ? AND @own.detail.title LIKE ? ', array('%ert%', 'Old Bookshop'));
asrt(count($books),0);
//joined+shared
$books = R::find('book', ' @joined.author.name LIKE ? AND @shared.shop.name LIKE ? ', array('%ert%', '%Books%'));
asrt(count($books),2);
$books = R::find('book', ' @joined.author.name LIKE ? AND @shared.shop.name LIKE ? ', array('%ert%', '%Read%'));
asrt(count($books),1);
$books = R::find('book', ' @joined.author.name LIKE ? AND @shared.shop.name LIKE ? ', array('%ert%', '%'));
asrt(count($books),2);
$books = R::find('book', ' @joined.author.name LIKE ? AND @shared.shop.name LIKE ? ', array('%ert%', 'Old Bookshop'));
asrt(count($books),0);
//own+shared
$books = R::find('book', ' @own.detail.title LIKE ? AND @shared.shop.name LIKE ? ', array('%ert%', '%Read%'));
asrt(count($books),1);
$books = R::find('book', ' @own.detail.title LIKE ? AND @shared.shop.name LIKE ? ', array('%ert%', '%Book%'));
asrt(count($books),2);
$books = R::find('book', ' @own.detail.title LIKE ? AND @shared.shop.name LIKE ? ', array('%ert%', '%'));
asrt(count($books),2);
$books = R::find('book', ' @own.detail.title LIKE ? AND @shared.shop.name LIKE ? ', array('%ert%', 'Old Bookshop'));
asrt(count($books),0);
//joined+own+shared
$books = R::find('book', ' @joined.author.name LIKE ? AND @own.detail.title LIKE ? AND @shared.shop.name LIKE ? ', array('%ert%', '%Book by%', 'Books%'));
asrt(count($books),2);
$books = R::find('book', ' @joined.author.name LIKE ? AND @own.detail.title LIKE ? AND @shared.shop.name LIKE ? ', array('%ert%', '%Book by%', 'Read%'));
asrt(count($books),1);
$books = R::find('book', ' @joined.author.name LIKE ? AND @own.detail.title LIKE ? AND @shared.shop.name LIKE ? ', array('%ert%', '%Book by%', 'Old'));
asrt(count($books),0);
$books = R::find('book', ' @joined.author.name LIKE ? AND @own.detail.title LIKE ? AND @shared.shop.name LIKE ? ', array('%ert%', '%Book by%', '%'));
asrt(count($books),2);
//joined+joined
$page = R::dispense('page');
$page->text = 'Lorem Ipsum';
$category = R::dispense('category');
$category->name = 'biography';
$publisher = R::dispense('publisher');
$publisher->name = 'Good Books';
$book->publisher = $publisher;
$book->ownPageList[] = $page;
$category->sharedBookList[] = $book;
$page2 = R::dispense('page');
$page2->text = 'Blah Blah';
$category2 = R::dispense('category');
$category2->name = 'fiction';
$publisher2 = R::dispense('publisher');
$publisher2->name = 'Gutenberg';
$book2->publisher = $publisher2;
$book2->ownPageList = array($page2);
$category2->sharedBookList[] = $book2;
R::store($category);
R::store($category2);
$books = R::find('book', ' @joined.author.name LIKE ? AND @joined.publisher.name LIKE ?', array('%ert%', 'Good Books'));
asrt(count($books),1);
$books = R::find('book', ' @joined.author.name LIKE ? AND @joined.publisher.name LIKE ?', array('%ert%', '%'));
asrt(count($books),2);
$books = R::find('book', ' @joined.author.name LIKE ? AND @joined.publisher.name LIKE ?', array('Unknown', '%'));
asrt(count($books),0);
$books = R::find('book', ' @joined.author.name LIKE ? AND @joined.publisher.name LIKE ?', array('%', '%'));
asrt(count($books),2);
//shared+shared
$books = R::find('book', ' @shared.shop.name LIKE ? AND @shared.category.name LIKE ?', array('Reader%', 'fiction'));
asrt(count($books),1);
$books = R::find('book', ' @shared.shop.name LIKE ? AND @shared.category.name LIKE ?', array('Book%', '%'));
asrt(count($books),2);
$books = R::find('book', ' @shared.shop.name LIKE ? AND @shared.category.name LIKE ?', array('Book%', 'biography'));
asrt(count($books),1);
$books = R::find('book', ' @shared.shop.name LIKE ? AND @shared.category.name LIKE ?', array('Old Bookshop', '%'));
asrt(count($books),0);
$books = R::find('book', ' @shared.shop.name LIKE ? AND @shared.category.name LIKE ?', array('%', 'horror'));
asrt(count($books),0);
//own+own
$books = R::find('book', ' @own.detail.title LIKE ? AND @own.page.text LIKE ?', array('Book%', 'Blah%'));
asrt(count($books),1);
$books = R::find('book', ' @own.detail.title LIKE ? AND @own.page.text LIKE ?', array('Book%', 'Lorem%'));
asrt(count($books),1);
$books = R::find('book', ' @own.detail.title LIKE ? AND @own.page.text LIKE ?', array('Book%', '%'));
asrt(count($books),2);
$books = R::find('book', ' @own.detail.title LIKE ? AND @own.page.text LIKE ?', array('%', '%'));
asrt(count($books),2);
$books = R::find('book', ' @own.detail.title LIKE ? AND @own.page.text LIKE ?', array('%', 'Nah'));
asrt(count($books),0);
$books = R::find('book', ' @own.detail.title LIKE ? AND @own.page.text LIKE ?', array('Nah', '%'));
asrt(count($books),0);
//joined+joined+shared+shared+own+own
$books = R::find('book', ' @own.detail.title LIKE ? AND @own.page.text LIKE ?
AND @joined.publisher.name LIKE ? AND @joined.author.name LIKE ?
AND @shared.shop.name LIKE ? AND @shared.category.name LIKE ?', array('Book%', 'Lorem%','Good%','Albert','Books4%','bio%'));
asrt(count($books),1);
$books = R::find('book', ' @own.detail.title LIKE ? AND @own.page.text LIKE ?
AND @joined.publisher.name LIKE ? AND @joined.author.name LIKE ?
AND @shared.shop.name LIKE ? AND @shared.category.name LIKE ?', array('%', '%','%','%','%','%'));
asrt(count($books),2);
//in order clause
$book = R::findOne('book', ' ORDER BY @shared.category.name ASC LIMIT 1');
asrt($book->author->name, 'Albert');
$book = R::findOne('book', ' ORDER BY @shared.category.name DESC LIMIT 1');
asrt($book->author->name, 'Bert');
$book = R::findOne('book', ' ORDER BY @own.detail.title ASC LIMIT 1');
asrt($book->author->name, 'Albert');
$book = R::findOne('book', ' ORDER BY @own.detail.title DESC LIMIT 1');
asrt($book->author->name, 'Bert');
//order+criteria
$book = R::findOne('book', ' @joined.publisher.name LIKE ? ORDER BY @shared.category.name ASC LIMIT 1', array('%'));
asrt($book->author->name, 'Albert');
$book = R::findOne('book', ' @joined.publisher.name LIKE ? ORDER BY @shared.category.name DESC LIMIT 1', array('%'));
asrt($book->author->name, 'Bert');
$book = R::findOne('book', ' @joined.publisher.name LIKE ? ORDER BY @own.detail.title ASC LIMIT 1', array('%'));
asrt($book->author->name, 'Albert');
$book = R::findOne('book', ' @joined.publisher.name LIKE ? ORDER BY @own.detail.title DESC LIMIT 1', array('%'));
asrt($book->author->name, 'Bert');
$books = R::find('book', ' @own.detail.title LIKE ? AND @own.page.text LIKE ?
AND @joined.publisher.name LIKE ? AND @joined.author.name LIKE ?
AND @shared.shop.name LIKE ? AND @shared.category.name LIKE ?
ORDER BY @own.detail.title ASC
', array('%', '%','%','%','%','%'));
asrt(count($books),2);
$first = reset($books);
$last = end($books);
asrt($first->author->name, 'Albert');
asrt($last->author->name, 'Bert');
$books = R::find('book', ' @own.detail.title LIKE ? AND @own.page.text LIKE ?
AND @joined.publisher.name LIKE ? AND @joined.author.name LIKE ?
AND @shared.shop.name LIKE ? AND @shared.category.name LIKE ?
ORDER BY
@shared.shop.name DESC,
@own.detail.title ASC
', array('%', '%','%','%','%','%'));
asrt(count($books),2);
$first = reset($books);
$last = end($books);
asrt($first->author->name, 'Bert');
asrt($last->author->name, 'Albert');
$books = R::find('book', ' @own.detail.title LIKE ? AND @own.page.text LIKE ?
AND @joined.publisher.name LIKE ? AND @joined.author.name LIKE ?
AND @shared.shop.name LIKE ? AND @shared.category.name LIKE ?
ORDER BY
@joined.publisher.name ASC,
@shared.shop.name DESC,
@own.detail.title ASC
', array('%', '%','%','%','%','%'));
asrt(count($books),2);
$first = reset($books);
$last = end($books);
asrt($first->author->name, 'Albert');
asrt($last->author->name, 'Bert');
}
/**
* Tests new parsed joins.
*
* @return void
*/
public function testParsedJoins()
{
$person = R::dispense('person');
$person->name = 'Albert';
$book = R::dispense('book');
$book->title = 'Book by Albert.';
$book->author = $person;
$person->movement = R::dispense(array('_type'=>'movement','name'=>'romanticism'));
R::store( $book );
$albert = $person;
$person = R::dispense('person');
$person->name = 'Bert';
$book = R::dispense('book');
$book->title = 'Book by Bert.';
$book->author = $person;
$bert = $person;
$person->movement = R::dispense(array('_type'=>'movement','name'=>'gothic'));
R::store( $book );
R::aliases(array( 'author' => 'person' ));
$books = R::find( 'book', ' @joined.author.name LIKE ? ', array('A%'));
asrt(count($books), 1);
$book = reset($books);
asrt($book->title,'Book by Albert.');
asrt($book->fetchAs('person')->author->name,'Albert');
$people = R::find( 'person', ' @joined.movement.name = ? ', array('romanticism')); // This works (aliases not involved)
asrt(count($people), 1);
$people = R::find( 'person', ' @joined.movement.name = ? ', array('gothic')); // This works (aliases not involved)
asrt(count($people), 1);
$people = R::find( 'person', ' @joined.movement.name = ? ', array('popscience')); // This works (aliases not involved)
asrt(count($people), 0);
$movements = R::find( 'movement', ' @own.author.name LIKE ? ', array( 'A%' )); // This works
asrt(count($movements), 1);
$movement = reset($movements);
asrt($movement->name, 'romanticism');
R::freeze(TRUE);
try{
R::find( 'person', ' @own.book.title LIKE ? ', array( 'A%' )); // This doesn't work as RedBean cannot guess which column it should bind the person to in the book table.
fail();
} catch(\Exception $e) {
pass();
}
R::freeze(FALSE);
$group = R::dispense('group');
$group->name = 'a';
$group->sharedAuthorList = array($bert, $albert);
R::store($group);
$group = R::dispense('group');
$group->name = 'b';
$group->sharedAuthorList = array($bert);
R::store($group);
$groups = R::find( 'group', ' @shared.author.name = ? ', array( 'Bert' )); // This works
asrt(count($groups),2);
R::tag($albert, array('male','writer'));
R::tag($bert, array('male'));
$people = R::find( 'person', ' @shared.tag.title = ? ', array( 'writer' )); // This works (aliases not involved)
asrt(count($people),1);
R::tag($albert, array('male','writer'));
R::tag($bert, array('male'));
$people = R::find( 'person', ' @shared.tag.title = ? ', array( 'male' )); // This works (aliases not involved)
asrt(count($people),2);
$user1 = R::dispense('user');
$user1->name = 'user1';
$user2 = R::dispense('user');
$user2->name = 'user2';
$status = R::dispense('status');
$status->whitelist = TRUE;
$user1->status = $status;
$status2 = R::dispense('status');
$status2->whitelist = FALSE;
$user2->status = $status2;
R::storeAll(array($user1,$user2));
$whitelisted = R::find( 'user', ' @joined.status.whitelist = ? ', array( 1 ) );
asrt(count($whitelisted), 1);
$user = reset($whitelisted);
asrt($user->name, 'user1');
$whitelisted = R::find( 'user', ' @joined.status.whitelist = ? ', array( 0 ) );
R::debug(0);
asrt(count($whitelisted), 1);
$user = reset($whitelisted);
asrt($user->name, 'user2');
}
/**
* Tests joins with ownCount().
*
* @return void
*/
public function testJoinsInCount()
{
R::nuke();
$author = R::dispense( 'author' );
$book = R::dispense( 'book' );
$info = R::dispense( 'info' );
$info->title = 'x';
$author->xownBookList[] = $book;
$book->info = $info;
$book = R::dispense( 'book' );
$info = R::dispense( 'info' );
$info->title = 'y';
$author->xownBookList[] = $book;
$book->info = $info;
R::store( $author );
$author = $author->fresh();
$books = $author->withCondition(' @joined.info.title != ? ', array('x'))->countOwn('book');
asrt($books, 1);
$books = $author->withCondition(' @joined.info.title != ? ', array('y'))->countOwn('book');
asrt($books, 1);
$books = $author->withCondition(' @joined.info.title IN (?,?) ', array('x','y'))->countOwn('book');
asrt($books, 2);
}
/**
* Test Joins.
*
* @return void
*/
public function testJoins()
{
R::nuke();
list($a1, $a2, $a3) = R::dispense('area', 3);
list($p1, $p2) = R::dispense('person', 2);
list($v1, $v2, $v3, $v4) = R::dispense('visit', 4);
$a1->name = 'Belgium';
$a2->name = 'Arabia';
$a3->name = 'France';
$v1->person = $p1;
$v2->person = $p1;
$v3->person = $p2;
$v4->person = $p2;
$v1->area = $a3;
$v2->area = $a2;
$v3->area = $a2;
$v4->area = $a1;
$v1->label = 'v1 to France';
$v2->label = 'v2 to Arabia';
$v3->label = 'v3 to Arabia';
$v4->label = 'v4 to Belgium';
R::storeAll( array($v1,$v2,$v3,$v4) );
$visits = $p1->ownVisit;
asrt( is_array( $visits ), TRUE );
asrt( count( $visits ), 2 );
$names = array();
foreach( $visits as $visit ) {
asrt( isset( $visit->label ), TRUE );
asrt( isset( $visit->name ), FALSE );
asrt( isset( $visit->visit_id ), FALSE );
$names[] = $visit->label;
}
$labelList = implode( ',', $names );
asrt( $labelList, 'v1 to France,v2 to Arabia' );
$visits = $p1
->with('ORDER BY @joined.area.name ASC')->ownVisit;
asrt( is_array( $visits ), TRUE );
asrt( count( $visits ), 2 );
$names = array();
foreach( $visits as $visit ) {
asrt( isset( $visit->label ), TRUE );
asrt( isset( $visit->name ), FALSE );
asrt( isset( $visit->visit_id ), FALSE );
$names[] = $visit->label;
}
$labelList = implode( ',', $names );
asrt( $labelList, 'v2 to Arabia,v1 to France' );
}
/**
* Helper for the next test.
*
* @param array $books the books we are going to check
* @param string $numberList the numbers that are expected
*
* @return void
*/
private function checkBookNumbers( $books, $numberList )
{
$numbers = explode( ',', $numberList );
asrt( is_array( $books ), TRUE );
asrt( count( $books ), count( $numbers ) );
$bookNumbers = '';
$bookNumberArray = array();
foreach( $books as $book ) {
asrt( isset( $book->num ), TRUE );
asrt( isset( $book->title), FALSE );
$bookNumberArray[] = $book->num;
}
$bookNumbers = implode( ',', $bookNumberArray);
asrt( $bookNumbers, $numberList );
}
/**
* Tests the more complicated scenarios for
* with-joins.
*
* @return void
*/
private function testComplexCombinationsJoins()
{
$author = R::dispense( 'author' );
$books = R::dispense( 'book', 4 );
$books[0]->num = 0;
$books[1]->num = 1;
$books[2]->num = 2;
$books[3]->num = 3;
$books[0]->info = R::dispense('info')->setAttr('title', 'Learning PHP');
$books[1]->info = R::dispense('info')->setAttr('title', 'Learning PHP and JavaScript');
$books[2]->info = R::dispense('info')->setAttr('title', 'Learning Cobol');
$books[3]->info = R::dispense('info')->setAttr('title','Gardening for Beginners');
$books[0]->category = R::dispense('category')->setAttr('title', 'computers');
$books[1]->category = R::dispense('category')->setAttr('title', 'computers');
$books[2]->category = R::dispense('category')->setAttr('title', 'computers');
$books[3]->category = R::dispense('category')->setAttr('title','gardening');
$author->ownBookList = $books;
R::store($author);
//Base test...
$books = $author->ownBookList;
$this->checkBookNumbers( $books, '0,1,2,3' );
//Just a basic Join...
$books = $author->withCondition(' @joined.info.title LIKE ? ORDER BY book.num ASC ', array( '%PHP%' ) )->ownBookList;
$this->checkBookNumbers( $books, '0,1' );
//Mix Join and criteria
$books = $author->withCondition(' @joined.info.title LIKE ? AND num > 0 ORDER BY book.num ASC ', array( '%PHP%' ) )->ownBookList;
$this->checkBookNumbers( $books, '1' );
//Basic join
$books = $author->withCondition(' @joined.info.title LIKE ? ORDER BY book.num ASC', array( '%ing%' ) )->ownBookList;
$this->checkBookNumbers( $books, '0,1,2,3' );
//Two joins
$books = $author->withCondition(' @joined.info.title LIKE ? AND @joined.category.title = ? ORDER BY book.num ASC', array( '%ing%', 'computers' ) )->ownBookList;
$this->checkBookNumbers( $books, '0,1,2' );
//Join the same type twice... and order
$books = $author->withCondition(' @joined.info.title LIKE ? AND @joined.category.title = ? ORDER BY @joined.info.title ASC ', array( '%ing%', 'computers' ) )->ownBookList;
$this->checkBookNumbers( $books, '2,0,1' );
//Join the same type twice
$books = $author->withCondition(' @joined.info.title LIKE ? AND @joined.info.title LIKE ? ORDER BY book.num ASC', array( '%ing%', '%Learn%' ) )->ownBookList;
$this->checkBookNumbers( $books, '0,1,2' );
//Join the same type 3 times and order
$books = $author->withCondition(' @joined.info.title LIKE ? AND @joined.info.title LIKE ? ORDER BY @joined.info.title DESC', array( '%ing%', '%Learn%' ) )->ownBookList;
$this->checkBookNumbers( $books, '1,0,2' );
//Join the same type 3 times and order and limit
$books = $author->withCondition(' @joined.info.title LIKE ? AND @joined.info.title LIKE ? ORDER BY @joined.info.title DESC LIMIT 1', array( '%ing%', '%Learn%' ) )->ownBookList;
$this->checkBookNumbers( $books, '1' );
//Other combinations I can think of...
$books = $author->withCondition(' @joined.category.title LIKE ? ORDER BY @joined.info.title DESC', array( '%ing%' ) )->ownBookList;
$this->checkBookNumbers( $books, '3' );
$books = $author->withCondition(' @joined.category.title LIKE ? AND num < 4 ORDER BY @joined.info.title DESC', array( '%ing%' ) )->ownBookList;
$this->checkBookNumbers( $books, '3' );
//multiple ordering
$books = $author->with(' ORDER BY @joined.category.title ASC, @joined.info.title ASC' )->ownBookList;
$this->checkBookNumbers( $books, '2,0,1,3' );
$books = $author->with(' ORDER BY @joined.category.title DESC, @joined.info.title ASC' )->ownBookList;
$this->checkBookNumbers( $books, '3,2,0,1' );
$books = $author->with(' ORDER BY @joined.category.title DESC, @joined.info.title ASC LIMIT 2' )->ownBookList;
$this->checkBookNumbers( $books, '3,2' );
}
/**
* Tests the more complicated scenarios for
* with-joins.
*
* @return void
*/
public function testComplexInFrozenMode()
{
R::freeze( FALSE );
$this->testComplexCombinationsJoins();
R::freeze( TRUE );
$this->testComplexCombinationsJoins();
R::freeze( FALSE );
}
/**
* Tests R::setNarrowFieldMode() and
* OODBBean::ignoreJoinFeature().
*/
public function testSystemWideSettingsForJoins()
{
R::nuke();
$author = R::dispense( 'author' );
$book = R::dispense( 'book' );
$info = R::dispense( 'info' );
$info->title = 'x';
$author->xownBookList[] = $book;
$book->info = $info;
R::store( $author );
$author = $author->fresh();
$books = $author->withCondition(' @joined.info.title != ? ', array('y1') )->xownBookList;
$firstBook = reset( $books );
asrt( isset( $firstBook->title ), FALSE );
R::setNarrowFieldMode( FALSE );
$author = $author->fresh();
$books = $author->withCondition(' @joined.info.title != ? ', array('y2') )->xownBookList;
$firstBook = reset( $books );
asrt( isset( $firstBook->title ), TRUE );
R::setNarrowFieldMode( TRUE );
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Keywords
*
* Tests whether we can use keywords as bean types and
* property names without running into security or stablity issues.
* RedBeanPHP should properly escape all bean types and properties
* so we may use whatever string we want.
*
* @file RedUNIT/Base/Keywords.php
* @desc Tests for possible keyword clashes.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Keywords extends Base
{
/**
* What drivers should be loaded for this test pack?
*
* CUBRID has inescapable keywords :/
*
* @return array
*/
public function getTargetDrivers()
{
return array( 'mysql', 'pgsql', 'sqlite' ); // CUBRID excluded for now.
}
/**
* Test if RedBeanPHP can properly handle keywords.
*
* @return void
*/
public function testKeywords()
{
$keywords = array(
'anokeyword', 'znokeyword', 'group', 'drop',
'inner', 'join', 'select', 'table',
'int', 'cascade', 'float', 'call',
'in', 'status', 'order', 'limit',
'having', 'else', 'if', 'while',
'distinct', 'like'
);
foreach ( $keywords as $k ) {
R::nuke();
$bean = R::dispense( $k );
$bean->$k = $k;
$id = R::store( $bean );
$bean = R::load( $k, $id );
$bean2 = R::dispense( 'other' );
$bean2->name = $k;
$bean->bean = $bean2;
$bean->ownBean[] = $bean2;
$bean->sharedBean[] = $bean2;
$id = R::store( $bean );
R::trash( $bean );
pass();
}
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
/**
* Large Number Limit Test (issue #386)
*
* Tests whether we can use large numbers for limit clause.
*
* @file RedUNIT/Base/Largenum.php
* @desc Test whether we can use large numbers in LIMIT clause (PDO bindings).
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Largenum extends Base
{
/**
* Test for issue #386.
* Can we use large numbers in LIMIT ?
*
* @return void
*/
public function testLargeNum()
{
if ( defined( 'HHVM_VERSION' ) ) return; //oops hhvm has incorrect binding for large nums.
$number = R::dispense( 'number' );
$number->name = 'big number';
R::store( $number );
//This should not cause an error... (some people use LIMIT 0, HUGE to simulate OFFSET on MYSQL).
$beans = R::findAll( 'number', ' LIMIT ? ', array( PHP_INT_MAX ) );
asrt( is_array( $beans ), TRUE );
asrt( count( $beans ), 1 );
pass();
}
}

View File

@@ -0,0 +1,256 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\Logger as Logger;
use RedBeanPHP\Logger\RDefault as RDefault;
use RedBeanPHP\Logger\RDefault\Debug as Debug;
/**
* Logging
*
* Tests the Query Logging tools that are part of RedBeanPHP.
*
* @file RedUNIT/Base/Logging.php
* @desc Tests Logging facilities.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Logging extends Base
{
/**
* Test basic logging functionality.
*
* @return void
*/
public function testBasicLogging()
{
R::nuke();
R::startLogging();
R::store( R::dispense( 'book' ) );
$logs = R::getLogs();
$count = count( $logs );
asrt( ( count( $logs ) > 0 ), TRUE );
asrt( ( R::getLogger() instanceof Logger ), TRUE );
R::stopLogging();
R::store( R::dispense( 'book' ) );
$logs = R::getLogs();
asrt( ( count( $logs ) === 0 ), TRUE );
}
/**
* Test for Issue #751 (Update Logger to accept parameter typed bindings):
* While debugging some of our queries, we noticed
* the logger would often display
* 'Array' as value for the bindings,
* even when the SQL query seemed to work correctly.
* Debugging this, it appeared the debug logger did
* not support the new parameter type bindings added in 5.3.
* This merge request adds support for
* the PDO::PARAM_INT and PDO::PARAM_STR
* to the Debug logger, as well as a visible support
* for the RPDO flagUseStringOnlyBinding flag.
*
* @return void
*/
public function testIssue751()
{
R::nuke();
$debugger = new Debug;
$database = R::getDatabaseAdapter()->getDatabase();
$database->setLogger( $debugger );
asrt( $database->getLogger(), $debugger );
$database->setEnableLogging( TRUE );
$debugger->setMode( RDefault::C_LOGGER_ARRAY );
/* debug logger with nostringonlybinding should have unquoted ints */
R::store( R::dispense( 'book' ) );
R::getAll( 'SELECT * FROM book WHERE id < ?', array( array( 999, \PDO::PARAM_INT ) ) );
asrt( count( $debugger->grep('999') ), 1 );
asrt( count( $debugger->grep('\'999\'') ), 0 );
asrt( count( $debugger->grep('rray') ), 0 );
$debugger->setUseStringOnlyBinding( FALSE );
$debugger->clear();
R::getAll( 'SELECT * FROM book WHERE id < ?', array( array( 999, \PDO::PARAM_STR ) ) );
/* ...but quoted strings */
asrt( count( $debugger->grep('\'999\'') ), 1 );
asrt( count( $debugger->grep('rray') ), 0 );
$debugger->setUseStringOnlyBinding( FALSE );
$debugger->clear();
/* even if PARAM INT if stringonlybinding then override */
$debugger->setUseStringOnlyBinding( TRUE );
R::getAll( 'SELECT * FROM book WHERE id < ?', array( array( 999, \PDO::PARAM_INT ) ) );
asrt( count( $debugger->grep('\'999\'') ), 1 );
asrt( count( $debugger->grep('rray') ), 0 );
/* if no type and stringonlybinding always quote */
$debugger->clear();
R::getAll( 'SELECT * FROM book WHERE id < ?', array( 999 ) );
asrt( count( $debugger->grep('\'999\'') ), 1 );
asrt( count( $debugger->grep('rray') ), 0 );
/* a more closer inspection */
/* log implicit INT param without StringOnly */
$debugger->clear();
$debugger->setUseStringOnlyBinding( FALSE );
$debugger->log(' Hello ? ', array( 123 ) );
asrt( count( $debugger->grep('123') ), 1 );
asrt( count( $debugger->grep('\'123\'') ), 0 );
/* log implicit STR param without StringOnly */
$debugger->clear();
$debugger->setUseStringOnlyBinding( FALSE );
$debugger->log(' Hello ? ', array( 'abc' ) );
asrt( count( $debugger->grep('\'abc\'') ), 1 );
/* log NULL param without StringOnly */
$debugger->clear();
$debugger->setUseStringOnlyBinding( FALSE );
$debugger->log(' Hello ? ', array( NULL ) );
asrt( count( $debugger->grep('NULL') ), 1 );
asrt( count( $debugger->grep('\'NULL\'') ), 0 );
/* log explicit INT param without StringOnly */
$debugger->clear();
$debugger->setUseStringOnlyBinding( FALSE );
$debugger->log(' Hello ? ', array( 123, \PDO::PARAM_INT ) );
asrt( count( $debugger->grep('123') ), 1 );
asrt( count( $debugger->grep('\'123\'') ), 0 );
/* log explicit STR param without StringOnly */
$debugger->clear();
$debugger->setUseStringOnlyBinding( FALSE );
$debugger->log(' Hello ? ', array( 'abc', \PDO::PARAM_STR ) );
asrt( count( $debugger->grep('\'abc\'') ), 1 );
/* log NULL with explicit param type without StringOnly */
$debugger->clear();
$debugger->setUseStringOnlyBinding( FALSE );
$debugger->log(' Hello ? ', array( NULL, \PDO::PARAM_STR ) );
asrt( count( $debugger->grep('NULL') ), 1 );
asrt( count( $debugger->grep('\'NULL\'') ), 0 );
$debugger->clear();
$debugger->setUseStringOnlyBinding( FALSE );
$debugger->log(' Hello ? ', array( NULL, \PDO::PARAM_INT ) );
asrt( count( $debugger->grep('NULL') ), 1 );
asrt( count( $debugger->grep('\'NULL\'') ), 0 );
/* log implicit INT param with StringOnly */
$debugger->clear();
$debugger->setUseStringOnlyBinding( TRUE );
$debugger->log(' Hello ? ', array( 123 ) );
asrt( count( $debugger->grep('\'123\'') ), 1 );
/* log implicit STR param with StringOnly */
$debugger->clear();
$debugger->setUseStringOnlyBinding( TRUE );
$debugger->log(' Hello ? ', array( 'abc' ) );
asrt( count( $debugger->grep('\'abc\'') ), 1 );
/* log NULL param with StringOnly */
$debugger->clear();
$debugger->setUseStringOnlyBinding( TRUE );
$debugger->log(' Hello ? ', array( NULL ) );
asrt( count( $debugger->grep('\'NULL\'') ), 1 );
/* log explicit INT param with StringOnly */
$debugger->clear();
$debugger->setUseStringOnlyBinding( TRUE );
$debugger->log(' Hello ? ', array( 123, \PDO::PARAM_INT ) );
asrt( count( $debugger->grep('\'123\'') ), 1 );
/* log explicit STR param with StringOnly */
$debugger->clear();
$debugger->setUseStringOnlyBinding( TRUE );
$debugger->log(' Hello ? ', array( 'abc', \PDO::PARAM_STR ) );
asrt( count( $debugger->grep('\'abc\'') ), 1 );
/* log NULL with explicit param type with StringOnly - remains just NULL */
$debugger->clear();
$debugger->setUseStringOnlyBinding( FALSE );
$debugger->log(' Hello ? ', array( NULL, \PDO::PARAM_STR ) );
asrt( count( $debugger->grep('NULL') ), 1 );
asrt( count( $debugger->grep('\'NULL\'') ), 0 );
$debugger->clear();
$debugger->setUseStringOnlyBinding( FALSE );
$debugger->log(' Hello ? ', array( NULL, \PDO::PARAM_INT ) );
asrt( count( $debugger->grep('NULL') ), 1 );
asrt( count( $debugger->grep('\'NULL\'') ), 0 );
$debugger->setUseStringOnlyBinding( FALSE );
/* Does stringonly mode switch along with Database mode ? */
$database->setUseStringOnlyBinding( TRUE );
$debugger->clear();
$debugger->log(' Hello ? ', array( 123, \PDO::PARAM_INT ) );
asrt( count( $debugger->grep('\'123\'') ), 1 );
$database->setUseStringOnlyBinding( FALSE );
$debugger->clear();
$debugger->log(' Hello ? ', array( 123, \PDO::PARAM_INT ) );
asrt( count( $debugger->grep('\'123\'') ), 0 );
asrt( count( $debugger->grep('123') ), 1 );
$database->setUseStringOnlyBinding( TRUE );
$debugger->clear();
$debugger->log(' Hello ? ', array( 123, \PDO::PARAM_INT ) );
asrt( count( $debugger->grep('\'123\'') ), 1 );
$database->setUseStringOnlyBinding( FALSE );
$debugger->setUseStringOnlyBinding( FALSE );
}
/**
* Can we manually set a logger and enable logging?
*
* @return void
*/
public function testCanSetLogger()
{
R::nuke();
R::store( R::dispense( 'bean' ) );
$logger = new RDefault;
$logger->setMode( RDefault::C_LOGGER_ARRAY );
$database = R::getDatabaseAdapter()->getDatabase();
$database->setLogger( $logger );
asrt( $database->getLogger(), $logger );
$database->setEnableLogging( FALSE );
$logs = $logger->getLogs();
asrt( is_array( $logs ), TRUE );
asrt( count( $logs ), 0 );
$database->setEnableLogging( TRUE );
$logs = $logger->getLogs();
asrt( is_array( $logs ), TRUE );
asrt( count( $logs ), 0 );
R::findOne( 'bean' ); //writes 3 log entries
$logs = $logger->getLogs();
asrt( is_array( $logs ), TRUE );
asrt( count( $logs ), 3 );
}
/**
* Test query counter.
*
* @return void
*/
public function testQueryCount()
{
R::nuke();
R::store( R::dispense( 'bean' ) );
R::resetQueryCount();
asrt( R::getQueryCount(), 0 );
R::findOne( 'bean' );
asrt( R::getQueryCount(), 1 );
R::resetQueryCount();
asrt( R::getQueryCount(), 0 );
R::findOne( 'bean' );
R::findOne( 'bean' );
R::findOne( 'bean' );
asrt( R::getQueryCount(), 0 );
R::store( R::dispense( 'bean2' ) );
R::resetQueryCount();
R::findOne( 'bean' );
R::findOne( 'bean2' );
asrt( R::getQueryCount(), 2 );
R::resetQueryCount();
R::findOne( 'bean', ' id < 100' );
R::findOne( 'bean', ' id < 101' );
R::findOne( 'bean', ' id < 102' );
R::findOne( 'bean', ' id < 103' );
asrt( R::getQueryCount(), 4 );
R::findOne( 'bean', ' id < 100' );
R::findOne( 'bean', ' id < 101' );
R::findOne( 'bean', ' id < 102' );
R::findOne( 'bean', ' id < 103' );
asrt( R::getQueryCount(), 4 );
R::findOne( 'bean', ' id < 104' );
asrt( R::getQueryCount(), 5 );
}
}

View File

@@ -0,0 +1,653 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\Logger\RDefault as RDefault;
use RedBeanPHP\Logger as Logger;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\Adapter as Adapter;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\RedException\SQL as SQL;
use RedBeanPHP\Driver\RPDO as RPDO;
use RedBeanPHP\SimpleModel as SimpleModel;
/**
* Misc
*
* @file RedUNIT/Base/Misc.php
* @desc Various tests.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Misc extends Base
{
/**
* Can we use data definition templates?
*
* @return void
*/
public function testDDLTemplates()
{
R::nuke();
R::debug( TRUE, 1 );
$writer = R::getWriter();
$writer->setDDLTemplate( 'createTable', 'joke', $writer->getDDLTemplate( 'createTable', 'joke' ) . ' /* haha */ ' );
$writer->setDDLTemplate( 'addColumn', 'joke', $writer->getDDLTemplate( 'addColumn', 'joke' ) . ' /* hihi */ ' );
$writer->setDDLTemplate( 'widenColumn', 'joke', $writer->getDDLTemplate( 'widenColumn', 'joke' ) . ' /* hoho */ ' );
$joke = R::dispense('joke');
R::store( $joke );
$logs = R::getDatabaseAdapter()->getDatabase()->getLogger()->grep( 'haha' );
asrt( count( $logs ), 1 );
$joke->punchline = 1;
R::store( $joke );
$logs = R::getDatabaseAdapter()->getDatabase()->getLogger()->grep( 'hihi' );
asrt( count( $logs ), 1 );
$joke->punchline = '...';
R::store( $joke );
$logs = R::getDatabaseAdapter()->getDatabase()->getLogger()->grep( 'hoho' );
asrt( count( $logs ), 1 );
R::debug( FALSE );
}
/**
* Github issue:
* Remove $NULL to directly return NULL #625
* @@ -1097,8 +1097,7 @@ public function &__get( $property )
* $this->all = FALSE;
* $this->via = NULL;
*
* - $NULL = NULL;
* - return $NULL;
* + return NULL;
*
* leads to regression:
* PHP Stack trace:
* PHP 1. {main}() testje.php:0
* PHP 2. RedBeanPHP\OODBBean->__get() testje.php:22
* Notice: Only variable references should be returned by reference in rb.php on line 2529
*/
public function testReferencedGetInBeans()
{
$bean = R::dispense( 'bean' );
//this will trigger notice if &__get() returns NULL instead of $NULL.#625
$x = $bean->hello;
pass();
$x = $bean->reference;
pass();
$x = $bean->nullvalue;
pass();
}
public static $setupPartialBeansTestDone = 0;
/**
* Check partial beans at setup()
*/
public function testPartialBeansAtSetup()
{
if (self::$setupPartialBeansTestDone) return; /* only needs to be tested once */
$currentDB = R::$currentDB;
$key = 'partialBeanBase' . time();
$dsn = 'sqlite:/tmp/test.txt';
$user = '';
$pass = '';
$frozen = FALSE;
$partialBeans = TRUE;
R::addDatabase( $key, $dsn, $user, $pass, $frozen, $partialBeans);
$redbean = R::getRedBean();
$wasItSet = $redbean->getCurrentRepository()->usePartialBeans( FALSE );
R::selectDatabase( $key );
$redbean = R::getRedBean();
$wasItSet = $redbean->getCurrentRepository()->usePartialBeans( FALSE );
asrt( $wasItSet, TRUE );
self::$setupPartialBeansTestDone = 1;
R::selectDatabase( $currentDB );
}
/**
* Test whether we can set the 'auto clear'
* option in OODB.
*
* @return void
*/
public function testAutoClearHistory()
{
testpack( 'Auto clear history' );
$book = R::dispense( 'book' );
$book->pages = 100;
$book->title = 'book';
R::store( $book );
$book = R::findOne( 'book' );
asrt( $book->hasChanged( 'title' ), FALSE );
$book->title = 'yes';
R::store( $book );
asrt( $book->hasChanged( 'title' ), TRUE );
OODB::autoClearHistoryAfterStore( TRUE );
$book = R::findOne( 'book' );
asrt( $book->hasChanged( 'title' ), FALSE );
$book->title = 'yes2';
R::store( $book );
asrt( $book->hasChanged( 'title' ), FALSE );
OODB::autoClearHistoryAfterStore( FALSE );
$book = R::findOne( 'book' );
asrt( $book->hasChanged( 'title' ), FALSE );
$book->title = 'yes';
R::store( $book );
asrt( $book->hasChanged( 'title' ), TRUE );
}
/**
* Tests the R::inspect() method on the Facade.
*
* @return void
*/
public function testInspect() {
testpack( 'Test R::inspect() ' );
R::nuke();
R::store( R::dispense( 'book' )->setAttr( 'title', 'book' ) );
$info = R::inspect();
asrt( count( $info ), 1 );
asrt( strtolower( $info[0] ), 'book' );
$info = R::inspect( 'book' );
asrt( count( $info ), 2 );
$keys = array_keys( $info );
sort($keys);
asrt( strtolower( $keys[0] ), 'id' );
asrt( strtolower( $keys[1] ), 'title' );
}
/**
* Test whether we can use the tableExist() method in OODB
* instances directly to help us determine
* the existance of a table.
*
* @return void
*/
public function testTableExist()
{
R::nuke();
R::store( R::dispense( 'book' ) );
R::freeze( FALSE );
asrt( R::getRedBean()->tableExists( 'book' ), TRUE );
asrt( R::getRedBean()->tableExists( 'book2' ), FALSE );
R::freeze( TRUE );
asrt( R::getRedBean()->tableExists( 'book' ), TRUE );
asrt( R::getRedBean()->tableExists( 'book2' ), FALSE );
R::freeze( FALSE );
}
/**
* Normally the check() method is always called indirectly when
* dealing with beans. This test ensures we can call check()
* directly. Even though frozen repositories do not rely on
* bean checking to improve performance the method should still
* offer the same functionality when called directly.
*
* @return void
*/
public function testCheckDirectly()
{
$bean = new OODBBean;
$bean->setProperty('id', 0);
$bean->setMeta( 'type', 'book' );
R::getRedBean()->check( $bean );
$bean->setMeta( 'type', '.' );
try {
R::getRedBean()->check( $bean );
fail();
} catch ( \Exception $e ) {
pass();
}
//check should remain the same even if frozen repo is used, method is public after all!
//we dont want to break the API!
R::freeze( TRUE );
try {
R::getRedBean()->check( $bean );
fail();
} catch ( \Exception $e ) {
pass();
}
R::freeze( FALSE );
}
/**
* Test Backward compatibility writer ESC-method.
*
* @return void
*/
public function testLegacyCode()
{
testpack( 'Test Backward compatibility methods in writer.' );
asrt( R::getWriter()->safeColumn( 'column', TRUE ), R::getWriter()->esc( 'column', TRUE ) );
asrt( R::getWriter()->safeColumn( 'column', FALSE ), R::getWriter()->esc( 'column', FALSE ) );
asrt( R::getWriter()->safeTable( 'table', TRUE ), R::getWriter()->esc( 'table', TRUE ) );
asrt( R::getWriter()->safeTable( 'table', FALSE ), R::getWriter()->esc( 'table', FALSE ) );
}
/**
* Test beautification and array functions.
*
* @return void
*/
public function testBeauficationAndArrayFunctions()
{
$bean = R::dispense( 'bean' );
$bean->isReallyAwesome = TRUE;
asrt( isset( $bean->isReallyAwesome ), TRUE );
asrt( isset( $bean->is_really_awesome ), TRUE );
unset( $bean->is_really_awesome );
asrt( isset( $bean->isReallyAwesome ), FALSE );
asrt( isset( $bean->is_really_awesome ), FALSE );
}
/**
* Test beautification of column names.
*
* @return void
*/
public function testBeautifulColumnNames()
{
testpack( 'Beautiful column names' );
$town = R::dispense( 'town' );
$town->isCapital = FALSE;
$town->hasTrainStation = TRUE;
$town->name = 'BeautyVille';
$houses = R::dispense( 'house', 2 );
$houses[0]->isForSale = TRUE;
$town->ownHouse = $houses;
R::store( $town );
$town = R::load( 'town', $town->id );
asrt( ( $town->isCapital == FALSE ), TRUE );
asrt( ( $town->hasTrainStation == TRUE ), TRUE );
asrt( ( $town->name == 'BeautyVille' ), TRUE );
testpack( 'Accept datetime objects.' );
$cal = R::dispense( 'calendar' );
$cal->when = new\DateTime( '2000-01-01', new\DateTimeZone( 'Pacific/Nauru' ) );
asrt( $cal->when, '2000-01-01 00:00:00' );
testpack( 'Affected rows test' );
$currentDriver = $this->currentlyActiveDriverID;
$toolbox = R::getToolBox();
$adapter = $toolbox->getDatabaseAdapter();
$writer = $toolbox->getWriter();
$redbean = $toolbox->getRedBean();
$pdo = $adapter->getDatabase();
$bean = $redbean->dispense( 'bean' );
$bean->prop = 3; //make test run with strict mode as well
$redbean->store( $bean );
$adapter->exec( 'UPDATE bean SET prop = 2' );
asrt( $adapter->getAffectedRows(), 1 );
testpack( 'Testing Logger' );
R::getDatabaseAdapter()->getDatabase()->setLogger( new RDefault );
asrt( ( R::getDatabaseAdapter()->getDatabase()->getLogger() instanceof Logger ), TRUE );
asrt( ( R::getDatabaseAdapter()->getDatabase()->getLogger() instanceof RDefault ), TRUE );
$bean = R::dispense( 'bean' );
$bean->property = 1;
$bean->unsetAll( array( 'property' ) );
asrt( $bean->property, NULL );
asrt( ( $bean->setAttr( 'property', 2 ) instanceof OODBBean ), TRUE );
asrt( $bean->property, 2 );
asrt( preg_match( '/\d\d\d\d\-\d\d\-\d\d/', R::isoDate() ), 1 );
asrt( preg_match( '/\d\d\d\d\-\d\d\-\d\d\s\d\d:\d\d:\d\d/', R::isoDateTime() ), 1 );
$redbean = R::getRedBean();
$adapter = R::getDatabaseAdapter();
$writer = R::getWriter();
asrt( ( $redbean instanceof OODB ), TRUE );
asrt( ( $adapter instanceof Adapter ), TRUE );
asrt( ( $writer instanceof QueryWriter ), TRUE );
R::setRedBean( $redbean );
pass(); //cant really test this
R::setDatabaseAdapter( $adapter );
pass(); //cant really test this
R::setWriter( $writer );
pass(); //cant really test this
$u1 = R::dispense( 'user' );
$u1->name = 'Gabor';
$u1->login = 'g';
$u2 = R::dispense( 'user' );
$u2->name = 'Eric';
$u2->login = 'e';
R::store( $u1 );
R::store( $u2 );
$list = R::getAssoc( 'select login,' . R::getWriter()->esc( 'name' ) . ' from ' . R::getWriter()->esc( 'user' ) . ' ' );
asrt( $list['e'], 'Eric' );
asrt( $list['g'], 'Gabor' );
$painting = R::dispense( 'painting' );
$painting->name = 'Nighthawks';
$id = R::store( $painting );
testpack( 'Testing SQL Error Types' );
foreach ( $writer->typeno_sqltype as $code => $text ) {
asrt( is_integer( $code ), TRUE );
asrt( is_string( $text ), TRUE );
}
foreach ( $writer->sqltype_typeno as $text => $code ) {
asrt( is_integer( $code ), TRUE );
asrt( is_string( $text ), TRUE );
}
testpack( 'Testing Nowhere Pt. 1 (unfrozen)' );
foreach (
array(
'exec', 'getAll', 'getCell', 'getAssoc', 'getRow', 'getCol'
)
as $method ) {
R::$method( 'select * from nowhere' );
pass();
}
testpack( 'Testing Nowhere Pt. 2 (frozen)' );
R::freeze( TRUE );
foreach (
array(
'exec', 'getAll', 'getCell', 'getAssoc', 'getRow', 'getCol'
)
as $method ) {
try {
R::$method( 'select * from nowhere' );
fail();
} catch ( SQL $e ) {
pass();
}
}
R::freeze( FALSE );
}
/**
* Test reflectional functions of database.
*
* @return void
*/
public function testDatabaseProperties()
{
testpack( 'Testing Database Properties' );
$adapter = R::getDatabaseAdapter();
if ( method_exists( R::getDatabaseAdapter()->getDatabase(), 'getPDO' ) ){
asrt( $adapter->getDatabase()->getPDO() instanceof \PDO, TRUE );
}
asrt( strlen( $adapter->getDatabase()->getDatabaseVersion() ) > 0, TRUE );
asrt( strlen( $adapter->getDatabase()->getDatabaseType() ) > 0, TRUE );
}
/**
* Test Transactions.
*
* @return void
*/
public function testTransactions()
{
testpack( 'transactions' );
$false = R::begin();
asrt( $false, FALSE );
$bean = R::dispense( 'bean' );
R::store( $bean );
R::commit();
asrt( R::count( 'bean' ), 1 );
R::trash( $bean );
R::setAllowFluidTransactions( TRUE );
asrt( R::begin(), TRUE );
$bean = R::dispense( 'bean' );
R::store( $bean );
asrt( R::commit(), TRUE );
asrt( R::count( 'bean' ), 1 );
R::trash( $bean );
asrt( R::begin(), TRUE );
$bean = R::dispense( 'bean' );
R::store( $bean );
R::rollback();
asrt( R::count( 'bean' ), 0 );
R::setAllowFluidTransactions( FALSE );
R::wipe('bean');
R::freeze( TRUE );
R::begin();
$bean = R::dispense( 'bean' );
R::store( $bean );
R::rollback();
asrt( R::count( 'bean' ), 0 );
R::freeze( FALSE );
testpack( 'genSlots' );
asrt( R::genSlots( array( 'a', 'b' ) ), '?,?' );
asrt( R::genSlots( array( 'a' ) ), '?' );
asrt( R::genSlots( array() ), '' );
asrt( R::genSlots( array('a', 'b'), ' IN( %s ) ' ), ' IN( ?,? ) ' );
}
/**
* Test nested FUSE scenarios.
*
* @return void
*/
public function testFUSEnested()
{
testpack( 'FUSE models cant touch nested beans in update() - issue 106' );
$spoon = R::dispense( 'spoon' );
$spoon->name = 'spoon for test bean';
$deep = R::dispense( 'deep' );
$deep->name = 'deepbean';
$item = R::dispense( 'item' );
$item->val = 'Test';
$item->deep = $deep;
$test = R::dispense( 'test' );
$test->item = $item;
$test->sharedSpoon[] = $spoon;
$test->isnowtainted = TRUE;
$id = R::store( $test );
$test = R::load( 'test', $id );
asrt( $test->item->val, 'Test2' );
$can = reset( $test->ownCan );
$spoon = reset( $test->sharedSpoon );
asrt( $can->name, 'can for bean' );
asrt( $spoon->name, 'S2' );
asrt( $test->item->deep->name, '123' );
asrt( count( $test->ownCan ), 1 );
asrt( count( $test->sharedSpoon ), 1 );
asrt( count( $test->sharedPeas ), 10 );
asrt( count( $test->ownChip ), 9 );
}
/**
* Tests FUSE and lists, FUSE enforces no more than
* 3 sugar cubes in coffee.
*
* @return void
*/
public function testCoffeeWithSugarAndFUSE()
{
$coffee = R::dispense( 'coffee' );
$coffee->size = 'XL';
$coffee->ownSugar = R::dispense( 'sugar', 5 );
$id = R::store( $coffee );
$coffee = R::load( 'coffee', $id );
asrt( count( $coffee->ownSugar ), 3 );
$coffee->ownSugar = R::dispense( 'sugar', 2 );
$id = R::store( $coffee );
$coffee = R::load( 'coffee', $id );
asrt( count( $coffee->ownSugar ), 2 );
$cocoa = R::dispense( 'cocoa' );
$cocoa->name = 'Fair Cocoa';
list( $taste1, $taste2 ) = R::dispense( 'taste', 2 );
$taste1->name = 'sweet';
$taste2->name = 'bitter';
$cocoa->ownTaste = array( $taste1, $taste2 );
R::store( $cocoa );
$cocoa->name = 'Koko';
R::store( $cocoa );
if ( method_exists( R::getDatabaseAdapter()->getDatabase(), 'getPDO' ) ) {
$pdo = R::getDatabaseAdapter()->getDatabase()->getPDO();
$driver = new RPDO( $pdo );
pass();
asrt( $pdo->getAttribute(\PDO::ATTR_ERRMODE ),\PDO::ERRMODE_EXCEPTION );
asrt( $pdo->getAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE ),\PDO::FETCH_ASSOC );
asrt( strval( $driver->GetCell( 'select 123' ) ), '123' );
}
$a = new SQL;
$a->setSqlState( 'test' );
$b = strval( $a );
asrt( ( strpos( $b, '[test] - ' ) === 0 ), TRUE );
}
/**
* ENUM Basic tests.
*
* @return void
*/
public function testENUMBasics() {
asrt( R::enum( 'gender:male' )->name, 'MALE' );
asrt( R::enum( 'country:South-Africa' )->name, 'SOUTH_AFRICA' );
asrt( R::enum( 'tester:T@E S_t' )->name, 'T_E_S_T' );
}
/**
* Test ENUM in Queries and with short hand notation.
*
* @return void
*/
public function testENUMInQuery()
{
testpack('Test ENUM in Query and test ENUM short notation');
R::nuke();
$coffee = R::dispense( 'coffee' );
$coffee->taste = R::enum( 'flavour:mocca' );
R::store( $coffee );
$coffee = R::dispense( 'coffee' );
$coffee->taste = R::enum( 'flavour:banana' );
R::store( $coffee );
$coffee = R::dispense( 'coffee' );
$coffee->taste = R::enum( 'flavour:banana' );
R::store( $coffee );
//now we have two flavours
asrt( R::count('flavour'), 2 );
//use in query
asrt( R::count( 'coffee', ' taste_id = ? ', array( R::enum( 'flavour:mocca' )->id ) ), 1);
//use in quer with short notation
asrt( R::count( 'coffee', ' taste_id = ? ', array( EID( 'flavour:mocca' ) ) ), 1);
//use in query
asrt( R::count( 'coffee', ' taste_id = ? ', array( R::enum( 'flavour:banana' )->id ) ), 2);
//use in quer with short notation
asrt( R::count( 'coffee', ' taste_id = ? ', array( EID( 'flavour:banana' ) ) ), 2);
//use in query
asrt( R::count( 'coffee', ' taste_id = ? ', array( R::enum( 'flavour:strawberry' )->id ) ), 0);
//use in quer with short notation
asrt( R::count( 'coffee', ' taste_id = ? ', array( EID( 'flavour:strawberry' ) ) ), 0);
}
/**
* Test ENUM functionality offered by Label Maker.
*
* @return void
*/
public function testENUM() {
testpack('test ENUM');
$coffee = R::dispense( 'coffee' );
$coffee->taste = R::enum( 'flavour:mocca' );
//did we create an enum?
asrt( implode( '', R::gatherLabels( R::enum( 'flavour' ) ) ), 'MOCCA' );
R::store( $coffee );
$coffee = $coffee->fresh();
//test enum identity check - with alias
asrt( $coffee->fetchAs( 'flavour' )->taste->equals( R::enum('flavour:mocca') ), TRUE );
asrt( $coffee->fetchAs( 'flavour' )->taste->equals( R::enum('flavour:banana') ), FALSE );
//now we have two flavours
asrt( R::count( 'flavour' ), 2 );
asrt( implode( ',', R::gatherLabels( R::enum( 'flavour') ) ), 'BANANA,MOCCA' );
$coffee->flavour = R::enum( 'flavour:mocca' );
R::store($coffee);
//same results, can we have multiple flavours?
asrt( $coffee->fetchAs( 'flavour' )->taste->equals( R::enum( 'flavour:mocca' ) ), TRUE );
asrt( $coffee->fetchAs( 'flavour' )->taste->equals( R::enum( 'flavour:banana' ) ), FALSE );
asrt( $coffee->flavour->equals( R::enum( 'flavour:mocca' ) ), TRUE );
//no additional mocca enum...
asrt( R::count( 'flavour' ), 2 );
$drink = R::dispense( 'drink' );
$drink->flavour = R::enum( 'flavour:choco' );
R::store( $drink );
//now we have three!
asrt( R::count('flavour'), 3 );
$drink = R::load( 'drink', $drink->id );
asrt( $drink->flavour->equals( R::enum('flavour:mint') ), FALSE );
asrt( $drink->flavour->equals( R::enum('flavour:choco') ), TRUE );
asrt( R::count( 'flavour' ), 4 );
//trash should not affect flavour!
R::trash( $drink );
asrt( R::count( 'flavour' ), 4 );
}
/**
* Test trashAll().
*/
public function testMultiDeleteUpdate()
{
testpack( 'test multi delete and multi update' );
$beans = R::dispenseLabels( 'bean', array( 'a', 'b' ) );
$ids = R::storeAll( $beans );
asrt( (int) R::count( 'bean' ), 2 );
R::trashAll( R::batch( 'bean', $ids ) );
asrt( (int) R::count( 'bean' ), 0 );
testpack( 'test assocManager check' );
$rb = new OODB( R::getWriter() );
try {
$rb->getAssociationManager();
fail();
} catch ( RedException $e ) {
pass();
}
}
/**
* Test Bean identity equality.
*/
public function testBeanIdentityEquality() {
$beanA = R::dispense( 'bean' );
$beanB = R::dispense( 'bean' );
$beanA->id = 1;
$beanB->id = 1;
asrt( $beanA->equals( $beanB ), TRUE );
asrt( $beanB->equals( $beanA ), TRUE );
asrt( $beanA->equals( $beanA ), TRUE );
asrt( $beanB->equals( $beanB ), TRUE );
$beanB->id = 2;
asrt( $beanA->equals( $beanB ), FALSE );
asrt( $beanB->equals( $beanA ), FALSE );
$beanA->id = '2';
asrt( $beanA->equals( $beanB ), TRUE );
asrt( $beanB->equals( $beanA ), TRUE );
$beanB = R::dispense( 'carrot' );
$beanB->id = $beanA->id;
asrt( $beanA->equals( $beanB ), FALSE );
asrt( $beanB->equals( $beanA ), FALSE );
}
/**
* Test if adding SimpleModles to a shared list will auto unbox them.
*/
public function testSharedListsAutoUnbox()
{
$boxedBean = R::dispense( 'boxedbean' );
$bean = R::dispense( 'bean' );
$model = new SimpleModel();
$model->loadBean($boxedBean);
$bean->ownBoxedbeanList[] = $model;
try {
R::store( $bean );
pass();
} catch ( \Exception $e ) {
fail();
}
}
/**
* Test if we can obtain a database server version string
* from the Facade.
*/
public function testGetDatabaseServerVersion()
{
$version = R::getDatabaseServerVersion();
asrt(is_string($version), TRUE);
asrt(strlen($version)>0, TRUE);
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
/**
* Namedparams
*
* Tests whether we can use named parameters in queries and
* SQL snippets.
*
* @file RedUNIT/Base/Namedparams.php
* @desc Test whether you can use named parameters in SQL snippets.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Namedparams extends Base
{
/**
* Test usage of named parameters in SQL snippets.
* Issue #299 on Github.
*
* @return void
*/
public function testNamedParamsInSnippets()
{
testpack( 'Test whether we can use named parameters in SQL snippets.' );
R::nuke();
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$book->title = 'book';
$book->sharedPage[] = $page;
R::store($book);
//should not give error like: Uncaught [HY093] - SQLSTATE[HY093]: Invalid parameter number: mixed named and positional parameters
$books = $page->withCondition(' title = :title ', array( ':title' => 'book' ) )->sharedBook;
asrt( count( $books ), 1 );
//should not give error...
$books = $page->withCondition( ' title = :title ', array( ':title' => 'book' ) )->sharedBook;
asrt( count( $books ), 1 );
R::nuke();
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$book->title = 'book';
$book->comment = 'comment';
$page->title = 'page';
$book->ownPage[] = $page;
R::store( $book );
//should also not give error..
$count = $book->countOwn( 'page' );
asrt( $count, 1 );
$book = $book->fresh();
//should also not give error..
$count = $book->withCondition( ' title = ? ', array( 'page' ) )->countOwn( 'page' );
asrt( $count, 1 );
$book = $book->fresh();
//should also not give error..
$count = $book->withCondition( ' title = :title ', array( ':title' => 'page' ) )->countOwn( 'page' );
asrt( $count, 1 );
$book = $book->fresh();
$pages = $book->withCondition( ' title = :title ', array( ':title' => 'page' ) )->ownPage;
asrt( count( $pages ), 1 );
//test with duplicate slots...
$page = reset( $pages );
$page2 = R::dispense( 'page' );
$page2->ownPage[] = $page;
R::store( $page2 );
$page2 = $page2->fresh();
$pages = $page2->withCondition( ' title = :title ', array( ':title' => 'page' ) )->ownPage;
asrt( count( $pages ), 1 );
//test with find()
$books = R::getRedBean()->find( 'book',
array(
'title' => array('book')),
' AND title = :title ', array(':title'=>'book'));
asrt( count( $books ), 1 );
$books = R::getRedBean()->find( 'book',
array(
'title' => array('book', 'book2'),
'comment' => array('comment', 'comment2')),
' AND title = :title ', array(':title'=>'book'));
asrt( count( $books ), 1 );
//just check numeric works as well...
$books = R::getRedBean()->find( 'book',
array(
'title' => array('book', 'book2'),
'comment' => array('comment', 'comment2')),
' AND title = ? ', array('book'));
asrt( count( $books ), 1 );
//just extra check to verify glue works
$books = R::getRedBean()->find( 'book',
array(
'title' => array('book', 'book2'),
'comment' => array('comment', 'comment2')),
' ORDER BY id ');
asrt( count( $books ), 1 );
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
/**
* Nuke
*
* Tests the nuke() command. The nuke command empties an entire database
* and should also be capable of removing all foreign keys, constraints and
* indexes.
*
* @file RedUNIT/Base/Nuke.php
* @desc Test the nuke() function.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Nuke extends Base
{
/**
* Test wipeAll().
*
* @return void
*/
public function testWipe()
{
R::nuke();
$bean = R::dispense( 'bean' );
asrt( count( R::inspect() ), 0 );
R::store( $bean );
asrt( count( R::inspect() ), 1 );
asrt( R::count( 'bean' ), 1 );
R::debug(1);
R::wipeAll();
asrt( count( R::inspect() ), 1 );
asrt( R::count( 'bean' ), 0 );
R::wipeAll( TRUE );
asrt( count( R::inspect() ), 0 );
asrt( R::count( 'bean' ), 0 );
}
/**
* Nuclear test suite.
*
* @return void
*/
public function testNuke()
{
$bean = R::dispense( 'bean' );
R::store( $bean );
asrt( count( R::getWriter()->getTables() ), 1 );
R::nuke();
asrt( count( R::getWriter()->getTables() ), 0 );
$bean = R::dispense( 'bean' );
R::store( $bean );
asrt( count( R::getWriter()->getTables() ), 1 );
R::freeze();
R::nuke();
// No effect
asrt( count( R::getWriter()->getTables() ), 1 );
R::freeze( FALSE );
}
/**
* Test noNuke().
*
* @return void
*/
public function testNoNuke() {
$bean = R::dispense( 'bean' );
R::store( $bean );
asrt( count( R::getWriter()->getTables() ), 1 );
R::noNuke( TRUE );
try {
R::nuke();
fail();
} catch( \Exception $e ) {
pass();
}
asrt( count( R::getWriter()->getTables() ), 1 );
R::noNuke( FALSE );
R::nuke();
asrt( count( R::getWriter()->getTables() ), 0 );
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\OODB as OODB;
/**
* Observers
*
* Tests the basic observer pattern used in RedBeanPHP.
*
* @file RedUNIT/Base/Observers.php
* @desc Tests the observer pattern in RedBeanPHP.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Observers extends Base
{
/**
* Test RedBeanPHP observers.
*
* @return void
*/
public function testObserverMechanism()
{
$toolbox = R::getToolBox();
$adapter = $toolbox->getDatabaseAdapter();
$writer = $toolbox->getWriter();
$redbean = $toolbox->getRedBean();
asrt( ( $adapter instanceof DBAdapter ), TRUE );
asrt( ( $writer instanceof QueryWriter ), TRUE );
asrt( ( $redbean instanceof OODB ), TRUE );
$observable = new \ObservableMock();
$observer = new \ObserverMock();
$observable->addEventListener( "event1", $observer );
$observable->addEventListener( "event3", $observer );
$observable->test( "event1", "testsignal1" );
asrt( $observer->event, "event1" );
asrt( $observer->info, "testsignal1" );
$observable->test( "event2", "testsignal2" );
asrt( $observer->event, "event1" );
asrt( $observer->info, "testsignal1" );
$observable->test( "event3", "testsignal3" );
asrt( $observer->event, "event3" );
asrt( $observer->info, "testsignal3" );
}
}

View File

@@ -0,0 +1,262 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
/**
* Partial Beans
*
* Test whether we can use 'partial bean mode'.
* In 'partial bean mode' only changed properties of a bean
* will get updated in the database. This feature has been designed
* to deal with 'incompatible table fields'. The specific case that
* led to this feature is available as test Postgres/Partial and is
* based on Github issue #547. This test only covers the basic functionality.
*
* @file RedUNIT/Base/Partial.php
* @desc Tests Partial Beans Mode
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Partial extends Base {
/**
* Github Issue #754.
* If I set up the default values to some specific columns,
* these columns can not act the same expectation in partial bean mode. #754.
* "When I upgrade my code to Redbean 5.4 and turn on the partial bean mode,
* I found this issue I mentioned.
* As Redbean recommends, I set up the default values
* to some specific columns before I use them.
* And then I use the partial bean mode to store
* the columns updated rather than the entire bean.
* But I found if I set up the default values,
* it will change the changelist in the bean which is the
* foundation of the partial bean mode."
*
* @return void
*/
public function testChangeListIssue()
{
R::nuke();
R::usePartialBeans( TRUE );
\Model_Coffee::$defaults = array(
'strength' => 'strong',
'beans' => 'Arabica',
'preparation' => 'Kettle'
);
$coffee = R::dispense('coffee');
$changelist = $coffee->getMeta('changelist');
asrt( count( $changelist), 3 );
$coffee->preparation = 'Espresso';
$changelist = $coffee->getMeta('changelist');
asrt( count( $changelist), 4 );
$id = R::store( $coffee );
$coffee = R::load( 'coffee', $id );
$changelist = $coffee->getMeta('changelist');
asrt( count( $changelist), 0 );
}
/**
* Github Issue #754.
* The importRow() function should clear the changeList.
*
* @return void
*/
public function testChangeListImportRow()
{
R::usePartialBeans( TRUE );
$bean = R::dispense( 'bean' );
asrt( count( $bean->getMeta('changelist') ), 0 );
$bean->property = 'abc';
asrt( count( $bean->getMeta('changelist') ), 1 );
$bean->importRow( array( 'property' => 123 ) );
asrt( count( $bean->getMeta('changelist') ), 0 );
}
/**
* Tests the basic scenarios for Partial Beans.
*
* @return void
*/
public function testPartialBeans()
{
R::nuke();
R::usePartialBeans( FALSE );
$book = R::dispense( 'book' );
$book->title = 'A book about half beans';
$book->price = 99;
$book->pages = 60;
$id = R::store( $book );
/* test baseline condition */
$book = R::load( 'book', $id );
asrt( $book->title, 'A book about half beans' );
asrt( (integer) $book->pages, 60 );
$book->pages++;
R::exec( 'UPDATE book SET title = ? ', array( 'Another Title' ) );
$id = R::store( $book );
$book = R::load( 'book', $id );
asrt( $book->title, 'A book about half beans' );
asrt( (integer) $book->pages, 61 );
/* now test partial beans mode */
R::usePartialBeans( TRUE );
$book->pages++;
R::exec( 'UPDATE book SET title = ? ', array( 'Another Title' ) );
$id = R::store( $book );
$book = R::load( 'book', $id );
asrt( $book->title, 'Another Title' );
asrt( (integer) $book->pages, 62 );
/* mask should be cleared... */
R::exec( 'UPDATE book SET pages = ? ', array( 64 ) );
$book->price = 92;
$id = R::store( $book );
$book = R::load( 'book', $id );
asrt( (integer) $book->pages, 64 );
asrt( $book->title, 'Another Title' );
asrt( (integer) $book->price, 92 );
R::usePartialBeans( FALSE );
}
/**
* Tests whether we can pass a list of specific bean types
* to apply partial saving to.
*
* @return void
*/
public function testPartialBeansTypeList()
{
R::nuke();
R::usePartialBeans( array( 'notbook' ) );
$book = R::dispense( 'book' );
$book->title = 'A book about half beans';
$book->price = 99;
$book->pages = 60;
$id = R::store( $book );
/* test baseline condition */
$book = R::load( 'book', $id );
asrt( $book->title, 'A book about half beans' );
asrt( (integer) $book->pages, 60 );
$book->pages++;
R::exec( 'UPDATE book SET title = ? ', array( 'Another Title' ) );
$id = R::store( $book );
$book = R::load( 'book', $id );
asrt( $book->title, 'A book about half beans' );
asrt( (integer) $book->pages, 61 );
/* now test partial beans mode */
R::usePartialBeans( array( 'book', 'more' ) );
$book->pages++;
R::exec( 'UPDATE book SET title = ? ', array( 'Another Title' ) );
$id = R::store( $book );
$book = R::load( 'book', $id );
asrt( $book->title, 'Another Title' );
asrt( (integer) $book->pages, 62 );
/* mask should be cleared... */
R::exec( 'UPDATE book SET pages = ? ', array( 64 ) );
$book->price = 92;
$id = R::store( $book );
$book = R::load( 'book', $id );
asrt( (integer) $book->pages, 64 );
asrt( $book->title, 'Another Title' );
asrt( (integer) $book->price, 92 );
R::usePartialBeans( FALSE );
}
/**
* Tests the basic scenarios for Partial Beans.
* Frozen.
*
* @return void
*/
public function testPartialBeansFrozen()
{
R::nuke();
R::usePartialBeans( FALSE );
$book = R::dispense( 'book' );
$book->title = 'A book about half beans';
$book->price = 99;
$book->pages = 60;
$id = R::store( $book );
/* test baseline condition */
$book = R::load( 'book', $id );
asrt( $book->title, 'A book about half beans' );
asrt( (integer) $book->pages, 60 );
$book->pages++;
R::exec( 'UPDATE book SET title = ? ', array( 'Another Title' ) );
$id = R::store( $book );
$book = R::load( 'book', $id );
asrt( $book->title, 'A book about half beans' );
asrt( (integer) $book->pages, 61 );
/* now test partial beans mode */
R::freeze( TRUE );
R::usePartialBeans( TRUE );
$book->pages++;
R::exec( 'UPDATE book SET title = ? ', array( 'Another Title' ) );
$id = R::store( $book );
$book = R::load( 'book', $id );
asrt( $book->title, 'Another Title' );
asrt( (integer) $book->pages, 62 );
/* mask should be cleared... */
R::exec( 'UPDATE book SET pages = ? ', array( 64 ) );
$book->price = 92;
$id = R::store( $book );
$book = R::load( 'book', $id );
asrt( (integer) $book->pages, 64 );
asrt( $book->title, 'Another Title' );
asrt( (integer) $book->price, 92 );
R::usePartialBeans( FALSE );
R::freeze( FALSE );
}
/**
* Tests whether we can pass a list of specific bean types
* to apply partial saving to.
* Frozen.
*
* @return void
*/
public function testPartialBeansTypeListFrozen()
{
R::nuke();
R::usePartialBeans( array( 'notbook' ) );
$book = R::dispense( 'book' );
$book->title = 'A book about half beans';
$book->price = 99;
$book->pages = 60;
$id = R::store( $book );
/* test baseline condition */
$book = R::load( 'book', $id );
asrt( $book->title, 'A book about half beans' );
asrt( (integer) $book->pages, 60 );
$book->pages++;
R::exec( 'UPDATE book SET title = ? ', array( 'Another Title' ) );
$id = R::store( $book );
$book = R::load( 'book', $id );
asrt( $book->title, 'A book about half beans' );
asrt( (integer) $book->pages, 61 );
/* now test partial beans mode */
R::freeze( TRUE );
R::usePartialBeans( array( 'book', 'more' ) );
$book->pages++;
R::exec( 'UPDATE book SET title = ? ', array( 'Another Title' ) );
$id = R::store( $book );
$book = R::load( 'book', $id );
asrt( $book->title, 'Another Title' );
asrt( (integer) $book->pages, 62 );
/* mask should be cleared... */
R::exec( 'UPDATE book SET pages = ? ', array( 64 ) );
$book->price = 92;
$id = R::store( $book );
$book = R::load( 'book', $id );
asrt( (integer) $book->pages, 64 );
asrt( $book->title, 'Another Title' );
asrt( (integer) $book->price, 92 );
R::usePartialBeans( FALSE );
R::freeze( FALSE );
}
}

View File

@@ -0,0 +1,126 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException as RedException;
/**
* Performance
*
* This performance test is used with runperf and xdebug profiler.
*
* @file RedUNIT/Base/Performance.php
* @desc Performance testing.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Performance extends Base
{
/**
* Setup
*
* @return void
*/
public function setup()
{
R::nuke();
//Prepare structure
$book = R::dispense( 'book' );
$book->title = 'book';
$pages = R::dispense( 'page', 10 );
foreach( $pages as $page ) {
$page->content = 'lorem ipsum';
$page->title = 'data';
$page->sequence = 'data';
$page->order = 'data';
$page->columns = 'data';
$page->paragraphs = 'data';
$page->paragraphs1 = 'data';
$page->paragraphs2 = 'data';
$page->paragraphs3 = 'data';
$page->paragraphs4 = 'data';
}
$book->xownPageList = $pages;
$tags = R::dispense( 'tag', 6 );
foreach( $tags as $tag ) {
$tag->label = 'tag';
}
$book->sharedTagList = $tags;
R::store( $book );
}
/**
* CRUD performance.
*
* @return void
*/
public function crud()
{
R::freeze( TRUE );
$book = R::dispense( 'book' );
$book->title = 'Book';
$page = R::dispense('page');
$page->content = 'Content';
$page->title = 'data';
$page->sequence = 'data';
$page->order = 'data';
$page->columns = 'data';
$page->paragraphs = 'data';
$page->paragraphs1 = 'data';
$page->paragraphs2 = 'data';
$page->paragraphs3 = 'data';
$page->paragraphs4 = 'data';
$tag = R::dispense('tag');
$tag->label = 'Tag ';
$book->noLoad()->ownPage[] = $page;
$book->noLoad()->sharedTag[] = $tag;
R::store( $book );
$book = $book->fresh();
$book->ownPage;
$book->sharedTag;
R::trash( $book );
}
/**
* CRUD performance Array Access.
*
* @return void
*/
public function crudaa()
{
R::freeze( TRUE );
$book = R::dispense( 'book' );
$book['title'] = 'Book';
$page = R::dispense('page');
$page['content'] = 'Content';
$page['title'] = 'data';
$page['sequence'] = 'data';
$page['order'] = 'data';
$page['columns'] = 'data';
$page['paragraphs'] = 'data';
$page['paragraphs1'] = 'data';
$page['paragraphs2'] = 'data';
$page['paragraphs3'] = 'data';
$page['paragraphs4'] = 'data';
$tag = R::dispense('tag');
$tag['label'] = 'Tag ';
$book->ownPage[] = $page;
$book->noLoad()->sharedTag[] = $tag;
R::store( $book );
$book = $book->fresh();
$book->ownPage;
$book->sharedTag;
R::trash( $book );
}
}

View File

@@ -0,0 +1,269 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
R::ext('xdispense', function($type){
return R::getRedBean()->dispense($type);
});
define('BOOK', 'tbl_book');
define('AUTHOR', 'tbl_author');
define('COAUTHOR', 'coAuthor');
define('FRIEND', 'tbl_friend');
define('PUBLISHER', 'tbl_publisher');
define('BOOKLIST', 'ownTbl_book');
define('FRIENDLIST', 'sharedTbl_friend');
/**
* Prefixes
*
* Tests whether we can use tables with prefixes.
* Some people seem to like that.
*
* @file RedUNIT/Base/Prefixes.php
* @desc Tests whether you can use RedBeanPHP with table prefixes.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Prefixes extends Base
{
/**
* Test prerequisites.
*/
public function testPrerequisites()
{
R::nuke();
$bean = R::xdispense( 'type_with_underscore' );
asrt( ( $bean instanceof OODBBean ), TRUE );
asrt( constant( 'BOOK' ), 'tbl_book' );
asrt( constant( 'AUTHOR' ), 'tbl_author' );
asrt( constant( 'PUBLISHER' ), 'tbl_publisher' );
asrt( constant( 'FRIEND' ), 'tbl_friend' );
asrt( constant( 'BOOKLIST' ), 'ownTbl_book' );
asrt( constant( 'FRIENDLIST' ), 'sharedTbl_friend' );
asrt( constant( 'COAUTHOR' ), 'coAuthor' );
}
/**
* Test basic CRUD operations.
*/
public function testBasicOperations()
{
//Can we dispense a naughty bean? (with underscore)
$author = R::xdispense( AUTHOR );
asrt( ( $author instanceof OODBBean ), TRUE );
asrt( $author->getMeta('type'), AUTHOR );
$author->name = 'Mr. Quill';
$book = R::xdispense( BOOK );
asrt( ( $book instanceof OODBBean ), TRUE );
asrt( $book->getMeta('type'), BOOK );
$book->title = 'Good Stories';
$friend = R::xdispense( FRIEND );
$friend->name = 'Muse';
asrt( ( $friend instanceof OODBBean ), TRUE );
asrt( $friend->getMeta('type'), FRIEND );
$publisher = R::xdispense( PUBLISHER );
$publisher->name = 'Good Books';
asrt( ( $publisher instanceof OODBBean ), TRUE );
asrt( $publisher->getMeta('type'), PUBLISHER );
asrt( is_array( $author->{BOOKLIST} ), TRUE );
//add books to the book list using the constant
$author->{BOOKLIST}[] = $book;
asrt( count( $author->{BOOKLIST} ), 1 );
//can we also add friends? (N-M)
$author->{FRIENDLIST}[] = $friend;
$author->{PUBLISHER} = $publisher;
$id = R::store( $author );
asrt( ( $id > 0 ), TRUE );
$author = $author->fresh();
//Can we add another friend after reload?
$author->{FRIENDLIST}[] = R::xdispense( FRIEND )->setAttr( 'name', 'buddy' );
R::store($author);
$author = $author->fresh();
//Now check the contents of the bean, its lists (books,friends) and parent (publisher)
asrt( $author->name, 'Mr. Quill' );
asrt( count( $author->{BOOKLIST} ), 1 );
$firstBook = reset( $author->{BOOKLIST} );
asrt( $firstBook->title, 'Good Stories' );
asrt( count( $author->{FRIENDLIST} ), 2 );
$firstFriend = reset( $author->{FRIENDLIST} );
$parent = $author->{PUBLISHER};
asrt( ( $parent instanceof OODBBean ), TRUE );
$tables = R::inspect();
//have all tables been prefixed?
foreach( $tables as $table ) asrt( strpos( $table, 'tbl_' ), 0 );
//Can we make an export?
$export = R::exportAll( R::findOne( AUTHOR ), TRUE );
$export = reset( $export );
asrt( isset( $export[ PUBLISHER ] ), TRUE );
asrt( isset( $export[ BOOKLIST ] ), TRUE );
asrt( isset( $export[ FRIENDLIST ] ), TRUE );
asrt( isset( $export[ 'ownBook' ] ), FALSE );
asrt( isset( $export[ 'sharedFriend' ] ), FALSE );
asrt( isset( $export[ 'publisher' ] ), FALSE );
//Can we duplicate?
$copy = R::dup( $author );
$copy->name = 'Mr. Clone';
R::store( $copy );
$copy = $copy->fresh();
asrt( $copy->name, 'Mr. Clone' );
asrt( count( $copy->{BOOKLIST} ), 1 );
$firstBook = reset( $copy->{BOOKLIST} );
asrt( $firstBook->title, 'Good Stories' );
asrt( count( $copy->{FRIENDLIST} ), 2 );
$firstFriend = reset( $copy->{FRIENDLIST} );
$parent = $copy->{PUBLISHER};
asrt( ( $parent instanceof OODBBean ), TRUE );
//Can we count?
asrt( R::count( AUTHOR ), 2 );
$copy = $copy->fresh();
asrt( $copy->countOwn( BOOK ), 1 );
asrt( $copy->countShared( FRIEND ), 2 );
//Can we delete?
R::trash( $author );
asrt( R::count( AUTHOR ), 1 );
//Can we nuke?
R::nuke();
asrt( R::count( AUTHOR ), 0 );
asrt( count( R::inspect() ), 0 );
}
/**
* Test basic operations in frozen mode.
*/
public function testBasicOperationsFrozen()
{
R::nuke();
$author = R::xdispense( AUTHOR );
$author->name = 'Mr. Quill';
$book = R::xdispense( BOOK );
$book->title = 'Good Stories';
$book2 = R::xdispense( BOOK );
$book2->title = 'Good Stories 2';
$friend = R::xdispense( FRIEND );
$friend->name = 'Muse';
$publisher = R::xdispense( PUBLISHER );
$publisher->name = 'Good Books';
$author->{BOOKLIST} = array( $book, $book2 );
$author->{FRIENDLIST}[] = $friend;
$author->{PUBLISHER} = $publisher;
$coAuthor = R::xdispense( AUTHOR );
$coAuthor->name = 'Xavier';
$book2->{COAUTHOR} = $coAuthor;
R::store( $author );
R::freeze( TRUE );
asrt( $author->name, 'Mr. Quill' );
asrt( count( $author->{BOOKLIST} ), 2 );
$firstBook = reset( $author->{BOOKLIST} );
asrt( $firstBook->title, 'Good Stories' );
asrt( count( $author->{FRIENDLIST} ), 1 );
$firstFriend = reset( $author->{FRIENDLIST} );
$parent = $author->{PUBLISHER};
asrt( ( $parent instanceof OODBBean ), TRUE );
$tables = R::inspect();
//have all tables been prefixed?
foreach( $tables as $table ) asrt( strpos( $table, 'tbl_' ), 0 );
//Can we make an export?
$export = R::exportAll( R::findOne( AUTHOR ), TRUE );
$export = reset( $export );
asrt( isset( $export[ PUBLISHER ] ), TRUE );
asrt( isset( $export[ BOOKLIST ] ), TRUE );
asrt( isset( $export[ FRIENDLIST ] ), TRUE );
asrt( isset( $export[ 'ownBook' ] ), FALSE );
asrt( isset( $export[ 'sharedFriend' ] ), FALSE );
asrt( isset( $export[ 'publisher' ] ), FALSE );
R::freeze( FALSE );
}
/**
* Test conditions and aliases.
*/
public function testConditionsAndAliases()
{
R::nuke();
$author = R::xdispense( AUTHOR );
$author->name = 'Mr. Quill';
$book = R::xdispense( BOOK );
$book->title = 'Good Stories';
$book2 = R::xdispense( BOOK );
$book2->title = 'Good Stories 2';
$friend = R::xdispense( FRIEND );
$friend->name = 'Muse';
$publisher = R::xdispense( PUBLISHER );
$publisher->name = 'Good Books';
$author->{BOOKLIST} = array( $book, $book2 );
$author->{FRIENDLIST}[] = $friend;
$author->{PUBLISHER} = $publisher;
$coAuthor = R::xdispense( AUTHOR );
$coAuthor->name = 'Xavier';
$book2->{COAUTHOR} = $coAuthor;
R::store( $author );
$author = $author->fresh();
asrt( R::count( AUTHOR ), 2 );
//Can we use with and withCondition?
asrt( count( $author->{BOOKLIST} ), 2 );
asrt( count( $author->with(' LIMIT 1 ')->{BOOKLIST} ), 1 );
asrt( count( $author->withCondition(' title LIKE ? ', array( '%2%' ) )->{BOOKLIST} ), 1 );
//Can we use an alias?
$book2 = $book2->fresh();
asrt( $book2->fetchAs( AUTHOR )->{COAUTHOR}->name, 'Xavier' );
$coAuthor = $book2->fetchAs( AUTHOR )->{COAUTHOR}->fresh();
asrt( count( $coAuthor->alias( COAUTHOR )->{BOOKLIST} ), 1 );
}
/**
* Test prettier tables using via().
*/
public function testViaPrettification()
{
R::nuke();
R::renameAssociation( 'tbl_author_tbl_friend', 'tbl_author_friend' );
$author = R::xdispense( AUTHOR );
$author->name = 'Mr. Quill';
$friend = R::xdispense( FRIEND );
$friend->name = 'Muse';
$author->{FRIENDLIST}[] = $friend;
$id = R::store( $author );
//print_r(R::inspect()); exit;
$author = R::load( AUTHOR, $id );
$tables = array_flip( R::inspect() );
asrt( isset( $tables[ 'tbl_author_friend' ] ), TRUE );
asrt( isset( $tables[ 'tbl_author_tbl_friend' ] ), FALSE );
asrt( count( $author->{FRIENDLIST} ), 1 );
AQueryWriter::clearRenames();
}
/**
* Test self-referential N-M relations.
*/
public function testSelfRefNM()
{
R::nuke();
$friend1 = R::xdispense( FRIEND );
$friend1->name = 'f1';
$friend2 = R::xdispense( FRIEND );
$friend2->name = 'f2';
$friend3 = R::xdispense( FRIEND );
$friend3->name = 'f3';
$friend1->{FRIENDLIST} = array( $friend2, $friend3 );
$friend3->{FRIENDLIST} = array( $friend1 );
R::storeAll( array( $friend1, $friend2, $friend3 ) );
$friend1 = $friend1->fresh();
$friend2 = $friend2->fresh();
$friend3 = $friend3->fresh();
asrt( count( $friend1->{FRIENDLIST} ), 2 );
asrt( count( $friend2->{FRIENDLIST} ), 1 );
asrt( count( $friend3->{FRIENDLIST} ), 1 );
$friend = reset( $friend3->{FRIENDLIST} );
asrt( $friend->name, 'f1' );
}
}

View File

@@ -0,0 +1,233 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\Util\MatchUp;
use RedBeanPHP\Util\Look;
/**
* MatchUp
*
* Tests the MatchUp functionality.
* Tired of creating login systems and password-forget systems?
* MatchUp is an ORM-translation of these kind of problems.
* A matchUp is a match-and-update combination in terms of beans.
* Typically login related problems are all about a match and
* a conditional update.
*
* @file RedUNIT/Base/Matchup.php
* @desc Tests MatchUp
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Productivity extends Base
{
/**
* Test matchup
*
* @return void
*/
public function testPasswordForget()
{
R::nuke();
$account = R::dispense( 'account' );
$account->uname = 'Shawn';
$account->pass = sha1( 'sheep' );
$account->archived = 0;
$account->attempts = 1;
R::store( $account );
$matchUp = new MatchUp( R::getToolbox() );
/* simulate a token generation script */
$account = NULL;
$didGenToken = $matchUp->matchUp( 'account', ' uname = ? AND archived = ?', array('Shawn',0), array(
'token' => sha1(rand(0,9000) . time()),
'tokentime' => time()
), NULL, $account );
asrt( $didGenToken, TRUE );
asrt( !is_null( $account->token ) , TRUE );
asrt( !is_null( $account->tokentime ) , TRUE );
/* simulate a password reset script */
$newpass = '1234';
$didResetPass = $matchUp->matchUp( 'account', ' token = ? AND tokentime > ? ', array( $account->token, time()-100 ), array(
'pass' => $newpass,
'token' => ''
), NULL, $account );
asrt( $account->pass, '1234' );
asrt( $account->token, '' );
/* simulate a login */
$didFindUsr = $matchUp->matchUp( 'account', ' uname = ? ', array( 'Shawn' ), array(
'attempts' => function( $acc ) {
return ( $acc->pass !== '1234' ) ? ( $acc->attempts + 1 ) : 0;
}
), NULL, $account);
asrt( $didFindUsr, TRUE );
asrt( $account->attempts, 0 );
/* Login failure */
$didFindUsr = $matchUp->matchUp( 'account', ' uname = ? ', array( 'Shawn' ), array(
'attempts' => function( $acc ) {
return ( $acc->pass !== '1236' ) ? ( $acc->attempts + 1 ) : 0;
}
), NULL, $account);
/* Create user if not exists */
$didFindUsr = R::matchUp( 'account', ' uname = ? ', array( 'Anonymous' ), array(
), array(
'uname' => 'newuser'
), $account);
asrt( $didFindUsr, FALSE );
asrt( $account->uname, 'newuser' );
}
/**
* Tests the look function.
*/
public function testLook()
{
R::nuke();
$beans = R::dispenseAll( 'color*3' );
list( $red, $green, $blue ) = $beans[0];
$red->name = 'red';
$green->name = 'green';
$blue->name = 'blue';
$red->thevalue = 'r';
$green->thevalue = 'g';
$blue->thevalue = 'b';
R::storeAll( array( $red, $green, $blue ) );
$look = R::getLook();
asrt( ( $look instanceof Look ), TRUE );
$str = R::getLook()->look( 'SELECT * FROM color WHERE thevalue != ? ORDER BY thevalue ASC', array( 'g' ), array( 'thevalue', 'name' ),
'<option value="%s">%s</option>', 'strtoupper', "\n"
);
asrt( $str,
"<option value=\"B\">BLUE</option>\n<option value=\"R\">RED</option>"
);
$str = R::look( 'SELECT * FROM color WHERE thevalue != ? ORDER BY thevalue ASC', array( 'g' ), array( 'thevalue', 'name' ),
'<option value="%s">%s</option>', 'strtoupper', "\n"
);
asrt( $str,
"<option value=\"B\">BLUE</option>\n<option value=\"R\">RED</option>"
);
}
/**
* Test Bean differ.
*/
public function testDiff()
{
R::nuke();
$ad = R::dispense( 'ad' );
$ad->title = 'dog looking for new home';
$ad->created = time();
$ad->modified = time();
$ad->ownDog[] = R::dispense( 'dog' );
$ad->ownDog[0]->name = 'Dweep';
$ad->ownDog[0]->color = 'green';
$ad->author = R::dispense('user');
$ad->author->name = 'John';
R::store( $ad );
$ad->title = 'green dog';
$diff = R::diff( $ad->fresh(), $ad );
/* simple case, property changed */
var_dump( $diff );
asrt( is_array( $diff ), TRUE );
asrt( count( $diff ), 1 );
asrt( $diff['ad.1.title'][0], 'dog looking for new home' );
asrt( $diff['ad.1.title'][1], 'green dog' );
/* test use specific format */
$diff = R::diff( $ad->fresh(), $ad, array(), '%1$s.%3$s' );
asrt( is_array( $diff ), TRUE );
asrt( count( $diff ), 1 );
asrt( $diff['ad.title'][0], 'dog looking for new home' );
asrt( $diff['ad.title'][1], 'green dog' );
/* skip created modified */
$ad = $ad->fresh();
$ad->modified = 111;
$ad->created = 111;
$diff = R::diff( $ad->fresh(), $ad );
asrt( is_array( $diff ), TRUE );
asrt( count( $diff ), 0 );
/* unless we set anothe filter */
$ad = $ad->fresh();
$ad->modified = 111;
$ad->created = 111;
$ad->name = 'x';
$diff = R::diff( $ad->fresh(), $ad, array( 'name' ) );
asrt( is_array( $diff ), TRUE );
asrt( count( $diff ), 2 );
asrt( $diff['ad.1.modified'][1], 111 );
asrt( $diff['ad.1.created'][1], 111 );
$ad = $ad->fresh();
/* also diff changes in related beans */
$ad->fetchAs('user')->author->name = 'Fred';
$dog = reset( $ad->ownDog );
$dog->color = 999;
$old = $ad->fresh();
$old->ownDog;
$old->fetchAs('user')->author;
$diff = R::diff( $ad, $old );
asrt( is_array( $diff ), TRUE );
asrt( count( $diff ), 2 );
asrt( $diff['ad.1.ownDog.1.color'][1], 'green' );
asrt( $diff['ad.1.ownDog.1.color'][0], 999 );
asrt( $diff['ad.1.author.1.name'][1], 'John' );
asrt( $diff['ad.1.author.1.name'][0], 'Fred' );
$diff = R::diff( $ad, null );
asrt( is_array( $diff ), TRUE );
asrt( count( $diff ), 0 );
$diff = R::diff( null, $ad );
asrt( is_array( $diff ), TRUE );
asrt( count( $diff ), 0 );
/* demo case */
list($book,$pages) = R::dispenseAll('book,page*2');
$book->title = 'Old Book';
$book->price = 999;
$book->ownPageList = $pages;
$pages[0]->text = 'abc';
$pages[1]->text = 'def';
R::store($book);
$book->title = 'new Book';
$page = end($book->ownPageList);
$page->text = 'new';
$oldBook = $book->fresh();
$oldBook->ownPageList;
$diff = R::diff($oldBook, $book);
}
/**
* Test misc. matchUp scenarios.
*
* @return void
*/
public function testMatchUpMisc()
{
R::nuke();
asrt( R::count( 'bean' ), 0 );
$found = R::matchUp( 'bean', ' id = ? ', array(1), array(), array(
'notfound' => function( $bean ) {
$bean->status = 'not found';
}
) );
asrt( $found, FALSE );
asrt( R::count( 'bean' ), 1 );
$bean = R::findOne( 'bean' );
asrt( $bean->status, 'not found' );
$null = R::matchUp( 'bean', ' id = ? ', array( $bean->id ) );
asrt( is_null( $null ), TRUE );
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
/**
* PullRequest530
*
* Tests whether this specific issue on github has been resolved.
* Pull Request #530 - OODBBean __set() checks if $property is a field link
*
* @file RedUNIT/Base/PullRequest530.php
* @desc Pull Request #530 - OODBBean __set() checks if $property is a field link
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class PullRequest530 extends Base
{
/**
* testPullRequest530
*
* Test to check if OODBBean correctly stores a bean if a field link is set directly.
* (We have to unset the linked bean (if loaded), so that the Repository::processEmbeddedBean
* function call does not update the field link property and overwrites the change
* in the following statement: <code>if ($bean->$linkField != $id) $bean->$linkField = $id;</code>
*
* @return void
*/
public function testPullRequest530()
{
testpack( 'Testing Pull Request #530 - OODBBean __set() checks if $property is a field link' );
R::freeze( FALSE );
$linkedObjects = R::dispense('linked', 2);
R::storeAll($linkedObjects);
$tester = R::dispense('parent');
$tester->linked = $linkedObjects[0];
R::store($tester);
$tester = R::findOne('parent');
asrt($tester->linked->id, $linkedObjects[0]->id);
$tester->linked_id = $linkedObjects[1]->id;
R::store($tester);
asrt($tester->linked->id, $linkedObjects[1]->id);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\OODBBean as OODBBean;
/**
* QuickExport
*
* Tests the Quick Export functionality.
* The Quick Export Utility Class provides functionality to easily
* expose the result of SQL queries as well-known formats like CSV.
*
* @file RedUNIT/Base/Quickexport.php
* @desc Tests Quick Export
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Quickexport extends Base
{
/**
* Test whether we can generate a CSV file from a query.
*
* @return void
*/
public function testCSV()
{
if ( phpversion() < 5.5 || strpos( strtolower( phpversion() ), 'hhvm' ) !== FALSE ) return;
R::store( R::dispense( array( '_type'=>'bean', 'a' => 1, 'b' => 2, 'c' => 3 ) ) );
$path = '/tmp/redbeantest.txt';
R::csv( 'SELECT a,b,c FROM bean', array(), array( 'A', 'B', 'C' ), $path, FALSE );
$csv = file_get_contents( $path );
$expected = "A,B,C\n1,2,3";
asrt( strpos($csv, $expected) !== FALSE, TRUE );
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,221 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException as RedException;
/**
* Tags
*
* Tests RedBeanPHP tagging functionality, should be easy
* to tag beans, collect tags and integrate tags in SQL
* snippets. Tags automatically result in N-M relations, i.e.
* shared lists.
*
* @file RedUNIT/Base/Tags.php
* @desc Tests the tagging of beans.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Tags extends Base
{
public function testTagsCache()
{
R::nuke();
R::getWriter()->setUseCache( TRUE );
list( $beer1, $beer2, $beer3 ) = R::dispense( 'beer', 3 );
$beer1->title = 'b1';
$beer2->title = 'b2';
$beer3->title = 'b3';
R::tag( $beer1, 'stout' );
R::tag( $beer2, 'porter' );
R::tag( $beer3, 'lager,popular' );
$beers = R::tagged( 'beer', 'lager,popular' );
asrt(count($beers),1);
R::exec( 'DELETE FROM beer_tag WHERE beer_id = ? -- keep-cache', array( $beer3->id ) );
$beers = R::tagged( 'beer', 'lager,popular' );
asrt(count($beers),1);
R::getWriter()->setUseCache( FALSE );
$beers = R::tagged( 'beer', 'lager,popular' );
asrt(count($beers),0);
}
/**
* Tests tags with SQL.
*
* @return void
*/
public function testTagsWithSQL()
{
R::nuke();
list( $m1, $m2, $m3 ) = R::dispense( 'movie', 3 );
$m1->title = 'Frankenstein';
$m2->title = 'Fall of the House Usher';
$m3->title = 'Sleepy Hollow';
R::tag($m1, 'horror,gothic');
R::tag($m2, 'horror,gothic,short');
R::tag($m3, 'horror,legend');
asrt( count( R::tagged( 'movie', 'horror' ) ), 3);
asrt( count( R::tagged( 'movie', 'horror', ' LIMIT 2' ) ), 2);
asrt( count( R::tagged( 'movie', 'horror', ' LIMIT ?', array( 2 ) ) ), 2);
asrt( count( R::tagged( 'movie', 'horror', ' ORDER BY movie.title DESC LIMIT ?', array( 2 ) ) ), 2);
asrt( count( R::tagged( 'movie', 'horror,gothic', ' ORDER BY movie.title DESC LIMIT ?', array( 1 ) ) ), 1);
asrt( count( R::tagged( 'movie', 'horror,gothic') ), 3 );
asrt( count( R::taggedAll( 'movie', 'horror,gothic') ), 2 );
asrt( R::countTaggedAll( 'movie', 'horror,gothic'), 2 );
asrt( count( R::tagged( 'movie', 'horror,gothic', ' LIMIT ? ', array( 2 ) ) ), 2 );
asrt( ( R::countTagged( 'movie', 'horror,gothic', ' LIMIT ? ', array( 2 ) ) ), 2 );
asrt( count( R::taggedAll( 'movie', 'horror,gothic', ' LIMIT ? ', array( 2 ) ) ), 2 );
asrt( R::countTaggedAll( 'movie', 'horror,gothic', ' LIMIT ? ', array( 2 ) ), 2 );
asrt( count( R::tagged( 'movie', 'horror,gothic', ' LIMIT ? ', array( 1 ) ) ), 1 );
asrt( ( R::countTagged( 'movie', 'horror,gothic', ' LIMIT ? ', array( 1 ) ) ), 1 );
asrt( count( R::taggedAll( 'movie', 'horror,gothic', ' LIMIT ? ', array( 1 ) ) ), 1 );
asrt( ( R::countTaggedAll( 'movie', 'horror,gothic', ' LIMIT ? ', array( 1 ) ) ), 1 );
asrt( count( R::tagged( 'movie', 'horror,legend', ' LIMIT ? ', array( 1 ) ) ), 1 );
asrt( ( R::countTagged( 'movie', 'horror,legend', ' LIMIT ? ', array( 1 ) ) ), 1 );
asrt( count( R::taggedAll( 'movie', 'horror,legend', ' LIMIT ? ', array( 1 ) ) ), 1 );
asrt( ( R::countTaggedAll( 'movie', 'horror,legend', ' LIMIT ? ', array( 1 ) ) ), 1 );
asrt( count( R::tagged( 'movie', 'gothic,legend', ' LIMIT ? ', array( 1 ) ) ), 1 );
asrt( ( R::countTagged( 'movie', 'gothic,legend', ' LIMIT ? ', array( 1 ) ) ), 1 );
asrt( count( R::taggedAll( 'movie', 'gothic,legend', ' LIMIT ? ', array( 1 ) ) ), 0 );
asrt( ( R::countTaggedAll( 'movie', 'gothic,legend', ' LIMIT ? ', array( 1 ) ) ), 0 );
asrt( count( R::tagged( 'movie', 'romance', ' LIMIT ? ', array( 1 ) ) ), 0 );
asrt( ( R::countTagged( 'movie', 'romance', ' LIMIT ? ', array( 1 ) ) ), 0 );
asrt( count( R::taggedAll( 'movie', 'romance', ' LIMIT ? ', array( 1 ) ) ), 0 );
asrt( ( R::countTaggedAll( 'movie', 'romance', ' LIMIT ? ', array( 1 ) ) ), 0 );
asrt( count( R::tagged( 'movie', 'romance,xmas', ' LIMIT ? ', array( 1 ) ) ), 0 );
asrt( ( R::countTagged( 'movie', 'romance,xmas', ' LIMIT ? ', array( 1 ) ) ), 0 );
asrt( count( R::taggedAll( 'movie', 'romance,xmas', ' LIMIT ? ', array( 1 ) ) ), 0 );
asrt( ( R::countTaggedAll( 'movie', 'romance,xmas', ' LIMIT ? ', array( 1 ) ) ), 0 );
asrt( count( R::tagged( 'movie', 'gothic,short', ' LIMIT ? ', array( 4 ) ) ), 2 );
asrt( ( R::countTagged( 'movie', 'gothic,short', ' LIMIT ? ', array( 4 ) ) ), 2 );
asrt( count( R::taggedAll( 'movie', 'gothic,short', ' LIMIT ? ', array( 4 ) ) ), 1 );
asrt( ( R::countTaggedAll( 'movie', 'gothic,short', ' LIMIT ? ', array( 4 ) ) ), 1 );
asrt( count( R::tagged( 'movie', 'gothic,short', ' LIMIT 4 ' ) ), 2 );
asrt( ( R::countTagged( 'movie', 'gothic,short', ' LIMIT 4 ' ) ), 2 );
asrt( count( R::taggedAll( 'movie', 'gothic,short', ' LIMIT 4 ' ) ), 1 );
asrt( ( R::countTaggedAll( 'movie', 'gothic,short', ' LIMIT 4 ' ) ), 1 );
asrt( count( R::tagged( 'movie', 'gothic,short', ' ORDER BY movie.id DESC LIMIT 4 ' ) ), 2 );
asrt( ( R::countTagged( 'movie', 'gothic,short', ' ORDER BY movie.id DESC LIMIT 4 ' ) ), 2 );
asrt( count( R::taggedAll( 'movie', 'gothic,short', ' ORDER BY movie.id DESC LIMIT 4 ' ) ), 1 );
asrt( ( R::countTaggedAll( 'movie', 'gothic,short', ' ORDER BY movie.id DESC LIMIT 4 ' ) ), 1 );
asrt( count( R::tagged( 'movie', 'short', ' LIMIT ? ', array( 4 ) ) ), 1 );
asrt( ( R::countTagged( 'movie', 'short', ' LIMIT ? ', array( 4 ) ) ), 1 );
asrt( count( R::taggedAll( 'movie', 'short', ' LIMIT ? ', array( 4 ) ) ), 1 );
asrt( ( R::countTaggedAll( 'movie', 'short', ' LIMIT ? ', array( 4 ) ) ), 1 );
asrt( count( R::tagged( 'movie', '', ' LIMIT ? ', array( 4 ) ) ), 0 );
asrt( ( R::countTagged( 'movie', '', ' LIMIT ? ', array( 4 ) ) ), 0 );
asrt( count( R::taggedAll( 'movie', '', ' LIMIT ? ', array( 4 ) ) ), 0 );
asrt( ( R::countTaggedAll( 'movie', '', ' LIMIT ? ', array( 4 ) ) ), 0 );
asrt( count( R::tagged( 'movie', '', ' LIMIT 4 ' ) ), 0 );
asrt( ( R::countTagged( 'movie', '', ' LIMIT 4 ' ) ), 0 );
asrt( count( R::taggedAll( 'movie', '', ' LIMIT 4 ' ) ), 0 );
asrt( ( R::countTaggedAll( 'movie', '', ' LIMIT 4 ' ) ), 0 );
asrt( count( R::tagged( 'movie', '', '' ) ), 0 );
asrt( ( R::countTagged( 'movie', '', '' ) ), 0 );
asrt( count( R::taggedAll( 'movie', '', '' ) ), 0 );
asrt( ( R::countTaggedAll( 'movie', '', '' ) ), 0 );
asrt( count( R::tagged( 'movie', '' ) ), 0 );
asrt( ( R::countTagged( 'movie', '' ) ), 0 );
asrt( count( R::taggedAll( 'movie', '' ) ), 0 );
asrt( ( R::countTaggedAll( 'movie', '' ) ), 0 );
}
/**
* Some basic tests.
*
* @return void
*/
public function testTags()
{
list( $c, $d, $e, $f ) = R::dispense( 'coffee', 4 );
R::tag( $c, 'strong,black' );
R::tag( $d, 'black' );
R::tag( $e, 'strong,sweet' );
R::tag( $f, 'black,strong' );
asrt( count( R::taggedAll( 'coffee', 'strong,sweet' ) ), 1 );
asrt( count( R::taggedAll( 'coffee', 'strong' ) ), 3 );
asrt( count( R::taggedAll( 'coffee', '' ) ), 0 );
asrt( count( R::taggedAll( 'coffee', 'sweet' ) ), 1 );
asrt( count( R::taggedAll( 'coffee', 'sweet,strong' ) ), 1 );
asrt( count( R::taggedAll( 'coffee', 'black,strong' ) ), 2 );
asrt( count( R::taggedAll( 'coffee', array( 'black', 'strong' ) ) ), 2 );
asrt( count( R::taggedAll( 'coffee', 'salty' ) ), 0 );
$blog = R::dispense( 'blog' );
$blog->title = 'testing';
$blog->blog = 'tesing';
R::store( $blog );
$blogpost = ( R::load( "blog", 1 ) );
$post = R::dispense( "post" );
$post->message = "hello";
R::tag( $post, "lousy,smart" );
asrt( implode( ',', R::tag( $post ) ), "lousy,smart" );
R::tag( $post, "clever,smart" );
$tagz = implode( ',', R::tag( $post ) );
asrt( ( $tagz == "smart,clever" || $tagz == "clever,smart" ), TRUE );
R::tag( $blog, array( "smart", "interesting" ) );
asrt( implode( ',', R::tag( $blog ) ), "smart,interesting" );
try {
R::tag( $blog, array( "smart", "interesting", "lousy!" ) );
pass();
} catch ( RedException $e ) {
fail();
}
asrt( implode( ',', R::tag( $blog ) ), "smart,interesting,lousy!" );
R::untag( $blog, array( "smart", "interesting" ) );
asrt( implode( ",", R::tag( $blog ) ), "lousy!" );
asrt( R::hasTag( $blog, array( "lousy!" ) ), TRUE );
asrt( R::hasTag( $blog, array( "lousy!", "smart" ) ), TRUE );
asrt( R::hasTag( $blog, array( "lousy!", "smart" ), TRUE ), FALSE );
R::tag( $blog, FALSE );
asrt( count( R::tag( $blog ) ), 0 );
R::tag( $blog, array( "funny", "comic" ) );
asrt( count( R::tag( $blog ) ), 2 );
R::addTags( $blog, array( "halloween" ) );
asrt( count( R::tag( $blog ) ), 3 );
asrt( R::hasTag( $blog, array( "funny", "commic", "halloween" ), TRUE ), FALSE );
R::unTag( $blog, "funny" );
R::addTags( $blog, "horror" );
asrt( count( R::tag( $blog ) ), 3 );
asrt( R::hasTag( $blog, array( "horror", "commic", "halloween" ), TRUE ), FALSE );
//no double tags
R::addTags( $blog, "horror" );
asrt( R::hasTag( $blog, array( "horror", "commic", "halloween" ), TRUE ), FALSE );
asrt( R::hasTag( $blog, "horror,commic,halloween", TRUE ), FALSE );
asrt( count( R::tag( $blog ) ), 3 );
testpack( "fetch tagged items" );
}
/**
* Fetching tagged items.
*
* @return void
*/
public function fetchTaggedItems()
{
$b = R::dispense( "book" );
$b->title = 'horror';
R::store( $b );
$c = R::dispense( "book" );
$c->title = 'creepy';
R::store( $c );
$d = R::dispense( "book" );
$d->title = "chicklit";
R::store( $d );
R::tag( $b, "horror,classic" );
R::tag( $d, "women,classic" );
R::tag( $c, "horror" );
$x = R::tagged( "book", "classic" );
asrt( count( $x ), 2 );
$x = R::tagged( "book", "classic,horror" );
asrt( count( $x ), 3 );
$x = R::tagged( "book", array( "classic", "horror" ) );
asrt( count( $x ), 3 );
}
}

View File

@@ -0,0 +1,129 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
/**
* Threeway
*
* Tests link/via relations, i.e. N-M tables with additional
* columns.
*
* @file RedUNIT/Base/Threeway.php
* @desc Various tests for 3-way tables or X-way tables.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Threeway extends Base
{
/**
* Test whether we can use threeway tables without being
* bothered by unique constraints.
*
* @return void
*/
public function testUniqueConstraintOnThreeways()
{
AQueryWriter::clearRenames();
R::nuke();
$person = R::dispense( 'person' );
$role = R::dispense( 'role' );
$person->sharedRole[] = $role;
R::store( $person );
$person->link( 'person_role', array(
'unit' => R::dispense('unit')
))->role = $role;
//Can we add a duplicate role now? - No because we started with a simple N-M table
//and unique constraint has been applied accordingly, manually change database.
asrt( R::count( 'person_role' ), 1 );
R::nuke();
$person = R::dispense( 'person' );
$role = R::dispense( 'role' );
$person->via('participant')->sharedRole[] = $role;
R::store( $person );
$person->link( 'participant', array(
'unit' => R::dispense('unit')
))->role = $role;
//Can we add a duplicate role now? - No because we started with a simple N-M table
//and unique constraint has been applied accordingly, manually change database.
asrt( R::count( 'participant' ), 1 );
R::nuke();
$participant = R::dispense( 'participant' );
$person = R::dispense( 'person' );
$role = R::dispense( 'role' );
$unit = R::dispense( 'unit' );
$participant->person = $person;
$participant->role = $role;
$participant->unit = $unit;
R::store( $participant );
$person->link( 'participant', array(
'unit' => R::dispense('unit')
))->role = $role;
R::store( $person );
//Can we add a duplicate role now?
asrt( R::count( 'participant' ), 2 );
AQueryWriter::clearRenames();
}
/**
* Test whether a duplicate bean in the list isnt saved.
* This was an issue with Postgres while testing the threeway tables.
* Postgres returned the ID as a string while other drivers returned
* a numeric value causing different outcome in array_diff when
* calculating the shared additions.
*
* @return void
*/
public function testIssueWithDriverReturnID()
{
AQueryWriter::clearRenames();
R::nuke();
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$book->sharedPageList[] = $page;
R::store( $book );
asrt( R::count( 'page' ), 1 );
$book = $book->fresh();
$book->sharedPageList[] = $page;
R::store( $book );
//don't save the duplicate bean!
asrt( R::count( 'page' ), 1 );
$book = $book->fresh();
$page->item = 2; //even if we change a property ?
$book->sharedPageList[] = $page;
R::store( $book );
foreach( $book->sharedPageList as $listItem) {
asrt( is_string( $listItem->id ), TRUE );
}
//same test but for own-list
R::nuke();
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$book->ownPageList[] = $page;
R::store( $book );
asrt( R::count( 'page' ), 1 );
$book = $book->fresh();
$book->ownPageList[] = $page;
R::store( $book );
//don't save the duplicate bean!
asrt( R::count( 'page' ), 1 );
$book = $book->fresh();
$book->ownPageList[] = $page;
$page->item = 3;
R::store( $book );
//don't save the duplicate bean!
asrt( R::count( 'page' ), 1 );
foreach( $book->ownPageList as $listItem) {
asrt( is_string( $listItem->id ), TRUE );
}
AQueryWriter::clearRenames();
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Trash
*
* Test trashing of beans.
*
* @file RedUNIT/Base/Trash.php
* @desc Tests R::trash()
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Trash extends Base
{
/**
* Test whether R::trash/trashAll() returns the correct
* number of deleted beans.
*
* @return void
*/
public function testTrash()
{
R::nuke();
$id = R::store(R::dispense(array(
'_type'=>'book',
'pages'=>3
)
));
asrt( R::count('book'), 1 );
$n = R::trash(R::findOne('book'));
asrt( $n, 1 );
asrt( R::count('book'), 0 );
list($books) = R::dispenseAll('book*10');
R::storeAll( $books );
asrt( R::count('book'), 10 );
$n = R::trashAll( $books );
asrt( R::count('book'), 0 );
}
}

View File

@@ -0,0 +1,249 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Traverse
*
* Tests whether RedBeanPHP can easily deal with hierarchies
* of beans.
*
* @file RedUNIT/Base/Traverse.php
* @desc Tests traversal functionality
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Traverse extends Base
{
/**
* Very simple traverse case (one-level).
*
* @return void
*/
public function testSimplestTraversal()
{
R::nuke();
$books = R::dispense( 'book', 10 );
$i = 1;
foreach( $books as $book ) {
$book->title = 'Book ' . ( $i++ );
}
$books[5]->marked = TRUE;
$shelf = R::dispense( 'shelf' );
$shelf->ownBook = $books;
$found = NULL;
$shelf->traverse('ownBookList', function( $book ) use ( &$found ) {
if ( $book->marked ) $found = $book;
});
asrt( ( $found->marked == TRUE ), TRUE );
asrt( $found->title, 'Book 6' );
}
/**
* Tests basic traversal.
*
* @return void
*/
public function testBasicTraversal()
{
R::nuke();
$pageA = R::dispense( 'page' )->setAttr( 'title', 'a' );
$pageB = R::dispense( 'page' )->setAttr( 'title', 'b' );
$pageC = R::dispense( 'page' )->setAttr( 'title', 'c' );
$pageD = R::dispense( 'page' )->setAttr( 'title', 'd' );
$pageE = R::dispense( 'page' )->setAttr( 'title', 'e' );
$pageF = R::dispense( 'page' )->setAttr( 'title', 'f' );
$pageG = R::dispense( 'page' )->setAttr( 'title', 'g' );
$pageH = R::dispense( 'page' )->setAttr( 'title', 'h' );
$pageA->ownPage = array( $pageB, $pageC );
$pageB->ownPage = array( $pageD );
$pageC->ownPage = array( $pageE, $pageF );
$pageD->ownPage = array( $pageG );
$pageF->ownPage = array( $pageH );
R::store( $pageA );
$pageA = $pageA->fresh();
//also tests non-existant column handling by count().
asrt( R::count( 'page', ' price = ? ', array( '5' ) ), 0);
asrt( R::count( 'tag', ' title = ? ', array( 'new' ) ), 0);
$pageA->traverse( 'ownPageList', function( $bean ) {
$bean->price = 5;
});
R::store( $pageA );
asrt( R::count( 'page', ' price = ? ', array( '5' ) ), 7);
}
/**
* Test traversing paths, ancestry.
*
* @return void
*/
public function testTraversePaths()
{
R::nuke();
$pageA = R::dispense( 'page' )->setAttr( 'title', 'a' );
$pageB = R::dispense( 'page' )->setAttr( 'title', 'b' );
$pageC = R::dispense( 'page' )->setAttr( 'title', 'c' );
$pageD = R::dispense( 'page' )->setAttr( 'title', 'd' );
$pageE = R::dispense( 'page' )->setAttr( 'title', 'e' );
$pageF = R::dispense( 'page' )->setAttr( 'title', 'f' );
$pageG = R::dispense( 'page' )->setAttr( 'title', 'g' );
$pageH = R::dispense( 'page' )->setAttr( 'title', 'h' );
$pageA->ownPage = array( $pageB, $pageC );
$pageB->ownPage = array( $pageD );
$pageC->ownPage = array( $pageE, $pageF );
$pageD->ownPage = array( $pageG );
$pageF->ownPage = array( $pageH );
R::store( $pageA );
$parents = array();
$pageF->traverse( 'page', function( $page ) use ( &$parents ) {
$parents[] = $page->title;
} );
asrt( implode( ',', $parents ), 'c,a' );
$parents = array();
$pageH->traverse( 'page', function( $page ) use ( &$parents ) {
$parents[] = $page->title;
} );
asrt( implode( ',', $parents ), 'f,c,a' );
$parents = array();
$pageG->traverse( 'page', function( $page ) use ( &$parents ) {
$parents[] = $page->title;
} );
asrt( implode( ',', $parents ), 'd,b,a' );
$path = array();
$pageA->traverse( 'ownPageList', function( $page ) use ( &$path ) {
$path[] = $page->title;
} );
asrt( implode( ',', $path ), 'b,d,g,c,e,f,h' );
$path = array();
$pageC->traverse( 'ownPageList', function( $page ) use ( &$path ) {
$path[] = $page->title;
} );
asrt( implode( ',', $path ), 'e,f,h' );
$path = array();
$pageA->traverse( 'ownPageList', function( $page ) use ( &$path ) {
$path[] = $page->title;
}, 2 );
asrt( implode( ',', $path ), 'b,d,c,e,f' );
}
/**
* Test traversal with embedded SQL snippets.
*
* @return void
*/
public function testTraversalWithSQL()
{
$tasks = R::dispense('task', 10);
foreach( $tasks as $key => $task ) {
$task->descr = 't'.$key;
}
$tasks[0]->ownTask = array( $tasks[1], $tasks[9], $tasks[7] );
$tasks[1]->ownTask = array( $tasks[5] );
$tasks[9]->ownTask = array( $tasks[3], $tasks[8] );
$tasks[2]->ownTask = array( $tasks[4] );
$tasks[7]->ownTask = array( $tasks[6] );
R::storeAll( $tasks );
$task = R::load('task', $tasks[0]->id);
$todo = array();
$task->with(' ORDER BY descr ASC ')->traverse('ownTaskList', function( $t ) use ( &$todo ) {
$todo[] = $t->descr;
} );
asrt( implode( ',', $todo ), 't1,t5,t7,t6,t9,t3,t8' );
$task = R::load( 'task', $tasks[0]->id );
$todo = array();
$task->withCondition( ' ( descr = ? OR descr = ? ) ', array( 't7','t6' ) )
->traverse( 'ownTaskList', function( $task ) use( &$todo ){
$todo[] = $task->descr;
} );
asrt( implode( ',', $todo ), 't7,t6' );
}
/**
* Test traversal with aliases.
*
* @return void
*/
public function testTraversalWithAlias()
{
R::nuke();
$book = R::dispense( 'book' );
$cats = R::dispense( 'category', 3 );
$cats[0]->gname = 'SF';
$cats[1]->gname = 'Fantasy';
$cats[2]->gname = 'Horror';
$book->genre = $cats[0];
$book->name = 'Space Story';
$cats[0]->genre = $cats[1];
$cats[2]->genre = $cats[1];
R::store( $book );
$book2 = R::dispense( 'book' );
$book2->genre = $cats[2];
$book2->name = 'Ghost Story';
R::store( $book2 );
$fantasy = R::load( 'category', $cats[1]->id );
$cats = array();
$book = $book->fresh();
$book->fetchAs( 'category' )->traverse( 'genre', function( $cat ) use ( &$cats ) {
$cats[] = $cat->gname;
} );
asrt( implode( ',', $cats ), 'SF,Fantasy' );
$catList = array();
$fantasy->alias( 'genre' )
->with( ' ORDER BY gname ASC ' )
->traverse( 'ownCategory', function( $cat ) use ( &$catList ) {
$catList[] = $cat->gname;
} );
asrt( implode( ',', $catList ), 'Horror,SF' );
}
/**
* Traverse can only work with own-lists, otherwise infinite loops.
*
* @return void
*/
public function testSharedTraversal()
{
$friend = R::dispense( 'friend' );
try {
$friend->traverse( 'sharedFriend', function( $friend ){ } );
fail();
} catch( RedException $e ) {
pass();
}
}
/**
* Test whether traverse() passes the depth of the
* current item in the tree along with the bean.
*/
public function testDepthCount()
{
R::nuke();
$page = R::dispense('page');
$page->num = 1;
$root = $page;
for($i = 2; $i < 10; $i++) {
$child = R::dispense('page');
$page->ownPageList[] = $child;
$child->num = $i;
$page = $child;
}
R::store($root);
$total = 0;
$page = $root->fresh();
$page->traverse('ownPageList', function( $child, $count ) use(&$total) {
asrt( $count+1, intval($child->num) );
$total += $count;
});
asrt( $total, 36 );
}
}

View File

@@ -0,0 +1,151 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Typechecking
*
* Tests whether RedBeanPHP handles type casting correctly.
*
* @file RedUNIT/Base/Typechecking.php
* @desc Tests basic bean validation rules; invalid bean handling.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Typechecking extends Base
{
/**
* Test types.
* Test how RedBeanPHP OODB and OODBBean handle type and type casts.
*
* Rules:
*
* 1. before storing a bean all types are preserved except booleans (they are converted to STRINGS '0' or '1')
* 2. after store-reload all bean property values are STRINGS or NULL
* (or ARRAYS but that's only from a user perspective because these are lazy loaded)
* 3. the ID returned by store() is an INTEGER (if possible; on 32 bit systems overflowing values will be cast to STRINGS!)
*
* After loading:
* ALL VALUES EXCEPT NULL -> STRING
* NULL -> NULL
*
* @note Why not simply return bean->id in store()? Because not every driver returns the same type:
* databases without insert_id support require a separate query or a suffix returning STRINGS, not INTEGERS.
*
* @note Why not preserve types? I.e. I store integer, why do I get back a string?
* Answer: types are handled different across database platforms, would cause overhead to inspect every value for type,
* also PHP is a dynamically typed language so types should not matter that much. Another reason: due to the nature
* of RB columns in the database might change (INT -> STRING) this would cause return types to change as well which would
* cause 'cascading errors', i.e. a column gets widened and suddenly your code would break.
*
* @note Unfortunately the 32/64-bit issue cannot be tested fully. Return-strategy store() is probably the safest
* solution.
*
* @return void
*/
public function testTypes()
{
testpack( 'Beans can only contain STRING and NULL after reload' );
R::nuke();
$bean = R::dispense( 'bean' );
$bean->number = 123;
$bean->float = 12.3;
$bean->bool = FALSE;
$bean->bool2 = TRUE;
$bean->text = 'abc';
$bean->null = null;
$bean->datetime = new\DateTime( 'NOW', new\DateTimeZone( 'Europe/Amsterdam' ) );
$id = R::store( $bean );
asrt( is_int( $id ), TRUE );
asrt( is_float( $bean->float ), TRUE );
asrt( is_integer( $bean->number ), TRUE );
asrt( is_string( $bean->bool ), TRUE );
asrt( is_string( $bean->bool2 ), TRUE );
asrt( is_string( $bean->datetime ), TRUE );
asrt( is_string( $bean->text ), TRUE );
asrt( is_null( $bean->null ), TRUE );
$bean = R::load('bean', $id );
asrt( is_string( $bean->id ), TRUE );
asrt( is_string( $bean->float ), TRUE );
asrt( is_string( $bean->number ), TRUE );
asrt( is_string( $bean->bool ), TRUE );
asrt( is_string( $bean->bool2 ), TRUE );
asrt( is_string( $bean->datetime ), TRUE );
asrt( is_string( $bean->text ), TRUE );
asrt( is_null( $bean->null ), TRUE );
asrt( $bean->bool, '0' );
asrt( $bean->bool2, '1' );
}
/**
* Test bean type checking.
*
* @return void
*/
public function testBeanTypeChecking()
{
$redbean = R::getRedBean();
$bean = $redbean->dispense( "page" );
// Set some illegal values in the bean; this should trigger Security exceptions.
// Arrays are not allowed.
$bean->name = array( "1" );
try {
$redbean->store( $bean );
fail();
} catch ( RedException $e ) {
pass();
}
try {
$redbean->check( $bean );
fail();
} catch ( RedException $e ) {
pass();
}
$bean->name = new OODBBean;
try {
$redbean->check( $bean );
fail();
} catch ( RedException $e ) {
pass();
}
// Property names should be alphanumeric
$prop = ".";
$bean->$prop = 1;
try {
$redbean->store( $bean );
fail();
} catch ( RedException $e ) {
pass();
}
try {
$redbean->check( $bean );
fail();
} catch ( RedException $e ) {
pass();
}
// Really...
$prop = "-";
$bean->$prop = 1;
try {
$redbean->store( $bean );
fail();
} catch ( RedException $e ) {
pass();
}
try {
$redbean->check( $bean );
fail();
} catch ( RedException $e ) {
pass();
}
}
}

View File

@@ -0,0 +1,448 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
use RedBeanPHP\Logger\RDefault as Logger;
/**
* Update
*
* Tests basic update functionality - however this test suite
* has grown to cover various other scenarios involving updates as
* well, including setting of property filters (necessary for
* spatial tools in MySQL), storiging INF value and more...
*
* @file RedUNIT/Base/Update.php
* @desc Tests basic storage features through OODB class.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Update extends Base
{
/**
* Tests whether no unncessary DESCRIBE-queries are executed,
* (Commit 3b8ce88e5b796bfde6485ab0a51a4fcfb1bcf0fa by davidsickmiller).
* Even thtough we add 2 properties, only 1 DESCRIBE query is necessary
* to load the column cache.
*/
public function testModifySchemaColCache()
{
R::nuke();
$toolbox = R::getToolbox();
$repository = $toolbox->getRedBean()->getCurrentRepository();
$database = $toolbox->getDatabaseAdapter()->getDatabase();
$logger = new Logger;
$database->setLogger( $logger );
$bean = R::dispense('bean');
$bean->property1 = 'test';
$bean->property2 = 'test'; //should not cause 2nd DESCRIBE.
R::startLogging();
R::store( $bean );
$logger = R::getLogger();
asrt(
count( $logger->grep('DESCRIBE') ) +
count( $logger->grep('SELECT column_name') ) +
count( $logger->grep('SHOW COLUMNS') ) + //CUBRID
count( $logger->grep('PRAGMA table_info') )
, 1);
R::stopLogging();
//new round, same results, no cache between beans
R::nuke();
$bean = R::dispense('bean');
$bean->property1 = 'test';
$bean->property2 = 'test'; //should not cause 2nd DESCRIBE.
R::startLogging();
R::store( $bean );
$logger = R::getLogger();
asrt(
count( $logger->grep('DESCRIBE') ) +
count( $logger->grep('SELECT column_name') ) +
count( $logger->grep('SHOW COLUMNS') ) + //CUBRID
count( $logger->grep('PRAGMA table_info') )
, 1);
R::stopLogging();
}
/**
* Test whether we can use SQL filters and
* whether they are being applied properly for
* different types of SELECT queries in the QueryWriter.
*/
public function testSQLFilters()
{
R::nuke();
AQueryWriter::setSQLFilters(array(
QueryWriter::C_SQLFILTER_READ => array(
'book' => array( 'title' => ' LOWER(book.title) '),
),
QueryWriter::C_SQLFILTER_WRITE => array(
'book' => array( 'title' => ' UPPER(?) '),
),
));
$book = R::dispense( 'book' );
$book->title = 'story';
R::store( $book );
asrt( R::getCell( 'SELECT title FROM book WHERE id = ?', array( $book->id ) ), 'STORY' );
$book = $book->fresh();
asrt( $book->title, 'story' );
$library = R::dispense( 'library' );
$library->sharedBookList[] = $book;
R::store( $library );
$library = $library->fresh();
$books = $library->sharedBookList;
$book = reset( $books );
asrt( $book->title, 'story' );
$otherBook = R::dispense('book');
$otherBook->sharedBook[] = $book;
R::store( $otherBook );
$otherBook = $otherBook->fresh();
$books = $otherBook->sharedBookList;
$book = reset( $books );
asrt( $book->title, 'story' );
$links = $book->ownBookBookList;
$link = reset( $links );
$link->shelf = 'x13';
AQueryWriter::setSQLFilters(array(
QueryWriter::C_SQLFILTER_READ => array(
'book' => array( 'title' => ' LOWER(book.title) '),
'book_book' => array( 'shelf' => ' LOWER(book_book.shelf) '),
),
QueryWriter::C_SQLFILTER_WRITE => array(
'book' => array( 'title' => ' UPPER(?) '),
'book_book' => array( 'shelf' => ' UPPER(?) ')
),
));
R::store( $link );
asrt( R::getCell( 'SELECT shelf FROM book_book WHERE id = ?', array( $link->id ) ), 'X13' );
$otherBook = $otherBook->fresh();
unset($book->sharedBookList[$otherBook->id]);
R::store( $book );
AQueryWriter::setSQLFilters(array());
}
/**
* Test unsetting properties.
*
* @return void
*/
public function testUnsetUpdate()
{
R::nuke();
$book = R::dispense( 'book' );
$book->name = 'x';
$book->price = 40;
R::store( $book );
$book = $book->fresh();
$book->name = 'y';
unset( $book->name );
R::store( $book );
$book = $book->fresh();
asrt( $book->name, 'x' );
asrt( (int) $book->price, 40 );
$book->price = 30;
R::store( $book );
$book = $book->fresh();
asrt( $book->name, 'x' );
asrt( (int) $book->price, 30 );
$book->price = 20;
unset( $book->price );
$book->name = 'y';
R::store( $book );
$book = $book->fresh();
asrt( $book->name, 'y' );
asrt( (int) $book->price, 30 );
}
/**
* Tests whether we can update or unset a parent bean
* with an alias without having to use fetchAs and
* without loading the aliased bean causing table-not-found
* errors.
*/
public function testUpdatingParentBeansWithAliases()
{
testpack( 'Test updating parent beans with aliases' );
R::nuke();
$trans = R::dispense( 'transaction' );
$seller = R::dispense( 'user' );
$trans->seller = $seller;
$id = R::store( $trans );
R::freeze( TRUE );
$trans = R::load( 'transaction', $id );
//should not try to load seller, should not require fetchAs().
try {
$trans->seller = R::dispense( 'user' );
pass();
} catch( Exception $e ) {
fail();
}
$trans = R::load( 'transaction', $id );
//same for unset...
try {
unset( $trans->seller );
pass();
} catch ( Exception $e ) {
fail();
}
R::freeze( FALSE );
$account = R::dispense( 'user' );
asrt( count( $account->alias( 'seller' )->ownTransaction ), 0 );
$account->alias( 'seller' )->ownTransaction = R::dispense( 'transaction', 10 );
$account->alias( 'boo' ); //try to trick me...
$id = R::store( $account );
R::freeze( TRUE );
$account = R::load( 'user', $id );
asrt( count( $account->alias( 'seller' )->ownTransaction ), 10 );
//you cannot unset a list
unset( $account->alias( 'seller' )->ownTransaction );
$id = R::store( $account );
$account = R::load( 'user', $id );
asrt( count( $account->alias( 'seller' )->ownTransaction ), 10 );
$account->alias( 'seller' )->ownTransaction = array();
$id = R::store( $account );
$account = R::load( 'user', $id );
asrt(count($account->alias( 'seller' )->ownTransaction), 0 );
asrt(count($account->ownTransaction), 0 );
R::freeze( FALSE );
//but also make sure we don't cause extra column issue #335
R::nuke();
$building = R::dispense('building');
$village = R::dispense('village');
$building->village = $village;
R::store($building);
$building = $building->fresh();
$building->village = NULL;
R::store($building);
$building = $building->fresh();
$columns = R::inspect('building');
asrt( isset( $columns['village'] ), FALSE );
asrt( isset( $building->village ), FALSE );
R::nuke();
$building = R::dispense('building');
$village = R::dispense('village');
$building->village = $village;
R::store($building);
$building = $building->fresh();
unset($building->village);
R::store($building);
$building = $building->fresh();
$columns = R::inspect('building');
asrt( isset( $columns['village'] ), FALSE );
asrt( isset( $building->village ), FALSE );
$building = R::dispense('building');
$village = R::dispense('village');
$building->village = $village;
R::store($building);
$building = $building->fresh();
$building->village = FALSE;
R::store($building);
$building = $building->fresh();
$columns = R::inspect('building');
asrt( isset( $columns['village'] ), FALSE );
asrt( isset( $building->village ), FALSE );
}
/**
* All kinds of tests for basic CRUD.
*
* Does the data survive?
*
* @return void
*/
public function testUpdatingBeans()
{
testpack( 'Test basic support UUID/override ID default value' );
$bean = R::dispense( 'bean' );
R::store( $bean );
if ($this->currentlyActiveDriverID === 'mysql') {
//otherwise UTF8 causes index overflow in mysql: SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes
R::exec('alter table bean modify column id char(3);');
} else {
R::getWriter()->widenColumn( 'bean', 'id', R::getWriter()->scanType( 'abc' ) );
}
$bean->id = 'abc';
R::store( $bean );
asrt( $bean->id, 'abc' );
testpack( 'Test Update' );
try {
R::store( array() );
fail();
} catch ( RedException $e ) {
pass();
}
$toolbox = R::getToolBox();
$adapter = $toolbox->getDatabaseAdapter();
$writer = $toolbox->getWriter();
$redbean = $toolbox->getRedBean();
$pdo = $adapter->getDatabase();
$page = $redbean->dispense( "page" );
$page->name = "old name";
$id = $redbean->store( $page );
asrt( $page->getMeta( 'tainted' ), FALSE );
$page->setAttr( 'name', "new name" );
asrt( $page->getMeta( 'tainted' ), TRUE );
$id = $redbean->store( $page );
$page = $redbean->load( "page", $id );
asrt( $page->name, "new name" );
// Null should == NULL after saving
$page->rating = NULL;
$newid = $redbean->store( $page );
$page = $redbean->load( "page", $id );
asrt( $page->name, "new name" );
asrt( ( $page->rating === NULL ), TRUE );
asrt( !$page->rating, TRUE );
$page->rating = FALSE;
$newid = $redbean->store( $page );
asrt( $newid, $id );
$page = $redbean->load( "page", $id );
asrt( $page->name, "new name" );
asrt( (bool) $page->rating, FALSE );
asrt( ( $page->rating == FALSE ), TRUE );
asrt( !$page->rating, TRUE );
$page->rating = TRUE;
$newid = $redbean->store( $page );
asrt( $newid, $id );
$page = $redbean->load( "page", $id );
asrt( $page->name, "new name" );
asrt( (bool) $page->rating, TRUE );
asrt( ( $page->rating == TRUE ), TRUE );
asrt( ( $page->rating == TRUE ), TRUE );
$page->rating = NULL;
R::store( $page );
$page = R::load( 'page', $page->id );
asrt( $page->rating, NULL );
$page->rating = '1';
$newid = $redbean->store( $page );
asrt( $newid, $id );
$page = $redbean->load( "page", $id );
asrt( $page->name, "new name" );
asrt( $page->rating, "1" );
$page->rating = "0";
$newid = $redbean->store( $page );
asrt( $page->rating, "0" );
$page->rating = 0;
$newid = $redbean->store( $page );
asrt( $page->rating, 0 );
$page->rating = "0";
$newid = $redbean->store( $page );
asrt( $newid, $id );
$page = $redbean->load( "page", $id );
asrt( $page->name, "new name" );
asrt( !$page->rating, TRUE );
asrt( ( $page->rating == 0 ), TRUE );
asrt( ( $page->rating == FALSE ), TRUE );
$page->rating = 5;
$newid = $redbean->store( $page );
asrt( $newid, $id );
$page = $redbean->load( "page", $id );
asrt( $page->name, "new name" );
asrt( strval( $page->rating ), "5" );
$page->rating = 300;
$newid = $redbean->store( $page );
asrt( $newid, $id );
$page = $redbean->load( "page", $id );
asrt( $page->name, "new name" );
asrt( strval( $page->rating ), "300" );
$page->rating = -2;
$newid = $redbean->store( $page );
asrt( $newid, $id );
$page = $redbean->load( "page", $id );
asrt( $page->name, "new name" );
asrt( strval( $page->rating ), "-2" );
$page->rating = 2.5;
$newid = $redbean->store( $page );
asrt( $newid, $id );
$page = $redbean->load( "page", $id );
asrt( $page->name, "new name" );
asrt( ( $page->rating == 2.5 ), TRUE );
$page->rating = -3.3;
$newid = $redbean->store( $page );
asrt( $newid, $id );
$page = $redbean->load( "page", $id );
asrt( $page->name, "new name" );
asrt( ( $page->rating == -3.3 ), TRUE );
$page->rating = "good";
$newid = $redbean->store( $page );
asrt( $newid, $id );
$page = $redbean->load( "page", $id );
asrt( $page->name, "new name" );
asrt( $page->rating, "good" );
$longtext = str_repeat( 'great! because..', 100 );
$page->rating = $longtext;
$newid = $redbean->store( $page );
asrt( $newid, $id );
$page = $redbean->load( "page", $id );
asrt( $page->name, "new name" );
asrt( $page->rating, $longtext );
// Test leading zeros
$numAsString = "0001";
$page->numasstring = $numAsString;
$redbean->store( $page );
$page = $redbean->load( "page", $id );
asrt( $page->numasstring, "0001" );
$page->numnotstring = "0.123";
$redbean->store( $page );
$page = $redbean->load( "page", $id );
asrt( $page->numnotstring == 0.123, TRUE );
$page->numasstring2 = "00.123";
$redbean->store( $page );
$page = $redbean->load( "page", $id );
asrt( $page->numasstring2, "00.123" );
try {
$redbean->trash( array() );
fail();
} catch ( RedException $e ) {
pass();
}
$redbean->trash( $page );
asrt( (int) $pdo->GetCell( "SELECT count(*) FROM page" ), 0 );
}
/**
* Tests whether empty strings are preserved as such.
*
* @return void
*/
public function testEmptyStringShouldNotBeStoredAsInteger()
{
R::nuke();
$bean = R::dispense('bean');
$bean->str = '';
R::store($bean);
$bean = $bean->fresh();
asrt( ( $bean->str === '' ), TRUE);
}
/**
* Test handling of infinity values.
*
* @return void
*/
public function testStoringInf()
{
R::nuke();
$bean = R::dispense( 'bean' );
$bean->inf = INF;
R::store( $bean );
$bean = $bean->fresh();
asrt( ( $bean->inf === 'INF' ), TRUE );
asrt( ( $bean->inf == 'INF' ), TRUE );
$bean->modifyme = 'yes';
R::store( $bean );
$bean = $bean->fresh();
asrt( ( $bean->inf === 'INF' ), TRUE );
asrt( ( $bean->inf == 'INF' ), TRUE );
$bean->modifyme = 'yes';
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Utf8
*
* Tests whether we can store and retrive unicode characters.
*
* @file RedUNIT/Base/UTF8.php
* @desc Tests handling of NULL values.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Utf8 extends Base
{
/**
* Tests whether we can process malformed strings in beans.
*
* @return void
*/
public function testMalformed()
{
$byte = pack( 'I', 129 );
$bean = R::dispense( 'bean' );
$bean->byte = $byte;
OODBBean::setEnforceUTF8encoding( TRUE );
$str = strval( $bean );
OODBBean::setEnforceUTF8encoding( FALSE );
pass();
}
/**
* Test UTF8 handling.
*
* @return void
*/
public function testUTF8()
{
//skip if < 5.3
if (version_compare(PHP_VERSION, '5.4', '<')) return pass();
$str = '𠜎ὃ𠻗𠻹𠻺𠼭𠼮𠽌𠾴𠾼𠿪𡁜';
$bean = R::dispense( 'bean' );
$bean->bla = $str;
R::store( $bean );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->bla, $str );
pass();
}
}

View File

@@ -0,0 +1,330 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\ToolBox as ToolBox;
use RedBeanPHP\AssociationManager as AssociationManager;
use RedBeanPHP\RedException\SQL as SQL;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
/**
* Via
*
* Tests the via() function to hop over an N-M table to
* obtain a indirectly related bean, i.e. given person, participant
* and project you should be able to obtain a the project associated
* with a certain person via() the participant entity.
*
* @file RedUNIT/Base/Via.php
* @desc Via tests
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Via extends Base
{
/**
* Tests fix for issue #378.
* Via property does not get cleared properly.
*
* @return void
*/
public function testIssue378()
{
R::nuke();
$mediaBean = R::dispense('media');
$fooPerson = R::dispense('person');
$mediaBean->sharedPersonList[] = $fooPerson;
R::store($mediaBean);
asrt( count( $mediaBean->sharedPersonList ), 1 );
$person = R::findOne('person');
$person->via('relation')->sharedMarriageList[] = R::dispense('marriage');
//this second one caused the via property to not get cleared
$person->via('relation')->sharedMarriageList;
asrt( count( $person->sharedMediaList ), 1 );
//also found this scenario, non-existing property
$book = R::dispense('book');
$book->sharedPage[] = R::dispense('page');
R::store($book);
$book = $book->fresh();
$book->via('garbage')->nothing;
asrt( count( $book->sharedPageList ), 1 );
//yet another
$book = R::dispense('magazine');
$book->ownAdList[] = R::dispense('ad');
$book->sharedPage[] = R::dispense('page');
R::store($book);
$book = $book->fresh();
$book->via('garbage')->ownAdList;
asrt( count( $book->sharedPageList ), 1 );
$book = R::dispense('folder');
$book->sharedPage[] = R::dispense('page');
R::store($book);
$book = $book->fresh();
$book->via('garbage')->sharedItemList[] = R::dispense('item');
asrt( count( $book->sharedPageList ), 1 );
$book = R::dispense('folder2');
$book->sharedPage[] = R::dispense('page');
R::store($book);
$book = $book->fresh();
$book->via('garbage')->sharedItemList[] = R::dispense('item');
$book->via('garbage')->sharedItemList[] = R::dispense('item');
asrt( count( $book->sharedPageList ), 1 );
$book = R::dispense('folder3');
$book->sharedPage[] = R::dispense('page');
R::store($book);
$book = $book->fresh();
$book->via('garbage')->item = R::dispense('item');
asrt( count( $book->sharedPageList ), 1 );
$book = R::dispense('folder3');
$book->sharedPage[] = R::dispense('page');
R::store($book);
$book = $book->fresh();
$book->via('garbage')->item = 'test';
asrt( count( $book->sharedPageList ), 1 );
//yet another
$book = R::dispense('leaflet');
$book->title = 'leaflet';
$book->sharedPage[] = R::dispense('page');
R::store($book);
$book = $book->fresh();
$book->via('garbage')->title;
asrt( count( $book->sharedPageList ), 1 );
//yet another
$book = R::dispense('paper');
$book->author = R::dispense('author');
$book->sharedPage[] = R::dispense('page');
R::store($book);
$book = $book->fresh();
$book->via('garbage')->author;
asrt( count( $book->sharedPageList ), 1 );
$book = R::dispense('paper2');
$book->sharedPage[] = R::dispense('page');
R::store($book);
$book = $book->fresh();
$book->via('garbage')->author;
asrt( count( $book->sharedPageList ), 1 );
//yet another one
$book = R::dispense('archive');
$book->sharedItem[] = R::dispense('item');
$book->sharedPage[] = R::dispense('page');
R::store($book);
$book = $book->fresh();
unset( $book->via('garbage')->sharedItem );
asrt( count( $book->sharedPageList ), 1 );
//theoretic cases
$book = R::dispense('guide');
$book->sharedItem[] = R::dispense('item');
$book->sharedPage[] = R::dispense('page');
R::store($book);
$book = $book->fresh();
$book->via('relation')->countShared('item');
$book->via('relation')->countShared('item');
asrt( count( $book->sharedPageList ), 1 );
$book = R::dispense('catalogue');
$book->sharedPage[] = R::dispense('page');
R::store($book);
$book = $book->fresh();
$book->via('relation')->countShared('item');
$book->via('relation')->countShared('item');
asrt( count( $book->sharedPageList ), 1 );
$book = R::dispense('tabloid');
$book->ownItemList[] = R::dispense('item');
$book->sharedPage[] = R::dispense('page');
R::store($book);
$book = $book->fresh();
$book->via('relation')->countOwn('item');
$book->via('relation')->countOwn('item');
asrt( count( $book->sharedPageList ), 1 );
$book = R::dispense('booklet');
$book->ownItemList[] = R::dispense('item');
$book->sharedPage[] = R::dispense('page');
R::store($book);
$book = $book->fresh();
$book->via('relation')->countOwn('item');
$book->via('relation')->countOwn('item');
asrt( count( $book->sharedPageList ), 1 );
AQueryWriter::clearRenames();
}
/**
* Via specific tests.
*
* @return void
*/
public function testViaAndSQL()
{
R::nuke();
list($p1, $p2) = R::dispense('participant', 2);
list($e1, $e2) = R::dispense('employee', 2);
list($x1, $x2) = R::dispense('project', 2);
$e1->name = 'Anna';
$e2->name = 'John';
$p1->project = $x1;
$p1->employee = $e1;
$p1->arole = 'designer';
$p2->project = $x1;
$p2->employee = $e2;
$p2->arole = 'coder';
R::storeAll(array( $p1, $p2 ));
$project = R::load('project', $x1->id);
$designers = $project
->withCondition(' participant.arole = ? ', array( 'designer' ) )
->via( 'participant' )
->sharedEmployeeList;
$anna = reset( $designers );
asrt(count($designers), 1);
asrt($anna->name, 'Anna');
$coders = $project
->withCondition(' participant.arole = ? ', array( 'coder' ) )
->via( 'participant' )
->sharedEmployeeList;
$john = reset( $coders );
asrt(count($coders), 1);
asrt($john->name, 'John');
}
/**
* Test Via and Link together.
*
* @return void
*/
public function testViaAndLink()
{
R::nuke();
list( $John, $Anna, $Celine ) = R::dispense( 'employee', 3 );
$John->badge = 'John';
$Anna->badge = 'Anna';
$Celine->badge = 'Celine';
$project = R::dispense( 'project' );
$project->name = 'x';
$project2 = R::dispense( 'project' );
$project2->name = 'y';
$John->link( 'participant', array(
'arole' => 'designer'
) )->project = $project;
$Anna->link( 'participant', array(
'arole' => 'developer'
) )->project = $project;
$Celine->link( 'participant', array(
'arole' => 'sales'
) )->project = $project2;
$Anna->link('participant', array(
'arole' => 'lead'
) )->project = $project2;
R::storeAll( array( $project, $project2, $John, $Anna, $Celine ) );
$employees = $project
->with(' ORDER BY badge ASC ')
->via( 'participant' )
->sharedEmployee;
asrt( is_array( $employees ), TRUE );
asrt( count( $employees ), 2 );
$badges = array();
foreach( $employees as $employee ) {
$badges[] = $employee->badge;
}
asrt( implode( ',', $badges ), 'Anna,John' );
$employees = $project2
->with(' ORDER BY badge ASC ')
->via( 'participant' )
->sharedEmployee;
asrt( is_array( $employees ), TRUE );
asrt( count( $employees ), 2 );
$badges = array();
foreach( $employees as $employee ) {
$badges[] = $employee->badge;
}
asrt( implode( ',', $badges ), 'Anna,Celine' );
$projects = $John->sharedProject;
asrt( is_array( $projects ), TRUE );
asrt( count( $projects ), 1 );
$projectList = array();
foreach( $projects as $project ) {
$projectList[] = $project->name;
}
sort( $projectList );
asrt( implode( ',', $projectList ), 'x' );
$projects = $Anna->sharedProject;
asrt( is_array( $projects ), TRUE );
asrt( count( $projects ), 2 );
$projectList = array();
foreach( $projects as $project ) {
$projectList[] = $project->name;
}
sort( $projectList );
asrt( implode( ',', $projectList ), 'x,y' );
$projects = $Anna->via( 'participant' )->sharedProject;
asrt( is_array( $projects ), TRUE );
asrt( count( $projects ), 2 );
$projectList = array();
foreach( $projects as $project ) {
$projectList[] = $project->name;
}
sort( $projectList );
asrt( implode( ',', $projectList ), 'x,y' );
$projects = $Celine->via( 'participant' )->sharedProject;
asrt( is_array( $projects ), TRUE );
asrt( count( $projects ), 1 );
$projectList = array();
foreach( $projects as $project ) {
$projectList[] = $project->name;
}
sort( $projectList );
asrt( implode( ',', $projectList ), 'y' );
$roles = $Anna->ownParticipant;
asrt( is_array( $roles ), TRUE );
asrt( count( $roles ), 2 );
$roleList = array();
foreach( $roles as $role ) {
$roleList[] = $role->arole;
}
sort( $roleList );
asrt( implode( ',', $roleList ), 'developer,lead' );
$project2->sharedEmployee[] = $John;
R::store( $project2 );
$projects = $John->sharedProject;
asrt( is_array( $projects ), TRUE );
asrt( count( $projects ), 2 );
$projectList = array();
foreach( $projects as $project ) {
$projectList[] = $project->name;
}
sort( $projectList );
asrt( implode( ',', $projectList ), 'x,y' );
$projects = $John->via( 'participant' )->sharedProject;
asrt( is_array( $projects ), TRUE );
asrt( count( $projects ), 2 );
$projectList = array();
foreach( $projects as $project ) {
$projectList[] = $project->name;
}
sort( $projectList );
asrt( implode( ',', $projectList ), 'x,y' );
}
/**
* Test effect of via on shared list removal of beans.
*
* @return void
*/
public function testViaAndRemove()
{
R::nuke();
$project = R::dispense( 'project' );
$employees = R::dispense( 'employee', 2);
$project->via( 'partcipant' )->sharedEmployeeList = $employees;
R::store( $project );
asrt( R::count('employee'), 2 );
asrt( R::count('participant'), 2 );
$project = $project->fresh();
$project->sharedEmployee = array();
R::store( $project );
asrt( R::count( 'employee' ), 2 );
asrt( R::count( 'participant' ), 0 );
}
}

View File

@@ -0,0 +1,581 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
/**
* With
*
* With/WithCondition can be used to fetch bean lists with additional
* requirements, filters or ordering specification using SQL snippets.
* This suite tests the handling of with() and withCondition() snippets.
* Additionally this test suite also tests noLoad() and all() modifiers.
*
* @file RedUNIT/Base/With.php
* @desc Tests query modification of own-lists with prefix-with
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class With extends Base
{
/**
* This test suite uses specific SQL, only suited for MySQL.
*
* @return array
*/
public function getTargetDrivers()
{
return array( 'mysql' );
}
/**
* Tests no-load modifier for lists.
*
* @return void
*/
public function testNoLoad()
{
$book = R::dispense( array(
'_type' => 'book',
'title' => 'Book of Lorem Ipsum',
'ownPage' => array(
array(
'_type' => 'page',
'content' => 'Lorem Ipsum',
)
),
'sharedTag' => array(
array(
'_type' => 'tag',
'label' => 'testing'
)
)
) );
R::store( $book );
$book = $book->fresh();
asrt( R::count( 'book' ), 1 );
asrt( count( $book->ownPage ), 1 );
//now try with no-load
$book = $book->fresh();
asrt( count( $book->noLoad()->ownPage ), 0 );
asrt( count( $book->noLoad()->sharedTag ), 0 );
//now try to add with no-load
$book = $book->fresh();
$book->noLoad()->xownPageList[] = R::dispense( 'page' );
$book->noLoad()->sharedTagList[] = R::dispense( 'tag' );
R::store( $book );
$book = $book->fresh();
asrt( count( $book->ownPage ), 2 );
asrt( count( $book->sharedTagList ), 2 );
//no-load overrides with and withCondition
$book = $book->fresh();
asrt( count( $book->with(' invalid sql ')->noLoad()->ownPage ), 0 );
asrt( count( $book->withCondition(' invalid sql ')->noLoad()->sharedTag ), 0 );
//no-load overrides all and alias
$book = $book->fresh();
asrt( count( $book->all()->noLoad()->ownPage ), 0 );
asrt( count( $book->alias('nothing')->noLoad()->sharedTag ), 0 );
//no-load gets cleared
$book = $book->fresh();
asrt( count( $book->ownPage ), 2 );
asrt( count( $book->sharedTagList ), 2 );
//We cant clear with no-load accidentally?
$book = $book->fresh();
$book->noLoad()->ownPage = array();
$book->noLoad()->sharedTagList = array();
R::store( $book );
asrt( count( $book->ownPage ), 2 );
asrt( count( $book->sharedTagList ), 2 );
//No-load does not have effect if list is already cached
$book = $book->fresh();
$book->ownPage;
$book->sharedTag;
asrt( count( $book->ownPage ), 2 );
asrt( count( $book->sharedTagList ), 2 );
}
/**
* Test all().
*
* @return void
*/
public function testAll()
{
$book = R::dispense( 'book' );
$book->ownPage = R::dispense( 'page', 10 );
R::store( $book );
asrt( count( $book->with( ' LIMIT 3 ' )->ownPage ), 3 );
asrt( count( $book->ownPage ), 3 );
asrt( count( $book->all()->ownPage ), 10 );
asrt( count( $book->ownPage ), 10 );
R::nuke();
asrt( count( $book->ownPage ), 10 );
asrt( count( $book->all()->ownPage ), 0 );
}
/**
* Test embedded SQL snippets using with and withCondition.
*
* @return void
*/
public function testEmbeddedSQL()
{
list( $page1, $page2, $page3 ) = R::dispense( 'page', 3 );
list( $ad1, $ad2, $ad3 ) = R::dispense( 'ad', 3 );
$ad2->name = 'shampoo';
$page3->name = 'homepage';
$page1->sharedAd = array( $ad1, $ad3 );
$page2->sharedAd = array( $ad2, $ad3 );
$page3->sharedAd = array( $ad3, $ad2, $ad1 );
R::storeAll( array( $page1, $page2, $page3 ) );
$page1 = R::load( 'page', $page1->id );
asrt( 1, count( $page1->with( ' LIMIT 1 ' )->sharedAd ) );
$page2 = R::load( 'page', $page2->id );
$adsOnPage2 = $page2->withCondition( ' `name` = ? ', array( 'shampoo' ) )->sharedAd;
asrt( 1, count( $adsOnPage2 ) );
$ad = reset( $adsOnPage2 );
asrt( $ad->name, 'shampoo' );
$ad = R::load( 'ad', $ad->id );
asrt( count( $ad->sharedPage ), 2 );
$ad = R::load( 'ad', $ad->id );
$homepage = reset( $ad->withCondition( ' `name` LIKE ? AND page.id > 0 ORDER BY id DESC ', array( '%ome%' ) )->sharedPage );
asrt( $homepage->name, 'homepage' );
}
/**
* More variations...
*
* @return void
*/
public function testEmbeddedSQLPart2()
{
list( $book1, $book2, $book3 ) = R::dispense( 'book', 3 );
$book1->position = 1;
$book2->position = 2;
$book3->position = 3;
$shelf = R::dispense( 'shelf' );
$shelf->ownBook = array( $book1, $book2, $book3 );
$id = R::store( $shelf );
$shelf = R::load( 'shelf', $id );
$books = $shelf->with( ' ORDER BY position ASC ' )->ownBook;
$book1 = array_shift( $books );
asrt( (int) $book1->position, 1 );
$book2 = array_shift( $books );
asrt( (int) $book2->position, 2 );
$book3 = array_shift( $books );
asrt( (int) $book3->position, 3 );
$books = $shelf->with( ' ORDER BY position DESC ' )->ownBook;
$book1 = array_shift( $books );
asrt( (int) $book1->position, 3 );
$book2 = array_shift( $books );
asrt( (int) $book2->position, 2 );
$book3 = array_shift( $books );
asrt( (int) $book3->position, 1 );
$shelf = R::load( 'shelf', $id );
$books = $shelf->with( ' AND position > 2 ' )->ownBook;
asrt( count( $books ), 1 );
$shelf = R::load( 'shelf', $id );
$books = $shelf->with( ' AND position < ? ', array( 3 ) )->ownBook;
asrt( count( $books ), 2 );
$shelf = R::load( 'shelf', $id );
$books = $shelf->with( ' AND position = 1 ' )->ownBook;
asrt( count( $books ), 1 );
$shelf = R::load( 'shelf', $id );
$books = $shelf->withCondition( ' position > -1 ' )->ownBook;
asrt( count( $books ), 3 );
// With-condition should not affect storing
$shelf = R::load( 'shelf', $id );
$books = $shelf->with( ' AND position = 1 ' )->ownBook;
asrt( count( $books ), 1 );
asrt( count( $shelf->ownBook ), 1 );
$book = reset( $shelf->ownBook );
$book->title = 'Trees and other Poems';
R::store( $shelf );
$books = $shelf->withCondition( ' position > -1 ' )->ownBook;
asrt( count( $books ), 3 );
asrt( count( $shelf->ownBook ), 3 );
$shelf = R::load( 'shelf', $id );
$books = $shelf->with( ' AND position = 1 ' )->ownBook;
// Also with trashing -- just trash one!
$shelf->ownBook = array();
R::store( $shelf );
$books = $shelf->withCondition( ' position > -1 ' )->ownBook;
asrt( count( $books ), 2 );
// With should cause a reload of a list
$shelf = R::load( 'shelf', $id );
$books = $shelf->with( ' AND position = 2 ' )->ownBook;
asrt( count( $books ), 1 );
$books = $shelf->withCondition( ' position > -1 ' )->ownBook;
asrt( count( $books ), 2 );
$book = reset( $books );
$book->title = 'Venetian Music';
// Should not affect storage (fact that we used with twice, unsetting prop)
R::store( $shelf );
$shelf = R::load( 'shelf', $id );
asrt( count( $shelf->ownBook ), 2 );
// Alias
list( $game1, $game2, $game3 ) = R::dispense( 'game', 3 );
list( $t1, $t2, $t3 ) = R::dispense( 'team', 3 );
$t1->name = 'Bats';
$t2->name = 'Tigers';
$t3->name = 'Eagles';
$game1->name = 'a';
$game1->team1 = $t1;
$game1->team2 = $t2;
$game2->name = 'b';
$game2->team1 = $t1;
$game2->team2 = $t3;
$game3->name = 'c';
$game3->team1 = $t2;
$game3->team2 = $t3;
R::storeAll( array( $game1, $game2, $game3 ) );
$team1 = R::load( 'team', $t1->id );
$team2 = R::load( 'team', $t2->id );
$team3 = R::load( 'team', $t3->id );
asrt( count( $team1->alias( 'team1' )->ownGame ), 2 );
asrt( count( $team2->alias( 'team1' )->ownGame ), 1 );
$team1 = R::load( 'team', $t1->id );
$team2 = R::load( 'team', $t2->id );
asrt( count( $team1->alias( 'team2' )->ownGame ), 0 );
asrt( count( $team2->alias( 'team2' )->ownGame ), 1 );
asrt( count( $team3->alias( 'team1' )->ownGame ), 0 );
$team3 = R::load( 'team', $t3->id );
asrt( count( $team3->alias( 'team2' )->ownGame ), 2 );
$team1 = R::load( 'team', $t1->id );
$games = $team1->alias( 'team1' )->ownGame;
$game4 = R::dispense( 'game' );
$game4->name = 'd';
$game4->team2 = $t3;
$team1->alias( 'team1' )->ownGame[] = $game4;
R::store( $team1 );
$team1 = R::load( 'team', $t1->id );
asrt( count( $team1->alias( 'team1' )->ownGame ), 3 );
foreach ( $team1->ownGame as $g ) {
if ( $g->name == 'a' ) $game = $g;
}
$game->name = 'match';
R::store( $team1 );
$team1 = R::load( 'team', $t1->id );
asrt( count( $team1->alias( 'team1' )->ownGame ), 3 );
$found = 0;
foreach ( $team1->ownGame as $g ) {
if ( $g->name == 'match' ) $found = 1;
}
if ( $found ) pass();
$team1->ownGame = array();
R::store( $team1 );
$team1 = R::load( 'team', $t1->id );
asrt( count( $team1->alias( 'team1' )->ownGame ), 0 );
$team1->ownBook[] = $book1;
R::store( $team1 );
$team1 = R::load( 'team', $t1->id );
asrt( count( $team1->alias( 'team1' )->ownGame ), 0 );
asrt( count( $team1->ownBook ), 1 );
}
/**
* Test when to reload and when to NOT reload beans.
* Use UNSET to reload a parent bean. Use UNSET or
* a modifier (with, withCondition, all) to reload a list.
* Use noLoad() to obtain an empty list - does not reload
* but sets an empty array.
*
* @return void
*/
public function testWhenToReload()
{
$book = R::dispense( 'book' );
$book->ownPage = R::dispense( 'page', 3 );
$book->author = R::dispense( 'author' );
$book->coauthor = R::dispense( 'author' );
R::store( $book );
$book = $book->fresh();
$firstPage = reset( $book->ownPage );
$id = $firstPage->id;
$book->ownPage[ $id ]->title = 'a';
//Do not reload an own list after manipulations
asrt( $book->ownPage[ $id ]->title, 'a' ); //dont reload!
$book->ownPage[] = R::dispense( 'page' ); //dont reload!
asrt( $book->ownPage[ $id ]->title, 'a' ); //dont reload!
asrt( $book->ownPageList[ $id ]->title, 'a' ); //dont reload!
asrt( $book->xownPageList[ $id ]->title, 'a' ); //dont reload!
asrt( $book->xownPage[ $id ]->title, 'a' ); //dont reload!
asrt( count( $book->ownPageList ), 4 );
//now trigger reload
unset( $book->ownPageList );
asrt( count( $book->ownPageList ), 3 );
$book->ownPage[] = R::dispense( 'page' );
asrt( count( $book->ownPageList ), 4 );
//now trigger reload
unset( $book->xownPageList );
asrt( count( $book->ownPageList ), 3 );
$book->ownPage[] = R::dispense( 'page' );
asrt( count( $book->ownPageList ), 4 );
//now trigger reload
unset( $book->xownPage );
asrt( count( $book->ownPageList ), 3 );
$book->ownPage[] = R::dispense( 'page' );
asrt( count( $book->ownPageList ), 4 );
//now trigger reload
unset( $book->ownPage );
asrt( count( $book->ownPageList ), 3 );
$book->ownPage[] = R::dispense( 'page' );
asrt( count( $book->ownPageList ), 4 );
//now trigger reload
$book->all()->ownPage;
asrt( count( $book->ownPageList ), 3 );
$book->ownPage[] = R::dispense( 'page' );
asrt( count( $book->ownPageList ), 4 );
//now trigger reload
$book->all()->xownPage;
asrt( count( $book->ownPageList ), 3 );
$book->ownPage[] = R::dispense( 'page' );
asrt( count( $book->ownPageList ), 4 );
//now trigger reload
$book->all()->ownPageList;
asrt( count( $book->ownPageList ), 3 );
$book->ownPage[] = R::dispense( 'page' );
asrt( count( $book->ownPageList ), 4 );
//now trigger reload
$book->all()->xownPageList;
asrt( count( $book->ownPageList ), 3 );
$book->ownPage[] = R::dispense( 'page' );
asrt( count( $book->ownPageList ), 4 );
//Do not reload an own list if told to not reload using noLoad()
$book->noLoad()->with(' LIMIT 1 ')->ownPage; //dont reload!
asrt( count( $book->xownPage ), 0); //dont reload!
$book->noLoad()->all()->ownPage; //dont reload!
asrt( count( $book->xownPage ), 0); //dont reload!
$book->noLoad()->alias('magazine')->ownPage; //dont reload!
asrt( count( $book->xownPage ), 0); //dont reload!
$book->noLoad()->withCondition('')->ownPage; //dont reload!
asrt( count( $book->xownPage ), 0); //dont reload!
//even if modifiers proceed noLoad()
$book->with(' LIMIT 1 ')->noLoad()->ownPage; //dont reload!
asrt( count( $book->xownPage ), 0); //dont reload!
$book->all()->noLoad()->ownPage; //dont reload!
asrt( count( $book->xownPage ), 0); //dont reload!
$book->alias('magazine')->noLoad()->ownPage; //dont reload!
asrt( count( $book->xownPage ), 0); //dont reload!
$book->withCondition('')->noLoad()->ownPage; //dont reload!
asrt( count( $book->xownPage ), 0); //dont reload!
//even in combinations
$book->all()->with(' LIMIT 1 ')->noLoad()->ownPage; //dont reload!
asrt( count( $book->xownPage ), 0); //dont reload!
$book->alias('magazine')->all()->noLoad()->ownPage; //dont reload!
asrt( count( $book->xownPage ), 0); //dont reload!
$book->alias('magazine')->with('LIMIT 1')->noLoad()->ownPage; //dont reload!
asrt( count( $book->xownPage ), 0); //dont reload!
$book->alias('magazine')->withCondition('')->noLoad()->ownPage; //dont reload!
asrt( count( $book->xownPage ), 0); //dont reload!
//now test shared list
$book->sharedTag = R::dispense( 'tag', 16 );
asrt( count( $book->sharedTag ), 16 );
$book->sharedTag[] = R::dispense( 'tag' );
asrt( count( $book->sharedTag ), 17 ); //dont reload after adding
$last = end( $book->sharedTagList );
$id = $last->id;
$book->sharedTag[ $id ]->title = 'b';
asrt( count( $book->sharedTag ), 17 ); //dont reload after manipulation
unset( $book->sharedTagList[ $id ] );
asrt( count( $book->sharedTag ), 16 ); //dont reload after manipulation
//now trigger reload
unset( $book->sharedTagList );
asrt( count( $book->sharedTag ), 0 );
$book->sharedTag = R::dispense( 'tag', 16 );
asrt( count( $book->sharedTag ), 16 );
//now trigger reload
unset( $book->sharedTag );
asrt( count( $book->sharedTag ), 0 );
$book->sharedTag = R::dispense( 'tag', 16 );
asrt( count( $book->sharedTag ), 16 );
//now trigger reload
$book->all()->sharedTag;
asrt( count( $book->sharedTag ), 0 );
$book->sharedTag = R::dispense( 'tag', 16 );
asrt( count( $book->sharedTag ), 16 );
//now trigger reload
$book->all()->sharedTagList;
asrt( count( $book->sharedTag ), 0 );
$book->sharedTag = R::dispense( 'tag', 16 );
asrt( count( $book->sharedTag ), 16 );
//Do not reload a sharedTag list if told to not reload using noLoad()
$book->noLoad()->with(' LIMIT 1 ')->sharedTag; //dont reload!
asrt( count( $book->sharedTag ), 0); //dont reload!
$book->noLoad()->all()->sharedTag; //dont reload!
asrt( count( $book->sharedTag ), 0); //dont reload!
$book->noLoad()->alias('magazine')->sharedTag; //dont reload!
asrt( count( $book->sharedTag ), 0); //dont reload!
$book->noLoad()->withCondition('')->sharedTag; //dont reload!
asrt( count( $book->sharedTag ), 0); //dont reload!
//even if modifiers proceed noLoad()
$book->with(' LIMIT 1 ')->noLoad()->sharedTag; //dont reload!
asrt( count( $book->sharedTag ), 0); //dont reload!
$book->all()->noLoad()->sharedTag; //dont reload!
asrt( count( $book->sharedTag ), 0); //dont reload!
$book->alias('magazine')->noLoad()->sharedTag; //dont reload!
asrt( count( $book->sharedTag ), 0); //dont reload!
$book->withCondition('')->noLoad()->ownPage; //dont reload!
asrt( count( $book->sharedTag ), 0); //dont reload!
//even in combinations
$book->all()->with(' LIMIT 1 ')->noLoad()->sharedTag; //dont reload!
asrt( count( $book->sharedTag ), 0); //dont reload!
$book->alias('magazine')->all()->noLoad()->sharedTag; //dont reload!
asrt( count( $book->sharedTag ), 0); //dont reload!
$book->alias('magazine')->with('LIMIT 1')->noLoad()->sharedTag; //dont reload!
asrt( count( $book->sharedTag ), 0); //dont reload!
$book->alias('magazine')->withCondition('')->noLoad()->sharedTag; //dont reload!
asrt( count( $book->sharedTag ), 0); //dont reload!
//test do not reload parent bean
$book->author->name = 'me';
asrt( $book->author->name, 'me' );
$book->fetchAs('author')->coauthor;
asrt( $book->author->name, 'me' );
$book->fetchAs('author')->author;
asrt( $book->author->name, 'me' );
$book->with(' LIMIT 1 ')->author;
asrt( $book->author->name, 'me' );
$book->withCondition('')->author;
asrt( $book->author->name, 'me' );
$book->all()->author;
asrt( $book->author->name, 'me' );
$book->noLoad()->author;
asrt( $book->author->name, 'me' );
$book->noLoad()->all()->author;
asrt( $book->author->name, 'me' );
$book->with('LIMIT 1')->noLoad()->all()->author;
asrt( $book->author->name, 'me' );
//now trigger reload
unset( $book->author );
asrt( $book->author->name, NULL );
$book->author->name = 'me';
asrt( $book->author->name, 'me' );
}
/**
* Tests whether modifiers are cleared after reading or
* writing a bean property.
*
* @return void
*/
public function testClearanceOfModFlags()
{
//test base condition, retrieving list or parent should not set flags
$book = R::dispense( 'book' );
asrt( $book->getModFlags(), '' );
$book->ownPage = R::dispense( 'page', 2 );
asrt( $book->getModFlags(), '' );
$book->xownPage = R::dispense( 'page', 2 );
asrt( $book->getModFlags(), '' );
$book->ownPageList = R::dispense( 'page', 2 );
asrt( $book->getModFlags(), '' );
$book->xownPageList = R::dispense( 'page', 2 );
asrt( $book->getModFlags(), '' );
$book->ownPage[] = R::dispense( 'page', 1 );
asrt( $book->getModFlags(), '' );
$book->xownPage[] = R::dispense( 'page', 1 );
asrt( $book->getModFlags(), '' );
$book->ownPageList[] = R::dispense( 'page', 1 );
asrt( $book->getModFlags(), '' );
$book->xownPageList[] = R::dispense( 'page', 1 );
asrt( $book->getModFlags(), '' );
$book->sharedPage = R::dispense( 'page', 2 );
asrt( $book->getModFlags(), '' );
$book->sharedPageList = R::dispense( 'page', 2 );
asrt( $book->getModFlags(), '' );
$book->sharedPage[] = R::dispense( 'page', 1 );
asrt( $book->getModFlags(), '' );
$book->sharedPageList[] = R::dispense( 'page', 1 );
asrt( $book->getModFlags(), '' );
$book->author = R::dispense( 'author' );
asrt( $book->getModFlags(), '' );
$book->title = 'title';
//Test whether appropriate flags are set and whether they are cleared after
//accessing a property.
$modifiers = array('with'=>'w', 'withCondition'=>'w', 'alias'=>'a', 'fetchAs'=>'f', 'all'=>'r', 'noLoad'=>'n');
$properties = array('ownPage', 'ownPageList', 'xownPage', 'xownPageList', 'sharedPage', 'sharedPageList', 'author', 'title');
foreach( $modifiers as $modifier => $flag ) {
foreach( $properties as $property ) {
$book = R::dispense( 'book' );
$book->$modifier('something');
$flags = $book->getModFlags();
$expect = $flag;
asrt( $flags, $expect );
$book->$property;
$flags = $book->getModFlags();
asrt( $flags, '' );
}
}
//now test combinations and also test whether we can
//clear modifiers manually using the clearModifiers() method.
foreach( $modifiers as $modifier => $flag ) {
foreach( $modifiers as $modifier2 => $flag2 ) {
foreach( $properties as $property ) {
$book = R::dispense( 'book' );
$book->$modifier( 'something' )->$modifier2( 'something' );
$flags = $book->getModFlags();
$expect = array($flag, $flag2);
$expect = array_unique( $expect );
sort( $expect );
$expect = implode( '', $expect );
asrt( $flags, $expect );
$book->$modifier( 'something' )->$modifier2( 'something' )->clearModifiers();
$flags = $book->getModFlags();
asrt( $flags, '' );
$book->$modifier( 'something' )->$modifier2( 'something' )->clearModifiers();
$book->$property;
$flags = $book->getModFlags();
asrt( $flags, '' );
}
}
}
$book = R::dispense( 'book' );
$book->ownPage = R::dispense( 'page', 2 );
$book->sharedPage = R::dispense( 'page', 2 );
R::store( $book );
$book = R::dispense( 'book' );
$book->alias('magazine')->ownPage = R::dispense( 'page', 2 );
R::store( $book );
//test modifier with countOwn and countShared methods
foreach( $modifiers as $modifier => $flag ) {
$book = R::dispense( 'book' );
if ($modifier === 'withCondition') $book->$modifier( ' 1 ' );
elseif ($modifier === 'with') $book->$modifier( ' LIMIT 1 ' );
elseif ($modifier === 'alias') $book->$modifier('magazine');
else $book->$modifier('something');
$flags = $book->getModFlags();
$expect = $flag;
asrt( $flags, $expect );
$book->countOwn('page');
$flags = $book->getModFlags();
asrt( $flags, '' );
if ($modifier === 'withCondition') $book->$modifier( ' 1 ' );
elseif ($modifier === 'with') $book->$modifier( ' LIMIT 1 ' );
elseif ($modifier === 'alias') $book->$modifier('magazine');
else $book->$modifier('something');
$flags = $book->getModFlags();
$expect = $flag;
asrt( $flags, $expect );
$book->countShared('page');
$flags = $book->getModFlags();
asrt( $flags, '' );
if ($modifier === 'withCondition') $book->$modifier( ' 1 ' );
elseif ($modifier === 'with') $book->$modifier( ' LIMIT 1 ' );
elseif ($modifier === 'alias') $book->$modifier('magazine');
else $book->$modifier('something');
$flags = $book->getModFlags();
$expect = $flag;
asrt( $flags, $expect );
unset( $book->author );
$flags = $book->getModFlags();
asrt( $flags, '' );
}
}
}

View File

@@ -0,0 +1,427 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
/**
* Writecache
*
* The Query Writer Cache tries to avoid unnecessary queries
* by using cache markers. This means repeatingly fetching the
* same parent bean for instance (in loop) won't hurt performance.
* The Query Writer Cache gets emptied if the chain of markers
* gets broken. All 'non destructive' queries are marked, as long
* as no other queries have been executed we can safely assume
* nothing in the database has changed for the current request.
* You might want to turn this form of caching off in long running
* PHP processes.
*
* @file RedUNIT/Base/Writecache.php
* @desc Tests the Query Writer cache implemented in the
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Writecache extends Base
{
/**
* What drivers should be loaded for this test pack?
*/
public function getTargetDrivers()
{
return array( 'mysql', 'pgsql', 'sqlite', 'CUBRID' );
}
/**
* Lynesth:
* Using flushCache() obviously fixes it.
* The example might be a bit stupid but it gets to the point.
* Someone might be using a specific query more than once with
* getCell (or any other) and thinking the
* cache works for those as well, try to make use of it.
*
* @return void
*/
public function testCacheFail()
{
R::nuke();
R::freeze( FALSE );
$author = R::dispense('author');
$author->name = "John";
$id = R::store($author);
$author = R::load('author', 1); // This gets cached
$author = $author->fresh(); // This gets cached
$author = R::findOne('author', ' id = ? ', array( 1 )); // This gets cached
asrt($author->name, 'John');
R::exec('UPDATE author SET name = "Bob" WHERE id = 1');
// It's broken because there's no cache check between those two calls
//more realistic query: 'SELECT MAX(id) FROM author -- keep-cache');
R::getCell('SELECT 123 -- keep-cache');
$author = R::load('author', $id);
asrt($author->name, 'John');
$author = $author->fresh();
asrt($author->name, 'John');
$author = R::findOne('author', ' id = ? ', array( 1 ));
asrt($author->name, 'John');
}
/**
* Test whether cache size remains constant (per type).
* Avoiding potential memory leaks. (Issue #424).
*
* @return void
*/
public function testCacheSize()
{
R::nuke();
R::useWriterCache( TRUE );
$writer = R::getWriter();
$bean = R::dispense( 'bean' );
$bean->prop = 1;
R::store( $bean );
$writer->flushCache( 20 );
$count = $writer->flushCache();
asrt( $count, 0 );
R::find( 'bean', ' prop < ? ', array( 1 ) );
$count = $writer->flushCache();
asrt( $count, 2 );
R::find( 'bean', ' prop < ? ', array( 2 ) );
$count = $writer->flushCache();
asrt( $count, 5 );
R::find( 'bean', ' prop < ? ', array( 2 ) );
$count = $writer->flushCache();
asrt( $count, 5 );
for( $i = 0; $i < 40; $i ++ ) {
R::find( 'bean', ' prop < ? ', array( $i ) );
}
$count = $writer->flushCache();
asrt( $count, 85 );
for( $i = 0; $i < 120; $i ++ ) {
R::find( 'bean', ' prop < ? ', array( $i ) );
}
$count = $writer->flushCache( 1 );
asrt( $count, 85 );
for( $i = 0; $i < 20; $i ++ ) {
R::find( 'bean', ' prop < ? ', array( $i ) );
}
$count = $writer->flushCache( 20 );
asrt( $count, 9 );
}
/**
* When using fetchAs(), Query Cache does not recognize objects
* that have been previously fetched, see issue #400.
*/
public function testCachingAndFetchAs()
{
testpack( 'Testing whether you can cache multiple records of the same type' );
R::debug( TRUE, 1 );
$logger = R::getDatabaseAdapter()->getDatabase()->getLogger();
R::nuke();
$coauthor1 = R::dispense( 'author' );
$coauthor1->name = 'John';
$book = R::dispense( 'book' );
$book->title = 'a Funny Tale';
$book->coauthor = $coauthor1;
$id = R::store( $book );
$coauthor = R::dispense( 'author' );
$coauthor->name = 'Pete';
$book = R::dispense( 'book' );
$book->title = 'a Funny Tale 2';
$book->coauthor = $coauthor;
$id = R::store( $book );
$book = R::dispense( 'book' );
$book->title = 'a Funny Tale 3';
$book->coauthor = $coauthor1;
$id = R::store( $book );
$books = R::find( 'book' );
$logger->clear();
$authors = array();
$authorsByName = array();
foreach($books as $book) {
$coAuthor = $book->with( ' ORDER BY title ASC ' )
->fetchAs( 'author' )->coauthor;
$authors[] = $coAuthor->name;
$authorsByName[ $coAuthor->name ] = $coAuthor;
}
asrt( count( $logger->grep( 'SELECT' ) ), 2 ); //must be 2! 3 if cache does not work!
asrt( count( $authors ), 3 );
asrt( isset( $authorsByName[ 'John' ] ), TRUE );
asrt( isset( $authorsByName[ 'Pete' ] ), TRUE );
$logger->clear();
$authors = array();
$authorsByName = array();
foreach($books as $book) {
$coAuthor = $book->with( ' ORDER BY title DESC ' )
->fetchAs( 'author' )->coauthor;
$authors[] = $coAuthor->name;
$authorsByName[ $coAuthor->name ] = $coAuthor;
}
asrt( count( $logger->grep( 'SELECT' ) ), 0 ); //must be 0!
asrt( count( $authors ), 3 );
asrt( isset( $authorsByName[ 'John' ] ), TRUE );
asrt( isset( $authorsByName[ 'Pete' ] ), TRUE );
}
/**
* Test effects of cache.
*
* @return void
*/
public function testCachingEffects()
{
testpack( 'Testing WriteCache Query Writer Cache' );
R::setNarrowFieldMode( FALSE );
R::useWriterCache( FALSE );
R::debug( TRUE, 1 );
$logger = R::getDatabaseAdapter()->getDatabase()->getLogger();
$book = R::dispense( 'book' )->setAttr( 'title', 'ABC' );
$book->ownPage[] = R::dispense( 'page' );
$id = R::store( $book );
// Test load cache -- without
$logger->clear();
$book = R::load( 'book', $id );
$book = R::load( 'book', $id );
asrt( count( $logger->grep( 'SELECT' ) ), 2 );
// With cache
R::useWriterCache( TRUE );
$logger->clear();
$book = R::load( 'book', $id );
$book = R::load( 'book', $id );
asrt( count( $logger->grep( 'SELECT' ) ), 1 );
R::useWriterCache( FALSE );
// Test find cache
$logger->clear();
$book = R::find( 'book' );
$book = R::find( 'book' );
asrt( count( $logger->grep( 'SELECT' ) ), 2 );
// With cache
R::getWriter()->setUseCache( TRUE );
$logger->clear();
$book = R::find( 'book' );
$book = R::find( 'book' );
asrt( count( $logger->grep( 'SELECT' ) ), 1 );
R::getWriter()->setUseCache( FALSE );
// Test combinations
$logger->clear();
$book = R::findOne( 'book', ' id = ? ', array( $id ) );
$book->ownPage;
R::batch( 'book', array( $id ) );
$book = R::findOne( 'book', ' id = ? ', array( $id ) );
$book->ownPage;
R::batch( 'book', array( $id ) );
asrt( count( $logger->grep( 'SELECT' ) ), 6 );
// With cache
R::getWriter()->setUseCache( TRUE );
$logger->clear();
R::batch( 'book', array( $id ) );
$book = R::findOne( 'book', ' id = ? ', array( $id ) );
$book->ownPage;
$book = R::findOne( 'book', ' id = ? ', array( $id ) );
$book->ownPage;
asrt( count( $logger->grep( 'SELECT' ) ), 3 );
R::getWriter()->setUseCache( FALSE );
// Test auto flush
$logger->clear();
$book = R::findOne( 'book' );
$book->name = 'X';
R::store( $book );
$book = R::findOne( 'book' );
asrt( count( $logger->grep( 'SELECT *' ) ), 2 );
// With cache
R::getWriter()->setUseCache( TRUE );
$logger->clear();
$book = R::findOne( 'book' );
$book->name = 'Y';
// Will flush
R::store( $book );
$book = R::findOne( 'book' );
// Now the same, auto flushed
asrt( count( $logger->grep( 'SELECT *' ) ), 2 );
R::getWriter()->setUseCache( FALSE );
// Test whether delete flushes as well (because uses selectRecord - might be a gotcha!)
R::store( R::dispense( 'garbage' ) );
$garbage = R::findOne( 'garbage' );
$logger->clear();
$book = R::findOne( 'book' );
R::trash( $garbage );
$book = R::findOne( 'book' );
asrt( count( $logger->grep( 'SELECT *' ) ), 2 );
R::store( R::dispense( 'garbage' ) );
$garbage = R::findOne( 'garbage' );
// With cache
R::getWriter()->setUseCache( TRUE );
$logger->clear();
$book = R::findOne( 'book' );
R::trash( $garbage );
$book = R::findOne( 'book' );
// Now the same, auto flushed
asrt( count( $logger->grep( 'SELECT *' ) ), 2 );
R::getWriter()->setUseCache( FALSE );
R::store( R::dispense( 'garbage' ) );
$garbage = R::findOne( 'garbage' );
// With cache
R::getWriter()->setUseCache( TRUE );
$logger->clear();
$book = R::findOne( 'book' );
R::getWriter()->queryRecord( 'garbage', array( 'id' => array( $garbage->id ) ) );
$book = R::findOne( 'book' );
// Now the same, auto flushed
asrt( count( $logger->grep( 'SELECT *' ) ), 2 );
$page = R::dispense('page');
$book->sharedPage[] = $page;
R::store( $book );
$logger->clear();
$link = R::getWriter()->queryRecordLink( 'book', 'page', $book->id, $page->id );
asrt( count( $logger->grep( 'SELECT' ) ), 1 );
$link = R::getWriter()->queryRecordLink( 'book', 'page', $book->id, $page->id );
asrt( count( $logger->grep( 'SELECT' ) ), 1 );
R::getWriter()->setUseCache( FALSE );
$link = R::getWriter()->queryRecordLink( 'book', 'page', $book->id, $page->id );
asrt( count( $logger->grep( 'SELECT' ) ), 2 );
R::getWriter()->setUseCache( TRUE );
R::setNarrowFieldMode( TRUE );
}
/**
* Try to fool the cache :)
*
* @return void
*/
public function testRegressions()
{
testpack( 'Testing possible regressions: Try to fool the cache' );
$str = 'SELECT * FROM ' . R::getWriter()->esc( 'bean', TRUE ) . ' WHERE ( ' . R::getWriter()->esc( 'id', TRUE ) . ' IN ( 1) ) ';
$bean = R::dispense( 'bean' );
$bean->title = 'abc';
$id = R::store( $bean );
$bean = R::load( 'bean', $id );
$bean->title = 'xxx';
R::store( $bean );
// Fire exact same query so cache may think no other query has been fired
R::exec( $str );
$bean = R::load( 'bean', $id );
asrt( $bean->title, 'xxx' );
}
/**
* Test keep-cache comment.
*
* @return void
*/
public function testKeepCacheCommentInSQL()
{
$bean = R::dispense( 'bean' );
$bean->title = 'abc';
$id = R::store( $bean );
$bean = R::load( 'bean', $id );
$bean->title = 'xxx';
R::store( $bean );
// Causes flush even though it contains -- keep-cache (not at the end, not intended)
R::findOne( 'bean', ' title = ? ', array( '-- keep-cache' ) );
$bean = R::load( 'bean', $id );
asrt( $bean->title, 'xxx' );
}
/**
*
* Same as above.. test keep cache.
*
* @return void
*/
public function testInstructNoDrop()
{
$str = 'SELECT * FROM ' . R::getWriter()->esc( 'bean', TRUE ) . ' -- keep-cache';
$bean = R::dispense( 'bean' );
$bean->title = 'abc';
$id = R::store( $bean );
$bean = R::load( 'bean', $id );
$bean->title = 'xxx';
R::store( $bean );
R::exec( $str );
$bean = R::load( 'bean', $id );
asrt( $bean->title, 'abc' );
R::nuke();
// Now INSTRUCT the cache to not drop the cache CASE 2
$str = 'SELECT * FROM ' . R::getWriter()->esc( 'bean', TRUE ) . ' -- keep-cache';
$bean = R::dispense( 'bean' );
$bean->title = 'abc';
$id = R::store( $bean );
$bean = R::load( 'bean', $id );
$bean->title = 'xxx';
R::store( $bean );
R::findOne( 'bean', ' title = ? ', array( 'cache' ) );
$bean = R::load( 'bean', $id );
asrt( $bean->title, 'xxx' );
}
/**
* Can we confuse the cache?
*
* @return void
*/
public function testConfusionRegression()
{
testpack( 'Testing possible confusion regression' );
$bean = R::dispense( 'bean' );
$bean->title = 'abc';
$id1 = R::store( $bean );
$bean = R::dispense( 'bean' );
$bean->title = 'abc2';
$id2 = R::store( $bean );
$bean = R::load( 'bean', $id1 );
asrt( $bean->title, 'abc' );
$bean = R::load( 'bean', $id2 );
asrt( $bean->title, 'abc2' );
}
/**
* Test Ghost beans....
*
* @return void
*/
public function testGhostBeans()
{
testpack( 'Testing ghost beans' );
$bean = R::dispense( 'bean' );
$bean->title = 'abc';
$id1 = R::store( $bean );
R::trash( $bean );
$bean = R::load( 'bean', $id1 );
asrt( (int) $bean->id, 0 );
}
/**
* Test explicit flush.
*
* @return void
*/
public function testExplicitCacheFlush()
{
testpack( 'Test cache flush (explicit)' );
R::setNarrowFieldMode( FALSE );
R::debug( TRUE, 1 );
$logger = R::getDatabaseAdapter()->getDatabase()->getLogger();
$bean = R::dispense( 'bean' );
$bean->title = 'abc';
$id1 = R::store( $bean );
$logger->clear();
$bean = R::load( 'bean', $id1 );
asrt( $bean->title, 'abc' );
asrt( count( $logger->grep( 'SELECT *' ) ), 1 );
$bean = R::load( 'bean', $id1 );
asrt( count( $logger->grep( 'SELECT *' ) ), 1 );
R::getWriter()->flushCache();
$bean = R::load( 'bean', $id1 );
asrt( count( $logger->grep( 'SELECT *' ) ), 2 );
R::getWriter()->flushCache();
R::getWriter()->setUseCache( FALSE );
R::setNarrowFieldMode( TRUE );
}
}

View File

@@ -0,0 +1,280 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Null
*
* Tests NULL handling.
*
* @file RedUNIT/Base/Xnull.php
* @desc Tests handling of NULL values.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Xnull extends Base
{
/**
* Tests whether we can create queries containing IS-NULL with
* the IS-NULL-Condition flag.
*/
public function testISNULLConditions()
{
R::nuke();
R::useISNULLConditions( FALSE );
$book = R::dispense('book');
$book->title = 'Much ado about Null';
R::store( $book );
$book = R::dispense('book');
$book->title = NULL;
R::store( $book );
$books = R::findLike('book', array( 'title' => NULL ) );
asrt(count($books), 2);
$wasFalse = R::useISNULLConditions( TRUE );
asrt( $wasFalse, FALSE );
$books = R::findLike('book', array( 'title' => NULL ) );
asrt(count($books), 1);
$books = R::find('book', ' title = :title ', array( 'title' => NULL ) );
asrt(count($books), 0);
}
/**
* Test Null bindings.
*/
public function testBindings()
{
R::nuke();
$book = R::dispense( 'book' );
$book->content = NULL;
//can we store a NULL?
asrt( is_null( $book->content ), TRUE );
R::store( $book );
//did we really store the NULL value ?
$book = R::findOne( 'book', ' content IS NULL ' );
asrt( ( $book instanceof OODBBean ), TRUE );
//still NULL, not empty STRING ?
asrt( is_null( $book->content ), TRUE );
$book->pages = 100;
R::store( $book );
//did we save it once again as NULL?
$book = R::findOne( 'book', ' content IS NULL ' );
asrt( ( $book instanceof OODBBean ), TRUE );
asrt( is_null( $book->content ), TRUE );
asrt( gettype( $book->pages ), 'string' );
$otherBook = R::dispense( 'book' );
$otherBook->pages = 99;
//also if the column is VARCHAR-like?
$otherBook->content = 'blah blah';
R::store( $otherBook );
$book = R::findOne( 'book', ' content IS NULL ' );
asrt( ( $book instanceof OODBBean ), TRUE );
asrt( is_null( $book->content ), TRUE );
asrt( intval( $book->pages ), 100 );
//can we query not NULL as well?
$book = R::findOne( 'book', ' content IS NOT NULL ' );
asrt( ( $book instanceof OODBBean ), TRUE );
asrt( is_null( $book->content ), FALSE );
asrt( intval( $book->pages ), 99 );
asrt( $book->content, 'blah blah' );
//Can we bind NULL directly?
$book->isGood = FALSE;
//Is NULL the default? And... no confusion with boolean FALSE?
R::store( $book );
$book = R::findOne( 'book', ' is_good IS NULL' );
asrt( ( $book instanceof OODBBean ), TRUE );
asrt( is_null( $book->content ), TRUE );
asrt( intval( $book->pages ), 100 );
$book = R::findOne( 'book', ' is_good = ?', array( 0 ) );
asrt( ( $book instanceof OODBBean ), TRUE );
asrt( is_null( $book->content ), FALSE );
asrt( intval( $book->pages ), 99 );
}
/**
* Tests whether we can NULLify a parent bean
* page->book if the parent (book) is already
* NULL. (isset vs array_key_exists bug).
*
* @return void
*/
public function testUnsetParent()
{
R::nuke();
$book = R::dispense( 'book' );
$book->title = 'My Book';
$page = R::dispense( 'page' );
$page->text = 'Lorem Ipsum';
$book->ownPage[] = $page;
R::store( $book );
$page = $page->fresh();
R::freeze( TRUE );
asrt( (int) $page->book->id, (int) $book->id );
unset( $page->book );
R::store( $page );
$page = $page->fresh();
asrt( (int) $page->book->id, (int) $book->id );
$page->book = NULL;
R::store( $page );
$page = $page->fresh();
asrt( $page->book, NULL );
asrt( $page->book_id, NULL );
asrt( $page->bookID, NULL );
asrt( $page->bookId, NULL );
$page = R::dispense( 'page' );
$page->text = 'Another Page';
$page->book = NULL;
try {
R::store( $page );
fail();
} catch( \Exception $exception ) {
pass();
}
unset($page->book);
R::store($page);
$page = $page->fresh();
$page->book = NULL; //this must set field id to NULL not ADD column!
try {
R::store($page);
pass();
} catch( \Exception $exception ) {
fail();
}
$page = $page->fresh();
$page->book = NULL;
R::store( $page );
$page = $page->fresh();
asrt( is_null( $page->book_id ), TRUE );
$page->book = $book;
R::store( $page );
$page = $page->fresh();
asrt( (int) $page->book->id, (int) $book->id );
$page->book = NULL;
R::store( $page );
asrt( is_null( $page->book_id ), TRUE );
asrt( is_null( $page->book ), TRUE );
R::freeze( FALSE );
}
/**
* Test nullifying aliased parent.
*
* @return void
*/
public function testUnsetAliasedParent()
{
R::nuke();
$book = R::dispense( 'book' );
$author = R::dispense( 'author' );
$book->coauthor = $author;
R::store( $book );
$book = $book->fresh();
asrt( is_null( $book->fetchAs('author')->coauthor ), FALSE );
unset( $book->coauthor );
R::store( $book );
$book = $book->fresh();
asrt( is_null( $book->fetchAs('author')->coauthor ), FALSE );
$book->coauthor = NULL;
R::store( $book );
$book = $book->fresh();
asrt( is_null( $book->fetchAs('author')->coauthor ), TRUE );
R::trash( $book );
R::trash( $author );
R::freeze( TRUE );
$book = R::dispense( 'book' );
$author = R::dispense( 'author' );
$book->coauthor = $author;
R::store( $book );
$book = $book->fresh();
asrt( is_null( $book->fetchAs('author')->coauthor ), FALSE );
unset( $book->coauthor );
R::store( $book );
$book = $book->fresh();
asrt( is_null( $book->fetchAs('author')->coauthor ), FALSE );
$book->coauthor = NULL;
R::store( $book );
$book = $book->fresh();
asrt( is_null( $book->fetchAs('author')->coauthor ), TRUE );
R::trash( $book );
R::trash( $author );
R::freeze( FALSE );
}
/**
* Test NULL handling, setting a property to NULL must
* cause a change.
*
* @return void
*/
public function testBasicNullHandling()
{
// NULL can change bean
$bean = R::dispense( 'bean' );
$bean->bla = 'a';
R::store( $bean );
$bean = $bean->fresh();
asrt( $bean->hasChanged( 'bla' ), FALSE );
$bean->bla = NULL;
asrt( $bean->hasChanged( 'bla' ), TRUE );
// NULL test
$page = R::dispense( 'page' );
$book = R::dispense( 'book' );
$page->title = 'a NULL page';
$page->book = $book;
$book->title = 'Why NUll is painful..';
R::store( $page );
$bookid = $page->book->id;
unset( $page->book );
$id = R::store( $page );
$page = R::load( 'page', $id );
$page->title = 'another title';
R::store( $page );
pass();
$page = R::load( 'page', $id );
$page->title = 'another title';
$page->book_id = NULL;
R::store( $page );
pass();
}
/**
* Here we test whether the column type is set correctly.
* Normally if you store NULL, the smallest type (bool/set) will
* be selected. However in case of a foreign key type INT should
* be selected because fks columns require matching types.
*
* @return void
*/
public function ColumnType()
{
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$book->ownPage[] = $page;
R::store( $book );
pass();
asrt( $page->getMeta( 'cast.book_id' ), 'id' );
}
/**
* Test meta column type.
*
* @return void
*/
public function TypeColumn()
{
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$page->book = $book;
R::store( $page );
pass();
asrt( $page->getMeta( 'cast.book_id' ), 'id' );
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace RedUNIT;
/**
* Blackhole
*
* The Blackhole class is the parent class of all tests that do not require a
* specific database connection.
*
* @file RedUNIT/Blackhole.php
* @desc Tests that do not require a database or can just use the base SQLite driver.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Blackhole extends RedUNIT
{
/**
* Returns a list of drivers for which this driver supports
* 'test rounds'. This class does not specify any drivers and
* can therefore be used as the base class of all tests not
* requiring a specific database connection.
*
* @see RedUNIT::getTargetDrivers() for details.
*
* @return array
*/
public function getTargetDrivers()
{
return array();
}
}

View File

@@ -0,0 +1,161 @@
<?php
namespace RedUNIT\Blackhole;
use RedUNIT\Blackhole as Blackhole;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\Logger\RDefault\Debug as Debugger;
/**
* Debug
*
* Tests debugging functions and checks whether the output
* of the debugger displays the correct information.
*
* @file RedUNIT/Blackhole/Debug.php
* @desc Tests Debugger II.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Debug extends Blackhole
{
/**
* Given a query, a set of bindings and an expected outcome,
* this method tests the result of the debugger.
*
* @param string $query
* @param mixed $bindings
* @param string $expected
* @param integer $mode
* @param string $expected2
*
* @return void
*/
private function testDebug($query, $bindings = NULL, $expected, $mode = 1, $expected2 = NULL)
{
$debugger = new Debugger;
$debugger->setMode( $mode );
$debugger->setParamStringLength( 20 );
ob_start();
if (!is_null($bindings)) {
$debugger->log($query, $bindings);
} else {
$debugger->log($query);
}
$out = ob_get_contents();
ob_clean();
ob_end_flush();
$logs = $debugger->getLogs();
$log = reset($logs);
$log = str_replace( "\e[32m", '', $log );
$log = str_replace( "\e[39m", '', $log );
asrt($log, $expected);
if (!$mode) {
asrt($out, $expected2);
}
$debugger->clear();
}
/**
* Tests the bean dump function used to inspect
* the contents of a bean.
*
* @return void
*/
public function testDump()
{
$beans = R::dispense( 'bean', 2 );
$beans[0]->name = 'hello';
$beans[1]->name = 'world';
$array = R::dump($beans);
asrt( is_array( $array ), TRUE );
foreach( $array as $item ) {
asrt( is_string( $item ), TRUE );
}
$beans[1]->name = 'world, and a very long string that should be shortened';
$array = R::dump($beans);
asrt( is_array( $array ), TRUE );
asrt( strpos( $array[1], '...' ), 35 );
dmp( $beans );
pass();
//test wrong input
asrt( is_array( R::dump( NULL ) ), TRUE );
asrt( count( R::dump( NULL ) ), 0 );
asrt( is_array( R::dump( '' ) ), TRUE );
asrt( count( R::dump( '' ) ), 0 );
asrt( is_array( R::dump( 1 ) ), TRUE );
asrt( count( R::dump( 1 ) ), 0 );
asrt( is_array( R::dump( TRUE ) ), TRUE );
asrt( count( R::dump( FALSE ) ), 0 );
}
/**
* Tests debugging with parameters.
*
* @return void
*/
public function testDebugger2()
{
testpack( 'Test debugger with params.' );
$this->testDebug('SELECT * FROM table', NULL, 'SELECT * FROM table');
$this->testDebug('SELECT * FROM book WHERE title = ?', array('my book'), 'SELECT * FROM book WHERE title = \'my book\'');
$this->testDebug('title = ? OR title = ?', array('book1', 'book2'), 'title = \'book1\' OR title = \'book2\'');
$this->testDebug('title = ? OR price = ?', array('book1', 20), 'title = \'book1\' OR price = 20');
$this->testDebug('number IN (?,?)', array(8,900), 'number IN (8,900)');
$this->testDebug('?', array(20), '20');
$this->testDebug('?,?', array('test',20), '\'test\',20');
$this->testDebug('?', array( NULL ), 'NULL');
$this->testDebug('title = ?', array( NULL ), 'title = NULL');
$this->testDebug('?,?', array( NULL,NULL ), 'NULL,NULL');
$this->testDebug('title = ?', array('a very long title that should be shortened'), 'title = \'a very long title th... \'');
$this->testDebug('title = ? OR title = ?', array('a very long title that should be shortened', 'another long title that should be shortened'), 'title = \'a very long title th... \' OR title = \'another long title t... \'');
$this->testDebug('title = ? OR ?', array('a very long title that should be shortened', NULL), 'title = \'a very long title th... \' OR NULL');
$this->testDebug('?,?', array('hello'), '\'hello\',:slot1');
$this->testDebug('title = :first OR title = :second', array(':first'=>'book1', ':second'=>'book2'), 'title = \'book1\' OR title = \'book2\'');
$this->testDebug('title = :first OR price = :second', array(':first'=>'book1', ':second'=>20), 'title = \'book1\' OR price = 20');
$this->testDebug('number IN (:one,:two)', array(':one'=>8, ':two'=>900), 'number IN (8,900)');
$this->testDebug('number IN (:one,:two)', array(':one'=>8, ':two'=>900, ':three'=>999), 'number IN (8,900)');
$this->testDebug('number IN (:one,:two)', array(':three'=>999, ':one'=>8, ':two'=>900), 'number IN (8,900)');
$this->testDebug('number IN (:one,:two)', array(':one'=>8, ':three'=>999, ':two'=>900), 'number IN (8,900)');
$this->testDebug(':a', array(':a'=>20), '20');
$this->testDebug(':a,?', array(':a'=>20, 30), '20,30');
$this->testDebug(':a,?', array(30, ':a'=>20), '20,30');
$this->testDebug('?,?', array('test',20), '\'test\',20');
$this->testDebug('?', array( NULL ), 'NULL');
$this->testDebug('title = ?', array( NULL ), 'title = NULL');
$this->testDebug('?,?', array( NULL,NULL ), 'NULL,NULL');
$this->testDebug('title = ?', array('a very long title that should be shortened'), 'title = \'a very long title th... \'');
$this->testDebug('title = ? OR title = ?', array('a very long title that should be shortened', 'another long title that should be shortened'), 'title = \'a very long title th... \' OR title = \'another long title t... \'');
$this->testDebug('title = ? OR ?', array('a very long title that should be shortened', NULL), 'title = \'a very long title th... \' OR NULL');
$this->testDebug('?,?', array('hello'), '\'hello\',:slot1');
$this->testDebug('hello ?', 'world', 'hello ?');
$this->testDebug(':slot0 :slot1 :slot2 :slot3 :slot4 :slot5 :slot6 :slot7 :slot8 :slot9 :slot10', array(
'a','b','c','d','e','f','g','h','i','j','k'
),"'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k'");
$this->testDebug('? ? ? ? ? ? ? ? ? ? ?', array(
'a','b','c','d','e','f','g','h','i','j','k'
),"'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k'");
$this->testDebug(':a :aaa :ab', array(':a'=>1,':aaa'=>2,':ab'=>3),'1 2 3');
Debugger::setOverrideCLIOutput( TRUE );
$this->testDebug('SELECT * FROM table', NULL, 'SELECT * FROM table', 0, 'SELECT * FROM table<br />');
$this->testDebug('DROP TABLE myths', NULL, 'DROP TABLE myths', 0, '<b style="color:red">DROP TABLE myths</b><br />');
$this->testDebug('SELECT * FROM book WHERE title = ?', array('my book'), 'SELECT * FROM book WHERE title = <b style="color:green">\'my book\'</b>');
Debugger::setOverrideCLIOutput( FALSE );
}
/**
* Test facade fancyDebug function.
*
* @return void
*/
public function testDebug2InFacade()
{
R::fancyDebug( TRUE );
pass();
}
}

View File

@@ -0,0 +1,130 @@
<?php
namespace RedUNIT\Blackhole;
use RedUNIT\Blackhole as Blackhole;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Export
*
* Tests whether we can export beans to arrays and whether we can
* use the exportAll function to convert entire hierarchies into
* nested array structures.
*
* @file RedUNIT/Blackhole/Export.php
* @desc Tests basic bean exporting features.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Export extends Blackhole
{
/**
* ExportAll.
*
* @return void
*/
public function testExportAll()
{
testpack( 'Test exportAll' );
$redbean = R::getRedBean();
$bean = R::dispense('bean');
$bean->import( array( 'a' => 1, 'b' => 2 ) );
$bean->setMeta( 'justametaproperty', 'hellothere' );
$arr = $bean->export();
asrt( is_array( $arr ), TRUE );
asrt( isset( $arr["a"] ), TRUE );
asrt( isset( $arr["b"] ), TRUE );
asrt( $arr["a"], 1 );
asrt( $arr["b"], 2 );
asrt( isset( $arr["__info"] ), FALSE );
$arr = $bean->export( TRUE );
asrt( isset( $arr["__info"] ), TRUE );
asrt( $arr["a"], 1 );
asrt( $arr["b"], 2 );
$exportBean = $redbean->dispense( 'abean' );
$exportBean->setMeta( 'metaitem.bla', 1 );
$exportedBean = $exportBean->export( TRUE );
asrt( $exportedBean["__info"]["metaitem.bla"], 1 );
asrt( $exportedBean["__info"]["type"], "abean" );
// Can we determine whether a bean is empty?
testpack( 'test $bean->isEmpty() function' );
$bean = R::dispense( 'bean' );
asrt( $bean->isEmpty(), TRUE );
asrt( ( count( $bean ) > 0 ), TRUE );
$bean->property = 1;
asrt( $bean->isEmpty(), FALSE );
asrt( ( count( $bean ) > 0 ), TRUE );
$bean->property = 0;
asrt( $bean->isEmpty(), TRUE );
asrt( ( count( $bean ) > 0 ), TRUE );
$bean->property = FALSE;
asrt( $bean->isEmpty(), TRUE );
asrt( ( count( $bean ) > 0 ), TRUE );
$bean->property = NULL;
asrt( $bean->isEmpty(), TRUE );
asrt( ( count( $bean ) > 0 ), TRUE );
unset( $bean->property );
asrt( $bean->isEmpty(), TRUE );
asrt( ( count( $bean ) > 0 ), TRUE );
// Export bug I found
$bandmember = R::dispense( 'bandmember' );
$bandmember->name = 'Duke';
$instrument = R::dispense( 'instrument' );
$instrument->name = 'Piano';
$bandmember->ownInstrument[] = $instrument;
$a = R::exportAll( $bandmember );
pass();
asrt( isset( $a[0] ), TRUE );
asrt( (int) $a[0]['id'], 0 );
asrt( $a[0]['name'], 'Duke' );
asrt( $a[0]['ownInstrument'][0]['name'], 'Piano' );
R::nuke();
$v = R::dispense( 'village' );
$b = R::dispense( 'building' );
$v->name = 'a';
$b->name = 'b';
$v->ownBuilding[] = $b;
$id = R::store( $v );
$a = R::exportAll( $v );
asrt( $a[0]['name'], 'a' );
asrt( $a[0]['ownBuilding'][0]['name'], 'b' );
$v = R::load( 'village', $id );
$b2 = R::dispense( 'building' );
$b2->name = 'c';
$v->ownBuilding[] = $b2;
$a = R::exportAll( $v );
asrt( $a[0]['name'], 'a' );
asrt( $a[0]['ownBuilding'][0]['name'], 'b' );
asrt( count( $a[0]['ownBuilding'] ), 2 );
list( $r1, $r2 ) = R::dispense( 'army', 2 );
$r1->name = '1';
$r2->name = '2';
$v->sharedArmy[] = $r2;
$a = R::exportAll( $v );
asrt( count( $a[0]['sharedArmy'] ), 1 );
R::store( $v );
$v = R::load( 'village', $id );
$a = R::exportAll( $v );
asrt( count( $a[0]['sharedArmy'] ), 1 );
asrt( $a[0]['name'], 'a' );
asrt( $a[0]['ownBuilding'][0]['name'], 'b' );
asrt( count( $a[0]['ownBuilding'] ), 2 );
$v->sharedArmy[] = $r1;
$a = R::exportAll( $v );
asrt( count( $a[0]['sharedArmy'] ), 2 );
$v = R::load( 'village', $id );
$a = R::exportAll( $v );
asrt( count( $a[0]['sharedArmy'] ), 1 );
$v->sharedArmy[] = $r1;
R::store( $v );
$v = R::load( 'village', $id );
$a = R::exportAll( $v );
asrt( count( $a[0]['sharedArmy'] ), 2 );
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace RedUNIT\Blackhole;
use RedUNIT\Blackhole as Blackhole;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\SimpleModel as SimpleModel;
/**
* Fusebox
*
* Tests whether we can convert a bean to a model and
* a model to a bean. This process is called boxing and
* unboxing.
*
* @file RedUNIT/Blackhole/Fusebox.php
* @desc Tests Boxing/Unboxing of beans.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Fusebox extends Blackhole
{
/**
* Test type hinting with boxed model
*
* @param Model_Soup $soup
*/
private function giveMeSoup( \Model_Soup $soup )
{
asrt( ( $soup instanceof \Model_Soup ), TRUE );
asrt( 'A bit too salty', $soup->taste() );
asrt( 'tomato', $soup->flavour );
}
/**
* Test unboxing
*
* @param OODBBean $bean
*/
private function giveMeBean( OODBBean $bean )
{
asrt( ( $bean instanceof OODBBean ), TRUE );
asrt( 'A bit too salty', $bean->taste() );
asrt( 'tomato', $bean->flavour );
}
/**
* Test boxing.
*
* @return void
*/
public function testBasicBox()
{
$soup = R::dispense( 'soup' );
$soup->flavour = 'tomato';
$this->giveMeSoup( $soup->box() );
$this->giveMeBean( $soup->box()->unbox() );
$this->giveMeBean( $soup );
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace RedUNIT\Blackhole;
use RedUNIT\Blackhole as Blackhole;
use RedBeanPHP\Facade as R;
use RedBeanPHP\QueryWriter as QueryWriter;
/**
* Glue
*
* RedBeanPHP does NOT parse entire queries and it does not
* ship with a query builder. However because RedBeanPHP
* facilitates mixing-in SQL snippets using methods like
* find(), via(), with(), withCondition() and so on...,
* it needs to be able to figure out how two query part strings
* can be connected to eachother. In particular parts beginning with or
* without 'WHERE', 'AND' and 'OR'. This test checks whether the
* QueryWriter has the ability to glue together query parts correctly.
* The gluer is part of the QueryWriter, however since this narrow
* slice of SQL syntax is so generic it's been implemented at Writer
* level and all drivers in RedBeanPHP inherit the generic implementation.
* At the moment of writing no additional glue methods had to be
* implemented.
*
* @file RedUNIT/Blackhole/Glue.php
* @desc Tests query gluer.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Glue extends Blackhole
{
/**
* Tests whether we can intelligently glue together SQL snippets.
*
* @return void
*/
public function testGlue()
{
$writer = R::getWriter();
//Can we add a condition without having to type 'WHERE' - usual suspects
asrt( $writer->glueSQLCondition( ' name = ? '), ' WHERE name = ? ' );
asrt( $writer->glueSQLCondition( ' value1 > ? OR value < ? '), ' WHERE value1 > ? OR value < ? ' );
//Does it recognize NON-WHERE conditions? - usual suspects
asrt( $writer->glueSQLCondition( ' ORDER BY name '), ' ORDER BY name ' );
asrt( $writer->glueSQLCondition( ' LIMIT 10 '), ' LIMIT 10 ' );
asrt( $writer->glueSQLCondition( ' OFFSET 20 '), ' OFFSET 20 ' );
//highly doubtful but who knows... - I think nobody will ever use this in a query snippet.
asrt( $writer->glueSQLCondition( ' GROUP BY grp '), ' GROUP BY grp ' );
asrt( $writer->glueSQLCondition( ' HAVING x = ? '), ' HAVING x = ? ' );
//can we replace WHERE with AND ?
asrt( $writer->glueSQLCondition( ' AND name = ? ', QueryWriter::C_GLUE_WHERE ), ' WHERE name = ? ' );
//can we glue with AND instead of WHERE ?
asrt( $writer->glueSQLCondition( ' value1 > ? OR value < ? ', QueryWriter::C_GLUE_AND ), ' AND value1 > ? OR value < ? ' );
//non-cases
asrt( $writer->glueSQLCondition( ' GROUP BY grp ', QueryWriter::C_GLUE_WHERE ), ' GROUP BY grp ' );
asrt( $writer->glueSQLCondition( ' GROUP BY grp ', QueryWriter::C_GLUE_AND ), ' GROUP BY grp ' );
}
}

View File

@@ -0,0 +1,283 @@
<?php
namespace RedUNIT\Blackhole;
use RedUNIT\Blackhole as Blackhole;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\RedException as RedException;
/**
* Import
*
* RedBeanPHP offers some methods to import arrays into
* beans. For instance using the dispense() method. This
* test suite checks whether RedBeanPHP can correctly convert
* array structures to beans and also checks the expected effects
* on the taint flags. This test suite further tests the 'simple'
* single bean import() function, the inject() function (bean-to-bean) and
* array access (because this is somehow related).
*
* @file RedUNIT/Blackhole/Import.php
* @desc Tests basic bean importing features.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Import extends Blackhole
{
/**
* Test import without trimming.
*
* @return void
*/
public function testImportWithoutTrim()
{
$book = R::dispense( 'book' );
$book->import( array( ' title ' => 'my book' ), array( ' title ' ), TRUE );
asrt( $book[' title '], 'my book' );
}
/**
* Test multi array dispense import.
*
* @return void
*/
public function testMultiRecurImport()
{
$books = R::dispense( array(
array( '_type' => 'book', 'title' => 'book one' ),
array( '_type' => 'book', 'title' => 'book two' ),
) );
asrt( is_array( $books ), TRUE );
asrt( count( $books ), 2 );
$book = reset( $books );
asrt( ( $book instanceof OODBBean ), TRUE );
asrt( $book->title, 'book one' );
$book = next( $books );
asrt( ( $book instanceof OODBBean ), TRUE );
asrt( $book->title, 'book two' );
}
/**
* Test recursive imports (formely known as R::graph).
*
* @return void
*/
public function testRecursiveImport()
{
$book = R::dispense(
array(
'_type'=>'book',
'title'=>'The magic book',
'ownPageList' => array(
array(
'_type' => 'page',
'content' => 'magic potions',
),
array(
'_type' => 'page',
'content' => 'magic spells',
)
)
)
);
$id = R::store( $book );
$book = R::load( 'book', $id );
asrt( $book->title, 'The magic book' );
$pages = $book->with(' ORDER BY content ASC ')->ownPageList;
asrt( count($pages), 2 );
$page1 = array_shift( $pages );
asrt( $page1->content, 'magic potions' );
$page2 = array_shift( $pages );
asrt( $page2->content, 'magic spells' );
R::nuke();
$book = R::dispense(
array(
'_type'=>'book',
'title'=>'The magic book',
'author' => array(
'_type' => 'author',
'name' => 'Dr. Evil'
),
'coAuthor' => array(
'_type' => 'author',
'name' => 'Dr. Creepy'
),
'ownPageList' => array(
array(
'_type' => 'page',
'content' => 'magic potions',
'ownRecipe' => array(
'a' => array('_type'=>'recipe', 'name'=>'Invisibility Salad'),
'b' => array('_type'=>'recipe', 'name'=>'Soup of Madness'),
'c' => array('_type'=>'recipe', 'name'=>'Love cake'),
)
),
array(
'_type' => 'page',
'content' => 'magic spells',
)
),
'sharedCategory' => array(
array(
'_type' => 'category',
'label' => 'wizardry'
),
)
)
);
$id = R::store( $book );
$book = R::load( 'book', $id );
asrt( $book->title, 'The magic book' );
$pages = $book->with(' ORDER BY content ASC ')->ownPageList;
asrt( count($pages), 2 );
$page1 = array_shift( $pages );
asrt( $page1->content, 'magic potions' );
$page2 = array_shift( $pages );
asrt( $page2->content, 'magic spells' );
$recipes = $page1->with(' ORDER BY name ASC ')->ownRecipeList;
asrt( count( $recipes ), 3 );
$recipe1 = array_shift( $recipes );
asrt( $recipe1->name, 'Invisibility Salad' );
$recipe2 = array_shift( $recipes );
asrt( $recipe2->name, 'Love cake' );
$recipe3 = array_shift( $recipes );
asrt( $recipe3->name, 'Soup of Madness' );
$categories = $book->sharedCategoryList;
asrt( count($categories), 1 );
$category = reset( $categories );
asrt( $category->label, 'wizardry' );
asrt( $book->author->name, 'Dr. Evil' );
asrt( $book->fetchAs('author')->coAuthor->name, 'Dr. Creepy' );
try {
$list = R::dispense( array() );
pass();
asrt( is_array( $list ), TRUE );
asrt( count( $list ), 0 );
} catch ( RedException $ex ) {
pass();
}
try {
R::dispense( array( array() ) );
fail();
} catch ( RedException $ex ) {
pass();
}
try {
R::dispense( array( 'a' ) );
fail();
} catch ( RedException $ex ) {
pass();
}
try {
R::dispense( array( 'property' => 'value' ) );
fail();
} catch ( RedException $ex ) {
pass();
}
}
/**
* Test import from and tainted.
*
* @return void
*/
public function testImportFromAndTainted()
{
testpack( 'Test importFrom() and Tainted' );
$bean = R::dispense( 'bean' );
R::store( $bean );
$bean->name = 'abc';
asrt( $bean->getMeta( 'tainted' ), TRUE );
R::store( $bean );
asrt( $bean->getMeta( 'tainted' ), FALSE );
$copy = R::dispense( 'bean' );
R::store( $copy );
$copy = R::load( 'bean', $copy->id );
asrt( $copy->getMeta( 'tainted' ), FALSE );
$copy->import( array( 'name' => 'xyz' ) );
asrt( $copy->getMeta( 'tainted' ), TRUE );
$copy->setMeta( 'tainted', FALSE );
asrt( $copy->getMeta( 'tainted' ), FALSE );
$copy->importFrom( $bean );
asrt( $copy->getMeta( 'tainted' ), TRUE );
testpack( 'Test basic import() feature.' );
$bean = R::dispense('bean');
$bean->import( array( "a" => 1, "b" => 2 ) );
asrt( $bean->a, 1 );
asrt( $bean->b, 2 );
$bean->import( array( "a" => 3, "b" => 4 ), "a,b" );
asrt( $bean->a, 3 );
asrt( $bean->b, 4 );
$bean->import( array( "a" => 5, "b" => 6 ), " a , b " );
asrt( $bean->a, 5 );
asrt( $bean->b, 6 );
$bean->import( array( "a" => 1, "b" => 2 ) );
testpack( 'Test inject() feature.' );
$coffee = R::dispense( 'coffee' );
$coffee->id = 2;
$coffee->liquid = 'black';
$cup = R::dispense( 'cup' );
$cup->color = 'green';
// Pour coffee in cup
$cup->inject( $coffee );
// Do we still have our own property?
asrt( $cup->color, 'green' );
// Did we pour the liquid in the cup?
asrt( $cup->liquid, 'black' );
// Id should not be transferred
asrt( $cup->id, 0 );
}
/**
* Test import using array access functions
*
* @return void
*/
public function testArrayAccess()
{
$book = R::dispense( 'book' );
$book->isAwesome = TRUE;
asrt( isset( $book->isAwesome ), TRUE );
$book = R::dispense( 'book' );
$book['isAwesome'] = TRUE;
asrt( isset( $book->isAwesome ), TRUE );
$book = R::dispense( 'book' );
$book['xownPageList'] = R::dispense( 'page', 2 );
asrt( isset( $book->ownPage ), TRUE );
asrt( isset( $book->xownPage ), TRUE );
asrt( isset( $book->ownPageList ), TRUE );
asrt( isset( $book->xownPageList ), TRUE );
$book = R::dispense( 'book' );
$book['ownPageList'] = R::dispense( 'page', 2 );
asrt( isset( $book->ownPage ), TRUE );
asrt( isset( $book->xownPage ), TRUE );
asrt( isset( $book->ownPageList ), TRUE );
asrt( isset( $book->xownPageList ), TRUE );
$book = R::dispense( 'book' );
$book['xownPage'] = R::dispense( 'page', 2 );
asrt( isset( $book->ownPage ), TRUE );
asrt( isset( $book->xownPage ), TRUE );
asrt( isset( $book->ownPageList ), TRUE );
asrt( isset( $book->xownPageList ), TRUE );
$book = R::dispense( 'book' );
$book['ownPage'] = R::dispense( 'page', 2 );
asrt( isset( $book->ownPage ), TRUE );
asrt( isset( $book->xownPage ), TRUE );
asrt( isset( $book->ownPageList ), TRUE );
asrt( isset( $book->xownPageList ), TRUE );
$book = R::dispense( 'book' );
$book['sharedTag'] = R::dispense( 'tag', 2 );
asrt( isset( $book->sharedTag ), TRUE );
asrt( isset( $book->sharedTagList ), TRUE );
$book = R::dispense( 'book' );
$book['sharedTagList'] = R::dispense( 'tag', 2 );
asrt( isset( $book->sharedTag ), TRUE );
asrt( isset( $book->sharedTagList ), TRUE );
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace RedUNIT\Blackhole;
use RedUNIT\Blackhole as Blackhole;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Labels
*
* Labels are very simple beans, only having a title and an ID.
* The idea behind labels is that they are very easy to generate
* from a comma separated list and they can easily be converted
* to such a list. Labels can be used for simplistic relations in the
* database like tagging or enums.
*
* @file RedUNIT/Blackhole/Labels.php
* @desc Tests Facade Label functions.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Labels extends Blackhole
{
/**
* Test basic labels.
*
* @return void
*/
public function testLabels()
{
testpack( 'Test Labels' );
$meals = R::dispenseLabels( 'meal', array( 'meat', 'fish', 'vegetarian' ) );
asrt( is_array( $meals ), TRUE );
asrt( count( $meals ), 3 );
foreach ( $meals as $m ) {
asrt( ( $m instanceof OODBBean ), TRUE );
}
$listOfMeals = implode( ',', R::gatherLabels( $meals ) );
asrt( $listOfMeals, 'fish,meat,vegetarian' );
}
}

View File

@@ -0,0 +1,123 @@
<?php
namespace RedUNIT\Blackhole;
use RedUNIT\Blackhole as Blackhole;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Meta
*
* Beans can have meta data. Meta data will not be stored
* in the database and cannot be loaded from the database
* (except for using special meta masks - this feature is available
* in 4.3.2+). This test suite tests whether we can set and get
* meta data from beans and verifies meta data does not end up
* in the database.
*
* @file RedUNIT/Blackhole/Meta.php
* @desc Tests meta data features on OODBBean class.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Meta extends Blackhole
{
/**
* Test meta data methods.
*
* @return void
*/
public function testMetaData()
{
testpack( 'Test meta data' );
$bean = new OODBBean;
$bean->setMeta( "this.is.a.custom.metaproperty", "yes" );
asrt( $bean->getMeta( "this.is.a.custom.metaproperty" ), "yes" );
asrt( $bean->getMeta( "nonexistant" ), NULL );
asrt( $bean->getMeta( "nonexistant", "abc" ), "abc" );
asrt( $bean->getMeta( "nonexistant.nested" ), NULL );
asrt( $bean->getMeta( "nonexistant,nested", "abc" ), "abc" );
$bean->setMeta( "test.two", "second" );
asrt( $bean->getMeta( "test.two" ), "second" );
$bean->setMeta( "another.little.property", "yes" );
asrt( $bean->getMeta( "another.little.property" ), "yes" );
asrt( $bean->getMeta( "test.two" ), "second" );
// Copy Metadata
$bean = new OODBBean;
$bean->setMeta( "meta.meta", "123" );
$bean2 = new OODBBean;
asrt( $bean2->getMeta( "meta.meta" ), NULL );
$bean2->copyMetaFrom( $bean );
asrt( $bean2->getMeta( "meta.meta" ), "123" );
}
/**
* Meta properties should not be saved.
*
* @return void
*/
public function testMetaPersist()
{
$bean = R::dispense( 'bean' );
$bean->property = 'test';
$bean->setMeta( 'meta', 'hello' );
R::store( $bean );
asrt( $bean->getMeta( 'meta' ), 'hello' );
$bean = $bean->fresh();
asrt( $bean->getMeta( 'meta' ), NULL );
}
/**
* You cant access meta data using the array accessors.
*
* @return void
*/
public function testNoArrayMetaAccess()
{
$bean = R::dispense( 'bean' );
$bean->setMeta( 'greet', 'hello' );
asrt( isset( $bean['greet'] ), FALSE );
asrt( isset( $bean['__info']['greet'] ), FALSE );
asrt( isset( $bean['__info'] ), FALSE );
asrt( isset( $bean['meta'] ), FALSE );
asrt( count( $bean ), 1 );
}
/**
* Test meta masks.
*
* @return void
*/
public function testMetaMask()
{
$rows = array(
array('id'=>1, 'name'=>'a', '__meta_rows'=>2, '__meta_columns'=>4),
array('id'=>2, 'name'=>'b', '__meta_rows'=>2, '__meta_columns'=>4)
);
$books = R::convertToBeans( 'book', $rows, '__meta' );
$book = reset($books);
$data = $book->getMeta('data.bundle');
asrt( $data['__meta_rows'], 2 );
asrt( $data['__meta_columns'], 4 );
$books = R::convertToBeans( 'book', $rows, array( '__meta_rows', '__meta_columns' ) );
$book = reset($books);
$data = $book->getMeta('data.bundle');
asrt( $data['__meta_rows'], 2 );
asrt( $data['__meta_columns'], 4 );
$books = R::convertToBeans( 'book', $rows, array( '__meta_rows' ) );
$book = reset($books);
$data = $book->getMeta('data.bundle');
asrt( $data['__meta_rows'], 2 );
asrt( isset($data['__meta_columns']), FALSE );
$books = R::convertToBeans( 'book', $rows, array( '__meta_rows', TRUE ) );
$book = reset($books);
$data = $book->getMeta('data.bundle');
asrt( isset($data['__meta_rows']), FALSE );
asrt( isset($data['__meta_columns']), FALSE );
}
}

View File

@@ -0,0 +1,624 @@
<?php
namespace RedUNIT\Blackhole;
use RedUNIT\Blackhole as Blackhole;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\Driver\RPDO as RPDO;
use RedBeanPHP\Logger\RDefault as RDefault;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\BeanHelper\SimpleFacadeBeanHelper as SimpleFacadeBeanHelper;
use RedBeanPHP\QueryWriter;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
use RedBeanPHP\QueryWriter\MySQL as MySQLQueryWriter;
use RedBeanPHP\QueryWriter\PostgreSQL as PostgresQueryWriter;
/**
* Misc
*
* This test suite contains tests for a various functionalities
* and scenarios. For more details please consult the document
* section attached to each individual test method listed here.
*
* @file RedUNIT/Blackhole/Misc.php
* @desc Tests various features that do not rely on a database connection.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Misc extends Blackhole
{
/*
* What drivers should be loaded for this test pack?
*/
public function getTargetDrivers()
{
return array( 'sqlite' );
}
/**
* Test whether we get the correct exception if we try to
* add a database we don't have a compatible QueryWriter for.
*
* @return void
*/
public function testUnsupportedDatabaseWriter()
{
$exception = NULL;
try {
R::addDatabase( 'x', 'blackhole:host=localhost;dbname=db', 'username', 'password' );
} catch( \Exception $e ) {
$exception = $e;
}
asrt( ( $exception instanceof RedException ), TRUE );
asrt( $exception->getMessage(), 'Unsupported database (blackhole).' );
$rpdo = new \TestRPO( new \MockPDO );
asrt( @$rpdo->testCap( 'utf8mb4' ), FALSE );
}
/**
* Misc tests.
* 'Tests' almost impossible lines to test.
* Not sure if very useful.
*
* @return void
*/
public function testMisc()
{
$null = R::getDatabaseAdapter()->getDatabase()->stringifyFetches( TRUE );
asrt( NULL, $null );
R::getDatabaseAdapter()->getDatabase()->stringifyFetches( FALSE );
}
/**
* Test whether we can toggle enforcement of the RedBeanPHP
* naming policy.
*
* @return void
*/
public function testEnforceNamingPolicy()
{
\RedBeanPHP\Util\DispenseHelper::setEnforceNamingPolicy( FALSE );
R::dispense('a_b');
pass();
\RedBeanPHP\Util\DispenseHelper::setEnforceNamingPolicy( TRUE );
try {
R::dispense('a_b');
fail();
} catch( \Exception $e ) {
pass();
}
}
/**
* Test R::csv()
*
* @return void
*/
public function testCSV()
{
\RedBeanPHP\Util\QuickExport::operation( 'test', TRUE, TRUE );
R::nuke();
$city = R::dispense('city');
$city->name = 'city1';
$city->region = 'region1';
$city->population = '200k';
R::store($city);
$qe = new \RedBeanPHP\Util\QuickExport( R::getToolBox() );
$out = $qe->csv( 'SELECT `name`, population FROM city WHERE region = :region ',
array( ':region' => 'region1' ),
array( 'city', 'population' ),
'/tmp/cities.csv'
);
$out = preg_replace( '/\W/', '', $out );
asrt( 'PragmapublicExpires0CacheControlmustrevalidatepostcheck0precheck0CacheControlprivateContentTypetextcsvContentDispositionattachmentfilenamecitiescsvContentTransferEncodingbinarycitypopulationcity1200k', $out );
}
/**
* Test whether sqlStateIn can detect lock timeouts.
*
* @return void
*/
public function testLockTimeoutDetection()
{
$queryWriter = new MySQLQueryWriter( R::getDatabaseAdapter() );
asrt($queryWriter->sqlStateIn('HY000', array(QueryWriter::C_SQLSTATE_LOCK_TIMEOUT), array(0,'1205')), TRUE);
$queryWriter = new PostgresQueryWriter( R::getDatabaseAdapter() );
asrt($queryWriter->sqlStateIn('55P03', array(QueryWriter::C_SQLSTATE_LOCK_TIMEOUT), array(0,'')), TRUE);
}
/**
* Tests setOption
*
* @return void
*/
public function testSetOptionFalse()
{
$false = R::getDatabaseAdapter()->setOption( 'unknown', 1 );
asrt( $false, FALSE );
}
/**
* Test whether we can use the JSONSerializable interface and
* whether old-style JSON is still the same (backwards compatibility).
*
* @return void
*/
public function testJSONSerialize()
{
$hotel = R::dispense( 'hotel' );
$hotel->name = 'Overlook';
$room = R::dispense( 'room' );
$room->number = 237;
$hotel->ownRoomList[] = $room;
$shine = (string) $hotel;
asrt( $shine, '{"id":0,"name":"Overlook"}' ); //basic JSON
$shine = json_encode( $hotel->jsonSerialize() ); //As of PHP 5.4 json_encode() will call jsonSerializable
asrt( $shine, '{"id":0,"name":"Overlook","ownRoom":[{"id":0,"number":237}]}' ); //should get full JSON
}
/**
* Tests max parameter binding.
*
* @return void
*/
public function testIntegerBindingMax()
{
if ( defined( 'HHVM_VERSION' ) ) return; //not for hhvm...
$driver = new RPDO( 'test-sqlite-53', 'user', 'pass' );
$max = $driver->getIntegerBindingMax();
asrt( $max, 2147483647 );
$driver = new RPDO( 'cubrid', 'user', 'pass' );
$max = $driver->getIntegerBindingMax();
asrt( $max, 2147483647 );
$driver = new RPDO( 'other', 'user', 'pass' );
$max = $driver->getIntegerBindingMax();
asrt( $max, PHP_INT_MAX );
}
/**
* Should not be able to pass invalid mode (must be 0 or 1).
*
*/
public function testInvalidDebugModeException()
{
try {
R::debug( TRUE, 6 );
fail();
} catch ( RedException $e ) {
pass();
}
R::debug( FALSE );
}
/**
* Adding a database twice no longer allowed, causes confusion
* and possible damage.
*/
public function testAddingTwice()
{
testpack( 'Test adding DB twice.' );
try {
R::addDatabase( 'sqlite', '' );
fail();
} catch ( RedException $ex ) {
pass();
}
}
/**
* Tests whether getID never produces a notice.
*
* @return void
*/
public function testGetIDShouldNeverPrintNotice()
{
set_error_handler(function($err, $errStr){
die('>>>>FAIL :'.$err.' '.$errStr);
});
$bean = new OODBBean;
$bean->getID();
restore_error_handler();
pass();
}
/**
* Tests setProperty.
*
* @return void
*/
public function testSetProperty()
{
$bean = R::dispense( 'bean' );
$bean->item = 2;
$bean->ownBean = R::dispense( 'bean', 2 );
R::store( $bean );
$bean = $bean->fresh();
$bean->ownBean;
$bean->setProperty( 'ownBean', array(), FALSE, FALSE );
asrt( count( $bean->ownBean ), 0 );
asrt( count( $bean->getMeta( 'sys.shadow.ownBean' ) ), 2 );
asrt( $bean->isTainted(), TRUE );
$bean->setProperty( 'ownBean', array(), TRUE, FALSE );
asrt( count( $bean->ownBean ), 0 );
asrt( count( $bean->getMeta( 'sys.shadow.ownBean' ) ), 0 );
asrt( $bean->isTainted(), TRUE );
$bean = $bean->fresh();
$bean->setProperty( 'ownBean', array(), TRUE, FALSE );
asrt( count( $bean->ownBean ), 0 );
asrt( count( $bean->getMeta( 'sys.shadow.ownBean' ) ), 0 );
asrt( $bean->isTainted(), FALSE );
$bean = $bean->fresh();
$bean->setProperty( 'ownBean', array(), TRUE, TRUE );
asrt( count( $bean->ownBean ), 0 );
asrt( count( $bean->getMeta( 'sys.shadow.ownBean' ) ), 0 );
asrt( $bean->isTainted(), TRUE );
}
/**
* Tests beansToArray().
*
* @return void
*/
public function testBeansToArray()
{
testpack('Test R::beansToArray method');
$bean1 = R::dispense( 'bean' );
$bean1->name = 'hello';
$bean2 = R::dispense( 'bean' );
$bean2->name = 'world';
$beans = array( $bean1, $bean2 );
$array = R::beansToArray( $beans );
asrt( $array[0]['name'], 'hello' );
asrt( $array[1]['name'], 'world' );
}
/**
* Test debugging with custom logger.
*
* @return void
*/
public function testDebugCustomLogger()
{
testpack( 'Test debug mode with custom logger' );
$pdoDriver = new RPDO( R::getDatabaseAdapter()->getDatabase()->getPDO() );
$customLogger = new \CustomLogger;
$pdoDriver->setDebugMode( TRUE, $customLogger );
$pdoDriver->Execute( 'SELECT 123' );
asrt( count( $customLogger->getLogMessage() ), 1 );
$pdoDriver->setDebugMode( TRUE, NULL );
asrt( ( $pdoDriver->getLogger() instanceof RDefault ), TRUE );
testpack( 'Test bean->getProperties method' );
$bean = R::dispense( 'bean' );
$bean->property = 'hello';
$props = $bean->getProperties();
asrt( isset( $props['property'] ), TRUE );
asrt( $props['property'], 'hello' );
}
/**
* Test Facade transactions.
*
* @return void
*
* @throws\Exception
*/
public function testTransactionInFacade()
{
testpack( 'Test transaction in facade' );
$bean = R::dispense( 'bean' );
$bean->name = 'a';
R::store( $bean );
R::trash( $bean );
R::freeze( TRUE );
$bean = R::dispense( 'bean' );
$bean->name = 'a';
R::store( $bean );
asrt( R::count( 'bean' ), 1 );
R::trash( $bean );
asrt( R::count( 'bean' ), 0 );
$bean = R::dispense( 'bean' );
$bean->name = 'a';
$id = R::transaction( function() use( &$bean ) {
return R::transaction( function() use( &$bean ) {
return R::store( $bean );
} );
} );
asrt( (int) $id, (int) $bean->id );
R::trash( $bean );
$bean = R::dispense( 'bean' );
$bean->name = 'a';
$id = R::transaction( function() use( &$bean ) {
return R::store( $bean );
} );
asrt( (int) $id, (int) $bean->id );
R::trash( $bean );
$bean = R::dispense( 'bean' );
$bean->name = 'a';
try {
R::transaction( function () use ( $bean ) {
R::store( $bean );
R::transaction( function () {
throw new\Exception();
} );
} );
} catch (\Exception $e ) {
pass();
}
asrt( R::count( 'bean' ), 0 );
$bean = R::dispense( 'bean' );
$bean->name = 'a';
try {
R::transaction( function () use ( $bean ) {
R::transaction( function () use ( $bean ) {
R::store( $bean );
throw new\Exception();
} );
} );
} catch (\Exception $e ) {
pass();
}
asrt( R::count( 'bean' ), 0 );
$bean = R::dispense( 'bean' );
$bean->name = 'a';
try {
R::transaction( function () use ( $bean ) {
R::transaction( function () use ( $bean ) {
R::store( $bean );
} );
} );
} catch (\Exception $e ) {
pass();
}
asrt( R::count( 'bean' ), 1 );
R::freeze( FALSE );
try {
R::transaction( 'nope' );
fail();
} catch (\Exception $e ) {
pass();
}
testpack( 'Test Camelcase 2 underscore' );
$names = array(
'oneACLRoute' => 'one_acl_route',
'ALLUPPERCASE' => 'alluppercase',
'clientServerArchitecture' => 'client_server_architecture',
'camelCase' => 'camel_case',
'peer2peer' => 'peer2peer',
'fromUs4You' => 'from_us4_you',
'lowercase' => 'lowercase',
'a1A2b' => 'a1a2b',
);
$bean = R::dispense( 'bean' );
foreach ( $names as $name => $becomes ) {
$bean->$name = 1;
asrt( isset( $bean->$becomes ), TRUE );
}
testpack( 'Misc Tests' );
R::debug( 1 );
flush();
ob_start();
R::exec( 'SELECT 123' );
$out = ob_get_contents();
ob_end_clean();
flush();
pass();
asrt( ( strpos( $out, 'SELECT 123' ) !== FALSE ), TRUE );
R::debug( 0 );
flush();
ob_start();
R::exec( 'SELECT 123' );
$out = ob_get_contents();
ob_end_clean();
flush();
pass();
asrt( $out, '' );
R::debug( 0 );
pass();
testpack( 'test to string override' );
$band = R::dispense( 'band' );
$str = strval( $band );
asrt( $str, 'bigband' );
testpack( 'test whether we can use isset/set in model' );
$band->setProperty( 'property1', 123 );
asrt( $band->property1, 123 );
asrt( $band->checkProperty( 'property1' ), TRUE );
asrt( $band->checkProperty( 'property2' ), FALSE );
$band = new \Model_Band;
$bean = R::dispense( 'band' );
$bean->property3 = 123;
$band->loadBean( $bean );
$bean->property4 = 345;
$band->setProperty( 'property1', 123 );
asrt( $band->property1, 123 );
asrt( $band->checkProperty( 'property1' ), TRUE );
asrt( $band->checkProperty( 'property2' ), FALSE );
asrt( $band->property3, 123 );
asrt( $band->property4, 345 );
testpack( 'Can we pass a\PDO object to Setup?' );
$pdo = new \PDO( 'sqlite:test.db' );
R::addDatabase( 'pdo', $pdo );
R::selectDatabase( 'pdo' );
R::getCell('SELECT 123;');
testpack( 'Test array interface of beans' );
$bean = R::dispense( 'bean' );
$bean->hello = 'hi';
$bean->world = 'planet';
asrt( $bean['hello'], 'hi' );
asrt( isset( $bean['hello'] ), TRUE );
asrt( isset( $bean['bye'] ), FALSE );
$bean['world'] = 'sphere';
asrt( $bean->world, 'sphere' );
foreach ( $bean as $key => $el ) {
if ( $el == 'sphere' || $el == 'hi' || $el == 0 ) {
pass();
} else {
fail();
}
if ( $key == 'hello' || $key == 'world' || $key == 'id' ) {
pass();
} else {
fail();
}
}
asrt( count( $bean ), 3 );
unset( $bean['hello'] );
asrt( count( $bean ), 2 );
asrt( count( R::dispense( 'countable' ) ), 1 );
// Otherwise untestable...
$bean->setBeanHelper( new SimpleFacadeBeanHelper() );
R::getRedBean()->setBeanHelper( new SimpleFacadeBeanHelper() );
pass();
// Test whether properties like owner and shareditem are still possible
testpack( 'Test Bean Interface for Lists' );
$bean = R::dispense( 'bean' );
// Must not be list, because first char after own is lowercase
asrt( is_array( $bean->owner ), FALSE );
// Must not be list, because first char after shared is lowercase
asrt( is_array( $bean->shareditem ), FALSE );
asrt( is_array( $bean->own ), FALSE );
asrt( is_array( $bean->shared ), FALSE );
asrt( is_array( $bean->own_item ), FALSE );
asrt( is_array( $bean->shared_item ), FALSE );
asrt( is_array( $bean->{'own item'} ), FALSE );
asrt( is_array( $bean->{'shared Item'} ), FALSE );
}
public function testConv2Beans()
{
$row1 = array('id' => 1, 'title'=>'test');
$row2 = array('id' => 2, 'title'=>'test2');
$beans = R::convertToBeans('page', array($row1, $row2));
asrt(count($beans), 2);
asrt($beans[2]->title, 'test2');
}
/**
* Test the most important invalid bean combinations.
*
* @return void
*/
public function testInvalidType()
{
$invalid = array(
'book_page', //no link beans
'a_b_c', //no prefix
'a b', //no space
'bean@', //no invalid symbols
'bean#', //no invalid symbols
'bean$', //sometimes used in DB, not allowed
'__bean',//no prefixes
'.bean', //no object notation
'bean-item', //no dash
'beanOther'); //no camelcase (uppercase because of file system issues)
foreach( $invalid as $j ) {
try {
R::dispense( $j );
fail();
} catch( RedException $e ) {
pass();
}
}
}
/**
* Test whether batch still works if no IDs have been passed.
*
* @return void
*/
public function testBatch0()
{
$zero = R::batch( 'page', array() );
asrt( is_array( $zero ), TRUE );
asrt( count( $zero ), 0 );
$zero = R::batch( 'page', FALSE );
asrt( is_array( $zero ), TRUE );
asrt( count( $zero ), 0 );
$zero = R::batch( 'page', NULL);
asrt( is_array( $zero ), TRUE );
asrt( count( $zero ), 0 );
}
/**
* Test whether connection failure does not reveal
* credentials.
*
* @return void
*/
public function testConnect()
{
$driver = new RPDO( 'dsn:invalid', 'usr', 'psst' );
try {
$driver->connect();
fail();
}
catch( \PDOException $e ) {
asrt( strpos( $e->getMessage(), 'invalid' ), FALSE );
asrt( strpos( $e->getMessage(), 'usr' ), FALSE );
asrt( strpos( $e->getMessage(), 'psst' ), FALSE );
}
}
/**
* Test whether we can create an instant database using
* R::setup().
*
* Probably only works on *NIX systems.
*
* @return void
*/
public function testSetup()
{
$tmpDir = sys_get_temp_dir();
R::setup();
}
/**
* Test camelCase to snake_case conversions.
*
* @return void
*/
public function testCamel2Snake()
{
asrt( AQueryWriter::camelsSnake('bookPage'), 'book_page' );
asrt( AQueryWriter::camelsSnake('FTP'), 'ftp' );
asrt( AQueryWriter::camelsSnake('ACLRules'), 'acl_rules' );
asrt( AQueryWriter::camelsSnake('SSHConnectionProxy'), 'ssh_connection_proxy' );
asrt( AQueryWriter::camelsSnake('proxyServerFacade'), 'proxy_server_facade' );
asrt( AQueryWriter::camelsSnake('proxySSHClient'), 'proxy_ssh_client' );
asrt( AQueryWriter::camelsSnake('objectACL2Factory'), 'object_acl2_factory' );
asrt( AQueryWriter::camelsSnake('bookItems4Page'), 'book_items4_page' );
asrt( AQueryWriter::camelsSnake('book☀Items4Page'), 'book☀_items4_page' );
}
/**
* Test that init SQL is being executed upon setting PDO.
*
* @return void
*/
public function testRunInitCodeOnSetPDO()
{
$pdo = R::getToolBox()->getDatabaseAdapter()->getDatabase()->getPDO();
$rpdo = new \RedBeanPHP\Driver\RPDO( $pdo );
$rpdo->setEnableLogging(true);
$logger = new \RedBeanPHP\Logger\RDefault\Debug;
$logger->setMode( \RedBeanPHP\Logger\RDefault::C_LOGGER_ARRAY );
$rpdo->setLogger( $logger );
$rpdo->setInitQuery('SELECT 123');
$rpdo->setPDO( $pdo );
$found = $logger->grep('SELECT 123');
asrt(count($found), 1);
asrt($found[0], 'SELECT 123');
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace RedUNIT\Blackhole;
use RedUNIT\Blackhole as Blackhole;
use RedBeanPHP\Facade as R;
use RedBeanPHP\Facade as Facade;
use RedBeanPHP\RedException as RedException;
/**
* Plugins
*
* This test suite tests whether we can define dynamic
* plugins using the ext() method on the facade.
*
* @file RedUNIT/Blackhole/Plugins.php
* @desc Tests extending R facade dynamically.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Plugins extends Blackhole
{
/**
* Test if we can dynamically extend the R-facade.
*
* @return void
*/
public function testDynamicPlugins()
{
testpack('Test dynamic plugins');
R::ext( 'makeTea', function() {
return 'sorry cant do that!';
});
asrt( R::makeTea(), 'sorry cant do that!' );
//with parameters
R::ext( 'multiply', function( $a, $b ) {
return $a * $b;
});
asrt( R::multiply( 3, 4 ), 12 );
//can we call R inside?
R::ext( 'singVersion', function() {
return R::getVersion() . ' lalala !';
} );
asrt( R::singVersion(), ( R::getVersion().' lalala !' ) );
//should also work with Facade
asrt( Facade::singVersion(), ( R::getVersion().' lalala !' ) );
//test error handling
try {
R::ext( '---', function() {} );
fail();
} catch ( RedException $e ) {
asrt( $e->getMessage(), 'Plugin name may only contain alphanumeric characters and underscores and cannot start with a number.' );
}
try {
R::__callStatic( '---', function() {} );
fail();
} catch ( RedException $e ) {
asrt( $e->getMessage(), 'Plugin name may only contain alphanumeric characters and underscores and cannot start with a number.' );
}
try {
R::invalidMethod();
fail();
} catch ( RedException $e ) {
asrt( $e->getMessage(), 'Plugin \'invalidMethod\' does not exist, add this plugin using: R::ext(\'invalidMethod\')' );
}
}
}

View File

@@ -0,0 +1,151 @@
<?php
namespace RedUNIT\Blackhole;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Stab tests for VMs without CUBRID database
*
* Tests CUBRID driver but without DB.
* For full tests see CUBRID test folder.
*
* @file RedUNIT/Base/Stub.php
* @desc Tests CUBRID without actual DB (mock adapter)
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Stub extends Base
{
/**
* Test CUBRID.
*
* @return void
*/
public function testCUBRID()
{
$mockdapter = new \Mockdapter();
$writer = new \DiagnosticCUBRIDWriter( $mockdapter );
pass();
$type = 'bean';
$targetType = 'other';
$property = 'property';
$targetProperty = 'other';
$value = 'value';
$properties = array( 'property','other' );
$table = 'bean';
$column = 'field';
$list = array();
$typedescription = ' STRING ';
$state = '';
$field = 'field';
$dbStructure = 'test';
$name = 'name';
$mockdapter->answerGetCol = array();
$writer->callMethod( 'buildFK', $type, $targetType, $property, $targetProperty, $isDep = FALSE );
pass();
$mockdapter->errorExec = new \RedBeanPHP\RedException\SQL('Test Exception');
$writer->callMethod( 'buildFK', $type, $targetType, $property, $targetProperty, $isDep = FALSE );
pass();
$mockdapter->errorExec = NULL;
$mockdapter->answerGetSQL = array(
array(
'CREATE TABLE' => 'CONSTRAINT [key] FOREIGN KEY ([bean]) REFERENCES [bean] ON DELETE CASCADE ON UPDATE RESTRICT'
)
);
$writer->addFK( $type, $targetType, $property, $targetProperty, $isDependent = FALSE );
pass();
$writer->callMethod( 'getKeyMapForType', 'bean' );
pass();
$writer->getTypeForID();
pass();
$writer->getTables();
pass();
$writer->createTable( $table );
pass();
$mockdapter->answerGetSQL = array(array('Field'=>'title','Type'=>'STRING'));
$writer->getColumns( $table );
pass();
asrt( $writer->scanType( 123, $flagSpecial = FALSE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_INTEGER );
asrt( $writer->scanType( 12.3, $flagSpecial = FALSE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_DOUBLE );
asrt( $writer->scanType( '0001', $flagSpecial = FALSE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_STRING );
asrt( $writer->scanType( '1001', $flagSpecial = FALSE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_INTEGER );
asrt( $writer->scanType( NULL, $flagSpecial = FALSE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_INTEGER );
asrt( $writer->scanType( '2019-01-01', $flagSpecial = FALSE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_STRING );
asrt( $writer->scanType( '2019-01-01 10:00:00', $flagSpecial = FALSE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_STRING );
asrt( $writer->scanType( '2019-01-01', $flagSpecial = TRUE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_SPECIAL_DATE );
asrt( $writer->scanType( '2019-01-01 10:00:00', $flagSpecial = TRUE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_SPECIAL_DATETIME );
pass();
$writer->code( $typedescription, $includeSpecials = FALSE );
$writer->code( $typedescription, $includeSpecials = TRUE );
asrt( $writer->code( 'INTEGER', FALSE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_INTEGER );
asrt( $writer->code( 'DOUBLE', FALSE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_DOUBLE );
asrt( $writer->code( 'STRING', FALSE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_STRING );
asrt( $writer->code( 'DATE', FALSE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_SPECIFIED );
asrt( $writer->code( 'DATETIME', FALSE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_SPECIFIED );
asrt( $writer->code( 'INTEGER', TRUE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_INTEGER );
asrt( $writer->code( 'DOUBLE', TRUE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_DOUBLE );
asrt( $writer->code( 'STRING', TRUE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_STRING );
asrt( $writer->code( 'DATE', TRUE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_SPECIAL_DATE );
asrt( $writer->code( 'DATETIME', TRUE ), \RedBeanPHP\QueryWriter\CUBRID::C_DATATYPE_SPECIAL_DATETIME );
pass();
$writer->addColumn( $type, $column, $field );
pass();
$writer->addUniqueConstraint( $type, $properties );
$mockdapter->errorExec = new \RedBeanPHP\RedException\SQL('Test Exception');
$writer->addUniqueConstraint( $type, $properties );
pass();
asrt( $writer->sqlStateIn( 'HY000', array() ), FALSE );
asrt( $writer->sqlStateIn( 'HY000', array(\RedBeanPHP\QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION) ), TRUE );
pass();
$writer->addIndex( $type, $name, $column );
pass();
$mockdapter->errorExec = NULL;
$writer->addIndex( $type, $name, $column );
pass();
$writer->wipeAll();
pass();
$mockdapter->answerGetCol = array( 'table1' );
$mockdapter->answerGetSQL = array(
array(
'CREATE TABLE' => 'CONSTRAINT [key] FOREIGN KEY ([bean]) REFERENCES [bean] ON DELETE CASCADE ON UPDATE RESTRICT'
)
);
$writer->wipeAll();
pass();
$writer->esc( $dbStructure, $noQuotes = FALSE );
pass();
}
/**
* Test base implementation of getKeyMapForType().
*
* @return void
*/
public function testKeyMap()
{
$proxyWriter = new \ProxyWriter;
$empty = $proxyWriter->callMethod( $proxyWriter, 'getKeyMapForType', 'bean' );
asrt( is_array( $empty ), TRUE );
asrt( count( $empty ), 0 );
}
/**
* Test whether autoresolve() function for BC exists.
*
* @return void
*/
public function testSetAutoResolve()
{
R::setAutoResolve( TRUE );
pass();
}
}

View File

@@ -0,0 +1,131 @@
<?php
namespace RedUNIT\Blackhole;
use RedUNIT\Blackhole as Blackhole;
use RedBeanPHP\Facade as R;
/**
* Tainted
*
* This test suite tests whether beans are marked as tainted
* under the correct circumstances.
*
* @file RedUNIT/Blackhole/Tainted.php
* @desc Tests tainted flag for OODBBean objects.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Tainted extends Blackhole
{
/**
* Test whether we can detect a change using hasChanged().
*
* @return void
*/
public function testHasChangedList()
{
R::nuke();
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
asrt( $book->hasListChanged( 'ownPage' ), FALSE );
$book->ownPage[] = $page;
asrt( $book->hasListChanged( 'ownPage' ), TRUE );
R::store( $book );
$book = $book->fresh();
asrt( $book->hasListChanged( 'ownPage' ), FALSE );
$page = R::dispense( 'page' );
$book->ownPageList[] = $page;
asrt( $book->hasListChanged( 'ownPage' ), TRUE );
R::store( $book );
$book = $book->fresh();
asrt( $book->hasListChanged( 'ownPage' ), FALSE );
asrt( count( $book->ownPageList ), 2 );
array_pop( $book->ownPageList );
asrt( count( $book->ownPageList ), 1 );
asrt( $book->hasListChanged( 'ownPage' ), TRUE );
array_pop( $book->ownPageList );
asrt( count( $book->ownPageList ), 0 );
asrt( $book->hasListChanged( 'ownPage' ), TRUE );
$book = $book->fresh();
asrt( $book->hasListChanged( 'ownPage' ), FALSE );
asrt( count( $book->ownPageList ), 2 );
$otherPage = R::dispense( 'page' );
array_pop( $book->ownPageList );
$book->ownPageList[] = $otherPage;
asrt( count( $book->ownPageList ), 2 );
asrt( $book->hasListChanged( 'ownPage' ), TRUE );
$book = $book->fresh();
$firstPage = reset( $book->ownPageList );
$firstPage->content = 'abc';
asrt( $book->hasListChanged( 'ownPage' ), FALSE );
$book = $book->fresh();
asrt( $book->hasListChanged( 'ownPage' ), FALSE );
$lastPage = end( $book->ownPageList );
$lastPage->ownText[] = R::dispense( 'text' );
asrt( $book->hasListChanged( 'ownPage' ), FALSE );
}
/**
* Tests whether we can clear the history of a bean.
*
* @return void
*/
public function testClearHist()
{
R::nuke();
$book = R::dispense( 'book' );
asrt( $book->hasChanged( 'title' ), FALSE );
$book->title = 'book';
asrt( $book->hasChanged( 'title' ), TRUE );
R::store( $book );
asrt( $book->hasChanged( 'title' ), TRUE );
$book->clearHistory();
asrt( $book->hasChanged( 'title' ), FALSE );
}
/**
* Test tainted.
*
* @return void
*/
public function testTainted()
{
testpack( 'Original Tainted Tests' );
$redbean = R::getRedBean();
$spoon = $redbean->dispense( "spoon" );
asrt( $spoon->getMeta( "tainted" ), TRUE );
$spoon->dirty = "yes";
asrt( $spoon->getMeta( "tainted" ), TRUE );
testpack( 'Tainted List test' );
$note = R::dispense( 'note' );
$note->text = 'abc';
$note->ownNote[] = R::dispense( 'note' )->setAttr( 'text', 'def' );
$id = R::store( $note );
$note = R::load( 'note', $id );
asrt( $note->isTainted(), FALSE );
// Shouldn't affect tainted
$note->text;
asrt( $note->isTainted(), FALSE );
$note->ownNote;
asrt( $note->isTainted(), TRUE );
testpack( 'Tainted Test Old Value' );
$text = $note->old( 'text' );
asrt( $text, 'abc' );
asrt( $note->hasChanged( 'text' ), FALSE );
$note->text = 'xxx';
asrt( $note->hasChanged( 'text' ), TRUE );
$text = $note->old( 'text' );
asrt( $text, 'abc' );
testpack( 'Tainted Non-exist' );
asrt( $note->hasChanged( 'text2' ), FALSE );
testpack( 'Misc Tainted Tests' );
$bean = R::dispense( 'bean' );
$bean->hasChanged( 'prop' );
$bean->old( 'prop' );
}
}

View File

@@ -0,0 +1,164 @@
<?php
namespace RedUNIT\Blackhole;
use RedUNIT\Blackhole as Blackhole;
use RedBeanPHP\Facade as R;
use RedBeanPHP\ToolBox as TB;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\Adapter as Adapter;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\BeanHelper as BeanHelper;
use RedBeanPHP\BeanHelper\SimpleFacadeBeanHelper as SimpleFacadeBeanHelper;
use RedBeanPHP\Repository as Repository;
use RedBeanPHP\Repository\Fluid as FluidRepo;
use RedBeanPHP\Repository\Frozen as FrozenRepo;
use RedBeanPHP\RedException as RedException;
/**
* Toolbox
*
* The Toolbox acts as a kind of micro service locator.
* The toolbox is passed around by RedBeanPHP core objects
* to share services. It contains an adapter, a query writer
* and the RedBeanPHP Object Database object (OODB).
* This test suite focuses on the toolbox.
*
* @file RedUNIT/Blackhole/Toolbox.php
* @desc Toolbox tests.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Toolbox extends Blackhole {
/**
* Tests the hasDatabase() method.
*
* @return void
*/
public function testDatabaseCheck()
{
R::addDatabase( 'key1', 'mysql:dsn1', 'user1', 'password1', TRUE );
asrt( R::hasDatabase( 'key1' ), TRUE );
asrt( R::hasDatabase( 'key2' ), FALSE );
}
/**
* Github issue #458, selectDatabase causes PHP notice undefined index
* if database key not found.
*
* @return void
*/
public function testInvalidDB()
{
try {
R::selectDatabase( 'idontexist' );
fail();
} catch ( RedException $exception ) {
pass();
}
}
/**
* Test whether we can obtain a toolbox properly.
*
* @return void
*/
public function testCanWeObtainToolbox()
{
$toolbox = R::getToolBox();
asrt( ( $toolbox instanceof TB), TRUE );
$extractedToolbox = R::getExtractedToolbox();
asrt( is_array( $extractedToolbox ), TRUE );
asrt( count( $extractedToolbox ), 4 );
asrt( ( $extractedToolbox[0] instanceof OODB ), TRUE );
asrt( ( $extractedToolbox[1] instanceof Adapter ), TRUE );
asrt( ( $extractedToolbox[2] instanceof QueryWriter ), TRUE );
asrt( ( $extractedToolbox[3] instanceof TB ), TRUE );
$beanHelper = new SimpleFacadeBeanHelper;
$toolbox2 = $beanHelper->getToolbox();
asrt( ( $toolbox2 instanceof TB), TRUE );
asrt( $toolbox, $toolbox2 );
$extractedToolbox = $beanHelper->getExtractedToolbox();
asrt( is_array( $extractedToolbox ), TRUE );
asrt( count( $extractedToolbox ), 4 );
asrt( ( $extractedToolbox[0] instanceof OODB ), TRUE );
asrt( ( $extractedToolbox[1] instanceof Adapter ), TRUE );
asrt( ( $extractedToolbox[2] instanceof QueryWriter ), TRUE );
asrt( ( $extractedToolbox[3] instanceof TB ), TRUE );
}
/**
* Does the toolbox contain the necessary tools ?
*
* @return void
*/
public function testDoesToolboxContainTheTools()
{
$toolbox = R::getToolBox();
asrt( ( $toolbox->getDatabaseAdapter() instanceof Adapter ), TRUE );
asrt( ( $toolbox->getRedBean() instanceof OODB ), TRUE );
asrt( ( $toolbox->getWriter() instanceof QueryWriter ), TRUE );
}
/**
* Tests whether freeze() switches the repository object
* as it is supposed to do.
*
* @return void
*/
public function testRepoSwitching()
{
asrt( class_exists( 'RedBeanPHP\Repository' ), TRUE );
asrt( class_exists( 'RedBeanPHP\Repository\Fluid' ), TRUE );
asrt( class_exists( 'RedBeanPHP\Repository\Frozen' ), TRUE );
R::freeze( FALSE );
$redbean = R::getRedBean();
$repo = $redbean->getCurrentRepository();
asrt( is_object( $repo ), TRUE );
asrt( ( $repo instanceof Repository ), TRUE );
asrt( ( $repo instanceof FluidRepo ), TRUE );
R::freeze( TRUE );
$fluid = $repo;
$repo = $redbean->getCurrentRepository();
asrt( is_object( $repo ), TRUE );
asrt( ( $repo instanceof Repository ), TRUE );
asrt( ( $repo instanceof FrozenRepo ), TRUE );
$frozen = $repo;
R::freeze( FALSE );
$redbean = R::getRedBean();
$repo = $redbean->getCurrentRepository();
asrt( is_object( $repo ), TRUE );
asrt( ( $repo instanceof Repository ), TRUE );
asrt( ( $repo instanceof FluidRepo ), TRUE );
asrt( $repo, $fluid );
R::freeze( TRUE );
$fluid = $repo;
$repo = $redbean->getCurrentRepository();
asrt( is_object( $repo ), TRUE );
asrt( ( $repo instanceof Repository ), TRUE );
asrt( ( $repo instanceof FrozenRepo ), TRUE );
asrt( $repo, $frozen );
R::freeze( FALSE );
}
/**
* Can we add and remove toolboxes using
* neat accessors?
*
* @return void
*/
public function testAddRemoveToolBox()
{
$t1 = R::getToolBox();
R::addToolBoxWithKey( 't1', $t1 );
asrt( ( R::getToolBoxByKey('t2') instanceof Toolbox), FALSE );
asrt( ( R::getToolBoxByKey('t1') instanceof Toolbox), FALSE );
asrt( R::removeToolBoxByKey('t1'), TRUE );
asrt( R::removeToolBoxByKey('t2'), FALSE );
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace RedUNIT\Blackhole;
use RedUNIT\Blackhole as Blackhole;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean;
/**
* Version
*
* This test suite tests whether we can properly
* obtain the version string. It also tests the availability
* of 'well known' entities like R, EID() and SimpleModel.
*
* @file RedUNIT/Blackhole/Version.php
* @desc Tests identification features.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Version extends Blackhole
{
/**
* Returns all features as a single string of
* comma separated values. For testing only.
*
* @return string
*/
private function getFeatureFlags()
{
$features = array();
$old = OODBBean::useFluidCount( TRUE );
OODBBean::useFluidCount( $old );
$features[] = intval( $old );
$old = R::noNuke( TRUE );
R::noNuke( $old );
$features[] = intval( $old );
$features[] = 0;
$old = R::setAllowHybridMode( TRUE );
R::setAllowHybridMode( $old );
$features[] = intval( $old );
$old = R::useISNULLConditions( TRUE );
R::useISNULLConditions( $old );
$features[] = intval( $old );
$features = implode( ',', $features );
return $features;
}
/**
* Test version info.
*
* @return void
*/
public function testVersion()
{
$version = R::getVersion();
asrt( is_string( $version ), TRUE );
}
/**
* Test whether basic tools are available for use.
*
* @return void
*/
public function testTools()
{
asrt( class_exists( '\\RedBean_SimpleModel' ), TRUE );
asrt( class_exists( '\\R' ), TRUE );
asrt( function_exists( 'EID' ), TRUE );
}
/**
* Test whether every label returns the correct set of
* feature flags.
*
* @return void
*/
public function testFeature()
{
R::useFeatureSet('original');
asrt( $this->getFeatureFlags(), '1,0,0,0,0' );
R::useFeatureSet('5.3');
asrt( $this->getFeatureFlags(), '1,0,0,0,0' );
R::useFeatureSet('novice/5.3');
asrt( $this->getFeatureFlags(), '1,1,0,0,0' );
R::useFeatureSet('5.4');
asrt( $this->getFeatureFlags(), '0,0,0,1,1' );
R::useFeatureSet('latest');
asrt( $this->getFeatureFlags(), '0,0,0,1,1' );
R::useFeatureSet('novice/5.4');
asrt( $this->getFeatureFlags(), '0,1,0,0,1' );
R::useFeatureSet('5.5');
asrt( $this->getFeatureFlags(), '0,0,0,1,1' );
R::useFeatureSet('novice/5.5');
asrt( $this->getFeatureFlags(), '0,1,0,0,1' );
R::useFeatureSet('novice/latest');
asrt( $this->getFeatureFlags(), '0,1,0,0,1' );
R::useFeatureSet('original');
asrt( $this->getFeatureFlags(), '1,0,0,0,0' );
}
/**
* Test whether an invalid feature set label will
* cause an exception.
*
* @return void
*/
public function testInvalidFeatureLabel()
{
try {
R::useFeatureSet('Invalid');
fail();
} catch( \Exception $e ) {
pass();
}
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace RedUNIT;
/**
* CUBRID
*
* The CUBRID class is the parent class for all CUBRID specific tests.
*
* @file RedUNIT/CUBRID.php
* @desc Base class for all test classes that aim to test the CUBRID database support.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class CUBRID extends RedUNIT
{
/**
* Returns a list of drivers for which this driver supports
* 'test rounds'. This class only supports the CUBRID driver.
*
* @see RedUNIT::getTargetDrivers() for details.
*
* @return array
*/
public function getTargetDrivers()
{
return array( 'CUBRID' );
}
}

View File

@@ -0,0 +1,147 @@
<?php
namespace RedUNIT\CUBRID;
use RedBeanPHP\Facade as R;
use \RedBeanPHP\RedException as RedException;
/**
* Setget
*
* This class has been designed to test set/get operations
* for a specific Query Writer / Adapter. Since RedBeanPHP
* creates columns based on values it's essential that you
* get back the 'same' value as you put in - or - if that's
* not the case, that there are at least very clear rules
* about what to expect. Examples of possible issues tested in
* this class include:
*
* - Test whether booleans are returned correctly (they will become integers)
* - Test whether large numbers are preserved
* - Test whether floating point numbers are preserved
* - Test whether date/time values are preserved
* and so on...
*
* @file RedUNIT/CUBRID/Setget.php
* @desc Tests whether values are stored correctly.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Setget extends \RedUNIT\CUBRID
{
/**
* Test whether we can store DateTime objects and get them back
* as 'date-time' strings representing the same date and time.
*
* @return void
*/
public function testDateObject()
{
$dt = new \DateTime();
$dt->setTimeZone( new \DateTimeZone( 'Europe/Amsterdam' ) );
$dt->setDate( 1981, 5, 1 );
$dt->setTime( 3, 13, 13 );
asrt( setget( $dt ), '1981-05-01 03:13:13.000' );
$bean = R::dispense( 'bean' );
$bean->dt = $dt;
}
/**
* Test numbers.
*
* @return void
*/
public function testNumbers()
{
asrt( setget( "-1" ), "-1" );
asrt( setget( -1 ), "-1" );
asrt( setget( "1.0" ), "1" );
asrt( setget( 1.0 ), "1" );
asrt( setget( "-0.25" ), "-0.2500000000000000" );
asrt( setget( -0.25 ), "-0.2500000000000000" );
asrt( setget( "0.12345678" ), "0.1234567800000000" );
asrt( setget( 0.12345678 ), "0.1234567800000000" );
asrt( setget( "-0.12345678" ), "-0.1234567800000000" );
asrt( setget( -0.12345678 ), "-0.1234567800000000" );
asrt( setget( "2147483647" ), "2147483647" );
asrt( setget( 2147483647 ), "2147483647" );
asrt( setget( -2147483647 ), "-2147483647" );
asrt( setget( "-2147483647" ), "-2147483647" );
asrt( setget( "2147483648" ), "2147483648.0000000000000000" );
asrt( setget( "-2147483648" ), "-2147483648.0000000000000000" );
asrt( setget( "199936710040730" ), "199936710040730.0000000000000000" );
asrt( setget( "-199936710040730" ), "-199936710040730.0000000000000000" );
}
/**
* Test dates.
*
* @return void
*/
public function testDates()
{
asrt( setget( "2010-10-11" ), "2010-10-11" );
asrt( setget( "2010-10-11 12:10" ), "2010-10-11 12:10" );
asrt( setget( "2010-10-11 12:10:11" ), "2010-10-11 12:10:11.000" );
asrt( setget( "x2010-10-11 12:10:11" ), "x2010-10-11 12:10:11" );
}
/**
* Test strings.
*
* @return void
*/
public function testStrings()
{
asrt( setget( "a" ), "a" );
asrt( setget( "." ), "." );
asrt( setget( "\"" ), "\"" );
asrt( setget( "just some text" ), "just some text" );
}
/**
* Test booleans.
*
* @return void
*/
public function testBool()
{
asrt( setget( TRUE ), "1" );
asrt( setget( FALSE ), "0" );
asrt( setget( "TRUE" ), "TRUE" );
asrt( setget( "FALSE" ), "FALSE" );
}
/**
* Test NULL.
*
* @return void
*/
public function testNull()
{
asrt( setget( "NULL" ), "NULL" );
asrt( setget( "NULL" ), "NULL" );
asrt( setget( "0123" ), "0123" );
asrt( setget( "0000123" ), "0000123" );
asrt( setget( NULL ), NULL );
asrt( ( setget( 0 ) == 0 ), TRUE );
asrt( ( setget( 1 ) == 1 ), TRUE );
asrt( ( setget( TRUE ) == TRUE ), TRUE );
asrt( ( setget( FALSE ) == FALSE ), TRUE );
// minor test sqltest
$a = R::getWriter()->sqlStateIn( '000', array() );
// Unknown state must return FALSE.
asrt( $a, FALSE );
try {
R::getWriter()->esc( '`aaa`' );
fail();
} catch (\Exception $e ) {
pass();
}
asrt( ( $e instanceof RedException ), TRUE );
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace RedUNIT\CUBRID;
use RedBeanPHP\Facade as R;
use \RedBeanPHP\QueryWriter\CUBRID as CUBRID;
/**
* Writer
*
* Tests for CUBRID Query Writer.
* This test class contains Query Writer specific tests.
* Use this class to add tests to test Query Writer specific
* behaviours, quirks and issues.
*
* @file RedUNIT/CUBRID/Writer.php
* @desc A collection of database specific writer functions.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Writer extends \RedUNIT\CUBRID
{
/**
* Test scanning and coding of values.
*
* @return void
*/
public function testScanningAndCoding()
{
$toolbox = R::getToolBox();
$adapter = $toolbox->getDatabaseAdapter();
$writer = $toolbox->getWriter();
$redbean = $toolbox->getRedBean();
$pdo = $adapter->getDatabase();
$writer->createTable( "testtable" );
$writer->addColumn( "testtable", "special", CUBRID::C_DATATYPE_SPECIAL_DATE );
$cols = $writer->getColumns( "testtable" );
asrt( $writer->code( $cols['special'], TRUE ), CUBRID::C_DATATYPE_SPECIAL_DATE );
asrt( $writer->code( $cols['special'], FALSE ), CUBRID::C_DATATYPE_SPECIFIED );
$writer->addColumn( "testtable", "special2", CUBRID::C_DATATYPE_SPECIAL_DATETIME );
$cols = $writer->getColumns( "testtable" );
asrt( $writer->code( $cols['special2'], TRUE ), CUBRID::C_DATATYPE_SPECIAL_DATETIME );
asrt( $writer->code( $cols['special'], FALSE ), CUBRID::C_DATATYPE_SPECIFIED );
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace RedUNIT;
/**
* Mysql
*
* The Mysql class is the parent class of all MySQL and MariaDB
* specific test classes.
*
* @file RedUNIT/Mysql.php
* @desc Base class for all tests that test support for MySQL/MariaDB database.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Mysql extends RedUNIT
{
/**
* Returns a list of drivers for which this driver supports
* 'test rounds'. This class only supports
* the MySQL/MariaDB driver.
*
* @see RedUNIT::getTargetDrivers() for details.
*
* @return array
*/
public function getTargetDrivers()
{
return array( 'mysql' );
}
}

View File

@@ -0,0 +1,130 @@
<?php
namespace RedUNIT\Mysql;
use RedUNIT\Mysql as Mysql;
use RedBeanPHP\Facade as R;
/**
* Bigint
*
* Tests handling of bigint type columns for primary key IDs,
* can we use bigint primary keys without issues ?
* These tests should be able to detect incorrect intval
* casts for instance.
*
* @file RedUNIT/Mysql/Bigint.php
* @desc Tests support for BIGINT primary keys.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Bigint extends Mysql
{
/**
* Test BIG INT primary key support.
*
* @return void
*/
public function testBigIntSupport()
{
R::nuke();
$createPageTableSQL = '
CREATE TABLE
`page`
(
id BIGINT(20) UNSIGNED NOT NULL,
book_id BIGINT(20) UNSIGNED NOT NULL,
magazine_id BIGINT(20) UNSIGNED NULL,
title VARCHAR(255),
PRIMARY KEY ( id )
)
ENGINE = InnoDB DEFAULT
CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci
AUTO_INCREMENT = 1223372036854775808';
$createBookTableSQL = '
CREATE TABLE
`book`
(
id BIGINT(20) UNSIGNED NOT NULL,
title VARCHAR(255),
PRIMARY KEY ( id )
)
ENGINE = InnoDB DEFAULT
CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci
AUTO_INCREMENT = 2223372036854775808';
$createPagePageTableSQL = '
CREATE TABLE
`page_page`
(
id BIGINT(20) UNSIGNED NOT NULL,
page_id BIGINT(20) UNSIGNED NOT NULL,
page2_id BIGINT(20) UNSIGNED NOT NULL,
PRIMARY KEY ( id )
)
ENGINE = InnoDB DEFAULT
CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci
AUTO_INCREMENT = 3223372036854775808';
R::exec( $createBookTableSQL );
R::exec( $createPageTableSQL );
R::exec( $createPagePageTableSQL );
//insert some records
$book1ID = '2223372036854775808';
$book2ID = '2223372036854775809';
$page1ID = '1223372036854775808';
$page2ID = '1223372036854775809';
$page3ID = '1223372036854775890';
$pagePage1ID = '3223372036854775808';
$insertBook1SQL = "
INSERT INTO book (id, title) VALUES( '$book1ID', 'book 1' );
";
$insertBook2SQL = "
INSERT INTO book (id, title) VALUES( '$book2ID', 'book 2' );
";
$insertPage1SQL = "
INSERT INTO page (id, book_id, title, magazine_id) VALUES( '$page1ID', '$book1ID', 'page 1 of book 1', '$book2ID' );
";
$insertPage2SQL = "
INSERT INTO page (id, book_id, title) VALUES( '$page2ID', '$book1ID', 'page 2 of book 1' );
";
$insertPage3SQL = "
INSERT INTO page (id, book_id, title) VALUES( '$page3ID', '$book2ID', 'page 1 of book 2' );
";
$insertPagePage1SQL = "
INSERT INTO page_page (id, page_id, page2_id) VALUES( '$pagePage1ID', '$page2ID', '$page3ID' );
";
R::exec( $insertBook1SQL );
R::exec( $insertBook2SQL );
R::exec( $insertPage1SQL );
R::exec( $insertPage2SQL );
R::exec( $insertPage3SQL );
R::exec( $insertPagePage1SQL );
//basic tour of basic functions....
$book1 = R::load( 'book', $book1ID );
asrt( $book1->id, $book1ID );
asrt( $book1->title, 'book 1' );
$book2 = R::load( 'book', $book2ID );
asrt( $book2->id, $book2ID );
asrt( $book2->title, 'book 2' );
asrt( count( $book1->ownPage ), 2 );
asrt( count( $book1->fresh()->with( 'LIMIT 1' )->ownPage ), 1 );
asrt( count( $book1->fresh()->withCondition( ' title = ? ', array('page 2 of book 1'))->ownPage ), 1 );
asrt( count($book2->ownPage), 1 );
asrt( $book2->fresh()->countOwn( 'page' ), 1 );
$page1 = R::load( 'page', $page1ID );
asrt( count( $page1->sharedPage ), 0 );
asrt( $page1->fetchAs( 'book' )->magazine->id, $book2ID );
$page2 = R::load( 'page', $page2ID );
asrt( count($page2->sharedPage), 1 );
asrt( $page2->fresh()->countShared( 'page' ), 1 );
$page3 = R::findOne( 'page', ' title = ? ', array( 'page 1 of book 2' ) );
asrt( $page3->id, $page3ID );
asrt( $page3->book->id, $book2ID );
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace RedUNIT\Mysql;
use RedUNIT\Mysql as Mysql;
use RedBeanPHP\Facade as R;
/**
* Double
*
* Tests whether double precision values are correctly stored and
* preserved.
*
* @file RedUNIT/Mysql/Double.php
* @desc Tests handling of double precision values.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Double extends Mysql
{
/**
* Test storage of doubles.
*
* @return void
*/
public function testDouble()
{
$toolbox = R::getToolBox();
$adapter = $toolbox->getDatabaseAdapter();
$writer = $toolbox->getWriter();
$redbean = $toolbox->getRedBean();
$pdo = $adapter->getDatabase();
$largeDouble = 999999888889999922211111; //8.88889999922211e+17;
$page = $redbean->dispense( "page" );
$page->weight = $largeDouble;
$id = $redbean->store( $page );
$cols = $writer->getColumns( 'page' );
asrt( $cols['weight'], 'double' );
$page = $redbean->load( 'page', $id );
$page->name = 'dont change the numbers!';
$redbean->store( $page );
$page = $redbean->load( 'page', $id );
$cols = $writer->getColumns( 'page' );
asrt( $cols['weight'], 'double' );
}
}

View File

@@ -0,0 +1,342 @@
<?php
namespace RedUNIT\Mysql;
use RedUNIT\Mysql as Mysql;
use RedBeanPHP\Facade as R;
/**
* Foreignkeys
*
* Tests creation and validity of foreign keys,
* foreign key constraints and indexes in Mysql/MariaDB.
* Also tests whether the correct contraint action has been selected.
*
* @file RedUNIT/Mysql/Foreignkeys.php
* @desc Tests creation of foreign keys.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Foreignkeys extends Mysql
{
/**
* Test whether we can use foreign keys with keywords.
*
* @return void
*/
public function testKWConflicts()
{
R::nuke();
$metrics = R::dispense( 'metrics' );
$constraint = R::dispense( 'constraint' );
$constraint->xownMetrics[] = $metrics;
R::store( $constraint );
asrt( 1, R::count( 'metrics' ) );
R::trash($constraint);
asrt( 0, R::count( 'metrics') );
}
/**
* Basic FK tests.
*
* @return void
*/
public function testFKS()
{
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$cover = R::dispense( 'cover' );
list( $g1, $g2 ) = R::dispense( 'genre', 2 );
$g1->name = '1';
$g2->name = '2';
$book->ownPage = array( $page );
$book->cover = $cover;
$book->sharedGenre = array( $g1, $g2 );
R::store( $book );
$fkbook = R::getAll( 'describe book' );
$fkgenre = R::getAll( 'describe book_genre' );
$fkpage = R::getAll( 'describe cover' );
$j = json_encode( R::getAll( 'SELECT
ke.referenced_table_name parent,
ke.table_name child,
ke.constraint_name
FROM
information_schema.KEY_COLUMN_USAGE ke
WHERE
ke.referenced_table_name IS NOT NULL
AND ke.CONSTRAINT_SCHEMA="oodb"
ORDER BY
constraint_name;' ) );
$json = '[
{
"parent": "genre",
"child": "book_genre",
"constraint_name": "c_fk_book_genre_genre_id"
},
{
"parent": "book",
"child": "book_genre",
"constraint_name": "c_fk_book_genre_book_id"
},
{
"parent": "cover",
"child": "book",
"constraint_name": "c_fk_book_cover_id"
},
{
"parent": "book",
"child": "page",
"constraint_name": "c_fk_page_book_id"
}
]';
$j1 = json_decode( $j, TRUE );
$j2 = json_decode( $json, TRUE );
foreach ( $j1 as $jrow ) {
$s = json_encode( $jrow );
$found = 0;
foreach ( $j2 as $k => $j2row ) {
if ( json_encode( $j2row ) === $s ) {
pass();
unset( $j2[$k] );
$found = 1;
break;
}
}
if ( !$found ) fail();
}
}
/**
* Test widen for constraint.
*
* @return void
*/
public function testWideningColumnForConstraint()
{
testpack( 'widening column for constraint' );
$bean1 = R::dispense( 'project' );
$bean2 = R::dispense( 'invoice' );
$bean3 = R::getRedBean()->dispense( 'invoice_project' );
$bean3->project_id = FALSE;
$bean3->invoice_id = TRUE;
R::store( $bean3 );
$cols = R::getColumns( 'invoice_project' );
asrt( $cols['project_id'], "int(11) unsigned" );
asrt( $cols['invoice_id'], "int(11) unsigned" );
}
/**
* Test adding of constraints directly by invoking
* the writer method.
*
* @return void
*/
public function testContrain()
{
R::nuke();
$sql = '
CREATE TABLE book (
id INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY ( id )
)
ENGINE = InnoDB
';
R::exec( $sql );
$sql = '
CREATE TABLE page (
id INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY ( id )
)
ENGINE = InnoDB
';
R::exec( $sql );
$sql = '
CREATE TABLE book_page (
id INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT,
book_id INT( 11 ) UNSIGNED NOT NULL,
page_id INT( 11 ) UNSIGNED NOT NULL,
PRIMARY KEY ( id )
)
ENGINE = InnoDB
';
R::exec( $sql );
$numOfFKS = R::getCell('
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
WHERE TABLE_NAME = "book_page" AND DELETE_RULE = "CASCADE"');
asrt( (int) $numOfFKS, 0 );
$writer = R::getWriter();
$writer->addFK( 'book_page', 'book', 'book_id', 'id', TRUE );
$writer->addFK( 'book_page', 'page', 'page_id', 'id', TRUE );
$numOfFKS = R::getCell('
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
WHERE TABLE_NAME = "book_page" AND DELETE_RULE = "CASCADE"');
asrt( (int) $numOfFKS, 2 );
$writer->addFK( 'book_page', 'book', 'book_id', 'id', TRUE );
$writer->addFK( 'book_page', 'page', 'page_id', 'id', TRUE );
$numOfFKS = R::getCell('
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
WHERE TABLE_NAME = "book_page" AND DELETE_RULE = "CASCADE"');
asrt( (int) $numOfFKS, 2 );
}
/**
* Test adding foreign keys.
*
* @return void
*/
public function testAddingForeignKey()
{
R::nuke();
$sql = '
CREATE TABLE book (
id INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY ( id )
)
ENGINE = InnoDB
';
R::exec( $sql );
$sql = '
CREATE TABLE page (
id INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT,
book_id INT( 11 ) UNSIGNED NOT NULL,
PRIMARY KEY ( id )
)
ENGINE = InnoDB
';
R::exec( $sql );
$numOfFKS = R::getCell('
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
WHERE TABLE_NAME = "page" AND DELETE_RULE = "CASCADE"');
asrt( (int) $numOfFKS, 0 );
$writer = R::getWriter();
//Can we add a foreign key with cascade?
$writer->addFK('page', 'book', 'book_id', 'id', TRUE);
$numOfFKS = R::getCell('
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
WHERE TABLE_NAME = "page" AND DELETE_RULE = "CASCADE"');
asrt( (int) $numOfFKS, 1 );
//dont add it twice
$writer->addFK('page', 'book', 'book_id', 'id', TRUE);
$numOfFKS = R::getCell('
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
WHERE TABLE_NAME = "page" AND DELETE_RULE = "CASCADE"');
asrt( (int) $numOfFKS, 1 );
//even if different
$writer->addFK('page', 'book', 'book_id', 'id', FALSE);
$numOfFKS = R::getCell('
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
WHERE TABLE_NAME = "page" ');
asrt( (int) $numOfFKS, 1 );
//Now add non-dep key
R::nuke();
$sql = '
CREATE TABLE book (
id INT( 11 ) UNSIGNED AUTO_INCREMENT,
PRIMARY KEY ( id )
)
ENGINE = InnoDB
';
R::exec( $sql );
$sql = '
CREATE TABLE page (
id INT( 11 ) UNSIGNED AUTO_INCREMENT,
book_id INT( 11 ) UNSIGNED NULL,
PRIMARY KEY ( id )
)
ENGINE = InnoDB
';
R::exec( $sql );
$numOfFKS = R::getCell('
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
WHERE TABLE_NAME = "page" AND DELETE_RULE = "CASCADE"');
asrt( (int) $numOfFKS, 0 );
//even if different
$writer->addFK('page', 'book', 'book_id', 'id', FALSE);
$numOfFKS = R::getCell('
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
WHERE TABLE_NAME = "page" AND DELETE_RULE = "CASCADE"');
asrt( (int) $numOfFKS, 0 );
$numOfFKS = R::getCell('
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
WHERE TABLE_NAME = "page" AND DELETE_RULE = "SET NULL"');
asrt( (int) $numOfFKS, 1 );
$writer->addFK('page', 'book', 'book_id', 'id', TRUE);
$numOfFKS = R::getCell('
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
WHERE TABLE_NAME = "page" ');
}
/**
* Test whether we can manually create indexes.
*
* @return void
*/
public function testAddingIndex()
{
R::nuke();
$sql = '
CREATE TABLE song (
id INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT,
album_id INT( 11 ) UNSIGNED NOT NULL,
category VARCHAR( 255 ),
PRIMARY KEY ( id )
)
ENGINE = InnoDB
';
R::exec( $sql );
$sql = 'SHOW INDEX FROM song';
$indexes = R::getAll( $sql );
asrt( count( $indexes ), 1 );
asrt( $indexes[0]['Table'], 'song' );
asrt( $indexes[0]['Key_name'], 'PRIMARY' );
$writer = R::getWriter();
$writer->addIndex('song', 'index1', 'album_id');
$indexes = R::getAll( 'SHOW INDEX FROM song' );
asrt( count( $indexes ), 2 );
asrt( $indexes[0]['Table'], 'song' );
asrt( $indexes[0]['Key_name'], 'PRIMARY' );
asrt( $indexes[1]['Table'], 'song' );
asrt( $indexes[1]['Key_name'], 'index1' );
//Cant add the same index twice
$writer->addIndex('song', 'index2', 'category');
$indexes = R::getAll( 'SHOW INDEX FROM song' );
asrt( count( $indexes ), 3 );
//Dont fail, just dont
try {
$writer->addIndex('song', 'index3', 'nonexistant');
pass();
} catch( \Exception $e ) {
fail();
}
asrt( count( $indexes ), 3 );
try {
$writer->addIndex('nonexistant', 'index4', 'nonexistant');
pass();
} catch( \Exception $e ) {
fail();
}
asrt( count( $indexes ), 3 );
try {
$writer->addIndex('nonexistant', '', 'nonexistant');
pass();
} catch( \Exception $e ) {
fail();
}
asrt( count( $indexes ), 3 );
}
}

View File

@@ -0,0 +1,105 @@
<?php
namespace RedUNIT\Mysql;
use RedUNIT\Mysql as Mysql;
use RedBeanPHP\Facade as R;
use RedBeanPHP\AssociationManager as AssociationManager;
use RedBeanPHP\RedException\SQL as SQL;
/**
* Freeze
*
* Tests whether database schema remains unmodified in frozen
* mode.
*
* @file RedUNIT/Mysql/Freeze.php
* @desc Tests freezing of databases for production environments.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Freeze extends Mysql
{
/**
* Tests freezing the database.
* After freezing the database, schema modifications are no longer
* allowed and referring to missing columns will now cause exceptions.
*
* @return void
*/
public function testFreezer()
{
$toolbox = R::getToolBox();
$adapter = $toolbox->getDatabaseAdapter();
$writer = $toolbox->getWriter();
$redbean = $toolbox->getRedBean();
$pdo = $adapter->getDatabase();
$a = new AssociationManager( $toolbox );
$post = $redbean->dispense( 'post' );
$post->title = 'title';
$redbean->store( $post );
$page = $redbean->dispense( 'page' );
$page->name = 'title';
$redbean->store( $page );
$page = $redbean->dispense( "page" );
$page->name = "John's page";
$idpage = $redbean->store( $page );
$page2 = $redbean->dispense( "page" );
$page2->name = "John's second page";
$idpage2 = $redbean->store( $page2 );
$a->associate( $page, $page2 );
$redbean->freeze( TRUE );
$page = $redbean->dispense( "page" );
$page->sections = 10;
$page->name = "half a page";
try {
$id = $redbean->store( $page );
fail();
} catch ( SQL $e ) {
pass();
}
$post = $redbean->dispense( "post" );
$post->title = "existing table";
try {
$id = $redbean->store( $post );
pass();
} catch ( SQL $e ) {
fail();
}
asrt( in_array( "name", array_keys( $writer->getColumns( "page" ) ) ), TRUE );
asrt( in_array( "sections", array_keys( $writer->getColumns( "page" ) ) ), FALSE );
$newtype = $redbean->dispense( "newtype" );
$newtype->property = 1;
try {
$id = $redbean->store( $newtype );
fail();
} catch ( SQL $e ) {
pass();
}
$logger = R::debug( TRUE, 1 );
// Now log and make sure no 'describe SQL' happens
$page = $redbean->dispense( "page" );
$page->name = "just another page that has been frozen...";
$id = $redbean->store( $page );
$page = $redbean->load( "page", $id );
$page->name = "just a frozen page...";
$redbean->store( $page );
$page2 = $redbean->dispense( "page" );
$page2->name = "an associated frozen page";
$a->associate( $page, $page2 );
$a->related( $page, "page" );
$a->unassociate( $page, $page2 );
$a->clearRelations( $page, "page" );
$items = $redbean->find( "page", array(), array( "1" ) );
$redbean->trash( $page );
$redbean->freeze( FALSE );
asrt( count( $logger->grep( "SELECT" ) ) > 0, TRUE );
asrt( count( $logger->grep( "describe" ) ) < 1, TRUE );
asrt( is_array( $logger->getLogs() ), TRUE );
R::debug( FALSE );
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace RedUNIT\Mysql;
use RedUNIT\Mysql as Mysql;
use RedBeanPHP\Facade as R;
/**
* Issue 411
*
* InnoDB has a maximum index length of 767 bytes, so with utf8mb4
* you can only store 191 characters. This means that when you
* subsequently add an index to the column you get a
* (not-so-obvious) MySQL-error. That's why we limit the varchar to
* 191 chars and then switch to TEXT type.
*
* @file RedUNIT/Mysql/Issue411.php
* @desc Tests intermediate varchar 191 type for MySQL utf8mb4.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Issue411 extends Mysql
{
/**
* Test varchar 191 condition.
*
* @return void
*/
public function testInnoDBIndexLimit()
{
R::nuke();
$book = R::dispense( 'book' );
$book->text = 'abcd';
R::store( $book );
$columns = R::inspect( 'book' );
asrt( isset( $columns['text'] ), TRUE );
asrt( $columns['text'], 'varchar(191)' );
$book = $book->fresh();
$book->text = str_repeat( 'x', 190 );
R::store( $book );
$columns = R::inspect( 'book' );
asrt( isset( $columns['text'] ), TRUE );
asrt( $columns['text'], 'varchar(191)' );
$book = $book->fresh();
$book->text = str_repeat( 'x', 191 );
R::store( $book );
$columns = R::inspect( 'book' );
asrt( isset( $columns['text'] ), TRUE );
asrt( $columns['text'], 'varchar(191)' );
$book = $book->fresh();
$book->text = str_repeat( 'x', 192 );
R::store( $book );
$columns = R::inspect( 'book' );
asrt( isset( $columns['text'] ), TRUE );
asrt( $columns['text'], 'varchar(255)' );
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace RedUNIT\Mysql;
use RedUNIT\Mysql as Mysql;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException\SQL as SQL;
/**
* Parambind
*
* Tests the parameter binding functionality in RedBeanPHP.
* These test scenarios include for instance: NULL handling,
* binding parameters in LIMIT clauses and so on.
*
* @file RedUNIT/Mysql/Parambind.php
* @desc Tests\PDO parameter binding.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Parambind extends Mysql
{
/**
* Test parameter binding with\PDO.
*
* @return void
*/
public function testPDOParameterBinding()
{
$toolbox = R::getToolBox();
$adapter = $toolbox->getDatabaseAdapter();
$writer = $toolbox->getWriter();
$redbean = $toolbox->getRedBean();
$pdo = $adapter->getDatabase();
R::getDatabaseAdapter()->getDatabase()->setUseStringOnlyBinding( TRUE );
try {
R::getAll( "select * from job limit ? ", array( 1 ) );
fail();
} catch (\Exception $e ) {
pass();
}
try {
R::getAll( "select * from job limit :l ", array( ":l" => 1 ) );
fail();
} catch (\Exception $e ) {
pass();
}
try {
R::exec( "select * from job limit ? ", array( 1 ) );
fail();
} catch (\Exception $e ) {
pass();
}
try {
R::exec( "select * from job limit :l ", array( ":l" => 1 ) );
fail();
} catch (\Exception $e ) {
pass();
}
R::getDatabaseAdapter()->getDatabase()->setUseStringOnlyBinding( FALSE );
try {
R::getAll( "select * from job limit ? ", array( 1 ) );
pass();
} catch (\Exception $e ) {
fail();
}
try {
R::getAll( "select * from job limit :l ", array( ":l" => 1 ) );
pass();
} catch (\Exception $e ) {
fail();
}
try {
R::exec( "select * from job limit ? ", array( 1 ) );
pass();
} catch (\Exception $e ) {
fail();
}
try {
R::exec( "select * from job limit :l ", array( ":l" => 1 ) );
pass();
} catch (\Exception $e ) {
fail();
}
testpack( "Test findOrDispense" );
$person = R::findOrDispense( "person", " job = ? ", array( "developer" ) );
asrt( ( count( $person ) > 0 ), TRUE );
$person = R::findOrDispense( "person", " job = ? ", array( "musician" ) );
asrt( ( count( $person ) > 0 ), TRUE );
$musician = array_pop( $person );
asrt( intval( $musician->id ), 0 );
try {
$adapter->exec( "an invalid query" );
fail();
} catch ( SQL $e ) {
pass();
}
asrt( (int) $adapter->getCell( "SELECT 123" ), 123 );
asrt( (int) $adapter->getCell( "SELECT ?", array( "987" ) ), 987 );
asrt( (int) $adapter->getCell( "SELECT ?+?", array( "987", "2" ) ), 989 );
asrt( (int) $adapter->getCell( "SELECT :numberOne+:numberTwo", array(
":numberOne" => 42, ":numberTwo" => 50 ) ), 92 );
$pair = $adapter->getAssoc( "SELECT 'thekey','thevalue' " );
asrt( is_array( $pair ), TRUE );
asrt( count( $pair ), 1 );
asrt( isset( $pair["thekey"] ), TRUE );
asrt( $pair["thekey"], "thevalue" );
testpack( 'Test whether we can properly bind and receive NULL values' );
asrt( $adapter->getCell( 'SELECT :nil ', array( ':nil' => 'NULL' ) ), 'NULL' );
asrt( $adapter->getCell( 'SELECT :nil ', array( ':nil' => NULL ) ), NULL );
asrt( $adapter->getCell( 'SELECT ? ', array( 'NULL' ) ), 'NULL' );
asrt( $adapter->getCell( 'SELECT ? ', array( NULL ) ), NULL );
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace RedUNIT\Mysql;
use RedUNIT\Mysql as Mysql;
use RedBeanPHP\Facade as R;
use RedBeanPHP\AssociationManager as AssociationManager;
/**
* Preexist
*
* Tests whether RedBeanPHP can work with existing
* MySQL schemas.
*
* @file RedUNIT/Mysql/Preexist.php
* @desc Tests integration with pre-existing schemas.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Preexist extends Mysql
{
/**
* Test integration with pre-existing schemas.
*
* @return void
*/
public function testPlaysNiceWithPreExitsingSchema()
{
$toolbox = R::getToolBox();
$adapter = $toolbox->getDatabaseAdapter();
$writer = $toolbox->getWriter();
$redbean = $toolbox->getRedBean();
$pdo = $adapter->getDatabase();
$a = new AssociationManager( $toolbox );
$page = $redbean->dispense( "page" );
$page->name = "John's page";
$idpage = $redbean->store( $page );
$page2 = $redbean->dispense( "page" );
$page2->name = "John's second page";
$idpage2 = $redbean->store( $page2 );
$a->associate( $page, $page2 );
$adapter->exec( "ALTER TABLE " . $writer->esc( 'page' ) . "
CHANGE " . $writer->esc( 'name' ) . " " . $writer->esc( 'name' ) . "
VARCHAR( 254 ) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL " );
$page = $redbean->dispense( "page" );
$page->name = "Just Another Page In a Table";
$cols = $writer->getColumns( "page" );
asrt( $cols["name"], "varchar(254)" );
$redbean->store( $page );
pass(); // No crash?
$cols = $writer->getColumns( "page" );
asrt( $cols["name"], "varchar(254)" ); //must still be same
}
}

View File

@@ -0,0 +1,160 @@
<?php
namespace RedUNIT\Mysql;
use RedUNIT\Mysql as Mysql;
use RedBeanPHP\Facade as R;
/**
* Setget
*
* This class has been designed to test set/get operations
* for a specific Query Writer / Adapter. Since RedBeanPHP
* creates columns based on values it's essential that you
* get back the 'same' value as you put in - or - if that's
* not the case, that there are at least very clear rules
* about what to expect. Examples of possible issues tested in
* this class include:
*
* - Test whether booleans are returned correctly (they will become integers)
* - Test whether large numbers are preserved
* - Test whether floating point numbers are preserved
* - Test whether date/time values are preserved
* and so on...
*
* @file RedUNIT/Mysql/Setget.php
* @desc Tests whether values are stored correctly.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Setget extends Mysql
{
/**
* Test whether we can store DateTime objects and get them back
* as 'date-time' strings representing the same date and time.
*
* @return void
*/
public function testDateObject()
{
$dt = new \DateTime();
$dt->setTimeZone( new \DateTimeZone( 'Europe/Amsterdam' ) );
$dt->setDate( 1981, 5, 1 );
$dt->setTime( 3, 13, 13 );
asrt( setget( $dt ), '1981-05-01 03:13:13' );
$bean = R::dispense( 'bean' );
$bean->dt = $dt;
}
/**
* Tests R::getInsertID convenience method.
*
* @return void
*/
public function testGetInsertID()
{
R::nuke();
$id = R::store( R::dispense( 'book' ) );
$id2 = R::getInsertID();
asrt( $id, $id2 );
}
/**
* Test numbers.
*
* @return void
*/
public function testNumbers()
{
asrt( setget( "-1" ), "-1" );
asrt( setget( -1 ), "-1" );
asrt( setget( "-0.25" ), "-0.25" );
asrt( setget( -0.25 ), "-0.25" );
asrt( setget( "1.0" ), "1" );
asrt( setget( 1.0 ), "1" );
asrt( setget( "3.20" ), "3.20" );
asrt( setget( "13.20" ), "13.20" );
asrt( setget( "134.20" ), "134.20" );
asrt( setget( 3.21 ), '3.21' );
asrt( setget( "0.12345678" ), "0.12345678" );
asrt( setget( 0.12345678 ), "0.12345678" );
asrt( setget( "-0.12345678" ), "-0.12345678" );
asrt( setget( -0.12345678 ), "-0.12345678" );
asrt( setget( "2147483647" ), "2147483647" );
asrt( setget( 2147483647 ), "2147483647" );
asrt( setget( -2147483647 ), "-2147483647" );
asrt( setget( "-2147483647" ), "-2147483647" );
asrt( setget( -4294967295 ), "-4294967295" );
asrt( setget( "-4294967295" ), "-4294967295" );
asrt( setget( 4294967295 ), "4294967295" );
asrt( setget( "4294967295" ), "4294967295" );
asrt( setget( "2147483648" ), "2147483648" );
asrt( setget( "-2147483648" ), "-2147483648" );
asrt( setget( "199936710040730" ), "199936710040730" );
asrt( setget( "-199936710040730" ), "-199936710040730" );
//Architecture dependent... only test this if you are sure what arch
//asrt(setget("2147483647123456"),"2.14748364712346e+15");
//asrt(setget(2147483647123456),"2.14748364712e+15");
}
/**
* Test dates.
*
* @return void
*/
public function testDates()
{
asrt( setget( "2010-10-11" ), "2010-10-11" );
asrt( setget( "2010-10-11 12:10" ), "2010-10-11 12:10" );
asrt( setget( "2010-10-11 12:10:11" ), "2010-10-11 12:10:11" );
asrt( setget( "x2010-10-11 12:10:11" ), "x2010-10-11 12:10:11" );
}
/**
* Test strings.
*
* @return void
*/
public function testStrings()
{
asrt( setget( "a" ), "a" );
asrt( setget( "." ), "." );
asrt( setget( "\"" ), "\"" );
asrt( setget( "just some text" ), "just some text" );
}
/**
* Test booleans.
*
* @return void
*/
public function testBool()
{
asrt( setget( TRUE ), "1" );
asrt( setget( FALSE ), "0" );
asrt( setget( "TRUE" ), "TRUE" );
asrt( setget( "FALSE" ), "FALSE" );
}
/**
* Test NULL.
*
* @return void
*/
public function testNull()
{
asrt( setget( "NULL" ), "NULL" );
asrt( setget( "NULL" ), "NULL" );
asrt( setget( "0123" ), "0123" );
asrt( setget( "0000123" ), "0000123" );
asrt( setget( NULL ), NULL );
asrt( ( setget( 0 ) == 0 ), TRUE );
asrt( ( setget( 1 ) == 1 ), TRUE );
asrt( ( setget( TRUE ) == TRUE ), TRUE );
asrt( ( setget( FALSE ) == FALSE ), TRUE );
}
}

View File

@@ -0,0 +1,291 @@
<?php
namespace RedUNIT\Mysql;
use RedUNIT\Mysql as Mysql;
use RedBeanPHP\Facade as R;
use RedBeanPHP\BeanHelper\SimpleFacadeBeanHelper as SimpleFacadeBeanHelper;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\ToolBox as ToolBox;
/**
* UUID
*
* Tests whether we can use UUIDs with MySQL/MariaDB, to this
* end we use a reference implementation of a UUID MySQL Writer:
* UUIDWriterMySQL, however this class is not part of the code base,
* it should be considered a reference or example implementation.
* These tests focus on whether UUIDs in general do not cause any
* unexpected issues.
*
* @file RedUNIT/Mysql/Uuid.php
* @desc Tests read support for UUID tables.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Uuid extends Mysql
{
/**
* Test Read-support.
*
* @return void
*/
public function testUUIDReadSupport()
{
R::nuke();
$createPageTableSQL = '
CREATE TABLE
`page`
(
id CHAR( 40 ),
book_id CHAR( 40 ),
magazine_id CHAR( 40 ),
title VARCHAR(255),
PRIMARY KEY ( id )
)
ENGINE = InnoDB DEFAULT
CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci ';
$createBookTableSQL = '
CREATE TABLE
`book`
(
id CHAR( 40 ),
title VARCHAR(255),
PRIMARY KEY ( id )
)
ENGINE = InnoDB DEFAULT
CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci ';
$createPagePageTableSQL = '
CREATE TABLE
`page_page`
(
id CHAR( 40 ),
page_id CHAR( 40 ),
page2_id CHAR( 40 ),
PRIMARY KEY ( id )
)
ENGINE = InnoDB DEFAULT
CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci ';
R::exec( $createBookTableSQL );
R::exec( $createPageTableSQL );
R::exec( $createPagePageTableSQL );
//insert some records
$book1ID = '6ccd780c-baba-1026-9564-0040f4311e21';
$book2ID = '6ccd780c-baba-1026-9564-0040f4311e22';
$page1ID = '6ccd780c-baba-1026-9564-0040f4311e23';
$page2ID = '6ccd780c-baba-1026-9564-0040f4311e24';
$page3ID = '6ccd780c-baba-1026-9564-0040f4311e25';
$pagePage1ID = '6ccd780c-baba-1026-9564-0040f4311e26';
$insertBook1SQL = "
INSERT INTO book (id, title) VALUES( '$book1ID', 'book 1' );
";
$insertBook2SQL = "
INSERT INTO book (id, title) VALUES( '$book2ID', 'book 2' );
";
$insertPage1SQL = "
INSERT INTO page (id, book_id, title, magazine_id) VALUES( '$page1ID', '$book1ID', 'page 1 of book 1', '$book2ID' );
";
$insertPage2SQL = "
INSERT INTO page (id, book_id, title) VALUES( '$page2ID', '$book1ID', 'page 2 of book 1' );
";
$insertPage3SQL = "
INSERT INTO page (id, book_id, title) VALUES( '$page3ID', '$book2ID', 'page 1 of book 2' );
";
$insertPagePage1SQL = "
INSERT INTO page_page (id, page_id, page2_id) VALUES( '$pagePage1ID', '$page2ID', '$page3ID' );
";
R::exec( $insertBook1SQL );
R::exec( $insertBook2SQL );
R::exec( $insertPage1SQL );
R::exec( $insertPage2SQL );
R::exec( $insertPage3SQL );
R::exec( $insertPagePage1SQL );
//basic tour of basic functions....
$book1 = R::load( 'book', $book1ID );
asrt( $book1->id, $book1ID );
asrt( $book1->title, 'book 1' );
$book2 = R::load( 'book', $book2ID );
asrt( $book2->id, $book2ID );
asrt( $book2->title, 'book 2' );
asrt( count( $book1->ownPage ), 2 );
asrt( count( $book1->fresh()->with( 'LIMIT 1' )->ownPage ), 1 );
asrt( count( $book1->fresh()->withCondition( ' title = ? ', array('page 2 of book 1'))->ownPage ), 1 );
asrt( count($book2->ownPage), 1 );
asrt( $book2->fresh()->countOwn( 'page' ), 1 );
$page1 = R::load( 'page', $page1ID );
asrt( count( $page1->sharedPage ), 0 );
asrt( $page1->fetchAs( 'book' )->magazine->id, $book2ID );
$page2 = R::load( 'page', $page2ID );
asrt( count($page2->sharedPage), 1 );
asrt( $page2->fresh()->countShared( 'page' ), 1 );
$page3 = R::findOne( 'page', ' title = ? ', array( 'page 1 of book 2' ) );
asrt( $page3->id, $page3ID );
asrt( $page3->book->id, $book2ID );
}
/**
* Test Full fluid UUID support.
*
* @return void
*/
public function testFullSupport()
{
R::nuke();
//Rewire objects to support UUIDs.
$oldToolBox = R::getToolBox();
$oldAdapter = $oldToolBox->getDatabaseAdapter();
$uuidWriter = new \UUIDWriterMySQL( $oldAdapter );
$newRedBean = new OODB( $uuidWriter );
$newToolBox = new ToolBox( $newRedBean, $oldAdapter, $uuidWriter );
R::configureFacadeWithToolbox( $newToolBox );
list( $mansion, $rooms, $ghosts, $key ) = R::dispenseAll( 'mansion,room*3,ghost*4,key' );
$mansion->name = 'Haunted Mansion';
$mansion->xownRoomList = $rooms;
$rooms[0]->name = 'Green Room';
$rooms[1]->name = 'Red Room';
$rooms[2]->name = 'Blue Room';
$ghosts[0]->name = 'zero';
$ghosts[1]->name = 'one';
$ghosts[2]->name = 'two';
$ghosts[3]->name = 'three';
$rooms[0]->sharedGhostList = array( $ghosts[0], $ghosts[1] );
$rooms[1]->sharedGhostList = array( $ghosts[0], $ghosts[2] );
$rooms[2]->sharedGhostList = array( $ghosts[1], $ghosts[3], $ghosts[2] );
$rooms[2]->xownKey = array( $key );
//Can we store a bean hierachy with UUIDs?
$id = R::store( $mansion );
asrt( is_string( $id ), TRUE );
asrt( strlen( $id ), 36 );
$haunted = R::load( 'mansion', $id );
asrt( $haunted->name, 'Haunted Mansion' );
asrt( is_string( $haunted->id ), TRUE );
asrt( strlen( $haunted->id ), 36 );
asrt( is_array( $haunted->xownRoomList ), TRUE );
asrt( count( $haunted->ownRoom ), 3 );
$rooms = $haunted->xownRoomList;
//Do some counting...
$greenRoom = NULL;
foreach( $rooms as $room ) {
if ( $room->name === 'Green Room' ) {
$greenRoom = $room;
break;
}
}
asrt( !is_null( $greenRoom ), TRUE );
asrt( is_array( $greenRoom->with(' ORDER BY id ')->sharedGhostList ), TRUE );
asrt( count( $greenRoom->sharedGhostList ), 2 );
$names = array();
foreach( $greenRoom->sharedGhost as $ghost ) $names[] = $ghost->name;
sort($names);
$names = implode(',', $names);
asrt($names, 'one,zero');
$rooms = $haunted->xownRoomList;
$blueRoom = NULL;
foreach( $rooms as $room ) {
if ( $room->name === 'Blue Room' ) {
$blueRoom = $room;
break;
}
}
asrt( !is_null( $blueRoom ), TRUE );
asrt( is_array( $blueRoom->sharedGhostList ), TRUE );
asrt( count( $blueRoom->sharedGhostList ), 3 );
$names = array();
foreach( $blueRoom->sharedGhost as $ghost ) $names[] = $ghost->name;
sort($names);
$names = implode(',', $names);
asrt($names, 'one,three,two');
$rooms = $haunted->xownRoomList;
$redRoom = NULL;
foreach( $rooms as $room ) {
if ( $room->name === 'Red Room' ) {
$redRoom = $room; break;
}
}
$names = array();
foreach( $redRoom->sharedGhost as $ghost ) $names[] = $ghost->name;
sort($names);
$names = implode(',', $names);
asrt($names, 'two,zero');
asrt( !is_null( $redRoom ), TRUE );
asrt( is_array( $redRoom->sharedGhostList ), TRUE );
asrt( count( $redRoom->sharedGhostList ), 2 );
//Can we repaint a room?
$redRoom->name = 'Yellow Room';
$id = R::store($redRoom);
$yellowRoom = R::load( 'room', $id );
asrt( $yellowRoom->name, 'Yellow Room');
asrt( !is_null( $yellowRoom ), TRUE );
asrt( is_array( $yellowRoom->sharedGhostList ), TRUE );
asrt( count( $yellowRoom->sharedGhostList ), 2 );
//Can we throw one ghost out?
array_pop( $yellowRoom->sharedGhost );
R::store( $yellowRoom );
$yellowRoom = $yellowRoom->fresh();
asrt( $yellowRoom->name, 'Yellow Room');
asrt( !is_null( $yellowRoom ), TRUE );
asrt( is_array( $yellowRoom->sharedGhostList ), TRUE );
asrt( count( $yellowRoom->sharedGhostList ), 1 );
//can we remove one of the rooms?
asrt( R::count('key'), 1);
$list = $mansion->withCondition(' `name` = ? ', array('Blue Room'))->xownRoomList;
$room = reset($list);
unset($mansion->xownRoomList[$room->id]);
R::store($mansion);
asrt(R::count('room'), 2);
//and what about its dependent beans?
asrt(R::count('key'), 0);
asrt(R::count('ghost_room'), 3);
//and can we find ghosts?
$ghosts = R::find('ghost');
asrt(count($ghosts), 4);
$ghosts = R::findAll('ghost', 'ORDER BY id');
asrt(count($ghosts), 4);
$ghosts = R::findAll('ghost', 'ORDER BY id LIMIT 2');
asrt(count($ghosts), 2);
$ghostZero = R::findOne('ghost', ' `name` = ? ', array( 'zero' ) );
asrt( ($ghostZero instanceof OODBBean), TRUE );
//can we create link properties on existing tables?
$blackRoom = R::dispense( 'room' );
$blackRoom->name = 'Black Room';
$ghostZero->link('ghost_room', array('mood'=>'grumpy'))->room = $blackRoom;
R::store($ghostZero);
$ghostZero = $ghostZero->fresh();
$list = $ghostZero->sharedRoomList;
asrt(count($list), 3);
$ghostZero = $ghostZero->fresh();
$list = $ghostZero->withCondition(' ghost_room.mood = ? ', array('grumpy'))->sharedRoomList;
asrt(count($list), 1);
//can we load a batch?
$ids = R::getCol('SELECT id FROM ghost');
$ghosts = R::batch('ghost', $ids);
asrt(count($ghosts), 4);
//can we do an aggregation?
$ghosts = $greenRoom->aggr('ownGhostRoom', 'ghost', 'ghost');
asrt(count($ghosts), 2);
//can we duplicate the mansion?
asrt(R::count('mansion'), 1);
asrt(R::count('room'), 3);
asrt(R::count('ghost'), 4);
$copy = R::dup($mansion);
R::store($copy);
asrt(R::count('mansion'), 2);
asrt(R::count('room'), 5); //black room does not belong to mansion 1
asrt(R::count('ghost'), 4);
//can we do some counting using the list?
asrt( $copy->countOwn('room'), 2);
$rooms = $copy->withCondition(' `name` = ? ', array('Green Room'))->xownRoomList;
$room = reset($rooms);
asrt($room->countShared('ghost'), 2);
//Finally restore old toolbox
R::configureFacadeWithToolbox( $oldToolBox );
}
}

View File

@@ -0,0 +1,744 @@
<?php
namespace RedUNIT\Mysql;
use RedBeanPHP\Facade as R;
use RedBeanPHP\AssociationManager as AssociationManager;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\QueryWriter\MySQL as MySQL;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
use RedBeanPHP\RedException\SQL as SQL;
use RedBeanPHP\RedException as RedException;
/**
* Writer
*
* Tests for MySQL and MariaDB Query Writer.
* This test class contains Query Writer specific tests.
* Use this class to add tests to test Query Writer specific
* behaviours, quirks and issues.
*
* @file RedUNIT/Mysql/Writer.php
* @desc A collection of database specific writer functions.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Writer extends \RedUNIT\Mysql
{
/**
* Test whether optimizations do not have effect on Writer query outcomes.
*
* @return void
*/
public function testWriterSpeedUp()
{
R::nuke();
$id = R::store( R::dispense( 'book' ) );
$writer = R::getWriter();
$count1 = $writer->queryRecordCount( 'book', array( 'id' => $id ), ' id = :id ', array( ':id' => $id ) );
$count2 = $writer->queryRecordCount( 'book', array( ), ' id = :id ', array( ':id' => $id ) );
$count3 = $writer->queryRecordCount( 'book', NULL, ' id = :id ', array( ':id' => $id ) );
$count4 = $writer->queryRecordCount( 'book', array( 'id' => $id ) );
asrt( $count1, $count2 );
asrt( $count2, $count3 );
asrt( $count3, $count4 );
R::nuke();
$books = R::dispenseAll( 'book*4' );
$ids = R::storeAll( $books[0] );
$writer->deleteRecord( 'book', array( 'id' => $ids[0] ) );
$writer->deleteRecord( 'book', array( 'id' => $ids[1] ), ' id = :id ', array( ':id' => $ids[1] ) );
$writer->deleteRecord( 'book', NULL, ' id = :id ', array( ':id' => $ids[2] ) );
$writer->deleteRecord( 'book', array(), ' id = :id ', array( ':id' => $ids[3] ) );
asrt( R::count( 'book' ), 0 );
R::nuke();
$id = R::store( R::dispense( 'book' ) );
$record = $writer->queryRecord( 'book', array( 'id' => $id ) );
asrt( is_array( $record ), TRUE );
asrt( is_array( $record[0] ), TRUE );
asrt( isset( $record[0]['id'] ), TRUE );
asrt( (int) $record[0]['id'], $id );
$record = $writer->queryRecord( 'book', array( 'id' => $id ), ' id = :id ', array( ':id' => $id ) );
asrt( is_array( $record ), TRUE );
asrt( is_array( $record[0] ), TRUE );
asrt( isset( $record[0]['id'] ), TRUE );
asrt( (int) $record[0]['id'], $id );
$record = $writer->queryRecord( 'book', NULL, ' id = :id ', array( ':id' => $id ) );
asrt( is_array( $record ), TRUE );
asrt( is_array( $record[0] ), TRUE );
asrt( isset( $record[0]['id'] ), TRUE );
asrt( (int) $record[0]['id'], $id );
$record = $writer->queryRecord( 'book', array(), ' id = :id ', array( ':id' => $id ) );
asrt( is_array( $record ), TRUE );
asrt( is_array( $record[0] ), TRUE );
asrt( isset( $record[0]['id'] ), TRUE );
asrt( (int) $record[0]['id'], $id );
}
/**
* Tests wheter we can write a deletion query
* for MySQL using NO conditions but only an
* additional SQL snippet.
*
* @return void
*/
public function testWriteDeleteQuery()
{
$queryWriter = R::getWriter();
asrt( ( $queryWriter instanceof MySQL ), TRUE );
R::nuke();
$bean = R::dispense( 'bean' );
$bean->name = 'a';
$id = R::store( $bean );
asrt( R::count( 'bean' ), 1 );
$queryWriter->deleteRecord( 'bean', array(), $addSql = ' id = :id ', $bindings = array( ':id' => $id ) );
asrt( R::count( 'bean' ), 0 );
}
/**
* Tests wheter we can write a counting query
* for MySQL using conditions and an additional SQL snippet.
*
* @return void
*/
public function testWriteCountQuery()
{
$queryWriter = R::getWriter();
asrt( ( $queryWriter instanceof MySQL ), TRUE );
R::nuke();
$bean = R::dispense( 'bean' );
$bean->name = 'a';
R::store( $bean );
$bean = R::dispense( 'bean' );
$bean->name = 'b';
R::store( $bean );
$bean = R::dispense( 'bean' );
$bean->name = 'b';
R::store( $bean );
$count = $queryWriter->queryRecordCount( 'bean', array( 'name' => 'b' ), $addSql = ' id > :id ', $bindings = array( ':id' => 0 ) );
asrt( $count, 2 );
}
/**
* Tests whether we can write a MySQL join and
* whether the correct exception is thrown in case
* of an invalid join.
*
* @return void
*/
public function testWriteJoinSnippets()
{
$queryWriter = R::getWriter();
asrt( ( $queryWriter instanceof MySQL ), TRUE );
$snippet = $queryWriter->writeJoin( 'book', 'page' ); //default must be LEFT
asrt( is_string( $snippet ), TRUE );
asrt( ( strlen( $snippet ) > 0 ), TRUE );
asrt( ' LEFT JOIN `page` ON `page`.id = `book`.page_id ', $snippet );
$snippet = $queryWriter->writeJoin( 'book', 'page', 'LEFT' );
asrt( is_string( $snippet ), TRUE );
asrt( ( strlen( $snippet ) > 0 ), TRUE );
asrt( ' LEFT JOIN `page` ON `page`.id = `book`.page_id ', $snippet );
$snippet = $queryWriter->writeJoin( 'book', 'page', 'RIGHT' );
asrt( is_string( $snippet ), TRUE );
asrt( ( strlen( $snippet ) > 0 ), TRUE );
asrt( ' RIGHT JOIN `page` ON `page`.id = `book`.page_id ', $snippet );
$snippet = $queryWriter->writeJoin( 'book', 'page', 'INNER' );
asrt( ' INNER JOIN `page` ON `page`.id = `book`.page_id ', $snippet );
$exception = NULL;
try {
$snippet = $queryWriter->writeJoin( 'book', 'page', 'MIDDLE' );
}
catch(\Exception $e) {
$exception = $e;
}
asrt( ( $exception instanceof RedException ), TRUE );
$errorMessage = $exception->getMessage();
asrt( is_string( $errorMessage ), TRUE );
asrt( ( strlen( $errorMessage ) > 0 ), TRUE );
asrt( $errorMessage, 'Invalid JOIN.' );
}
/**
* Test whether we can store JSON as a JSON column
* and whether this plays well with the other data types.
*/
public function testSetGetJSON()
{
/* a stub test in case full test cannot be performed, see below */
R::useJSONFeatures( TRUE );
asrt( R::getWriter()->scanType( '[1,2,3]', TRUE ), MySQL::C_DATATYPE_SPECIAL_JSON );
R::useJSONFeatures( FALSE );
global $travis;
if ($travis) return;
/* does not work on MariaDB */
$version = strtolower( R::getCell('select version()') );
if ( strpos( $version, 'mariadb' ) !== FALSE ) return;
// Check if database platform is MariaDB < 10.2
$selectVersion = R::getDatabaseAdapter()->getCol( 'SELECT VERSION()' );
list ( $version, $dbPlatform ) = explode( '-', reset ( $selectVersion ) );
list( $versionMajor, $versionMinor, $versionPatch ) = explode( '.', $version );
if ( $dbPlatform == "MariaDB" && $versionMajor <= 10 && $versionMinor < 2 ) {
// No support for JSON columns, abort test
return;
}
R::nuke();
$bean = R::dispense('bean');
$message = json_encode( array( 'message' => 'hello', 'type' => 'greeting' ) );
$bean->data = $message;
R::store( $bean );
$columns = R::inspect('bean');
asrt( array_key_exists( 'data', $columns ), TRUE );
asrt( ( $columns['data'] !== 'json' ), TRUE );
R::useJSONFeatures( TRUE );
R::nuke();
$bean = R::dispense('bean');
$message = array( 'message' => 'hello', 'type' => 'greeting' );
$bean->data = $message;
R::store( $bean );
$columns = R::inspect('bean');
asrt( array_key_exists( 'data', $columns ), TRUE );
asrt( $columns['data'], 'json' );
$bean = $bean->fresh();
$message = json_decode( $bean->data, TRUE );
asrt( $message['message'], 'hello' );
asrt( $message['type'], 'greeting' );
$message['message'] = 'hi';
$bean->data = $message;
R::store( $bean );
pass();
$bean = R::findOne( 'bean' );
$message = json_decode( $bean->data );
asrt( $message->message, 'hi' );
$book = R::dispense( 'book' );
$book->page = 'lorem ipsum';
R::store( $book );
$book = $book->fresh();
asrt( $book->page, 'lorem ipsum' );
$book2 = R::dispense( 'book' );
$book2->page = array( 'chapter' => '1' );
R::store( $book2 );
pass(); //should not try to modify column and trigger exception
$book = $book->fresh();
asrt( $book->page, 'lorem ipsum' );
$columns = R::inspect('book');
asrt( ( $columns['page'] !== 'json' ), TRUE );
$building = R::dispense( 'building' );
$building->year = 'MLXXVIII';
R::store( $building );
$shop = R::dispense( 'building' );
$shop->year = '2010-01-01';
R::store( $shop );
$building = R::load( 'building', $building->id );
asrt( $building->year, 'MLXXVIII' );
$columns = R::inspect( 'building' );
asrt( strpos( strtolower( $columns['year'] ), 'date' ), FALSE );
$shop->anno = '2010-01-01';
R::store( $shop );
$columns = R::inspect( 'building' );
asrt( $columns['anno'], 'date' );
R::useJSONFeatures( FALSE );
}
/**
* Test Facade bind function method.
* Test for MySQL WKT spatial format.
*/
public function testFunctionFilters()
{
R::nuke();
R::bindFunc( 'read', 'location.point', 'asText' );
R::bindFunc( 'write', 'location.point', 'GeomFromText' );
R::store(R::dispense('location'));
R::freeze( TRUE );
try {
R::find('location');
fail();
} catch( SQL $exception ) {
pass();
}
R::freeze( FALSE );
try {
R::find('location');
pass();
} catch( SQL $exception ) {
fail();
}
$location = R::dispense( 'location' );
$location->point = 'POINT(14 6)';
R::store($location);
$columns = R::inspect( 'location' );
asrt( $columns['point'], 'point' );
$location = $location->fresh();
asrt( $location->point, 'POINT(14 6)' );
R::nuke();
$location = R::dispense( 'location' );
$location->point = 'LINESTRING(0 0,1 1,2 2)';
R::store($location);
$columns = R::inspect( 'location' );
asrt( $columns['point'], 'linestring' );
$location->bustcache = 2;
R::store($location);
$location = $location->fresh();
asrt( $location->point, 'LINESTRING(0 0,1 1,2 2)' );
R::nuke();
$location = R::dispense( 'location' );
$location->point = 'POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5))';
R::store($location);
$columns = R::inspect( 'location' );
asrt( $columns['point'], 'polygon' );
$location->bustcache = 4;
R::store($location);
$location = $location->fresh();
asrt( $location->point, 'POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5))' );
R::bindFunc( 'read', 'location.point', NULL );
$location->bustcache = 1;
R::store($location);
$location = $location->fresh();
asrt( ( $location->point === 'POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5))' ), FALSE );
$filters = AQueryWriter::getSQLFilters();
asrt( is_array( $filters ), TRUE );
asrt( count( $filters ), 2 );
asrt( isset( $filters[ QueryWriter::C_SQLFILTER_READ] ), TRUE );
asrt( isset( $filters[ QueryWriter::C_SQLFILTER_WRITE] ), TRUE );
R::bindFunc( 'read', 'place.point', 'asText' );
R::bindFunc( 'write', 'place.point', 'GeomFromText' );
R::bindFunc( 'read', 'place.line', 'asText' );
R::bindFunc( 'write', 'place.line', 'GeomFromText' );
R::nuke();
$place = R::dispense( 'place' );
$place->point = 'POINT(13.2 666.6)';
$place->line = 'LINESTRING(9.2 0,3 1.33)';
R::store( $place );
$columns = R::inspect( 'place' );
asrt( $columns['point'], 'point' );
asrt( $columns['line'], 'linestring' );
$place = R::findOne('place');
asrt( $place->point, 'POINT(13.2 666.6)' );
asrt( $place->line, 'LINESTRING(9.2 0,3 1.33)' );
R::bindFunc( 'read', 'place.point', NULL );
R::bindFunc( 'write', 'place.point', NULL );
R::bindFunc( 'read', 'place.line', NULL );
R::bindFunc( 'write', 'place.line', NULL );
}
/**
* Test scanning and coding of values.
*
* @return void
*/
public function testScanningAndCoding()
{
$toolbox = R::getToolBox();
$adapter = $toolbox->getDatabaseAdapter();
$writer = $toolbox->getWriter();
$redbean = $toolbox->getRedBean();
$pdo = $adapter->getDatabase();
$a = new AssociationManager( $toolbox );
$adapter->exec( "DROP TABLE IF EXISTS testtable" );
asrt( in_array( "testtable", $adapter->getCol( "show tables" ) ), FALSE );
$writer->createTable( "testtable" );
asrt( in_array( "testtable", $adapter->getCol( "show tables" ) ), TRUE );
asrt( count( array_diff( $writer->getTables(), $adapter->getCol( "show tables" ) ) ), 0 );
asrt( count( array_keys( $writer->getColumns( "testtable" ) ) ), 1 );
asrt( in_array( "id", array_keys( $writer->getColumns( "testtable" ) ) ), TRUE );
asrt( in_array( "c1", array_keys( $writer->getColumns( "testtable" ) ) ), FALSE );
$writer->addColumn( "testtable", "c1", MySQL::C_DATATYPE_UINT32 );
asrt( count( array_keys( $writer->getColumns( "testtable" ) ) ), 2 );
asrt( in_array( "c1", array_keys( $writer->getColumns( "testtable" ) ) ), TRUE );
foreach ( $writer->sqltype_typeno as $key => $type ) {
if ( $type < 100 ) {
asrt( $writer->code( $key, TRUE ), $type );
} else {
asrt( $writer->code( $key, TRUE ), MySQL::C_DATATYPE_SPECIFIED );
}
}
asrt( $writer->code( MySQL::C_DATATYPE_SPECIAL_DATETIME ), MySQL::C_DATATYPE_SPECIFIED );
asrt( $writer->code( "unknown" ), MySQL::C_DATATYPE_SPECIFIED );
asrt( $writer->scanType( FALSE ), MySQL::C_DATATYPE_BOOL );
asrt( $writer->scanType( TRUE ), MySQL::C_DATATYPE_BOOL );
asrt( $writer->scanType( 0 ), MySQL::C_DATATYPE_BOOL );
asrt( $writer->scanType( 1 ), MySQL::C_DATATYPE_BOOL );
asrt( $writer->scanType( INF ), MySQL::C_DATATYPE_TEXT7 );
asrt( $writer->scanType( NULL ), MySQL::C_DATATYPE_BOOL );
asrt( $writer->scanType( 2 ), MySQL::C_DATATYPE_UINT32 );
asrt( $writer->scanType( 255 ), MySQL::C_DATATYPE_UINT32 ); //no more uint8
asrt( $writer->scanType( 256 ), MySQL::C_DATATYPE_UINT32 );
asrt( $writer->scanType( -1 ), MySQL::C_DATATYPE_DOUBLE );
asrt( $writer->scanType( 1.5 ), MySQL::C_DATATYPE_DOUBLE );
asrt( $writer->scanType( "abc" ), MySQL::C_DATATYPE_TEXT7 );
asrt( $writer->scanType( str_repeat( 'abcd', 100000 ) ), MySQL::C_DATATYPE_TEXT32 );
asrt( $writer->scanType( "2001-10-10", TRUE ), MySQL::C_DATATYPE_SPECIAL_DATE );
asrt( $writer->scanType( "2001-10-10 10:00:00", TRUE ), MySQL::C_DATATYPE_SPECIAL_DATETIME );
asrt( $writer->scanType( "2001-10-10" ), MySQL::C_DATATYPE_TEXT7 );
asrt( $writer->scanType( "2001-10-10 10:00:00" ), MySQL::C_DATATYPE_TEXT7 );
asrt( $writer->scanType( "1.23", TRUE ), MySQL::C_DATATYPE_SPECIAL_MONEY );
asrt( $writer->scanType( "12.23", TRUE ), MySQL::C_DATATYPE_SPECIAL_MONEY );
asrt( $writer->scanType( "124.23", TRUE ), MySQL::C_DATATYPE_SPECIAL_MONEY );
asrt( $writer->scanType( str_repeat( "lorem ipsum", 100 ) ), MySQL::C_DATATYPE_TEXT16 );
$writer->widenColumn( "testtable", "c1", MySQL::C_DATATYPE_UINT32 );
$writer->addColumn( "testtable", "special", MySQL::C_DATATYPE_SPECIAL_DATE );
$cols = $writer->getColumns( "testtable" );
asrt( $writer->code( $cols['special'], TRUE ), MySQL::C_DATATYPE_SPECIAL_DATE );
asrt( $writer->code( $cols['special'], FALSE ), MySQL::C_DATATYPE_SPECIFIED );
$writer->addColumn( "testtable", "special2", MySQL::C_DATATYPE_SPECIAL_DATETIME );
$cols = $writer->getColumns( "testtable" );
asrt( $writer->code( $cols['special2'], TRUE ), MySQL::C_DATATYPE_SPECIAL_DATETIME );
asrt( $writer->code( $cols['special'], FALSE ), MySQL::C_DATATYPE_SPECIFIED );
$cols = $writer->getColumns( "testtable" );
asrt( $writer->code( $cols["c1"] ), MySQL::C_DATATYPE_UINT32 );
$writer->widenColumn( "testtable", "c1", MySQL::C_DATATYPE_DOUBLE );
$cols = $writer->getColumns( "testtable" );
asrt( $writer->code( $cols["c1"] ), MySQL::C_DATATYPE_DOUBLE );
$writer->widenColumn( "testtable", "c1", MySQL::C_DATATYPE_TEXT7 );
$cols = $writer->getColumns( "testtable" );
asrt( $writer->code( $cols["c1"] ), MySQL::C_DATATYPE_TEXT7 );
$writer->widenColumn( "testtable", "c1", MySQL::C_DATATYPE_TEXT8 );
$cols = $writer->getColumns( "testtable" );
asrt( $writer->code( $cols["c1"] ), MySQL::C_DATATYPE_TEXT8 );
$id = $writer->updateRecord( "testtable", array( array( "property" => "c1", "value" => "lorem ipsum" ) ) );
$row = $writer->queryRecord( "testtable", array( "id" => array( $id ) ) );
asrt( $row[0]["c1"], "lorem ipsum" );
$writer->updateRecord( "testtable", array( array( "property" => "c1", "value" => "ipsum lorem" ) ), $id );
$row = $writer->queryRecord( "testtable", array( "id" => array( $id ) ) );
asrt( $row[0]["c1"], "ipsum lorem" );
$writer->deleteRecord( "testtable", array( "id" => array( $id ) ) );
$row = $writer->queryRecord( "testtable", array( "id" => array( $id ) ) );
asrt( empty( $row ), TRUE );
$writer->addColumn( "testtable", "c2", MySQL::C_DATATYPE_UINT32 );
}
/**
* (FALSE should be stored as 0 not as '')
*
* @return void
*/
public function testZeroIssue()
{
testpack( "Zero issue" );
$toolbox = R::getToolBox();
$redbean = $toolbox->getRedBean();
$adapter = $toolbox->getDatabaseAdapter();
$writer = $toolbox->getWriter();
$pdo = $adapter->getDatabase();
$pdo->Execute( "DROP TABLE IF EXISTS `zero`" );
$bean = $redbean->dispense( "zero" );
$bean->zero = FALSE;
$bean->title = "bla";
$redbean->store( $bean );
asrt( count( $redbean->find( "zero", array(), " zero = 0 " ) ), 1 );
R::store( R::dispense( 'hack' ) );
testpack( "Test RedBean Security - bean interface " );
asrt( in_array( "hack", $adapter->getCol( "show tables" ) ), TRUE );
$bean = $redbean->load( "page", "13; drop table hack" );
asrt( in_array( "hack", $adapter->getCol( "show tables" ) ), TRUE );
try {
$bean = $redbean->load( "page where 1; drop table hack", 1 );
} catch (\Exception $e ) {
}
asrt( in_array( "hack", $adapter->getCol( "show tables" ) ), TRUE );
$bean = $redbean->dispense( "page" );
$evil = "; drop table hack";
$bean->id = $evil;
try {
$redbean->store( $bean );
} catch (\Exception $e ) {
}
asrt( in_array( "hack", $adapter->getCol( "show tables" ) ), TRUE );
unset( $bean->id );
$bean->name = "\"" . $evil;
try {
$redbean->store( $bean );
} catch (\Exception $e ) {
}
asrt( in_array( "hack", $adapter->getCol( "show tables" ) ), TRUE );
$bean->name = "'" . $evil;
try {
$redbean->store( $bean );
} catch (\Exception $e ) {
}
asrt( in_array( "hack", $adapter->getCol( "show tables" ) ), TRUE );
$bean->$evil = 1;
try {
$redbean->store( $bean );
} catch (\Exception $e ) {
}
asrt( in_array( "hack", $adapter->getCol( "show tables" ) ), TRUE );
unset( $bean->$evil );
$bean->id = 1;
$bean->name = "\"" . $evil;
try {
$redbean->store( $bean );
} catch (\Exception $e ) {
}
asrt( in_array( "hack", $adapter->getCol( "show tables" ) ), TRUE );
$bean->name = "'" . $evil;
try {
$redbean->store( $bean );
} catch (\Exception $e ) {
}
asrt( in_array( "hack", $adapter->getCol( "show tables" ) ), TRUE );
$bean->$evil = 1;
try {
$redbean->store( $bean );
} catch (\Exception $e ) {
}
asrt( in_array( "hack", $adapter->getCol( "show tables" ) ), TRUE );
try {
$redbean->trash( $bean );
} catch (\Exception $e ) {
}
asrt( in_array( "hack", $adapter->getCol( "show tables" ) ), TRUE );
try {
$redbean->find( "::", array(), "" );
} catch (\Exception $e ) {
pass();
}
$adapter->exec( "drop table if exists sometable" );
testpack( "Test RedBean Security - query writer" );
try {
$writer->createTable( "sometable` ( `id` INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT , PRIMARY KEY ( `id` ) ) ENGINE = InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ; drop table hack; --" );
} catch (\Exception $e ) {
}
asrt( in_array( "hack", $adapter->getCol( "show tables" ) ), TRUE );
testpack( "Test ANSI92 issue in clearrelations" );
$pdo->Execute( "DROP TABLE IF EXISTS book_group" );
$pdo->Execute( "DROP TABLE IF EXISTS author_book" );
$pdo->Execute( "DROP TABLE IF EXISTS book" );
$pdo->Execute( "DROP TABLE IF EXISTS author" );
$redbean = $toolbox->getRedBean();
$a = new AssociationManager( $toolbox );
$book = $redbean->dispense( "book" );
$author1 = $redbean->dispense( "author" );
$author2 = $redbean->dispense( "author" );
$book->title = "My First Post";
$author1->name = "Derek";
$author2->name = "Whoever";
set1toNAssoc( $a, $book, $author1 );
set1toNAssoc( $a, $book, $author2 );
pass();
$pdo->Execute( "DROP TABLE IF EXISTS book_group" );
$pdo->Execute( "DROP TABLE IF EXISTS book_author" );
$pdo->Execute( "DROP TABLE IF EXISTS author_book" );
$pdo->Execute( "DROP TABLE IF EXISTS book" );
$pdo->Execute( "DROP TABLE IF EXISTS author" );
$redbean = $toolbox->getRedBean();
$a = new AssociationManager( $toolbox );
$book = $redbean->dispense( "book" );
$author1 = $redbean->dispense( "author" );
$author2 = $redbean->dispense( "author" );
$book->title = "My First Post";
$author1->name = "Derek";
$author2->name = "Whoever";
$a->associate( $book, $author1 );
$a->associate( $book, $author2 );
pass();
testpack( "Test Association Issue Group keyword (Issues 9 and 10)" );
$pdo->Execute( "DROP TABLE IF EXISTS `book_group`" );
$pdo->Execute( "DROP TABLE IF EXISTS `group`" );
$group = $redbean->dispense( "group" );
$group->name = "mygroup";
$redbean->store( $group );
try {
$a->associate( $group, $book );
pass();
} catch ( SQL $e ) {
fail();
}
// Test issue SQL error 23000
try {
$a->associate( $group, $book );
pass();
} catch ( SQL $e ) {
fail();
}
asrt( (int) $adapter->getCell( "select count(*) from book_group" ), 1 ); //just 1 rec!
$pdo->Execute( "DROP TABLE IF EXISTS book_group" );
$pdo->Execute( "DROP TABLE IF EXISTS author_book" );
$pdo->Execute( "DROP TABLE IF EXISTS book" );
$pdo->Execute( "DROP TABLE IF EXISTS author" );
$redbean = $toolbox->getRedBean();
$a = new AssociationManager( $toolbox );
$book = $redbean->dispense( "book" );
$author1 = $redbean->dispense( "author" );
$author2 = $redbean->dispense( "author" );
$book->title = "My First Post";
$author1->name = "Derek";
$author2->name = "Whoever";
$a->unassociate( $book, $author1 );
$a->unassociate( $book, $author2 );
pass();
$redbean->trash( $redbean->dispense( "bla" ) );
pass();
$bean = $redbean->dispense( "bla" );
$bean->name = 1;
$bean->id = 2;
$redbean->trash( $bean );
pass();
}
/**
* Test special data types.
*
* @return void
*/
public function testTypes()
{
testpack( 'Special data types' );
$bean = R::dispense( 'bean' );
$bean->date = 'someday';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['date'], 'varchar(191)' );
$bean = R::dispense( 'bean' );
$bean->date = '2011-10-10';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['date'], 'varchar(191)' );
}
/**
* Test date types.
*
* @return void
*/
public function testTypesDates()
{
$bean = R::dispense( 'bean' );
$bean->date = '2011-10-10';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['date'], 'date' );
}
/**
* Test money types.
*
* @return void
*/
public function testTypesMon()
{
$bean = R::dispense( 'bean' );
$bean->amount = '22.99';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['amount'], 'decimal(10,2)' );
R::nuke();
$bean = R::dispense( 'bean' );
$bean->amount = '-22.99';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['amount'], 'decimal(10,2)' );
}
/**
* Date-time
*
* @return void
*/
public function testTypesDateTimes()
{
$bean = R::dispense( 'bean' );
$bean->date = '2011-10-10 10:00:00';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['date'], 'datetime' );
$bean = R::dispense( 'bean' );
try {
$bean = R::dispense( 'bean' );
$bean->title = 123;
$bean->setMeta( 'cast.title', 'invalid' );
R::store( $bean );
fail();
} catch ( RedException $e ) {
pass();
} catch (\Exception $e ) {
fail();
}
$bean = R::dispense( 'bean' );
$bean->title = 123;
$bean->setMeta( 'cast.title', 'text' );
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['title'], 'text' );
R::nuke();
$bean = R::dispense( 'bean' );
$bean->title = 123;
$bean->setMeta( 'cast.title', 'string' );
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['title'], 'varchar(191)' );
}
/**
* Stored and reloads spatial data to see if the
* value is preserved correctly.
*
* @return void
*/
protected function setGetSpatial( $data )
{
R::nuke();
$place = R::dispense( 'place' );
$place->location = $data;
R::store( $place );
asrt( R::getCell( 'SELECT AsText(location) FROM place LIMIT 1' ), $data );
}
/**
* Can we manually add a MySQL time column?
*
* @return void
*/
public function testTime()
{
R::nuke();
$clock = R::dispense('clock');
$clock->time = '10:00:00';
$clock->setMeta('cast.time', 'time');
R::store( $clock );
$columns = R::inspect('clock');
asrt( $columns['time'], 'time' );
$clock = R::findOne('clock');
$clock->time = '12';
R::store($clock);
$clock = R::findOne('clock');
$time = $clock->time;
asrt( ( strpos( $time, ':' ) > 0 ), TRUE );
}
/**
* Can we use the 'ignoreDisplayWidth'-feature for MySQL 8
* compatibility?
*
* @return void
*/
public function testWriterFeature()
{
$adapter = R::getToolBox()->getDatabaseAdapter();
$writer = new \RedBeanPHP\QueryWriter\MySQL( $adapter );
$writer->useFeature('ignoreDisplayWidth');
asrt($writer->typeno_sqltype[MySQL::C_DATATYPE_BOOL],' TINYINT UNSIGNED ');
asrt($writer->typeno_sqltype[MySQL::C_DATATYPE_UINT32],' INT UNSIGNED ');
asrt($writer->sqltype_typeno['tinyint unsigned'],MySQL::C_DATATYPE_BOOL);
asrt($writer->sqltype_typeno['int unsigned'],MySQL::C_DATATYPE_UINT32);
//Can we also pass invalid features without errors?
$writer->useFeature('nonsense');
pass();
}
/**
* Can we pass an options array to Writer Constructor?
*
* @return void
*/
public function testWriterOptions()
{
$adapter = R::getToolBox()->getDatabaseAdapter();
$writer = new \RedBeanPHP\QueryWriter\MySQL( $adapter, array('noInitcode'=>TRUE) );
pass();
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace RedUNIT;
/**
* Postgres
*
* The Postgres class is the parent class of all PostgreSQL specific
* test classes.
*
* @file RedUNIT/Postgres.php
* @desc Base class for all PostgreSQL specific tests.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Postgres extends RedUNIT
{
/**
* Returns a list of drivers for which this driver supports
* 'test rounds'. This class only supports the PostgreSQL driver.
*
* @see RedUNIT::getTargetDrivers() for details.
*
* @return array
*/
public function getTargetDrivers()
{
return array( 'pgsql' );
}
}

View File

@@ -0,0 +1,120 @@
<?php
namespace RedUNIT\Postgres;
use RedUNIT\Postgres as Postgres;
use RedBeanPHP\Facade as R;
/**
* Bigint
*
* Tests handling of bigint type columns for primary key IDs,
* can we use bigint primary keys without issues ?
* These tests should be able to detect incorrect intval
* casts for instance.
*
* @file RedUNIT/Postgres/Bigint.php
* @desc Tests support for BIGINT primary keys.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Bigint extends Postgres
{
/**
* Test BIG INT primary key support.
*
* @return void
*/
public function testBigIntSupport()
{
R::nuke();
$createPageTableSQL = '
CREATE TABLE
page
(
id BIGSERIAL PRIMARY KEY,
book_id BIGSERIAL,
magazine_id BIGSERIAL,
title VARCHAR(255)
)';
$createBookTableSQL = '
CREATE TABLE
book
(
id BIGSERIAL PRIMARY KEY,
title VARCHAR(255)
)';
$createPagePageTableSQL = '
CREATE TABLE
page_page
(
id BIGSERIAL PRIMARY KEY,
page_id BIGSERIAL,
page2_id BIGSERIAL
) ';
R::exec( $createBookTableSQL );
R::exec( $createPageTableSQL );
R::exec( $createPagePageTableSQL );
//insert some records
$book1ID = '2223372036854775808';
$book2ID = '2223372036854775809';
$page1ID = '1223372036854775808';
$page2ID = '1223372036854775809';
$page3ID = '1223372036854775890';
$pagePage1ID = '3223372036854775808';
R::exec("ALTER SEQUENCE book_id_seq RESTART WITH $book1ID");
R::exec("ALTER SEQUENCE page_id_seq RESTART WITH $page1ID");
R::exec("ALTER SEQUENCE page_page_id_seq RESTART WITH $pagePage1ID");
$insertBook1SQL = "
INSERT INTO book (title) VALUES( 'book 1' );
";
$insertBook2SQL = "
INSERT INTO book (title) VALUES( 'book 2' );
";
$insertPage1SQL = "
INSERT INTO page (id, book_id, title, magazine_id) VALUES( '$page1ID', '$book1ID', 'page 1 of book 1', '$book2ID' );
";
$insertPage2SQL = "
INSERT INTO page (id, book_id, title) VALUES( '$page2ID', '$book1ID', 'page 2 of book 1' );
";
$insertPage3SQL = "
INSERT INTO page (id, book_id, title) VALUES( '$page3ID', '$book2ID', 'page 1 of book 2' );
";
$insertPagePage1SQL = "
INSERT INTO page_page (id, page_id, page2_id) VALUES( '$pagePage1ID', '$page2ID', '$page3ID' );
";
R::exec( $insertBook1SQL );
R::exec( $insertBook2SQL );
R::exec( $insertPage1SQL );
R::exec( $insertPage2SQL );
R::exec( $insertPage3SQL );
R::exec( $insertPagePage1SQL );
//basic tour of basic functions....
$book1 = R::load( 'book', $book1ID );
asrt( $book1->id, $book1ID );
asrt( $book1->title, 'book 1' );
$book2 = R::load( 'book', $book2ID );
asrt( $book2->id, $book2ID );
asrt( $book2->title, 'book 2' );
asrt( count( $book1->ownPage ), 2 );
asrt( count( $book1->fresh()->with( 'LIMIT 1' )->ownPage ), 1 );
asrt( count( $book1->fresh()->withCondition( ' title = ? ', array('page 2 of book 1'))->ownPage ), 1 );
asrt( count($book2->ownPage), 1 );
asrt( $book2->fresh()->countOwn( 'page' ), 1 );
$page1 = R::load( 'page', $page1ID );
asrt( count( $page1->sharedPage ), 0 );
asrt( $page1->fetchAs( 'book' )->magazine->id, $book2ID );
$page2 = R::load( 'page', $page2ID );
asrt( count($page2->sharedPage), 1 );
asrt( $page2->fresh()->countShared( 'page' ), 1 );
$page3 = R::findOne( 'page', ' title = ? ', array( 'page 1 of book 2' ) );
asrt( $page3->id, $page3ID );
asrt( $page3->book->id, $book2ID );
}
}

View File

@@ -0,0 +1,292 @@
<?php
namespace RedUNIT\Postgres;
use RedUNIT\Postgres as Postgres;
use RedBeanPHP\Facade as R;
/**
* Foreignkeys
*
* Tests creation and validity of foreign keys,
* foreign key constraints and indexes in PostgreSQL.
* Also tests whether the correct contraint action has been selected.
*
* @file RedUNIT/Postgres/Foreignkeys.php
* @desc Tests the creation of foreign keys.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Foreignkeys extends Postgres
{
/**
* Test foreign keys with postgres.
*/
public function testForeignKeysWithPostgres()
{
testpack( 'Test Postgres Foreign keys' );
$a = R::getWriter()->addFK( 'a', 'b', 'c', 'd' ); //must fail
pass(); //survive without exception
asrt( $a, FALSE ); //must return FALSE
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$cover = R::dispense( 'cover' );
list( $g1, $g2 ) = R::dispense( 'genre', 2 );
$g1->name = '1';
$g2->name = '2';
$book->ownPage = array( $page );
$book->cover = $cover;
$book->sharedGenre = array( $g1, $g2 );
R::store( $book );
$sql = "SELECT
tc.constraint_name, tc.table_name, kcu.column_name,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name
FROM
information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
WHERE constraint_type = 'FOREIGN KEY' AND (tc.table_name='book' OR tc.table_name='book_genre' OR tc.table_name='page');";
$fks = R::getAll( $sql );
$json = '[
{
"constraint_name": "book_cover_id_fkey",
"table_name": "book",
"column_name": "cover_id",
"foreign_table_name": "cover",
"foreign_column_name": "id"
},
{
"constraint_name": "page_book_id_fkey",
"table_name": "page",
"column_name": "book_id",
"foreign_table_name": "book",
"foreign_column_name": "id"
},
{
"constraint_name": "book_genre_genre_id_fkey",
"table_name": "book_genre",
"column_name": "genre_id",
"foreign_table_name": "genre",
"foreign_column_name": "id"
},
{
"constraint_name": "book_genre_book_id_fkey",
"table_name": "book_genre",
"column_name": "book_id",
"foreign_table_name": "book",
"foreign_column_name": "id"
}
]';
$j = json_encode( $fks );
$j1 = json_decode( $j, TRUE );
$j2 = json_decode( $json, TRUE );
foreach ( $j1 as $jrow ) {
$s = json_encode( $jrow );
$found = 0;
foreach ( $j2 as $k => $j2row ) {
if ( json_encode( $j2row ) === $s ) {
pass();
unset( $j2[$k] );
$found = 1;
}
}
if ( !$found ) fail();
}
}
/**
* Test constraint function directly in Writer.
*
* @return void
*/
public function testConstraint()
{
R::nuke();
$database = R::getCell('SELECT current_database()');
$sql = 'CREATE TABLE book (id SERIAL PRIMARY KEY)';
R::exec( $sql );
$sql = 'CREATE TABLE page (id SERIAL PRIMARY KEY)';
R::exec( $sql );
$sql = 'CREATE TABLE book_page (
id SERIAL PRIMARY KEY,
book_id INTEGER,
page_id INTEGER
)';
R::exec( $sql );
$writer = R::getWriter();
$sql = "
SELECT
COUNT(*)
FROM information_schema.key_column_usage AS k
LEFT JOIN information_schema.table_constraints AS c ON c.constraint_name = k.constraint_name
WHERE k.table_catalog = '$database'
AND k.table_schema = 'public'
AND k.table_name = 'book_page'
AND c.constraint_type = 'FOREIGN KEY'";
$numFKS = R::getCell( $sql );
asrt( (int) $numFKS, 0 );
$writer->addFK( 'book_page', 'book', 'book_id', 'id', TRUE );
$writer->addFK( 'book_page', 'page', 'page_id', 'id', TRUE );
$numFKS = R::getCell( $sql );
asrt( (int) $numFKS, 2 );
$writer->addFK( 'book_page', 'book', 'book_id', 'id', TRUE );
$writer->addFK( 'book_page', 'page', 'page_id', 'id', TRUE );
$numFKS = R::getCell( $sql );
asrt( (int) $numFKS, 2 );
}
/**
* Test adding foreign keys.
*
* @return void
*/
public function testAddingForeignKey()
{
R::nuke();
$database = R::getCell('SELECT current_database()');
$sql = 'CREATE TABLE book (
id SERIAL PRIMARY KEY
)';
R::exec( $sql );
$sql = 'CREATE TABLE page (
id SERIAL PRIMARY KEY,
book_id INTEGER
)';
R::exec( $sql );
$writer = R::getWriter();
$sql = "
SELECT
COUNT(*)
FROM information_schema.key_column_usage AS k
LEFT JOIN information_schema.table_constraints AS c ON c.constraint_name = k.constraint_name
WHERE k.table_catalog = '$database'
AND k.table_schema = 'public'
AND k.table_name = 'page'
AND c.constraint_type = 'FOREIGN KEY'";
$numFKS = R::getCell( $sql );
asrt( (int) $numFKS, 0 );
$writer->addFK('page', 'page', 'book_id', 'id', TRUE);
$sql = "
SELECT
COUNT(*)
FROM information_schema.key_column_usage AS k
LEFT JOIN information_schema.table_constraints AS c ON c.constraint_name = k.constraint_name
WHERE k.table_catalog = '$database'
AND k.table_schema = 'public'
AND k.table_name = 'page'
AND c.constraint_type = 'FOREIGN KEY'";
$numFKS = R::getCell( $sql );
asrt( (int) $numFKS, 1 );
//dont add twice
$writer->addFK('page', 'page', 'book_id', 'id', TRUE);
$sql = "
SELECT
COUNT(*)
FROM information_schema.key_column_usage AS k
LEFT JOIN information_schema.table_constraints AS c ON c.constraint_name = k.constraint_name
WHERE k.table_catalog = '$database'
AND k.table_schema = 'public'
AND k.table_name = 'page'
AND c.constraint_type = 'FOREIGN KEY'";
$numFKS = R::getCell( $sql );
asrt( (int) $numFKS, 1 );
//even if it is different
$writer->addFK('page', 'page', 'book_id', 'id', FALSE);
$sql = "
SELECT
COUNT(*)
FROM information_schema.key_column_usage AS k
LEFT JOIN information_schema.table_constraints AS c ON c.constraint_name = k.constraint_name
WHERE k.table_catalog = '$database'
AND k.table_schema = 'public'
AND k.table_name = 'page'
AND c.constraint_type = 'FOREIGN KEY'";
$numFKS = R::getCell( $sql );
asrt( (int) $numFKS, 1 );
R::nuke();
$sql = 'CREATE TABLE book (
id SERIAL PRIMARY KEY
)';
R::exec( $sql );
$sql = 'CREATE TABLE page (
id SERIAL PRIMARY KEY,
book_id INTEGER
)';
R::exec( $sql );
$writer = R::getWriter();
$sql = "
SELECT
COUNT(*)
FROM information_schema.key_column_usage AS k
LEFT JOIN information_schema.table_constraints AS c ON c.constraint_name = k.constraint_name
WHERE k.table_catalog = '$database'
AND k.table_schema = 'public'
AND k.table_name = 'page'
AND c.constraint_type = 'FOREIGN KEY'";
$numFKS = R::getCell( $sql );
asrt( (int) $numFKS, 0 );
$writer->addFK('page', 'page', 'book_id', 'id', FALSE);
$sql = "
SELECT
COUNT(*)
FROM information_schema.key_column_usage AS k
LEFT JOIN information_schema.table_constraints AS c ON c.constraint_name = k.constraint_name
WHERE k.table_catalog = '$database'
AND k.table_schema = 'public'
AND k.table_name = 'page'
AND c.constraint_type = 'FOREIGN KEY'";
$numFKS = R::getCell( $sql );
asrt( (int) $numFKS, 1 );
}
/**
* Test whether we can manually create indexes.
*
* @return void
*/
public function testAddingIndex()
{
R::nuke();
$sql = 'CREATE TABLE song (
id SERIAL PRIMARY KEY,
album_id INTEGER,
category VARCHAR(255)
)';
R::exec( $sql );
$indexes = R::getAll( " SELECT * FROM pg_indexes WHERE schemaname = 'public' AND tablename = 'song' ");
asrt( count( $indexes ), 1 );
$writer = R::getWriter();
$writer->addIndex( 'song', 'index1', 'album_id' );
$indexes = R::getAll( " SELECT * FROM pg_indexes WHERE schemaname = 'public' AND tablename = 'song' ");
asrt( count( $indexes ), 2 );
//Cant add the same index twice
$writer->addIndex( 'song', 'index1', 'album_id' );
$indexes = R::getAll( " SELECT * FROM pg_indexes WHERE schemaname = 'public' AND tablename = 'song' ");
asrt( count( $indexes ), 2 );
$writer->addIndex( 'song', 'index2', 'category' );
$indexes = R::getAll( " SELECT * FROM pg_indexes WHERE schemaname = 'public' AND tablename = 'song' ");
asrt( count( $indexes ), 3 );
//Dont fail, just dont
try {
$writer->addIndex( 'song', 'index3', 'nonexistant' );
pass();
} catch( \Exception $e ) {
fail();
}
$indexes = R::getAll( " SELECT * FROM pg_indexes WHERE schemaname = 'public' AND tablename = 'song' ");
asrt( count( $indexes ), 3 );
try {
$writer->addIndex( 'nonexistant', 'index4', 'nonexistant' );
pass();
} catch( \Exception $e ) {
fail();
}
$indexes = R::getAll( " SELECT * FROM pg_indexes WHERE schemaname = 'public' AND tablename = 'song' ");
asrt( count( $indexes ), 3 );
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace RedUNIT\Postgres;
use RedUNIT\Postgres as Postgres;
use RedBeanPHP\Facade as R;
/**
* Parambind
*
* Tests the parameter binding functionality in RedBeanPHP.
* These test scenarios include for instance: NULL handling,
* binding parameters in LIMIT clauses and so on.
*
* @file RedUNIT/Postgres/Parambind.php
* @desc Tests\PDO parameter binding for Postgres.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Parambind extends Postgres
{
/**
* Test parameter binding.
*
* @return void
*/
public function testParamBindingWithPostgres()
{
testpack( "param binding pgsql" );
$page = R::dispense( "page" );
$page->name = "abc";
$page->number = 2;
R::store( $page );
R::exec( "insert into page (name) values(:name) ", array( ":name" => "my name" ) );
R::exec( "insert into page (number) values(:one) ", array( ":one" => 1 ) );
R::exec( "insert into page (number) values(:one) ", array( ":one" => "1" ) );
R::exec( "insert into page (number) values(:one) ", array( ":one" => "1234" ) );
R::exec( "insert into page (number) values(:one) ", array( ":one" => "-21" ) );
pass();
testpack( 'Test whether we can properly bind and receive NULL values' );
$adapter = R::getDatabaseAdapter();
asrt( $adapter->getCell( 'SELECT TEXT( :nil ) ', array( ':nil' => 'NULL' ) ), 'NULL' );
asrt( $adapter->getCell( 'SELECT TEXT( :nil ) ', array( ':nil' => NULL ) ), NULL );
asrt( $adapter->getCell( 'SELECT TEXT( ? ) ', array( 'NULL' ) ), 'NULL' );
asrt( $adapter->getCell( 'SELECT TEXT( ? ) ', array( NULL ) ), NULL );
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace RedUNIT\Postgres;
use RedUNIT\Postgres as Postgres;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException\SQL as SQLException;
/**
* Partial Beans
*
* This class has been designed to test 'partial bean mode'.
* In 'partial bean mode' only changed properties are being saved,
* not entire beans. This can be useful when you have unsupported
* column types in your table, this is what we test here. In this
* example we have a table that contains a boolean column, this column
* does not accept the value '' as FALSE as shown in the test, it will
* trigger an Invalid Text Representation Exception. Thanks to 'partial beans'
* we can work around this, by selectively updating the non-boolean properties
* of the bean. If we choose to update the boolean property this is no longer
* a problem because we set the value ourselves it will be compatible. However
* automatically loaded and stored properties by RedBeanPHP are subject to
* type inference, and the boolean FALSE value will become '', which is the
* crux of the issue here.
*
* @file RedUNIT/Postgres/Partial.php
* @desc Tests whether 'partial beans' can be used to support non-RB columns
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Partial extends Postgres
{
/**
* Excerpt from issue #547:
* "When I load a bean (via $bean = R::findOne(...);), only change a few
* values and then call R::store($bean);, it can happen that I get an error like:
* Error in SQL query:
* SQLSTATE[22P02]: Invalid text representation:
* 7 ERROR: invalid input syntax for type boolean: ""
* This happens, when there is a boolean field set to FALSE and
* I don't update that field. When R::store() is called,
* the value isn't translated to '0' but instead stays FALSE. PostgreSQL doesn't like this.
* When setting a boolean value, it gets converted correctly,
* but any unchanged values stay of type boolean.
*
* @return void
*/
public function testIssue547BoolCol()
{
R::nuke();
R::usePartialBeans( FALSE );
$bean = R::dispense( 'bean' );
$bean->property1 = 'value1';
$id = R::store( $bean );
R::freeze( TRUE );
R::exec('ALTER TABLE bean ADD COLUMN "property2" BOOLEAN DEFAULT FALSE;');
$bean = R::load( 'bean', $id );
$bean->property1 = 'value1b';
/* we cant save the bean, because there is an unsupported field type in the table */
/* this was the bug... (or missing feature?) */
try {
R::store( $bean );
fail();
} catch( SQLException $e ) {
asrt( strpos( $e->getMessage(), 'Invalid text representation' ) > 1, TRUE );
asrt( $e->getSQLState(), '22P02' );
}
/* solved by adding feature partial beans, now only the changed properties get saved */
R::usePartialBeans( TRUE );
$id = R::store( $bean );
/* no exception... */
pass();
/* also test behavior of boolean column in general */
$bean = R::load( 'bean', $id );
asrt( $bean->property1, 'value1b' );
asrt( $bean->property2, FALSE );
$bean->property2 = TRUE;
$id = R::store( $bean );
$bean = R::load( 'bean', $id );
asrt( $bean->property1, 'value1b' );
asrt( $bean->property2, TRUE );
$bean->property2 = FALSE;
$id = R::store( $bean );
$bean = R::load( 'bean', $id );
asrt( $bean->property1, 'value1b' );
asrt( $bean->property2, FALSE );
$bean->property2 = 't';
$id = R::store( $bean );
$bean = R::load( 'bean', $id );
asrt( $bean->property1, 'value1b' );
asrt( $bean->property2, TRUE );
$bean->property2 = 'f';
$id = R::store( $bean );
$bean = R::load( 'bean', $id );
asrt( $bean->property1, 'value1b' );
asrt( $bean->property2, FALSE );
/* but invalid text should not work */
$bean->property2 = 'tx'; //instead of just 't'
try {
$id = R::store( $bean );
fail();
} catch( SQLException $e ) {
asrt( strpos( $e->getMessage(), 'Invalid text representation' ) > 1, TRUE );
asrt( $e->getSQLState(), '22P02' );
}
R::usePartialBeans( FALSE );
}
}

View File

@@ -0,0 +1,138 @@
<?php
namespace RedUNIT\Postgres;
use RedUNIT\Postgres as Postgres;
use RedBeanPHP\Facade as R;
/**
* Setget
*
* This class has been designed to test set/get operations
* for a specific Query Writer / Adapter. Since RedBeanPHP
* creates columns based on values it's essential that you
* get back the 'same' value as you put in - or - if that's
* not the case, that there are at least very clear rules
* about what to expect. Examples of possible issues tested in
* this class include:
*
* - Test whether booleans are returned correctly (they will become integers)
* - Test whether large numbers are preserved
* - Test whether floating point numbers are preserved
* - Test whether date/time values are preserved
* and so on...
*
* @file RedUNIT/Postgres/Setget.php
* @desc Tests whether values are correctly stored.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Setget extends Postgres
{
/**
* Test whether we can store DateTime objects and get them back
* as 'date-time' strings representing the same date and time.
*
* @return void
*/
public function testDateObject()
{
$dt = new \DateTime();
$dt->setTimeZone( new \DateTimeZone( 'Europe/Amsterdam' ) );
$dt->setDate( 1981, 5, 1 );
$dt->setTime( 3, 13, 13 );
asrt( setget( $dt ), '1981-05-01 03:13:13' );
$bean = R::dispense( 'bean' );
$bean->dt = $dt;
}
/**
* Test numbers.
*
* @return void
*/
public function testNumbers()
{
asrt( setget( "-1" ), "-1" );
asrt( setget( -1 ), "-1" );
asrt( setget( "1.0" ), "1" );
asrt( setget( 1.0 ), "1" );
asrt( setget( "-0.25" ), "-0.25" );
asrt( setget( -0.25 ), "-0.25" );
asrt( setget( "3.20" ), "3.20" );
asrt( setget( "13.20" ), "13.20" );
asrt( setget( "134.20" ), "134.20" );
asrt( setget( 3.21 ), '3.21' );
asrt( setget( "0.12345678" ), "0.12345678" );
asrt( setget( 0.12345678 ), "0.12345678" );
asrt( setget( "-0.12345678" ), "-0.12345678" );
asrt( setget( -0.12345678 ), "-0.12345678" );
asrt( setget( "2147483647" ), "2147483647" );
asrt( setget( 2147483647 ), "2147483647" );
asrt( setget( -2147483647 ), "-2147483647" );
asrt( setget( "-2147483647" ), "-2147483647" );
asrt( setget( "2147483648" ), "2147483648" );
asrt( setget( "-2147483648" ), "-2147483648" );
asrt( setget( "199936710040730" ), "199936710040730" );
asrt( setget( "-199936710040730" ), "-199936710040730" );
}
/**
* Test dates.
*
* @return void
*/
public function testDates()
{
asrt( setget( "2010-10-11" ), "2010-10-11" );
asrt( setget( "2010-10-11 12:10" ), "2010-10-11 12:10" );
asrt( setget( "2010-10-11 12:10:11" ), "2010-10-11 12:10:11" );
asrt( setget( "x2010-10-11 12:10:11" ), "x2010-10-11 12:10:11" );
}
/**
* Test strings.
*
* @return void
*/
public function testStrings()
{
asrt( setget( "a" ), "a" );
asrt( setget( "." ), "." );
asrt( setget( "\"" ), "\"" );
asrt( setget( "just some text" ), "just some text" );
}
/**
* Test booleans.
*
* @return void
*/
public function testBool()
{
asrt( setget( TRUE ), "1" );
asrt( setget( FALSE ), "0" );
asrt( setget( "TRUE" ), "TRUE" );
asrt( setget( "FALSE" ), "FALSE" );
}
/**
* Test NULL.
*
* @return void
*/
public function testNull()
{
asrt( setget( "NULL" ), "NULL" );
asrt( setget( "NULL" ), "NULL" );
asrt( setget( NULL ), NULL );
asrt( ( setget( 0 ) == 0 ), TRUE );
asrt( ( setget( 1 ) == 1 ), TRUE );
asrt( ( setget( TRUE ) == TRUE ), TRUE );
asrt( ( setget( FALSE ) == FALSE ), TRUE );
}
}

View File

@@ -0,0 +1,317 @@
<?php
namespace RedUNIT\Postgres;
use RedUNIT\Postgres as Postgres;
use RedBeanPHP\Facade as R;
/**
* Trees
*
* This class has been designed to test tree traversal using
* R::children() and R::parents() relying on
* recursive common table expressions.
*
* @file RedUNIT/Postgres/Trees.php
* @desc Tests trees
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Trees extends Postgres
{
protected function summarize( $beans )
{
$names = array();
foreach( $beans as $bean ) {
$names[] = $bean->title;
}
return implode( ',', $names );
}
/**
* Test trees
*
* @return void
*/
public function testCTETrees()
{
R::nuke();
$pages = R::dispense(array(
'_type' => 'page',
'title' => 'home',
'ownPageList' => array(array(
'_type' => 'page',
'title' => 'shop',
'ownPageList' => array(array(
'_type' => 'page',
'title' => 'wines',
'ownPageList' => array(array(
'_type' => 'page',
'title' => 'whiskies',
))
))
))
));
R::store( $pages );
$whiskyPage = R::findOne( 'page', 'title = ?', array('whiskies') );
asrt( $this->summarize( R::parents( $whiskyPage, ' ORDER BY title ASC ' ) ), 'home,shop,whiskies,wines' );
asrt( $this->summarize( R::children( $whiskyPage, ' ORDER BY title ASC ' ) ), 'whiskies' );
$homePage = R::findOne( 'page', 'title = ?', array('home') );
asrt( $this->summarize( R::parents( $homePage, ' ORDER BY title ASC ' ) ), 'home' );
asrt( $this->summarize( R::children( $homePage, ' ORDER BY title ASC ' ) ), 'home,shop,whiskies,wines' );
$shopPage = R::findOne( 'page', 'title = ?', array('shop') );
asrt( $this->summarize( R::parents( $shopPage, ' ORDER BY title ASC ' ) ), 'home,shop' );
asrt( $this->summarize( R::children( $shopPage, ' ORDER BY title ASC ' ) ), 'shop,whiskies,wines' );
$winePage = R::findOne( 'page', 'title = ?', array('wines') );
asrt( $this->summarize( R::parents( $winePage, ' ORDER BY title ASC ' ) ), 'home,shop,wines' );
asrt( $this->summarize( R::children( $winePage, ' ORDER BY title ASC ' ) ), 'whiskies,wines' );
asrt( $this->summarize( R::children( $winePage, ' title NOT IN (\'wines\') ORDER BY title ASC ' ) ), 'whiskies' );
asrt( $this->summarize( R::parents( $winePage, ' title NOT IN (\'home\') ORDER BY title ASC ' ) ), 'shop,wines' );
asrt( $this->summarize( R::parents( $winePage, ' ORDER BY title ASC ', array() ) ), 'home,shop,wines' );
asrt( $this->summarize( R::children( $winePage, ' ORDER BY title ASC ', array() ) ), 'whiskies,wines' );
asrt( $this->summarize( R::children( $winePage, ' title NOT IN (\'wines\') ORDER BY title ASC ', array() ) ), 'whiskies' );
asrt( $this->summarize( R::parents( $winePage, ' title NOT IN (\'home\') ORDER BY title ASC ', array() ) ), 'shop,wines' );
asrt( $this->summarize( R::children( $winePage, ' title != ? ORDER BY title ASC ', array( 'wines' ) ) ), 'whiskies' );
asrt( $this->summarize( R::parents( $winePage, ' title != ? ORDER BY title ASC ', array( 'home' ) ) ), 'shop,wines' );
asrt( $this->summarize( R::children( $winePage, ' title != :title ORDER BY title ASC ', array( ':title' => 'wines' ) ) ), 'whiskies' );
asrt( $this->summarize( R::parents( $winePage, ' title != :title ORDER BY title ASC ', array( ':title' => 'home' ) ) ), 'shop,wines' );
asrt( R::countChildren( $homePage ), 3 );
asrt( R::countParents( $whiskyPage ), 3 );
asrt( R::countChildren( $winePage, ' title != :title ', array( ':title' => 'wines' ) ) , 1 );
asrt( R::countChildren( $winePage, ' title = :title ', array( ':title' => 'wines' ) ) , 1 );
asrt( R::countParents( $winePage, ' title != :title ', array( ':title' => 'home' ) ) , 2 );
}
/**
* Test CTE and Parsed Joins.
*
* @return void
*/
public function testCTETreesAndParsedJoins()
{
R::nuke();
list($cards, $details, $colors) = R::dispenseAll('card*9,detail*4,color*2');
$colors[0]->name = 'red';
$colors[1]->name = 'black';
$details[0]->points = 500;
$details[1]->points = 200;
$details[2]->points = 300;
$details[3]->points = 100;
$cards[0]->ownCardList = array( $cards[1] );
$cards[1]->ownCardList = array( $cards[2], $cards[3] );
$cards[2]->ownCardList = array( $cards[4], $cards[5] );
$cards[3]->ownCardList = array( $cards[6], $cards[7], $cards[8] );
$cards[0]->ownOwner[] = R::dispense(array('_type'=>'owner', 'name'=>'User0'));
$cards[1]->ownOwner[] = R::dispense(array('_type'=>'owner', 'name'=>'User1'));
$cards[2]->ownOwner[] = R::dispense(array('_type'=>'owner', 'name'=>'User2'));
$cards[3]->ownOwner[] = R::dispense(array('_type'=>'owner', 'name'=>'User3'));
$cards[4]->ownOwner[] = R::dispense(array('_type'=>'owner', 'name'=>'User4'));
$cards[5]->ownOwner[] = R::dispense(array('_type'=>'owner', 'name'=>'User5'));
$cards[6]->ownOwner[] = R::dispense(array('_type'=>'owner', 'name'=>'User6'));
$cards[7]->ownOwner[] = R::dispense(array('_type'=>'owner', 'name'=>'User7'));
$cards[8]->ownOwner[] = R::dispense(array('_type'=>'owner', 'name'=>'User8'));
$cards[0]->detail = $details[0];
$cards[1]->detail = $details[0];
$cards[2]->detail = $details[1];
$cards[3]->detail = $details[2];
$cards[4]->detail = $details[3];
$cards[5]->detail = $details[3];
$cards[6]->detail = $details[3];
$cards[7]->detail = $details[3];
$cards[8]->detail = $details[3];
$colors[0]->sharedCardList = array( $cards[0], $cards[2], $cards[4], $cards[6], $cards[8] );
$colors[1]->sharedCardList = array( $cards[1], $cards[3], $cards[5], $cards[7] );
R::storeAll(array_merge($cards,$details,$colors));
$cardsWith100Points = R::children($cards[0], ' @joined.detail.points = ? ', array(100));
asrt(count($cardsWith100Points),5);
$cardsWith200Points = R::children($cards[0], ' @joined.detail.points = ? ', array(200));
asrt(count($cardsWith200Points),1);
$cardsWith300Points = R::children($cards[0], ' @joined.detail.points = ? ', array(300));
asrt(count($cardsWith200Points),1);
$cardsWith500Points = R::children($cards[0], ' @joined.detail.points = ? ', array(500));
asrt(count($cardsWith200Points),1);
for($i=8; $i>=4; $i--) {
$cardsWithMoreThan100Points = R::parents($cards[$i], ' @joined.detail.points > ? ', array(100));
asrt(count($cardsWithMoreThan100Points),3);
}
$cardsWithMoreThan200Points = R::parents($cards[8], ' @joined.detail.points > ? ', array(200));
asrt(count($cardsWithMoreThan200Points),3);
$cardsWithMoreThan200Points = R::parents($cards[7], ' @joined.detail.points > ? ', array(200));
asrt(count($cardsWithMoreThan200Points),3);
$cardsWithMoreThan200Points = R::parents($cards[6], ' @joined.detail.points > ? ', array(200));
asrt(count($cardsWithMoreThan200Points),3);
$cardsWithMoreThan200Points = R::parents($cards[5], ' @joined.detail.points > ? ', array(200));
asrt(count($cardsWithMoreThan200Points),2);
$cardsWithMoreThan200Points = R::parents($cards[4], ' @joined.detail.points > ? ', array(200));
asrt(count($cardsWithMoreThan200Points),2);
for($i=8; $i>=2; $i--) {
$cardsWithMoreThan300Points = R::parents($cards[4], ' @joined.detail.points > ? ', array(300));
asrt(count($cardsWithMoreThan200Points),2);
}
$black = R::children($cards[0], ' @shared.color.name = ? ', array('black'));
asrt(count($black),4);
$red = R::children($cards[0], ' @shared.color.name = ? ', array('red'));
asrt(count($red),5);
$black = R::parents($cards[8], ' @shared.color.name = ? ', array('black'));
asrt(count($black),2);
$red = R::parents($cards[6], ' @shared.color.name = ? ', array('red'));
asrt(count($red),2);
$found = R::children($cards[0], ' @own.owner.name = ? ', array('User0'));
asrt(count($found),1);
$found = R::children($cards[0], ' @own.owner.name IN (?,?) ', array('User1','User2'));
asrt(count($found),2);
$found = R::parents($cards[8], ' @own.owner.name IN (?,?) ', array('User3','User0'));
asrt(count($found),2);
$found = R::children($cards[8], ' @own.owner.name IN (?,?) ', array('User3','User0'));
asrt(count($found),0);
$found = R::children($cards[3], ' @own.owner.name IN (?,?) ', array('User3','User7'));
asrt(count($found),2);
}
/**
* Test CTE and Parsed Joins with aliases as well
* as chained parsed joins.
*
* @return void
*/
public function testCTETreesAndParsedJoinsAndAliases() {
R::nuke();
R::aliases(array( 'author' => 'person', 'chart' => 'image' ));
$person = R::dispense( 'person' );
$ceo = R::dispense('person');
$ceo->name = 'John';
$role = R::dispense('role');
$role->label = 'CEO';
$ceo->sharedRoleList = array( $role );
$person->name = 'James';
$editor = R::dispense('role');
$editor->label = 'editor';
$person->sharedRoleList = array( $editor );
$website = R::dispense('page');
$about = R::dispense('page');
$investor = R::dispense('page');
$links = R::dispense('page');
$category = R::dispense('category');
$category->title = 'business';
$about->sharedCategoryList = array( $category );
$blog = R::dispense('page');
$article = R::dispense('page');
$investor->title = 'investors';
$links->title = 'links';
$about->title = 'about';
$chart = R::dispense('image');
$chart->file = 'report.jpg';
$picture = R::dispense('image');
$picture->file = 'logo.jpg';
$picture->source = $website;
$website->ownImageList = array( $picture );
$article->ownImageList = array( $picture );
$investor->ownImageList = array( $picture );
$about->ownImageList = array( $chart );
$about->author = $ceo;
$about->coauthor = $person;
$article->title = 'a walk in the park';
$website->ownPageList = array( $about, $blog );
$blog->ownPageList = array( $article );
$article->ownPageList = array( $links );
$about->ownPageList = array( $investor );
$article->author = $person;
R::store( $website );
//joined-alias
$pages = R::children( $website, ' @joined.author.name = ? ', array('James') );
asrt(count($pages), 1);
$page = reset($pages);
asrt($page->title, 'a walk in the park');
//Chained joined-alias+shared
$pages = R::children( $website, ' @joined.author.shared.role.label = ? ', array('CEO') );
asrt(count($pages),1);
$page = reset($pages);
asrt($page->title,'about');
//joined-alias+shared parents
$pages = R::parents( $investor, ' @joined.author.shared.role.label = ? ', array('CEO') );
asrt(count($pages),1);
$page = reset($pages);
asrt($page->title,'about');
//joined-alias parents
$pages = R::parents( $links, ' @joined.author.name = ? ', array('James') );
asrt(count($pages), 1);
$page = reset($pages);
asrt($page->title, 'a walk in the park');
//own-alias
$pages = R::children( $website, '@own.chart.file = ?', array('report.jpg') );
asrt(count($pages),1);
$page = reset($pages);
asrt($page->title,'about');
//own-alias parent
$pages = R::parents( $investor, '@own.chart.file = ?', array('report.jpg') );
asrt(count($pages),1);
$page = reset($pages);
asrt($page->title,'about');
//own-alias parent + joined-alias+shared parents
$pages = R::parents( $investor, '@own.chart.file = ? AND @joined.author.shared.role.label = ? ', array('report.jpg', 'CEO') );
asrt(count($pages),1);
$page = reset($pages);
asrt($page->title,'about');
//own-alias + Chained joined-alias+shared
$pages = R::children( $website, ' @own.chart.file = ? AND @joined.author.shared.role.label = ? ', array('report.jpg', 'CEO') );
asrt(count($pages),1);
$page = reset($pages);
asrt($page->title,'about');
//own-alias + Chained joined-alias+shared + joined-alias
$pages = R::children( $website, ' @joined.author.name = ? AND @own.chart.file = ? AND @joined.author.shared.role.label = ? ', array('John', 'report.jpg', 'CEO') );
asrt(count($pages),1);
$page = reset($pages);
asrt($page->title,'about');
//own-alias + Chained joined-alias+shared + joined-alias parents
$pages = R::parents( $investor, ' @joined.author.name = ? AND @own.chart.file = ? AND @joined.author.shared.role.label = ? ', array('John', 'report.jpg', 'CEO') );
asrt(count($pages),1);
$page = reset($pages);
asrt($page->title,'about');
$pages = R::parents( $links, ' @joined.author.name = ? AND @own.chart.file = ? AND @joined.author.shared.role.label = ? ', array('James', 'logo.jpg', 'editor') );
asrt(count($pages),1);
$page = reset($pages);
asrt($page->title,'a walk in the park');
$pages = R::parents( $links, ' @joined.author.name = ? AND @own.chart.file = ? AND @joined.author.shared.role.label = ? ', array('James', 'report.jpg', 'editor') );
asrt(count($pages),0);
//other variations
$pages = R::parents( $investor, ' @shared.category.title = ? ', array('business') );
asrt(count($pages),1);
$page = reset($pages);
asrt($page->title,'about');
R::aliases(array());
//joined-alias - explicit/non-global
$pages = R::children( $website, ' @joined.person[as:author].name = ? ', array('James') );
asrt(count($pages), 1);
$page = reset($pages);
asrt($page->title, 'a walk in the park');
//Chained joined-alias+shared - explicit/non-global
$pages = R::children( $website, ' @joined.person[as:author].shared.role.label = ? ', array('CEO') );
asrt(count($pages),1);
$page = reset($pages);
asrt($page->title,'about');
//joined-alias+shared parents - explicit/non-global
$pages = R::parents( $investor, ' @joined.person[as:author].shared.role.label = ? ', array('CEO') );
asrt(count($pages),1);
$page = reset($pages);
asrt($page->title,'about');
//joined-alias parents - explicit/non-global
$pages = R::parents( $links, ' @joined.person[as:author].name = ? ', array('James') );
asrt(count($pages), 1);
$page = reset($pages);
asrt($page->title, 'a walk in the park');
//double joined-alias parents - explicit/non-global
$pages = R::children( $website, ' @joined.person[as:author/coauthor].name = ? ', array('James') );
asrt(count($pages), 2);
//own-alias - explicit/non-global
$pages = R::children( $website, ' @own.image[alias:source].file = ?', array('logo.jpg') );
asrt(count($pages),1);
$pages = R::children( $website, ' @own.image[alias:source].file = ?', array('report.jpg') );
asrt(count($pages),0);
$pages = R::children( $about, ' @own.image[alias:source].file = ?', array('logo.jpg') );
asrt(count($pages),0);
}
}

View File

@@ -0,0 +1,278 @@
<?php
namespace RedUNIT\Postgres;
use RedUNIT\Postgres as Postgres;
use RedBeanPHP\Facade as R;
use RedBeanPHP\BeanHelper\SimpleFacadeBeanHelper as SimpleFacadeBeanHelper;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\ToolBox as ToolBox;
/**
* UUID
*
* Tests whether we can use UUIDs with PostgreSQL, to this
* end we use a reference implementation of a UUID MySQL Writer:
* UUIDWriterPostgres, however this class is not part of the code base,
* it should be considered a reference or example implementation.
* These tests focus on whether UUIDs in general do not cause any
* unexpected issues.
*
* @file RedUNIT/Postgres/Uuid.php
* @desc Tests read support for UUID tables.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Uuid extends Postgres
{
/**
* Test Read-support.
*
* @return void
*/
public function testUUIDReadSupport()
{
R::nuke();
$createPageTableSQL = '
CREATE TABLE
page
(
id UUID PRIMARY KEY,
book_id UUID,
magazine_id UUID,
title VARCHAR(255)
)';
$createBookTableSQL = '
CREATE TABLE
book
(
id UUID PRIMARY KEY,
title VARCHAR(255)
)';
$createPagePageTableSQL = '
CREATE TABLE
page_page
(
id UUID PRIMARY KEY,
page_id UUID,
page2_id UUID
)';
R::exec( $createBookTableSQL );
R::exec( $createPageTableSQL );
R::exec( $createPagePageTableSQL );
//insert some records
$book1ID = '6ccd780c-baba-1026-9564-0040f4311e21';
$book2ID = '6ccd780c-baba-1026-9564-0040f4311e22';
$page1ID = '6ccd780c-baba-1026-9564-0040f4311e23';
$page2ID = '6ccd780c-baba-1026-9564-0040f4311e24';
$page3ID = '6ccd780c-baba-1026-9564-0040f4311e25';
$pagePage1ID = '6ccd780c-baba-1026-9564-0040f4311e26';
$insertBook1SQL = "
INSERT INTO book (id, title) VALUES( '$book1ID', 'book 1' );
";
$insertBook2SQL = "
INSERT INTO book (id, title) VALUES( '$book2ID', 'book 2' );
";
$insertPage1SQL = "
INSERT INTO page (id, book_id, title, magazine_id) VALUES( '$page1ID', '$book1ID', 'page 1 of book 1', '$book2ID' );
";
$insertPage2SQL = "
INSERT INTO page (id, book_id, title) VALUES( '$page2ID', '$book1ID', 'page 2 of book 1' );
";
$insertPage3SQL = "
INSERT INTO page (id, book_id, title) VALUES( '$page3ID', '$book2ID', 'page 1 of book 2' );
";
$insertPagePage1SQL = "
INSERT INTO page_page (id, page_id, page2_id) VALUES( '$pagePage1ID', '$page2ID', '$page3ID' );
";
R::exec( $insertBook1SQL );
R::exec( $insertBook2SQL );
R::exec( $insertPage1SQL );
R::exec( $insertPage2SQL );
R::exec( $insertPage3SQL );
R::exec( $insertPagePage1SQL );
//basic tour of basic functions....
$book1 = R::load( 'book', $book1ID );
asrt( $book1->id, $book1ID );
asrt( $book1->title, 'book 1' );
$book2 = R::load( 'book', $book2ID );
asrt( $book2->id, $book2ID );
asrt( $book2->title, 'book 2' );
asrt( count( $book1->ownPage ), 2 );
asrt( count( $book1->fresh()->with( 'LIMIT 1' )->ownPage ), 1 );
asrt( count( $book1->fresh()->withCondition( ' title = ? ', array('page 2 of book 1'))->ownPage ), 1 );
asrt( count($book2->ownPage), 1 );
asrt( $book2->fresh()->countOwn( 'page' ), 1 );
$page1 = R::load( 'page', $page1ID );
asrt( count( $page1->sharedPage ), 0 );
asrt( $page1->fetchAs( 'book' )->magazine->id, $book2ID );
$page2 = R::load( 'page', $page2ID );
asrt( count($page2->sharedPage), 1 );
asrt( $page2->fresh()->countShared( 'page' ), 1 );
$page3 = R::findOne( 'page', ' title = ? ', array( 'page 1 of book 2' ) );
asrt( $page3->id, $page3ID );
asrt( $page3->book->id, $book2ID );
}
/**
* Test Full fluid UUID support.
*
*/
public function testFullSupport()
{
//Rewire objects to support UUIDs.
$oldToolBox = R::getToolBox();
$oldAdapter = $oldToolBox->getDatabaseAdapter();
$uuidWriter = new \UUIDWriterPostgres( $oldAdapter );
$newRedBean = new OODB( $uuidWriter );
$newToolBox = new ToolBox( $newRedBean, $oldAdapter, $uuidWriter );
R::configureFacadeWithToolbox( $newToolBox );
list( $mansion, $rooms, $ghosts, $key ) = R::dispenseAll( 'mansion,room*3,ghost*4,key' );
$mansion->name = 'Haunted Mansion';
$mansion->xownRoomList = $rooms;
$rooms[0]->name = 'Green Room';
$rooms[1]->name = 'Red Room';
$rooms[2]->name = 'Blue Room';
$ghosts[0]->name = 'zero';
$ghosts[1]->name = 'one';
$ghosts[2]->name = 'two';
$ghosts[3]->name = 'three';
$rooms[0]->noLoad()->sharedGhostList = array( $ghosts[0], $ghosts[1] );
$rooms[1]->noLoad()->sharedGhostList = array( $ghosts[0], $ghosts[2] );
$rooms[2]->noLoad()->sharedGhostList = array( $ghosts[1], $ghosts[3], $ghosts[2] );
$rooms[2]->xownKey = array( $key );
//Can we store a bean hierachy with UUIDs?
$id = R::store( $mansion );
asrt( is_string( $id ), TRUE );
asrt( strlen( $id ), 36 );
$haunted = R::load( 'mansion', $id );
asrt( $haunted->name, 'Haunted Mansion' );
asrt( is_string( $haunted->id ), TRUE );
asrt( strlen( $haunted->id ), 36 );
asrt( is_array( $haunted->xownRoomList ), TRUE );
asrt( count( $haunted->ownRoom ), 3 );
$rooms = $haunted->xownRoomList;
//Do some counting...
$greenRoom = NULL;
foreach( $rooms as $room ) {
if ( $room->name === 'Green Room' ) {
$greenRoom = $room;
break;
}
}
asrt( !is_null( $greenRoom ), TRUE );
asrt( is_array( $greenRoom->with(' ORDER BY id ')->sharedGhostList ), TRUE );
asrt( count( $greenRoom->sharedGhostList ), 2 );
$names = array();
foreach( $greenRoom->sharedGhost as $ghost ) $names[] = $ghost->name;
sort($names);
$names = implode(',', $names);
asrt($names, 'one,zero');
$rooms = $haunted->xownRoomList;
$blueRoom = NULL;
foreach( $rooms as $room ) {
if ( $room->name === 'Blue Room' ) {
$blueRoom = $room;
break;
}
}
asrt( !is_null( $blueRoom ), TRUE );
asrt( is_array( $blueRoom->sharedGhostList ), TRUE );
asrt( count( $blueRoom->sharedGhostList ), 3 );
$names = array();
foreach( $blueRoom->sharedGhost as $ghost ) $names[] = $ghost->name;
sort($names);
$names = implode(',', $names);
asrt($names, 'one,three,two');
$rooms = $haunted->xownRoomList;
$redRoom = NULL;
foreach( $rooms as $room ) {
if ( $room->name === 'Red Room' ) {
$redRoom = $room; break;
}
}
$names = array();
foreach( $redRoom->sharedGhost as $ghost ) $names[] = $ghost->name;
sort($names);
$names = implode(',', $names);
asrt($names, 'two,zero');
asrt( !is_null( $redRoom ), TRUE );
asrt( is_array( $redRoom->sharedGhostList ), TRUE );
asrt( count( $redRoom->sharedGhostList ), 2 );
//Can we repaint a room?
$redRoom->name = 'Yellow Room';
$id = R::store($redRoom);
$yellowRoom = R::load( 'room', $id );
asrt( $yellowRoom->name, 'Yellow Room');
asrt( !is_null( $yellowRoom ), TRUE );
asrt( is_array( $yellowRoom->sharedGhostList ), TRUE );
asrt( count( $yellowRoom->sharedGhostList ), 2 );
//Can we throw one ghost out?
array_pop( $yellowRoom->sharedGhost );
R::store( $yellowRoom );
$yellowRoom = $yellowRoom->fresh();
asrt( $yellowRoom->name, 'Yellow Room');
asrt( !is_null( $yellowRoom ), TRUE );
asrt( is_array( $yellowRoom->sharedGhostList ), TRUE );
asrt( count( $yellowRoom->sharedGhostList ), 1 );
//can we remove one of the rooms?
asrt( R::count('key'), 1);
$list = $mansion->withCondition(' "name" = ? ', array('Blue Room'))->xownRoomList;
$room = reset($list);
unset($mansion->xownRoomList[$room->id]);
R::store($mansion);
asrt(R::count('room'), 2);
//and what about its dependent beans?
asrt(R::count('key'), 0);
asrt(R::count('ghost_room'), 3);
//and can we find ghosts?
$ghosts = R::find('ghost');
asrt(count($ghosts), 4);
$ghosts = R::findAll('ghost', 'ORDER BY id');
asrt(count($ghosts), 4);
$ghosts = R::findAll('ghost', 'ORDER BY id LIMIT 2');
asrt(count($ghosts), 2);
$ghostZero = R::findOne('ghost', ' "name" = ? ', array( 'zero' ) );
asrt( ($ghostZero instanceof OODBBean), TRUE );
//can we create link properties on existing tables?
$blackRoom = R::dispense( 'room' );
$blackRoom->name = 'Black Room';
$ghostZero->link('ghost_room', array('mood'=>'grumpy'))->room = $blackRoom;
R::store($ghostZero);
$ghostZero = $ghostZero->fresh();
$list = $ghostZero->sharedRoomList;
asrt(count($list), 3);
$ghostZero = $ghostZero->fresh();
$list = $ghostZero->withCondition(' ghost_room.mood = ? ', array('grumpy'))->sharedRoomList;
asrt(count($list), 1);
//can we load a batch?
$ids = R::getCol('SELECT id FROM ghost');
$ghosts = R::batch('ghost', $ids);
asrt(count($ghosts), 4);
//can we do an aggregation?
$ghosts = $greenRoom->aggr('ownGhostRoom', 'ghost', 'ghost');
asrt(count($ghosts), 2);
//can we duplicate the mansion?
asrt(R::count('mansion'), 1);
asrt(R::count('room'), 3);
asrt(R::count('ghost'), 4);
$copy = R::dup($mansion);
R::store($copy);
asrt(R::count('mansion'), 2);
asrt(R::count('room'), 5); //black room does not belong to mansion 1
asrt(R::count('ghost'), 4);
//can we do some counting using the list?
asrt( $copy->countOwn('room'), 2);
$rooms = $copy->withCondition(' "name" = ? ', array('Green Room'))->xownRoomList;
$room = reset($rooms);
asrt($room->countShared('ghost'), 2);
//Finally restore old toolbox
R::configureFacadeWithToolbox( $oldToolBox );
}
}

View File

@@ -0,0 +1,710 @@
<?php
namespace RedUNIT\Postgres;
use RedUNIT\Postgres as Postgres;
use RedBeanPHP\Facade as R;
use RedBeanPHP\AssociationManager as AssociationManager;
use RedBeanPHP\QueryWriter\PostgreSQL as PostgreSQL;
use RedBeanPHP\RedException\SQL as SQL;
use RedBeanPHP\RedException as RedException;
/**
* Writer
*
* Tests for PostgreSQL Query Writer.
* This test class contains Query Writer specific tests.
* Use this class to add tests to test Query Writer specific
* behaviours, quirks and issues.
*
* @file RedUNIT/Postgres/Writer.php
* @desc A collection of writer specific tests.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Writer extends Postgres
{
/**
* Test whether optimizations do not have effect on Writer query outcomes.
*
* @return void
*/
public function testWriterSpeedUp()
{
R::nuke();
$id = R::store( R::dispense( 'book' ) );
$writer = R::getWriter();
$count1 = $writer->queryRecordCount( 'book', array( 'id' => $id ), ' id = :id ', array( ':id' => $id ) );
$count2 = $writer->queryRecordCount( 'book', array( ), ' id = :id ', array( ':id' => $id ) );
$count3 = $writer->queryRecordCount( 'book', NULL, ' id = :id ', array( ':id' => $id ) );
$count4 = $writer->queryRecordCount( 'book', array( 'id' => $id ) );
asrt( $count1, $count2 );
asrt( $count2, $count3 );
asrt( $count3, $count4 );
R::nuke();
$books = R::dispenseAll( 'book*4' );
$ids = R::storeAll( $books[0] );
$writer->deleteRecord( 'book', array( 'id' => $ids[0] ) );
$writer->deleteRecord( 'book', array( 'id' => $ids[1] ), ' id = :id ', array( ':id' => $ids[1] ) );
$writer->deleteRecord( 'book', NULL, ' id = :id ', array( ':id' => $ids[2] ) );
$writer->deleteRecord( 'book', array(), ' id = :id ', array( ':id' => $ids[3] ) );
asrt( R::count( 'book' ), 0 );
R::nuke();
$id = R::store( R::dispense( 'book' ) );
$record = $writer->queryRecord( 'book', array( 'id' => $id ) );
asrt( is_array( $record ), TRUE );
asrt( is_array( $record[0] ), TRUE );
asrt( isset( $record[0]['id'] ), TRUE );
asrt( (int) $record[0]['id'], $id );
$record = $writer->queryRecord( 'book', array( 'id' => $id ), ' id = :id ', array( ':id' => $id ) );
asrt( is_array( $record ), TRUE );
asrt( is_array( $record[0] ), TRUE );
asrt( isset( $record[0]['id'] ), TRUE );
asrt( (int) $record[0]['id'], $id );
$record = $writer->queryRecord( 'book', NULL, ' id = :id ', array( ':id' => $id ) );
asrt( is_array( $record ), TRUE );
asrt( is_array( $record[0] ), TRUE );
asrt( isset( $record[0]['id'] ), TRUE );
asrt( (int) $record[0]['id'], $id );
$record = $writer->queryRecord( 'book', array(), ' id = :id ', array( ':id' => $id ) );
asrt( is_array( $record ), TRUE );
asrt( is_array( $record[0] ), TRUE );
asrt( isset( $record[0]['id'] ), TRUE );
asrt( (int) $record[0]['id'], $id );
}
/**
* Tests wheter we can write a deletion query
* for PostgreSQL using NO conditions but only an
* additional SQL snippet.
*
* @return void
*/
public function testWriteDeleteQuery()
{
$queryWriter = R::getWriter();
asrt( ( $queryWriter instanceof PostgreSQL ), TRUE );
R::nuke();
$bean = R::dispense( 'bean' );
$bean->name = 'a';
$id = R::store( $bean );
asrt( R::count( 'bean' ), 1 );
$queryWriter->deleteRecord( 'bean', array(), $addSql = ' id = :id ', $bindings = array( ':id' => $id ) );
asrt( R::count( 'bean' ), 0 );
}
/**
* Tests wheter we can write a counting query
* for PostgreSQL using conditions and an additional SQL snippet.
*
* @return void
*/
public function testWriteCountQuery()
{
$queryWriter = R::getWriter();
asrt( ( $queryWriter instanceof PostgreSQL ), TRUE );
R::nuke();
$bean = R::dispense( 'bean' );
$bean->name = 'a';
R::store( $bean );
$bean = R::dispense( 'bean' );
$bean->name = 'b';
R::store( $bean );
$bean = R::dispense( 'bean' );
$bean->name = 'b';
R::store( $bean );
$count = $queryWriter->queryRecordCount( 'bean', array( 'name' => 'b' ), $addSql = ' id > :id ', $bindings = array( ':id' => 0 ) );
asrt( $count, 2 );
}
/**
* Tests whether we can write a PostgreSQL join and
* whether the correct exception is thrown in case
* of an invalid join.
*
* @return void
*/
public function testWriteJoinSnippets()
{
$queryWriter = R::getWriter();
asrt( ( $queryWriter instanceof PostgreSQL ), TRUE );
$snippet = $queryWriter->writeJoin( 'book', 'page' ); //default must be LEFT
asrt( is_string( $snippet ), TRUE );
asrt( ( strlen( $snippet ) > 0 ), TRUE );
asrt( ' LEFT JOIN "page" ON "page".id = "book".page_id ', $snippet );
$snippet = $queryWriter->writeJoin( 'book', 'page', 'LEFT' );
asrt( is_string( $snippet ), TRUE );
asrt( ( strlen( $snippet ) > 0 ), TRUE );
asrt( ' LEFT JOIN "page" ON "page".id = "book".page_id ', $snippet );
$snippet = $queryWriter->writeJoin( 'book', 'page', 'RIGHT' );
asrt( is_string( $snippet ), TRUE );
asrt( ( strlen( $snippet ) > 0 ), TRUE );
asrt( ' RIGHT JOIN "page" ON "page".id = "book".page_id ', $snippet );
$snippet = $queryWriter->writeJoin( 'book', 'page', 'INNER' );
asrt( ' INNER JOIN "page" ON "page".id = "book".page_id ', $snippet );
$exception = NULL;
try {
$snippet = $queryWriter->writeJoin( 'book', 'page', 'MIDDLE' );
}
catch(\Exception $e) {
$exception = $e;
}
asrt( ( $exception instanceof RedException ), TRUE );
$errorMessage = $exception->getMessage();
asrt( is_string( $errorMessage ), TRUE );
asrt( ( strlen( $errorMessage ) > 0 ), TRUE );
asrt( $errorMessage, 'Invalid JOIN.' );
}
/**
* Test whether we can store JSON as a JSON column
* and whether this plays well with the other data types.
*/
public function testSetGetJSON()
{
global $travis;
if ($travis) return;
R::nuke();
$bean = R::dispense('bean');
$message = json_encode( array( 'message' => 'hello', 'type' => 'greeting' ) ) ;
$bean->data = $message;
R::store( $bean );
$columns = R::inspect('bean');
asrt( array_key_exists( 'data', $columns ), TRUE );
asrt( ( $columns['data'] !== 'json' ), TRUE );
R::useJSONFeatures( TRUE );
R::nuke();
$bean = R::dispense('bean');
$message = array( 'message' => 'hello', 'type' => 'greeting' );
$bean->data = $message;
R::store( $bean );
$columns = R::inspect('bean');
asrt( array_key_exists( 'data', $columns ), TRUE );
asrt( $columns['data'], 'json' );
$bean = $bean->fresh();
$message = json_decode( $bean->data, TRUE );
asrt( $message['message'], 'hello' );
asrt( $message['type'], 'greeting' );
$message['message'] = 'hi';
$bean->data = $message;
R::store( $bean );
pass();
$bean = R::findOne( 'bean' );
$message = json_decode( $bean->data );
asrt( $message->message, 'hi' );
$book = R::dispense( 'book' );
$book->page = 'lorem ipsum';
R::store( $book );
$book = $book->fresh();
asrt( $book->page, 'lorem ipsum' );
$book2 = R::dispense( 'book' );
$book2->page = array( 'chapter' => '1' );
R::store( $book2 );
pass(); //should not try to modify column and trigger exception
$book = $book->fresh();
asrt( $book->page, 'lorem ipsum' );
$columns = R::inspect('book');
asrt( ( $columns['page'] !== 'json' ), TRUE );
$building = R::dispense( 'building' );
$building->year = 'MLXXVIII';
R::store( $building );
$shop = R::dispense( 'building' );
$shop->year = '2010-01-01';
R::store( $shop );
$building = R::load( 'building', $building->id );
asrt( $building->year, 'MLXXVIII' );
$columns = R::inspect( 'building' );
asrt( strpos( strtolower( $columns['year'] ), 'date' ), FALSE );
$shop->anno = '2010-01-01';
R::store( $shop );
$columns = R::inspect( 'building' );
asrt( $columns['anno'], 'date' );
R::useJSONFeatures( FALSE );
}
/**
* Test scanning and coding.
*
* @return void
*/
public function testScanningAndCoding()
{
$toolbox = R::getToolBox();
$adapter = $toolbox->getDatabaseAdapter();
$writer = $toolbox->getWriter();
$redbean = $toolbox->getRedBean();
$pdo = $adapter->getDatabase();
$a = new AssociationManager( $toolbox );
$adapter->exec( "DROP TABLE IF EXISTS testtable" );
asrt( in_array( "testtable", $writer->getTables() ), FALSE );
$writer->createTable( "testtable" );
asrt( in_array( "testtable", $writer->getTables() ), TRUE );
asrt( count( array_keys( $writer->getColumns( "testtable" ) ) ), 1 );
asrt( in_array( "id", array_keys( $writer->getColumns( "testtable" ) ) ), TRUE );
asrt( in_array( "c1", array_keys( $writer->getColumns( "testtable" ) ) ), FALSE );
$writer->addColumn( "testtable", "c1", 1 );
asrt( count( array_keys( $writer->getColumns( "testtable" ) ) ), 2 );
asrt( in_array( "c1", array_keys( $writer->getColumns( "testtable" ) ) ), TRUE );
foreach ( $writer->sqltype_typeno as $key => $type ) {
if ( $type < 100 ) {
asrt( $writer->code( $key, TRUE ), $type );
} else {
asrt( $writer->code( $key ), PostgreSQL::C_DATATYPE_SPECIFIED );
}
}
asrt( $writer->code( PostgreSQL::C_DATATYPE_SPECIAL_DATETIME ), PostgreSQL::C_DATATYPE_SPECIFIED );
asrt( $writer->code( "unknown" ), PostgreSQL::C_DATATYPE_SPECIFIED );
asrt( $writer->scanType( FALSE ), PostgreSQL::C_DATATYPE_INTEGER );
asrt( $writer->scanType( TRUE ), PostgreSQL::C_DATATYPE_INTEGER );
asrt( $writer->scanType( NULL ), PostgreSQL::C_DATATYPE_INTEGER );
asrt( $writer->scanType( 2 ), PostgreSQL::C_DATATYPE_INTEGER );
asrt( $writer->scanType( 255 ), PostgreSQL::C_DATATYPE_INTEGER );
asrt( $writer->scanType( 256 ), PostgreSQL::C_DATATYPE_INTEGER );
asrt( $writer->scanType( -1 ), PostgreSQL::C_DATATYPE_INTEGER );
asrt( $writer->scanType( 1.5 ), PostgreSQL::C_DATATYPE_DOUBLE );
asrt( $writer->scanType( INF ), PostgreSQL::C_DATATYPE_TEXT );
asrt( $writer->scanType( "abc" ), PostgreSQL::C_DATATYPE_TEXT );
asrt( $writer->scanType( "2001-10-10", TRUE ), PostgreSQL::C_DATATYPE_SPECIAL_DATE );
asrt( $writer->scanType( "2001-10-10 10:00:00", TRUE ), PostgreSQL::C_DATATYPE_SPECIAL_DATETIME );
asrt( $writer->scanType( "2001-10-10 10:00:00" ), PostgreSQL::C_DATATYPE_TEXT );
asrt( $writer->scanType( "2001-10-10" ), PostgreSQL::C_DATATYPE_TEXT );
asrt( $writer->scanType( str_repeat( "lorem ipsum", 100 ) ), PostgreSQL::C_DATATYPE_TEXT );
$writer->widenColumn( "testtable", "c1", PostgreSQL::C_DATATYPE_TEXT );
$cols = $writer->getColumns( "testtable" );
asrt( $writer->code( $cols["c1"] ), PostgreSQL::C_DATATYPE_TEXT );
$writer->addColumn( "testtable", "special", PostgreSQL::C_DATATYPE_SPECIAL_DATE );
$cols = $writer->getColumns( "testtable" );
asrt( $writer->code( $cols['special'], TRUE ), PostgreSQL::C_DATATYPE_SPECIAL_DATE );
asrt( $writer->code( $cols['special'], FALSE ), PostgreSQL::C_DATATYPE_SPECIFIED );
$writer->addColumn( "testtable", "special2", PostgreSQL::C_DATATYPE_SPECIAL_DATETIME );
$cols = $writer->getColumns( "testtable" );
asrt( $writer->code( $cols['special2'], TRUE ), PostgreSQL::C_DATATYPE_SPECIAL_DATETIME );
asrt( $writer->code( $cols['special'], FALSE ), PostgreSQL::C_DATATYPE_SPECIFIED );
$id = $writer->updateRecord( "testtable", array( array( "property" => "c1", "value" => "lorem ipsum" ) ) );
$row = $writer->queryRecord( "testtable", array( "id" => array( $id ) ) );
asrt( $row[0]["c1"], "lorem ipsum" );
$writer->updateRecord( "testtable", array( array( "property" => "c1", "value" => "ipsum lorem" ) ), $id );
$row = $writer->queryRecord( "testtable", array( "id" => array( $id ) ) );
asrt( $row[0]["c1"], "ipsum lorem" );
$writer->deleteRecord( "testtable", array( "id" => array( $id ) ) );
$row = $writer->queryRecord( "testtable", array( "id" => array( $id ) ) );
asrt( empty( $row ), TRUE );
}
/**
* (FALSE should be stored as 0 not as '')
*
* @return void
*/
public function testZeroIssue()
{
testpack( "Zero issue" );
$toolbox = R::getToolBox();
$redbean = $toolbox->getRedBean();
$bean = $redbean->dispense( "zero" );
$bean->zero = FALSE;
$bean->title = "bla";
$redbean->store( $bean );
asrt( count( $redbean->find( "zero", array(), " zero = 0 " ) ), 1 );
testpack( "Test ANSI92 issue in clearrelations" );
$a = new AssociationManager( $toolbox );
$book = $redbean->dispense( "book" );
$author1 = $redbean->dispense( "author" );
$author2 = $redbean->dispense( "author" );
$book->title = "My First Post";
$author1->name = "Derek";
$author2->name = "Whoever";
set1toNAssoc( $a, $book, $author1 );
set1toNAssoc( $a, $book, $author2 );
pass();
}
/**
* Various.
* Tests whether writer correctly handles keyword 'group' and SQL state 23000 issue.
* These tests remain here to make sure issues 9 and 10 never happen again.
* However this bug will probably never re-appear due to changed architecture.
*
* @return void
*/
public function testIssue9and10()
{
$toolbox = R::getToolBox();
$redbean = $toolbox->getRedBean();
$adapter = $toolbox->getDatabaseAdapter();
$a = new AssociationManager( $toolbox );
$book = $redbean->dispense( "book" );
$author1 = $redbean->dispense( "author" );
$author2 = $redbean->dispense( "author" );
$book->title = "My First Post";
$author1->name = "Derek";
$author2->name = "Whoever";
$a->associate( $book, $author1 );
$a->associate( $book, $author2 );
pass();
testpack( "Test Association Issue Group keyword (Issues 9 and 10)" );
R::nuke();
$group = $redbean->dispense( "group" );
$group->name = "mygroup";
$redbean->store( $group );
try {
$a->associate( $group, $book );
pass();
} catch ( SQL $e ) {
fail();
}
// Test issue SQL error 23000
try {
$a->associate( $group, $book );
pass();
} catch ( SQL $e ) {
fail();
}
asrt( (int) $adapter->getCell( "select count(*) from book_group" ), 1 ); //just 1 rec!
}
/**
* Test various.
* Test various somewhat uncommon trash/unassociate scenarios.
* (i.e. unassociate unrelated beans, trash non-persistant beans etc).
* Should be handled gracefully - no output checking.
*
* @return void
*/
public function testVaria()
{
$toolbox = R::getToolBox();
$redbean = $toolbox->getRedBean();
$a = new AssociationManager( $toolbox );
$book = $redbean->dispense( "book" );
$author1 = $redbean->dispense( "author" );
$author2 = $redbean->dispense( "author" );
$book->title = "My First Post";
$author1->name = "Derek";
$author2->name = "Whoever";
$a->unassociate( $book, $author1 );
$a->unassociate( $book, $author2 );
pass();
$redbean->trash( $redbean->dispense( "bla" ) );
pass();
$bean = $redbean->dispense( "bla" );
$bean->name = 1;
$bean->id = 2;
$redbean->trash( $bean );
pass();
}
/**
* Test special types.
*
* @return void
*/
public function testTypes()
{
testpack( 'Special data types' );
$bean = R::dispense( 'bean' );
$bean->date = 'someday';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['date'], 'text' );
$bean = R::dispense( 'bean' );
$bean->date = '2011-10-10';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['date'], 'text' );
}
/**
* Test dates.
*
* @return void
*/
public function testTypesDates()
{
$bean = R::dispense( 'bean' );
$bean->date = '2011-10-10';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['date'], 'date' );
}
/**
* Datetime.
*
* @return void
*/
public function testTypesDateTimes()
{
$bean = R::dispense( 'bean' );
$bean->date = '2011-10-10 10:00:00';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['date'], 'timestamp without time zone' );
}
/**
* Test spatial data types.
*
* @return void
*/
public function testTypesPoints()
{
$bean = R::dispense( 'bean' );
$bean->point = '(92,12)';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['point'], 'point' );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->point, '(92,12)' );
$bean->note = 'taint';
R::store( $bean );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->point, '(92,12)' );
}
/**
* Test points.
*
* @return void
*/
public function testTypesDecPoints()
{
$bean = R::dispense( 'bean' );
$bean->point = '(9.2,1.2)';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['point'], 'point' );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->point, '(9.2,1.2)' );
$bean->note = 'taint';
R::store( $bean );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->point, '(9.2,1.2)' );
}
/**
* Test polygons.
*
* @return void
*/
public function testPolygons()
{
$bean = R::dispense( 'bean' );
$bean->polygon = '((0,0),(1,1),(2,0))';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['polygon'], 'polygon' );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->polygon, '((0,0),(1,1),(2,0))' );
$bean->note = 'taint';
R::store( $bean );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->polygon, '((0,0),(1,1),(2,0))' );
$bean = R::dispense( 'bean' );
$bean->polygon = '((0,0),(1.2,1),(2,0.3))';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['polygon'], 'polygon' );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->polygon, '((0,0),(1.2,1),(2,0.3))' );
$bean->note = 'taint';
R::store( $bean );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->polygon, '((0,0),(1.2,1),(2,0.3))' );
}
/**
* Test multi points.
*
* @return void
*/
public function testTypesMultiDecPoints()
{
$bean = R::dispense( 'bean' );
$bean->line = '[(1.2,1.4),(2.2,34)]';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['line'], 'lseg' );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->line, '[(1.2,1.4),(2.2,34)]' );
$bean->note = 'taint';
R::store( $bean );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->line, '[(1.2,1.4),(2.2,34)]' );
}
/**
* More points...
*
* @return void
*/
public function testTypesWeirdPoints()
{
$bean = R::dispense( 'bean' );
$bean->circle = '<(9.2,1.2),7.9>';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['circle'], 'circle' );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->circle, '<(9.2,1.2),7.9>' );
$bean->note = 'taint';
R::store( $bean );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->circle, '<(9.2,1.2),7.9>' );
}
/**
* Test money types.
*
* @return void
*/
public function testTypesMon()
{
$bean = R::dispense( 'bean' );
$bean->amount = '22.99';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['amount'], 'numeric' );
R::nuke();
$bean = R::dispense( 'bean' );
$bean->amount = '-22.99';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['amount'], 'numeric' );
}
/**
* Test money data type.
*
* @return void
*/
public function testTypesMoney()
{
$bean = R::dispense( 'bean' );
$bean->money = '$123.45';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['money'], 'money' );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->money, '$123.45' );
$bean->note = 'taint';
R::store( $bean );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->money, '$123.45' );
$bean->money = '$123,455.01';
R::store($bean);
$bean = $bean->fresh();
asrt( $bean->money, '$123,455.01' );
}
/**
* Test negative money data type.
*
* @return void
*/
public function testTypesNegativeMoney()
{
$bean = R::dispense( 'bean' );
$bean->money = '-$123.45';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['money'], 'money' );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->money, '-$123.45' );
$bean->note = 'taint';
R::store( $bean );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->money, '-$123.45' );
}
/**
* Issue #340
* Redbean is currently picking up bcrypt hashed passwords
* (which look like this: $2y$12$85lAS....SnpDNVGPAC7w0G)
* as PostgreSQL money types.
* Then, once R::store is called on the bean, it chokes and throws the following error:
* PHP Fatal error: Uncaught [22P02] - SQLSTATE[22P02]: Invalid text representation: 7 ERROR:
* invalid input syntax for type money: ....
*
* @return void
*/
public function testTypesInvalidMoney()
{
$bean = R::dispense( 'bean' );
$bean->nomoney = '$2y$12$85lAS';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['nomoney'], 'text' );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->nomoney, '$2y$12$85lAS' );
$bean->note = 'taint';
R::store( $bean );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->nomoney, '$2y$12$85lAS' );
}
/**
* Test types of strings.
*
* @return void
*/
public function testTypesStrings()
{
$bean = R::dispense( 'bean' );
$bean->data = 'abcdefghijk';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['data'], 'text' );
$bean = R::load( 'bean', $bean->id );
asrt( $bean->data, 'abcdefghijk' );
$bean->data = '(1,2)';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['data'], 'text' );
$bean->data = '[(1.2,1.4),(2.2,34)]';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['data'], 'text' );
$bean->data = '<(9.2,1.2),7.9>';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['data'], 'text' );
$bean->data = '$25';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['data'], 'text' );
$bean->data = '2012-10-10 10:00:00';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['data'], 'text' );
}
/**
* Can we manually add a Postgres time column without a time zone
* and with a time zone?
*
* @return void
*/
public function testTime()
{
R::nuke();
$clock = R::dispense('clock');
$clock->time = '10:00:00';
$clock->setMeta('cast.time', 'time');
R::store( $clock );
$columns = R::inspect('clock');
asrt( $columns['time'], 'time without time zone' );
R::nuke();
$clock = R::dispense('clock');
$clock->time = '10:00:00 PST';
$clock->setMeta('cast.time', 'time with time zone');
R::store( $clock );
$columns = R::inspect('clock');
asrt( $columns['time'], 'time with time zone' );
}
}

View File

@@ -0,0 +1,32 @@
<?php
/**
* Pretest
*
* These tests will run before the configuration takes place
* in the unit test suite (mostly error handling tests).
*
* @file RedUNIT/Pretest.php
* @desc Test scripts that run before configuration
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
testpack('Running pre-tests. (before config).');
try {
R::debug( TRUE );
fail();
} catch( Exception $e ) {
pass();
}
asrt( R::testConnection(), FALSE);
R::addDatabase( 'broken', 'mysql:host=nowhere', 'defunct', 'void' );
R::selectDatabase( 'broken' );
asrt( R::testConnection(), FALSE );

View File

@@ -0,0 +1,34 @@
<?php
namespace RedUNIT;
/**
* Sqlite
*
* The Sqlite class is the parent class of all SQLite3 specific test
* classes.
*
* @file RedUNIT/Sqlite.php
* @desc Base class for all SQLite specific tests.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Sqlite extends RedUNIT
{
/**
* Returns a list of drivers for which this driver supports
* 'test rounds'. This class only supports the SQLite3 driver.
*
* @see RedUNIT::getTargetDrivers() for details.
*
* @return array
*/
public function getTargetDrivers()
{
return array( 'sqlite' );
}
}

View File

@@ -0,0 +1,197 @@
<?php
namespace RedUNIT\Sqlite;
use RedUNIT\Sqlite as Sqlite;
use RedBeanPHP\Facade as R;
/**
* Foreignkeys
*
* Tests creation and validity of foreign keys,
* foreign key constraints and indexes in SQLite.
* Also tests whether the correct contraint action has been selected.
*
* @file RedUNIT/Sqlite/Foreignkeys.php
* @desc Tests the creation of foreign keys.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Foreignkeys extends Sqlite
{
/**
* addIndex should not trigger exception...
*
* @return void
*/
public function testIndexException()
{
R::nuke();
$book = R::dispense( 'book' );
$book->title = 'a';
R::store( $book );
try {
R::getWriter()->addIndex( 'book' , '\'', 'title' );
pass();
} catch( \Exception $e ) {
fail();
}
R::getWriter()->addIndex( 'book' , '\'', 'title' );
pass();
}
/**
* Test foreign keys with SQLite.
*
* @return void
*/
public function testForeignKeysWithSQLite()
{
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$cover = R::dispense( 'cover' );
list( $g1, $g2 ) = R::dispense( 'genre', 2 );
$g1->name = '1';
$g2->name = '2';
$book->ownPage = array( $page );
$book->cover = $cover;
$book->sharedGenre = array( $g1, $g2 );
R::store( $book );
$fkbook = R::getAll( 'pragma foreign_key_list(book)' );
$fkgenre = R::getAll( 'pragma foreign_key_list(book_genre)' );
$fkpage = R::getAll( 'pragma foreign_key_list(page)' );
asrt( $fkpage[0]['from'], 'book_id' );
asrt( $fkpage[0]['to'], 'id' );
asrt( $fkpage[0]['table'], 'book' );
asrt( count( $fkgenre ), 2 );
if ( $fkgenre[0]['from'] == 'book' ) {
asrt( $fkgenre[0]['to'], 'id' );
asrt( $fkgenre[0]['table'], 'book' );
}
if ( $fkgenre[0]['from'] == 'genre' ) {
asrt( $fkgenre[0]['to'], 'id' );
asrt( $fkgenre[0]['table'], 'genre' );
}
asrt( $fkbook[0]['from'], 'cover_id' );
asrt( $fkbook[0]['to'], 'id' );
asrt( $fkbook[0]['table'], 'cover' );
}
/**
* Constrain test for SQLite Writer.
*
* @return void
*/
public function testConstrain()
{
R::nuke();
$sql = 'CREATE TABLE book ( id INTEGER PRIMARY KEY AUTOINCREMENT ) ';
R::exec( $sql );
$sql = 'CREATE TABLE page ( id INTEGER PRIMARY KEY AUTOINCREMENT ) ';
R::exec( $sql );
$sql = 'CREATE TABLE book_page (
id INTEGER PRIMARY KEY AUTOINCREMENT,
book_id INTEGER,
page_id INTEGER
) ';
R::exec( $sql );
$sql = 'PRAGMA foreign_key_list("book_page")';
$fkList = R::getAll( $sql );
asrt( count( $fkList), 0 );
$writer = R::getWriter();
$writer->addFK( 'book_page', 'book', 'book_id', 'id', TRUE );
$writer->addFK( 'book_page', 'page', 'page_id', 'id', TRUE );
$sql = 'PRAGMA foreign_key_list("book_page")';
$fkList = R::getAll( $sql );
asrt( count( $fkList), 2 );
$writer->addFK( 'book_page', 'book', 'book_id', 'id', TRUE );
$writer->addFK( 'book_page', 'page', 'page_id', 'id', TRUE );
$sql = 'PRAGMA foreign_key_list("book_page")';
$fkList = R::getAll( $sql );
asrt( count( $fkList), 2 );
}
/**
* Test adding foreign keys.
*
* @return void
*/
public function testAddingForeignKeys()
{
R::nuke();
$sql = 'CREATE TABLE book ( id INTEGER PRIMARY KEY AUTOINCREMENT ) ';
R::exec( $sql );
$sql = 'CREATE TABLE page ( id INTEGER PRIMARY KEY AUTOINCREMENT, book_id INTEGER ) ';
R::exec( $sql );
asrt( count( R::getAll(' PRAGMA foreign_key_list("page") ') ), 0 );
$writer = R::getWriter();
$writer->addFK('page', 'book', 'book_id', 'id', TRUE);
asrt( count( R::getAll(' PRAGMA foreign_key_list("page") ') ), 1 );
$writer->addFK('page', 'book', 'book_id', 'id', TRUE);
asrt( count( R::getAll(' PRAGMA foreign_key_list("page") ') ), 1 );
$writer->addFK('page', 'book', 'book_id', 'id', FALSE);
asrt( count( R::getAll(' PRAGMA foreign_key_list("page") ') ), 1 );
R::nuke();
$sql = 'CREATE TABLE book ( id INTEGER PRIMARY KEY AUTOINCREMENT ) ';
R::exec( $sql );
$sql = 'CREATE TABLE page ( id INTEGER PRIMARY KEY AUTOINCREMENT, book_id INTEGER ) ';
R::exec( $sql );
asrt( count( R::getAll(' PRAGMA foreign_key_list("page") ') ), 0 );
$writer = R::getWriter();
$writer->addFK('page', 'book', 'book_id', 'id', FALSE);
asrt( count( R::getAll(' PRAGMA foreign_key_list("page") ') ), 1 );
$writer->addFK('page', 'book', 'book_id', 'id', TRUE);
asrt( count( R::getAll(' PRAGMA foreign_key_list("page") ') ), 1 );
$writer->addFK('page', 'book', 'book_id', 'id', FALSE);
asrt( count( R::getAll(' PRAGMA foreign_key_list("page") ') ), 1 );
}
/**
* Test whether we can manually create indexes.
*
* @return void
*/
public function testAddingIndex()
{
R::nuke();
$sql = 'CREATE TABLE song (
id INTEGER PRIMARY KEY AUTOINCREMENT,
album_id INTEGER,
category TEXT
) ';
R::exec( $sql );
$writer = R::getWriter();
$indexes = R::getAll('PRAGMA index_list("song") ');
asrt( count( $indexes ), 0 );
$writer->addIndex( 'song', 'index1', 'album_id' );
$indexes = R::getAll('PRAGMA index_list("song") ');
asrt( count( $indexes ), 1 );
$writer->addIndex( 'song', 'index1', 'album_id' );
$indexes = R::getAll('PRAGMA index_list("song") ');
asrt( count( $indexes ), 1 );
$writer->addIndex( 'song', 'index2', 'category' );
$indexes = R::getAll('PRAGMA index_list("song") ');
asrt( count( $indexes ), 2 );
try {
$writer->addIndex( 'song', 'index1', 'nonexistant' );
pass();
} catch ( \Exception $ex ) {
fail();
}
$indexes = R::getAll('PRAGMA index_list("song") ');
asrt( count( $indexes ), 2 );
try {
$writer->addIndex( 'nonexistant', 'index1', 'nonexistant' );
pass();
} catch ( \Exception $ex ) {
fail();
}
$indexes = R::getAll('PRAGMA index_list("song") ');
asrt( count( $indexes ), 2 );
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace RedUNIT\Sqlite;
use RedUNIT\Sqlite as Sqlite;
use RedBeanPHP\Facade as R;
/**
* Parambind
*
* Tests the parameter binding functionality in RedBeanPHP.
* These test scenarios include for instance: NULL handling,
* binding parameters in LIMIT clauses and so on.
*
* @file RedUNIT/Sqlite/Parambind.php
* @desc Tests\PDO parameter binding.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Parambind extends Sqlite
{
/**
* Test parameter binding with SQLite.
*
* @return void
*/
public function testParamBindWithSQLite()
{
$toolbox = R::getToolBox();
$adapter = $toolbox->getDatabaseAdapter();
$writer = $toolbox->getWriter();
$redbean = $toolbox->getRedBean();
$pdo = $adapter->getDatabase();
asrt( (int) $adapter->getCell( "SELECT 123" ), 123 );
asrt( (int) $adapter->getCell( "SELECT ?", array( "987" ) ), 987 );
asrt( (int) $adapter->getCell( "SELECT ?+?", array( "987", "2" ) ), 989 );
asrt( (int) $adapter->getCell(
"SELECT :numberOne+:numberTwo",
array(
":numberOne" => 42,
":numberTwo" => 50 )
),
92
);
$pair = $adapter->getAssoc( "SELECT 'thekey','thevalue' " );
asrt( is_array( $pair ), TRUE );
asrt( count( $pair ), 1 );
asrt( isset( $pair["thekey"] ), TRUE );
asrt( $pair["thekey"], "thevalue" );
testpack( 'Test whether we can properly bind and receive NULL values' );
asrt( $adapter->getCell( 'SELECT :nil ', array( ':nil' => 'NULL' ) ), 'NULL' );
asrt( $adapter->getCell( 'SELECT :nil ', array( ':nil' => NULL ) ), NULL );
asrt( $adapter->getCell( 'SELECT ? ', array( 'NULL' ) ), 'NULL' );
asrt( $adapter->getCell( 'SELECT ? ', array( NULL ) ), NULL );
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace RedUNIT\Sqlite;
use RedUNIT\Sqlite as Sqlite;
use RedBeanPHP\Facade as R;
/**
* Rebuild
*
* SQLite cannot ALTER tables like other databases can.
* To implement fluid mode in RedBeanPHP we have to
* rebuild the entire table whenever we add or remove a column.
* This test class tests whether rebuilding tables works properly,
* i.e. we get the same table plus/minus some column...
*
* @file RedUNIT/Sqlite/Rebuild.php
* @desc Test rebuilding of tables for SQLite
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Rebuild extends Sqlite
{
/**
* Test SQLite table rebuilding.
*
* @return void
*/
public function testRebuilder()
{
$toolbox = R::getToolBox();
$adapter = $toolbox->getDatabaseAdapter();
$writer = $toolbox->getWriter();
$redbean = $toolbox->getRedBean();
$pdo = $adapter->getDatabase();
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$book->xownPage[] = $page;
$id = R::store( $book );
$book = R::load( 'book', $id );
asrt( count( $book->xownPage ), 1 );
asrt( (int) R::getCell( 'SELECT COUNT(*) FROM page' ), 1 );
R::trash( $book );
asrt( (int) R::getCell( 'SELECT COUNT(*) FROM page' ), 0 );
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$book->xownPage[] = $page;
$id = R::store( $book );
$book = R::load( 'book', $id );
asrt( count( $book->xownPage ), 1 );
asrt( (int) R::getCell( 'SELECT COUNT(*) FROM page' ), 1 );
$book->added = 2;
R::store( $book );
$book->added = 'added';
R::store( $book );
R::trash( $book );
asrt( (int) R::getCell( 'SELECT COUNT(*) FROM page' ), 0 );
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace RedUNIT\Sqlite;
use RedUNIT\Sqlite as Sqlite;
use RedBeanPHP\Facade as R;
/**
* Setget
*
* This class has been designed to test set/get operations
* for a specific Query Writer / Adapter. Since RedBeanPHP
* creates columns based on values it's essential that you
* get back the 'same' value as you put in - or - if that's
* not the case, that there are at least very clear rules
* about what to expect. Examples of possible issues tested in
* this class include:
*
* - Test whether booleans are returned correctly (they will become integers)
* - Test whether large numbers are preserved
* - Test whether floating point numbers are preserved
* - Test whether date/time values are preserved
* and so on...
*
* @file RedUNIT/Sqlite/Setget.php
* @desc Tests whether values are stored correctly.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Setget extends Sqlite
{
/**
* Test whether we can store DateTime objects and get them back
* as 'date-time' strings representing the same date and time.
*
* @return void
*/
public function testDateObject()
{
$dt = new \DateTime();
$dt->setTimeZone( new \DateTimeZone( 'Europe/Amsterdam' ) );
$dt->setDate( 1981, 5, 1 );
$dt->setTime( 3, 13, 13 );
asrt( setget( $dt ), '1981-05-01 03:13:13' );
$bean = R::dispense( 'bean' );
$bean->dt = $dt;
}
/**
* Test numbers.
*
* @return void
*/
public function testNumbers()
{
asrt( setget( "-1" ), "-1" );
asrt( setget( -1 ), "-1" );
asrt( setget( "-0.25" ), "-0.25" );
asrt( setget( -0.25 ), "-0.25" );
asrt( setget( "1.0" ), "1" );
asrt( setget( 1.0 ), "1" );
asrt( setget( "0.12345678" ), "0.12345678" );
asrt( setget( 0.12345678 ), "0.12345678" );
asrt( setget( "-0.12345678" ), "-0.12345678" );
asrt( setget( -0.12345678 ), "-0.12345678" );
asrt( setget( "2147483647" ), "2147483647" );
asrt( setget( 2147483647 ), "2147483647" );
asrt( setget( -2147483647 ), "-2147483647" );
asrt( setget( "-2147483647" ), "-2147483647" );
asrt( setget( "2147483648" ), "2147483648" );
asrt( setget( "-2147483648" ), "-2147483648" );
asrt( setget( "199936710040730" ), "199936710040730" );
asrt( setget( "-199936710040730" ), "-199936710040730" );
}
/**
* Test dates.
*
* @return void
*/
public function testDates()
{
asrt( setget( "2010-10-11" ), "2010-10-11" );
asrt( setget( "2010-10-11 12:10" ), "2010-10-11 12:10" );
asrt( setget( "2010-10-11 12:10:11" ), "2010-10-11 12:10:11" );
asrt( setget( "x2010-10-11 12:10:11" ), "x2010-10-11 12:10:11" );
}
/**
* Test strings.
*
* @return void
*/
public function testStrings()
{
asrt( setget( "a" ), "a" );
asrt( setget( "." ), "." );
asrt( setget( "\"" ), "\"" );
asrt( setget( "just some text" ), "just some text" );
}
/**
* Test booleans.
*
* @return void
*/
public function testBool()
{
asrt( setget( TRUE ), "1" );
asrt( setget( FALSE ), "0" );
asrt( setget( "TRUE" ), "TRUE" );
asrt( setget( "FALSE" ), "FALSE" );
}
/**
* Test NULL.
*
* @return void
*/
public function testNull()
{
asrt( setget( "NULL" ), "NULL" );
asrt( setget( "NULL" ), "NULL" );
asrt( setget( NULL ), NULL );
asrt( ( setget( 0 ) == 0 ), TRUE );
asrt( ( setget( 1 ) == 1 ), TRUE );
asrt( ( setget( TRUE ) == TRUE ), TRUE );
asrt( ( setget( FALSE ) == FALSE ), TRUE );
}
}

View File

@@ -0,0 +1,406 @@
<?php
namespace RedUNIT\Sqlite;
use RedUNIT\Sqlite as Sqlite;
use RedBeanPHP\Facade as R;
use RedBeanPHP\AssociationManager as AssociationManager;
use RedBeanPHP\QueryWriter\SQLiteT as SQLiteT;
use RedBeanPHP\RedException\SQL as SQL;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\QueryWriter\AQueryWriter;
/**
* Writer
*
* Tests for SQLite Query Writer.
* This test class contains Query Writer specific tests.
* Use this class to add tests to test Query Writer specific
* behaviours, quirks and issues.
*
* @file RedUNIT/Sqlite/Writer.php
* @desc Tests writer specific functions.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Writer extends Sqlite
{
/**
* Test whether SQLite QueryWriter safely filters FOR-UPDATE snippets.
*
* @return void
*/
public function testWriterShouldFilterSelectForUpdate()
{
R::nuke();
$id = R::store( R::dispense('bean') );
R::debug( TRUE, 1 );
$bean = R::findForUpdate('bean', 'id > 0');
$logs = R::getDatabaseAdapter()->getDatabase()->getLogger()->grep( AQueryWriter::C_SELECT_SNIPPET_FOR_UPDATE );
asrt( count($logs), 0 ); //if no cache clear, then would have been 2
$bean = R::loadForUpdate('bean', $id);
$logs = R::getDatabaseAdapter()->getDatabase()->getLogger()->grep( AQueryWriter::C_SELECT_SNIPPET_FOR_UPDATE );
asrt( count($logs), 0 ); //if no cache clear, then would have been 2
R::getWriter()->setUseCache( FALSE );
R::debug( FALSE );
R::nuke();
}
/**
* Test whether optimizations do not have effect on Writer query outcomes.
*
* @return void
*/
public function testWriterSpeedUp()
{
R::nuke();
$id = R::store( R::dispense( 'book' ) );
$writer = R::getWriter();
$count1 = $writer->queryRecordCount( 'book', array( 'id' => $id ), ' id = :id ', array( ':id' => $id ) );
$count2 = $writer->queryRecordCount( 'book', array( ), ' id = :id ', array( ':id' => $id ) );
$count3 = $writer->queryRecordCount( 'book', NULL, ' id = :id ', array( ':id' => $id ) );
$count4 = $writer->queryRecordCount( 'book', array( 'id' => $id ) );
asrt( $count1, $count2 );
asrt( $count2, $count3 );
asrt( $count3, $count4 );
R::nuke();
$books = R::dispenseAll( 'book*4' );
$ids = R::storeAll( $books[0] );
$writer->deleteRecord( 'book', array( 'id' => $ids[0] ) );
$writer->deleteRecord( 'book', array( 'id' => $ids[1] ), ' id = :id ', array( ':id' => $ids[1] ) );
$writer->deleteRecord( 'book', NULL, ' id = :id ', array( ':id' => $ids[2] ) );
$writer->deleteRecord( 'book', array(), ' id = :id ', array( ':id' => $ids[3] ) );
asrt( R::count( 'book' ), 0 );
R::nuke();
$id = R::store( R::dispense( 'book' ) );
$record = $writer->queryRecord( 'book', array( 'id' => $id ) );
asrt( is_array( $record ), TRUE );
asrt( is_array( $record[0] ), TRUE );
asrt( isset( $record[0]['id'] ), TRUE );
asrt( (int) $record[0]['id'], $id );
$record = $writer->queryRecord( 'book', array( 'id' => $id ), ' id = :id ', array( ':id' => $id ) );
asrt( is_array( $record ), TRUE );
asrt( is_array( $record[0] ), TRUE );
asrt( isset( $record[0]['id'] ), TRUE );
asrt( (int) $record[0]['id'], $id );
$record = $writer->queryRecord( 'book', NULL, ' id = :id ', array( ':id' => $id ) );
asrt( is_array( $record ), TRUE );
asrt( is_array( $record[0] ), TRUE );
asrt( isset( $record[0]['id'] ), TRUE );
asrt( (int) $record[0]['id'], $id );
$record = $writer->queryRecord( 'book', array(), ' id = :id ', array( ':id' => $id ) );
asrt( is_array( $record ), TRUE );
asrt( is_array( $record[0] ), TRUE );
asrt( isset( $record[0]['id'] ), TRUE );
asrt( (int) $record[0]['id'], $id );
}
/**
* Tests wheter we can write a deletion query
* for SQLite using NO conditions but only an
* additional SQL snippet.
*
* @return void
*/
public function testWriteDeleteQuery()
{
$queryWriter = R::getWriter();
asrt( ( $queryWriter instanceof SQLiteT ), TRUE );
R::nuke();
$bean = R::dispense( 'bean' );
$bean->name = 'a';
$id = R::store( $bean );
asrt( R::count( 'bean' ), 1 );
$queryWriter->deleteRecord( 'bean', array(), $addSql = ' id = :id ', $bindings = array( ':id' => $id ) );
asrt( R::count( 'bean' ), 0 );
}
/**
* Tests wheter we can write a counting query
* for SQLite using conditions and an additional SQL snippet.
*
* @return void
*/
public function testWriteCountQuery()
{
$queryWriter = R::getWriter();
asrt( ( $queryWriter instanceof SQLiteT ), TRUE );
R::nuke();
$bean = R::dispense( 'bean' );
$bean->name = 'a';
R::store( $bean );
$bean = R::dispense( 'bean' );
$bean->name = 'b';
R::store( $bean );
$bean = R::dispense( 'bean' );
$bean->name = 'b';
R::store( $bean );
$count = $queryWriter->queryRecordCount( 'bean', array( 'name' => 'b' ), $addSql = ' id > :id ', $bindings = array( ':id' => 0 ) );
asrt( $count, 2 );
}
/**
* Tests whether we can write a SQLite join and
* whether the correct exception is thrown in case
* of an invalid join.
*
* @return void
*/
public function testWriteJoinSnippets()
{
$queryWriter = R::getWriter();
asrt( ( $queryWriter instanceof SQLiteT ), TRUE );
$snippet = $queryWriter->writeJoin( 'book', 'page' ); //default must be LEFT
asrt( is_string( $snippet ), TRUE );
asrt( ( strlen( $snippet ) > 0 ), TRUE );
asrt( ' LEFT JOIN `page` ON `page`.id = `book`.page_id ', $snippet );
$snippet = $queryWriter->writeJoin( 'book', 'page', 'LEFT' );
asrt( is_string( $snippet ), TRUE );
asrt( ( strlen( $snippet ) > 0 ), TRUE );
asrt( ' LEFT JOIN `page` ON `page`.id = `book`.page_id ', $snippet );
$snippet = $queryWriter->writeJoin( 'book', 'page', 'RIGHT' );
asrt( is_string( $snippet ), TRUE );
asrt( ( strlen( $snippet ) > 0 ), TRUE );
asrt( ' RIGHT JOIN `page` ON `page`.id = `book`.page_id ', $snippet );
$snippet = $queryWriter->writeJoin( 'book', 'page', 'INNER' );
asrt( ' INNER JOIN `page` ON `page`.id = `book`.page_id ', $snippet );
$exception = NULL;
try {
$snippet = $queryWriter->writeJoin( 'book', 'page', 'MIDDLE' );
}
catch(\Exception $e) {
$exception = $e;
}
asrt( ( $exception instanceof RedException ), TRUE );
$errorMessage = $exception->getMessage();
asrt( is_string( $errorMessage ), TRUE );
asrt( ( strlen( $errorMessage ) > 0 ), TRUE );
asrt( $errorMessage, 'Invalid JOIN.' );
}
/**
* Test scanning and coding.
*
* @return void
*/
public function testScanningAndCoding()
{
$toolbox = R::getToolBox();
$adapter = $toolbox->getDatabaseAdapter();
$writer = $toolbox->getWriter();
$redbean = $toolbox->getRedBean();
$pdo = $adapter->getDatabase();
$a = new AssociationManager( $toolbox );
asrt( in_array( "testtable", $writer->getTables() ), FALSE );
$writer->createTable( "testtable" );
asrt( in_array( "testtable", $writer->getTables() ), TRUE );
asrt( count( array_keys( $writer->getColumns( "testtable" ) ) ), 1 );
asrt( in_array( "id", array_keys( $writer->getColumns( "testtable" ) ) ), TRUE );
asrt( in_array( "c1", array_keys( $writer->getColumns( "testtable" ) ) ), FALSE );
$writer->addColumn( "testtable", "c1", 1 );
asrt( count( array_keys( $writer->getColumns( "testtable" ) ) ), 2 );
asrt( in_array( "c1", array_keys( $writer->getColumns( "testtable" ) ) ), TRUE );
foreach ( $writer->sqltype_typeno as $key => $type ) {
asrt( $writer->code( $key ), $type );
}
asrt( $writer->code( "unknown" ), 99 );
asrt( $writer->scanType( FALSE ), SQLiteT::C_DATATYPE_INTEGER );
asrt( $writer->scanType( NULL ), SQLiteT::C_DATATYPE_INTEGER );
asrt( $writer->scanType( 2 ), SQLiteT::C_DATATYPE_INTEGER );
asrt( $writer->scanType( 255 ), SQLiteT::C_DATATYPE_INTEGER );
asrt( $writer->scanType( 256 ), SQLiteT::C_DATATYPE_INTEGER );
asrt( $writer->scanType( -1 ), SQLiteT::C_DATATYPE_INTEGER );
asrt( $writer->scanType( 1.5 ), SQLiteT::C_DATATYPE_NUMERIC );
asrt( $writer->scanType( 2147483648 - 1 ), SQLiteT::C_DATATYPE_INTEGER );
asrt( $writer->scanType( 2147483648 ), SQLiteT::C_DATATYPE_TEXT );
asrt( $writer->scanType( -2147483648 + 1), SQLiteT::C_DATATYPE_INTEGER );
asrt( $writer->scanType( -2147483648 ), SQLiteT::C_DATATYPE_TEXT );
asrt( $writer->scanType( INF ), SQLiteT::C_DATATYPE_TEXT );
asrt( $writer->scanType( "abc" ), SQLiteT::C_DATATYPE_TEXT );
asrt( $writer->scanType( '2010-10-10' ), SQLiteT::C_DATATYPE_NUMERIC );
asrt( $writer->scanType( '2010-10-10 10:00:00' ), SQLiteT::C_DATATYPE_NUMERIC );
asrt( $writer->scanType( str_repeat( "lorem ipsum", 100 ) ), SQLiteT::C_DATATYPE_TEXT );
$writer->widenColumn( "testtable", "c1", 2 );
$cols = $writer->getColumns( "testtable" );
asrt( $writer->code( $cols["c1"] ), 2 );
$id = $writer->updateRecord( "testtable", array( array( "property" => "c1", "value" => "lorem ipsum" ) ) );
$row = $writer->queryRecord( "testtable", array( "id" => array( $id ) ) );
asrt( $row[0]["c1"], "lorem ipsum" );
$writer->updateRecord( "testtable", array( array( "property" => "c1", "value" => "ipsum lorem" ) ), $id );
$row = $writer->queryRecord( "testtable", array( "id" => array( $id ) ) );
asrt( $row[0]["c1"], "ipsum lorem" );
$writer->deleteRecord( "testtable", array( "id" => array( $id ) ) );
$row = $writer->queryRecord( "testtable", array( "id" => array( $id ) ) );
asrt( empty( $row ), TRUE );
}
/**
* (FALSE should be stored as 0 not as '')
*
* @return void
*/
public function testZeroIssue()
{
testpack( "Zero issue" );
$toolbox = R::getToolBox();
$redbean = $toolbox->getRedBean();
$bean = $redbean->dispense( "zero" );
$bean->zero = FALSE;
$bean->title = "bla";
$redbean->store( $bean );
asrt( count( $redbean->find( "zero", array(), " zero = 0 " ) ), 1 );
testpack( "Test ANSI92 issue in clearrelations" );
$redbean = $toolbox->getRedBean();
$a = new AssociationManager( $toolbox );
$book = $redbean->dispense( "book" );
$author1 = $redbean->dispense( "author" );
$author2 = $redbean->dispense( "author" );
$book->title = "My First Post";
$author1->name = "Derek";
$author2->name = "Whoever";
set1toNAssoc( $a, $book, $author1 );
set1toNAssoc( $a, $book, $author2 );
pass();
}
/**
* Various.
* Tests whether writer correctly handles keyword 'group' and SQL state 23000 issue.
* These tests remain here to make sure issues 9 and 10 never happen again.
* However this bug will probably never re-appear due to changed architecture.
*
* @return void
*/
public function testIssue9and10()
{
$toolbox = R::getToolBox();
$redbean = $toolbox->getRedBean();
$adapter = $toolbox->getDatabaseAdapter();
$a = new AssociationManager( $toolbox );
$book = $redbean->dispense( "book" );
$author1 = $redbean->dispense( "author" );
$author2 = $redbean->dispense( "author" );
$book->title = "My First Post";
$author1->name = "Derek";
$author2->name = "Whoever";
$a->associate( $book, $author1 );
$a->associate( $book, $author2 );
pass();
testpack( "Test Association Issue Group keyword (Issues 9 and 10)" );
$group = $redbean->dispense( "group" );
$group->name = "mygroup";
$redbean->store( $group );
try {
$a->associate( $group, $book );
pass();
} catch ( SQL $e ) {
fail();
}
// Test issue SQL error 23000
try {
$a->associate( $group, $book );
pass();
} catch ( SQL $e ) {
print_r( $e );
fail();
}
asrt( (int) $adapter->getCell( "select count(*) from book_group" ), 1 ); //just 1 rec!
}
/**
* Test various.
* Test various somewhat uncommon trash/unassociate scenarios.
* (i.e. unassociate unrelated beans, trash non-persistant beans etc).
* Should be handled gracefully - no output checking.
*
* @return void
*/
public function testVaria2()
{
$toolbox = R::getToolBox();
$redbean = $toolbox->getRedBean();
$a = new AssociationManager( $toolbox );
$book = $redbean->dispense( "book" );
$author1 = $redbean->dispense( "author" );
$author2 = $redbean->dispense( "author" );
$book->title = "My First Post";
$author1->name = "Derek";
$author2->name = "Whoever";
$a->unassociate( $book, $author1 );
$a->unassociate( $book, $author2 );
pass();
$redbean->trash( $redbean->dispense( "bla" ) );
pass();
$bean = $redbean->dispense( "bla" );
$bean->name = 1;
$bean->id = 2;
$redbean->trash( $bean );
pass();
}
/**
* Test special data types.
*
* @return void
*/
public function testSpecialDataTypes()
{
testpack( 'Special data types' );
$bean = R::dispense( 'bean' );
$bean->date = 'someday';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['date'], 'TEXT' );
$bean = R::dispense( 'bean' );
$bean->date = '2011-10-10';
R::nuke();
$bean = R::dispense( 'bean' );
$bean->date = '2011-10-10';
R::store( $bean );
$cols = R::getColumns( 'bean' );
asrt( $cols['date'], 'NUMERIC' );
}
/**
* Test renewed error handling in SQLite.
* In fluid mode ignore table/column not exists (HY000 + code 1).
* In frozen mode ignore nothing.
*
* @return void
*/
public function testErrorHandling()
{
R::nuke();
R::store( R::dispense( 'book' ) );
R::freeze( FALSE );
R::find( 'book2', ' id > 0' );
pass();
R::find( 'book', ' id2 > ?' );
pass();
$exception = NULL;
try {
R::find( 'book', ' id = ?', array( 0, 1 ) );
} catch( \Exception $e ) {
$exception = $e;
}
asrt( ( $exception instanceof SQL ), TRUE );
R::freeze( TRUE );
$exception = NULL;
try {
R::find( 'book2', ' id > 0' );
} catch( \Exception $e ) {
$exception = $e;
}
asrt( ( $exception instanceof SQL ), TRUE );
$exception = NULL;
try {
R::find( 'book', ' id2 > 0' );
} catch( \Exception $e ) {
$exception = $e;
}
asrt( ( $exception instanceof SQL ), TRUE );
}
}