Views视图

10 ODB视图

ODB视图是一种C++类或结构类型,它包含一个或多个持久对象、数据库表或原生SQL查询执行结果的轻量级只读投影。

Some of the common applications of views include loading a subset of data members from objects or columns from database tables, executing and handling results of arbitrary SQL queries, including aggregate queries and stored procedure calls, as well as joining multiple objects and/or database tables using object relationships or custom join conditions.

视图的一些常见应用包括从数据库表的对象或列加载数据成员的子集,执行和处理任意SQL查询的结果,包括聚合查询和存储过程调用,以及使用对象关系或自定义连接条件连接多个对象和/或数据库表。

Many relational databases also define the concept of views. Note, however, that ODB views are not mapped to database views. Rather, by default, an ODB view is mapped to an SQL SELECT query. However, if desired, it is easy to create an ODB view that is based on a database view.

许多关系数据库也定义了视图的概念。但是请注意,ODB视图没有映射到数据库视图。相反,在默认情况下,ODB视图被映射到SQL SELECT查询。但是,如果需要,很容易创建一个基于数据库视图的ODB视图。

Usually, views are defined in terms of other persistent entities, such as persistent objects, database tables, sequences, etc. Therefore, before we can examine our first view, we need to define a few persistent objects and a database table. We will use this model in examples throughout this chapter. Here we assume that you are familiar with ODB object relationship support (Chapter 6, "Relationships").

通常,视图是根据其他持久实体定义的,比如持久对象、数据库表、序列等。因此,在检查第一个视图之前,我们需要定义几个持久对象和一个数据库表。我们将在本章的示例中使用这个模型。这里我们假设您熟悉ODB对象关系支持(第6章,“关系”)。

#pragma db object

class country

{

  ...

  #pragma db id

  std::string code_; // ISO 2-letter country code.

  std::string name_;

};

#pragma db object

class employer

{

  ...

  #pragma db id

  unsigned long id_;

  std::string name_;

};

#pragma db object

class employee

{

  ...

  #pragma db id

  unsigned long id_;

  std::string first_;

  std::string last_;

  unsigned short age_;

  shared_ptr<country> residence_;

  shared_ptr<country> nationality_;

  shared_ptr<employer> employed_by_;

};

Besides these objects, we also have the legacy employee_extra table that is not mapped to any persistent class. It has the following definition:

除了这些对象之外,我们还有没有映射到任何持久类的employee_extra表。它的定义如下:

CREATE TABLE employee_extra(

  employee_id INTEGER NOT NULL,

  vacation_days INTEGER NOT NULL,

  previous_employer_id INTEGER)

The above persistent objects and database table as well as many of the views shown in this chapter are based on the view example which can be found in the odb-examples package of the ODB distribution.

上面的持久化对象和数据库表以及本章中显示的许多视图都基于视图示例,该示例可以在ODB发行版的ODB -examples包中找到。

To declare a view we use the db view pragma, for example:

要声明一个视图,我们使用db view pragma,例如:

#pragma db view object(employee)

struct employee_name

{

  std::string first;

  std::string last;

};

The above example shows one of the simplest views that we can create. It has a single associated object (employee) and its purpose is to extract the employee's first and last names without loading any other data, such as the referenced country and employer objects.

上面的例子展示了我们可以创建的最简单的视图之一。它有一个关联的对象(employee),它的目的是提取员工的姓和名,而不加载任何其他数据,比如引用的国家和雇主对象。

Views use the same query facility (Chapter 4, "Querying the Database") as persistent objects. Because support for queries is optional and views cannot be used without this support, you need to compile any header that defines a view with the --generate-query ODB compiler option.

视图使用与持久化对象相同的查询功能(第4章,“查询数据库”)。因为对查询的支持是可选的,没有这种支持就不能使用视图,所以要编译任何带有视图的头文件都需要使用ODB编译器选项 --generate-query 。

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:

为了查询视图的数据库,我们使用database::query()、database::query_one()或database::query_value()函数,其方式与查询对象的数据库完全相同。例如,下面的代码片段显示了我们如何找到所有小于31岁的员工的名字:

typedef odb::query<employee_name> query;

typedef odb::result<employee_name> result;

transaction t (db.begin ());

result r (db.query<employee_name> (query::age < 31));

for (result::iterator i (r.begin ()); i != r.end (); ++i)

{

  const employee_name& en (*i);

  cout << en.first << " " << en.last << endl;

}

t.commit ();

A view can be defined as a projection of one or more objects, one or more tables, a combination of objects and tables, or it can be the result of a custom SQL query. The following sections discuss each of these kinds of view in more detail.

视图可以定义为一个或多个对象、一个或多个表、对象和表的组合的投影,也可以是自定义SQL查询的结果。下面的部分将更详细地讨论每种类型的视图。

 

10.1 Object Views  对象视图

To associate one or more objects with a view we use the db object pragma (Section 14.2.1, "object"). We have already seen a simple, single-object view in the introduction to this chapter. To associate the second and subsequent objects we repeat the db object pragma for each additional object, for example:

要将一个或多个对象与一个视图关联,我们使用db object pragma(章节14.2.1,"object")。在本章的介绍中,我们已经看到了一个简单的单对象视图。为了关联第二个和后续的对象,我们为每个额外的对象重复db object pragma,例如:

#pragma db view object(employee) object(employer)

struct employee_employer

{

  std::string first;

  std::string last;

  std::string name;

};

The complete syntax of the db object pragma is shown below:

db对象的完整语法如下所示:

object(name [= alias] [join-type] [: join-condition])

The name part is a potentially qualified persistent class name that has been defined previously. The optional alias part gives this object an alias. If provided, the alias is used in several contexts instead of the object's unqualified name. We will discuss aliases further as we cover each of these contexts below. The optional join-type part specifies the way this object is associated. It can be left, right, full, inner, and cross with left being the default. Finally, the optional join-condition part provides the criteria which should be used to associate this object with any of the previously associated objects or, as we will see in Section 10.4, "Mixed Views", tables. Note that while the first associated object can have an alias, it cannot have a join type or condition.

名称部分是以前定义的可能要使用的持久类名。可选的别名部分给这个对象一个别名。如果提供了别名,则在多个上下文中使用该别名来代替对象的非限定名称。我们将在下面讨论这些上下文时进一步讨论别名。可选的join-type部分指定该对象的关联方式。它可以是left、right、full、inner和cross, left是默认值。最后,可选的join-condition部分提供了一些条件,这些条件应该被用来将这个对象与任何先前关联的对象相关联,或者,正如我们在10.4节“混合视图”中看到的,表相关联。请注意,虽然第一个关联对象可以有别名,但它不能有联接类型或条件。

For each subsequent associated object the ODB compiler needs a join condition and there are several ways to specify it. The easiest way is to omit it altogether and let the ODB compiler try to come up with a join condition automatically. To do this the ODB compiler will examine each previously associated object for object relationships (Chapter 6, "Relationships") that may exist between these objects and the object being associated. If such a relationship exists and is unambiguous, that is there is only one such relationship, then the ODB compiler will automatically use it to come up with the join condition for this object. This is exactly what happens in the previous example: there is a single relationship (employee::employed_by) between the employee and employer objects.

对于每个后续关联的对象,ODB编译器都需要一个联接条件,并且有几种方法可以指定联接条件。最简单的方法是完全忽略它,让ODB编译器尝试自动提出一个连接条件。为此,ODB编译器将检查每个先前关联的对象是否存在对象关系(第6章,“关系”),这些对象和被关联的对象之间可能存在对象关系。如果存在这样的关系,并且是明确的,即只有一个这样的关系,那么ODB编译器将自动使用它来为这个对象提出连接条件。这正是在前面的例子中所发生的在employee和employer 对象之间有一个单一的关系(employee::employed_by)。

On the other hand, consider this view:

另一方面,考虑以下观点:

#pragma db view object(employee) object(country)

struct employee_residence

{

  std::string first;

  std::string last;

  std::string name;

};

While there is a relationship between country and employee, it is ambiguous. It can be employee::residence_ (which is what we want) or it can be employee::nationality_ (which we don't want). As result, when compiling the above view, the ODB compiler will issue an error indicating an ambiguous object relationship. To resolve this ambiguity, we can explicitly specify the object relationship that should be used to create the join condition as the name of the corresponding data member. Here is how we can fix the employee_residence view:

虽然国家和雇员之间存在着某种关系,但这种关系是模糊的。可以是employee::residence_(这是我们想要的),也可以是employee::nationality_(这是我们不想要的)。因此,在编译上述视图时,ODB编译器将发出一个错误,指示不明确的对象关系。为了解决这种模糊性,我们可以显式地指定应该用于创建连接条件的对象关系作为相应数据成员的名称。下面是修复employee_residence视图的方法:

#pragma db view object(employee) object(country: employee::residence_)

struct employee_residence

{

  std::string first;

  std::string last;

  std::string name;

};

It is possible to associate the same object with a single view more than once using different join conditions. However, in this case, we have to use aliases to assign different names for each association. For example:

可以使用不同的连接条件将同一对象与单个视图关联多次。但是,在这种情况下,我们必须使用别名为每个关联分配不同的名称。例如:

#pragma db view object(employee) \

  object(country = res_country: employee::residence_) \

  object(country = nat_country: employee::nationality_)

struct employee_country

{

  ...

};

Note that correctly defining data members in this view requires the use of a mechanism that we haven't yet covered. We will see how to do this shortly.

请注意,在这个视图中正确定义数据成员需要使用一种我们尚未介绍的机制。我们将很快看到如何做到这一点。

If we assign an alias to an object and refer to a data member of this object in one of the join conditions, we have to use the unqualified alias name instead of the potentially qualified object name. For example:

如果将别名赋给对象并在其中一个联接条件中引用该对象的数据成员,则必须使用非限定的别名而不是潜在的限定对象名。例如:

#pragma db view object(employee = ee) object(country: ee::residence_)

struct employee_residence

{

  ...

};

The last way to specify a join condition is to provide a custom query expression. This method is primarily useful if you would like to associate an object using a condition that does not involve an object relationship. Consider, for example, a modified employee object from the beginning of the chapter with an added country of birth member. For one reason or another we have decided not to use a relationship to the country object, as we have done with residence and nationality.

指定连接条件的最后一种方法是提供自定义查询表达式。如果您希望使用不涉及对象关系的条件来关联对象,则此方法非常有用。例如,考虑本章开头的修改过的employee对象,其中添加了出生成员的国家。由于这样或那样的原因,我们决定不使用与国家对象的关系,就像我们在处理居住权和国籍时那样。

#pragma db object

class employee

{

  ...

  std::string birth_place_; // Country name.

};

If we now want to create a view that returns the birth country code for an employee, then we have to use a custom join condition when associating the country object. For example:

如果现在想要创建一个返回雇员出生国家代码的视图,那么在关联国家对象时必须使用自定义的连接条件。例如:

#pragma db view object(employee) \

  object(country: employee::birth_place_ == country::name_)

struct employee_birth_code

{

  std::string first;

  std::string last;

  std::string code;

};

The syntax of the query expression in custom join conditions is the same as in the query facility used to query the database for objects (Chapter 4, "Querying the Database") except that for query members, instead of using odb::query<object>::member names, we refer directly to object members.

自定义连接条件中的查询表达式的语法与用于为对象查询数据库的查询工具中的语法相同(第4章,“查询数据库”),只是对于查询成员,我们不使用odb::query<object>::member name,而是直接引用对象成员。

Looking at the views we have defined so far, you may be wondering how the ODB compiler knows which view data members correspond to which object data members. While the names are similar, they are not exactly the same, for example employee_name::first and employee::first_.

看看我们到目前为止定义的视图,您可能想知道ODB编译器如何知道哪个视图数据成员对应于哪个对象数据成员。虽然名称相似,但并不完全相同,例如employee_name::first和employee::first_。

As with join conditions, when it comes to associating data members, the ODB compiler tries to do this automatically. It first searches all the associated objects for an exact name match. If no match is found, then the ODB compiler compares the so-called public names. A public name of a member is obtained by removing the common member name decorations, such as leading and trailing underscores, the m_ prefix, etc. In both of these searches the ODB compiler also makes sure that the types of the two members are the same or compatible.

与连接条件一样,当涉及到关联数据成员时,ODB编译器会尝试自动完成这项工作。它首先搜索所有相关联的对象以获得精确的名称匹配。如果没有找到匹配,那么ODB编译器将比较所谓的公共名称。成员的公共名称可以通过删除公共成员名修饰来获得,例如前导和末尾的下划线、m_前缀等。在这两种搜索中,ODB编译器还确保两个成员的类型相同或兼容。

If one of the above searches returned a match and it is unambiguous, that is there is only one match, then the ODB compiler will automatically associate the two members. On the other hand, if no match is found or the match is ambiguous, the ODB compiler will issue an error. To associate two differently-named members or to resolve an ambiguity, we can explicitly specify the member association using the db column pragma (Section 14.4.9, "column"). For example:

如果上面的一个搜索返回了一个匹配,并且没有歧义,即只有一个匹配,那么ODB编译器将自动关联这两个成员。另一方面,如果没有找到匹配或匹配不明确,ODB编译器将发出错误。要关联两个名称不同的成员或解决歧义,可以使用db column pragma(第14.4.9节,"column")显式指定成员关联。例如:

#pragma db view object(employee) object(employer)

struct employee_employer

{

  std::string first;

  std::string last;

  #pragma db column(employer::name_)

  std::string employer_name;

};

If an object data member specifies the SQL type with the db type pragma (Section 14.4.3, "type"), then this type is also used for the associated view data members.

如果一个对象数据成员用db type pragma(章节14.4.3,“type”)指定SQL类型,那么这个类型也用于相关的视图数据成员。

Note also that similar to join conditions, if we assign an alias to an object and refer to a data member of this object in one of the db column pragmas, then we have to use the unqualified alias name instead of the potentially qualified object name. For example:

还请注意,与连接条件类似,如果将别名赋给一个对象,并在其中一个db column pragmas中引用该对象的数据成员,则必须使用非限定的别名,而不是潜在的限定对象名。例如:

#pragma db view object(employee) \

  object(country = res_country: employee::residence_) \

  object(country = nat_country: employee::nationality_)

struct employee_country

{

  std::string first;

  std::string last;

  #pragma db column(res_country::name_)

  std::string res_country_name;

  #pragma db column(nat_country::name_)

  std::string nat_country_name;

};

Besides specifying just the object member, we can also specify a +-expression in the db column pragma. A +-expression consists of string literals and object member references connected using the + operator. It is primarily useful for defining aggregate views based on SQL aggregate functions, for example:

除了指定对象成员之外,我们还可以在db column pragma中指定+-表达式。+-表达式由使用+操作符连接的字符串字面量和对象成员引用组成。它主要用于基于SQL聚合函数定义聚合视图,例如:

#pragma db view object(employee)

struct employee_count

{

  #pragma db column("count(" + employee::id_ + ")")

  std::size_t count;

};

When querying the database for a view, we may want to provide additional query criteria based on the objects associated with this view. To support this a view defines query members for all the associated objects which allows us to refer to such objects' members using the odb::query<view>::member expressions. This is similar to how we can refer to object members using the odb::query<object>::member expressions when querying the database for an object. For example:

当在数据库中查询视图时,我们可能希望根据与该视图关联的对象提供额外的查询条件。为了支持这一点,视图为所有关联的对象定义了查询成员,这允许我们使用odb::query<view>::member表达式来引用这些对象的成员。这类似于在数据库中查询对象时使用odb::query<object>::member表达式引用对象成员的方式。例如:

typedef odb::query<employee_count> query;

transaction t (db.begin ());

// 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.

//

employee_count ec (

  db.query_value<employee_count> (query::last == "Doe"));

cout << ec.count << endl;

t.commit ();

In the above query we used the last name data member from the associated employee object to only count employees with the specific name.

在上面的查询中,我们使用了来自关联雇员对象的姓氏数据成员来只统计具有特定名称的雇员。

When a view has only one associated object, the query members corresponding to this object are defined directly in the odb::query<view> scope. For instance, in the above example, we referred to the last name member as odb::query<employee_count>::last. However, if a view has multiple associated objects, then query members corresponding to each such object are defined in a nested scope named after the object. As an example, consider the employee_employer view again:

当一个视图只有一个关联对象时,与该对象对应的查询成员直接定义在odb::query<view>范围。例如,在上面的例子中,我们将姓成员称为odb::query<employee_count>::last。但是,如果一个视图有多个相关联的对象,那么对应于每个这样的对象的查询成员将定义在一个以该对象命名的嵌套作用域中。例如,再次考虑employee_employer视图:

#pragma db view object(employee) object(employer)

struct employee_employer

{

  std::string first;

  std::string last;

  #pragma db column(employer::name_)

  std::string employer_name;

};

Now, to refer to the last name data member from the employee object we use the odb::query<...>::employee::last expression. Similarly, to refer to the employer name, we use the odb::query<...>::employer::name expression. For example:

现在,为了引用雇员对象的姓氏数据成员,我们使用odb::query<...>::employee::last, 类似地,为了引用雇主名称,我们使用db::query<...>::employer::name表达式。例如:

typedef odb::result<employee_employer> result;

typedef odb::query<employee_employer> query;

transaction t (db.begin ());

result r (db.query<employee_employer> (

  query::employee::last == "Doe" &&

  query::employer::name == "Simple Tech Ltd"));

for (result::iterator i (r.begin ()); i != r.end (); ++i)

  cout << i->first << " " << i->last <<  " " << i->employer_name << endl;

t.commit ();

If we assign an alias to an object, then this alias is used to name the query members scope instead of the object name. As an example, consider the employee_country view again:

如果给对象赋了别名,则使用该别名来命名查询成员作用域而不是对象名称。例如,再次考虑employee_country视图:

#pragma db view object(employee) \

  object(country = res_country: employee::residence_) \

  object(country = nat_country: employee::nationality_)

struct employee_country

{

  ...

};

And a query which returns all the employees that have the same country of residence and nationality:

以及一个返回所有具有相同居住国家和国籍的雇员的查询:

typedef odb::query<employee_country> query;

typedef odb::result<employee_country> result;

transaction t (db.begin ());

result r (db.query<employee_country> (

  query::res_country::name == query::nat_country::name));

for (result::iterator i (r.begin ()); i != r.end (); ++i)

  cout << i->first << " " << i->last << " " << i->res_country_name << endl;

t.commit ();

Note also that unlike object query members, view query members do no support referencing members in related objects. For example, the following query is invalid:

还请注意,与对象查询成员不同,视图查询成员不支持引用相关对象中的成员。例如,以下查询是无效的:

typedef odb::query<employee_name> query;

typedef odb::result<employee_name> result;

transaction t (db.begin ());

result r (db.query<employee_name> (

  query::employed_by->name == "Simple Tech Ltd"));

t.commit ();

To get this behavior, we would instead need to associate the employer object with this view and then use the query::employer::name expression instead of query::employed_by->name.

为了获得这种行为,我们需要将雇主对象与这个视图关联起来,然后使用query::employer::name表达式,而不是query::employed_by->name。

As we have discussed above, if specified, an object alias is used instead of the object name in the join condition, data member references in the db column pragma, as well as to name the query members scope. The object alias is also used as a table name alias in the underlying SELECT statement generated by the ODB compiler. Normally, you would not use the table alias directly with object views. However, if for some reason you need to refer to a table column directly, for example, as part of a native query expression, and you need to qualify the column with the table, then you will need to use the table alias instead.

如上所述,如果指定了对象别名,则在连接条件、db column pragma中的数据成员引用以及为查询成员作用域命名时使用对象别名而不是对象名。在ODB编译器生成的底层SELECT语句中,对象别名也用作表名别名。通常,您不会直接在对象视图中使用表别名。但是,如果由于某种原因需要直接引用表列(例如,作为本机查询表达式的一部分),并且需要用表限定列,那么就需要使用表别名。

 

10.2 Object Loading Views  对象加载视图

A special variant of object views is object loading views. Object loading views allow us to load one or more complete objects instead of, or in addition to, a subset of data member. While we can often achieve the same end result by calling database::load(), using a view has several advantages.

对象视图的一种特殊变体是对象加载视图。对象加载视图允许我们加载一个或多个完整的对象,而不是数据成员的子集。虽然我们通常可以通过调用database::load()来实现相同的最终结果,但使用视图有几个优点。

If we need to load multiple objects, then using a view allows us to do this with a single SELECT statement execution instead of one for each object that would be necessary in case of load(). A view can also be useful for loading only a single object if the query criterion that we would like to use involves other, potentially unrelated, objects. We will examine concrete examples of these and other scenarios in the rest of this section.

如果我们需要加载多个对象,那么使用一个视图可以让我们用一个SELECT语句来完成这个任务,而不是像load()那样需要对每个对象执行一个语句。如果我们想要使用的查询条件涉及其他可能不相关的对象,视图也可以用于只加载单个对象。在本节的其余部分中,我们将研究这些场景和其他场景的具体示例。

To load a complete object as part of a view we use a data member of the pointer to object type, just like for object relationships (Chapter 6, "Relationships"). As an example, here is how we can load both the employee and employer objects from the previous section with a single statement:

要加载一个完整的对象作为视图的一部分,我们使用对象类型指针的数据成员,就像对象关系(第六章,“关系”)。作为一个例子,下面是我们如何用一条语句加载上一节中的employee和employer对象:

#pragma db view object(employee) object(employer)

struct employee_employer

{

  shared_ptr<employee> ee;

  shared_ptr<employer> er;

};

We use an object loading view just like any other view. In the result of a query, as we would expect, the pointer data members point to the loaded objects. For example:

我们像使用其他视图一样使用对象加载视图。如我们所料,在查询的结果中,指针数据成员指向加载的对象。例如:

typedef odb::query<employee_employer> query;

transaction t (db.begin ());

for (const employee_employer& r:

       db.query<employee_employer> (query::employee::age < 31))

{

  cout << r.ee->age () << " " << r.er->name () << endl;

}

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 (notice the distinct result modifier discussed later in Section 10.5, "View Query Conditions"):

另一个例子是,考虑使用基于雇员的条件加载雇主对象的查询。例如,我们想找到雇佣65岁以上员工的所有雇主。我们可以使用这个对象加载视图来实现这样的查询(注意后面在10.5节“查看查询条件”中讨论的不同的结果修饰符):

#pragma db view object(employer) object(employee) query(distinct)

struct employer_view

{

  shared_ptr<employer> er;

};

And this is how we can use this view to find all the employers that employ seniors:

这就是我们如何利用这个视图来找到所有雇佣老年人的雇主:

typedef odb::query<employer_view> query;

db.query<employer_view> (query::employee::age > 65)

We can even use object loading views to load completely unrelated (from the ODB object relationships point of view) objects. For example, the following view will load all the employers that are named the same as a country (notice the inner join type):

我们甚至可以使用对象加载视图来加载完全不相关的(从ODB对象关系的角度来看)对象。例如,下面的视图将加载所有与国家名称相同的雇主(注意内部连接类型):

#pragma db view object(employer) \

  object(country inner: employer::name == country::name)

struct employer_named_country

{

  shared_ptr<employer> e;

  shared_ptr<country> c;

};

An object loading view can contain ordinary data members in addition to object pointers. For example, if we are only interested in the country code in the above view, then we can reimplement it like this:

对象加载视图除了包含对象指针外,还可以包含普通的数据成员。例如,如果我们只对上述视图中的国家代码感兴趣,那么我们可以像这样重新实现它:

#pragma db view object(employer) \

  object(country inner: employer::name == country::name)

struct employer_named_country

{

  shared_ptr<employer> e;

  std::string code;

};

Object loading views also have a few rules and restrictions. Firstly, the pointed-to object in the data member must be associated with the view. Furthermore, if the associated object has an alias, then the data member name must be the same as the alias (more precisely, the public name derived from the data member must match the alias; which means we can use normal data member decorations such as trailing underscores, etc., see the previous section for more information on public names). The following view illustrates the use of aliases as data member names:

对象加载视图也有一些规则和限制。首先,数据成员中被指向的对象必须与视图关联。此外,如果关联的对象有别名,则数据成员名必须与别名相同(更准确地说,从数据成员派生的公共名必须与别名匹配这意味着我们可以使用普通的数据成员修饰,如尾随下划线等,有关公共名称的更多信息,请参阅前一节)。下面的视图演示了如何使用别名作为数据成员名:

#pragma db view object(employee)               \

  object(country = res: employee::residence_)  \

  object(country = nat: employee::nationality_)

struct employee_country

{

  shared_ptr<country> res;

  shared_ptr<country> nat_;

};

Finally, the object pointers must be direct data members of the view. Using, for example, a composite value that contains pointers as a view data member is not supported. Note also that depending on the join type you are using, some of the resulting pointers might be NULL.

最后,对象指针必须是视图的直接数据成员。例如,不支持使用包含指针作为视图数据成员的复合值。还要注意,根据使用的联接类型,一些结果指针可能是NULL。

Up until now we have consistently used shared_ptr as an object pointer in our views. Can we use other pointers, such as unique_ptr or raw pointers? To answer this question we first need to discuss what happens with object pointers that may be inside objects that a view loads. As a concrete example, let us revisit the employee_employer view from the beginning of this section:

到目前为止,我们一直在视图中使用shared_ptr作为对象指针。是否可以使用其他指针,如unique_ptr或原始指针?为了回答这个问题,我们首先需要讨论一下对象指针在视图加载的对象内部会发生什么。作为一个具体的例子,让我们回顾一下本节开始时的employee_employer视图:

#pragma db view object(employee) object(employer)

struct employee_employer

{

  shared_ptr<employee> ee;

  shared_ptr<employer> er;

};

This view loads two objects: employee and employer. The employee object, however, also contains a pointer to employer (see the employed_by_ data member). In fact, this is the same object that the view loads since employer is associated with the view using this same relationship (ODB automatically uses it since it is the only one). The correct result of loading such a view is then clear: both er and er->employed_by_ must point to (or share) the same instance.

这个视图加载两个对象:employee和employer。然而,employee对象也包含一个指向雇主的指针(参见employed_by_ data成员)。实际上,这是视图加载的同一个对象,因为雇主与使用相同关系的视图相关联(ODB自动使用它,因为它是唯一的一个)。加载这样一个视图的正确结果是 clear: both er 和 er->employed_by_  必须指向(或共享)同一个实例。

Just like object loading via the database class functions, views achieve this correct behavior of only loading a single instance of the same object with the help of session's object cache (Chapter 11, "Session"). In fact, object loading views enforce this by throwing the session_required exception if there is no current session and the view loads an object that is also indirectly loaded by one of the other objects. The ODB compiler will also issue diagnostics if such an object has session support disabled (Section 14.1.10, "session").

就像通过数据库类函数加载对象一样,视图在session对象缓存的帮助下实现了只加载同一个对象的单个实例的正确行为(第11章,“session”)。事实上,对象加载视图通过抛出session_required异常来强制执行这一点,如果没有当前会话,并且视图加载的对象也被其他对象间接加载。如果这样的对象禁用了会话支持(章节14.1.10,“session”),ODB编译器也会发出诊断。

With this understanding we can now provide the correct implementation of our transaction that uses the employee_employer view:

有了这个理解,我们现在可以提供使用employee_employer视图的事务的正确实现:

typedef odb::query<employee_employer> query;

transaction t (db.begin ());

odb::session s;

for (const employee_employer& r:

       db.query<employee_employer> (query::employee::age < 31))

{

  assert (r.ee->employed_by_ == r.er);

  cout << r.ee->age () << " " << r.er->name () << endl;

}

t.commit ();

It might seem logical, then, to always load all the objects from all the eager relationships with the view. After all, this will lead to them all being loaded with a single statement. While this is theoretically true, the reality is slightly more nuanced. If there is a high probability of the object already have been loaded and sitting in the cache, then not loading the object as part of the view (and therefore not fetching all its data from the database) might result in better performance.

因此,总是从与视图的所有迫切关系中加载所有对象似乎是合乎逻辑的。毕竟,这将导致它们都被装入一个语句。虽然这在理论上是正确的,但现实要微妙得多。如果对象很可能已经被加载并保存在缓存中,那么不将对象作为视图的一部分加载(因此不从数据库获取其所有数据)可能会导致更好的性能。

Now we can also answer the question about which pointers we can use in object loading views. From the above discussion it should be clear that if an object that we are loading is also part of a relationship inside another object that we are loading, then we should use some form of a shared ownership pointer. If, however, there are no relationships involved, as is the case, for example, in our employer_named_country and employee_country views above, then we can use a unique ownership pointer such as unique_ptr.

现在我们还可以回答在对象加载视图中可以使用哪些指针的问题。从上面的讨论可以清楚地看出,如果一个我们正在加载的对象也是另一个我们正在加载的对象内部关系的一部分,那么我们应该使用某种形式的共享所有权指针。但是,如果没有关系,例如,在上面的employer_named_country和employee_country视图中,我们可以使用唯一的所有权指针,例如unique_ptr。

Note also that your choice of a pointer type can be limited by the "official" object pointer type assigned to the object (Section 3.3, "Object and View Pointers"). For example, if the object pointer type is shared_ptr, you will not be able to use unique_ptr to load such an object into a view since initializing unique_ptr from shared_ptr would be a mistake.

还需要注意的是,你对指针类型的选择可能受到分配给对象的“官方”对象指针类型的限制(第3.3节,“对象和视图指针”)。例如,如果对象指针类型是shared_ptr,你将不能使用unique_ptr将这样的对象加载到视图中,因为从shared_ptr初始化unique_ptr将是一个错误。

Unless you want to perform your own object cleanup, raw object pointers in views are not particularly useful. They do have one special semantics, however: If a raw pointer is used as a view member, then, before creating a new instance, the implementation will check if the member is NULL. If it is not, then it is assumed to point to an existing instance and the implementation will load the data into it instead of creating a new one. The primary use of this special functionality is to implement by-value loading with the ability to detect NULL values.

除非您想要执行自己的对象清理,否则视图中的原始对象指针并不是特别有用。它们确实有一个特殊的语义,然而:如果一个原始指针被用作视图成员,那么,在创建一个新实例之前,实现将检查该成员是否为NULL。如果不是,则假定它指向一个现有的实例,实现将加载数据到该实例中,而不是创建一个新的实例。这个特殊功能的主要用途是通过检测NULL值的能力实现按值加载。

To illustrate this functionality, consider the following view that load the employee's residence country by value:

为了说明这个功能,考虑下面的视图,它按值加载员工的居住国家:

#pragma db view object(employee) \

  object(country = res: employee::residence_) transient

struct employee_res_country

{

  typedef country* country_ptr;

  #pragma db member(res_) virtual(country_ptr) get(&this.res) \

    set(this.res_null = ((?) == nullptr))

  country res;

  bool res_null;

};

Here we are using a virtual data member (Section 14.4.13, "virtual") to add an object pointer member to the view. Its accessor expression returns the pointer to the res member so that the implementation can load the data into it. The modifier expression checks the passed pointer to initialize the NULL value indicator. Here, the two possible values that can be passed to the modifier expression are the address of the res member that we returned earlier from the accessor and NULL (strictly speaking, there is a third possibility: the address of an object that was found in the session cache).

这里我们使用虚数据成员(第14.4.13节,“virtual”)向视图添加一个对象指针成员。它的访问器表达式返回res成员的指针,以便实现可以将数据加载到res成员中。修饰符表达式检查传递的指针以初始化NULL值指示器。这里,可以传递给修饰符表达式的两个可能的值是我们之前从访问器返回的res成员的地址和NULL(严格地说,还有第三种可能:在会话缓存中找到的对象的地址)。

If we are not interested in the NULL indicator, then the above view can simplified to this:

如果我们对NULL指示器不感兴趣,那么上面的视图可以简化为:

#pragma db view object(employee) \

  object(country = res: employee::residence_) transient

struct employee_res_country

{

  typedef country* country_ptr;

  #pragma db member(res_) virtual(country_ptr) get(&this.res) set()

  country res;

};

 

That is, we specify an empty modifier expression which leads to the value being ignored.

也就是说,我们指定了一个空的修饰符表达式,这会导致忽略该值

As another example of by-value loading, consider a view that allows us to load objects into existing instances that have been allocated outside the view:

作为另一个按值加载的例子,考虑这样一个视图,它允许我们将对象加载到已经在视图外分配的现有实例中:

#pragma db view object(employee)               \

  object(country = res: employee::residence_)  \

  object(country = nat: employee::nationality_)

struct employee_country

{

  employee_country (country& r, country& n): res (&r), nat (&n) {}

  country* res;

  country* nat;

};

 

//And here is how we can use this view:

typedef odb::result<employee_country> result;

transaction t (db.begin ());

result r (db.query<employee_country> (...);

for (result::iterator i (r.begin ()); i != r.end (); ++i)

{

  country res, nat;

  employee_country v (res, nat);

  i.load (v);

  if (v.res != nullptr)

    ... // Result is in res.

  if (v.nat != nullptr)

    ... // Result is in nat.

}

t.commit ();

As a final example of the by-value loading, consider the following view which implements a slightly more advanced logic: if the object is already in the session cache, then it sets the pointer data member in the view (er_p) to that. Otherwise, it loads the data into the by-value instance (er). We can also check whether the pointer data member points to the instance to distinguish between the two outcomes. And we can check it for nullptr to detect NULL values.

作为按值加载的最后一个例子,考虑下面的视图,它实现了一个稍微高级一点的逻辑:如果对象已经在会话缓存中,那么它将视图(er_p)中的指针数据成员设置为该对象。否则,它将数据加载到按值实例(er)中。我们还可以检查指针数据成员是否指向实例,以区分这两个结果。我们可以检查它的nullptr来检测NULL值。

#pragma db view object(employer)

struct employer_view

{

  // Since we may be getting the pointer as both smart and raw, we

  // need to create a bit of support code to use in the modifier

  // expression.

  //

  void set_er (employer* p) {er_p = p;}                   // &er or NULL.

  void set_er (shared_ptr<employer> p) {er_p = p.get ();} // From cache.

  #pragma db get(&this.er) set(set_er(?))

  employer* er_p;

  #pragma db transient

  employer er;

  // Return-by-value support (e.g., query_value()).

  //

  employer_view (): er_p (0) {}

  employer_view (const employer_view& x)

    : er_p (x.er_p == &x.er ? &er : x.er_p), er (x.er) {}

};

We can use object loading views with polymorphic objects (Section 8.2, "Polymorphism Inheritance"). Note, however, that when loading a derived object via the base pointer in a view, a separate statement will be executed to load the dynamic part of the object. There is no support for by-value loading for polymorphic objects.

我们可以在多态对象中使用对象加载视图(第8.2节,“多态性继承”)。但是,请注意,当视图中通过基指针加载派生对象时,将执行一个单独的语句来加载对象的动态部分。对于多态对象,不支持按值加载。

We can also use object loading views with objects without id (Section 14.1.6, "no_id"). Note, however, that for such objects, NULL values are not automatically detected (since there is no primary key, which is otherwise guaranteed to be not NULL, there might not be a column on which to base this detection). The workaround for this limitation is to load an otherwise not NULL column next to the object which will serve as an indicator. For example:

我们也可以使用没有id的对象加载视图(章节14.1.6,"no_id")。但是,请注意,对于这样的对象,NULL值不会被自动检测到(因为没有主键,否则保证它不是NULL,因此可能没有用于此检测的列)。这个限制的解决方案是在将作为指示符的对象旁边加载一个否则不为NULL的列。例如:

#pragma db object no_id

class object

{

  ...

  int n; // NOT NULL

  std::string s;

};

#include <odb/nullable.hxx>

#pragma db view object(object)

struct view

{

  odb::nullable<int> n; // If 'n' is NULL, then, logically, so is 'o'.

  unique_ptr<object> o;

};

 

10.3 Table Views  表视图

A table view is similar to an object view except that it is based on one or more database tables instead of persistent objects. Table views are primarily useful when dealing with ad-hoc tables that are not mapped to persistent classes.

表视图类似于对象视图,不同的是它基于一个或多个数据库表,而不是持久对象。表视图在处理没有映射到持久类的特别表时非常有用。

To associate one or more tables with a view we use the db table pragma (Section 14.2.2, "table"). To associate the second and subsequent tables we repeat the db table pragma for each additional table. For example, the following view is based on the employee_extra legacy table we have defined at the beginning of the chapter.

要将一个或多个表与一个视图关联,我们使用db table  pragma(章节14.2.2,"table")。为了关联第二个和后续的表,我们为每个额外的表重复db table pragma。例如,下面的视图基于我们在本章开头定义的employee_extra遗留表。

#pragma db view table("employee_extra")

struct employee_vacation

{

  #pragma db column("employee_id") type("INTEGER")

  unsigned long employee_id;

  #pragma db column("vacation_days") type("INTEGER")

  unsigned short vacation_days;

};

Besides the table name in the db table pragma we also have to specify the column name for each view data member. Note that unlike for object views, the ODB compiler does not try to automatically come up with column names for table views. Furthermore, we cannot use references to object members either, since there are no associated objects in table views. Instead, the actual column name or column expression must be specified as a string literal. The column name can also be qualified with a table name either in the "table.column" form or, if either a table or a column name contains a period, in the "table"."column" form. The following example illustrates the use of a column expression:

除了db table pragma中的表名之外,我们还必须为每个视图数据成员指定列名。注意,与对象视图不同,ODB编译器不会自动为表视图生成列名。此外,我们也不能使用对对象成员的引用,因为在表视图中没有关联的对象。相反,必须将实际的列名或列表达式指定为字符串字面值。列名也可以用“table.column”形式中的表名来限定。或者,如果一个表或一个列名包含句号,则在“table”.“Column”形式中。下面的例子演示了列表达式的用法:

#pragma db view table("employee_extra")

struct employee_max_vacation

{

  #pragma db column("max(vacation_days)") type("INTEGER")

  unsigned short max_vacation_days;

};

Both the asociated table names and the column names can be qualified with a database schema, for example:

关联的表名和列名都可以用数据库模型限定,例如:

#pragma db view table("hr.employee_extra")

struct employee_max_vacation

{

  #pragma db column("hr.employee_extra.vacation_days") type("INTEGER")

  unsigned short vacation_days;

};

For more information on database schemas and the format of the qualified names, refer to Section 14.1.8, "schema".

有关数据库模型和限定名格式的更多信息,请参阅14.1.8节“schema”。

Note also that in the above examples we specified the SQL type for each of the columns to make sure that the ODB compiler has knowledge of the actual types as specified in the database schema.  This is required to obtain correct and optimal generated code.

还请注意,在上面的示例中,我们为每个列指定了SQL类型,以确保ODB编译器了解数据库模型中指定的实际类型。这是获得正确和最佳生成代码所必需的。

The complete syntax of the db table pragma is similar to the db object pragma and is shown below:

db表pragma的完整语法类似于db对象的pragma,如下所示:

table("name" [= "alias"] [join-type] [: join-condition])

The name part is a database table name. The optional alias part gives this table an alias. If provided, the alias must be used instead of the table whenever a reference to a table is used. Contexts where such a reference may be needed include the join condition (discussed below), column names, and query expressions. The optional join-type part specifies the way this table is associated. It can be left, right, full, inner, and cross with left being the default. Finally, the optional join-condition part provides the criteria which should be used to associate this table with any of the previously associated tables or, as we will see in Section 10.4, "Mixed Views", objects. Note that while the first associated table can have an alias, it cannot have a join type or condition.

name部分是一个数据库表名。可选的别名部分给这个表一个别名。如果提供了别名,则在使用对表的引用时必须使用别名而不是表。可能需要此类引用的上下文包括连接条件(下面将讨论)、列名和查询表达式。可选的join-type部分指定该表的关联方式。它可以是left、right、full、inner和cross, left是默认值。最后,可选的join-condition部分提供了一些条件,用于将这个表与之前关联的任何表相关联,或者像我们在10.4节“混合视图”中看到的那样,与对象相关联。请注意,虽然第一个关联表可以有别名,但它不能有联接类型或条件。

Similar to object views, for each subsequent associated table the ODB compiler needs a join condition. However, unlike for object views, for table views the ODB compiler does not try to come up with one automatically. Furthermore, we cannot use references to object members corresponding to object relationships either, since there are no associated objects in table views.Instead, for each subsequent associated table, a join condition must be specified as a custom query expression. While the syntax of the query expression is the same as in the query facility used to query the database for objects (Chapter 4, "Querying the Database"), a join condition for a table is normally specified as a single string literal containing a native SQL query expression.

与对象视图类似,ODB编译器对每个后续关联的表都需要一个连接条件。但是,与对象视图不同,对于表视图,ODB编译器不会尝试自动生成一个。此外,我们也不能使用对应于对象关系的对象成员的引用,因为在表视图中没有关联的对象。相反,对于每个后续关联的表,必须将连接条件指定为自定义查询表达式。虽然查询表达式的语法与用于为对象查询数据库的查询工具相同(第4章,“查询数据库”),但表的连接条件通常指定为包含原生SQL查询表达式的单个字符串字面值。

As an example of a multi-table view, consider the employee_health table that we define in addition to employee_extra:

作为多表视图的一个例子,考虑我们在employee_extra之外定义的employee_health表:

CREATE TABLE employee_health(

  employee_id INTEGER NOT NULL,

  sick_leave_days INTEGER NOT NULL)

Given these two tables we can now define a view that returns both the vacation and sick leave information for each employee:

有了这两个表,我们现在可以定义一个视图,返回每个员工的假期和病假信息:

#pragma db view table("employee_extra" = "extra") \

  table("employee_health" = "health": \

        "extra.employee_id = health.employee_id")

struct employee_leave

{

  #pragma db column("extra.employee_id") type("INTEGER")

  unsigned long employee_id;

  #pragma db column("vacation_days") type("INTEGER")

  unsigned short vacation_days;

  #pragma db column("sick_leave_days") type("INTEGER")

  unsigned short sick_leave_days;

};

 

Querying the database for a table view is the same as for an object view except that we can only use native query expressions. For example:

在数据库中查询表视图与查询对象视图是一样的,只是我们只能使用本机查询表达式。例如:

typedef odb::query<employee_leave> query;

typedef odb::result<employee_leave> result;

transaction t (db.begin ());

unsigned short v_min = ...

unsigned short l_min = ...

result r (db.query<employee_leave> (

  "vacation_days > " + query::_val(v_min) + "AND"

  "sick_leave_days > " + query::_val(l_min)));

t.commit ();

 

10.4 Mixed Views  混合视图

A mixed view has both associated objects and tables. As a first example of a mixed view, let us improve employee_vacation from the previous section to return the employee's first and last names instead of the employee id. To achieve this we have to associate both the employee object and the employee_extra table with the view:

混合视图同时具有关联的对象和表。作为混合视图的第一个示例,让我们改进上一节中的employee_vacation,以返回员工的姓和名,而不是员工id。为了实现这一点,我们必须将employee对象和employee_extra表与视图关联起来:

#pragma db view object(employee) \

  table("employee_extra" = "extra": "extra.employee_id = " + employee::id_)

struct employee_vacation

{

  std::string first;

  std::string last;

  #pragma db column("extra.vacation_days") type("INTEGER")

  unsigned short vacation_days;

}

When querying the database for a mixed view, we can use query members for the parts of the query expression that involves object members but have to fall back to using the native syntax for the parts that involve table columns. For example:

在为混合视图查询数据库时,我们可以为查询表达式中涉及对象成员的部分使用查询成员,但必须为涉及表列的部分使用本机语法。例如:

typedef odb::query<employee_vacation> query;

typedef odb::result<employee_vacation> result;

transaction t (db.begin ());

result r (db.query<employee_vacation> (

  (query::last == "Doe") + "AND extra.vacation_days <> 0"));

for (result::iterator i (r.begin ()); i != r.end (); ++i)

  cout << i->first << " " << i->last << " " << i->vacation_days << endl;

t.commit ();

As another example, consider a more advanced view that associates two objects via a legacy table. This view allows us to find the previous employer name for each employee:

作为另一个例子,考虑一个更高级的视图,它通过一个遗留表关联两个对象。这个视图允许我们找到每个雇员的前雇主名称:

#pragma db view object(employee) \

  table("employee_extra" = "extra": "extra.employee_id = " + employee::id_) \

  object(employer: "extra.previous_employer_id = " + employer::id_)

struct employee_prev_employer

{

  std::string first;

  std::string last;

  // If previous_employer_id is NULL, then the name will be NULL as well.

  // We use the odb::nullable wrapper to handle this.

  //

  #pragma db column(employer::name_)

  odb::nullable<std::string> prev_employer_name;

};

 

10.5 View Query Conditions  视图查询条件

Object, table, and mixed views can also specify an optional query condition that should be used whenever the database is queried for this view. To specify a query condition we use the db query pragma (Section 14.2.3, "query").

对象、表和混合视图还可以指定一个可选的查询条件,在为该视图查询数据库时应该使用该条件。为了指定查询条件,我们使用db query pragma(第14.2.3节,"query")。

As an example, consider a view that returns some information about all the employees that are over a predefined retirement age. One way to implement this would be to define a standard object view as we have done in the previous sections and then use a query like this:

例如,考虑一个返回关于超过预定义退休年龄的所有员工的信息的视图。实现这个的一种方法是定义一个标准的对象视图,就像我们在前几节做的那样,然后使用这样的查询:

result r (db.query<employee_retirement> (query::age > 50));

The problem with the above approach is that we have to keep repeating the query::age > 50 expression every time we execute the query, even though this expression always stays the same. View query conditions allow us to solve this problem. For example:

上述方法的问题是,我们必须不断重复 query::age > 50 表达式,即使这个表达式总是保持不变。查看查询条件可以帮助我们解决这个问题。例如:

#pragma db view object(employee) query(employee::age > 50)

struct employee_retirement

{

  std::string first;

  std::string last;

  unsigned short age;

};

 

With this improvement we can rewrite our query like this:

有了这个改进,我们可以像这样重写我们的查询:

result r (db.query<employee_retirement> ());

But what if we may also need to restrict the result set based on some varying criteria, such as the employee's last name? Or, in other words, we may need to combine a constant query expression specified in the db query pragma with the varying expression specified at the query execution time. To allow this, the db query pragma syntax supports the use of the special (?) placeholder that indicates the position in the constant query expression where the runtime expression should be inserted. For example:

但是,如果我们可能还需要根据一些不同的标准(比如员工的姓)来限制结果集,该怎么办呢?或者,换句话说,我们可能需要将db query pragma中指定的常量查询表达式与查询执行时指定的变化表达式组合在一起。为此,db query pragma语法支持使用特殊(?)占位符,该占位符指示在常量查询表达式中应该插入运行时表达式的位置。例如:

#pragma db view object(employee) query(employee::age > 50 && (?))

struct employee_retirement

{

  std::string first;

  std::string last;

  unsigned short name;

};

With this change we can now use additional query criteria in our view:

有了这个改变,我们现在可以在视图中使用额外的查询条件:

result r (db.query<employee_retirement> (query::last == "Doe"));

The syntax of the expression in a query condition is the same as in the query facility used to query the database for objects (Chapter 4, "Querying the Database") except for two differences. Firstly, for query members, instead of using odb::query<object>::member names, we refer directly to object members, using the object alias instead of the object name if an alias was assigned. Secondly, query conditions support the special (?) placeholder which can be used both in the C++-integrated query expressions as was shown above and in native SQL expressions specified as string literals. The following view is an example of the latter case:

查询条件中的表达式语法与用于查询数据库中的对象的查询工具(第4章,“查询数据库”)相同,但有两个不同之处。首先,对于查询成员,没有使用 odb::query<object>::member 成员名,而是直接引用对象成员,如果分配了别名,则使用对象别名而不是对象名称。其次,查询条件支持特殊(?)占位符,它既可以在上面所示的C++集成查询表达式中使用,也可以在指定为字符串的原生SQL表达式中使用。以下是后一种情况的一个例子:

#pragma db view table("employee_extra") \

  query("vacation_days <> 0 AND (?)")

struct employee_vacation

{

  ...

};

Another common use case for query conditions are views with the ORDER BY or GROUP BY clause. Such clauses are normally present in the same form in every query involving such views. As an example, consider an aggregate view which calculate the minimum and maximum ages of employees for each employer:

查询条件的另一个常见用例是带有ORDER BY或GROUP BY子句的视图。在涉及此类视图的每个查询中,这些子句通常以相同的形式出现。例如,考虑一个聚合视图,它为每个雇主计算雇员的最小和最大年龄:

#pragma db view object(employee) object(employer) \

  query((?) + "GROUP BY" + employer::name_)

struct employer_age

{

  #pragma db column(employer::name_)

  std::string employer_name;

  #pragma db column("min(" + employee::age_ + ")")

  unsigned short min_age;

  #pragma db column("max(" + employee::age_ + ")")

  unsigned short max_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:

查询条件可以由一个或多个结果修饰符跟随(如果不需要常量查询表达式,也可以替换)。当前支持的结果修饰符是不同的(翻译为SELECT distinct)和for_update(翻译为FOR UPDATE或同等的数据库系统支持它)。举个例子,考虑这样一个视图,它允许我们获得一些根据对象id排序的雇主信息,而不需要任何重复:

#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 views are a low-level mechanism for capturing results of native SQL queries, stored procedure calls, etc. Native views don't have associated tables or objects. Instead, we use the db query pragma to specify the native SQL query, which should normally include the select-list and, if applicable, the from-list. For example, here is how we can re-implement the employee_vacation table view from Section 10.3 above as a native view:

ODB支持的最后一种视图是本机视图。本机视图是一种低级机制,用于捕获本机SQL查询、存储过程调用等结果。本机视图没有关联的表或对象。相反,我们使用db query pragma来指定本机SQL查询,该查询通常应该包括select-list,如果适用的话,还应该包括from-list。例如,下面是我们如何将上面章节10.3中的employee_vacation表视图重新实现为一个本地视图:

#pragma db view query("SELECT employee_id, vacation_days " \

                      "FROM employee_extra")

struct employee_vacation

{

  #pragma db type("INTEGER")

  unsigned long employee_id;

  #pragma db type("INTEGER")

  unsigned short vacation_days;

};

In native views the columns in the query select-list are associated with the view data members in the order specified. That is, the first column is stored in the first member, the second column — in the second member, and so on. The ODB compiler does not perform any error checking in this association. As a result you must make sure that the number and order of columns in the query select-list match the number and order of data members in the view. This is also the reason why we are not required to provide the column name for each data member in native views, as is the case for object and table views.

在本机视图中,查询选择列表中的列按照指定的顺序与视图数据成员关联。也就是说,第一列存储在第一个成员中,第二列—存储在第二个成员中,依此类推。ODB编译器在此关联中不执行任何错误检查。因此,您必须确保查询选择列表中列的数量和顺序与视图中数据成员的数量和顺序匹配。这也是为什么我们不需要像对象视图和表视图那样,为本地视图中的每个数据成员提供列名的原因。

Note also that while it is always possible to implement a table view as a native view, the table views must be preferred since they are safer. In a native view, if you add, remove, or rearrange data members without updating the column list in the query, or vice versa, at best, this will result in a runtime error. In contrast, in a table view such changes will result in the query being automatically updated.

还要注意的是,虽然始终可以将表视图实现为本机视图,但表视图必须是首选,因为它们更安全。在本机视图中,如果添加、删除或重新排列数据成员而不更新查询中的列列表,或者反之亦然,这最多将导致运行时错误。相反,在表视图中,这样的更改将导致查询被自动更新。

Similar to object and table views, the query specified for a native view can contain the special (?) placeholder which is replaced with the query expression specified at the query execution time. If the native query does not contain a placeholder, as in the example above, then any query expression specified at the query execution time is appended to the query text along with the WHERE keyword, if required. The following example shows the usage of the placeholder:

与对象视图和表视图类似,为本机视图指定的查询可以包含特殊(?)占位符,该占位符将被在查询执行时指定的查询表达式所替换。如果本机查询不包含占位符(如上例所示),那么在查询执行时指定的任何查询表达式都会被附加到查询文本中(如果需要的话),并带有WHERE关键字。下面的例子展示了占位符的用法:

#pragma db view query("SELECT employee_id, vacation_days " \

                      "FROM employee_extra " \

                      "WHERE vacation_days <> 0 AND (?)")

struct employee_vacation

{

  ...

};

As another example, consider a view that returns the next value of a database sequence:

另一个例子,考虑一个返回数据库序列下一个值的视图:

#pragma db view query("SELECT nextval('my_seq')")

struct sequence_value

{

  unsigned long long value;

};

While this implementation can be acceptable in some cases, it has a number of drawbacks. Firstly, the name of the sequence is fixed in the view, which means if we have a second sequence, we will have to define another, almost identical view. Similarly, the operation that we perform on the sequence is also fixed. In some situations, instead of returning the next value, we may need the last value.

虽然这种实现在某些情况下是可以接受的,但它有许多缺点。首先,序列的名称在视图中是固定的,这意味着如果我们有第二个序列,我们将不得不定义另一个几乎相同的视图。类似地,我们对序列执行的操作也是固定的。在某些情况下,我们可能需要最后一个值,而不是返回下一个值。

Note that we cannot use the placeholder mechanism to resolve these problems since placeholders can only be used in the WHERE, GROUP BY, and similar clauses. In other words, the following won't work:

注意,我们不能使用占位符机制来解决这些问题,因为占位符只能在WHERE、GROUP BY和类似的子句中使用。换句话说,以下方法行不通:

#pragma db view query("SELECT nextval('(?)')")

struct sequence_value

{

  unsigned long long value;

};

result r (db.query<sequence_value> ("my_seq"));

To support these kinds of use cases, ODB allows us to specify the complete query for a native view at runtime rather than at the view definition. To indicate that a native view has a runtime query, we can either specify the empty db query pragma or omit the pragma altogether. For example:

为了支持这些用例,ODB允许我们在运行时(而不是视图定义)为本机视图指定完整的查询。要表明本机视图有一个运行时查询,我们可以指定空的db query pragma,也可以忽略该pragma。例如:

#pragma db view

struct sequence_value

{

  unsigned long long value;

};

Given this view, we can perform the following queries:

给定这个视图,我们可以执行以下查询:

typedef odb::query<sequence_value> query;

typedef odb::result<sequence_value> result;

string seq_name = ...

result l (db.query<sequence_value> (

  "SELECT lastval('" + seq_name + "')"));

result n (db.query<sequence_value> (

  "SELECT nextval('" + seq_name + "')"));

Native views can also be used to call and handle results of stored procedures. The semantics and limitations of stored procedures vary greatly between database systems while some do not support this functionality at all. As a result, support for calling stored procedures using native views is described for each database system in Part II, "Database Systems".

有了这个视图,我们可以执行以下查询:本机视图还可以用来调用和处理存储过程的结果。存储过程的语义和限制在不同的数据库系统之间差别很大,而有些系统根本不支持这种功能。因此,在第2部分“数据库系统”中对每个数据库系统使用本机视图调用存储过程的支持进行了描述。

10.7 Other View Features and Limitations  其他视图特性和限制

Views cannot be derived from other views. However, you can derive a view from a transient C++ class. View data members cannot be object pointers. If you need to access data from a pointed-to object, then you will need to associate such an object with the view. Similarly, view data members cannot be containers. These two limitations also apply to composite value types that contain object pointers or containers. Such composite values cannot be used as view data members.

视图不能从其他视图派生。但是,你可以从一个临时C++类中派生一个视图。视图数据成员不能是对象指针。如果您需要从一个指向对象访问数据,那么您需要将这样一个对象与视图关联起来。类似地,视图数据成员不能是容器。这两个限制也适用于包含对象指针或容器的复合值类型。这样的复合值不能用作视图数据成员。

On the other hand, composite values that do not contain object pointers or containers can be used in views. As an example, consider a modified version of the employee persistent class that stores a person's name as a composite value:

另一方面,不包含对象指针或容器的复合值可以在视图中使用。例如,考虑一个修改后的employee持久化类,它将一个人的名字存储为一个复合值:

#pragma db value

class person_name

{

  std::string first_;

  std::string last_;

};

#pragma db object

class employee

{

  ...

  person_name name_;

  ...

};

Given this change, we can re-implement the employee_name view like this:

有了这个改变,我们可以像这样重新实现employee_name视图:

#pragma db view object(employee)

struct employee_name

{

  person_name name;

};

It is also possible to extract some or all of the nested members of a composite value into individual view data members. Here is how we could have defined the employee_name view if we wanted to keep its original structure:

也可以将复合值的部分或全部嵌套成员提取到单独的视图数据成员中。下面是我们如何定义employee_name视图,如果我们想保持它原来的结构:

#pragma db view object(employee)

struct employee_name

{

  #pragma db column(employee::name.first_)

  std::string first;

  #pragma db column(employee::name.last_)

  std::string last;

};

 

10.1 对象视图

10.2 对象加载视图

10.3 表格视图

10.4 混合视图

10.5 查看查询条件

10.6 原生视图

10.7 其他视图功能和限制