diff options
Diffstat (limited to 'odb/odb/relational/mysql')
-rw-r--r-- | odb/odb/relational/mysql/common.cxx | 400 | ||||
-rw-r--r-- | odb/odb/relational/mysql/common.hxx | 171 | ||||
-rw-r--r-- | odb/odb/relational/mysql/context.cxx | 868 | ||||
-rw-r--r-- | odb/odb/relational/mysql/context.hxx | 194 | ||||
-rw-r--r-- | odb/odb/relational/mysql/header.cxx | 136 | ||||
-rw-r--r-- | odb/odb/relational/mysql/inline.cxx | 42 | ||||
-rw-r--r-- | odb/odb/relational/mysql/model.cxx | 161 | ||||
-rw-r--r-- | odb/odb/relational/mysql/schema.cxx | 489 | ||||
-rw-r--r-- | odb/odb/relational/mysql/source.cxx | 724 |
9 files changed, 3185 insertions, 0 deletions
diff --git a/odb/odb/relational/mysql/common.cxx b/odb/odb/relational/mysql/common.cxx new file mode 100644 index 0000000..d049443 --- /dev/null +++ b/odb/odb/relational/mysql/common.cxx @@ -0,0 +1,400 @@ +// file : odb/relational/mysql/common.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> + +#include <odb/relational/mysql/common.hxx> + +using namespace std; + +namespace relational +{ + namespace mysql + { + // + // member_base + // + + sql_type const& member_base:: + member_sql_type (semantics::data_member& m) + { + return parse_sql_type (column_type (m, key_prefix_), m); + } + + void member_base:: + traverse_simple (member_info& mi) + { + switch (mi.st->type) + { + // Integral types. + // + case sql_type::TINYINT: + case sql_type::SMALLINT: + case sql_type::MEDIUMINT: + case sql_type::INT: + case sql_type::BIGINT: + { + traverse_integer (mi); + break; + } + + // Float types. + // + case sql_type::FLOAT: + case sql_type::DOUBLE: + { + traverse_float (mi); + break; + } + case sql_type::DECIMAL: + { + traverse_decimal (mi); + break; + } + + // Data-time types. + // + case sql_type::DATE: + case sql_type::TIME: + case sql_type::DATETIME: + case sql_type::TIMESTAMP: + case sql_type::YEAR: + { + traverse_date_time (mi); + break; + } + + // String and binary types. + // + case sql_type::CHAR: + case sql_type::VARCHAR: + case sql_type::TINYTEXT: + case sql_type::TEXT: + case sql_type::MEDIUMTEXT: + case sql_type::LONGTEXT: + { + // For string types the limit is in characters rather + // than in bytes. The fixed-length pre-allocated buffer + // optimization can only be used for 1-byte encodings. + // To support this we will need the character encoding + // in sql_type. + // + traverse_long_string (mi); + break; + } + case sql_type::BINARY: + case sql_type::TINYBLOB: + { + // BINARY's range is always 255 or less from MySQL 5.0.3. + // TINYBLOB can only store up to 255 bytes. + // + traverse_short_string (mi); + break; + } + case sql_type::VARBINARY: + case sql_type::BLOB: + case sql_type::MEDIUMBLOB: + case sql_type::LONGBLOB: + { + if (mi.st->range && mi.st->range_value <= 255) + traverse_short_string (mi); + else + traverse_long_string (mi); + + break; + } + + // Other types. + // + case sql_type::BIT: + { + traverse_bit (mi); + break; + } + case sql_type::ENUM: + { + traverse_enum (mi); + break; + } + case sql_type::SET: + { + traverse_set (mi); + break; + } + case sql_type::invalid: + { + assert (false); + break; + } + } + } + + // + // member_image_type + // + + static const char* integer_types[] = + { + "char", + "short", + "int", + "int", + "long long" + }; + + static const char* float_types[] = + { + "float", + "double" + }; + + member_image_type:: + member_image_type (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + member_image_type:: + member_image_type () + : relational::member_base (0, 0, string (), string ()) {} + + member_image_type:: + member_image_type (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : relational::member_base (type, ct, fq_type, key_prefix) {} + + string member_image_type:: + image_type (semantics::data_member& m) + { + type_.clear (); + member_base::traverse (m, true); + return type_; + } + + void member_image_type:: + traverse_composite (member_info& mi) + { + type_ = "composite_value_traits< " + mi.fq_type () + + ", id_mysql >::image_type"; + } + + void member_image_type:: + traverse_integer (member_info& mi) + { + if (mi.st->unsign) + type_ = "unsigned "; + else if (mi.st->type == sql_type::TINYINT) + type_ = "signed "; + + type_ += integer_types[mi.st->type - sql_type::TINYINT]; + } + + void member_image_type:: + traverse_float (member_info& mi) + { + type_ = float_types[mi.st->type - sql_type::FLOAT]; + } + + void member_image_type:: + traverse_decimal (member_info&) + { + type_ = "details::buffer"; + } + + void member_image_type:: + traverse_date_time (member_info& mi) + { + if (mi.st->type == sql_type::YEAR) + type_ = "short"; + else + type_ = "MYSQL_TIME"; + } + + void member_image_type:: + traverse_string (member_info&) + { + type_ = "details::buffer"; + } + + void member_image_type:: + traverse_bit (member_info&) + { + type_ = "unsigned char*"; + } + + void member_image_type:: + traverse_enum (member_info& mi) + { + // Represented as either integer or string. + // + type_ = "mysql::value_traits< " + mi.fq_type () + + ", mysql::id_enum >::image_type"; + } + + void member_image_type:: + traverse_set (member_info&) + { + // Represented as string. + // + type_ = "details::buffer"; + } + + entry<member_image_type> member_image_type_; + + // + // member_database_type + // + + static const char* integer_database_id[] = + { + "id_tiny", + "id_utiny", + "id_short", + "id_ushort", + "id_long", // INT24 + "id_ulong", // INT24 UNSIGNED + "id_long", + "id_ulong", + "id_longlong", + "id_ulonglong" + }; + + static const char* float_database_id[] = + { + "id_float", + "id_double" + }; + + static const char* date_time_database_id[] = + { + "id_date", + "id_time", + "id_datetime", + "id_timestamp", + "id_year" + }; + + static const char* char_bin_database_id[] = + { + "id_string", // CHAR + "id_blob", // BINARY, + "id_string", // VARCHAR + "id_blob", // VARBINARY + "id_string", // TINYTEXT + "id_blob", // TINYBLOB + "id_string", // TEXT + "id_blob", // BLOB + "id_string", // MEDIUMTEXT + "id_blob", // MEDIUMBLOB + "id_string", // LONGTEXT + "id_blob" // LONGBLOB + }; + + member_database_type_id:: + member_database_type_id (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + member_database_type_id:: + member_database_type_id () + : member_base::base (0, 0, string (), string ()), // virtual base + base (0, 0, string (), string ()) {} + + member_database_type_id:: + member_database_type_id (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base::base (type, ct, fq_type, key_prefix), // virtual base + base (type, ct, fq_type, key_prefix) {} + + string member_database_type_id:: + database_type_id (type& m) + { + type_id_.clear (); + member_base::traverse (m, true); + return type_id_; + } + + void member_database_type_id:: + traverse_composite (member_info&) + { + assert (false); + } + + void member_database_type_id:: + traverse_integer (member_info& mi) + { + size_t i ( + (mi.st->type - sql_type::TINYINT) * 2 + (mi.st->unsign ? 1 : 0)); + type_id_ = string ("mysql::") + integer_database_id[i]; + } + + void member_database_type_id:: + traverse_float (member_info& mi) + { + type_id_ = string ("mysql::") + + float_database_id[mi.st->type - sql_type::FLOAT]; + } + + void member_database_type_id:: + traverse_decimal (member_info&) + { + type_id_ = "mysql::id_decimal"; + } + + void member_database_type_id:: + traverse_date_time (member_info& mi) + { + type_id_ = string ("mysql::") + + date_time_database_id[mi.st->type - sql_type::DATE]; + } + + void member_database_type_id:: + traverse_string (member_info& mi) + { + type_id_ = string ("mysql::") + + char_bin_database_id[mi.st->type - sql_type::CHAR]; + } + + void member_database_type_id:: + traverse_bit (member_info&) + { + type_id_ = "mysql::id_bit"; + } + + void member_database_type_id:: + traverse_enum (member_info&) + { + type_id_ = "mysql::id_enum"; + } + + void member_database_type_id:: + traverse_set (member_info&) + { + type_id_ = "mysql::id_set"; + } + + entry<member_database_type_id> member_database_type_id_; + + // + // query_columns + // + + struct query_columns: relational::query_columns, context + { + query_columns (base const& x): base_impl (x) {} + + virtual string + database_type_id (semantics::data_member& m) + { + return member_database_type_id_.database_type_id (m); + } + + private: + member_database_type_id member_database_type_id_; + }; + entry<query_columns> query_columns_; + } +} diff --git a/odb/odb/relational/mysql/common.hxx b/odb/odb/relational/mysql/common.hxx new file mode 100644 index 0000000..b43dc0d --- /dev/null +++ b/odb/odb/relational/mysql/common.hxx @@ -0,0 +1,171 @@ +// file : odb/relational/mysql/common.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_MYSQL_COMMON_HXX +#define ODB_RELATIONAL_MYSQL_COMMON_HXX + +#include <odb/relational/common.hxx> +#include <odb/relational/mysql/context.hxx> + +namespace relational +{ + namespace mysql + { + struct member_base: virtual relational::member_base_impl<sql_type>, context + { + member_base (base const& x): base (x), base_impl (x) {} + + // This c-tor is for the direct use inside the mysql namespace. + // If you do use this c-tor, you should also explicitly call + // relational::member_base (aka base). + // + member_base () {} + + virtual sql_type const& + member_sql_type (semantics::data_member&); + + virtual void + traverse_simple (member_info&); + + virtual void + traverse_integer (member_info&) + { + } + + virtual void + traverse_float (member_info&) + { + } + + virtual void + traverse_decimal (member_info&) + { + } + + virtual void + traverse_date_time (member_info&) + { + } + + virtual void + traverse_string (member_info&) + { + } + + virtual void + traverse_short_string (member_info& mi) + { + traverse_string (mi); + } + + virtual void + traverse_long_string (member_info& mi) + { + traverse_string (mi); + } + + virtual void + traverse_bit (member_info&) + { + } + + virtual void + traverse_enum (member_info&) + { + } + + virtual void + traverse_set (member_info&) + { + } + }; + + struct member_image_type: relational::member_image_type, + member_base + { + member_image_type (base const&); + member_image_type (); + member_image_type (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + virtual string + image_type (semantics::data_member&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_float (member_info&); + + virtual void + traverse_decimal (member_info&); + + virtual void + traverse_date_time (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_bit (member_info&); + + virtual void + traverse_enum (member_info&); + + virtual void + traverse_set (member_info&); + + private: + string type_; + }; + + struct member_database_type_id: relational::member_database_type_id, + member_base + { + member_database_type_id (base const&); + member_database_type_id (); + member_database_type_id (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + + virtual string + database_type_id (type&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_float (member_info&); + + virtual void + traverse_decimal (member_info&); + + virtual void + traverse_date_time (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_bit (member_info&); + + virtual void + traverse_enum (member_info&); + + virtual void + traverse_set (member_info&); + + private: + string type_id_; + }; + } +} +#endif // ODB_RELATIONAL_MYSQL_COMMON_HXX diff --git a/odb/odb/relational/mysql/context.cxx b/odb/odb/relational/mysql/context.cxx new file mode 100644 index 0000000..8b3d983 --- /dev/null +++ b/odb/odb/relational/mysql/context.cxx @@ -0,0 +1,868 @@ +// file : odb/relational/mysql/context.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> +#include <sstream> + +#include <odb/sql-token.hxx> +#include <odb/sql-lexer.hxx> + +#include <odb/relational/mysql/context.hxx> +#include <odb/relational/mysql/common.hxx> + +using namespace std; + +namespace relational +{ + namespace mysql + { + namespace + { + struct type_map_entry + { + char const* const cxx_type; + char const* const db_type; + char const* const db_id_type; + bool const null; + }; + + type_map_entry type_map[] = + { + {"bool", "TINYINT(1)", 0, false}, + + {"char", "CHAR(1)", 0, false}, + {"signed char", "TINYINT", 0, false}, + {"unsigned char", "TINYINT UNSIGNED", 0, false}, + + {"short int", "SMALLINT", 0, false}, + {"short unsigned int", "SMALLINT UNSIGNED", 0, false}, + + {"int", "INT", 0, false}, + {"unsigned int", "INT UNSIGNED", 0, false}, + + {"long int", "BIGINT", 0, false}, + {"long unsigned int", "BIGINT UNSIGNED", 0, false}, + + {"long long int", "BIGINT", 0, false}, + {"long long unsigned int", "BIGINT UNSIGNED", 0, false}, + + {"float", "FLOAT", 0, false}, + {"double", "DOUBLE", 0, false}, + + {"::std::string", "TEXT", "VARCHAR(128)", false}, + + {"::size_t", "BIGINT UNSIGNED", 0, false}, + {"::std::size_t", "BIGINT UNSIGNED", 0, false} + }; + } + + context* context::current_; + + context:: + ~context () + { + if (current_ == this) + current_ = 0; + } + + context:: + context (ostream& os, + semantics::unit& u, + options_type const& ops, + features_type& f, + sema_rel::model* m) + : root_context (os, u, ops, f, data_ptr (new (shared) data (os))), + base_context (static_cast<data*> (root_context::data_.get ()), m), + data_ (static_cast<data*> (base_context::data_)) + { + assert (current_ == 0); + current_ = this; + + generate_grow = true; + need_alias_as = true; + insert_send_auto_id = true; + delay_freeing_statement_result = false; + need_image_clone = false; + generate_bulk = false; + global_index = false; + global_fkey = true; + data_->bind_vector_ = "MYSQL_BIND*"; + data_->truncated_vector_ = "my_bool*"; + + // Populate the C++ type to DB type map. + // + for (size_t i (0); i < sizeof (type_map) / sizeof (type_map_entry); ++i) + { + type_map_entry const& e (type_map[i]); + + type_map_type::value_type v ( + e.cxx_type, + db_type_type ( + e.db_type, e.db_id_type ? e.db_id_type : e.db_type, e.null)); + + data_->type_map_.insert (v); + } + } + + context:: + context () + : data_ (current ().data_) + { + } + + string const& context:: + convert_expr (string const& sqlt, semantics::data_member& m, bool to) + { + sql_type const& t (parse_sql_type (sqlt, m)); + return to ? t.to : t.from; + } + + string context:: + quote_id_impl (qname const& id) const + { + string r; + + bool f (true); + for (qname::iterator i (id.begin ()); i < id.end (); ++i) + { + if (i->empty ()) + continue; + + // Warn if the name is greater than the 64 limit. + // + if (i->size () > 64) + { + cerr << "warning: SQL name '" << *i << "' is longer than " + << "the MySQL name limit of 64 characters and will " + << "be truncated" << endl; + + cerr << "info: consider shortening it using #pragma db " + << "table/column/index or --*-regex options" << endl; + } + + if (f) + f = false; + else + r += '.'; + + r += '`'; + r.append (*i, 0, 64); // Max identifier length is 64. + r += '`'; + } + + return r; + } + + namespace + { + struct has_grow: traversal::class_ + { + has_grow (bool& r, user_section* s) + : r_ (r), section_ (s) + { + *this >> inherits_ >> *this; + } + + virtual void + traverse (type& c) + { + bool view (context::view (c)); + + // Ignore transient bases. + // + if (!(context::object (c) || view || context::composite (c))) + return; + + if (section_ == 0 && c.count ("mysql-grow")) + r_ = c.get<bool> ("mysql-grow"); + else + { + // r_ should be false. + // + if (!view) + inherits (c); + + if (!r_) + names (c); + + if (section_ == 0) + c.set ("mysql-grow", r_); + } + } + + private: + bool& r_; + user_section* section_; + traversal::inherits inherits_; + }; + + struct has_grow_member: member_base + { + has_grow_member (bool& r, user_section* section = 0) + : relational::member_base (0, 0, string (), string (), section), + r_ (r) {} + + has_grow_member (bool& r, + user_section* section, + semantics::type* t, + const custom_cxx_type* ct, + string const& key_prefix = string ()) + : relational::member_base (t, ct, string (), key_prefix, section), + r_ (r) {} + + virtual bool + pre (member_info& mi) + { + // If we have a key prefix (container), then it can't be in a + // section (while mi.m can). The same for top-level -- if we got + // called, then we shouldn't ignore it. + // + return !key_prefix_.empty () || top_level_ || + (section_ == 0 && !separate_load (mi.m)) || + (section_ != 0 && *section_ == section (mi.m)); + } + + virtual void + traverse_composite (member_info& mi) + { + // By calling grow() instead of recursing, we reset any overrides. + // We also don't pass section since they don't apply inside + // composites. + // + r_ = r_ || context::grow (dynamic_cast<semantics::class_&> (mi.t)); + } + + virtual void + traverse_decimal (member_info&) + { + r_ = true; + } + + virtual void + traverse_long_string (member_info&) + { + r_ = true; + } + + virtual void + traverse_short_string (member_info&) + { + r_ = true; // @@ Short string optimization disabled. + } + + virtual void + traverse_enum (member_info&) + { + r_ = true; + } + + virtual void + traverse_set (member_info&) + { + r_ = true; + } + + private: + bool& r_; + }; + } + + bool context:: + grow_impl (semantics::class_& c, user_section* section) + { + if (section == 0 && c.count ("mysql-grow")) + return c.get<bool> ("mysql-grow"); + + bool r (false); + has_grow ct (r, section); + has_grow_member mt (r, section); + traversal::names names; + ct >> names >> mt; + ct.traverse (c); + return r; + } + + bool context:: + grow_impl (semantics::data_member& m) + { + bool r (false); + has_grow_member mt (r); + mt.traverse (m, true); + return r; + } + + bool context:: + grow_impl (semantics::data_member& m, + semantics::type& t, + const custom_cxx_type* ct, + string const& kp) + { + bool r (false); + has_grow_member mt (r, 0, &t, ct, kp); + mt.traverse (m, true); + return r; + } + + string context:: + database_type_impl (semantics::type& t, + semantics::names* hint, + bool id, + bool* null) + { + using semantics::enum_; + using semantics::enumerator; + using semantics::array; + + string r; + + // Enum mapping. + // + if (enum_* e = dynamic_cast<enum_*> (&t)) + { + // We can only map to ENUM if the C++ enumeration is contiguous + // and starts with 0. + // + enum_::enumerates_iterator i (e->enumerates_begin ()), + end (e->enumerates_end ()); + + if (i != end) + { + r += "ENUM("; + + for (unsigned long long j (0); i != end; ++i, ++j) + { + enumerator const& er (i->enumerator ()); + + if (er.value () != j) + break; + + if (j != 0) + r += ", "; + + r += quote_string (er.name ()); + } + + if (i == end) + r += ")"; + else + r.clear (); + } + + if (!r.empty ()) + return r; + } + + r = base_context::database_type_impl (t, hint, id, null); + + if (!r.empty ()) + return r; + + // char[N] mapping. + // + else if (array* a = dynamic_cast<array*> (&t)) + { + semantics::type& bt (a->base_type ()); + if (bt.is_a<semantics::fund_char> ()) + { + unsigned long long n (a->size ()); + + if (n == 0) + return r; + else if (n == 1) + r = "CHAR("; + else + { + r = "VARCHAR("; + n--; + } + + ostringstream ostr; + ostr << n; + r += ostr.str (); + r += ')'; + } + } + + return r; + } + + // + // SQL type parsing. + // + + sql_type const& context:: + parse_sql_type (string const& t, semantics::data_member& m, bool custom) + { + // If this proves to be too expensive, we can maintain a cache of + // parsed types across contexts. + // + data::sql_type_cache::iterator i (data_->sql_type_cache_.find (t)); + + if (i != data_->sql_type_cache_.end () + && (custom ? i->second.custom_cached : i->second.straight_cached)) + { + return (custom ? i->second.custom : i->second.straight); + } + else + { + try + { + sql_type st ( + parse_sql_type ( + t, + custom ? &unit.get<custom_db_types> ("custom-db-types") : 0)); + + if (custom) + return data_->sql_type_cache_[t].cache_custom (st); + else + return data_->sql_type_cache_[t].cache_straight (st); + } + catch (invalid_sql_type const& e) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: " << e.message () << endl; + + throw operation_failed (); + } + } + } + + inline sql_type + error (bool fail, string const& m) + { + if (!fail) + return sql_type (); + else + throw context::invalid_sql_type (m); + } + + sql_type context:: + parse_sql_type (string sqlt, custom_db_types const* ct) + { + try + { + sql_type r; + + // First run the type through the custom mapping, if requested. + // + if (ct != 0) + { + for (custom_db_types::const_iterator i (ct->begin ()); + i != ct->end (); ++i) + { + custom_db_type const& t (*i); + + if (t.type.match (sqlt)) + { + r.to = t.type.replace (sqlt, t.to); + r.from = t.type.replace (sqlt, t.from); + sqlt = t.type.replace (sqlt, t.as); + break; + } + } + } + + sql_lexer l (sqlt); + + // While most type names use single identifier, there are + // a couple of exceptions to this rule: + // + // NATIONAL CHAR|VARCHAR + // CHAR BYTE (BINARY) + // CHARACTER VARYING (VARCHAR) + // LONG VARBINARY (MEDIUMBLOB) + // LONG VARCHAR (MEDIUMTEXT) + // + // + enum state + { + parse_prefix, + parse_name, + parse_range, + parse_sign, + parse_done + }; + + state s (parse_prefix); + string prefix; + bool flt (false); + + for (sql_token t (l.next ()); + s != parse_done && t.type () != sql_token::t_eos; + t = l.next ()) + { + sql_token::token_type tt (t.type ()); + + switch (s) + { + case parse_prefix: + { + if (tt == sql_token::t_identifier) + { + string const& id (context::upcase (t.identifier ())); + + if (id == "NATIONAL" || + id == "CHAR" || + id == "CHARACTER" || + id == "LONG") + { + prefix = id; + s = parse_name; + continue; + } + } + + s = parse_name; + } + // Fall through. + case parse_name: + { + if (tt == sql_token::t_identifier) + { + bool match (true); + string const& id (context::upcase (t.identifier ())); + + // Numeric types. + // + if (id == "BIT") + { + r.type = sql_type::BIT; + } + else if (id == "TINYINT" || id == "INT1") + { + r.type = sql_type::TINYINT; + } + else if (id == "BOOL" || id == "BOOLEAN") + { + r.type = sql_type::TINYINT; + r.range = true; + r.range_value = 1; + } + else if (id == "SMALLINT" || id == "INT2") + { + r.type = sql_type::SMALLINT; + } + else if (id == "MEDIUMINT" || + id == "INT3" || + id == "MIDDLEINT") + { + r.type = sql_type::MEDIUMINT; + } + else if (id == "INT" || id == "INTEGER" || id == "INT4") + { + r.type = sql_type::INT; + } + else if (id == "BIGINT" || id == "INT8") + { + r.type = sql_type::BIGINT; + } + else if (id == "SERIAL") + { + r.type = sql_type::BIGINT; + r.unsign = true; + } + else if (id == "FLOAT") + { + // Assign a type only once we know the precision of the + // float; it can be either 4 or 8 byte. + // + flt = true; + } + else if (id == "FLOAT4") + { + r.type = sql_type::FLOAT; + } + else if (id == "DOUBLE" || id == "FLOAT8") + { + r.type = sql_type::DOUBLE; + } + else if (id == "DECIMAL" || + id == "DEC" || + id == "NUMERIC" || + id == "FIXED") + { + r.type = sql_type::DECIMAL; + } + // + // Date-time types. + // + else if (id == "DATE") + { + r.type = sql_type::DATE; + } + else if (id == "TIME") + { + r.type = sql_type::TIME; + } + else if (id == "DATETIME") + { + r.type = sql_type::DATETIME; + } + else if (id == "TIMESTAMP") + { + r.type = sql_type::TIMESTAMP; + } + else if (id == "YEAR") + { + r.type = sql_type::YEAR; + } + // + // String and binary types. + // + else if (id == "NCHAR") + { + r.type = sql_type::CHAR; + } + else if (id == "VARCHAR") + { + r.type = prefix == "LONG" + ? sql_type::MEDIUMTEXT + : sql_type::VARCHAR; + } + else if (id == "NVARCHAR") + { + r.type = sql_type::VARCHAR; + } + else if (id == "VARYING" && prefix == "CHARACTER") + { + r.type = sql_type::VARCHAR; + } + else if (id == "BINARY") + { + r.type = sql_type::BINARY; + } + else if (id == "BYTE" && prefix == "CHAR") + { + r.type = sql_type::BINARY; + } + else if (id == "VARBINARY") + { + r.type = prefix == "LONG" + ? sql_type::MEDIUMBLOB + : sql_type::VARBINARY; + } + else if (id == "TINYBLOB") + { + r.type = sql_type::TINYBLOB; + } + else if (id == "TINYTEXT") + { + r.type = sql_type::TINYTEXT; + } + else if (id == "BLOB") + { + r.type = sql_type::BLOB; + } + else if (id == "TEXT") + { + r.type = sql_type::TEXT; + } + else if (id == "MEDIUMBLOB") + { + r.type = sql_type::MEDIUMBLOB; + } + else if (id == "MEDIUMTEXT") + { + r.type = sql_type::MEDIUMTEXT; + } + else if (id == "LONGBLOB") + { + r.type = sql_type::LONGBLOB; + } + else if (id == "LONGTEXT") + { + r.type = sql_type::LONGTEXT; + } + else if (id == "ENUM") + { + r.type = sql_type::ENUM; + } + else if (id == "SET") + { + r.type = sql_type::SET; + } + else + match = false; + + if (match) + { + s = parse_range; + continue; + } + } + + // Some prefixes can also be type names if not followed + // by the actual type name. + // + if (!prefix.empty ()) + { + if (prefix == "CHAR" || prefix == "CHARACTER") + { + r.type = sql_type::CHAR; + } + else if (prefix == "LONG") + { + r.type = sql_type::MEDIUMTEXT; + } + } + + if (r.type == sql_type::invalid) + { + if (tt == sql_token::t_identifier) + { + return error (ct, "unknown MySQL type '" + t.identifier () + + "'"); + } + else + return error (ct, "expected MySQL type name"); + } + + s = parse_range; + } + // Fall through. + case parse_range: + { + if (t.punctuation () == sql_token::p_lparen) + { + t = l.next (); + + // ENUM and SET have a list of members instead of the range. + // + if (r.type == sql_type::ENUM || r.type == sql_type::SET) + { + while (true) + { + if (t.type () != sql_token::t_string_lit) + { + return error (ct, "string literal expected in MySQL " + "ENUM or SET declaration"); + } + + if (r.type == sql_type::ENUM) + r.enumerators.push_back (t.literal ()); + + t = l.next (); + + if (t.punctuation () == sql_token::p_rparen) + break; + else if (t.punctuation () != sql_token::p_comma) + { + return error (ct, "comma expected in MySQL ENUM or " + "SET declaration"); + } + + t = l.next (); + } + } + else + { + if (t.type () != sql_token::t_int_lit) + { + return error (ct, "integer range expected in MySQL type " + "declaration"); + } + + unsigned int v; + istringstream is (t.literal ()); + + if (!(is >> v && is.eof ())) + { + return error (ct, "invalid range value '" + t.literal () + + "' in MySQL type declaration"); + } + + r.range = true; + r.range_value = v; + + t = l.next (); + + if (t.punctuation () == sql_token::p_comma) + { + // We have the second range value. Skip it. + // + // In FLOAT the two-value range means something + // completely different than the single-value. + // Pretend we don't have the range in the former + // case. + // + if (flt) + r.range = false; + + l.next (); + t = l.next (); + } + } + + if (t.punctuation () != sql_token::p_rparen) + { + return error (ct, "expected ')' in MySQL type declaration"); + } + + s = parse_sign; + continue; + } + + s = parse_sign; + } + // Fall through. + case parse_sign: + { + if (tt == sql_token::t_identifier && + context::upcase (t.identifier ()) == "UNSIGNED") + { + r.unsign = true; + } + + s = parse_done; + break; + } + case parse_done: + { + assert (false); + break; + } + } + } + + if (s == parse_name && !prefix.empty ()) + { + // Some prefixes can also be type names if not followed + // by the actual type name. + // + if (prefix == "CHAR" || prefix == "CHARACTER") + { + r.type = sql_type::CHAR; + } + else if (prefix == "LONG") + { + r.type = sql_type::MEDIUMTEXT; + } + } + + if (flt) + { + r.type = !r.range || r.range_value < 24 + ? sql_type::FLOAT + : sql_type::DOUBLE; + } + + if (r.type == sql_type::invalid) + return error (ct, "incomplete MySQL type declaration"); + + // If range is omitted for CHAR or BIT types, it defaults to 1. + // + if ((r.type == sql_type::CHAR || r.type == sql_type::BIT) && !r.range) + { + r.range = true; + r.range_value = 1; + } + + return r; + } + catch (sql_lexer::invalid_input const& e) + { + return error (ct, "invalid MySQL type declaration: " + e.message); + } + } + } +} diff --git a/odb/odb/relational/mysql/context.hxx b/odb/odb/relational/mysql/context.hxx new file mode 100644 index 0000000..98574f2 --- /dev/null +++ b/odb/odb/relational/mysql/context.hxx @@ -0,0 +1,194 @@ +// file : odb/relational/mysql/context.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_MYSQL_CONTEXT_HXX +#define ODB_RELATIONAL_MYSQL_CONTEXT_HXX + +#include <map> +#include <vector> + +#include <odb/relational/context.hxx> + +namespace relational +{ + namespace mysql + { + struct sql_type + { + // Keep the order in each block of types. + // + enum core_type + { + // Integral types. + // + TINYINT, + SMALLINT, + MEDIUMINT, + INT, + BIGINT, + + // Float types. + // + FLOAT, + DOUBLE, + DECIMAL, + + // Data-time types. + // + DATE, + TIME, + DATETIME, + TIMESTAMP, + YEAR, + + // String and binary types. + // + CHAR, + BINARY, + VARCHAR, + VARBINARY, + TINYTEXT, + TINYBLOB, + TEXT, + BLOB, + MEDIUMTEXT, + MEDIUMBLOB, + LONGTEXT, + LONGBLOB, + + // Other types. + // + BIT, + ENUM, + SET, + + // Invalid type. + // + invalid + }; + + sql_type () : type (invalid), unsign (false), range (false) {} + + core_type type; + bool unsign; + bool range; + unsigned int range_value; // MySQL max value is 2^32 - 1 (LONGBLOG/TEXT). + std::vector<std::string> enumerators; // Enumerator strings for ENUM. + + // Conversion expressions for custom database types. + // + std::string to; + std::string from; + }; + + class context: public virtual relational::context + { + public: + sql_type const& + parse_sql_type (string const&, + semantics::data_member&, + bool custom = true); + public: + struct invalid_sql_type + { + invalid_sql_type (string const& message): message_ (message) {} + + string const& + message () const {return message_;} + + private: + string message_; + }; + + // If custom_db_types is NULL, then this function returns + // invalid type instead of throwing in case an unknown type + // is encountered. + // + static sql_type + parse_sql_type (string, custom_db_types const* = 0); + + protected: + virtual string const& + convert_expr (string const&, semantics::data_member&, bool); + + virtual bool + grow_impl (semantics::class_&, user_section*); + + virtual bool + grow_impl (semantics::data_member&); + + virtual bool + grow_impl (semantics::data_member&, + semantics::type&, + const custom_cxx_type*, + string const&); + + protected: + virtual string + quote_id_impl (qname const&) const; + + protected: + virtual string + database_type_impl (semantics::type&, semantics::names*, bool, bool*); + + public: + virtual + ~context (); + context (); + context (std::ostream&, + semantics::unit&, + options_type const&, + features_type&, + sema_rel::model*); + + static context& + current () + { + return *current_; + } + + private: + static context* current_; + + private: + struct data: base_context::data + { + data (std::ostream& os): base_context::data (os) {} + + struct sql_type_cache_entry + { + sql_type_cache_entry () + : custom_cached (false), straight_cached (false) {} + + sql_type const& + cache_custom (sql_type const& t) + { + custom = t; + custom_cached = true; + return custom; + } + + sql_type const& + cache_straight (sql_type const& t) + { + straight = t; + straight_cached = true; + return straight; + } + + sql_type custom; // With custom mapping. + sql_type straight; // Without custom mapping. + + bool custom_cached; + bool straight_cached; + }; + + typedef std::map<string, sql_type_cache_entry> sql_type_cache; + sql_type_cache sql_type_cache_; + }; + data* data_; + }; + } +} + +#endif // ODB_RELATIONAL_MYSQL_CONTEXT_HXX diff --git a/odb/odb/relational/mysql/header.cxx b/odb/odb/relational/mysql/header.cxx new file mode 100644 index 0000000..27bae48 --- /dev/null +++ b/odb/odb/relational/mysql/header.cxx @@ -0,0 +1,136 @@ +// file : odb/relational/mysql/header.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/header.hxx> + +#include <odb/relational/mysql/common.hxx> +#include <odb/relational/mysql/context.hxx> + +namespace relational +{ + namespace mysql + { + namespace header + { + namespace relational = relational::header; + + struct image_member: relational::image_member_impl<sql_type>, + member_base + { + image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) {} + + virtual void + traverse_integer (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_float (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_decimal (member_info& mi) + { + // Exchanged as strings. Can have up to 65 digits not counting + // '-' and '.'. If range is not specified, the default is 10. + // + + /* + @@ Disabled. + os << "char " << mi.var << "value[" << + (t.range ? t.range_value : 10) + 3 << "];" + */ + + os << image_type << " " << mi.var << "value;" + << "unsigned long " << mi.var << "size;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "my_bool " << mi.var << "null;" + << endl; + + } + + virtual void + traverse_short_string (member_info& mi) + { + // If range is not specified, the default buffer size is 255. + // + /* + @@ Disabled. + os << "char " << mi.var << "value[" << + (t.range ? t.range_value : 255) + 1 << "];" + */ + + os << image_type << " " << mi.var << "value;" + << "unsigned long " << mi.var << "size;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "unsigned long " << mi.var << "size;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_bit (member_info& mi) + { + // Valid range is 1 to 64. + // + unsigned int n (mi.st->range / 8 + (mi.st->range % 8 ? 1 : 0)); + + os << "unsigned char " << mi.var << "value[" << n << "];" + << "unsigned long " << mi.var << "size;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_enum (member_info& mi) + { + // Represented as either integer or string. Since we don't know + // at the code generation time which one it is, we have to always + // keep size in case it is a string. + // + os << image_type << " " << mi.var << "value;" + << "unsigned long " << mi.var << "size;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_set (member_info& mi) + { + // Represented as string. + // + os << image_type << " " << mi.var << "value;" + << "unsigned long " << mi.var << "size;" + << "my_bool " << mi.var << "null;" + << endl; + } + }; + entry<image_member> image_member_; + } + } +} diff --git a/odb/odb/relational/mysql/inline.cxx b/odb/odb/relational/mysql/inline.cxx new file mode 100644 index 0000000..bfa2c94 --- /dev/null +++ b/odb/odb/relational/mysql/inline.cxx @@ -0,0 +1,42 @@ +// file : odb/relational/mysql/inline.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/inline.hxx> + +#include <odb/relational/mysql/common.hxx> +#include <odb/relational/mysql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mysql + { + namespace inline_ + { + namespace relational = relational::inline_; + + struct null_member: relational::null_member_impl<sql_type>, + member_base + { + null_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_simple (member_info& mi) + { + if (get_) + os << "r = r && i." << mi.var << "null;"; + else + os << "i." << mi.var << "null = 1;"; + } + }; + entry<null_member> null_member_; + } + } +} diff --git a/odb/odb/relational/mysql/model.cxx b/odb/odb/relational/mysql/model.cxx new file mode 100644 index 0000000..17ed4c0 --- /dev/null +++ b/odb/odb/relational/mysql/model.cxx @@ -0,0 +1,161 @@ +// file : odb/relational/mysql/model.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <sstream> + +#include <odb/relational/model.hxx> +#include <odb/relational/mysql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mysql + { + namespace model + { + namespace relational = relational::model; + + struct object_columns: relational::object_columns, context + { + object_columns (base const& x): base (x) {} + + virtual string + default_bool (semantics::data_member&, bool v) + { + // MySQL has TRUE and FALSE as just aliases for 1 and 0. Still + // use them for self-documentation. + // + return v ? "TRUE" : "FALSE"; + } + + virtual string + default_enum (semantics::data_member& m, tree en, string const& name) + { + // Make sure the column is mapped to an ENUM or integer type. + // + sql_type const& t (parse_sql_type (column_type (), m, false)); + + switch (t.type) + { + case sql_type::ENUM: + case sql_type::TINYINT: + case sql_type::SMALLINT: + case sql_type::MEDIUMINT: + case sql_type::INT: + case sql_type::BIGINT: + break; + default: + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: column with default value specified as C++ " + << "enumerator must map to MySQL ENUM or integer type" + << endl; + + throw operation_failed (); + } + } + + using semantics::enum_; + using semantics::enumerator; + + enumerator& er (dynamic_cast<enumerator&> (*unit.find (en))); + enum_& e (er.enum_ ()); + + if (t.type == sql_type::ENUM) + { + // Assuming the enumerators in the C++ enum and MySQL ENUM are + // in the same order, calculate the poistion of the C++ + // enumerator and use that as an index in the MySQL ENUM. + // + size_t pos (0); + + for (enum_::enumerates_iterator i (e.enumerates_begin ()), + end (e.enumerates_end ()); i != end; ++i) + { + if (&i->enumerator () == &er) + break; + + pos++; + } + + if (pos < t.enumerators.size ()) + return t.enumerators[pos]; + else + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: unable to map C++ enumerator '" << name + << "' to MySQL ENUM value" << endl; + + throw operation_failed (); + } + } + else + { + ostringstream ostr; + + if (e.unsigned_ ()) + ostr << er.value (); + else + ostr << static_cast<long long> (er.value ()); + + return ostr.str (); + } + } + }; + entry<object_columns> object_columns_; + + struct member_create: relational::member_create, context + { + member_create (base const& x): base (x) {} + + virtual string + table_options (semantics::data_member& m, semantics::type& c) + { + string r (relational::member_create::table_options (m, c)); + + string const& engine (options.mysql_engine ()); + if (engine != "default") + { + // Note: MySQL table options can be separated with spaces. + // + if (!r.empty ()) + r += ' '; + + r += "ENGINE="; + r += engine; + } + + return r; + } + }; + entry<member_create> member_create_; + + struct class_: relational::class_, context + { + class_ (base const& x): base (x) {} + + virtual string + table_options (type& c) + { + string r (relational::class_::table_options (c)); + + string const& engine (options.mysql_engine ()); + if (engine != "default") + { + // Note: MySQL table options can be separated with spaces. + // + if (!r.empty ()) + r += ' '; + + r += "ENGINE="; + r += engine; + } + + return r; + } + }; + entry<class_> class__; + } + } +} diff --git a/odb/odb/relational/mysql/schema.cxx b/odb/odb/relational/mysql/schema.cxx new file mode 100644 index 0000000..60dc95b --- /dev/null +++ b/odb/odb/relational/mysql/schema.cxx @@ -0,0 +1,489 @@ +// file : odb/relational/mysql/schema.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/schema.hxx> + +#include <odb/relational/mysql/common.hxx> +#include <odb/relational/mysql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mysql + { + namespace schema + { + namespace relational = relational::schema; + using relational::table_set; + + // + // Drop. + // + + struct drop_foreign_key: relational::drop_foreign_key, context + { + drop_foreign_key (base const& x): base (x) {} + + virtual void + drop (sema_rel::table& t, sema_rel::foreign_key& fk) + { + /* + // @@ This does not work: in MySQL control statements can only + // be used in stored procedures. It seems the only way to + // implement this is to define, execute, and drop a stored + // procedure, which is just too ugly. + // + // Another option would be to use CREATE TABLE IF NOT EXISTS + // to create a dummy table with a dummy constraint that makes + // the following DROP succeed. Note, however, that MySQL issues + // a notice if the table already exist so would need to suppress + // that as well. Still not sure that the utility of this support + // justifies this kind of a hack. + // + os << "IF EXISTS (SELECT NULL FROM information_schema.TABLE_CONSTRAINTS" << endl + << " WHERE CONSTRAINT_TYPE = " << quote_string ("FOREIGN KEY") << "AND" << endl + << " CONSTRAINT_SCHEMA = DATABASE() AND" << endl + << " CONSTRAINT_NAME = " << quote_string (fk.name ()) << ") THEN" << endl + << " ALTER TABLE " << quote_id (t.name ()) << " DROP FOREIGN KEY " << quote_id (fk.name ()) << ";" << endl + << "END IF;" << endl; + */ + + // So for now we only do this in migration. + // + if (dropped_ == 0) + { + if (fk.not_deferrable ()) + pre_statement (); + else + { + if (format_ != schema_format::sql) + return; + + os << "/*" << endl; + } + + os << "ALTER TABLE " << quote_id (t.name ()) << endl + << " DROP FOREIGN KEY " << quote_id (fk.name ()) << endl; + + if (fk.not_deferrable ()) + post_statement (); + else + os << "*/" << endl + << endl; + } + } + + using base::drop; + + virtual void + traverse (sema_rel::drop_foreign_key& dfk) + { + // Find the foreign key we are dropping in the base model. + // + sema_rel::foreign_key& fk (find<sema_rel::foreign_key> (dfk)); + + if (fk.not_deferrable () || in_comment) + base::traverse (dfk); + else + { + if (format_ != schema_format::sql) + return; + + os << endl + << " /*" + << endl; + + drop (dfk); + + os << endl + << " */"; + } + } + + virtual void + drop_header () + { + os << "DROP FOREIGN KEY "; + } + }; + entry<drop_foreign_key> drop_foreign_key_; + + struct drop_index: relational::drop_index, context + { + drop_index (base const& x): base (x) {} + + virtual void + drop (sema_rel::index& in) + { + sema_rel::table& t (static_cast<sema_rel::table&> (in.scope ())); + + os << "DROP INDEX " << name (in) << " ON " << + quote_id (t.name ()) << endl; + } + }; + entry<drop_index> drop_index_; + + // + // Create. + // + + struct create_column: relational::create_column, context + { + create_column (base const& x): base (x) {} + + virtual void + auto_ (sema_rel::primary_key&) + { + os << " AUTO_INCREMENT"; + } + }; + entry<create_column> create_column_; + + struct create_foreign_key: relational::create_foreign_key, context + { + create_foreign_key (base const& x): base (x) {} + + void + diagnose (sema_rel::foreign_key& fk) + { + if (fk.on_delete () != sema_rel::foreign_key::no_action) + { + cerr << "warning: foreign key '" << fk.name () << "' has " << + "ON DELETE clause but is disabled in MySQL due to lack " + "of deferrable constraint support" << endl; + + cerr << "info: consider using non-deferrable foreign keys (" << + "--fkeys-deferrable-mode)" << endl; + } + } + + virtual void + traverse_create (sema_rel::foreign_key& fk) + { + // MySQL does not support deferrable constraint checking. Output + // such foreign keys as comments, for documentation, unless we + // are generating embedded schema. + // + if (fk.not_deferrable ()) + base::traverse_create (fk); + else + { + diagnose (fk); + + // Don't bloat C++ code with comment strings if we are + // generating embedded schema. + // + if (format_ != schema_format::sql) + return; + + os << endl + << " /*" << endl + << " CONSTRAINT "; + create (fk); + os << endl + << " */"; + } + } + + virtual void + traverse_add (sema_rel::foreign_key& fk) + { + if (fk.not_deferrable () || in_comment) + base::traverse_add (fk); + else + { + diagnose (fk); + + if (format_ != schema_format::sql) + return; + + os << endl + << " /*" + << endl; + + add (fk); + + os << endl + << " */"; + } + } + + virtual void + deferrable (sema_rel::deferrable) + { + // This will still be called to output the comment. + } + }; + entry<create_foreign_key> create_foreign_key_; + + struct create_index: relational::create_index, context + { + create_index (base const& x): base (x) {} + + virtual void + create (sema_rel::index& in) + { + os << "CREATE "; + + if (!in.type ().empty ()) + os << in.type () << ' '; + + os << "INDEX " << name (in); + + if (!in.method ().empty ()) + os << " USING " << in.method (); + + os << endl + << " ON " << table_name (in) << " ("; + + columns (in); + + os << ")" << endl; + + if (!in.options ().empty ()) + os << ' ' << in.options () << endl; + } + }; + entry<create_index> create_index_; + + struct create_table: relational::create_table, context + { + create_table (base const& x): base (x) {} + + // See if there are any undefined foreign keys that are not + // deferrable. + // + bool + check_undefined_fk_deferrable_only (sema_rel::table& t) + { + for (sema_rel::table::names_iterator i (t.names_begin ()); + i != t.names_end (); ++i) + { + using sema_rel::foreign_key; + + if (foreign_key* fk = dynamic_cast<foreign_key*> (&i->nameable ())) + { + if (!fk->count ("mysql-fk-defined") && + fk->not_deferrable ()) + return false; + } + } + return true; + } + + virtual void + traverse (sema_rel::table& t) + { + if (pass_ == 1) + base::traverse (t); + else + { + // Add undefined foreign keys. + // + if (check_undefined_fk (t)) + { + bool deferrable (check_undefined_fk_deferrable_only (t)); + + if (!deferrable || format_ == schema_format::sql) + { + if (deferrable) + { + os << "/*" << endl; + in_comment = true; + } + else + pre_statement (); + + os << "ALTER TABLE " << quote_id (t.name ()); + + instance<create_foreign_key> cfk (*this); + trav_rel::unames n (*cfk); + names (t, n); + os << endl; + + if (deferrable) + { + in_comment = false; + os << "*/" << endl + << endl; + } + else + post_statement (); + } + } + } + } + }; + entry<create_table> create_table_; + + // + // Alter. + // + + struct alter_column: relational::alter_column, context + { + alter_column (base const& x): base (x) {} + + virtual void + alter_header () + { + os << "MODIFY COLUMN "; + } + }; + entry<alter_column> alter_column_; + + struct alter_table_pre: relational::alter_table_pre, context + { + alter_table_pre (base const& x): base (x) {} + + // Check if we are only dropping deferrable foreign keys. + // + bool + check_drop_deferrable_only (sema_rel::alter_table& at) + { + if (check<sema_rel::add_column> (at) || + check_alter_column_null (at, true)) + return false; + + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + using sema_rel::foreign_key; + using sema_rel::drop_foreign_key; + + if (drop_foreign_key* dfk = + dynamic_cast<drop_foreign_key*> (&i->nameable ())) + { + foreign_key& fk (find<foreign_key> (*dfk)); + + if (fk.not_deferrable ()) + return false; + } + } + return true; + } + + virtual void + alter (sema_rel::alter_table& at) + { + if (check_drop_deferrable_only (at)) + { + if (format_ != schema_format::sql) + return; + + os << "/*" << endl; + in_comment = true; + + os << "ALTER TABLE " << quote_id (at.name ()); + instance<drop_foreign_key> dfk (*this); + trav_rel::unames n (*dfk); + names (at, n); + os << endl; + + in_comment = false; + os << "*/" << endl + << endl; + } + else + base::alter (at); + } + }; + entry<alter_table_pre> alter_table_pre_; + + struct alter_table_post: relational::alter_table_post, context + { + alter_table_post (base const& x): base (x) {} + + // Check if we are only adding deferrable foreign keys. + // + bool + check_add_deferrable_only (sema_rel::alter_table& at) + { + if (check<sema_rel::drop_column> (at) || + check_alter_column_null (at, false)) + return false; + + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + using sema_rel::add_foreign_key; + + if (add_foreign_key* afk = + dynamic_cast<add_foreign_key*> (&i->nameable ())) + { + if (afk->not_deferrable ()) + return false; + } + } + return true; + } + + virtual void + alter (sema_rel::alter_table& at) + { + if (check_add_deferrable_only (at)) + { + if (format_ != schema_format::sql) + return; + + os << "/*" << endl; + in_comment = true; + + os << "ALTER TABLE " << quote_id (at.name ()); + instance<create_foreign_key> cfk (*this); + trav_rel::unames n (*cfk); + names (at, n); + os << endl; + + in_comment = false; + os << "*/" << endl + << endl; + } + else + base::alter (at); + } + }; + entry<alter_table_post> alter_table_post_; + + // + // Schema version table. + // + + struct version_table: relational::version_table, context + { + version_table (base const& x): base (x) {} + + virtual void + create_table () + { + pre_statement (); + + os << "CREATE TABLE IF NOT EXISTS " << qt_ << " (" << endl + << " " << qn_ << " VARCHAR(128) NOT NULL PRIMARY KEY," << endl + << " " << qv_ << " BIGINT UNSIGNED NOT NULL," << endl + << " " << qm_ << " TINYINT(1) NOT NULL)" << endl; + + string const& engine (options.mysql_engine ()); + if (engine != "default") + os << " ENGINE=" << engine << endl; + + post_statement (); + } + + virtual void + create (sema_rel::version v) + { + pre_statement (); + + os << "INSERT IGNORE INTO " << qt_ << " (" << endl + << " " << qn_ << ", " << qv_ << ", " << qm_ << ")" << endl + << " VALUES (" << qs_ << ", " << v << ", 0)" << endl; + + post_statement (); + } + }; + entry<version_table> version_table_; + } + } +} diff --git a/odb/odb/relational/mysql/source.cxx b/odb/odb/relational/mysql/source.cxx new file mode 100644 index 0000000..9131ea7 --- /dev/null +++ b/odb/odb/relational/mysql/source.cxx @@ -0,0 +1,724 @@ +// file : odb/relational/mysql/source.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/source.hxx> + +#include <odb/relational/mysql/common.hxx> +#include <odb/relational/mysql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mysql + { + namespace source + { + namespace relational = relational::source; + + namespace + { + const char* integer_buffer_types[] = + { + "MYSQL_TYPE_TINY", + "MYSQL_TYPE_SHORT", + "MYSQL_TYPE_LONG", // *_bind_param() doesn't support INT24. + "MYSQL_TYPE_LONG", + "MYSQL_TYPE_LONGLONG" + }; + + const char* float_buffer_types[] = + { + "MYSQL_TYPE_FLOAT", + "MYSQL_TYPE_DOUBLE" + }; + + const char* date_time_buffer_types[] = + { + "MYSQL_TYPE_DATE", + "MYSQL_TYPE_TIME", + "MYSQL_TYPE_DATETIME", + "MYSQL_TYPE_TIMESTAMP", + "MYSQL_TYPE_SHORT" + }; + + const char* char_bin_buffer_types[] = + { + "MYSQL_TYPE_STRING", // CHAR + "MYSQL_TYPE_BLOB", // BINARY, + "MYSQL_TYPE_STRING", // VARCHAR + "MYSQL_TYPE_BLOB", // VARBINARY + "MYSQL_TYPE_STRING", // TINYTEXT + "MYSQL_TYPE_BLOB", // TINYBLOB + "MYSQL_TYPE_STRING", // TEXT + "MYSQL_TYPE_BLOB", // BLOB + "MYSQL_TYPE_STRING", // MEDIUMTEXT + "MYSQL_TYPE_BLOB", // MEDIUMBLOB + "MYSQL_TYPE_STRING", // LONGTEXT + "MYSQL_TYPE_BLOB" // LONGBLOB + }; + } + + // + // + struct object_columns: relational::object_columns, context + { + object_columns (base const& x): base (x) {} + + virtual bool + column (semantics::data_member& m, + string const& table, + string const& column) + { + // When we store a ENUM column in the MySQL database, if we bind + // an integer parameter, then it is treated as an index and if we + // bind a string, then it is treated as a enumerator. Everything + // would have worked well if the same logic applied to the select + // operation. That is, if we bind integer, then the database sends + // the index and if we bind string then the database sends the + // enumerator. Unfortunately, MySQL always sends the enumerator + // and to get the index one has to resort to the enum+0 hack. + // + // This causes the following problem: at code generation time we + // do not yet know which format we want. This is determined at + // C++ compile time by traits (the reason we don't know this is + // because we don't want to drag database-specific runtimes, + // which define the necessary traits, as well as their + // prerequisites into the ODB compilation process). As a result, + // we cannot decide at code generation time whether we need the + // +0 hack or not. One way to overcome this would be to construct + // the SELECT statements at runtime, something along these lines: + // + // "enum" + enum_traits<type>::hack + "," + // + // However, this complicates the code generator quite a bit: we + // either have to move to std::string storage for all the + // statements and all the databases, which is kind of a waste, + // or do some deep per-database customizations, which is hairy. + // So, instead, we are going to use another hack (hey, what the + // hell, right?) by loading both the index and enumerator + // combined into a string: + // + // CONCAT (enum+0, ' ', enum) + // + // For cases where we need the index, everything works since + // MySQL will convert the leading number and stop at the space. + // For cases where we need the enumerator, we do a bit of pre- + // processing (see enum_traits) before handing the value off + // to value_traits. + // + + string const& type (column_type ()); + + if (sk_ != statement_select || + parse_sql_type (type, m).type != sql_type::ENUM) + { + return base::column (m, table, column); + } + + // Qualified column and conversion expression. + // + string qc; + if (!table.empty ()) + { + qc += table; + qc += '.'; + } + qc += column; + qc = convert_from (qc, type, m); + + string r ("CONCAT(" + qc + "+0,' '," + qc + ")"); + + sc_.push_back ( + relational::statement_column (table, r, type, m, key_prefix_)); + return true; + } + }; + entry<object_columns> object_columns_; + + struct view_columns: relational::view_columns, context + { + view_columns (base const& x): base (x) {} + + virtual bool + column (semantics::data_member& m, + string const& table, + string const& column) + { + // The same idea as in object_columns. + // + string const& type (column_type ()); + + if (parse_sql_type (type, m).type != sql_type::ENUM) + { + return base::column (m, table, column); + } + + // Column and conversion expression. + // + string c (convert_from (column, type, m)); + + string r ("CONCAT(" + c + "+0,' '," + c + ")"); + sc_.push_back (relational::statement_column (table, r, type, m)); + return true; + } + }; + entry<view_columns> view_columns_; + + // + // bind + // + + struct bind_member: relational::bind_member_impl<sql_type>, + member_base + { + bind_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_integer (member_info& mi) + { + // While the is_unsigned should indicate whether the + // buffer variable is unsigned, rather than whether the + // database type is unsigned, in case of the image types, + // this is the same. + // + os << b << ".buffer_type = " << + integer_buffer_types[mi.st->type - sql_type::TINYINT] << ";" + << b << ".is_unsigned = " << (mi.st->unsign ? "1" : "0") << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_float (member_info& mi) + { + os << b << ".buffer_type = " << + float_buffer_types[mi.st->type - sql_type::FLOAT] << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_decimal (member_info& mi) + { + os << b << ".buffer_type = MYSQL_TYPE_NEWDECIMAL;" + << b << ".buffer = " << arg << "." << mi.var << "value.data ();" + << b << ".buffer_length = static_cast<unsigned long> (" << endl + << arg << "." << mi.var << "value.capacity ());" + << b << ".length = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << b << ".buffer_type = " << + date_time_buffer_types[mi.st->type - sql_type::DATE] << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;"; + + if (mi.st->type == sql_type::YEAR) + os << b << ".is_unsigned = 0;"; + + os << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_short_string (member_info& mi) + { + // MySQL documentation is quite confusing about the use of + // buffer_length and length when it comes to input parameters. + // Source code, however, tells us that it uses buffer_length + // only if length is NULL. + // + os << b << ".buffer_type = " << + char_bin_buffer_types[mi.st->type - sql_type::CHAR] << ";" + << b << ".buffer = " << arg << "." << mi.var << "value.data ();" + << b << ".buffer_length = static_cast<unsigned long> (" << endl + << arg << "." << mi.var << "value.capacity ());" + << b << ".length = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << b << ".buffer_type = " << + char_bin_buffer_types[mi.st->type - sql_type::CHAR] << ";" + << b << ".buffer = " << arg << "." << mi.var << "value.data ();" + << b << ".buffer_length = static_cast<unsigned long> (" << endl + << arg << "." << mi.var << "value.capacity ());" + << b << ".length = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_bit (member_info& mi) + { + // Treated as a BLOB. + // + os << b << ".buffer_type = MYSQL_TYPE_BLOB;" + << b << ".buffer = " << arg << "." << mi.var << "value;" + << b << ".buffer_length = static_cast<unsigned long> (" << endl + << "sizeof (" << arg << "." << mi.var << "value));" + << b << ".length = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_enum (member_info& mi) + { + // Represented as either integer or string. + // + os << "mysql::enum_traits::bind (" << b << "," << endl + << arg << "." << mi.var << "value," << endl + << arg << "." << mi.var << "size," << endl + << "&" << arg << "." << mi.var << "null);"; + } + + virtual void + traverse_set (member_info& mi) + { + // Represented as a string. + // + os << b << ".buffer_type = MYSQL_TYPE_STRING;" + << b << ".buffer = " << arg << "." << mi.var << "value.data ();" + << b << ".buffer_length = static_cast<unsigned long> (" << endl + << arg << "." << mi.var << "value.capacity ());" + << b << ".length = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + }; + entry<bind_member> bind_member_; + + // + // grow + // + + struct grow_member: relational::grow_member_impl<sql_type>, + member_base + { + grow_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) {} + + virtual void + traverse_integer (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_float (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_decimal (member_info& mi) + { + // @@ Optimization disabled. + // + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + + virtual void + traverse_date_time (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_short_string (member_info& mi) + { + // @@ Optimization disabled. + // + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + + virtual void + traverse_bit (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_enum (member_info& mi) + { + // Represented as either integer or string (and we don't know + // at the code generation time which one it is). + // + os << "if (" << e << ")" << endl + << "{" + << "if (mysql::enum_traits::grow (" << + "i." << mi.var << "value, " << + "i." << mi.var << "size))" << endl + << "grew = true;" // String + << "else" << endl + << e << " = 0;" // Integer. + << "}"; + } + + virtual void + traverse_set (member_info& mi) + { + // Represented as a string. + // + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + }; + entry<grow_member> grow_member_; + + // + // init image + // + + struct init_image_member: relational::init_image_member_impl<sql_type>, + member_base + { + init_image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + set_null (member_info& mi) + { + os << "i." << mi.var << "null = 1;"; + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_float (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_decimal (member_info& mi) + { + // @@ Optimization: can remove growth check if buffer is fixed. + // + os << "std::size_t size (0);" + << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = static_cast<unsigned long> (size);" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_short_string (member_info& mi) + { + // @@ Optimization: can remove growth check if buffer is fixed. + // + os << "std::size_t size (0);" + << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = static_cast<unsigned long> (size);" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << "std::size_t size (0);" + << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = static_cast<unsigned long> (size);" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + + virtual void + traverse_bit (member_info& mi) + { + // Represented as a BLOB. + // + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "sizeof (i." << mi.var << "value)," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = static_cast<unsigned long> (size);"; + } + + virtual void + traverse_enum (member_info& mi) + { + // Represented as either integer or string. + // + os << "if (mysql::enum_traits::set_image (" << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "is_null," << endl + << member << "))" << endl + << "grew = true;" + << endl + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_set (member_info& mi) + { + // Represented as a string. + // + os << "std::size_t size (0);" + << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = static_cast<unsigned long> (size);" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + }; + entry<init_image_member> init_image_member_; + + // + // init value + // + + struct init_value_member: relational::init_value_member_impl<sql_type>, + member_base + { + init_value_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + get_null (string const& var) const + { + os << "i." << var << "null"; + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_float (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_decimal (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_short_string (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_bit (member_info& mi) + { + // Represented as a BLOB. + // + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_enum (member_info& mi) + { + // Represented as either integer or string. + // + os << "mysql::enum_traits::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_set (member_info& mi) + { + // Represented as a string. + // + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + }; + entry<init_value_member> init_value_member_; + + struct class_: relational::class_, context + { + class_ (base const& x): base (x) {} + + virtual void + init_auto_id (semantics::data_member& m, string const& im) + { + // Don't set the id value to 0 if this is a nullable wrapper. This + // will allow the user to use the NO_AUTO_VALUE_ON_ZERO mode by + // making it NULL when they want the auto semantics: + // + // #pragma db auto + // odb::nullable<int64_t> id; + // + semantics::type& t (utype (m)); + if (wrapper (t) && t.template get<bool> ("wrapper-null-handler")) + return; + + os << im << "value = 0;" + << endl; + } + + virtual string + join_syntax (view_object const& vo) + { + if (vo.join == view_object::full) + { + error (vo.loc) + << "FULL OUTER JOIN is not supported by MySQL" << endl; + throw operation_failed (); + } + + return base::join_syntax (vo); + } + }; + entry<class_> class_entry_; + + struct include: relational::include, context + { + include (base const& x): base (x) {} + + virtual void + extra_post () + { + os << "#include <odb/mysql/enum.hxx>" << endl; + } + }; + entry<include> include_; + } + } +} |