65.9K
CodeProject 正在变化。 阅读更多。
Home

DB2 访问类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (8投票s)

2003年11月28日

12分钟阅读

viewsIcon

85175

downloadIcon

1200

一组以 ADO 风格封装 DB2 CLI API 的类。

目录

概述

这些类封装了复杂的 DB2 CLI API,以便开发人员能够以简单、ADO 风格的方式访问 DB2 数据库。

环境

这些类可以在 Windows 或 AIX 环境中使用。编译器使用预定义的宏来识别它们。对于 MSVC,它是 _MSC_VER;对于 xlC,它是 __xlC__

在当前版本中,我使用 STL(标准模板库)来操作字符串和集合。不幸的是,AIX C++ 编译器(xlC)的当前版本不支持 STL。因此,我使用了 STLport,这是一个免费的、跨平台的 ANSI STL 库。

可以从以下网址下载 STLport:http://www.stlport.org/

使用代码

要在程序中使用 DB2 访问类,您需要在项目中添加 2 个 .cpp 文件和 4 个 .h 文件,并在源文件中包含 sqldb2.h

当使用 VC++ 编译 sqldb2.cppsqldata.cpp 时,您可能会遇到错误:fatal error C1010: unexpected end of file while looking for precompiled header directive。要解决此问题,您应该在 sqldb2.cppsqldata.cpp 中包含预编译头文件(通常是 stdafx.h)。

连接到数据库并执行 SQL 语句

在访问 DB2 之前,应用程序必须首先调用全局函数 CSqlDatabase::Initialize 来初始化 DB2 CLI 环境,并在之后调用 CSqlDatabase::Uninitialize 来释放该环境。

使用 CSqlDatabase 类连接到数据库;使用 CSqlCommand 类执行 SQL 语句。

#include <stdio.h>
#include "sqldb2.h"

void main()
{
    // Step 1: initialize the DB2 CLI environment
    CSqlDatabase::Initialize();

    // Step 2: Establish a connection to database
    CSqlDatabase db;
    db.Connect("mydb", "user_name", "password");

    // Step 3: Execute SQL statement
    CSqlCommand cmd;
    cmd.Create(&db, CSqlCommand::execDirect);
    cmd.SetCommand("CREATE TABLE Employee (Name VARCHAR(20), "
       "Sex CHAR(1), EmployDate DATE, Age INT)");
    cmd.Execute();

    // Step 4: Execute another SQL statement
    cmd.Reset();
    cmd.SetCommand("INSERT INTO Employee VALUES ('John', 'M', "
          "'2000-1-1', 25)");
    cmd.Execute();

    // Step 5: Close database connection
    db.Close();

    // Step 6: Cleanup environment and release resources
    CSqlDatabase::Uninitialize();
}

执行参数化 SQL 语句

如果您需要重复执行一个命令,而只需要更改少数参数,则参数化命令更有效。

    // Assume the database connection has been established
    CSqlCommand cmd;
    cmd.Create(&db);
    cmd.SetCommand("INSERT INTO Employee VALUES (?, ?, ?, ?)");

    // Add parameters to the command
    CSqlParameter param;
    param.CreateParameter(SQL_CHAR, SQL_PARAM_INPUT, 20);
    cmd.Parameters().Add(param);
    param.CreateParameter(SQL_CHAR, SQL_PARAM_INPUT, 1);
    cmd.Parameters().Add(param);
    param.CreateParameter(SQL_TYPE_DATE, SQL_PARAM_INPUT);
    cmd.Parameters().Add(param);
    param.CreateParameter(SQL_INTEGER, SQL_PARAM_INPUT);
    cmd.Parameters().Add(param);

    // Set the value of the parameters and execute the command
    cmd.Parameters()[0] = "Mike";
    cmd.Parameters()[1] = 'M';
    cmd.Parameters()[2] = "2000-1-1";
    cmd.Parameters()[3] = 25;
    cmd.Execute();

    cmd.Parameters()[0] = "Jane";
    cmd.Parameters()[1] = 'F';
    cmd.Parameters()[2] = "1999-10-20";
    cmd.Parameters()[3] = 22;
    cmd.Execute();

    cmd.Close();

执行查询

CSqlCommand 类提供了一种执行 SQL 语句的方法,但没有提供一种访问该语句可能生成的查询结果集的方法。要执行查询,我们应该使用 CSqlRecordset 类。

    // Assume the database connection has been established
    // Create and initialize a CSqlRecordset object
    CSqlRecordset rs;
    rs.Create(&db);

    // Specify the search condition of the query
    rs.m_strFilter = "Age > ? AND Sex = ?";
    CSqlParameter param;
    param.CreateParameter(SQL_INTEGER, SQL_PARAM_INPUT);
    rs.Parameters().Add(param);
    param.CreateParameter(SQL_CHAR, SQL_PARAM_INPUT, 1);
    rs.Parameters().Add(param);

    // Search condition: Age > 20 AND Sex = 'M'
    rs.Parameters()[0] = 20;
    rs.Parameters()[1] = "M";

    // Specify the return columns in the result set.
    // If we don't specify the return columns, 
    // CSqlRecordset returns all columns as default.
    CSqlField fd;
    fd.CreateField("Name", SQL_C_CHAR, 20);
    rs.Fields().Add(fd);
    fd.CreateField("Age", SQL_C_LONG);
    rs.Fields().Add(fd);

    // Set the size of local row cache, by default the cache size is 1.
    rs.SetCacheSize(10);
    // Perform query
    rs.Open("Employee", CSqlRecordset::sqlCmdTable);
    rs.MoveNext();
    while (!rs.IsEOF())
    {
        printf("Name:%s Age:%d\n", (char*)rs.Fields()[0], 
           (int)rs.Fields()[1]);
        rs.MoveNext();
    }

    // Specify another search condition: Age > 30 AND Sex = 'F'
    rs.Parameters()[0] = 30;
    rs.Parameters()[1] = "F";

    // Perform query again
    rs.Requery();
    rs.MoveNext();
    ...

调用存储过程

    // Assume the database connection has been established
    // Create and initialize a CSqlRecordset object
    CSqlRecordset rs;
    rs.Create(&db);

    // Call AutoBindProcParams() to retrieve parameter 
    // information of the procedure from database
    // and bind these parameters automatically
    rs.AutoBindProcParams("ProcName", "SchemaName");
    // Sepcify the value of input parameters
    rs.Parameters()[0] = 1;
    ...

    // Call the stored procedure
    rs.Open("ProcName", CSqlRecordset::sqlCmdStoreProc, 
       SQL_CURSOR_FORWARD_ONLY);
    printf("Store procedure return code = %d\n", rs.GetRetStatus());

    // Print the result sets if available
    rs.MoveNext();
    while (!rs.IsEOF())
    {
        for (int n=0; n<rs.GetFields(); n++)
        {
            string strVal;
            rs.Fields()[n].ToString(strVal);
            printf("%s ", strVal.data());
        }
        printf("\n");
        rs.MoveNext();
    }
    rs.Close();

访问大型对象数据

有两种方法可以访问大型对象(BLOB、CLOB 或 DBCLOB)数据

  • 通过调用 CSqlField::CreateFileField (或 CSqlParameter::CreateFileParam)将 LOB 字段(或参数)绑定到文件;
  • 将 LOB 字段绑定到定位器,然后使用 CSqlLocator 对象操作该定位器。
    // Assume the database connection has been established
    CSqlRecordset rs;
    rs.Create(&db);

    // There are two LOB columns in the table. 
    // uses file binding for the column lob_col1
    CSqlField fd;
    fd.CreateFileField("lob_col1");
    fd = "lob_col1.txt";      // Specify the file name to be bound
    rs.Fields().Add(fd);

    // Bind the column lob_col2 to a locator
    fd.CreateField("lob_col2", SQL_C_CLOB_LOCATOR);
    rs.Fields().Add(fd);

    rs.Open("lob_table", CSqlRecordset::sqlCmdTable);
    rs.MoveNext();
    while (!rs.IsEOF())
    {
        // Open file lob_col1.txt to get value of lob_col1
        FILE *pFile = fopen("lob_col1.txt", "r");
        ...

        // Use CSqlLocator to get the value of lob_col2
        CSqlLocator loc;
        loc.Open(&db, rs.Fields()[1]);  // Associate the 
                  // locator to CSqlLocator object
        int nSize = loc.GetLength();    // Get the length of the LOB data
        char buf[100];
        loc.GetSubString(buf, 40, 80);  // Retrieve a substring 
                  // from the LOB data
        loc.Free();                     // Free the locator

        rs.MoveNext();
    }
    rs.Close();

处理错误或异常

对于每个继承自 CSqlObject CSqlDatabase/CSqlCommand/CSqlRecordset 对象,应用程序都可以调用其成员函数 GetLastError 来获取数据库操作的错误信息。

void HandleError(CSqlObject* pSqlObject)
{
    CSqlErrorInfo e;
    while (pSqlObject->GetLastError(e))
    {
        printf("SQL error message: %s\n", e.GetErrorText());
    }
}

另一种方法是覆盖 CSqlObject::OnSqlError 函数,以便应用程序在错误发生后立即处理它。

CSqlObject

CSqlObject CSqlDatabaseCSqlCommandCSqlRecordset 的基类。它封装了一个 DB2 CLI 句柄(可以是连接句柄或语句句柄),并管理在该句柄上发生的错误消息。

CSqlObject();
创建一个 CSqlObject 对象。
SQLHANDLE GetHandle() const;
返回 DB2 CLI 句柄。
BOOL IsOpen() const;
如果 DB2 CLI 句柄已创建,则返回 TRUE,否则返回 FALSE
CSqlDatabase* GetDatabase() const;
返回该对象运行的数据库连接的指针。
virtual void Close();
关闭句柄并释放资源。
BOOL SqlCheck(SQLRETURN nSqlRet);
检查 DB2 CLI API 调用的返回代码。如果调用成功,则返回 TRUE,否则返回 FALSE
BOOL SetAttribute(SQLINTEGER nAttr, SQLPOINTER pValue, 
    SQLINTEGER nValueSize=0);
设置句柄的属性。
参数
nAttr
指定属性的标识符。
pValue
指向属性值的指针。
nValueSize
如果 pValue 指向字符字符串或二进制缓冲区,则此参数应为值的长度。
BOOL GetAttribute(SQLINTEGER nAttr, SQLPOINTER pValue, 
    SQLINTEGER nBuffSize=0, SQLINTEGER* pnValueSize=NULL);
获取句柄属性的当前设置。
参数
nAttr
指定属性的标识符。
pValue
指向缓冲区以检索属性值的指针。
nBuffSize
指定缓冲区的长度。
pnValueSize
指向整数以检索属性值长度的指针。
BOOL GetLastError(CSqlErrorInfo& rErrorInfo);
获取此对象上发生的最后错误。

CSqlDatabase

CSqlDatabase 继承自 CSqlObject 类。

CSqlDatabase();
创建一个 CSqlDatabase 对象。
static BOOL Initialize(BOOL bMultiThread=TRUE);
初始化 DB2 CLI 环境。
参数
bMultiThread
指定应用程序是否在多线程环境中使用 DB2 CLI。
static void Uninitialize();
释放 DB2 CLI 环境。
请参阅示例
static BOOL SetEnvAttr(SQLINTEGER nAttr, SQLINTEGER nValue);
设置 DB2 CLI 环境的属性。
参数
nAttr
指定属性的标识符。
nValue
指定属性的值。
static BOOL GetEnvAttr(SQLINTEGER nAttr, SQLINTEGER& nValue);
获取 DB2 CLI 环境属性的值。
参数
nAttr
指定属性的标识符。
nValue
用于检索值的整数变量。
BOOL Connect(PCSTR pszDB, PCSTR pszUser, PCSTR pszPwd, 
  DWORD dwOption=defaultOption, DWORD dwTxnIsolation=SQL_TXN_READ_COMMITTED);
建立到数据库的连接。
参数
pszDB
指定数据库名称。
pszUser
指定用户名。
pszPwd
指定用户的密码。
dwOption
指定数据库连接的属性。它可以是 0 或以下值的组合
readOnly
指定连接是只读的。默认情况下它是读写的。
manualCommit
应用程序必须通过 CommitTrans() 或 RollbackTrans() 调用手动显式提交或回滚事务。默认情况下,DB2 会自动隐式提交每个语句。
autoUnlock
指定在关闭游标时释放读锁。默认情况下,读锁不会自动释放。
dwTxnIsolation
设置事务隔离级别。
请参阅示例
void Close();
关闭当前数据库连接。
BOOL BeginTrans();
开始新事务
BOOL CommitTrans();
保存所有更改并结束当前事务。
BOOL RollbackTrans();
取消当前事务中所做的任何更改并结束事务。

CSqlCommand

CSqlCommand 继承自 CSqlObject 类。

CSqlCommand();
创建一个 CSqlCommand 对象。
BOOL Create(CSqlDatabase* pDB, DWORD dwOption=defaultOption);
初始化 CSqlCommand 对象。
参数
pDB
指向此命令运行的数据库连接的指针。
dwOption
指定命令选项。它可以是 0 或以下值的组合
execDirect
直接执行命令,无需准备。
autoCloseCursor
如果打开第二个游标,则自动关闭已打开的游标。
nonScanEscape
禁用对 SQL 字符串的转义子句扫描。请勿使用此选项调用存储过程。
preFetch
指示服务器在发送当前数据块后立即预取下一个数据块。
请参阅示例
virtual void Close;
关闭命令对象。
virtual void Reset();
取消绑定所有参数,并重置命令句柄以供重用。
BOOL SetCommand(PCSTR lpszSQL);
指定要执行的 SQL 语句,如果未指定 execDirect 选项,则准备该语句。
参数
lpszSQL
SQL 语句的字符串指针。
BOOL Execute();
执行当前 SQL 语句。
SQLINTEGER GetRowCount();
返回受 SQL 语句影响的行数。
CSqlParameters& Parameters();
返回此命令上绑定的参数集合。

CSqlRecordset

CSqlRecordset 继承自 CSqlCommand 类。

CSqlRecordset();
创建一个 CSqlRecordset 对象。
string m_strFilter;
在调用 Open() 并将 nCmdType 设置为 sqlCmdTable 之前,指定查询的搜索条件。
string m_strSort;
在调用 Open() 并将 nCmdType 设置为 sqlCmdTable 之前,指定结果集的排序顺序。
BOOL Open(PCSTR pszCommand, int nCmdType, 
   DWORD nCursorType=SQL_CURSOR_STATIC, BOOL bUseBookmarks=FALSE);
通过执行查询、存储过程或通用 SQL 语句来打开记录集。
参数
pszCommand
要执行的命令的字符串指针。
nCmdType
指定应如何解释命令参数 nCmdType
sqlCmdSQL
将命令评估为 SQL 语句。
sqlCmdTable
将命令评估为表名。
sqlCmdStoreProc
将命令评估为存储过程的名称。
nCursorType
指定游标类型。它可以是以下值之一
SQL_CURSOR_FORWARD_ONLY
SQL_CURSOR_STATIC (默认)
SQL_CURSOR_KEYSET_DRIVEN
bUseBookmarks
指定是否启用书签。如果 nCursorType 设置为 SQL_CURSOR_FORWARD_ONLY,则忽略此参数。
请参阅示例
BOOL Requery();
重新执行当前命令以刷新整个结果集。
BOOL NextRecordset();
清除当前记录集,并通过遍历一系列命令返回下一个记录集。
virtual void Close();
关闭对象并释放任何资源。
virtual void Reset();
取消绑定所有列和参数,并重置命令句柄以供重用。
BOOL AutoBindProcParams(PCSTR pszProcName, PCSTR pszSchemaName=NULL);
检索与过程关联的所有参数,并将它们添加到参数列表中。此函数必须在 Open(). 之前调用。
参数
pszProcName
指定存储过程的名称。
pszSchemaName
指定过程所属的模式的名称。
BOOL IsScrollable() const;
BOOL IsUpdatable() const;
如果当前游标是可滚动的、可更新的,则返回 TRUE
BOOL SetMaxRows(DWORD nMaxRows=0);
指定从查询返回的最大行数。默认值为 0 表示返回所有行。必须在 Open(). 之前调用。
void SetCacheSize(int nCacheSize=0);
指定本地行缓存的行数。合理的缓存大小可以减少网络流量并提高性能。默认值为 1。必须在 Open(). 之前调用。
int GetCacheSize() const;
返回本地行缓存的大小。
int GetRetStatus() const;
返回此存储过程的返回代码(由 RETURN 语句指定)。
SQLUSMALLINT GetRowStatus() const;
返回当前行的状态。它可以是以下值之一
SQL_ROW_SUCCESS
已成功获取行。
SQL_ROW_ERROR
获取行时发生错误。
SQL_ROW_NOROW
行集重叠了结果集的末尾,未返回任何行。
SQL_ROW_DELETED
自上次从此结果集中获取以来,该行已被删除(通过调用Delete())。
SQL_ROW_UPDATED
自上次从此结果集中获取以来,该行已被更新(通过调用 Update())。
BOOL IsDeleted() const;
如果当前行标记为要删除,则返回 TRUE。此函数仅对可更新的游标有效。
BOOL IsBOF() const;
BOOL IsEOF() const;
如果当前游标定位在第一行之前或最后一行之后,则返回 TRUE
BOOL Move(SQLINTEGER nOffset, BOOL bRefresh=FALSE);
将游标位置从当前行移动到第 nOffset 行。此函数仅对可滚动的游标有效。
参数
nOffset
指定与当前行的偏移量。
bRefresh
指定是否通过从服务器获取数据来刷新本地行缓存。
BOOL MoveFirst();
BOOL MoveLast();
BOOL MovePrevious();
BOOL MoveNext();
移动到第一行、最后一行、上一行、下一行。除了 MoveNext 之外,这些函数仅对可滚动的游标有效。
请参阅示例
BOOL Refresh();
刷新当前行的状态。此函数仅对可滚动的游标有效。
BOOL GetBookmark(SqlBookmark& rBookmark);
获取当前游标位置的书签。此函数仅对可滚动的游标有效。
参数
rBookmark
用于检索书签的引用。
BOOL SetBookmark(const SqlBookmark& rBookmark);
移动到书签指定的下一个位置。此函数仅对可滚动的游标有效。
参数
rBookmark
之前从 GetBookmark() 获取的书签。
BOOL Update();
BOOL Delete();
更新或删除当前行。这些函数仅对可更新的游标有效。
int GetFields() const;
CSqlFields& Fields();
返回此记录集中绑定的字段(列)的集合。

CSqlVariant 类

CSqlVariant CSqlParameter CSqlField 的基类。它表示一个具有标准 SQL 数据类型的变体。

CSqlVariant();
CSqlVariant(const CSqlVariant& rVar);
创建一个 CSqlVariant 对象。
SQLSMALLINT GetSqlType() const;
SQLSMALLINT GetDataType() const;
SQLUINTEGER GetPrecision() const;
SQLSMALLINT GetScale() const;
返回变体的 SQL 数据类型、C 数据类型、精度、标度。
SQLINTEGER GetMaxSize() const;
返回变体缓冲区的最大长度(以字节为单位)。
SQLINTEGER GetDisplaySize() const;
返回以字符形式显示变体数据所需的长度(以字节为单位)。
SQLINTEGER GetLength() const;
返回变体值的长度(以字节为单位)。(如果变体是定位器,则返回定位器的长度,而不是 LOB 数据)。返回值可能是 SQL_NULL_DATA,表示值为 null。
BOOL IsNull() const;
如果值是 null,则返回 TRUE
void SetNull();
将值设置为 null。
BOOL SetValue(const void* pValue, SQLINTEGER nValueSize=0);
在没有任何转换的情况下设置变体的值。
参数
pValue
指向值缓冲区的指针。
nValueSize
指定值(如果是字符串或 LOB)的长度。
BOOL FromInteger(int nValue);
BOOL FromDouble(double dValue);
BOOL FromString(const char* pszValue);
通过从 intdoublechar* 参数转换来设置值。
int ToString(std::string& strValue);
将值转换为字符串。
operator short() const;
operator int() const;
operator double() const;
operator const char*() const;
将值转换为 shortintdoublechar* 数据类型。
SQLCHAR* GetBuffer() const;
const DATE_STRUCT* GetDate() const;
const TIME_STRUCT* GetTime() const;
const TIMESTAMP_STRUCT* GetTimeStamp() const;
返回指向值并转换为不同数据类型的指针。

CSqlParameter 类

CSqlParameter 继承自 CSqlVariant 类。

CreateParameter();
CreateParameter(const CSqlParameter& rValue);
创建一个 CSqlParameter 对象。
BOOL CreateParameter(SQLSMALLINT nSqlType, SQLSMALLINT nIoType, 
    SQLUINTEGER nPrecision=0, SQLSMALLINT nScale=0);
初始化参数。
参数
nSqlType
指定参数的 SQL 数据类型。
nIoType
指定参数的输入/输出类型。它可以是以下值之一
SQL_PARAM_INPUT
SQL_PARAM_OUTPUT
SQL_PARAM_INPUT_OUTPUT
nPrecision
指定参数的精度。
对于二进制或单字节字符串,它是以字节为单位的最大长度;
对于双字节字符串,它是以双字节为单位的最大长度;
对于 NUMERIC 或 DECIMAL,它是十进制精度;
对于定长数据类型,忽略此参数。
nScale
如果 nSqlType SQL_NUMERIC, SQL_ECIMALSQL_TIMESTAMP,则指定标度。
请参阅示例
BOOL CreateFileParam(SQLSMALLINT nSqlType);
初始化文件绑定参数。此参数的值是文件名,而不是 LOB 数据。
参数
nSqlType
指定参数的 SQL 数据类型。它只能是 SQL_BLOBSQL_CLOBSQL_DBCLOB
CSqlParameter& operator=(const CSqlParameter& rValue);
CSqlParameter& operator=(int nValue);
CSqlParameter& operator=(double nValue);
CSqlParameter& operator=(const char* pszValue);
设置参数的值。

CSqlField

CSqlField 继承自 CSqlVariant 类。

CSqlField();
CSqlField(const CSqlField& rData);
创建一个 CSqlField 对象。
BOOL CreateField(PCSTR pszFieldName, SQLSMALLINT nDataType, 
    SQLINTEGER nDataSize=0);
初始化字段(列)对象。
参数
pszFieldName
指定字段的名称。它可以是表中的列名,也可以是 SQL 表达式。
nDataType
指定字段的 C 数据类型标识符。
nDataSize
指定字段的最大长度(以字节为单位)。如果 nDataType 指定了定长数据类型,则忽略此参数。
请参阅示例
BOOL CreateField(CSqlCommand* pStmt, int nOrdinal);
根据记录集中指定列的属性初始化字段。
参数
pStmt
指向包含指定列的记录集对象的指针。
nOrdinal
标识列的序号。
BOOL CreateFileField(PCSTR pszFieldName, 
   SQLUINTEGER nFileOption=SQL_FILE_OVERWRITE);
初始化文件绑定字段。此字段的值是文件名,而不是 LOB 数据。
参数
pszFieldName
指定字段的名称。它通常是表中的 LOB 列的名称。
nFileOption
指定文件访问选项。它可以是以下值之一
SQL_FILE_CREATE
创建一个新文件。如果同名文件已存在,则返回 SQL_ERROR
SQL_FILE_OVERWRITE
如果文件已存在,则覆盖它。否则,创建一个新文件。
SQL_FILE_APPEND
如果文件已存在,则将数据追加到其中。否则,创建一个新文件。
请参阅示例
CSqlField& operator=(const CSqlField& rValue);
CSqlField& operator=(int nValue);
CSqlField& operator=(double nValue);
CSqlField& operator=(const char* pszValue);
设置字段的值。

CSqlLocator

CSqlLocator 没有基类。

CSqlLocator();
创建一个 CSqlLocator 对象。
BOOL Open(CSqlDatabase* pDB, const CSqlVariant& rVariant);
初始化 CSqlLocator 对象。
参数
pDB
指定一个数据库连接来操作定位器。
rVariant
指定一个代表定位器的变体(通常是字段)对象。
SQLINTEGER GetLength();
返回由定位器表示的 LOB 数据的总长度。
SQLINTEGER GetSubString(SQLPOINTER pBuff, SQLINTEGER nStartPos, 
    SQLINTEGER nLength);
从 LOB 数据中获取一部分。
参数
pBuff
指定一个缓冲区以检索返回的字符串。
nStartPos
指定要返回的第一个字节的位置。
nLength
指定要返回的字符串的长度。
BOOL Free();
释放定位器对象。
请参阅示例

CSqlErrorInfo

CSqlErrorInfo 没有基类。

CSqlErrorInfo();
CSqlErrorInfo(const CSqlErrorInfo& rEI);
创建一个 CSqlErrorInfo 对象。
const char* GetErrorText();
返回错误对象的文本。
请参阅示例
© . All rights reserved.