From 64cc9e9f0ed1ac6742ce9d5b370bf7de7b1cb461 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 20 Jan 2012 10:30:22 +0200 Subject: Work around SQL Server 2005 bug with long data and OUTPUT clause --- odb/option-types.cxx | 48 ++++++++++++++++++++++++++ odb/option-types.hxx | 74 +++++++++++++++++++++++++++++++++++++++-- odb/options.cli | 26 +++++++++++---- odb/relational/mssql/common.hxx | 63 +++++++++++++++++++++++++++++++++++ odb/relational/mssql/source.cxx | 35 +++++++++++++++++-- 5 files changed, 235 insertions(+), 11 deletions(-) (limited to 'odb') diff --git a/odb/option-types.cxx b/odb/option-types.cxx index 715577d..fcd7b88 100644 --- a/odb/option-types.cxx +++ b/odb/option-types.cxx @@ -12,8 +12,10 @@ using namespace std; +// // database // + static const char* database_[] = { "mssql", @@ -55,8 +57,10 @@ operator<< (ostream& os, database db) return os << db.string (); } +// // schema_format // + static const char* schema_format_[] = { "embedded", @@ -95,8 +99,10 @@ operator<< (ostream& os, schema_format sf) return os << sf.string (); } +// // oracle_version // + istream& operator>> (istream& is, oracle_version& v) { @@ -134,3 +140,45 @@ operator<< (ostream& os, oracle_version v) { return os << v.ver_major () << '.' << v.ver_minor (); } + +// +// mssql_version +// + +istream& +operator>> (istream& is, mssql_version& v) +{ + unsigned short major, minor; + + // Extract the major version. + // + is >> major; + + if (!is.fail ()) + { + // Extract the decimal point. + // + char p; + is >> p; + + if (!is.fail () && p == '.') + { + // Extract the minor version. + // + is >> minor; + + if (!is.fail ()) + v = mssql_version (major, minor); + } + else + is.setstate (istream::failbit); + } + + return is; +} + +ostream& +operator<< (ostream& os, mssql_version v) +{ + return os << v.ver_major () << '.' << v.ver_minor (); +} diff --git a/odb/option-types.hxx b/odb/option-types.hxx index 2b96941..b674eae 100644 --- a/odb/option-types.hxx +++ b/odb/option-types.hxx @@ -69,8 +69,6 @@ operator<< (std::ostream&, schema_format); // struct oracle_version { - oracle_version (): major_ (0), minor_ (0) {} - oracle_version (unsigned short major, unsigned short minor) : major_ (major), minor_ (minor) { @@ -139,4 +137,76 @@ operator>> (std::istream&, oracle_version&); std::ostream& operator<< (std::ostream&, oracle_version); +// +// +struct mssql_version +{ + mssql_version (unsigned short major, unsigned short minor) + : major_ (major), minor_ (minor) + { + } + + unsigned short + ver_major () const + { + return major_; + } + + unsigned short + ver_minor () const + { + return minor_; + } + +private: + unsigned short major_; + unsigned short minor_; +}; + +inline bool +operator== (const mssql_version& x, const mssql_version& y) +{ + return x.ver_major () == y.ver_major (); +} + +inline bool +operator!= (const mssql_version& x, const mssql_version& y) +{ + return !(x == y); +} + +inline bool +operator< (const mssql_version& x, const mssql_version& y) +{ + return x.ver_major () < y.ver_major () || + (x.ver_major () == y.ver_major () && + x.ver_minor () < y.ver_minor ()); +} + +inline bool +operator> (const mssql_version& x, const mssql_version& y) +{ + return x.ver_major () > y.ver_major () || + (x.ver_major () == y.ver_major () && + x.ver_minor () > y.ver_minor ()); +} + +inline bool +operator<= (const mssql_version& x, const mssql_version& y) +{ + return !(x > y); +} + +inline bool +operator>= (const mssql_version& x, const mssql_version& y) +{ + return !(x < y); +} + +std::istream& +operator>> (std::istream&, mssql_version&); + +std::ostream& +operator<< (std::ostream&, mssql_version); + #endif // ODB_OPTION_TYPES_HXX diff --git a/odb/options.cli b/odb/options.cli index 4287231..fedc00a 100644 --- a/odb/options.cli +++ b/odb/options.cli @@ -430,12 +430,24 @@ class options // SQL Server-specific options. // + ::mssql_version --mssql-server-version (10, 0) + { + "", + "Specify the minimum SQL Server server version with which the generated + C++ code will be used. This information is used to enable + version-specific optimizations and workarounds in the generated C++ + code. The version must be in the \c{\i{major}\b{.}\i{minor}} form, for + example, \cb{9.0} (SQL Server 2005), \cb{10.5} (2008R2), or \cb{11.0} + (2012). If this options is not specified, then 10.0 (SQL Server 2008) + or later is assumed." + }; + unsigned int --mssql-short-limit = 1024 { "", - "Specify the short data size limit. If character, national character, - or binary data type has a maximum length (in bytes) less than this - limit, then it is treated as \i{short data}, otherwise it is \i{long + "Specify the short data size limit. If character, national character, or + binary data type has a maximum length (in bytes) less than or equal to + this limit, then it is treated as \i{short data}, otherwise it is \i{long data}. For short data ODB pre-allocates an intermediate buffer of the maximum size and binds it directly to a parameter or result column. This way the underlying API (ODBC) can read/write directly @@ -466,13 +478,15 @@ class options // Oracle-specific options. // - ::oracle_version --oracle-client-version + ::oracle_version --oracle-client-version (10, 1) { "", "Specify the minimum Oracle client library (OCI) version with which the generated C++ code will be linked. This information is used to enable - version-specific optimizations in the generated C++ code. The version - must be in the \c{\i{major}\b{.}\i{minor}} form, for example, \cb{11.2}." + version-specific optimizations and workarounds in the generated C++ + code. The version must be in the \c{\i{major}\b{.}\i{minor}} form, + for example, \cb{11.2}. If this options is not specified, then 10.1 + or later is assumed." }; // diff --git a/odb/relational/mssql/common.hxx b/odb/relational/mssql/common.hxx index 5bd37f5..eacc435 100644 --- a/odb/relational/mssql/common.hxx +++ b/odb/relational/mssql/common.hxx @@ -357,6 +357,69 @@ namespace relational private: string type_id_; }; + + struct has_long_data: object_columns_base, context + { + has_long_data (bool& r): r_ (r) {} + + virtual bool + traverse_column (semantics::data_member& m, string const&, bool) + { + if (inverse (m)) + return false; + + sql_type const& st (column_sql_type (m)); + + switch (st.type) + { + 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 ()) + 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::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::TEXT: + case sql_type::NTEXT: + case sql_type::IMAGE: + { + r_ = true; + break; + } + default: + break; + } + + return true; + } + + private: + bool& r_; + }; } } #endif // ODB_RELATIONAL_MSSQL_COMMON_HXX diff --git a/odb/relational/mssql/source.cxx b/odb/relational/mssql/source.cxx index 0e449dd..afb5887 100644 --- a/odb/relational/mssql/source.cxx +++ b/odb/relational/mssql/source.cxx @@ -1297,12 +1297,41 @@ namespace relational relational::query_parameters&, persist_position p) { - if (p != persist_after_columns) + semantics::data_member* id (id_member (c)); + + if (id == 0 || !auto_ (*id)) return; - semantics::data_member* id (id_member (c)); + // 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 ("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) + os << endl + << strlit ("; SELECT SCOPE_IDENTITY()"); + + return; + } + } - if (id != 0 && id->count ("auto")) + if (p == persist_after_columns) os << strlit (" OUTPUT INSERTED." + column_qname (*id)) << endl; } -- cgit v1.1