<commit_before><?php
namespace Core\Lib\Data\Adapter\Db;

use Core\Lib\Data\Container;
use Core\Lib\Traits\ArrayTrait;
use Core\Lib\Traits\ConvertTrait;
use Core\Lib\Errors\Exceptions\InvalidArgumentException;
use Core\Lib\Errors\Exceptions\UnexpectedValueException;

/**
 * QueryBuilder.php
 *
 * @author Michael "Tekkla" Zorn <<EMAIL>>
 * @copyright 2015
 * @license MIT
 */
class QueryBuilder
{

    use ArrayTrait;
    use ConvertTrait;

    private $method;

    private $definition;

    private $tbl;

    private $alias = '';

    private $fields = [];

    private $values = [];

    private $join = [];

    private $filter = '';

    private $params = [];

    private $order = '';

    private $group_by = '';

    private $having = '';

    private $sql = '';

    /**
     *
     * @var array
     */
    private $limit = [];

    /**
     * Constructor
     *
     * @param array $definition Optional QueryDefinition
     */
    public function __construct(Array $definition = [])
    {
        if ($definition) {
            $this->processQueryDefinition($definition);
        }
    }

    /**
     * SELECT statement
     *
     * @param array|string $fields Fieldlist as comma seperated string or value array.
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function Select($fields = '')
    {
        $this->method = 'SELECT';

        $this->Columns($fields);

        return $this;
    }

    /**
     * SELECT DISTINCT statement
     *
     * @param array|string $fields Fieldlist as comma seperated string or value array.
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function SelectDistinct($fields = '')
    {
        $this->method = 'SELECT DISTINCT';

        $this->Columns($fields);

        return $this;
    }

    /**
     * INSERT INTO statement
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function InsertInto()
    {
        $this->method = 'INSERT INTO';

        return $this;
    }

    /**
     * INTO statement
     *
     * @param string $tbl Name of table
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function Into($tbl)
    {
        $this->From($tbl);

        return $this;
    }

    /**
     * DELETE statement
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function Delete()
    {
        $this->method = 'DELETE';

        return $this;
    }

    /**
     * Colums to use in query
     *
     * @param array|string $fields Fieldlist as comma seperated string or value array.
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function Columns($columns = '')
    {
        if (empty($columns)) {
            return;
        }

        if (! is_array($columns)) {
            $this->fields = explode(',', $columns);
        }

        $this->fields = array_map('trim', $this->fields);

        return $this;
    }

    /**
     * From statement
     *
     * @param string $tbl Table name
     * @param string $alias Optional: Table alias
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function From($tbl, $alias = '')
    {
        $this->tbl = $tbl;

        if ($alias) {
            $this->alias = $alias;
        }

        return $this;
    }

    /**
     * Filter statement
     *
     * @param string $filter Filterstring
     * @param array $params Optional: Paramenter list
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function Where($filter, $params = [])
    {
        $this->filter;
        $this->params = $params;

        return $this;
    }

    /**
     * Order statement
     *
     * @param string $order Orderstring
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function Order($order)
    {
        $this->order = $order;

        return $this;
    }

    /**
     * Join statement
     *
     * @param string $tbl Table name of table to join
     * @param string $as Alias of join table
     * @param string $by How to join
     * @param string $condition Join condition
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function Join($tbl, $as, $by, $condition)
    {
        $this->join[] = [
            'tbl' => $tbl,
            'as' => $as,
            'by' => $by,
            'cond' => $condition
        ];

        return $this;
    }

    /**
     * GroupBy statement.
     *
     * @param string|array field name or list of field names as array
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function GroupBy($field)
    {
        if (is_array($field)) {
            $val = implode(', ', $field);
        }

        $this->group_by = $val;
        return $this;
    }

    public function Having()
    {}

    /**
     * Limit statement.
     *
     * @param int $lower Lower limit
     * @param int $upper Optional: Upper limit
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function Limit($lower, $upper = null)
    {
        $this->limit['lower'] = (int) $lower;

        if (isset($upper)) {
            $this->limit['upper'] = (int) $upper;
        }

        return $this;
    }

    /**
     * Buildss the sql string for select queries.
     *
     * @return string Sql string
     */
    public function build()
    {
        $join = '';
        $filter = '';
        $order = '';
        $limit = '';

        // Create the fieldprefix. If given as alias use this, otherwise we use the tablename
        $field_prefifx = $this->alias ? $this->alias : '{db_prefix}' . $this->tbl;

        // Biuld joins
        if ($this->join) {
            $tmp = [];

            foreach ($this->join as $def) {
                $tmp[] = ' ' . $def['by'] . ' JOIN ' . '{db_prefix}' . (isset($def['as']) ? $def['tbl'] . ' AS ' . $def['as'] : $def['join']) . ' ON (' . $def['cond'] . ')';
            }
        }

        $join = isset($tmp) ? implode(' ', $tmp) : '';

        // Create fieldlist
        if ($this->fields) {

            if (! is_array($this->fields)) {
                $this->fields = (array) $this->fields;
            }

            // Add `` to some field names as reaction to those stupid developers who chose systemnames as fieldnames
            foreach ($this->fields as $key_field => $field) {

                // Subquery?
                if (is_array($field)) {
                    $builder = new QueryBuilder($this->sql);
                    $field = '(' . PHP_EOL . $builder->build() . PHP_EOL . ')';
                }

                $fields_to_quote = [
                    'date',
                    'time'
                ];

                if (in_array($field, $fields_to_quote)) {
                    $field = '`' . $field . '`';
                }

                // Extend fieldname either by table alias or name when no dot as alias/table indicator is found.
                // if (strpos($field, '.') === false) {
                // $field .= ($this->alias ? $this->alias : $this->tbl) . '.' . $field;
                // }

                $this->fields[$key_field] = $field;
            }

            $fieldlist = implode(', ', $this->fields);
        }
        else {
            $fieldlist = ($this->alias ? $this->alias : $this->tbl) . '.*';
        }

        // Create filterstatement
        $filter = $this->filter ? ' WHERE ' . $this->filter : '';

        // Create group by statement
        $group_by = '';

        if ($this->group_by) {

            if (is_array($this->group_by)) {
                $this->group_by = implode(', ', $this->group_by);
            }

            $group_by = ' GROUP BY ' . $this->group_by;
        }

        // Create having statement
        $having = $this->having ? ' HAVING ' . $this->having : '';

        // Create order statement
        $order = $this->order ? ' ORDER BY ' . $this->order : '';

        // Create limit statement
        if ($this->limit) {

            $limit = ' LIMIT ';

            if (isset($this->limit['lower'])) {
                $limit .= $this->limit['lower'];
            }

            if (isset($this->limit['lower']) && isset($this->limit['upper'])) {
                $limit .= ',' . $this->limit['upper'];
            }
        }

        // We need a string for the table. if there is an alias, we have to set it
        $tbl = $this->alias ? $this->tbl . ' AS ' . $this->alias : $this->tbl;

        switch ($this->method) {

            case 'UPDATE':

                // we have to build our own fieldlist
                $fields = [];

                foreach ($this->fields as $field) {
                    $fields[] = $field . '=:' . $field;
                }

                $fieldlist = implode(',', $fields);

                $this->sql = $this->method . ' {db_prefix}' . $tbl . ' SET ' . $fieldlist . $filter;

                break;

            case 'INSERT':
            case 'REPLACE':
                $fieldlist = ! $fieldlist || $fieldlist == '*' ? '' : ' (' . $fieldlist . ')';

                // Build values
                $values = [];

                foreach ($this->fields as $field) {
                    $values[] = ':' . $field;
                }

                $values = implode(', ', $values);

                $this->sql = $this->method . ' INTO {db_prefix}' . $tbl . $fieldlist . ' VALUES (' . $values . ')';
                break;

            case 'DELETE':
                $this->sql = $this->method . ' FROM {db_prefix}' . $tbl . $filter . $order . $limit;
                break;

            default:
                $this->sql = $this->method . ' ' . $fieldlist . ' FROM {db_prefix}' . $tbl . $join . $filter . $group_by . $having . $order . $limit;
                break;
        }

        // Finally cleanup parameters by removing parameter not needed in query and parse array parameter into sql string.
        foreach ($this->params as $key => $val) {

            // Do cleanup
            if (strpos($this->sql, $key) === false) {
                unset($this->params[$key]);
                continue;
            }

            // Replace array parameter against sql valid part
            if (is_array($val)) {
                $prepared = $this->prepareArrayQuery($key, $val);
            }
        }

        return $this->sql;
    }

    private function processQueryDefinition($def)
    {
        // Store defintion
        $this->definition = $def;

        // Use set query type or use 'row' as default
        if (isset($this->definition['method'])) {
            $this->method = strtoupper($this->definition['method']);
            unset($this->definition['method']);
        }
        else {
            $this->method = 'SELECT';
        }

        // All methods need the table defintion
        $this->processTblDefinition();

        // Process data definition?
        if (isset($this->definition['data'])) {
            $this->processDataDefinition();
        }

        switch ($this->method) {

            case 'INSERT':
            case 'REPLACE':
                $this->processInsert();
                break;

            case 'UPDATE':
                $this->processUpdate();
                break;

            case 'DELETE':
                $this->processDelete();
                break;

            default:
                $this->processSelect();
                break;
        }

        return $this;
    }

    private function processSelect()
    {
        $this->processTblDefinition();

        $this->processFieldDefinition();

        $this->processFilterDefinition();

        $this->processParamsDefinition();

        $this->processGroupByDefinition();

        $this->processHavingDefinition();

        $this->processJoinDefinition();

        $this->processOrderDefinition();

        $this->processLimitDefinition();
    }

    /**
     * Processes insert definition
     *
     * @throws UnexpectedValueException
     */
    private function processInsert()
    {
        if (! isset($this->definition['fields']) && ! isset($this->definition['field'])) {
            Throw new UnexpectedValueException('QueryBuilder need a "field" or "fields" list element to process "INSERT" definition.');
        }

        if (! isset($this->definition['params'])) {
            Throw new UnexpectedValueException('QueryBuilder need a assoc array param list to process "INSERT" definition.');
        }

        $this->processFieldDefinition();
        $this->processParamsDefinition();
    }

    /**
     *
     * @throws UnexpectedValueException
     */
    private function processUpdate()
    {
        if (! isset($this->definition['fields']) && ! isset($this->definition['field'])) {
            Throw new UnexpectedValueException('QueryBuilder need a "field" or "fields" list element to process "UPDATE" definition.');
        }
        if (! isset($this->definition['params'])) {
            Throw new UnexpectedValueException('QueryBuilder need a assoc array param list to process "UPDATE" definition.');
        }

        $this->processFieldDefinition();
        $this->processFilterDefinition();
        $this->processParamsDefinition();

        $this->processLimitDefinition();
        $this->processOrderDefinition();
    }

    private function processDelete()
    {
        $this->processFieldDefinition();
        $this->processFilterDefinition();
        $this->processOrderDefinition();
        $this->processLimitDefinition();
        $this->processParamsDefinition();
    }

    /**
     * Processes value defintion.
     * Takes also care of set alias definition.
     *
     * @throws UnexpectedValueException
     */
    private function processTblDefinition()
    {
        if (! isset($this->definition['tbl']) && ! isset($this->definition['table'])) {
            Throw new UnexpectedValueException('QueryBuilder needs a table name. Provide tablename by setting "tbl" or "table" element in your query definition');
        }
        else {
            $this->tbl = isset($this->definition['tbl']) ? $this->definition['tbl'] : $this->definition['table'];
        }

        if (isset($this->definition['alias'])) {
            $this->alias = $this->definition['alias'];
        }
    }

    /**
     * Processes field definition.
     * Uses '*' as field when no definition is set.
     */
    private function processFieldDefinition()
    {
        if (isset($this->definition['field'])) {
            $this->fields = $this->definition['field'];
        }
        elseif (isset($this->definition['fields'])) {
            $this->fields = $this->definition['fields'];
        }
        else {
            $this->fields = '*';
        }
    }

    /**
     * Processes data defintion
     *
     * Take care of data type. Uses container field names for field list
     * and field values for paramters.
     *
     * @throws UnexpectedValueException
     */
    private function processDataDefinition()
    {
        // Data definition only as \Core\Lib\Data\Container ever<thing else causes an exception
        if (! $this->definition['data'] instanceof Container) {
            Throw new UnexpectedValueException('QueryBuilder can only process data attributes of type \Core\Lib\Data\Container.');
        }

        // Autodetection of method when none is set e.g. SELECT is set.
        if ($this->method == 'SELECT') {

            // Get name of primary field. Is false when no primary field exists.
            $primary = $this->definition['data']->getPrimary();

            // Set method to UPDATE when primary exists and has a value. Otherwise we INSERT a new record.
            $this->method = ($primary !== false && ! empty($this->definition['data'][$primary])) ? 'UPDATE' : 'INSERT';
        }

        // Check for allowed querymethods
        $allowed_methods = [
            'UPDATE',
            'INSERT',
            'REPLACE'
        ];

        if (! in_array($this->method, $allowed_methods)) {
            Throw new UnexpectedValueException('QueryBuilder can only process querymethods of type "update", "insert" or "replace".');
        }

        /* @var $field \Core\Lib\Data\Field */
        foreach ($this->definition['data'] as $field_name => $field) {

            // Value handling
            $value = $field->get();

            // Field is flagged as serialized?
            if ($field->getSerialize()) {
                $value = serialize($value);
            }

            // Handle fields flagged as primary
            if ($field->getPrimary()) {

                // Different modes need different handlings
                switch ($this->method) {

                    // Ignore field when mode is insert
                    case 'INSERT':
                        continue;
                        break;

                    // Use field as filter on update
                    case 'UPDATE':
                        $this->definition['filter'] = $field_name . ' = :__primary_' . $field_name;
                        $this->definition['params'][':__primary_' . $field_name] = $value;
                        break;

                    // Add field to fieldlist on replace
                    case 'REPLACE':
                    default:
                        $this->definition['fields'][] = $field_name;
                        $this->definition['params'][':' . $field_name] = $value;
                        break;
                }
            }<commit_msg>Updates processDataDeinition() related to Field object changes<commit_after><?php
namespace Core\Lib\Data\Adapter\Db;

use Core\Lib\Data\Container;
use Core\Lib\Traits\ArrayTrait;
use Core\Lib\Traits\ConvertTrait;
use Core\Lib\Errors\Exceptions\InvalidArgumentException;
use Core\Lib\Errors\Exceptions\UnexpectedValueException;

/**
 * QueryBuilder.php
 *
 * @author Michael "Tekkla" Zorn <<EMAIL>>
 * @copyright 2015
 * @license MIT
 */
class QueryBuilder
{

    use ArrayTrait;
    use ConvertTrait;

    private $method;

    private $definition;

    private $tbl;

    private $alias = '';

    private $fields = [];

    private $values = [];

    private $join = [];

    private $filter = '';

    private $params = [];

    private $order = '';

    private $group_by = '';

    private $having = '';

    private $sql = '';

    /**
     *
     * @var array
     */
    private $limit = [];

    /**
     * Constructor
     *
     * @param array $definition Optional QueryDefinition
     */
    public function __construct(Array $definition = [])
    {
        if ($definition) {
            $this->processQueryDefinition($definition);
        }
    }

    /**
     * SELECT statement
     *
     * @param array|string $fields Fieldlist as comma seperated string or value array.
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function Select($fields = '')
    {
        $this->method = 'SELECT';

        $this->Columns($fields);

        return $this;
    }

    /**
     * SELECT DISTINCT statement
     *
     * @param array|string $fields Fieldlist as comma seperated string or value array.
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function SelectDistinct($fields = '')
    {
        $this->method = 'SELECT DISTINCT';

        $this->Columns($fields);

        return $this;
    }

    /**
     * INSERT INTO statement
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function InsertInto()
    {
        $this->method = 'INSERT INTO';

        return $this;
    }

    /**
     * INTO statement
     *
     * @param string $tbl Name of table
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function Into($tbl)
    {
        $this->From($tbl);

        return $this;
    }

    /**
     * DELETE statement
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function Delete()
    {
        $this->method = 'DELETE';

        return $this;
    }

    /**
     * Colums to use in query
     *
     * @param array|string $fields Fieldlist as comma seperated string or value array.
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function Columns($columns = '')
    {
        if (empty($columns)) {
            return;
        }

        if (! is_array($columns)) {
            $this->fields = explode(',', $columns);
        }

        $this->fields = array_map('trim', $this->fields);

        return $this;
    }

    /**
     * From statement
     *
     * @param string $tbl Table name
     * @param string $alias Optional: Table alias
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function From($tbl, $alias = '')
    {
        $this->tbl = $tbl;

        if ($alias) {
            $this->alias = $alias;
        }

        return $this;
    }

    /**
     * Filter statement
     *
     * @param string $filter Filterstring
     * @param array $params Optional: Paramenter list
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function Where($filter, $params = [])
    {
        $this->filter;
        $this->params = $params;

        return $this;
    }

    /**
     * Order statement
     *
     * @param string $order Orderstring
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function Order($order)
    {
        $this->order = $order;

        return $this;
    }

    /**
     * Join statement
     *
     * @param string $tbl Table name of table to join
     * @param string $as Alias of join table
     * @param string $by How to join
     * @param string $condition Join condition
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function Join($tbl, $as, $by, $condition)
    {
        $this->join[] = [
            'tbl' => $tbl,
            'as' => $as,
            'by' => $by,
            'cond' => $condition
        ];

        return $this;
    }

    /**
     * GroupBy statement.
     *
     * @param string|array field name or list of field names as array
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function GroupBy($field)
    {
        if (is_array($field)) {
            $val = implode(', ', $field);
        }

        $this->group_by = $val;
        return $this;
    }

    public function Having()
    {}

    /**
     * Limit statement.
     *
     * @param int $lower Lower limit
     * @param int $upper Optional: Upper limit
     *
     * @return \Core\Lib\Data\Adapter\Db\QueryBuilder
     */
    public function Limit($lower, $upper = null)
    {
        $this->limit['lower'] = (int) $lower;

        if (isset($upper)) {
            $this->limit['upper'] = (int) $upper;
        }

        return $this;
    }

    /**
     * Buildss the sql string for select queries.
     *
     * @return string Sql string
     */
    public function build()
    {
        $join = '';
        $filter = '';
        $order = '';
        $limit = '';

        // Create the fieldprefix. If given as alias use this, otherwise we use the tablename
        $field_prefifx = $this->alias ? $this->alias : '{db_prefix}' . $this->tbl;

        // Biuld joins
        if ($this->join) {
            $tmp = [];

            foreach ($this->join as $def) {
                $tmp[] = ' ' . $def['by'] . ' JOIN ' . '{db_prefix}' . (isset($def['as']) ? $def['tbl'] . ' AS ' . $def['as'] : $def['join']) . ' ON (' . $def['cond'] . ')';
            }
        }

        $join = isset($tmp) ? implode(' ', $tmp) : '';

        // Create fieldlist
        if ($this->fields) {

            if (! is_array($this->fields)) {
                $this->fields = (array) $this->fields;
            }

            // Add `` to some field names as reaction to those stupid developers who chose systemnames as fieldnames
            foreach ($this->fields as $key_field => $field) {

                // Subquery?
                if (is_array($field)) {
                    $builder = new QueryBuilder($this->sql);
                    $field = '(' . PHP_EOL . $builder->build() . PHP_EOL . ')';
                }

                $fields_to_quote = [
                    'date',
                    'time'
                ];

                if (in_array($field, $fields_to_quote)) {
                    $field = '`' . $field . '`';
                }

                // Extend fieldname either by table alias or name when no dot as alias/table indicator is found.
                // if (strpos($field, '.') === false) {
                // $field .= ($this->alias ? $this->alias : $this->tbl) . '.' . $field;
                // }

                $this->fields[$key_field] = $field;
            }

            $fieldlist = implode(', ', $this->fields);
        }
        else {
            $fieldlist = ($this->alias ? $this->alias : $this->tbl) . '.*';
        }

        // Create filterstatement
        $filter = $this->filter ? ' WHERE ' . $this->filter : '';

        // Create group by statement
        $group_by = '';

        if ($this->group_by) {

            if (is_array($this->group_by)) {
                $this->group_by = implode(', ', $this->group_by);
            }

            $group_by = ' GROUP BY ' . $this->group_by;
        }

        // Create having statement
        $having = $this->having ? ' HAVING ' . $this->having : '';

        // Create order statement
        $order = $this->order ? ' ORDER BY ' . $this->order : '';

        // Create limit statement
        if ($this->limit) {

            $limit = ' LIMIT ';

            if (isset($this->limit['lower'])) {
                $limit .= $this->limit['lower'];
            }

            if (isset($this->limit['lower']) && isset($this->limit['upper'])) {
                $limit .= ',' . $this->limit['upper'];
            }
        }

        // We need a string for the table. if there is an alias, we have to set it
        $tbl = $this->alias ? $this->tbl . ' AS ' . $this->alias : $this->tbl;

        switch ($this->method) {

            case 'UPDATE':

                // we have to build our own fieldlist
                $fields = [];

                foreach ($this->fields as $field) {
                    $fields[] = $field . '=:' . $field;
                }

                $fieldlist = implode(',', $fields);

                $this->sql = $this->method . ' {db_prefix}' . $tbl . ' SET ' . $fieldlist . $filter;

                break;

            case 'INSERT':
            case 'REPLACE':
                $fieldlist = ! $fieldlist || $fieldlist == '*' ? '' : ' (' . $fieldlist . ')';

                // Build values
                $values = [];

                foreach ($this->fields as $field) {
                    $values[] = ':' . $field;
                }

                $values = implode(', ', $values);

                $this->sql = $this->method . ' INTO {db_prefix}' . $tbl . $fieldlist . ' VALUES (' . $values . ')';
                break;

            case 'DELETE':
                $this->sql = $this->method . ' FROM {db_prefix}' . $tbl . $filter . $order . $limit;
                break;

            default:
                $this->sql = $this->method . ' ' . $fieldlist . ' FROM {db_prefix}' . $tbl . $join . $filter . $group_by . $having . $order . $limit;
                break;
        }

        // Finally cleanup parameters by removing parameter not needed in query and parse array parameter into sql string.
        foreach ($this->params as $key => $val) {

            // Do cleanup
            if (strpos($this->sql, $key) === false) {
                unset($this->params[$key]);
                continue;
            }

            // Replace array parameter against sql valid part
            if (is_array($val)) {
                $prepared = $this->prepareArrayQuery($key, $val);
            }
        }

        return $this->sql;
    }

    private function processQueryDefinition($def)
    {
        // Store defintion
        $this->definition = $def;

        // Use set query type or use 'row' as default
        if (isset($this->definition['method'])) {
            $this->method = strtoupper($this->definition['method']);
            unset($this->definition['method']);
        }
        else {
            $this->method = 'SELECT';
        }

        // All methods need the table defintion
        $this->processTblDefinition();

        // Process data definition?
        if (isset($this->definition['data'])) {
            $this->processDataDefinition();
        }

        switch ($this->method) {

            case 'INSERT':
            case 'REPLACE':
                $this->processInsert();
                break;

            case 'UPDATE':
                $this->processUpdate();
                break;

            case 'DELETE':
                $this->processDelete();
                break;

            default:
                $this->processSelect();
                break;
        }

        return $this;
    }

    private function processSelect()
    {
        $this->processTblDefinition();

        $this->processFieldDefinition();

        $this->processFilterDefinition();

        $this->processParamsDefinition();

        $this->processGroupByDefinition();

        $this->processHavingDefinition();

        $this->processJoinDefinition();

        $this->processOrderDefinition();

        $this->processLimitDefinition();
    }

    /**
     * Processes insert definition
     *
     * @throws UnexpectedValueException
     */
    private function processInsert()
    {
        if (! isset($this->definition['fields']) && ! isset($this->definition['field'])) {
            Throw new UnexpectedValueException('QueryBuilder need a "field" or "fields" list element to process "INSERT" definition.');
        }

        if (! isset($this->definition['params'])) {
            Throw new UnexpectedValueException('QueryBuilder need a assoc array param list to process "INSERT" definition.');
        }

        $this->processFieldDefinition();
        $this->processParamsDefinition();
    }

    /**
     *
     * @throws UnexpectedValueException
     */
    private function processUpdate()
    {
        if (! isset($this->definition['fields']) && ! isset($this->definition['field'])) {
            Throw new UnexpectedValueException('QueryBuilder need a "field" or "fields" list element to process "UPDATE" definition.');
        }
        if (! isset($this->definition['params'])) {
            Throw new UnexpectedValueException('QueryBuilder need a assoc array param list to process "UPDATE" definition.');
        }

        $this->processFieldDefinition();
        $this->processFilterDefinition();
        $this->processParamsDefinition();

        $this->processLimitDefinition();
        $this->processOrderDefinition();
    }

    private function processDelete()
    {
        $this->processFieldDefinition();
        $this->processFilterDefinition();
        $this->processOrderDefinition();
        $this->processLimitDefinition();
        $this->processParamsDefinition();
    }

    /**
     * Processes value defintion.
     * Takes also care of set alias definition.
     *
     * @throws UnexpectedValueException
     */
    private function processTblDefinition()
    {
        if (! isset($this->definition['tbl']) && ! isset($this->definition['table'])) {
            Throw new UnexpectedValueException('QueryBuilder needs a table name. Provide tablename by setting "tbl" or "table" element in your query definition');
        }
        else {
            $this->tbl = isset($this->definition['tbl']) ? $this->definition['tbl'] : $this->definition['table'];
        }

        if (isset($this->definition['alias'])) {
            $this->alias = $this->definition['alias'];
        }
    }

    /**
     * Processes field definition.
     * Uses '*' as field when no definition is set.
     */
    private function processFieldDefinition()
    {
        if (isset($this->definition['field'])) {
            $this->fields = $this->definition['field'];
        }
        elseif (isset($this->definition['fields'])) {
            $this->fields = $this->definition['fields'];
        }
        else {
            $this->fields = '*';
        }
    }

    /**
     * Processes data defintion
     *
     * Take care of data type. Uses container field names for field list
     * and field values for paramters.
     *
     * @throws UnexpectedValueException
     */
    private function processDataDefinition()
    {
        // Data definition only as \Core\Lib\Data\Container ever<thing else causes an exception
        if (! $this->definition['data'] instanceof Container) {
            Throw new UnexpectedValueException('QueryBuilder can only process data attributes of type \Core\Lib\Data\Container.');
        }

        // Autodetection of method when none is set e.g. SELECT is set.
        if ($this->method == 'SELECT') {

            // Get name of primary field. Is false when no primary field exists.
            $primary = $this->definition['data']->getPrimary();

            // Set method to UPDATE when primary exists and has a value. Otherwise we INSERT a new record.
            $this->method = ($primary !== false && ! empty($this->definition['data'][$primary])) ? 'UPDATE' : 'INSERT';
        }

        // Check for allowed querymethods
        $allowed_methods = [
            'UPDATE',
            'INSERT',
            'REPLACE'
        ];

        if (! in_array($this->method, $allowed_methods)) {
            Throw new UnexpectedValueException('QueryBuilder can only process querymethods of type "update", "insert" or "replace".');
        }

        /* @var $field \Core\Lib\Data\Field */
        foreach ($this->definition['data'] as $field_name => $field) {

            // Value handling
            $value = $field->getValue();

            // Field is flagged as serialized?
            if ($field->getSerialize()) {
                $value = serialize($value);
            }

            // Handle fields flagged as primary
            if ($field->getPrimary()) {

                // Different modes need different handlings
                switch ($this->method) {

                    // Ignore field when mode is insert
                    case 'INSERT':
                        continue;
                        break;

                    // Use field as filter on update
                    case 'UPDATE':
                        $this->definition['filter'] = $field_name . ' = :__primary_' . $field_name;
                        $this->definition['params'][':__primary_' . $field_name] = $value;
                        break;

                    // Add field to fieldlist on replace
                    case 'REPLACE':
                    default:
                        $this->definition['fields'][] = $field_name;
                        $this->definition['params'][':' . $field_name] = $value;
                        break;
                }
            }
{'fraction_non_alphanumeric': 0.14249871699834635, 'fraction_numerical': 0.00022808918287050236, 'mean_word_length': 1.6314802310750993, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 321, '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': '4656665', 'n_tokens_mistral': 9738, 'n_tokens_neox': 9290, 'n_words': 2300}