diff options
Diffstat (limited to 'odb/relational')
-rw-r--r-- | odb/relational/context.hxx | 12 | ||||
-rw-r--r-- | odb/relational/header.hxx | 121 | ||||
-rw-r--r-- | odb/relational/inline.hxx | 53 | ||||
-rw-r--r-- | odb/relational/mysql/source.cxx | 26 | ||||
-rw-r--r-- | odb/relational/pgsql/schema.cxx | 2 | ||||
-rw-r--r-- | odb/relational/pgsql/source.cxx | 11 | ||||
-rw-r--r-- | odb/relational/schema.hxx | 8 | ||||
-rw-r--r-- | odb/relational/source.cxx | 544 | ||||
-rw-r--r-- | odb/relational/source.hxx | 777 | ||||
-rw-r--r-- | odb/relational/type-processor.cxx | 772 |
10 files changed, 2258 insertions, 68 deletions
diff --git a/odb/relational/context.hxx b/odb/relational/context.hxx index e654aee..dfe1f9c 100644 --- a/odb/relational/context.hxx +++ b/odb/relational/context.hxx @@ -47,6 +47,12 @@ namespace relational } string + column_qname (data_member_path const& mp) const + { + return quote_id (column_name (mp)); + } + + string column_qname (semantics::data_member& m, string const& key_prefix, string const& default_name) const @@ -61,6 +67,12 @@ namespace relational } string + table_qname (semantics::class_& obj, data_member_path const& mp) const + { + return quote_id (table_name (obj, mp)); + } + + string table_qname (semantics::data_member& m, table_prefix const& p) const { return quote_id (table_name (m, p)); diff --git a/odb/relational/header.hxx b/odb/relational/header.hxx index cc89e9a..4ac919c 100644 --- a/odb/relational/header.hxx +++ b/odb/relational/header.hxx @@ -305,7 +305,7 @@ namespace relational typedef container_traits base; container_traits (semantics::class_& c) - : object_members_base (true, false), c_ (c) + : object_members_base (true, false, false), c_ (c) { } @@ -390,7 +390,7 @@ namespace relational } } - string name (prefix_ + public_name (m) + "_traits"); + string name (flat_prefix_ + public_name (m) + "_traits"); // Figure out column counts. // @@ -1269,20 +1269,82 @@ namespace relational // if (c.count ("objects")) { - /* - typedef std::vector<semantics::class_*> objects; - - objects const& objs (c.get<objects> ("objects")); - */ - - /* - os << "struct query_type: query_base_type, query_columns" - << "{" - << "query_type ();" - << "query_type (const std::string&);" - << "query_type (const query_base_type&);" - << "};"; - */ + view_objects& objs (c.get<view_objects> ("objects")); + + if (objs.size () > 1) + { + os << "struct query_columns" + << "{"; + + for (view_objects::const_iterator i (objs.begin ()); + i < objs.end (); + ++i) + { + bool alias (!i->alias.empty ()); + semantics::class_& o (*i->object); + string const& name (alias ? i->alias : o.name ()); + string const& type (o.fq_name ()); + + os << "// " << name << endl + << "//" << endl; + + if (alias && i->alias != table_name (o)) + os << "static const char " << name << "_alias_[];" + << endl + << "typedef" << endl + << "odb::pointer_query_columns< " << type << ", " << + name << "_alias_ >" << endl + << name << ";" + << endl; + else + os << "typedef" << endl + << "odb::pointer_query_columns<" << endl + << " " << type << "," << endl + << " " << "odb::access::object_traits< " << type << + " >::table_name >" << endl + << name << ";" + << endl; + } + + os << "};" + << "struct query_type: query_base_type, query_columns" + << "{"; + } + else + { + // For a single object view we generate a shortcut without + // an intermediate typedef. + // + view_object const& vo (objs[0]); + + bool alias (!vo.alias.empty ()); + semantics::class_& o (*vo.object); + string const& type (o.fq_name ()); + + if (alias && vo.alias != table_name (o)) + os << "static const char query_alias[];" + << endl + << "struct query_type:" << endl + << " query_base_type," << endl + << " odb::pointer_query_columns< " << type << + ", query_alias >" + << "{"; + else + os << "struct query_type:" << endl + << " query_base_type," << endl + << " odb::pointer_query_columns<" << endl + << " " << type << "," << endl + << " odb::access::object_traits< " << type << + " >::table_name >" + << "{"; + } + + os << "query_type ();" + << "query_type (bool);" + << "query_type (const char*);" + << "query_type (const std::string&);" + << "query_type (const query_base_type&);" + << "};"; } else os << "typedef query_base_type query_type;" @@ -1307,7 +1369,7 @@ namespace relational // init (view, image) // os << "static void" << endl - << "init (view_type&, const image_type&);" + << "init (view_type&, const image_type&, database&);" << endl; // column_count @@ -1318,8 +1380,14 @@ namespace relational // Statements. // - os << "static const char query_statement[];" - << endl; + view_query& vq (c.get<view_query> ("query")); + + if (vq.kind != view_query::runtime) + { + os << "static query_base_type" << endl + << "query_statement (const query_base_type&);" + << endl; + } // // Functions. @@ -1447,17 +1515,20 @@ namespace relational bool abst (abstract (c)); string const& type (c.fq_name ()); - os << "// " << c.name () << endl - << "//" << endl; - if (options.generate_query ()) { + bool has_ptr (has_a (c, test_pointer)); + + if (has_ptr || !abst) + os << "// " << c.name () << endl + << "//" << endl; + // query_columns // // If we don't have any pointers, then query_columns is generated // in pass 1 (see the comment in class1 for details). // - if (has_a (c, test_pointer)) + if (has_ptr) query_columns_type_->traverse (c); // query_type @@ -1469,10 +1540,14 @@ namespace relational << " query_columns< " << type << ", table_name >" << "{" << "query_type ();" + << "query_type (bool);" + << "query_type (const char*);" << "query_type (const std::string&);" << "query_type (const query_base_type&);" << "};"; } + + // Move header comment out of if-block if adding any code here. } virtual void diff --git a/odb/relational/inline.hxx b/odb/relational/inline.hxx index 1134df6..9abc86f 100644 --- a/odb/relational/inline.hxx +++ b/odb/relational/inline.hxx @@ -215,6 +215,20 @@ namespace relational os << "inline" << endl << traits << "::query_type::" << endl + << "query_type (bool v)" << endl + << " : query_base_type (v)" + << "{" + << "}"; + + os << "inline" << endl + << traits << "::query_type::" << endl + << "query_type (const char* q)" << endl + << " : query_base_type (q)" + << "{" + << "}"; + + os << "inline" << endl + << traits << "::query_type::" << endl << "query_type (const std::string& q)" << endl << " : query_base_type (q)" << "{" @@ -258,6 +272,45 @@ namespace relational view_extra (c); + // query_type + // + if (c.count ("objects")) + { + os << "inline" << endl + << traits << "::query_type::" << endl + << "query_type ()" + << "{" + << "}"; + + os << "inline" << endl + << traits << "::query_type::" << endl + << "query_type (bool v)" << endl + << " : query_base_type (v)" + << "{" + << "}"; + + os << "inline" << endl + << traits << "::query_type::" << endl + << "query_type (const char* q)" << endl + << " : query_base_type (q)" + << "{" + << "}"; + + os << "inline" << endl + << traits << "::query_type::" << endl + << "query_type (const std::string& q)" << endl + << " : query_base_type (q)" + << "{" + << "}"; + + os << "inline" << endl + << traits << "::query_type::" << endl + << "query_type (const query_base_type& q)" << endl + << " : query_base_type (q)" + << "{" + << "}"; + } + // callback () // os << "inline" << endl diff --git a/odb/relational/mysql/source.cxx b/odb/relational/mysql/source.cxx index 02bc74b..8f80a5a 100644 --- a/odb/relational/mysql/source.cxx +++ b/odb/relational/mysql/source.cxx @@ -127,7 +127,7 @@ namespace relational } line_ += column; - line_ += "+0, ' ', "; + line_ += "+0,' ',"; if (!table.empty ()) { @@ -142,6 +142,30 @@ namespace relational }; entry<object_columns> object_columns_; + struct view_columns: relational::view_columns, context + { + view_columns (base const& x): base (x) {} + + virtual void + column (semantics::data_member& m, string const& column) + { + // The same idea as in object_columns. + // + if (column_sql_type (m).type != sql_type::ENUM) + { + base::column (m, column); + return; + } + + line_ += "CONCAT("; + line_ += column; + line_ += "+0,' ',"; + line_ += column; + line_ += ")"; + } + }; + entry<view_columns> view_columns_; + // // bind // diff --git a/odb/relational/pgsql/schema.cxx b/odb/relational/pgsql/schema.cxx index a1ef29d..098d6e9 100644 --- a/odb/relational/pgsql/schema.cxx +++ b/odb/relational/pgsql/schema.cxx @@ -178,7 +178,7 @@ namespace relational struct member_create: object_members_base, context { member_create (emitter& e, ostream& os, relational::tables& tables) - : object_members_base (false, true), + : object_members_base (false, true, false), e_ (e), os_ (os), tables_ (tables) diff --git a/odb/relational/pgsql/source.cxx b/odb/relational/pgsql/source.cxx index 28d04e6..dbda7be 100644 --- a/odb/relational/pgsql/source.cxx +++ b/odb/relational/pgsql/source.cxx @@ -1019,10 +1019,10 @@ namespace relational { os << "sts.connection ()," << endl << "query_statement_name," << endl - << "query_statement + q.clause ()," << endl - << "q.parameter_types ()," << endl - << "q.parameter_count ()," << endl - << "q.parameters_binding ()," << endl + << "qs.clause ()," << endl + << "qs.parameter_types ()," << endl + << "qs.parameter_count ()," << endl + << "qs.parameters_binding ()," << endl << "imb"; } @@ -1044,7 +1044,8 @@ namespace relational if (!object (c_) || abstract (c_)) return; - string scope (scope_ + "::" + prefix_ + public_name (m) + "_traits"); + string scope (scope_ + "::" + flat_prefix_ + public_name (m) + + "_traits"); // Statment names. // diff --git a/odb/relational/schema.hxx b/odb/relational/schema.hxx index 3fde82a..8e56ff9 100644 --- a/odb/relational/schema.hxx +++ b/odb/relational/schema.hxx @@ -72,7 +72,9 @@ namespace relational typedef member_drop base; member_drop (emitter& e, ostream& os, std::vector<tables>& t) - : object_members_base (false, true), common (e, os), tables_ (t) + : object_members_base (false, true, false), + common (e, os), + tables_ (t) { } @@ -363,7 +365,9 @@ namespace relational typedef member_create base; member_create (emitter& e, ostream& os, std::vector<tables>& t) - : object_members_base (false, true), common (e, os), tables_ (t) + : object_members_base (false, true, false), + common (e, os), + tables_ (t) { } diff --git a/odb/relational/source.cxx b/odb/relational/source.cxx index 8384d96..4f8ecfc 100644 --- a/odb/relational/source.cxx +++ b/odb/relational/source.cxx @@ -3,6 +3,11 @@ // copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file +#include <odb/gcc.hxx> + +#include <odb/lookup.hxx> +#include <odb/cxx-lexer.hxx> + #include <odb/relational/source.hxx> #include <odb/relational/generate.hxx> @@ -12,6 +17,545 @@ namespace relational { namespace source { + static inline void + add_space (string& s) + { + string::size_type n (s.size ()); + if (n != 0 && s[n - 1] != ' ') + s += ' '; + } + + static string + translate_name_trailer (string& t, + cpp_ttype& tt, + cpp_ttype& ptt, + cxx_tokens_lexer& lex) + { + string r; + + for (; tt != CPP_EOF; ptt = tt, tt = lex.next (t)) + { + bool done (false); + + switch (tt) + { + case CPP_SCOPE: + case CPP_DOT: + { + r += cxx_lexer::token_spelling[tt]; + break; + } + default: + { + // Handle CPP_KEYWORD here to avoid a warning (it is not + // part of the cpp_ttype enumeration). + // + if (tt == CPP_NAME || tt == CPP_KEYWORD) + { + // For names like 'foo::template bar'. + // + if (ptt == CPP_NAME || ptt == CPP_KEYWORD) + r += ' '; + + r += t; + } + else + done = true; + + break; + } + } + + if (done) + break; + } + + return r; + } + + static class_::expression + translate_name (string& t, + cpp_ttype& tt, + cpp_ttype& ptt, + cxx_tokens_lexer& lex, + tree scope, + location_t loc, + string const& prag, + bool check_ptr, + view_alias_map const& amap, + view_object_map const& omap) + { + using semantics::data_member; + typedef class_::expression expression; + + bool multi_obj ((amap.size () + omap.size ()) > 1); + + string r ("query_type"); + string name; + + bool fail (false); + context& ctx (context::current ()); + + // This code is quite similar to view_data_members in the type + // processor. + // + try + { + tree decl (0); + view_object* vo (0); + + // Check if this is an alias. + // + if (tt == CPP_NAME) + { + view_alias_map::const_iterator i (amap.find (t)); + + if (i != amap.end ()) + { + if (multi_obj) + { + r += "::"; + r += i->first; + } + + vo = i->second; + fail = true; // This must be a data member. + + // Skip '::'. + // + ptt = tt; + tt = lex.next (t); + + if (tt != CPP_SCOPE) + { + error (loc) + << "member name expected after an alias in db pragma " + << prag << endl; + throw generation_failed (); + } + + ptt = tt; + tt = lex.next (t); + + decl = lookup::resolve_scoped_name ( + t, tt, ptt, lex, vo->object->tree_node (), name, false); + } + } + + // If it is not an alias, do the normal lookup. + // + if (vo == 0) + { + // Also get the object type. We need to do it so that + // we can get the correct (derived) object name (the + // member can come from a base class). + // + tree type; + decl = lookup::resolve_scoped_name ( + t, tt, ptt, lex, scope, name, false, &type); + + type = TYPE_MAIN_VARIANT (type); + + view_object_map::const_iterator i (omap.find (type)); + + if (i == omap.end ()) + { + // Not an object associated with this view. Assume it + // is some other valid name. + // + return expression ( + name + translate_name_trailer (t, tt, ptt, lex)); + } + + vo = i->second; + + if (multi_obj) + { + r += "::"; + r += vo->object->name (); + } + } + + // Check that we have a data member. + // + if (TREE_CODE (decl) != FIELD_DECL) + { + if (fail) + { + error (loc) + << "name '" << name << "' in db pragma " << prag << " " + << "does not refer to a data member" << endl; + throw generation_failed (); + } + else + return expression ( + name + translate_name_trailer (t, tt, ptt, lex)); + } + + expression e (vo); + + data_member* m (dynamic_cast<data_member*> (ctx.unit.find (decl))); + + r += "::"; + r += ctx.public_name (*m); + + // Assemble the member path if we may need to return a pointer + // expression. + // + if (check_ptr) + e.member_path.push_back (m); + + fail = true; // Now we definitely fail if anything goes wrong. + + // Finally, resolve nested members if any. + // + for (; tt == CPP_DOT; ptt = tt, tt = lex.next (t)) + { + // Check if this member is actually of a composite value type. + // This is to handle expressions like "object::member.is_null ()" + // correctly. The remaining issue here is that in the future + // is_null()/is_not_null() will be valid for composite values + // as well. + // + if (!context::composite_wrapper (m->type ())) + break; + + ptt = tt; + tt = lex.next (t); + + if (tt != CPP_NAME) + { + error (loc) + << "name expected after '.' in db pragma " << prag << endl; + throw generation_failed (); + } + + tree type (TYPE_MAIN_VARIANT (TREE_TYPE (decl))); + + decl = lookup_qualified_name ( + type, get_identifier (t.c_str ()), false, false); + + if (decl == error_mark_node || TREE_CODE (decl) != FIELD_DECL) + { + error (loc) + << "name '" << t << "' in db pragma " << prag << " does not " + << "refer to a data member" << endl; + throw generation_failed (); + } + + m = dynamic_cast<data_member*> (ctx.unit.find (decl)); + + //@@ Temporarily translate '.' to '::' until the query is changed + // to use '.' for composite member access. + // + r += "::"; + r += ctx.public_name (*m); + + if (check_ptr) + e.member_path.push_back (m); + } + + // If requested, check if this member is a pointer. We only do this + // if there is nothing after this name. + // + if (check_ptr && tt == CPP_EOF) + { + using semantics::type; + + type* t (&m->type ()); + + if (type* c = context::container_wrapper (*t)) + t = &context::container_vt (*c); + + if (context::object_pointer (*t)) + return e; + } + + // Read the remainder of the expression (e.g., '.is_null ()') if + // the member is not composite and we bailed out from the above + // loop. + // + if (tt == CPP_DOT) + r += translate_name_trailer (t, tt, ptt, lex); + + return expression (r); + } + catch (lookup::invalid_name const&) + { + if (fail) + { + error (loc) << "invalid name in db pragma " << prag << endl; + throw generation_failed (); + } + else + return expression ( + name + translate_name_trailer (t, tt, ptt, lex)); + } + catch (lookup::unable_to_resolve const& e) + { + if (fail) + { + error (loc) << "unable to resolve name '" << e.name () + << "' in db pragma " << prag << endl; + throw generation_failed (); + } + else + return expression ( + name + translate_name_trailer (t, tt, ptt, lex)); + } + } + + class_::expression class_:: + translate_expression (type& c, + cxx_tokens const& ts, + tree scope, + location_t loc, + string const& prag, + bool* placeholder) + { + // The overall idea is as folows: read in tokens and add them + // to the string. If a token starts a name, try to resolve it + // to an object member (taking into account aliases). If this + // was successful, translate it to the query column reference. + // Otherwise, output it as is. + // + // If the placeholder argument is not NULL, then we need to + // detect the special '(?)' token sequence and replace it + // with the query variable ('q'). + // + expression e (""); + string& r (e.value); + + view_alias_map const& amap (c.get<view_alias_map> ("alias-map")); + view_object_map const& omap (c.get<view_object_map> ("object-map")); + + cxx_tokens_lexer lex; + lex.start (ts); + + string t; + for (cpp_ttype tt (lex.next (t)), ptt (CPP_EOF); tt != CPP_EOF;) + { + // Try to format the expression to resemble the style of the + // generated code. + // + switch (tt) + { + case CPP_NOT: + { + add_space (r); + r += '!'; + break; + } + case CPP_COMMA: + { + r += ", "; + break; + } + case CPP_OPEN_PAREN: + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD) + add_space (r); + + r += '('; + break; + } + case CPP_CLOSE_PAREN: + { + r += ')'; + break; + } + case CPP_OPEN_SQUARE: + { + r += '['; + break; + } + case CPP_CLOSE_SQUARE: + { + r += ']'; + break; + } + case CPP_OPEN_BRACE: + { + add_space (r); + r += "{ "; + break; + } + case CPP_CLOSE_BRACE: + { + add_space (r); + r += '}'; + break; + } + case CPP_SEMICOLON: + { + r += ';'; + break; + } + case CPP_ELLIPSIS: + { + add_space (r); + r += "..."; + break; + } + case CPP_PLUS: + case CPP_MINUS: + { + bool unary (ptt != CPP_NAME && + ptt != CPP_SCOPE && + ptt != CPP_NUMBER && + ptt != CPP_STRING && + ptt != CPP_CLOSE_PAREN && + ptt != CPP_PLUS_PLUS && + ptt != CPP_MINUS_MINUS); + + if (!unary) + add_space (r); + + r += cxx_lexer::token_spelling[tt]; + + if (!unary) + r += ' '; + break; + } + case CPP_PLUS_PLUS: + case CPP_MINUS_MINUS: + { + if (ptt != CPP_NAME && + ptt != CPP_CLOSE_PAREN && + ptt != CPP_CLOSE_SQUARE) + add_space (r); + + r += cxx_lexer::token_spelling[tt]; + break; + } + case CPP_DEREF: + case CPP_DEREF_STAR: + case CPP_DOT: + case CPP_DOT_STAR: + { + r += cxx_lexer::token_spelling[tt]; + break; + } + case CPP_STRING: + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + r += strlit (t); + break; + } + case CPP_NUMBER: + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + r += t; + break; + } + case CPP_SCOPE: + case CPP_NAME: + { + // Start of a name. + // + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + // Check if this is a pointer expression. + // + // If r is not empty, then it means this is not just the + // name. If placeholder is not 0, then we are translating + // a query expression, not a join condition. + // + expression e ( + translate_name ( + t, tt, ptt, lex, + scope, loc, prag, + r.empty () && placeholder == 0, amap, omap)); + + if (e.kind == expression::literal) + r += e.value; + else + return e; + + continue; // We have already extracted the next token. + } + case CPP_QUERY: + { + if (placeholder != 0 && !*placeholder) + { + if (ptt == CPP_OPEN_PAREN) + { + // Get the next token and see if it is ')'. + // + ptt = tt; + tt = lex.next (t); + + if (tt == CPP_CLOSE_PAREN) + { + r += 'q'; + *placeholder = true; + } + else + { + // The same as in the default case. + // + add_space (r); + r += "? "; + } + continue; // We have already gotten the next token. + } + } + // Fall through. + } + default: + { + // Handle CPP_KEYWORD here to avoid a warning (it is not + // part of the cpp_ttype enumeration). + // + if (tt == CPP_KEYWORD) + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + r += t; + } + else + { + // All the other operators. + // + add_space (r); + r += cxx_lexer::token_spelling[tt]; + r += ' '; + } + break; + } + } + + // + // Watch out for the continue statements above if you add any + // logic here. + // + + ptt = tt; + tt = lex.next (t); + } + + return e; + } + void generate () { diff --git a/odb/relational/source.hxx b/odb/relational/source.hxx index f83ad24..502d9f0 100644 --- a/odb/relational/source.hxx +++ b/odb/relational/source.hxx @@ -11,6 +11,7 @@ #include <vector> #include <sstream> +#include <odb/error.hxx> #include <odb/emitter.hxx> #include <odb/relational/context.hxx> @@ -163,6 +164,196 @@ namespace relational bool last_; }; + struct view_columns: object_columns_base, virtual context + { + typedef view_columns base; + + view_columns (): in_composite_ (false) {} + + virtual void + traverse_composite (semantics::data_member* pm, semantics::class_& c) + { + if (in_composite_) + { + object_columns_base::traverse_composite (pm, c); + return; + } + + // Override the column prerix. + // + semantics::data_member& m (*pm); + + // If we have literal column specified, use that. + // + if (m.count ("column")) + { + table_column const& tc (m.get<table_column> ("column")); + + if (!tc.table.empty ()) + table_prefix_ = tc.table; + + column_prefix_ = object_columns_base::column_prefix (m); + } + // Otherwise, see if there is a column expression. For composite + // members in a view, this should be a single reference. + // + else if (m.count ("column-expr")) + { + column_expr const& e (m.get<column_expr> ("column-expr")); + + if (e.size () > 1) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: column expression specified for a data member " + << "of a composite value type" << endl; + + throw generation_failed (); + } + + data_member_path const& mp (e.back ().member_path); + + if (mp.size () > 1) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: invalid data member in db pragma column" + << endl; + + throw generation_failed (); + } + + table_prefix_ = e.back ().table; + column_prefix_ = object_columns_base::column_prefix (*mp.back ()); + } + else + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: no column prefix provided for a view data member" + << endl; + + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": info: use db pragma column to specify the column prefix" + << endl; + + throw generation_failed (); + } + + in_composite_ = true; + object_columns_base::traverse_composite (pm, c); + in_composite_ = false; + } + + virtual bool + traverse_column (semantics::data_member& m, + string const& name, + bool first) + { + if (!first) + { + line_ += ','; + os << strlit (line_) << endl; + } + + line_.clear (); + + string col; + + // If we are inside a composite value, use the standard + // column name machinery. + // + if (in_composite_) + { + if (!table_prefix_.empty ()) + { + col += quote_id (table_prefix_); + col += '.'; + } + + col += quote_id (name); + } + // If we have literal column specified, use that. + // + else if (m.count ("column")) + { + table_column const& tc (m.get<table_column> ("column")); + + if (!tc.expr) + { + if (!tc.table.empty ()) + { + col += quote_id (tc.table); + col += '.'; + } + + col += quote_id (tc.column); + } + else + col += tc.column; + } + // Otherwise, see if there is a column expression. + // + else if (m.count ("column-expr")) + { + column_expr const& e (m.get<column_expr> ("column-expr")); + + for (column_expr::const_iterator i (e.begin ()); i != e.end (); ++i) + { + switch (i->kind) + { + case column_expr_part::literal: + { + col += i->value; + break; + } + case column_expr_part::reference: + { + col += quote_id (i->table); + col += '.'; + col += quote_id (column_name (i->member_path)); + break; + } + } + } + } + else + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: no column name provided for a view data member" + << endl; + + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": info: use db pragma column to specify the column name" + << endl; + + throw generation_failed (); + } + + column (m, col); + + return true; + } + + // The column argument is a qualified and quoted column or + // expression. + // + virtual void + column (semantics::data_member&, string const& column) + { + line_ += column; + } + + virtual void + flush () + { + if (!line_.empty ()) + os << strlit (line_) << endl; + } + + protected: + string line_; + bool in_composite_; + string table_prefix_; // Table corresponding to column_prefix_; + }; + struct object_joins: object_columns_base, virtual context { typedef object_joins base; @@ -527,7 +718,7 @@ namespace relational typedef container_traits base; container_traits (semantics::class_& c) - : object_members_base (true, true), c_ (c) + : object_members_base (true, true, false), c_ (c) { if (object (c)) scope_ = "access::object_traits< " + c.fq_name () + " >"; @@ -634,7 +825,7 @@ namespace relational eager_ptr = has_a (*cvt, test_eager_pointer); } - string name (prefix_ + public_name (m) + "_traits"); + string name (flat_prefix_ + public_name (m) + "_traits"); string scope (scope_ + "::" + name); os << "// " << m.name () << endl @@ -1578,16 +1769,16 @@ namespace relational typedef container_cache_members base; container_cache_members () - : object_members_base (true, false) + : object_members_base (true, false, false) { } virtual void traverse_container (semantics::data_member& m, semantics::type&) { - string traits (prefix_ + public_name (m) + "_traits"); + string traits (flat_prefix_ + public_name (m) + "_traits"); os << db << "::container_statements_impl< " << traits << " > " << - prefix_ << m.name () << ";"; + flat_prefix_ << m.name () << ";"; } }; @@ -1596,7 +1787,7 @@ namespace relational typedef container_cache_init_members base; container_cache_init_members () - : object_members_base (true, false), first_ (true) + : object_members_base (true, false, false), first_ (true) { } @@ -1613,7 +1804,7 @@ namespace relational os << "," << endl << " "; - os << prefix_ << m.name () << " (c)"; + os << flat_prefix_ << m.name () << " (c)"; } protected: @@ -1635,7 +1826,7 @@ namespace relational }; container_calls (call_type call) - : object_members_base (true, false), + : object_members_base (true, false, false), call_ (call), obj_prefix_ ("obj.") { @@ -1686,8 +1877,8 @@ namespace relational string const& name (m.name ()); string obj_name (obj_prefix_ + name); - string sts_name (prefix_ + name); - string traits (prefix_ + public_name (m) + "_traits"); + string sts_name (flat_prefix_ + name); + string traits (flat_prefix_ + public_name (m) + "_traits"); // If this is a wrapped container, then we need to "unwrap" it. // @@ -2792,8 +2983,8 @@ namespace relational view_query_statement_ctor_args (type&) { os << "sts.connection ()," << endl - << "query_statement + q.clause ()," << endl - << "q.parameters_binding ()," << endl + << "qs.clause ()," << endl + << "qs.parameters_binding ()," << endl << "imb"; } @@ -2810,6 +3001,42 @@ namespace relational view_extra (c); // + // Query. + // + + // query_type + // + if (c.count ("objects")) + { + view_objects& objs (c.get<view_objects> ("objects")); + + if (objs.size () > 1) + { + for (view_objects::const_iterator i (objs.begin ()); + i < objs.end (); + ++i) + { + if (!i->alias.empty () && i->alias != table_name (*i->object)) + os << "const char " << traits << "::query_columns::" << endl + << i->alias << "_alias_[] = " << strlit (i->alias) << ";" + << endl; + } + } + else + { + // For a single object view we generate a shortcut without + // an intermediate typedef. + // + view_object const& vo (objs[0]); + + if (!vo.alias.empty () && vo.alias != table_name (*vo.object)) + os << "const char " << traits << "::" << endl + << "query_alias[] = " << strlit (vo.alias) << ";" + << endl; + } + } + + // // Functions. // @@ -2848,22 +3075,503 @@ namespace relational // init (view, image) // os << "void " << traits << "::" << endl - << "init (view_type& o, const image_type& i)" + << "init (view_type& o, const image_type& i, database& db)" << "{" << "ODB_POTENTIALLY_UNUSED (o);" << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (db);" << endl; names (c, init_value_member_names_); os << "}"; - // query_statement + // query_statement() // - os << "const char " << traits << "::query_statement[] =" << endl - << strlit (c.get<string> ("query")) << endl - << strlit (" ") << ";" << endl - << endl; + view_query& vq (c.get<view_query> ("query")); + + if (vq.kind != view_query::runtime) + { + os << traits << "::query_base_type" << endl + << traits << "::" << endl + << "query_statement (const query_base_type& q)" + << "{"; + + if (vq.kind == view_query::complete) + { + os << "query_base_type r (" << endl; + + bool ph (false); + + if (!vq.literal.empty ()) + { + // See if we have the '(?)' placeholder. + // + // @@ Ideally we would need to make sure we don't match + // this inside strings and quoted identifier. So the + // proper way to handle this would be to tokenize the + // statement using sql_lexer, once it is complete enough. + // + string::size_type p (vq.literal.find ("(?)")); + + if (p != string::npos) + { + ph = true; + os << strlit (string (vq.literal, 0, p + 1)) + << " + q + " + << strlit (string (vq.literal, p + 2)); + } + else + os << strlit (vq.literal); + } + else + // Output the pragma location for easier error tracking. + // + os << "// From " << + location_file (vq.loc).leaf () << ":" << + location_line (vq.loc) << ":" << + location_column (vq.loc) << endl + << translate_expression ( + c, vq.expr, vq.scope, vq.loc, "query", &ph).value; + + os << ");"; + + // If there was no placeholder, add the query condition + // at the end. + // + if (!ph) + os << "r += q.clause_prefix ();" + << "r += q;"; + } + else // vq.kind == view_query::condition + { + os << "query_base_type r (" << endl + << strlit ("SELECT ") << endl; + + // Generate select-list. + // + { + instance<view_columns> t; + t->traverse (c); + } + + os << ");" + << endl; + + // Generate from-list. + // + view_objects const& objs (c.get<view_objects> ("objects")); + + for (view_objects::const_iterator i (objs.begin ()); + i != objs.end (); + ++i) + { + string l; + + // First object. + // + if (i == objs.begin ()) + { + l = "FROM "; + l += table_qname (*i->object); + + if (!i->alias.empty ()) + { + l += " AS "; + l += quote_id (i->alias); + } + + os << "r += " << strlit (l) << ";" + << endl; + + continue; + } + + expression e ( + translate_expression ( + c, i->cond, i->scope, i->loc, "object")); + + // Literal expression. + // + if (e.kind == expression::literal) + { + l = "LEFT JOIN "; + l += table_qname (*i->object); + + if (!i->alias.empty ()) + { + l += " AS "; + l += quote_id (i->alias); + } + + l += " ON"; + + os << "r += " << strlit (l) << ";" + // Output the pragma location for easier error tracking. + // + << "// From " << + location_file (i->loc).leaf () << ":" << + location_line (i->loc) << ":" << + location_column (i->loc) << endl + << "r += " << e.value << ";" + << endl; + + continue; + } + + // We have an object relationship (pointer) for which we need + // to come up with the corresponding JOIN condition. If this + // is a to-many relationship, then we first need to JOIN the + // container table. This code is similar to object_joins. + // + using semantics::data_member; + + data_member& m (*e.member_path.back ()); + + // Resolve the pointed-to object to view_object and do + // some sanity checks while at it. + // + semantics::class_* c (0); + + if (semantics::type* cont = container_wrapper (m.type ())) + c = object_pointer (container_vt (*cont)); + else + c = object_pointer (m.type ()); + + view_object const* vo (0); + + // Check if the pointed-to object has been previously + // associated with this view and is unambiguous. A + // pointer to ourselves is always assumed to point + // to this association. + // + if (i->object == c) + vo = &*i; + else + { + bool ambig (false); + + for (view_objects::const_iterator j (objs.begin ()); + j != i; + ++j) + { + if (j->object != c) + continue; + + if (vo == 0) + { + vo = &*j; + continue; + } + + // If it is the first ambiguous object, issue the + // error. + // + if (!ambig) + { + error (i->loc) + << "pointed-to object '" << c->name () << "' is " + << "ambiguous" << endl; + + info (i->loc) + << "candidates are:" << endl; + + info (vo->loc) + << " '" << vo->name () << "'" << endl; + + ambig = true; + } + + info (j->loc) + << " '" << j->name () << "'" << endl; + } + + if (ambig) + { + info (i->loc) + << "use the full join condition clause in db pragma " + << "object to resolve this ambiguity" << endl; + + throw generation_failed (); + } + + if (vo == 0) + { + error (i->loc) + << "pointed-to object '" << c->name () << "' " + << "specified in the join condition has not been " + << "previously associated with this view" << endl; + + throw generation_failed (); + } + } + + // Left and right-hand side table names. + // + string lt (e.vo->alias.empty () + ? table_name (*e.vo->object) + : e.vo->alias); + + string rt (vo->alias.empty () + ? table_name (*vo->object) + : vo->alias); + + // First join the container table if necessary. + // + data_member* im (inverse (m)); + + semantics::type* cont ( + container_wrapper (im != 0 ? im->type () : m.type ())); + + // Container table. + // + string ct; + if (cont != 0) + { + if (im != 0) + { + // For now a direct member can only be directly in + // the object scope. When this changes, the inverse() + // function would have to return a member path instead + // of just a single member. + // + table_prefix tp (table_name (*vo->object) + "_", 1); + ct = table_qname (*im, tp); + } + else + ct = table_qname (*e.vo->object, e.member_path); + } + + if (cont != 0) + { + l = "LEFT JOIN "; + l += ct; + l += " ON"; + os << "r += " << strlit (l) << ";"; + + // If we are the pointed-to object, then we have to turn + // things around. This is necessary to have the proper + // JOIN order. There seems to be a pattern there but + // it is not yet intuitively clear what it means. + // + if (im != 0) + { + if (i->object == c) + { + // container.value = pointer.id + // + l = ct; + l += '.'; + l += column_qname (*im, "value", "value"); + l += "="; + l += quote_id (lt); + l += '.'; + l += column_qname (*id_member (*e.vo->object)); + } + else + { + // container.id = pointed-to.id + // + l = ct; + l += '.'; + l += column_qname (*im, "id", "object_id"); + l += "="; + l += quote_id (rt); + l += '.'; + l += column_qname (*id_member (*vo->object)); + } + } + else + { + if (i->object == c) + { + // container.id = pointer.id + // + l = ct; + l += '.'; + l += column_qname (m, "id", "object_id"); + l += "="; + l += quote_id (lt); + l += '.'; + l += column_qname (*id_member (*e.vo->object)); + } + else + { + // container.value = pointed-to.id + // + l = ct; + l += '.'; + l += column_qname (m, "value", "value"); + l += "="; + l += quote_id (rt); + l += '.'; + l += column_qname (*id_member (*vo->object)); + } + } + + os << "r += " << strlit (l) << ";"; + } + + l = "LEFT JOIN "; + l += table_qname (*i->object); + + if (!i->alias.empty ()) + { + l += " AS "; + l += quote_id (i->alias); + } + + l += " ON"; + os << "r += " << strlit (l) << ";"; + + if (cont != 0) + { + if (im != 0) + { + if (i->object == c) + { + // container.id = pointed-to.id + // + l = ct; + l += '.'; + l += column_qname (*im, "id", "object_id"); + l += "="; + l += quote_id (rt); + l += '.'; + l += column_qname (*id_member (*vo->object)); + } + else + { + // container.value = pointer.id + // + l = ct; + l += '.'; + l += column_qname (*im, "value", "value"); + l += "="; + l += quote_id (lt); + l += '.'; + l += column_qname (*id_member (*e.vo->object)); + } + } + else + { + if (i->object == c) + { + // container.value = pointed-to.id + // + l = ct; + l += '.'; + l += column_qname (m, "value", "value"); + l += "="; + l += quote_id (rt); + l += '.'; + l += column_qname (*id_member (*vo->object)); + } + else + { + // container.id = pointer.id + // + l = ct; + l += '.'; + l += column_qname (m, "id", "object_id"); + l += "="; + l += quote_id (lt); + l += '.'; + l += column_qname (*id_member (*e.vo->object)); + } + } + } + else + { + if (im != 0) + { + // our.id = pointed-to.pointer + // + l = quote_id (lt); + l += '.'; + l += column_qname (*id_member (*e.vo->object)); + l += " = "; + l += quote_id (rt); + l += '.'; + l += column_qname (*im); + } + else + { + // our.pointer = pointed-to.id + // + l = quote_id (lt); + l += '.'; + l += column_qname (e.member_path); + l += " = "; + l += quote_id (rt); + l += '.'; + l += column_qname (*id_member (*vo->object)); + } + } + + os << "r += " << strlit (l) << ";" + << endl; + } + + // Generate the query condition. + // + if (!vq.literal.empty () || !vq.expr.empty ()) + { + os << "query_base_type c (" << endl; + + bool ph (false); + + if (!vq.literal.empty ()) + { + // See if we have the '(?)' placeholder. + // + // @@ Ideally we would need to make sure we don't match + // this inside strings and quoted identifier. So the + // proper way to handle this would be to tokenize the + // statement using sql_lexer, once it is complete enough. + // + string::size_type p (vq.literal.find ("(?)")); + + if (p != string::npos) + { + ph = true; + os << strlit (string (vq.literal, 0, p + 1)) + << " + q + " + << strlit (string (vq.literal, p + 2)); + } + else + os << strlit (vq.literal); + } + else + // Output the pragma location for easier error tracking. + // + os << "// From " << + location_file (vq.loc).leaf () << ":" << + location_line (vq.loc) << ":" << + location_column (vq.loc) << endl + << translate_expression ( + c, vq.expr, vq.scope, vq.loc, "query", &ph).value; + + os << ");"; + + if (!ph) + os << "c += q;"; + + os << "r += c.clause_prefix ();" + << "r += c;" + << endl; + } + else + { + os << "r += q.clause_prefix ();" + << "r += q;" + << endl; + } + } + + os << "return r;" + << "}"; + } // query () // @@ -2889,8 +3597,14 @@ namespace relational << "bind (imb.bind, im);" << "sts.image_version (im.version);" << "imb.version++;" - << "}" - << "shared_ptr<select_statement> st (" << endl + << "}"; + + if (vq.kind == view_query::runtime) + os << "query_base_type const& qs (q);"; + else + os << "query_base_type const& qs (query_statement (q));"; + + os << "shared_ptr<select_statement> st (" << endl << "new (shared) select_statement (" << endl; view_query_statement_ctor_args (c); @@ -2905,12 +3619,33 @@ namespace relational "class_view> > r (" << endl << "new (shared) " << db << "::result_impl<view_type, class_view> (" << endl - << "q, st, sts));" + << "qs, st, sts));" << endl << "return result<view_type> (r);" << "}"; } + struct expression + { + explicit + expression (std::string const& v): kind (literal), value (v) {} + expression (view_object* vo): kind (pointer), vo (vo) {} + + enum kind_type {literal, pointer}; + + kind_type kind; + std::string value; + data_member_path member_path; + view_object* vo; + }; + + expression + translate_expression (type& c, + cxx_tokens const&, + tree scope, + location_t loc, + string const& prag, + bool* placeholder = 0); // // composite // diff --git a/odb/relational/type-processor.cxx b/odb/relational/type-processor.cxx index a74af9b..650294d 100644 --- a/odb/relational/type-processor.cxx +++ b/odb/relational/type-processor.cxx @@ -7,7 +7,10 @@ #include <vector> +#include <odb/error.hxx> +#include <odb/lookup.hxx> #include <odb/cxx-lexer.hxx> +#include <odb/common.hxx> #include <odb/relational/context.hxx> #include <odb/relational/type-processor.hxx> @@ -1079,6 +1082,449 @@ namespace relational tree container_traits_; }; + // + // + struct view_data_member: traversal::data_member, context + { + view_data_member (semantics::class_& c) + : view_ (c), + query_ (c.get<view_query> ("query")), + amap_ (c.get<view_alias_map> ("alias-map")), + omap_ (c.get<view_object_map> ("object-map")) + { + } + + struct assoc_member + { + semantics::data_member* m; + view_object* vo; + }; + + typedef vector<assoc_member> assoc_members; + + virtual void + traverse (semantics::data_member& m) + { + using semantics::data_member; + + if (transient (m)) + return; + + data_member* src_m (0); // Source member. + + // Resolve member references in column expressions. + // + if (m.count ("column")) + { + // Column literal. + // + if (query_.kind != view_query::condition) + { + warn (m.get<location_t> ("column-location")) + << "db pragma column ignored in a view with " + << (query_.kind == view_query::runtime ? "runtime" : "complete") + << " query" << endl; + } + + return; + } + else if (m.count ("column-expr")) + { + column_expr& e (m.get<column_expr> ("column-expr")); + + if (query_.kind != view_query::condition) + { + warn (e.loc) + << "db pragma column ignored in a view with " + << (query_.kind == view_query::runtime ? "runtime" : "complete") + << " query" << endl; + return; + } + + for (column_expr::iterator i (e.begin ()); i != e.end (); ++i) + { + // This code is quite similar to translate_expression in the + // source generator. + // + try + { + if (i->kind != column_expr_part::reference) + continue; + + lex_.start (i->value); + + string t; + cpp_ttype tt (lex_.next (t)); + + string name; + tree decl (0); + semantics::class_* obj (0); + + // Check if this is an alias. + // + if (tt == CPP_NAME) + { + view_alias_map::iterator j (amap_.find (t)); + + if (j != amap_.end ()) + { + i->table = j->first; + obj = j->second->object; + + // Skip '::'. + // + if (lex_.next (t) != CPP_SCOPE) + { + error (i->loc) + << "member name expected after an alias in db pragma " + << "column" << endl; + throw generation_failed (); + } + + tt = lex_.next (t); + + cpp_ttype ptt; // Not used. + decl = lookup::resolve_scoped_name ( + t, tt, ptt, lex_, obj->tree_node (), name, false); + } + } + + // If it is not an alias, do the normal lookup. + // + if (obj == 0) + { + // Also get the object type. We need to do it so that + // we can get the correct (derived) table name (the + // member can come from a base class). + // + tree type; + cpp_ttype ptt; // Not used. + decl = lookup::resolve_scoped_name ( + t, tt, ptt, lex_, i->scope, name, false, &type); + + type = TYPE_MAIN_VARIANT (type); + + view_object_map::iterator j (omap_.find (type)); + + if (j == omap_.end ()) + { + error (i->loc) + << "name '" << name << "' in db pragma column does not " + << "refer to a data member of a persistent class that " + << "is used in this view" << endl; + throw generation_failed (); + } + + obj = j->second->object; + i->table = table_name (*obj); + } + + // Check that we have a data member. + // + if (TREE_CODE (decl) != FIELD_DECL) + { + error (i->loc) << "name '" << name << "' in db pragma column " + << "does not refer to a data member" << endl; + throw generation_failed (); + } + + data_member* m (dynamic_cast<data_member*> (unit.find (decl))); + i->member_path.push_back (m); + + // Finally, resolve nested members if any. + // + for (; tt == CPP_DOT; tt = lex_.next (t)) + { + lex_.next (t); // Get CPP_NAME. + + tree type (TYPE_MAIN_VARIANT (TREE_TYPE (decl))); + + decl = lookup_qualified_name ( + type, get_identifier (t.c_str ()), false, false); + + if (decl == error_mark_node || TREE_CODE (decl) != FIELD_DECL) + { + error (i->loc) << "name '" << t << "' in db pragma column " + << "does not refer to a data member" << endl; + throw generation_failed (); + } + + m = dynamic_cast<data_member*> (unit.find (decl)); + i->member_path.push_back (m); + } + + // If the expression is just this reference, then we have + // a source member. + // + if (e.size () == 1) + src_m = m; + } + catch (lookup::invalid_name const&) + { + error (i->loc) << "invalid name in db pragma column" << endl; + throw generation_failed (); + } + catch (lookup::unable_to_resolve const& e) + { + error (i->loc) << "unable to resolve name '" << e.name () + << "' in db pragma column" << endl; + throw generation_failed (); + } + } + + // We have the source member, check that the C++ types are the + // same (sans cvr-qualification and wrapping) and issue a warning + // if they differ. In rare cases where this is not a mistake, the + // user can a phony expression (e.g., "" + person:name) to disable + // the warning. Note that in this case there will be no type pragma + // copying, which is probably ok seeing that the C++ types are + // different. + // + // + if (src_m != 0 && + !member_resolver::check_types (m.type (), src_m->type ())) + { + warn (e.loc) + << "object data member '" << src_m->name () << "' specified " + << "in db pragma column has a different type compared to the " + << "view data member" << endl; + + info (src_m->file (), src_m->line (), src_m->column ()) + << "object data member is defined here" << endl; + + info (m.file (), m.line (), m.column ()) + << "view data member is defined here" << endl; + } + } + // This member has no column information. If we are generting our + // own query, try to find a member with the same (or similar) name + // in one of the associated objects. + // + else if (query_.kind == view_query::condition) + { + view_objects& objs (view_.get<view_objects> ("objects")); + + assoc_members exact_members, pub_members; + member_resolver resolver (exact_members, pub_members, m); + + for (view_objects::iterator i (objs.begin ()); i != objs.end (); ++i) + resolver.traverse (*i); + + assoc_members& members ( + !exact_members.empty () ? exact_members : pub_members); + + // Issue diagnostics if we didn't find any or found more + // than one. + // + if (members.empty ()) + { + error (m.file (), m.line (), m.column ()) + << "unable to find a corresponding data member for '" + << m.name () << "' in any of the associated objects" << endl; + + info (m.file (), m.line (), m.column ()) + << "use db pragma column to specify the corresponding data " + << "member or column name" << endl; + + throw generation_failed (); + } + else if (members.size () > 1) + { + error (m.file (), m.line (), m.column ()) + << "corresponding data member for '" << m.name () << "' is " + << "ambiguous" << endl; + + info (m.file (), m.line (), m.column ()) + << "candidates are:" << endl; + + for (assoc_members::const_iterator i (members.begin ()); + i != members.end (); + ++i) + { + info (i->m->file (), i->m->line (), i->m->column ()) + << " '" << i->m->name () << "' in object '" + << i->vo->name () << "'" << endl; + } + + info (m.file (), m.line (), m.column ()) + << "use db pragma column to resolve this ambiguity" << endl; + + throw generation_failed (); + } + + // Synthesize the column expression for this member. + // + assoc_member const& am (members.back ()); + + column_expr& e (m.set ("column-expr", column_expr ())); + e.push_back (column_expr_part ()); + column_expr_part& ep (e.back ()); + + ep.kind = column_expr_part::reference; + ep.table = am.vo->alias.empty () + ? table_name (*am.vo->object) + : am.vo->alias; + ep.member_path.push_back (am.m); + + src_m = am.m; + } + + // If we have the source member and don't have the type pragma of + // our own, but the source member does, then copy the columnt type + // over. + // + if (src_m != 0 && !m.count ("type") && src_m->count ("type")) + m.set ("column-type", src_m->get<string> ("column-type")); + + // Check the return statements above if you add any extra logic + // here. + } + + struct member_resolver: traversal::class_ + { + member_resolver (assoc_members& members, + assoc_members& pub_members, + semantics::data_member& m) + : member_ (members, pub_members, m) + { + *this >> names_ >> member_; + *this >> inherits_ >> *this; + } + + void + traverse (view_object& vo) + { + member_.vo_ = &vo; + traverse (*vo.object); + } + + virtual void + traverse (type& c) + { + if (!object (c)) + return; // Ignore transient bases. + + names (c); + inherits (c); + } + + public: + static bool + check_types (semantics::type& t1, semantics::type& t2) + { + using semantics::type; + using semantics::derived_type; + + // Require that the types be the same sans the wrapping and + // cvr-qualification. + // + type* pt1 (&t1); + type* pt2 (&t2); + + if (type* wt1 = context::wrapper (*pt1)) + pt1 = wt1; + + if (type* wt2 = context::wrapper (*pt2)) + pt2 = wt2; + + if (derived_type* dt1 = dynamic_cast<derived_type*> (pt1)) + pt1 = &dt1->base_type (); + + if (derived_type* dt2 = dynamic_cast<derived_type*> (pt2)) + pt2 = &dt2->base_type (); + + if (pt1 != pt2) + return false; + + return true; + } + + private: + struct data_member: traversal::data_member + { + data_member (assoc_members& members, + assoc_members& pub_members, + semantics::data_member& m) + : members_ (members), + pub_members_ (pub_members), + name_ (m.name ()), + pub_name_ (context::current ().public_name (m)), + type_ (m.type ()) + { + } + + virtual void + traverse (type& m) + { + // First see if we have the exact match. + // + if (name_ == m.name ()) + { + if (check (m)) + { + assoc_member am; + am.m = &m; + am.vo = vo_; + members_.push_back (am); + } + + return; + } + + // Don't bother with public name matching if we already + // have an exact match. + // + if (members_.empty ()) + { + if (pub_name_ == context::current ().public_name (m)) + { + if (check (m)) + { + assoc_member am; + am.m = &m; + am.vo = vo_; + pub_members_.push_back (am); + } + + return; + } + } + } + + bool + check (semantics::data_member& m) + { + // Make sure that the found node can possibly match. + // + if (context::transient (m) || context::inverse (m)) + return false; + + return check_types (m.type (), type_); + } + + assoc_members& members_; + assoc_members& pub_members_; + + string name_; + string pub_name_; + semantics::type& type_; + + view_object* vo_; + }; + + traversal::names names_; + data_member member_; + traversal::inherits inherits_; + }; + + private: + semantics::class_& view_; + view_query& query_; + view_alias_map& amap_; + view_object_map& omap_; + cxx_string_lexer lex_; + }; + struct class_: traversal::class_, context { class_ () @@ -1107,28 +1553,104 @@ namespace relational traverse_view (c); } + // + // View. + // + + struct relationship + { + semantics::data_member* member; + string name; + view_object* pointer; + view_object* pointee; + }; + + typedef vector<relationship> relationships; + virtual void traverse_view (type& c) { + bool has_q (c.count ("query")); + bool has_o (c.count ("objects")); + + // Determine the kind of query template we've got. + // + view_query& vq (has_q + ? c.get<view_query> ("query") + : c.set ("query", view_query ())); + if (has_q) + { + if (!vq.literal.empty ()) + { + string q (upcase (vq.literal)); + vq.kind = (q.compare (0, 7, "SELECT ") == 0) + ? view_query::complete + : view_query::condition; + } + else if (!vq.expr.empty ()) + { + // If the first token in the expression is a string and + // it starts with "SELECT " or is equal to "SELECT", then + // we have a complete query. + // + if (vq.expr.front ().type == CPP_STRING) + { + string q (upcase (vq.expr.front ().literal)); + vq.kind = (q.compare (0, 7, "SELECT ") == 0 || q == "SELECT") + ? view_query::complete + : view_query::condition; + } + else + vq.kind = view_query::condition; + } + else + vq.kind = view_query::runtime; + } + else + vq.kind = has_o ? view_query::condition : view_query::runtime; + + // We cannot have an incomplete query if there are not objects + // to derive the rest from. + // + if (vq.kind == view_query::condition && !has_o) + { + error (c.file (), c.line (), c.column ()) + << "view '" << c.fq_name () << "' has an incomplete query " + << "template and no associated objects" << endl; + + info (c.file (), c.line (), c.column ()) + << "use db pragma query to provide a complete query template" + << endl; + + info (c.file (), c.line (), c.column ()) + << "or use db pragma object to associate one or more objects " + << "with the view" + << endl; + + throw generation_failed (); + } + // Resolve referenced objects from tree nodes to semantic graph // nodes. // - if (c.count ("objects")) + view_alias_map& amap (c.set ("alias-map", view_alias_map ())); + view_object_map& omap (c.set ("object-map", view_object_map ())); + + if (has_o) { using semantics::class_; - typedef vector<view_object> objects; - objects& objs (c.get<objects> ("objects")); + view_objects& objs (c.get<view_objects> ("objects")); - for (objects::iterator i (objs.begin ()); i < objs.end (); ++i) + for (view_objects::iterator i (objs.begin ()); i != objs.end (); ++i) { tree n (TYPE_MAIN_VARIANT (i->node)); if (TREE_CODE (n) != RECORD_TYPE) { - os << c.file () << ":" << c.line () << ":" << c.column () << ":" - << " error: name '" << i->name << "' in db pragma object " - << " does not name a class" << endl; + error (i->loc) + << "name '" << i->orig_name << "' in db pragma object does " + << "not name a class" << endl; throw generation_failed (); } @@ -1137,22 +1659,242 @@ namespace relational if (!object (o)) { - os << c.file () << ":" << c.line () << ":" << c.column () << ":" - << " error: name '" << i->name << "' in db pragma object " - << "does not name a persistent class" << endl; + error (i->loc) + << "name '" << i->orig_name << "' in db pragma object does " + << "not name a persistent class" << endl; - os << o.file () << ":" << o.line () << ":" << o.column () << ":" - << " info: class '" << i->name << "' is defined here" - << endl; + info (o.file (), o.line (), o.column ()) + << "class '" << i->orig_name << "' is defined here" << endl; throw generation_failed (); } i->object = &o; + + if (i->alias.empty ()) + { + if (!omap.insert (view_object_map::value_type (n, &*i)).second) + { + error (i->loc) + << "persistent class '" << i->orig_name << "' is used in " + << "the view more than once" << endl; + + info (i->loc) + << "use the alias clause to assign it a different name" + << endl; + + throw generation_failed (); + } + } + else + { + if (!amap.insert ( + view_alias_map::value_type (i->alias, &*i)).second) + { + error (i->loc) + << "alias '" << i->alias << "' is used in the view more " + << "than once" << endl; + + throw generation_failed (); + } + } + + // If we have to generate the query and there was no JOIN + // condition specified by the user, try to come up with one + // automatically based on object relationships. + // + if (vq.kind == view_query::condition && + i->cond.empty () && + i != objs.begin ()) + { + relationships rs; + + // Check objects specified prior to this one for any + // relationships. We don't examine objects that were + // specified after this one because that would require + // rearranging the JOIN order. + // + for (view_objects::iterator j (objs.begin ()); j != i; ++j) + { + // First see if any of the objects that were specified + // prior to this object point to it. + // + { + relationship_resolver r (rs, *i, true); + r.traverse (*j); + } + + // Now see if this object points to any of the objects + // specified prior to it. Ignore self-references if any, + // since they were already added to the list in the + // previous pass. + // + { + relationship_resolver r (rs, *j, false); + r.traverse (*i); + } + } + + // Issue diagnostics if we didn't find any or found more + // than one. + // + if (rs.empty ()) + { + error (i->loc) + << "unable to find an object relationship involving " + << "object '" << i->name () << "' and any of the previously " + << "associated objects" << endl; + + info (i->loc) + << "use the join condition clause in db pragma object " + << "to specify a custom join condition" << endl; + + throw generation_failed (); + } + else if (rs.size () > 1) + { + error (i->loc) + << "object relationship for object '" << i->name () << "' " + << "is ambiguous" << endl; + + info (i->loc) + << "candidates are:" << endl; + + for (relationships::const_iterator j (rs.begin ()); + j != rs.end (); + ++j) + { + semantics::data_member& m (*j->member); + + info (m.file (), m.line (), m.column ()) + << " '" << j->name << "' " + << "in object '" << j->pointer->name () << "' " + << "pointing to '" << j->pointee->name () << "'" + << endl; + } + + info (i->loc) + << "use the join condition clause in db pragma object " + << "to resolve this ambiguity" << endl; + + throw generation_failed (); + } + + // Synthesize the condition. + // + relationship const& r (rs.back ()); + + string name (r.pointer->alias.empty () + ? r.pointer->object->fq_name () + : r.pointer->alias); + name += "::"; + name += r.name; + + lexer.start (name); + + string t; + for (cpp_ttype tt (lexer.next (t)); + tt != CPP_EOF; + tt = lexer.next (t)) + { + cxx_token ct; + ct.type = tt; + ct.literal = t; + i->cond.push_back (ct); + } + } } } + + // Handle data members. + // + { + view_data_member t (c); + traversal::names n (t); + names (c, n); + } } + struct relationship_resolver: object_members_base + { + relationship_resolver (relationships& rs, + view_object& pointee, + bool self_pointer) + : object_members_base (false, false, true), + relationships_ (rs), + self_pointer_ (self_pointer), + pointer_ (0), + pointee_ (pointee) + { + } + + void + traverse (view_object& pointer) + { + pointer_ = &pointer; + object_members_base::traverse (*pointer.object); + } + + virtual void + traverse_simple (semantics::data_member& m) + { + if (semantics::class_* c = object_pointer (m.type ())) + { + // Ignore inverse sides of the same relationship to avoid + // phony conflicts caused by the direct side that will end + // up in the relationship list as well. + // + if (inverse (m)) + return; + + // Ignore self-pointers if requested. + // + if (!self_pointer_ && pointer_->object == c) + return; + + if (pointee_.object == c) + { + relationships_.push_back (relationship ()); + relationships_.back ().member = &m; + relationships_.back ().name = member_prefix_ + m.name (); + relationships_.back ().pointer = pointer_; + relationships_.back ().pointee = &pointee_; + } + } + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type& t) + { + if (semantics::class_* c = + object_pointer (context::container_vt (t))) + { + if (inverse (m, "value")) + return; + + // Ignore self-pointers if requested. + // + if (!self_pointer_ && pointer_->object == c) + return; + + if (pointee_.object == c) + { + relationships_.push_back (relationship ()); + relationships_.back ().member = &m; + relationships_.back ().name = member_prefix_ + m.name (); + relationships_.back ().pointer = pointer_; + relationships_.back ().pointee = &pointee_; + } + } + } + + private: + relationships& relationships_; + bool self_pointer_; + view_object* pointer_; + view_object& pointee_; + }; + void assign_pointer (type& c) { @@ -1301,7 +2043,7 @@ namespace relational bool punc (false); bool scoped (false); - for (cpp_ttype tt = lexer.next (t); + for (cpp_ttype tt (lexer.next (t)); tt != CPP_EOF; tt = lexer.next (t)) { @@ -1479,7 +2221,7 @@ namespace relational } private: - cxx_lexer lexer; + cxx_string_lexer lexer; data_member member_; traversal::names member_names_; |