<?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 NULL 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 );
	}
}
