1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
|
# boost_sqlite
This library provides a simple C++ sqlite wrapper.
It includes:
- typed queries
- prepared statements
- json support
- custom functions (scalar, aggregrate, windows)
- event hooks
- virtual tables
sqlite provides an excellent C-API, so this library does not attempt to hide, but to augment it.
## Building the library
You can either build the library and link against `boost_sqlite` for embedding it,
or `boost_sqlite_ext` for extensions.
If you want to use it for extensions you'll need to
define `BOOST_SQLITE_COMPILE_EXTENSION` or include `boost/sqlite/extensions.hpp` first.
## Quickstart
First we open a database. Note that this can be `":memory:"` for an in-memory database.
```cpp
boost::sqlite::connection conn{"./my-database.db"};
```
Next we're creating tables using boost::sqlite::connection::execute,
because it can execute multiple statements in one command:
```cpp
conn.execute(R"(
create table if not exists author (
id integer primary key autoincrement,
first_name text,
last_name text
);
create table if not exists library(
id integer primary key autoincrement,
name text unique,
author integer references author(id)
);
)"
);
```
Next, we'll use a prepared statement to insert multiple values by index:
```cpp
conn.prepare("insert into author (first_name, last_name) values (?1, ?2), (?3, ?4), (?5, ?6), (?7, ?8)")
.execute({"vinnie", "falco", "richard", "hodges", "ruben", "perez", "peter", "dimov"});
```
Prepared statements can also be used multiple time and used with named parameters instead of indexed.
```cpp
{
conn.query("begin transaction;");
auto st = conn.prepare("insert into library (\"name\", author) values ($library, "
" (select id from author where first_name = $fname and last_name = $lname))");
st.execute({{"library", "beast"}, {"fname", "vinnie"}, {"lname", "falco"}});
st.execute({{"library", "mysql"}, {"fname", "ruben"}, {"lname", "perez"}});
st.execute({{"library", "mp11"}, {"fname", "peter"}, {"lname", "dimov"}});
st.execute({{"library", "variant2"}, {"fname", "peter"}, {"lname", "dimov"}});
conn.query("commit;");
}
```
Now that we have the values in the table, let's add a custom aggregate function to create a comma separated list:
```cpp
struct collect_libs
{
void step(std::string & name, span<sqlite::value, 1> args)
{
if (name.empty())
name = args[0].get_text();
else
(name += ", ") += args[0].get_text();
}
std::string final(std::string & name) { return name; }
};
sqlite::create_aggregate_function(conn, "collect_libs", collect_libs{});
```
Print out the query with aggregates libraries:
```cpp
for (boost::sqlite::row r : conn.query(
"select first_name, collect_libs(name) "
" from author inner join library l on author.id = l.author group by last_name"))
std::cout << r.at(0u).get_text() << " authored " << r.at(1u).get_text() << std::endl;
```
Alternatively a query result can also be read manually instead of using a loop:
```cpp
boost::sqlite::row r;
boost::sqlite::query q = conn.query(
"select first_name, collect_libs(name) "
" from author inner join library l on author.id = l.author group by last_name")
do
{
auto r = q.current();''
std::cout << r.at(0u).get_text() << " authored " << r.at(1u).get_text() << std::endl;
}
while (q.read_next());
```
## Fields, values & parameters
sqlite3 has a weak typesystem, where everything is one of
following [value_types](@ref boost::sqlite::value_type):
- `integer`
- `floating`
- `text`
- `blob`
- `null`
The result of a query is a [field](@ref boost::sqlite::field) type,
while a [value](@ref boost::sqlite::value) is used in functions.
Fields & values can have [subtypes](https://www.sqlite.org/c3ref/value_subtype.html),
while parameter to prepared statements do not have thos associated.
Because of this the values that can be bound to an [execute](@ref boost::sqlite::statement::execute)
need to be convertible to a fixed set of types (see [param_ref](@ref boost::sqlite::param_ref) for details).
When a [value](@ref boost::sqlite::value) is returned from a custom function,
such as done through [create_scalar_function](@ref boost::sqlite::create_scalar_function), additional types
can be added with the following tag_invoke function:
```cpp
void tag_invoke(const struct set_result_tag &, sqlite3_context * ctx, const my_type & value);
```
An implementation can look like this:
```cpp
void tag_invoke(const struct set_result_tag &, sqlite3_context * ctx, const my_type & value)
{
auto data = value.to_string();
sqlite3_result_text(ctx, data.c_str(), data.size(), sqlite3_free);
sqlite3_result_subtype(ctx, my_subtype);
}
```
## Typed queries
Queries can be typed through tuples, describe or, if you're on C++20, by plain structs.
The type to hold them is `static_resultset<T>` which will check if the columns match the result types before usage.
Tuples are matched by position, structs by name.
```cpp
for (auto q : conn.query<std::tuple<std::string, std::string>>(
"select first_name, collect_libs(name) "
" from author inner join library l on author.id = l.author group by last_name"))
std::cout << std::get<0>(q) << " authored " << std::get<0>(q) << std::endl;
```
```cpp
struct query_result { std::string first_name, lib_name;};
BOOST_DESCRIBE_STRUCT(query_result, (), (first_name, lib_name)); // this can be omitted with C++20.
for (auto q : conn.query<query_result>(
"select first_name, collect_libs(name) as lib_name"
" from author inner join library l on author.id = l.author group by last_name"))
std::cout << q.first_name << " authored " << q.lib_name << std::endl;
```
The following types are allowed in a static query result:
- `sqlite::value`
- `int`
- `sqlite_int64`
- `double`
- `std::string`
- `sqlite::string_view`
- `sqlite::blob`
- `sqlite::blob_view`
You'll need to include `boost/sqlite/static_resultset.hpp` for this to work.
## Custom functions
Since sqlite is running in the same process you can add custom functions that can be used from within sqlite.
- [collation](@ref boost::sqlite::create_collation)
- [scalar function](@ref boost::sqlite::create_scalar_function)
- [aggregate function](@ref boost::sqlite::create_aggregate_function)
- [window function](@ref boost::sqlite::create_window_function)
## Vtables
This library also simplifies adding virtual tables significantly;
virtual tables are table that are backed by code instead of data.
See [create_module](@ref boost::sqlite::create_module) and [prototype](@ref boost::sqlite::vtab_module_prototype) for more details.
## Modules
This library can also be used to build a sql plugin:
```cpp
BOOST_SQLITE_EXTENSION(testlibrary, conn)
{
// create a function that can be used in the plugin
create_scalar_function(
conn, "assert",
[](boost::sqlite::context<>, boost::span<boost::sqlite::value, 1u> sp)
{
if (sp.front().get_int() == 0)
throw std::logic_error("assertion failed");
});
}
```
The plugin can then be loaded & used like this:
```sql
SELECT load_extension('./test_library');
select assert((3 * 4) = 12);
```
To build a plugin you need to define `BOOST_SQLITE_COMPILE_EXTENSION`
(e.g. by including `boost/sqlite/extension.hpp` or linking against `boost_sqlite_ext`).
This will include the matching sqlite header (`sqlite3ext.h`) and
will move all the symbols into an inline namespace `ext` inside `boost::sqlite`.
<a name="api-reference"></a>
## Reference
* [Reference](#reference): Covers the topics discussed in this document.
## Library Comparisons
While there are many sqlite wrappers out there, most haven't been updated in the last five years - while sqlite has.
Here are some actively maintained ones:
- [SQLiteCpp](https://github.com/SRombauts/SQLiteCpp)
SQLiteCpp is the closest to this library, a C++11 wrapper only depending on sqlite & the STL.
It's great and served as an inspiration for this library.
boost.sqlite does provide more functionality when it comes to hooks, custom functions & virtual tables.
Furthermore, boost.sqlite has a non-throwing interface and supports variants & json, as those are available through boost.
- [sqlite_modern_cpp](https://github.com/SqliteModernCpp/sqlite_modern_cpp)
This library takes a different approach, by making everything an `iostream` interface.
`iostream` interfaces have somewhat fallen out of favor.
- [sqlite_orm](https://github.com/fnc12/sqlite_orm)
As the name says, it's an ORM. While there is nothing wrong with ORMs, they are one layer of abstraction
above a client library like this.
- [SOCI](https://github.com/SOCI/soci)
SOCI is an abstraction layer for multiple databases in C++, including sqlite.
It's interfaces encourages dynamic building of query string, which should not be considered safe.
|