За последние 24 часа нас посетили 22870 программистов и 1252 робота. Сейчас ищут 584 программиста ...

Работа с множеством СУБД через единый интерфейс.

Тема в разделе "Решения, алгоритмы", создана пользователем Hight, 6 апр 2007.

  1. Hight

    Hight Старожил
    Команда форума Модератор

    С нами с:
    5 мар 2006
    Сообщения:
    7.153
    Симпатии:
    0
    Адрес:
    из злой параллельной вселенной
    Экспериментировал на досуге. Может кому пригодится.

    Небольшое введение:

    Существует множество СУРБД/СУБД и все они используют для своей работы SQL и собственные агрегатные функции. Но синтаксис SQL и агрегатные функции в СУБД от разных производителей немного отличаются друг от друга. Вследствие чего запросы, составленные для одной СУБД, могут не работать в другой.
    Данная особенность усложняет разработку ПО, ориентированного на множество СУБД.

    Проблемы, возникающие при разработке ПО, ориентированного на использование множества СУБД:
    1. “Сложность” составления универсальных запросов, одинаково работающих в разных СУБД, а иногда и невозможность составления таковых.
    2. Нагромождение условных операторов в коде программы при невозможности написания универсального запроса для всех СУРБД на которые ориентированна программа.
    3. Существенное замедление работы приложения при использовании инструментов, работающих по принципу:
    • Все запросы составляются с учётом использования лексем некого универсального SQL.
    • Инструмент, получив некий универсальный запрос, анализирует его и составляет на его основе запросы, удовлетворяющие требования к синтаксису запросов той или иной СУБД.
    Существуют и другие проблемы, как существуют и другие варианты решений по обеспечению работы приложения с разными СУРБД. Разумеется каждое решение обладает своими достоинствами и недостатками.


    ***

    Задачка: Разработать концепцию и прототип решения для разработки ПО, ориентированного на работу с множеством СУБД.

    Условия:
    1. Простота использования
    2. Высокая скорость
    3. Отсутствие ограничений на работу с СУРБД (то-есть создать иллюзию, как будто работаешь напрямую с функциями PHP - это основная идея)
    4. Высокая гибкость решения
    5. Легкая модифицируемость

    Концепция: Решение должно создавать иллюзию, что идёт работа напрямую с функциями по работе с СУБД языка PHP. При этом автоматизируются некоторые функции.

    Прототип оформлен в виде класса. Написан на PHP.

    Основные принципы:
    1. Класс обеспечивает автоматическое подключение и отключение от сервера БД, данная функциональность реализована в конструкторе и деструкторе соответственно. Вследствие чего работа приложения возможна только при использовании PHP5.
    2. Запросы к БД и экранирование символов осуществляются при использовании соответствующих методов класса. Выбор типа сервера БД происходит автоматически в зависимости от конфигурации.
    3. Все запросы, используемые приложением, размещаются в отдельных файлах. То-есть для СУРБД MySQL запросы находятся в файле “mysql_queries.php”, для PostgreSQL в файле “pgsql_queries.php” соответственно. Подключение данных файлов производится в теле самой программы при помощи условного оператора.

    Тем самым написав один раз программу с использованием данного класса, в случае необходимости её портирования на новую СУРБД, нам не придётся изменять код самой программы, а придётся дописать файл с запросами, удовлетворяющими синтаксису новой СУРБД. Соответственно, в случае отсутствия поддержки классом новой СУБД, придётся реализовать данную поддержку.
    Так же возможна разработка ПО способного изначально без проблем работать на серверах с разными БД.

    Класс создаёт иллюзию, как будто напрямую работаешь с функциями языка PHP, что, на мой взгляд, очень удобно.

    В дальнейшем можно внести в класс поддержку большего числа функций по работе с СУБД. Сейчас реализована, исключительно для примера, поддержка MySQL и PgSQL. Так же реализованы только основные функции, например mysql_query и pg_query, а так же ещё несколько.

    Пример решения:
    сlass_db.php – файл с классом для работы с БД. Описание методов класса прилагается.
    config.php – файл конфигурации
    db_error.log – лог ошибок
    mysql_queries.php – файл с запросами для MySQL
    pgsql_queries.php – файл с запросами для PostgreSQL
    test.php – пример написании маленькой программки с использование вышеописанного класса.

    Не буду углубляться дальше, просто скачайте и посмотрите исходники. Там всё понятно. Если алгоритм и принципы решения понравятся тогда сделаю полнофункциональный класс с хорошим описанием.

    Скачать: www.hight.fatal.ru/trash/class_db.rar

    жду комментариев.
     
  2. Ti

    Ti Активный пользователь

    С нами с:
    3 июл 2006
    Сообщения:
    2.378
    Симпатии:
    1
    Адрес:
    d1.ru, Екатеринбург
    PDO не рулед?
    В PDO уже многие функции есть + туева куча поддерживаемых типов баз данных
    Обрабатывать ошибки можно стандартно для всей системы сайта - через исключения.

    PHP:
    1.  
    2. <?
    3. class db extends PDO {
    4.     function factory($params) {
    5.         $class = "db_$params[adapter]";
    6.         $obj = new $class($params);
    7.         $obj->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); # обработка ошибок - исключения
    8.         return $obj;
    9.     }
    10.     # общие функции для всех db_* ...
    11. }
    12.  
    13.  
    14. class db_mysql extends db {
    15.     function __construct($params) {
    16.         # значения по-умолчанию для mysql
    17.         $persistent = false; # постоянное подключение?
    18.  
    19.         # получение параметров
    20.         extract($params);
    21.         $PDOParams = array( PDO::ATTR_PERSISTENT => $persistent );
    22.  
    23.         # подключение
    24.         parent::__construct("mysql:host=$host;dbname=$dbname", $user, $pass, $PDOParams);
    25.     }
    26.     # специфичные функции для mysql ...
    27. }
    28.  
    29. # использование
    30. $params = array(
    31.     'adapter'=>'mysq',
    32.     'host'=>'localhost',
    33.     'user'=>'user',
    34.     'pass'=>'password',
    35.     'dbname'=>'test',
    36.     'persistent'=>true
    37. );
    38. db::factory($params);
    39.  
    а функции мы выносим в отдельный класс db_{название_класса}, для каждого класса который юзает db, по той же схеме, т. е. db_{название_класса} - общие для всех db, расширяющий его класс db_{название_класса}_mysql - для специфичные для mysql
     
  3. Hight

    Hight Старожил
    Команда форума Модератор

    С нами с:
    5 мар 2006
    Сообщения:
    7.153
    Симпатии:
    0
    Адрес:
    из злой параллельной вселенной
    не рулед. не надо навязывать другие решения... то-есть DbSimple не предлагать. Меня интересует исключительно теория - как обеспечить поддержку множества БД приложением.

    Первый пост в 4:52 am написал, самому сложно читать. :)
     
  4. Ti

    Ti Активный пользователь

    С нами с:
    3 июл 2006
    Сообщения:
    2.378
    Симпатии:
    1
    Адрес:
    d1.ru, Екатеринбург
    ИМХО, не гуманно все функции работы сайта с db хранить в одном файле:
    есть класс topic - ему объект БД возвращает объект db_topic (или, расширяющий его, объект db_topic_mysql)


    ок. Но кто мешает юзать другие бибиотеки по той же схеме?
    ЗЫ. а PDO уже так же стандартен как DOM
     
  5. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    Мне решение не понравилось своим подходом. В нем реализовано что-то типа поддержки "многоязычности", но только для языков запросов - для каждого запроса, использованного в проекте, нужно написать его отдельно для MySQL, отдельно для HySQL... неудобно, слишком много писанины :( когда мы делаем подобным образом поддержку многоязычности, с этим неудобством можно смириться - разные языки различаются всеми фразами. А в данном случае будет различаться только небольшая часть запросов, а подавляющая часть будет одинаковой для всех СУБД, в результате получается куча копи-паста... даже в тестовом примере, словарь запросов для MySQL ничем не отличается от словаря для Postgre. И еще одна заруба - при добавлении новой СУБД файл class_db.php придется дописывать весьма основательно.
     
  6. Ti

    Ti Активный пользователь

    С нами с:
    3 июл 2006
    Сообщения:
    2.378
    Симпатии:
    1
    Адрес:
    d1.ru, Екатеринбург
    постом выше, глубокоуважаемый Ti предлагал в db_{class}_mysql печатать толька отличия от db_{class} ;)
     
  7. Hight

    Hight Старожил
    Команда форума Модератор

    С нами с:
    5 мар 2006
    Сообщения:
    7.153
    Симпатии:
    0
    Адрес:
    из злой параллельной вселенной
    всё, пошёл диалог! =) Вот я как раз и ищу подход.
    Насчёт писанины согласен, конечно её будет много... например: если в программе используется 50 запросов то будет 2 файла с теми самыми 50 запросами. Но с другой стороны этот подход надёжен и быстр, не в плане разработки, а в плане производительности. Ибо не придётся парсить какой-то один запрос и подстраивать его под определённый сервер БД. Да и файлик с запросами погружается только один, а можно вообще разделить файлы с запросами, то-есть каждый модуль программы будет подгружать только свои запросы.
    а вот это можно легко обойти. можно изменить структура класса и разнести функции БД по разным файлам, тем самым, чтобы добавить поддержку новой БД нужно будет просто написать файл с функциями по уже готовому шаблону.
     
  8. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    Hight
    Я не против такого подхода в принципе, мне просто не по душе идея хранения всех запросов где-то там отдельно... хранить все запросы системы в куче, имхо, нерационально - вырванные из контекста, они не несут никакого смысла. В программе же участки кода без запросов станут совершенно непонятными, придется каждый раз сверяться со словарем запросов, чтобы понять, что у нас в программе происходит... тоже небольшое удовольствие.

    Ti
    "Боб Доул очень любит, когда Боб Доул говорит о Бобе Доуле. Боб Доул!" ("Симпсоны") ;)
     
  9. Ti

    Ti Активный пользователь

    С нами с:
    3 июл 2006
    Сообщения:
    2.378
    Симпатии:
    1
    Адрес:
    d1.ru, Екатеринбург
    Буду спорить. Хранить запросы отдельно вполне здраво.
    В участке кода, вместо запросов, будет абстрактная функция получения данных. Инкапсуляция рулед.
     
  10. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    Ti
    Если вместо них остается что-то читабельное, например метод класса, по которому можно сказать, что именно он возвращает, тогда возможно. Если же вместо запроса останется только обращение к массиву, да еще по малопонятному ключу... во-первых, начинается беготня по словарю, стоит его хоть чуть-чуть подзабыть, во-вторых, представь, что тебе нужно изменить какой-то из запросов. Что ты будешь делать? Просто изменишь текст запроса в словаре? Как бы не так ;) тебе придется пройтись по всему проекту, чтобы убедиться, что этот запрос нигде больше не используется, кроме модифицируемой точки, и если используется - сделать копию, ее уже и править. Это типичная ситуация из категории "хотели как лучше".
     
  11. Hight

    Hight Старожил
    Команда форума Модератор

    С нами с:
    5 мар 2006
    Сообщения:
    7.153
    Симпатии:
    0
    Адрес:
    из злой параллельной вселенной
    на мой взгляд притянуто за уши... всегда можно сделать комментарий перед выполнением функции query в котором написать выполняемый запрос и полностью разъяснить зачем, куда и почему, даже ключ самого массива $query можно сделать идентичным запросу, чтобы было наглядно, но это конечно извращение :)
    да, просто изменю сам запрос. даже если запрос и используется где-то ещё по ходу программы это ничего не меняет. всё это надо предусматривать по ходу разработки.

    Dagdamor
    кстати, а как бы ты реализовал данную функциональность?
     
  12. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Глубоко в топик не вдавался (времени небыло), но из общей мысли и возникшего спора возникла вот такая идея:
    PHP:
    1.  
    2. <?php
    3. /*
    4. *  DB - это прослойка для универсальности функций запросов к базе (mysql_query, pgsql_query и.т.д.)
    5. */
    6. class Data extends DB{
    7.    
    8.     public __construct(){
    9.     }
    10.    
    11.     /*
    12.     * Тупейший пример. Просто надо выбрать что-то для чего-то из таблицы
    13.     */
    14.     public getSomeSimpleData($id){
    15.         $return $this->query('SELECT * FROM table1 WHERE table1_id = '.(int)$id);
    16.     }                                                                  
    17.    
    18.     /*
    19.     * А вот тут уже что-то сложное
    20.     */                            
    21.     public getSomeComplexData($id){
    22.         /*
    23.         * Просто создаем пустой метод
    24.         */
    25.     }                            
    26.    
    27.     /*
    28.     * и.т.д. Везде, где запросы универсальны, пишем их сюда. Там где сложные запросы,
    29.     * которые требуют учёта спицифики каждой из баз данных, делаем как с getSomeComplexData()
    30.     */
    31. }
    32.  
    33. class Data_MySQL extends Data{
    34.  
    35.     public __construct(){
    36.         parent::__construct();
    37.     }                                                                                                                                                                                                                                                
    38.    
    39.     /*
    40.     * Просто тупо переопределяем функцию и пишем специфичный для MySQL запрос
    41.     */
    42.     public getSomeComplexData($id){
    43.         return $this->query('SELECT CONCAT(field1, field2) As field3 FROM table1 WHERE table1_id = '.(int)$id);
    44.     }
    45.    
    46.     /*
    47.     * И.т.д.
    48.     */
    49. }
    50.  
    51. class Data_PgSQL extends Data{
    52.  
    53.     public __construct(){
    54.         parent::__construct();
    55.     }                                                                                                                                                                                                                                                
    56.    
    57.     /*
    58.     * Просто тупо переопределяем функцию и пишем специфичный для PgSQL запрос
    59.     */
    60.     public getSomeComplexData($id){
    61.         return $this->query('SELECT (field1||field2) AS field3 FROM table1 WHERE table1_id = '.(int)$id);  
    62.     }
    63.    
    64.     /*
    65.     * И.т.д.
    66.     */
    67. }      
    68. ?>
    69.  
    Юзать це просто. В зависимости от используемой базы генерируем имя класса, инициализируем и вызываем методы :)
    PHP:
    1. <?php
    2. $class = 'Data_'.$current_db_name;
    3. $db = new $class;
    4. $resource1 = $db->getSomeSimpleData();
    5. $resource2 = $db->getSomeComplexData();
    6. ?>
    Ну и.т.д. Таким образом можно реализовать поддержку многих баз данных с минимально необходимым кол-вом Copy/Paste :)
     
  13. ONK

    ONK Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    281
    Симпатии:
    0
    Адрес:
    СПб
    Dagdamor, SQL различных СУБД различается настолько, что может быть совместим лиш при использовании самых примитивных запросов. Даже наиболее юзаемые LIMIT|OFFSET|TOP не совместимы с наиболее юзаемыми СУБД, про INSERT SELECT| REPLACE| ALTER TABLE не даже смысла упоминать.

    Попытки создать создать супер универсальный драйвер баз данных обречены на провал. ИМО, правильный путь - это индивидуальные решения. Как на уровне замены шаблонов SQL запросов в зависимости от текущей БД, так и на более высоком уровне типа get_some_data_as_array() (убирание процесса выбора драйвера, составления запроса и специфических действий в процедуру абстрактного запроса данных).

    Лично мне больше нравится идея с шаблонами запросов и универсальным интерфейсом работы с СУБД, за которым скрывается драйвер конкретной СУБД.
    Т.к. это сохраняет возможность пострения универсальных SQL запросов + позволяет избежать дополнительного кода кода и сэкономить время разработки. Всё это моё личное мнение и чистая теория, т.к. у меня пока не доходило до необходимости работать с разными СУБД одновременно, хотя я над этим думал.
     
  14. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    несколько тезисов:
    1. а надо ли менять СУБД как перчатки? на мой взгляд - нет ничего плохого в привязке к конкретной СУБД, просто нужно здраво подходить к её выбору.
    2. СУБД очень сильно отличаются. на разных СУБД одно и то же я бы писал совершенно по разному (из соображений эффективности и наглядности). это касается не только самих запросов, но и логики работы с БД. поэтому на мой взгляд многобазовость нужно реализовывать не на уровне драйвера СУБД, а на уровне модели.
     
  15. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    приведу пример, в sqlite можно написать такую конструкцию:
    Код (Text):
    1. select * from [catalog] where '$url' like [path]||'%'
    мускул, емнип, на такое не способен впринципе.
    да, это накладывает ограничение на размеры каталога, зато какое красивое решение =^_^=
     
  16. MiksIr

    MiksIr Активный пользователь

    С нами с:
    29 ноя 2006
    Сообщения:
    2.340
    Симпатии:
    44
    ONK и что, правда часто приходится писать запросы, которые не укладываются в ANSI99?
     
  17. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    MiksIr, очень :) например с использованием LIMIT-ов :)
     
  18. MiksIr

    MiksIr Активный пользователь

    С нами с:
    29 ноя 2006
    Сообщения:
    2.340
    Симпатии:
    44
    Ой, ну это да ;) Хотя mysql и postgresql поддерживают одинаковое написание ;) вот mssql вроде мимо, там TOP что ли...
     
  19. Hight

    Hight Старожил
    Команда форума Модератор

    С нами с:
    5 мар 2006
    Сообщения:
    7.153
    Симпатии:
    0
    Адрес:
    из злой параллельной вселенной
    оффтоп завязывайте... давно известно, что запросы к разным БД отличаются. речь идёт о решении данной проблемы, а не о её констатации как факта.
     
  20. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    dark-demon
    Это выборка ветки каталога, у которой path является подстрокой $url?
    Учти, что в твоем решении SQLite не сможет ни использовать индексы, ни как-либо оптимизировать маску поиска. Другими словами, запрос получается короткий, но тормозной. Кроме того, если у какой-то из записей path содержит символ '%', запрос вернет что попало ;) Для мускула запрос может выглядеть так:

    Код (Text):
    1. SELECT * FROM catalog WHERE path=LEFT('$url',LENGTH(path))
    Не так уж и плохо смотрится, имхо.
     
  21. Anonymous

    Anonymous Guest

    В Оракле вообще нет такой команды и даже подобия ее. =)
     
  22. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    dark-demon
    Согласен и с тем, и с другим.
    Если проект легкий - можно обойтись простыми запросами, а если тяжелый - тут и словарь запросов не поможет ;)
     
  23. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    а он полюбому тут не быстрый, поэтому и "ограничение на размер"...
    этого не может быть, потому, что этого не может быть впринципе :)
    ладно, беру свои слова обратно. на мускуле так сделать можно, но менее красиво. по скорости - наврятли это быстрее...

    Горбунов Олег, неужели там реализованы курсоры? :)
     
  24. Anonymous

    Anonymous Guest

    Курсоры там есть, но совсем для других целей. =)
    Так же для оракла нет команд типа mysql_num_rows — все это связано с архитектурой Оракла — записи из результирующего набора начинают отдаватся сразу, еще ДО завершения всей выборки. Отсюда и невозможность посчитать число строк перед отдачей напрямую. (обходные пути ессно, есть)
     
  25. ONK

    ONK Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    281
    Симпатии:
    0
    Адрес:
    СПб
    Горбунов Олег, это как можно отсортировать строки, не обработав весь рекордсет, он что волшебный этот ОРАКЛ?