Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
80.00% covered (success)
80.00%
8 / 10
CRAP
89.42% covered (success)
89.42%
93 / 104
SqlBuilder
0.00% covered (danger)
0.00%
0 / 1
80.00% covered (success)
80.00%
8 / 10
58.58
89.42% covered (success)
89.42%
93 / 104
 quote
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
7 / 7
 where
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
11 / 11
 build_sql_in
100.00% covered (success)
100.00%
1 / 1
9
100.00% covered (success)
100.00%
21 / 21
 update
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
6 / 6
 updateLastChanged
0.00% covered (danger)
0.00%
0 / 1
12.00
0.00% covered (danger)
0.00%
0 / 10
 insert
100.00% covered (success)
100.00%
1 / 1
11
100.00% covered (success)
100.00%
24 / 24
 insertInto
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 insertValues
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
6 / 6
 insertOnDuplicateKey
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
10 / 10
 autoComment
0.00% covered (danger)
0.00%
0 / 1
7.39
80.00% covered (success)
80.00%
4 / 5
<?php
/**
 * SqlBuilder.php
 *
 * @package Sql
 * @author Informatica Asociada
 * @copyright 2015
 * @version 1.0.1
 */
/** @noinspection SqlNoDataSourceInspection */
/** @noinspection SqlResolve */
namespace ia\Sql\Mysql;
use function count;
use ia\Util\Str;
/**
 * SqlBuilder Helps build Sql statements like insert, update, where clauses, in clauses
 */
class SqlBuilder {
    public $dontQuoteValue = array(
        'CURDATE()'=>1,'CURRENT_DATE()'=>1,'CURRENT_DATE'=>1,'SYSDATE()'=>1,'UTC_DATE()'=>1,
        'CURRENT_DATETIME'=>1,'NOW()'=>1,
        'CURRENT_TIME()'=>1,'CURRENT_TIME'=>1,'CURTIME()'=>1,'UTC_TIME()'=>1,
        'CURRENT_TIMESTAMP()'=>1,'CURRENT_TIMESTAMP'=>1,'LOCALTIMESTAMP()'=>1,'LOCALTIMESTAMP'=>1,'UNIX_TIMESTAMP()'=>1,'UTC_TIMESTAMP()'=>1
    );
    public $fieldNameDontQuote = array(
    );
    public $dontOnUpdateFieldName = array(
        'alta_db'=>1,
    );
    /**
     * Returns value protected with strit or not.
     *
     * @param string $fieldName
     * @param mixed $value
     * @param array $fieldNameDontQuote fieldnames not to quote [fieldName1, fieldName2, ...]
     * @return string
     */
    public function quote($fieldName, $value, $fieldNameDontQuote=array()) {
        if($value === null) {
            return 'NULL';
        }
        if(!is_array($fieldNameDontQuote)) {
            $fieldNameDontQuote = [$fieldNameDontQuote];
        }
        if(array_key_exists($fieldName,array_flip($fieldNameDontQuote)) || array_key_exists($fieldName,$this->fieldNameDontQuote) || array_key_exists(strtoupper($value),$this->dontQuoteValue)) {
            return $value;
        }
        return Str::strit($value);
    }
    /**
     * Build a where clause from an array or string
     *
     * @example
     *
     * @param array|string $where array('col_1'=>3,...)  col_1 = '3' AND ...  or string col_1=1
     * @param array $whereDontQuoteFieldName array('col_2',...) col_1's value wont be quoted
     * @return string where clause
     *
     */
    public function where($where,$whereDontQuoteFieldName=array()) {
        if(empty($where)) {
            return '';
        }
        if(!is_array($where)) {
            return '(' . $where . ')';
        }
        $dontQuote = is_array($whereDontQuoteFieldName) ? $whereDontQuoteFieldName : array($whereDontQuoteFieldName);
        $whereClause = [];
        foreach($where as $fieldName => $value) {
            $whereClause[] = is_numeric($fieldName) ?
                $value :
                $this->build_sql_in($fieldName, $value, $dontQuote);
        }
        return '(' . implode(' AND ', $whereClause) . ')';
    }
    /**
     * Returns $fieldName=$value or $fieldName IS NULL or $fieldName IN ( values in array)
     * Protecting $filedNmae and $value as required.
     *
     * @param string $fieldName
     * @param array|string $whereValues
     * @param array $fieldNameDontQuote
     * @return string $fieldName=$value or $fieldName IS NULL or $fieldName IN ( values in array)
     */
    public function build_sql_in($fieldName, $whereValues, $fieldNameDontQuote=array() ) {
        $field = Str::fieldit($fieldName);
        if(!is_array($whereValues)) {
            if($whereValues === null) {
                return ' '.$field.' IS NULL';
            }
            return ' '.$field.'='.$this->quote($fieldName, $whereValues, $fieldNameDontQuote);
        }
        if(empty($whereValues)) {
            return '';
        }
        if(count($whereValues) == 1) {
            $value = reset($whereValues);
            if($value === null) {
                return ' '.$field.' IS NULL';
            }
            return ' '.$field.'='.$this->quote($fieldName, $value, $fieldNameDontQuote);
        }
        $nulls = '';
        $inArr=array();
        foreach($whereValues as $s) {
            if($s === null) {
                $nulls = $field." IS NULL OR ";
            } else {
                $inArr[] = $this->quote($fieldName, $s, $fieldNameDontQuote);
            }
        }
        if(empty($inArr)) {
            return " $field IS NULL";
        }
        return "($nulls".$field." IN (".implode(',',$inArr).'))';
    }
    /**
     * Create an Sql update query.
     *
     * @param string $table
     * @param array $values array('col_1'=>'value', ...)
     * @param string|array $where array('col_1'=>'3',...)  col_1 = 3 AND ...  or string col_1=1
     * @param array $fieldNameDontQuote  array('col_2',...) col_2's value wont be quoted in the set clause
     * @param array $whereDontQuoteFieldName array('col_1',...) col_1's value wont be quoted in the where clause
     * @return string Sql update command
     *
     * @see $dontQuoteValue
     * @see $dontQuoteFieldName
     * @see where()
     */
    public function update($table, $values, $where=array(), $fieldNameDontQuote=array(), $whereDontQuoteFieldName=array() ) {
        $set = '';
        foreach($values as $fieldName => $value)
            if($value === null) {
                $set .= ','.Str::fieldit($fieldName).'=NULL';
            } else {
                $set .= ','.Str::fieldit($fieldName).'='.$this->quote($fieldName, $value, $fieldNameDontQuote);
            }
        return "UPDATE".$this->autoComment().Str::fieldit($table)." SET ".substr($set, 1)." WHERE ".$this->where($where, $whereDontQuoteFieldName);
    }
    /**
     * SqlBuilder::updateLastChanged()
     *
     * @param string $table
     * @param string $values
     * @param array $where
     * @param array $fieldsChanged ['last_updated'=>'NOW()', 'updated_by'=>'userName']
     * @param array $fieldNameDontQuote
     * @param array $whereDontQuoteFieldName
     * @return array sql statemtns ['update', 'update $fieldsChanged WHERE lastUpdated changed row' ]
     */
    public function updateLastChanged(string $table, array $values, array $where=[], array $fieldsChanged=[], array $fieldNameDontQuote=[], array $whereDontQuoteFieldName=[] ) {
        if($fieldsChanged === []) {
            return [$this->update($table, $values, $where, $fieldNameDontQuote, $whereDontQuoteFieldName)];
        }
        $ifChangeFields = [];
        foreach($fieldsChanged as $fieldName => $value) {
            $ifChangeFields[$fieldName] = $value;
            unset($values[$fieldName]);
        }
        $sql[] = $this->sqlBuilder->update($table, $values, $where, $fieldNameDontQuote, $whereDontQuoteFieldName );
        $where[] = 'ROW_COUNT() > 0';
        $sql[] = $this->sqlBuilder->update($table, $ifChangeFields, $where, $fieldNameDontQuote, $whereDontQuoteFieldName);
        return $sql;
    }
    /**
     * Create an Sql insert query
     *
     * @param string $table
     * @param array $values array('col_1'=>'value', ...)
     * @param bool $autoOnUpdate true: an on duplicate key update clause will be added with $onUpdate concateanted
     *        with all fields not in $fieldNameDontQuote nor in $this->dontOnUpdateFieldName
     * @param string $onUpdate add to ON DUPLICATE KEY UPDATE  ie set last_insert_id: primary_key=LAST_INSERT_ID(primary_key)
     * @param array $fieldNameDontOnUpdate array('col_1',...) col_1's value wont be quoted in the values clause
     * @param array $fieldNameDontQuote  array('col_1',...) col_1's value wont be quoted in the values clause
     * @return string Sql insert command
     *
     */
    public function insert($table, $values, $autoOnUpdate=false, $onUpdate='', $fieldNameDontOnUpdate=array(), $fieldNameDontQuote=array()) {
        if(is_array($onUpdate)) {
            $temp = [];
            foreach($onUpdate as $fieldName) {
                $field = Str::fieldit($fieldName);
                $temp[] = "$field=VALUES($field)";
            }
            $onDup = implode(',', $temp);
        } else {
            $onDup = trim($onUpdate);
        }
        if(!empty($onDup)) {
            if(substr($onDup,0,1) !== ',')
                $onDup = ','.$onDup;
            if(substr($onDup,-1) === ',')
                $onDup = substr($onDup,0,-1);
        }
        $dontOnDup = array_merge($this->dontOnUpdateFieldName, array_flip($fieldNameDontOnUpdate));
        $fields = '';
        $vals = '';
        foreach($values as $fieldName => $value) {
            $f = Str::fieldit($fieldName);
            $fields .= ",$f";
            if($autoOnUpdate && !array_key_exists($fieldName,$dontOnDup) && stripos($value,'DEFAULT(') === false ) {
                $onDup .= ",$f=VALUES($f)";
            }
            $vals .= ','.$this->quote($fieldName, $value, $fieldNameDontQuote);
        }
        if(!empty($onDup)) {
            $onDup = ' ON DUPLICATE KEY UPDATE '.substr($onDup,1);
        }
        return "INSERT".$this->autoComment()."INTO ".Str::fieldit($table)."(".substr($fields,1).") VALUES(".substr($vals,1).")$onDup";
    }
    /**
     * Build an insert into clause, with the filed names from  the keys of $values array
     *
     * @example
     *
     *
     * @param string $table table name for query
     * @param array $values array('col_1'=>'value', ...)
     * @return string insert into clause, with the filed names from  the keys of $values array
     *
     * @see insertValues()
     * @see insertOnDuplicateKey()
     *
     */
    public function insertInto($table,$values) {
        $fields = '';
        foreach($values as $fieldName => $value) {
            $fields .= ','.Str::fieldit($fieldName);
        }
        return "INSERT INTO ".Str::fieldit($table)."(".substr($fields,1).") ";
    }
    /**
     * Returns a values clause for multiple inserts
     *
     *
     * @example
     *
     *
     * @param array $values array('col_1'=>'value', ...)
     * @param array $fieldNameDontQuote fieldnames not to quote [fieldName1, fieldName2, ...]
     * @return string
     *
     * @see $dontQuoteValue
     * @see $dontQuoteFieldName
     * @see insertInto()
     * @see insertOnDuplicateKey()
     */
    public function insertValues($values, $fieldNameDontQuote=array()) {
        $vals = [];
        foreach($values as $fieldName => $value) {
            $vals[] = $this->quote($fieldName, $value, $fieldNameDontQuote);
        }
        if(empty($vals))
            return '';
        return '('.implode(',',$vals).')';
    }
    /**
     * Build an ON DUPLICATE KEY UPDATE clause
     *
     * @param array $values array('col_1'=>'value', ...)
     * @param bool $updateAllFields true set fieldname=VALUES(fieldname) for all fields
     * @param string $onUpdate  add to ON DUPLICATE KEY UPDATE  ie set last_insert_id: ,primary_key=LAST_INSERT_ID(primary_key)
     * @param array $fieldNameDontOnUpdate
     * @return string an ON DUPLICATE KEY UPDATE clause
     *
     * @see $dontOnUpdateFieldName
     * @see insertInto()
     * @see insertValues() trim
     */
    public function insertOnDuplicateKey($values, $updateAllFields=true, $onUpdate='', $fieldNameDontOnUpdate=array()) {
        //@TODO onUpdate=['fieldName1,...']
        $onDup = trim($onUpdate);
        $dontOnDup = array_merge($this->dontOnUpdateFieldName, array_flip($fieldNameDontOnUpdate));
        foreach($values as $fieldName => $value)
            if(!array_key_exists($fieldName,$dontOnDup) ) {
                $f = Str::fieldit($fieldName);
                if($updateAllFields) {
                    $onDup .= ",$f=VALUES($f)";
                }
            }
        if(empty($onDup))
            return '';
        return ' ON DUPLICATE KEY UPDATE '.$onDup;
    }
    /**
     * SQL comment indicating function/class:method issued query
     *
     * @return string Sql comment ie  /* class::method  or function * /, blank if could't resolve
     */
    protected function autoComment() {
        $dbt=debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
        if(isset($dbt[3]) && isset($dbt[3]['function'])) {
            return ' /* '.(isset($dbt[3]['class']) ? $dbt[3]['class'].'::' : '').$dbt[3]['function'].' */ ';
        }
        if(isset($dbt[2]) && $dbt[2]['function']) {
            return ' /* '.(isset($dbt[2]['class']) ? $dbt[2]['class'].'::' : '').$dbt[2]['function'].' */ ';
        }
        // @codeCoverageIgnoreStart
        return ' ';
        // @codeCoverageIgnoreEnd
    }
}