introduction to a simple
C++ database access library
SOCI
London C++ Meetup
Slides licensed under the Creative Commons Attribution License
Mateusz.Loskot.NET
C++ hacker, geospatial developer
Open Source developer
Boost.Geometry, GDAL, libLAS, PostGIS, SOCI, ...
Charter Member @OSGeo
Senior Programmer @Cadcorp
SOCI
Sochi
(IPA: [ˈsotɕɪ])
Photo by Mikhail Mordasov | Copyright (C) Mikhail Mordasov (AFP - Getty Images)
(pre-)HISTORY
Simple Oracle Call Interface
- 2004-09: 1.0.0
- Posted to comp.lang.c++.moderated
HISTORY
Simple Open (Database) Call Interface
- 2006-01: 2.0.0 PostgreSQL backend
- 2006-03: 2.0.1 MySQL and SQLite backends
- (@mloskot joined the project)
- 2006-12: 2.2.0 ODBC and Firebird backends
- 2008-07: 3.0.0 New naming convention
- 2011-10: 3.1.0 Ada bindings
- 2013-03: 3.2.0 DB2 backend, moved to GitHub
- 2013-04: 3.2.1 Bugfix release (current)
- ????-??: 4.0.0 Lots of plans!
Canonical Machinery
class statement {public:statement(session& s);void exchange(/*...*/);void define_and_bind();void prepare(string q);bool execute();bool fetch();};
class session {public:// open connectionsession(string c);void begin();void commit();void rollback();};
CANONICAL MACHINERY STEP-BY-STEP
-
Connect
-
Collect exchange variables
-
Bind*
local objects to SQL
placeholders
- Define* local objects for query output
-
Bind*
local objects to SQL
placeholders
- Prepare statement
-
Execute statement
-
Fetch data from result set
* bind , define terminology from Oracle Call Interface heritage; SOCI documentation use binding for directions.
CANONICAL MACHINERY IN ACTION
std::string name; // define variableint id = 101; // bind variablesession db("dbname=test");statement st(db);st.exchange(id); // bind inputst.exchange(name); // define outputst.define_and_bind();st.prepare("SELECT name FROM person ""WHERE id = ?");st.execute();while (st.fetch()) {std::cout << name << std::endl;}
What's the 'canonical' problem?
Why that is a problem?
express yourself
I'm expressin' with my full capabilities
Now I'm livin' in correctional facilities
...I get straight, meditate, like a Buddhist
...But my technique is very necessary
N.W.A - Express Youself, 1988
The SYNTAX nirvana
int v = 7;std::cout << v;
C++ natural syntax
syntax-first development
XP and TDD inspired
holy grail
person p;session sql(...);sql << "SELECT name,age FROM person",into(p);
or, monty python?
C++ techniques
what's the SOCI magic?
anatomy of database session
connect on construction
using namespace soci;session sql([BACKEND], [CONNECTION STRING]);
connect in two steps
session sql;sql.open([BACKEND], [CONNECTION STRING]);
SESSION BACKEND
Database-specific connection session
string connstr("dbname=test user=skippy");// uses factory object providedsession sql(postgresql, connstr);// loads backend by name at run-timesession sql("postgresql", connstr);session sql("postgresql://dbname=test");
advanced backend parameters
connection_parameters p("odbc","DSN=test");// Force SQL_DRIVER_NOPROMPT p.set_option(odbc_option_driver_complete,"0");session sql(p);
SIMPLE SQL statements
Executed only once, immediately
session sql;sql << "DROP TABLE employee";
string table_name("employee");sql << "DROP TABLE " << table_name;
int id = 7;sql << "DELETE FROM "<< table_name<< " WHERE id="<< id;
anatomy of the staTement SYNTAX
sql << "DROP TABLE " << table_name;
sql.once << "DROP TABLE " << table_name;
class session {public:once_type once;template <typename T>once_temp_type operator<<(T t) {return once << t;}};
ANATOMY OF the ONCE
sql.once << "DROP TABLE " << table_name;
class once_type { session* session_; public: template <typename T> once_temp_type operator<<(T t) {once_temp_type once_temp(*session_); return once_temp << t;} };
anatomy of the temporary
sql.once << "DROP TABLE " ? << table_name ?;
class once_temp_type {ref_counted_statement* st_;public:template <typename T>once_temp_type& operator<<(T t) {// push into buffer, stringstreamst_->accumulate(t);return *this;}~once_temp_type() { st_->dec_ref(); }};
death of the TEMPORARY
sql.once << "DROP TABLE " << table_name; ?
class ref_counted_statement {statement st_;public:void def_ref() {if (--counter == 0) final_action();}void final_action() {st_.prepare(get_query());st_.define_and_bind();st_.execute();}};
statement execution summary
- sql << "..." << ... ;
- sql. once << "..." << ... ;
- start up the temporary object
-
pass the temporary through expression
- collecting all streamable elements
- reach end of the expression
-
destroy the temporary object
- prepare statement
- execute statement
- exchange data
- clean up
C++11 caveats
noexcept(false)
Thanks to Roger Orr and Anthony Williams
for reporting this issue in SOCI.
ELEMENTS of data exchange
- Bind local output data (aka define)
- Bind local input data (aka bind)
- Bind objects of static types
- Bind objects in dynamic result set
-
Bind objects of
user-defined
types
- Object-Relational Mapping (ORM)
-
Handle
NULL
and data conditions
SQL with DATA exchange
string name; // output variablesql << "SELECT name FROM person",into(name);
int id = 7; // input variablesql << "SELECT name FROM personWHERE id=:i", into(name), use(id);
indicator ind;sql << "SELECT name FROM person",into(name, ind);if (ind == soci::i_null) { ... }
ANATOMY of BINDING
class once_temp_type {ref_counted_statement* st_;public:once_temp_type& operator,(into_type_ptr i){// push onto statement intosst_->exchange(i);}once_temp_type& operator,(use_type_ptr u){// push onto statement usesst_->exchange(u);}};
anatomy of into
sql << "SELECT name FROM p", into(name);
template <typename T>into_type_ptr into(T& t) {return do_into(t, typenameexchange_traits<T>::type_family());}
template <typename T>into_type_ptr do_into(T& t,basic_type_tag) {return into_type_ptr(new into_type<T>(t));}
anatomy of INTO_TYPE
class into_type {// statement backend define_by_posvoid define(statement* s, int& position);// convert data to exchange typevoid post_fetch();void* data_;indicator* ind_;exchange_type type_;};
machinery of into
template <typename T>into_type_ptr do_into(T& t,basic_type_tag);
template <typename T>into_type_ptr do_into(T& t,indicator& ind,basic_type_tag);
template <typename T>into_type_ptr do_into(T& t,user_type_tag);
template <typename T>into_type_ptr do_into(T& t,indicator& ind,user_type_tag);
anatomy of use
sql<<"SELECT n FROM p WHERE id=:i", use(id);
template <typename T>use_type_ptr use(T& t, string name) {return do_use(t, name, typenameexchange_traits<T>::type_family());}
template <typename T>use_type_ptr do_use(T& t, string name, basic_type_tag) {return use_type_ptr(new use_type<T>(t, name));}
anatomy of use_type
class use_type {// statement backend bind_by_posvoid bind(statement* s, int& position);// convert data to exchange typevoid pre_use();void* data_;indicator* ind_;exchange_type type_;string name_;};
machinery of use
template <typename T>use_type_ptr do_use(T& t, string name, basic_type_tag);
template <typename T>use_type_ptr do_use(T& t,indicator& ind, string name, basic_type_tag);
template <typename T>use_type_ptr do_use(T& t,string name, user_type_tag);
template <typename T>use_type_ptr do_use(T& t,indicator& ind, string name, user_type_tag);
static types for INTO AND USE
-
char, char*, char[], std::string
- (unsigned) short, int, long long
- double
- std::tm
- std::vector<T> for T as above
Binding dynamic result set
row r;sql << "SELECT name,age FROM p", into(r);
for (size_t i=0; i<r.size(); ++i) { column_properties& c = r.get_properties();if (c.get_data_type() == dt_string) string name = r.get<string>(i); else if (c.get_data_type() == dt_integer) int age = r.get<int>(i); }
string name; int age;r >> name >> age;
AVAILABLE (LIMITED) dt_* mappingS
SOCI Base Types
- dt_string
- dt_integer
- dt_long_long
- dt_unsigned_long_long
- dt_double
- dt_date
user-defined types support
template<> struct type_conversion<MyX> {typedef string base_type;void from_base(string s,indicator i,MyX& x){if (i == i_null) { ... }x.from_string(s); // convert}void to_base(MyX x,string& s,indicator& i){s = x.to_string();// converti = i_ok;}};
object-relational mapping
database table
CREATE TABLE person (name VARCHAR(60),age INT)
c++ type
struct Person {string name;int age;};
MAP soci::values via conversion
template<> struct type_conversion<Person>{typedef values base_type;from_base(values v,indicator i,Person& p){p.name = v.get<string>();p.age = v.get<int>();}to_base(Person p,values& v,indicator& i){v.set("name", p.name);v.set("age", p.age);i = i_ok;}};
template<> struct type_conversion<MyX> {typedef string base_type;void from_base(string s,indicator i,MyX& x){if (i == i_null) { ... }x.from_string(s); // convert}void to_base(MyX x,string& s,indicator& i){s = x.to_string();// converti = i_ok;}};
THERE IS MORE
Prepared statements and repeated execution
int i;statement st = (sql.prepare<< "INSERT INTO n(v) VALUES(:v)",use(i));for (i = 0 i < 100; ++i){st.execute(true);}
soci::rowset for SELECT QUERIES
statement wrapper with STL-like iterator interface
rowset<row> rs = (sql.prepare<< "SELECT name,age FROM person");for (auto it=rs.begin(); it!=rs.end(); ++it){// dynamic data extraction from each rowcout << "name: " << it->get<string>(0)<< ",age: " << it->get<int>(1)<< endl;}
THERE is EVEN more
Thank you!
SOCI
By Mateusz Loskot
SOCI
Introduction to SOCI - the C++ database access library
- 21,518