373 lines
12 KiB
PHP
373 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* OracleSQLTranslator.php
|
|
*
|
|
* A translator from MySQL dialect into Oracle dialect for Limesurvey
|
|
* (http://www.limesurvey.org/)
|
|
*
|
|
* Copyright (c) 2012, André Rothe <arothe@phosco.info, phosco@gmx.de>
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without modification,
|
|
* are permitted provided that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
|
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
|
* DAMAGE.
|
|
*/
|
|
|
|
namespace PHPSQLParser;
|
|
require_once dirname(__FILE__) . '/../vendor/autoload.php';
|
|
include_once $rootdir . '/classes/adodb/adodb.inc.php';
|
|
|
|
$_ENV['DEBUG'] = 1;
|
|
|
|
/**
|
|
* This class enhances the PHPSQLCreator to translate incoming
|
|
* parser output into another SQL dialect (here: Oracle SQL).
|
|
*
|
|
* @author arothe
|
|
*
|
|
*/
|
|
class OracleSQLTranslator extends PHPSQLCreator {
|
|
|
|
private $con; # this is the database connection from LimeSurvey
|
|
private $preventColumnRefs = array();
|
|
private $allTables = array();
|
|
const ASTERISK_ALIAS = "[#RePl#]";
|
|
|
|
public function __construct($con) {
|
|
parent::__construct();
|
|
$this->con = $con;
|
|
$this->initGlobalVariables();
|
|
}
|
|
|
|
private function initGlobalVariables() {
|
|
$this->preventColumnRefs = false;
|
|
$this->allTables = array();
|
|
}
|
|
|
|
public static function dbgprint($txt) {
|
|
if (isset($_ENV['DEBUG'])) {
|
|
print $txt;
|
|
}
|
|
}
|
|
|
|
public static function preprint($s, $return = false) {
|
|
$x = "<pre>";
|
|
$x .= print_r($s, 1);
|
|
$x .= "</pre>";
|
|
if ($return) {
|
|
return $x;
|
|
}
|
|
self::dbgprint($x . "<br/>\n");
|
|
}
|
|
|
|
protected function processAlias($parsed) {
|
|
if ($parsed === false) {
|
|
return "";
|
|
}
|
|
# we don't need an AS between expression and alias
|
|
$sql = " " . $parsed['name'];
|
|
return $sql;
|
|
}
|
|
|
|
protected function processDELETE($parsed) {
|
|
if (count($parsed['TABLES']) > 1) {
|
|
die("cannot translate delete statement into Oracle dialect, multiple tables are not allowed.");
|
|
}
|
|
return "DELETE";
|
|
}
|
|
|
|
public static function getColumnNameFor($column) {
|
|
if (strtolower($column) === 'uid') {
|
|
$column = "uid_";
|
|
}
|
|
// TODO: add more here, if necessary
|
|
return $column;
|
|
}
|
|
|
|
public static function getShortTableNameFor($table) {
|
|
if (strtolower($table) === 'surveys_languagesettings') {
|
|
$table = 'surveys_lngsettings';
|
|
}
|
|
// TODO: add more here, if necessary
|
|
return $table;
|
|
}
|
|
|
|
protected function processTable($parsed, $index) {
|
|
if ($parsed['expr_type'] !== 'table') {
|
|
return "";
|
|
}
|
|
|
|
$sql = $this->getShortTableNameFor($parsed['table']);
|
|
$alias = $this->processAlias($parsed['alias']);
|
|
$sql .= $alias;
|
|
|
|
if ($index !== 0) {
|
|
$sql = $this->processJoin($parsed['join_type']) . " " . $sql;
|
|
$sql .= $this->processRefType($parsed['ref_type']);
|
|
$sql .= $this->processRefClause($parsed['ref_clause']);
|
|
}
|
|
|
|
# store the table and its alias for later use
|
|
$last = array_pop($this->allTables);
|
|
$last['tables'][] = array('table' => $this->getShortTableNameFor($parsed['table']), 'alias' => trim($alias));
|
|
$this->allTables[] = $last;
|
|
|
|
return $sql;
|
|
}
|
|
|
|
protected function processFROM($parsed) {
|
|
$this->allTables[] = array('tables' => array(), 'alias' => '');
|
|
return parent::processFROM($parsed);
|
|
}
|
|
|
|
protected function processTableExpression($parsed, $index) {
|
|
if ($parsed['expr_type'] !== 'table_expression') {
|
|
return "";
|
|
}
|
|
$sql = substr($this->processFROM($parsed['sub_tree']), 5); // remove FROM keyword
|
|
$sql = "(" . $sql . ")";
|
|
|
|
$alias .= $this->processAlias($parsed['alias']);
|
|
$sql .= $alias;
|
|
|
|
# store the tables-expression-alias for later use
|
|
$last = array_pop($this->allTables);
|
|
$last['alias'] = trim($alias);
|
|
$this->allTables[] = $last;
|
|
|
|
if ($index !== 0) {
|
|
$sql = $this->processJoin($parsed['join_type']) . " " . $sql;
|
|
$sql .= $this->processRefType($parsed['ref_type']);
|
|
$sql .= $this->processRefClause($parsed['ref_clause']);
|
|
}
|
|
return $sql;
|
|
}
|
|
|
|
private function getTableNameFromExpression($expr) {
|
|
$pos = strpos($expr, ".");
|
|
if ($pos === false) {
|
|
$pos = -1;
|
|
}
|
|
return trim(substr($expr, 0, $pos + 1), ".");
|
|
}
|
|
|
|
private function getColumnNameFromExpression($expr) {
|
|
$pos = strpos($expr, ".");
|
|
if ($pos === false) {
|
|
$pos = -1;
|
|
}
|
|
return substr($expr, $pos + 1);
|
|
}
|
|
|
|
private function isCLOBColumnInDB($table, $column) {
|
|
$res = $this->con->GetOne(
|
|
"SELECT count(*) FROM user_lobs WHERE table_name='" . strtoupper($table) . "' AND column_name='"
|
|
. strtoupper($column) . "'");
|
|
return ($res >= 1);
|
|
}
|
|
|
|
protected function isCLOBColumn($table, $column) {
|
|
$tables = end($this->allTables);
|
|
|
|
if ($table === "") {
|
|
foreach ($tables['tables'] as $k => $v) {
|
|
if ($this->isCLOBColumn($v['table'], $column)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
# check the aliases, $table cannot be empty
|
|
foreach ($tables['tables'] as $k => $v) {
|
|
if ((strtolower($v['alias']) === strtolower($table))
|
|
|| (strtolower($tables['alias']) === strtolower($table))) {
|
|
if ($this->isCLOBColumnInDB($v['table'], $column)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
# it must be a valid table name
|
|
return $this->isCLOBColumnInDB($table, $column);
|
|
}
|
|
|
|
protected function processOrderByExpression($parsed) {
|
|
if ($parsed['expr_type'] !== 'expression') {
|
|
return "";
|
|
}
|
|
|
|
$table = $this->getTableNameFromExpression($parsed['base_expr']);
|
|
$col = $this->getColumnNameFromExpression($parsed['base_expr']);
|
|
|
|
$sql = ($table !== "" ? $table . "." : "") . $col;
|
|
|
|
# check, if the column is a CLOB
|
|
if ($this->isCLOBColumn($table, $col)) {
|
|
$sql = "cast(substr(" . $sql . ",1,200) as varchar2(200))";
|
|
}
|
|
|
|
return $sql . " " . $parsed['direction'];
|
|
}
|
|
|
|
protected function processColRef($parsed) {
|
|
if ($parsed['expr_type'] !== 'colref') {
|
|
return "";
|
|
}
|
|
|
|
$table = $this->getTableNameFromExpression($parsed['base_expr']);
|
|
$col = $this->getColumnNameFromexpression($parsed['base_expr']);
|
|
|
|
# we have to change the column name, if the column is uid
|
|
# we have to change the tablereference, if the tablename is too long
|
|
$col = $this->getColumnNameFor($col);
|
|
$table = $this->getShortTableNameFor($table);
|
|
|
|
# if we have * as colref, we cannot use other columns
|
|
# we have to add alias.* if we know all table aliases
|
|
if (($table === "") && ($col === "*")) {
|
|
array_pop($this->preventColumnRefs);
|
|
$this->preventColumnRefs[] = true;
|
|
return ASTERISK_ALIAS; # this is the position, we have to replace later
|
|
}
|
|
|
|
$alias = "";
|
|
if (isset($parsed['alias'])) {
|
|
$alias = $this->processAlias($parsed['alias']);
|
|
}
|
|
|
|
return (($table !== "") ? ($table . "." . $col) : $col) . $alias;
|
|
}
|
|
|
|
protected function processFunctionOnSelect($parsed) {
|
|
$old = end($this->preventColumnRefs);
|
|
$sql = $this->processFunction($parsed);
|
|
|
|
if ($old !== end($this->preventColumnRefs)) {
|
|
# prevents wrong handling of count(*)
|
|
array_pop($this->preventColumnRefs);
|
|
$this->preventColumnRefs[] = $old;
|
|
$sql = str_replace(ASTERISK_ALIAS, "*", $sql);
|
|
}
|
|
return $sql;
|
|
}
|
|
|
|
protected function processSELECT($parsed) {
|
|
$this->preventColumnRefs[] = false;
|
|
|
|
$sql = "";
|
|
foreach ($parsed as $k => $v) {
|
|
$len = strlen($sql);
|
|
$sql .= $this->processColRef($v);
|
|
$sql .= $this->processSelectExpression($v);
|
|
$sql .= $this->processFunctionOnSelect($v);
|
|
$sql .= $this->processConstant($v);
|
|
|
|
if ($len == strlen($sql)) {
|
|
$this->stop('SELECT', $k, $v, 'expr_type');
|
|
}
|
|
|
|
$sql .= ",";
|
|
}
|
|
$sql = substr($sql, 0, -1);
|
|
return "SELECT " . $sql;
|
|
}
|
|
|
|
private function correctColRefStatement($sql) {
|
|
$alias = "";
|
|
$tables = end($this->allTables);
|
|
|
|
# should we correct the selection list?
|
|
if (array_pop($this->preventColumnRefs)) {
|
|
|
|
# do we have a table-expression alias?
|
|
if ($tables['alias'] !== "") {
|
|
$alias = $tables['alias'] . ".*";
|
|
} else {
|
|
foreach ($tables['tables'] as $k => $v) {
|
|
$alias .= ($v['alias'] === "" ? $v['table'] : $v['alias']) . ".*,";
|
|
}
|
|
$alias = substr($alias, 0, -1);
|
|
}
|
|
$sql = str_replace(ASTERISK_ALIAS, $alias, $sql);
|
|
}
|
|
return $sql;
|
|
}
|
|
|
|
protected function processSelectStatement($parsed) {
|
|
$sql = $this->processSELECT($parsed['SELECT']);
|
|
$from = $this->processFROM($parsed['FROM']);
|
|
|
|
# correct * references with tablealias.*
|
|
# this must be called after processFROM(), because we need the table information
|
|
$sql = $this->correctColRefStatement($sql) . " " . $from;
|
|
|
|
if (isset($parsed['WHERE'])) {
|
|
$sql .= " " . $this->processWHERE($parsed['WHERE']);
|
|
}
|
|
if (isset($parsed['GROUP'])) {
|
|
$sql .= " " . $this->processGROUP($parsed['GROUP']);
|
|
}
|
|
if (isset($parsed['ORDER'])) {
|
|
$sql .= " " . $this->processORDER($parsed['ORDER']);
|
|
}
|
|
|
|
# select finished, we remove its tables
|
|
# FIXME: we should add it to the previous tablelist with the
|
|
# global alias, if such one exists
|
|
array_pop($this->allTables);
|
|
|
|
return $sql;
|
|
}
|
|
|
|
public function create($parsed) {
|
|
$k = key($parsed);
|
|
switch ($k) {
|
|
case "USE":
|
|
# this statement is not an Oracle statement
|
|
$this->created = "";
|
|
break;
|
|
|
|
default:
|
|
$this->created = parent::create($parsed);
|
|
break;
|
|
}
|
|
return $this->created;
|
|
}
|
|
|
|
public function process($sql) {
|
|
self::dbgprint($sql . "<br/>");
|
|
|
|
$this->initGlobalVariables();
|
|
$parser = new PHPSQLParser($sql);
|
|
self::preprint($parser->parsed);
|
|
|
|
$sql = $this->create($parser->parsed);
|
|
self::dbgprint($sql . "<br/>");
|
|
|
|
return $sql;
|
|
}
|
|
}
|
|
|
|
//$translator = new OracleSQLTranslator(false);
|
|
//$translator->process("SELECT q.qid, question, gid FROM questions as q WHERE (select count(*) from answers as a where a.qid=q.qid and scale_id=0)=0 and sid=11929 AND type IN ('F', 'H', 'W', 'Z', '1') and q.parent_qid=0");
|
|
//$translator->process("SELECT *, (SELECT a from xyz WHERE b>1) haha, (SELECT *, b from zks,abc WHERE d=1) hoho FROM blubb d, blibb c WHERE d.col = c.col");
|
|
|
|
?>
|