From 73c98a67ef4ed605cf69e0d212934c4dc1f3eb8e Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 19 Jul 2011 13:42:18 +0200 Subject: New design for NULL semantics Now, instead of being specified as part of the SQL type with the type pragma, there are separate null and not_null pragmas. The not_null pragma was used to control NULL-ness of object pointers. Now the two pragmas are used consistently for object pointers and simple values (and in the future will work for composite values and containers). --- odb/context.cxx | 64 ++++++++++++++++++++------- odb/context.hxx | 43 ++++-------------- odb/pragma.cxx | 40 +++++++++++++++-- odb/relational/mysql/context.cxx | 10 +---- odb/relational/mysql/context.hxx | 5 +-- odb/relational/mysql/schema.cxx | 18 +++++++- odb/relational/mysql/source.cxx | 4 +- odb/relational/pgsql/context.cxx | 12 +---- odb/relational/pgsql/context.hxx | 5 +-- odb/relational/pgsql/source.cxx | 4 +- odb/relational/schema.hxx | 8 ++++ odb/relational/sqlite/context.cxx | 12 +---- odb/relational/sqlite/context.hxx | 6 +-- odb/relational/sqlite/source.cxx | 4 +- odb/relational/type-processor.cxx | 93 +++++++++++++++++++++++++++++---------- odb/validator.cxx | 83 +++++++++++++++++++++++++++++++--- 16 files changed, 282 insertions(+), 129 deletions(-) diff --git a/odb/context.cxx b/odb/context.cxx index 04761f8..520caea 100644 --- a/odb/context.cxx +++ b/odb/context.cxx @@ -145,6 +145,47 @@ context () context* context::current_; +bool context:: +null (semantics::data_member& m) +{ + semantics::type& t (m.type ()); + + // By default pointers can be null. + // + if (object_pointer (t)) + return m.count ("null") || + (!m.count ("not-null") && + (t.count ("null") || !t.count ("not-null"))); + else + // Everything else by default is not null. + // + return m.count ("null") || + (!m.count ("not-null") && t.count ("null")); +} + +bool context:: +null (semantics::data_member& m, string const& kp) +{ + if (kp.empty ()) + return null (m); + + semantics::type& c (m.type ()); + semantics::type& t (member_type (m, kp)); + + if (object_pointer (t)) + return m.count (kp + "-null") || + (!m.count (kp + "-not-null") && + (c.count (kp + "-null") || + (!c.count (kp + "-not-null") && + (t.count ("null") || !t.count ("not-null"))))); + else + return m.count (kp + "-null") || + (!m.count (kp + "-not-null") && + (c.count (kp + "-null") || + (!c.count (kp + "-not-null") && + t.count ("null")))); +} + string context:: upcase (string const& s) { @@ -192,7 +233,7 @@ comp_value_ (semantics::class_& c) { bool r (true); - //@@ This is bad. Did I add new value pragmas and forgot to + //@@ This is bad. Did we add new value pragmas and forgot to // account for them here? // r = r && c.count ("value"); @@ -206,7 +247,10 @@ comp_value_ (semantics::class_& c) r = r && !c.count ("index-column"); r = r && !c.count ("key-column"); r = r && !c.count ("id-column"); + r = r && !c.count ("null"); r = r && !c.count ("not-null"); + r = r && !c.count ("value-null"); + r = r && !c.count ("value-not-null"); r = r && !c.count ("unordered"); c.set ("composite-value", r); @@ -269,10 +313,7 @@ column_type (semantics::data_member& m, string const& kp) } string context:: -database_type_impl (semantics::type& t, - semantics::names* hint, - semantics::context& ctx, - column_type_flags f) +database_type_impl (semantics::type& t, semantics::names* hint, bool id) { type_map_type::const_iterator end (data_->type_map_.end ()), i (end); @@ -293,16 +334,9 @@ database_type_impl (semantics::type& t, i = data_->type_map_.find (t.fq_name ()); if (i != end) - { - string r (ctx.count ("id") ? i->second.id_type : i->second.type); - - if ((f & ctf_default_null) == 0) - r += " NOT NULL"; - - return r; - } - - return string (); + return id ? i->second.id_type : i->second.type; + else + return string (); } static string diff --git a/odb/context.hxx b/odb/context.hxx index 54f6832..a256866 100644 --- a/odb/context.hxx +++ b/odb/context.hxx @@ -123,6 +123,12 @@ public: return c.abstract () || c.count ("abstract"); } + bool + null (semantics::data_member&); + + bool + null (semantics::data_member&, string const& key_prefix); + // Database names and types. // public: @@ -225,23 +231,6 @@ public: return pointer_kind (p) == pk_weak; } - bool - null_pointer (semantics::data_member& m) - { - return !(m.count ("not-null") || m.type ().count ("not-null")); - } - - bool - null_pointer (semantics::data_member& m, string const& key_prefix) - { - if (key_prefix.empty ()) - return null_pointer (m); - - return !(m.count (key_prefix + "-not-null") || - m.type ().count ("not-null") || - member_type (m, key_prefix).count ("not-null")); - } - semantics::data_member* inverse (semantics::data_member& m) { @@ -441,33 +430,19 @@ public: // Per-database customizable functionality. // protected: - typedef unsigned short column_type_flags; - - static column_type_flags const ctf_none = 0; - - // Default type should be NULL-able. - // - static column_type_flags const ctf_default_null = 0x01; - // Return empty string if there is no mapping. // string - database_type (semantics::type& t, - semantics::names* hint, - semantics::context& c, - column_type_flags f) + database_type (semantics::type& t, semantics::names* hint, bool id) { - return current ().database_type_impl (t, hint, c, f); + return current ().database_type_impl (t, hint, id); } // The default implementation uses the type map (populated by the database- // specific context implementation) to come up with a mapping. // virtual string - database_type_impl (semantics::type&, - semantics::names*, - semantics::context&, - column_type_flags); + database_type_impl (semantics::type&, semantics::names*, bool); public: typedef context root_context; diff --git a/odb/pragma.cxx b/odb/pragma.cxx index 3b8d222..8408489 100644 --- a/odb/pragma.cxx +++ b/odb/pragma.cxx @@ -198,9 +198,13 @@ check_decl_type (tree d, string const& name, string const& p, location_t l) return false; } } - else if (p == "not_null") + else if (p == "null" || + p == "not_null" || + p == "value_null" || + p == "value_not_null") { - // Not_null can be used for both members and types (container or pointer). + // Null pragmas can be used for both members and types (values, + // containers, and pointers). // if (tc != FIELD_DECL && !TYPE_P (d)) { @@ -518,9 +522,15 @@ handle_pragma (cpp_reader* reader, tt = pragma_lex (&t); } - else if (p == "not_null") + else if (p == "null" || + p == "not_null" || + p == "value_null" || + p == "value_not_null") { + // null // not_null + // value_null + // value_not_null // // Make sure we've got the correct declaration type. @@ -766,7 +776,10 @@ handle_pragma_qualifier (cpp_reader* reader, string const& p) p == "index_type" || p == "key_type" || p == "table" || + p == "null" || p == "not_null" || + p == "value_null" || + p == "value_not_null" || p == "inverse" || p == "unordered" || p == "transient") @@ -903,12 +916,30 @@ handle_pragma_db_table (cpp_reader* reader) } extern "C" void +handle_pragma_db_null (cpp_reader* reader) +{ + handle_pragma_qualifier (reader, "null"); +} + +extern "C" void handle_pragma_db_not_null (cpp_reader* reader) { handle_pragma_qualifier (reader, "not_null"); } extern "C" void +handle_pragma_db_value_null (cpp_reader* reader) +{ + handle_pragma_qualifier (reader, "value_null"); +} + +extern "C" void +handle_pragma_db_value_not_null (cpp_reader* reader) +{ + handle_pragma_qualifier (reader, "value_not_null"); +} + +extern "C" void handle_pragma_db_inverse (cpp_reader* reader) { handle_pragma_qualifier (reader, "inverse"); @@ -945,7 +976,10 @@ register_odb_pragmas (void*, void*) c_register_pragma_with_expansion ("db", "index_type", handle_pragma_db_itype); c_register_pragma_with_expansion ("db", "key_type", handle_pragma_db_ktype); c_register_pragma_with_expansion ("db", "table", handle_pragma_db_table); + c_register_pragma_with_expansion ("db", "null", handle_pragma_db_null); c_register_pragma_with_expansion ("db", "not_null", handle_pragma_db_not_null); + c_register_pragma_with_expansion ("db", "value_null", handle_pragma_db_value_null); + c_register_pragma_with_expansion ("db", "value_not_null", handle_pragma_db_value_not_null); c_register_pragma_with_expansion ("db", "inverse", handle_pragma_db_inverse); c_register_pragma_with_expansion ("db", "unordered", handle_pragma_db_unordered); c_register_pragma_with_expansion ("db", "transient", handle_pragma_db_transient); diff --git a/odb/relational/mysql/context.cxx b/odb/relational/mysql/context.cxx index e9446c2..431470a 100644 --- a/odb/relational/mysql/context.cxx +++ b/odb/relational/mysql/context.cxx @@ -234,12 +234,9 @@ namespace relational } string context:: - database_type_impl (semantics::type& t, - semantics::names* hint, - semantics::context& ctx, - column_type_flags f) + database_type_impl (semantics::type& t, semantics::names* hint, bool id) { - string r (base_context::database_type_impl (t, hint, ctx, f)); + string r (base_context::database_type_impl (t, hint, id)); if (!r.empty ()) return r; @@ -290,9 +287,6 @@ namespace relational if (e->unsigned_ ()) r += " UNSIGNED"; } - - if ((f & ctf_default_null) == 0) - r += " NOT NULL"; } return r; diff --git a/odb/relational/mysql/context.hxx b/odb/relational/mysql/context.hxx index 019b878..00ebe57 100644 --- a/odb/relational/mysql/context.hxx +++ b/odb/relational/mysql/context.hxx @@ -97,10 +97,7 @@ namespace relational protected: virtual string - database_type_impl (semantics::type&, - semantics::names*, - semantics::context&, - column_type_flags); + database_type_impl (semantics::type&, semantics::names*, bool); public: virtual diff --git a/odb/relational/mysql/schema.cxx b/odb/relational/mysql/schema.cxx index 0b44c53..3848c75 100644 --- a/odb/relational/mysql/schema.cxx +++ b/odb/relational/mysql/schema.cxx @@ -37,11 +37,27 @@ namespace relational } }; - struct object_columns: relational::object_columns + struct object_columns: relational::object_columns, context { object_columns (base const& x): base (x) {} virtual void + null (semantics::data_member& m) + { + if (!context::null (m, prefix_)) + os << " NOT NULL"; + else + { + // MySQL TIMESTAMP is by default NOT NULL. If we want it + // to contain NULL values, we need to explicitly declare + // the column as NULL. + // + if (column_sql_type (m, prefix_).type == sql_type::TIMESTAMP) + os << " NULL"; + } + } + + virtual void constraints (semantics::data_member& m) { base::constraints (m); diff --git a/odb/relational/mysql/source.cxx b/odb/relational/mysql/source.cxx index 17bffdc..d3b4129 100644 --- a/odb/relational/mysql/source.cxx +++ b/odb/relational/mysql/source.cxx @@ -588,7 +588,7 @@ namespace relational { os << "}"; - if (!null_pointer (mi.m, key_prefix_)) + if (!null (mi.m, key_prefix_)) os << "else" << endl << "throw null_pointer ();"; } @@ -786,7 +786,7 @@ namespace relational << endl << "if (i." << mi.var << "null)" << endl; - if (null_pointer (mi.m, key_prefix_)) + if (null (mi.m, key_prefix_)) os << member << " = ptr_traits::pointer_type ();"; else os << "throw null_pointer ();"; diff --git a/odb/relational/pgsql/context.cxx b/odb/relational/pgsql/context.cxx index 5a6b158..6c6c5a0 100644 --- a/odb/relational/pgsql/context.cxx +++ b/odb/relational/pgsql/context.cxx @@ -211,12 +211,9 @@ namespace relational } string context:: - database_type_impl (semantics::type& t, - semantics::names* hint, - semantics::context& ctx, - column_type_flags f) + database_type_impl (semantics::type& t, semantics::names* hint, bool id) { - string r (base_context::database_type_impl (t, hint, ctx, f)); + string r (base_context::database_type_impl (t, hint, id)); if (!r.empty ()) return r; @@ -224,13 +221,8 @@ namespace relational using semantics::enum_; if (t.is_a ()) - { r = "INTEGER"; - if ((f & ctf_default_null) == 0) - r += " NOT NULL"; - } - return r; } diff --git a/odb/relational/pgsql/context.hxx b/odb/relational/pgsql/context.hxx index 66d9318..a4fbc00 100644 --- a/odb/relational/pgsql/context.hxx +++ b/odb/relational/pgsql/context.hxx @@ -85,10 +85,7 @@ namespace relational protected: virtual string - database_type_impl (semantics::type& t, - semantics::names* hint, - semantics::context& ctx, - column_type_flags f); + database_type_impl (semantics::type& t, semantics::names* hint, bool); public: virtual diff --git a/odb/relational/pgsql/source.cxx b/odb/relational/pgsql/source.cxx index 0ed6890..3b7664d 100644 --- a/odb/relational/pgsql/source.cxx +++ b/odb/relational/pgsql/source.cxx @@ -502,7 +502,7 @@ namespace relational { os << "}"; - if (!null_pointer (mi.m, key_prefix_)) + if (!null (mi.m, key_prefix_)) os << "else" << endl << "throw null_pointer ();"; } @@ -674,7 +674,7 @@ namespace relational << endl << "if (i." << mi.var << "null)" << endl; - if (null_pointer (mi.m, key_prefix_)) + if (null (mi.m, key_prefix_)) os << member << " = ptr_traits::pointer_type ();"; else os << "throw null_pointer ();"; diff --git a/odb/relational/schema.hxx b/odb/relational/schema.hxx index b9243df..9d64276 100644 --- a/odb/relational/schema.hxx +++ b/odb/relational/schema.hxx @@ -189,6 +189,7 @@ namespace relational os << " " << quote_id (name) << " "; type (m); + null (m); constraints (m); reference (m); @@ -202,6 +203,13 @@ namespace relational } virtual void + null (semantics::data_member& m) + { + if (!context::null (m, prefix_)) + os << " NOT NULL"; + } + + virtual void constraints (semantics::data_member& m) { if (m.count ("id")) diff --git a/odb/relational/sqlite/context.cxx b/odb/relational/sqlite/context.cxx index b026822..4d00621 100644 --- a/odb/relational/sqlite/context.cxx +++ b/odb/relational/sqlite/context.cxx @@ -197,12 +197,9 @@ namespace relational } string context:: - database_type_impl (semantics::type& t, - semantics::names* hint, - semantics::context& ctx, - column_type_flags f) + database_type_impl (semantics::type& t, semantics::names* hint, bool id) { - string r (base_context::database_type_impl (t, hint, ctx, f)); + string r (base_context::database_type_impl (t, hint, id)); if (!r.empty ()) return r; @@ -210,13 +207,8 @@ namespace relational using semantics::enum_; if (t.is_a ()) - { r = "INTEGER"; - if ((f & ctf_default_null) == 0) - r += " NOT NULL"; - } - return r; } diff --git a/odb/relational/sqlite/context.hxx b/odb/relational/sqlite/context.hxx index c625069..b5c3d85 100644 --- a/odb/relational/sqlite/context.hxx +++ b/odb/relational/sqlite/context.hxx @@ -49,10 +49,8 @@ namespace relational protected: virtual string - database_type_impl (semantics::type&, - semantics::names*, - semantics::context&, - column_type_flags); + database_type_impl (semantics::type&, semantics::names*, bool); + public: virtual ~context (); diff --git a/odb/relational/sqlite/source.cxx b/odb/relational/sqlite/source.cxx index 35a3246..6a99fc8 100644 --- a/odb/relational/sqlite/source.cxx +++ b/odb/relational/sqlite/source.cxx @@ -321,7 +321,7 @@ namespace relational { os << "}"; - if (!null_pointer (mi.m, key_prefix_)) + if (!null (mi.m, key_prefix_)) os << "else" << endl << "throw null_pointer ();"; } @@ -437,7 +437,7 @@ namespace relational << endl << "if (i." << mi.var << "null)" << endl; - if (null_pointer (mi.m, key_prefix_)) + if (null (mi.m, key_prefix_)) os << member << " = ptr_traits::pointer_type ();"; else os << "throw null_pointer ();"; diff --git a/odb/relational/type-processor.cxx b/odb/relational/type-processor.cxx index f7b0b2a..0f1c1d2 100644 --- a/odb/relational/type-processor.cxx +++ b/odb/relational/type-processor.cxx @@ -131,14 +131,7 @@ namespace relational type = idt.get ("type"); if (type.empty ()) - { - column_type_flags f (ctf_none); - - if (null_pointer (m)) - f |= ctf_default_null; - - type = database_type (idt, id.belongs ().hint (), id, f); - } + type = database_type (idt, id.belongs ().hint (), true); } else { @@ -149,12 +142,22 @@ namespace relational type = t.get ("type"); if (type.empty ()) - type = database_type (t, m.belongs ().hint (), m, ctf_none); + type = database_type (t, m.belongs ().hint (), m.count ("id")); } if (!type.empty ()) { m.set ("column-type", type); + + // Issue a warning if we are relaxing null-ness. + // + if (m.count ("null") && m.type ().count ("not-null")) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " warning: data member declared null while its type is " + << "declared not null" << endl; + } + return; } @@ -221,14 +224,7 @@ namespace relational type = idt.get ("type"); if (type.empty ()) - { - column_type_flags f (ctf_none); - - if (null_pointer (m, prefix)) - f |= ctf_default_null; - - type = database_type (idt, id.belongs ().hint (), id, f); - } + type = database_type (idt, id.belongs ().hint (), true); } else { @@ -236,7 +232,7 @@ namespace relational type = t.get ("type"); if (type.empty ()) - type = database_type (t, hint, m, ctf_none); + type = database_type (t, hint, false); } if (!type.empty ()) @@ -366,6 +362,10 @@ namespace relational t.set ("container-kind", ck); + // Mark id column as not null. + // + t.set ("id-not-null", string ()); + // Get the value type. // try @@ -404,6 +404,33 @@ namespace relational t.set ("value-tree-type", vt); t.set ("value-tree-hint", vh); + // If we have a set container, automatically mark the value + // column as not null. If we already have an explicit null for + // this column, issue an error. + // + if (ck == ck_set) + { + if (t.count ("value-null")) + { + os << t.file () << ":" << t.line () << ":" << t.column () << ":" + << " error: set container cannot contain null values" << endl; + + throw generation_failed (); + } + else + t.set ("value-not-null", string ()); + } + + // Issue a warning if we are relaxing null-ness in the + // container type. + // + if (t.count ("value-null") && vt->count ("not-null")) + { + os << t.file () << ":" << t.line () << ":" << t.column () << ":" + << " warning: container value declared null while its type " + << "is declared not null" << endl; + } + // Get the index type for ordered containers. // if (ck == ck_ordered) @@ -443,6 +470,7 @@ namespace relational t.set ("index-tree-type", it); t.set ("index-tree-hint", ih); + t.set ("index-not-null", string ()); } // Get the key type for maps. @@ -484,6 +512,7 @@ namespace relational t.set ("key-tree-type", kt); t.set ("key-tree-hint", kh); + t.set ("key-not-null", string ()); } } @@ -507,6 +536,28 @@ namespace relational if (ck == ck_ordered && m.count ("value-inverse")) m.set ("unordered", string ()); // Keep compatible with pragma. + // Issue an error if we have a null column in a set container. + // This can only happen if the value is declared as null in + // the member. + // + if (ck == ck_set && m.count ("value-null")) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " error: set container cannot contain null values" << endl; + + throw generation_failed (); + } + + // Issue a warning if we are relaxing null-ness in the member. + // + if (m.count ("value-null") && + (t.count ("value-not-null") || vt->count ("not-null"))) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " warning: container value declared null while the container " + << "type or value type declares it as not null" << endl; + } + return true; } @@ -729,12 +780,6 @@ namespace relational throw generation_failed (); } - if (m.count ("not-null") && !kp.empty ()) - { - m.remove ("not-null"); - m.set (kp + "-not-null", string ()); // Keep compatible with pragma. - } - // See if this is the inverse side of a bidirectional relationship. // If so, then resolve the member and cache it in the context. // diff --git a/odb/validator.cxx b/odb/validator.cxx index 487d69a..b07f5cd 100644 --- a/odb/validator.cxx +++ b/odb/validator.cxx @@ -15,6 +15,29 @@ using namespace std; namespace { + // Resolve null overrides. + // + static void + override_null (semantics::node& n, string const& prefix = "") + { + string p (prefix.empty () ? prefix : prefix + '-'); + + if (n.count (p + "null") && n.count (p + "not-null")) + { + if (n.get (p + "null-loc") < + n.get (p + "not-null-loc")) + { + n.remove (p + "null"); + n.remove (p + "null-loc"); + } + else + { + n.remove (p + "not-null"); + n.remove (p + "not-null-loc"); + } + } + } + struct data_member: traversal::data_member { data_member (bool& valid) @@ -42,6 +65,11 @@ namespace valid_ = false; } + + // Resolve null overrides. + // + override_null (m); + override_null (m, "value"); } bool& valid_; @@ -119,22 +147,44 @@ namespace // // + struct value_type: traversal::type + { + value_type (bool& valid): valid_ (valid) {} + + virtual void + traverse (semantics::type& t) + { + // Resolve null overrides. + // + override_null (t); + override_null (t, "value"); + } + + bool& valid_; + }; + + // + // struct class_: traversal::class_ { - class_ (bool& valid, semantics::unit& unit) - : valid_ (valid), unit_ (unit), member_ (valid) + class_ (bool& valid, semantics::unit& unit, value_type& vt) + : valid_ (valid), unit_ (unit), vt_ (vt), member_ (valid) { *this >> names_ >> member_; } - virtual void traverse (type& c) { if (c.count ("object")) traverse_object (c); - else if (context::comp_value (c)) - traverse_value (c); + else + { + if (context::comp_value (c)) + traverse_value (c); + + vt_.dispatch (c); + } } virtual void @@ -234,8 +284,23 @@ namespace } } else + { c.set ("id-member", id); + // Automatically mark the id member as not null. If we already have + // an explicit null pragma for this member, issue an error. + // + if (id->count ("null")) + { + cerr << id->file () << ":" << id->line () << ":" << id->column () + << ": error: object id member cannot be null" << endl; + + valid_ = false; + } + else + id->set ("not-null", string ()); + } + // Check members. // member_.count_ = 0; @@ -317,6 +382,7 @@ namespace bool& valid_; semantics::unit& unit_; + value_type& vt_; data_member member_; traversal::names names_; @@ -332,16 +398,21 @@ validate (options const&, traversal::unit unit; traversal::defines unit_defines; + traversal::declares unit_declares; traversal::namespace_ ns; - class_ c (valid, u); + value_type vt (valid); + class_ c (valid, u, vt); unit >> unit_defines >> ns; unit_defines >> c; + unit >> unit_declares >> vt; traversal::defines ns_defines; + traversal::declares ns_declares; ns >> ns_defines >> ns; ns_defines >> c; + ns >> ns_declares >> vt; unit.dispatch (u); -- cgit v1.1