<commit_before><?php
namespace Automatorm\Orm;

use Automatorm\Exception;
use Automatorm\Database\SqlString;

class Data
{
    protected $__data = [];           // Data from columns on this table
    protected $__external = [];       // Links to foreign key objects
    
    protected $__update = [];         // Keys to be updated
    protected $__updateExternal = []; // Foreign keys to be updated
    
    protected $__schema;              // Schema object for this database
    protected $__namespace;           // Namespace of the Model for this data - used to find Schema again
    protected $__table;               // Class this data is associated with
    protected $__model;               // Fragment of Schema object for this table
    
    protected $__locked = true;       // Can we use __set() - for updates/inserts
    protected $__new = false;         // Is this to be a new row? (used with Model::new_db())
    protected $__delete = false;      // Row is marked for deletion
    
    public function __construct(array $data, $table, Schema $schema, $new = false)
    {
        $this->__table = $table;
        $this->__schema = $schema;
        $this->__namespace = $schema->namespace;
        $this->__model = $schema->getTable($table);
        $this->__new = $new;
        $this->__locked = !$new;
        
        $this->updateData($data);
    }
    
    public function updateData($data)
    {
        $this->__data = [];
        
        // Pull in data from $data
        foreach ($data as $key => $value) {
            // Make a special object for dates
            if (!is_null($value) and (
                $this->__model['columns'][$key] == 'datetime'
                or $this->__model['columns'][$key] == 'timestamp'
                or $this->__model['columns'][$key] == 'date'
            )) {
                $this->__data[$key] = new \DateTimeImmutable($value, new \DateTimeZone('UTC'));
            } else {
                $this->__data[$key] = $value;
            }
        }
    }

    // Generally used when this class is accessed through $modelobject->db()
    // This returns an 'unlocked' version of this object that can be used to modify the database row.
    public function __clone()
    {
        $this->__locked = false;
        $this->__external = array();
    }
    
    /**
     * Create a open cloned copy of this object, ready to reinsert as a new row.
     *
     * @param bool $cloneExternalProps Clone M2M properties as well
     * @return self
     */
    public function duplicate($cloneExternalProps = false)
    {
        $clone = clone $this;
        $clone->__new = true;
        $clone->__delete = false;
        unset($clone->__data['id']);
        foreach (array_keys($clone->__data) as $key) {
            $clone->__update[$key] = true;
        }
        
        // Clone M-M joins
        if ($cloneExternalProps) {
            foreach (array_keys($this->__model['many-to-many']) as $key) {
                $clone->__external[$key] = $this->{$key};
                $clone->__updateExternal[$key] = true;
            }
        }
        
        return $clone;
    }

    /**
     * Mark data object for deletion when commited
     *
     * @return self
     */
    public function delete()
    {
        if ($this->__new) {
            throw new Exception\Model('MODEL_DATA:CANNOT_DELETE_UNCOMMITED_DATA');
        }
        $this->__delete = true;
        return $this;
    }
    
    // Accessor method for object properties (columns from the db)
    public function &__get($var)
    {
        /* This property is a native database column, return it */
        if (isset($this->__data[$var])) {
            return $this->__data[$var];
        }
        
        /* This property has already been defined, return it */
        if (isset($this->__external[$var])) {
            $data = $this->__external[$var];
            
            // If this data object isn't locked, it's likely being used for a insert/update.
            // If ->var contains a Collection, this must be a M2M relationship - mark it for update
            if (!$this->__locked && $data instanceof Collection) {
                $this->__updateExternal[$var] = true;
            }
            
            return $data;
        }
        
        /* This property hasn't been defined, so it's not one of the table columns.
         * We want to look at foreign keys and pivots
         * 
         * If we try to access a foreign key column without the _id on the end assume we want the object, not the id
         * From example at the top: $proj->account_id returns 1      $proj->account returns Account object with id 1
         */
        
        try {
            $data = $this->join($var);
            
            // If this Data object isn't locked, it's likely being used for a insert/update.
            // If ->var contains a Collection, this must be a M2M relationship - mark it for update
            if (!$this->__locked && $data instanceof Collection) {
                $this->__updateExternal[$var] = true;
            }
            
            return $data;
        } catch (Exception\Model $e) {
            if ($e->code == 'MODEL_DATA:UNKNOWN_FOREIGN_PROPERTY') {
                return null;
            }
            throw $e;
        }
    }
    
    public static function groupJoin(Collection $collection, $var, $where = [], $onlyCount = false)
    {
        if (!$collection->count()) {
            return $collection;
        }
        
        /* FOREIGN KEYS */
        if (key_exists($var, $proto->__model['one-to-one'])) {
            return static::groupJoin121($collection, $var, $where, $onlyCount);
        }
        
        if (key_exists($var, $proto->__model['many-to-one'])) {
            return static::groupJoinM21($collection, $var, $where, $onlyCount);
        }
        
        if (key_exists($var, $proto->__model['one-to-many'])) {
            return static::groupJoin12M($collection, $var, $where, $onlyCount);
        }
        
        if (key_exists($var, $proto->__model['many-to-many'])) {
            return static::groupJoinM2M($collection, $var, $where, $onlyCount);
        }
        
        throw new Exception\Model('MODEL:CALLED_GROUP_JOIN_ON_UNKNOWN_FOREIGN_PROPERTY', [$var, $collection]);
    }

    protected static function groupJoin121(Collection $collection, $var, $where, $countOnly = false)
    {
        $proto = $collection[0]->_data;
        $ids = $collection->id->toArray();
        
        /* Call Tablename::factory(foreign key id) to get the object we want */
        $table = $proto->__model['one-to-one'][$var]['table'];
        $schema = Schema::getSchemaByName($proto->__model['one-to-one'][$var]['schema']);
        
        if ($countOnly) {
            list($data) = static::factoryDataCount(['id' => $ids] + $where, $table, $schema);
            return $data['count'];
        }
        
        return Model::factoryObjectCache($ids, $table, $schema);
    }

    protected static function groupJoinM21(Collection $collection, $var, $where, $countOnly = false)
    {
        $proto = $collection[0]->_data;<commit_msg>Removing Exception (for now) when calling non-existant property on Collections<commit_after><?php
namespace Automatorm\Orm;

use Automatorm\Exception;
use Automatorm\Database\SqlString;

class Data
{
    protected $__data = [];           // Data from columns on this table
    protected $__external = [];       // Links to foreign key objects
    
    protected $__update = [];         // Keys to be updated
    protected $__updateExternal = []; // Foreign keys to be updated
    
    protected $__schema;              // Schema object for this database
    protected $__namespace;           // Namespace of the Model for this data - used to find Schema again
    protected $__table;               // Class this data is associated with
    protected $__model;               // Fragment of Schema object for this table
    
    protected $__locked = true;       // Can we use __set() - for updates/inserts
    protected $__new = false;         // Is this to be a new row? (used with Model::new_db())
    protected $__delete = false;      // Row is marked for deletion
    
    public function __construct(array $data, $table, Schema $schema, $new = false)
    {
        $this->__table = $table;
        $this->__schema = $schema;
        $this->__namespace = $schema->namespace;
        $this->__model = $schema->getTable($table);
        $this->__new = $new;
        $this->__locked = !$new;
        
        $this->updateData($data);
    }
    
    public function updateData($data)
    {
        $this->__data = [];
        
        // Pull in data from $data
        foreach ($data as $key => $value) {
            // Make a special object for dates
            if (!is_null($value) and (
                $this->__model['columns'][$key] == 'datetime'
                or $this->__model['columns'][$key] == 'timestamp'
                or $this->__model['columns'][$key] == 'date'
            )) {
                $this->__data[$key] = new \DateTimeImmutable($value, new \DateTimeZone('UTC'));
            } else {
                $this->__data[$key] = $value;
            }
        }
    }

    // Generally used when this class is accessed through $modelobject->db()
    // This returns an 'unlocked' version of this object that can be used to modify the database row.
    public function __clone()
    {
        $this->__locked = false;
        $this->__external = array();
    }
    
    /**
     * Create a open cloned copy of this object, ready to reinsert as a new row.
     *
     * @param bool $cloneExternalProps Clone M2M properties as well
     * @return self
     */
    public function duplicate($cloneExternalProps = false)
    {
        $clone = clone $this;
        $clone->__new = true;
        $clone->__delete = false;
        unset($clone->__data['id']);
        foreach (array_keys($clone->__data) as $key) {
            $clone->__update[$key] = true;
        }
        
        // Clone M-M joins
        if ($cloneExternalProps) {
            foreach (array_keys($this->__model['many-to-many']) as $key) {
                $clone->__external[$key] = $this->{$key};
                $clone->__updateExternal[$key] = true;
            }
        }
        
        return $clone;
    }

    /**
     * Mark data object for deletion when commited
     *
     * @return self
     */
    public function delete()
    {
        if ($this->__new) {
            throw new Exception\Model('MODEL_DATA:CANNOT_DELETE_UNCOMMITED_DATA');
        }
        $this->__delete = true;
        return $this;
    }
    
    // Accessor method for object properties (columns from the db)
    public function &__get($var)
    {
        /* This property is a native database column, return it */
        if (isset($this->__data[$var])) {
            return $this->__data[$var];
        }
        
        /* This property has already been defined, return it */
        if (isset($this->__external[$var])) {
            $data = $this->__external[$var];
            
            // If this data object isn't locked, it's likely being used for a insert/update.
            // If ->var contains a Collection, this must be a M2M relationship - mark it for update
            if (!$this->__locked && $data instanceof Collection) {
                $this->__updateExternal[$var] = true;
            }
            
            return $data;
        }
        
        /* This property hasn't been defined, so it's not one of the table columns.
         * We want to look at foreign keys and pivots
         * 
         * If we try to access a foreign key column without the _id on the end assume we want the object, not the id
         * From example at the top: $proj->account_id returns 1      $proj->account returns Account object with id 1
         */
        
        try {
            $data = $this->join($var);
            
            // If this Data object isn't locked, it's likely being used for a insert/update.
            // If ->var contains a Collection, this must be a M2M relationship - mark it for update
            if (!$this->__locked && $data instanceof Collection) {
                $this->__updateExternal[$var] = true;
            }
            
            return $data;
        } catch (Exception\Model $e) {
            if ($e->code == 'MODEL_DATA:UNKNOWN_FOREIGN_PROPERTY') {
                return null;
            }
            throw $e;
        }
    }
    
    public static function groupJoin(Collection $collection, $var, $where = [], $onlyCount = false)
    {
        if (!$collection->count()) {
            return $collection;
        }
        
        /* FOREIGN KEYS */
        if (key_exists($var, $proto->__model['one-to-one'])) {
            return static::groupJoin121($collection, $var, $where, $onlyCount);
        }
        
        if (key_exists($var, $proto->__model['many-to-one'])) {
            return static::groupJoinM21($collection, $var, $where, $onlyCount);
        }
        
        if (key_exists($var, $proto->__model['one-to-many'])) {
            return static::groupJoin12M($collection, $var, $where, $onlyCount);
        }
        
        if (key_exists($var, $proto->__model['many-to-many'])) {
            return static::groupJoinM2M($collection, $var, $where, $onlyCount);
        }
        
        #throw new Exception\Model('MODEL:CALLED_GROUP_JOIN_ON_UNKNOWN_FOREIGN_PROPERTY', [$var, $collection]);
        return new Collection();
    }

    protected static function groupJoin121(Collection $collection, $var, $where, $countOnly = false)
    {
        $proto = $collection[0]->_data;
        $ids = $collection->id->toArray();
        
        /* Call Tablename::factory(foreign key id) to get the object we want */
        $table = $proto->__model['one-to-one'][$var]['table'];
        $schema = Schema::getSchemaByName($proto->__model['one-to-one'][$var]['schema']);
        
        if ($countOnly) {
            list($data) = static::factoryDataCount(['id' => $ids] + $where, $table, $schema);
            return $data['count'];
        }
        
        return Model::factoryObjectCache($ids, $table, $schema);
    }

    protected static function groupJoinM21(Collection $collection, $var, $where, $countOnly = false)
    {
        $proto = $collection[0]->_data;
{'fraction_non_alphanumeric': 0.12678211280339383, 'fraction_numerical': 0.002781834619931845, 'mean_word_length': 1.8910333735424205, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 115, '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': '459933', 'n_tokens_mistral': 3989, 'n_tokens_neox': 3919, 'n_words': 1143}