<?php
namespace RedUNIT\Base;

use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\Adapter\DBAdapter;
use RedBeanPHP\QueryWriter\PostgreSQL;
use RedBeanPHP\QueryWriter\SQLiteT;
use RedBeanPHP\QueryWriter\MySQL;
use RedBeanPHP\QueryWriter\CUBRID;
use RedBeanPHP\OODB;
use RedBeanPHP\BeanHelper;
use RedBeanPHP\Driver\RPDO;

/**
 * Bean
 *
 * One of the core test suites of RedBeanPHP, tests the
 * bean interface and all basic bean functions, including
 * deletion, updating, inserting, importing and more...
 *
 * @file    RedUNIT/Base/Bean.php
 * @desc    Tests list manipulations of 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 Bean extends Base
{
	/**
	 * Tests whether we can override the __toString() method of
	 * a bean (for example to display binary data).
	 */
	public function testToStringOverride()
	{
		$bean = R::dispense('string');
		$bean->text = '"Hello"';
		$base64 = strval( $bean );
		asrt( $base64, 'IkhlbGxvIg==' );
	}

	/**
	 * Tests whether we can use RedBeanPHP core objects without
	 * Facade related objects in a non-static style.
	 *
	 * @return void
	 */
	public function testLooselyWired()
	{
		$pdo = R::getDatabaseAdapter()->getDatabase()->getPDO();
		$database = new RPDO( $pdo );
		$adapter = new DBAdapter( $database );
		if ($this->currentlyActiveDriverID == 'pgsql') $writer = new PostgreSQL( $adapter );
		if ($this->currentlyActiveDriverID == 'mysql') $writer = new MySQL( $adapter );
		if ($this->currentlyActiveDriverID == 'sqlite') $writer = new SQLiteT( $adapter );
		if ($this->currentlyActiveDriverID == 'CUBRID') $writer = new CUBRID( $adapter );
		$oodb = new OODB( $writer, FALSE );
		$bean = $oodb->dispense( 'bean' );
		$bean->name = 'coffeeBean';
		$id = $oodb->store( $bean );
		asrt( ( $id > 0 ), TRUE );
		$bean = $oodb->load( 'bean', $id );
		asrt( $bean->name, 'coffeeBean' );
		asrt( $oodb->count( 'bean' ), 1 );
		$oodb->trash( $bean );
		asrt( $oodb->count( 'bean' ), 0 );
	}

	/**
	 * From github (issue #549):
	 * Imagine you have a simple Post object, which has a person_id and
	 * is supposed to be associated to a Person.
	 *
	 * $post = R::findOne('post');
	 * $a = $post->person ?? null;
	 * $b = $post->person;
	 * $c = $post->person ?? null;
	 *
	 * var_dump($a ? 'Person A is named ' . $a->name : 'No person A');
	 * var_dump($b ? 'Person B is named ' . $b->name : 'No person B');
	 * var_dump($c ? 'Person C is named ' . $c->name : 'No person C');
	 *
	 * I would expect to either have the person's name in the output three times,
	 * or 'No person' in the output three times.
	 * What actually happens is that $a is null,
	 * so the first access fails, but the other two succeed.
	 * I'm guessing this happens because the implementation of offsetExists
	 * doesn't agree with offsetGet around relations.
	 *
	 * @return void
	 */
	public function testIssue549OffsetExistsVsOffsetGet()
	{
		R::nuke();
		list( $post, $person ) = R::dispenseAll( 'post,person' );
		$post->person = $person;
		R::store( $post );
		$post = R::findOne('post');
		$a = (isset( $post->person )) ? $post->person : NULL;
		$b = $post->person;
		$c = (isset( $post->person )) ? $post->person : NULL;
		$strA = ($a ? 'Y' : 'N');
		$strB = ($b ? 'Y' : 'N');
		$strC = ($c ? 'Y' : 'N');
		$out = "{$strA}{$strB}{$strC}";
		asrt( $out, 'NYY' );
		asrt( isset( $post->other ), FALSE );
		R::nuke();
		list( $post, $person ) = R::dispenseAll( 'post,person' );
		$post->person = $person;
		R::store( $post );
		$post = R::findOne('post');
		$a = ( $post->exists('person') ) ? $post->person : NULL;
		$b = $post->person;
		$c = ( $post->exists('person') ) ? $post->person : NULL;
		$strA = ($a ? 'Y' : 'N');
		$strB = ($b ? 'Y' : 'N');
		$strC = ($c ? 'Y' : 'N');
		$out = "{$strA}{$strB}{$strC}";
		asrt( $out, 'YYY' );
		asrt( isset( $post->other ), FALSE );
		asrt( $post->exists('comment'), FALSE );
	}

	/**
	 * Tests whether we can send results of a query to meta data
	 * when converting to bean.
	 */
	public function testImportMeta()
	{
		R::nuke();
		$book = R::dispense( array(
			'_type'  => 'book',
			'title'  => 'Bean Recipes',
			'author' => 'Meastro de la Bean'
		) );
		$pages = R::dispenseAll( 'page*2' );
		$book->ownPageList = reset( $pages );
		R::store( $book );
		$data = R::getRow('SELECT book.*,
								  COUNT(page.id) AS meta_count,
								  1234 AS meta_extra
						   FROM book
						   LEFT JOIN page ON page.book_id = book.id
						   GROUP BY book.id
						   ');
		$bean = R::convertToBean( 'book', $data, 'meta_' );
		asrt( isset( $bean->title ), TRUE );
		asrt( isset( $bean->author ), TRUE );
		asrt( isset( $bean->meta_count ), FALSE );
		asrt( isset( $bean->meta_extra ), FALSE );
		$data = $bean->getMeta( 'data.bundle' );
		asrt( intval( $data['meta_count'] ), 2);
		asrt( intval( $data['meta_extra'] ), 1234);
		//now with multiple beans
		$book = R::dispense( array(
			'_type'  => 'book',
			'title'  => 'Bean Adventures',
			'author' => 'Mr Adventure'
		) );
		$pages = R::dispenseAll( 'page*3' );
		$book->ownPageList = reset( $pages );
		R::store( $book );
		$data = R::getAll('SELECT book.*,
								  COUNT(page.id) AS meta_pages
						   FROM book
						   LEFT JOIN page ON page.book_id = book.id
						   GROUP BY book.id
						   ');
		$books = R::convertToBeans( 'book', $data, 'meta_' );
		$found = 0;
		foreach( $books as $book ) {
			if ( $book->title == 'Bean Recipes' ) {
				$found++;
				asrt( isset( $book->title ), TRUE );
				asrt( isset( $book->author ), TRUE );
				asrt( isset( $book->meta_count ), FALSE );
				asrt( isset( $book->meta_extra ), FALSE );
				$data = $book->getMeta( 'data.bundle' );
				asrt( intval( $data['meta_pages'] ), 2);
			}
			if ( $book->title == 'Bean Adventures' ) {
				$found++;
				asrt( isset( $book->title ), TRUE );
				asrt( isset( $book->author ), TRUE );
				asrt( isset( $book->meta_pages ), FALSE );
				asrt( isset( $book->meta_extra ), FALSE );
				$data = $book->getMeta( 'data.bundle' );
				asrt( intval( $data['meta_pages'] ), 3);
			}
		}
		asrt( $found, 2 );
	}

	/**
	 * Test beautification conflicts...
	 * Issue #418
	 *
	 * @return void
	 */
	public function testBeau()
	{
		R::nuke();
		$book = R::dispense( 'book' );
		$book->ownerId = 2;
		$book->ownerCritic = 'a';
		$book->sharedbyReader = 'b';
		$id = R::store( $book );
		$columns = R::inspect( 'book' );
		asrt( isset( $columns['owner_id'] ), TRUE );
		asrt( isset( $columns['owner_critic'] ), TRUE );
		asrt( isset( $columns['sharedby_reader'] ), TRUE );
		asrt( isset( $columns['ownerId'] ), FALSE );
		asrt( isset( $columns['ownerCritic'] ), FALSE );
		asrt( isset( $columns['sharedbyReader'] ), FALSE );
		R::nuke();
		$book = R::dispense( 'book' );
		$book->xownerId = 2;
		$book->xownerCritic = 'a';
		$book->sharedbyReader = 'b';
		$id = R::store( $book );
		$columns = R::inspect( 'book' );
		asrt( isset( $columns['xowner_id'] ), TRUE );
		asrt( isset( $columns['xowner_critic'] ), TRUE );
		asrt( isset( $columns['sharedby_reader'] ), TRUE );
		asrt( isset( $columns['xownerId'] ), FALSE );
		asrt( isset( $columns['xownerCritic'] ), FALSE );
		asrt( isset( $columns['sharedbyReader'] ), FALSE );
	}

	/**
	 * Other tests...
	 */
	public function testMisc()
	{
		R::nuke();
		$book = R::dispense( 'book' );
		$book->ownPage[] = R::dispense( 'page' );
		R::store( $book );
		R::nuke();
		R::store( $book );
		asrt( R::count( 'book' ), 0 );
		$book->ownPage;
		R::store( $book );
		asrt( R::count( 'book' ), 0 );
		$book->title = 'x';
		R::store( $book );
		asrt( R::count( 'book' ), 0 );
	}

	/**
	 * Can we set a date string by passing a date object?
	 */
	 public function testBeanDates()
	 {
		 $bean = R::dispense('bean');
		 $dateTime = '1980-01-01 10:11:12';
		 $bean->date = new \DateTime( $dateTime );
		 asrt( $bean->date, $dateTime );
	 }

	/**
	 * Only fire update query if the bean really contains different
	 * values. But make sure beans several 'parents' away still get
	 * saved.
	 *
	 * @return void
	 */
	public function testBeanTainting()
	{
		$logger = R::getDatabaseAdapter()->getDatabase()->getLogger();
		list( $i, $k, $c, $s ) = R::dispenseAll( 'invoice,customer,city,state' );
		$i->customer = $k;
		$i->status = 0;
		$k->city = $c;
		$c->state = $s;
		$s->name = 'x';
		R::store( $i );
		$i = $i->fresh();
		asrt( $i->customer->city->state->name, 'x' );
		$i->status = 1;
		R::freeze( TRUE );
		$logger = R::debug( 1, 1 );
		//do we properly skip unmodified but tainted parent beans?
		R::store( $i );
		$numberOfUpdateQueries = $logger->grep( 'UPDATE' );
		asrt( count( $numberOfUpdateQueries ), 1 );
		//does cascade update still work?
		$i = $i->fresh();
		$i->customer->city->state->name = 'y';
		R::store( $i );
		$i = $i->fresh();
		asrt( $i->customer->city->state->name, 'y' );
		$i = $i->fresh();
		$differentCity = R::dispense( 'city' );
		R::store( $differentCity );
		$i->customer->city = $differentCity;
		R::store( $i );
		$i = $i->fresh();
		asrt( ( $i->customer->city->id != $c->id ), TRUE );
		asrt( is_null( $i->customer->city->state ), TRUE );
		$i->customer->city = NULL;
		R::store( $i );
		$i = $i->fresh();
		asrt( is_null( $i->customer->city ), TRUE );
		$i->customer = $k;
		$i->status = 0;
		$k->city = $c;
		$c->state = $s;
		$s->name = 'x';
		R::store( $i );
		R::freeze( FALSE );
		$i = $i->fresh();
		//can we still change remote parent?
		$i->customer->city->name = 'q';
		$logger->clear();
		R::store($i);
		$numberOfUpdateQueries = $logger->grep( 'UPDATE' );
		//print_r($logger->getLogs());
		asrt( count( $numberOfUpdateQueries ), 1 );
		$i = $i->fresh();
		asrt( $i->customer->city->name, 'q' );
		//do we properly skip unmodified but tainted parent beans?
		$i->status = 3;
		$logger->clear();
		R::store( $i );
		$numberOfUpdateQueries = $logger->grep( 'UPDATE' );
		asrt( count( $numberOfUpdateQueries ), 1 );
	}

	/**
	 * Test whether the number of update queries
	 * executed is limited to the ones that are absolutely
	 * necessary to sync the database.
	 *
	 * @return void
	 */
	public function testUpdateQueries()
	{
		$book = R::dispense( 'book' );
		$book->title = 'Eye of Wight';
		$book->xownPageList = R::dispense( 'page', 10 );
		$book->sharedCategoryList = R::dispense( 'category', 2 );
		$n = 1;
		foreach( $book->xownPageList as $page ) {
			$page->number = $n++;
		}
		$book->sharedCategory[0]->name = 'adventure';
		$book->sharedCategory[1]->name = 'puzzle';
		$book->author = R::dispense( 'author' );
		$book->author->name = 'John';
		$book->map = R::dispense( 'map' );
		$book->map->name = 'Wight';
		$book->map->xownLocationList = R::dispense( 'location', 3 );
		asrt( $book->getMeta('tainted'), TRUE );
		asrt( $book->getMeta('changed'), TRUE );
		R::store( $book );
		asrt( $book->getMeta('tainted'), FALSE );
		asrt( $book->getMeta('changed'), FALSE );
		$logger = R::debug( 1, 1 );
		$book = $book->fresh();
		asrt( $book->getMeta('tainted'), FALSE );
		asrt( $book->getMeta('changed'), FALSE );
		$book->author;
		asrt( $book->getMeta('tainted'), TRUE );
		asrt( $book->getMeta('changed'), FALSE );
		$logger->clear();
		R::store( $book );
		//read only, no updates
		$numberOfUpdateQueries = $logger->grep( 'UPDATE' );
		asrt( count( $numberOfUpdateQueries ), 0 );
		$book->title = 'Spirit of the Stones';
		R::store( $book );
		//changed title, 1 update
		$numberOfUpdateQueries = $logger->grep( 'UPDATE' );
		asrt( count( $numberOfUpdateQueries ), 1 );
		$logger->clear();
		//store again, no changes, no updates
		R::store( $book );
		$numberOfUpdateQueries = $logger->grep( 'UPDATE' );
		asrt( count( $numberOfUpdateQueries ), 0 );
		$logger->clear();
		$book = $book->fresh();
		$book->xownPageList;
		asrt( $book->getMeta('tainted'), TRUE );
		asrt( $book->getMeta('changed'), FALSE );
		R::store( $book );
		//access only, no update
		$numberOfUpdateQueries = $logger->grep( 'UPDATE' );
		asrt( count( $numberOfUpdateQueries ), 0 );
		$logger->clear();
		$book = $book->fresh();
		$book->sharedCategoryList;
		asrt( $book->getMeta('tainted'), TRUE );
		asrt( $book->getMeta('changed'), FALSE );
		R::store( $book );
		//access only, no update
		$numberOfUpdateQueries = $logger->grep( 'UPDATE' );
		asrt( count( $numberOfUpdateQueries ), 0 );
		$logger->clear();
		$book = $book->fresh();
		unset($book->xownPageList[5]);
		asrt( $book->getMeta('tainted'), TRUE );
		asrt( $book->getMeta('changed'), FALSE );
		R::store( $book );
		//remove only, no update, just 1 delete
		$numberOfUpdateQueries = $logger->grep( 'UPDATE' );
		asrt( count( $numberOfUpdateQueries ), 0 );
		$numberOfUpdateQueries = $logger->grep( 'DELETE' );
		asrt( count( $numberOfUpdateQueries ), 1 );
		$book = $book->fresh();
		asrt( count( $book->xownPageList ), 9 );
		$logger->clear();
		$book = $book->fresh();
		$book->xownPageList[] = R::dispense('page');
		asrt( $book->getMeta('tainted'), TRUE );
		asrt( $book->getMeta('changed'), FALSE );
		R::store( $book );
		//no update, 1 insert, just adding
		$numberOfUpdateQueries = $logger->grep( 'UPDATE' );
		asrt( count( $numberOfUpdateQueries ), 0 );
		$numberOfUpdateQueries = $logger->grep( 'INSERT' );
		asrt( count( $numberOfUpdateQueries ), 1 );
		$book = $book->fresh();
		asrt( count( $book->xownPageList ), 10 );
		$logger->clear();
		$book = $book->fresh();
		$book->map->xownLocationList[1]->name = 'Horshoe Bay';
		asrt( $book->getMeta('tainted'), TRUE );
		asrt( $book->getMeta('changed'), FALSE );
		asrt( $book->map->getMeta('tainted'), TRUE );
		asrt( $book->map->getMeta('changed'), FALSE );
		asrt( $book->map->xownLocationList[1]->getMeta('tainted'), TRUE );
		asrt( $book->map->xownLocationList[1]->getMeta('changed'), TRUE );
		R::store( $book );
		//1 update for child of parent, no other updates
		$numberOfUpdateQueries = $logger->grep( 'UPDATE' );
		asrt( count( $numberOfUpdateQueries ), 1 );
		$book = $book->fresh();
		asrt( $book->map->xownLocationList[1]->name, 'Horshoe Bay' );
		$logger->clear();
		R::store( $book );
		//just access, no updates
		$numberOfUpdateQueries = $logger->grep( 'UPDATE' );
		asrt( count( $numberOfUpdateQueries ), 0 );
		$logger->clear();
		$book = $book->fresh();
		$book->ownPageList[2]->number = 99;
		R::store( $book );
		//1 update, do not update rest of pages or book itself
		$numberOfUpdateQueries = $logger->grep( 'UPDATE' );
		asrt( count( $numberOfUpdateQueries ), 1 );
		$book = $book->fresh();
		$book->author->name = 'Worsley';
		$logger->clear();
		R::store( $book );
		//1 update for parent
		$numberOfUpdateQueries = $logger->grep( 'UPDATE' );
		asrt( count( $numberOfUpdateQueries ), 1 );
		$author = R::dispense('author');
		$author->name = 'J.W.';
		R::store( $author );
		$book = $book->fresh();
		$book->author = $author;
		$author->name = 'JW';
		$logger->clear();
		R::store( $book );
		//2 updates, one for author, one for link field: author_id needs update.
		$numberOfUpdateQueries = $logger->grep( 'UPDATE' );
		asrt( count( $numberOfUpdateQueries ), 2 );
		$author->country = R::dispense( 'country' )->setAttr( 'name', 'England' );
		R::store( $author );
		$book = $book->fresh();
		$book->author->country->name = 'Wight';
		$logger->clear();
		R::store( $book );
		//1 update, country only, dont update for intermediate parents: book -> author -> ...
		$numberOfUpdateQueries = $logger->grep( 'UPDATE' );
		asrt( count( $numberOfUpdateQueries ), 1 );
	}

	/**
	 * Tests effects of importFrom and setProperty.
	 *
	 * @return void
	 */
	public function testImportFromAndSetProp()
	{
		$bean = R::dispense( 'bean' );
		asrt( $bean->getMeta( 'tainted' ), TRUE );
		asrt( $bean->getMeta( 'changed' ), TRUE );
		$bean->setMeta( 'tainted', FALSE );
		$bean->setMeta( 'changed', FALSE );
		asrt( $bean->getMeta( 'tainted' ), FALSE );
		asrt( $bean->getMeta( 'changed' ), FALSE );
		$bean->importFrom( R::dispense( 'bean' ) );
		asrt( $bean->getMeta( 'tainted' ), TRUE );
		asrt( $bean->getMeta( 'changed' ), TRUE );
		$bean->setMeta( 'tainted', FALSE );
		$bean->setMeta( 'changed', FALSE );
		asrt( $bean->getMeta( 'tainted' ), FALSE );
		asrt( $bean->getMeta( 'changed' ), FALSE );
		$bean->setProperty( 'id', 0, TRUE, TRUE );
		asrt( $bean->getMeta( 'tainted' ), TRUE );
		asrt( $bean->getMeta( 'changed' ), TRUE );
		$bean->setMeta( 'tainted', FALSE );
		$bean->setMeta( 'changed', FALSE );
		asrt( $bean->getMeta( 'tainted' ), FALSE );
		asrt( $bean->getMeta( 'changed' ), FALSE );
		$bean->setProperty( 'id', 0, TRUE, FALSE );
		asrt( $bean->getMeta( 'tainted' ), FALSE );
		asrt( $bean->getMeta( 'changed' ), FALSE );
		$bean->name = 'x';
		asrt( $bean->getMeta( 'tainted' ), TRUE );
		asrt( $bean->getMeta( 'changed' ), TRUE );
	}

	/**
	 * Setup
	 *
	 * @return void
	 */
	private function _createBook()
	{
		R::nuke();
		$book = R::dispense( 'book' );
		$pages = R::dispense( 'page', 2 );
		$ads = R::dispense('ad', 3 );
		$tags = R::dispense( 'tag', 2 );
		$author = R::dispense( 'author' );
		$coauthor = R::dispense( 'author' );
		$book->alias( 'magazine' )->ownAd = $ads;
		$book->ownPage = $pages;
		$book->sharedTag = $tags;
		$book->via( 'connection' )->sharedUser = array( R::dispense( 'user' ) );
		$book->author = $author;
		$book->coauthor = $coauthor;
		R::store( $book );
		return $book->fresh();
	}

	/*
	 * Can we add a bean to a list?
	 *
	 * @return void
	 */
	public function testWhetherWeCanAddToLists()
	{
		$book = $this->_createBook();
		$book->ownPage[] = R::dispense( 'page' );
		R::store( $book );
		asrt( R::count('page'), 3 );
		$book = $this->_createBook();
		$book->ownPageList[] = R::dispense('page');
		R::store( $book );
		asrt( R::count('page'), 3 );
		$book = $this->_createBook();
		$book->xownPage[] = R::dispense('page');
		R::store( $book );
		asrt( R::count('page'), 3 );
		$book = $this->_createBook();
		$book->xownPageList[] = R::dispense('page');
		R::store( $book );
		asrt( R::count('page'), 3 );

		$ads = R::dispense('ad', 3 );
		$book = $this->_createBook();
		$book->alias('magazine')->ownAd = $ads;
		$book->ownPage[] = R::dispense('page');
		R::store( $book );
		asrt( R::count('ad'), 6 );
		asrt( R::count('page'), 3 );
		$ads = R::dispense('ad', 3 );
		$book = $this->_createBook();
		$book->alias('magazine')->ownAdList = $ads;
		$book->ownPageList[] = R::dispense('page');
		R::store( $book );
		asrt( R::count('ad'), 6 );
		asrt( R::count('page'), 3 );
		$ads = R::dispense('ad', 3 );
		$book = $this->_createBook();
		$book->alias('magazine')->xownAd = $ads;
		$book->xownPage[] = R::dispense('page');
		R::store( $book );
		asrt( R::count('ad'), 3 );
		asrt( R::count('page'), 3 );
		$ads = R::dispense('ad', 3 );
		$book = $this->_createBook();
		$book->alias('magazine')->xownAdList = $ads;
		$book->xownPageList[] = R::dispense('page');
		R::store( $book );
		asrt( R::count('ad'), 3 );
		asrt( R::count('page'), 3 );

		$book = $this->_createBook();
		$book->sharedTag[] = R::dispense('tag');
		R::store( $book );
		asrt( R::count('tag'), 3 );
		$book = $this->_createBook();
		$book->sharedTagList[] = R::dispense('tag');
		R::store( $book );
		asrt( R::count('tag'), 3 );
	}

	/**
	 * Can we delete a bean in a list by its ID?
	 * Only the UNSET() variant should work.
	 *
	 * @return void
	 */
	public function testDeleteByIDs()
	{
		$book = $this->_createBook();
		$firstPage = reset( $book->ownPageList );
		$book->ownPage[ $firstPage->id ] = NULL;
		try { R::store( $book ); fail(); }catch(\Exception $e) { pass(); }
		$book = $this->_createBook();
		asrt( count( $book->ownPage ), 2 );
		$firstPage = reset( $book->ownPageList );
		unset( $book->ownPage[ $firstPage->id ] );
		R::store( $book );
		$book = $book->fresh();
		asrt( count( $book->ownPage ), 1 );
		$firstPage = reset( $book->ownPageList );
		$book->ownPage[ $firstPage->id ] = FALSE;
		try { R::store( $book ); fail(); }catch(\Exception $e) { pass(); }
		$book = $book->fresh();
		asrt( count( $book->ownPage ), 0 );

		$book = $this->_createBook();
		$firstAd = reset( $book->alias('magazine')->ownAd );
		$book->alias('magazine')->ownAd[ $firstAd->id ] = NULL;
		try { R::store( $book ); fail(); }catch(\Exception $e) { pass(); }
		$book = $this->_createBook();
		asrt( count( $book->alias('magazine')->ownAd ), 3 );
		$firstAd = reset( $book->alias('magazine')->ownAdList );
		unset( $book->alias('magazine')->ownAdList[ $firstAd->id ] );
		R::store( $book );
		$book = $book->fresh();
		asrt( count( $book->alias('magazine')->ownAd ), 2 );
		$firstAd = reset( $book->alias('magazine')->ownAd );
		$book->alias('magazine')->ownAd[ $firstAd->id ] = FALSE;
		try { R::store( $book ); fail(); }catch(\Exception $e) { pass(); }
		$book = $book->fresh();
		asrt( count( $book->alias('magazine')->ownAd ), 1 );

	}

	/**
	 * You CAN delete an own-list by assiging an empty array.
	 *
	 * @return void
	 */
	public function testDeleteOwnListWithEmptyArray()
	{
		$book = $this->_createBook();
		asrt( isset($book->ownPage), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->ownPage ), 2 ); //when loaded has 2
		$book->ownPage = array(); //remove all
		R::store( $book );
		asrt( isset($book->ownPage), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->ownPage ), 0 );
	}

	/**
	 * You cannot delete an own-list by assigning NULL.
	 *
	 * @return void
	 */
	public function testCANTDeleteOwnListWithNULL()
	{
		$book = $this->_createBook();
		asrt( isset($book->ownPage), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->ownPage ), 2 ); //when loaded has 2
		$book->ownPage = NULL; //remove all
		R::store( $book );
		asrt( isset($book->ownPage), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->ownPage ), 2 );
	}

	/**
	 * You cannot delete an own-list by assigning FALSE.
	 *
	 * @return void
	 */
	public function testCANTDeleteOwnListWithFalse()
	{
		$book = $this->_createBook();
		asrt( isset($book->ownPage), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->ownPage ), 2 ); //when loaded has 2
		$book->ownPage = FALSE; //remove all
		R::store( $book );
		asrt( isset($book->ownPage), TRUE ); //not loaded yet, lazy loading
		asrt( $book->ownPage, '0' );
	}

	/**
	 * You cannot delete an own-list by unsetting it.
	 */
	public function testCANTDeleteOwnListWithUnset()
	{
		$book = $this->_createBook();
		asrt( isset($book->ownPage), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->ownPage ), 2 ); //when loaded has 2
		unset( $book->ownPage ); //does NOT remove all
		R::store( $book );
		asrt( isset($book->ownPage), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->ownPage ), 2 );
	}

	/**
	 * You CAN delete an aliased own-list by assiging an empty array.
	 *
	 * @return void
	 */
	public function testDeleteAliasedOwnListWithEmptyArray()
	{
		$book = $this->_createBook();
		asrt( isset($book->alias('magazine')->ownAd), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->alias('magazine')->ownAd ), 3 ); //when loaded has 2
		$book->alias('magazine')->ownAd = array(); //remove all
		$book->ownPage[] = R::dispense('page');
		R::store( $book );
		asrt( isset($book->alias('magazine')->ownAd), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->alias('magazine')->ownAd ), 0 );
		asrt( count( $book->alias('magazine')->ownPage ), 0 ); //also test possible confusion
		asrt( count( $book->all()->ownPageList ), 3 );
	}

	/**
	 * You cannot delete an aliased own-list by assigning NULL.
	 *
	 * @return void
	 */
	public function testCANTDeleteAliasedOwnListWithNULL()
	{
		$book = $this->_createBook();
		asrt( isset($book->alias('magazine')->ownAd), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->alias('magazine')->ownAd ), 3 ); //when loaded has 2
		$book->alias('magazine')->ownAd = NULL; //remove all
		R::store( $book );
		asrt( isset($book->alias('magazine')->ownAd), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->alias('magazine')->ownAd ), 3 );
	}

	/**
	 * You cannot delete an aliased own-list by assigning FALSE.
	 *
	 * @return void
	 */
	public function testCANTDeleteAliasedOwnListWithFalse()
	{
		$book = $this->_createBook();
		asrt( isset($book->alias('magazine')->ownAd), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->alias('magazine')->ownAd ), 3 ); //when loaded has 2
		$book->alias('magazine')->ownAd = FALSE; //remove all
		R::store( $book );
		asrt( isset($book->alias('magazine')->ownAd), TRUE ); //not loaded yet, lazy loading
		asrt( $book->alias('magazine')->ownAd, '0' );
	}

	/**
	 * You cannot delete an aliased own-list by unsetting it.
	 *
	 * @return void
	 */
	public function testCANTDeleteAliasedOwnListWithUnset()
	{
		$book = $this->_createBook();
		asrt( isset($book->alias('magazine')->ownAd), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->alias('magazine')->ownAd ), 3 ); //when loaded has 2
		unset( $book->alias('magazine')->ownAd ); //does NOT remove all
		R::store( $book );
		asrt( isset($book->alias('magazine')->ownAd), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->alias('magazine')->ownAd ), 3 );
	}

	/**
	 * You CAN delete an x-own-list by assiging an empty array.
	 *
	 * @return void
	 */
	public function testDeleteXOwnListWithEmptyArray()
	{
		$book = $this->_createBook();
		asrt( isset($book->xownPage), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->xownPage ), 2 ); //when loaded has 2
		$book->xownPage = array(); //remove all
		R::store( $book );
		asrt( isset($book->xownPage), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->xownPage ), 0 );
	}

	/**
	 * You cannot delete an x-own-list by assigning NULL.
	 *
	 * @return  void
	 */
	public function testCANTDeleteXOwnListWithNULL()
	{
		$book = $this->_createBook();
		asrt( isset($book->xownPage), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->xownPage ), 2 ); //when loaded has 2
		$book->xownPage = NULL; //remove all
		R::store( $book );
		asrt( isset($book->xownPage), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->xownPage ), 2 );
	}

	/**
	 * You cannot delete an x-own-list by assigning FALSE.
	 *
	 * @return void
	 */
	public function testCANTDeleteXOwnListWithFalse()
	{
		$book = $this->_createBook();
		asrt( isset($book->xownPage), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->xownPage ), 2 ); //when loaded has 2
		$book->xownPage = FALSE; //remove all
		R::store( $book );
		asrt( isset($book->xownPage), TRUE ); //not loaded yet, lazy loading
		asrt( $book->xownPage, '0' );
	}

	/**
	 * You cannot delete an x-own-list by unsetting it.
	 *
	 * @return void
	 */
	public function testCANTDeleteXOwnListWithUnset()
	{
		$book = $this->_createBook();
		asrt( isset($book->xownPage), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->xownPage ), 2 ); //when loaded has 2
		unset( $book->xownPage ); //does NOT remove all
		R::store( $book );
		asrt( isset($book->xownPage), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->xownPage ), 2 );
	}

	/**
	 * You CAN delete a shared-list by assiging an empty array.
	 *
	 * @return void
	 */
	public function testDeleteSharedListWithEmptyArray()
	{
		$book = $this->_createBook();
		asrt( isset($book->sharedTag), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->sharedTag ), 2 ); //when loaded has 2
		$book->sharedTag = array(); //remove all
		R::store( $book );
		asrt( isset($book->sharedTag), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->sharedTag ), 0 );
	}

	/**
	 * You cannot delete a shared list by assigning NULL.
	 *
	 * @return void
	 */
	public function testCANTDeleteSharedListWithNULL()
	{
		$book = $this->_createBook();
		asrt( isset($book->sharedTag), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->sharedTag ), 2 ); //when loaded has 2
		$book->sharedTag = NULL; //remove all
		R::store( $book );
		asrt( isset($book->sharedTag), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->sharedTag ), 2 );
	}

	/**
	 * You cannot delete a shared-list by assigning FALSE.
	 *
	 * @return void
	 */
	public function testCANTDeleteSharedListWithFalse()
	{
		$book = $this->_createBook();
		asrt( isset($book->sharedTag), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->sharedTag ), 2 ); //when loaded has 2
		$book->sharedTag = FALSE; //remove all
		R::store( $book );
		asrt( isset($book->sharedTag), TRUE ); //not loaded yet, lazy loading
		asrt( $book->sharedTag, '0' );
	}

	/**
	 * You cannot delete a shared-list by unsetting it.
	 *
	 * @return void
	 */
	public function testCANTDeleteSharedWithUnset()
	{
		$book = $this->_createBook();
		asrt( isset($book->sharedTag), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->sharedTag ), 2 ); //when loaded has 2
		unset( $book->sharedTag ); //does NOT remove all
		R::store( $book );
		asrt( isset($book->sharedTag), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->sharedTag ), 2 );
	}

	/**
	 * You CAN delete a shared-list by assiging an empty array.
	 *
	 * @return void
	 */
	public function testDeleteViaSharedListWithEmptyArray()
	{
		$book = $this->_createBook();
		asrt( isset($book->via('connection')->sharedUser), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->via('connection')->sharedUser ), 1 ); //when loaded has 2
		$book->via('connection')->sharedUser = array(); //remove all
		R::store( $book );
		asrt( isset($book->via('connection')->sharedUser), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->via('connection')->sharedUser ), 0 );
	}

	/**
	 * You cannot delete a shared-list by assigning NULL.
	 *
	 * @return void
	 */
	public function testCANTDeleteViaSharedListWithNULL()
	{
		$book = $this->_createBook();
		asrt( isset($book->via('connection')->sharedUser), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->via('connection')->sharedUser ), 1 ); //when loaded has 2
		$book->via('connection')->sharedUser = NULL; //remove all
		R::store( $book );
		asrt( isset($book->via('connection')->sharedUser), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->via('connection')->sharedUser ), 1 );
	}

	/**
	 * You cannot delete a shared list by assigning FALSE.
	 *
	 * @return void
	 */
	public function testCANTDeleteViaSharedListWithFalse()
	{
		$book = $this->_createBook();
		asrt( isset($book->via('connection')->sharedUser), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->via('connection')->sharedUser ), 1 ); //when loaded has 1
		$book->via('connection')->sharedUser = FALSE; //remove all
		R::store( $book );
		asrt( isset($book->via('connection')->sharedUser), TRUE ); //not loaded yet, lazy loading
		asrt( count( $book->via('connection')->sharedUser ), 1 ); //when loaded has 1

	}

	/**
	 * You cannot delete a shared-list by unsetting it.
	 *
	 * @return void
	 */
	public function testCANTDeleteViaSharedWithUnset()
	{
		$book = $this->_createBook();
		asrt( isset($book->via('connection')->sharedUser), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->via('connection')->sharedUser ), 1 ); //when loaded has 2
		unset( $book->via('connection')->sharedUser ); //does NOT remove all
		R::store( $book );
		asrt( isset($book->via('connection')->sharedUser), FALSE ); //not loaded yet, lazy loading
		asrt( count( $book->via('connection')->sharedUser ), 1 );
	}

	/**
	 * You cannot delete a parent bean by unsetting it.
	 *
	 * @return void
	 */
	public function testYouCANTDeleteParentBeanWithUnset()
	{
		$book = $this->_createBook();
		asrt( isset($book->author), FALSE );
		asrt( (boolean) ($book->author), TRUE );
		unset( $book->author );
		R::store( $book );
		$book = $book->fresh();
		asrt( isset($book->author), FALSE );
		asrt( (boolean) ($book->author), TRUE );
	}

	/**
	 * You cannot delete a parent bean by setting it to NULL.
	 *
	 * @return void
	 */
	public function testYouCANDeleteParentBeanWithNULL()
	{
		$book = $this->_createBook();
		asrt( isset($book->author), FALSE );
		asrt( (boolean) ($book->author), TRUE );
		$book->author = NULL;
		R::store( $book );
		$book = $book->fresh();
		asrt( isset($book->author), FALSE );
		asrt( (boolean) ($book->author), FALSE );
	}

	/**
	 * You CAN delete a parent bean by setting it to FALSE.
	 *
	 * @return void
	 */
	public function testYouCANDeleteParentBeanWithFALSE()
	{
		$book = $this->_createBook();
		asrt( isset($book->author), FALSE );
		asrt( (boolean) ($book->author), TRUE );
		$book->author = FALSE;
		R::store( $book );
		$book = $book->fresh();
		asrt( isset($book->author), FALSE );
		asrt( (boolean) ($book->author), FALSE );
	}

	/**
	 * You cannot delete an aliased parent bean by unsetting it.
	 *
	 * @return void
	 */
	public function testYouCANTDeleteAliasedParentBeanWithUnset()
	{
		$book = $this->_createBook();
		asrt( isset($book->fetchAs('author')->coauthor), FALSE );
		asrt( (boolean) ($book->fetchAs('author')->coauthor), TRUE );
		unset( $book->fetchAs('author')->coauthor );
		R::store( $book );
		$book = $book->fresh();
		asrt( isset($book->fetchAs('author')->coauthor), FALSE );
		asrt( (boolean) ($book->fetchAs('author')->coauthor), TRUE );
	}

	/**
	 * You CAN delete an aliased parent bean by setting it to NULL.
	 *
	 * @return void
	 */
	public function testYouCANDeleteAliasedParentBeanWithNULL()
	{
		$book = $this->_createBook();
		asrt( isset($book->fetchAs('author')->coauthor), FALSE );
		asrt( (boolean) ($book->fetchAs('author')->coauthor), TRUE );
		$book->fetchAs('author')->coauthor = NULL;
		R::store( $book );
		$book = $book->fresh();
		asrt( isset($book->fetchAs('author')->coauthor), FALSE );
		asrt( (boolean) ($book->fetchAs('author')->coauthor), FALSE );
	}

	/**
	 * You cannot delete an aliased parent bean by setting it to FALSE.
	 *
	 * @return void
	 */
	public function testYouCANDeleteAliasedParentBeanWithFALSE()
	{
		$book = $this->_createBook();
		asrt( isset($book->fetchAs('author')->coauthor), FALSE );
		asrt( (boolean) ($book->fetchAs('author')->coauthor), TRUE );
		$book->fetchAs('author')->coauthor = FALSE;
		R::store( $book );
		$book = $book->fresh();
		asrt( isset($book->fetchAs('author')->coauthor), FALSE );
		asrt( (boolean) ($book->fetchAs('author')->coauthor), FALSE );
	}

	/**
	 * Tests the effects of unsetting on the shadow of a list.
	 *
	 * @return void
	 */
	public function testUnsettingAListAndShadow()
	{
		$book = $this->_createBook();
		//should work with ownPage and ownPageList as well...
		unset( $book->ownPageList );
		R::store( $book );
		$book = $book->fresh();
		asrt( count( $book->ownPage ), 2 );
		unset( $book->ownPage );
		//shadow should be reloaded as well...
		$book->with(' LIMIT 1 ')->ownPage;
		R::store( $book );
		$book = $book->fresh();
		asrt( count( $book->ownPage ), 2 );
		asrt( count( $book->getMeta('sys.shadow.ownPage') ), 2 );
		unset( $book->ownPage );
		asrt( $book->getMeta('sys.shadow.ownPage'), NULL );
		//no load must clear shadow as well...
		$book->noLoad()->ownPage[] = R::dispense( 'page' );
		asrt( count( $book->getMeta('sys.shadow.ownPage') ), 0 );
		R::store( $book );
		$book = $book->fresh();
		asrt( count( $book->ownPage ), 3 );
		$lists = array( 'ownPage', 'ownPageList', 'xownPage', 'xownPageList', 'sharedPage', 'sharedPageList' );
		foreach( $lists as $list ) {
			$book = R::dispense( 'book' );
			$book->$list;
			$shadowKey = $list;
			if ( strpos( $list, 'x' ) === 0) $shadowKey = substr( $shadowKey, 1 );
			$shadowKey = preg_replace( '/List$/', '', $shadowKey );
			asrt( is_array( $book->getMeta('sys.shadow.'.$shadowKey) ), TRUE );
			unset( $book->$list );
			asrt( $book->getMeta('sys.shadow.'.$shadowKey), NULL );
			$book->$list; //reloading brings back shadow
			asrt( is_array( $book->getMeta('sys.shadow.'.$shadowKey) ), TRUE );
			$book->$list = array(); //keeps shadow (very important to compare deletions!)
			asrt( is_array( $book->getMeta('sys.shadow.'.$shadowKey) ), TRUE );
			R::store( $book ); //clears shadow
			$book->alias('magazine')->$list; //reloading with alias also brings back shadow
			unset( $book->$list );
			asrt( $book->getMeta('sys.shadow.'.$shadowKey), NULL );
			$book = $book->fresh(); //clears shadow, reload
			asrt( $book->getMeta('sys.shadow.'.$shadowKey), NULL );
			$book->noLoad()->$list; //reloading with noload also brings back shadow
			asrt( is_array( $book->getMeta('sys.shadow.'.$shadowKey) ), TRUE );
			asrt( count( $book->getMeta('sys.shadow.'.$shadowKey) ), 0 );
			$book = $book->fresh(); //clears shadow, reload
			asrt( $book->getMeta('sys.shadow.'.$shadowKey), NULL );
			$book->all()->$list; //reloading with all also brings back shadow
			asrt( is_array( $book->getMeta('sys.shadow.'.$shadowKey) ), TRUE );
			$book = $book->fresh(); //clears shadow, reload
			asrt( $book->getMeta('sys.shadow.'.$shadowKey), NULL );
			$book->with(' LIMIT 1 ')->$list; //reloading with with- all also brings back shadow
			asrt( is_array( $book->getMeta('sys.shadow.'.$shadowKey) ), TRUE );
			$book = $book->fresh(); //clears shadow, reload
			asrt( $book->getMeta('sys.shadow.'.$shadowKey), NULL );
			$book->$list = array(); //keeps shadow (very important to compare deletions!)
			asrt( is_array( $book->getMeta('sys.shadow.'.$shadowKey) ), TRUE );
			$book = $book->fresh(); //clears shadow, reload
			asrt( $book->getMeta('sys.shadow.'.$shadowKey), NULL );
			$book->$list = array(); //keeps shadow (very important to compare deletions!)
			asrt( is_array( $book->getMeta('sys.shadow.'.$shadowKey) ), TRUE );
			R::trash( $book );
			asrt( $book->getMeta('sys.shadow.'.$shadowKey), NULL );
		}

		//no shadow for parent bean
		$book = $book->fresh();
		$book->author = R::dispense( 'author' );
		asrt( $book->getMeta('sys.shadow.author'), NULL );
		R::store( $book );
		$book = $book->fresh();
		unset( $book->author ); //we can unset and it does not remove
		R::store( $book );
		$book = $book->fresh();
		asrt( is_object( $book->author ),TRUE );
		//but we can also remove
		$book->author = NULL;
		R::store( $book );
		$book = $book->fresh();
		asrt( $book->author, NULL );

	}

	/**
	 * Test whether the tainted flag gets set correctly.
	 *
	 * @return void
	 */
	public function testAccessingTainting()
	{
		$book = $this->_createBook();
		asrt( $book->isTainted(), FALSE );
		$book->ownPage;
		asrt( $book->isTainted(), TRUE );
		$book = $book->fresh();
		asrt( $book->isTainted(), FALSE );
		$book->author;
		asrt( $book->isTainted(), TRUE );
		$book = $book->fresh();
		asrt( $book->isTainted(), FALSE );
		$book->fetchAs('author')->coauthor;
		asrt( $book->isTainted(), TRUE );
		$book = $book->fresh();
		asrt( $book->isTainted(), FALSE );
		$book->alias('magazine')->xownAdList;
		asrt( $book->isTainted(), TRUE );
		$book = $book->fresh();
		asrt( $book->isTainted(), FALSE );
		$book->title = 'Hello';
		asrt( $book->isTainted(), TRUE );
		$book = $book->fresh();
		asrt( $book->isTainted(), FALSE );
		$book->sharedTag;
		asrt( $book->isTainted(), TRUE );
		$book = $book->fresh();
		asrt( $book->isTainted(), FALSE );
		$book->via('connection')->sharedUser;
		asrt( $book->isTainted(), TRUE );
		$book = $book->fresh();
		asrt( $book->isTainted(), FALSE );
		$book->coauthor;
		asrt( $book->isTainted(), TRUE );
		$book = $book->fresh();
		asrt( $book->isTainted(), FALSE );
		$book->ownFakeList;
		asrt( $book->isTainted(), TRUE );
		$book = $book->fresh();
		asrt( $book->isTainted(), FALSE );
		$book->sharedFakeList;
		asrt( $book->isTainted(), TRUE );
		$book = $book->fresh();
		asrt( $book->isTainted(), FALSE );
		$book->alias('fake')->ownFakeList;
		asrt( $book->isTainted(), TRUE );
		$book = $book->fresh();
		asrt( $book->isTainted(), FALSE );
		$book->title;
		asrt( $book->isTainted(), FALSE );
		$book = $book->fresh();
		asrt( $book->isTainted(), FALSE );
		$book->title = 1;
		$book->setMeta( 'tainted', FALSE );
		asrt( $book->isTainted(), FALSE );
	}
}
