aboutsummaryrefslogtreecommitdiff
path: root/odb
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-02-06 08:57:30 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-02-06 08:57:30 +0200
commitf5e457f5dee11cbd20fc3557f79d6e1f235fb89f (patch)
tree17b21ea5494e37aea6a336284990ebb6424466e8 /odb
parent2ca4828d303fdd27c573429910f7a25fd1e3727c (diff)
Implement join types support in views
Diffstat (limited to 'odb')
-rw-r--r--odb/context.hxx2
-rw-r--r--odb/pragma.cxx68
-rw-r--r--odb/relational/mysql/source.cxx13
-rw-r--r--odb/relational/processor.cxx15
-rw-r--r--odb/relational/source.cxx72
-rw-r--r--odb/relational/source.hxx17
-rw-r--r--odb/relational/sqlite/source.cxx19
7 files changed, 191 insertions, 15 deletions
diff --git a/odb/context.hxx b/odb/context.hxx
index d6e4713..c408063 100644
--- a/odb/context.hxx
+++ b/odb/context.hxx
@@ -159,8 +159,10 @@ struct view_object
name () const;
enum kind_type { object, table };
+ enum join_type { left, right, full, inner, cross };
kind_type kind;
+ join_type join;
tree obj_node; // Tree node if kind is object.
std::string obj_name; // Name as specified in the pragma if kind is object.
qname tbl_name; // Table name if kind is table.
diff --git a/odb/pragma.cxx b/odb/pragma.cxx
index 618df67..08dc7ae 100644
--- a/odb/pragma.cxx
+++ b/odb/pragma.cxx
@@ -1131,7 +1131,7 @@ handle_pragma (cxx_lexer& l,
else if (p == "table")
{
// table (<name>)
- // table (<name> [= "<alias>"] [: "<cond>"] (view only)
+ // table (<name> [= "<alias>"] [type] [: "<cond>"] (view only)
//
// <name> := "name" | "name.name" | "name"."name"
//
@@ -1183,9 +1183,41 @@ handle_pragma (cxx_lexer& l,
tt = l.next (tl, &tn);
}
+ if (tt == CPP_NAME)
+ {
+ // We have a JOIN type.
+ //
+ if (tl == "left")
+ vo.join = view_object::left;
+ else if (tl == "right")
+ vo.join = view_object::right;
+ else if (tl == "full")
+ vo.join = view_object::full;
+ else if (tl == "inner")
+ vo.join = view_object::inner;
+ else if (tl == "cross")
+ vo.join = view_object::cross;
+ else
+ {
+ error (l) << "unknown join type '" << tl << "'" << endl;
+ return;
+ }
+
+ tt = l.next (tl, &tn);
+ }
+ else
+ vo.join = view_object::left;
+
if (tt == CPP_COLON)
{
// We have a condition.
+ //
+ if (vo.join == view_object::cross)
+ {
+ error (l)
+ << "no join condition can be specified for a cross join" << endl;
+ return;
+ }
tt = l.next (tl, &tn);
@@ -1653,7 +1685,7 @@ handle_pragma (cxx_lexer& l,
}
else if (p == "object")
{
- // object (fq-name [ = name] [: expr])
+ // object (fq-name [ = name] [type] [: expr])
//
// Make sure we've got the correct declaration type.
@@ -1706,9 +1738,41 @@ handle_pragma (cxx_lexer& l,
tt = l.next (tl, &tn);
}
+ if (tt == CPP_NAME)
+ {
+ // We have a JOIN type.
+ //
+ if (tl == "left")
+ vo.join = view_object::left;
+ else if (tl == "right")
+ vo.join = view_object::right;
+ else if (tl == "full")
+ vo.join = view_object::full;
+ else if (tl == "inner")
+ vo.join = view_object::inner;
+ else if (tl == "cross")
+ vo.join = view_object::cross;
+ else
+ {
+ error (l) << "unknown join type '" << tl << "'" << endl;
+ return;
+ }
+
+ tt = l.next (tl, &tn);
+ }
+ else
+ vo.join = view_object::left;
+
if (tt == CPP_COLON)
{
// We have a condition.
+ //
+ if (vo.join == view_object::cross)
+ {
+ error (l)
+ << "no join condition can be specified for a cross join" << endl;
+ return;
+ }
tt = l.next (tl, &tn);
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_> 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<semantics::scope&> (*unit.find (i->scope)));
@@ -4851,20 +4858,28 @@ traverse_view (type& c)
semantics::scope& scope (
dynamic_cast<semantics::scope&> (*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<view_query> ("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_> class_entry_;
}