/*
* 这个例子程序是Berkeley DB的示例程序之一(DB/example_cxx/AccessMethod.cpp),
* 它演示了如何使用Berkeley DB的基本功能,包括打开一个数据库,存入若干个
* key/data pair,然后遍历数据库中的数据,最后关闭数据库。
*
* 原始代码中有一些英文注释,但是对于初学者还是不够详细,我没有删除原来
* 的注释,而且添加了针对每一个Berkeley DB操作的更加详细的说明,请参考。
*
* 代码的非关键部分都已删除,所以这里的内容
* 无法直接编译运行。可以直接编译运行的版本我会放到空间的附件中。
*
*
* 用词约定:
* 本文提到的“数据库”是指Berkeley DB的database,相当于关系数据库的一个表。
* 一个数据库当中保存着很多个key/data pair,相当于关系数据库的一个表当中
* 保存着很多条记录。也就是说一个key/data pair相当于关系数据库的一条记录。
* 而数据库环境(DbEnv)是指Berkeley Db的执行环境,相当于一个关系数据库管理系统。
*/

/* 测试用例类声明 */
class AccessExample
{
public:
   AccessExample();
   void run(bool removeExistingDatabase, const char *fileName);

private:
   // no need for copy and assignment
   AccessExample(const AccessExample &);
   void operator = (const AccessExample &);
};


/*
* 这个例子程序演示了如何使用Berkeley DB的基本功能,包括打开一个数据库,存入
* 若干个key/data pair,然后遍历数据库中的数据,最后关闭数据库。
*/
int
main(int argc, char *argv[])
{
   // Use a try block just to report any errors.
   // An alternate approach to using exceptions is to
   // use error models (see DbEnv::set_error_model()) so
   // that error codes are returned for all Berkeley DB methods.
   //
   try {
       AccessExample app;
       app.run((bool)(rflag == 1 ? true : false), database);
       return (EXIT_SUCCESS);
   }
   catch (DbException &dbe) {
       cerr << "AccessExample: " << dbe.what() << "/n";
       return (EXIT_FAILURE);
   }
}

void AccessExample::run(bool removeExistingDatabase, const char *fileName)
{
// Remove the previous database.
if (removeExistingDatabase)
   (void)remove(fileName);

// Create the database object.
// There is no environment for this simple example.
// 这里我们没有指定显式的environment,BerkeleyDB会在内部创建一个专门供
// 这个数据库使用的environment. 同时,第二个参数表明,当出现错误后,
// Berkeley DB会抛出异常。
Db db(0, 0);

// 当出现运行错误后,错误信息写入cerr流。
//
// Berkeley DB在调试模式下,可以可选地输出非常丰富的错误信息和其他运行期信息,
// 大大简化了调试过程。比如,你可以让Berkeley DB把错误信息写到一个文件、
// 一个c++ io流中,或者调用用户注册的回调函数由用户自己处理错误信息,
// 以及在错误信息中前缀某些自定义信息,等等。
//
// 关于错误报告和调试的文档:
// http://www.oracle.com/technology/documentation/berkeley-db/db/ref/debug/runtime.html
// http://www.oracle.com/technology/documentation/berkeley-db/db/ref/am_misc/error.html
// http://www.oracle.com/technology/documentation/berkeley-db/db/ref/env/error.html
// http://www.oracle.com/technology/documentation/berkeley-db/db/ref/program/errorret.html
db.set_error_stream(&cerr);
// 在每个错误信息前缀 "AccessExample"
db.set_errpfx("AccessExample");

// 设置数据库页的size为1024字节。数据库页的设置会在较大程度上影响数据库的性能,
// 这里有关于页设置的说明:
// http://www.oracle.com/technology/documentation/berkeley-db/db/ref/am_conf/pagesize.html
db.set_pagesize(1024);        /* Page size: 1K. */

// 设置cache的大小,数据库的页调入内存后,就放在cache当中,也就是说,cache
// 就是存放调入内存的数据库页面的,如果整个数据库中每一个页面都可以调入
// 内存长期存放,那么数据库的速度自然很快,所以,cache确实是越大越好的,
// 当它大于所有数据库页面所占空间后,对性能就没什么影响了。
//
// 设置cache size的指导 :
// http://www.oracle.com/technology/documentation/berkeley-db/db/api_cxx/frame.html
//
// 如果cache填满了,数据库会淘汰不用的页面,若这样的页面有改动,会写回
// 数据库文件。腾出空间后再调入目标页面。这涉及很多磁盘操作,所以数据库
// 操作会突然变慢很多,应用程序的性能就会偶尔
// 发生短暂地下降。为了避免这种性能抖动,你可以在一个单独运行的线程当中,
// 定期淘汰页面腾出cache的空间,保证cache总是有一定的空间可用,
// 这叫做memory trickle。
//
// 方法见此链接:
// http://www.oracle.com/technology/documentation/berkeley-db/db/api_cxx/frame.html
db.set_cachesize(0, 32 * 1024, 0);

// 打开数据库。这个函数的文档 :
// http://www.oracle.com/technology/documentation/berkeley-db/db/api_cxx/frame.html
//
// 这里创建的是一个btree数据库。Berkeley DB支持四种Access Method,也就是
// 数据库文件内部组织数据的方式,包括btree, hash, queue和record number,
// 分别使用DB_BTREE, DB_HASH, DB_QUEUE和DB_RECNO来指定。
//
// 每一种access method都有自己的优点和缺点,适用于某种需求。所以你应该根据自己
// 的应用程序的数据存储需求,数据访问方式来决定使用哪种access method.
// 关于如何选择合适的access method:
// http://www.oracle.com/technology/documentation/berkeley-db/db/ref/am_conf/select.html
db.open(NULL, fileName, NULL, DB_BTREE, DB_CREATE, 0664);

//
// Insert records into the database, where the key is the user
// input and the data is the user input in reverse order.
//
char buf[1024], rbuf[1024];
char *p, *t;
int ret;
u_int32_t len;

// 循环获取用户输入的字符串str, 把str逆序得到str1, 然后存储(str, str1)
// 作为一个key/data pair.
for (;;) {
   cout << "input> ";
   cout.flush();

   cin.getline(buf, sizeof(buf));
   if (cin.eof())
       break;

   if ((len = (u_int32_t)strlen(buf)) <= 0)
       continue;
   for (t = rbuf, p = buf + (len - 1); p >= buf;)
       *t++ = *p--;
   *t++ = '/0';

   // Dbt类用于存储用户的一个数据项的信息。一个key/data pair是一次存储
   // 操作的单位,相当于关系数据库
   // 的一个行(row),key是这行的主键,data是其他各个字段的集合。
   //
   // Berkeley DB不对data做细分和理解,
   // 应用程序自然知道自己存储的数据的结构和意义。
   //
   // key/data pair中的key和data都是一个数据项,它们各需要一个Dbt对象来描述。
   // 由于Berkeley DB存储的是字节串,
   // 它不理会数据的更多意义,比如类型等,所关心数据项信息只包括:
   // 字节串起始地址,长度,内存管理的flags(约定了在读和写一个key/data pair
   // 时,由谁分配和释放Dbt::data所指向的内存),
   // 以及用于做分块读取的字段,这个后面再讲。
   //
   // 这里我们创建的两个对象key和data分别代表要存储的一个key/data pair
   // 的key和data。我们把字节串的起始地址和长度传给了它们,Berkeley DB即
   // 可得到这两个字节串。
   Dbt key(buf, len + 1);
   Dbt data(rbuf, len + 1);

   // 存储这个key/data pair。DB_NOOVERWRITE 表示如果已经有了这个key,
   // 那么不要覆盖那个key/data pair,
   // 而是返回错误。Db::put的第四个参数允许你设置若干种flag,来控制插入
   // 一个key/data pair的行为。
   //
   // Db::put的文档:
   // http://www.oracle.com/technology/documentation/berkeley-db/db/api_cxx/frame.html
   ret = db.put(0, &key, &data, DB_NOOVERWRITE);

   // 如上所述,由于不覆盖已有的相同key的key/data pair,如果这样的key
   // 真的存在,Db::put就会返回DB_KEYEXIST。
   if (ret == DB_KEYEXIST) {
       cout << "Key " << buf << " already exists./n";
   }
}
cout << "/n";

// We put a try block around this section of code
// to ensure that our database is properly closed
// in the event of an error.
//
try {
   // Acquire a cursor for the table.
   Dbc *dbcp;
   // 创建一个游标来遍历数据库。游标的作用与ODBC/JDBC等的游标的意义相同,
   // 它指向一个key/data pair,可以
   // 更改、读取、删除它所指向的key/data pair,同时具有游标稳定性--
   // 它所指向的key/data pair不会被其他游标修改或者删除。
   db.cursor(NULL, &dbcp, 0);

   // Walk through the table, printing the key/data pairs.
   // 此处我们是要使用游标dbcp遍历整个数据库,所以我们不需要指定key的值,
   // key和data都是用来存储返回结果的。
   // 并且,在这种默认情况下,用于保存返回结果的内存有Berkeley DB
   // 负责分配和释放。
   // 你也可以通过指定其他的flag,并且自己分配并且/或者自己释放存储着
   // 结果字节串的内存。
   //
   // 类Dbt的文档:
   // http://www.oracle.com/technology/documentation/berkeley-db/db/api_cxx/frame.html
   // 里面有所有的flags,以及几种Dbt的内存管理方式。
   Dbt key;
   Dbt data;

   // 循环获取下一条key/data pair。当没有更多的key/data pair时候,
   // Dbc::get会返回非0值。一个游标dbcp在创建之初,
   // 并不指向任何一条key/data pair,而第一次调用Dbc::get并且传入
   // DB_NEXT flag,就会使得dbcp位于第一个key/data pair。
   // Dbc::get的文档:
   // http://www.oracle.com/technology/documentation/berkeley-db/db/api_cxx/frame.html
   //
   // key/data pair的顺序是由数据库的存取方式定义的。比如对于btree这种
   // 存取方式,key的顺序由它们的大小关系决定。
   // 你可以配置key的比较函数来自定义key的比较方式,见文档:
   // http://www.oracle.com/technology/documentation/berkeley-db/db/api_cxx/frame.html
   while (dbcp->get(&key, &data, DB_NEXT) == 0) {

       // 获取Dbt中的数据,也就是字节串的首地址。由于key和data对象
       // 使用了默认的flags,所以它们所引用的内存由Berkeley Db负责
       // 分配和回收。你也可以使用其他的内存管理方式。
       // 详情Dbt的文档。
       char *key_string = (char *)key.get_data();
       char *data_string = (char *)data.get_data();
       cout << key_string << " : " << data_string << "/n";
   }

   // 关闭游标。 一定别忘了做这个,并且尽早关闭游标。
   // 这是因为游标稳定性导致游标所引用的  
   // 页面被锁定,使用同一个数据库的其他进程或者线程无法访问这些页面。
   dbcp->close();
}
catch (DbException &dbe) {
   cerr << "AccessExample: " << dbe.what() << "/n";
}

// 关闭数据库。
db.close(0);
}