<commit_before><?php
namespace ChrisAndChris\Common\RowMapperBundle\Services\Model\Mapping;

use ChrisAndChris\Common\RowMapperBundle\Command\DatabaseMapperCommand;
use ChrisAndChris\Common\RowMapperBundle\Entity\Mapping\Field;
use ChrisAndChris\Common\RowMapperBundle\Entity\Mapping\Relation;
use ChrisAndChris\Common\RowMapperBundle\Exceptions\Mapping\MappingInitFailedException;
use ChrisAndChris\Common\RowMapperBundle\Exceptions\Mapping\NoPrimaryKeyFoundException;
use ChrisAndChris\Common\RowMapperBundle\Exceptions\Mapping\NoSuchColumnException;
use ChrisAndChris\Common\RowMapperBundle\Exceptions\Mapping\NoSuchTableException;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;

/**
 * @name MappingHandler
 * @version    1.0.1
 * @since      v2.1.0
 * @package    RowMapperBundle
 * @author     ChrisAndChris
 * @link       https://github.com/chrisandchris
 */
class MappingRepository
{

    /** @var \stdClass */
    private $mapping;
    /** @var DatabaseMapperCommand */
    private $databaseMapper;

    public function __construct($cacheDir, $dir, $filename = 'mapping.json', DatabaseMapperCommand $command = null)
    {
        $this->databaseMapper = $command;
        $this->setMapping($cacheDir . '/' . $dir . '/' . basename($filename));
    }

    public function setMapping($mapping, $forceException = false)
    {
        if (is_file($mapping)) {
            $mapping = file_get_contents($mapping);
        } else {
            if ($this->databaseMapper instanceof DatabaseMapperCommand && !$forceException) {
                $this->runMapper();
                $this->setMapping($mapping, true);
            }
            throw new MappingInitFailedException(sprintf(
                'No file found at path "%s"',
                $mapping
            ));
        }
        $this->mapping = json_decode($mapping, true);
    }

    private function runMapper()
    {
        $this->databaseMapper->run(new ArrayInput([]), new NullOutput());
    }

    /**
     * @param string       $table   the table to check for
     * @param array|string $columns a single column or an array of columns
     * @return void
     * @throws NoSuchColumnException
     * @throws NoSuchTableException
     */
    public function hasColumns($table, $columns)
    {
        $this->hasTable($table);

        if (!is_array($columns)) {
            $columns = [$columns];
        }

        foreach ($columns as $column) {
            if (!isset($this->mapping[$table]['fields'][$column])) {
                throw new NoSuchColumnException(
                    sprintf(
                        'No column "%s" for table "%s" found',
                        $table,
                        $column
                    )
                );
            }
        }
    }

    /**
     * @param $table
     * @return void
     * @throws NoSuchTableException
     */
    public function hasTable($table)
    {
        if (!isset($this->mapping[$table])) {
            throw new NoSuchTableException(sprintf(
                'No table named "%s" found',
                $table
            ));
        }
    }

    public function getRelations($table)
    {
        $this->hasTable($table);

        return $this->getRecursiveRelations($table, 1, false);
    }

    /**
     * @param      $table
     * @param int  $deepness
     * @param bool $withAliases
     * @return Relation[] an array of relations
     */
    public function getRecursiveRelations($table, $deepness = 1, $withAliases = true)
    {
        $relations = $this->buildRecursiveRelations($table, $deepness, $withAliases);

        foreach ($this->mapping as $mappedTable => $mapping) {
            if (!isset($mapping['relations'])) {
                break;
            }
            foreach ($mapping['relations'] as $relation) {
                if ($relation['target'][0] == $table) {
                    if ($this->getTableIndex($mappedTable, false)) {
                        break;
                    }
                    $entity = new Relation();
                    $entity->source = $table;
                    $entity->target = $mappedTable;
                    $entity->alias = $this->getTableAlias($entity);
                    $entity->sourceField = $relation['target'][1];
                    $entity->targetField = $relation['source'];
                    $relations[] = $entity;
                }
            }
        }

        return $relations;
    }

    private function buildRecursiveRelations($table, $deepness, $withAliases)
    {
        if ($deepness === 0) {
            return [];
        }

        if (is_array($table)) {
            list ($table, $alias) = $table;<commit_msg>fixed a bug causing extremely expensive queries<commit_after><?php
namespace ChrisAndChris\Common\RowMapperBundle\Services\Model\Mapping;

use ChrisAndChris\Common\RowMapperBundle\Command\DatabaseMapperCommand;
use ChrisAndChris\Common\RowMapperBundle\Entity\Mapping\Field;
use ChrisAndChris\Common\RowMapperBundle\Entity\Mapping\Relation;
use ChrisAndChris\Common\RowMapperBundle\Exceptions\Mapping\MappingInitFailedException;
use ChrisAndChris\Common\RowMapperBundle\Exceptions\Mapping\NoPrimaryKeyFoundException;
use ChrisAndChris\Common\RowMapperBundle\Exceptions\Mapping\NoSuchColumnException;
use ChrisAndChris\Common\RowMapperBundle\Exceptions\Mapping\NoSuchTableException;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;

/**
 * @name MappingHandler
 * @version    1.0.1
 * @since      v2.1.0
 * @package    RowMapperBundle
 * @author     ChrisAndChris
 * @link       https://github.com/chrisandchris
 */
class MappingRepository
{

    /** @var \stdClass */
    private $mapping;
    /** @var DatabaseMapperCommand */
    private $databaseMapper;

    public function __construct($cacheDir, $dir, $filename = 'mapping.json', DatabaseMapperCommand $command = null)
    {
        $this->databaseMapper = $command;
        $this->setMapping($cacheDir . '/' . $dir . '/' . basename($filename));
    }

    public function setMapping($mapping, $forceException = false)
    {
        if (is_file($mapping)) {
            $mapping = file_get_contents($mapping);
        } else {
            if ($this->databaseMapper instanceof DatabaseMapperCommand && !$forceException) {
                $this->runMapper();
                $this->setMapping($mapping, true);
            }
            throw new MappingInitFailedException(sprintf(
                'No file found at path "%s"',
                $mapping
            ));
        }
        $this->mapping = json_decode($mapping, true);
    }

    private function runMapper()
    {
        $this->databaseMapper->run(new ArrayInput([]), new NullOutput());
    }

    /**
     * @param string       $table   the table to check for
     * @param array|string $columns a single column or an array of columns
     * @return void
     * @throws NoSuchColumnException
     * @throws NoSuchTableException
     */
    public function hasColumns($table, $columns)
    {
        $this->hasTable($table);

        if (!is_array($columns)) {
            $columns = [$columns];
        }

        foreach ($columns as $column) {
            if (!isset($this->mapping[$table]['fields'][$column])) {
                throw new NoSuchColumnException(
                    sprintf(
                        'No column "%s" for table "%s" found',
                        $table,
                        $column
                    )
                );
            }
        }
    }

    /**
     * @param $table
     * @return void
     * @throws NoSuchTableException
     */
    public function hasTable($table)
    {
        if (!isset($this->mapping[$table])) {
            throw new NoSuchTableException(sprintf(
                'No table named "%s" found',
                $table
            ));
        }
    }

    public function getRelations($table)
    {
        $this->hasTable($table);

        return $this->getRecursiveRelations($table, 1, false);
    }

    /**
     * @param      $table
     * @param int  $deepness
     * @param bool $withAliases
     * @return Relation[] an array of relations
     */
    public function getRecursiveRelations($table, $deepness = 1, $withAliases = true)
    {
        $relations = $this->buildRecursiveRelations($table, $deepness, $withAliases);

        foreach ($this->mapping as $mappedTable => $mapping) {
            // @todo improve this, there might be a better solution to prevent incredibly long queries
            if (!isset($mapping['relations']) || count($relations) > 5) {
                break;
            }
            foreach ($mapping['relations'] as $relation) {
                if ($relation['target'][0] == $table) {
                    if ($this->getTableIndex($mappedTable, false)) {
                        break;
                    }
                    $entity = new Relation();
                    $entity->source = $table;
                    $entity->target = $mappedTable;
                    $entity->alias = $this->getTableAlias($entity);
                    $entity->sourceField = $relation['target'][1];
                    $entity->targetField = $relation['source'];
                    $relations[] = $entity;
                }
            }
        }

        return $relations;
    }

    private function buildRecursiveRelations($table, $deepness, $withAliases)
    {
        if ($deepness === 0) {
            return [];
        }

        if (is_array($table)) {
            list ($table, $alias) = $table;
{'fraction_non_alphanumeric': 0.12239366138448707, 'fraction_numerical': 0.0023978315262718933, 'mean_word_length': 2.005325814536341, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 50, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6545145', 'n_tokens_mistral': 2607, 'n_tokens_neox': 2487, 'n_words': 525}