scclib-sqlite
Stable Cloud Computing Sqlite Library
sqld.cc
Go to the documentation of this file.
1 /*
2 BSD 3-Clause License
3 
4 Copyright (c) 2022, Stable Cloud Computing, Inc.
5 
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions are met:
8 
9 1. Redistributions of source code must retain the above copyright notice, this
10  list of conditions and the following disclaimer.
11 
12 2. Redistributions in binary form must reproduce the above copyright notice,
13  this list of conditions and the following disclaimer in the documentation
14  and/or other materials provided with the distribution.
15 
16 3. Neither the name of the copyright holder nor the names of its
17  contributors may be used to endorse or promote products derived from
18  this software without specific prior written permission.
19 
20 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31 #include <sqlite/sqld.h>
32 #include <sqlite3.h>
33 #include <string>
34 #include <system_error>
35 #include <cassert>
36 
42 static const std::string::size_type npos = std::string::npos;
43 
44 using namespace scc::sqld;
45 
46 Conn::Conn(const std::string& uri) : m_db(nullptr)
47 {
48  int r = sqlite3_open(uri.c_str(), &m_db);
49  if (r != SQLITE_OK)
50  {
51  throw std::runtime_error(sqlite3_errstr(r));
52  }
53 }
54 
55 Conn::~Conn()
56 {
57  close();
58 }
59 
60 void Conn::close()
61 {
62  if (m_db)
63  {
64  sqlite3_close(m_db);
65  m_db = nullptr;
66  }
67 }
68 
69 void Conn::reopen(const std::string& uri)
70 {
71  close();
72 
73  int r = sqlite3_open(uri.c_str(), &m_db);
74  if (r != SQLITE_OK)
75  {
76  throw std::runtime_error(sqlite3_errstr(r));
77  }
78 }
79 
80 Trans::Trans(Conn& conn) : m_conn(conn), m_active(false)
81 {
82 }
83 
84 Trans::~Trans()
85 {
86  if (m_active)
87  {
88  abort();
89  }
90 }
91 
93 {
94  if (m_active)
95  {
96  throw std::runtime_error("begin() transaction when already active");
97  }
98  Req r(m_conn);
99  r.sql() << "BEGIN;";
100  r.exec();
101  m_active = true;
102 }
103 
105 {
106  if (!m_active)
107  {
108  throw std::runtime_error("commit() transaction when not active");
109  }
110  Req r(m_conn);
111  r.sql() << "COMMIT;";
112  r.exec();
113  m_active = false;
114 }
115 
117 {
118  if (!m_active)
119  {
120  throw std::runtime_error("abort() transaction when not active");
121  }
122  Req r(m_conn);
123  r.sql() << "ROLLBACK;";
124  r.exec();
125  m_active = false;
126 }
127 
128 Req::Req(Conn& conn) : m_conn(conn), m_stmt(nullptr), m_pos(0), m_cols(0)
129 {
130 }
131 
132 Req::~Req()
133 {
134  finalize();
135 }
136 
137 void Req::finalize()
138 {
139  if (m_stmt)
140  {
141  sqlite3_finalize(m_stmt);
142  m_stmt = nullptr;
143  }
144 }
145 
147 {
148  finalize();
149 
150  m_sql.str(""); // clear out the statement
151  m_pos = 0;
152  m_cols = 0;
153 }
154 
156 {
157  finalize();
158 
159  m_pos = 0;
160  m_cols = 0;
161 }
162 
163 //#include <iostream>
164 
165 void Req::prepare()
166 {
167  finalize(); // clean up the last statement if any
168 
169  if (m_pos >= m_sql.str().size()) // nothing to execute
170  {
171  return;
172  }
173 
174 //std::cout << "prepare: " << m_sql.str().substr(m_pos) << std::endl;
175 
176  /*
177  The sqlite library compiles one statement at a time, and keeps track of where it left off,
178  so start at the current position.
179  */
180  const char *head = &(m_sql.str()[m_pos]), *tail = nullptr;
181 
182  int r = sqlite3_prepare_v2(m_conn.m_db, head, m_sql.str().size()+1, &m_stmt, &tail); // include 0 character
183  if (r != SQLITE_OK)
184  {
185  throw std::runtime_error(sqlite3_errstr(r));
186  }
187 
188  assert(tail);
189  m_pos += tail-head; // advance (when there are no more statements this will be larger than the sql string)
190 }
191 
193 {
194  if (m_cols)
195  {
196  throw std::runtime_error("exec_select() called with current row data");
197  }
198 
199  while (1)
200  {
201  prepare(); // go process the next request in the sql stream
202 
203  if (!m_stmt) // this happens when we are done processing or there is only whitespace left
204  {
205  return 0;
206  }
207 
208  int r = sqlite3_step(m_stmt);
209 
210  if (r == SQLITE_DONE) // no row data, go on to next statement
211  {
212  continue;
213  }
214 
215  if (r != SQLITE_ROW)
216  {
217  throw std::runtime_error(sqlite3_errstr(r));
218  }
219 
220  m_cols = sqlite3_column_count(m_stmt); // we have row data, keep track of number of columns in row
221 
222  break;
223  }
224 
225  return m_cols;
226 }
227 
228 void Req::exec()
229 {
230  if (m_cols)
231  {
232  throw std::runtime_error("exec() called with current row data");
233  }
234 
235  while (1)
236  {
237  int r = exec_select();
238 
239  if (r == 0) // done
240  {
241  return;
242  }
243 
244  while (next_row()) // keep going until we have no more row data
245  {
246  }
247 
248  // let the exec_select run again to make sure there are no more statements
249  }
250 }
251 
253 {
254  if (!m_stmt)
255  {
256  throw std::runtime_error("next_row() called with invalid statement");
257  }
258  if (!m_cols)
259  {
260  throw std::runtime_error("next_row() called without current row data");
261  }
262 
263  int r = sqlite3_step(m_stmt);
264 
265  if (r == SQLITE_DONE) // no more columns
266  {
267  m_cols = 0;
268  return 0;
269  }
270 
271  if (r != SQLITE_ROW) // another column coming
272  {
273  throw std::runtime_error(sqlite3_errstr(r));
274  }
275 
276  return sqlite3_column_count(m_stmt);
277 }
278 
279 std::string Req::col_name(int col)
280 {
281  if (!m_stmt)
282  {
283  throw std::runtime_error("column operation called with invalid statement");
284  }
285  if (!m_cols)
286  {
287  throw std::runtime_error("column operation called when row not available");
288  }
289  if (col < 0 || col >= m_cols)
290  {
291  throw std::runtime_error("column operation called with invalid column number");
292  }
293 
294  return sqlite3_column_name(m_stmt, col);
295 }
296 
297 void Req::col_name(int col, std::string& str)
298 {
299  if (!m_stmt)
300  {
301  throw std::runtime_error("column operation called with invalid statement");
302  }
303  if (!m_cols)
304  {
305  throw std::runtime_error("column operation called when row not available");
306  }
307  if (col < 0 || col >= m_cols)
308  {
309  throw std::runtime_error("column operation called with invalid column number");
310  }
311 
312  str.assign(sqlite3_column_name(m_stmt, col));
313 }
314 
315 std::string Req::col_text(int col)
316 {
317  if (!m_stmt)
318  {
319  throw std::runtime_error("column operation called with invalid statement");
320  }
321  if (!m_cols)
322  {
323  throw std::runtime_error("column operation called when row not available");
324  }
325  if (col < 0 || col >= m_cols)
326  {
327  throw std::runtime_error("column operation called with invalid column number");
328  }
329 
330  return reinterpret_cast<const char*>(sqlite3_column_text(m_stmt, col));
331 }
332 
333 void Req::col_text(int col, std::string& str)
334 {
335  if (!m_stmt)
336  {
337  throw std::runtime_error("column operation called with invalid statement");
338  }
339  if (!m_cols)
340  {
341  throw std::runtime_error("column operation called when row not available");
342  }
343  if (col < 0 || col >= m_cols)
344  {
345  throw std::runtime_error("column operation called with invalid column number");
346  }
347 
348  str.assign(reinterpret_cast<const char*>(sqlite3_column_text(m_stmt, col)));
349 }
350 
351 int Req::col_int(int col)
352 {
353  if (!m_stmt)
354  {
355  throw std::runtime_error("column operation called with invalid statement");
356  }
357  if (!m_cols)
358  {
359  throw std::runtime_error("column operation called when row not available");
360  }
361  if (col < 0 || col >= m_cols)
362  {
363  throw std::runtime_error("column operation called with invalid column number");
364  }
365 
366  return sqlite3_column_int(m_stmt, col);
367 }
368 
369 int64_t Req::col_int64(int col)
370 {
371  if (!m_stmt)
372  {
373  throw std::runtime_error("column operation called with invalid statement");
374  }
375  if (!m_cols)
376  {
377  throw std::runtime_error("column operation called when row not available");
378  }
379  if (col < 0 || col >= m_cols)
380  {
381  throw std::runtime_error("column operation called with invalid column number");
382  }
383 
384  return sqlite3_column_int64(m_stmt, col);
385 }
386 
387 double Req::col_real(int col)
388 {
389  if (!m_stmt)
390  {
391  throw std::runtime_error("column operation called with invalid statement");
392  }
393  if (!m_cols)
394  {
395  throw std::runtime_error("column operation called when row not available");
396  }
397  if (col < 0 || col >= m_cols)
398  {
399  throw std::runtime_error("column operation called with invalid column number");
400  }
401 
402  return sqlite3_column_double(m_stmt, col);
403 }
404 
405 void Req::col_blob(int col, std::vector<char>& str)
406 {
407  if (!m_stmt)
408  {
409  throw std::runtime_error("column operation called with invalid statement");
410  }
411  if (!m_cols)
412  {
413  throw std::runtime_error("column operation called when row not available");
414  }
415  if (col < 0 || col >= m_cols)
416  {
417  throw std::runtime_error("column operation called with invalid column number");
418  }
419 
420  int sz = sqlite3_column_bytes(m_stmt, col);
421 
422  const char* v = reinterpret_cast<const char*>(sqlite3_column_blob(m_stmt, col));
423 
424  str.assign(v, v+sz);
425 }
scc::sqld::Req::clear
void clear()
Clear the request and the sql() stream.
Definition: sqld.cc:146
sqld.h
File descriptor.
scc::sqld::Conn
Database connection.
Definition: sqld.h:61
scc::sqld::Req::col_text
std::string col_text(int)
Return UTF-8 TEXT.
Definition: sqld.cc:315
scc::sqld::Req::col_int
int col_int(int)
Return 32-bit INTEGER.
Definition: sqld.cc:351
scc::sqld::Req::sql
std::ostringstream & sql()
Sql streamer.
Definition: sqld.h:230
scc::sqld::Req::col_blob
void col_blob(int, std::vector< char > &)
Return BLOB unstructured data.
Definition: sqld.cc:405
scc::sqld::Req
Database request.
Definition: sqld.h:128
scc::sqld::Trans::abort
void abort()
ROLLBACK (abort) the transaction.
Definition: sqld.cc:116
scc::sqld::Conn::Conn
Conn(const std::string &="file:mem?mode=memory&cache=shared")
Constructs and open a sqlite in-memory database connection.
Definition: sqld.cc:46
scc::sqld::Req::exec
void exec()
Execute all statements, ignoring all row data.
Definition: sqld.cc:228
scc::sqld::Req::col_real
double col_real(int)
Return 64-bit REAL.
Definition: sqld.cc:387
scc::sqld::Trans::begin
void begin()
BEGIN the transaction.
Definition: sqld.cc:92
scc::sqld::Req::col_int64
int64_t col_int64(int)
Return 64-bit INTEGER.
Definition: sqld.cc:369
scc::sqld::Req::exec_select
int exec_select()
Executes in select mode.
Definition: sqld.cc:192
scc::sqld::Req::reset
void reset()
Reset the request without clearing the sql() stream.
Definition: sqld.cc:155
scc::sqld::Trans::commit
void commit()
COMMIT the transaction.
Definition: sqld.cc:104
scc::sqld::Req::col_name
std::string col_name(int)
Return column name.
Definition: sqld.cc:279
scc::sqld::Conn::reopen
void reopen(const std::string &="file:mem?mode=memory&cache=shared")
Reopen the connection.
Definition: sqld.cc:69
scc::sqld::Req::next_row
int next_row()
Get the next row.
Definition: sqld.cc:252