diff options
Diffstat (limited to 'odb/relational/mssql/context.cxx')
-rw-r--r-- | odb/relational/mssql/context.cxx | 577 |
1 files changed, 577 insertions, 0 deletions
diff --git a/odb/relational/mssql/context.cxx b/odb/relational/mssql/context.cxx new file mode 100644 index 0000000..bda8df9 --- /dev/null +++ b/odb/relational/mssql/context.cxx @@ -0,0 +1,577 @@ +// file : odb/relational/mssql/context.cxx +// author : Constantin Michael <constantin@codesynthesis.com> +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// 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 + { + 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(8000)", "VARCHAR(900)"}, + + {"::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<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; + 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 (string const& id) const + { + string r; + r.reserve (130); // Max MSSQL identifier length is 128. + r += '['; + r.append (id, 0, 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<semantics::enum_> ()) + 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<sql_type> (key); + } + + sql_type context:: + parse_sql_type (string const& t) + { + sql_parser p (t); + return p.parse (); + } + } +} |