diff options
Diffstat (limited to 'odb/odb/relational/sqlite/context.cxx')
-rw-r--r-- | odb/odb/relational/sqlite/context.cxx | 490 |
1 files changed, 490 insertions, 0 deletions
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); + } + } +} |