<commit_before><?php

namespace Silktide\Reposition\Repository;

use Silktide\Reposition\Collection\Collection;
use Silktide\Reposition\Exception\RepositoryException;
use Silktide\Reposition\QueryBuilder\TokenSequencerInterface;
use Silktide\Reposition\QueryBuilder\QueryBuilderInterface;
use Silktide\Reposition\Storage\StorageInterface;
use Silktide\Reposition\Metadata\EntityMetadata;
use Silktide\Reposition\Metadata\EntityMetadataProviderInterface;

/**
 *
 */
abstract class AbstractRepository implements RepositoryInterface, MetadataRepositoryInterface
{

    const ANSI_DUPLICATE_KEY_ERROR_CODE = "23505";

    /**
     * @var EntityMetadata
     */
    protected $entityMetadata;

    /**
     * @var string
     */
    protected $collectionName;

    /**
     * @var string
     */
    protected $primaryKey;

    /**
     * @var QueryBuilderInterface
     */
    protected $queryBuilder;

    /**
     * @var StorageInterface
     */
    protected $storage;

    /**
     * @var EntityMetadataProviderInterface
     */
    protected $metadataProvider;

    /**
     * Flag to set if relationships are included by default
     * Alternatively, an array selecting the relationships to include by default
     *
     * @var bool|array
     */
    protected $includeRelationshipsByDefault = false;

    /**
     * @param EntityMetadata $entityMetadata
     * @param QueryBuilderInterface $queryBuilder
     * @param StorageInterface $storage
     * @param EntityMetadataProviderInterface $metadataProvider
     */
    public function __construct(EntityMetadata $entityMetadata, QueryBuilderInterface $queryBuilder, StorageInterface $storage, EntityMetadataProviderInterface $metadataProvider)
    {
        $this->entityMetadata = $entityMetadata;
        $this->queryBuilder = $queryBuilder;
        $this->storage = $storage;
        $this->metadataProvider = $metadataProvider;
        $this->configureMetadata();
    }

    /**
     * Configure the metadata for the entity this repository interacts with
     *
     * Override this method to set additional fields or define relationships with other entities
     *
     */
    protected function configureMetadata()
    {
        $this->entityMetadata->setCollection($this->collectionName);
        if (!empty($this->primaryKey)) {
            $this->entityMetadata->setPrimaryKey($this->primaryKey);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function getEntityName()
    {
        return $this->entityMetadata->getEntity();
    }

    /**
     * {@inheritDoc}
     */
    public function getCollectionName()
    {
        return $this->entityMetadata->getCollection();
    }

    /**
     * {@inheritDoc}
     */
    public function getEntityMetadata()
    {
        return $this->entityMetadata;
    }

    /**
     * {@inheritDoc}
     */
    public function find($id, $includeRelationships = null)
    {
        $query = $this->queryBuilder->find($this->entityMetadata);
        $this->addIncludes($query, $includeRelationships);
        $query->where()
            ->ref($this->getCollectionName() . "." . $this->entityMetadata->getPrimaryKey())
            ->op("=")
            ->val($id);
        return $this->doQuery($query);
    }

    /**
     * {@inheritDoc}
     */
    public function filter(array $filters, array $sort = [], $limit = 0, array $options = [], $includeRelationships = null)
    {
        $query = $this->queryBuilder->find($this->entityMetadata);
        $this->addIncludes($query, $includeRelationships);

        $this->createWhereFromFilters($query, $filters);

        if (!empty($sort)) {
            $query->sort($sort);
        }

        if (!empty($limit)) {
            $query->limit($limit);
        }

        return $this->doQuery($query);
    }

    /**
     * {@inheritDoc}
     */
    public function save($entity, array $options = [])
    {
        $query = $this->queryBuilder->save($this->entityMetadata, $options)->entity($entity);
        try {
            $saveResult = $this->doQuery($query, false);
        } catch (\PDOException $e) {
            $pkMetadata = $this->entityMetadata->getPrimaryKeyMetadata();
            // if this entity has an auto incrementing PK, or the error is not about PK conflicts, re-throw the error
            if ($pkMetadata[EntityMetadata::METADATA_FIELD_AUTO_INCREMENTING] == true || $e->errorInfo[0] != self::ANSI_DUPLICATE_KEY_ERROR_CODE) {
                throw $e;
            }

            // this is a duplicate key on a collection with a PK that does not auto increment.
            // force the save to be an update
            $query->setOption("saveType", "update");
            $saveResult = $this->doQuery($query, false);
        }

        // get the primary key value, if there is one, and save it on the entity
        if (isset($saveResult[StorageInterface::NEW_INSERT_ID_RETURN_FIELD])) {
            $pkValue = $saveResult[StorageInterface::NEW_INSERT_ID_RETURN_FIELD];
            $this->entityMetadata->setEntityValue($entity, $this->entityMetadata->getPrimaryKey(), $pkValue);
        } else {
            $pkValue = null;
        }

        // save "* to many" relationships
        foreach ($this->entityMetadata->getRelationships() as $alias => $relationship) {
            $type = $relationship[EntityMetadata::METADATA_RELATIONSHIP_TYPE];

            if ($type == EntityMetadata::RELATIONSHIP_TYPE_ONE_TO_ONE) {
                // This covers "many to one" as well
                continue;
            }

            /* We need to know the following:
             * Which child entities have been added and removed
             * The field names used on both sides of the relationship
             * The corresponding values for both the parent and child entities
             */

            // get the collection object for this relationship
            $property = $relationship[EntityMetadata::METADATA_RELATIONSHIP_PROPERTY];
            $collection = $this->entityMetadata->getEntityValue($entity, $property);
            if (!$collection instanceof Collection) {
                throw new RepositoryException("The property for the '$alias' relationship of '{$this->entityMetadata->getEntity()}' is required to be a Collection object");
            }

            // get the field used on the child entity
            $childMetadata = $this->metadataProvider->getEntityMetadata($relationship[EntityMetadata::METADATA_ENTITY]);

            $theirField = !empty($relationship[EntityMetadata::METADATA_RELATIONSHIP_THEIR_FIELD])
                ? $relationship[EntityMetadata::METADATA_RELATIONSHIP_THEIR_FIELD]
                : $childMetadata->getPrimaryKey();

            // get the field and value used on the parent entity
            $ourValue = null;
            $ourField = null;
            // if we haven't specified a field for the parent, use the primary key
            if (empty($relationship[EntityMetadata::METADATA_RELATIONSHIP_OUR_FIELD])) {
                // using the PK
                if ($pkValue !== null) {
                    // This was a new record and we already have the value for the parent PK, so set it now
                    $ourValue = $pkValue;<commit_msg>skip relationships that don't have a corresponding collection, rather than erroring<commit_after><?php

namespace Silktide\Reposition\Repository;

use Silktide\Reposition\Collection\Collection;
use Silktide\Reposition\Exception\RepositoryException;
use Silktide\Reposition\QueryBuilder\TokenSequencerInterface;
use Silktide\Reposition\QueryBuilder\QueryBuilderInterface;
use Silktide\Reposition\Storage\StorageInterface;
use Silktide\Reposition\Metadata\EntityMetadata;
use Silktide\Reposition\Metadata\EntityMetadataProviderInterface;

/**
 *
 */
abstract class AbstractRepository implements RepositoryInterface, MetadataRepositoryInterface
{

    const ANSI_DUPLICATE_KEY_ERROR_CODE = "23505";

    /**
     * @var EntityMetadata
     */
    protected $entityMetadata;

    /**
     * @var string
     */
    protected $collectionName;

    /**
     * @var string
     */
    protected $primaryKey;

    /**
     * @var QueryBuilderInterface
     */
    protected $queryBuilder;

    /**
     * @var StorageInterface
     */
    protected $storage;

    /**
     * @var EntityMetadataProviderInterface
     */
    protected $metadataProvider;

    /**
     * Flag to set if relationships are included by default
     * Alternatively, an array selecting the relationships to include by default
     *
     * @var bool|array
     */
    protected $includeRelationshipsByDefault = false;

    /**
     * @param EntityMetadata $entityMetadata
     * @param QueryBuilderInterface $queryBuilder
     * @param StorageInterface $storage
     * @param EntityMetadataProviderInterface $metadataProvider
     */
    public function __construct(EntityMetadata $entityMetadata, QueryBuilderInterface $queryBuilder, StorageInterface $storage, EntityMetadataProviderInterface $metadataProvider)
    {
        $this->entityMetadata = $entityMetadata;
        $this->queryBuilder = $queryBuilder;
        $this->storage = $storage;
        $this->metadataProvider = $metadataProvider;
        $this->configureMetadata();
    }

    /**
     * Configure the metadata for the entity this repository interacts with
     *
     * Override this method to set additional fields or define relationships with other entities
     *
     */
    protected function configureMetadata()
    {
        $this->entityMetadata->setCollection($this->collectionName);
        if (!empty($this->primaryKey)) {
            $this->entityMetadata->setPrimaryKey($this->primaryKey);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function getEntityName()
    {
        return $this->entityMetadata->getEntity();
    }

    /**
     * {@inheritDoc}
     */
    public function getCollectionName()
    {
        return $this->entityMetadata->getCollection();
    }

    /**
     * {@inheritDoc}
     */
    public function getEntityMetadata()
    {
        return $this->entityMetadata;
    }

    /**
     * {@inheritDoc}
     */
    public function find($id, $includeRelationships = null)
    {
        $query = $this->queryBuilder->find($this->entityMetadata);
        $this->addIncludes($query, $includeRelationships);
        $query->where()
            ->ref($this->getCollectionName() . "." . $this->entityMetadata->getPrimaryKey())
            ->op("=")
            ->val($id);
        return $this->doQuery($query);
    }

    /**
     * {@inheritDoc}
     */
    public function filter(array $filters, array $sort = [], $limit = 0, array $options = [], $includeRelationships = null)
    {
        $query = $this->queryBuilder->find($this->entityMetadata);
        $this->addIncludes($query, $includeRelationships);

        $this->createWhereFromFilters($query, $filters);

        if (!empty($sort)) {
            $query->sort($sort);
        }

        if (!empty($limit)) {
            $query->limit($limit);
        }

        return $this->doQuery($query);
    }

    /**
     * {@inheritDoc}
     */
    public function save($entity, array $options = [])
    {
        $query = $this->queryBuilder->save($this->entityMetadata, $options)->entity($entity);
        try {
            $saveResult = $this->doQuery($query, false);
        } catch (\PDOException $e) {
            $pkMetadata = $this->entityMetadata->getPrimaryKeyMetadata();
            // if this entity has an auto incrementing PK, or the error is not about PK conflicts, re-throw the error
            if ($pkMetadata[EntityMetadata::METADATA_FIELD_AUTO_INCREMENTING] == true || $e->errorInfo[0] != self::ANSI_DUPLICATE_KEY_ERROR_CODE) {
                throw $e;
            }

            // this is a duplicate key on a collection with a PK that does not auto increment.
            // force the save to be an update
            $query->setOption("saveType", "update");
            $saveResult = $this->doQuery($query, false);
        }

        // get the primary key value, if there is one, and save it on the entity
        if (isset($saveResult[StorageInterface::NEW_INSERT_ID_RETURN_FIELD])) {
            $pkValue = $saveResult[StorageInterface::NEW_INSERT_ID_RETURN_FIELD];
            $this->entityMetadata->setEntityValue($entity, $this->entityMetadata->getPrimaryKey(), $pkValue);
        } else {
            $pkValue = null;
        }

        // save "* to many" relationships
        foreach ($this->entityMetadata->getRelationships() as $alias => $relationship) {
            $type = $relationship[EntityMetadata::METADATA_RELATIONSHIP_TYPE];

            if ($type == EntityMetadata::RELATIONSHIP_TYPE_ONE_TO_ONE) {
                // This covers "many to one" as well
                continue;
            }

            /* We need to know the following:
             * Which child entities have been added and removed
             * The field names used on both sides of the relationship
             * The corresponding values for both the parent and child entities
             */

            // get the collection object for this relationship
            $property = $relationship[EntityMetadata::METADATA_RELATIONSHIP_PROPERTY];
            $collection = $this->entityMetadata->getEntityValue($entity, $property);
            if (!$collection instanceof Collection) {
                // if this isn't a collection object we can't process this relationship
                continue;
            }

            // get the field used on the child entity
            $childMetadata = $this->metadataProvider->getEntityMetadata($relationship[EntityMetadata::METADATA_ENTITY]);

            $theirField = !empty($relationship[EntityMetadata::METADATA_RELATIONSHIP_THEIR_FIELD])
                ? $relationship[EntityMetadata::METADATA_RELATIONSHIP_THEIR_FIELD]
                : $childMetadata->getPrimaryKey();

            // get the field and value used on the parent entity
            $ourValue = null;
            $ourField = null;
            // if we haven't specified a field for the parent, use the primary key
            if (empty($relationship[EntityMetadata::METADATA_RELATIONSHIP_OUR_FIELD])) {
                // using the PK
                if ($pkValue !== null) {
                    // This was a new record and we already have the value for the parent PK, so set it now
                    $ourValue = $pkValue;
{'fraction_non_alphanumeric': 0.11090290790478173, 'fraction_numerical': 0.0009716149628704282, 'mean_word_length': 2.4581233501319892, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 123, 'https://': 0, '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': '1672710', 'n_tokens_mistral': 3784, 'n_tokens_neox': 3630, 'n_words': 910}