aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-02-05 14:17:07 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-02-05 14:17:07 +0200
commit2ca4828d303fdd27c573429910f7a25fd1e3727c (patch)
treed2637c11861d44a634ecc09bbe88c3a3ff350b16
parent8e69f40ab32dc8604b68f360ae30fa961ba036ee (diff)
Implement result modifiers in view query condition
-rw-r--r--NEWS6
-rw-r--r--doc/manual.xhtml53
-rw-r--r--odb/context.hxx7
-rw-r--r--odb/pragma.cxx60
-rw-r--r--odb/relational/mssql/source.cxx11
-rw-r--r--odb/relational/oracle/source.cxx15
-rw-r--r--odb/relational/processor.cxx14
-rw-r--r--odb/relational/source.cxx13
-rw-r--r--odb/relational/source.hxx9
-rw-r--r--odb/relational/sqlite/source.cxx9
10 files changed, 183 insertions, 14 deletions
diff --git a/NEWS b/NEWS
index d59dd96..f003c4b 100644
--- a/NEWS
+++ b/NEWS
@@ -27,6 +27,12 @@ Version 2.4.0
Section 15.2, "Persistent Class Template Instantiations" in the ODB
manual.
+ * Support for result modifiers in view query conditions. Currently
+ supported result modifiers are 'distinct' (which is translated to
+ SELECT DISTINCT) and 'for_update' (which is translated to FOR UPDATE or
+ equivalent for database systems that support it). For details, refer to
+ Section 10.5, "View Query Conditions" in the ODB manual.
+
* Besides odb::stderr_tracer there is now odb::stderr_full_tracer that
traces statement preparations and deallocations in addition to their
executions. This new implementation can be useful when you want to
diff --git a/doc/manual.xhtml b/doc/manual.xhtml
index f91a4af..3e941dc 100644
--- a/doc/manual.xhtml
+++ b/doc/manual.xhtml
@@ -10366,10 +10366,12 @@ t.commit ();
<p>As another example, consider a query that loads the <code>employer</code>
objects using some condition based on its employees. For instance, we
want to find all the employers that employ people over 65 years old.
- We can use this object loading view to implement such a query:</p>
+ We can use this object loading view to implement such a query (notice
+ the <code>distinct</code> result modifier discussed later in
+ <a href="#10.5">Section 10.5, "View Query Conditions"</a>):</p>
<pre class="cxx">
-#pragma db view object(employer) object(employee)
+#pragma db view object(employer) object(employee) query(distinct)
struct employer_view
{
shared_ptr&lt;employer> er;
@@ -10382,7 +10384,7 @@ struct employer_view
<pre class="cxx">
typedef odb::query&lt;employee_employer> query;
-db.query&lt;employer_view> ((query::employee::age > 65) + distinct))
+db.query&lt;employer_view> (query::employee::age > 65)
</pre>
<p>We can even use object loading views to load completely unrelated
@@ -11020,7 +11022,7 @@ struct employee_vacation
<pre class="cxx">
#pragma db view object(employee) object(employer) \
- query ((?) + "GROUP BY" + employer::name_)
+ query((?) + "GROUP BY" + employer::name_)
struct employer_age
{
#pragma db column(employer::name_)
@@ -11034,6 +11036,36 @@ struct employer_age
};
</pre>
+ <p>The query condition can be optionally followed (or replaced,
+ if no constant query expression is needed) by one or more
+ <em>result modifiers</em>. Currently supported result modifiers
+ are <code>distinct</code> (which is translated to <code>SELECT
+ DISTINCT</code>) and <code>for_update</code> (which is translated
+ to <code>FOR UPDATE</code> or equivalent for database systems
+ that support it). As an example, consider a view that
+ allows us to get some information about employers ordered
+ by the object id and without any duplicates:</p>
+
+ <pre class="cxx">
+#pragma db view object(employer) object(employee) \
+ query((?) + "ORDER BY" + employer::name_, distinct)
+struct employer_info
+{
+ ...
+};
+ </pre>
+
+ <p>If we don't require ordering, then this view can be re-implemented
+ like this:</p>
+
+ <pre class="cxx">
+#pragma db view object(employer) object(employee) query(distinct)
+struct employer_info
+{
+ ...
+};
+ </pre>
+
<h2><a name="10.6">10.6 Native Views</a></h2>
<p>The last kind of view supported by ODB is a native view. Native
@@ -15006,12 +15038,13 @@ class employer
<h3><a name="14.2.3">14.2.3 <code>query</code></a></h3>
<p>The <code>query</code> specifier specifies a query condition
- for an object or table view or a native SQL query for a native
- view. An empty <code>query</code> specifier indicates that a
- native SQL query is provided at runtime. For more information
- on query conditions refer to <a href="#10.5">Section 10.5, "View
- Query Conditions"</a>. For more information on native SQL queries,
- refer to <a href="#10.6">Section 10.6, "Native Views"</a>.</p>
+ and, optionally, result modifiers for an object or table view
+ or a native SQL query for a native view. An empty <code>query</code>
+ specifier indicates that a native SQL query is provided at runtime.
+ For more information on query conditions refer to
+ <a href="#10.5">Section 10.5, "View Query Conditions"</a>. For
+ more information on native SQL queries, refer to
+ <a href="#10.6">Section 10.6, "Native Views"</a>.</p>
<h3><a name="14.2.4">14.2.4 <code>pointer</code></a></h3>
diff --git a/odb/context.hxx b/odb/context.hxx
index 34f9692..d6e4713 100644
--- a/odb/context.hxx
+++ b/odb/context.hxx
@@ -199,6 +199,8 @@ view_relationship_map;
//
struct view_query
{
+ view_query (): distinct (false), for_update (false) {}
+
enum kind_type
{
runtime,
@@ -212,6 +214,11 @@ struct view_query
cxx_tokens expr;
tree scope;
location_t loc;
+
+ // Result modifiers (only for condition).
+ //
+ bool distinct; // SELECT DISTINCT
+ bool for_update; // SELECT FOR UPDATE
};
//
diff --git a/odb/pragma.cxx b/odb/pragma.cxx
index 7a3d6ec..618df67 100644
--- a/odb/pragma.cxx
+++ b/odb/pragma.cxx
@@ -176,8 +176,8 @@ parse_expression (cxx_lexer& l,
cxx_tokens& ts,
string const& prag)
{
- // Keep reading tokens until we see a matching ')' while keeping track
- // of their balance.
+ // Keep reading tokens until we see a mis-matching ')' or ',' while
+ // keeping track of the '()' balance.
//
size_t balance (0);
@@ -201,6 +201,13 @@ parse_expression (cxx_lexer& l,
balance--;
break;
}
+ case CPP_COMMA:
+ {
+ if (balance == 0)
+ done = true;
+ else
+ break;
+ }
case CPP_STRING:
{
ct.literal = tl;
@@ -1584,6 +1591,55 @@ handle_pragma (cxx_lexer& l,
return; // Diagnostics has already been issued.
}
+ // Disallow query(, distinct).
+ //
+ if (tt == CPP_COMMA && vq.expr.empty ())
+ {
+ error (l) << "query expression expected in db pragma " << p << endl;
+ return;
+ }
+
+ // The query expression can be omitted with the modifier in its
+ // place. Handle this case.
+ //
+ if (vq.expr.size () == 1 && vq.expr.front ().type == CPP_NAME)
+ {
+ string const& n (vq.expr.front ().literal);
+
+ if (n == "distinct")
+ vq.distinct = true;
+ else if (n == "for_update")
+ vq.for_update = true;
+
+ if (vq.distinct || vq.for_update)
+ vq.expr.clear ();
+ }
+
+ if (tt == CPP_COMMA)
+ {
+ do
+ {
+ if (l.next (tl, &tn) != CPP_NAME)
+ {
+ error (l) << "result modifier expected in db pragma " << p << endl;
+ return;
+ }
+
+ if (tl == "distinct")
+ vq.distinct = true;
+ else if (tl == "for_update")
+ vq.for_update = true;
+ else
+ {
+ error (l) << "unknown result modifier '" << tl << "'" << endl;
+ return;
+ }
+
+ tt = l.next (tl, &tn);
+
+ } while (tt == CPP_COMMA);
+ }
+
if (tt != CPP_CLOSE_PAREN)
{
error (l) << "')' expected at the end of db pragma " << p << endl;
diff --git a/odb/relational/mssql/source.cxx b/odb/relational/mssql/source.cxx
index ca36dfb..71bc40e 100644
--- a/odb/relational/mssql/source.cxx
+++ b/odb/relational/mssql/source.cxx
@@ -1179,6 +1179,17 @@ namespace relational
}
}
+ virtual string
+ from_trailer (type& c)
+ {
+ return c.get<view_query> ("query").for_update
+ ? " WITH (UPDLOCK)"
+ : "";
+ }
+
+ virtual string
+ select_trailer (type&) {return "";}
+
private:
// Go via the dynamic creation to get access to the constructor.
//
diff --git a/odb/relational/oracle/source.cxx b/odb/relational/oracle/source.cxx
index ea4a14b..71b032e 100644
--- a/odb/relational/oracle/source.cxx
+++ b/odb/relational/oracle/source.cxx
@@ -619,6 +619,21 @@ namespace relational
return r;
}
+
+ virtual string
+ select_trailer (type& c)
+ {
+ view_query const& vq (c.get<view_query> ("query"));
+
+ if (vq.for_update && vq.distinct)
+ {
+ error (vq.loc)
+ << "Oracle does not support FOR UPDATE with DISTINCT" << endl;
+ throw operation_failed ();
+ }
+
+ return base::select_trailer (c);
+ }
};
entry<class_> class_entry_;
}
diff --git a/odb/relational/processor.cxx b/odb/relational/processor.cxx
index 7658725..16904b6 100644
--- a/odb/relational/processor.cxx
+++ b/odb/relational/processor.cxx
@@ -1298,11 +1298,23 @@ namespace relational
vq.kind = view_query::condition;
}
else
- vq.kind = view_query::runtime;
+ vq.kind = (vq.distinct || vq.for_update)
+ ? view_query::condition // The query(distinct) case.
+ : view_query::runtime;
}
else
vq.kind = has_o ? view_query::condition : view_query::runtime;
+ if ((vq.distinct || vq.for_update) && vq.kind != view_query::condition)
+ {
+ error (vq.loc)
+ << "result modifier specified for "
+ << (vq.kind == view_query::runtime ? "runtime" : "native")
+ << " query" << endl;
+
+ throw operation_failed ();
+ }
+
// We cannot have an incomplete query if there are not objects
// to derive the rest from.
//
diff --git a/odb/relational/source.cxx b/odb/relational/source.cxx
index 39a210e..dc083bb 100644
--- a/odb/relational/source.cxx
+++ b/odb/relational/source.cxx
@@ -4770,6 +4770,8 @@ traverse_view (type& c)
if (!i->alias.empty ())
l += (need_alias_as ? " AS " : " ") + quote_id (i->alias);
+ l += from_trailer (c);
+
from.push_back (l);
continue;
}
@@ -4829,6 +4831,8 @@ traverse_view (type& c)
if (!alias.empty ())
l += (need_alias_as ? " AS " : " ") + quote_id (alias);
+ l += from_trailer (c);
+
from.push_back (l);
if (poly_depth != 1)
@@ -5539,7 +5543,7 @@ traverse_view (type& c)
string sep (versioned || query_optimize ? "\n" : " ");
os << "query_base_type r (" << endl
- << strlit ("SELECT" + sep);
+ << strlit ((vq.distinct ? "SELECT DISTINCT" : "SELECT") + sep);
for (statement_columns::const_iterator i (sc.begin ()),
e (sc.end ()); i != e;)
@@ -5632,6 +5636,13 @@ traverse_view (type& c)
<< "r += c.clause_prefix ();"
<< "r += c;"
<< "}";
+
+ string st (select_trailer (c));
+ if (!st.empty ())
+ {
+ os << "r += " << strlit (sep) << ";"
+ << "r += " << strlit (st) << ";";
+ }
}
else
{
diff --git a/odb/relational/source.hxx b/odb/relational/source.hxx
index a725b6a..48d0e1d 100644
--- a/odb/relational/source.hxx
+++ b/odb/relational/source.hxx
@@ -6677,6 +6677,15 @@ namespace relational
<< "imb";
}
+ virtual string
+ from_trailer (type&) { return "";}
+
+ virtual string
+ select_trailer (type& c)
+ {
+ return c.get<view_query> ("query").for_update ? "FOR UPDATE" : "";
+ }
+
virtual void
traverse_view (type& c);
diff --git a/odb/relational/sqlite/source.cxx b/odb/relational/sqlite/source.cxx
index 3dfaae2..0824eb2 100644
--- a/odb/relational/sqlite/source.cxx
+++ b/odb/relational/sqlite/source.cxx
@@ -246,6 +246,15 @@ namespace relational
{
os << im << "null = true;";
}
+
+ virtual string
+ select_trailer (type&)
+ {
+ // SQLite has not support for FOR UPDATE and since this is an
+ // optimization, we simply ignore it.
+ //
+ return "";
+ }
};
entry<class_> class_entry_;
}