0% found this document useful (0 votes)
7 views35 pages

Working With Databases in C++ An Introduction by Dane Bulat Medium

This article provides an introduction to working with databases in C++ using the SOCI library and MySQL server. It covers the installation of necessary libraries, setting up a MySQL database environment, and writing a simple C++ application to interact with the database. The article also explains how to compile and run the application, making it a comprehensive guide for beginners in database programming with C++.

Uploaded by

rpgonlinevyneeh
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views35 pages

Working With Databases in C++ An Introduction by Dane Bulat Medium

This article provides an introduction to working with databases in C++ using the SOCI library and MySQL server. It covers the installation of necessary libraries, setting up a MySQL database environment, and writing a simple C++ application to interact with the database. The article also explains how to compile and run the application, making it a comprehensive guide for beginners in database programming with C++.

Uploaded by

rpgonlinevyneeh
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 35

Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

dane-bulat.medium.com

Working with Databases in C++: An


Introduction - Dane Bulat - Medium
Dane Bulat

36–46 minutos

Using the SOCI library and MySQL database


server

Introduction

This article will serve as an introduction to working with databases


in C++. After reading this article, you will have accumulated

1 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

enough knowledge to start writing C++ applications that interact


with your favourite database backends.

The full pipeline of developing an application will be explored —


from installing necessary libraries to compiling our final application.
More specifically, this article will detail how to:

• Download and Install the SOCI library.


This section will describe how to download SOCI and install its
libraries and header files on your system.

• Set up a database environment on a MySQL server.


In order to safely access the MySQL server from our C++
application, we will create a user and assign that user a role. This
role will describe what operations the user can perform and which
databases are accessible on the server.

• Write a simple C++ SOCI application.


This section will detail how to use the SOCI API to insert and
retrieve data from the database.

• Compile and run our C++ application.


This section will demonstrate how to compile and run our C++
application from the command line using the GCC compiler.

What is SOCI?

SOCI stands for Simple Open (Database) Call Interface and is a


database access library written in C++. Several database
backends have been developed for SOCI, meaning that you can
use one simple API provided by SOCI to work with a range of
databases.

For example, it’s entirely possible to write a single C++ application

2 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

with SOCI that interacts with several database backends, such as


MySQL, PostgreSQL and and SQLite 3. Without SOCI, you would
need to learn and implement the individual C++ APIs provided by
each particular backend your application requires, which would be
time-consuming and tedious.

SOCI supports popular open source database backends including


MySQL, PostgreSQL, SQLite 3 and Firebird, as well as database
servers used commercially such as Oracle.

Prerequisites

All software adopted in this article is open-source and freely


available to download and install on your system. To complete the
steps demonstrated in this article, make sure you have the
following software already installed on your system:

• C++ Compiler
Such as GCC, LLVM/Clang or Microsoft Visual C++.

• CMake 2.8+
Used for building the SOCI API and libraries. Download CMake or
install the cmake package using your OS’s native package
manager.

• MySQL Server and MySQL Shared Libraries


MySQL can be installed via your operating system’s package
manager (Linux) or via the MySQL Community Server download
page. Shared libraries are also included in a standard MySQL
installation. MySQL Version 8.0.18 is used throughout this article.

Installing Library Dependencies

3 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

This section will detail the installation of SOCI on the Mac OS X


and Linux operating systems (Unix style systems). Instructions on
how to install the Boost libraries will also be provided as SOCI
uses the Boost.Date_Time library.

For readers who are using Windows, please refer to the boost
download page where you can download and install Boost via
prebuilt Windows binaries. Also refer to the SOCI Installation page
for specific instructions on running CMake on Windows and
installing SOCI to your system.

Installing Boost

Luckily, the Boost libraries can be installed via a single command


using your operating system’s native package manager. The
following commands will install the Boost libraries and
corresponding header files on your system. In addition to Mac OS
X, installation commands are provided for major Linux distributions
— so please choose the appropriate installation command for your
operating system:

• Mac OS X: sudo brew install boost

• Arch: sudo pacman -S boost

• Debian and Ubuntu: sudo apt-get install libboost-all-


dev

• CentOS and Fedora: sudo dnf install boost-devel

The default install location for the Boost libraries is the


/usr/local/lib directory. The header files are installed in the
/usr/local/include/boost directory. You can confirm the install
locations by using the ls command in the terminal:

4 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

Listing the Boost header files in /usr/local/include/boost.

Listing the Boost libraries in /usr/local/lib.

Installing SOCI

The process to install SOCI is also quite straightforward, but


involves a few more steps. We are going to download the SOCI Git
repository, generate a native build environment using CMake,
produce the SOCI libraries and header files, and finally install
(copy) them to appropriate locations on our system.

That might sound like a lot of steps to break down and understand,
so let’s start by executing the following two commands in a

5 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

terminal. The cd command firstly navigates you to your home


directory. The git clone command then downloads the SOCI Git
repository to this directory:

cd ~
git clone git://github.com/SOCI/soci.git

This repository contains everything we need to install the SOCI


libraries and header files — it contains CMake build files,
documentation, tests, and the actual C++ source code. As of this
writing, the latest stable version of SOCI will be downloaded (SOCI
version 4.0).

Moving forward, the next task is to generate a native build


environment using CMake. A native build environment contains
scripts that a particular operating system uses in order to compile
source code, create libraries and build executables. On a Unix-
style operating system (such as Mac OS X and Linux), a Makefile
will be produced by CMake. We then invoke the Unix make
program to read the Makefile, which in turn will compile and
generate SOCI’s headers and shared libraries.

CMake can be described as a simple interpreter that reads a script


and subsequently generates build files for a particular platform in a
compiler-independent way.

Next, we create a directory named build in the soci repository


that will contain the native build environment produced by CMake:

cd soci # Enter the soci repository


mkdir build # Create a new build directory
cd build # Enter the build directory

It is perfectly fine to name the build directory anything you like,

6 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

but we will stick to convention in this case. Moving forward, we


execute three more commands that will actually install SOCI to our
system:

# Generate native build files


cmake -G "Unix Makefiles" ..# Generate SOCI libraries and
headers
make# Copy libraries and headers to filesystem
make install

Let’s take a look at these different commands and examine how


they work:

• cmake -G "Unix Makefiles" ..


The cmake command is invoked to generate our native build
environment. Two important arguments are passed here. The -G
option specifies a build system generator. In this case, the "Unix
Makefiles" generator is selected in order for CMake to produce a
Makefile. Lastly, .. points to the root directory of the soci
repository which also includes the CMakeLists.txt file — cmake
requires this file to generate the build files.

• make
A program that will read the Makefile in the build directory and
generate the shared libraries, source files, and binaries that make
up the SOCI library.

• make install
This command will copy the generated shared libraries and include
files to appropriate locations on your filesystem.

SOCI Shared libraries will be copied to /usr/local/lib, while


header files will be copied to /usr/local/include/soci. Near

7 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

the end of this article, we will configure the C++ compiler to search
through these directories in order to resolve our application’s
header and library dependencies.

Let’s confirm the install locations by using the ls command in the


terminal:

Listing the SOCI header files in /usr/local/include/soci.

Listing the SOCI libraries in /usr/local/lib.

At this point, SOCI should be installed on your system and ready


to use! The next section will detail how we go about setting up a
database environment that our application will interact with during

8 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

runtime.

Preparing A MySQL Database

In this section, we will walk through setting up our database


environment. More specifically, we will be working with a MySQL
database server that will serve as the database backend to our
C++ application.

Before starting on the C++ application, we need to complete a few


tasks on the database server. For this demonstration, we will:

• Create a new database called soci_db.

• Create a table called users that will enable us to insert and


retrieve some data from our C++ application.

• Create a role called role_soci_dev and assign it privileges


appropriate for developers working on our application.

• Create a user called soci_dev1@localhost that is able to


connect to MySQL from the C++ application and work with the
soci_db database.

• Grant the role_soci_dev role to the soci_dev1 user, and set it


as the default role.

The following gist is a MySQL script that sets up our database


environment:

Our entire SQL script to setup a MySQL database.

If you are already familiar with the SQL statements used in the
script and do not feel like further explanation is necessary, feel free
to download the file and execute the script from your MySQL
server.

9 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

Once logged into MySQL as the root user, run MySQL’s source
command to execute the script:

source /path/to/script.sql

• Replace/path/to/ to the location that the downloaded script


resides on your filesystem.

If you have executed the script and understand all the SQL
commands, feel free to proceed to the next section in this article
(Developing a SOCI C++ Application).

The following subsections will walk you through each statement


contained in the script above, and provide additional explanation of
what the statement does.

Creating a New Database and Table

We will firstly log in to our MySQL server as the root user. Let’s
open up a terminal and run the following command:

mysql -u root -p
Enter password: <your root password>

• The -u flag requires a username that represents the user we are


logging in as. Because we would like administrative privileges on
our MySQL server, we shall log in as the root user.

• The -p flag is necessary if the user you are logging in as requires


a password. Go ahead and enter your root password when
prompted, followed by the Enter key.

Let’s proceed to create a new database and use it via the following
two statements:

CREATE DATABASE IF NOT EXISTS soci_db;

10 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

USE soci_db;

• We create a database called soci_db with the CREATE DATABASE


statement. The IF NOT EXISTS clause makes sure that the
database name is unique on the server.

• We then execute the USE statement in order to enter the soci_db


database container so we can proceed to create objects within it.

For the purposes of our simple proof-of-concept application, we


will create a table called users and store some rudimentary data
— a first name, last name, email address, and a boolean
representing if the user is active or not. The CREATE TABLE
statement is invoked to create the users table:

CREATE TABLE users (


id INT AUTO_INCREMENT,
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
active BOOL DEFAULT TRUE,
PRIMARY KEY(id)
);

Notice how we make an effort to adhere to robust database table


design principles by adding a primary key and a few constraints to
our table’s columns:

• The id column serves as the primary key, which means that all
records in the users table can be uniquely identified with the id
value. The id column of the first row inserted into the table will
contain one. The AUTO_INCREMENT property will also increment id
by one for each new record.

11 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

• The NOT NULL constraint is applied to the first_name, last_name


and email columns. These columns must therefore always contain
a value, and not contain a NULL value. The UNIQUE constraint is
also applied to the email column, which ensures that all email
address values stored in the table are different.

• Lastly, the DEFAULT constraint is applied to the active column.


MySQL will automatically provide a TRUE value if nothing is
provided for the active column when inserting a new user.

To verify that our table has been created successfully, lets describe
it with the DESC statement:

DESC users;

It’s good practice to study the output of the DESC statement and
confirm that the table has the correct column names, data types,
and constraints set up as intend before moving forward.

Describing the users table with the DESC statement.

Creating a Role

Our next task is to create a role that permits operations relevant

12 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

only to the soci_db database. This role will then be granted to a


user on our database server.

Later on when we develop a simple C++ application, we will use


the SOCI API to perform just two tasks on our database backend
— inserting and retrieving users. With this in mind, the
connecting user should not have access outside the soci_db
database, or be able to perform tasks that a superuser would
typically do.

To accomplish this, we will create a developer role that can be


granted to developers working on the soci_db database. In other
words, database users who have been granted this role will be
able to execute many SQL statements within the soci_db
database in order to develop the C++ application effectively.

Let’s go ahead and create a new role called role_soci_dev with


the CREATE ROLE statement:

CREATE ROLE role_soci_dev;

Role names will be saved along with user names in the


mysql.user system table. To easily query roles on our database
server, we have prefixed the role name with role_.

Next, we will grant privileges to our newly created role. These


privileges will dictate what statements a user will be able to
perform on the database server.

In our case, we would like to give a developer a high degree of


freedom inside the soci_db database — such as creating,
deleting and updating database objects, as well as inserting data.
Luckily, there is a privilege within MySQL called ALL that will
satisfy this requirement.

13 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

Let’s run the GRANT statement to add the necessary privileges to


the role_soc_dev role:

GRANT ALL ON soci_db.* TO role_soci_dev;

• The above statement can be read as: Give the role_soci_dev


role ALL privileges for all tables when using the soci_db database.

• The asterisk (*) in soci_db.* represents all tables in the soci_db


database. To grant these privileges to just a single table, replace
the asterisk with a valid table name.

We can verify that the ALL privilege has been granted to our role
by executing the SHOW GRANTS statement:

SHOW GRANTS FOR role_soci_dev;

The output to this statement should include:

• GRANT USAGE ON *.* TO `role_soci_dev`@`%`


The USAGE privilege will only permit users to be able to log in to the
MySQL server. In other words, this privilege carries the meaning of
“no privileges”, and is granted to every newly created user and
role.

• GRANT ALL PRIVILEGES ON `soci_db`.* TO


`role_soci_dev`@`%`
This output verifies that the ALL privilege on the soci_db database
has been granted to our role successfully.

We are moving forward nicely! We have now created a role on our


MySQL server that can be granted to any user that we create. In
other words, if we hire five developers to work on our application,
we can create five users on our MySQL server and give them the
role we just created via a single statement.

14 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

Creating a User

Let’s go ahead and create a new user on the MySQL server called
soci_dev1. This user will log in to the MySQL server from our C++
application. The CREATE USER statement creates a new user:

CREATE USER soci_dev1@localhost IDENTIFIED BY


‘Secure123’;

• The soci_dev1 user connects to the MySQL server from


localhost. In other words, this user is only able to connect to
MySQL from the localhost machine, and not remotely.

• The IDENTIFIED BY clause allows us to specify a password that


will be required every time the user logs in to the MySQL server.
Feel free to use a more secure password on your end!

If you now go ahead and log in to MySQL as the soci_dev1 user,


you won’t be able to access any databases, or execute any
statements for that matter. As mentioned previously, the USAGE
privilege is the only privilege granted to the user at this point.

To enable the user to perform operations on the server, we need to


execute just a few more statements. Lets start with using the
GRANT statement to give the user the role_soci_dev role we
created a moment ago:

GRANT role_soci_dev TO soci_dev1@localhost;

Note that we must specify both the username (soci_dev1) and


hostname (localhost) when referencing our user.

Let’s confirm that the role is applied to the user by executing the
SHOW GRANTS statement:

SHOW GRANTS FOR soci_dev1@localhost USING

15 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

role_soci_dev;

Showing privileges granted to the soci_dev1@localhost user.

The USING clause allows us to view the privileges associated with


the role_soci_dev role. If you were to execute this statement
without the USING clause, the applied role name will only be
displayed without its associated privileges — go ahead and give it
a try.

Now, if we proceed to log in to MySQL with the soci_dev1 user,


we still won’t be able to actually do anything! The role_soci_dev
role will remain inactive until we tell MySQL what role to activate
for the user after they have logged in. Therefore, we must set a
default role that will be active when the user logs in.

To accomplish this, lets use the following SET DEFAULT ROLE


statement:

SET DEFAULT ROLE ALL TO soci_dev1@localhost;

Now, every role that has been granted to our user will be active, by
default, when they log in to MySQL.

Verifying the Database Environment

16 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

Let’s now confirm everything is working by logging out as the root


user, and logging in as the soci_dev1 user:

/* exit mysql as the root user */


exit# back in the terminal, log in to MySQL as the soci_dev1
user
mysql -u soci_dev1 -p
Enter password: <Secure123>

Notice how we specify soci_dev1 after the -u flag to log in as our


new user. Note that it is not necessary to append the the
hostname (@localhost) to the user in this case. Also remember to
input the password you gave the soci_dev1 user that was
specified in the CREATE USER statement.

Before proceeding to write the C++ application, let’s confirm that


we have set up our database environment correctly. More
specifically, we would like to confirm that:

• The soci_dev1 user has the correct privileges.

• We can access and work with the soci_db database.

• We cannot access any other databases on the server.

Firstly, let’s confirm that we are logged in as the user we intend to


be by running the following statement:

SELECT user();

soci_dev1@localhost should be returned after executing this


statement.

You will sometimes see the current_user() and current_user


(without parenthesis) functions used instead of user().

Secondly, let’s verify the privileges we have by running the SHOW

17 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

GRANTS statement:

SHOW GRANTS;

Notice that we have used the short form of the SHOW GRANTS
statement to display our own privileges.

Thirdly, we need to confirm that we only have access to the


soci_db database:

SHOW DATABASES;

The only database name displayed in this list should be soci_db.


As this user does not have the necessary privileges to access any
other database, no other database names are displayed, including
the MySQL system databases.

Lastly, lets enter the soci_db database and show the users table
by running the following statements:

USE soci_db;
SHOW TABLES;

Feel free to run additional statements to verify other aspects of our


database environment as the soci_dev1 user. It might be a good
idea to examine the users table with the DESC statement, or to
even start playing with data by using the INSERT, SELECT, UPDATE
and DELETE statements.

With a database environment set up, let’s proceed to writing our


C++ application with SOCI.

Developing a C++ Application Using SOCI

This section will walk through writing a simple C++ application


using the SOCI library. The application will perform the following

18 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

tasks:

• Connect to the soci_db database on our MySQL backend as the


soci_dev1 user.

• Insert a new user into the users table. The application will display
prompts allowing you to input values for the new user’s first name,
last name, and email address.

• Retrieve and display all records from the users table. For every
row that is retrieved from the database, the value inside each
column will be output to the terminal.

The following gist contains the entire C++source code for this
demonstration. Feel free to type out the code in your own
hello.cpp source file while trying to understand how the
application works.

Alternatively, you can download the source file and proceed to the
Compiling and Running Our Application section to run the
program. After running the program, you may wish to return to this
section to type out the code yourself and study the explanations
that follow:

Our entire C++ application code.

The following sub-sections will break down the application’s C++


code and provide an explanation on what is being performed.

Header Files and Namespaces

We start by including all the header files our application requires.


In this case, our program needs to interface with the soci.h
header file, as well as the header file relevant to the database
backend we are adopting.

19 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

As our database backend is MySQL, the application must include


the soci-mysql.h header file. If your solution has adopted an
Oracle backend, you would be required to include the soci-
oracle.h header file instead, and so on.

Additionally, we include three standard library header files in order


to work with exceptions (exception), terminal input and output
(iostream), and strings (string):

#include <soci/soci.h>
#include <soci/mysql/soci-mysql.h>
#include <exception>
#include <iostream>
#include <string>

It is considered good practice to firstly include external library


header files, followed by header files local to your project, and
finally standard library header files. Moreover, header files listed in
alphabetical order adds another level of organisation to your
includes section.

Moving forward, we bring the soci namespace and some C++


standard library items into scope:

using namespace soci;using std::cin;


using std::cout;
using std::endl;
using std::string;

Everything in SOCI is declared in the soci namespace, so we


expose the entire soci namespace to our application. From now
on, any item we reference that is declared in the soci namespace
will not require the soci:: prefix, which will improve the readability
of our code.

20 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

Furthermore, we individually bring some standard library items into


scope that will be used for handling data input and output to the
terminal.

Exception Handling

The main function starts by defining a try ... catch block to


handle errors appropriately:

int main() { try {


// ...
}
catch(const std::exception& e) {
std::cerr << "Error: " << e.what() << endl;
}
}

It is important to implement some exception handling in our


program as SOCI will throw an exception if an error occurs when it
interacts with the backend database.

All database related errors will result in an exception being thrown


— such as when the SOCI library is unable to make a successful
connection to the backend database, is unable to retrieve data,
and so on.

If an exception is thrown during runtime, it will propagate to the


main function and be handled in the catch block. In our case, we
call the exception::what() method to attempt to describe the
error that occurred before the application terminates. The error
message will be displayed in the terminal.

Connecting to the MySQL Database

21 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

Moving forward, our application immediately attempts to connect


to our MySQL database backend. In order to do this, we create a
soci::session object stored in a variable called sql, and pass it
some information:

// Connect to MySQL database


session sql(mysql, "db=soci_db user=soci_dev1
password=Secure123");

The soci:session object encapsulates the database connection


and other backend related details. Therefore, we must pass it the
following items:

• mysql: The MySQL backend factory object that enables


interactions with a MySQL backend. This object is declared inside
soci-mysql.h.

• "db=soci_db user=soci_dev1 password=Secure123": The


generic connection string that specifies which database to use,
and what user to log in as.

The generic connection string passed to the soci::session


constructor includes:

• db=soci_db: It will connect to a database on our server called


soci_db.

• user=soci_dev1: It will log in to our server as the soci_dev1


user.

• password=Secure123: The password Secure123 is used for the


soci_dev1 user.

Remember to input the correct information that reflects your


database environment — use the correct database name, user

22 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

name, and password you created earlier in this article!

Getting Data

After the soci::session object is created, we are connected to


the database backend and are ready to interact with it in some
way.

Let’s take a look at inserting a new user record into the users
table using SOCI. Furthermore, it would be nice if we could
manually type in values for the new user’s first name, last name,
and email columns.

To accomplish this, we start by creating three empty string objects


that will store the user’s first name, last name, and email
information. These string objects are then passed into a utility
function called get_data in order to populate the string objects
with values. We also pass a prompt string as the first parameter to
this function:

// Get data for new user


string first_name, last_name, email; get_data("> Enter first name:
", first_name);
get_data("> Enter last name: ", last_name);
get_data("> Enter email address: ", email);

The get_data function is defined directly below our using


statements:

template<typename T>
void get_data(const string prompt, T& value) {
cout << prompt;
cin >> value;
}

23 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

This function simply displays a prompt in the terminal, and allows


us to enter a value that will be used to initialise the object T. Let’s
take a closer look at the implementation:

• template<typename T>
The function get_data is in fact a template function. In the first
line we declare a type called T, and use it as a placeholder for the
function’s second parameter.

• void get_data(const string prompt, T& value)


The T placeholder serves as the type for the second parameter,
called value. It is also passed by reference — denoted by the &
symbol. The first parameter is of type const string, and will
serve as a simple prompt.

• cout << prompt;


The prompt is output to the terminal, and should instruct the user
of what type of data to enter.

• cin >> value;


Lastly, T is initialised with some data using cin.

The get_data template function is a useful utility that can be


reused throughout the application. It prevents us from having to re-
type the same code in multiple places to do the same task of
initialising an item with some custom data.

Inserting Data

The application now proceeds to insert a new user in the users


table with the string data we have collected via the keyboard. We
call another utility function called insert_user to accomplish this:

// Insert a new row into users table

24 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

insert_user(sql, first_name, last_name, email);

insert_user is another utility function we have written ourselves


that enables us to quickly add many users to the database with
one function call. It is defined near the top of our source code:

void insert_user(session& sql,


const string& first_name,
const string& last_name,
const string& email) { // Insert data into users table
sql << "INSERT INTO users(first_name, last_name, email)"
"VALUES(:fn, :ln, :e)",
use(first_name, "fn"),
use(last_name, "ln"),
use(email, "e"); cout << "> Successfully inserted user." <<
endl << endl;
}

Firstly, we must provide four items to the insert_user function:

• session& sql: A reference to our session object, which contains


our database connection. It is required to execute SQL
statements.

• const string& parameters: Are references to string objects


representing the user’s first_name, last_name and email
values that we would like to insert into the database.

Inside the function, we use the session object to immediately


execute an INSERT INTO SQL statement to add a new user to the
database:

// Insert data into users table


sql << "INSERT INTO users(first_name, last_name, email)"

25 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

"VALUES(:fn, :ln, :e)",


use(first_name, "fn"),
use(last_name, "ln"),
use(email, "e");

Notice that the SQL statement we would like to execute is a string


enclosed in double quotes, and comes after the insertion operator
(<<). We realise from this statement that the insertion operator has
been overloaded within the soci::session object to provide an
easy mechanism for executing SQL statements. With SOCI, we
also do not need to terminate the SQL statement with a semi
colon.

In order to attach data to the SQL string, SOCI provides


mechanisms to bind local buffers for input and output data. To
use this data binding feature, we firstly specify placeholders within
the SQL statement string in the form of :placeholder_name. For
example, we have specified :fn, :ln, and :e placeholder names
in the statement above, which are essentially “holes” that the
first_name, last_name, and email strings will fill. The
placeholders represent holes for input variables.

A comma follows the SQL statement string, and then the


soci::use expression is called three times (also separated by
commas) to bind our local variables to the SQL string. We can bind
variables in two ways:

• Binding by position: Where soci::use expressions are used in


order of the “holes” in the statement.

• Binding by name: Where the placeholder name is also passed to


soci::use to clearly associate the local variable with the given
placeholder.

26 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

Binding by name is adopted in our implementation because we


also pass the placeholder name along with the variable it will be
associated with. For example, the first_name string will be
slotted into the :fn placeholder, and so on.

SOCI assumes local variables provided as soci::use elements


live as long as it takes to execute the whole statement. Therefore,
named variables should be used to ensure object lifetime is
sufficient.

The function lastly outputs a message to the terminal, informing


the application’s user that the SQL statement executed
successfully:

cout << "> Successfully inserted user." << endl << endl;

Retrieving Data

In addition to inserting data, we would also like SOCI to retrieve


data from the database. Therefore, this section will describe how
we can retrieve all rows from the users table and display each
row’s data to the terminal.

Another utility function named display_users is used that


handles the task of displaying all records in the users table. The
soci::session object containing the database connection is also
passed by reference into this function:

// Retrieve all rows from users table and output data


display_users(sql);

The display_users function is defined just above the main


function in our source file:

void display_users(session& sql) { // Retrieve all rows from

27 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

users table
rowset<row> rs = (sql.prepare << "SELECT * FROM users");
// Iterate through the result set
for (rowset<row>::const_iterator it = rs.begin();
it != rs.end(); ++it) { const row& r = *it;
std::cout << "ID: " << r.get<int>(0) << endl
<< "First Name: " << r.get<string>(1) << endl
<< "Last Name: " << r.get<string>(2) << endl
<< "Email: " << r.get<string>(3) << endl
<< "Active: " << r.get<int>(4) << endl << endl;
}
}

Let’s firstly take a look at how we can retrieve row data from
MySQL:

rowset<row> rs = (sql.prepare << "SELECT * FROM users");

The soci::rowset<T> container class provides a means of


executing queries and accessing results dynamically using an
STL-like interface. In the line above, we are retrieving rows from
the users table and storing them in a soci::row object. In other
words, the query results are bound to soci::row elements, which
are accessible through the soci::rowset<T> interface.

We call the soci::session::prepare method to not only execute


the SQL statement, but also set up a PREPARED statement on the
MySQL server. As a result, the SELECT statement will be cached
on the database server and will not need to be re-parsed by
MySQL in future calls — meaning it can execute immediately,
potentially speeding up data retrieval.

With the rows fetched from the database backend and returned to

28 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

our application, we then enter a for loop and iterate through the
result set:

// Iterate through the result set


for (rowset<row>::const_iterator it = rs.begin();
it != rs.end(); ++it) {
// ...
}

Similar to iterating through a STL container such as a vector or


list, we instantiate a constant input iterator that points to the first
soci::row in the soci::rowset<T> result set with a call to
rs.begin(). The loop will iterate on each row until it reaches the
last row. The loop ends when the iterator reaches the last item in
the soci::rowset<T>. The last soci::row is checked by calling
rs.end().

Our for loop implementation is displayed in the following code


snippet:

const row& r = *it;


std::cout << "ID: " << r.get<int>(0) << endl
<< "First Name: " << r.get<string>(1) << endl
<< "Last Name: " << r.get<string>(2) << endl
<< "Email: " << r.get<string>(3) << endl
<< "Active: " << r.get<int>(4) << endl << endl;

Inside the for loop we create a variable called r that stores a


constant reference to the soci::row that the iterator is currently
pointing at in memory. From here we can use this variable to
access the value of each column of the row, and output its value to
the terminal.

The soci::row::get method is called multiple times within the

29 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

cout statement. Let’s firstly examine its syntax:

row.get<column_data_type>(column_index)

• column_data_type : Represents the data type of the column we


would like to retrieve.

• column_index: The column index number, where the first (far-left)


column of the table has an index value of 0, the next column
having an index value of 1, and so on.

The data type expressed in column_data_type must match up


with the actual data in the database column. For example:

row.get<int>(0) // Returns user's id as an int


row.get<string>(1) // Returns user’s first name as a string

If an incorrect type T is requested when calling row::get<T>(),


an exception of type std::bad_cast is thrown.

With that, our application is complete. I hope it gives you some


ideas of how you can develop simple applications with SOCI, and
that it also encourages you to check out the official SOCI
documentation to learn more about the workings of the API.

Let’s move forward and talk about compiling and running our
application!

Compiling and Running Our Application

This section will walk through the process of compiling and running
our application. The instructions presented in this section will
primarily focus on UNIX environments — such as the Mac OS X
and Linux operating systems.

For readers who are using Windows, feel free to read through the

30 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

following instructions and try to mirror the process using your IDE’s
compiler settings. As soon as I’m in possession of a Windows
system, this section will be updated with specific instructions for
Windows OS!

UNIX Environments (Mac OS X, Linux, UNIX)

We shall use the G++ compiler (also referred to as the GCC


compiler) to compile our C++ application. Additionally, we will do
the entire compilation process inside a terminal.

To check if your system has the g++ compiler installed, run the
following command inside a terminal:

which g++

The path to the g++ binary should output to the terminal after
running this command — a typical location is /usr/bin/g++. If
nothing is returned, either:

• Mac OS X: Download the Xcode command line tools.

• Linux: Download and install the appropriate gcc package using


your Linux distribution’s native package manager.

Moving forward, we now need to configure some search paths that


the g++ compiler will consult to locate necessary header files and
shared libraries on our filesystem that are required by our
application. These search paths will be set via environment
variables.

The following command exports the CPATH environment variable.


The compiler will use the search paths set in this variable to find
our application’s header files:

export CPATH="/usr/local/include:/usr/local/mysql/include"

31 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

• /usr/local/mysql/include is not a standard file location that


compilers will check by default. Therefore, we specify it here so
mysql.h can be found.

The next command exports the LIBRARY_PATH environment


variable. The compiler will use the search paths set in this variable
to locate necessary shared libraries during the linking phase:

export LIBRARY_PATH="/usr/local/lib:/usr/local/mysql/lib"

• /usr/local/mysql/lib is also a custom location that the


compiler doesn’t check by default. Since the libmysqlclient.so
shared library is located here, we must export its location.

Additionally, we export the LD_LIBRARY_PATH environment


variable, which is consulted when the actual program is executed
(after it has been compiled and linked) to search for directories
containing shared libraries:

export LD_LIBRARY_PATH="/usr/local/lib:/usr/local/mysql/lib"

Use the printenv command to verify your environment. Call


printenv by itself to list all of your environment variables, or
printenv VARIABLE_NAME to display the value of a single
environment variable.

With the search paths set up we are now ready to compile the
application using the g++ command. After navigating to the
directory containing your hello.cpp source file, execute the
following command:

g++ -std=c++11 hello.cpp -o hello -lsoci_core -lsoci_mysql -ldl


-lmysqlclient

The following list breaks down each argument we provide the g++
command:

32 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

• -std=c++11 : We will request to use the features of the C++ 11


standard to build our target.

• hello.cpp: The single source file representing our program.

• -o hello: An executable file called hello will be produced as


output.

• -lsoci_core -lsoci_mysql -ldl: Shared libraries required to


interface with the SOCI API.

• -lmysqlclient: The official MySQL shared library.

Providing everything went smoothly during the compiling and


linking phases, we can now run our final executable from the
terminal:

./hello

Running our final executable.

In Summary

We have covered a lot of ground in this article and explored the


various stages that need considering if you are a developer of
database software using C++.

33 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

We looked at installing our adopted database access library —


SOCI, as well as the Boost libraries. We also set up a database
environment with MySQL, which involved creating various
database objects and a user with developer privileges. From there,
we wrote a simple C++ application to demonstrate some of the
SOCI API. Our application connects to a database backend, and is
able to interact with it to insert and retrieve data. Lastly, we looked
at compiling our application using the GCC compiler which
involved exporting necessary environment variables, enabling the
compiler to resolve the application’s header and library
dependencies.

To continue learning about SOCI and implementing more


sophisticated database applications, I certainly recommend
reading the official SOCI documentation, including the User Guide
section, which breaks down each feature of the library whilst also
providing example code.

I have also be published an article that builds directly from this one
and walks through developing a more sophisticated application
with SOCI and MySQL:

In addition, you may wish to try the following challenges to build on


and solidify the knowledge you have gained in this article:

• Use SOCI to connect to another backend database, such as


PostgreSQL or Firebird.

• Write some unit tests with a testing library, such as Boost.Test.

• Expand your database environment by creating more tables with


certain relationships, and then query them with the SOCI API.

Keep in mind that you will need to include the correct SOCI header

34 of 35 20/08/2023, 21:10
Working with Databases in C++: An Introduction | by Dane Bulat | Medium about:reader?url=https%3A%2F%2Ffanyv88.com%3A443%2Fhttps%2Fdane-bulat.medium.com%2Fwork...

file, and link to the correct shared library depending on your


adopted database backend.

35 of 35 20/08/2023, 21:10

You might also like