From e075f77af3a09c22a8bec660c69c9fa7a9808d8e Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 18 Nov 2011 14:54:27 +0200 Subject: Implement remaining database constructors, update options --- odb/mssql/database.cxx | 494 +++++++++++++++++++++++++++++++----------- odb/mssql/database.hxx | 126 ++++++++--- odb/mssql/details/options.cli | 33 +-- 3 files changed, 479 insertions(+), 174 deletions(-) diff --git a/odb/mssql/database.cxx b/odb/mssql/database.cxx index 0eb0ed9..bafe176 100644 --- a/odb/mssql/database.cxx +++ b/odb/mssql/database.cxx @@ -3,8 +3,7 @@ // copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC // license : ODB NCUEL; see accompanying LICENSE file -//@@ disabled functionality - +#include // std::strcmp, std::strncmp #include #include @@ -21,195 +20,426 @@ namespace odb namespace mssql { database:: - database (const string& c, SQLHENV e, auto_ptr f) - : connect_string_ (c), environment_ (e), factory_ (f) + database (const std::string& user, + const std::string& password, + const std::string& db, + const std::string& server, + const std::string& driver, + const std::string& extra_connect_string, + SQLHENV environment, + std::auto_ptr factory) + : user_ (user), + password_ (password), + db_ (db), + protocol_ (protocol_auto), + port_ (0), + server_ (server), + driver_ (driver), + extra_connect_string_ (extra_connect_string), + environment_ (environment), + factory_ (factory) { - if (environment_ == 0) - { - SQLRETURN r ( - SQLAllocHandle (SQL_HANDLE_ENV, - SQL_NULL_HANDLE, - &environment_)); - - if (!SQL_SUCCEEDED (r)) - throw database_exception ( - 0, "?????", "unable to allocate environment handle"); - - auto_environment_.reset (environment_); - - // Set ODBC version. - // - r = SQLSetEnvAttr (environment_, - SQL_ATTR_ODBC_VERSION, - (SQLPOINTER) SQL_OV_ODBC3, - 0); - - if (!SQL_SUCCEEDED (r)) - translate_error (environment_, SQL_HANDLE_ENV); - } - - if (factory_.get () == 0) - factory_.reset (new connection_pool_factory ()); + init (); + } - factory_->database (*this); + database:: + database (const std::string& user, + const std::string& password, + const std::string& db, + protocol_type protocol, + const std::string& host, + const std::string& instance, + const std::string& driver, + const std::string& extra_connect_string, + SQLHENV environment, + std::auto_ptr factory) + : user_ (user), + password_ (password), + db_ (db), + protocol_ (protocol), + host_ (host), + instance_ (instance), + port_ (0), + driver_ (driver), + extra_connect_string_ (extra_connect_string), + environment_ (environment), + factory_ (factory) + { + init (); } - /* database:: - database (const string& user, - const string& password, - const string& service, - const string& host, + database (const std::string& user, + const std::string& password, + const std::string& db, + const std::string& host, unsigned int port, - ub2 charset, - ub2 ncharset, - OCIEnv* environment, - auto_ptr factory) + const std::string& driver, + const std::string& extra_connect_string, + SQLHENV environment, + std::auto_ptr factory) : user_ (user), password_ (password), - service_ (service), + db_ (db), + protocol_ (protocol_tcp), host_ (host), port_ (port), - charset_ (charset), - ncharset_ (ncharset), + driver_ (driver), + extra_connect_string_ (extra_connect_string), environment_ (environment), factory_ (factory) { - if (environment_ == 0) - { - sword s (OCIEnvNlsCreate (&environment_, - OCI_THREADED, - 0, 0, 0, 0, 0, 0, - charset, - ncharset)); + init (); + } + + database:: + database (const string& connect_string, + SQLHENV environment, + auto_ptr factory) + : protocol_ (protocol_auto), + port_ (0), + connect_string_ (connect_string), + environment_ (environment), + factory_ (factory) + { + init (); + } - if (s == OCI_ERROR) - translate_error (environment_); + database:: + database (int& argc, + char* argv[], + bool erase, + const std::string& extra_connect_string, + SQLHENV environment, + auto_ptr factory) + : protocol_ (protocol_auto), + port_ (0), + extra_connect_string_ (extra_connect_string), + environment_ (environment), + factory_ (factory) + { + using namespace details; - auto_environment_.reset (environment_); + try + { + cli::argv_file_scanner scan (argc, argv, "--options-file", erase); + options ops (scan, cli::unknown_mode::skip, cli::unknown_mode::skip); + + user_ = ops.user (); + password_ = ops.password (); + db_ = ops.database (); + server_ = ops.server (); + driver_ = ops.driver (); + } + catch (const cli::exception& e) + { + ostringstream oss; + oss << e; + throw cli_exception (oss.str ()); } - ostringstream ss; + init (); + } + + /* + + NOTE: This code hasn't been tested. - if (!host.empty ()) + void database:: + parse () + { + // Parse the server string and extract individual parts (protocol, + // host, instance, and port). + // + string port; + + if (server_.compare (0, 4, "lpc:") == 0) { - ss << "//" << host_; + // lpc:[\] + // + protocol_ = protocol_shm; + string::size_type p (server_.find (4, '\\')); - if (port != 0) - ss << ":" << port; + if (p == string::npos) + host_.assign (server_, 4, string::npos); + else + { + host_.assign (server_, 4, p - 4); + instance_.assign (server_, p + 1, string::npos); + } } + else if (server_.compare (0, 3, "np:") == 0) + { + // np:\pipe\[MSSQL$\]sql\query + // + protocol_ = protocol_pipe; + + string::size_type p (server_.find (3, '\\')); + + if (p != string::npos) + { + host_.assign (server_, 3, p - 3); + + p = server_.find (p + 1, '$'); - if (!service_.empty ()) + if (p != string::npos) + { + p++; + instance_.assign (server_, p, server_.find (p, '\\') - p); + } + } + } + else { - if (!host.empty ()) - ss << "/" << service_; + // [\][,] + // tcp:[\][,] + // + string::size_type p1 (0), p2; + + if (server_.compare (0, 4, "tcp:") == 0) + { + protocol_ = protocol_tcp; + p1 = 4; + } + + p2 = server_.find (p1, '\\'); + + if (p2 == string::npos) + { + p2 = server_.find (p1, ','); + + if (p2 == string::npos) + host_.assign (server_, p1, string::npos); + else + { + host_.assign (server_, 4, p2 - p1); + port.assign (server_, p2 + 1, string::npos); + } + } else - ss << service_; - } + { + host_.assign (server_, 4, p2 - p1); - // @@ Quote FQ connect identifier. - // - db_ = ss.str (); + p1 = server_.find (p2 + 1, ','); - if (factory_.get () == 0) - factory_.reset (new connection_pool_factory ()); + if (p1 == string::npos) + instance_.assign (server_, p2 + 1, string::npos); + else + { + instance_.assign (server_, p2 + 1, p1 - p2 - 1); + port.assign (server_, p1 + 1, string::npos); + } + } + } - factory_->database (*this); + if (!port.empty ()) + { + istringstream is (port); + is >> port; + protocol_ = protocol_tcp; + } } */ - database:: - database (int& argc, - char* argv[], - bool erase, - SQLHENV environment, - auto_ptr factory) - : environment_ (environment), - factory_ (factory) + void database:: + init () { - /* + SQLRETURN r; + if (environment_ == 0) { - sword s (OCIEnvNlsCreate (&environment_, - OCI_THREADED, - 0, 0, 0, 0, 0, 0, - charset, - ncharset)); + r = SQLAllocHandle (SQL_HANDLE_ENV, SQL_NULL_HANDLE, &environment_); - if (s == OCI_ERROR) - translate_error (environment_); + if (!SQL_SUCCEEDED (r)) + throw database_exception ( + 0, "?????", "unable to allocate environment handle"); auto_environment_.reset (environment_); - } - using namespace details; + // Set ODBC version. + // + r = SQLSetEnvAttr (environment_, + SQL_ATTR_ODBC_VERSION, + (SQLPOINTER) SQL_OV_ODBC3, + 0); - try + if (!SQL_SUCCEEDED (r)) + translate_error (environment_, SQL_HANDLE_ENV); + } + + // Build the connection string. + // + if (connect_string_.empty ()) { - cli::argv_file_scanner scan (argc, argv, "--options-file", erase); - options ops (scan, cli::unknown_mode::skip, cli::unknown_mode::skip); + // Find the driver. + // + if (driver_.empty ()) + { + for (bool first (true);; ) + { + char desc[256]; + SQLSMALLINT desc_size, attr_size; + + r = SQLDrivers (environment_, + first ? SQL_FETCH_FIRST : SQL_FETCH_NEXT, + (SQLCHAR*) desc, + sizeof (desc), + &desc_size, + 0, + 0, + &attr_size); + + if (r == SQL_NO_DATA) + break; + else if (!SQL_SUCCEEDED (r)) + translate_error (environment_, SQL_HANDLE_ENV); + + // Native Client 9.0 (first version). + // + if (strcmp (desc, "SQL Native Client") == 0 || + strncmp (desc, "SQL Server Native Client", 24) == 0) + { + // Compare driver strings lexicographically. Provided that + // Microsoft keeps its naming consistent, we will get the + // correct result. For example, "SQL Server Native Client + // 10.0" (SQL Server 2008) will be greater than "SQL Native + // Client" (SQL Server 2005). Similarly, "SQL Server Native + // Client 11.0" (SQL Server 2012) will be preferred over + // "SQL Server Native Client 10.0" (SQL Server 2008). + // + if (desc > driver_) + driver_ = desc; + } - if (ops.user_specified ()) - user_ = ops.user (); + if (first) + first = false; + } + } - if (ops.password_specified ()) - password_ = ops.password (); + connect_string_ += "DRIVER={"; + connect_string_ += driver_; + connect_string_ += "};"; - if (ops.database_specified ()) + // If necessary, assemble the server address string, depending + // on which protocol we are using. + if (server_.empty ()) { - if (ops.host_specified () || - ops.port_specified () || - ops.service_specified ()) - - throw cli_exception ("--host, --port, or --service options " - "cannot be specified together with " - "--database option"); - db_ = ops.database (); - } - else - { - bool host_present (false); - ostringstream oss; - - if (ops.host_specified () && !ops.host ().empty ()) + switch (protocol_) { - host_present = true; + case protocol_auto: + { + server_ = (host_.empty () ? "localhost" : host_.c_str ()); - host_ = ops.host (); - oss << "//" << host_; + if (!instance_.empty ()) + { + server_ += '\\'; + server_ += instance_; + } - if (ops.port_specified ()) + break; + } + case protocol_tcp: { - port_ = ops.port (); + server_ = "tcp:"; + server_ += (host_.empty () ? "localhost" : host_.c_str ()); + // Port seems to take precedence over instance. For example, + // if you specify both, and the instance name is invalid, the + // Native Client driver still connects without any problems. + // if (port_ != 0) - oss << ":" << port_; + { + ostringstream os; + os << port_; + server_ += ','; + server_ += os.str (); + } + else if (!instance_.empty ()) + { + server_ += '\\'; + server_ += instance_; + } + + break; + } + case protocol_lpc: + { + server_ = "lpc:"; + server_ += (host_.empty () ? "localhost" : host_.c_str ()); + + if (!instance_.empty ()) + { + server_ += '\\'; + server_ += instance_; + } + + break; + } + case protocol_np: + { + server_ = "np:\\\\"; + server_ += (host_.empty () ? "." : host_.c_str ()); + server_ += "\\pipe\\"; + + if (!instance_.empty ()) + { + server_ += "MSSQL$"; + server_ += instance_; + server_ += '\\'; + } + + server_ += "sql\\query"; + break; } } + } - if (ops.service_specified () && !ops.service ().empty ()) - { - service_ = ops.service (); + // The Address attribute seems to be preferred over SERVER. However, + // SQL Server 2005 Native Client only seem to support Address since + // SP1. Since we don't know the exact driver version, for now always + // use SERVER with SQL Server 2005 driver. + // + connect_string_ += (driver_ == "SQL Native Client" + ? "SERVER={" + : "Address={"); + + connect_string_ += server_; + connect_string_ += "};"; - if (host_present) - oss << "/" << service_; - else - oss << service_; + // Add login information. + // + if (user_.empty ()) + // Windows authentication. + // + connect_string_ += "Trusted_Connection=yes;"; + else + { + connect_string_ += "UID={"; + connect_string_ += user_; + connect_string_ += "};"; + + if (!password_.empty ()) + { + connect_string_ += "PWD={"; + connect_string_ += password_; + connect_string_ += "};"; } + } - db_ = oss.str (); + // Add database. + // + if (!db_.empty ()) + { + connect_string_ += "Database={"; + connect_string_ += db_; + connect_string_ += "};"; } - // @@ Quote FQ connect identifier. + // Add any extra connection attributes. // + if (!extra_connect_string_.empty ()) + connect_string_ += extra_connect_string_; } - catch (const cli::exception& e) - { - ostringstream oss; - oss << e; - throw cli_exception (oss.str ()); - } - */ if (factory_.get () == 0) factory_.reset (new connection_pool_factory ()); diff --git a/odb/mssql/database.hxx b/odb/mssql/database.hxx index d20d860..cabdbb4 100644 --- a/odb/mssql/database.hxx +++ b/odb/mssql/database.hxx @@ -32,35 +32,82 @@ namespace odb { class transaction_impl; + enum protocol + { + protocol_auto, + protocol_tcp, // TCP/IP. + protocol_lpc, // Shared memory (local procedure call). + protocol_np // Named pipes. + }; + class LIBODB_MSSQL_EXPORT database: public odb::database { public: - database (const std::string& connect_string, + typedef mssql::protocol protocol_type; + + // Connect to the specified server using the latest available SQL + // Server Native Client ODBC driver by default. If user is empty, + // then use Windows authentication. If database is empty, then + // use the default database for this user. + // + database (const std::string& user, + const std::string& password, + const std::string& db, + const std::string& server, + const std::string& driver = "", + const std::string& extra_connect_string = "", SQLHENV environment = 0, std::auto_ptr factory = std::auto_ptr (0)); - /* + // By default connect to the default instance on localhost using + // default protocol and the latest available SQL Server Native + // Client ODBC driver. If user is empty, then use Windows + // authentication. If database is empty, then use the default + // database for this user. + // database (const std::string& user, const std::string& password, - const std::string& service, + const std::string& db, + protocol_type protocol = protocol_auto, const std::string& host = "", - unsigned int port = 0, - ub2 charset = 0, - ub2 ncharset = 0, - OCIEnv* environment = 0, + const std::string& instance = "", + const std::string& driver = "", + const std::string& extra_connect_string = "", + SQLHENV environment = 0, + std::auto_ptr factory = + std::auto_ptr (0)); + + // Connect using TCP/IP to the specified host and port. If port is + // 0, use the default port (1433). + // + database (const std::string& user, + const std::string& password, + const std::string& db, + const std::string& host, + unsigned int port, + const std::string& driver = "", + const std::string& extra_connect_string = "", + SQLHENV environment = 0, + std::auto_ptr factory = + std::auto_ptr (0)); + + // Connect using a custom SQL Server Native Client ODBC driver + // conection string. + // + database (const std::string& connect_string, + SQLHENV environment = 0, std::auto_ptr factory = std::auto_ptr (0)); // Extract the database parameters from the command line. The // following options are recognized: // - // --user - // --password - // --database - // --service - // --host - // --port + // --user | -U + // --password | -P + // --database | -d + // --server | -S + // --driver // --options-file // // For more information, see the output of the print_usage() function @@ -68,11 +115,10 @@ namespace odb // argv array and the argc count is updated accordingly. This // constructor may throw the cli_exception exception. // - */ - database (int& argc, char* argv[], bool erase = false, + const std::string& extra_connect_string = "", SQLHENV environment = 0, std::auto_ptr = std::auto_ptr (0)); @@ -90,7 +136,6 @@ namespace odb connection (); public: - /* const std::string& user () const { @@ -109,10 +154,10 @@ namespace odb return db_; } - const std::string& - service () const + protocol_type + protocol () const { - return service_; + return protocol_; } const std::string& @@ -121,12 +166,35 @@ namespace odb return host_; } + const std::string& + instance () const + { + return instance_; + } + unsigned int port () const { return port_; } - */ + + const std::string& + server () const + { + return server_; + } + + const std::string& + driver () const + { + return driver_; + } + + const std::string& + extra_connect_string () const + { + return extra_connect_string_; + } const std::string& connect_string () const @@ -170,20 +238,20 @@ namespace odb connection_ (); private: - /* + void + init (); + + private: std::string user_; std::string password_; - std::string db_; - - std::string service_; + protocol_type protocol_; std::string host_; + std::string instance_; unsigned int port_; - - ub2 charset_; - ub2 ncharset_; - */ - + std::string server_; + std::string driver_; + std::string extra_connect_string_; std::string connect_string_; auto_handle auto_environment_; diff --git a/odb/mssql/details/options.cli b/odb/mssql/details/options.cli index 693f644..2158bea 100644 --- a/odb/mssql/details/options.cli +++ b/odb/mssql/details/options.cli @@ -13,35 +13,42 @@ namespace odb { class options { - std::string --user | --username + std::string --user | -U { "", - "PostgreSQL database user." + "SQL Server database user. If not specified, then Windows + authentication is used." }; - std::string --password + std::string --password | -P { "", - "PostgreSQL database password." + "SQL Server database password. Omit this option if the user + password is blank or Windows authentication is used." }; - std::string --database | --dbname + std::string --database | -d { "", - "PostgreSQL database name." + "SQL Server database name. If not specified, then the default + database for this user is used." }; - std::string --host + std::string --server | -S { - "", - "PostgreSQL database host name or address (localhost by default)." + "", + "SQL Server instance address in the + \c{[\i{protocol}\b{:}]\i{host}[\b{\\}\i{instance}][\b{,}\i{port}]} + format, where \ci{protocol} can be \cb{tcp} (TCP/IP), + \cb{lpc} (shared memory), or \cb{np} (named pipe). If not specifid, + then \cb{localhost} is used." }; - std::string --port + std::string --driver { - "", - "PostgreSQL database port number or socket file name extension for - Unix-domain connections." + "", + "SQL Server Native Client ODBC driver name. If not specified, then + the latest available driver is used." }; std::string --options-file -- cgit v1.1