autosoft
Guest
Создание приложения для нескольких SQL-серверов
Каждый сервер имеет свои особенности в использовании SQL. Например, в DML: способ формирования соединений таблиц, способ формирования порционных выборок и т.д.
Предположим, что доступ к серверу осуществляется путем использования ADODB или подобным ему. В статье "Абстрактный доступ к БД с помощью ADODB" опубликованной в PHP Inside №2 говорится, что: "Если бы Вы использовали абстрактный класс доступа к БД - Вам, скорее всего, не пришлось бы менять php-код (только в одном месте указать что используется PostgreSQL). Менять SQL-запросы все равно придется, но и это не всегда необходимо".
Что скажет общественность на такой вариант "адаптации" SQL-запросов под особенности конкретного SQL-сервера. Для того, что бы не "менять SQL-запросы" размещать в них специальные логические конструкции (по аналогии с препроцессором C/C++) и производить "окончательную подготовку" такого запроса непосредственно перед его выполнением.
Идея не нова. Предлагаю её реализацию применительно к PHP.
Например, следующий программный код для PHP 5.0.5 позволит решить такую задачу:
Результатом выполнения будет:
SELECT A.*, B.* FROM A, OUTER B WHERE A.ID = B.A_ID ORDER BY 1
SELECT A.*, B.* FROM A LEFT JOIN B ON A.ID = B.A_ID ORDER BY 1
Класс t_sql_parser:
В целом синтаксическую конструкцию использования такого "препроцессора" можна описать следующим образом:
запрос
[#if (перечисление_серверов)
запрос
[#else
запрос]
#endif]
запрос
перечисление_серверов ::= [!] имя_сервера [|| перечисление_серверов | && перечисление_серверов]
"Служебными" являются конструкции: #if, #else, #endif. Все остальные игнорируются.
Служебные конструкции являются регистрозависимыми.
Конструкция #if ... #endif может указываться в любом месте запроса.
Уровень вложенности конструкций #if ... #endif неограничен.
Особенность: если в запросе требуется явное использование символа '#' то его необходимо дублировать.
Пока могу отметить только одно возможное улучшение - PHP это компилируемо-транслируемый язык, поэтому если такой препроцессор SQL-запросов реализовать в виде расширения PHP, можно существенно повысить скорость окончательной подготовки SQL-запроса.
Какие ещё преимущества и недостатки есть у такого подхода?
Каждый сервер имеет свои особенности в использовании SQL. Например, в DML: способ формирования соединений таблиц, способ формирования порционных выборок и т.д.
Предположим, что доступ к серверу осуществляется путем использования ADODB или подобным ему. В статье "Абстрактный доступ к БД с помощью ADODB" опубликованной в PHP Inside №2 говорится, что: "Если бы Вы использовали абстрактный класс доступа к БД - Вам, скорее всего, не пришлось бы менять php-код (только в одном месте указать что используется PostgreSQL). Менять SQL-запросы все равно придется, но и это не всегда необходимо".
Что скажет общественность на такой вариант "адаптации" SQL-запросов под особенности конкретного SQL-сервера. Для того, что бы не "менять SQL-запросы" размещать в них специальные логические конструкции (по аналогии с препроцессором C/C++) и производить "окончательную подготовку" такого запроса непосредственно перед его выполнением.
Идея не нова. Предлагаю её реализацию применительно к PHP.
Например, следующий программный код для PHP 5.0.5 позволит решить такую задачу:
PHP:
$sql =
"SELECT A.*, B.*
FROM
#if (informix)
A, OUTER B WHERE A.ID = B.A_ID
#else
A LEFT JOIN B ON A.ID = B.A_ID
#endif
ORDER BY 1";
$sql_parser = new t_sql_parser("informix");
echo $sql_parser->parse($sql), "<br>";
$sql_parser = new t_sql_parser("firebird");
echo $sql_parser->parse($sql), "<br>";
SELECT A.*, B.* FROM A, OUTER B WHERE A.ID = B.A_ID ORDER BY 1
SELECT A.*, B.* FROM A LEFT JOIN B ON A.ID = B.A_ID ORDER BY 1
Класс t_sql_parser:
PHP:
class t_sql_parser {
private $f_server_string;
private $f_system_char;
private $f_separator_char;
private $f_index;
private $f_length;
private $f_sql;
private $f_sql_result;
function __construct($server_string) {
$this->f_server_string = $server_string;
$this->f_system_char = array(" ", "(", ")", "+", "-", "/", "*", "&",
"|", "[", "]", ",", ".", "#", "'", "!", "\"", "\r", "\n");
$this->f_separator_char = array(" ", "\r", "\n");
}
function parse($sql) {
$this->f_sql = $sql;
$this->f_sql_result = "";
$this->f_index = 0;
$this->f_length = strlen($sql);
while (strlen($word = $this->word()))
switch ($word) {
case "#if":
$this->parse_if(true);
break;
case "#else":
throw new Exception("Ошибка использования #else");
break;
case "#endif":
throw new Exception("Ошибка использования #endif");
break;
default:
$this->f_sql_result .= $word;
}
return $this->f_sql_result;
}
private function parse_word($word, $need) {
if (!strlen($word))
throw new Exception("Отсутствует '$need'");
if (strcmp($word, $need))
throw new Exception("Вместо '$need' указано '$word'");
}
private function parse_if($enabled) {
while (in_array($word = $this->word(), $this->f_separator_char));
$this->parse_word($word, "(");
$param = $this->parse_if_param();
while (in_array($word = $this->word(), $this->f_separator_char));
$this->parse_word($word, ")");
while (strlen($word = $this->word())) // #if
switch ($word) {
case "#if":
$this->parse_if($enabled && $param);
break;
case "#else":
break 2;
case "#endif":
return;
default:
if ($enabled && $param) $this->f_sql_result .= $word;
}
while (strlen($word = $this->word())) // #else
switch ($word) {
case "#if":
$this->parse_if($enabled && !$param);
break;
case "#else":
throw new Exception("Ошибка использования #else");
break;
case "#endif":
return;
default:
if ($enabled && !$param) $this->f_sql_result .= $word;
}
throw new Exception("Отсутствует конструкция #endif");
}
private function parse_if_param() {
while (in_array($word = $this->word(), $this->f_separator_char));
if (!strcmp($word, "(")) {
$result = $this->parse_if_param();
while (in_array($word = $this->word(), $this->f_separator_char));
$this->parse_word($word, ")");
} else
if (!strcmp($word, "!")) $result = !$this->parse_if_param();
else {
if (!strcmp($word, "||") || !strcmp($word, "&&") ||
in_array($word, $this->f_system_char))
throw new Exception("Ошибка использования $word");
$result = !strcmp($this->f_server_string, $word);
}
$index_save = $this->f_index;
while (in_array($word = $this->word(), $this->f_separator_char));
switch ($word) {
case "||":
$result = $result | $this->parse_if_param();
break;
case "&&":
$result = $result & $this->parse_if_param();
break;
default:
$this->f_index = $index_save;
}
return $result;
}
private function item() {
$result = "";
if ($this->f_index < $this->f_length) {
while (($this->f_index < $this->f_length) &&
(!in_array($char = $this->f_sql{$this->f_index}, $this->f_system_char))) {
$result .= $char;
$this->f_index++;
}
if (!strlen($result)) {
$result = $char;
$this->f_index++;
}
}
return $result;
}
private function word() {
switch ($result = $this->item()) {
case "#":
if (strcmp($next = $this->item(), "#")) $result .= $next;
break;
case "|":
$index_save = $this->f_index;
if (!strcmp($this->item(), "|")) $result = '||';
else $this->f_index = $index_save;
break;
case "&":
$index_save = $this->f_index;
if (!strcmp($this->item(), "&")) $result = '&&';
else $this->f_index = $index_save;
break;
}
return $result;
}
}
запрос
[#if (перечисление_серверов)
запрос
[#else
запрос]
#endif]
запрос
перечисление_серверов ::= [!] имя_сервера [|| перечисление_серверов | && перечисление_серверов]
"Служебными" являются конструкции: #if, #else, #endif. Все остальные игнорируются.
Служебные конструкции являются регистрозависимыми.
Конструкция #if ... #endif может указываться в любом месте запроса.
Уровень вложенности конструкций #if ... #endif неограничен.
Особенность: если в запросе требуется явное использование символа '#' то его необходимо дублировать.
Пока могу отметить только одно возможное улучшение - PHP это компилируемо-транслируемый язык, поэтому если такой препроцессор SQL-запросов реализовать в виде расширения PHP, можно существенно повысить скорость окончательной подготовки SQL-запроса.
Какие ещё преимущества и недостатки есть у такого подхода?