From d59e3c27450747e5a04585ee9e943376b5bcfa41 Mon Sep 17 00:00:00 2001 From: Michael Shepanski Date: Thu, 6 Nov 2014 16:40:53 +1100 Subject: Implement {query,execute}_{one,value}() shortcut functions Useful in situations where the query is know to return at most one element (*_one) or exactly one element (*_value). --- NEWS | 7 ++ doc/manual.xhtml | 212 ++++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 170 insertions(+), 49 deletions(-) diff --git a/NEWS b/NEWS index 0e2d50f..b1dd122 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,12 @@ Version 2.4.0 + * New database class functions, query_one(), query_value(), provide + convenient shortcuts for situations where the query is know to return + at most one element (query_one) or exactly one element (query_value). + Corresponding execute_one() and execute_value() functions for prepared + queries are also provided. For details, refer to Sections 4.3, "Executing + a Query" and 4.5, "Prepared Queries" in the ODB manual. + * New pragma, on_delete, allows the specification of an on-delete semantics (translated to the ON DELETE SQL clause) for an object pointer. For more information, refer to Section 14.4.15, "on_delete" in the ODB manual. diff --git a/doc/manual.xhtml b/doc/manual.xhtml index dd03def..13eb954 100644 --- a/doc/manual.xhtml +++ b/doc/manual.xhtml @@ -2052,14 +2052,16 @@ Hello, Joe! { transaction t (db->begin ()); - result r (db->query<person> (query::first == "Joe" && - query::last == "Dirt")); - - result::iterator i (r.begin ()); + // Here we know that there can be only one Joe Dirt in our + // database so we use the query_one() shortcut instead of + // manually iterating over the result returned by query(). + // + auto_ptr<person> joe ( + db->query_one<person> (query::first == "Joe" && + query::last == "Dirt")); - if (i != r.end ()) + if (joe.get () != 0) { - auto_ptr<person> joe (i.load ()); joe->age (joe->age () + 1); db->update (*joe); } @@ -2117,8 +2119,12 @@ struct person_stat }; -

To get the result of a view we use the same query() - function as when querying the database for an object. Here is +

Normally, to get the result of a view we use the same + query() function as when querying the database for + an object. Here, however, we are executing an aggregate query + which always returns exactly one element. Therefore, instead + of getting the result instance and then iterating over it, we + can use the shortcut query_value() function. Here is how we can load and print our statistics using the view we have just created:

@@ -2128,11 +2134,9 @@ struct person_stat { transaction t (db->begin ()); - odb::result<person_stat> r (db->query<person_stat> ()); - // The result of this query always has exactly one element. // - const person_stat& ps (*r.begin ()); + person_stat ps (db->query_value<person_stat> ()); cout << "count : " << ps.count << endl << "min age: " << ps.min_age << endl @@ -2185,16 +2189,15 @@ max age: 33 { transaction t (db->begin ()); - result r (db->query<person> (query::first == "John" && - query::last == "Doe")); - - result::iterator i (r.begin ()); + // Here we know that there can be only one John Doe in our + // database so we use the query_one() shortcut again. + // + auto_ptr<person> john ( + db->query_one<person> (query::first == "John" && + query::last == "Doe")); - if (i != r.end ()) - { - auto_ptr<person> john (i.load ()); + if (john.get () != 0) db->erase (*john); - } t.commit (); } @@ -2494,8 +2497,6 @@ psql --user=odb_test --dbname=odb_test -f person-pgsql.sql libraries. - -

Do not be concerned if, at this point, much appears unclear. The intent of this chapter is to give you only a general idea of how to persist C++ objects with ODB. We will cover all the details throughout the remainder @@ -4920,11 +4921,104 @@ find_minors (database& db, const query& name_query) result r (find_minors (db, query::first == "John")); +

The result of executing a query is zero, one, or more objects + matching the query criteria. The query() function + returns this result as an instance of the odb::result + class template, which provides a stream-like interface and is + discussed in detail in the next section.

+ +

In situations where we know that a query produces at most one + element, we can instead use the database::query_one() and + database::query_value() shortcut functions, for example:

+ +
+  typedef odb::query<person> query;
+
+  auto_ptr<person> p (
+    db.query_one<person> (
+      query::email == "jon@example.com"));
+  
+ +

The shortcut query functions have the following signatures:

+ +
+  template <typename T>
+  typename object_traits<T>::pointer_type
+  query_one ();
+
+  template <typename T>
+  bool
+  query_one (T&);
+
+  template <typename T>
+  T
+  query_value ();
+
+  template <typename T>
+  typename object_traits<T>::pointer_type
+  query_one (const odb::query<T>&);
+
+  template <typename T>
+  bool
+  query_one (const odb::query<T>&, T&);
+
+  template <typename T>
+  T
+  query_value (const odb::query<T>&);
+  
+ +

Similar to query(), the first three functions are used + to return the only persistent object of a given type stored in the + database. The second three versions use the passed query instance + to only return the object matching the query criteria.

+ +

Similar to the database::find() functions + (Section 3.9, "Loading Persistent Objects"), + query_one() can either allocate a new instance of the + object class in the dynamic memory or it can load the object's state + into an existing instance. The query_value() function + allocates and returns the object by value.

+ +

The query_one() function allows us to determine + if the query result contains zero or one element. If no objects + matching the query criteria were found in the database, the + first version of query_one() returns the NULL + pointer while the second — false. If the second + version returns false, then the passed object + remains unchanged. For example:

+ +
+  if (unique_ptr<person> p = db.query_one<person> (
+        query::email == "jon@example.com"))
+  {
+    ...
+  }
+
+  person p;
+  if (db.query_one<person> (query::email == "jon@example.com", p))
+  {
+    ...
+  }
+  
+ +

If the query executed using query_one() or + query_value() returns more than one element, + then these functions fail with an assertion. Additionally, + query_value() also fails with an assertion if + the query returned no elements.

+ +

Common situations where we can use the shortcut functions are a + query condition that uses a data member with the + unique constraint (at most one element returned; + see Section 14.7, "Index Definition Pragmas") + as well as aggregate queries (exactly one element returned; see + Chapter 10, "Views").

+

4.4 Query Result

-

The result of executing a query is zero, one, or more objects - matching the query criteria. The result is returned as an instance - of the odb::result class template, for example:

+

The database::query() function returns the result of + executing a query as an instance of the odb::result + class template, for example:

   typedef odb::query<person> query;
@@ -5183,7 +5277,6 @@ namespace odb
   }
   
-

4.5 Prepared Queries

Most modern relational database systems have the notion of a prepared @@ -5195,12 +5288,12 @@ namespace odb

In ODB all the non-query database operations such as persist(), load(), update(), - etc., are implemented in terms of prepared statements that are - cached and reused. While the query() database - operation also uses the prepared statement, this statement - is not cached or reused by default since ODB has no knowledge - of whether a query will be executed multiple times or only - once. Instead, ODB provides a mechanism, called prepared queries, + etc., are implemented in terms of prepared statements that are cached + and reused. While the query(), query_one(), + and query_one() database operations also use prepared + statements, these statements are not cached or reused by default since + ODB has no knowledge of whether a query will be executed multiple times + or only once. Instead, ODB provides a mechanism, called prepared queries, that allows us to prepare a query once and execute it multiple times. In other words, ODB prepared queries are a thin wrapper around the underlying database's prepared statement functionality.

@@ -5302,6 +5395,15 @@ namespace odb result<T> execute (bool cache = true); + typename object_traits<T>::pointer_type + execute_one (); + + bool + execute_one (T& object); + + T + execute_value (); + const char* name () const; @@ -5343,6 +5445,21 @@ namespace odb also that re-executing a prepared query invalidates the previous execution result, whether cached or uncached.

+

The execute_one() and execute_value() + functions can be used as shortcuts to execute a query that is + known to return at most one or exactly one object, respectively. + The arguments and return values in these functions have the same + semantics as in query_one() and query_value(). + And similar to execute() above, prepare_query() + and execute_one/value() can be seen as the + query_one/value() function split into two: + prepare_query() takes the first + query_one/value() argument (the query condition) while + execute_one/value() takes the second argument (if any) + and returns the result. Note also that execute_one/value() + never caches its result but invalidates the result of any previous + execute() call on the same prepared query.

+

The name() function returns the prepared query name. This is the same name as was passed as the first argument in the prepare_query() call. The statement() @@ -9746,11 +9863,12 @@ struct employee_name to compile any header that defines a view with the --generate-query ODB compiler option.

-

To query the database for a view we use the database::query() - function in exactly the same way as we would use it to query the - database for an object. For example, the following code fragment - shows how we can find the names of all the employees that are - younger than 31:

+

To query the database for a view we use the + database::query(), database::query_one(), or + database::query_value() functions in exactly the same way + as we would use them to query the database for an object. For example, + the following code fragment shows how we can find the names of all the + employees that are younger than 31:

 typedef odb::query<employee_name> query;
@@ -10028,18 +10146,18 @@ struct employee_count
      querying the database for an object. For example:

-typedef odb::result<employee_count> result;
 typedef odb::query<employee_count> query;
 
 transaction t (db.begin ());
 
-// Find the number of employees with the Doe last name.
+// Find the number of employees with the Doe last name. Result of this
+// aggregate query contains only one element so use the query_value()
+// shortcut function.
 //
-result r (db.query<employee_count> (query::last == "Doe"));
+employee_count ec (
+  db.query_value<employee_count> (query::last == "Doe"));
 
-// Result of this aggregate query contains only one element.
-//
-cout << r.begin ()->count << endl;
+cout << ec.count << endl;
 
 t.commit ();
   
@@ -19660,20 +19778,16 @@ struct person_min_max_age
 typedef odb::query<person_min_max_age> query;
-typedef odb::result<person_min_max_age> result;
 
 transaction t (db.begin ());
 
+// We know this query always returns a single row, so use query_value().
 // We have to pass dummy values for OUT parameters.
 //
-result r (
-  db.query<person_min_max_age> (
+person_min_max_age mma (
+  db.query_value<person_min_max_age> (
     query::_val (0) + "," + query::_val (0)));
 
-// We know this query always returns a single row.
-//
-person_min_max_age mma (*r.begin ());
-
 cerr << mma.min_age << " " << mma.max_age << endl;
 
 t.commit ();
-- 
cgit v1.1