Commit d91c2228 authored by Vadim Zeitlin's avatar Vadim Zeitlin

Add get_dummy_from_{table,clause}() methods

These functions are helpful to allow writing queries not using any
tables portably, i.e. they basically abstract the difference between
"select ... from dual" in Oracle and just "select ..." in almost all the
other backends.
parent 78bedc93
......@@ -6,6 +6,7 @@ Version 4.0.0 differs from 3.2.x in the following ways:
- Provide context of the failure in soci_error::what() which now returns a
longer and more useful message. Use the new get_error_message() method to get
just the brief error message which used to be returned by what().
- Add helpers for generating portable DDL and DML statements.
- Firebird
-- Add SOCI_FIREBIRD_EMBEDDED option to allow building with embedded library.
......
......@@ -87,6 +87,9 @@ The `session` class encapsulates the connection to the database.
void uppercase_column_names(bool forceToUpper);
std::string get_dummy_from_table() const;
std::string get_dummy_from_clause() const;
details::session_backend * get_backend();
std::string get_backend_name() const;
......@@ -124,6 +127,7 @@ This class contains the following members:
* `set_log_stream` and `get_log_stream` functions for setting and getting the current stream object used for basic query logging. By default, it is `NULL`, which means no logging The string value that is actually logged into the stream is one-line verbatim copy of the query string provided by the user, without including any data from the `use` elements. The query is logged exactly once, before the preparation step.
* `get_last_query` retrieves the text of the last used query.
* `uppercase_column_names` allows to force all column names to uppercase in dynamic row description; this function is particularly useful for portability, since various database servers report column names differently (some preserve case, some change it).
* `get_dummy_from_table` and `get_dummy_from_clause()`: helpers for writing portable DML statements, see [DML helpers](statement.html#dml) for more details.
* `get_backend` returns the internal pointer to the concrete backend implementation of the session. This is provided for advanced users that need access to the functionality that is not otherwise available.
*`get_backend_name` is a convenience forwarder to the same function of the backend object.
......
......@@ -7,6 +7,7 @@
* [Transactions](#transactions)
* [Portable DDL](#ddl)
* [Metadata queries](#metadata)
* [Portable DML](#dml)
* [Basic logging support](#logging)
### <a name="preparation"></a> Statement preparation and repeated execution
......@@ -392,6 +393,28 @@ Similarly, to get the description of all columns in the given table:
// ci fields describe each column in turn
}
### <a name="dml"></a> Portable DDL
Only two related functions are currently available in this category:
`get_dummy_from_clause()` can be used to construct select statements that don't
operate on any table in a portable way, as while some databases allow simply
omitting the from clause in this case, others -- e.g. Oracle -- still require
providing some syntactically valid from clause even if it is not used. To use
this function, simply append the result of this function to the statement:
double databasePi;
session << ("select 4*atan(1)" + session.get_dummy_from_clause()),
into(databasePi);
If just the name of the dummy table is needed, and not the full clause, you can
use `get_dummy_from_table()` to obtain it.
Notice that both functions require the session to be connected as their result
depends on the database it is connected to.
### <a name="logging"></a> Basic logging support
The following members of the `session` class support the basic logging functionality:
......@@ -411,4 +434,4 @@ The first two functions allow to set the user-provided output stream object for
Each statement logs its query string before the preparation step (whether explicit or implicit) and therefore logging is effective whether the query succeeds or not. Note that each prepared query is logged only once, independent on how many times it is executed.
The `get_last_query` function allows to retrieve the last used query.
\ No newline at end of file
The `get_last_query` function allows to retrieve the last used query.
......@@ -256,6 +256,8 @@ struct db2_session_backend : details::session_backend
void commit();
void rollback();
virtual std::string get_dummy_from_table() const { return "sysibm.sysdummy1"; }
std::string get_backend_name() const { return "DB2"; }
void clean_up();
......
......@@ -163,6 +163,8 @@ struct empty_session_backend : details::session_backend
void commit();
void rollback();
virtual std::string get_dummy_from_table() const { return std::string(); }
std::string get_backend_name() const { return "empty"; }
void clean_up();
......
......@@ -302,6 +302,8 @@ struct firebird_session_backend : details::session_backend
virtual bool get_next_sequence_value(session & s,
std::string const & sequence, long & value);
virtual std::string get_dummy_from_table() const { return "rdb$database"; }
virtual std::string get_backend_name() const { return "firebird"; }
void cleanUp();
......
......@@ -241,6 +241,10 @@ struct mysql_session_backend : details::session_backend
virtual bool get_last_insert_id(session&, std::string const&, long&);
// Note that MySQL supports both "SELECT 2+2" and "SELECT 2+2 FROM DUAL"
// syntaxes, but there doesn't seem to be any reason to use the longer one.
virtual std::string get_dummy_from_table() const { return std::string(); }
virtual std::string get_backend_name() const { return "mysql"; }
void clean_up();
......
......@@ -301,6 +301,8 @@ struct odbc_session_backend : details::session_backend
virtual bool get_last_insert_id(session & s,
std::string const & table, long & value);
virtual std::string get_dummy_from_table() const;
virtual std::string get_backend_name() const { return "odbc"; }
void configure_connection();
......@@ -325,7 +327,7 @@ struct odbc_session_backend : details::session_backend
};
// Determine the type of the database we're connected to.
database_product get_database_product();
database_product get_database_product() const;
// Return full ODBC connection string.
std::string get_connection_string() const { return connection_string_; }
......@@ -334,7 +336,9 @@ struct odbc_session_backend : details::session_backend
SQLHDBC hdbc_;
std::string connection_string_;
database_product product_;
private:
mutable database_product product_;
};
class SOCI_ODBC_DECL odbc_soci_error : public soci_error
......
......@@ -428,6 +428,8 @@ struct oracle_session_backend : details::session_backend
return "nvl";
}
virtual std::string get_dummy_from_table() const { return "dual"; }
virtual std::string get_backend_name() const { return "oracle"; }
void clean_up();
......
......@@ -376,6 +376,8 @@ struct postgresql_session_backend : details::session_backend
virtual bool get_next_sequence_value(session & s,
std::string const & sequence, long & value);
virtual std::string get_dummy_from_table() const { return std::string(); }
virtual std::string get_backend_name() const { return "postgresql"; }
void clean_up();
......
......@@ -150,6 +150,19 @@ public:
std::string empty_blob();
std::string nvl();
// And some functions to help with writing portable DML statements.
// Get the name of the dummy table that needs to be used in the FROM clause
// of a SELECT statement not operating on any tables, e.g. "dual" for
// Oracle. The returned string is empty if no such table is needed.
std::string get_dummy_from_table() const;
// Returns a possibly empty string that needs to be used as a FROM clause
// of a SELECT statement not operating on any tables, e.g. " FROM DUAL"
// (notice the leading space).
std::string get_dummy_from_clause() const;
// Sets the failover callback object.
void set_failover_callback(failover_callback & callback);
......
......@@ -436,7 +436,9 @@ public:
{
return "coalesce";
}
virtual std::string get_dummy_from_table() const = 0;
void set_failover_callback(failover_callback & callback, session & sql)
{
failoverCallback_ = &callback;
......
......@@ -296,6 +296,8 @@ struct sqlite3_session_backend : details::session_backend
return "x\'\'";
}
virtual std::string get_dummy_from_table() const { return std::string(); }
virtual std::string get_backend_name() const { return "sqlite3"; }
void clean_up();
......
......@@ -253,6 +253,37 @@ bool odbc_session_backend::get_last_insert_id(
return true;
}
std::string odbc_session_backend::get_dummy_from_table() const
{
std::string table;
switch ( get_database_product() )
{
case prod_firebird:
table = "rdb$database";
break;
case prod_oracle:
table = "dual";
break;
case prod_mssql:
case prod_mysql:
case prod_sqlite:
case prod_postgresql:
// No special dummy table needed.
break;
// These cases are here just to make the switch exhaustive, we
// can't really do anything about them anyhow.
case prod_unknown:
case prod_uninitialized:
break;
}
return table;
}
void odbc_session_backend::reset_transaction()
{
SQLRETURN rc = SQLSetConnectAttr( hdbc_, SQL_ATTR_AUTOCOMMIT,
......@@ -301,7 +332,7 @@ odbc_blob_backend * odbc_session_backend::make_blob_backend()
}
odbc_session_backend::database_product
odbc_session_backend::get_database_product()
odbc_session_backend::get_database_product() const
{
// Cache the product type, it's not going to change during our life time.
if (product_ != prod_uninitialized)
......
......@@ -450,6 +450,22 @@ std::string session::nvl()
return backEnd_->nvl();
}
std::string session::get_dummy_from_table() const
{
ensureConnected(backEnd_);
return backEnd_->get_dummy_from_table();
}
std::string session::get_dummy_from_clause() const
{
std::string clause = get_dummy_from_table();
if (!clause.empty())
clause.insert(0, " from ");
return clause;
}
void session::set_failover_callback(failover_callback & callback)
{
ensureConnected(backEnd_);
......
......@@ -4171,6 +4171,17 @@ TEST_CASE_METHOD(common_tests, "Blank padding", "[core][insert][exception]")
CHECK(tvarchar == test1);
}
TEST_CASE_METHOD(common_tests, "Select without table", "[core][select][dummy_from]")
{
soci::session sql(backEndFactory_, connectString_);
int plus17;
sql << ("select abs(-17)" + sql.get_dummy_from_clause()),
into(plus17);
CHECK(plus17 == 17);
}
} // namespace test_cases
} // namespace tests
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment