From 81ea37904e4959414b53b225b4b5e56e1b561bdc Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 22 Jul 2011 14:49:29 +0200 Subject: Add pragma for setting type's or member's default value New pragma: default. New test: default. --- odb/context.cxx | 9 +-- odb/gcc.hxx | 1 + odb/pragma.cxx | 138 +++++++++++++++++++++++++++++++++++++++ odb/relational/mysql/schema.cxx | 79 ++++++++++++++++++++++ odb/relational/pgsql/schema.cxx | 37 +++++++++++ odb/relational/schema.cxx | 122 ++++++++++++++++++++++++++++++++++ odb/relational/schema.hxx | 57 +++++++++++++++- odb/relational/sqlite/schema.cxx | 26 +++++++- odb/validator.cxx | 12 ++++ 9 files changed, 474 insertions(+), 7 deletions(-) diff --git a/odb/context.cxx b/odb/context.cxx index 0227763..39a01f7 100644 --- a/odb/context.cxx +++ b/odb/context.cxx @@ -247,15 +247,16 @@ 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 ("default"); + 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 ("options"); r = r && !c.count ("value-options"); r = r && !c.count ("index-options"); r = r && !c.count ("key-options"); r = r && !c.count ("id-options"); - 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); diff --git a/odb/gcc.hxx b/odb/gcc.hxx index feb69ba..7967b74 100644 --- a/odb/gcc.hxx +++ b/odb/gcc.hxx @@ -27,6 +27,7 @@ extern "C" #include #include #include +#include #include diff --git a/odb/pragma.cxx b/odb/pragma.cxx index bf1b1d1..a77cf48 100644 --- a/odb/pragma.cxx +++ b/odb/pragma.cxx @@ -183,6 +183,17 @@ check_decl_type (tree d, string const& name, string const& p, location_t l) return false; } } + else if (p == "default") + { + // Default can be used for both members and types. + // + if (tc != FIELD_DECL && !TYPE_P (d)) + { + error (l) << "name '" << name << "' in db pragma '" << p << "' does " + << "not refer to a type or data member" << endl; + return false; + } + } else if (p == "value_column" || p == "index_column" || p == "key_column" || @@ -598,6 +609,125 @@ handle_pragma (cpp_reader* reader, tt = pragma_lex (&t); } + else if (p == "default") + { + // default () () + // default (null) (n) + // default (true|false) (t|f) + // default ([+|-]) (-|+) + // default ("string") (s) + // default () (e) + // + // + + // Make sure we've got the correct declaration type. + // + if (decl != 0 && !check_decl_type (decl, decl_name, p, loc)) + return; + + if (pragma_lex (&t) != CPP_OPEN_PAREN) + { + error () << "'(' expected after db pragma '" << p << "'" << endl; + return; + } + + tt = pragma_lex (&t); + + // Encode the kind of value we have in the first letter of + // the string. + // + switch (tt) + { + case CPP_CLOSE_PAREN: + { + // Default value override. + // + break; + } + case CPP_STRING: + { + val = "s"; + val += TREE_STRING_POINTER (t); + tt = pragma_lex (&t); + break; + } + case CPP_NAME: + { + // This can be the null, true, or false keyword or a enumerator + // name. + // + string n (IDENTIFIER_POINTER (t)); + + if (n == "null" || n == "true" || n == "false") + { + val = n[0]; + tt = pragma_lex (&t); + break; + } + // Fall throught. + } + case CPP_SCOPE: + { + // We have a potentially scopped enumerator name. + // + string n; + tree decl = parse_scoped_name (t, tt, n, false, p); + + if (decl == 0) + return; + + node = decl; + val = "e" + n; + break; + } + case CPP_MINUS: + case CPP_PLUS: + { + val = (tt == CPP_MINUS ? "-" : "+"); + tt = pragma_lex (&t); + + if (tt != CPP_NUMBER) + { + error () << "expected numeric constant after '" << val + << "' in db pragma '" << p << "'" << endl; + return; + } + + // Fall through. + } + case CPP_NUMBER: + { + int tc (TREE_CODE (t)); + + if (tc != INTEGER_CST && tc != REAL_CST) + { + error () << "unexpected numeric constant in db pragma '" << p + << "'" << endl; + return; + } + + if (val.empty ()) + val = "+"; + + node = t; + tt = pragma_lex (&t); + break; + } + default: + { + error () << "unexpected expression in db pragma '" << p << "'" << endl; + return; + } + } + + if (tt != CPP_CLOSE_PAREN) + { + error () << "')' expected at the end of db pragma '" << p << "'" << endl; + return; + } + + tt = pragma_lex (&t); + } else if (p == "inverse") { // inverse (name) @@ -843,6 +973,7 @@ handle_pragma_qualifier (cpp_reader* reader, string const& p) p == "not_null" || p == "value_null" || p == "value_not_null" || + p == "default" || p == "inverse" || p == "unordered" || p == "transient") @@ -1033,6 +1164,12 @@ handle_pragma_db_value_not_null (cpp_reader* r) } extern "C" void +handle_pragma_db_default (cpp_reader* r) +{ + handle_pragma_qualifier (r, "default"); +} + +extern "C" void handle_pragma_db_inverse (cpp_reader* r) { handle_pragma_qualifier (r, "inverse"); @@ -1107,6 +1244,7 @@ register_odb_pragmas (void*, void*) 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", "default", handle_pragma_db_default); 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/schema.cxx b/odb/relational/mysql/schema.cxx index 3848c75..4a7171f 100644 --- a/odb/relational/mysql/schema.cxx +++ b/odb/relational/mysql/schema.cxx @@ -58,6 +58,85 @@ namespace relational } virtual void + default_bool (semantics::data_member&, bool v) + { + // MySQL has TRUE and FALSE as just aliases for 1 and 0. Still + // use them for self-documentation. + // + os << " DEFAULT " << (v ? "TRUE" : "FALSE"); + } + + virtual void + default_enum (semantics::data_member& m, tree en, string const& name) + { + // Make sure the column is mapped to an ENUM or integer type. + // + sql_type const& t (column_sql_type (m)); + + switch (t.type) + { + case sql_type::ENUM: + case sql_type::TINYINT: + case sql_type::SMALLINT: + case sql_type::MEDIUMINT: + case sql_type::INT: + case sql_type::BIGINT: + break; + default: + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: column with default value specified as C++ " + << "enumerator must map to MySQL ENUM or integer type" + << endl; + + throw generation_failed (); + } + } + + using semantics::enum_; + using semantics::enumerator; + + enumerator& er (dynamic_cast (*unit.find (en))); + enum_& e (er.enum_ ()); + + if (t.type == sql_type::ENUM) + { + // Assuming the enumerators in the C++ enum and MySQL ENUM are + // in the same order, calculate the poistion of the C++ + // enumerator and use that as an index in the MySQL ENUM. + // + size_t pos (0); + + for (enum_::enumerates_iterator i (e.enumerates_begin ()), + end (e.enumerates_end ()); i != end; ++i) + { + if (&i->enumerator () == &er) + break; + + pos++; + } + + if (pos < t.enumerators.size ()) + os << " DEFAULT " << t.enumerators[pos]; + else + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: unable to map C++ enumerator '" << name + << "' to MySQL ENUM value" << endl; + + throw generation_failed (); + } + } + else + { + if (e.unsigned_ ()) + os << " DEFAULT " << er.value (); + else + os << " DEFAULT " << static_cast (er.value ()); + } + } + + virtual void constraints (semantics::data_member& m) { base::constraints (m); diff --git a/odb/relational/pgsql/schema.cxx b/odb/relational/pgsql/schema.cxx index b7901e5..6530e82 100644 --- a/odb/relational/pgsql/schema.cxx +++ b/odb/relational/pgsql/schema.cxx @@ -77,6 +77,43 @@ namespace relational } virtual void + default_bool (semantics::data_member&, bool v) + { + os << " DEFAULT " << (v ? "TRUE" : "FALSE"); + } + + virtual void + default_enum (semantics::data_member& m, tree en, string const&) + { + // Make sure the column is mapped to an integer type. + // + switch (column_sql_type (m).type) + { + case sql_type::SMALLINT: + case sql_type::INTEGER: + case sql_type::BIGINT: + break; + default: + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: column with default value specified as C++ " + << "enumerator must map to PostgreSQL integer type" << endl; + + throw generation_failed (); + } + } + + using semantics::enumerator; + + enumerator& e (dynamic_cast (*unit.find (en))); + + if (e.enum_ ().unsigned_ ()) + os << " DEFAULT " << e.value (); + else + os << " DEFAULT " << static_cast (e.value ()); + } + + virtual void reference (semantics::data_member&) { } diff --git a/odb/relational/schema.cxx b/odb/relational/schema.cxx index 6acb6dc..a347d49 100644 --- a/odb/relational/schema.cxx +++ b/odb/relational/schema.cxx @@ -3,6 +3,12 @@ // copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file +#include + +#include +#include +#include + #include #include @@ -14,6 +20,122 @@ namespace relational { namespace schema { + // object_columns + // + void object_columns:: + default_ (semantics::data_member& m) + { + string s; + tree n (0); + + semantics::type& t (m.type ()); + + if (m.count ("default")) + { + s = m.get ("default"); + + // Empty string is a default value override which means + // there is no default value. + // + if (s.empty ()) + return; + + if (m.count ("default-node")) + n = m.get ("default-node"); + } + else if (t.count ("default")) + { + s = t.get ("default"); + + if (s.empty ()) + return; + + if (t.count ("default-node")) + n = t.get ("default-node"); + } + else + return; // No default value for this column. + + // The first letter in the default value string identifies + // the type of the value. See pragma.cxx for details. + // + switch (s[0]) + { + case 'n': + { + default_null (m); + break; + } + case 't': + case 'f': + { + default_bool (m, s[0] == 't'); + break; + } + case '+': + case '-': + { + switch (TREE_CODE (n)) + { + case INTEGER_CST: + { + HOST_WIDE_INT hwl (TREE_INT_CST_LOW (n)); + HOST_WIDE_INT hwh (TREE_INT_CST_HIGH (n)); + + unsigned long long l (hwl); + unsigned long long h (hwh); + unsigned short width (HOST_BITS_PER_WIDE_INT); + + unsigned long long v ((h << width) + l); + + default_integer (m, v, s[0] == '-'); + break; + } + case REAL_CST: + { + double v; + + REAL_VALUE_TYPE d (TREE_REAL_CST (n)); + + if (REAL_VALUE_ISINF (d)) + v = numeric_limits::infinity (); + else if (REAL_VALUE_ISNAN (d)) + v = numeric_limits::quiet_NaN (); + else + { + char tmp[256]; + real_to_decimal (tmp, &d, sizeof (tmp), 0, true); + istringstream is (tmp); + is >> v; + } + + if (s[0] == '-') + v = -v; + + default_float (m, v); + break; + } + default: + assert (false); + } + break; + } + + case 's': + { + default_string (m, string (s, 1, string::npos)); + break; + } + case 'e': + { + default_enum (m, n, string (s, 1, string::npos)); + break; + } + default: + assert (false); + } + } + struct schema_emitter: emitter, context { virtual void diff --git a/odb/relational/schema.hxx b/odb/relational/schema.hxx index b218c2b..6ada960 100644 --- a/odb/relational/schema.hxx +++ b/odb/relational/schema.hxx @@ -190,8 +190,11 @@ namespace relational type (m); null (m); - constraints (m); - reference (m); + + // An id member cannot have a default value. + // + if (!m.count ("id")) + default_ (m); // If we have options, add them. // @@ -200,6 +203,9 @@ namespace relational if (!o.empty ()) os << " " << o; + constraints (m); + reference (m); + return true; } @@ -217,6 +223,49 @@ namespace relational } virtual void + default_null (semantics::data_member&) + { + os << " DEFAULT NULL"; + } + + virtual void + default_bool (semantics::data_member&, bool v) + { + // Most databases do not support boolean literals. Those that + // do should override this. + // + os << " DEFAULT " << (v ? "1" : "0"); + } + + virtual void + default_integer (semantics::data_member&, unsigned long long v, bool neg) + { + os << " DEFAULT " << (neg ? "-" : "") << v; + } + + virtual void + default_float (semantics::data_member&, double v) + { + os << " DEFAULT " << v; + } + + virtual void + default_string (semantics::data_member&, string const& v) + { + os << " DEFAULT " << quote_string (v); + } + + virtual void + default_enum (semantics::data_member&, + tree /*enumerator*/, + string const& /*name*/) + { + // Has to be implemented by the database-specific override. + // + assert (false); + } + + virtual void constraints (semantics::data_member& m) { if (m.count ("id")) @@ -234,6 +283,10 @@ namespace relational } protected: + void + default_ (semantics::data_member&); + + protected: string prefix_; }; diff --git a/odb/relational/sqlite/schema.cxx b/odb/relational/sqlite/schema.cxx index 195a71e..253772d 100644 --- a/odb/relational/sqlite/schema.cxx +++ b/odb/relational/sqlite/schema.cxx @@ -19,11 +19,35 @@ namespace relational // // Create. // - struct object_columns: relational::object_columns + struct object_columns: relational::object_columns, context { object_columns (base const& x): base (x) {} virtual void + default_enum (semantics::data_member& m, tree en, string const&) + { + // Make sure the column is mapped to INTEGER. + // + if (column_sql_type (m).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 generation_failed (); + } + + using semantics::enumerator; + + enumerator& e (dynamic_cast (*unit.find (en))); + + if (e.enum_ ().unsigned_ ()) + os << " DEFAULT " << e.value (); + else + os << " DEFAULT " << static_cast (e.value ()); + } + + virtual void constraints (semantics::data_member& m) { base::constraints (m); diff --git a/odb/validator.cxx b/odb/validator.cxx index b07f5cd..790a991 100644 --- a/odb/validator.cxx +++ b/odb/validator.cxx @@ -287,6 +287,18 @@ namespace { c.set ("id-member", id); + // Complain if an id member has a default value (default value + // for the id's type is ok -- we will ignore it). + // + if (id->count ("default")) + { + cerr << id->file () << ":" << id->line () << ":" << id->column () + << ": error: object id member cannot have default value" + << endl; + + valid_ = false; + } + // Automatically mark the id member as not null. If we already have // an explicit null pragma for this member, issue an error. // -- cgit v1.1