From 8f6a9c51bc64226d7c296e4b0172f9e56a7eea3b Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 15 Jul 2015 18:43:03 +0200 Subject: Implement SQLite incremental BLOB/TEXT I/O --- doc/manual.xhtml | 358 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 358 insertions(+) (limited to 'doc/manual.xhtml') diff --git a/doc/manual.xhtml b/doc/manual.xhtml index 7b86be9..5c28564 100644 --- a/doc/manual.xhtml +++ b/doc/manual.xhtml @@ -726,6 +726,7 @@ for consistency. +
18.1.1String Type Mapping
18.1.2Binary Type Mapping
18.1.3Incremental BLOB/TEXT I/O
@@ -20829,6 +20830,18 @@ t.commit (); TEXT NOT NULL + + + odb::sqlite::text + TEXT (STREAM) + NOT NULL + + + + odb::sqlite::blob + BLOB (STREAM) + NOT NULL +

It is possible to map the char C++ type to the @@ -20983,6 +20996,351 @@ db.query<object> ("uuid = " + query::_val<odb::sqlite::id_blob> (u)); db.query<object> (query::uuid == query::_ref (u)); +

18.1.3 Incremental BLOB/TEXT I/O

+ +

This section describes the SQLite ODB runtime library support for + incremental reading and writing of BLOB and + TEXT values. The provided API is a thin wrapper + around the native SQLite sqlite3_blob_*() function + family. As a result, it is highly recommended that you familiarize + yourself with the semantics of this SQLite functionality before + continuing with this section.

+ +

The SQLite runtime provides the blob and + text types that can be used to represent + BLOB and TEXT data members + that will be read/written using the incremental I/O. + For example:

+ +
+#include <odb/sqlite/blob.hxx>
+#include <odb/sqlite/text.hxx>
+
+#pragma db object
+class object
+{
+public
+  #pragma db id auto
+  unsigned long id;
+
+  odb::sqlite::blob b; // Mapped to BLOB.
+  odb::sqlite::text t; // Mapped to TEXT.
+};
+  
+ +

The blob and text types should be + viewed as descriptors of the BLOB and + TEXT values (rather than the values themselves) + that can be used to open the values for reading or + writing. These two types have an identical interface that + is presented below. Notice that it is essentially the list + of arguments (except for size which is discussed + below) to the sqlite3_blob_open() function:

+ +
+namespace odb
+{
+  namespace sqlite
+  {
+    class blob|text
+    {
+    public:
+      explicit
+      blob|text (std::size_t = 0);
+
+      std::size_t size ()
+      void        size (std::size_t);
+
+      const std::string& db () const;
+      const std::string& table () const;
+      const std::string& column () const;
+      long long          rowid () const;
+
+      void
+      clear ();
+    };
+  }
+}
+  
+ +

To read/write data from/to a incremental BLOB or + TEXT value we use the corresponding + blob_stream and text_stream + stream types. Their interfaces closely mimic the + underlying sqlite3_blob_*() functions + and are presented below. Note that in order to create + a stream we have to pass the corresponding descriptor:

+ +
+#include <odb/sqlite/stream.hxx>
+
+namespace odb
+{
+  namespace sqlite
+  {
+    class stream
+    {
+    public:
+      stream (const char* db,
+              const char* table,
+              const char* column,
+              long long rowid,
+              bool rw);
+
+      std::size_t
+      size () const;
+
+      // The following two functions throw std::invalid_argument if
+      // offset + n is past size().
+      //
+      void
+      read (void* buf, std::size_t n, std::size_t offset = 0);
+
+      void
+      write (const void* buf, std::size_t n, std::size_t offset = 0);
+
+      sqlite3_blob*
+      handle () const;
+
+      // Close without reporting errors, if any.
+      //
+      ~stream ();
+
+      // Close with reporting errors, if any.
+      //
+      void
+      close ();
+
+      // Open the same BLOB but in a different row. Can be faster
+      // than creating a new stream instance. Note that the stream
+      // must be in the open state prior to calling this function.
+      //
+      void
+      reopen (long long rowid);
+    };
+  }
+}
+
+#include <odb/sqlite/blob-stream.hxx>
+
+namespace odb
+{
+  namespace sqlite
+  {
+    class blob_stream: public stream
+    {
+    public:
+      blob_stream (const blob&, bool rw);
+    };
+  }
+}
+
+#include <odb/sqlite/text-stream.hxx>
+
+namespace odb
+{
+  namespace sqlite
+  {
+    class text_stream: public stream
+    {
+    public:
+      text_stream (const text&, bool rw);
+    };
+  }
+}
+  
+ +

The rw argument to the constructors above + specifies whether to open the value for reading only + (false) or to read and write + (true).

+ +

In SQLite the incremental BLOB and + TEXT sizes are fixed in the sense that + they must be specified before the object is persisted + or updated and the following write operations can + only write that much data. This is what the size + data member in the descriptors is for. You can also determine + the size of the opened value, for both reading and writing, + using the size() stream function. The + following example puts all of this together:

+ +
+#include <odb/sqlite/blob-stream.hxx>
+#include <odb/sqlite/text-stream.hxx>
+
+string txt (1024 * 1024, 't');
+vector<char> blb (1024 * 1024, 'b');
+
+object o;
+
+// Persist.
+//
+{
+  transaction tx (db.begin ());
+
+  // Specify the sizes of the values before calling persist().
+  //
+  o.t.size (txt.size ());
+  o.b.size (blb.size ());
+
+  db.persist (o);
+
+  // Write the data.
+  //
+  blob_stream bs (o.b, true); // Open for read/write.
+  assert (bs.size () == blb.size ());
+  bs.write (blb.data (), blb.size ());
+
+  text_stream ts (o.t, true); // Open for read/write.
+  assert (ts.size () == txt.size ());
+  ts.write (txt.data (), txt.size ());
+
+  tx.commit ();
+}
+
+// Load.
+//
+{
+  transaction tx (db.begin ());
+  auto_ptr<object> p (db.load<object> (o.id));
+
+  text_stream ts (p->t, false); // Open for reading.
+  vector<char> t (ts.size () + 1, '\0');
+  ts.read (t.data (), t.size () - 1);
+  assert (string (t.data ()) == txt);
+
+  blob_stream bs (p->b, false); // Open for reading.
+  vector<char> b (bs.size (), '\0');
+  bs.read (b.data (), b.size ());
+  assert (b == blb);
+
+  tx.commit ();
+}
+
+// Update
+//
+txt.resize (txt.size () + 1, 't');
+txt[0] = 'A';
+txt[txt.size () - 1] = 'Z';
+
+blb.resize (blb.size () - 1);
+blb.front () = 'A';
+blb.back () = 'Z';
+
+{
+  transaction tx (db.begin ());
+
+  // Specify the new sizes of the values before calling update().
+  //
+  o.t.size (txt.size ());
+  o.b.size (blb.size ());
+
+  db.update (o);
+
+  // Write the data.
+  //
+  blob_stream bs (o.b, true);
+  bs.write (blb.data (), blb.size ());
+
+  text_stream ts (o.t, true);
+  ts.write (txt.data (), txt.size ());
+
+  tx.commit ();
+}
+  
+ +

For the most part, the incremental BLOB and + TEXT descriptors can be used as any other + simple values. Specifically, they can be used as container + elements (Chapter 5, "Containers"), as + NULL-able values (Section 7.3, + "Pointers and NULL Value Semantics"), and in views + (Chapter 10, "Views"). The following + example illustrates the use within a view:

+ +
+#pragma db view object(object)
+struct load_b
+{
+  odb::sqlite::blob b;
+};
+
+typedef odb::query<load_b> query;
+
+transaction tx (db.begin ());
+
+for (load_b& lb: db.query<load_b> (query::t == "test"))
+{
+  blob_stream bs (lb.b, false);
+  vector<char> b (bs.size (), '\0');
+  bs.read (b.data (), b.size ());
+}
+
+tx.commit ();
+  
+ +

However, being a low-level, SQLite-specific mechanism, the + incremental I/O has a number of nuances that should be kept in + mind. Firstly, the streams should be opened within a transaction + and, unless already closed, they will be automatically closed + when the transaction is committed or rolled back. The following + modification of the previous example helps to illustrate this + point:

+ +
+{
+  transaction tx (db.begin ());
+
+  // ...
+
+  db.persist (o);
+
+  blob_stream bs (o.b, true);
+
+  tx.commit ();
+
+  // ERROR: stream is closed.
+  //
+  bs.write (blb.data (), blb.size ());
+}
+
+// ERROR: not in transaction.
+//
+text_stream ts (o.t, true);
+  
+ +

Because loading an object with an incremental BLOB or + TEXT value involves additional actions after the + database function returns (that is, reading the actual data), + certain commonly-expected "round-trip" assumptions will no + longer hold unless special steps are taken, for instance + (again, continuing with our example):

+ +
+transaction tx (db.begin ());
+
+auto_ptr<object> p (db.load<object> (o.id));
+p->name = "foo"; // Update some other member.
+db.update (*p);  // Bad behavior: incremental BLOB/TEXT invalidated.
+
+tx.commit ();
+  
+ +

One way to restore the expected behavior is to place the + incremental BLOB and TEXT values + into their own, separately loaded/updated sections + (Chapter 9, "Sections"). The alternative + approach would be to perform the incremental I/O as part + of the database operation post_* callbacks + (Section 14.1.7, "callback").

+ +

Finally, note that when using incremental TEXT + values, the data that we read/write is the raw bytes in + the encoding used by the database (UTF-8 by + default; see SQLite PRAGMA encoding documentation + for details).

+

18.2 SQLite Database Class

The SQLite database class has the following -- cgit v1.1