// file : odb/relational/mssql/context.cxx // author : Constantin Michael // copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file #include #include #include #include #include using namespace std; namespace relational { namespace mssql { namespace { struct type_map_entry { const char* const cxx_type; const char* const db_type; const char* const db_id_type; }; type_map_entry type_map[] = { {"bool", "BIT", 0}, {"char", "TINYINT", 0}, {"signed char", "TINYINT", 0}, {"unsigned char", "TINYINT", 0}, {"short int", "SMALLINT", 0}, {"short unsigned int", "SMALLINT", 0}, {"int", "INT", 0}, {"unsigned int", "INT", 0}, {"long int", "BIGINT", 0}, {"long unsigned int", "BIGINT", 0}, {"long long int", "BIGINT", 0}, {"long long unsigned int", "BIGINT", 0}, {"float", "REAL", 0}, {"double", "FLOAT", 0}, {"::std::string", "VARCHAR(512)", "VARCHAR(256)"}, {"::std::wstring", "NVARCHAR(512)", "NVARCHAR(256)"}, {"::size_t", "BIGINT", 0}, {"::std::size_t", "BIGINT", 0}, // Windows GUID/UUID (typedef struct _GUID {...} GUID, UUID;). // {"::_GUID", "UNIQUEIDENTIFIER", 0} }; } context* context::current_; context:: ~context () { if (current_ == this) current_ = 0; } context:: context (ostream& os, semantics::unit& u, options_type const& ops, sema_rel::model* m) : root_context (os, u, ops, data_ptr (new (shared) data (os))), base_context (static_cast (root_context::data_.get ()), m), data_ (static_cast (base_context::data_)) { assert (current_ == 0); current_ = this; generate_grow = false; need_alias_as = true; insert_send_auto_id = false; 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)); data_->type_map_.insert (v); } } context:: context () : data_ (current ().data_) { } 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; 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) { string r (base_context::database_type_impl (t, hint, id)); if (!r.empty ()) return r; using semantics::enum_; if (t.is_a ()) r = "INT"; return r; } // // SQL type parsing. // namespace { struct sql_parser { typedef context::invalid_sql_type invalid_sql_type; sql_parser (std::string const& sql) : l_ (sql) { } sql_type parse () { r_ = sql_type (); try { parse_name (); } catch (sql_lexer::invalid_input const& e) { throw invalid_sql_type ("invalid SQL Server type declaration: " + e.message); } return r_; } void parse_name () { sql_token t (l_.next ()); if (t.type () != sql_token::t_identifier) { throw invalid_sql_type ("expected SQL Server type name " "instead of '" + t.string () + "'"); } 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; parse_precision (l_.next ()); } 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; parse_precision (l_.next ()); } else if (id == "DOUBLE") { t = l_.next (); if (t.type () != sql_token::t_identifier || upcase (t.identifier ()) != "PRECISION") { throw invalid_sql_type ("expected 'PRECISION' instead of '" + t.string () + "'"); } r_.type = sql_type::FLOAT; r_.has_prec = true; r_.prec = 53; // It appears that DOUBLE PRECISION can be follows by the // precision specification. // parse_precision (l_.next ()); } else if (id == "CHAR" || id == "CHARACTER") { parse_char_trailer (false); } else if (id == "VARCHAR") { r_.type = sql_type::VARCHAR; r_.has_prec = true; r_.prec = 1; parse_precision (l_.next ()); } else if (id == "TEXT") { r_.type = sql_type::TEXT; } else if (id == "NCHAR") { r_.type = sql_type::NCHAR; r_.has_prec = true; r_.prec = 1; parse_precision (l_.next ()); } else if (id == "NVARCHAR") { r_.type = sql_type::NVARCHAR; r_.has_prec = true; r_.prec = 1; parse_precision (l_.next ()); } else if (id == "NTEXT") { r_.type = sql_type::NTEXT; } 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; } else if (id == "CHAR" || id == "CHARACTER") { parse_char_trailer (true); } else { throw invalid_sql_type ( "expected 'CHAR', 'CHARACTER', or 'TEXT' instead of '" + t.string () + "'"); } } 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; parse_precision (t); } else if (id == "VARBINARY") { r_.type = sql_type::VARBINARY; r_.has_prec = true; r_.prec = 1; parse_precision (l_.next ()); } else if (id == "IMAGE") { r_.type = sql_type::IMAGE; } 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; parse_precision (l_.next ()); } 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; parse_precision (l_.next ()); } 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; parse_precision (l_.next ()); } else if (id == "UNIQUEIDENTIFIER") { r_.type = sql_type::UNIQUEIDENTIFIER; } else if (id == "ROWVERSION" || id == "TIMESTAMP") { r_.type = sql_type::ROWVERSION; } else { throw invalid_sql_type ("unexpected SQL Server type name '" + t.identifier () + "'"); } } void 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 ())) { throw invalid_sql_type ( "invalid precision value '" + t.literal () + "' in SQL " "Server type declaration"); } 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 { throw invalid_sql_type ( "integer precision expected in SQL Server type declaration"); } // 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) { throw invalid_sql_type ( "unexpected scale in SQL Server type declaration"); } t = l_.next (); if (t.type () != sql_token::t_int_lit) { throw invalid_sql_type ( "integer scale expected in SQL Server type declaration"); } istringstream is (t.literal ()); if (!(is >> r_.scale && is.eof ())) { throw invalid_sql_type ( "invalid scale value '" + t.literal () + "' in SQL Server " "type declaration"); } r_.has_scale = true; t = l_.next (); } if (t.punctuation () != sql_token::p_rparen) { throw invalid_sql_type ( "expected ')' in SQL Server type declaration"); } } } void 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; parse_precision (t); } private: string upcase (string const& s) { return context::upcase (s); } private: sql_lexer l_; sql_type r_; }; } sql_type const& context:: column_sql_type (semantics::data_member& m, string const& kp) { string key (kp.empty () ? string ("mssql-column-sql-type") : "mssql-" + kp + "-column-sql-type"); if (!m.count (key)) { try { m.set (key, parse_sql_type (column_type (m, kp))); } catch (invalid_sql_type const& e) { cerr << m.file () << ":" << m.line () << ":" << m.column () << ": error: " << e.message () << endl; throw operation_failed (); } } return m.get (key); } sql_type context:: parse_sql_type (string const& t) { sql_parser p (t); return p.parse (); } } }