
Предыстория
Ключевые слова constexpr
/consteval
в С++ живут уже не первый год, но для многих по‑прежнему остаются чем‑то неяснымиили чересчур академичными. По старой памяти их роль ограничивается чем‑то вроде «вычислить факториал в compile‑time», «сделать генератор чисел» или, в лучшем случае, «записать пару if constexpr
для метапрограммирования». Словом, игрушка для шаблонных фокусов, но не инструмент, способный менять архитектурную парадигму.
Однако C++ постепенно эволюционирует. С выходом новых стандартов (C++20, C++23 и предстоящего C++26) и с расширением constexpr
‑совместимости в стандартной библиотеке, — пространство применения constexpr
и consteval
стремительно выходит за рамки тривиальных вычислений. Мы получаем возможность работать с полноценными структурами данных, парсить текст, реагировать на ошибки осмысленно и строго уже в момент компиляции, а не где‑то в CI или, что хуже, в runtime. Именно здесь возникает новое мышление: если что‑то может быть проверено до запуска — оно обязано быть проверено до запуска.
В этой статье мы посмотрим, как можно реализовать полную compile‑time валидацию SQL‑запросов на основе схемы базы данных, встраиваемой прямо в код. Без магии, без рантайма, без сторонних тулов. Только стандартный C++ и ваша структура БД. Валидация таблиц, столбцов, типов аргументов и их количества — всё на compile‑time.
Представьте, если бы компилятор сам указывал «такой таблицы нет», «несуществующий столбец», «несовместимые типы» — до запуска программы. Такой подход полностью устраняет «сюрпризы» во время исполнения и исключает класс ошибок, связанных с генерацией SQL во время работы программы. Ваша программа даже не соберётся.
Ссылка на полный проект с тестовыми примерами
Архитектура решения
Архитектура механизма проверки SQL на этапе компиляции строится на следующих шагах:
Схема базы данных. Описываем структуру БД в статическом файле (например, XML, JSON или SQL DDL) с таблицами, полями и их типами.
-
Встраивание схемы. Содержимое схемы включается в исходники. В будущем C++26 для этого может использоваться директива #embed, которая автоматически встраивает файл как массив байт:
constexpr const char data[] = { #embed "test.json", 0};
В C++20/23 можно использовать, например, скрипт‑генератор и преобразовать содержимое файла в строковый литерал
constexpr std::string_view
. Парсинг схемы на этапе компиляции. Специальная
consteval
‑функция (илиconstexpr
‑функция, выполняемая при компиляции) читаетstring_view
схемы и анализирует ее. В результате компиляции формируется константная модель БД: таблицы преобразуются в constexpr структуры или массивы, столбцы — в constexpr значения. Получаем в коде C++ типобезопасное описание схемы (наподобие «отображения» из имен в типы полей).SQL‑валидатор на этапе компиляции. Любой SQL‑запрос в коде оформляется, например, как строковый литерал с передачей аргументов полей. С помощью consteval ‑функций мы разбираем этот запрос на лексемы (SELECT, FROM, WHERE и т. д.), сверяем упоминаемые таблицы и поля с ранее построенной схемой. Если находится несоответствие (не та таблица, не тот столбец, несовместимые типы), генерируем
static_assert
с подробным сообщением. Если проверка проходит, компиляция продолжается.
Благодаря таким метаданным мы имеем все имена таблиц/столбцов и их типы. Эти структуры определяются в коде (сгенерированы из внешнего описания) и имеют только constexpr
‑данные. В схеме нет дублей: исходная информация хранится только в одном месте (схема базы данных) и преобразуется в C++‑типы автоматически. Таким образом, уже к моменту валидации запросов компилятор «знает», какие таблицы существуют и какие поля в них есть.
Схема описывается несколькими простыми структурами:
Column
— описывает столбец: содержит имя ( std::string_view
) и тип поля.
Table
— описывает таблицу: содержит имя таблицы и массив std::array<Column> полей.
Schema
— список всех таблиц в базе.
constexpr size_t MAX_COLUMNS = 100;
constexpr size_t MAX_TABLES = 100;
struct Table
{
enum ColumnType : int
{
BOOLEAN,
DATETIME,
DOUBLE,
ENUM,
HASHTABLE,
IDENTIFIER,
INT64,
INTEGER,
MONEY,
STRING,
UUID,
};
struct Column
{
std::string_view mName;
ColumnType mType;
bool mIsNotNull = false;
};
std::string_view mName;
std::array< Column, MAX_COLUMNS > mColumns{};
size_t Column_count = 0;
};
struct Schema
{
std::array< Table, MAX_TABLES > mTables{};
size_t mTableCount = 0;
};
Здесь вынужден сделать уточнение, что столько кривой вариант работы через std::array — вынужденная необходимость С++23
, которая уже в С++26
позволит оперировать векторами и иными контейнерами с динамической памятью.
Для того, чтобы перетащить актуальную версию схемы базы данных (для примера это будет XML‑файл, описывающий схему базы данных в формате, характерном для внутреннего DSL), я набросал небольшую кастомную команду в cmake:
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/generated_dicx.hpp
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}
COMMAND python3 ${CMAKE_SOURCE_DIR}/tools/embed_dicx.py
${CMAKE_SOURCE_DIR}/db.dicx
${CMAKE_BINARY_DIR}/generated_dicx.hpp
DEPENDS ${CMAKE_SOURCE_DIR}/db.dicx
)
Которая запускает python‑скрипт по копированию файла схемы в хэдер‑файлик, который затем будет подключен к проекту для парсинга в нашу структуру. Суть скрипта — предоставить нам при компиляции актуальный вид БД в constexpr виде:
constexpr std::string_view dicx_xml =
R"DICX(<?xml version="1.0" encoding="UTF-8" ?>
<table name="Company" responsible="Косинцев А.В.">
<comment>Таблица для компаний</comment>
<column index_in_pk="0" is_pk_part="1" name="Uuid">
<comment>Uuid компании</comment>
<format>
<type>UUID</type>
<not_null>true</not_null>
</format>
</column>
</table>
<!-- другие столбцы и таблицы... -->
)DICX";
В C++26 это будет решено директивой #embed
, без внешних скриптов, но для поддержания работоспособности примера в С++23 сойдет и текущий способ.
Это небольшое достижение позволит нам перейти к парсингу dicx_xml
.
Для предоставления общего интерфейса парсера используется паттерн CRTP (Curiously Recurring Template Pattern). Так, наш шаблонный класс BaseParserCRTP
— это наиболее простой способ заиметь constexpr
-поле класса, инициализируемое наследником, который сам определит, как распарсить свою схему базы данных. Сам же BaseParserCRTP реализует общую логику получения готовой структуры Schema, ее таблиц и их количества.
template< typename Derived >
class BaseParserCRTP
{
protected:
static inline constexpr Schema mSchema = Derived::Parse();
public:
consteval static size_t TableCount()
{
return mSchema.mTableCount;
}
consteval static const Table* GetTable( std::string_view name )
{
for( auto const& t : mSchema.mTables )
if( t.mName == name )
return &t;
return nullptr;
}
};
Для своего XML‑варианта я определю своего наследника с нужным парсером, и именно он получит на обработку нашу сгенерированную версию схемы БД:
#include "base_parser.hpp"
#include "generated_dicx.hpp"
#include <string_view>
class DicxParser : public BaseParserCRTP< DicxParser >
{
public:
consteval static Schema Parse();
private:
using ColumnType = Table::ColumnType;
static consteval size_t FindTag( std::string_view xml, std::string_view tag, size_t start = 0 );
static consteval std::string_view ExtractAttr( std::string_view tag, std::string_view key );
static consteval ColumnType ParseColumnType( std::string_view s );
};
inline consteval Schema DicxParser::Parse()
{
Schema r;
size_t pos = 0;
while( true )
{
auto tbl_pos = FindTag( dicx_xml, "<table ", pos );
if( tbl_pos == dicx_xml.size() )
break;
auto end_tag = FindTag( dicx_xml, ">", tbl_pos );
auto tag_str = dicx_xml.substr( tbl_pos, end_tag - tbl_pos );
auto name = ExtractAttr( tag_str, "name=" );
auto& table = r.mTables[ r.mTableCount++ ];
table.mName = name;
size_t col_pos = end_tag;
auto table_close = FindTag( dicx_xml, "</table>", end_tag );
while( true )
{
auto cpos = FindTag( dicx_xml, "<column ", col_pos );
if( cpos >= table_close )
break;
auto cend = FindTag( dicx_xml, ">", cpos );
auto ctag = dicx_xml.substr( cpos, cend - cpos );
auto col_name = ExtractAttr( ctag, "name=" );
auto& Column = table.mColumns[ table.Column_count++ ];
Column.mName = col_name;
auto tpos = FindTag( dicx_xml, "<type>", cend );
auto tend = FindTag( dicx_xml, "</type>", tpos );
auto type_name = utils::Trim( dicx_xml.substr( tpos + 6, tend - tpos - 6 ) );
Column.mType = ParseColumnType( type_name );
auto nn_pos = FindTag( dicx_xml, "<not_null>", tpos );
if( nn_pos < table_close && nn_pos < FindTag( dicx_xml, "</not_null>", nn_pos ) )
{
auto nn_end = FindTag( dicx_xml, "</not_null>", nn_pos );
Column.mIsNotNull = dicx_xml.substr( nn_pos + 10, nn_end - nn_pos - 10 ) == "true";
}
col_pos = tend;
}
pos = table_close;
}
return r;
}
inline consteval DicxParser::ColumnType DicxParser::ParseColumnType( std::string_view s )
{
if( s == "UUID" )
return ColumnType::UUID;
if( s == "STRING" )
return ColumnType::STRING;
if( s == "INTEGER" )
return ColumnType::INTEGER;
if( s == "DOUBLE" )
return ColumnType::DOUBLE;
if( s == "BOOLEAN" )
return ColumnType::BOOLEAN;
if( s == "DATETIME" )
return ColumnType::DATETIME;
if( s == "ENUM" )
return ColumnType::ENUM;
if( s == "HASHTABLE" )
return ColumnType::HASHTABLE;
if( s == "IDENTIFIER" )
return ColumnType::IDENTIFIER;
if( s == "INT64" )
return ColumnType::INT64;
if( s == "MONEY" )
return ColumnType::MONEY;
throw "Unknown Column type: " __FILE__;
}
DicxParser
наследуется от BaseParserCRTP
и знает формат нашего.dicx XML (таблицы, столбцы, типы). За счёт CRTP весь парсинг происходит без виртуальных вызовов.
После того как схема представлена в constexpr‑структурах, любой SQL‑запрос можно проверить компилятором. Это может быть простой рекурсивный разбор (например, только SELECT‑FROM‑WHERE без сложных JOIN), выполненный в consteval функции.
При этом собственным критерием удобства был формат написания запросов, по типу таких:
SQL::Select< "SELECT Folder, Id FROM CompanyFolderCounters" >();
SQL::Insert< "INSERT INTO Company (Description, Uuid, Name, Rating)" >
( description, uuid, name, rate );
Это позволило бы оставить гибкость SQL‑языка, и при этом не потерять аргументы полей.
Наш SqlChecker
крайне прост и его методы параметризуются ровно тем способом, пример использования которого вы видите выше. Разумеется, представленный вариант крайне примитивен и служит целью показать возможность использования С++ для валидации SQL‑запросов.
template< typename T >
concept DBParser = std::derived_from< T, BaseParserCRTP< T > >;
template< DBParser Parser >
class SqlChecker
{
public:
virtual ~SqlChecker() = default;
consteval SqlChecker() = default;
consteval static inline size_t TableCount();
template< FixedString SQL, typename... Args >
consteval void CheckInsert() const;
template< FixedString SQL >
consteval void ValidateSelect() const;
private:
consteval static Table const* GetTable( std::string_view table_name )
{
return Parser::GetTable( table_name );
}
};
Из‑за ограничений С++23 на constexpr
‑контейнеры, пришлось, не дожидаясь 26 года, сообразить FixedString
, который бы не содержал ничего лишнего, и полностью умещался в constexpr контекст:
template< size_t N >
struct FixedString
{
char mData[ N ]{};
constexpr FixedString( const char ( &str )[ N ] )
{
for( size_t i = 0; i < N; ++i )
mData[ i ] = str[ i ];
}
constexpr operator std::string_view() const
{
return { mData, N - 1 };
}
};
Иначе вы бы сразу столкнулись с сообщением вроде:
“Type 'std::string_view' is not a structural type because it has a non‑static data member that is not public.”
Мой простенький метод валидации вычитки из базы данных выглядит так:
template< FixedString SQL >
consteval void ValidateSelect() const
{
constexpr std::string_view sql = SQL;
if constexpr( constexpr bool is_select = sql.starts_with( "SELECT" ); !is_select )
static_assert( is_select, "It's not SELECT query" );
constexpr auto from = sql.find( "FROM" );
if( constexpr bool is_from = from != std::string_view::npos; !is_from )
static_assert( is_from, "FROM not found" );
constexpr auto columns = sql.substr( 7, from - 7 );
if constexpr( utils::Trim( columns ) == "*" )
{
constexpr auto tbl = utils::Trim( sql.substr( from + 5 ) );
static_assert( GetTable( tbl ), "Unknown table" );
}
else
{
constexpr auto tbl = utils::Trim( sql.substr( from + 5 ) );
constexpr auto sd = GetTable( utils::Trim( tbl ) );
if constexpr( constexpr bool is_table_present = sd; !is_table_present )
static_assert( is_table_present, "Unknown table" );
constexpr auto column_result = utils::Split( columns, ',' );
constexpr auto column_tokens = column_result.first;
constexpr size_t column_count = column_result.second;
// Проверка одного аргумента по соответствию в БД
auto test_one = [ & ]< size_t I >() consteval
{
constexpr auto name = utils::Trim( column_tokens[ I ] );
constexpr bool ok = [ & ]
{
for( size_t j = 0; j < sd->Column_count; ++j )
if( sd->mColumns[ j ].mName == name )
return true;
return false;
}();
if constexpr( !ok )
{
static_assert( ok, "Unknown column" );
}
};
[ & ]< size_t... Is >( std::index_sequence< Is... > ) consteval
{ ( test_one.template operator()< Is >(), ... ); }( std::make_index_sequence< column_count >{} );
}
}
Здесь целая эпопея из слова constexpr
, но таковы требования языка к тому, чтобы убедиться в compile‑time принадлежности той или иной переменной. Отдельная боль для column_result
в том, что декомпозиция пока что не может быть constexpr
.
Облегчение здесь может быть в том, что ограничения и минусы стилистики языка имеют решение в грядущем стандарте, а значит, можно надеяться на более лаконичную и меньшую по количеству костылей версию данной задумки.
Ради справедливости приложу версию Insert
валидации, но полную версию всего здесь описанного можно будет глянуть и потыкать ЗДЕСЬ:
template< FixedString SQL, typename... Args >
consteval void CheckInsert() const
{
constexpr std::string_view sqlv = SQL;
// Проверка, что запрос начинается с INSERT
if constexpr( !sqlv.starts_with( "INSERT" ) )
{
static_assert( false, "It's not INSERT query" );
}
// Нахождение позиции "INTO" и скобок
constexpr auto into_pos = sqlv.find( "INTO" );
if constexpr( into_pos == std::string_view::npos )
{
static_assert( false, "INTO not found in INSERT query" );
}
else
{
// Извлечение списка полей между скобками
constexpr auto paren1 = sqlv.find( '(', into_pos );
constexpr std::string_view table = utils::Trim( sqlv.substr( into_pos + 4, paren1 - ( into_pos + 4 ) ) );
constexpr auto paren2 = sqlv.find( ')', paren1 );
constexpr std::string_view Columns_list = sqlv.substr( paren1 + 1, paren2 - ( paren1 + 1 ) );
constexpr auto split_result = utils::Split( Columns_list, ',' );
constexpr auto columns = split_result.first;
constexpr size_t fcount = split_result.second;
// Получаем дескриптор таблицы по имени
constexpr auto sd = GetTable( table );
if constexpr( !sd )
{
static_assert( false, "Unknown table" );
}
// Проверка, что количество аргументов совпадает с количеством полей
if constexpr( fcount != sizeof...( Args ) )
{
static_assert( false, "Column count is not equal to argument count" );
}
else
{
constexpr auto trimmed_Columns = [ & ]() consteval
{
std::array< std::string_view, sizeof...( Args ) > out{};
for( size_t i = 0; i < sizeof...( Args ); ++i )
{
out[ i ] = utils::Trim( columns[ i ] );
}
return out;
}();
// Проверка одного аргумента по соответствию в БД
auto test_one = [ & ]< typename T, size_t I >() consteval
{
constexpr std::string_view column_name = trimmed_Columns[ I ];
// 1) Сначала проверяем, существует ли такое поле в sd->Columns
constexpr bool column_exists = [ & ]() consteval -> bool
{
for( size_t j = 0; j < sd->Column_count; ++j )
{
if( sd->mColumns[ j ].mName == column_name )
{
return true;
}
}
return false;
}();
if constexpr( !column_exists )
{
static_assert( false, "Unknown column name" );
}
else
{
// 2) Теперь безопасно можем найди индекс поля (поскольку он точно есть)
constexpr size_t found_idx = [ & ]() consteval
{
for( size_t j = 0; j < sd->Column_count; ++j )
{
if( sd->mColumns[ j ].mName == column_name )
{
return j;
}
}
return size_t( 42 );
}();
// 3) Проверяем тип T на совпадение с типом поля
using U = std::decay_t< T >;
constexpr auto f_type = sd->mColumns[ found_idx ].mType;
if constexpr( f_type == Table::INTEGER )
{
static_assert( std::is_same_v< U, int >, "Type mismatch: expected INTEGER" );
}
else if constexpr( f_type == Table::DOUBLE )
{
static_assert( std::is_same_v< U, double >, "Type mismatch: expected DOUBLE" );
}
else if constexpr( f_type == Table::STRING )
{
static_assert( std::is_same_v< U, std::string >, "Type mismatch: expected STRING" );
}
else if constexpr( f_type == Table::UUID )
{
static_assert( std::is_same_v< U, Uuid >, "Type mismatch: expected UUID" );
}
else if constexpr( constexpr bool is_Column_not_found = true )
{
static_assert( false && sd->mColumns[ found_idx ].mType,
"Type mismatch: unknown column type in table" );
}
}
};
[ & ]< size_t... Is >( std::index_sequence< Is... > ) consteval
{
( test_one.template operator()< typename std::tuple_element< Is, std::tuple< Args... > >::type, Is >(),
... );
}( std::make_index_sequence< fcount >{} );
}
}
}
В отличие от SELECT, валидация INSERT требует строгой проверки количества и порядка значений — в точном соответствии с порядком полей, а также передаваемых типов аргументов - Uuid - значит - Uuid.
После этих двух примеров становиться очевидным, что в constexpr
пока что не получается пройтись по количеству типов аргументов иначе, как через шаблоны, инстанцируемые на этапе компиляции, но да не будет это помехой.
Однако, цель достигнута и работа нашего SqlChecker'a
примерно такая:
1) Лексический анализ: строковый литерал запроса разбивается на токены (ключевые слова, имена таблиц/полей, литералы, операторы).
2) Проверка синтаксиса: анализируем структуру запросов (простая грамматика SELECT). Если синтаксис не соответствует поддерживаемому подмножеству SQL, вызываем static_assert
.
3) Проверка таблиц/столбцов: для каждого упомянутого имени таблицы ищем соответствующую таблицу в constexpr
‑схеме. Аналогично проверяем поля и их типы: например, убеждаемся, что литерал конвертируется в тип поля (например, не сравниваем число с текстом). При несовместимости типов — static_assert(«Type mismatch:...»).
Примитивный валидатор для SQL‑запросов готов, и теперь осталось его красиво обернуть:
#include "dicx_parser.hpp"
#include "sql_checker.hpp"
/// Обертка для работы с БД
struct SQL
{
template< FixedString Query, typename... Args >
constexpr static void Insert( Args const&... args )
{
static_assert( mChecker.TableCount() > 0, "No tables loaded" );
mChecker.CheckInsert< Query, Args... >();
// Здесь обычная вставка
// Вызывается только если compile-time проверка прошла успешно
}
template< FixedString Query >
consteval static void Select()
{
static_assert( mChecker.TableCount() > 0, "No tables loaded" );
mChecker.ValidateSelect< Query >();
// Здесь обычная вставка
// Вызывается только если compile-time проверка прошла успешно
}
private:
inline static constexpr SqlChecker< DicxParser > mChecker;
};
Данная структура скрывает детали реализации и позволяет выполнять вставку напрямую через передачу запроса и аргументов, валидируя запрос любой сложности внутри.
Ошибки, отловленные во время компиляции
Uuid uuid;
std::string description = "description", name = "name";
double rate = 4.6;
int int_uuid;
// Случайно передадим вместо Uuid-идентификатора - простое целое число
SQL::Insert< "INSERT INTO Company (Description, Uuid, Name, Rating)" >
( description, int_uuid, name, rate );
// ВЫВОД: error: In template: static assertion failed due to requirement
// 'std::is_same_v<int, Uuid>': Type mismatch: expected UUID
// Передадим меньшее количество аргументов, чем полей на вставку
SQL::Insert< "INSERT INTO Company (Description, Uuid, Name, Rating)" >
( description, name, rate );
// ВЫВОД: error: In template: static assertion failed:
// Column count is not equal to argument count
// Случайно ошибемся в названии столбца Uuid
SQL::Insert< "INSERT INTO Company (Description, UUid, Name, Rating)" >
( description, uuid, name, rate );
// ВЫВОД: error: In template: static assertion failed: Unknown column name
// Если такой таблицы нет, то:
SQL::Select< "SELECT Id FROM Car" >();
// ВЫВОД: Unknown table
И, разумеется, где-то я лукавлю, поскольку иногда сообщения могут выглядеть больше, стопкой варнингов или не подсвечивать конкретное место, оставляя гадать, какое же это поле.. Но это то малое, что уже можно использовать сейчас, и расширить и углубить в перспективе. Не раз и не два мы попадались на ошибки соответствия полей, аргументов и их текстового нейминга, а также банальных опечаток.
Однако, нужно отдавать отчет, что использовать constexpr
‑парсинга означает более сложную сборку и потенциально более долгий этап компиляции (особенно при больших схемах и сложных запросах). Имеет смысл поддерживать подмножества SQL, достаточные для основных операций. Тем не менее переотправка большей части логики в компилятор — оправданная плата за отсутствие неожиданных ошибок в рантайме.
Расширения и будущее
Новый стандарт C++26 продолжит расширять возможности такого решения. Директива #embed позволит включать файлы схемы или дамп БД прямо в код без внешних скриптов. Расширение constexpr ‑контейнеров позволят использовать динамическую память и умные указатели в constexpr
‑контексте. Это означает, что скоро не придется ограничивать число таблиц/столбцов жёсткими массивами — можно будет применять std::vector, std::string.
Помимо этого, возможно сочетание сгенерированного описания схемы с рефлексией в C++26: можно автоматически генерировать классы/типы по описанию таблиц или загружать новые версии схем, не меняя основной код. Тем не менее, текущий вариант в виде интеграции с системами сборки (например, CMake) не в меньше степени гарантирует, что при изменении базы данных создаётся обновлённый заголовок/ресурс, тогда как компиляция проверяет целостность.
Заключение
«Ошибки, предсказанные компилятором, — это ошибки, которые никогда не случились»
Ключевые слова constexpr
и consteval
— это не только средства для предвычислений, но и полноценный инструмент для чего угодно на этапе компиляции. Применив их к работе с базой данных, мы переносим всю ответственность за корректность SQL‑запросов в компилятор. Проект собирается лишь если все запросы совпадают со схемой. Это устраняет класс ошибок, связанных с базой данных, ещё до запуска приложения. С приходом C++26 эта модель станет ещё мощнее, но уже сейчас это хорошее подспорье для того, чтобы обезопасить рантайм, не усложняя стилистику использования SQL. Поэтому, можно смело сказать:
«Мы привыкли к тому, что
constexpr
— это математика. Пора понять, что это язык мышления компилятора»
Ссылка на полный проект с тестовыми примерами
Косинцев Артём
Инженер-программист
Комментарии (4)
Akina
18.07.2025 20:36Не, я мало что понимаю в С++, потому смотрел по диагонали. Но даже так - мне кажется, что применимость... ну скажем так, ограниченная. Во-первых, очень сильна привязка к конкретному диалекту SQL (порой изменения диалекта таковы, что даже изменение версии СУБД приведёт к ложным срабатываниям). Во-вторых, потенция ложных фэйлов, например, для СУБД с "мягкой" системой типов и автоприведением оных (а в большинстве СУБД это автоприведение хоть где-то, хоть в каких-то моментах, да встречается). В третьих, система совершенно не сможет работать с динамическим SQL, когда в формируемом запросе параметризованы имена объектов (скажем, имена таблиц или полей). Есть и в четвёртых, и в пятых... то есть, если я правильно понимаю, то код валидатора придётся каждый раз, для каждой программы или при каждом обновлении СУБД, создавать чуть ли не заново.
Makcal
Будет невероятно сложно поддерживать все реализации SQL из разных СУБД. По сути мы занимаемся полным копированием кода СУБД в C++. Идея очень крутая, но реализация сомнительно сложная. К тому же, проще и лучше сразу делать конструкторы запросов в виде функций и классов, как это делает sqlpp
ice872
Согласен, к примеру сейчас в проекте около 4 сотен таблиц и текущий подход превратит работу в ад.
А по sqlpp - время сборки проекта передаёт привет