diff options
Diffstat (limited to 'odb/odb/relational/mssql/context.cxx')
-rw-r--r-- | odb/odb/relational/mssql/context.cxx | 766 |
1 files changed, 766 insertions, 0 deletions
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); + } + } +} |