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 --- odb/relational/sqlite/source.cxx | 182 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 2 deletions(-) (limited to 'odb/relational/sqlite/source.cxx') diff --git a/odb/relational/sqlite/source.cxx b/odb/relational/sqlite/source.cxx index 0d821ac..2f6623e 100644 --- a/odb/relational/sqlite/source.cxx +++ b/odb/relational/sqlite/source.cxx @@ -17,6 +17,72 @@ namespace relational { namespace relational = relational::source; + struct query_parameters: relational::query_parameters, context + { + query_parameters (base const& x): base (x) {} + + virtual string + next (semantics::data_member& m, + const string& column, + const string& sqlt) + { + // Handle stream columns. Specifically, we somehow need to + // pass the column name to the code that runs in the + // statement. So what we are going to do is encode it + // in the parameter name. + // + if (sk_ == statement_insert || sk_ == statement_update) + { + const sql_type& t (parse_sql_type (sqlt, m, false)); + if (t.stream) + { + // The column name is quoted. + // + string r (column); + r[0] = '$'; // Replace leading '"'. + r.resize (r.size () - 1); // Remove trailing '"'. + + // Verify it only contains allowed characters. + // + for (size_t i (1); i != r.size (); ++i) + { + char c (r[i]); + if (c != '_' && + (c < '0' || c > '9') && + (c < 'a' || c > 'z') && + (c < 'A' || c > 'Z')) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: unsupported character '" << c << "' in " + << sqlt << " column name " << column << endl; + + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": info: STREAM column can contain alpha-numeric " + << "characters plus '_'" << endl; + + throw operation_failed (); + } + } + + // For TEXT columns, since we use the *_bind_zeroblob() + // function (there is no *_bind_zerotext()), the value + // that will be stored is BLOB, not TEXT, unless we + // explicitly CAST it. The user better make sure the + // encoding of raw TEXT data they are going to write + // matches the database encoding. + // + if (t.type == sql_type::TEXT) + r = "CAST(" + r + " AS TEXT)"; + + return r; + } + } + + return "?"; + } + }; + entry query_parameters_; + // // bind // @@ -71,6 +137,15 @@ namespace relational "value.capacity ();" << b << ".is_null = &" << arg << "." << mi.var << "null;"; } + + virtual void + traverse_stream (member_info& mi) + { + os << b << ".type = sqlite::bind::stream;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } }; entry bind_member_; @@ -110,6 +185,13 @@ namespace relational << "grew = true;" << "}"; } + + virtual void + traverse_stream (member_info&) + { + os << e << " = false;" + << endl; + } }; entry grow_member_; @@ -166,6 +248,17 @@ namespace relational << "i." << mi.var << "null = is_null;" << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; } + + virtual void + traverse_stream (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;"; + } }; entry init_image_member_; @@ -220,10 +313,65 @@ namespace relational << "i." << mi.var << "null);" << endl; } + + virtual void + traverse_stream (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } }; entry init_value_member_; - struct container_traits: relational::container_traits, context + struct statement_columns_common: context + { + void + process (relational::statement_columns& cs, statement_kind sk) + { + using relational::statement_columns; + + // For SELECT statements, add _ROWID_ "follow-up" column to + // each stream column. The reason we need both, and not just + // ROWID is the NULL value. Let's hope that SELECT'ing a BLOB + // but not actually reading it with sqlite3_result_blob() is + // as fast as not SELECT'ing it. + // + if (sk != statement_select) + return; + + for (statement_columns::iterator i (cs.begin ()); + i != cs.end (); ++i) + { + if (parse_sql_type (i->type, *i->member).stream) + { + // Column is already table-qualified and quoted. Do some + // surgery to replace it with _ROWID_. That is, we want to + // transform "table"."column" to "table"."_ROWID_". + // + string c (i->column); + string::size_type n (c.size ()), p (c.rfind ('"', n - 2)); + assert (p != string::npos); + string as (c, p + 1, n - p - 2); + c.resize (p); + c += "\"_ROWID_\""; + + // We are going to pack this "tightly", without any newlines, + // so that the statement processing code treats them as a + // single column. + // + i->column += ','; + i->column += c; + } + } + } + }; + + struct container_traits: relational::container_traits, + statement_columns_common { container_traits (base const& x): base (x) {} @@ -234,10 +382,32 @@ namespace relational // interleaving statements. // } + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk, + bool) + { + statement_columns_common::process (cols, sk); + } }; entry container_traits_; - struct class_: relational::class_, context + struct section_traits: relational::section_traits, + statement_columns_common + { + section_traits (base const& x): base (x) {} + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk) + { + statement_columns_common::process (cols, sk); + } + }; + entry section_traits_; + + struct class_: relational::class_, statement_columns_common { class_ (base const& x): base (x) {} @@ -274,6 +444,14 @@ namespace relational return base::join_syntax (vo); } + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk, + bool) + { + statement_columns_common::process (cols, sk); + } }; entry class_entry_; } -- cgit v1.1