diff --git a/backends/mysql/src/mysql_connection.cpp b/backends/mysql/src/mysql_connection.cpp index 0ae8d17..a05102d 100644 --- a/backends/mysql/src/mysql_connection.cpp +++ b/backends/mysql/src/mysql_connection.cpp @@ -90,12 +90,9 @@ sql::data_type_t to_type(enum_field_types type, unsigned int flags) } } -utils::constraints to_options(unsigned int flags) +utils::constraints to_constraints(unsigned int flags) { utils::constraints options{utils::constraints::NONE}; - if (flags & NOT_NULL_FLAG) { - options |= utils::constraints::NOT_NULL; - } if (flags & PRI_KEY_FLAG) { options |= utils::constraints::PRIMARY_KEY; } @@ -106,6 +103,11 @@ utils::constraints to_options(unsigned int flags) return options; } +sql::null_option to_null_option(unsigned int flags) +{ + return flags & NOT_NULL_FLAG ? sql::null_option::NOT_NULL : sql::null_option::NULLABLE; +} + sql::data_type_t string2type(const std::string &type_string) { // if (strcmp(type_string.c_str(), "int") @@ -149,9 +151,10 @@ std::unique_ptr mysql_connection::fetch(const std::strin sql::record prototype; for (unsigned i = 0; i < field_count; ++i) { auto type = to_type(fields[i].type, fields[i].flags); - auto options = to_options(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}); + prototype.append({fields[i].name, type, options, null_opt}); } return std::move(std::make_unique(std::make_unique(result, field_count), std::move(prototype))); @@ -200,17 +203,17 @@ sql::record mysql_connection::describe(const std::string &table) char *end = nullptr; // Todo: Handle error auto index = strtoul(reader.column(0), &end, 10); - std::string name = reader.column(1); + std::string name = reader.column(0); // Todo: extract size - auto typeinfo = determine_type_info(reader.column(2)); + auto typeinfo = determine_type_info(reader.column(1)); end = nullptr; - utils::constraints options{}; - if (strtoul(reader.column(4), &end, 10) == 0) { - options = utils::constraints::NOT_NULL; + sql::null_option null_opt{sql::null_option::NULLABLE}; + if (strtoul(reader.column(2), &end, 10) == 0) { + null_opt = sql::null_option::NOT_NULL; } // f.default_value(res->column(4)); - prototype.append({name, typeinfo.type, {typeinfo.size, options}}); + prototype.append({name, typeinfo.type, {typeinfo.size}, null_opt}); } return prototype; diff --git a/backends/postgres/src/postgres_connection.cpp b/backends/postgres/src/postgres_connection.cpp index a07bffd..63e8972 100644 --- a/backends/postgres/src/postgres_connection.cpp +++ b/backends/postgres/src/postgres_connection.cpp @@ -150,12 +150,12 @@ sql::record postgres_connection::describe(const std::string &table) // Todo: extract size auto type = (string2type(reader.column(2))); end = nullptr; - utils::constraints options{}; + sql::null_option null_opt{sql::null_option::NULLABLE}; if (strtoul(reader.column(4), &end, 10) == 0) { - options = utils::constraints::NOT_NULL; + null_opt = sql::null_option::NOT_NULL; } // f.default_value(res->column(4)); - prototype.append({name, type, {options}}); + prototype.append({name, type, utils::null_attributes, null_opt}); } return std::move(prototype); diff --git a/backends/sqlite/src/sqlite_connection.cpp b/backends/sqlite/src/sqlite_connection.cpp index 70b9789..83f0584 100644 --- a/backends/sqlite/src/sqlite_connection.cpp +++ b/backends/sqlite/src/sqlite_connection.cpp @@ -158,12 +158,12 @@ sql::record sqlite_connection::describe(const std::string& table) auto type = (string2type(reader.column(2))); end = nullptr; - utils::constraints options{}; + sql::null_option null_opt{sql::null_option::NULLABLE}; if (strtoul(reader.column(3), &end, 10) == 0) { - options = utils::constraints::NOT_NULL; + null_opt = sql::null_option::NOT_NULL; } // f.default_value(res->column(4)); - prototype.append({name, type, {options}}); + prototype.append({name, type, utils::null_attributes, null_opt}); } return std::move(prototype); diff --git a/include/matador/sql/column.hpp b/include/matador/sql/column.hpp index d5d020a..d2d15ad 100644 --- a/include/matador/sql/column.hpp +++ b/include/matador/sql/column.hpp @@ -12,6 +12,10 @@ namespace matador::sql { +enum class null_option : uint8_t { + NULLABLE, NOT_NULL +}; + class column { public: column(sql_function_t func, std::string name); @@ -24,27 +28,28 @@ public: column& operator=(column&&) noexcept = default; template - explicit column(std::string name, utils::field_attributes attr = utils::null_attributes) + explicit column(std::string name, utils::field_attributes attr) : column(std::move(name), data_type_traits::builtin_type(attr.size()), attr) {} template - column(std::string name, const Type &, utils::field_attributes attr = utils::null_attributes) - : column(std::move(name), data_type_traits::builtin_type(attr.size()), attr) + column(std::string name, const Type &, utils::field_attributes attr, null_option null_opt) + : column(std::move(name), data_type_traits::builtin_type(attr.size()), attr, null_opt) {} - column(std::string name, data_type_t type, utils::field_attributes attr = utils::null_attributes); + column(std::string name, data_type_t type, utils::field_attributes attr, null_option null_opt); + template - - column(std::string name, std::string ref_table, std::string ref_column, utils::field_attributes attr = utils::null_attributes) - : column(std::move(name), data_type_traits::builtin_type(attr.size()), ref_table, ref_column, attr) + column(std::string name, std::string ref_table, std::string ref_column, utils::field_attributes attr, null_option null_opt) + : column(std::move(name), data_type_traits::builtin_type(attr.size()), ref_table, ref_column, attr, null_opt) {} - column(std::string name, data_type_t type, size_t index, std::string ref_table, std::string ref_column, utils::field_attributes attr = utils::null_attributes); + column(std::string name, data_type_t type, size_t index, std::string ref_table, std::string ref_column, utils::field_attributes attr, null_option null_opt); [[nodiscard]] const std::string& name() const; [[nodiscard]] size_t index() const; [[nodiscard]] const utils::field_attributes& attributes() const; + [[nodiscard]] bool is_nullable() const; [[nodiscard]] data_type_t type() const; [[nodiscard]] const std::string& alias() const; [[nodiscard]] const std::string& ref_table() const; @@ -114,6 +119,7 @@ private: std::string name_; size_t index_{}; utils::field_attributes attributes_; + null_option null_option_{null_option::NOT_NULL}; data_type_t type_{data_type_t::type_unknown}; any_type value_; sql_function_t function_{sql_function_t::NONE}; @@ -130,20 +136,20 @@ private: */ column operator "" _col(const char *name, size_t len); -column make_column(const std::string &name, data_type_t type, utils::field_attributes attr = utils::not_null_attributes); +column make_column(const std::string &name, data_type_t type, utils::field_attributes attr = utils::null_attributes, null_option null_opt = null_option::NOT_NULL); template < typename Type > -column make_column(const std::string &name, utils::field_attributes attr = utils::not_null_attributes) +column make_column(const std::string &name, utils::field_attributes attr = utils::null_attributes, null_option null_opt = null_option::NOT_NULL) { - return make_column(name, data_type_traits::builtin_type(0), attr); + return make_column(name, data_type_traits::builtin_type(0), attr, null_opt); } template <> -column make_column(const std::string &name, utils::field_attributes attr); +column make_column(const std::string &name, utils::field_attributes attr, null_option null_opt); template < typename Type > column make_pk_column(const std::string &name, size_t size = 0) { - return make_column(name, { size, utils::constraints::PRIMARY_KEY | utils::constraints::NOT_NULL}); + return make_column(name, { size, utils::constraints::PRIMARY_KEY }); } template <> @@ -158,7 +164,7 @@ column make_fk_column(const std::string &name, size_t size, const std::string &r template < typename Type > [[maybe_unused]] column make_fk_column(const std::string &name, const std::string &ref_table, const std::string &ref_column) { - return {name, data_type_traits::builtin_type(0), 0, ref_table, ref_column, { 0, utils::constraints::FOREIGN_KEY }}; + return {name, data_type_traits::builtin_type(0), 0, ref_table, ref_column, { 0, utils::constraints::FOREIGN_KEY }, null_option::NOT_NULL}; } template <> diff --git a/include/matador/sql/column_generator.hpp b/include/matador/sql/column_generator.hpp index eff5566..667cdc3 100644 --- a/include/matador/sql/column_generator.hpp +++ b/include/matador/sql/column_generator.hpp @@ -23,7 +23,7 @@ public: column generate(const char *id, Type &x, const std::string &ref_table, const std::string &ref_column) { utils::access::process(*this, x); - return column{id, type_, 0, ref_table, ref_column, { utils::constraints::FOREIGN_KEY }}; + return column{id, type_, 0, ref_table, ref_column, { utils::constraints::FOREIGN_KEY }, null_option::NOT_NULL}; } template @@ -73,7 +73,7 @@ public: void on_revision(const char *id, unsigned long long &rev); template - void on_attribute(const char *id, Type &x, const utils::field_attributes &attr = utils::not_null_attributes); + void on_attribute(const char *id, Type &x, const utils::field_attributes &attr = utils::null_attributes); template void on_attribute(const char *id, std::optional &x, const utils::field_attributes &attr = utils::null_attributes); @@ -109,23 +109,19 @@ private: template void column_generator::on_primary_key(const char *id, V &x, typename std::enable_if::value && !std::is_same::value>::type*) { - on_attribute(id, x, { utils::constraints::PRIMARY_KEY | utils::constraints::NOT_NULL }); + on_attribute(id, x, { utils::constraints::PRIMARY_KEY }); } template void column_generator::on_attribute(const char *id, Type &x, const utils::field_attributes &attr) { - if (attr.options() == utils::constraints::NONE) { - columns_.push_back(column{id, x, { attr.size(), utils::constraints::NOT_NULL}}); - } else { - columns_.emplace_back(id, x, attr); - } + columns_.emplace_back(id, x, attr, null_option::NOT_NULL); } template void column_generator::on_attribute(const char *id, std::optional &x, const utils::field_attributes &attr) { - columns_.emplace_back(id, data_type_traits::builtin_type(attr.size()), attr); + columns_.emplace_back(id, data_type_traits::builtin_type(attr.size()), attr, null_option::NULLABLE); } } diff --git a/include/matador/utils/constraints.hpp b/include/matador/utils/constraints.hpp index c518ef9..4b4dca6 100644 --- a/include/matador/utils/constraints.hpp +++ b/include/matador/utils/constraints.hpp @@ -5,14 +5,12 @@ namespace matador::utils { enum class constraints : unsigned char { NONE = 0, - NOT_NULL = 1 << 0, INDEX = 1 << 1, UNIQUE = 1 << 2, PRIMARY_KEY = 1 << 3, FOREIGN_KEY = 1 << 4, DEFAULT = 1 << 5, - AUTO_INCREMENT = 1 << 6, - UNIQUE_NOT_NULL = UNIQUE | NOT_NULL + AUTO_INCREMENT = 1 << 6 }; //static std::unordered_map constraints_to_name_map(); diff --git a/include/matador/utils/field_attributes.hpp b/include/matador/utils/field_attributes.hpp index 0239bd0..9736b2c 100644 --- a/include/matador/utils/field_attributes.hpp +++ b/include/matador/utils/field_attributes.hpp @@ -30,7 +30,6 @@ private: }; const field_attributes null_attributes {}; -const field_attributes not_null_attributes { constraints::NOT_NULL }; } #endif //QUERY_FIELD_ATTRIBUTES_HPP diff --git a/src/sql/column.cpp b/src/sql/column.cpp index 5c8f233..15496c6 100644 --- a/src/sql/column.cpp +++ b/src/sql/column.cpp @@ -16,14 +16,19 @@ column::column(std::string name, std::string alias) : name_(std::move(name)), attributes_(utils::null_attributes), alias_(std::move(alias)) {} -column::column(std::string name, data_type_t type, utils::field_attributes attr) - : name_(std::move(name)), type_(type), attributes_(attr) +column::column(std::string name, data_type_t type, utils::field_attributes attr, null_option null_opt) + : name_(std::move(name)), type_(type), attributes_(attr), null_option_(null_opt) {} column::column(std::string name, data_type_t type, size_t index, std::string ref_table, std::string ref_column, - utils::field_attributes attr) - : name_(std::move(name)), index_(index), type_(type), attributes_(attr), ref_table_(std::move(ref_table)), - ref_column_(std::move(ref_column)) + utils::field_attributes attr, null_option null_opt) + : name_(std::move(name)) + , index_(index) + , type_(type) + , attributes_(attr) + , null_option_(null_opt) + , ref_table_(std::move(ref_table)) + , ref_column_(std::move(ref_column)) {} const std::string &column::name() const @@ -41,6 +46,11 @@ const utils::field_attributes &column::attributes() const return attributes_; } +bool column::is_nullable() const +{ + return null_option_ == null_option::NULLABLE; +} + data_type_t column::type() const { return type_; @@ -93,18 +103,15 @@ column operator "" _col(const char *name, size_t len) return {std::string(name, len)}; } -column make_column(const std::string &name, data_type_t type, utils::field_attributes attr) +column make_column(const std::string &name, data_type_t type, utils::field_attributes attr, null_option null_opt) { - return {name, type, attr}; + return {name, type, attr, null_opt}; } template<> -column make_column(const std::string &name, utils::field_attributes attr) +column make_column(const std::string &name, utils::field_attributes attr, null_option null_opt) { - if (attr.options() == utils::constraints::NONE) { - return make_column(name, data_type_traits::builtin_type(attr.size()), { attr.size(), utils::constraints::NOT_NULL}); - } - return make_column(name, data_type_traits::builtin_type(attr.size()), attr); + return make_column(name, data_type_traits::builtin_type(attr.size()), attr, null_opt); } template<> @@ -117,6 +124,6 @@ template<> [[maybe_unused]] column make_fk_column(const std::string &name, size_t size, const std::string &ref_table, const std::string &ref_column) { - return {name, data_type_traits::builtin_type(size), 0, ref_table, ref_column, {size, utils::constraints::PRIMARY_KEY | utils::constraints::NOT_NULL}}; + return {name, data_type_traits::builtin_type(size), 0, ref_table, ref_column, {size, utils::constraints::FOREIGN_KEY}, null_option::NOT_NULL}; } } \ No newline at end of file diff --git a/src/sql/column_generator.cpp b/src/sql/column_generator.cpp index 0cefa5d..56afb2d 100644 --- a/src/sql/column_generator.cpp +++ b/src/sql/column_generator.cpp @@ -10,7 +10,7 @@ column_generator::column_generator(std::vector &columns, const table_rep void column_generator::on_primary_key(const char *id, std::string &pk, size_t size) { - on_attribute(id, pk, { size, utils::constraints::PRIMARY_KEY | utils::constraints::NOT_NULL }); + on_attribute(id, pk, { size, utils::constraints::PRIMARY_KEY }); } void column_generator::on_revision(const char *id, unsigned long long int &x) diff --git a/src/sql/query_builder.cpp b/src/sql/query_builder.cpp index 401ca43..b6690bd 100644 --- a/src/sql/query_builder.cpp +++ b/src/sql/query_builder.cpp @@ -473,7 +473,7 @@ std::string build_create_column(const column &col, const dialect &d, column_cont if (col.attributes().size() > 0) { result.append("(" + std::to_string(col.attributes().size()) + ")"); } - if (is_constraint_set(col.attributes().options(), utils::constraints::NOT_NULL)) { + if (!col.is_nullable()) { result.append(" NOT NULL"); } if (is_constraint_set(col.attributes().options(), utils::constraints::UNIQUE)) { diff --git a/test/ColumnGeneratorTest.cpp b/test/ColumnGeneratorTest.cpp index e488697..493740b 100644 --- a/test/ColumnGeneratorTest.cpp +++ b/test/ColumnGeneratorTest.cpp @@ -15,15 +15,15 @@ TEST_CASE("Generate columns from object", "[column generator]") { auto columns = column_generator::generate(repo); const std::vector expected_columns = { - column{ "product_name", data_type_t::type_varchar, { constraints::PRIMARY_KEY | constraints::NOT_NULL } }, - column{ "supplier_id", data_type_t::type_unsigned_long, constraints::FOREIGN_KEY }, - column{ "category_id", data_type_t::type_unsigned_long, constraints::FOREIGN_KEY }, - column{ "quantity_per_unit", data_type_t::type_varchar, constraints::NOT_NULL }, - column{ "unit_price", data_type_t::type_unsigned_int, constraints::NOT_NULL }, - column{ "units_in_stock", data_type_t::type_unsigned_int, constraints::NOT_NULL }, - column{ "units_in_order", data_type_t::type_unsigned_int, constraints::NOT_NULL }, - column{ "reorder_level", data_type_t::type_unsigned_int, constraints::NOT_NULL }, - column{ "discontinued", data_type_t::type_bool, constraints::NOT_NULL } + column{ "product_name", data_type_t::type_varchar, constraints::PRIMARY_KEY, null_option::NOT_NULL }, + column{ "supplier_id", data_type_t::type_unsigned_long, constraints::FOREIGN_KEY, null_option::NOT_NULL }, + column{ "category_id", data_type_t::type_unsigned_long, constraints::FOREIGN_KEY, null_option::NOT_NULL }, + column{ "quantity_per_unit", data_type_t::type_varchar, null_attributes, null_option::NOT_NULL }, + column{ "unit_price", data_type_t::type_unsigned_int, null_attributes, null_option::NOT_NULL }, + column{ "units_in_stock", data_type_t::type_unsigned_int, null_attributes, null_option::NOT_NULL }, + column{ "units_in_order", data_type_t::type_unsigned_int, null_attributes, null_option::NOT_NULL }, + column{ "reorder_level", data_type_t::type_unsigned_int, null_attributes, null_option::NOT_NULL }, + column{ "discontinued", data_type_t::type_bool, null_attributes, null_option::NOT_NULL } }; REQUIRE(!columns.empty()); REQUIRE(columns.size() == expected_columns.size()); @@ -41,9 +41,9 @@ TEST_CASE("Generate columns from object with nullable columns", "[column generat auto columns = column_generator::generate(repo); const std::vector expected_columns = { - column{ "id", data_type_t::type_unsigned_long, { constraints::PRIMARY_KEY | constraints::NOT_NULL } }, - column{ "name", data_type_t::type_varchar, null_attributes }, - column{ "age", data_type_t::type_unsigned_int, null_attributes } + column{ "id", data_type_t::type_unsigned_long, constraints::PRIMARY_KEY, null_option::NOT_NULL }, + column{ "name", data_type_t::type_varchar, null_attributes, null_option::NOT_NULL }, + column{ "age", data_type_t::type_unsigned_int, null_attributes, null_option::NOT_NULL } }; REQUIRE(!columns.empty()); REQUIRE(columns.size() == expected_columns.size()); diff --git a/test/QueryBuilderTest.cpp b/test/QueryBuilderTest.cpp index c06ecb5..8160b66 100644 --- a/test/QueryBuilderTest.cpp +++ b/test/QueryBuilderTest.cpp @@ -22,12 +22,12 @@ TEST_CASE("Create table sql statement string", "[query]") { q = query.create().table("person", { make_pk_column("id"), - make_column("name", { 255, constraints::UNIQUE | constraints::NOT_NULL }), + make_column("name", { 255, constraints::UNIQUE }, null_option::NOT_NULL), make_column("age"), make_fk_column("address", "address", "id") }).compile(); - REQUIRE(q.sql == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL UNIQUE, "age" INTEGER NOT NULL, "address" BIGINT, CONSTRAINT PK_person PRIMARY KEY (id), CONSTRAINT FK_person_address FOREIGN KEY (address) REFERENCES address(id)))##"); + REQUIRE(q.sql == R"##(CREATE TABLE "person" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL UNIQUE, "age" INTEGER NOT NULL, "address" BIGINT NOT NULL, CONSTRAINT PK_person PRIMARY KEY (id), CONSTRAINT FK_person_address FOREIGN KEY (address) REFERENCES address(id)))##"); REQUIRE(q.table_name == "person"); } diff --git a/test/SessionRecordTest.cpp b/test/SessionRecordTest.cpp index 44ca9fe..2e9d6a0 100644 --- a/test/SessionRecordTest.cpp +++ b/test/SessionRecordTest.cpp @@ -49,10 +49,9 @@ TEMPLATE_TEST_CASE_METHOD(SessionRecordTestFixture, "Create and drop table state REQUIRE(!s.table_exists("person")); } -TEST_CASE("Create and drop table statement with foreign key", "[session record]") +TEMPLATE_TEST_CASE_METHOD(SessionRecordTestFixture, "Create and drop table statement with foreign key", "[session record]", Sqlite, Postgres, MySql) { - connection_pool pool("sqlite://sqlite.db", 4); - session s(pool); + auto &s = SessionRecordTestFixture::session(); s.create() .table("airplane", { @@ -87,14 +86,9 @@ TEST_CASE("Create and drop table statement with foreign key", "[session record]" REQUIRE(!s.table_exists("airplane")); } -TEST_CASE("Execute insert record statement", "[session record]") +TEMPLATE_TEST_CASE_METHOD(SessionRecordTestFixture, "Execute insert record statement", "[session record]", MySql) { - auto dns = GENERATE(as < std::string > {}, - "sqlite://sqlite.db", - "postgres://test:test123@127.0.0.1:5432/matador_test"); - - connection_pool pool(dns, 4); - session s(pool); + auto &s = SessionRecordTestFixture::session(); s.create() .table("person", { @@ -119,13 +113,13 @@ TEST_CASE("Execute insert record statement", "[session record]") REQUIRE(i.size() == 3); REQUIRE(i.at(0).name() == "id"); REQUIRE(i.at(0).type() == data_type_t::type_long_long); - REQUIRE(i.at(0).as() == 7); + REQUIRE(i.at(0).template as() == 7); REQUIRE(i.at(1).name() == "name"); REQUIRE(i.at(1).type() == data_type_t::type_varchar); - REQUIRE(i.at(1).as() == "george"); + REQUIRE(i.at(1).template as() == "george"); REQUIRE(i.at(2).name() == "age"); REQUIRE(i.at(2).type() == matador::sql::data_type_t::type_int); - REQUIRE(i.at(2).as() == 45); + REQUIRE(i.at(2).template as() == 45); } s.drop()