entity query and record progress

This commit is contained in:
Sascha Kuehl 2024-03-04 20:09:39 +01:00
parent 830185c3c5
commit be610ffcad
31 changed files with 294 additions and 50 deletions

View File

@ -37,7 +37,7 @@ public:
size_t execute(const std::string &stmt) override;
sql::record describe(const std::string& table) override;
std::vector<sql::column_definition> describe(const std::string& table) override;
bool exists(const std::string &schema_name, const std::string &table_name) override;

View File

@ -181,13 +181,13 @@ std::unique_ptr<sql::query_result_impl> mysql_connection::fetch(const std::strin
auto field_count = mysql_num_fields(result);
auto fields = mysql_fetch_fields(result);
sql::record prototype;
std::vector<sql::column_definition> prototype;
for (unsigned i = 0; i < field_count; ++i) {
auto type = to_type(fields[i].type, fields[i].flags);
auto options = to_constraints(fields[i].flags);
auto null_opt = to_null_option(fields[i].flags);
prototype.append({fields[i].name, type, options, null_opt});
prototype.emplace_back(fields[i].name, type, options, null_opt);
}
return std::move(std::make_unique<sql::query_result_impl>(std::make_unique<mysql_result_reader>(result, field_count), std::move(prototype)));
@ -216,7 +216,7 @@ size_t mysql_connection::execute(const std::string &stmt)
return mysql_affected_rows(mysql_.get());
}
sql::record mysql_connection::describe(const std::string &table)
std::vector<sql::column_definition> mysql_connection::describe(const std::string &table)
{
std::string stmt("SHOW COLUMNS FROM " + table);
@ -230,7 +230,7 @@ sql::record mysql_connection::describe(const std::string &table)
}
mysql_result_reader reader(result, mysql_num_fields(result));
sql::record prototype;
std::vector<sql::column_definition> prototype;
while (reader.fetch()) {
char *end = nullptr;
@ -242,7 +242,7 @@ sql::record mysql_connection::describe(const std::string &table)
if (strtoul(reader.column(2), &end, 10) == 0) {
null_opt = sql::null_option::NOT_NULL;
}
prototype.append({name, typeinfo.type, {typeinfo.size}, null_opt, prototype.size()});
prototype.push_back({name, typeinfo.type, {typeinfo.size}, null_opt, prototype.size()});
}
return prototype;

View File

@ -33,7 +33,7 @@ public:
size_t execute(const std::string &stmt) override;
sql::record describe(const std::string& table) override;
std::vector<sql::column_definition> describe(const std::string& table) override;
bool exists(const std::string &schema_name, const std::string &table_name) override;

View File

@ -51,14 +51,13 @@ std::unique_ptr<sql::query_result_impl> postgres_connection::fetch(const std::st
throw_postgres_error(res, conn_, "postgres", stmt);
sql::record prototype;
std::vector<sql::column_definition> prototype;
auto num_col = PQnfields(res);
for (int i = 0; i < num_col; ++i) {
const char *col_name = PQfname(res, i);
auto type = PQftype(res, i);
auto size = PQfmod(res, i);
// std::cout << "column " << col_name << ", type " << type << " (size: " << size << ")\n";
prototype.append(sql::column_definition{col_name});
prototype.emplace_back(col_name);
}
return std::move(std::make_unique<sql::query_result_impl>(std::make_unique<postgres_result_reader>(res), std::move(prototype)));
}
@ -131,7 +130,7 @@ sql::data_type_t string2type(const char *type)
}
}
sql::record postgres_connection::describe(const std::string &table)
std::vector<sql::column_definition> postgres_connection::describe(const std::string &table)
{
std::string stmt(
"SELECT ordinal_position, column_name, udt_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema='public' AND table_name='" + table + "'");
@ -141,7 +140,7 @@ sql::record postgres_connection::describe(const std::string &table)
throw_postgres_error(res, conn_, "postgres", stmt);
postgres_result_reader reader(res);
sql::record prototype;
std::vector<sql::column_definition> prototype;
while (reader.fetch()) {
char *end = nullptr;
// Todo: Handle error
@ -156,7 +155,7 @@ sql::record postgres_connection::describe(const std::string &table)
null_opt = sql::null_option::NOT_NULL;
}
// f.default_value(res->column(4));
prototype.append({name, type, utils::null_attributes, null_opt, index});
prototype.emplace_back(name, type, utils::null_attributes, null_opt, index);
}
return std::move(prototype);

View File

@ -33,14 +33,14 @@ public:
size_t execute(const std::string &stmt) override;
sql::record describe(const std::string& table) override;
std::vector<sql::column_definition> describe(const std::string& table) override;
bool exists(const std::string &schema_name, const std::string &table_name) override;
private:
struct fetch_context
{
sql::record prototype;
std::vector<sql::column_definition> prototype;
sqlite_result_reader::rows rows;
};

View File

@ -65,7 +65,7 @@ int sqlite_connection::parse_result(void* param, int column_count, char** values
if (context->prototype.empty()) {
for(int i = 0; i < column_count; ++i) {
context->prototype.append(sql::column_definition{columns[i]});
context->prototype.emplace_back(columns[i]);
}
}
@ -144,12 +144,12 @@ sql::data_type_t string2type(const char *type)
}
}
sql::record sqlite_connection::describe(const std::string& table)
std::vector<sql::column_definition> sqlite_connection::describe(const std::string& table)
{
const auto result = fetch_internal("PRAGMA table_info(" + table + ")");
sqlite_result_reader reader(result.rows, result.prototype.size());
sql::record prototype;
std::vector<sql::column_definition> prototype;
while (reader.fetch()) {
char *end = nullptr;
// Todo: add index to column
@ -163,7 +163,7 @@ sql::record sqlite_connection::describe(const std::string& table)
null_opt = sql::null_option::NOT_NULL;
}
// f.default_value(res->column(4));
prototype.append({name, type, utils::null_attributes, null_opt, index});
prototype.emplace_back(name, type, utils::null_attributes, null_opt, index);
}
return std::move(prototype);

View File

@ -37,10 +37,19 @@ private:
using namespace matador;
TEST_CASE_METHOD(SessionFixture, "Session relation test", "[session][relation]") {
ses.attach<matador::test::airplane>("airplane");
using namespace matador;
ses.attach<test::airplane>("airplane");
ses.create_schema();
ses.insert<test::airplane>(1, "Boeing", "A380");
REQUIRE(true);
}
TEST_CASE_METHOD(SessionFixture, "Find object with id", "[session][find]") {
ses.attach<matador::test::airplane>("airplane");
ses.create_schema();
auto a380 = ses.insert<test::airplane>(1, "Boeing", "A380");
ses.find<test::airplane>(1);
}

View File

@ -10,6 +10,17 @@
namespace matador::sql {
using any_db_type = std::variant<
long long,
unsigned long long,
double,
bool,
const char*,
std::string,
utils::blob,
placeholder,
nullptr_t>;
using any_type = std::variant<
char, short, int, long, long long,
unsigned char, unsigned short, unsigned int, unsigned long, unsigned long long,

View File

@ -9,7 +9,6 @@
#include "matador/sql/query_result.hpp"
#include "matador/sql/record.hpp"
#include "matador/sql/statement.hpp"
#include "matador/sql/schema.hpp"
#include "matador/utils/logger.hpp"
@ -17,6 +16,8 @@
namespace matador::sql {
class schema;
class connection
{
public:
@ -32,7 +33,7 @@ public:
[[nodiscard]] bool is_open() const;
[[nodiscard]] const connection_info& info() const;
[[nodiscard]] record describe(const std::string &table_name) const;
[[nodiscard]] std::vector<sql::column_definition> describe(const std::string &table_name) const;
[[nodiscard]] bool exists(const std::string &schema_name, const std::string &table_name) const;
[[nodiscard]] bool exists(const std::string &table_name) const;

View File

@ -26,7 +26,7 @@ public:
virtual std::unique_ptr<query_result_impl> fetch(const std::string &stmt) = 0;
virtual std::unique_ptr<statement_impl> prepare(query_context context) = 0;
virtual record describe(const std::string &table) = 0;
virtual std::vector<sql::column_definition> describe(const std::string &table) = 0;
virtual bool exists(const std::string &schema_name, const std::string &table_name) = 0;
protected:

View File

@ -0,0 +1,62 @@
#ifndef QUERY_ENTITY_QUERY_BUILDER_HPP
#define QUERY_ENTITY_QUERY_BUILDER_HPP
#include "matador/sql/query_context.hpp"
namespace matador::sql {
template < typename PrimaryKeyType >
class entity_query_builder
{
public:
// determine pk
// collect eager relations for joins
template<class EntityType>
query_context build() {
EntityType obj;
matador::utils::access::process(*this, obj);
return {};
}
template < class V >
void on_primary_key(const char *id, V &, typename std::enable_if<std::is_integral<V>::value && !std::is_same<bool, V>::value>::type* = 0)
{
}
void on_primary_key(const char *id, std::string &, size_t)
{
}
void on_revision(const char * /*id*/, unsigned long long &/*rev*/) {}
template<typename Type>
void on_attribute(const char * /*id*/, Type &, const utils::field_attributes &/*attr*/ = utils::null_attributes) {}
template<class Pointer>
void on_belongs_to(const char *id, Pointer &, const utils::foreign_attributes &/*attr*/)
{
}
template<class Pointer>
void on_has_one(const char *id, Pointer &, const utils::foreign_attributes &attr)
{
}
template<class ContainerType>
void on_has_many(const char *, ContainerType &, const char *, const char *, const utils::foreign_attributes &attr)
{
}
template<class ContainerType>
void on_has_many(const char *id, ContainerType &c, const utils::foreign_attributes &attr)
{
on_has_many(id, c, "", "", attr);
}
private:
PrimaryKeyType pk_;
};
}
#endif //QUERY_ENTITY_QUERY_BUILDER_HPP

View File

@ -0,0 +1,37 @@
#ifndef QUERY_FIELD_HPP
#define QUERY_FIELD_HPP
#include "matador/sql/any_type.hpp"
#include "matador/sql/any_type_to_visitor.hpp"
#include <string>
namespace matador::sql {
class field
{
public:
[[nodiscard]] const std::string& name() const;
template<class Type>
Type as() const
{
const Type* ptr= std::get_if<Type>(&value_);
if (ptr) {
return *ptr;
}
any_type_to_visitor<Type> visitor;
std::visit(visitor, const_cast<any_type&>(value_));
return visitor.result;
}
friend std::ostream& operator<<(std::ostream &out, const field &col);
private:
std::string name_;
any_type value_;
};
}
#endif //QUERY_FIELD_HPP

View File

@ -16,7 +16,7 @@ public:
size_t execute(const std::string &stmt) override;
std::unique_ptr<query_result_impl> fetch(const std::string &stmt) override;
std::unique_ptr<statement_impl> prepare(query_context context) override;
record describe(const std::string &table) override;
std::vector<sql::column_definition> describe(const std::string &table) override;
bool exists(const std::string &schema_name, const std::string &table_name) override;
private:

View File

@ -11,7 +11,7 @@ struct query_context
std::string sql;
std::string command_name;
sql::table table{""};
record prototype;
std::vector<column_definition> prototype;
std::vector<std::string> result_vars;
std::vector<std::string> bind_vars;
};

View File

@ -107,13 +107,13 @@ private:
namespace detail {
template < typename Type >
Type* create_prototype(const record &/*prototype*/)
Type* create_prototype(const std::vector<column_definition> &/*prototype*/)
{
return new Type{};
}
template <>
record* create_prototype<record>(const record &prototype);
record* create_prototype<record>(const std::vector<column_definition> &prototype);
}
@ -128,7 +128,7 @@ public:
explicit query_result(std::unique_ptr<query_result_impl> impl)
: impl_(std::move(impl)) {}
query_result(std::unique_ptr<query_result_impl> impl, record record_prototype)
query_result(std::unique_ptr<query_result_impl> impl, std::vector<column_definition> record_prototype)
: record_prototype_(std::move(record_prototype))
, impl_(std::move(impl)) {}
@ -151,7 +151,7 @@ private:
}
private:
record record_prototype_;
std::vector<column_definition> record_prototype_;
std::unique_ptr<query_result_impl> impl_;
};

View File

@ -55,7 +55,7 @@ private:
class query_result_impl
{
public:
query_result_impl(std::unique_ptr<query_result_reader> &&reader, record prototype);
query_result_impl(std::unique_ptr<query_result_reader> &&reader, std::vector<column_definition> prototype);
template<typename ValueType>
void on_primary_key(const char *id, ValueType &value, typename std::enable_if<std::is_integral<ValueType>::value && !std::is_same<bool, ValueType>::value>::type* = 0)
@ -110,11 +110,11 @@ public:
return true;
}
[[nodiscard]] const record& prototype() const;
[[nodiscard]] const std::vector<column_definition>& prototype() const;
protected:
size_t column_index_ = 0;
record prototype_;
std::vector<column_definition> prototype_;
std::unique_ptr<query_result_reader> reader_;
detail::pk_reader pk_reader_;
};

View File

@ -31,6 +31,28 @@ public:
return insert(new Type(std::forward<Args>(args)...));
}
template<typename Type, typename PrimaryKeyType>
entity<Type> find(const PrimaryKeyType &pk) {
auto c = pool_.acquire();
if (!c.valid()) {
throw std::logic_error("no database connection available");
}
// collect all columns
// - evaluate fetch::Eager flag for relations
// build pk where condition
// - check if type has pk
// - check type
// pk_condition_builder<Type> builder;
// auto cond = builder.build(pk);
// create query with relations as requested
//
// c->query(*schema_).select<Type>().from("xyz").where().fetch_all();
return {};
}
template<typename Type>
void drop_table();
void drop_table(const std::string &table_name);
@ -40,7 +62,7 @@ public:
[[nodiscard]] size_t execute(const std::string &sql) const;
statement prepare(query_context q) const;
record describe_table(const std::string &table_name) const;
std::vector<sql::column_definition> describe_table(const std::string &table_name) const;
bool table_exists(const std::string &table_name) const;
[[nodiscard]] const schema& tables() const;

View File

@ -35,6 +35,7 @@ set(SQL_SOURCES
sql/noop_connection.cpp
sql/query_part.cpp
sql/any_type_to_string_visitor.cpp
sql/field.cpp
)
set(SQL_HEADER
@ -86,6 +87,8 @@ set(SQL_HEADER
../include/matador/sql/query_part.hpp
../include/matador/sql/any_type_to_string_visitor.hpp
../include/matador/sql/query_helper.hpp
../include/matador/sql/field.hpp
../include/matador/sql/entity_query_builder.hpp
)
set(QUERY_SOURCES

View File

@ -2,7 +2,9 @@
#include "matador/sql/backend_provider.hpp"
#include "matador/sql/connection_impl.hpp"
#include "matador/sql/schema.hpp"
#include <algorithm>
#include <stdexcept>
#include <utility>
@ -68,7 +70,7 @@ const connection_info &connection::info() const
return connection_info_;
}
record connection::describe(const std::string &table_name) const
std::vector<sql::column_definition> connection::describe(const std::string &table_name) const
{
return std::move(connection_->describe(table_name));
}
@ -94,12 +96,21 @@ sql::query connection::query(const sql::schema &schema) const
return sql::query(*const_cast<connection*>(this), schema);
}
bool is_unknown(const std::vector<sql::column_definition> &columns) {
return std::all_of(std::begin(columns), std::end(columns), [](const auto &col) {
return col.type() == data_type_t::type_unknown;
});
}
query_result<record> connection::fetch(const query_context &q) const
{
if (q.prototype.empty() || q.prototype.unknown()) {
if (q.prototype.empty() || is_unknown(q.prototype)) {
const auto table_prototype = describe(q.table.name);
for (auto &col : q.prototype) {
if (const auto rit = table_prototype.find(col.name()); col.type() == data_type_t::type_unknown && rit != table_prototype.end()) {
const auto rit = std::find_if(std::begin(table_prototype), std::end(table_prototype), [&col](const auto &value) {
return value.name() == col.name();
});
if (col.type() == data_type_t::type_unknown && rit != table_prototype.end()) {
const_cast<column_definition&>(col).type(rit->type());
}
}

15
src/sql/field.cpp Normal file
View File

@ -0,0 +1,15 @@
#include "matador/sql/field.hpp"
namespace matador::sql {
const std::string &field::name() const
{
return name_;
}
std::ostream &operator<<(std::ostream &out, const field &col)
{
return out;
}
}

View File

@ -39,7 +39,7 @@ std::unique_ptr<statement_impl> noop_connection::prepare(query_context context)
return {};
}
record noop_connection::describe(const std::string &table)
std::vector<sql::column_definition> noop_connection::describe(const std::string &table)
{
return {};
}

View File

@ -141,18 +141,18 @@ query_builder &query_builder::select(const std::vector<column> &columns)
for (const auto &col: columns) {
result.append(dialect_.prepare_identifier(col));
query_.result_vars.emplace_back(col.name);
query_.prototype.append(column_definition{col.name});
query_.prototype.emplace_back(col.name);
}
} else {
auto it = columns.begin();
result.append(dialect_.prepare_identifier(*it));
query_.result_vars.emplace_back(it->name);
query_.prototype.append(column_definition{(*it++).name});
query_.prototype.emplace_back((*it++).name);
for (; it != columns.end(); ++it) {
result.append(", ");
result.append(dialect_.prepare_identifier(*it));
query_.result_vars.emplace_back(it->name);
query_.prototype.append(column_definition{(*it).name});
query_.prototype.emplace_back((*it).name);
}
}

View File

@ -33,18 +33,18 @@ void query_compiler::visit(query_select_part &select_part)
for (const auto &col: columns) {
result.append(dialect_.prepare_identifier(col));
query_.result_vars.emplace_back(col.name);
query_.prototype.append(column_definition{col.name});
query_.prototype.emplace_back(col.name);
}
} else {
auto it = columns.begin();
result.append(dialect_.prepare_identifier(*it));
query_.result_vars.emplace_back(it->name);
query_.prototype.append(column_definition{(*it++).name});
query_.prototype.emplace_back((*it++).name);
for (; it != columns.end(); ++it) {
result.append(", ");
result.append(dialect_.prepare_identifier(*it));
query_.result_vars.emplace_back(it->name);
query_.prototype.append(column_definition{(*it).name});
query_.prototype.emplace_back((*it).name);
}
}

View File

@ -3,7 +3,7 @@
namespace matador::sql::detail {
template<>
record *create_prototype<record>(const record &prototype)
record *create_prototype<record>(const std::vector<column_definition> &prototype)
{
return new record{prototype};
}

View File

@ -11,7 +11,7 @@ void detail::pk_reader::on_primary_key(const char *id, std::string &value, size_
data_type_traits<std::string>::read_value(reader_, id, column_index_++, value, size);
}
query_result_impl::query_result_impl(std::unique_ptr<query_result_reader> &&reader, record prototype)
query_result_impl::query_result_impl(std::unique_ptr<query_result_reader> &&reader, std::vector<column_definition> prototype)
: prototype_(std::move(prototype))
, reader_(std::move(reader))
, pk_reader_(*reader_)
@ -44,7 +44,7 @@ query_result_impl::on_attribute(const char *id, any_type &value, data_type_t typ
reader_->read_value(id, column_index_++, value, type, attr.size());
}
const record& query_result_impl::prototype() const
const std::vector<column_definition>& query_result_impl::prototype() const
{
return prototype_;
}

View File

@ -71,7 +71,7 @@ statement session::prepare(query_context q) const
return c->prepare(std::move(q));
}
record session::describe_table(const std::string &table_name) const
std::vector<sql::column_definition> session::describe_table(const std::string &table_name) const
{
auto c = pool_.acquire();
if (!c.valid()) {

View File

@ -35,7 +35,10 @@ add_executable(tests
models/optional.hpp
ConvertTest.cpp
DummyConnection.hpp
DummyConnection.cpp)
DummyConnection.cpp
EntityQueryBuilderTest.cpp
models/author.hpp
models/book.hpp)
target_link_libraries(tests PRIVATE
Catch2::Catch2WithMain

View File

@ -0,0 +1,11 @@
#include <catch2/catch_test_macros.hpp>
#include <matador/sql/connection.hpp>
using namespace matador::sql;
TEST_CASE("Create sql query for entity", "[query][entity][builder]") {
connection noop("noop://noop.db");
schema scm("noop");
}

View File

@ -4,7 +4,6 @@
#include <matador/sql/condition.hpp>
#include <matador/sql/connection.hpp>
#include <matador/sql/dialect_builder.hpp>
#include <matador/sql/query_builder.hpp>
#include <matador/sql/query.hpp>
using namespace matador::sql;

27
test/models/author.hpp Normal file
View File

@ -0,0 +1,27 @@
#ifndef QUERY_AUTHOR_HPP
#define QUERY_AUTHOR_HPP
#include "matador/utils/access.hpp"
#include <string>
namespace matador::test {
struct category
{
unsigned long id{};
std::string name;
template<class Operator>
void process(Operator &op)
{
namespace field = matador::utils::access;
using namespace matador::utils;
field::primary_key(op, "id", id);
field::attribute(op, "name", name, 255);
}
};
}
#endif //QUERY_AUTHOR_HPP

34
test/models/book.hpp Normal file
View File

@ -0,0 +1,34 @@
#ifndef QUERY_BOOK_HPP
#define QUERY_BOOK_HPP
#include "matador/sql/entity.hpp"
#include "matador/utils/access.hpp"
#include "matador/utils/foreign_attributes.hpp"
#include <string>
namespace matador::test {
struct author;
struct book
{
unsigned long id{};
matador::sql::entity<author> book_author;
std::string title;
unsigned short published_in{};
template<typename Operator>
void process(Operator &op)
{
namespace field = matador::utils::access;
field::primary_key(op, "id", id);
field::attribute(op, "title", title, 511);
field::has_one(op, "author_id", book_author, matador::utils::default_foreign_attributes);
field::attribute(op, "published_in", published_in);
}
};
}
#endif //QUERY_BOOK_HPP