Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Hah. I've never used sqlite3 C API before. And I've never written an output <code>streambuf</code> implementation. But seeing how I will probably be using sqlite3 in a c++ codebase in the future, I thought I'd spent some time with </p> <ul> <li><a href="http://www.sqlite.org/c3ref/funclist.html" rel="noreferrer">http://www.sqlite.org/c3ref/funclist.html</a></li> <li><p>cppreference <a href="http://en.cppreference.com/w/cpp/io/basic_streambuf" rel="noreferrer">http://en.cppreference.com/w/cpp/io/basic_streambuf</a></p> <blockquote> <p><img src="https://i.stack.imgur.com/4cabH.png" alt="enter image description here"></p> </blockquote></li> </ul> <p>So it turns out you <em>can</em> <a href="http://www.sqlite.org/c3ref/blob_open.html" rel="noreferrer">open a blob field for incremental IO</a>. However, though you can read/write the BLOB, you can't change the size (except via a separate UPDATE statement). </p> <p>So, the steps for my demonstration became:</p> <ol> <li>insert a record into a table, binding a "zero-blob" of a certain (fixed) size</li> <li>open the blob field in the newly inserted record</li> <li>wrap the blob handle in a custom <code>blob_buf</code> object that derives from <code>std::basic_streambuf&lt;&gt;</code> and can be used with <code>std::ostream</code> to write to that blob</li> <li>serialize some data into the <code>ostream</code></li> <li>flush</li> <li>destruct/cleanup</li> </ol> <p>It works :)</p> <p>The code in <code>main</code>:</p> <pre><code>int main() { sqlite3 *db = NULL; int rc = sqlite3_open_v2("test.sqlite3", &amp;db, SQLITE_OPEN_READWRITE, NULL); if (rc != SQLITE_OK) { std::cerr &lt;&lt; "database open failed: " &lt;&lt; sqlite3_errmsg(db) &lt;&lt; "\n"; exit(255); } // 1. insert a record into a table, binding a "zero-blob" of a certain (fixed) size sqlite3_int64 inserted = InsertRecord(db); { // 2. open the blob field in the newly inserted record // 3. wrap the blob handle in a custom `blob_buf` object that derives from `std::basic_streambuf&lt;&gt;` and can be used with `std::ostream` to write to that blob blob_buf buf(OpenBlobByRowId(db, inserted)); std::ostream writer(&amp;buf); // this stream now writes to the blob! // 4. serialize some data into the `ostream` auto payload = CanBeSerialized { "hello world", { 1, 2, 3.4, 1e7, -42.42 } }; boost::archive::text_oarchive oa(writer); oa &lt;&lt; payload; #if 0 // used for testing with larger data std::ifstream ifs("test.cpp"); writer &lt;&lt; ifs.rdbuf(); #endif // 5. flush writer.flush(); // 6. destruct/cleanup } sqlite3_close(db); // ==7653== HEAP SUMMARY: // ==7653== in use at exit: 0 bytes in 0 blocks // ==7653== total heap usage: 227 allocs, 227 frees, 123,540 bytes allocated // ==7653== // ==7653== All heap blocks were freed -- no leaks are possible } </code></pre> <p>You'll recognize the steps outlined.</p> <p>To test it, assume you create a new sqlite database:</p> <pre><code>sqlite3 test.sqlite3 &lt;&lt;&lt; "CREATE TABLE DEMO(ID INTEGER PRIMARY KEY AUTOINCREMENT, FILE BLOB);" </code></pre> <p>Now, once you have run the program, you can query for it:</p> <pre><code>sqlite3 test.sqlite3 &lt;&lt;&lt; "SELECT * FROM DEMO;" 1|22 serialization::archive 10 0 0 11 hello world 5 0 1 2 3.3999999999999999 10000000 -42.420000000000002 </code></pre> <p>If you enable the test code (that puts more data than the blob_size allows) you'll see the blob getting truncated:</p> <pre><code>contents truncated at 256 bytes </code></pre> <h1>Full Program</h1> <pre><code>#include &lt;sqlite3.h&gt; #include &lt;string&gt; #include &lt;iostream&gt; #include &lt;ostream&gt; #include &lt;fstream&gt; #include &lt;boost/serialization/vector.hpp&gt; #include &lt;boost/archive/text_oarchive.hpp&gt; template&lt;typename CharT, typename TraitsT = std::char_traits&lt;CharT&gt; &gt; class basic_blob_buf : public std::basic_streambuf&lt;CharT, TraitsT&gt; { sqlite3_blob* _blob; // owned int max_blob_size; typedef std::basic_streambuf&lt;CharT, TraitsT&gt; base_type; enum { BUFSIZE = 10 }; // Block size - tuning? char buf[BUFSIZE+1/*for the overflow character*/]; size_t cur_offset; std::ostream debug; // no copying basic_blob_buf(basic_blob_buf const&amp;) = delete; basic_blob_buf&amp; operator= (basic_blob_buf const&amp;) = delete; public: basic_blob_buf(sqlite3_blob* blob, int max_size = -1) : _blob(blob), max_blob_size(max_size), buf {0}, cur_offset(0), // debug(std::cerr.rdbuf()) // or just use `nullptr` to suppress debug output debug(nullptr) { debug.setf(std::ios::unitbuf); if (max_blob_size == -1) { max_blob_size = sqlite3_blob_bytes(_blob); debug &lt;&lt; "max_blob_size detected: " &lt;&lt; max_blob_size &lt;&lt; "\n"; } this-&gt;setp(buf, buf + BUFSIZE); } int overflow (int c = base_type::traits_type::eof()) { auto putpointer = this-&gt;pptr(); if (c!=base_type::traits_type::eof()) { // add the character - even though pptr might be epptr *putpointer++ = c; } if (cur_offset &gt;= size_t(max_blob_size)) return base_type::traits_type::eof(); // signal failure size_t n = std::distance(this-&gt;pbase(), putpointer); debug &lt;&lt; "Overflow " &lt;&lt; n &lt;&lt; " bytes at " &lt;&lt; cur_offset &lt;&lt; "\n"; if (cur_offset+n &gt; size_t(max_blob_size)) { std::cerr &lt;&lt; "contents truncated at " &lt;&lt; max_blob_size &lt;&lt; " bytes\n"; n = size_t(max_blob_size) - cur_offset; } if (SQLITE_OK != sqlite3_blob_write(_blob, this-&gt;pbase(), n, cur_offset)) { debug &lt;&lt; "sqlite3_blob_write reported an error\n"; return base_type::traits_type::eof(); // signal failure } cur_offset += n; if (this-&gt;pptr() &gt; (this-&gt;pbase() + n)) { debug &lt;&lt; "pending data has not been written"; return base_type::traits_type::eof(); // signal failure } // reset buffer this-&gt;setp(buf, buf + BUFSIZE); return base_type::traits_type::not_eof(c); } int sync() { return base_type::traits_type::eof() != overflow(); } ~basic_blob_buf() { sqlite3_blob_close(_blob); } }; typedef basic_blob_buf&lt;char&gt; blob_buf; struct CanBeSerialized { std::string sometext; std::vector&lt;double&gt; a_vector; template&lt;class Archive&gt; void serialize(Archive &amp; ar, const unsigned int version) { ar &amp; boost::serialization::make_nvp("sometext", sometext); ar &amp; boost::serialization::make_nvp("a_vector", a_vector); } }; #define MAX_BLOB_SIZE 256 sqlite3_int64 InsertRecord(sqlite3* db) { sqlite3_stmt *stmt = NULL; int rc = sqlite3_prepare_v2(db, "INSERT INTO DEMO(ID, FILE) VALUES(NULL, ?)", -1, &amp;stmt, NULL); if (rc != SQLITE_OK) { std::cerr &lt;&lt; "prepare failed: " &lt;&lt; sqlite3_errmsg(db) &lt;&lt; "\n"; exit(255); } else { rc = sqlite3_bind_zeroblob(stmt, 1, MAX_BLOB_SIZE); if (rc != SQLITE_OK) { std::cerr &lt;&lt; "bind_zeroblob failed: " &lt;&lt; sqlite3_errmsg(db) &lt;&lt; "\n"; exit(255); } rc = sqlite3_step(stmt); if (rc != SQLITE_DONE) { std::cerr &lt;&lt; "execution failed: " &lt;&lt; sqlite3_errmsg(db) &lt;&lt; "\n"; exit(255); } } rc = sqlite3_finalize(stmt); if (rc != SQLITE_OK) { std::cerr &lt;&lt; "finalize stmt failed: " &lt;&lt; sqlite3_errmsg(db) &lt;&lt; "\n"; exit(255); } return sqlite3_last_insert_rowid(db); } sqlite3_blob* OpenBlobByRowId(sqlite3* db, sqlite3_int64 rowid) { sqlite3_blob* pBlob = NULL; int rc = sqlite3_blob_open(db, "main", "DEMO", "FILE", rowid, 1/*rw*/, &amp;pBlob); if (rc != SQLITE_OK) { std::cerr &lt;&lt; "blob_open failed: " &lt;&lt; sqlite3_errmsg(db) &lt;&lt; "\n"; exit(255); } return pBlob; } int main() { sqlite3 *db = NULL; int rc = sqlite3_open_v2("test.sqlite3", &amp;db, SQLITE_OPEN_READWRITE, NULL); if (rc != SQLITE_OK) { std::cerr &lt;&lt; "database open failed: " &lt;&lt; sqlite3_errmsg(db) &lt;&lt; "\n"; exit(255); } // 1. insert a record into a table, binding a "zero-blob" of a certain (fixed) size sqlite3_int64 inserted = InsertRecord(db); { // 2. open the blob field in the newly inserted record // 3. wrap the blob handle in a custom `blob_buf` object that derives from `std::basic_streambuf&lt;&gt;` and can be used with `std::ostream` to write to that blob blob_buf buf(OpenBlobByRowId(db, inserted)); std::ostream writer(&amp;buf); // this stream now writes to the blob! // 4. serialize some data into the `ostream` auto payload = CanBeSerialized { "hello world", { 1, 2, 3.4, 1e7, -42.42 } }; boost::archive::text_oarchive oa(writer); oa &lt;&lt; payload; #if 0 // used for testing with larger data std::ifstream ifs("test.cpp"); writer &lt;&lt; ifs.rdbuf(); #endif // 5. flush writer.flush(); // 6. destruct/cleanup } sqlite3_close(db); } </code></pre> <p>PS. I've kept error handling... very crude. You'll want to introduce a helper function to check sqlite3 errorcodes and translate into exception(s) maybe. :)</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload