#include "matador/sql/query_builder.hpp" #include "matador/sql/dialect.hpp" #include "matador/utils/string.hpp" #include namespace matador::sql { namespace detail { any_type_to_string_visitor::any_type_to_string_visitor(const dialect &d, query_context &query) : d(d), query(query) {} void any_type_to_string_visitor::to_string(const char *val) { result = "'" + d.prepare_literal(val) + "'"; } void any_type_to_string_visitor::to_string(std::string &val) { result = "'" + d.prepare_literal(val) + "'"; } void any_type_to_string_visitor::to_string(placeholder &/*val*/) { query.bind_vars.emplace_back("unknown"); result = d.next_placeholder(query.bind_vars); } } // poor mens state machine // but does the job for query query_builder::query_state_transition_map query_builder::transitions_{ {state_t::QUERY_INIT, {state_t::QUERY_CREATE, state_t::QUERY_DROP, state_t::QUERY_SELECT, state_t::QUERY_INSERT, state_t::QUERY_UPDATE, state_t::QUERY_DELETE}}, {state_t::QUERY_CREATE, {state_t::QUERY_TABLE_CREATE}}, {state_t::QUERY_DROP, {state_t::QUERY_TABLE_DROP}}, {state_t::QUERY_SELECT, {state_t::QUERY_FROM}}, {state_t::QUERY_INSERT, {state_t::QUERY_INTO}}, {state_t::QUERY_UPDATE, {state_t::QUERY_SET}}, {state_t::QUERY_DELETE, {state_t::QUERY_FROM}}, {state_t::QUERY_TABLE_CREATE, {state_t::QUERY_FINISH}}, {state_t::QUERY_TABLE_DROP, {state_t::QUERY_FINISH}}, {state_t::QUERY_FROM, {state_t::QUERY_OFFSET, state_t::QUERY_LIMIT, state_t::QUERY_WHERE, state_t::QUERY_ORDER_BY, state_t::QUERY_GROUP_BY, state_t::QUERY_FINISH}}, {state_t::QUERY_SET, {state_t::QUERY_WHERE, state_t::QUERY_FINISH}}, {state_t::QUERY_INTO, {state_t::QUERY_VALUES}}, {state_t::QUERY_WHERE, {state_t::QUERY_ORDER_BY, state_t::QUERY_GROUP_BY, state_t::QUERY_OFFSET, state_t::QUERY_LIMIT, state_t::QUERY_FINISH}}, {state_t::QUERY_ORDER_BY, {state_t::QUERY_ORDER_DIRECTION}}, {state_t::QUERY_ORDER_DIRECTION, {state_t::QUERY_OFFSET, state_t::QUERY_LIMIT, state_t::QUERY_FINISH}}, {state_t::QUERY_GROUP_BY, {state_t::QUERY_ORDER_BY, state_t::QUERY_FINISH}}, {state_t::QUERY_OFFSET, {state_t::QUERY_LIMIT}}, {state_t::QUERY_LIMIT, {state_t::QUERY_FINISH}}, {state_t::QUERY_VALUES, {state_t::QUERY_FINISH}}, {state_t::QUERY_FINISH, {}}, }; query_builder::query_state_to_string_map query_builder::state_strings_{ {state_t::QUERY_INIT, "init"}, {state_t::QUERY_CREATE, "create"}, {state_t::QUERY_DROP, "drop"}, {state_t::QUERY_SELECT, "select"}, {state_t::QUERY_INSERT, "insert"}, {state_t::QUERY_UPDATE, "update"}, {state_t::QUERY_DELETE, "delete"}, {state_t::QUERY_TABLE_CREATE, "table"}, {state_t::QUERY_TABLE_DROP, "table"}, {state_t::QUERY_FROM, "from"}, {state_t::QUERY_SET, "set"}, {state_t::QUERY_INTO, "into"}, {state_t::QUERY_WHERE, "where"}, {state_t::QUERY_ORDER_BY, "order_by"}, {state_t::QUERY_GROUP_BY, "group_by"}, {state_t::QUERY_OFFSET, "offset"}, {state_t::QUERY_LIMIT, "limit"}, {state_t::QUERY_FINISH, "finish"}, }; query_builder::query_command_to_string_map query_builder::command_strings_{ {command_t::UNKNOWN, "unknown"}, {command_t::CREATE, "create"}, {command_t::DROP, "drop"}, {command_t::SELECT, "select"}, {command_t::INSERT, "insert"}, {command_t::UPDATE, "update"}, {command_t::REMOVE, "remove"}, }; query_builder::query_builder(const dialect &d) : dialect_(d), value_to_string_(d, query_) {} query_builder &query_builder::create() { initialize(command_t::CREATE, state_t::QUERY_CREATE); query_parts_.emplace_back(dialect_.token_at(dialect::token_t::CREATE)); return *this; } query_builder &query_builder::drop() { initialize(command_t::DROP, state_t::QUERY_DROP); query_parts_.emplace_back(dialect_.token_at(dialect::token_t::DROP)); return *this; } query_builder &query_builder::select(std::initializer_list columns) { return select(std::vector{columns}); } query_builder &query_builder::select(const std::vector &columns) { initialize(command_t::SELECT, state_t::QUERY_SELECT); query_parts_.emplace_back(dialect_.token_at(dialect::token_t::SELECT) + " "); query_.prototype.clear(); std::string result; if (columns.size() < 2) { for (const auto &col: columns) { result.append(dialect_.prepare_identifier(col)); query_.result_vars.emplace_back(col.name()); query_.prototype.append(col); } } else { auto it = columns.begin(); result.append(dialect_.prepare_identifier(*it)); query_.result_vars.emplace_back(it->name()); query_.prototype.append(column{*it++}); for (; it != columns.end(); ++it) { result.append(", "); result.append(dialect_.prepare_identifier(*it)); query_.result_vars.emplace_back(it->name()); query_.prototype.append(column{*it}); } } query_parts_.emplace_back(result); return *this; } query_builder &query_builder::insert() { initialize(command_t::INSERT, state_t::QUERY_INSERT); query_parts_.emplace_back(dialect_.token_at(dialect::token_t::INSERT)); return *this; } query_builder &query_builder::update(const std::string &table) { initialize(command_t::UPDATE, state_t::QUERY_UPDATE); query_.table_name = table; query_parts_.emplace_back(dialect_.token_at(dialect::token_t::UPDATE) + " " + dialect_.prepare_identifier(table)); return *this; } query_builder &query_builder::remove() { initialize(command_t::REMOVE, state_t::QUERY_DELETE); query_parts_.emplace_back(dialect_.token_at(dialect::token_t::REMOVE)); return *this; } query_builder &query_builder::table(const std::string &table, std::initializer_list columns) { return this->table(table, std::vector{columns}); } struct fk_context { std::string column; std::string ref_table; std::string ref_column; }; struct column_context { std::vector primary_keys; std::vector foreign_contexts; }; std::string build_create_column(const column &col, const dialect &d, column_context &context); query_builder &query_builder::table(const std::string &table, const std::vector &columns) { transition_to(state_t::QUERY_TABLE_CREATE); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::TABLE) + " " + dialect_.prepare_identifier(table) + " "); query_.table_name = table; std::string result = "("; column_context context; if (columns.size() < 2) { for (const auto &col: columns) { result.append(build_create_column(col, dialect_, context)); } } else { auto it = columns.begin(); result.append(build_create_column(*it++, dialect_, context)); for (; it != columns.end(); ++it) { result.append(", "); result.append(build_create_column(*it, dialect_, context)); } } if (!context.primary_keys.empty()) { result.append(", CONSTRAINT PK_" + table + " PRIMARY KEY (" + utils::join(context.primary_keys, ", ") + ")"); } for (const auto &fk: context.foreign_contexts) { result += ", CONSTRAINT FK_" + table; result += "_" + fk.column; result += " FOREIGN KEY (" + fk.column + ")"; result += " REFERENCES " + fk.ref_table + "(" + fk.ref_column + ")"; } result += ")"; query_parts_.emplace_back(result); return *this; } query_builder &query_builder::table(const std::string &table) { transition_to(state_t::QUERY_TABLE_DROP); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::TABLE) + " " + dialect_.prepare_identifier(table)); query_.table_name = table; return *this; } query_builder &query_builder::into(const std::string &table, std::initializer_list column_names) { return into(table, std::vector{column_names}); } query_builder &query_builder::into(const std::string &table, const std::vector &column_names) { transition_to(state_t::QUERY_INTO); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::INTO) + " " + dialect_.prepare_identifier(table) + " "); query_.table_name = table; std::string result{"("}; if (column_names.size() < 2) { for (const auto &col: column_names) { result.append(dialect_.prepare_identifier(col)); } } else { auto it = column_names.begin(); result.append(dialect_.prepare_identifier(*it++)); for (; it != column_names.end(); ++it) { result.append(", "); result.append(dialect_.prepare_identifier(*it)); } } result += (")"); query_parts_.emplace_back(result); return *this; } query_builder &query_builder::values(std::initializer_list values) { return this->values(std::vector{values}); } query_builder &query_builder::values(const std::vector &values) { transition_to(state_t::QUERY_VALUES); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::VALUES) + " "); std::string result{"("}; if (values.size() < 2) { for (auto val: values) { // query_.result_vars.push_back(val); std::visit(value_to_string_, val); result.append(value_to_string_.result); } } else { auto it = values.begin(); auto val = *it++; // query_.result_vars.push_back(val); std::visit(value_to_string_, val); result.append(value_to_string_.result); for (; it != values.end(); ++it) { result.append(", "); val = *it; // query_.result_vars.push_back(val); std::visit(value_to_string_, val); result.append(value_to_string_.result); } } result += (")"); query_parts_.emplace_back(result); return *this; } query_builder &query_builder::from(const std::string &table, const std::string &as) { transition_to(state_t::QUERY_FROM); if (dialect_.default_schema_name().empty()) { query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::FROM) + " " + dialect_.prepare_identifier(table) + (as.empty() ? "" : " " + as)); } else { query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::FROM) + " " + dialect_.prepare_identifier(dialect_.default_schema_name()) + "." + dialect_.prepare_identifier(table) + (as.empty() ? "" : " " + as)); } query_.table_name = table; return *this; } query_builder &query_builder::join(const std::string &table, join_type_t) { return *this; } query_builder &query_builder::on(const std::string &column, const std::string &join_column) { return *this; } query_builder &query_builder::set(std::initializer_list key_values) { return set(std::vector{key_values}); } query_builder &query_builder::set(const std::vector &key_values) { transition_to(state_t::QUERY_SET); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::SET) + " "); std::string result; if (key_values.size() < 2) { for (const auto &col: key_values) { result.append(dialect_.prepare_identifier(col.name()) + "="); auto var = col.value(); std::visit(value_to_string_, var); result.append(value_to_string_.result); } } else { auto it = key_values.begin(); result.append(dialect_.prepare_identifier(it->name()) + "="); auto var = (it++)->value(); std::visit(value_to_string_, var); result.append(value_to_string_.result); for (; it != key_values.end(); ++it) { result.append(", "); result.append(dialect_.prepare_identifier((*it).name()) + "="); var = it->value(); std::visit(value_to_string_, var); result.append(value_to_string_.result); } } query_parts_.emplace_back(result); return *this; } query_builder &query_builder::where(const basic_condition &cond) { transition_to(state_t::QUERY_WHERE); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::WHERE) + " "); query_parts_.emplace_back(cond.evaluate(const_cast(dialect_), query_)); return *this; } query_builder &query_builder::order_by(const std::string &column) { transition_to(state_t::QUERY_ORDER_BY); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::ORDER_BY) + " " + dialect_.prepare_identifier(column)); return *this; } query_builder &query_builder::group_by(const std::string &column) { transition_to(state_t::QUERY_GROUP_BY); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::GROUP_BY) + " " + dialect_.prepare_identifier(column)); return *this; } query_builder &query_builder::asc() { transition_to(state_t::QUERY_ORDER_DIRECTION); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::ASC)); return *this; } query_builder &query_builder::desc() { transition_to(state_t::QUERY_ORDER_DIRECTION); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::DESC)); return *this; } query_builder &query_builder::offset(size_t count) { transition_to(state_t::QUERY_OFFSET); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::OFFSET) + " " + std::to_string(count)); return *this; } query_builder &query_builder::limit(size_t count) { transition_to(state_t::QUERY_LIMIT); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::LIMIT) + " " + std::to_string(count)); return *this; } query_context query_builder::compile() { for (const auto &part: query_parts_) { query_.sql.append(part); } query_.command_name = command_strings_[command_]; return query_; } void query_builder::transition_to(query_builder::state_t next) { if (transitions_[state_].count(next) == 0) { throw std::logic_error("invalid next state " + state_strings_[next]); } state_ = next; } void query_builder::initialize(query_builder::command_t cmd, query_builder::state_t state) { command_ = cmd; query_ = {}; state_ = state; query_parts_.clear(); } std::string build_create_column(const column &col, const dialect &d, column_context &context) { std::string result = d.prepare_identifier(col.name()) + " " + d.data_type_at(col.type()); if (col.attributes().size() > 0) { result.append("(" + std::to_string(col.attributes().size()) + ")"); } if (!col.is_nullable()) { result.append(" NOT NULL"); } if (is_constraint_set(col.attributes().options(), utils::constraints::UNIQUE)) { result.append(" UNIQUE"); } if (is_constraint_set(col.attributes().options(), utils::constraints::PRIMARY_KEY)) { context.primary_keys.emplace_back(col.name()); } if (is_constraint_set(col.attributes().options(), utils::constraints::FOREIGN_KEY)) { context.foreign_contexts.push_back({col.name(), col.ref_table(), col.ref_column()}); } return result; } column alias(const std::string &column, const std::string &as) { return {column, as}; } column alias(column &&col, const std::string &as) { col.alias(as); return std::move(col); } column count(const std::string &column) { return {sql_function_t::COUNT, column}; } column count_all() { return count("*"); } }