Здравствуйте, Evgeny.Panasyuk, Вы писали:
E>>Ну вот в случае 2-х namespaces, как-то удаётся обойтись без описания join... EP>Каких namespaces?
С++
EP>Это будет как минимум не проще, и при этом на более низком уровне.
Проще это будет при использовании библиотек метапрограммирования, буста там и прочей обвязки. Если написать аналогичных библиотек про AST, то будет проще...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[4]: [proof-of-concept] compile time query language
Здравствуйте, 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, Вы писали:
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
Здравствуйте, 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) запросы к БД, так что я вижу только увеличение сложности и уровня абстракции
Здравствуйте, 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
Здравствуйте, 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 вызовет его, разницы никакой.
Здравствуйте, 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
Здравствуйте, 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 варианта на индексах, против ручного варианта без индексов).
Здравствуйте, 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>
Вот только эту функцию (recent_customers) и придётся менять.
А в случае DAL и простых строковых запросов у тебя внутри DAL нужно будет менять много мест, так как нет нормальных средств декомпозиции.
EP>>За счёт типизации как раз проводить рефакторинги намного проще. Ошибки отлавливаются на этапе компиляции. В случае с текстовыми запросами точно также придётся проводить рефакторинги, только уже без помощи типизации. CK>Ты предлагаешь парадоксальную вещь — рефакторить схему БД в коде. Изменили имя поля — рефакторить?
Нет, я говорю что легче рефакторить код, имея типизацию. Обновил схему в коде из БД — все ошибки вылезли во время компиляции.
CK>Суть БД в том, что приложению класть на layout данных в БД, для этого есть SQL.
БД абстрагирует от физического layout и прочего, но не от схемы. При изменении схемы, код придётся менять и в моём и в твоём варианте. Причём в твоём ещё может придётся менять код промежуточного слоя.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Все поля (даже одного типа данных) не всегда нужны. И поэтому либо функция на каждую проекцию, либо тормоза.
Их можно как-инбудь передать в функцию. Ты же тоже их по сути явно передаешь через свой `select`.
EP>А зачем вручную денормализовать когда есть materialized view?
Это просто пример. Materialized views кстати далеко не всегда можно использовать. Например БД может их тупо не уметь делать (постгрес например).
EP>Не буду, это пример на декомпозицию. Я уже выше показывал: EP>
EP>>Можно часто используемый запрос в отдельную функцию:
EP>>
EP>Вот только эту функцию (recent_customers) и придётся менять. EP>А в случае DAL и простых строковых запросов у тебя внутри DAL нужно будет менять много мест, так как нет нормальных средств декомпозиции.
Если они не в compile-time это не значит что они плохие. Кстати, как ты планируешь гарантировать существование нужных таблиц и полей в БД? Тот случай когда в коде все ок и все компилируется, но схема изменилась. КМК из constexpr к БД никак не подключиться
EP>Нет, я говорю что легче рефакторить код, имея типизацию. Обновил схему в коде из БД — все ошибки вылезли во время компиляции.
Ну можно ведь не рефактроирть. Не обязательно приложению заранее знать какие там у объекта поля в БД есть. Если это какой-нибудь веб сервис — очень часто его можно написать так, чтобы он ничего такого не знал заранее и динамически загружал схему (и даже изменения на лету подхватывал). Мне, в моей практике, обычно что-то подобное попадается. А вот в таком стиле как ты хочешь реализовать, тоже, но обычно это более простые приложения.
Re[12]: [proof-of-concept] compile time query language
Здравствуйте, 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
Здравствуйте, 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
Здравствуйте, 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
Здравствуйте, 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
EP>В рамках одной из КСВ тем сделал proof-of-concept встроенного языка запросов времени компиляции, а точнее одной конкретной формы выражения (примеров EDSL и так предостаточно). EP>Все этапы, включая генерацию текста SQL запроса, вынесены в compile-time.
Кстати, было бы интересно, на мой взгляд, использовать этот подход для генерации protobuf кода. Очень часто через protobuf общаются только С++ приложения и вместо использования IDL и отдельного этапа сборки для генерации обвязки, можно было бы использовать метапрограммирование.
Re[2]: [proof-of-concept] compile time query language
Здравствуйте, chaotic-kotik, Вы писали:
CK>Кстати, было бы интересно, на мой взгляд, использовать этот подход для генерации protobuf кода. Очень часто через protobuf общаются только С++ приложения и вместо использования IDL и отдельного этапа сборки для генерации обвязки, можно было бы использовать метапрограммирование.
Рекурсивная сериализация структур данных? Это делается на Boost.Fusion (а сейчас и на Boost.Hana) по сути в одну короткую функцию