From c1877f84f3596f67245abe6658b08c050bd1e686 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 4 Sep 2012 11:22:07 +0200 Subject: NULL handling improvements Add support for specifying NULL-ness for types with built-in mapping. Handle Oracle [N]VARCHAR2 and SQLite FLOAT oddities using this mechanism instead of overriding it at the schema generation level. Also use the is_null argument that is passed to value_traits::init_image() to indicate whether the value can be NULL. --- doc/manual.xhtml | 26 ++++++++----- odb/context.cxx | 77 ++++++++++++++++++++++++++++++--------- odb/context.hxx | 26 ++++++++----- odb/relational/mssql/context.cxx | 48 ++++++++++++------------ odb/relational/mysql/context.cxx | 44 +++++++++++----------- odb/relational/oracle/context.cxx | 46 ++++++++++++----------- odb/relational/oracle/schema.cxx | 44 ---------------------- odb/relational/pgsql/context.cxx | 44 +++++++++++----------- odb/relational/source.hxx | 6 ++- odb/relational/sqlite/context.cxx | 42 +++++++++++---------- 10 files changed, 217 insertions(+), 186 deletions(-) diff --git a/doc/manual.xhtml b/doc/manual.xhtml index ab0ab65..a1e0360 100644 --- a/doc/manual.xhtml +++ b/doc/manual.xhtml @@ -13878,13 +13878,13 @@ class object float REAL - NOT NULL + NULL double REAL - NOT NULL + NULL @@ -13894,6 +13894,14 @@ class object +

SQLite represents NaN FLOAT values + as NULL values. As a result, columns of the + float and double types are by default + declared as NULL. However, you can override this by + explicitly declaring them as NOT NULL with the + db not_null pragma (Section + 12.4.6, "null/not_null").

+

The SQLite ODB runtime library also provides support for mapping the std::vector<char>, std::vector<unsigned char>, @@ -15344,13 +15352,13 @@ class object

In Oracle empty VARCHAR2 and NVARCHAR2 strings are represented as NULL values. As a result, - in the generated schema, columns of these types are declared as - NULL even if explicitly declared as - NOT NULL with the db not_null pragma - (Section 12.4.6, "null/not_null"), - except for primary key columns. This also means that for object ids - that are mapped to these Oracle types, an empty string is an invalid - value.

+ columns of the std::string type are by default declared + as NULL except for primary key columns. However, you + can override this by explicitly declaring them as NOT NULL + with the db not_null pragma (Section + 12.4.6, "null/not_null"). This also means that for + object ids that are mapped to these Oracle types, an empty string is + an invalid value.

The Oracle ODB runtime library also provides support for mapping the std::string type to the Oracle CHAR, diff --git a/odb/context.cxx b/odb/context.cxx index 6b59033..fab5432 100644 --- a/odb/context.cxx +++ b/odb/context.cxx @@ -580,7 +580,7 @@ readonly (semantics::data_member& m) } bool context:: -null (data_member_path const& mp) +null (data_member_path const& mp) const { // Outer members can override the null-ability of the inner ones. So // start from the most outer member. @@ -595,9 +595,10 @@ null (data_member_path const& mp) } bool context:: -null (semantics::data_member& m) +null (semantics::data_member& m) const { - semantics::type& t (utype (m)); + semantics::names* hint; + semantics::type& t (utype (m, hint)); if (object_pointer (t)) { @@ -612,9 +613,7 @@ null (semantics::data_member& m) return true; if (!t.count ("not-null")) - { return true; - } } return false; @@ -633,6 +632,8 @@ null (semantics::data_member& m) if (!t.count ("not-null")) { + semantics::type* pt; + // Check if this type is a wrapper. // if (t.get ("wrapper")) @@ -645,9 +646,25 @@ null (semantics::data_member& m) // Otherwise, check the wrapped type. // - if (t.get ("wrapper-type")->count ("null")) + pt = t.get ("wrapper-type"); + hint = t.get ("wrapper-hint"); + pt = &utype (*pt, hint); + + if (pt->count ("null")) return true; + + if (pt->count ("not-null")) + return false; } + else + pt = &t; + + // See if this is one of the types with built-in mapping. + // + type_map_type::const_iterator i (data_->type_map_.find (*pt, hint)); + + if (i != data_->type_map_.end ()) + return i->second.null; } } @@ -656,13 +673,14 @@ null (semantics::data_member& m) } bool context:: -null (semantics::data_member& m, string const& kp) +null (semantics::data_member& m, string const& kp) const { if (kp.empty ()) return null (m); semantics::type& c (utype (m)); semantics::type& t (member_utype (m, kp)); + semantics::names* hint (0); // No hint for container elements. if (object_pointer (t)) { @@ -705,6 +723,8 @@ null (semantics::data_member& m, string const& kp) if (!t.count ("not-null")) { + semantics::type* pt; + // Check if this type is a wrapper. // if (t.get ("wrapper")) @@ -717,12 +737,25 @@ null (semantics::data_member& m, string const& kp) // Otherwise, check the wrapped type. // - semantics::type& wt ( - utype (*t.get ("wrapper-type"))); + pt = t.get ("wrapper-type"); + hint = t.get ("wrapper-hint"); + pt = &utype (*pt, hint); - if (wt.count ("null")) + if (pt->count ("null")) return true; + + if (pt->count ("not-null")) + return false; } + else + pt = &t; + + // See if this is one of the types with built-in mapping. + // + type_map_type::const_iterator i (data_->type_map_.find (*pt, hint)); + + if (i != data_->type_map_.end ()) + return i->second.null; } } } @@ -1511,28 +1544,36 @@ column_options (semantics::data_member& m, string const& kp) return r; } -string context:: -database_type_impl (semantics::type& t, semantics::names* hint, bool id) +context::type_map_type::const_iterator context::type_map_type:: +find (semantics::type& t, semantics::names* hint) { - type_map_type::const_iterator end (data_->type_map_.end ()), i (end); + const_iterator e (end ()), i (e); // First check the hinted name. This allows us to handle things like // size_t which is nice to map to the same type irrespective of the // actual type. Since this type can be an alias for the one we are // interested in, go into nested hints. // - for (; hint != 0 && i == end; hint = hint->hint ()) + for (; hint != 0 && i == e; hint = hint->hint ()) { - i = data_->type_map_.find (t.fq_name (hint)); + i = base::find (t.fq_name (hint)); } // If the hinted name didn't work, try the primary name (e.g., // ::std::string) instead of a user typedef (e.g., my_string). // - if (i == end) - i = data_->type_map_.find (t.fq_name ()); + if (i == e) + i = base::find (t.fq_name ()); + + return i; +} + +string context:: +database_type_impl (semantics::type& t, semantics::names* hint, bool id) +{ + type_map_type::const_iterator i (data_->type_map_.find (t, hint)); - if (i != end) + if (i != data_->type_map_.end ()) return id ? i->second.id_type : i->second.type; else return string (); diff --git a/odb/context.hxx b/odb/context.hxx index 3651344..dc5eb05 100644 --- a/odb/context.hxx +++ b/odb/context.hxx @@ -502,14 +502,14 @@ public: // Null-able. // - static bool - null (data_member_path const&); + bool + null (data_member_path const&) const; - static bool - null (semantics::data_member&); + bool + null (semantics::data_member&) const; - static bool - null (semantics::data_member&, string const& key_prefix); + bool + null (semantics::data_member&, string const& key_prefix) const; // Optimistic concurrency. // @@ -911,15 +911,23 @@ public: struct db_type_type { db_type_type () {} - db_type_type (string const& t, string const& it) - : type (t), id_type (it) + db_type_type (string const& t, string const& it, bool n) + : type (t), id_type (it), null (n) { } string type; string id_type; + bool null; + }; + + struct type_map_type: std::map + { + typedef std::map base; + + const_iterator + find (semantics::type&, semantics::names* hint); }; - typedef std::map type_map_type; protected: struct data diff --git a/odb/relational/mssql/context.cxx b/odb/relational/mssql/context.cxx index efd8f2f..df6d6fc 100644 --- a/odb/relational/mssql/context.cxx +++ b/odb/relational/mssql/context.cxx @@ -20,43 +20,44 @@ namespace relational { struct type_map_entry { - const char* const cxx_type; - const char* const db_type; - const char* const db_id_type; + 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", "BIT", 0}, + {"bool", "BIT", 0, false}, - {"char", "TINYINT", 0}, - {"signed char", "TINYINT", 0}, - {"unsigned char", "TINYINT", 0}, + {"char", "TINYINT", 0, false}, + {"signed char", "TINYINT", 0, false}, + {"unsigned char", "TINYINT", 0, false}, - {"short int", "SMALLINT", 0}, - {"short unsigned int", "SMALLINT", 0}, + {"short int", "SMALLINT", 0, false}, + {"short unsigned int", "SMALLINT", 0, false}, - {"int", "INT", 0}, - {"unsigned int", "INT", 0}, + {"int", "INT", 0, false}, + {"unsigned int", "INT", 0, false}, - {"long int", "BIGINT", 0}, - {"long unsigned int", "BIGINT", 0}, + {"long int", "BIGINT", 0, false}, + {"long unsigned int", "BIGINT", 0, false}, - {"long long int", "BIGINT", 0}, - {"long long unsigned int", "BIGINT", 0}, + {"long long int", "BIGINT", 0, false}, + {"long long unsigned int", "BIGINT", 0, false}, - {"float", "REAL", 0}, - {"double", "FLOAT", 0}, + {"float", "REAL", 0, false}, + {"double", "FLOAT", 0, false}, - {"::std::string", "VARCHAR(512)", "VARCHAR(256)"}, - {"::std::wstring", "NVARCHAR(512)", "NVARCHAR(256)"}, + {"::std::string", "VARCHAR(512)", "VARCHAR(256)", false}, + {"::std::wstring", "NVARCHAR(512)", "NVARCHAR(256)", false}, - {"::size_t", "BIGINT", 0}, - {"::std::size_t", "BIGINT", 0}, + {"::size_t", "BIGINT", 0, false}, + {"::std::size_t", "BIGINT", 0, false}, // Windows GUID/UUID (typedef struct _GUID {...} GUID, UUID;). // - {"::_GUID", "UNIQUEIDENTIFIER", 0} + {"::_GUID", "UNIQUEIDENTIFIER", 0, false} }; } @@ -97,7 +98,8 @@ namespace relational 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)); + db_type_type ( + e.db_type, e.db_id_type ? e.db_id_type : e.db_type, e.null)); data_->type_map_.insert (v); } diff --git a/odb/relational/mysql/context.cxx b/odb/relational/mysql/context.cxx index 1e44b34..8bf700e 100644 --- a/odb/relational/mysql/context.cxx +++ b/odb/relational/mysql/context.cxx @@ -21,38 +21,39 @@ namespace relational { struct type_map_entry { - const char* const cxx_type; - const char* const db_type; - const char* const db_id_type; + 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", "TINYINT(1)", 0}, + {"bool", "TINYINT(1)", 0, false}, - {"char", "TINYINT", 0}, - {"signed char", "TINYINT", 0}, - {"unsigned char", "TINYINT UNSIGNED", 0}, + {"char", "TINYINT", 0, false}, + {"signed char", "TINYINT", 0, false}, + {"unsigned char", "TINYINT UNSIGNED", 0, false}, - {"short int", "SMALLINT", 0}, - {"short unsigned int", "SMALLINT UNSIGNED", 0}, + {"short int", "SMALLINT", 0, false}, + {"short unsigned int", "SMALLINT UNSIGNED", 0, false}, - {"int", "INT", 0}, - {"unsigned int", "INT UNSIGNED", 0}, + {"int", "INT", 0, false}, + {"unsigned int", "INT UNSIGNED", 0, false}, - {"long int", "BIGINT", 0}, - {"long unsigned int", "BIGINT UNSIGNED", 0}, + {"long int", "BIGINT", 0, false}, + {"long unsigned int", "BIGINT UNSIGNED", 0, false}, - {"long long int", "BIGINT", 0}, - {"long long unsigned int", "BIGINT UNSIGNED", 0}, + {"long long int", "BIGINT", 0, false}, + {"long long unsigned int", "BIGINT UNSIGNED", 0, false}, - {"float", "FLOAT", 0}, - {"double", "DOUBLE", 0}, + {"float", "FLOAT", 0, false}, + {"double", "DOUBLE", 0, false}, - {"::std::string", "TEXT", "VARCHAR(255)"}, + {"::std::string", "TEXT", "VARCHAR(255)", false}, - {"::size_t", "BIGINT UNSIGNED", 0}, - {"::std::size_t", "BIGINT UNSIGNED", 0} + {"::size_t", "BIGINT UNSIGNED", 0, false}, + {"::std::size_t", "BIGINT UNSIGNED", 0, false} }; } @@ -94,7 +95,8 @@ namespace relational 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)); + db_type_type ( + e.db_type, e.db_id_type ? e.db_id_type : e.db_type, e.null)); data_->type_map_.insert (v); } diff --git a/odb/relational/oracle/context.cxx b/odb/relational/oracle/context.cxx index 01b690a..4ba8659 100644 --- a/odb/relational/oracle/context.cxx +++ b/odb/relational/oracle/context.cxx @@ -20,38 +20,41 @@ namespace relational { struct type_map_entry { - const char* const cxx_type; - const char* const db_type; - const char* const db_id_type; + 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", "NUMBER(1)", 0}, + {"bool", "NUMBER(1)", 0, false}, - {"char", "NUMBER(3)", 0}, - {"signed char", "NUMBER(3)", 0}, - {"unsigned char", "NUMBER(3)", 0}, + {"char", "NUMBER(3)", 0, false}, + {"signed char", "NUMBER(3)", 0, false}, + {"unsigned char", "NUMBER(3)", 0, false}, - {"short int", "NUMBER(5)", 0}, - {"short unsigned int", "NUMBER(5)", 0}, + {"short int", "NUMBER(5)", 0, false}, + {"short unsigned int", "NUMBER(5)", 0, false}, - {"int", "NUMBER(10)", 0}, - {"unsigned int", "NUMBER(10)", 0}, + {"int", "NUMBER(10)", 0, false}, + {"unsigned int", "NUMBER(10)", 0, false}, - {"long int", "NUMBER(19)", 0}, - {"long unsigned int", "NUMBER(20)", 0}, + {"long int", "NUMBER(19)", 0, false}, + {"long unsigned int", "NUMBER(20)", 0, false}, - {"long long int", "NUMBER(19)", 0}, - {"long long unsigned int", "NUMBER(20)", 0}, + {"long long int", "NUMBER(19)", 0, false}, + {"long long unsigned int", "NUMBER(20)", 0, false}, - {"float", "BINARY_FLOAT", 0}, - {"double", "BINARY_DOUBLE", 0}, + {"float", "BINARY_FLOAT", 0, false}, + {"double", "BINARY_DOUBLE", 0, false}, - {"::std::string", "VARCHAR2(512)", 0}, + // Oracle treats empty VARCHAR2 (and NVARCHAR2) strings as NULL. + // + {"::std::string", "VARCHAR2(512)", 0, true}, - {"::size_t", "NUMBER(20)", 0}, - {"::std::size_t", "NUMBER(20)", 0} + {"::size_t", "NUMBER(20)", 0, false}, + {"::std::size_t", "NUMBER(20)", 0, false} }; } @@ -92,7 +95,8 @@ namespace relational 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)); + db_type_type ( + e.db_type, e.db_id_type ? e.db_id_type : e.db_type, e.null)); data_->type_map_.insert (v); } diff --git a/odb/relational/oracle/schema.cxx b/odb/relational/oracle/schema.cxx index 29c1130..f07e0d6 100644 --- a/odb/relational/oracle/schema.cxx +++ b/odb/relational/oracle/schema.cxx @@ -159,50 +159,6 @@ namespace relational }; entry create_table_; - struct create_column: relational::create_column, context - { - create_column (base const& x): base (x) {} - - virtual void - null (sema_rel::column& c) - { - // Oracle interprets empty VARCHAR2 and NVARCHAR2 strings as - // NULL. As an empty string is valid within the C++ context, - // VARCHAR2 and NVARCHAR2 columns are always specified as - // nullable, except when are a part of a primary key. - // - if (!c.null ()) - { - // This should never fail since we have already parsed this. - // - sql_type const& t (parse_sql_type (c.type ())); - - if (t.type == sql_type::VARCHAR2 || t.type == sql_type::NVARCHAR2) - { - // See if this column is a part of a primary key. - // - bool pk (false); - - for (sema_rel::column::contained_iterator i ( - c.contained_begin ()); i != c.contained_end (); ++i) - { - if (i->key ().is_a ()) - { - pk = true; - break; - } - } - - if (!pk) - return; - } - } - - base::null (c); - } - }; - entry create_column_; - struct create_foreign_key: relational::create_foreign_key, context { create_foreign_key (schema_format f, relational::create_table& ct) diff --git a/odb/relational/pgsql/context.cxx b/odb/relational/pgsql/context.cxx index bed0d1a..391c65e 100644 --- a/odb/relational/pgsql/context.cxx +++ b/odb/relational/pgsql/context.cxx @@ -21,38 +21,39 @@ namespace relational { struct type_map_entry { - const char* const cxx_type; - const char* const db_type; - const char* const db_id_type; + 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", "BOOLEAN", 0}, + {"bool", "BOOLEAN", 0, false}, - {"char", "SMALLINT", 0}, - {"signed char", "SMALLINT", 0}, - {"unsigned char", "SMALLINT", 0}, + {"char", "SMALLINT", 0, false}, + {"signed char", "SMALLINT", 0, false}, + {"unsigned char", "SMALLINT", 0, false}, - {"short int", "SMALLINT", 0}, - {"short unsigned int", "SMALLINT", 0}, + {"short int", "SMALLINT", 0, false}, + {"short unsigned int", "SMALLINT", 0, false}, - {"int", "INTEGER", 0}, - {"unsigned int", "INTEGER", 0}, + {"int", "INTEGER", 0, false}, + {"unsigned int", "INTEGER", 0, false}, - {"long int", "BIGINT", 0}, - {"long unsigned int", "BIGINT", 0}, + {"long int", "BIGINT", 0, false}, + {"long unsigned int", "BIGINT", 0, false}, - {"long long int", "BIGINT", 0}, - {"long long unsigned int", "BIGINT", 0}, + {"long long int", "BIGINT", 0, false}, + {"long long unsigned int", "BIGINT", 0, false}, - {"float", "REAL", 0}, - {"double", "DOUBLE PRECISION", 0}, + {"float", "REAL", 0, false}, + {"double", "DOUBLE PRECISION", 0, false}, - {"::std::string", "TEXT", 0}, + {"::std::string", "TEXT", 0, false}, - {"::size_t", "BIGINT", 0}, - {"::std::size_t", "BIGINT", 0} + {"::size_t", "BIGINT", 0, false}, + {"::std::size_t", "BIGINT", 0, false} }; } @@ -94,7 +95,8 @@ namespace relational 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)); + db_type_type ( + e.db_type, e.db_id_type ? e.db_id_type : e.db_type, e.null)); data_->type_map_.insert (v); } diff --git a/odb/relational/source.hxx b/odb/relational/source.hxx index 3ad463b..21954e0 100644 --- a/odb/relational/source.hxx +++ b/odb/relational/source.hxx @@ -1298,7 +1298,11 @@ namespace relational else { type = mi.fq_type (); - os << "bool is_null;"; + + // Indicate to the value_traits whether this column can be NULL. + // + os << "bool is_null (" << + (null (mi.m, key_prefix_) ? "true" : "false") << ");"; } if (comp) diff --git a/odb/relational/sqlite/context.cxx b/odb/relational/sqlite/context.cxx index faaa3b5..52c7751 100644 --- a/odb/relational/sqlite/context.cxx +++ b/odb/relational/sqlite/context.cxx @@ -22,35 +22,38 @@ namespace relational { struct type_map_entry { - const char* const cxx_type; - const char* const db_type; - const char* const db_id_type; + 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}, + {"bool", "INTEGER", 0, false}, - {"char", "INTEGER", 0}, - {"signed char", "INTEGER", 0}, - {"unsigned char", "INTEGER", 0}, + {"char", "INTEGER", 0, false}, + {"signed char", "INTEGER", 0, false}, + {"unsigned char", "INTEGER", 0, false}, - {"short int", "INTEGER", 0}, - {"short unsigned int", "INTEGER", 0}, + {"short int", "INTEGER", 0, false}, + {"short unsigned int", "INTEGER", 0, false}, - {"int", "INTEGER", 0}, - {"unsigned int", "INTEGER", 0}, + {"int", "INTEGER", 0, false}, + {"unsigned int", "INTEGER", 0, false}, - {"long int", "INTEGER", 0}, - {"long unsigned int", "INTEGER", 0}, + {"long int", "INTEGER", 0, false}, + {"long unsigned int", "INTEGER", 0, false}, - {"long long int", "INTEGER", 0}, - {"long long unsigned int", "INTEGER", 0}, + {"long long int", "INTEGER", 0, false}, + {"long long unsigned int", "INTEGER", 0, false}, - {"float", "REAL", 0}, - {"double", "REAL", 0}, + // SQLite stores NaN as NULL. + // + {"float", "REAL", 0, true}, + {"double", "REAL", 0, true}, - {"::std::string", "TEXT", 0} + {"::std::string", "TEXT", 0, false} }; } @@ -92,7 +95,8 @@ namespace relational 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)); + db_type_type ( + e.db_type, e.db_id_type ? e.db_id_type : e.db_type, e.null)); data_->type_map_.insert (v); } -- cgit v1.1