405 lines
14 KiB
PHP
405 lines
14 KiB
PHP
<?php namespace Alive\Storage\Sql;
|
|
|
|
use Alive\{
|
|
constructor,
|
|
Arrayobj
|
|
};
|
|
|
|
class QueryBuilder {
|
|
protected $fields;
|
|
protected $where;
|
|
protected $order_by;
|
|
protected $group_by;
|
|
protected $limit;
|
|
|
|
public static $syntax = [
|
|
'against' => 'AGAINST',
|
|
'and' => 'AND',
|
|
'as' => 'AS',
|
|
'charset' => 'CHARACTER SET',
|
|
'collate' => 'COLLATE',
|
|
'create' => 'CREATE',
|
|
'database' => 'DATABASE',
|
|
'delete' => 'DELETE FROM',
|
|
'distinct' => 'DISTINCT',
|
|
'drop' => 'DROP',
|
|
'engine' => 'ENGINE',
|
|
'!exist' => 'IF NOT EXISTS',
|
|
'exist' => 'IF EXISTS',
|
|
'explain' => 'EXPLAIN',
|
|
'from' => 'FROM',
|
|
'grant' => 'GRANT',
|
|
'grant_option' => 'GRANT OPTION',
|
|
'group_by' => 'GROUP BY',
|
|
'having' => 'HAVING',
|
|
'in' => 'IN',
|
|
'insert' => 'INSERT INTO',
|
|
'join' => 'JOIN',
|
|
'join-left' => 'LEFT',
|
|
'join-right' => 'RIGHT',
|
|
'join-inner' => 'INNER',
|
|
'join-full' => 'FULL',
|
|
'join-self' => 'SELF',
|
|
#'join-outer' => 'OUTER',
|
|
'join-cross' => 'CROSS',
|
|
'like' => 'LIKE',
|
|
'limit' => 'LIMIT',
|
|
'match' => 'MATCH',
|
|
'not_in' => 'NOT IN',
|
|
'on' => 'ON',
|
|
'on_table' => 'ON TABLE',
|
|
'or' => 'OR',
|
|
'order_by' => 'ORDER BY',
|
|
'offset' => 'OFFSET',
|
|
'revoke' => 'REVOKE',
|
|
'select' => 'SELECT',
|
|
'set' => 'SET',
|
|
'table' => 'TABLE',
|
|
'table_charset' => 'DEFAULT CHARSET',
|
|
'to' => 'TO',
|
|
'update' => 'UPDATE',
|
|
'values' => 'VALUES',
|
|
'where' => 'WHERE'
|
|
];
|
|
|
|
static $escape_char = '`';
|
|
|
|
protected $compiled = [];
|
|
|
|
public static function select($param) {
|
|
$param = Arrayobj::make($param);
|
|
|
|
return static::prepare_array([
|
|
$param->if_has('explain' , static::$syntax['explain']),
|
|
static::$syntax['select'],
|
|
$param->if_has('distinct' , static::$syntax['distinct']),
|
|
static::group_fields($param['fields'] ?: '*'),
|
|
static::$syntax['from'],
|
|
static::full_tablename($param),
|
|
static::prepare_join($param['join']),
|
|
static::prepare_where($param['where'], false, $param->ternary('escaped', true)),
|
|
$param->if_has('group_by' , static::prepare_group($param['group_by'], $param['alias'] ?? null)),
|
|
$param->if_has('having' , static::$syntax['having']." {$param['having']}"),
|
|
/* @todo UNION | INTERSECT | EXCEPT GOES HERE !*/
|
|
$param->if_has('order_by' , static::prepare_order($param['order_by'], $param['alias'] ?? null)),
|
|
static::prepare_limit($param)
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* This function will translate parameters into a "create database" or "create table", depending
|
|
* on given param.
|
|
*
|
|
* @param array $param 'subject': table or database
|
|
*
|
|
* @return Type Description
|
|
*/
|
|
public static function create($param) {
|
|
$param = Arrayobj::make($param);
|
|
return strtolower( $param['subject'] ) === 'table' ? static::create_table($param) : static::create_database($param);
|
|
}
|
|
|
|
public static function create_table($param) {
|
|
$param = is_array($param) ? Arrayobj::make($param) : $param;
|
|
|
|
return static::prepare_array([
|
|
static::$syntax['create'],
|
|
static::$syntax['table'],
|
|
$param->if_has('!exist', static::$syntax['!exist']),
|
|
static::full_tablename($param),
|
|
static::group_create_fields($param->mandatory('fields'), true),
|
|
$param->if_has('collation' , static::$syntax['collate']." {$param['collation']}" )
|
|
]);
|
|
}
|
|
|
|
public static function create_database($param) {
|
|
$param = is_array($param) ? Arrayobj::make($param) : $param;
|
|
|
|
return static::prepare_array([
|
|
static::$syntax['create'],
|
|
static::$syntax['database'],
|
|
$param->if_has('!exist', static::$syntax['!exist']),
|
|
static::escape( $param->mandatory('database') )
|
|
]);
|
|
}
|
|
|
|
public static function insert($param) {
|
|
$param = Arrayobj::make($param);
|
|
|
|
$field_label = static::group_fields( $param->mandatory('fields'), true, true );
|
|
$field_values = static::group_values( $param->mandatory('values'), $param['escaped'] ?: false );
|
|
|
|
return static::prepare_array([
|
|
static::$syntax['insert'],
|
|
static::full_tablename($param),
|
|
$field_label,
|
|
static::$syntax['values'],
|
|
$field_values
|
|
]);
|
|
}
|
|
|
|
public static function grant($param) {
|
|
$param = Arrayobj::make($param);
|
|
|
|
$field_label = static::group_fields( $param->mandatory('privileges') );
|
|
$users = static::group_fields( $param->mandatory('users') );
|
|
|
|
return static::prepare_array([
|
|
static::$syntax['grant'],
|
|
$field_label,
|
|
static::$syntax['on_table'],
|
|
static::full_tablename($param),
|
|
static::$syntax['to'],
|
|
$users,
|
|
$param->if_has('grant_option', static::$syntax['grant_option'])
|
|
]);
|
|
}
|
|
|
|
public static function delete($param) {
|
|
$param = Arrayobj::make($param);
|
|
|
|
return static::prepare_array([
|
|
static::$syntax['delete'],
|
|
static::full_tablename($param),
|
|
static::prepare_where($param['where'], false, $param->ternary('escaped', true)),
|
|
static::prepare_order($param),
|
|
static::prepare_limit($param)
|
|
]);
|
|
}
|
|
|
|
public static function update($param) {
|
|
$param = Arrayobj::make($param);
|
|
|
|
$fields = static::group_values_and_fields($param->mandatory('fields'), $param->mandatory('values'));
|
|
|
|
return static::prepare_array([
|
|
static::$syntax['update'],
|
|
static::full_tablename($param),
|
|
static::$syntax['set'],
|
|
$fields,
|
|
static::prepare_where($param['where'])
|
|
]);
|
|
}
|
|
|
|
public static function drop($param) {
|
|
$param = Arrayobj::make($param);
|
|
|
|
return static::prepare_array([
|
|
static::$syntax['drop'],
|
|
$param->exist('table_name') ? static::$syntax['table']." ".static::full_tablename($param) : static::$syntax['database']." ".static::escape($param->mandatory('database'))
|
|
]);
|
|
}
|
|
|
|
public static function full_tablename($param) {
|
|
is_array($param) && ($param = Arrayobj::make($param));
|
|
return $param->if_has('database', static::escape($param['database']).".") . static::escape($param->mandatory('table_name')) . $param->if_has('alias', " ".static::$syntax['as']." " . $param['alias']);
|
|
}
|
|
|
|
public static function group_fields($fields, $enclose = false, $escape = false) {
|
|
if (is_array($fields)) {
|
|
return ($enclose ? "(" : "") .implode(', ', $escape ? array_map(function($item){ return static::escape($item); }, $fields) : $fields).($enclose ? ")" : "");
|
|
}
|
|
else {
|
|
return $escape ? static::escape($fields) : $fields;
|
|
}
|
|
}
|
|
|
|
public static function group_create_fields($fields, $enclose = false) {
|
|
if (is_array($fields)) {
|
|
$retval = [];
|
|
|
|
foreach($fields as $key => $value) {
|
|
$retval[] = static::escape($key)." ".$value;
|
|
}
|
|
|
|
return ($enclose ? "(" : "") .implode(', ', $retval).($enclose ? ")" : "");
|
|
}
|
|
else {
|
|
return $fields;
|
|
}
|
|
}
|
|
|
|
public static function group_values($values, $escaped = false) {
|
|
$tmp = array_pop($values);
|
|
array_push($values, $tmp);
|
|
|
|
# Are we dealing with an array of values ?
|
|
if ( is_array($tmp) ) {
|
|
$retval = [];
|
|
|
|
foreach($values as $item) {
|
|
$retval[] = implode(', ', $escaped ? $item : static::escape_values($item) );
|
|
}
|
|
|
|
return "(".implode('), (', $retval).")";
|
|
}
|
|
else {
|
|
return "(".implode(', ', $escaped ? $values : static::escape_values($values)).")";
|
|
}
|
|
}
|
|
|
|
public static function escape_values($values) {
|
|
$type_function = function(& $item) {
|
|
|
|
switch( $t = gettype($item) ) {
|
|
case "boolean":
|
|
$item = $item ? 1 : 0;
|
|
break;
|
|
|
|
case "double":
|
|
case "integer":
|
|
break;
|
|
|
|
case "NULL":
|
|
$item = "NULL";
|
|
break;
|
|
|
|
case "string":
|
|
$item = "\"$item\"";
|
|
break;
|
|
}
|
|
|
|
|
|
return $item;
|
|
};
|
|
|
|
return is_array($values) ? array_map($type_function, $values) : $type_function($values);
|
|
}
|
|
|
|
public static function group_values_and_fields($fields, $values) {
|
|
$retval = [];
|
|
|
|
foreach($fields as $key => $item) {
|
|
$retval[] = "{$item} = {$values[$key]}";
|
|
}
|
|
|
|
return implode(', ', $retval);
|
|
}
|
|
|
|
public static function prepare_array($sql) {
|
|
return implode(" ", array_filter($sql)).";";
|
|
}
|
|
|
|
public static function prepare_where($where, $recursion = false, $escaped = false) {
|
|
$retval = [];
|
|
|
|
if (is_array($where)) {
|
|
$count = count($where);
|
|
for($i = 0; $i < $count; $i++) {
|
|
$item = $where[$i];
|
|
|
|
if ( ! Arrayobj::array_is_associative($item) ) {
|
|
$retval[] = "(".static::prepare_where($item, true, $escaped).")";
|
|
}
|
|
else {
|
|
$comparison = (isset($item['comparison']) ? $item['comparison'] : "=");
|
|
|
|
# are we having an IN comparison here ...
|
|
if ( $is_array = (is_array($item['value']) && count($item['value']) > 1) ) {
|
|
switch ($item['comparison']) {
|
|
case '=':
|
|
$comparison = '=';
|
|
break;
|
|
|
|
case '!=':
|
|
$comparison = 'not_in';
|
|
break;
|
|
}
|
|
}
|
|
|
|
$value = static::group_fields($item['value'], true);
|
|
|
|
|
|
switch($comparison) {
|
|
case 'match':
|
|
$retval[] = static::$syntax[$comparison].' ('.static::fieldname($item['field'], $item['alias'] ?? null).") ".static::$syntax['against'].
|
|
" (".(!$escaped || $is_array ? $value : static::escape_values($value))." IN BOOLEAN MODE)".
|
|
($i + 1 < $count ? " ".static::$syntax[ isset($item['operator']) ? $item['operator'] : "and" ] : "");
|
|
|
|
break;
|
|
|
|
default:
|
|
$retval[] = static::fieldname($item['field'], $item['alias'] ?? null)." " . ( isset(static::$syntax[$comparison]) ? static::$syntax[$comparison] : $comparison) .
|
|
" ".(!$escaped || $is_array ? $value : static::escape_values($value)).
|
|
($i + 1 < $count ? " ".static::$syntax[ isset($item['operator']) ? $item['operator'] : "and" ] : "");
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $retval ? ($recursion ? "" : static::$syntax['where'] . " ") . implode(" ", $retval ) : "";
|
|
}
|
|
|
|
public static function prepare_join($joins) {
|
|
$retval = [];
|
|
|
|
if ( is_array($joins) ) {
|
|
$count = count($joins);
|
|
|
|
for($i = 0; $i < $count; $i++) {
|
|
$join = [];
|
|
|
|
$table = Arrayobj::make([
|
|
'table_name' => $joins[$i]['table'],
|
|
'alias' => $joins[$i]['alias_right']
|
|
]);
|
|
|
|
$join[] = static::$syntax[ "join-".$joins[$i]['type'] ] ?? $joins[$i]['type'];
|
|
$join[] = static::$syntax[ 'join' ];
|
|
$join[] = static::full_tablename($table);
|
|
$join[] = static::$syntax[ 'on' ];
|
|
|
|
foreach($joins[$i]['fields'] as $left_field => $right_field) {
|
|
#$join[] = $joins[$i]['alias_left'].".".static::escape($left_field);
|
|
$join[] = static::fieldname($left_field, $joins[$i]['alias_left']);
|
|
$join[] = $joins[$i]['comparison'];
|
|
$join[] = static::fieldname($right_field, $joins[$i]['alias_right']);
|
|
}
|
|
|
|
$retval[] = implode(' ', $join);
|
|
}
|
|
|
|
}
|
|
|
|
return implode(' ', $retval);
|
|
}
|
|
|
|
public static function prepare_order($order, $alias = null)
|
|
{
|
|
$retval = [];
|
|
|
|
if (is_array($order)) {
|
|
foreach($order as $item) {
|
|
$retval[] = static::fieldname($item['field'], $alias).( !empty($item['order']) ? " ".$item['order'] : "" );
|
|
}
|
|
}
|
|
|
|
return $retval ? static::$syntax['order_by']." ".implode(', ', $retval) : "";
|
|
}
|
|
|
|
public static function prepare_group($group)
|
|
{
|
|
return $group ? static::$syntax['group_by']." ".( is_array($group) ? implode(', ', $group) : $group ) : "";
|
|
}
|
|
|
|
public static function prepare_limit($param)
|
|
{
|
|
return implode(' ', array_filter([
|
|
$param->if_has('limit' , static::$syntax['limit'] ." {$param['limit']}"),
|
|
$param->if_has('offset', static::$syntax['offset']." {$param['offset']}")
|
|
]));
|
|
}
|
|
|
|
public static function fieldname($field, $alias = null)
|
|
{
|
|
return strpos($field, '.') ? $field : (!empty($alias) ? $alias."." : "").static::escape($field);
|
|
}
|
|
|
|
public static function escape($field)
|
|
{
|
|
return static::$escape_char . str_replace(static::$escape_char, '', $field) . static::$escape_char;
|
|
}
|
|
}
|