summaryrefslogtreecommitdiff
path: root/odb/odb/relational/sqlite
diff options
context:
space:
mode:
Diffstat (limited to 'odb/odb/relational/sqlite')
-rw-r--r--odb/odb/relational/sqlite/common.cxx217
-rw-r--r--odb/odb/relational/sqlite/common.hxx147
-rw-r--r--odb/odb/relational/sqlite/context.cxx490
-rw-r--r--odb/odb/relational/sqlite/context.hxx146
-rw-r--r--odb/odb/relational/sqlite/header.cxx63
-rw-r--r--odb/odb/relational/sqlite/inline.cxx42
-rw-r--r--odb/odb/relational/sqlite/model.cxx91
-rw-r--r--odb/odb/relational/sqlite/schema.cxx455
-rw-r--r--odb/odb/relational/sqlite/source.cxx471
9 files changed, 2122 insertions, 0 deletions
diff --git a/odb/odb/relational/sqlite/common.cxx b/odb/odb/relational/sqlite/common.cxx
new file mode 100644
index 0000000..03a3599
--- /dev/null
+++ b/odb/odb/relational/sqlite/common.cxx
@@ -0,0 +1,217 @@
+// file : odb/relational/sqlite/common.cxx
+// license : GNU GPL v3; see accompanying LICENSE file
+
+#include <cassert>
+
+#include <odb/relational/sqlite/common.hxx>
+
+using namespace std;
+
+namespace relational
+{
+ namespace sqlite
+ {
+ //
+ // member_base
+ //
+
+ sql_type const& member_base::
+ member_sql_type (semantics::data_member& m)
+ {
+ return parse_sql_type (column_type (m, key_prefix_), m);
+ }
+
+ void member_base::
+ traverse_simple (member_info& mi)
+ {
+ switch (mi.st->type)
+ {
+ case sql_type::INTEGER:
+ {
+ traverse_integer (mi);
+ break;
+ }
+ case sql_type::REAL:
+ {
+ traverse_real (mi);
+ break;
+ }
+ case sql_type::TEXT:
+ {
+ if (mi.st->stream)
+ traverse_text_stream (mi);
+ else
+ traverse_text (mi);
+ break;
+ }
+ case sql_type::BLOB:
+ {
+ if (mi.st->stream)
+ traverse_blob_stream (mi);
+ else
+ traverse_blob (mi);
+ break;
+ }
+ case sql_type::invalid:
+ {
+ assert (false);
+ break;
+ }
+ }
+ }
+
+ //
+ // member_image_type
+ //
+
+ member_image_type::
+ member_image_type (base const& x)
+ : member_base::base (x), // virtual base
+ base (x) {}
+
+ member_image_type::
+ member_image_type ()
+ : relational::member_base (0, 0, string (), string ()) {}
+
+ member_image_type::
+ member_image_type (semantics::type* type,
+ const custom_cxx_type* ct,
+ string const& fq_type,
+ string const& key_prefix)
+ : relational::member_base (type, ct, fq_type, key_prefix) {}
+
+ string member_image_type::
+ image_type (semantics::data_member& m)
+ {
+ type_.clear ();
+ member_base::traverse (m, true);
+ return type_;
+ }
+
+ void member_image_type::
+ traverse_composite (member_info& mi)
+ {
+ type_ = "composite_value_traits< " + mi.fq_type () +
+ ", id_sqlite >::image_type";
+ }
+
+ void member_image_type::
+ traverse_integer (member_info&)
+ {
+ type_ = "long long";
+ }
+
+ void member_image_type::
+ traverse_real (member_info&)
+ {
+ type_ = "double";
+ }
+
+ void member_image_type::
+ traverse_string (member_info&)
+ {
+ type_ = "details::buffer";
+ }
+
+ void member_image_type::
+ traverse_stream (member_info&)
+ {
+ type_ = "sqlite::stream_buffers";
+ }
+
+ entry<member_image_type> member_image_type_;
+
+ //
+ // member_database_type
+ //
+
+ member_database_type_id::
+ member_database_type_id (base const& x)
+ : member_base::base (x), // virtual base
+ base (x) {}
+
+ member_database_type_id::
+ member_database_type_id ()
+ : member_base::base (0, 0, string (), string ()), // virtual base
+ base (0, 0, string (), string ()) {}
+
+ member_database_type_id::
+ member_database_type_id (semantics::type* type,
+ const custom_cxx_type* ct,
+ string const& fq_type,
+ string const& key_prefix)
+ : member_base::base (type, ct, fq_type, key_prefix), // virtual base
+ base (type, ct, fq_type, key_prefix) {}
+
+ string member_database_type_id::
+ database_type_id (type& m)
+ {
+ type_id_.clear ();
+ member_base::traverse (m, true);
+ return type_id_;
+ }
+
+ void member_database_type_id::
+ traverse_composite (member_info&)
+ {
+ assert (false);
+ }
+
+ void member_database_type_id::
+ traverse_integer (member_info&)
+ {
+ type_id_ = "sqlite::id_integer";
+ }
+
+ void member_database_type_id::
+ traverse_real (member_info&)
+ {
+ type_id_ = "sqlite::id_real";
+ }
+
+ void member_database_type_id::
+ traverse_text (member_info&)
+ {
+ type_id_ = "sqlite::id_text";
+ }
+
+ void member_database_type_id::
+ traverse_blob (member_info&)
+ {
+ type_id_ = "sqlite::id_blob";
+ }
+
+ void member_database_type_id::
+ traverse_text_stream (member_info&)
+ {
+ type_id_ = "sqlite::id_text_stream";
+ }
+
+ void member_database_type_id::
+ traverse_blob_stream (member_info&)
+ {
+ type_id_ = "sqlite::id_blob_stream";
+ }
+
+ entry<member_database_type_id> member_database_type_id_;
+
+ //
+ // query_columns
+ //
+
+ struct query_columns: relational::query_columns, context
+ {
+ query_columns (base const& x): base_impl (x) {}
+
+ virtual string
+ database_type_id (semantics::data_member& m)
+ {
+ return member_database_type_id_.database_type_id (m);
+ }
+
+ private:
+ member_database_type_id member_database_type_id_;
+ };
+ entry<query_columns> query_columns_;
+ }
+}
diff --git a/odb/odb/relational/sqlite/common.hxx b/odb/odb/relational/sqlite/common.hxx
new file mode 100644
index 0000000..4d6089e
--- /dev/null
+++ b/odb/odb/relational/sqlite/common.hxx
@@ -0,0 +1,147 @@
+// file : odb/relational/sqlite/common.hxx
+// license : GNU GPL v3; see accompanying LICENSE file
+
+#ifndef ODB_RELATIONAL_SQLITE_COMMON_HXX
+#define ODB_RELATIONAL_SQLITE_COMMON_HXX
+
+#include <odb/relational/common.hxx>
+#include <odb/relational/sqlite/context.hxx>
+
+namespace relational
+{
+ namespace sqlite
+ {
+ struct member_base: virtual relational::member_base_impl<sql_type>, context
+ {
+ member_base (base const& x): base (x), base_impl (x) {}
+
+ // This c-tor is for the direct use inside the sqlite namespace.
+ // If you do use this c-tor, you should also explicitly call
+ // relational::member_base (aka base).
+ //
+ member_base () {}
+
+ virtual sql_type const&
+ member_sql_type (semantics::data_member&);
+
+ virtual void
+ traverse_simple (member_info&);
+
+ virtual void
+ traverse_integer (member_info&)
+ {
+ }
+
+ virtual void
+ traverse_real (member_info&)
+ {
+ }
+
+ virtual void
+ traverse_text (member_info& m)
+ {
+ traverse_string (m);
+ }
+
+ virtual void
+ traverse_blob (member_info& m)
+ {
+ traverse_string (m);
+ }
+
+ // String covers both text and blob.
+ //
+ virtual void
+ traverse_string (member_info&)
+ {
+ }
+
+ virtual void
+ traverse_text_stream (member_info& m)
+ {
+ traverse_stream (m);
+ }
+
+ virtual void
+ traverse_blob_stream (member_info& m)
+ {
+ traverse_stream (m);
+ }
+
+ virtual void
+ traverse_stream (member_info&)
+ {
+ }
+ };
+
+ struct member_image_type: relational::member_image_type,
+ member_base
+ {
+ member_image_type (base const&);
+ member_image_type ();
+ member_image_type (semantics::type* type,
+ const custom_cxx_type*,
+ string const& fq_type = string (),
+ string const& key_prefix = string ());
+ virtual string
+ image_type (semantics::data_member&);
+
+ virtual void
+ traverse_composite (member_info&);
+
+ virtual void
+ traverse_integer (member_info&);
+
+ virtual void
+ traverse_real (member_info&);
+
+ virtual void
+ traverse_string (member_info&);
+
+ virtual void
+ traverse_stream (member_info&);
+
+ private:
+ string type_;
+ };
+
+ struct member_database_type_id: relational::member_database_type_id,
+ member_base
+ {
+ member_database_type_id (base const&);
+ member_database_type_id ();
+ member_database_type_id (semantics::type* type,
+ const custom_cxx_type*,
+ string const& fq_type = string (),
+ string const& key_prefix = string ());
+
+ virtual string
+ database_type_id (type&);
+
+ virtual void
+ traverse_composite (member_info&);
+
+ virtual void
+ traverse_integer (member_info&);
+
+ virtual void
+ traverse_real (member_info&);
+
+ virtual void
+ traverse_text (member_info&);
+
+ virtual void
+ traverse_blob (member_info&);
+
+ virtual void
+ traverse_text_stream (member_info&);
+
+ virtual void
+ traverse_blob_stream (member_info&);
+
+ private:
+ string type_id_;
+ };
+ }
+}
+#endif // ODB_RELATIONAL_SQLITE_COMMON_HXX
diff --git a/odb/odb/relational/sqlite/context.cxx b/odb/odb/relational/sqlite/context.cxx
new file mode 100644
index 0000000..9a4369f
--- /dev/null
+++ b/odb/odb/relational/sqlite/context.cxx
@@ -0,0 +1,490 @@
+// file : odb/relational/sqlite/context.cxx
+// license : GNU GPL v3; see accompanying LICENSE file
+
+#include <vector>
+#include <cassert>
+#include <sstream>
+
+#include <odb/sql-token.hxx>
+#include <odb/sql-lexer.hxx>
+
+#include <odb/relational/sqlite/context.hxx>
+#include <odb/relational/sqlite/common.hxx>
+
+using namespace std;
+
+namespace relational
+{
+ namespace sqlite
+ {
+ namespace
+ {
+ struct type_map_entry
+ {
+ char const* const cxx_type;
+ char const* const db_type;
+ char const* const db_id_type;
+ bool const null;
+ };
+
+ type_map_entry type_map[] =
+ {
+ {"bool", "INTEGER", 0, false},
+
+ {"char", "TEXT", 0, false},
+ {"wchar_t", "TEXT", 0, false},
+ {"signed char", "INTEGER", 0, false},
+ {"unsigned char", "INTEGER", 0, false},
+
+ {"short int", "INTEGER", 0, false},
+ {"short unsigned int", "INTEGER", 0, false},
+
+ {"int", "INTEGER", 0, false},
+ {"unsigned int", "INTEGER", 0, false},
+
+ {"long int", "INTEGER", 0, false},
+ {"long unsigned int", "INTEGER", 0, false},
+
+ {"long long int", "INTEGER", 0, false},
+ {"long long unsigned int", "INTEGER", 0, false},
+
+ // SQLite stores NaN as NULL.
+ //
+ {"float", "REAL", 0, true},
+ {"double", "REAL", 0, true},
+
+ {"::std::string", "TEXT", 0, false},
+ {"::std::wstring", "TEXT", 0, false}
+ };
+ }
+
+ context* context::current_;
+
+ context::
+ ~context ()
+ {
+ if (current_ == this)
+ current_ = 0;
+ }
+
+ context::
+ context (ostream& os,
+ semantics::unit& u,
+ options_type const& ops,
+ features_type& f,
+ sema_rel::model* m)
+ : root_context (os, u, ops, f, data_ptr (new (shared) data (os))),
+ base_context (static_cast<data*> (root_context::data_.get ()), m),
+ data_ (static_cast<data*> (base_context::data_))
+ {
+ assert (current_ == 0);
+ current_ = this;
+
+ generate_grow = true;
+ need_alias_as = true;
+ insert_send_auto_id = true;
+ delay_freeing_statement_result = false;
+ need_image_clone = false;
+ generate_bulk = false;
+ global_index = true;
+ global_fkey = false;
+ data_->bind_vector_ = "sqlite::bind*";
+ data_->truncated_vector_ = "bool*";
+
+ // Populate the C++ type to DB type map.
+ //
+ for (size_t i (0); i < sizeof (type_map) / sizeof (type_map_entry); ++i)
+ {
+ type_map_entry const& e (type_map[i]);
+
+ type_map_type::value_type v (
+ e.cxx_type,
+ db_type_type (
+ e.db_type, e.db_id_type ? e.db_id_type : e.db_type, e.null));
+
+ data_->type_map_.insert (v);
+ }
+ }
+
+ context::
+ context ()
+ : data_ (current ().data_)
+ {
+ }
+
+ string const& context::
+ convert_expr (string const& sqlt, semantics::data_member& m, bool to)
+ {
+ sql_type const& t (parse_sql_type (sqlt, m));
+ return to ? t.to : t.from;
+ }
+
+ namespace
+ {
+ struct has_grow: traversal::class_
+ {
+ has_grow (bool& r, user_section* s)
+ : r_ (r), section_ (s)
+ {
+ *this >> inherits_ >> *this;
+ }
+
+ virtual void
+ traverse (type& c)
+ {
+ // Ignore transient bases.
+ //
+ if (!(context::object (c) || context::composite (c)))
+ return;
+
+ if (section_ == 0 && c.count ("sqlite-grow"))
+ r_ = c.get<bool> ("sqlite-grow");
+ else
+ {
+ // r_ should be false.
+ //
+ inherits (c);
+
+ if (!r_)
+ names (c);
+
+ if (section_ == 0)
+ c.set ("sqlite-grow", r_);
+ }
+ }
+
+ private:
+ bool& r_;
+ user_section* section_;
+ traversal::inherits inherits_;
+ };
+
+ struct has_grow_member: member_base
+ {
+ has_grow_member (bool& r, user_section* section = 0)
+ : relational::member_base (0, 0, string (), string (), section),
+ r_ (r) {}
+
+ has_grow_member (bool& r,
+ user_section* section,
+ semantics::type* t,
+ const custom_cxx_type* ct,
+ string const& key_prefix = string ())
+ : relational::member_base (t, ct, string (), key_prefix, section),
+ r_ (r) {}
+
+ virtual bool
+ pre (member_info& mi)
+ {
+ // If we have a key prefix (container), then it can't be in a
+ // section (while mi.m can). The same for top-level -- if we got
+ // called, then we shouldn't ignore it.
+ //
+ return !key_prefix_.empty () || top_level_ ||
+ (section_ == 0 && !separate_load (mi.m)) ||
+ (section_ != 0 && *section_ == section (mi.m));
+ }
+
+ virtual void
+ traverse_composite (member_info& mi)
+ {
+ // By calling grow() instead of recursing, we reset any overrides.
+ // We also don't pass section since they don't apply inside
+ // composites.
+ //
+ r_ = r_ || context::grow (dynamic_cast<semantics::class_&> (mi.t));
+ }
+
+ virtual void
+ traverse_string (member_info&)
+ {
+ r_ = true;
+ }
+
+ private:
+ bool& r_;
+ };
+ }
+
+ bool context::
+ grow_impl (semantics::class_& c, user_section* section)
+ {
+ if (section == 0 && c.count ("sqlite-grow"))
+ return c.get<bool> ("sqlite-grow");
+
+ bool r (false);
+ has_grow ct (r, section);
+ has_grow_member mt (r, section);
+ traversal::names names;
+ ct >> names >> mt;
+ ct.traverse (c);
+ return r;
+ }
+
+ bool context::
+ grow_impl (semantics::data_member& m)
+ {
+ bool r (false);
+ has_grow_member mt (r);
+ mt.traverse (m, true);
+ return r;
+ }
+
+ bool context::
+ grow_impl (semantics::data_member& m,
+ semantics::type& t,
+ const custom_cxx_type* ct,
+ string const& kp)
+ {
+ bool r (false);
+ has_grow_member mt (r, 0, &t, ct, kp);
+ mt.traverse (m, true);
+ return r;
+ }
+
+ string context::
+ database_type_impl (semantics::type& t,
+ semantics::names* hint,
+ bool id,
+ bool* null)
+ {
+ string r (base_context::database_type_impl (t, hint, id, null));
+
+ if (!r.empty ())
+ return r;
+
+ using semantics::array;
+
+ // char[N] mapping.
+ //
+ if (array* a = dynamic_cast<array*> (&t))
+ {
+ semantics::type& bt (a->base_type ());
+ if (bt.is_a<semantics::fund_char> () ||
+ bt.is_a<semantics::fund_wchar> ())
+ {
+ if (a->size () != 0)
+ r = "TEXT";
+ }
+ }
+
+ return r;
+ }
+
+ //
+ // SQL type parsing.
+ //
+
+ namespace
+ {
+ struct sql_parser
+ {
+ typedef context::invalid_sql_type invalid_sql_type;
+
+ sql_parser (custom_db_types const* ct): ct_ (ct) {}
+
+ sql_type
+ parse (string sql)
+ {
+ sql_type r;
+
+ // First run the type through the custom mapping, if requested.
+ //
+ if (ct_ != 0)
+ {
+ for (custom_db_types::const_iterator i (ct_->begin ());
+ i != ct_->end (); ++i)
+ {
+ custom_db_type const& t (*i);
+
+ if (t.type.match (sql))
+ {
+ r.to = t.type.replace (sql, t.to);
+ r.from = t.type.replace (sql, t.from);
+ sql = t.type.replace (sql, t.as);
+ break;
+ }
+ }
+ }
+
+ // Parse the type into a sequence of identifiers.
+ //
+ try
+ {
+ l_.lex (sql);
+
+ for (sql_token t (l_.next ()); t.type () != sql_token::t_eos;)
+ {
+ sql_token::token_type tt (t.type ());
+
+ if (tt == sql_token::t_identifier)
+ {
+ ids_.push_back (context::upcase (t.identifier ()));
+ t = l_.next ();
+
+ if (t.punctuation () == sql_token::p_lparen)
+ {
+ if (!parse_range ())
+ return error (m_);
+
+ t = l_.next ();
+ }
+ }
+ else
+ return error ("expected SQLite type name instead of '" +
+ t.string () + "'");
+ }
+ }
+ catch (sql_lexer::invalid_input const& e)
+ {
+ return error ("invalid SQLite type declaration: " + e.message);
+ }
+
+ if (ids_.empty ())
+ return error ("expected SQLite type name");
+
+ // First check our own types.
+ //
+ if (ids_.size () == 2 && ids_[0] == "TEXT" && ids_[1] == "STREAM")
+ {
+ r.type = sql_type::TEXT;
+ r.stream = true;
+ }
+ if (ids_.size () == 2 && ids_[0] == "BLOB" && ids_[1] == "STREAM")
+ {
+ r.type = sql_type::BLOB;
+ r.stream = true;
+ }
+ //
+ // Apply the first four rules of the SQLite type to affinity
+ // conversion algorithm.
+ //
+ else if (find ("INT"))
+ r.type = sql_type::INTEGER;
+ else if (find ("TEXT") || find ("CHAR") || find ("CLOB"))
+ r.type = sql_type::TEXT;
+ else if (find ("BLOB"))
+ r.type = sql_type::BLOB;
+ else if (find ("REAL") || find ("FLOA") || find ("DOUB"))
+ r.type = sql_type::REAL;
+ else
+ {
+ // Instead of the fifth rule which maps everything else
+ // to NUMERICAL (which we don't have), map some commonly
+ // used type names to one of the above types.
+ //
+ string const& id (ids_[0]);
+
+ if (id == "NUMERIC")
+ r.type = sql_type::REAL;
+ else if (id == "DECIMAL")
+ r.type = sql_type::TEXT;
+ else if (id == "BOOLEAN" || id == "BOOL")
+ r.type = sql_type::INTEGER;
+ else if (id == "DATE" || id == "TIME" || id == "DATETIME")
+ r.type = sql_type::TEXT;
+ else
+ return error ("unknown SQLite type '" + id + "'");
+ }
+
+ return r;
+ }
+
+ bool
+ parse_range ()
+ {
+ // Skip tokens until we get the closing paren.
+ //
+ for (sql_token t (l_.next ());; t = l_.next ())
+ {
+ if (t.punctuation () == sql_token::p_rparen)
+ break;
+
+ if (t.type () == sql_token::t_eos)
+ {
+ m_ = "missing ')' in SQLite type declaration";
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private:
+ sql_type
+ error (string const& m)
+ {
+ if (ct_ == 0)
+ return sql_type ();
+ else
+ throw invalid_sql_type (m);
+ }
+
+ bool
+ find (string const& str) const
+ {
+ for (identifiers::const_iterator i (ids_.begin ());
+ i != ids_.end (); ++i)
+ {
+ if (i->find (str) != string::npos)
+ return true;
+ }
+
+ return false;
+ }
+
+ private:
+ custom_db_types const* ct_;
+ sql_lexer l_;
+ string m_; // Error message.
+
+ typedef vector<string> identifiers;
+ identifiers ids_;
+ };
+ }
+
+ sql_type const& context::
+ parse_sql_type (string const& t, semantics::data_member& m, bool custom)
+ {
+ // If this proves to be too expensive, we can maintain a cache of
+ // parsed types across contexts.
+ //
+ data::sql_type_cache::iterator i (data_->sql_type_cache_.find (t));
+
+ if (i != data_->sql_type_cache_.end ()
+ && (custom ? i->second.custom_cached : i->second.straight_cached))
+ {
+ return (custom ? i->second.custom : i->second.straight);
+ }
+ else
+ {
+ try
+ {
+ sql_type st (
+ parse_sql_type (
+ t,
+ custom ? &unit.get<custom_db_types> ("custom-db-types") : 0));
+
+ if (custom)
+ return data_->sql_type_cache_[t].cache_custom (st);
+ else
+ return data_->sql_type_cache_[t].cache_straight (st);
+ }
+ catch (invalid_sql_type const& e)
+ {
+ cerr << m.file () << ":" << m.line () << ":" << m.column ()
+ << ": error: " << e.message () << endl;
+
+ throw operation_failed ();
+ }
+ }
+ }
+
+ sql_type context::
+ parse_sql_type (string const& sqlt, custom_db_types const* ct)
+ {
+ sql_parser p (ct);
+ return p.parse (sqlt);
+ }
+ }
+}
diff --git a/odb/odb/relational/sqlite/context.hxx b/odb/odb/relational/sqlite/context.hxx
new file mode 100644
index 0000000..777998b
--- /dev/null
+++ b/odb/odb/relational/sqlite/context.hxx
@@ -0,0 +1,146 @@
+// file : odb/relational/sqlite/context.hxx
+// license : GNU GPL v3; see accompanying LICENSE file
+
+#ifndef ODB_RELATIONAL_SQLITE_CONTEXT_HXX
+#define ODB_RELATIONAL_SQLITE_CONTEXT_HXX
+
+#include <map>
+
+#include <odb/relational/context.hxx>
+
+namespace relational
+{
+ namespace sqlite
+ {
+ struct sql_type
+ {
+ // Keep the order in each block of types.
+ //
+ enum core_type
+ {
+ INTEGER,
+ REAL,
+ TEXT,
+ BLOB,
+ invalid
+ };
+
+ sql_type (): type (invalid), stream (false) {}
+
+ core_type type;
+ bool stream; // TEXT or BLOB via sqlite3_blob_open().
+
+ // Conversion expressions for custom database types.
+ //
+ std::string to;
+ std::string from;
+ };
+
+ class context: public virtual relational::context
+ {
+ public:
+ sql_type const&
+ parse_sql_type (string const&,
+ semantics::data_member&,
+ bool custom = true);
+ public:
+ struct invalid_sql_type
+ {
+ invalid_sql_type (string const& message): message_ (message) {}
+
+ string const&
+ message () const {return message_;}
+
+ private:
+ string message_;
+ };
+
+ // If custom_db_types is NULL, then this function returns
+ // invalid type instead of throwing in case an unknown type
+ // is encountered.
+ //
+ static sql_type
+ parse_sql_type (string const&, custom_db_types const* = 0);
+
+ protected:
+ virtual string const&
+ convert_expr (string const&, semantics::data_member&, bool);
+
+ virtual bool
+ grow_impl (semantics::class_&, user_section*);
+
+ virtual bool
+ grow_impl (semantics::data_member&);
+
+ virtual bool
+ grow_impl (semantics::data_member&,
+ semantics::type&,
+ const custom_cxx_type*,
+ string const&);
+
+ protected:
+ virtual string
+ database_type_impl (semantics::type&, semantics::names*, bool, bool*);
+
+ public:
+ virtual
+ ~context ();
+ context ();
+ context (std::ostream&,
+ semantics::unit&,
+ options_type const&,
+ features_type& f,
+ sema_rel::model*);
+
+ static context&
+ current ()
+ {
+ return *current_;
+ }
+
+ private:
+ static context* current_;
+
+ private:
+ struct data: base_context::data
+ {
+ data (std::ostream& os): base_context::data (os) {}
+
+ struct sql_type_cache_entry
+ {
+ sql_type_cache_entry ()
+ : custom_cached (false), straight_cached (false) {}
+
+ sql_type const&
+ cache_custom (sql_type const& t)
+ {
+ custom = t;
+ custom_cached = true;
+ return custom;
+ }
+
+ sql_type const&
+ cache_straight (sql_type const& t)
+ {
+ straight = t;
+ straight_cached = true;
+ return straight;
+ }
+
+ sql_type custom; // With custom mapping.
+ sql_type straight; // Without custom mapping.
+
+ bool custom_cached;
+ bool straight_cached;
+ };
+
+ typedef std::map<string, sql_type_cache_entry> sql_type_cache;
+ sql_type_cache sql_type_cache_;
+ };
+
+ data* data_;
+ };
+ }
+}
+
+#endif // ODB_RELATIONAL_SQLITE_CONTEXT_HXX
diff --git a/odb/odb/relational/sqlite/header.cxx b/odb/odb/relational/sqlite/header.cxx
new file mode 100644
index 0000000..1aafe7a
--- /dev/null
+++ b/odb/odb/relational/sqlite/header.cxx
@@ -0,0 +1,63 @@
+// file : odb/relational/sqlite/header.cxx
+// license : GNU GPL v3; see accompanying LICENSE file
+
+#include <odb/relational/header.hxx>
+
+#include <odb/relational/sqlite/common.hxx>
+#include <odb/relational/sqlite/context.hxx>
+
+namespace relational
+{
+ namespace sqlite
+ {
+ namespace header
+ {
+ namespace relational = relational::header;
+
+ struct image_member: relational::image_member_impl<sql_type>,
+ member_base
+ {
+ image_member (base const& x)
+ : member_base::base (x), // virtual base
+ member_base::base_impl (x), // virtual base
+ base_impl (x),
+ member_base (x) {}
+
+ virtual void
+ traverse_integer (member_info& mi)
+ {
+ os << image_type << " " << mi.var << "value;"
+ << "bool " << mi.var << "null;"
+ << endl;
+ }
+
+ virtual void
+ traverse_real (member_info& mi)
+ {
+ os << image_type << " " << mi.var << "value;"
+ << "bool " << mi.var << "null;"
+ << endl;
+ }
+
+ virtual void
+ traverse_string (member_info& mi)
+ {
+ os << image_type << " " << mi.var << "value;"
+ << "std::size_t " << mi.var << "size;"
+ << "bool " << mi.var << "null;"
+ << endl;
+ }
+
+ virtual void
+ traverse_stream (member_info& mi)
+ {
+ os << image_type << " " << mi.var << "value;"
+ << "std::size_t " << mi.var << "size;"
+ << "bool " << mi.var << "null;"
+ << endl;
+ }
+ };
+ entry<image_member> image_member_;
+ }
+ }
+}
diff --git a/odb/odb/relational/sqlite/inline.cxx b/odb/odb/relational/sqlite/inline.cxx
new file mode 100644
index 0000000..dd3274f
--- /dev/null
+++ b/odb/odb/relational/sqlite/inline.cxx
@@ -0,0 +1,42 @@
+// file : odb/relational/sqlite/inline.cxx
+// license : GNU GPL v3; see accompanying LICENSE file
+
+#include <odb/relational/inline.hxx>
+
+#include <odb/relational/sqlite/common.hxx>
+#include <odb/relational/sqlite/context.hxx>
+
+using namespace std;
+
+namespace relational
+{
+ namespace sqlite
+ {
+ namespace inline_
+ {
+ namespace relational = relational::inline_;
+
+ struct null_member: relational::null_member_impl<sql_type>,
+ member_base
+ {
+ null_member (base const& x)
+ : member_base::base (x), // virtual base
+ member_base::base_impl (x), // virtual base
+ base_impl (x),
+ member_base (x)
+ {
+ }
+
+ virtual void
+ traverse_simple (member_info& mi)
+ {
+ if (get_)
+ os << "r = r && i." << mi.var << "null;";
+ else
+ os << "i." << mi.var << "null = true;";
+ }
+ };
+ entry<null_member> null_member_;
+ }
+ }
+}
diff --git a/odb/odb/relational/sqlite/model.cxx b/odb/odb/relational/sqlite/model.cxx
new file mode 100644
index 0000000..da16ded
--- /dev/null
+++ b/odb/odb/relational/sqlite/model.cxx
@@ -0,0 +1,91 @@
+// file : odb/relational/sqlite/model.cxx
+// license : GNU GPL v3; see accompanying LICENSE file
+
+#include <sstream>
+
+#include <odb/relational/model.hxx>
+
+#include <odb/relational/sqlite/common.hxx>
+#include <odb/relational/sqlite/context.hxx>
+
+using namespace std;
+
+namespace relational
+{
+ namespace sqlite
+ {
+ namespace model
+ {
+ namespace relational = relational::model;
+
+ struct object_columns: relational::object_columns, context
+ {
+ object_columns (base const& x): base (x) {}
+
+ virtual string
+ type (semantics::data_member& m)
+ {
+ // Translate BLOB|TEXT STREAM to just BLOB|TEXT.
+ //
+ string r (relational::object_columns::type (m));
+
+ sql_type const& t (parse_sql_type (r, m, false));
+ if (t.stream)
+ {
+ switch (t.type)
+ {
+ case sql_type::BLOB: r = "BLOB"; break;
+ case sql_type::TEXT: r = "TEXT"; break;
+ default: break;
+ }
+ }
+
+ return r;
+ }
+
+ virtual bool
+ null (semantics::data_member& m)
+ {
+ return options.sqlite_override_null () || base::null (m);
+ }
+
+ virtual string
+ default_enum (semantics::data_member& m, tree en, string const&)
+ {
+ // Make sure the column is mapped to INTEGER.
+ //
+ sql_type const& t (parse_sql_type (column_type (), m, false));
+ if (t.type != sql_type::INTEGER)
+ {
+ cerr << m.file () << ":" << m.line () << ":" << m.column ()
+ << ": error: column with default value specified as C++ "
+ << "enumerator must map to SQLite INTEGER" << endl;
+
+ throw operation_failed ();
+ }
+
+ using semantics::enumerator;
+
+ enumerator& e (dynamic_cast<enumerator&> (*unit.find (en)));
+
+ ostringstream ostr;
+
+ if (e.enum_ ().unsigned_ ())
+ ostr << e.value ();
+ else
+ ostr << static_cast<long long> (e.value ());
+
+ return ostr.str ();
+ }
+
+ virtual void
+ primary_key (sema_rel::primary_key& pk)
+ {
+ if (pk.auto_ () && options.sqlite_lax_auto_id ())
+ pk.extra ()["lax"] = "true";
+ }
+ };
+ entry<object_columns> object_columns_;
+ }
+ }
+}
diff --git a/odb/odb/relational/sqlite/schema.cxx b/odb/odb/relational/sqlite/schema.cxx
new file mode 100644
index 0000000..f5549b4
--- /dev/null
+++ b/odb/odb/relational/sqlite/schema.cxx
@@ -0,0 +1,455 @@
+// file : odb/relational/sqlite/schema.cxx
+// license : GNU GPL v3; see accompanying LICENSE file
+
+#include <odb/relational/schema.hxx>
+
+#include <odb/relational/sqlite/common.hxx>
+#include <odb/relational/sqlite/context.hxx>
+
+namespace relational
+{
+ namespace sqlite
+ {
+ namespace schema
+ {
+ namespace relational = relational::schema;
+
+ //
+ // Drop.
+ //
+
+ struct drop_column: trav_rel::drop_column, relational::common
+ {
+ drop_column (relational::common const& c)
+ : relational::common (c), first_ (true) {}
+
+ virtual void
+ traverse (sema_rel::drop_column& dc)
+ {
+ // SQLite does not support dropping columns. If this column is
+ // not NULLable, then there is nothing we can do. Otherwise, do
+ // a logical DROP by setting all the values to NULL.
+ //
+ sema_rel::column& c (find<sema_rel::column> (dc));
+
+ if (!c.null ())
+ {
+ cerr << "error: SQLite does not support dropping of columns" <<
+ endl;
+ cerr << "info: first dropped column is '" << dc.name () <<
+ "' in table '" << dc.table ().name () << "'" << endl;
+ cerr << "info: could have performed logical drop if the column " <<
+ "allowed NULL values" << endl;
+ throw operation_failed ();
+ }
+
+ if (first_)
+ first_ = false;
+ else
+ os << "," << endl
+ << " ";
+
+ os << quote_id (dc.name ()) << " = NULL";
+ }
+
+ private:
+ bool first_;
+ };
+ // Not registered as an override.
+
+ struct drop_index: relational::drop_index, context
+ {
+ drop_index (base const& x): base (x) {}
+
+ virtual string
+ name (sema_rel::index& in)
+ {
+ // In SQLite, index names can be qualified with the database.
+ //
+ sema_rel::table& t (static_cast<sema_rel::table&> (in.scope ()));
+ sema_rel::qname n (t.name ().qualifier ());
+ n.append (in.name ());
+ return quote_id (n);
+ }
+ };
+ entry<drop_index> drop_index_;
+
+ struct drop_table: relational::drop_table, context
+ {
+ drop_table (base const& x): base (x) {}
+
+ virtual void
+ traverse (sema_rel::table& t, bool migration)
+ {
+ // In SQLite there is no way to drop foreign keys except as part
+ // of the table.
+ //
+ if (pass_ != 2)
+ return;
+
+ // Polymorphic base cleanup code. Because we cannot drop foreign
+ // keys, we will trigger cascade deletion. The only way to work
+ // around this problem is to delete from the root table and rely
+ // on the cascade to clean up the rest.
+ //
+ if (migration && t.extra ()["kind"] == "polymorphic derived object")
+ {
+ using sema_rel::model;
+ using sema_rel::table;
+ using sema_rel::primary_key;
+ using sema_rel::foreign_key;
+
+ model& m (dynamic_cast<model&> (t.scope ()));
+
+ table* p (&t);
+ do
+ {
+ // The polymorphic link is the first primary key.
+ //
+ for (table::names_iterator i (p->names_begin ());
+ i != p->names_end (); ++i)
+ {
+ if (foreign_key* fk = dynamic_cast<foreign_key*> (
+ &i->nameable ()))
+ {
+ p = m.find<table> (fk->referenced_table ());
+ assert (p != 0); // Base table should be there.
+ break;
+ }
+ }
+ }
+ while (p->extra ()["kind"] != "polymorphic root object");
+
+ primary_key& rkey (*p->find<primary_key> (""));
+ primary_key& dkey (*t.find<primary_key> (""));
+ assert (rkey.contains_size () == dkey.contains_size ());
+ delete_ (p->name (), t.name (), rkey, dkey);
+ }
+
+ drop (t, migration);
+ }
+ };
+ entry<drop_table> drop_table_;
+
+ //
+ // Create.
+ //
+
+ struct create_column: relational::create_column, context
+ {
+ create_column (base const& x): base (x) {}
+
+ virtual void
+ traverse (sema_rel::add_column& ac)
+ {
+ using sema_rel::alter_table;
+ using sema_rel::add_column;
+ using sema_rel::add_foreign_key;
+
+ alter_table& at (static_cast<alter_table&> (ac.scope ()));
+
+ pre_statement ();
+
+ os << "ALTER TABLE " << quote_id (at.name ()) << endl
+ << " ADD COLUMN ";
+
+ // In SQLite it is impossible to alter a column later, so unless
+ // it has a default value, we add it as NULL. Without this, it
+ // will be impossible to add a column to a table that contains
+ // some rows.
+ //
+ create (ac);
+
+ // SQLite doesn't support adding foreign keys other than inline
+ // via a column definition. See if we can handle any.
+ //
+ add_foreign_key* afk (0);
+
+ for (add_column::contained_iterator i (ac.contained_begin ());
+ i != ac.contained_end ();
+ ++i)
+ {
+ if ((afk = dynamic_cast<add_foreign_key*> (&i->key ())))
+ {
+ // Check that it is a single-column foreign key. Also make
+ // sure the column and foreign key are from the same changeset.
+ //
+ if (afk->contains_size () != 1 || &ac.scope () != &afk->scope ())
+ afk = 0;
+ else
+ break;
+ }
+ }
+
+ if (afk != 0)
+ {
+ os << " CONSTRAINT " << quote_id (afk->name ()) << " REFERENCES " <<
+ quote_id (afk->referenced_table ().uname ()) << " (" <<
+ quote_id (afk->referenced_columns ()[0]) << ")";
+
+ bool del (afk->on_delete () != sema_rel::foreign_key::no_action);
+ bool def (!afk->not_deferrable ());
+
+ if (del || def)
+ {
+ instance<relational::create_foreign_key> cfk (*this);
+
+ if (del)
+ cfk->on_delete (afk->on_delete ());
+
+ if (def)
+ cfk->deferrable (afk->deferrable ());
+ }
+
+ afk->set ("sqlite-fk-defined", true); // Mark it as defined.
+ }
+
+ os << endl;
+ post_statement ();
+ }
+
+ virtual void
+ auto_ (sema_rel::primary_key& pk)
+ {
+ if (pk.extra ().count ("lax"))
+ os << " /*AUTOINCREMENT*/";
+ else
+ os << " AUTOINCREMENT";
+ }
+ };
+ entry<create_column> create_column_;
+
+ struct create_foreign_key: relational::create_foreign_key, context
+ {
+ create_foreign_key (base const& x): base (x) {}
+
+ virtual void
+ traverse (sema_rel::foreign_key& fk)
+ {
+ // In SQLite, all constraints are defined as part of a table.
+ //
+ os << "," << endl
+ << " CONSTRAINT ";
+
+ create (fk);
+ }
+
+ virtual string
+ table_name (sema_rel::foreign_key& fk)
+ {
+ // In SQLite, the referenced table cannot be qualified with the
+ // database name (it has to be in the same database anyway).
+ //
+ return quote_id (fk.referenced_table ().uname ());
+ }
+ };
+ entry<create_foreign_key> create_foreign_key_;
+
+ struct create_index: relational::create_index, context
+ {
+ create_index (base const& x): base (x) {}
+
+ virtual string
+ name (sema_rel::index& in)
+ {
+ // In SQLite, index names can be qualified with the database.
+ //
+ sema_rel::table& t (static_cast<sema_rel::table&> (in.scope ()));
+ sema_rel::qname n (t.name ().qualifier ());
+ n.append (in.name ());
+ return quote_id (n);
+ }
+
+ virtual string
+ table_name (sema_rel::index& in)
+ {
+ // In SQLite, the index table cannot be qualified with the
+ // database name (it has to be in the same database).
+ //
+ return quote_id (
+ static_cast<sema_rel::table&> (in.scope ()).name ().uname ());
+ }
+ };
+ entry<create_index> create_index_;
+
+ struct create_table: relational::create_table, context
+ {
+ create_table (base const& x): base (x) {}
+
+ void
+ traverse (sema_rel::table& t)
+ {
+ // For SQLite we do everything in a single pass since there
+ // is no way to add constraints later.
+ //
+ if (pass_ == 1)
+ create (t);
+ }
+ };
+ entry<create_table> create_table_;
+
+ //
+ // Alter.
+ //
+
+ struct alter_table_pre: relational::alter_table_pre, context
+ {
+ alter_table_pre (base const& x): base (x) {}
+
+ virtual void
+ alter (sema_rel::alter_table& at)
+ {
+ // SQLite can only add a single column per ALTER TABLE statement.
+ //
+ instance<create_column> cc (*this);
+ trav_rel::unames n (*cc);
+ names (at, n);
+
+ // SQLite does not support altering columns.
+ //
+ if (sema_rel::alter_column* ac = check<sema_rel::alter_column> (at))
+ {
+ cerr << "error: SQLite does not support altering of columns"
+ << endl;
+ cerr << "info: first altered column is '" << ac->name () <<
+ "' in table '" << at.name () << "'" << endl;
+ throw operation_failed ();
+ }
+
+ // SQLite does not support dropping constraints. We are going to
+ // ignore this if the column is NULL'able since in most cases
+ // the constraint is going to be dropped as a result of the
+ // column drop (e.g., an object pointer member got deleted).
+ // If we were not to allow this, then it would be impossible
+ // to do logical drop for pointer columns.
+ //
+ for (sema_rel::alter_table::names_iterator i (at.names_begin ());
+ i != at.names_end (); ++i)
+ {
+ using sema_rel::foreign_key;
+ using sema_rel::drop_foreign_key;
+
+ drop_foreign_key* dfk (
+ dynamic_cast<drop_foreign_key*> (&i->nameable ()));
+
+ if (dfk == 0)
+ continue;
+
+ foreign_key& fk (find<foreign_key> (*dfk));
+
+ for (foreign_key::contains_iterator j (fk.contains_begin ());
+ j != fk.contains_end (); ++j)
+ {
+ if (j->column ().null ())
+ continue;
+
+ cerr << "error: SQLite does not support dropping of foreign " <<
+ "keys" << endl;
+ cerr << "info: first dropped foreign key is '" << dfk->name () <<
+ "' in table '" << at.name () << "'" << endl;
+ cerr << "info: could have ignored it if the contained " <<
+ "column(s) allowed NULL values" << endl;
+ throw operation_failed ();
+ }
+ }
+ }
+ };
+ entry<alter_table_pre> alter_table_pre_;
+
+ struct alter_table_post: relational::alter_table_post, context
+ {
+ alter_table_post (base const& x): base (x) {}
+
+ virtual void
+ alter (sema_rel::alter_table& at)
+ {
+ // SQLite does not support altering columns (we have to do this
+ // in both alter_table_pre/post because of the
+ // check_alter_column_null() test in the common code).
+ //
+ if (sema_rel::alter_column* ac = check<sema_rel::alter_column> (at))
+ {
+ cerr << "error: SQLite does not support altering of columns"
+ << endl;
+ cerr << "info: first altered column is '" << ac->name () <<
+ "' in table '" << at.name () << "'" << endl;
+ throw operation_failed ();
+ }
+
+ // Try to do logical column drop.
+ //
+ if (check<sema_rel::drop_column> (at))
+ {
+ pre_statement ();
+
+ os << "UPDATE " << quote_id (at.name ()) << endl
+ << " SET ";
+
+ drop_column dc (*this);
+ trav_rel::unames n (dc);
+ names (at, n);
+ os << endl;
+
+ post_statement ();
+ }
+
+ // SQLite doesn't support adding foreign keys other than inline
+ // via a column definition. See if there are any that we couldn't
+ // handle that way.
+ //
+ for (sema_rel::alter_table::names_iterator i (at.names_begin ());
+ i != at.names_end (); ++i)
+ {
+ sema_rel::add_foreign_key* afk (
+ dynamic_cast<sema_rel::add_foreign_key*> (&i->nameable ()));
+
+ if (afk == 0 || afk->count ("sqlite-fk-defined"))
+ continue;
+
+ cerr << "error: SQLite does not support adding foreign keys"
+ << endl;
+ cerr << "info: first added foreign key is '" << afk->name () <<
+ "' in table '" << at.name () << "'" << endl;
+ throw operation_failed ();
+ }
+ }
+ };
+ entry<alter_table_post> alter_table_post_;
+
+ //
+ // Schema version table.
+ //
+
+ struct version_table: relational::version_table, context
+ {
+ version_table (base const& x): base (x) {}
+
+ virtual void
+ create_table ()
+ {
+ pre_statement ();
+
+ os << "CREATE TABLE IF NOT EXISTS " << qt_ << " (" << endl
+ << " " << qn_ << " TEXT NOT NULL PRIMARY KEY," << endl
+ << " " << qv_ << " INTEGER NOT NULL," << endl
+ << " " << qm_ << " INTEGER NOT NULL)" << endl;
+
+ post_statement ();
+ }
+
+ virtual void
+ create (sema_rel::version v)
+ {
+ pre_statement ();
+
+ os << "INSERT OR IGNORE INTO " << qt_ << " (" << endl
+ << " " << qn_ << ", " << qv_ << ", " << qm_ << ")" << endl
+ << " VALUES (" << qs_ << ", " << v << ", 0)" << endl;
+
+ post_statement ();
+ }
+ };
+ entry<version_table> version_table_;
+ }
+ }
+}
diff --git a/odb/odb/relational/sqlite/source.cxx b/odb/odb/relational/sqlite/source.cxx
new file mode 100644
index 0000000..5a4b9d3
--- /dev/null
+++ b/odb/odb/relational/sqlite/source.cxx
@@ -0,0 +1,471 @@
+// file : odb/relational/sqlite/source.cxx
+// license : GNU GPL v3; see accompanying LICENSE file
+
+#include <odb/relational/source.hxx>
+
+#include <odb/relational/sqlite/common.hxx>
+#include <odb/relational/sqlite/context.hxx>
+
+using namespace std;
+
+namespace relational
+{
+ namespace sqlite
+ {
+ namespace source
+ {
+ 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> query_parameters_;
+
+ //
+ // bind
+ //
+
+ struct bind_member: relational::bind_member_impl<sql_type>,
+ member_base
+ {
+ bind_member (base const& x)
+ : member_base::base (x), // virtual base
+ member_base::base_impl (x), // virtual base
+ base_impl (x),
+ member_base (x)
+ {
+ }
+
+ virtual void
+ traverse_integer (member_info& mi)
+ {
+ os << b << ".type = sqlite::bind::integer;"
+ << b << ".buffer = &" << arg << "." << mi.var << "value;"
+ << b << ".is_null = &" << arg << "." << mi.var << "null;";
+ }
+
+ virtual void
+ traverse_real (member_info& mi)
+ {
+ os << b << ".type = sqlite::bind::real;"
+ << b << ".buffer = &" << arg << "." << mi.var << "value;"
+ << b << ".is_null = &" << arg << "." << mi.var << "null;";
+ }
+
+ virtual void
+ traverse_text (member_info& mi)
+ {
+ os << b << ".type = sqlite::image_traits<" << endl
+ << " " << mi.fq_type () << "," << endl
+ << " sqlite::id_text>::bind_value;"
+ << b << ".buffer = " << arg << "." << mi.var << "value.data ();"
+ << b << ".size = &" << arg << "." << mi.var << "size;"
+ << b << ".capacity = " << arg << "." << mi.var <<
+ "value.capacity ();"
+ << b << ".is_null = &" << arg << "." << mi.var << "null;";
+ }
+
+ virtual void
+ traverse_blob (member_info& mi)
+ {
+ os << b << ".type = sqlite::bind::blob;"
+ << b << ".buffer = " << arg << "." << mi.var << "value.data ();"
+ << b << ".size = &" << arg << "." << mi.var << "size;"
+ << b << ".capacity = " << arg << "." << mi.var <<
+ "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> bind_member_;
+
+ //
+ // grow
+ //
+
+ struct grow_member: relational::grow_member_impl<sql_type>,
+ member_base
+ {
+ grow_member (base const& x)
+ : member_base::base (x), // virtual base
+ member_base::base_impl (x), // virtual base
+ base_impl (x),
+ member_base (x) {}
+
+ virtual void
+ traverse_integer (member_info&)
+ {
+ os << e << " = false;"
+ << endl;
+ }
+
+ virtual void
+ traverse_real (member_info&)
+ {
+ os << e << " = false;"
+ << endl;
+ }
+
+ virtual void
+ traverse_string (member_info& mi)
+ {
+ os << "if (" << e << ")" << endl
+ << "{"
+ << "i." << mi.var << "value.capacity (i." << mi.var << "size);"
+ << "grew = true;"
+ << "}";
+ }
+
+ virtual void
+ traverse_stream (member_info&)
+ {
+ os << e << " = false;"
+ << endl;
+ }
+ };
+ entry<grow_member> grow_member_;
+
+ //
+ // init image
+ //
+
+ struct init_image_member: relational::init_image_member_impl<sql_type>,
+ member_base
+ {
+ init_image_member (base const& x)
+ : member_base::base (x), // virtual base
+ member_base::base_impl (x), // virtual base
+ base_impl (x),
+ member_base (x)
+ {
+ }
+
+ virtual void
+ set_null (member_info& mi)
+ {
+ os << "i." << mi.var << "null = true;";
+ }
+
+ virtual void
+ traverse_integer (member_info& mi)
+ {
+ os << traits << "::set_image (" << endl
+ << "i." << mi.var << "value," << endl
+ << "is_null," << endl
+ << member << ");"
+ << "i." << mi.var << "null = is_null;";
+ }
+
+ virtual void
+ traverse_real (member_info& mi)
+ {
+ os << traits << "::set_image (" << endl
+ << "i." << mi.var << "value," << endl
+ << "is_null," << endl
+ << member << ");"
+ << "i." << mi.var << "null = is_null;";
+ }
+
+ virtual void
+ traverse_string (member_info& mi)
+ {
+ os << "std::size_t cap (i." << mi.var << "value.capacity ());"
+ << traits << "::set_image (" << endl
+ << "i." << mi.var << "value," << endl
+ << "i." << mi.var << "size," << endl
+ << "is_null," << endl
+ << member << ");"
+ << "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> init_image_member_;
+
+ //
+ // init value
+ //
+
+ struct init_value_member: relational::init_value_member_impl<sql_type>,
+ member_base
+ {
+ init_value_member (base const& x)
+ : member_base::base (x), // virtual base
+ member_base::base_impl (x), // virtual base
+ base_impl (x),
+ member_base (x)
+ {
+ }
+
+ virtual void
+ get_null (string const& var) const
+ {
+ os << "i." << var << "null";
+ }
+
+ virtual void
+ traverse_integer (member_info& mi)
+ {
+ os << traits << "::set_value (" << endl
+ << member << "," << endl
+ << "i." << mi.var << "value," << endl
+ << "i." << mi.var << "null);"
+ << endl;
+ }
+
+ virtual void
+ traverse_real (member_info& mi)
+ {
+ os << traits << "::set_value (" << endl
+ << member << "," << endl
+ << "i." << mi.var << "value," << endl
+ << "i." << mi.var << "null);"
+ << endl;
+ }
+
+ virtual void
+ traverse_string (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;
+ }
+
+ 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> init_value_member_;
+
+ 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) {}
+
+ virtual void
+ cache_result (string const&)
+ {
+ // Caching is not necessary since SQLite can execute several
+ // interleaving statements.
+ //
+ }
+
+ virtual void
+ process_statement_columns (relational::statement_columns& cols,
+ statement_kind sk,
+ bool)
+ {
+ statement_columns_common::process (cols, sk);
+ }
+ };
+ entry<container_traits> container_traits_;
+
+ 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,
+ bool)
+ {
+ statement_columns_common::process (cols, sk);
+ }
+ };
+ entry<section_traits> section_traits_;
+
+ struct class_: relational::class_, statement_columns_common
+ {
+ class_ (base const& x): base (x) {}
+
+ virtual void
+ init_auto_id (semantics::data_member& m, string const& im)
+ {
+ // Don't set the id value to NULL if this is a nullable wrapper.
+ // This will allow the user to control whether the value is auto or
+ // manually assigned by using something like this:
+ //
+ // #pragma db auto
+ // odb::nullable<int64_t> id;
+ //
+ semantics::type& t (utype (m));
+ if (wrapper (t) && t.template get<bool> ("wrapper-null-handler"))
+ return;
+
+ os << im << "null = true;"
+ << endl;
+ }
+
+ virtual string
+ select_trailer (type&)
+ {
+ // SQLite has not support for FOR UPDATE and since this is an
+ // optimization, we simply ignore it.
+ //
+ return "";
+ }
+
+ virtual string
+ join_syntax (view_object const& vo)
+ {
+ const char* n (0);
+
+ if (vo.join == view_object::full)
+ n = "FULL OUTER JOIN";
+ else if (vo.join == view_object::right)
+ n = "RIGHT OUTER JOIN";
+
+ if (n != 0)
+ {
+ error (vo.loc) << n << " is not supported by SQLite" << endl;
+ throw operation_failed ();
+ }
+
+ 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_> class_entry_;
+ }
+ }
+}