From f5e457f5dee11cbd20fc3557f79d6e1f235fb89f Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 6 Feb 2015 08:57:30 +0200 Subject: Implement join types support in views --- odb/relational/mysql/source.cxx | 13 ++++++++ odb/relational/processor.cxx | 15 +++++++-- odb/relational/source.cxx | 72 ++++++++++++++++++++++++++++++++++------ odb/relational/source.hxx | 17 ++++++++++ odb/relational/sqlite/source.cxx | 19 +++++++++++ 5 files changed, 123 insertions(+), 13 deletions(-) (limited to 'odb/relational') diff --git a/odb/relational/mysql/source.cxx b/odb/relational/mysql/source.cxx index bcc7f23..9725f18 100644 --- a/odb/relational/mysql/source.cxx +++ b/odb/relational/mysql/source.cxx @@ -681,6 +681,19 @@ namespace relational { os << im << "value = 0;"; } + + virtual string + join_syntax (view_object const& vo) + { + if (vo.join == view_object::full) + { + error (vo.loc) + << "FULL OUTER JOIN is not supported by MySQL" << endl; + throw operation_failed (); + } + + return base::join_syntax (vo); + } }; entry class_entry_; diff --git a/odb/relational/processor.cxx b/odb/relational/processor.cxx index 16904b6..9442850 100644 --- a/odb/relational/processor.cxx +++ b/odb/relational/processor.cxx @@ -1344,6 +1344,15 @@ namespace relational for (view_objects::iterator i (objs.begin ()); i != objs.end (); ++i) { + if (i == objs.begin () && i->join != view_object::left) + { + error (i->loc) + << "no join type can be specified for the first associated " + << (i->kind == view_object::object ? "object" : "table") + << endl; + throw operation_failed (); + } + if (i->kind != view_object::object) { // Make sure we have join conditions for tables unless it @@ -1362,11 +1371,13 @@ namespace relational // 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. + // automatically based on object relationships. CROSS JOIN + // has no condition. // if (vq.kind == view_query::condition && i->cond.empty () && - i != objs.begin ()) + i != objs.begin () && + i->join != view_object::cross) { relationships rs; diff --git a/odb/relational/source.cxx b/odb/relational/source.cxx index dc083bb..e0fbab3 100644 --- a/odb/relational/source.cxx +++ b/odb/relational/source.cxx @@ -4776,12 +4776,19 @@ traverse_view (type& c) continue; } - l = "LEFT JOIN "; + l = join_syntax (*i); + l += ' '; l += quote_id (i->tbl_name); if (!i->alias.empty ()) l += (need_alias_as ? " AS " : " ") + quote_id (i->alias); + if (i->join == view_object::cross) // No ON condition for CROSS JOIN. + { + from.push_back (l); + continue; + } + semantics::scope& scope ( dynamic_cast (*unit.find (i->scope))); @@ -4851,20 +4858,28 @@ traverse_view (type& c) semantics::scope& scope ( dynamic_cast (*unit.find (i->scope))); - expression e ( - translate_expression ( - c, i->cond, scope, i->loc, "object")); + expression e (i->join == view_object::cross + ? expression ("") // Dummy literal expression. + : translate_expression ( + c, i->cond, scope, i->loc, "object")); // Literal expression. // if (e.kind == expression::literal) { - l = "LEFT JOIN "; + l = join_syntax (*i); + l += ' '; l += table_qname (o); if (!alias.empty ()) l += (need_alias_as ? " AS " : " ") + quote_id (alias); + if (i->join == view_object::cross) // No ON condition for CROSS JOIN. + { + from.push_back (l); + continue; + } + l += " ON"; // Add the pragma location for easier error tracking. @@ -4891,6 +4906,9 @@ traverse_view (type& c) // is a to-many relationship, then we first need to JOIN the // container table. This code is similar to object_joins. // + // Note that this cannot be CROSS JOIN; we've handled that case + // above. + // using semantics::data_member; data_member& m (*e.member_path.back ()); @@ -5029,7 +5047,8 @@ traverse_view (type& c) string ct; // Container table. if (cont != 0) { - l = "LEFT JOIN "; + l = join_syntax (*i); + l += ' '; // The same relationship can be used by multiple associated // objects. So if the object that contains this container has @@ -5174,7 +5193,15 @@ traverse_view (type& c) } } - l = "LEFT JOIN "; + // If we have already joined the container with the desired + // join type, then use LEFT JOIN to join the object to the + // container. This is the right thing to do since an entry + // in the container can only point (either via id or value) + // to a single object. + // + l = (cont == 0 ? join_syntax (*i) : "LEFT JOIN"); + l += ' '; + l += table_qname (o); if (!alias.empty ()) @@ -5563,13 +5590,36 @@ traverse_view (type& c) { if (i->compare (0, 5, "FROM ") == 0) os << "r += " << strlit (*i) << ";"; - else if (i->compare (0, 5, "LEFT ") == 0) - os << endl - << "r += " << strlit (sep + *i) << ";"; else if (i->compare (0, 3, "// ") == 0) os << *i << endl; else - os << "r += " << *i << ";"; // Already a string literal. + { + // See if this is a JOIN. The exact spelling is database-dependent, + // but we know there is the JOIN word in there somewhere and before + // it we should only have keywords and spaces. + // + size_t p (i->find ("JOIN ")); + if (p != string::npos) + { + // Make sure before it we only have A-Z and spaces. + // + for (char c; p != 0; --p) + { + c = (*i)[p - 1]; + if ((c < 'A' || c > 'Z') && c != ' ') + break; + } + + if (p == 0) + os << endl + << "r += " << strlit (sep + *i) << ";"; + } + + // Something else, assume already a string literal. + // + if (p != 0) + os << "r += " << *i << ";"; + } } // Generate the query condition. diff --git a/odb/relational/source.hxx b/odb/relational/source.hxx index 48d0e1d..bf675f2 100644 --- a/odb/relational/source.hxx +++ b/odb/relational/source.hxx @@ -6686,6 +6686,23 @@ namespace relational return c.get ("query").for_update ? "FOR UPDATE" : ""; } + virtual string + join_syntax (view_object const& vo) + { + const char* r; + + switch (vo.join) + { + case view_object::left: r = "LEFT JOIN"; break; + case view_object::right: r = "RIGHT JOIN"; break; + case view_object::full: r = "FULL JOIN"; break; + case view_object::inner: r = "INNER JOIN"; break; + case view_object::cross: r = "CROSS JOIN"; break; + } + + return r; + } + virtual void traverse_view (type& c); diff --git a/odb/relational/sqlite/source.cxx b/odb/relational/sqlite/source.cxx index 0824eb2..0e842c1 100644 --- a/odb/relational/sqlite/source.cxx +++ b/odb/relational/sqlite/source.cxx @@ -255,6 +255,25 @@ namespace relational // return ""; } + + virtual string + join_syntax (view_object const& vo) + { + const char* n (0); + + if (vo.join == view_object::full) + n = "FULL OUTER JOIN"; + else if (vo.join == view_object::right) + n = "RIGHT OUTER JOIN"; + + if (n != 0) + { + error (vo.loc) << n << " is not supported by SQLite" << endl; + throw operation_failed (); + } + + return base::join_syntax (vo); + } }; entry class_entry_; } -- cgit v1.1