diff options
Diffstat (limited to 'odb/odb/relational/sqlite')
-rw-r--r-- | odb/odb/relational/sqlite/common.cxx | 217 | ||||
-rw-r--r-- | odb/odb/relational/sqlite/common.hxx | 147 | ||||
-rw-r--r-- | odb/odb/relational/sqlite/context.cxx | 490 | ||||
-rw-r--r-- | odb/odb/relational/sqlite/context.hxx | 146 | ||||
-rw-r--r-- | odb/odb/relational/sqlite/header.cxx | 63 | ||||
-rw-r--r-- | odb/odb/relational/sqlite/inline.cxx | 42 | ||||
-rw-r--r-- | odb/odb/relational/sqlite/model.cxx | 91 | ||||
-rw-r--r-- | odb/odb/relational/sqlite/schema.cxx | 455 | ||||
-rw-r--r-- | odb/odb/relational/sqlite/source.cxx | 471 |
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_; + } + } +} |