<commit_before><?php
namespace LazyRecord;
use PDO;
use PDOException;
use RuntimeException;
use Exception;
use Iterator;
use ArrayAccess;
use Countable;

use SQLBuilder\QueryBuilder;
use LazyRecord\OperationResult\OperationSuccess;
use LazyRecord\OperationResult\OperationError;
use LazyRecord\ConnectionManager;
use LazyRecord\Schema\SchemaLoader;
use SerializerKit\YamlSerializer;
use SerializerKit\XmlSerializer;
use SerializerKit\JsonSerializer;

/**
 * base collection class
 */
class BaseCollection
    implements Iterator, ArrayAccess, Countable, ExporterInterface
{
    public $schema;

    protected $_lastSql;

    protected $_vars;

    /**
     * @var SQLBuilder\QueryBuilder
     */
    public $_readQuery;

    /**
     * @var PDOStatement handle
     */
    protected $handle;


    /**
     * handle data for items
     *
     * @var array
     */ 
    protected $_itemData = null;



    /**
     * preset vars for creating
     */
    protected $_presetVars = array();


    /**
     * postCreate hook
     */
    protected $_postCreate;


    /**
     * current data item cursor position
     *
     * @var integer
     */
    protected $_itemCursor = null;



    /**
     * operation result object
     */
    protected $_result;


    protected $_alias = 'm';

    protected $explictSelect = false;


    public function __construct() {
        // init a query
        $this->schema = SchemaLoader::load( static::schema_proxy_class );
    }

    public function getSchema() {
        return $this->schema;
    }

    public function __get( $key ) {
        /**
         * lazy attributes
         */
        if( $key === '_schema' ) {
            return SchemaLoader::load( static::schema_proxy_class );
        }
        elseif( $key === '_handle' ) {
            return $this->handle ?: $this->prepareData();
        }
        elseif( $key === '_query' ) {
            return $this->_readQuery 
                    ? $this->_readQuery
                    : $this->_readQuery = $this->createQuery( 
                        $this->_schema->getReadSourceId() 
                    );
        }
        elseif( $key === '_items' ) {
            return $this->_itemData ?: $this->_readRows();
        }
    }


    /**
     * Free cached row data and result handle, 
     * But still keep the same query
     *
     * @return $this
     */
    public function free()
    {
        $this->_itemData = null;
        $this->_result = null;
        $this->_itemCursor = null;
        $this->handle = null;
        return $this;
    }


    /**
     * Dispatch undefined methods to QueryBuilder object,
     * To achieve mixin-like feature.
     */
    public function __call($m,$a)
    {
        $q = $this->_query;
        if( method_exists($q,$m) ) {
            return call_user_func_array(array($q,$m),$a);
        }
        throw new Exception("Undefined method $m");
    }

    public function getAlias()
    {
        return $this->_alias;
    }

    public function setAlias($alias)
    {
        $this->_alias = $alias;
        return $this;
    }

    public function setExplictSelect($boolean = true)
    {
        $this->explictSelect = $boolean;
        return $this;
    }


    // TODO: maybe we should move this method into RuntimeSchema.
    // Because it's used in BaseModel class too
    public function getQueryDriver( $dsId )
    {
        return ConnectionManager::getInstance()->getQueryDriver( $dsId );
    }

    public function getWriteQueryDriver()
    {
        return $this->getQueryDriver( 
            $this->schema->getWriteSourceId()
        );
    }

    public function getReadQueryDriver()
    {
        return $this->getQueryDriver( 
            $this->schema->getReadSourceId()
        );
    }


    public function createQuery( $dsId )
    {
        $q = new QueryBuilder;
        $q->driver = $this->getQueryDriver( $dsId );
        $q->table( $this->schema->table );
        $q->select(
            $this->explictSelect 
                ? $this->getExplicitColumnSelect($q->driver)
                : '*'
        );
        $q->alias( $this->getAlias() ); // main table alias
        return $q;
    }


    // xxx: this might be used in other join statements.
    public function getExplicitColumnSelect($driver)
    {
        $alias = $this->getAlias();
        return array_map(function($name) use($alias,$driver) { 
                return $alias . '.' . $driver->getQuoteColumn( $name );
        }, $this->schema->getColumnNames());
    }

    /**
     * prepare data handle, call fetch method to read data from database, and 
     * catch the handle.
     *
     * Which calls doFetch() to do a query operation.
     */
    public function prepareData($force = false)
    {
        if( ! $this->handle || $force ) {
            $this->_result = $this->fetch();
        }
        return $this->handle;
    }



    /**
     * Fetch Build sql from current query and make a query to database.
     *
     * @return OperationResult
     */
    public function fetch()
    {
        /* fetch by current query */
        $query = $this->_query;
        $this->_lastSql = $sql = $query->build();
        $this->_vars = $vars = $query->vars;
        $dsId = $this->schema->getReadSourceId();

        // XXX: here we use SQLBuilder\QueryBuilder to build our variables,
        //   but PDO doesnt accept boolean type value, we need to transform it.
        foreach( $vars as $k => & $v ) {
            if( $v === false )
                $v = 'FALSE';
            elseif( $v === true )
                $v = 'TRUE';
        }

        try {
            $this->handle = ConnectionManager::getInstance()->prepareAndExecute($dsId,$sql, $vars );
        }
        catch ( Exception $e )
        {
            return new OperationError( 'Collection fetch failed: ' . $e->getMessage() , array( 
                'vars' => $vars,
                'sql' => $sql,
                'exception' => $e,
            ));
        }
        return new OperationSuccess('Updated', array( 'sql' => $sql ));
    }


    /**
     * Clone current read query and apply select to count(*)
     * So that we can use the same conditions to query item count.
     *
     * @return int
     */
    public function queryCount()
    {
        $dsId = $this->schema->getReadSourceId();
        $q = clone $this->_query;
        $q->select( 'count(*)' ); // override current select.

        // when selecting count(*), we dont' use groupBys or order by
        $q->orders = array();
        return (int) ConnectionManager::getInstance()
                    ->prepareAndExecute($dsId,$q->build(),$q->vars)
                    ->fetchColumn();
    }


    /**
     * Get current selected item size 
     * by using php function `count`
     *
     * @return integer size
     */
    public function size()
    {
        return count($this->_items);
    }

    public function count() 
    {
        return $this->size();
    }

<commit_msg>build sql<commit_after><?php
namespace LazyRecord;
use PDO;
use PDOException;
use RuntimeException;
use Exception;
use Iterator;
use ArrayAccess;
use Countable;

use SQLBuilder\QueryBuilder;
use LazyRecord\OperationResult\OperationSuccess;
use LazyRecord\OperationResult\OperationError;
use LazyRecord\ConnectionManager;
use LazyRecord\Schema\SchemaLoader;
use SerializerKit\YamlSerializer;
use SerializerKit\XmlSerializer;
use SerializerKit\JsonSerializer;

/**
 * base collection class
 */
class BaseCollection
    implements Iterator, ArrayAccess, Countable, ExporterInterface
{
    public $schema;

    protected $_lastSql;

    protected $_vars;

    /**
     * @var SQLBuilder\QueryBuilder
     */
    public $_readQuery;

    /**
     * @var PDOStatement handle
     */
    protected $handle;


    /**
     * handle data for items
     *
     * @var array
     */ 
    protected $_itemData = null;



    /**
     * preset vars for creating
     */
    protected $_presetVars = array();


    /**
     * postCreate hook
     */
    protected $_postCreate;


    /**
     * current data item cursor position
     *
     * @var integer
     */
    protected $_itemCursor = null;



    /**
     * operation result object
     */
    protected $_result;


    protected $_alias = 'm';

    protected $explictSelect = false;


    public function __construct() {
        // init a query
        $this->schema = SchemaLoader::load( static::schema_proxy_class );
    }

    public function getSchema() {
        return $this->schema;
    }

    public function __get( $key ) {
        /**
         * lazy attributes
         */
        if( $key === '_schema' ) {
            return SchemaLoader::load( static::schema_proxy_class );
        }
        elseif( $key === '_handle' ) {
            return $this->handle ?: $this->prepareData();
        }
        elseif( $key === '_query' ) {
            return $this->_readQuery 
                    ? $this->_readQuery
                    : $this->_readQuery = $this->createQuery( 
                        $this->_schema->getReadSourceId() 
                    );
        }
        elseif( $key === '_items' ) {
            return $this->_itemData ?: $this->_readRows();
        }
    }


    /**
     * Free cached row data and result handle, 
     * But still keep the same query
     *
     * @return $this
     */
    public function free()
    {
        $this->_itemData = null;
        $this->_result = null;
        $this->_itemCursor = null;
        $this->handle = null;
        return $this;
    }


    /**
     * Dispatch undefined methods to QueryBuilder object,
     * To achieve mixin-like feature.
     */
    public function __call($m,$a)
    {
        $q = $this->_query;
        if( method_exists($q,$m) ) {
            return call_user_func_array(array($q,$m),$a);
        }
        throw new Exception("Undefined method $m");
    }

    public function getAlias()
    {
        return $this->_alias;
    }

    public function setAlias($alias)
    {
        $this->_alias = $alias;
        return $this;
    }

    public function setExplictSelect($boolean = true)
    {
        $this->explictSelect = $boolean;
        return $this;
    }


    // TODO: maybe we should move this method into RuntimeSchema.
    // Because it's used in BaseModel class too
    public function getQueryDriver( $dsId )
    {
        return ConnectionManager::getInstance()->getQueryDriver( $dsId );
    }

    public function getWriteQueryDriver()
    {
        return $this->getQueryDriver( 
            $this->schema->getWriteSourceId()
        );
    }

    public function getReadQueryDriver()
    {
        return $this->getQueryDriver( 
            $this->schema->getReadSourceId()
        );
    }


    public function createQuery( $dsId )
    {
        $q = new QueryBuilder;
        $q->driver = $this->getQueryDriver( $dsId );
        $q->table( $this->schema->table );
        $q->select(
            $this->explictSelect 
                ? $this->getExplicitColumnSelect($q->driver)
                : '*'
        );
        $q->alias( $this->getAlias() ); // main table alias
        return $q;
    }


    // xxx: this might be used in other join statements.
    public function getExplicitColumnSelect($driver)
    {
        $alias = $this->getAlias();
        return array_map(function($name) use($alias,$driver) { 
                return $alias . '.' . $driver->getQuoteColumn( $name );
        }, $this->schema->getColumnNames());
    }

    /**
     * prepare data handle, call fetch method to read data from database, and 
     * catch the handle.
     *
     * Which calls doFetch() to do a query operation.
     */
    public function prepareData($force = false)
    {
        if( ! $this->handle || $force ) {
            $this->_result = $this->fetch();
        }
        return $this->handle;
    }



    /**
     * Fetch Build sql from current query and make a query to database.
     *
     * @return OperationResult
     */
    public function fetch()
    {
        /* fetch by current query */
        $query = $this->_query;
        $this->_lastSql = $sql = $query->build();
        $this->_vars = $vars = $query->vars;
        $dsId = $this->schema->getReadSourceId();

        // XXX: here we use SQLBuilder\QueryBuilder to build our variables,
        //   but PDO doesnt accept boolean type value, we need to transform it.
        foreach( $vars as $k => & $v ) {
            if( $v === false )
                $v = 'FALSE';
            elseif( $v === true )
                $v = 'TRUE';
        }

        try {
            $this->handle = ConnectionManager::getInstance()->prepareAndExecute($dsId,$sql, $vars );
        }
        catch ( Exception $e )
        {
            return new OperationError( 'Collection fetch failed: ' . $e->getMessage() , array( 
                'vars' => $vars,
                'sql' => $sql,
                'exception' => $e,
            ));
        }
        return new OperationSuccess('Updated', array( 'sql' => $sql ));
    }


    /**
     * Clone current read query and apply select to count(*)
     * So that we can use the same conditions to query item count.
     *
     * @return int
     */
    public function queryCount()
    {
        $dsId = $this->schema->getReadSourceId();
        $q = clone $this->_query;
        $q->select( 'count(*)' ); // override current select.

        // when selecting count(*), we dont' use groupBys or order by
        $q->orders = array();
        $sql = $q->build();
        return (int) ConnectionManager::getInstance()
                    ->prepareAndExecute($dsId,$sql,$q->vars)
                    ->fetchColumn();
    }


    /**
     * Get current selected item size 
     * by using php function `count`
     *
     * @return integer size
     */
    public function size()
    {
        return count($this->_items);
    }

    public function count() 
    {
        return $this->size();
    }


{'fraction_non_alphanumeric': 0.1273967684021544, 'fraction_numerical': 0.0, 'mean_word_length': 1.8299126193863036, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 147, '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': '908431', 'n_tokens_mistral': 3981, 'n_tokens_neox': 3825, 'n_words': 1005}