Commit 2c45f2bc authored by Mateusz Loskot's avatar Mateusz Loskot

[docs] Fix issues reported by markdownlint

parent 46071f5f
{
"default": true,
"MD013": false,
"MD026": false,
"MD024": false,
"MD029": {"style": "ordered"}
}
......@@ -27,7 +27,7 @@ The SOCI DB2 backend requires IBM DB2 Call Level Interface (CLI) library.
On Unix, before using the DB2 backend please make sure, that you have sourced DB2 profile into your environment:
```sh
```console
. ~/db2inst1/sqllib/db2profile
```
......
......@@ -22,7 +22,7 @@ The SOCI Oracle backend requires Oracle's `libclntsh` client library. Depending
Note that the SOCI library itself depends also on `libdl`, so the minimum set of libraries needed to compile a basic client program is:
```sh
```console
-lsoci_core -lsoci_oracle -ldl -lclntsh -lnnz10
```
......
......@@ -29,7 +29,7 @@ The SOCI PostgreSQL backend requires PostgreSQL's `libpq` client library.
Note that the SOCI library itself depends also on `libdl`, so the minimum set of libraries needed to compile a basic client program is:
```sh
```console
-lsoci_core -lsoci_postgresql -ldl -lpq
```
......
......@@ -6,16 +6,18 @@ And, if this is still not enough, you can use the backend-specific methods direc
## Affected rows
It can be useful to know how many rows were affected by the last SQL statement, most often when using <tt>INSERT</tt>, <tt>UPDATE</tt> or <tt>DELETE</tt>.
It can be useful to know how many rows were affected by the last SQL statement, most often when using `INSERT`, `UPDATE` or `DELETE`.
SOCI provides `statement::get_affected_rows()` method allowing to do this:
statement st = (sql.prepare << "update some_table ...");
st.execute(true);
```cpp
statement st = (sql.prepare << "update some_table ...");
st.execute(true);
if ( !st.get_affected_rows() )
{
... investigate why no rows were modified ...
}
if ( !st.get_affected_rows() )
{
... investigate why no rows were modified ...
}
```
### Portability note
......@@ -35,24 +37,26 @@ If you know which kind of database you use, you may use only one of them: when w
However if you use multiple SOCI backends or even just a single ODBC backend but support connecting to databases of different types, you actually must use both of them in the following way to insert a row:
long id;
statement st;
if ( sql.get_next_sequence_value("table_sequence", id) )
{
st << "insert into table(id, f1, f2) values(:id, :f1, :f2)",
use(id), use(f1), use(f2);
}
else
{
// We're not using sequences, so don't specify the value,
// it will be automatically generated by the database on insert.
st << "insert into table(f1, f2) value(:f1, :f2)",
use(f1), use(f2);
// If the ID used for the above row is needed later, get it:
if ( !sql.get_last_insert_id("table", id) )
... unexpected error, handle appropriately ...
}
```cpp
long id;
statement st;
if ( sql.get_next_sequence_value("table_sequence", id) )
{
st << "insert into table(id, f1, f2) values(:id, :f1, :f2)",
use(id), use(f1), use(f2);
}
else
{
// We're not using sequences, so don't specify the value,
// it will be automatically generated by the database on insert.
st << "insert into table(f1, f2) value(:f1, :f2)",
use(f1), use(f2);
// If the ID used for the above row is needed later, get it:
if ( !sql.get_last_insert_id("table", id) )
... unexpected error, handle appropriately ...
}
```
### Portability note
......@@ -68,12 +72,14 @@ library does *not* prevent the client applications from reaching into the low-le
Most of the SOCI classes have the `getBackEnd` method, which returns the pointer to the actual backend object that implements the given functionality.
The knowledge of the actual backend allows the client application to get access to all low-level details that are involved.
blob b(sql);
```cpp
blob b(sql);
oracle_session_back_end * sessionBackEnd = static_cast<oracle_session_back_end *>(sql.get_back_end());
oracle_blob_back_end * blobBackEnd = static_cast<oracle_blob_back_end *>(b.get_back_end());
oracle_session_back_end * sessionBackEnd = static_cast<oracle_session_back_end *>(sql.get_back_end());
oracle_blob_back_end * blobBackEnd = static_cast<oracle_blob_back_end *>(b.get_back_end());
OCILobDisableBuffering(sessionBackEnd->svchp_, sessionBackEnd->errhp_, blobBackEnd->lobp_);
OCILobDisableBuffering(sessionBackEnd->svchp_, sessionBackEnd->errhp_, blobBackEnd->lobp_);
```
The above example retrieves the `rowid` ("something" that identifies the row in the table) from the table and uses the `get_back_end` function to extract the actual object that implements this functionality.
Assuming that it is the `"postgresql"` backend which is in use, the downcast is performed to use the `postgresql_rowid_back_end` interface to get the actual OID value that is a physical, low-level implementation of row identifier on PostgreSQL databases.
......
......@@ -9,11 +9,13 @@ SOCI provides mechanisms to bind local buffers for input and output data.
The `into` expression is used to add binding information to
the statement:
int count;
sql << "select count(*) from person", into(count);
```cpp
int count;
sql << "select count(*) from person", into(count);
string name;
sql << "select name from person where id = 7", into(name);
string name;
sql << "select name from person where id = 7", into(name);
```
In the above examples, some data is retrieved from the database and transmitted *into* the given local variable.
......@@ -23,40 +25,48 @@ There should be as many `into` elements as there are expected columns in the res
The `use` expression associates the SQL placeholder (written with colon) with the local data:
int val = 7;
sql << "insert into numbers(val) values(:val)", use(val);
```cpp
int val = 7;
sql << "insert into numbers(val) values(:val)", use(val);
```
In the above statement, the first "val" is a column name (assuming that there is appropriate table `numbers` with this column), the second "val" (with colon) is a placeholder and its name is ignored here, and the third "val" is a name of local variable.
To better understand the meaning of each "val" above, consider also:
int number = 7;
sql << "insert into numbers(val) values(:blabla)", use(number);
```cpp
int number = 7;
sql << "insert into numbers(val) values(:blabla)", use(number);
```
Both examples above will insert the value of some local variable into the table `numbers` - we say that the local variable is *used* in the SQL statement.
There should be as many `use` elements as there are parameters used in the SQL query.
### Object lifetime and immutability:
### Object lifetime and immutability
SOCI assumes that local variables provided as `use` elements live at least as long at it takes to execute the whole statement.
In short statement forms like above, the statement is executed *sometime* at the end of the full expression and the whole process is driven by the invisible temporary object handled by the library.
If the data provided by user comes from another temporary variable, it might be possible for the compiler to arrange them in a way that the user data will be destroyed *before* the statement will have its chance to execute, referencing objects that no longer exist:
// Dangerous code!
```cpp
// Dangerous code!
string getNameFromSomewhere();
string getNameFromSomewhere();
sql << "insert into person(name) values(:n)", use(getNameFromSomewhere());
sql << "insert into person(name) values(:n)", use(getNameFromSomewhere());
```
In the above example, the data passed to the database comes from the temporary variable that is a result of call to `getNameFromSomewhere` - this should be avoided and named variables should be used to ensure safe lifetime relations:
// Safe code
string getNameFromSomewhere();
```cpp
// Safe code
string name = getNameFromSomewhere();
sql << "insert into person(name) values(:n)", use(name);
string getNameFromSomewhere();
string name = getNameFromSomewhere();
sql << "insert into person(name) values(:n)", use(name);
```
It is still possible to provide `const` data for use elements.
......@@ -64,12 +74,14 @@ Note that some database servers, like Oracle, allow PL/SQL procedures to modify
The above example can be ultimately written in the following way:
// Safe and efficient code
```cpp
// Safe and efficient code
string getNameFromSomewhere();
string getNameFromSomewhere();
string const& name = getNameFromSomewhere();
sql << "insert into person(name) values(:n)", use(name);
string const& name = getNameFromSomewhere();
sql << "insert into person(name) values(:n)", use(name);
```
### Portability note
......@@ -80,14 +92,16 @@ In order to compile SOCI with those old client libraries, define the `SOCI_POSTG
If there is more output or input "holes" in the single statement, it is possible to use many `into` and `use` expressions, separated by commas, where each expression will be responsible for the consecutive "hole" in the statement:
string firstName = "John", lastName = "Smith";
int personId = 7;
```cpp
string firstName = "John", lastName = "Smith";
int personId = 7;
sql << "insert into person(id, firstname, lastname) values(:id, :fn, :ln)",
sql << "insert into person(id, firstname, lastname) values(:id, :fn, :ln)",
use(personId), use(firstName), use(lastName);
sql << "select firstname, lastname from person where id = :id",
sql << "select firstname, lastname from person where id = :id",
into(firstName), into(lastName), use(personId);
```
In the code above, the order of "holes" in the SQL statement and the order of `into` and `use` expression should match.
......@@ -97,18 +111,22 @@ The SQL placeholders that have their names (with colon) can be bound by name to
This explicit naming allows to use different order of elements:
string firstName = "John", lastName = "Smith";
int personId = 7;
sql << "insert into person(id, firstname, lastname) values(:id, :fn, :ln)",
use(firstName, "fn"), use(lastName, "ln"), use(personId, "id");
```cpp
string firstName = "John", lastName = "Smith";
int personId = 7;
sql << "insert into person(id, firstname, lastname) values(:id, :fn, :ln)",
use(firstName, "fn"), use(lastName, "ln"), use(personId, "id");
```
or bind the same local data to many "holes" at the same time:
string addr = "...";
sql << "update person"
" set mainaddress = :addr, contactaddress = :addr"
" where id = 7",
use(addr, "addr");
```cpp
string addr = "...";
sql << "update person"
" set mainaddress = :addr, contactaddress = :addr"
" where id = 7",
use(addr, "addr");
```
### Portability notes
......@@ -128,30 +146,36 @@ This allows the database backend to optimize access and data transfer and benefi
It is possible to `use` the vector as a data source:
std::vector<int> v;
// ...
sql << "insert into t ...", use(v);
```cpp
std::vector<int> v;
// ...
sql << "insert into t ...", use(v);
```
as well as a destination:
std::vector<int> v;
v.resize(100);
sql << "select ...", into(v);
```cpp
std::vector<int> v;
v.resize(100);
sql << "select ...", into(v);
```
In the latter case the initial size of the vector defines the maximum number of data elements that the user is willing to accept and after executing the query the vector will be automatically resized to reflect that actual number of rows that were read and transmitted.
That is, the vector will be automatically shrunk if the amount of data that was available was smaller than requested.
It is also possible to operate on the chosen sub-range of the vector:
std::vector<int> v;
// ...
std::size_t begin = ...;
std::size_t end = ...;
sql << "insert into t ...", use(v, begin, end);
```cpp
std::vector<int> v;
// ...
std::size_t begin = ...;
std::size_t end = ...;
sql << "insert into t ...", use(v, begin, end);
// or:
// or:
sql << "select ...", into(v, begin, end);
sql << "select ...", into(v, begin, end);
```
Above, only the sub-range of the vector is used for data transfer and in the case of `into` operation, the `end` variable will be automatically adjusted to reflect the amount of data that was actually transmitted, but the vector object as a whole will retain its initial size.
......
......@@ -6,10 +6,12 @@ The integration with Boost types is optional and is *not* enabled by default, wh
In order to enable the support for any of the above types, the user needs to either include one of these headers:
#include <boost-optional.h>
#include <boost-tuple.h>
#include <boost-fusion.h>
#include <boost-gregorian-date.h>
```cpp
#include <boost-optional.h>
#include <boost-tuple.h>
#include <boost-fusion.h>
#include <boost-gregorian-date.h>
```
or to define the `SOCI_USE_BOOST` macro before including the `soci.h` main header file.
......@@ -21,50 +23,54 @@ The `boost::optional<T>` objects can be used everywhere where the regular user p
Example:
boost::optional<string> name;
sql << "select name from person where id = 7", into(name);
if (name.is_initialized())
{
// OK, the name was retrieved and is not-null
cout << "The name is " << name.get();
}
else
{
// the name is null
}
```cpp
boost::optional<string> name;
sql << "select name from person where id = 7", into(name);
if (name.is_initialized())
{
// OK, the name was retrieved and is not-null
cout << "The name is " << name.get();
}
else
{
// the name is null
}
```
The `boost::optional<T>` objects are fully supported for both `into` and `use` elements, in both single and vector forms. They can be also used for user-defined data types.
## Boost.Tuple
`boost::tuple<T1, ...>` allows to work with whole rows of information and in some cases can be more convenient to use than the more dynamically-oriented `row` type.
boost::tuple<string, string, int> person;
```cpp
boost::tuple<string, string, int> person;
sql << "select name, phone, salary from persons where ...",
sql << "select name, phone, salary from persons where ...",
into(person);
```
Tuples are supported for both `into` and `use` elements.
They can be used with `rowset` as well.
Tuples can be also composed with `boost::optional<T>`
boost::tuple<string, boost::optional<string>, int> person;
```cpp
boost::tuple<string, boost::optional<string>, int> person;
sql << "select name, phone, salary from persons where ...",
sql << "select name, phone, salary from persons where ...",
into(person);
if (person.get<1>().is_initialized())
{
// the given person has a phone number
}
else
{
// this person does not have a phone number
}
if (person.get<1>().is_initialized())
{
// the given person has a phone number
}
else
{
// this person does not have a phone number
}
```
## Boost.Fusion
......
......@@ -9,11 +9,15 @@ which meaning is backend-dependent.
Example:
session sql(oracle, "service=orcl user=scott password=tiger");
```cpp
session sql(oracle, "service=orcl user=scott password=tiger");
```
Another example might be:
session sql(postgresql, "dbname=mydb");
```cpp
session sql(postgresql, "dbname=mydb");
```
Above, the `sql` object is a local (automatic) object that encapsulates the connection.
......@@ -25,11 +29,15 @@ Dynamically loadable backends are compiled as shared libraries and allow to sele
The usage is similar to the above, but instead of providing the factory object, the backend name is expected:
session sql("postgresql", "dbname=mydb");
```cpp
session sql("postgresql", "dbname=mydb");
```
For convenience, the URL-like form that combines both the backend name with connection parameters is supported as well:
session sql("postgresql://dbname=mydb");
```cpp
session sql("postgresql://dbname=mydb");
```
The last two constructors described above try to locate the shared library with the name `libsoci_ABC.so` (or `libsoci_ABC.dll` on Windows), where ABC is the backend name.
In the above examples, the expected library name will be `libsoci_postgresql.so` for Unix-like systems.
......@@ -39,9 +47,11 @@ Using this constructor is the only way to pass any non-default options to the ba
For example, to suppress any interactive prompts when using ODBC backend you could do:
connection_parameters parameters("odbc", "DSN=mydb");
parameters.set_option(odbc_option_driver_complete, "0" /* SQL_DRIVER_NOPROMPT */);
session sql(parameters);
```cpp
connection_parameters parameters("odbc", "DSN=mydb");
parameters.set_option(odbc_option_driver_complete, "0" /* SQL_DRIVER_NOPROMPT */);
session sql(parameters);
```
Notice that you need to `#include<soci-odbc.h>` to obtain the option name declaration.
The existing options are described in the backend-specific part of the documentation.
......@@ -60,27 +70,31 @@ The run-time selection of backends is also supported with libraries linked stati
Each backend provides a separate function of the form `register_factory_*name*`, where `*name*` is a backend name. Thus:
extern "C" void register_factory_postgresql();
// ...
register_factory_postgresql();
session sql("postgresql://dbname=mydb");
```cpp
extern "C" void register_factory_postgresql();
// ...
register_factory_postgresql();
session sql("postgresql://dbname=mydb");
```
The above example registers the backend for PostgreSQL and later creates the session object for that backend.
This form is provided for those projects that prefer static linking but still wish to benefit from run-time backend selection.
An alternative way to set up the session is to create it in the disconnected state and connect later:
session sql;
```cpp
session sql;
// some time later:
sql.open(postgresql, "dbname=mydb");
// some time later:
sql.open(postgresql, "dbname=mydb");
// or:
sql.open("postgresql://dbname=mydb");
// or:
sql.open("postgresql://dbname=mydb");
// or also:
connection_parameters parameters("postgresql", "dbname=mydb");
sql.open(parameters);
// or also:
connection_parameters parameters("postgresql", "dbname=mydb");
sql.open(parameters);
```
The rules for backend naming are the same as with the constructors described above.
......@@ -109,28 +123,32 @@ The following backends are also available, with various levels of completeness:
The `failover_callback` interface can be used as a callback channel for notifications of events that are automatically processed when the session is forcibly closed due to connectivity problems. The user can override the following methods:
// Called when the failover operation has started,
// after discovering connectivity problems.
virtual void started();
// Called after successful failover and creating a new connection;
// the sql parameter denotes the new connection and allows the user
// to replay any initial sequence of commands (like session configuration).
virtual void finished(session & sql);
// Called when the attempt to reconnect failed,
// if the user code sets the retry parameter to true,
// then new connection will be attempted;
// the newTarget connection string is a hint that can be ignored
// by external means.
virtual void failed(bool & retry, std::string & newTarget);
// Called when there was a failure that prevents further failover attempts.
virtual void aborted();
```cpp
// Called when the failover operation has started,
// after discovering connectivity problems.
virtual void started();
// Called after successful failover and creating a new connection;
// the sql parameter denotes the new connection and allows the user
// to replay any initial sequence of commands (like session configuration).
virtual void finished(session & sql);
// Called when the attempt to reconnect failed,
// if the user code sets the retry parameter to true,
// then new connection will be attempted;
// the newTarget connection string is a hint that can be ignored
// by external means.
virtual void failed(bool & retry, std::string & newTarget);
// Called when there was a failure that prevents further failover attempts.
virtual void aborted();
```
The user-provided callback implementation can be installed (or reset) with:
sql.set_failover_callback(myCallback);
```cpp
sql.set_failover_callback(myCallback);
```
### Portability note
......
......@@ -3,17 +3,19 @@
All DB-related errors manifest themselves as exceptions of type `soci_error`, which is derived from `std::runtime_error`.
This allows to handle database errors within the standard exception framework:
int main()
```cpp
int main()
{
try
{
try
{
// regular code
}
catch (std::exception const & e)
{
cerr << "Bang! " << e.what() << endl;
}
// regular code
}
catch (std::exception const & e)
{
cerr << "Bang! " << e.what() << endl;
}
}
```
The `soci_error` class exposes two public functions:
......@@ -31,61 +33,67 @@ If the error category is not recognized by the backend, it defaults to `unknown`
The MySQL backend can throw instances of the `mysql_soci_error`, which is publicly derived from `soci_error` and has an additional public `err_num_` member containing the MySQL error code (as returned by `mysql_errno()`):
int main()
```cpp
int main()
{
try
{
// regular code
}
catch (soci::mysql_soci_error const & e)
{
try
{
// regular code
}
catch (soci::mysql_soci_error const & e)
{
cerr << "MySQL error: " << e.err_num_
<< " " << e.what() << endl;
}
catch (soci::exception const & e)
{
cerr << "Some other error: " << e.what() << endl;
}
cerr << "MySQL error: " << e.err_num_
<< " " << e.what() << endl;
}
catch (soci::exception const & e)
{
cerr << "Some other error: " << e.what() << endl;
}
}
```
## Oracle
The Oracle backend can also throw the instances of the `oracle_soci_error`, which is publicly derived from `soci_error` and has an additional public `err_num_` member containing the Oracle error code:
int main()
```cpp
int main()
{
try
{
// regular code
}
catch (soci::oracle_soci_error const & e)
{
cerr << "Oracle error: " << e.err_num_
<< " " << e.what() << endl;
}
catch (soci::exception const & e)
{
try
{
// regular code
}
catch (soci::oracle_soci_error const & e)
{
cerr << "Oracle error: " << e.err_num_
<< " " << e.what() << endl;
}
catch (soci::exception const & e)
{
cerr << "Some other error: " << e.what() << endl;
}
cerr << "Some other error: " << e.what() << endl;
}
}
```
## PostgreSQL
The PostgreSQL backend can also throw the instances of the `postgresql_soci_error`, which is publicly derived from `soci_error` and has an additional public `sqlstate()` member function returning the five-character "SQLSTATE" error code:
int main()
```cpp
int main()
{
try
{
// regular code
}
catch (soci::postgresql_soci_error const & e)
{
cerr << "PostgreSQL error: " << e.sqlstate()
<< " " << e.what() << endl;
}
catch (soci::exception const & e)
{
try
{
// regular code
}
catch (soci::postgresql_soci_error const & e)
{
cerr << "PostgreSQL error: " << e.sqlstate()
<< " " << e.what() << endl;
}
catch (soci::exception const & e)
{
cerr << "Some other error: " << e.what() << endl;
}
cerr << "Some other error: " << e.what() << endl;
}
}
```
This diff is collapsed.
# SOCI - The C++ Database Access Library
SOCI is a database access library written in C++ that makes an illusion of embedding
SQL queries in the regular C++ code, staying entirely within the Standard C++.
......@@ -8,37 +10,42 @@ If you find existing libraries too difficult for your needs or just distracting,
The simplest motivating code example for the SQL query that is supposed to retrieve a single row is:
int id = ...;
string name;
int salary;
```cpp
int id = ...;
string name;
int salary;
sql << "select name, salary from persons where id = " << id,
sql << "select name, salary from persons where id = " << id,
into(name), into(salary);
```
## Basic ORM
The following benefits from extensive support for object-relational mapping:
int id = ...;
Person p;
```cpp
int id = ...;
Person p;
sql << "select first_name, last_name, date_of_birth "
"from persons where id = " << id,
into(p);
sql << "select first_name, last_name, date_of_birth "
"from persons where id = " << id, into(p);
```
## Integrations
Integration with STL is also supported:
Rowset<string> rs = (sql.prepare << "select name from persons");
std::copy(rs.begin(), rs.end(), std::ostream_iterator<std::string>(std::cout, "\n"));
```cpp
Rowset<string> rs = (sql.prepare << "select name from persons");
std::copy(rs.begin(), rs.end(), std::ostream_iterator<std::string>(std::cout, "\n"));
```
SOCI offers also extensive [integration with Boost](boost.md) datatypes (optional, tuple and fusion) and flexible support for user-defined datatypes.
## Database Backends
Starting from its 2.0.0 release, SOCI uses the plug-in architecture for backends