Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > DSEL для работы с БД


Автор: Lazin 27.6.2009, 15:53
Возникла идея создать сабж..

Идея состоит в следующем, допустим у нас есть код, который умеет подключаться к БД, выполнять запрос и получать его результаты, соответственно остается распарсить таблицу результатов запроса и получить коллекцию объектов.

в простейших случаях это просто, так как одному экземпляру объекта, соответствует одна запись в таблице результатов запроса. Если у нас есть объект
Код

struct Employe
{
    std::string name;
    std::string sname;
    int age;
};

и запрос для получения списка этих объектов:
Код

SELECT name, sname, age FROM EmployeTable

то код будет прост и прямолинеен.. но что, если у нас не просто коллекция объектов, а иерархия объектов, и для получения каждого уровня иерархии нужен отдельный запрос, либо запрос один, но он объединяет несколько таблиц? Тогда придется писать код, который преобразует результаты запросов в иерархию объектов.
Код

struct Employe
{
    std::string name;
    std::string sname;
    int age;
};

struct Department
{
    int id;
    std::string name;
    std::vector<Employe> employe_list;

};

Код

SELECT name, sname, age, dep_id FROM EmployeTable ORDER BY dep_id

Код

SELECT id, name FROM DepTable ORDER BY id

в принципе, возможно написать библиотеку, которая позволяла-бы описывать то, как нужно извлекать данные из recordset-ов с результатами запросов, с помощью техники expression templates, а так-же позволяла-бы объединять результаты нескольких запросов в одну структура данных, в данном примере, результаты выполнения 2х селектов, в последовательность Department-ов, каждый их которых содержал бы набор объектов типа Employe, которые ему принадлежат...
код может при этом выглядеть так:
Код

struct Employe
{
    std::string name;
    std::string sname;
    int age;
    
    Employe(std::string name, std::string sname, int age)
        : name(name)
        , sname(sname)
        , age(age)
    {
    }
};

struct Department
{
    int id;
    std::string name;
    std::vector<Person> employe_list;
    
    Department( int id, std::string name)
        : id(id)
        , name(name)
    {
    }
};



namespace detail
{
    using namespace rdb;

    //запрос для получения списка сотрудников, отсортированых по отделам в которых они работают
    struct QueryEmployeList : query<QueryEmployeList>
    {
        std::string get_query_text() const
        {
            return "SELECT name, sname, age, dep_id FROM EmployeTable ORDER BY dep_id";
        }
    };

    // DSEL элементы для анализа результатов запроса
    // каждый из них "знает" из какого именно запроса(точнее результата выполнения этого запроса)
    // он может извлекать данные, а так-же "знает" как их интерпретировать(как int, как string etc)
    field<QueryEmployeList, std::string, 0> const employe_name;
    field<QueryEmployeList, std::string, 1> const employe_sname;
    field<QueryEmployeList, int, 2> const employe_age;
    field<QueryEmployeList, int, 3> const employe_dep;
    
    // запрос списка отделов
    struct QueryDepartmentList : query<QueryDepartmentList>
    {
        std::string get_query_text() const
        {
            return "SELECT id, name FROM DepTable ORDER BY id";
        }
    };
    
    // DSEL elements
    named_field<QueryDepartmentList, int> dep_id("id");
    named_field<QueryDepartmentList, std::string> dep_name("name");
    
    // ф-я для выполняет запросы и по их результатам строит коллекцию объектов
    void get_all_departments(connection& con, std::vector<Department>& dep_list)
    {
        recordset emps = execute_query(con, QueryEmployeList);
        recordset deps = execute_query(con, QueryDepartmentList);

        zip( deps, emps
           , create_a<Department>(dep_id, dep_name)            // create department
           , employe_dep == dep_id                             // while employe_dep == dep_id do
           , append_a( member_a<Department::employe_list>(_1)  // department.push_back( create employe )
                     , create_a<Employe>(employe_name, employe_sname, employe_age))
           , std::back_inserter(dep_list) );
    }
}

//connect db
rdb::connection connection( rdb::vendor::sqlite3, /*connection string*/db_file_name.c_str());

std::vector<Department> deps;
detail::get_all_departments(connection, deps);

Пользователь, предварительно описывает запросы, и элементы для DSL доступа к данным. Ф-я zip - принимает 6 параметров, первые 2 - запросы, первый из них, соответствует более высокому уровню иерархии(Department), второй более низкому(Employe). Далее ф-я zip вызывает первый функтор(3-й параметр), передавая ему в качестве параметра первый recordset, этот функтор должен вернуть объект класса Department. Далее, до тех пор, пока второй функтор(4-й параметр, получает в качестве параметров 2 recordset-a) возвращает true, zip вызывает 5-й функтор, передавая ему в качестве параметров, ранее созданый объект класса Department и второй recordset. Этот функтор создает объект класса Employe и добавляет его в employe_list объекта department. После того как 4-й функтор вернет false, полученый объект класса Department будет записан в контейнер(через итератор), курсор первого recordset-a переместится на следующую запись, и все повториться заново. В результате будем иметь контейнер заполненный данными.
Этот код зависит от того, как запросы возвращают данные, но он может от этого и не зависеть. 

В общем вопрос в том, стали бы вы использовать что-то подобное. Думаю реализовать это вполне возможно, вопрос в том, нужно-ли.. smile 
зы
как делать update-ы я пока не думал, но, если у нас есть все для того, что-бы извлекать данные из recordset-ов и создавать на основе этой информации структуры данных, то обратная задача то-же вполне решаема.. 

Автор: unicuum 27.6.2009, 16:35
Цитата(Lazin @  27.6.2009,  15:53 Найти цитируемый пост)
с помощью техники expression templates, а так-же позволяла-бы объединять результаты нескольких запросов в одну структура данных

Напоминает наборы данных в дотнете. Они там являются частичной или полной копией базы данных в оперативной памяти. И им так же доступны все операции и разнообразные выражения. Да, этой техникой пользуются, и думаю хорошей реализацией на C++ тоже будут пользоваться.

А вот другой вариант это структуры данных и их совмещение с базами данных. Например, древовидный список можно представить в виде одной таблицы. Можно его считать из базы в особую древовидную структуру, а можно закешировать в оперативной памяти в том виде, в каком это лежит в базе данных и лишь отображать по иному (MVC и так далее).

Опять же, если мы на языке C++ в программу введём таблицы, поля и прочее, то по аналогии с дотнетом, можно сказать, что у нас получится строгая типизация. Там это делают за счёт наследования от набора данных. Здесь ещё вот в чём вопрос, быстрее ли обращаться к базе, которая зачастую имеет собственные механизмы кэширования, или записывать то, что нам нужно в оперативную память.

Опять же, даже если приложение многоуровневое, получив некие данные, можно записать их в базу (с возможностью или без возможности кэширования), и не тратить ресурсы приложения на это.

Добавлено через 3 минуты и 36 секунд
В общем, я так понимаю, тема про отсоединённые и присоединённые объекты, а так же строгая и не строгая типизация в C++. Поправь если неправильно понял мысль. smile 

Автор: Lazin 27.6.2009, 16:44
ах да, еще фишка в том, что к объектам, нет ни каких требований

Автор: Lazin 27.6.2009, 20:50
Цитата(unicuum @  27.6.2009,  16:35 Найти цитируемый пост)
Поправь если неправильно понял мысль

ты это о чем вообще?

Автор: unicuum 27.6.2009, 21:10
Цитата(Lazin @  27.6.2009,  20:50 Найти цитируемый пост)
ты это о чем вообще? 

Свой же топик хочешь свести к флуду, думаешь он популярнее что-ли от этого станет. smile 

Автор: Lazin 27.6.2009, 21:34
Цитата(unicuum @  27.6.2009,  21:10 Найти цитируемый пост)
Свой же топик хочешь свести к флуду, думаешь он популярнее что-ли от этого станет

pofique
мне кажется ты особо даже не вникал, возможно даже не прочитал то, что я написал, но решил немного пофлудить smile 

Цитата(unicuum @  27.6.2009,  16:35 Найти цитируемый пост)
Напоминает наборы данных в дотнете. Они там являются частичной или полной копией базы данных в оперативной памяти. И им так же доступны все операции и разнообразные выражения. Да, этой техникой пользуются, и думаю хорошей реализацией на C++ тоже будут пользоваться.

мимо

Цитата(unicuum @  27.6.2009,  16:35 Найти цитируемый пост)
А вот другой вариант это структуры данных и их совмещение с базами данных.

что-то вроде. скорее это десериализация объектов, только объект не должен быть унаследован от какого либо класса, короче, полный decoupling того, как данные должны извлекаться из БД от логики приложения..
к примеру, если у меня изменится схема БД, я не хочу просто поправить код приложения в одном месте и все, никакого отношения к кэшированию, дотнету и тд, это не имеет

Цитата(unicuum @  27.6.2009,  16:35 Найти цитируемый пост)
В общем, я так понимаю, тема про отсоединённые и присоединённые объекты, а так же строгая и не строгая типизация в C++. Поправь если неправильно понял мысль

нет, никаких присоединенных и отсоединенных объектов, просто библиотека для извлечения данных

Добавлено через 5 минут и 10 секунд
в общем, это не ORM, просто библиотека для чтения данных
коротко: у нас есть таблицы с результатами выполнения запроса, мы должны на основе этих таблиц создать некую произвольную структуру данных в памяти. Причем эта структура данных не должна ничего "знать" о логике работы с БД. Для этого я предлагаю создать простой DSEL, что-бы иметь возможность описывать то, как преобразовывать данные из одной формы(набор таблиц) в другую(коллекции объектов, иерархии объектов). Соответственно, прежде чем тратить свое время, я решил поинтересоваться, стоит-ли затея потраченного времени.
Собственно все, прошу прощения за то, что так много букв... smile 

Автор: unicuum 27.6.2009, 22:55
Цитата(Lazin @  27.6.2009,  21:34 Найти цитируемый пост)
DSEL, что-бы иметь возможность описывать то, как преобразовывать данные из одной формы(набор таблиц) в другую(коллекции объектов, иерархии объектов).

Не вижу никакой разницы, будет у тебя тот же самый адаптер данных с методами Fill и Update, только с шаблоном преобразования в некую структуру данных. Правильное решение зависит не от кода, который ты написал, а от умения работать с базой данных.

Цитата(Lazin @  27.6.2009,  21:34 Найти цитируемый пост)
Соответственно, прежде чем тратить свое время, я решил поинтересоваться, стоит-ли затея потраченного времени.

Если для себя, то стоит, а если для других, то нет. Опять же надо задуматься, нужно ли преобразовывать данные из одной формы в другую. Их дублирование зачастую бывает излишним. К тому же в данном случае лично я воспринимаю созданные в С++ объекты именно как кэш, иначе бы база данных была не нужна.

Автор: mes 28.6.2009, 10:28
Цитата(unicuum @  27.6.2009,  21:55 Найти цитируемый пост)
лично я воспринимаю созданные в С++ объекты именно как кэш, иначе бы база данных была не нужна. 

скорее отображение результатов запроса БД на типо-безопасные объекты ( работа с которыми в остальной части программы будет проверена компилятором).

Автор: unicuum 28.6.2009, 11:03
Цитата(mes @  28.6.2009,  10:28 Найти цитируемый пост)
скорее отображение результатов запроса БД на типо-безопасные объекты ( работа с которыми в остальной части программы будет проверена компилятором).

Так Lazin похоже не хочет их отображать. Я так понимаю из его последнего сообщения ему нужна трансформация данных из базы в объекты и обратно. И всё это заметь, на иных структурах данных и без строгой типизации. Так, конечно, может и интересно сделать это в академических целях, но не очень полезно учитывая мощные механизмы заложенные в базах данных.

Автор: Lazin 28.6.2009, 11:34
Цитата(mes @  28.6.2009,  10:28 Найти цитируемый пост)
скорее отображение результатов запроса БД на типо-безопасные объекты ( работа с которыми в остальной части программы будет проверена компилятором).

именно smile 

Автор: unicuum 28.6.2009, 12:12
Цитата(Lazin @  28.6.2009,  11:34 Найти цитируемый пост)
именно smile  

Тогда вообще смысла нет, я бы этим пользоваться не стал.

Добавлено через 7 минут и 35 секунд
Кстати, Lazin, судя по твоему коду у тебя самая обычная строгая типизация, хоть ты и говоришь, нет, да, нет, да. В первый раз когда я тебе это сказал, отнекиваться стал, а теперь пожалуйста. За счёт этого твои объекты и получаются типо-безопасными. На чём бы не программировал, хоть на C++, хоть на Java, хоть на дотнетовских языках, это везде одинаково делается.

Автор: Lazin 29.6.2009, 06:10
профит этого подхода вот в чем. Программируем мы обычно на объектно ориентированых языках программирования, в которых объекты, это сущности, на взаимодействии которых строится программа, моделируют объекты реального мира, или не очень реального... smile 
в реляционной базе данных, информация хранится в другой форме, форме, удобной для хранения, но непригодной для использования в ОО коде, для того, что-бы мы могли работать с данными их БД(я имею ввиду не тупо отсортировать или найти определенную запись, тут можно обойтись написанием одного запроса) нужно создать объекты, причем связи между таблицами должны быть преобразованы в связи между объектами
в общем, обычный подход, мы пишем код, преобразующий результаты запроса в объекты, получается обычно что-то вроде этого:
Код

....
while (!recset1->EOF)
{
    Department dep(
        recset1->Field(_b_str("ID"))->AsInteger(),
        recset2->Field(_b_str("Name"))->AsString() );

    while(!recset2->EOF && recset2->Field(_b_str("dep_id")) == dep.id)
    {
         Employe emp(
              recset2->Field( _b_str("Name") ),
              recset2->Field( _b_str("SName") ),
              recset2->Field( _b_str("age") ) );
         dep.employe_list(emp);
         recset2->MoveNext();
    }
    *out++ = dep;
    recset1->MoveNext();
};


я предлагаю этот код сгенирировать с помощью шаблонов, может получиться что-то вроде этого:
Код

zip_view<...>
  view ( deps, emps
            , create_a<Department>(dep_id, dep_name) 
            , employe_dep == dep_id                            
            , append_a( member_a<Department::employe_list>(_1) 
                      , create_a<Employe>(employe_name, employe_sname, employe_age)) );

std::copy(std::back_inserter(container), view.begin(), view.end());



Цитата(unicuum @  28.6.2009,  12:12 Найти цитируемый пост)
В первый раз когда я тебе это сказал, отнекиваться стал, а теперь пожалуйста
без обид, но ты вообще какой-то бред несешь...

Автор: xvr 29.6.2009, 16:03
У меня дежавю, или этот вопрос уже поднимался? Какая то библиотека для обеспечения функциональности все равно понадобится, и если для ее применения потребуется не наследовать объекты от нее, а заворачивать их в нее (с помощью шаблонов) разница не очень большая.
Кроме того, это 'заворачивание' должно быть 'live' - иначе при заполнении этих шаблонов будет выкачанна вся база данных одним куском  smile 

Автор: unicuum 29.6.2009, 23:11
Цитата(Lazin @  29.6.2009,  06:10 Найти цитируемый пост)
без обид, но ты вообще какой-то бред несешь...

Пожалуй ты прав, мне лучше не выссказываться в этих топиках. А то я вечно бред несу, и по проектированию, и по программированию. Видно судьба у меня такая на этом форуме, слишком умные люди окружают. Пойду лучше на другой форум, где все не такие гениальные. smile 

Автор: jonie 29.6.2009, 23:26
Lazin зацени подход к параметрам в запросах - http://sqlitepp.berlios.de/#binders
мб наведет на какие-то другие мысли... а-то все на одно смотреть негоже)

Автор: Lazin 30.6.2009, 08:29
Цитата(xvr @  29.6.2009,  16:03 Найти цитируемый пост)
У меня дежавю, или этот вопрос уже поднимался? Какая то библиотека для обеспечения функциональности все равно понадобится, и если для ее применения потребуется не наследовать объекты от нее, а заворачивать их в нее (с помощью шаблонов) разница не очень большая.
именно, предыдущая тема помогла мне с разработкой основы библиотеки, но там есть ограничение для классов, загружаемых из БД(при использовании моей библиотеки, у объекта должен быть конструктор с определенным набором параметров, этот набор параметров зависит от того, что должен вернуть запрос).. а подобный DSL позволит классам не знать ничего о том, как они загружаются из БД

Цитата(xvr @  29.6.2009,  16:03 Найти цитируемый пост)
Кроме того, это 'заворачивание' должно быть 'live' - иначе при заполнении этих шаблонов будет выкачанна вся база данных одним куском
оно у меня загружается по мере необходимости

Цитата(jonie @  29.6.2009,  23:26 Найти цитируемый пост)
Lazin зацени подход к параметрам в запросах - http://sqlitepp.berlios.de/#binders
мб наведет на какие-то другие мысли... а-то все на одно смотреть негоже)

похоже на SOCI, посмотрю smile 

Цитата(unicuum @  29.6.2009,  23:11 Найти цитируемый пост)
Пожалуй ты прав, мне лучше не выссказываться в этих топиках. А то я вечно бред несу, и по проектированию, и по программированию. Видно судьба у меня такая на этом форуме, слишком умные люди окружают. Пойду лучше на другой форум, где все не такие гениальные.

высказывайся сколько угодно, но только по делу.. я просто не понимаю, причем здесь дотнетовский DataSet, кэширование, какие-то адаптеры... В первом посте я написал:
Цитата(Lazin @  27.6.2009,  15:53 Найти цитируемый пост)
допустим у нас есть код, который умеет подключаться к БД, выполнять запрос и получать его результаты, соответственно остается распарсить таблицу результатов запроса и получить коллекцию объекто

то есть эти детали пока не важны... далее:
Цитата(Lazin @  27.6.2009,  15:53 Найти цитируемый пост)
в принципе, возможно написать библиотеку, которая позволяла-бы описывать то, как нужно извлекать данные из recordset-ов с результатами запросов, с помощью техники expression templates

По моему из этого не сложно сделать вывод, что речь идет о отображении содержимого таблиц и связей между ними на объекты языка программирования, где там идет речь о кэшировании, о деталях реализации?
Я не готов здесь обсуждать сферического коня в вакууменечто абстрактное, как в твоей теме о паттернах проектирования, все должно быть утилитарно  smile 

Автор: xvr 30.6.2009, 10:04
Цитата(Lazin @ 30.6.2009,  08:29)
Цитата(jonie @  29.6.2009,  23:26 Найти цитируемый пост)
Lazin зацени подход к параметрам в запросах - http://sqlitepp.berlios.de/#binders
мб наведет на какие-то другие мысли... а-то все на одно смотреть негоже)

похоже на SOCI, посмотрю smile 

Цитата из его readme -
Цитата

Library approaches with binding variables to SQL queries was inspired by 
Maciej Sobczak' SOCI wrapper (http://www.msobczak.com/prog/soci/).


Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)