【开源库学习】libodb库学习(六)

7 值类型

  • 在第3.1节“概念和术语”中,我们已经讨论了值和值类型的概念,以及简单值和复合值之间的区别。本章更详细地介绍了简单值类型和复合值类型。

7.1 简单值类型

  • 简单值类型是一种基本的C++类型或映射到单个数据库列的类类型。对于每个支持的数据库系统,ODB编译器为大多数基本的C++类型(如intfloat)以及某些类类型(如std::string)提供了一个到合适数据库类型的默认映射。有关每个数据库系统的默认映射的更多信息,请参阅第二部分“数据库系统”。我们还可以使用db-type pragma为这些或我们自己的值类型提供自定义映射(第14.3.1节,“type”)。

7.2 复合值类型

  • 复合值类型是映射到多个数据库列的类或结构类型。要声明复合值类型,我们使用db-value pragma,例如:
#pragma db value
class basic_name
{
  ...

  std::string first_;
  std::string last_;
};
  • 上述代码片段的完整版本和本节中提供的其他代码示例可以在odb-examples中的composite 示例中找到。

  • 复合值类型不必定义默认构造函数,除非它被用作容器的元素。在这种情况下,默认构造函数可以设置为私有,前提是我们还将<odb/core.hxx>标头中定义的odb::access类设置为该值类型的朋友。例如:

#include <odb/core.hxx>

#pragma db value
class basic_name
{
public:
  basic_name (const std::string& first, const std::string& last);

  ...

private:
  friend class odb::access;

  basic_name () {} // Needed for storing basic_name in containers.

  ...
};
  • ODB编译器还需要访问复合值类型的非瞬态(第14.4.11节,“瞬态”)数据成员。它使用与第3.2节“声明持久对象和值”中讨论的持久类相同的机制。

  • 复合值的成员可以是其他值类型(简单或复合)、容器(第5章,“容器”)和指向对象的指针(第6章,“关系”)。同样,复合值类型可以在对象成员中使用,作为容器的元素,也可以作为另一种复合值类型的基础。特别是,复合值类型可以用作集合容器中的元素类型(第5.2节,“集合和多集合容器”),也可以用作映射容器中的键类型(第5.3节,“映射和多映射容器”)。用作容器元素的复合值类型不能包含其他容器,因为不允许使用容器的容器。以下示例说明了一些可能的用例:

#pragma db value
class basic_name
{
  ...

  std::string first_;
  std::string last_;
};

typedef std::vector<basic_name> basic_names;

#pragma db value
class name_extras
{
  ...

  std::string nickname_;
  basic_names aliases_;
};

#pragma db value
class name: public basic_name
{
  ...

  std::string title_;
  name_extras extras_;
};

#pragma db object
class person
{
  ...

  name name_;
};
  • 复合值类型可以在持久类、视图或其他复合值中定义,甚至可以设置为私有,前提是我们将odb::access设置为包含类的朋友,例如:
#pragma db object
class person
{
  ...

  #pragma db value
  class name
  {
    ...

    std::string first_;
    std::string last_;
  };

  name name_;
};
  • 复合值类型也可以定义为C++类模板的实例化,例如:
template <typename T>
struct point
{
  T x;
  T y;
  T z;
};

typedef point<int> int_point;
#pragma db value(int_point)

#pragma db object
class object
{
  ...

  int_point center_;
};
  • 请注意,这种复合值类型的数据库支持代码是在编译包含db value pragma的标头时生成的,而不是在编译包含模板定义或typedef名称的标头时。这允许我们使用在其他文件中定义的模板,例如在utility 标准头文件中定义了std::pair
#include <utility> // std::pair

typedef std::pair<std::string, std::string> phone_numbers;
#pragma db value(phone_numbers)

#pragma db object
class person
{
  ...

  phone_numbers phone_;
};
  • 我们还可以在数据库查询中使用复合值类型的数据成员(第4章,“查询数据库”)。对于持久类中的每个复合值,查询类定义了一个嵌套成员,其中包含与值类型中的数据成员对应的成员。然后,我们可以使用成员访问语法(.)来引用值类型中的数据成员。例如,上面显示的person对象的查询类包含name成员(其名称来源于name_data成员),而name成员又包含extras成员(其姓名来源于复合值类型的name::extras-data成员)。对于嵌套的复合值类型,此过程会递归进行,因此,我们可以在数据库中查询person对象时使用query::name.extras.nickname表达式。例如:
typedef odb::query<person> query;
typedef odb::result<person> result;

transaction t (db.begin ());

result r (db.query<person> (
  query::name.extras.nickname == "Squeaky"));

...

t.commit ();

7.2.1 复合对象ID

  • 对象id可以是复合值类型,例如:
#pragma db value
class name
{
  ...

  std::string first_;
  std::string last_;
};

#pragma db object
class person
{
  ...

  #pragma db id
  name name_;
};
  • 但是,可用作对象id的值类型有许多限制。这样的值类型不能有容器、对象指针或只读数据成员。它还必须是默认可构造的、可复制可构造的和可复制可分配的。此外,如果将此复合值类型用作对象id的持久类启用了会话支持(第11章,“会话”),则它还必须实现小于比较运算符(operator<)。

7.2.2 复合值列和表名称

  • 为简单值类型的数据成员自定义列名很简单:我们只需使用db-column pragma指定所需的名称(第14.4.9节,“column”)。对于复合值类型,事情稍微复杂一些,因为它们被映射到多列。考虑以下示例:
#pragma db value
class name
{
  ...

  std::string first_;
  std::string last_;
};

#pragma db object
class person
{
  ...

  #pragma db id auto
  unsigned long id_;

  name name_;
};
  • first_last_成员的列名是通过使用经过净化的person::name_成员的名称作为前缀,使用值类型(first_last_)中的成员名称作为后缀来构造的。因此,上述类的数据库模式如下:
CREATE TABLE person (
  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
  name_first TEXT NOT NULL,
  name_last TEXT NOT NULL);
  • 我们可以使用db column pragma自定义前缀和后缀,如下例所示:
#pragma db value
class name
{
  ...

  #pragma db column("first_name")
  std::string first_;

  #pragma db column("last_name")
  std::string last_;
};

#pragma db object
class person
{
  ...

  #pragma db column("person_")
  name name_;
};
  • 数据库架构更改如下:
CREATE TABLE person (
  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
  person_first_name TEXT NOT NULL,
  person_last_name TEXT NOT NULL);
  • 我们还可以将列前缀设为空,例如:
#pragma db object
class person
{
  ...

  #pragma db column("")
  name name_;
};
  • 这将导致以下架构:
CREATE TABLE person (
  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
  first_name TEXT NOT NULL,
  last_name TEXT NOT NULL);
  • 当复合值类型用作容器的元素时,同样的原则也适用,除了使用db-value_column(第14.4.36节,“value_column”)或db-key_column(第14.4.45节,“key_column。

  • 当复合值类型包含容器时,会使用额外的表来存储其元素(第5章,“容器”)。此类表的名称的构造方式类似于列名,除了默认情况下对象名和成员名都用作前缀。例如:

#pragma db value
class name
{
  ...

  std::string first_;
  std::string last_;
  std::vector<std::string> nicknames_;
};

#pragma db object
class person
{
  ...

  name name_;
};
  • 相应的数据库架构如下:
CREATE TABLE person_name_nicknames (
  object_id BIGINT UNSIGNED NOT NULL,
  index BIGINT UNSIGNED NOT NULL,
  value TEXT NOT NULL)

CREATE TABLE person (
  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
  name_first TEXT NOT NULL,
  name_last TEXT NOT NULL);
  • 要自定义容器表名,我们可以使用db-table pragma(第14.4.20节,“table”),例如:
#pragma db value
class name
{
  ...

  #pragma db table("nickname")
  std::vector<std::string> nicknames_;
};

#pragma db object
class person
{
  ...

  #pragma db table("person_")
  name name_;
};
  • 这将导致以下架构更改:
CREATE TABLE person_nickname (
  object_id BIGINT UNSIGNED NOT NULL,
  index BIGINT UNSIGNED NOT NULL,
  value TEXT NOT NULL)
  • 与列类似,我们可以将表前缀设为空。

7.3 指针和NULL值语义

  • 关系数据库系统有一个特殊的NULL值的概念,用于表示列中没有有效值。虽然默认情况下ODB将值映射到不允许NULL值的列,但可以使用db-NULL pragma(第14.4.6节,“NULL/not_NULL”)进行更改。

  • 为了正确支持NULL语义,C++值类型必须具有NULL值的概念或类似的特殊状态概念。大多数基本的C++类型,如int或std::string,都没有这个概念,因此不能直接用于启用NULL的数据成员(在从数据库加载NULL值的情况下,这些数据成员将被默认初始化)。

  • 为了允许将不支持NULL语义的值类型轻松转换为支持NULL语义,ODB提供了ODB::NULL类模板。它允许我们将现有的C++类型包装到一个类似容器的类中,该类可以是NULL,也可以包含包装类型的值。ODB还会自动为ODB::NULL类型的数据成员启用NULL值。例如:

#include <odb/nullable.hxx>

#pragma db object
class person
{
  ...

  std::string first_;                    // TEXT NOT NULL
  odb::nullable<std::string> middle_;    // TEXT NULL
  std::string last_;                     // TEXT NOT NULL
};
  • odb::null类模板在<odb/nullable.hxx>头文件中定义,并具有以下接口:
namespace odb
{
  template <typename T>
  class nullable
  {
  public:
    typedef T value_type;

    nullable ();
    nullable (const T&);
    nullable (const nullable&);
    template <typename Y> explicit nullable (const nullable<Y>&);

    nullable& operator= (const T&);
    nullable& operator= (const nullable&);
    template <typename Y> nullable& operator= (const nullable<Y>&);

    void swap (nullable&);

    // Accessor interface.
    //
    bool null () const;

    T&       get ();
    const T& get () const;

    // Pointer interface.
    //
    operator bool_convertible () const;

    T*       operator-> ();
    const T* operator-> () const;

    T&       operator* ();
    const T& operator* () const;

    // Reset to the NULL state.
    //
    void reset ();
  };
}
  • 以下示例显示了如何使用此接口:
  nullable<string> ns;

  // Using the accessor interface.
  //
  if (ns.null ())
  {
    s = "abc";
  }
  else
  {
    string s (ns.get ());
    ns.reset ();
  }

  // The same using the pointer interface.
  //
  if (ns)
  {
    s = "abc";
  }
  else
  {
    string s (*ns);
    ns.reset ();
  }
  • odb::null类模板要求包装类型具有公共默认构造函数和复制构造函数以及复制赋值运算符。还要注意,odb::null实现并不是最有效的,因为它总是包含包装类型的完全构造值。对于C++基本类型或std::string等简单类型,这通常不是问题。然而,对于更复杂的类型,这可能会成为一个问题。在这种情况下,您可能需要考虑使用可选值概念的更有效实现,例如Boost中的可选类模板(第23.4节,“可选库”)。

  • 另一个常见的C++值表示形式是指针,它可以是NULL。ODB将自动处理指向值的指针数据成员,但是,它不会像ODB::nullable那样自动为这些数据成员启用NULL值。相反,如果需要NULL值,我们需要使用db-NULL pragma显式启用它。例如:

#pragma db object
class person
{
  ...

  std::string first_;

  #pragma db null
  std::auto_ptr<std::string> middle_;

  std::string last_;
};
  • ODB编译器内置了对使用std::auto_ptrstd::unique_ptr(C++11)和shared_ptr(TR1或C++11)作为值指针的支持。此外,ODB配置文件库可用于常用的框架和库(如Boost和Qt),为这些框架和库中的智能指针提供支持(第三部分,“配置文件”)。

  • ODB还支持复合值的NULL语义。在关系数据库中,对于该复合值的所有简单数据成员,NULL复合值被转换为NULL值。例如:

#pragma db value
struct name
{
  std::string first_;
  odb::nullable<std::string> middle_;
  std::string last_;
};

#pragma db object
class person
{
  ...
  odb::nullable<name> name_;
};
  • ODB不支持容器的NULL语义。这也意味着包含容器的复合值不能为NULL。考虑到这一限制,我们仍然可以在容器类型的数据成员中使用智能指针。唯一的限制是这些指针不能为NULL。例如:
#pragma db object
class person
{
  ...

  std::auto_ptr<std::vector<std::string> > aliases_;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值