function PHPSQLresolvePositionalArguments($sqlTree) { $returnTree = $sqlTree; //subqueries can be in FROM and WHERE if (!empty($returnTree['FROM'])) { foreach ($returnTree['FROM'] as &$fromNode) { if (isSubquery($fromNode)) { $fromNode['sub_tree'] = PHPSQLresolvePositionalArguments($fromNode['sub_tree']); $fromNode['base_expr'] = getBaseExpr($fromNode); } } } if (!empty($returnTree['WHERE'])) { foreach ($returnTree['WHERE'] as &$whereNode) { if (isSubquery($whereNode)) { $whereNode['sub_tree'] = PHPSQLresolvePositionalArguments($whereNode['sub_tree']); $whereNode['base_expr'] = getBaseExpr($whereNode); } } } //only do something if there is an ORDER BY if (!empty($returnTree['ORDER'])) { foreach ($returnTree['ORDER'] as &$orderNode) { if ($orderNode['expr_type'] === "pos") { $selNode = $returnTree['SELECT'][(int) $orderNode['base_expr'] - 1]; //rewrite things if (!isColref($selNode)) { $orderNode['expr_type'] = "colref"; if (hasAlias($selNode)) { $orderNode['base_expr'] = extractColumnAlias($selNode); } else { $orderNode['base_expr'] = buildEscapedString(array($selNode)); } $orderNode['no_quotes'] = array("delim" => ".", "parts" => array($orderNode['base_expr'])); } else { $orderNode['expr_type'] = $selNode['expr_type']; $orderNode['base_expr'] = $selNode['base_expr']; if (!empty($selNode['no_quotes'])) { $orderNode['no_quotes'] = $selNode['no_quotes']; } } } } } return $returnTree; }
function setNoQuotes(&$node, array $parts, $delim = false) { if ($delim !== false) { $node['no_quotes']['delim'] = $delim; } else { if (count($parts) > 1) { //apply a default delimiter $node['no_quotes']['delim'] = "."; } } $node['no_quotes']['parts'] = $parts; $node['base_expr'] = getBaseExpr($node); }
function process_sql($sql, $recLevel = 0, $whereSubquery = false) { #only useful for the fetch worker for debugging $this->shown_temp_table_create = true; $this->sql = $sql; $parser = null; $straight_join = false; $conn = false; $this->shard_sql = ""; #identical SQL which will be broadcast to all shards $this->coord_sql = ""; #the summary table sql $this->in_lists = array(); $error = array(); $select = null; if (!is_array($sql)) { #TODO: support parser re-use #$this->parsed = $this->client->do('sql_parse',$sql); $parser = new \PHPSQLParser\PHPSQLParser($sql); $this->parsed = PHPSQLbuildShardQuery($parser->parsed, $this->headNodeTables); $this->parsedCopy = $this->parsed; } else { $this->parsed = $sql; } if (!empty($this->parsed['UNION ALL'])) { $queries = array(); foreach ($this->parsed['UNION ALL'] as $sub_tree) { $this->process_sql($sub_tree, $recLevel++); $queries = array_merge($queries, $this->shard_sql); } $this->table_name = "aggregation_tmp_" . mt_rand(1, 100000000); $coord_sql = "SELECT * from " . $this->table_name; } elseif (!empty($this->parsed['UNION'])) { $queries = array(); foreach ($this->parsed['UNION'] as $sub_tree) { $this->process_sql($sub_tree, $recLevel++); $queries = array_merge($queries, $this->shard_sql); } $this->table_name = "aggregation_tmp_" . mt_rand(1, 100000000); #UNION operation requires deduplication of the temporary table $coord_sql = "SELECT DISTINCT * from " . $this->table_name; } elseif (!empty($this->parsed['SELECT'])) { #reset the important variables $select = $from = $where = $group = $order_by = $order_by_coord = ""; $this->errors = array(); #we only support SQL_MODE=ONLY_FULL_GROUP_BY, and we build the GROUP BY from the SELECT expression //unset($this->parsed['GROUP']); #The SELECT clause is processed first. $distinct = false; if (!empty($this->parsed['OPTIONS'])) { if (in_array('STRAIGHT_JOIN', $this->parsed['OPTIONS'])) { $straight_join = true; } unset($this->parsed['OPTIONS']); } $select = $this->process_select($this->parsed['SELECT'], $recLevel, $straight_join, $whereSubquery); if (!empty($select['error'])) { $this->errors = $select['error']; return false; } unset($this->parsed['SELECT']); if (empty($this->parsed['FROM'])) { // $this->errors = array('Unsupported query', 'Missing FROM clause'); // return false; $this->table_name = "aggregation_tmp_" . mt_rand(1, 100000000); $select['coord_sql'] .= "\nFROM `{$this->table_name}`"; } else { $select['shard_sql'] .= "\n" . $this->process_from($this->parsed['FROM'], $recLevel); $this->table_name = "aggregation_tmp_" . mt_rand(1, 100000000); #we only select from a single table here $select['coord_sql'] .= "\nFROM `{$this->table_name}`"; if (array_key_exists("USE INDEX", $this->parsed) || array_key_exists("IGNORE INDEX", $this->parsed)) { $index = ""; if (array_key_exists("USE INDEX", $this->parsed)) { $index .= " USE INDEX "; $tree = $this->parsed['USE INDEX']; unset($this->parsed['USE INDEX']); } if (array_key_exists("IGNORE INDEX", $this->parsed)) { $index .= " IGNORE INDEX "; $tree = $this->parsed['IGNORE INDEX']; unset($this->parsed['IGNORE INDEX']); } if ($tree !== false) { foreach ($tree as $node) { $index .= $node['base_expr'] . " "; } } $select['shard_sql'] .= $index; $select['coord_sql'] .= $index; } unset($this->parsed['FROM']); } if ($this->push_where !== false && $this->push_where) { if (!empty($this->parsed['WHERE'])) { $this->parsed['WHERE'][] = array('expr_type' => 'operator', 'base_expr' => 'and', 'sub_tree' => ""); } if (!$parser) { $parser = new \PHPSQLParser\PHPSQLParser(); } $this->messages[] = "Where clause push detected. Pushing additional WHERE condition:'" . $this->push_where . "' to each storage node.\n"; if ($this->push_where) { foreach ($parser->process_expr_list($parser->split_sql($this->push_where)) as $item) { $this->parsed['WHERE'][] = $item; } } } #note that this will extract inlists and store them in $this->in_lists (if inlist optimization is on) if (!empty($this->parsed['WHERE'])) { $where_clauses = $this->process_where($this->parsed['WHERE']); unset($this->parsed['WHERE']); } if (!empty($this->parsed['ORDER'])) { $order_by = ""; $order_by_coord = ""; foreach ($this->parsed['ORDER'] as $o) { if ($order_by) { $order_by .= ","; } if ($order_by_coord) { $order_by_coord .= ","; } #check if order by arguemnt is just a number. if yes, we dont need to quote this if (isset($o['alias']['name']) && is_numeric(trim($o['alias']['name'], "`"))) { $o['alias']['name'] = trim($o['alias']['name'], "`"); } if (isset($o['alias']['name']) && !empty($o['alias']['name'])) { $order_by .= "`" . trim($o['alias']['name'], "`") . "`" . ' ' . $o['direction']; $order_by_coord .= "`" . trim($o['alias']['name'], "`") . "`" . ' ' . $o['direction']; } else { $order_by .= "`" . trim($o['base_expr'], "`") . "`" . ' ' . $o['direction']; if (isset($o['origParse']['sub_tree']) && $o['origParse']['sub_tree'] !== false) { $base = buildEscapedString(array($o['origParse'])); } else { $base = str_replace("`", "", getBaseExpr($o)); } $order_by_coord .= "`" . $base . "`" . ' ' . $o['direction']; } } //only do order by on shards if LIMIT statement is present - otherwise sorting has //to be done on coordination node if (!empty($this->parsed['LIMIT'])) { $order_by = "ORDER BY {$order_by}"; } else { $order_by = ""; } $order_by_coord = "ORDER BY {$order_by_coord}"; unset($this->parsed['ORDER']); } #ADDED GROUP BY SUPPORT HERE $group_by = ""; $group_by_coord = ""; if (!empty($this->parsed['GROUP'])) { foreach ($this->parsed['GROUP'] as $g) { if ($group_by) { $group_by .= ","; } $group_by = str_replace("`", "", getBaseExpr($g)); if ($group_by_coord) { $group_by_coord .= ","; } if (isset($g['origParse']['sub_tree']) && $g['origParse']['sub_tree'] !== false) { $base = buildEscapedString(array($g['origParse'])); } else { $base = $g['base_expr']; } $group_by_coord .= "`" . $base . "`"; } $group_by = " GROUP BY {$group_by}"; $group_by_coord = " GROUP BY {$group_by_coord}"; unset($this->parsed['GROUP']); } $limit = ""; $limit_coord = ""; if (!empty($this->parsed['LIMIT'])) { if ($this->parsed['LIMIT']['offset'] == "") { $this->parsed['LIMIT']['offset'] = 0; } $limit .= " LIMIT {$this->parsed['LIMIT']['offset']},{$this->parsed['LIMIT']['rowcount']}"; $limit_coord .= " LIMIT {$this->parsed['LIMIT']['offset']},{$this->parsed['LIMIT']['rowcount']}"; unset($this->parsed['LIMIT']); } foreach ($this->parsed as $key => $clauses) { $this->errors[] = array('Unsupported query', $key . ' clause is not supported'); } if ($this->errors) { return false; } $queries = array(); if (!empty($where_clauses)) { foreach ($where_clauses as $where) { $queries[] = $select['shard_sql'] . ' ' . $where . ' ' . $group_by . ' ' . $order_by . ' ' . $limit; } } else { if ($order_by === "") { $queries[] = $select['shard_sql'] . $group_by . ' ' . $limit; } else { $queries[] = $select['shard_sql'] . $group_by . ' ' . $order_by . ' ' . $limit; } } } else { $this->errors = array('Unsupported query', 'Missing expected clause:SELECT'); return false; } $this->coord_sql = $select['coord_sql'] . ' ' . $group_by_coord . ' ' . $order_by_coord . ' ' . $limit_coord; $this->coord_odku = $select['coord_odku']; $this->shard_sql = $queries; $this->agg_key_cols = $select['group_aliases']; if ($this->verbose) { echo "-- INPUT SQL:\n{$sql}\n"; echo "\n--PARALLEL OPTIMIZATIONS:\n"; if ($this->agg_key_cols) { echo "\n* The following projections were selected for a UNIQUE CHECK on the storage node operation:\n{$this->agg_key_cols}\n"; if ($this->coord_odku) { echo "\n* storage node result set merge optimization enabled:\nON DUPLICATE KEY UPDATE\n" . join(",\n", $this->coord_odku) . "\n"; } } echo "\n"; foreach ($this->messages as $msg) { echo "-- {$msg}\n"; } echo "\n-- SQL TO SEND TO SHARDS:\n"; print_r($this->shard_sql); echo "\n-- AGGREGATION SQL:\n{$this->coord_sql}" . ($this->agg_key_cols && $this->coord_odku ? "\nON DUPLICATE KEY UPDATE\n" . join(",\n", $this->coord_odku) . "\n" : "\n"); } else { if (is_array($sql)) { $this->outputString .= "-- INPUT SQL:\nArray\n"; } else { $this->outputString .= "-- INPUT SQL:\n{$sql}\n"; } $this->outputString .= "\n--PARALLEL OPTIMIZATIONS:\n"; if ($this->agg_key_cols) { $this->outputString .= "\n* The following projections were selected for a UNIQUE CHECK on the storage node operation:\n{$this->agg_key_cols}\n"; if ($this->coord_odku) { $this->outputString .= "\n* storage node result set merge optimization enabled:\nON DUPLICATE KEY UPDATE\n" . join(",\n", $this->coord_odku) . "\n"; } } $this->outputString .= "\n"; foreach ($this->messages as $msg) { $this->outputString .= "-- {$msg}\n"; } $this->outputString .= "\n-- SQL TO SEND TO SHARDS:\n"; $tmpReturn = print_r($this->shard_sql, true); //foreach($tmpReturn as $line) $this->outputString .= $tmpReturn . "\n"; $this->outputString .= "\n-- AGGREGATION SQL:\n{$this->coord_sql}" . ($this->agg_key_cols && $this->coord_odku ? "\nON DUPLICATE KEY UPDATE\n" . join(",\n", $this->coord_odku) . "\n" : "\n"); } return true; }