S60_DBMS_APIs

来源:互联网 发布:猎鹰9号 知乎 编辑:程序博客网 时间:2024/04/30 16:45

S60_DBMS_APIs

symbian 2010-06-09 09:45:46 阅读88 评论0  字号: 订阅

1. Introduction
2. DBMS structure and elements
        2.1 Permanent file stores and streams
        2.2 Create a database
        2.3 Define tables within a database
                2.3.1 Columns
                2.3.2 Column sets
                2.3.3 Index keys
        2.4 Query schema information
        2.5 Open and close a database
        2.6 Create data
                2.6.1 Long column data in a database
3. Using RowSets and cursors
        3.1 RowSet definition
                3.1.1 RowSet base
                3.1.2 Table RowSets
                3.1.3 SQL views
        3.2 Cursors
                3.2.1 Cursor states
        3.3 Retrieving data by index
4. Using SQL with DBMS
        4.1 SQL and the native C++ API
        4.2 Breakdown of a Select statement
        4.3 Supported SQL subset
        4.4 Using TDbQuery class
        4.5 SQL schema and data updates
                4.5.1 Database Definition Language (DDL)
                4.5.2 SQL data update statements (DML)
                4.5.3 Examples
5. Incremental database operations
        5.1 Overview
        5.2 Database incremental operations — RDbIncremental
        5.3 DML statement incremental execution — RDbUpdate
                5.3.1 DML synchronous
                5.3.2 DML asynchronous
6. Sharing databases
        6.1 DBMS server session
        6.2 Database change notifier
        6.3 Transactions and locks

1.介绍
这篇文档介绍了Symbian OS中怎样有效使用关系数据库API,即DBMS API。
Symbian OS提供了创建和维护数据库的功能,并实现了通过本地和SQL可靠安全访问数据库的方法。SQL调用支持事务和回滚机制,这就保证了数据全写或全部未写。

2.数据库结构和元素
Symbian OS DBMS提供了一个关系数据库的功能接口。所有Symbian OSDBMS都具有一个元素层次:最顶端的是包含数据库的文件存储,它包含表。表是由列定义的,由行组成的。列是由数据类型和长度定义的,DBMS支持一个丰富的数据类型。

开发者过去使用的非DBMS数据库环境,比如ISAM,行可以被认为是记录,列类似域。但是,需要指出的是,在DBMS中,行和列都具有除了记录和域之外的其他属性。

Symbian OS DBMS是一个功能强大的,轻量级的实现,它支持使用SQL,DDL(Data Definition Language),DML(Data Modeling Language)进行常用的增加、查询、获取、更新和删除操作。尽管是轻量级,Symbian OS DBMS给手持设备使用者提供了适用的功能,数据库大小也仅仅局限于可用资源。

2.1永久性文件存储和流
Symbian OS DBMS依赖文件服务器,永久文件存储和流的实现。在Symbian OS中,文件存储和流是非常相近的。

通常理解,文件存储字面意义上讲,就是磁盘上的文件,一个文件在目录列表中拥有一个可见的名字。但是文件的实现决定了它的内容和意图并不能在“视图”中表示。

流是典型的向/从一个存储进行内化/外化的对象,一个存储可以理解成流的集合。

永久文件存储实现了一种典型类型,它当中的流可以被个别改变,而不是使用其他存储类型一次性替换。网络对象包含的应用程序数据被结构化,以便是数据修改时可以加载或写入,RAM经常是用以改变的媒介。

永久文件存储使用类CPermanentFileStore,它继承自CFileStore,这些类在文件存储API中定义。这种存储实现了存储虚框架中大部分功能,最重要的是,永久存储中的流可以:
* 预先被创建
* 重写
* 替换
* 删除

2.2 创建数据库

有两个API可以用来创建数据库:
1. RDbStoreDatabase 提供了专有的创建和打开数据库的接口,这样的数据库是不能共享的,数据库以文件的形式存在,所以它又称为客户端访问。
2. RDbNameDatabase 提供了用名字和格式标识的创建和打开数据库的接口,这个类允许客户端(专有)和服务的共享数据库访问。

不管是哪种数据库,数据库都是建立在文件上的。在创建阶段,两种API都需要以专有模式使用RFs文件服务器会话,如果数据库需要被共享,就必须使用RDbNameDatabase来创建和关闭数据库,最后使用数据库服务器会话类RDbs来打开数据库。

在使用数据库时,首选RDbNameDatabase。

使用RDbStoreDatabase创建数据库
class CBookDb : public CBase
{
    ...
private: // Member data
    RFs iFsSession;
    RDbStoreDatabase iBookDb;
    CFileStore* iFileStore;
    ...
};

TInt CBookDb::CreateDb(const TFileName& aNewBookFile)
{
    Close();
    // Create empty database file.
    TRAPD(error,
        iFileStore = CPermanentFileStore::ReplaceL(iFsSession,
        aNewBookFile, EFileRead|EFileWrite);
    iFileStore->SetTypeL(iFileStore->Layout());// Set file store type
    TStreamId id = iBookDb.CreateL(iFileStore);// Create stream object
    iFileStore->SetRootL(id);// Keep database ID as root of store
    iFileStore->CommitL();// Complete creation by committing
    // Create Book tables and indexes
    CreateBooksTableL();
    CreateBooksIndexL();
);

使用RDbNameDatabase创建数据库
使用RDbNameDatabase创建数据库更简单,因为无需使用流。
...
RFs iFsSession;
RDbNamedDatabase iBookDb;
...

TInt CBookDb::CreateDb(const TFileName& aNewBookFile)
{
    Close();
    TInt error=iBookDb.Replace(iFsSession, aNewBookFile);
    if(error!=KErrNone)
    {
        return error;
    }
    ...// Database is now open. Create tables etc.
}

Replace方法创建一个新数据库或替换现有数据库,它会将数据库文件置空,然后数据库就是打开状态,并为后面的建表做好了准备。注意,通过创建数据库,现在的访问模式是专有的。Replace方法中的TFileName为文件全称(包含路径)。

在Symbian OS DBMS中,数据库名字长度被限制为不超过64个字符。数据库名字跟文件名字相对应,这取决于程序员是否在文件上使用扩展名,下面是几个合法的名字:
_LIT(KDbName,"C://system//apps//DBMS//DBMS.dat");
_LIT(KDbName,"C://system//apps//DBMS//DBMS");

在S60第三版上的合法路径和名字:
Application private path:
_LIT(KDbName,"C://Private//<SID>//DBMS.dat");
_LIT(KDbName,"C://Private//<SID>//DBMS");

Public path (e.g.):
_LIT(KDbName,"C://Data//<Some_path_element>//DBMS.dat");
_LIT(KDbName,"C://Data//<Some_path_element>//DBMS");

2.3 在数据库中创建表

要定义表结构,开发者需要了解三个关键的API概念:列(column),列集(column set)和索引键(index key)。

数据库中的表必须名字唯一,表中的列名也必须唯一,表和列名都是大小写敏感的。

数据库中的表是由一系列“列”定义而成的,每个列都具有名字,类型等属性,如果是文本或二进制,还具有最大长度的属性。

与列对于的类是TDbCol。

列集

描述表的列的集合使用类CDbColSet。
TDbColSetter()可以用来遍历整个列集。

下面的代码片段示例了如何在永久文件存储中建立数据库表结构。
...
const int KTitleMaxLength = 60;
_LIT(KBooksTable, "Books");
_LIT(KBooksAuthorCol, "Author");
_LIT(KBooksTitleCol, "Title");
_LIT(KBooksDescriptionCol, "Description");

...
// Specify columns for Books table
TDbCol authorCol(KBooksAuthorCol, EDbColText); // Default length
TDbCol titleCol(KBooksTitleCol, EDbColText, KTitleMaxLength);
titleCol.iAttributes = TDbCol::ENotNull;
// Stream Data
TDbCol descriptionCol(KBooksDescriptionCol, EDbColLongText);

// Create columnset
CDbColSet* bookColSet = CDbColSet::NewLC();
bookColSet->AddL(authorCol);
bookColSet->AddL(titleCol);
bookColSet->AddL(descriptionCol);

// Create the Books table
User::LeaveIfError(iBookDb.CreateTable(KBooksTable,
*bookColSet));
CleanupStack::PopAndDestroy(bookColSet);
...

首先,使用宏_LIT定义列的名字,然后定义列,并将他们Add到bookColSet中。示例代码中提供了文本域的默认长度和自定义长度,其中默认长度为50。

对于titleCol列,将它的成员iAttributes赋值为ENotNull,这样就强制表中每个row的titleCol都必须赋值。

索引键

索引键需要一个或多个表列,每个键都具有一些属性,例如唯一性,文本列的比较规范,生成键的列。如果没有索引键定义,表中的行的顺序是任意的。

索引键对应的类是CDbKey,键对应的列对应的类是TDbKeyCol。s

下面的代码片段示例了在一个表中建立两个列索引的过程。
...
_LIT(KBooksTable, "Books");
_LIT(KBooksAuthorCol, "Author");
_LIT(KBooksTitleCol, "Title");
_LIT(KBooksIndexName,"BooksIndex");

...
// Create index consisting of two columns
TDbKeyCol authorCol(KBooksAuthorCol);
TDbKeyCol titleCol(KBooksTitleCol);
CDbKey* index = CDbKey::NewLC(); // create index key set
index->AddL(titleCol);
index->AddL(authorCol);

User::LeaveIfError(iBookDb.CreateIndex(
KBooksIndexName, KBooksTable, *index));
CleanupStack::PopAndDestroy(index);

首先,指定用于创建索引的列,然后定义CDbKey对象,并将列Add,最后使用数据库的CreatIndex()来创建索引。

查询模式信息

有API来查询数据库结构,比如索引,表结构等。数据库的基类RDbDatabase提供了一些基本方法。
* TableNamesL() 得到数据库中所有表的名字,返回CDbTableNames。
* IndexNamesL() 得到一个表所有索引的名字,返回CDbIndexNames,需要表名做参数。
* ColSetL() 得的一个表的所有列,返回CDbColSet,需要表名做参数。
* KeyL() 对一个表的索引进行索引定义,需要表名和索引名作为参数。

表的列集CDbColSet可以使用查询语句从表中或SQL视图中获得,列集CDbColSet可以使用TDbColSetIter类作为游标。

下面的示例代码演示了如何获取表的列名和大小,得到的结果放在CDesCArrayFlat中。

...
private: // Member data
RDbStoreDatabase iBookDb;
...
// Items in the array are in format <column_name>: <column_size>
CDesCArrayFlat* CBookDb::ColumnNamesAndSizesL()
{
    RDbTable booksTable;
    TBuf<KDbMaxColName> columnNameAndSize;
    _LIT(KDelimiter, ": ");
    _LIT(KNoSize,"No size");
    
    // Open the Books table.
    User::LeaveIfError(
    booksTable.Open(iBookDb, KBooksTable,booksTable.EReadOnly));
    CleanupClosePushL(booksTable); // Remember to pop and close
    CDesCArrayFlat* resultArray = new (ELeave)CDesC16ArrayFlat(KArrayGranularity);
    CleanupStack::PushL(resultArray);

    // Iterate through the columns of Books table. Extract the
    // column name and column size (size only for text columns).
    CDbColSet* colSet = booksTable.ColSetL();
    CleanupStack::PushL(colSet);
    TDbColSetIter colIter(*colSet);
    while(colIter)
    {
        columnNameAndSize.Zero();
        columnNameAndSize.Append(colIter->iName);
        columnNameAndSize.Append(KDelimiter);
        if(colIter->iType == EDbColText)
            columnNameAndSize.AppendNum(colIter->iMaxLength);
        else
            columnNameAndSize.Append(KNoSize);
        resultArray->AppendL(columnNameAndSize);
        colIter++;
    }
    CleanupStack::PopAndDestroy(colSet);
    CleanupStack::Pop(resultArray);
    // Pop the booksTable from cleanup stack and close it.
    CleanupStack::PopAndDestroy();
    return resultArray;
}

2.5 打开/关闭数据库
在2.2中讨论了,数据库可以以专有的客户端模式打开,或以共享的C/S模式打开。RDbNameDatabase支持这两种模式,RDbStoreDatabase仅支持客户端模式。

推荐使用RDbNameDatabase。客户端访问数据库更有效率,但数据库不能被其他应用程序访问,因为在访问期间,数据库文件已被锁。

Client/Server模式
下面的代码片段演示了以C/S模式打开和关闭数据库,代码实现需要RDbNameDatabase和数据库服务器会话类RDbs。

...
private: // Member data
RDbs iDbSession;
RDbNamedDatabase iBookDb;
...
void CBookDb::OpenDbInClientServerModeL(const TFileName& aExistingBookFile)
{
    User::LeaveIfError(iDbSession.Connect());
    User::LeaveIfError(iBookDb.Open(iDbSession, aExistingBookFile));
}

... // Use the open database

void CBookDb::CloseDb()
{
    iBookDb.Close(); // note the order of closing.
    iDbSession.Close();
}

Client-side模式
下面代码示例了使用RDbNameDatabase以Client-side模式打开和关闭数据库。代码跟前面的C/S模式类似,不用的是使用文件服务器会话RFs,而不是数据库服务器会话RDbs。

...
private: // Member data
RFs iFsSession;
RDbNamedDatabase iBookDb;
...
void CBookDb::OpenDbInClientSideModeL(const TFileName&
aExistingBookFile)
{
    User::LeaveIfError(iFsSession.Connect());
    User::LeaveIfError(iBookDb.Open(iFsSession, aExistingBookFile));
}

... // Use the open database

void CBookDb::CloseDb()
{
    iBookDb.Close(); // note the order of closing.
    iFsSession.Close();
}

如果是RDbStoreDatabase,流和会话必须要正确地处理。
...
private: // Member data
RFs iFsSession;
RDbStoreDatabase iBooksDb;
CFileStore* iFileStore;
...
TInt CBookDb::OpenDb(const TFileName& aExistingBookFile)
{
    Close();
    if(!BaflUtils::FileExists(iFsSession, aExistingBookFile))
    {
        return KErrNotFound;
    }
    TRAPD(error,
        iFileStore = CPermanentFileStore::OpenL(iFsSession,
        aExistingBookFile, EFileRead|EFileWrite);
    
        // Set file store type
        iFileStore->SetTypeL(iFileStore->Layout());
        iBookDb.OpenL(iFileStore,iFileStore->Root()));

    if(error!=KErrNone)
   {
        return error;
    }
    iOpen = ETrue;
    return KErrNone;
}

void CBookDb::Close()
{
    iBookDb.Close();
    if(iFileStore)
    {
        delete iFileStore; // Closes the file too
        iFileStore = NULL;
    }
    iFsSession.Close();
}

2.6 创建数据
一旦打开了数据库,我们就可以维护其中的数据。注意,向数据库增加数据或从数据库获取数据都无需定义索引。

下面的代码演示了如何向表结构增加数据,代码假设数据库已经被打开。
...
_LIT(KBooksTable, "Books");
_LIT(KBooksAuthorCol, "Author");
_LIT(KBooksTitleCol, "Title");
_LIT(KBooksDescriptionCol, "Description");
...

private: // Member data
RDbStoreDatabase iBookDb;
...

void CBookDb::AddBookWithCppApiL(const TDesC& aAuthor,
    const TDesC& aTitle,
    const TDesC& aDescription)
{
    // Create an updateable database table object.
    // Assume the database is open.
    RDbTable table;
    User::LeaveIfError(table.Open(iBookDb,
        KBooksTable, table.EUpdatable));
    CleanupClosePushL(table); // Remember to pop and close

    // Construct CdbColSet. Use it to query column numbers.
    CDbColSet* booksColSet = table.ColSetL();
    CleanupStack::PushL(booksColSet);
    table.Reset();
    table.InsertL(); // Insert empty row
    // Set the author and title for the new row
    table.SetColL(booksColSet->ColNo(KBooksAuthorCol), aAuthor);
    table.SetColL(booksColSet->ColNo(KBooksTitleCol), aTitle);
    // Set the description. Use a stream for the long column
    RDbColWriteStream writeStream;
    writeStream.OpenLC(table, booksColSet->ColNo(KBooksDescriptionCol));
    writeStream.WriteL(aDescription);
    writeStream.Close();
    CleanupStack::Pop(); // writeStream
    CleanupStack::PopAndDestroy(booksColSet);

    table.PutL(); // Complete insertion
    
    CleanupStack::Pop() // table
    table.Close();
}

数据库中的长列(Long Column)
访问数据库中EDbColLongText的列需要使用RDbColReadStream,同样,需要使用RDbColWriteStream可以在RowSet中改变这种列的内容。

Symbian OS在使用流进行读取数据时,只支持一次读取一个列。

下面的代码片段示例了使用流来获取数据库Long Column的内容:
...
_LIT(KBooksTable, "Books");
_LIT(KBooksDescriptionCol, "Description");

...
private: // Member data
RDbStoreDatabase iBookDb;
...
TBuf<128> description; // read result placed here
RDbTable table;
User::LeaveIfError(table.Open(iBookDb, KBooksTable, table.EReadOnly));
table.Reset();

// Find the column number for ‘Description’ column
CDbColSet* colSet = table.ColSetL();
TDbColNo descrColNo = colSet->ColNo(KBooksDescriptionCol);
delete colSet;

table.FirstL(); // Set cursor to first row. Should check
// this succeeds.
table.GetL(); // Get the first row for operation

// Read all characters. Assume there are no more
// than 128 characters.
RDbColReadStream readStream;
readStream.OpenLC(table, descrColNo);
readStream.ReadL(description, table.ColLength(descrColNo));
readStream.Close();
CleanupStack::Pop(); //readStream
CleanupStack::PopAndDestroy(colSet);
table.Close();
...

使用RDbColWriteStream来增加或更新长列数据的示例如下:
RDbTable table;
// Open table in updatable mode
User::LeaveIfError(table.Open(iBookDb, KBooksTable, table.EUpdatable);
CDbColSet* booksColSet = table.ColSetL();
CleanupStack::PushL(booksColSet);

... // Find or create a row here and retrieve it for operation

// Use a stream to read data from the long column
RDbColWriteStream writeStream;
writeStream.OpenLC(table,
booksColSet->ColNo(KBooksDescriptionCol));
writeStream.WriteL(aDescription);
writeStream.Close();
CleanupStack::Pop(); // writeStream
...
CleanupStack::PopAndDestroy(booksColSet);
table.Close();

3. 使用RowSets和游标
Symbian OS DBMS使用RowSet来查找、获取和修改数据库数据。需要注意的是,RowSet中的数据并不是它自身的,而是数据库行的索引,所以它持有的是数据库数据本身

3.1 RowSet定义
相关的API有三个关键概念:
* RowSet基类(RDbRowSet) - 这是一个虚基类,它提供了导航、获取行和更新数据的功能。下面是它的两个具体实现。
* 表RowSet(RDbTable) - 提供了表的全视图。
* SQL视图(RDbView) - 提供了表的一个视图。视图依赖于创建视图的SQL查询。它可能只包含一部分数据和部分列。

3.1.1 RowSet基类
这个基类提供了以下功能:
* 查找和匹配行
* 从数据库获取数据
* 更新或插入行
* 使用游标导航RowSet
* 在RowSet中得到行的模式(schema)
* 提取和设置一行数据中的列

3.1.2 Table RowSets
Table RowSet提供了包含表中所有行和列的一个RowSet。可以使用索引来对RowSet进行排序,这样可以从一个表中进行快速的、基于主键的获取数据。如果不使用索引,行排序是不能做到的。Table RowSets对应的类是RDbTable。

Table RowSet在需要展现表的全部结构或某种排序下需要对所有数据都进行处理时,是很有用的。如果表中的每行都具有单独的、唯一的标识,推荐使用这种RowSet进行快速查找。但是,在大多数情况下,SQL视图更值得推荐。

3.1.3 SQL视图
RDbView提供了基于SQL查询的RowSet,SQL查询被封装在类TDbQuery中。

一个SQL视图是通过对数据库进行SQL查询而得到的,它是依赖于表的。RDbView的Prepare()方法分析SQL并且指定如何评估数据。默认情况下,可以使用游标导航来评估一个视图。

Since evaluation may be time-consuming, a pre-evaluation window TDbWindow may be specified in the prepare phase, allowing the RowSet to be evaluated once and navigated quickly. Full evaluation may take a long time, and may be best done in steps.

For large RowSets, it can be useful to define the balance between memory usage (greatest if the view stores the complete RowSet) and speed (slowest if the RowSet is evaluated for each cursor navigation). This can be achieved by defining a limited (or partial) pre-evaluation window with a preferred size. A partial evaluation window can also be used to get a partial view to SQL result set for local navigation.
The following code constructs a view using an SQL select:
...
private: // Member data
RDbStoreDatabase iBookDb;
...

_LIT(KViewSql,"SELECT Author, Title FROM Books"
RDbView view;
User::LeaveIfError(view.Prepare(iBookDb, TDbQuery(KViewSql),view.EReadOnly));
CleanupClosePushL(view);
User::LeaveIfError(view.EvaluateAll());
for (view.FirstL(); view.AtRow(); view.NextL())
{
    view.GetL(); // Fetch a cached copy of current row

    ... // perform operations for the row
}
CleanupStack::PopAndDestroy(); // This also closes the view

3.2 游标
游标用来导航RowSet中所有的有效行。

3.2.1 游标状态
游标最常见的状态是维护了一个从RowSet中定为一行的值,但是,游标还可能是其他几种状态。

一个游标可能维护“开始”和“结束”的状态。在初始化时,也就是在Reset后,游标是在开始处,这个时候游标处在第一行的前面,结束状态时,游标处在最后一行后面。导航到第一行等同于导航到“开始”的下一行,如果RowSet为空,“开始”的下一行就会是“结束”,从RowSet的结尾进行导航是类似的。

当更新或插入一行时,游标具有一个状态会保护导航,直到更新或插入操作结束。

当删除当前行时,游标并没有改变,它会维护着因为删除操作而产生的当前无效的值,如果使用GetL()方法试图访问这个被删的值会导致Leave。导航到下一个、前一个、或一个指定的行会移动游标到需要的行,如果行不够,就会到开始/结束。

在导航出现错误时,比如存储或文件系统错误,或多个RowSet并发更新一个表,会导致游标无效。导航到一个固定的位置(而不是next/previous)会restore游标。

3.3 通过索引获取数据
在使用SQL视图或RDbTable得到Rowset后,可以使用索引来快捷的获取数据。

RDbTable提供了一个简单的SeekL()方法来快速查找一行,它使用TDbSeekKey,并且只能找到第一个匹配行,这在使用唯一的索引键进行快速查找是很有好处的。如果需要进行复杂的查找操作,RDbView和基类RDbRowSet提供了足够的方法。

下面的代码片段使用RDbTable和索引来查找匹配的第一行:
...
_LIT(KBooksTable, "Books");
_LIT(KBooksIndexName,"BooksIndex");
...

private: // Member data
RDbStoreDatabase iBookDb;
...

void CBookDb::GetABookFastL(const TDesC& aTitle, TDes& aResult)
{
    RDbTable rowset;
    RDbTable table;
    TDbSeekKey seekKey(aTitle); // Initialize one-column seek key
   
    // Open the ‘Books’ table. Specify what index is used.
    User::LeaveIfError(table.Open(iBookDb, KBooksTable, rowset.EReadOnly));
    CleanupClosePushL(table); // Remember to pop and close
    User::LeaveIfError(table.SetIndex(KBooksIndexName));
    if( rowset.SeekL(seekKey) ) // SeekL requires index is set
    {
        // Cursor is set to the found row. Do something with it.
        rowset.GetL();
        ...
    }
    else
    {
        // Result not found
    }
    ...
    // Pop the table from cleanup stack and close it.
    CleanupStack::PopAndDestroy();
}

索引数据类型必须与目标类型匹配,比如TInt对应ColIntnn。TDbSeekKey在创建时使用了索引值aTitle,在示例中,我们在操作前已知索引,如果我们并不知道表的索引,需要使用下面方法获取索引:
CDbNames* idxs = iBookDb.IndexNamesL(KBooksTable);
在Seek前,为表设置索引是非常重要的。最后,SeekL()尝试去匹配该值,如果匹配成功,游标会指向该行,然后调用GetL()便会获取匹配的数据。

4. 使用SQL
4.1 SQL和本地C++ API
Symbian OS DBMS 支持通过本地C++ API操作,也支持SQL语句回调操作。在早期版本,对SQL的支持仅限于Select语句,对数据库的操作主要依靠本地C++ API。现在,已支持更多的SQL,从开发者角度讲,大多数操作都可以使用这两种方法来完成。

一般来讲,使用SQL更简单,而且执行更快。但有一些限制,像长事务、compaction、恢复和更新二进制列则需要使用C++ API。另外,在拥有大量数据时,使用SQL插入数据也比使用C++ API要慢。

需要注意,从一开始DBMS引擎就没有支持一个完整的SQL实现,它是为资源有限的手持设备设计的,所以实现基于数据库的SQL的能力,尽管这样,这种实现对应手持设备的日常需求也是足够的了。

在Symbian OS DBMS中,SQL语句清楚地传递给SQL引擎,例如C++ 会将SQL语句以描述符或字符串的形式传递过去。

4.2 选择查询语句
作为经常使用的语句,这里主要介绍查询语句。为了清楚起见,这里主要介绍SQL语法,而不涉及到Symbian OS DBMS的成员函数。

选择语句会得到一个RowSet,里面盛放的是表中的实际数据。选择语句的格式如下:
SELECT select-list FROM table-name [ WHERE search-condition ] [ ORDER BY sort-order ]

select-list有两种形式:指定“*”获取表中所有列,此时获取的RowSet是无序的;指定一个使用逗号分隔的需要获取的列,此时获取的RowSet是可以排序的。

table-name指定了表名,这个表是必须在数据库中存在的。

search-condition指定了获取的行必须满足的条件,这个条件的结果应该是一个布尔值,例如,a=1 OR NOT b=2 AND c=3

Note: In SQL, the equals (=) character is not an assignment

RDbRowSet::FindL()和RDbRowConstraint::Open()也使用search-condition,来指定通过RDbRowSet::FindL()或RDbRowSet::MatchL()匹配的RowSet中拥有哪些行。

断言用来创建查询条件,有三种类型:比较(comparison),like和null。

比较断言用来比较一列的值与一个描述符值是否相等。列的比较是依赖类型的,所以用于比较的描述符必须与比较列的类型相同,数字、文本、时间等都必须类型匹配,另外,二进制是不能进行比较的。

like断言用于判断一个文本列是否匹配给定的字符串格式。可以查看TDesC::Match()方法。

null断言用于判断列值是否为NULL。它适用于所有类型。

sort-order使用ORDER BY来指定。如果没有指定,得到的RowSet是没有顺序的。指定时得到的RowSet可以以升序(默认)或降序排序。

下面是一个选择查询语句示例:
_LIT(KSQLStatement,"SELECT Author, Title FROM Books WHERE Author = ’Brown, Dale’ ORDER BY Author, Title");
...
User::LeaveIfError(view.Prepare(iBookDb, TDbQuery(KSQLStatement,EDbCompareNormal)));

4.3 SQL支持的子集
下面列举了Symbian OS DBMS的SQL子集实现:

S60_DBMS_APIs - 黑黑的大鲨鱼 - 黑黑的大鲨鱼

4.4 使用TDbQuery
这个类的目的是使包含SQL语句的字符串可以用来构造TDbQuery,并且在构造时默认为文本比较。示例如下:
_LIT(KSQLStatement ="SELECT Author, Title FROM Books WHERE Author LIKE 'BR*'");
RDbView dbview;
view.Prepare(iBookDb, KSQLStatement);

view.Prepare()等同于:
view.Prepare(iBookDb, TDbQuery(KSQLStatement, EDbCompareNormal));

如果要指定另一种比较类型,必须在构造时显式指定,例如:
view.Prepare(iBookDb, TDbQuery(KSQLStatement, EDbCompareFolded));

4.5 SQL Schema 和数据更新
SQL Schema和数据的修改可以直接通过SQL命令来完成,像使用RDbDatabase和RDbIncremental的接口一样。但SQL语句的功能可能并没有DBMS API那么强大。

4.5.1 Database Definition Language(DDL)
DDL提供了改变数据库的功能,例如创建/删除表和索引,像改变表中数据那样。DDL语句不能修改数据库中的数据。但是一旦表被Drop了,表中所有数据都会丢失。Drop一个索引只是简单的移除了通过索引访问该表的功能。

4.5.1.1 Create table statement
CREATE TABLE table-name (column-definition,…)

4.5.1.2 Drop table statement
DROP TABLE table-name

4.5.1.3 Alter table statement
ALTER TABLE table-name { ADD add-column-set [ DROP drop-column-set ] | DROP drop-column-set }

4.5.1.4 Create index statement
CREATE [ UNIQUE ] INDEX index-name ON table-name ( sort-specification,… )

4.5.1.5 Drop index statement
DROP INDEX index-name FROM table-name

4.5.2 SQL data update statements (DML)
DML is used to alter functionality in the database at the column level, and the usage is confined to row insertion, deletion, and update.

DML statements can modify the content of existing data in the database.

4.5.2.1 Insert statement
INSERT INTO table-name [ ( column-identifier,… ) ] VALUES ( column-value,… )

4.5.2.2 Delete statement
DELETE FROM table-name [ WHERE search-condition ]

4.5.2.3 Update statement
UPDATE table-name SET update-column,… [ WHERE search-condition ]

4.5.3 示例
这个示例演示了使用RDbDatabase::Execute()方法执行DDL和DML语句,如果操作耗时时间太长,建议使用增量方法(见后面的“增量数据库操作”)。

private: // Member data
RDbStoreDatabase iBookDb; // Could be RDbNamedDatabase
...

TInt CBookDb::AddDateColumn()
{
    _LIT(KSqlAddDate, "ALTER TABLE Books ADD PublishDate DATE");
    return iBookDb.Execute(KSqlAddDate);
}
TInt CBookDb::DropBooksTable()
{
    _LIT(KSqlDropBooks, "DROP TABLE Books");
    return iBookDb.Execute(KSqlDropBooks);
}
TInt CbookDb::DeleteAllBooks()
{
    _LIT(KSqlDeleteAllBooks, "DELETE FROM Books");
    return iBookDb.Execute(KSqlDeleteAllBooks);
}
// Delete books, name of which starts with F-letter
TInt CbookDb::DeleteFBooks()
{
    _LIT(KSqlDeleteFBooks, "DELETE FROM Books where
    Title like ‘F*’");
    return iBookDb.Execute(KSqlDeleteFBooks);
}
TInt CBookDb::UpdateBookTitle(const TDesC& aOldTitleKey,const TDesC& aNewTitle)
{
    _LIT(KSQLUpdateStart, "UPDATE Books SET Title = '");
    _LIT(KSQLUpdateMiddle, "' WHERE Title = '");
    _LIT(KSQLUpdateEnd, "'");

    // SQL: UPDATE Books SET Title = ‘aNewTitle’
    // WHERE Title = ‘aOldTitleKey’
    TBuf<KCustomSqlMaxLength> sqlStr;
    sqlStr.Append(KSQLUpdateStart);
    sqlStr.Append(aNewTitle);
    sqlStr.Append(KSQLUpdateMiddle);
    sqlStr.Append(aOldTitleKey);
    sqlStr.Append(KSQLUpdateEnd);
    return iBookDb.Execute(sqlStr);
}

5. 增量数据库操作
5.1 介绍

一些数据库操作可能会花销较长时间,比如创建新的索引或数据库恢复。有时候这些操作花销的时间还是可以接受的,但一般情况下这些操作都会使设备无响应。

为了解决这个问题,Symbian OS DBMS实现了数据库的增量操作API。它允许耗时较长的任务分步进行,以便使设备可以响应其他事件。

这些API有两个主要概念:DDL(SQL schema update)用来增量进行操作;DML(SQL data update)用来增量进行语句执行。分别对应类RDbIncremental和RDbUpdate。

在增量操作过程中,数据库不能进行其他操作,比如打开表,Prepare View等。增量开始时也要求数据库中没有RowSet和打开的未完成的事务。

如果数据库中没有显式事务已开始,在增量操作时,会开始一个自动事务,并且在操作结束时提交事务,如果操作失败,则回滚。

5.2 Database incremental operations — RDbIncremental
RDbIncremental允许分步执行长时间的DDL语句,比如改变/移除表和索引,数据库压缩或恢复等。

下面的代码片段演示了使用RDbIncremental执行SQL DDL语句。
_LIT(KSqlTxt, "DROP TABLE Books"); // SQL for execution
RDbIncremental incOp; // Create Incremental Object

TInt incStep = 0xFFFF; // Step Variable
TInt incStat = incOp.Execute(iBookDb, KSqlTxt, incStep);

// Initialize Execution
while (incStep>0 && incStat==KErrNone)
{
    incStat = incOp.Next(incStep); // Do the work
}
...
incOp.Close();

5.3 DML statement incremental execution — RDbUpdate
RDbUpdate用于增量执行DML语句。可以同步或异步使用这个类,异步使用时,如果需要可将它包装在一个活动对象中。

5.3.1 DML synchronous
RDbUpdate提供了同步执行增量维护数据的操作。Execute初始化操作,然后一直调用Next()来执行操作。
TInt CBookDb::RemoveBooks(const TDesC& aTitle, TInt& aResultCount)
{
    RDbUpdate updOp;

    // Sql: DELETE FROM Books WHERE Title LIKE 'aTitle'
    TBuf<KCustomSqlMaxLength> sqlStr;
    sqlStr.Append(_L("DELETE FROM "));
    sqlStr.Append(KBooksTable);
    sqlStr.Append(_L(" WHERE "));
    sqlStr.Append(KBooksTitleCol);
    sqlStr.Append(_L(" LIKE '"));
    sqlStr.Append(aTitle);
    sqlStr.Append(_L("'"));

    // Initialize execution and perform the first step.
    // Note: Execute() returns 0 (=KErrNone), but it does not
    // affect database until Next() is called.
    TInt incStat = updOp.Execute(iBookDb, sqlStr, EDbCompareFolded);
    incStat = updOp.Next(); // This will leave, if Execute() failed.
    // Just in case, if the operation has more steps
    while( incStat == 1 )
    {
        incStat = updOp.Next();
    }
    aResultCount = updOp.RowCount();
    updOp.Close();
    return incStat; // KErrNone or system wide error code
}
在示例中,一旦DML操作初始化,任何其他较长时间的操作请求都可能使设备无响应。这也就是为什么使用同步方法时需要慎重。

5.3.2 DML asynchronous

RDbUpdate也提供了执行异步操作的方法。

为了做出好的设计,异步代码应该放在一个活动对象中。RDbUpdate的Next方法带有一个TRequestStatus参数来标识活动对象的状态。Next结束时会执行RunL方法。

下面代码片段示例了异步方法:
TInt CBooksDb::RemoveAllBooks(TInt& aResultCount)
{
    _LIT(KSql, "DELETE FROM Books");
    RDbUpdate updOp;
    TRequestStatus incStat(1);
    TInt updStat = updOp.Execute(iBookDb, KSql, EDbCompareFolded);
    while (updStat==KErrNone && incStat ==1)
    {
        updOp.Next(incStat); // Start async operation. Call returns
        // immediately. Operation will continue in the background.
        // For simplicity wait completion here. If active objects were
        // used, active objects framework would handle completion.
        User::WaitForRequest(incStat);
}
aResultCount = updOp.RowCount();
updOp.Close();
if(updStat!=KErrNone)
return updStat; // System wide error code
else
return incStat.Int(); // KErrNone or system wide error code
}

6. 数据库共享
Symbian OS DBMS提供了允许多个客户端同时访问数据库的能力。事务机制确保了在一个时间只有一个客户端可以改变数据。

这里涉及到两个主要概念:DBMS服务会话 和 数据库改变通知

共享数据库只能通过RDbNameDatabase来访问。

原创粉丝点击