diff options
Diffstat (limited to 'odb/odb/relational/mssql')
-rw-r--r-- | odb/odb/relational/mssql/common.cxx | 603 | ||||
-rw-r--r-- | odb/odb/relational/mssql/common.hxx | 293 | ||||
-rw-r--r-- | odb/odb/relational/mssql/context.cxx | 766 | ||||
-rw-r--r-- | odb/odb/relational/mssql/context.hxx | 194 | ||||
-rw-r--r-- | odb/odb/relational/mssql/header.cxx | 312 | ||||
-rw-r--r-- | odb/odb/relational/mssql/inline.cxx | 42 | ||||
-rw-r--r-- | odb/odb/relational/mssql/model.cxx | 66 | ||||
-rw-r--r-- | odb/odb/relational/mssql/schema.cxx | 651 | ||||
-rw-r--r-- | odb/odb/relational/mssql/source.cxx | 1201 |
9 files changed, 4128 insertions, 0 deletions
diff --git a/odb/odb/relational/mssql/common.cxx b/odb/odb/relational/mssql/common.cxx new file mode 100644 index 0000000..1070d21 --- /dev/null +++ b/odb/odb/relational/mssql/common.cxx @@ -0,0 +1,603 @@ +// file : odb/relational/mssql/common.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> + +#include <odb/relational/mssql/common.hxx> + +using namespace std; + +namespace relational +{ + namespace mssql + { + // + // 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) + { + const sql_type& st (*mi.st); + + // The same long/short data test as in context.cxx:long_data(). + // + switch (st.type) + { + // Integral types. + // + case sql_type::BIT: + case sql_type::TINYINT: + case sql_type::SMALLINT: + case sql_type::INT: + case sql_type::BIGINT: + { + traverse_integer (mi); + break; + } + + // Fixed and floating point types. + // + case sql_type::DECIMAL: + { + traverse_decimal (mi); + break; + } + case sql_type::SMALLMONEY: + { + traverse_smallmoney (mi); + break; + } + case sql_type::MONEY: + { + traverse_money (mi); + break; + } + case sql_type::FLOAT: + { + if (st.prec > 24) + traverse_float8 (mi); + else + traverse_float4 (mi); + + break; + } + + // String and binary types. + // + case sql_type::CHAR: + case sql_type::VARCHAR: + { + // Zero precision means max in VARCHAR(max). + // + if (st.prec == 0 || st.prec > options.mssql_short_limit ()) + traverse_long_string (mi); + else + traverse_string (mi); + + break; + } + case sql_type::TEXT: + { + traverse_long_string (mi); + break; + } + case sql_type::NCHAR: + case sql_type::NVARCHAR: + { + // Zero precision means max in NVARCHAR(max). Note that + // the precision is in 2-byte UCS-2 characters, not bytes. + // + if (st.prec == 0 || st.prec * 2 > options.mssql_short_limit ()) + traverse_long_nstring (mi); + else + traverse_nstring (mi); + + break; + } + case sql_type::NTEXT: + { + traverse_long_nstring (mi); + break; + } + case sql_type::BINARY: + case sql_type::VARBINARY: + { + // Zero precision means max in VARCHAR(max). + // + if (st.prec == 0 || st.prec > options.mssql_short_limit ()) + traverse_long_binary (mi); + else + traverse_binary (mi); + + break; + } + case sql_type::IMAGE: + { + traverse_long_binary (mi); + break; + } + + // Date-time types. + // + case sql_type::DATE: + { + traverse_date (mi); + break; + } + case sql_type::TIME: + { + traverse_time (mi); + break; + } + case sql_type::DATETIME: + case sql_type::DATETIME2: + case sql_type::SMALLDATETIME: + { + traverse_datetime (mi); + break; + } + case sql_type::DATETIMEOFFSET: + { + traverse_datetimeoffset (mi); + break; + } + + // Other types. + // + case sql_type::UNIQUEIDENTIFIER: + { + traverse_uniqueidentifier (mi); + break; + } + case sql_type::ROWVERSION: + { + traverse_rowversion (mi); + break; + } + case sql_type::invalid: + { + assert (false); + break; + } + } + } + + // + // member_image_type + // + + static const char* integer_types[] = + { + "unsigned char", + "unsigned char", + "short", + "int", + "long long" + }; + + 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_mssql >::image_type"; + } + + void member_image_type:: + traverse_integer (member_info& mi) + { + type_ = integer_types[mi.st->type - sql_type::BIT]; + } + + void member_image_type:: + traverse_decimal (member_info&) + { + type_ = "mssql::decimal"; + } + + void member_image_type:: + traverse_smallmoney (member_info&) + { + type_ = "mssql::smallmoney"; + } + + void member_image_type:: + traverse_money (member_info&) + { + type_ = "mssql::money"; + } + + void member_image_type:: + traverse_float4 (member_info&) + { + type_ = "float"; + } + + void member_image_type:: + traverse_float8 (member_info&) + { + type_ = "double"; + } + + void member_image_type:: + traverse_string (member_info&) + { + type_ = "char*"; + } + + void member_image_type:: + traverse_long_string (member_info&) + { + type_ = "mssql::long_callback"; + } + + void member_image_type:: + traverse_nstring (member_info&) + { + type_ = "mssql::ucs2_char*"; + } + + void member_image_type:: + traverse_long_nstring (member_info&) + { + type_ = "mssql::long_callback"; + } + + void member_image_type:: + traverse_binary (member_info&) + { + type_ = "char*"; + } + + void member_image_type:: + traverse_long_binary (member_info&) + { + type_ = "mssql::long_callback"; + } + + void member_image_type:: + traverse_date (member_info&) + { + type_ = "mssql::date"; + } + + void member_image_type:: + traverse_time (member_info&) + { + type_ = "mssql::time"; + } + + void member_image_type:: + traverse_datetime (member_info&) + { + type_ = "mssql::datetime"; + } + + void member_image_type:: + traverse_datetimeoffset (member_info&) + { + type_ = "mssql::datetimeoffset"; + } + + void member_image_type:: + traverse_uniqueidentifier (member_info&) + { + type_ = "mssql::uniqueidentifier"; + } + + void member_image_type:: + traverse_rowversion (member_info&) + { + type_ = "unsigned char*"; + } + + entry<member_image_type> member_image_type_; + + // + // member_database_type + // + + static const char* integer_database_id[] = + { + "mssql::id_bit", + "mssql::id_tinyint", + "mssql::id_smallint", + "mssql::id_int", + "mssql::id_bigint" + }; + + 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 (semantics::data_member& 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) + { + type_id_ = integer_database_id[mi.st->type - sql_type::BIT]; + } + + void member_database_type_id:: + traverse_decimal (member_info&) + { + type_id_ = "mssql::id_decimal"; + } + + void member_database_type_id:: + traverse_smallmoney (member_info&) + { + type_id_ = "mssql::id_smallmoney"; + } + + void member_database_type_id:: + traverse_money (member_info&) + { + type_id_ = "mssql::id_money"; + } + + void member_database_type_id:: + traverse_float4 (member_info&) + { + type_id_ = "mssql::id_float4"; + } + + void member_database_type_id:: + traverse_float8 (member_info&) + { + type_id_ = "mssql::id_float8"; + } + + void member_database_type_id:: + traverse_string (member_info&) + { + type_id_ = "mssql::id_string"; + } + + void member_database_type_id:: + traverse_long_string (member_info&) + { + type_id_ = "mssql::id_long_string"; + } + + void member_database_type_id:: + traverse_nstring (member_info&) + { + type_id_ = "mssql::id_nstring"; + } + + void member_database_type_id:: + traverse_long_nstring (member_info&) + { + type_id_ = "mssql::id_long_nstring"; + } + + void member_database_type_id:: + traverse_binary (member_info&) + { + type_id_ = "mssql::id_binary"; + } + + void member_database_type_id:: + traverse_long_binary (member_info&) + { + type_id_ = "mssql::id_long_binary"; + } + + void member_database_type_id:: + traverse_date (member_info&) + { + type_id_ = "mssql::id_date"; + } + + void member_database_type_id:: + traverse_time (member_info&) + { + type_id_ = "mssql::id_time"; + } + + void member_database_type_id:: + traverse_datetime (member_info&) + { + type_id_ = "mssql::id_datetime"; + } + + void member_database_type_id:: + traverse_datetimeoffset (member_info&) + { + type_id_ = "mssql::id_datetimeoffset"; + } + + void member_database_type_id:: + traverse_uniqueidentifier (member_info&) + { + type_id_ = "mssql::id_uniqueidentifier"; + } + + void member_database_type_id:: + traverse_rowversion (member_info&) + { + type_id_ = "mssql::id_rowversion"; + } + + 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); + } + + virtual void + column_ctor (string const& type, string const& name, string const& base) + { + os << name << " ("; + + if (multi_dynamic) + os << "odb::query_column< " << type << " >& qc," << endl; + + os << "const char* t," << endl + << "const char* c," << endl + << "const char* conv," << endl + << "unsigned short p = 0," << endl + << "unsigned short s = 0xFFFF)" << endl + << " : " << base << " (" << (multi_dynamic ? "qc, " : "") << + "t, c, conv, p, s)" + << "{" + << "}"; + } + + virtual void + column_ctor_args_extra (semantics::data_member& m) + { + // For some types we need to pass precision and scale. + // + sql_type const& st (parse_sql_type (column_type (), m)); + + switch (st.type) + { + case sql_type::DECIMAL: + { + os << ", " << st.prec << ", " << st.scale; + break; + } + case sql_type::FLOAT: + { + os << ", " << st.prec; + break; + } + case sql_type::CHAR: + case sql_type::VARCHAR: + { + os << ", " << st.prec; + break; + } + case sql_type::TEXT: + { + os << ", 0"; // Unlimited. + break; + } + case sql_type::NCHAR: + case sql_type::NVARCHAR: + { + os << ", " << st.prec; // In 2-byte characters. + break; + } + case sql_type::NTEXT: + { + os << ", 0"; // Unlimited. + break; + } + case sql_type::BINARY: + case sql_type::VARBINARY: + { + os << ", " << st.prec; + break; + } + case sql_type::IMAGE: + { + os << ", 0"; // Unlimited. + break; + } + // Date-time types. + // + case sql_type::TIME: + case sql_type::DATETIME2: + case sql_type::DATETIMEOFFSET: + { + os << ", 0, " << st.scale; // Fractional seconds (scale). + break; + } + case sql_type::DATETIME: + { + os << ", 0, 3"; + break; + } + case sql_type::SMALLDATETIME: + { + os << ", 0, 8"; + break; + } + default: + { + break; + } + } + } + + private: + member_database_type_id member_database_type_id_; + }; + entry<query_columns> query_columns_; + } +} diff --git a/odb/odb/relational/mssql/common.hxx b/odb/odb/relational/mssql/common.hxx new file mode 100644 index 0000000..42ea412 --- /dev/null +++ b/odb/odb/relational/mssql/common.hxx @@ -0,0 +1,293 @@ +// file : odb/relational/mssql/common.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_MSSQL_COMMON_HXX +#define ODB_RELATIONAL_MSSQL_COMMON_HXX + +#include <odb/relational/common.hxx> +#include <odb/relational/mssql/context.hxx> + +namespace relational +{ + namespace mssql + { + 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 mssql 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_decimal (member_info&) + { + } + + virtual void + traverse_smallmoney (member_info&) + { + } + + virtual void + traverse_money (member_info&) + { + } + + virtual void + traverse_float4 (member_info&) + { + } + + virtual void + traverse_float8 (member_info&) + { + } + + virtual void + traverse_string (member_info&) + { + } + + virtual void + traverse_long_string (member_info&) + { + } + + virtual void + traverse_nstring (member_info&) + { + } + + virtual void + traverse_long_nstring (member_info&) + { + } + + virtual void + traverse_binary (member_info&) + { + } + + virtual void + traverse_long_binary (member_info&) + { + } + + virtual void + traverse_date (member_info&) + { + } + + virtual void + traverse_time (member_info&) + { + } + + virtual void + traverse_datetime (member_info&) + { + } + + virtual void + traverse_datetimeoffset (member_info&) + { + } + + virtual void + traverse_uniqueidentifier (member_info&) + { + } + + virtual void + traverse_rowversion (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_decimal (member_info&); + + virtual void + traverse_smallmoney (member_info&); + + virtual void + traverse_money (member_info&); + + virtual void + traverse_float4 (member_info&); + + virtual void + traverse_float8 (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_long_string (member_info&); + + virtual void + traverse_nstring (member_info&); + + virtual void + traverse_long_nstring (member_info&); + + virtual void + traverse_binary (member_info&); + + virtual void + traverse_long_binary (member_info&); + + virtual void + traverse_date (member_info&); + + virtual void + traverse_time (member_info&); + + virtual void + traverse_datetime (member_info&); + + virtual void + traverse_datetimeoffset (member_info&); + + virtual void + traverse_uniqueidentifier (member_info&); + + virtual void + traverse_rowversion (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 (semantics::data_member&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_decimal (member_info&); + + virtual void + traverse_smallmoney (member_info&); + + virtual void + traverse_money (member_info&); + + virtual void + traverse_float4 (member_info&); + + virtual void + traverse_float8 (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_long_string (member_info&); + + virtual void + traverse_nstring (member_info&); + + virtual void + traverse_long_nstring (member_info&); + + virtual void + traverse_binary (member_info&); + + virtual void + traverse_long_binary (member_info&); + + virtual void + traverse_date (member_info&); + + virtual void + traverse_time (member_info&); + + virtual void + traverse_datetime (member_info&); + + virtual void + traverse_datetimeoffset (member_info&); + + virtual void + traverse_uniqueidentifier (member_info&); + + virtual void + traverse_rowversion (member_info&); + + private: + string type_id_; + }; + + struct has_long_data: object_columns_base, context + { + has_long_data (bool& r): r_ (r) {} + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + if (!inverse (m, key_prefix_)) + object_columns_base::traverse_pointer (m, c); + } + + virtual bool + traverse_column (semantics::data_member& m, string const&, bool) + { + if (long_data (parse_sql_type (column_type (), m))) + r_ = true; + + return true; + } + + private: + bool& r_; + }; + } +} +#endif // ODB_RELATIONAL_MSSQL_COMMON_HXX diff --git a/odb/odb/relational/mssql/context.cxx b/odb/odb/relational/mssql/context.cxx new file mode 100644 index 0000000..afe1aa5 --- /dev/null +++ b/odb/odb/relational/mssql/context.cxx @@ -0,0 +1,766 @@ +// file : odb/relational/mssql/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/mssql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mssql + { + 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", "BIT", 0, false}, + + {"char", "CHAR(1)", 0, false}, + {"wchar_t", "NCHAR(1)", 0, false}, + {"signed char", "TINYINT", 0, false}, + {"unsigned char", "TINYINT", 0, false}, + + {"short int", "SMALLINT", 0, false}, + {"short unsigned int", "SMALLINT", 0, false}, + + {"int", "INT", 0, false}, + {"unsigned int", "INT", 0, false}, + + {"long int", "BIGINT", 0, false}, + {"long unsigned int", "BIGINT", 0, false}, + + {"long long int", "BIGINT", 0, false}, + {"long long unsigned int", "BIGINT", 0, false}, + + {"float", "REAL", 0, false}, + {"double", "FLOAT", 0, false}, + + {"::std::string", "VARCHAR(512)", "VARCHAR(256)", false}, + {"::std::wstring", "NVARCHAR(512)", "NVARCHAR(256)", false}, + + {"::size_t", "BIGINT", 0, false}, + {"::std::size_t", "BIGINT", 0, false}, + + // Windows GUID/UUID (typedef struct _GUID {...} GUID, UUID;). + // + {"::_GUID", "UNIQUEIDENTIFIER", 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 = false; + need_alias_as = true; + insert_send_auto_id = false; + delay_freeing_statement_result = true; + need_image_clone = true; + generate_bulk = true; + global_index = false; + global_fkey = true; + data_->bind_vector_ = "mssql::bind*"; + + // 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 128 limit. + // + if (i->size () > 128) + { + cerr << "warning: SQL name '" << *i << "' is longer than the " + << "SQL Server name limit of 128 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, 128); // Max identifier length is 128. + r += ']'; + } + + return r; + } + + string context:: + database_type_impl (semantics::type& t, + semantics::names* hint, + bool id, + bool* null) + { + string r (base_context::database_type_impl (t, hint, id, null)); + + if (!r.empty ()) + return r; + + using semantics::array; + + // char[N] mapping. + // + if (array* a = dynamic_cast<array*> (&t)) + { + semantics::type& bt (a->base_type ()); + bool c (bt.is_a<semantics::fund_char> ()); + + if (c || bt.is_a<semantics::fund_wchar> ()) + { + unsigned long long n (a->size ()); + + if (n == 0) + return r; + if (n == 1) + r = c ? "CHAR(" : "NCHAR("; + else + { + r = c ? "VARCHAR(" : "NVARCHAR("; + n--; + } + + if (n > (c ? 8000 : 4000)) + r += "max)"; + else + { + ostringstream ostr; + ostr << n; + r += ostr.str (); + r += ')'; + } + } + } + + return r; + } + + bool context:: + long_data (sql_type const& st) + { + bool r (false); + + // The same test as in common.cxx:traverse_simple(). + // + switch (st.type) + { + case sql_type::CHAR: + case sql_type::VARCHAR: + case sql_type::BINARY: + case sql_type::VARBINARY: + { + // Zero precision means max in VARCHAR(max). + // + if (st.prec == 0 || st.prec > options.mssql_short_limit ()) + r = true; + + break; + } + case sql_type::NCHAR: + case sql_type::NVARCHAR: + { + // Zero precision means max in NVARCHAR(max). Note that + // the precision is in 2-byte UCS-2 characters, not bytes. + // + if (st.prec == 0 || st.prec * 2 > options.mssql_short_limit ()) + r = true; + + break; + } + case sql_type::TEXT: + case sql_type::NTEXT: + case sql_type::IMAGE: + { + r = true; + break; + } + default: + break; + } + + return r; + } + + // + // SQL type parsing. + // + + namespace + { + struct sql_parser + { + typedef context::invalid_sql_type invalid_sql_type; + + sql_parser (custom_db_types const* ct): ct_ (ct) {} + + sql_type + parse (std::string sql) + { + r_ = sql_type (); + m_.clear (); + + // 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 (sql)) + { + r_.to = t.type.replace (sql, t.to); + r_.from = t.type.replace (sql, t.from); + sql = t.type.replace (sql, t.as); + break; + } + } + } + + l_.lex (sql); + + bool ok (true); + + try + { + ok = parse_name (); + } + catch (sql_lexer::invalid_input const& e) + { + ok = false; + m_ = "invalid SQL Server type declaration: " + e.message; + } + + if (!ok) + { + if (ct_ == 0) + return sql_type (); + else + throw invalid_sql_type (m_); + } + + return r_; + } + + bool + parse_name () + { + sql_token t (l_.next ()); + + if (t.type () != sql_token::t_identifier) + { + m_ = "expected SQL Server type name instead of '" + + t.string () + "'"; + return false; + } + + string id (upcase (t.identifier ())); + + if (id == "BIT") + { + r_.type = sql_type::BIT; + } + else if (id == "TINYINT") + { + r_.type = sql_type::TINYINT; + } + else if (id == "SMALLINT") + { + r_.type = sql_type::SMALLINT; + } + else if (id == "INT" || + id == "INTEGER") + { + r_.type = sql_type::INT; + } + else if (id == "BIGINT") + { + r_.type = sql_type::BIGINT; + } + else if (id == "DECIMAL" || + id == "NUMERIC" || + id == "DEC") + { + r_.type = sql_type::DECIMAL; + + r_.has_prec = true; + r_.prec = 18; + + r_.has_scale = true; + r_.scale = 0; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "SMALLMONEY") + { + r_.type = sql_type::SMALLMONEY; + } + else if (id == "MONEY") + { + r_.type = sql_type::MONEY; + } + else if (id == "REAL") + { + r_.type = sql_type::FLOAT; + + r_.has_prec = true; + r_.prec = 24; + } + else if (id == "FLOAT") + { + r_.type = sql_type::FLOAT; + + r_.has_prec = true; + r_.prec = 53; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "DOUBLE") + { + t = l_.next (); + + if (t.type () != sql_token::t_identifier || + upcase (t.identifier ()) != "PRECISION") + { + m_ = "expected 'PRECISION' instead of '" + t.string () + "'"; + return false; + } + + r_.type = sql_type::FLOAT; + + r_.has_prec = true; + r_.prec = 53; + + // It appears that DOUBLE PRECISION can be followed by the + // precision specification. + // + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "CHAR" || + id == "CHARACTER") + { + if (!parse_char_trailer (false)) + return false; + } + else if (id == "VARCHAR") + { + r_.type = sql_type::VARCHAR; + + r_.has_prec = true; + r_.prec = 1; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "TEXT") + { + r_.type = sql_type::TEXT; + r_.has_prec = true; + r_.prec = 0; + } + else if (id == "NCHAR") + { + r_.type = sql_type::NCHAR; + + r_.has_prec = true; + r_.prec = 1; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "NVARCHAR") + { + r_.type = sql_type::NVARCHAR; + + r_.has_prec = true; + r_.prec = 1; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "NTEXT") + { + r_.type = sql_type::NTEXT; + r_.has_prec = true; + r_.prec = 0; + } + else if (id == "NATIONAL") + { + t = l_.next (); + + if (t.type () == sql_token::t_identifier) + id = upcase (t.identifier ()); + + if (id == "TEXT") + { + r_.type = sql_type::NTEXT; + r_.has_prec = true; + r_.prec = 0; + } + else if (id == "CHAR" || + id == "CHARACTER") + { + if (!parse_char_trailer (true)) + return false; + } + else + { + m_ = "expected 'CHAR', 'CHARACTER', or 'TEXT' instead of '" + + t.string () + "'"; + return false; + } + } + else if (id == "BINARY") + { + // Can be just BINARY or BINARY VARYING. + // + t = l_.next (); + + if (t.type () == sql_token::t_identifier) + id = upcase (t.identifier ()); + + if (id == "VARYING") + { + r_.type = sql_type::VARBINARY; + t = l_.next (); + } + else + r_.type = sql_type::BINARY; + + r_.has_prec = true; + r_.prec = 1; + + if (!parse_precision (t)) + return false; + } + else if (id == "VARBINARY") + { + r_.type = sql_type::VARBINARY; + + r_.has_prec = true; + r_.prec = 1; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "IMAGE") + { + r_.type = sql_type::IMAGE; + r_.has_prec = true; + r_.prec = 0; + } + else if (id == "DATE") + { + r_.type = sql_type::DATE; + } + else if (id == "TIME") + { + r_.type = sql_type::TIME; + + r_.has_scale = true; + r_.scale = 7; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "DATETIME") + { + r_.type = sql_type::DATETIME; + } + else if (id == "DATETIME2") + { + r_.type = sql_type::DATETIME2; + + r_.has_scale = true; + r_.scale = 7; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "SMALLDATETIME") + { + r_.type = sql_type::SMALLDATETIME; + } + else if (id == "DATETIMEOFFSET") + { + r_.type = sql_type::DATETIMEOFFSET; + + r_.has_scale = true; + r_.scale = 7; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "UNIQUEIDENTIFIER") + { + r_.type = sql_type::UNIQUEIDENTIFIER; + } + else if (id == "ROWVERSION" || + id == "TIMESTAMP") + { + r_.type = sql_type::ROWVERSION; + } + else + { + m_ = "unexpected SQL Server type name '" + t.identifier () + "'"; + return false; + } + + return true; + } + + bool + parse_precision (sql_token t) + { + if (t.punctuation () == sql_token::p_lparen) + { + // Parse the precision. + // + t = l_.next (); + + if (t.type () == sql_token::t_identifier && + upcase (t.identifier ()) == "MAX") + { + r_.prec = 0; + r_.has_prec = true; + } + else if (t.type () == sql_token::t_int_lit) + { + unsigned short v; + istringstream is (t.literal ()); + + if (!(is >> v && is.eof ())) + { + m_ = "invalid precision value '" + t.literal () + "' in SQL " + "Server type declaration"; + return false; + } + + switch (r_.type) + { + case sql_type::TIME: + case sql_type::DATETIME2: + case sql_type::DATETIMEOFFSET: + { + r_.scale = v; + r_.has_scale = true; + break; + } + default: + { + r_.prec = v; + r_.has_prec = true; + break; + } + } + } + else + { + m_ = "integer precision expected in SQL Server type declaration"; + return false; + } + + // Parse the scale if present. + // + t = l_.next (); + + if (t.punctuation () == sql_token::p_comma) + { + // Scale can only be specified for the DECIMAL type. + // + if (r_.type != sql_type::DECIMAL) + { + m_ = "unexpected scale in SQL Server type declaration"; + return false; + } + + t = l_.next (); + + if (t.type () != sql_token::t_int_lit) + { + m_ = "integer scale expected in SQL Server type declaration"; + return false; + } + + istringstream is (t.literal ()); + + if (!(is >> r_.scale && is.eof ())) + { + m_ = "invalid scale value '" + t.literal () + "' in SQL " + "Server type declaration"; + return false; + } + + r_.has_scale = true; + t = l_.next (); + } + + if (t.punctuation () != sql_token::p_rparen) + { + m_ = "expected ')' in SQL Server type declaration"; + return false; + } + } + + return true; + } + + bool + parse_char_trailer (bool nat) + { + sql_token t (l_.next ()); + + string id; + + if (t.type () == sql_token::t_identifier) + id = upcase (t.identifier ()); + + if (id == "VARYING") + { + r_.type = nat ? sql_type::NVARCHAR : sql_type::VARCHAR; + t = l_.next (); + } + else + r_.type = nat ? sql_type::NCHAR : sql_type::CHAR; + + r_.has_prec = true; + r_.prec = 1; + + return parse_precision (t); + } + + private: + string + upcase (string const& s) + { + return context::upcase (s); + } + + private: + custom_db_types const* ct_; + sql_lexer l_; + sql_type r_; + string m_; // Error message. + }; + } + + 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 (); + } + } + } + + sql_type context:: + parse_sql_type (string const& sqlt, custom_db_types const* ct) + { + sql_parser p (ct); + return p.parse (sqlt); + } + } +} diff --git a/odb/odb/relational/mssql/context.hxx b/odb/odb/relational/mssql/context.hxx new file mode 100644 index 0000000..7701aaa --- /dev/null +++ b/odb/odb/relational/mssql/context.hxx @@ -0,0 +1,194 @@ +// file : odb/relational/mssql/context.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_MSSQL_CONTEXT_HXX +#define ODB_RELATIONAL_MSSQL_CONTEXT_HXX + +#include <map> + +#include <odb/relational/context.hxx> + +namespace relational +{ + namespace mssql + { + struct sql_type + { + // Keep the order in each block of types. + // + enum core_type + { + // Integral types. + // + BIT, + TINYINT, + SMALLINT, + INT, + BIGINT, + + // Fixed and floating point types. + // + DECIMAL, + SMALLMONEY, + MONEY, + FLOAT, + + // String and binary types. + // + CHAR, + VARCHAR, + TEXT, + + NCHAR, + NVARCHAR, + NTEXT, + + BINARY, + VARBINARY, + IMAGE, + + // Date-time types. + // + DATE, + TIME, + DATETIME, + DATETIME2, + SMALLDATETIME, + DATETIMEOFFSET, + + // Other types. + // + UNIQUEIDENTIFIER, + ROWVERSION, + + // Invalid type. + // + invalid + }; + + sql_type () : + type (invalid), + has_prec (false), prec (0), + has_scale (false), scale (0) + { + } + + core_type type; + + bool has_prec; + unsigned short prec; // Max numeric value is 8000. 0 indicates + // 'max' as in VARCHAR(max). + bool has_scale; + unsigned short scale; // Max value is 38. + + // 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); + + // Return true if this type is long data. + // + bool + long_data (sql_type const&); + + 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 const&, custom_db_types const* = 0); + + protected: + virtual string const& + convert_expr (string const&, semantics::data_member&, bool); + + 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_MSSQL_CONTEXT_HXX diff --git a/odb/odb/relational/mssql/header.cxx b/odb/odb/relational/mssql/header.cxx new file mode 100644 index 0000000..ebdc734 --- /dev/null +++ b/odb/odb/relational/mssql/header.cxx @@ -0,0 +1,312 @@ +// file : odb/relational/mssql/header.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/header.hxx> + +#include <odb/relational/mssql/common.hxx> +#include <odb/relational/mssql/context.hxx> + +namespace relational +{ + namespace mssql + { + namespace header + { + namespace relational = relational::header; + + struct class1: relational::class1, context + { + class1 (base const& x): base (x) {} + + virtual void + object_public_extra_pre (type& c) + { + bool abst (abstract (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + if (poly_derived || (abst && !poly)) + return; + + // Bulk operations batch size. + // + { + unsigned long long b (c.count ("bulk") + ? c.get<unsigned long long> ("bulk") + : 1); + + os << "static const std::size_t batch = " << b << "UL;" + << endl; + } + + // rowvesion + // + bool rv (false); + if (semantics::data_member* m = optimistic (c)) + { + sql_type t (parse_sql_type (column_type (*m), *m)); + rv = (t.type == sql_type::ROWVERSION); + } + + os << "static const bool rowversion = " << rv << ";" + << endl; + + // Disable bulk update if we have ROWVERSION since we don't + // yet support batch extraction of the version. + // + if (rv && c.count ("bulk-update")) + c.remove ("bulk-update"); + } + + virtual void + object_public_extra_post (type& c) + { + bool abst (abstract (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + if (poly_derived || (abst && !poly)) + return; + + if (semantics::data_member* m = optimistic (c)) + { + sql_type t (parse_sql_type (column_type (*m), *m)); + if (t.type == sql_type::ROWVERSION) + { + os << "static version_type" << endl + << "version (const id_image_type&);" + << endl; + } + } + } + }; + entry<class1> class1_entry_; + + struct section_traits: relational::section_traits, context + { + section_traits (base const& x): base (x) {} + + virtual void + section_public_extra_pre (user_section&) + { + if (abstract (c_) && !polymorphic (c_)) + return; + + // rowvesion + // + bool rv (false); + if (semantics::data_member* m = optimistic (c_)) + { + sql_type t (parse_sql_type (column_type (*m), *m)); + rv = (t.type == sql_type::ROWVERSION); + } + + os << "static const bool rowversion = " << rv << ";" + << endl; + } + }; + entry<section_traits> section_traits_; + + struct image_type: relational::image_type, context + { + image_type (base const& x): base (x) {}; + + virtual void + image_extra (type& c) + { + if (!(composite (c) || (abstract (c) && !polymorphic (c)))) + { + type* poly_root (polymorphic (c)); + + // If this is a polymorphic type, only add callback to the root. + // + if (poly_root == 0 || poly_root == &c) + { + bool gc (options.generate_query ()); + + if (gc) + os << "mssql::change_callback change_callback_;" + << endl; + + os << "mssql::change_callback*" << endl + << "change_callback ()" + << "{"; + + if (gc) + os << "return &change_callback_;"; + else + os << "return 0;"; + + os << "}"; + } + } + } + }; + entry<image_type> image_type_; + + 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;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_decimal (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_smallmoney (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_money (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_float4 (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_float8 (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + // Extra character for the null-terminator that ODBC always adds. + // + os << "char " << mi.var << "value[" << mi.st->prec + 1 << "];" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << "mutable " << image_type << " " << mi.var << "callback;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_nstring (member_info& mi) + { + // Extra character for the null-terminator that ODBC always adds. + // + os << "mssql::ucs2_char " << mi.var << "value[" << + mi.st->prec + 1 << "];" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_long_nstring (member_info& mi) + { + os << "mutable " << image_type << " " << mi.var << "callback;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_binary (member_info& mi) + { + os << "char " << mi.var << "value[" << mi.st->prec << "];" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_long_binary (member_info& mi) + { + os << "mutable " << image_type << " " << mi.var << "callback;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_date (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_time (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_datetime (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_datetimeoffset (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_uniqueidentifier (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_rowversion (member_info& mi) + { + os << "unsigned char " << mi.var << "value[8];" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + }; + entry<image_member> image_member_; + } + } +} diff --git a/odb/odb/relational/mssql/inline.cxx b/odb/odb/relational/mssql/inline.cxx new file mode 100644 index 0000000..eb581d6 --- /dev/null +++ b/odb/odb/relational/mssql/inline.cxx @@ -0,0 +1,42 @@ +// file : odb/relational/mssql/inline.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/inline.hxx> + +#include <odb/relational/mssql/common.hxx> +#include <odb/relational/mssql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mssql + { + 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 << "size_ind == SQL_NULL_DATA;"; + else + os << "i." << mi.var << "size_ind = SQL_NULL_DATA;"; + } + }; + entry<null_member> null_member_; + } + } +} diff --git a/odb/odb/relational/mssql/model.cxx b/odb/odb/relational/mssql/model.cxx new file mode 100644 index 0000000..0f5a85c --- /dev/null +++ b/odb/odb/relational/mssql/model.cxx @@ -0,0 +1,66 @@ +// file : odb/relational/mssql/model.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <sstream> + +#include <odb/relational/model.hxx> + +#include <odb/relational/mssql/common.hxx> +#include <odb/relational/mssql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mssql + { + namespace model + { + namespace relational = relational::model; + + struct object_columns: relational::object_columns, context + { + object_columns (base const& x): base (x) {} + + virtual string + default_enum (semantics::data_member& m, tree en, string const&) + { + // Make sure the column is mapped to an integer or DECIMAL type. + // + switch (parse_sql_type (column_type (), m, false).type) + { + case sql_type::BIT: + case sql_type::TINYINT: + case sql_type::SMALLINT: + case sql_type::INT: + case sql_type::BIGINT: + case sql_type::DECIMAL: + break; + default: + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: column with default value specified as C++ " + << "enumerator must map to SQL Server integer type" << endl; + + throw operation_failed (); + } + } + + using semantics::enumerator; + + enumerator& e (dynamic_cast<enumerator&> (*unit.find (en))); + + ostringstream ostr; + + if (e.enum_ ().unsigned_ ()) + ostr << e.value (); + else + ostr << static_cast<long long> (e.value ()); + + return ostr.str (); + } + }; + entry<object_columns> object_columns_; + } + } +} diff --git a/odb/odb/relational/mssql/schema.cxx b/odb/odb/relational/mssql/schema.cxx new file mode 100644 index 0000000..c5f6bc1 --- /dev/null +++ b/odb/odb/relational/mssql/schema.cxx @@ -0,0 +1,651 @@ +// file : odb/relational/mssql/schema.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/schema.hxx> + +#include <odb/relational/mssql/common.hxx> +#include <odb/relational/mssql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mssql + { + namespace schema + { + namespace relational = relational::schema; + using relational::table_set; + + struct sql_emitter: relational::sql_emitter + { + sql_emitter (const base& x): base (x) {} + + virtual void + post () + { + if (!first_) // Ignore empty statements. + { + os << ';' << endl + << "GO" << endl + << endl; + } + } + }; + entry<sql_emitter> sql_emitter_; + + // + // File. + // + + struct sql_file: relational::sql_file, context + { + sql_file (const base& x): base (x) {} + + virtual void + prologue () + { + // Suppress the (x rows affected) messages from sqlcmd for DML + // statements. We only use DML for schema version management. + // + if ((model == 0 || model->version () != 0) && + !options.suppress_schema_version ()) + os << "SET NOCOUNT ON;" << endl + << endl; + } + }; + entry<sql_file> sql_file_; + + // + // Drop. + // + + struct drop_column: relational::drop_column, context + { + drop_column (base const& x): base (x) {} + + virtual void + traverse (sema_rel::drop_column& dc) + { + if (first_) + first_ = false; + else + os << "," << endl + << " "; + + os << quote_id (dc.name ()); + } + }; + entry<drop_column> drop_column_; + + 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) + { + bool migration (dropped_ == 0); + + if (migration) + { + if (fk.not_deferrable ()) + pre_statement (); + else + { + if (format_ != schema_format::sql) + return; + + os << "/*" << endl; + } + } + else + { + // Here we drop potentially deferrable keys and also need to + // test if the key exists. + // + pre_statement (); + + os << "IF OBJECT_ID(" << quote_string (fk.name ()) << ", " << + quote_string ("F") << ") IS NOT NULL" << endl + << " "; + } + + os << "ALTER TABLE " << quote_id (t.name ()) << endl + << (migration ? " " : " ") << "DROP CONSTRAINT " << + quote_id (fk.name ()) << endl; + + + if (!migration || fk.not_deferrable ()) + post_statement (); + else + os << "*/" << endl + << endl; + } + + 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)); + + bool c (!fk.not_deferrable () && !in_comment); + + if (c && format_ != schema_format::sql) + return; + + if (!first_) + os << (c ? "" : ",") << endl + << " "; + + if (c) + os << "/* "; + + os << quote_id (fk.name ()); + + if (c) + os << " */"; + + if (first_) + { + if (c) + // There has to be a real name otherwise the whole statement + // would have been commented out. + // + os << endl + << " "; + else + first_ = false; + } + } + }; + 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_; + + struct drop_table: relational::drop_table, context + { + drop_table (base const& x): base (x) {} + + virtual void + drop (sema_rel::table& t, bool migration) + { + // SQL Server has no IF EXISTS conditional for dropping tables. + // The following approach appears to be the recommended way to + // drop a table if it exists. + // + sema_rel::qname const& name (t.name ()); + + pre_statement (); + + if (!migration) + os << "IF OBJECT_ID(" << quote_string (name.string ()) << + ", " << quote_string ("U") << ") IS NOT NULL" << endl + << " "; + + os << "DROP TABLE " << quote_id (name) << endl; + + post_statement (); + } + }; + entry<drop_table> drop_table_; + + // + // Create. + // + + struct create_column: relational::create_column, context + { + create_column (base const& x): base (x) {} + + virtual void + traverse (sema_rel::add_column& ac) + { + if (first_) + first_ = false; + else + os << "," << endl + << " "; + + create (ac); + } + + virtual void + auto_ (sema_rel::primary_key&) + { + os << " IDENTITY"; + } + }; + 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 SQL Server 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) + { + // SQL Server 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) + { + bool c (!fk.not_deferrable () && !in_comment); + + if (c) + diagnose (fk); + + if (c && format_ != schema_format::sql) + return; + + if (!first_) + os << (c ? "" : ",") << endl + << " "; + + if (c) + os << "/*" << endl + << " "; + + os << "CONSTRAINT "; + create (fk); + + if (c) + os << endl + << " */"; + + if (first_) + { + if (c) + // There has to be a real key otherwise the whole statement + // would have been commented out. + // + os << endl + << " "; + else + first_ = false; + } + } + + virtual void + deferrable (sema_rel::deferrable) + { + // This will still be called to output the comment. + } + }; + entry<create_foreign_key> create_foreign_key_; + + 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 ("mssql-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 ()) << endl + << " ADD "; + + 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 + traverse (sema_rel::column& c) + { + // Relax (NULL) in pre and tighten (NOT NULL) in post. + // + if (pre_ != c.null ()) + return; + + using sema_rel::table; + table& at (static_cast<table&> (c.scope ())); + + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " ALTER COLUMN "; + alter (c); + os << endl; + + post_statement (); + } + }; + 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) + { + 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) + { + // SQL Server can only alter one kind of thing at a time. + // + if (check<sema_rel::drop_foreign_key> (at)) + { + bool deferrable (check_drop_deferrable_only (at)); + + if (!deferrable || format_ == schema_format::sql) + { + if (deferrable) + { + os << "/*" << endl; + in_comment = true; + } + else + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " DROP CONSTRAINT "; + + instance<drop_foreign_key> dfc (*this); + trav_rel::unames n (*dfc); + names (at, n); + os << endl; + + if (deferrable) + { + in_comment = false; + os << "*/" << endl + << endl; + } + else + post_statement (); + } + } + + if (check<sema_rel::add_column> (at)) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " ADD "; + + instance<create_column> cc (*this); + trav_rel::unames n (*cc); + names (at, n); + os << endl; + + post_statement (); + } + + // For ALTER COLUMN, SQL Server can only have one per ALTER TABLE. + // + { + bool tl (true); // (Im)perfect forwarding. + instance<alter_column> ac (*this, tl); + trav_rel::unames n (*ac); + names (at, n); + } + } + }; + 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) + { + 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) + { + // SQL Server can only alter one kind of thing at a time. + // + if (check<sema_rel::drop_column> (at)) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " DROP COLUMN "; + + instance<drop_column> dc (*this); + trav_rel::unames n (*dc); + names (at, n); + os << endl; + + post_statement (); + } + + // For ALTER COLUMN, SQL Server can only have one per ALTER TABLE. + // + { + bool fl (false); // (Im)perfect forwarding. + instance<alter_column> ac (*this, fl); + trav_rel::unames n (*ac); + names (at, n); + } + + if (check<sema_rel::add_foreign_key> (at)) + { + bool deferrable (check_add_deferrable_only (at)); + + if (!deferrable || format_ == schema_format::sql) + { + if (deferrable) + { + os << "/*" << endl; + in_comment = true; + } + else + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " ADD "; + + instance<create_foreign_key> cfc (*this); + trav_rel::unames n (*cfc); + names (at, n); + os << endl; + + if (deferrable) + { + in_comment = false; + os << "*/" << endl + << endl; + } + else + post_statement (); + } + } + } + }; + 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 << "IF OBJECT_ID(" << quote_string (table_.string ()) << + ", " << quote_string ("U") << ") IS NULL" << endl + << " CREATE TABLE " << qt_ << " (" << endl + << " " << qn_ << " VARCHAR(256) NOT NULL PRIMARY KEY," << endl + << " " << qv_ << " BIGINT NOT NULL," << endl + << " " << qm_ << " BIT NOT NULL)" << endl; + + post_statement (); + } + + virtual void + create (sema_rel::version v) + { + pre_statement (); + + os << "IF NOT EXISTS (SELECT 1 FROM " << qt_ << " WHERE " << qn_ << + " = " << qs_ << ")" << endl + << " INSERT 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/mssql/source.cxx b/odb/odb/relational/mssql/source.cxx new file mode 100644 index 0000000..573104d --- /dev/null +++ b/odb/odb/relational/mssql/source.cxx @@ -0,0 +1,1201 @@ +// file : odb/relational/mssql/source.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/source.hxx> + +#include <odb/relational/mssql/common.hxx> +#include <odb/relational/mssql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mssql + { + namespace source + { + namespace relational = relational::source; + + // + // + struct query_parameters: relational::query_parameters + { + query_parameters (base const& x): base (x) {} + + virtual string + auto_id (semantics::data_member&, const string&, const string&) + { + return ""; + } + }; + entry<query_parameters> query_parameters_; + + // + // + struct object_columns: relational::object_columns, context + { + object_columns (base const& x) + : base (x), rowversion_ (false), column_count_ (0) {} + + virtual bool + column (semantics::data_member& m, + string const& table, + string const& column) + { + // Don't add a column for auto id in the INSERT statement. + // Only simple, direct id can be auto. + // + if (sk_ == statement_insert && key_prefix_.empty () && auto_ (m)) + return false; + + // Don't update the ROWVERSION column explicitly. + // + if (sk_ == statement_update) + { + sql_type t (parse_sql_type (column_type (), m)); + if (t.type == sql_type::ROWVERSION) + { + rowversion_ = true; + return false; + } + } + + bool r (base::column (m, table, column)); + + // Count the number of columns in the UPDATE statement, but + // excluding soft-deleted. + // + if (sk_ == statement_update && r && !deleted (member_path_)) + column_count_++; + + return r; + } + + virtual void + traverse_post (semantics::nameable& n) + { + if (rowversion_ && column_count_ == 0) + { + location l (n.location ()); + error (l) << "ROWVERSION in an object without any readwrite " + "data members" << endl; + error (l) << "UPDATE statement will be empty" << endl; + throw operation_failed (); + } + } + + private: + bool rowversion_; + size_t column_count_; + }; + entry<object_columns> object_columns_; + + // + // + struct persist_statement_params: relational::persist_statement_params, + context + { + persist_statement_params (base const& x): base (x) {} + + virtual string + version_value (semantics::data_member& m) + { + sql_type t (parse_sql_type (column_type (), m)); + return t.type == sql_type::ROWVERSION ? "DEFAULT" : "1"; + } + }; + entry<persist_statement_params> persist_statement_params_; + + // + // bind + // + + static const char* integer_buffer_types[] = + { + "mssql::bind::bit", + "mssql::bind::tinyint", + "mssql::bind::smallint", + "mssql::bind::int_", + "mssql::bind::bigint" + }; + + 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) + { + os << b << ".type = " << + integer_buffer_types[mi.st->type - sql_type::BIT] << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"; + } + + virtual void + traverse_decimal (member_info& mi) + { + os << b << ".type = mssql::bind::decimal;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode precision (p) and scale (s) as (p * 100 + s). + // + << b << ".capacity = " << mi.st->prec * 100 + mi.st->scale << ";"; + } + + virtual void + traverse_smallmoney (member_info& mi) + { + os << b << ".type = mssql::bind::smallmoney;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"; + } + + virtual void + traverse_money (member_info& mi) + { + os << b << ".type = mssql::bind::money;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"; + } + + virtual void + traverse_float4 (member_info& mi) + { + os << b << ".type = mssql::bind::float4;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + << b << ".capacity = " << mi.st->prec << ";"; + } + + virtual void + traverse_float8 (member_info& mi) + { + os << b << ".type = mssql::bind::float8;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + << b << ".capacity = " << mi.st->prec << ";"; + } + + virtual void + traverse_string (member_info& mi) + { + os << b << ".type = mssql::bind::string;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + << b << ".capacity = static_cast<SQLLEN> (sizeof (" << + arg << "." << mi.var << "value));"; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << b << ".type = mssql::bind::long_string;" + << b << ".buffer = &" << arg << "." << mi.var << "callback;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode the column size with 0 indicating unlimited. + // + << b << ".capacity = " << mi.st->prec << ";"; + } + + virtual void + traverse_nstring (member_info& mi) + { + os << b << ".type = mssql::bind::nstring;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + << b << ".capacity = static_cast<SQLLEN> (sizeof (" << + arg << "." << mi.var << "value));"; + } + + virtual void + traverse_long_nstring (member_info& mi) + { + os << b << ".type = mssql::bind::long_nstring;" + << b << ".buffer = &" << arg << "." << mi.var << "callback;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode the column size (in bytes) with 0 indicating unlimited. + // + << b << ".capacity = " << mi.st->prec * 2 << ";"; + } + + virtual void + traverse_binary (member_info& mi) + { + os << b << ".type = mssql::bind::binary;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + << b << ".capacity = static_cast<SQLLEN> (sizeof (" << + arg << "." << mi.var << "value));"; + } + + virtual void + traverse_long_binary (member_info& mi) + { + os << b << ".type = mssql::bind::long_binary;" + << b << ".buffer = &" << arg << "." << mi.var << "callback;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode the column size with 0 indicating unlimited. + // + << b << ".capacity = " << mi.st->prec << ";"; + } + + virtual void + traverse_date (member_info& mi) + { + os << b << ".type = mssql::bind::date;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"; + } + + virtual void + traverse_time (member_info& mi) + { + os << b << ".type = mssql::bind::time;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode fractional seconds (scale). + // + << b << ".capacity = " << mi.st->scale << ";"; + } + + virtual void + traverse_datetime (member_info& mi) + { + unsigned short scale (0); + + switch (mi.st->type) + { + case sql_type::DATETIME: + { + // Looks like it is 3 (rounded to 0.000, 0.003, or 0.007). + // + scale = 3; + break; + } + case sql_type::DATETIME2: + { + scale = mi.st->scale; + break; + } + case sql_type::SMALLDATETIME: + { + // No seconds in SMALLDATATIME. Encode it a special precision + // value (8). + // + scale = 8; + break; + } + default: + { + assert (false); + break; + } + } + + os << b << ".type = mssql::bind::datetime;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode fractional seconds (scale). + // + << b << ".capacity = " << scale << ";"; + } + + virtual void + traverse_datetimeoffset (member_info& mi) + { + os << b << ".type = mssql::bind::datetimeoffset;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode fractional seconds (scale). + // + << b << ".capacity = " << mi.st->scale << ";"; + } + + virtual void + traverse_uniqueidentifier (member_info& mi) + { + os << b << ".type = mssql::bind::uniqueidentifier;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"; + } + + virtual void + traverse_rowversion (member_info& mi) + { + os << b << ".type = mssql::bind::rowversion;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"; + } + }; + entry<bind_member> bind_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 << "size_ind = SQL_NULL_DATA;"; + } + + virtual void + check_accessor (member_info& mi, member_access& ma) + { + // We cannot use accessors that return by-value for long data + // members. + // + if (long_data (*mi.st) && ma.by_value) + { + error (ma.loc) << "accessor returning a value cannot be used " + << "for a data member of SQL Server long data " + << "type" << endl; + info (ma.loc) << "accessor returning a const reference is required" + << endl; + info (mi.m.location ()) << "data member is defined here" << endl; + throw operation_failed (); + } + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_decimal (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_smallmoney (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 4;"; + } + + virtual void + traverse_money (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 8;"; + } + + virtual void + traverse_float4 (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_float8 (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_string (member_info& mi) + { + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + // Don't mention the extra character for the null-terminator. + << "sizeof (i." << mi.var << "value) - 1," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "size_ind =" << endl + << " is_null ? SQL_NULL_DATA : static_cast<SQLLEN> (size);"; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "callback.callback.param," << endl + << "i." << mi.var << "callback.context.param," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "size_ind = is_null ? " << + "SQL_NULL_DATA : SQL_DATA_AT_EXEC;"; + } + + virtual void + traverse_nstring (member_info& mi) + { + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + // Don't mention the extra character for the null-terminator. + << "sizeof (i." << mi.var << "value) / 2 - 1," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "size_ind =" << endl + << " is_null ? SQL_NULL_DATA : static_cast<SQLLEN> (size * 2);"; + } + + virtual void + traverse_long_nstring (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "callback.callback.param," << endl + << "i." << mi.var << "callback.context.param," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "size_ind = is_null ? " << + "SQL_NULL_DATA : SQL_DATA_AT_EXEC;"; + } + + virtual void + traverse_binary (member_info& mi) + { + 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 << "size_ind =" << endl + << " is_null ? SQL_NULL_DATA : static_cast<SQLLEN> (size);"; + } + + virtual void + traverse_long_binary (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "callback.callback.param," << endl + << "i." << mi.var << "callback.context.param," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "size_ind = is_null ? " << + "SQL_NULL_DATA : SQL_DATA_AT_EXEC;"; + } + + virtual void + traverse_date (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_time (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, " << mi.st->scale << ", " << + "is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null" << endl + << " ? SQL_NULL_DATA" << endl + << " : static_cast<SQLLEN> (sizeof (i." << mi.var << "value));"; + } + + virtual void + traverse_datetime (member_info& mi) + { + // The same code as in bind. + // + unsigned short scale (0); + + switch (mi.st->type) + { + case sql_type::DATETIME: + { + scale = 3; + break; + } + case sql_type::DATETIME2: + { + scale = mi.st->scale; + break; + } + case sql_type::SMALLDATETIME: + { + scale = 8; + break; + } + default: + { + assert (false); + break; + } + } + + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, " << scale << ", " << + "is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_datetimeoffset (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, " << mi.st->scale << ", " << + "is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null" << endl + << " ? SQL_NULL_DATA" << endl + << " : static_cast<SQLLEN> (sizeof (i." << mi.var << "value));"; + } + + virtual void + traverse_uniqueidentifier (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_rowversion (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 8;"; + } + }; + 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 << "size_ind == SQL_NULL_DATA"; + } + + virtual void + check_modifier (member_info& mi, member_access& ma) + { + // We cannot use by-value modifier for long data members. + // + if (long_data (*mi.st) && ma.placeholder ()) + { + error (ma.loc) << "modifier accepting a value cannot be used " + << "for a data member of SQL Server long data " + << "type" << endl; + info (ma.loc) << "modifier returning a non-const reference is " + << "required" << endl; + info (mi.m.location ()) << "data member is defined here" << endl; + throw operation_failed (); + } + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_decimal (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_smallmoney (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_money (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_float4 (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_float8 (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "static_cast<std::size_t> (i." << mi.var << "size_ind)," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "callback.callback.result," << endl + << "i." << mi.var << "callback.context.result);" + << endl; + } + + virtual void + traverse_nstring (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "static_cast<std::size_t> (" << + "i." << mi.var << "size_ind / 2)," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_long_nstring (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "callback.callback.result," << endl + << "i." << mi.var << "callback.context.result);" + << endl; + } + + virtual void + traverse_binary (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "static_cast<std::size_t> (i." << mi.var << "size_ind)," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_long_binary (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "callback.callback.result," << endl + << "i." << mi.var << "callback.context.result);" + << endl; + } + + virtual void + traverse_date (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_time (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_datetime (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_datetimeoffset (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_uniqueidentifier (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_rowversion (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + }; + entry<init_value_member> init_value_member_; + + struct statement_columns_common: context + { + void + process (relational::statement_columns& cols, + statement_kind sk, + bool dynamic) + { + using relational::statement_columns; + + // Long data columns must come last in the SELECT statement. If + // this statement is going to be processed at runtime, then this + // will be taken care of then. + // + if (sk != statement_select || dynamic) + return; + + // Go over the columns list while keeping track of how many + // columns we have examined. If the current column is long data, + // then move it to the back. Stop once we have examined all the + // columns. + // + size_t n (cols.size ()); + for (statement_columns::iterator i (cols.begin ()); n != 0; --n) + { + if (long_data (parse_sql_type (i->type, *i->member))) + { + cols.push_back (*i); + i = cols.erase (i); + } + else + ++i; + } + } + }; + + struct container_traits: relational::container_traits, + statement_columns_common + { + container_traits (base const& x): base (x) {} + + virtual void + cache_result (string const&) + { + // Caching is not necessary since with MARS enabled SQL Server + // can execute several interleaving statements. + // + } + + virtual void + init_value_extra () + { + os << "sts.select_statement ().stream_result ();" + << endl; + } + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk, + bool dynamic) + { + statement_columns_common::process (cols, sk, dynamic); + } + }; + entry<container_traits> container_traits_; + + struct section_traits: relational::section_traits, + statement_columns_common + { + section_traits (base const& x): base (x) {} + + virtual void + init_value_extra () + { + os << "st.stream_result ();"; + } + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk, + bool dynamic) + { + statement_columns_common::process (cols, sk, dynamic); + } + + virtual string + optimistic_version_increment (semantics::data_member& m) + { + sql_type t (parse_sql_type (column_type (m), m)); + return t.type != sql_type::ROWVERSION + ? "1" + : "version (sts.id_image ())"; + } + + virtual string + update_statement_extra (user_section&) + { + string r; + + semantics::data_member* ver (optimistic (c_)); + + if (ver == 0 || + parse_sql_type (column_type (*ver), *ver).type != + sql_type::ROWVERSION) + return r; + + // ROWVERSION & SQL Server 2005 incompatibility is detected + // in persist_statement_extra. + // + r = "OUTPUT INSERTED." + + convert_from (column_qname (*ver, column_prefix ()), *ver); + + return r; + } + }; + entry<section_traits> section_traits_; + + struct class_: relational::class_, statement_columns_common + { + class_ (base const& x): + base (x), init_version_value_member_id_image_ ("v", "version_") {} + + virtual void + init_image_pre (type& c) + { + if (options.generate_query () && + !(composite (c) || (abstract (c) && !polymorphic (c)))) + { + type* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + if (poly_derived) + os << "{" + << "root_traits::image_type& ri (root_image (i));" + << endl; + + string i (poly_derived ? "ri" : "i"); + + os << "if (" << i << ".change_callback_.callback != 0)" << endl + << "(" << i << ".change_callback_.callback) (" << + i << ".change_callback_.context);"; + + if (poly_derived) + os << "}"; + else + os << endl; + } + } + + virtual void + init_value_extra () + { + os << "st.stream_result ();"; + } + + virtual string + persist_statement_extra (type& c, + relational::query_parameters&, + persist_position p) + { + string r; + + type* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + // If we are a derived type in a polymorphic hierarchy, then + // auto id/version are handled by the root. + // + if (poly_derived) + return r; + + // See if we have auto id or ROWVERSION version. + // + data_member_path* id (id_member (c)); + semantics::data_member* ver (optimistic (c)); + + if (id != 0 && !auto_ (*id)) + id = 0; + + if (ver != 0) + { + sql_type t (parse_sql_type (column_type (*ver), *ver)); + if (t.type != sql_type::ROWVERSION) + ver = 0; + } + + if (id == 0 && ver == 0) + return r; + + // SQL Server 2005 has a bug that causes it to fail on an + // INSERT statement with the OUTPUT clause if data for one + // of the inserted columns is supplied at execution (long + // data). To work around this problem we use the less + // efficient batch of INSERT and SELECT statements. + // + if (options.mssql_server_version () <= mssql_version (9, 0)) + { + bool ld (false); + + if (c.count ("mssql-has-long-data")) + ld = c.get<bool> ("mssql-has-long-data"); + else + { + has_long_data t (ld); + t.traverse (c); + c.set ("mssql-has-long-data", ld); + } + + if (ld) + { + if (p == persist_after_values) + { + // SQL Server 2005 has no eqivalent of SCOPE_IDENTITY for + // ROWVERSION. + // + if (ver != 0) + { + error (c.location ()) << "in SQL Server 2005 ROWVERSION " << + "value cannot be retrieved for a persistent class " << + "containing long data" << endl; + throw operation_failed (); + } + + // We also cannot support bulk INSERT. + // + if (c.count ("bulk-persist")) + { + error (c.location ()) << "in SQL Server 2005 bulk " << + "persist operation cannot be implemented for a " << + "persistent class containing long data" << endl; + throw operation_failed (); + } + + r = "; SELECT " + + convert_from ("SCOPE_IDENTITY()", *id->back ()); + } + + return r; + } + } + + if (p == persist_after_columns) + { + r = "OUTPUT "; + + // Top-level auto id column. + // + if (id != 0) + r += "INSERTED." + + convert_from (column_qname (*id), *id->back ()); + + // Top-level version column. + // + if (ver != 0) + { + if (id != 0) + r += ','; + + r += "INSERTED." + convert_from ( + column_qname (*ver, column_prefix ()), *ver); + } + } + + return r; + } + + virtual string + update_statement_extra (type& c) + { + string r; + + type* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + // If we are a derived type in a polymorphic hierarchy, then + // version is handled by the root. + // + if (poly_derived) + return r; + + semantics::data_member* ver (optimistic (c)); + + if (ver == 0 || + parse_sql_type (column_type (*ver), *ver).type != + sql_type::ROWVERSION) + return r; + + // Long data & SQL Server 2005 incompatibility is detected + // in persist_statement_extra. + // + r = "OUTPUT INSERTED." + + convert_from (column_qname (*ver, column_prefix ()), *ver); + + return r; + } + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk, + bool dynamic) + { + statement_columns_common::process (cols, sk, dynamic); + } + + virtual string + optimistic_version_init (semantics::data_member& m, bool index) + { + sql_type t (parse_sql_type (column_type (m), m)); + return t.type != sql_type::ROWVERSION + ? "1" + : (index + ? "version (sts.id_image (i))" + : "version (sts.id_image ())"); + } + + virtual string + optimistic_version_increment (semantics::data_member& m, bool index) + { + sql_type t (parse_sql_type (column_type (m), m)); + return t.type != sql_type::ROWVERSION + ? "1" + : (index + ? "version (sts.id_image (i))" + : "version (sts.id_image ())"); + } + + virtual bool + optimistic_insert_bind_version (semantics::data_member& m) + { + sql_type t (parse_sql_type (column_type (m), m)); + return t.type == sql_type::ROWVERSION; + } + + virtual void + object_extra (type& c) + { + bool abst (abstract (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + if (poly_derived || (abst && !poly)) + return; + + if (semantics::data_member* m = optimistic (c)) + { + sql_type t (parse_sql_type (column_type (*m), *m)); + if (t.type == sql_type::ROWVERSION) + { + string const& type (class_fq_name (c)); + string traits ("access::object_traits_impl< " + type + ", id_" + + db.string () + " >"); + + os << traits << "::version_type" << endl + << traits << "::" << endl + << "version (const id_image_type& i)" + << "{" + << "version_type v;"; + init_version_value_member_id_image_->traverse (*m); + os << "return v;" + << "}"; + } + } + } + + virtual string + from_trailer (type& c) + { + return c.get<view_query> ("query").for_update + ? " WITH (UPDLOCK)" + : ""; + } + + virtual string + select_trailer (type&) {return "";} + + private: + // Go via the dynamic creation to get access to the constructor. + // + instance<relational::init_value_member> + init_version_value_member_id_image_; + }; + entry<class_> class_entry_; + } + } +} |