Re[21]: [proof-of-concept] compile time query language
От: Erop Россия  
Дата: 13.07.16 10:10
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

E>>Ну вот в случае 2-х namespaces, как-то удаётся обойтись без описания join...

EP>Каких namespaces?
С++

EP>Это будет как минимум не проще, и при этом на более низком уровне.

Проще это будет при использовании библиотек метапрограммирования, буста там и прочей обвязки. Если написать аналогичных библиотек про AST, то будет проще...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[4]: [proof-of-concept] compile time query language
От: Evgeny.Panasyuk Россия  
Дата: 13.07.16 11:11
Оценка:
Здравствуйте, chaotic-kotik, Вы писали:

EP>>DAL как реализовывать? На каком языке/библиотеке?

CK>Я как правило предпочитаю нативную клиентскую библиотеку использовать ну и обычные текстовые запросы + prepare для некоторых.

Для большого количества сложных запросов это неудобно — нет типизации запросов и нормальных средств декомпозиции.
Если же запросов немного — то думаю не критично.

EP>>Где делать требуемые проекции, фильтрации? Всё в DAL расписывать?

CK>Да, а что такого? Вынести в отдельный модуль, чтобы все приложение работало через какой-нибудь абстрактный интерфейс и не знало вообще, с чем оно работает, с БД, или еще каким-нибудь источником данных.
CK>...
CK>И что ты понимаешь под фильтрациями? Where statement в SQL или обработку результатов запроса?

Where clause. В разных частях программы могут потребоваться разные фильтры, разный набор колонок в select.
В итоге вместо from(foo).where(foo.id > 42_i).select(foo.value, foo.id, foo.number) у тебя в DAL будут функции вида foo_where_id_gt_42_select_value_id_number на каждый чих.

CK>Тут можно свапнуть БД на другую (например в тестах использовать sqlite вместо постгреса или вообще какой-нибудь nosql).


Возможность swap'а БД может быть внутри библиотеки запросов. То есть пишешь запрос на типизированном EDSL, а выхлоп будет отличаться в зависимости от БД.

CK>и при каждом изменении схемы переписывать код


Во-первых тебе и в случае с DAL придётся переписывать код.
Во-вторых как раз за счёт средств декомпозиции запросов на типизированом EDSL переписывать нужно меньше, так как легко вынести повторно используемые части запроса в отдельные функции.
Re[22]: [proof-of-concept] compile time query language
От: Evgeny.Panasyuk Россия  
Дата: 13.07.16 11:23
Оценка:
Здравствуйте, Erop, Вы писали:

E>>>Ну вот в случае 2-х namespaces, как-то удаётся обойтись без описания join...

EP>>Каких namespaces?
E>С++

О чём речь?
Re[23]: [proof-of-concept] compile time query language
От: Erop Россия  
Дата: 13.07.16 11:47
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

E>>>>Ну вот в случае 2-х namespaces, как-то удаётся обойтись без описания join...

EP>>>Каких namespaces?
E>>С++

EP>О чём речь?

namecpace foo {
    int name;
    int bum;
    int bom;
}// namecpace foo

namecpace bar {
    int name;
    int bah;
    int buh;
}// namecpace bar

// где-то дальше
using namecpace foo;
using namecpace bar;

int res = bum + buh +
// Но
    foo.name + bar.name;
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[5]: [proof-of-concept] compile time query language
От: chaotic-kotik  
Дата: 13.07.16 12:01
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Для большого количества сложных запросов это неудобно — нет типизации запросов и нормальных средств декомпозиции.

EP>Если же запросов немного — то думаю не критично.

Что есть "нормальные средства декомпозиции"? Как это должно выглядеть в коде?

EP>Where clause. В разных частях программы могут потребоваться разные фильтры, разный набор колонок в select.

EP>В итоге вместо from(foo).where(foo.id > 42_i).select(foo.value, foo.id, foo.number) у тебя в DAL будут функции вида foo_where_id_gt_42_select_value_id_number на каждый чих.

Ясно. Думаю что код на таком уровне не должен работать (он не должен знать что у тебя есть табличка foo и поле id, не важно как, в виде EDSL или части имени метода). Это чревато ошибками и (что намного хуже) проблемами с производительностью. Так как where clauses у тебя разбросаны по всему коду и чтобы понять, какие индексы нужно создавать, придется собирать статистику с работающего приложения, профилировать и тд. Ну а потом кто-нибудь добавить еще один фильтр и придется все начинать сначала.

Когда же у тебя есть DAL, ты будешь работать через него и все эти where можно просто проскроллить и глазами посмотреть в одном месте. И выглядит это обычно не так как ты описал а как-то так:

auto publisher = DAL.get_publisher(id);  // executes `SELECT * FROM "Publishers" WHERE id = 42`
...
Filter flt;
flt.include_if(Placement.Fields.RECT, Filter.GT, BoundingRectangle(100, 200));
auto placements_list = publisher.get_placements(filter);  // executes `SELECT * FROM "Placements" WHERE id = 42 and (width > 200 and height > 100)`


EP>Во-первых тебе и в случае с DAL придётся переписывать код.

EP>Во-вторых как раз за счёт средств декомпозиции запросов на типизированом EDSL переписывать нужно меньше, так как легко вынести повторно используемые части запроса в отдельные функции.

DAL можно вынести в отдельный модуль, второе предложение — не понял как это, честно говоря, тебе что так что так нужен слой, который бы абстрагировал работу с БД, чтобы код приложения работал с объектами в памяти а не генерировал (пусть и через хитрый EDSL) запросы к БД, так что я вижу только увеличение сложности и уровня абстракции
Отредактировано 13.07.2016 12:02 chaotic-kotik . Предыдущая версия .
Re[6]: [proof-of-concept] compile time query language
От: Evgeny.Panasyuk Россия  
Дата: 13.07.16 19:46
Оценка:
Здравствуйте, chaotic-kotik, Вы писали:

CK>Так как where clauses у тебя разбросаны по всему коду и чтобы понять, какие индексы нужно создавать, придется собирать статистику с работающего приложения, профилировать и тд. Ну а потом кто-нибудь добавить еще один фильтр и придется все начинать сначала.


SQL код генерируется во время компиляции — легко получить список всех запросов. Либо из asm/бинарного кода, либо просто убрать определение значения строки — тогда компилятор выведет список всех запросов в ошибках компиляции/линковки.

CK>Когда же у тебя есть DAL, ты будешь работать через него и все эти where можно просто проскроллить и глазами посмотреть в одном месте. И выглядит это обычно не так как ты описал а как-то так:

CK>
CK>auto publisher = DAL.get_publisher(id);  // executes `SELECT * FROM "Publishers" WHERE id = 42`
CK>


Так в том и дело, "SELECT *" выдаст все колонки, что часто неэффективно, и в разных местах требуются разные проекции. В случае с DAL придётся либо плодить функции на каждый чих, либо вводить аналогичный EDSL.

CK>
CK>Filter flt;
CK>flt.include_if(Placement.Fields.RECT, Filter.GT, BoundingRectangle(100, 200));
CK>auto placements_list = publisher.get_placements(filter);  // executes `SELECT * FROM "Placements" WHERE id = 42 and (width > 200 and height > 100)`
CK>


А здесь ты точно также создаёшь фильтр на стороне пользователя, только в другой форме, но суть та же.

EP>>Во-первых тебе и в случае с DAL придётся переписывать код.

CK>DAL можно вынести в отдельный модуль,

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

EP>>Во-вторых как раз за счёт средств декомпозиции запросов на типизированом EDSL переписывать нужно меньше, так как легко вынести повторно используемые части запроса в отдельные функции.

EP>>второе предложение — не понял как это, честно говоря,

Можно часто используемый запрос в отдельную функцию:
auto recent_customers()
{
    return from(customers) ...;
}
...
{
    auto q = from(recent_customers).join(...).where(...).select(...);
    // ...
}

Или например кусок запроса, типа фильтра:
auto common_filter(auto q)
{
    return q.where(...);
}

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

EP>тебе что так что так нужен слой, который бы абстрагировал работу с БД, чтобы код приложения работал с объектами в памяти а не генерировал (пусть и через хитрый EDSL) запросы к БД, так что я вижу только увеличение сложности и уровня абстракции


EDSL запросов по сути не особо отличается от того же Boost.Range например — практически такое же декларативное описание "запроса" к контейнерам. При этом вызовы Boost.Range ни в какой DAL не прячут.
Re[7]: [proof-of-concept] compile time query language
От: chaotic-kotik  
Дата: 14.07.16 07:51
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>SQL код генерируется во время компиляции — легко получить список всех запросов. Либо из asm/бинарного кода, либо просто убрать определение значения строки — тогда компилятор выведет список всех запросов в ошибках компиляции/линковки.


А что если у меня условия формируются динамически? Допустим есть сервис, к сервису происходит обращение вида GET http://path?field1<100&field2>200 а задача сервиса — понять как сформировать запрос и преобразовать его в какой-нибудь JSON например. Ну или limit/offset clauses, тоже всегда динамически выставляются. В общем, запрос в compile time это очень примитивный сценарий использования. В таких случаях список запросов уже совсем не просто получить, придется использовать средства профилирования БД. Ну и весь type safety куда-то внезапно улетучивается и становится окончательно непонятно, с чего это вдруг мы используем какой-то EDSL.

EP>Так в том и дело, "SELECT *" выдаст все колонки, что часто неэффективно, и в разных местах требуются разные проекции. В случае с DAL придётся либо плодить функции на каждый чих, либо вводить аналогичный EDSL.


Ну это я для простоты через * написал, само собой так делать не нужно никогда.

CK>>
CK>>Filter flt;
CK>>flt.include_if(Placement.Fields.RECT, Filter.GT, BoundingRectangle(100, 200));
CK>>auto placements_list = publisher.get_placements(filter);  // executes `SELECT * FROM "Placements" WHERE id = 42 and (width > 200 and height > 100)`
CK>>


EP>А здесь ты точно также создаёшь фильтр на стороне пользователя, только в другой форме, но суть та же.


Ну по сути да. Можно это в expression template запихать, но тогда теряется динамика. Еще из такого можно легко prepared statement сгенирировать, а в статике нельзя (ну либо твой EDSL начнет работать полностью в динамике).

EP>Вот только его абстракция будет постоянно протекать — придётся на каждую проекцию делать функции, точно также реализовывать внешние фильтры, при этом не особо ясно что получаем из бенефитов.


Ну так если сделать так, как я выше описал то не придется. Я вдел реализации, которые позволяют запросы генерировать из AST, который можно например сериализовать в промежуточный формат или например получить AST из строчки URL или POST-данных HTTP запроса а потом сделать из этого запрос. Так же этот AST можно было анализировать перед исполнением на предмет ошибок и SQL injection. При этом AST был (само собой) абстрактным и не содержал названий полей и таблиц, вместо этого он оперировал сущностями предметной области.

EP>EDSL запросов по сути не особо отличается от того же Boost.Range например — практически такое же декларативное описание "запроса" к контейнерам. При этом вызовы Boost.Range ни в какой DAL не прячут.


Ну так boost.range оперирует объектами в коде программы, SQL запрос оперирует данными в таблицах и по хорошему код не должен ничего знать про имена таблиц/полей, layout, join-s и тд. Для этого и нужен слой DAL. Иначе миграции схемы БД превратятся в сложные рефакторинги а это не есть хорошо. И ты ошибочно предполагаешь что построение запроса и конвертация результатов запроса — это то что нужно оптимизировать. Построение запроса это слезы, этот запрос потом отправляется на другую машину, где он парсится, оптимизируется и исполняется. За время оптимизации запроса можно его миллион раз сгенерировать повторно. Zero overhead преобразование результатов тоже мало полезного дает — некоторые БД отдают результаты в виде текста, некоторые предоставляют апи для доступа к данным результатов запросов вида `int getFieldAsInt(int field_index, int default_value)` и что ты вызовешь этот метод в своем EDSL, что класс в DAL вызовет его, разницы никакой.
Отредактировано 14.07.2016 7:53 chaotic-kotik . Предыдущая версия .
Re[8]: [proof-of-concept] compile time query language
От: Evgeny.Panasyuk Россия  
Дата: 14.07.16 09:57
Оценка:
Здравствуйте, chaotic-kotik, Вы писали:

CK>Ну и весь type safety куда-то внезапно улетучивается и становится окончательно непонятно, с чего это вдруг мы используем какой-то EDSL.


При динамических запросах type-safety никуда не исчезает. Исчезает возможность сгенерировать код запроса во время компиляции, и то только для случаев где пространство динамического выбора большое.

EP>>Так в том и дело, "SELECT *" выдаст все колонки, что часто неэффективно, и в разных местах требуются разные проекции. В случае с DAL придётся либо плодить функции на каждый чих, либо вводить аналогичный EDSL.

CK>Ну это я для простоты через * написал, само собой так делать не нужно никогда.

Так а как ты предлагаешь делать? На каждый вариант проекции делать отдельную функцию в DAL? Постоянно модифицировать API DAL при необходимости нового поля?

EP>>А здесь ты точно также создаёшь фильтр на стороне пользователя, только в другой форме, но суть та же.

CK>Ну по сути да.

Так а в чём профит?

EP>>EDSL запросов по сути не особо отличается от того же Boost.Range например — практически такое же декларативное описание "запроса" к контейнерам. При этом вызовы Boost.Range ни в какой DAL не прячут.

CK>Ну так boost.range оперирует объектами в коде программы, SQL запрос оперирует данными в таблицах и по хорошему код не должен ничего знать про имена таблиц/полей, layout, join-s и тд.

Почему не должен знать? Зачем вводить новые слои на ровном месте? То есть что это даст по факту? И реально ли это необходимо?

CK>Для этого и нужен слой DAL. Иначе миграции схемы БД превратятся в сложные рефакторинги а это не есть хорошо.


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

CK>И ты ошибочно предполагаешь что построение запроса и конвертация результатов запроса — это то что нужно оптимизировать.


Во-первых, я это не предполагаю. Я лишь сказал что это возможно, в ответ на что получил сомнения в реализуемости. Собственно весь пример это лишь proof-of-concept.
Во-вторых, если у нас в любом случае есть compile-time EDSL запросов (а-ля sqlpp11) и можно достаточно просто (используя новые версии C++, Hana-style и т.п.) оптимизировать в том числе и эти моменты — то почему бы и нет.

CK>Zero overhead преобразование результатов тоже мало полезного дает


Это по сути несколько строчек кода, смотри map_result — так почему бы и нет?

CK>- некоторые БД отдают результаты в виде текста, некоторые предоставляют апи для доступа к данным результатов запросов вида `int getFieldAsInt(int field_index, int default_value)` и что ты вызовешь этот метод в своем EDSL, что класс в DAL вызовет его, разницы никакой.


Вот только в DAL ты будешь путаться в индексах, а EDSL это разрулит автоматом.
Более того, в той КСВ теме даже были заявления, что мол руками никто код с индексами и не пишет, вместо этого используют более медленные варианты (этим оправдывали сравнение EDSL варианта на индексах, против ручного варианта без индексов).
Re[9]: [proof-of-concept] compile time query language
От: chaotic-kotik  
Дата: 14.07.16 11:45
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Так а как ты предлагаешь делать? На каждый вариант проекции делать отдельную функцию в DAL? Постоянно модифицировать API DAL при необходимости нового поля?


Отдельную ф-ю на каждый вариант проектции делать не нужно, ее нужно делать на каждый поддерживаемый тип данных, ф-я при этом должна принимать фильтр, который строится в runtime динамически. Поинт в том, чтобы приложение работало с объектами класса Publisher и Placement а не с таблицами, проекциями и джоинами.

EP>Так а в чём профит?

EP>Почему не должен знать? Зачем вводить новые слои на ровном месте? То есть что это даст по факту? И реально ли это необходимо?

Простой пример, приложение работающее с каталогом — есть две таблицы, которые обозначают модель мобильного телефона и конкретное устройство, скажем iPhone 6s и 64х гигабайтная версия iPhone 6s. Пользователю показывают в каталоге продуктов iPhone 6s, когда он по нему кликает — показывают список устройств с разным объемом памяти и размером экрана. Чтобы показать все параметры нужно сделать join двух таблиц. Мы попрофилировали и поняли, что джоин съедает дофига, нужно денормализовать эти две таблицы сделав одну. Теперь мы можем одним select-ом выбрать iPhone 6s 64GB устройство или iPhone 6s модель телефона и там будут все нужные данные.

Теперь я пойду в код и поменяю один файл с кодом в своем dal, а ты будешь искать в коде все функции, использующие эти таблицы. Мне не нужно будет менять прикладную логику вообще, я смогу сделать так, что код будет работать и со старой схемой и с новой не напрягаясь (dal сможет определить при подключении с какой схемой ему предстоит работать) и прикладной код вообще не изменится. Когда я мигрирую схему в проде, мне даже не придется перезапускать сервисы, так как после рестарта БД сервис заново подключится к ней и начнет использовать код, умеющий работать с новой схемой. Собственно для всего этого и нужен динамизм и поэтому запросы, размазанные по коду (пусть и проверяющиеся в compile time компилятором С++) не работают.

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


Ты предлагаешь парадоксальную вещь — рефакторить схему БД в коде. Изменили имя поля — рефакторить? Суть БД в том, что приложению класть на layout данных в БД, для этого есть SQL. Маппинг твоей объектной модели на поля таблиц БД должен быть не кодом а конфигурацией. Изменил схему — поменяй конфиг. Можно конечно этот конфиг хранить в коде приложения и генерировать запросы в compile-time, но вообще лучше подкачивать его из БД.

EP>Во-вторых, если у нас в любом случае есть compile-time EDSL запросов (а-ля sqlpp11) и можно достаточно просто (используя новые версии C++, Hana-style и т.п.) оптимизировать в том числе и эти моменты — то почему бы и нет.


Потому что это очень ограниченный способ.

EP>Вот только в DAL ты будешь путаться в индексах, а EDSL это разрулит автоматом.

EP>Более того, в той КСВ теме даже были заявления, что мол руками никто код с индексами и не пишет, вместо этого используют более медленные варианты (этим оправдывали сравнение EDSL варианта на индексах, против ручного варианта без индексов).

Тоже мне проблема, индексы
Отредактировано 14.07.2016 11:50 chaotic-kotik . Предыдущая версия .
Re[10]: [proof-of-concept] compile time query language
От: Evgeny.Panasyuk Россия  
Дата: 14.07.16 20:12
Оценка:
Здравствуйте, chaotic-kotik, Вы писали:

EP>>Так а как ты предлагаешь делать? На каждый вариант проекции делать отдельную функцию в DAL? Постоянно модифицировать API DAL при необходимости нового поля?

CK>Отдельную ф-ю на каждый вариант проектции делать не нужно, ее нужно делать на каждый поддерживаемый тип данных,

Все поля (даже одного типа данных) не всегда нужны. И поэтому либо функция на каждую проекцию, либо тормоза.

EP>>Так а в чём профит?

EP>>Почему не должен знать? Зачем вводить новые слои на ровном месте? То есть что это даст по факту? И реально ли это необходимо?
CK>Простой пример, приложение работающее с каталогом — есть две таблицы, которые обозначают модель мобильного телефона и конкретное устройство, скажем iPhone 6s и 64х гигабайтная версия iPhone 6s. Пользователю показывают в каталоге продуктов iPhone 6s, когда он по нему кликает — показывают список устройств с разным объемом памяти и размером экрана. Чтобы показать все параметры нужно сделать join двух таблиц. Мы попрофилировали и поняли, что джоин съедает дофига, нужно денормализовать эти две таблицы сделав одну.

А зачем вручную денормализовать когда есть materialized view?

CK>Теперь мы можем одним select-ом выбрать iPhone 6s 64GB устройство или iPhone 6s модель телефона и там будут все нужные данные.

CK>Теперь я пойду в код и поменяю один файл с кодом в своем dal, а ты будешь искать в коде все функции, использующие эти таблицы.

Не буду, это пример на декомпозицию. Я уже выше показывал:

EP>Можно часто используемый запрос в отдельную функцию:
EP>

EP>auto recent_customers()
EP>{
EP>    return from(customers) ...;
EP>}
EP>...
EP>{
EP>    auto q = from(recent_customers).join(...).where(...).select(...);
EP>    // ...
EP>}
EP>

Вот только эту функцию (recent_customers) и придётся менять.
А в случае DAL и простых строковых запросов у тебя внутри DAL нужно будет менять много мест, так как нет нормальных средств декомпозиции.

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

CK>Ты предлагаешь парадоксальную вещь — рефакторить схему БД в коде. Изменили имя поля — рефакторить?

Нет, я говорю что легче рефакторить код, имея типизацию. Обновил схему в коде из БД — все ошибки вылезли во время компиляции.

CK>Суть БД в том, что приложению класть на layout данных в БД, для этого есть SQL.


БД абстрагирует от физического layout и прочего, но не от схемы. При изменении схемы, код придётся менять и в моём и в твоём варианте. Причём в твоём ещё может придётся менять код промежуточного слоя.
Отредактировано 14.07.2016 20:40 Evgeny.Panasyuk . Предыдущая версия .
Re[11]: [proof-of-concept] compile time query language
От: chaotic-kotik  
Дата: 14.07.16 21:05
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Все поля (даже одного типа данных) не всегда нужны. И поэтому либо функция на каждую проекцию, либо тормоза.


Их можно как-инбудь передать в функцию. Ты же тоже их по сути явно передаешь через свой `select`.

EP>А зачем вручную денормализовать когда есть materialized view?


Это просто пример. Materialized views кстати далеко не всегда можно использовать. Например БД может их тупо не уметь делать (постгрес например).

EP>Не буду, это пример на декомпозицию. Я уже выше показывал:

EP>

EP>>Можно часто используемый запрос в отдельную функцию:
EP>>

EP>>auto recent_customers()
EP>>{
EP>>    return from(customers) ...;
EP>>}
EP>>...
EP>>{
EP>>    auto q = from(recent_customers).join(...).where(...).select(...);
EP>>    // ...
EP>>}
EP>>

EP>Вот только эту функцию (recent_customers) и придётся менять.
EP>А в случае DAL и простых строковых запросов у тебя внутри DAL нужно будет менять много мест, так как нет нормальных средств декомпозиции.

Если они не в compile-time это не значит что они плохие. Кстати, как ты планируешь гарантировать существование нужных таблиц и полей в БД? Тот случай когда в коде все ок и все компилируется, но схема изменилась. КМК из constexpr к БД никак не подключиться

EP>Нет, я говорю что легче рефакторить код, имея типизацию. Обновил схему в коде из БД — все ошибки вылезли во время компиляции.


Ну можно ведь не рефактроирть. Не обязательно приложению заранее знать какие там у объекта поля в БД есть. Если это какой-нибудь веб сервис — очень часто его можно написать так, чтобы он ничего такого не знал заранее и динамически загружал схему (и даже изменения на лету подхватывал). Мне, в моей практике, обычно что-то подобное попадается. А вот в таком стиле как ты хочешь реализовать, тоже, но обычно это более простые приложения.
Re[12]: [proof-of-concept] compile time query language
От: Evgeny.Panasyuk Россия  
Дата: 14.07.16 21:18
Оценка:
Здравствуйте, chaotic-kotik, Вы писали:

EP>>Все поля (даже одного типа данных) не всегда нужны. И поэтому либо функция на каждую проекцию, либо тормоза.

CK>Их можно как-инбудь передать в функцию. Ты же тоже их по сути явно передаешь через свой `select`.

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

EP>>[/q]

EP>>Вот только эту функцию (recent_customers) и придётся менять.
EP>>А в случае DAL и простых строковых запросов у тебя внутри DAL нужно будет менять много мест, так как нет нормальных средств декомпозиции.
CK>Если они не в compile-time это не значит что они плохие.

Дело не compile/run-time, а в отсутствии типизации.

CK>Кстати, как ты планируешь гарантировать существование нужных таблиц и полей в БД? Тот случай когда в коде все ок и все компилируется, но схема изменилась.


Например проверять версию схемы в runtime.
С DAL у тебя точно такие же проблемы. Точнее даже хуже — например обновил схему, типа обновил код, но ошибки типа неверных имён полей в тексте запросов и т.п. могут спокойно дожить до runtime'а.

EP>>Нет, я говорю что легче рефакторить код, имея типизацию. Обновил схему в коде из БД — все ошибки вылезли во время компиляции.

CK>Ну можно ведь не рефактроирть.

Так ведь DAL всё равно придётся рефакторить.

CK>Не обязательно приложению заранее знать какие там у объекта поля в БД есть.


Выше, для проекций, ты именно предлагаешь вынести знание о полях в приложение.
Re[13]: [proof-of-concept] compile time query language
От: chaotic-kotik  
Дата: 14.07.16 21:37
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>В итоге у тебя будет и EDSL для передачи фильтров, и EDSL для передачи списка полей, плюс приложение будет знать о конкретных полях. Так в чём в итоге профит, если вся абстракция уже протекла?


Nope.

EP>Дело не compile/run-time, а в отсутствии типизации.


Которая тут не нужна.

EP>Например проверять версию схемы в runtime.

EP>С DAL у тебя точно такие же проблемы. Точнее даже хуже — например обновил схему, типа обновил код, но ошибки типа неверных имён полей в тексте запросов и т.п. могут спокойно дожить до runtime'а.

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

Далее, нам нужно завести новое поле — добавляем новую запись в таблицу метаданных, запускаем скрипт — скрипт делает миграцию (делает ALTER TABLE нужной таблице).

При старте сервис читает метаданные и после этого может работать с новой схемой. В этой архитектуре ничего рефактрить не нужно никогда вообще. Поля новые добавлять или удалять при этом можно. Ес-но это упрощенный пример, в реальной жизни все несколько сложнее, может быть множество сущностный, они могут тоже добавляться и удаляться, могут поддерживаться всякие словари и даже индексы могут автоматически строиться при миграциях. И тут все динамически потому что до старта приложения проверять нечего.
Re[14]: [proof-of-concept] compile time query language
От: Evgeny.Panasyuk Россия  
Дата: 14.07.16 23:25
Оценка: +1
Здравствуйте, chaotic-kotik, Вы писали:

EP>>Дело не compile/run-time, а в отсутствии типизации.

CK>Которая тут не нужна.

Почему? Вручную выписывать индексы, структуры, кое-как склеивать текст запроса, и к тому же ручную контролировать корректность всего этого. Ради чего?

EP>>Например проверять версию схемы в runtime.

EP>>С DAL у тебя точно такие же проблемы. Точнее даже хуже — например обновил схему, типа обновил код, но ошибки типа неверных имён полей в тексте запросов и т.п. могут спокойно дожить до runtime'а.
CK>Пытаюсь тут на простую схему намекать. Представь что у тебя есть таблица с документами (куча полей), есть таблица с метаданными, в метаданных описано в какой таблице хранятся документы,

Это лишь один частный случай, в котором нужна динамика. EDSL запросов может работать в том числе и с динамикой, там где это необходимо — например как в sqlpp11.

CK>Далее, нам нужно завести новое поле — добавляем новую запись в таблицу метаданных, запускаем скрипт — скрипт делает миграцию (делает ALTER TABLE нужной таблице).

CK>При старте сервис читает метаданные и после этого может работать с новой схемой. В этой архитектуре ничего рефактрить не нужно никогда вообще.

Неверно. Не надо рефакторить только определённом наборе вариантов расширяемость которых заранее заложена в изначальную задачу. Причём в случае EDSL в этих же случаях точно также не нужно рефакторить.
Если же произошло любое изменение вне этой расширяемости (например изменилась структура метаданных) — то нужно точно также рефакторить. При этом, опять таки, на основе текстовых запросов это всё делается труднее.

То есть ты говоришь мол есть документ с произвольным набором полей описанным метаданными, и на основе того что можно в него добавлять поля без рефакторинга, ты делаешь неверное обобщение "Ну можно ведь не рефактроирть"
Re[15]: [proof-of-concept] compile time query language
От: chaotic-kotik  
Дата: 15.07.16 08:40
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Почему? Вручную выписывать индексы, структуры, кое-как склеивать текст запроса, и к тому же ручную контролировать корректность всего этого. Ради чего?


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

CK>>Пытаюсь тут на простую схему намекать. Представь что у тебя есть таблица с документами (куча полей), есть таблица с метаданными, в метаданных описано в какой таблице хранятся документы,

EP>Это лишь один частный случай, в котором нужна динамика. EDSL запросов может работать в том числе и с динамикой, там где это необходимо — например как в sqlpp11.

Этот один частный случай я за последние лет наверное пять вижу вообще везде. Entity framework например похожим образом работает, или Hibernate. Реально с SQL-ем напрямую работают разве что всякие простые приложения, во всех сложных приложениях, с которыми мне приходилось работать, реализуется некий слой абстракции вокруг БД.

CK>>Далее, нам нужно завести новое поле — добавляем новую запись в таблицу метаданных, запускаем скрипт — скрипт делает миграцию (делает ALTER TABLE нужной таблице).

CK>>При старте сервис читает метаданные и после этого может работать с новой схемой. В этой архитектуре ничего рефактрить не нужно никогда вообще.

EP>Неверно. Не надо рефакторить только определённом наборе вариантов расширяемость которых заранее заложена в изначальную задачу. Причём в случае EDSL в этих же случаях точно также не нужно рефакторить.


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

Вот например есть табличка каталога оборудования и я добавил туда свойство `last_edit_time` которое содержит время последнего изменения. Теперь при выгрузке данных в xml мне нужно это поле читать, в UI контентщика мне нужно его показывать и фильтровать по нему, но в UI пользователя — не нужно показывать. Следовательно, два из трех запросов мне нужно изменить. Мало того, у этого поля должно быть имя в приложении (и в пользовательском и в сервисе), т.е. добавляя поле ты изменяешь сразу несколько артефактов. В сервисе появляется возможность обратиться к URL `/get_catalog_item?id=XXX&last_edit_time<7d-ago`, в UI контентщика появится возможность по этому полю искать и тд.

С общепринятым подходом ты просто добавишь запись в таблицу, в которой у тебя свойства каталога оборудования лежат, вот и все (может это будет SDL какой-нибудь, не обязательно табличка). В этой записи будет информация о том что поле доступно в таких то запросах, по нему можно фильтровать, его не нужно показывать пользователю но нужно показывать контентщику и тд. После миграции твой веб сервис будет принимать свойство last_edit_time (которое возможно будет по другому называться, имя поля в БД будет скрыто) и будет по нему фильтровать, в UI контентщика появится возможность выбрать это поле и отсортировать по нему или отфильтровать. Все это без необходимости изменять код вообще хоть где-нибудь. Конечно, если свойство добавляется в UI пользователя — возможно придется изменить верстку (или гуй) под это дело, но и то не всегда. Я видел несколько крупных веб проектов и одну систему документооборота предприятия, все они были построены таким образом.

Вот собственно это и есть современное состояние дел. Поэтому я и написал о том что вся эта статика — не нужна. Очень поверхностно смотрел на sqlpp11, но мне показалось что это не шаг вперед, а скорее шаг в сторону. Писать запросы в коде более удобно, но это очень низкий уровень. Ну да, можно отловить баги в SQL запросах, но я могу SQL написать и проверить заранее в терминале и потом скопировать его себе в код один в один. Никогда проблема опечаток в SQL у меня остро не стояла, к счастью, поэтому проверка SQL запросов (на самом базовом уровне) во время компиляции — кажется мне чем-то странным.

EP>Если же произошло любое изменение вне этой расширяемости (например изменилась структура метаданных) — то нужно точно также рефакторить. При этом, опять таки, на основе текстовых запросов это всё делается труднее.


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

EP>То есть ты говоришь мол есть документ с произвольным набором полей описанным метаданными, и на основе того что можно в него добавлять поля без рефакторинга, ты делаешь неверное обобщение "Ну можно ведь не рефактроирть"


Я описал упрощенную схему. В реальной жизни это скорее — есть модель данных, которая описана метаданными и в которую заложена расширяемость, поэтому ее можно легко расширять и это не требует изменений кода зачастую (если завязок на конкретные поля в коде сервиса/UI нет, что справедливо для большинства полей). Там конечно же бывает не одна табличка а куча таблиц, связей и тд.
Re: [proof-of-concept] compile time query language
От: chaotic-kotik  
Дата: 15.07.16 08:55
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>

CTQL — Compile Time Query Language

EP>В рамках одной из КСВ тем сделал proof-of-concept встроенного языка запросов времени компиляции, а точнее одной конкретной формы выражения (примеров EDSL и так предостаточно).
EP>Все этапы, включая генерацию текста SQL запроса, вынесены в compile-time.

Кстати, было бы интересно, на мой взгляд, использовать этот подход для генерации protobuf кода. Очень часто через protobuf общаются только С++ приложения и вместо использования IDL и отдельного этапа сборки для генерации обвязки, можно было бы использовать метапрограммирование.
Re[2]: [proof-of-concept] compile time query language
От: Evgeny.Panasyuk Россия  
Дата: 15.07.16 09:37
Оценка:
Здравствуйте, chaotic-kotik, Вы писали:

CK>Кстати, было бы интересно, на мой взгляд, использовать этот подход для генерации protobuf кода. Очень часто через protobuf общаются только С++ приложения и вместо использования IDL и отдельного этапа сборки для генерации обвязки, можно было бы использовать метапрограммирование.


Рекурсивная сериализация структур данных? Это делается на Boost.Fusion (а сейчас и на Boost.Hana) по сути в одну короткую функцию
Автор: Evgeny.Panasyuk
Дата: 06.10.14
.
Отредактировано 15.07.2016 9:38 Evgeny.Panasyuk . Предыдущая версия .
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.