ulmus/src/Modeler/Query.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;
}
}