From 2ca4828d303fdd27c573429910f7a25fd1e3727c Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 5 Feb 2015 14:17:07 +0200 Subject: Implement result modifiers in view query condition --- NEWS | 6 ++++ doc/manual.xhtml | 53 ++++++++++++++++++++++++++++------- odb/context.hxx | 7 +++++ odb/pragma.cxx | 60 ++++++++++++++++++++++++++++++++++++++-- odb/relational/mssql/source.cxx | 11 ++++++++ odb/relational/oracle/source.cxx | 15 ++++++++++ odb/relational/processor.cxx | 14 +++++++++- odb/relational/source.cxx | 13 ++++++++- odb/relational/source.hxx | 9 ++++++ odb/relational/sqlite/source.cxx | 9 ++++++ 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 ();

As another example, consider a query that loads the employer 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:

+ We can use this object loading view to implement such a query (notice + the distinct result modifier discussed later in + Section 10.5, "View Query Conditions"):

-#pragma db view object(employer) object(employee)
+#pragma db view object(employer) object(employee) query(distinct)
 struct employer_view
 {
   shared_ptr<employer> er;
@@ -10382,7 +10384,7 @@ struct employer_view
   
 typedef odb::query<employee_employer> query;
 
-db.query<employer_view> ((query::employee::age > 65) + distinct))
+db.query<employer_view> (query::employee::age > 65)
   

We can even use object loading views to load completely unrelated @@ -11020,7 +11022,7 @@ struct employee_vacation

 #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
 };
   
+

The query condition can be optionally followed (or replaced, + if no constant query expression is needed) by one or more + result modifiers. 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). As an example, consider a view that + allows us to get some information about employers ordered + by the object id and without any duplicates:

+ +
+#pragma db view object(employer) object(employee) \
+  query((?) + "ORDER BY" + employer::name_, distinct)
+struct employer_info
+{
+  ...
+};
+  
+ +

If we don't require ordering, then this view can be re-implemented + like this:

+ +
+#pragma db view object(employer) object(employee) query(distinct)
+struct employer_info
+{
+  ...
+};
+  
+

10.6 Native Views

The last kind of view supported by ODB is a native view. Native @@ -15006,12 +15038,13 @@ class employer

14.2.3 query

The query specifier specifies a query condition - for an object or table view or a native SQL query for a native - view. An empty query specifier indicates that a - native SQL query is provided at runtime. For more information - on query conditions refer to Section 10.5, "View - Query Conditions". For more information on native SQL queries, - refer to Section 10.6, "Native Views".

+ and, optionally, result modifiers for an object or table view + or a native SQL query for a native view. An empty query + specifier indicates that a native SQL query is provided at runtime. + For more information on query conditions refer to + Section 10.5, "View Query Conditions". For + more information on native SQL queries, refer to + Section 10.6, "Native Views".

14.2.4 pointer

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 ("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 ("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_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 ("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_entry_; } -- cgit v1.1