数据库对象
静态非应用程序依赖函数可用于 SmartEDB 运行时控制、数据库控制(打开、连接和关闭数据库)、事务管理和游标导航。但要创建和修改单个数据库对象,C 应用程序使用由 mcocomp 模式编译器生成的强类型对象接口。
模式编译器根据特定的 DDL 类定义生成以下类型的 C 接口函数:
- 用于创建和删除对象的 new 和delete 方法;
- 用于存储和检索对象字段数据的 put 和 get 方法;
- oid 和 autoid 访问方法;
- 用于通过索引查找对象的 find 和 search 方法;
- 用于响应数据库事件的事件方法。
有些函数对整个数据库对象进行操作,而有些则对对象内的字段进行操作。
对于对象操作函数,编译器使用类名后跟
_new()
、_delete()
或_delete_all()
来生成函数名。对于字段操作函数,编译器使用类或结构名,后跟字段名,然后是操作词,所有这些都由下划线分隔。操作词可以是以下任意一个:put、get、at、put_range、get_range、alloc、erase、pack和 size。
new 和 delete
在 C 应用程序中,创建和删除对象是通过调用生成的 _new()
和 _delete()
函数来实现的。
_new()
函数为对象预留初始空间,并返回对新创建的数据对象的引用(句柄)。
这些函数仅针对类生成。因为结构体在数据库中从不单独实例化(总是属于某个类),对于结构体不会生成新函数。
对于未声明oid 类:
MCO_RET classname_new (
/*IN*/ mco_trans_h t,
/*OUT*/ classname *handle
);
对于使用 oid 声明的类,必须传递该 oid
:
MCO_RET classname_new (
/*IN*/ mco_trans_h t,
/*IN*/ databasename_oid * oid,
/*OUT*/ classname *handle
);
_delete()
函数会永久删除传入句柄的对象,而 _delete_all()
函数则会从数据库中删除该类的所有对象。被对象占用的存储页也会返回给存储管理器以供重复使用。
MCO_RET classname_delete (/*IN*/ classname *handle);
MCO_RET classname_delete_all ( /*IN*/ mco_trans_h t );
put 和 get
对于在模式文件中声明的对象的每个字段以及结构的每个元素,都会生成 _put()
和 _get()
函数。_put()
函数用于更新特定字段的值。根据字段的类型,生成的 _put()
函数将采用以下形式之一。 对于标量类型的字段,其形式为:
MCO_RET classname_fieldname_put(
/*IN*/ classname *handle,
/*IN*/ <type> value
);
对于char 和string 字段,需要一个指针和长度参数:
MCO_RET classname_fieldname_put(
/*IN*/ classname *handle,
/*IN*/ const char *value,
/*IN*/ uint2 len
);
理解_put()
操作如何将数据复制到数据库字段和任何相关索引是很重要的。请考虑以下模式:
persistent class MyClass
{
char<10> buf;
tree<buf> idx;
}
下面的代码片段将 abc
放入 char<10>
类型的字段 buf
中:
MyClass cls;
char buf[10] = "abc"
...
MyClass_new(t, &cls);
MyClass_buf_put(&cls, buf, strlen(buf));
SmartEDB 运行时会将指定数量的字符(3 个)复制到字段中,并用 MCO_SPACE_CHAR
填充剩余的 7 个字节。
MCO_SPACE_CHAR
的默认值为 \0
。这会将字段未使用部分的值进行标准化,以便后续的排序操作。运行时不会从输入字符串中复制额外的空终止符。
如果此字段上有索引,则根据该类是瞬态类还是持久类来处理索引节点。对于瞬态类,不会将任何数据复制到索引节点。对于持久类,会将字段的全部内容从字段值复制到索引节点。因此,对于上述示例,字节 [abc\0\0\0\0\0\0\0]
将首先复制到字段 buf
中,然后在事务提交时,从字段 buf
复制到索引节点(因为这是一个持久类)。
请注意,如果将长度为 10 的字符数组复制到字段 buf
中,则该字段中没有空终止符。如果将超过 10 个字符的字符数组用作 _put()
的参数,则仅复制指定数量的字符(显然 <= 10)。
_get()
函数用于将对象的一个字段绑定到应用程序变量,该函数的形式取决于字段的类型,具体如下。
对于标量类型的字段,其形式将为:
MCO_RET classname_fieldname_get(
/*IN*/ classname *handle,
/*OUT*/ <type> *value
);
对于固定长度的字符字段,必须指定其长度:
MCO_RET classname_fieldname_get(
/*IN*/ classname *handle,
/*OUT*/ char *dest,
/*IN*/ uint2 dest_size
);
如果字段是字符串,则该函数会多接收两个参数:用于接收字符串的缓冲区大小,以及一个用于接收实际返回字节数的输出参数。因此生成的函数将具有以下形式:
MCO_RET classname_fieldname_get(
/*IN*/ classname *handle,
/*OUT*/ char *dest,
/*IN*/ uint2 dest_size,
/*OUT*/ uint2 *len
);
关于字符和字符串字段(以字段field
为例)中 _get()
的行为,有以下几点需要注意:
- 如果目标缓冲区足够大,它会将整个字符或字符串字段的值加上一个空终止符。 •
- 对于字段值为“
1234567890
”,调用_get(..., buf, sizeof(buf), ... )
到一个char buf[100]
变量中,将提取“1234567890\0
”; - 如果目标缓冲区恰好能容纳整个值,它会提取整个字符或字符串字段的值,但不包含空终止符。例如,对于值“
1234567890
”,调用_get(..., buf, sizeof(buf), ... )
到char buf[10]
变量中,将提取“1234567890
”(无空终止符); - 如果目标缓冲区小于整个值的大小,它会提取尽可能多的字符或字符串字段的值,但不包含空终止符。例如,对于值“
1234567890
”,调用_get(..., buf, sizeof(buf), ... )
到char buf[5]
变量中,将提取“12345
”(无空终止符)。
数字和十进制生成的函数
正如基本数据类型页面所述,数据库中类型为 decimal 或 numeric 的字段的值在内部以整数形式存储,其大小由指定的宽度决定:
宽度 | 存储类型 |
---|---|
1 - 2 | signed< 1 > |
3 - 4 | signed< 2 > |
5 - 9 | signed< 4 > |
10 - 19 | signed< 8 > |
对于这些字段,将生成上面描述的标准 _put()
和_get()
函数,传递给 _put()
和_get()
的参数将是一个整数指针或相应大小的值。
除了 _put()
和_get()
之外,还生成了以下函数以允许将字段值指定为字符串:
MCO_RET CLASS_FIELD_put_chars( CLASS *handle, char const* buf);
MCO_RET CLASS_FIELD_get_chars( CLASS *handle, char* buf, int buf_size);
_put_chars()
函数将输入的字符串转换为整数值,并将其存储在数据库字段中。
_get_chars()
函数从数据库字段中提取值,并将其表示为字符串。
为了方便整数值和字符串之间的转换,还生成了两个辅助函数:
MCO_RET CLASS_FIELD_to_chars( TYPE scaled_num, char* buf, int buf_size);
MCO_RET CLASS_FIELD_from_chars( TYPE* scaled_num, char const* buf);
考虑这样一个定义小数的模式:
class A {
...
decimal<10,3> dec;
...
};
下面的代码片段演示了在实践中如何使用这些函数:
A a;
int8 i8;
/* 分配一个对象 */
A_new ( t, &a );
...
/* 将 int8 值放入数值字段 */
A_dec_from_chars( &i8, "123456");
A_dec_put( &a, i8 );
...
/* 将字符数组值放入十进制字段 */
A_dec_to_chars( 987654321, buf, sizeof(buf));
A_dec_put_chars( &a, buf );
...
/* 将字符数组值放入十进制字段 */
A_dec_to_chars( 987654321, buf, sizeof(buf));
A_dec_put_chars( &a, buf );
...
A_from_cursor(t, &csr, &a);
printf("\n\tContents of first record A: \n");
...
/* 从数字/十进制字段获取值 */
A_dec_get( &a, &i8);
A_dec_get_chars( &a, buf, sizeof(buf));
printf("\t\tdec=%lld, chars(%s)\n", i8, buf );
fixed_put 和 fixed_get
通常,数据库类会包含许多字段,因此获取和存储这些对象需要对每个单独的字段进行一系列的 _get()
和 _put()
函数调用。为了简化编码工作,模式编译器会为所有标量字段和固定长度的数组生成一个 C 语言结构,并且还会生成额外的 <classname>_fixed_get()
和 <classname>_fixed_put()
函数,这可以显著减少所需的函数调用次数。但是,正如其名称所示,这些函数只能为给定类的固定大小字段生成。如果一个类包含可变长度的字段(例如字符串、向量或二进制大对象字段),则必须使用它们各自的 _get()
和 _put()
函数来访问这些字段。
例如以下模式:
struct B
{
signed<1> i1;
signed<2> i2;
signed<4> i4;
char<10> c10;
float f;
};
class A
{
unsigned<1> ui1;
unsigned<2> ui2;
unsigned<4> ui4;
double d;
string s;
B b;
list;
};
会生成以下结构:
/* 类的固定部分的结构 */
typedef struct B_fixed_ {
int1 i1;
int2 i2;
int4 i4;
char c10[10];
float f;
} B_fixed;
typedef struct A_fixed_ {
uint1 ui1;
uint2 ui2;
uint4 ui4;
double d;
B_fixed b;
} A_fixed;
具有以下访问功能:
MCO_RET B_fixed_get( B *handle_, B_fixed* dst_ );
MCO_RET B_fixed_put( B *handle_, B_fixed const* src_ );
MCO_RET A_fixed_get( A *handle_, A_fixed* dst_ );
MCO_RET A_fixed_put( A *handle_, A_fixed const* src_ );
利用这些函数,A 类的对象可以通过两次函数调用进行写入:A_fixed_put()
用于固定大小的部分,A_s_put()
用于类型为字符串 s 的可变长度字段。同样,该类的对象也可以通过两次函数调用进行读取:A_fixed_get()
和 A_s_get()
。
以下代码片段说明了固定长度结构和 fixed_get()
函数的用法:
int main(int argc, char* argv[])
{
MCO_RET rc;
...
mco_trans_h t;
mco_cursor_t csr; /* 使用游标浏览数据库内容 */
A a; /* 对象句柄 */
A_fixed _a; /* A 类的固定大小部分 */
B b; /* 结构体句柄 */
B_fixed _b; /* B 类的固定大小部分 */
uint1 ui1; /* 值占位符 */
uint2 ui2;
...
/* 打开一个只读事务,读取对象 A 并显示其内容 */
rc = mco_trans_start(connection, MCO_READ_ONLY, MCO_TRANS_FOREGROUND, &t);
if ( MCO_S_OK == rc )
{
rc = A_list_cursor(t, &csr);
if ( MCO_S_OK == rc )
{
A_from_cursor(t, &csr, &a);
A_fixed_get(&a, &_a);
A_s_get( &a, buf, sizeof(buf), &ui2 );
printf("\n\tContents of record A: s (%s), ui1 = %d, b.i1 = %d\n",
buf, _a.ui1, _a.b.i1 );
}
}
rc = mco_trans_commit( t );
}
可空字段
如果在数据库模式中已将标量类元素(int
、float
、double
)声明为可为空,则会生成以下接口:
MCO_RET classname_fieldname_indicator_get( classname *handle, uint1 *result );
如果字段为空,则参数result
在返回时将具有1的值,否则结果将为0。
MCO_RET classname_fieldname_indicator_put( classname *handle, uint1 value);
传递1表示设置空指示符,传递0表示清除空指示符。
设置或清除空值指示器对底层字段的值没有影响。如果一个可为空的 uint2
字段的值为 5,并且对该字段调用了 <classname_fieldname>_indicator_put( h, 1 )
,那么在调用之后,该字段的值仍为 5。
对于所有类型的字段,相应的 <classname_fieldname>_get()
形式可以返回 MCO_S_NULL_VALUE
。<classname>_fixed_get()
也可以返回 MCO_S_NULL_VALUE
,这表明一个或多个组成字段为空;需要进一步使用 <classname_fieldname>_indicator_get()
来确定哪些字段为空。
有关包含在索引中的可空字段的行为说明,请参阅可空字段和索引页面。
检查点和大小
当一个索引字段被事务修改时,该对象会从为该类定义的所有索引中移除,无论修改的字段是否存在于其他索引中。一旦对象从索引中移除,就无法通过任何搜索功能定位该对象。在事务提交时,该对象会被重新放入索引中。
_checkpoint()
API 是在事务中将对象重新放入索引的唯一方法,从而使其可通过搜索功能可见。
MCO_RET classname_checkpoint ( /*IN*/ classname *handle);
如果违反了唯一索引约束,_checkpoint()
将返回状态码 MCO_S_DUPLICATE
。
对于字符串和向量类型的字段,还会生成一个额外的 _size()
函数,用于返回字符串值的实际长度或向量中的元素数量,以便您为其分配空间:
MCO_RET classname_fieldname_size( /*IN*/ classname *handle, /*OUT*/ uint2 *size);
动态对象的自动压缩
如果一个类包含动态扩展的组件(类型为向量或字符串的字段),那么该类中频繁更新的对象可能会出现内存空洞。为防止此类碎片化,提供了一种自动压缩功能。要启用自动压缩,请在传递给 mco_db_open_dev()
的 mco_db_params_t
中为数据库参数
autocompact_threshold
指定一个非零值。如果对象的大小超过此 autocompact_threshold
值,则自动压缩算法会重新分配对象,消除任何内部碎片。
请注意,对象压缩不应频繁执行。建议将此阈值设置为比正常对象预期大小大几千字节的字节数。
向量和定长数组
SmartEDB 中的向量按定义来说是可变长度的,而数组则是固定长度的。对于 C 应用程序,向量和固定长度数组需要一些特殊函数。固定长度数组在记录布局中被分配指定数量的静态内存,但向量字段最初只是引用,必须在运行时为其分配存储空间。_alloc()
函数在数据布局中为向量字段的元素预留空间。应用程序必须调用 _alloc()
函数来提供向量的大小,然后才能将值存储到向量字段中。否则,向量引用将保持为空。对于现有对象的向量字段调用 _alloc()
函数将调整向量的大小。如果新大小小于当前大小,则向量将被截断至新大小。
MCO_RET classname_fieldname_alloc(
/*IN*/ classname *handle,
/*IN*/ uint2 size
);
对向量和数组字段执行操作的函数需要一个索引参数,但除此之外,其功能与标量对应函数相同。对于声明为向量或固定大小数组的字段,其 _put()
函数的形式为:
MCO_RET classname_fieldname_put(
/*IN*/ classname *handle,
/*IN*/ uint2 index,
/*IN*/ <type> value
);
声明为字符串向量的字段具有以下形式:
MCO_RET classname_fieldname_put(
/*IN*/ classname *handle,
/*IN*/ uint2 index,
/*IN*/ const char * value,
/*IN*/ uint2 len
);
为了方便起见,会生成 _put_range()
方法,以便将一组值赋给向量或数组。(请注意,IN 数组的大小应小于或等于向量的 _alloc()
函数调用中指定的向量大小,或者小于或等于生成的头文件中 <classname_fieldname>_size
常量所定义的数组大小。)
MCO_RET classname_fieldname_put_range(
/*IN*/ classname *handle,
/*IN*/ uint2 start_index,
/*IN*/ uint2 num,
/*IN*/ const <type> *src
);
注意:
_put_range()
方法仅为由简单标量元素组成的向量生成,对于结构向量和字符串向量,不生成此方法。原因是,对于简单的类型向量元素,模式编译器可以生成优化的方法来为它们赋值。只有在编译时知道vector元素的大小时,才有可能进行这种优化。- 没有必要使用
_put_range()
方法来设置向量。 _put()
函数总是可以迭代以为所需范围分配单个向量元素值。
要访问向量中的特定元素,会生成 _at()
函数。_at()
函数的形式会根据向量中存储的元素类型而有所不同。对于固定长度字段的向量,其形式为:
MCO_RET classname_fieldname_at( /*IN*/ classname *handle, /*IN*/ uint2 index, /*OUT*/ <type> *result );
如果向量由字符串或固定长度的字节数组(char<n>
)组成,那么 _at() 函数会多接受两个参数:用于接收字符串的缓冲区的最大大小以及实际返回的字符串长度:
MCO_RET classname_fieldname_at(
/*IN*/ classname *handle,
/*IN*/ uint2 index,
/*OUT*/ char *result,
/*IN*/ uint2 bufsize,
/*OUT*/ uint2 *len
);
在为长度可变的元素向量分配内存时,可能需要先确定向量元素的实际大小。为此,为字符串向量生成了 _at_len()
函数:
MCO_RET classname_fieldname_at_len(
/*IN*/ classname,
/*IN*/ uint2 pos,
/*OUT*/ uint2 *retlen
);
对于标量元素的向量,_get_range()
函数返回向量元素的范围:
MCO_RET classname_fieldname_get_range(
/*IN*/ classname,
/*IN*/ uint2 startIndex,
/*IN*/ uint2 num,
/*OUT*/ const <type> *dest
);
对于结构体向量、字符串向量以及可选结构体字段,会生成 _erase()
函数。该函数会从布局中移除向量中的一个元素,并从该元素包含的所有索引中移除。
请注意,向量的大小保持不变。如果尝试获取已删除的元素,运行时将返回一个空指针和 MCO_S_OK
。_erase()
函数仅针对结构体向量生成,不针对基本类型的向量生成。对于基本类型的向量,应用程序应在向量元素中 _put()
一个可识别的值,以便将其解释为空值。
MCO_RET classname_fieldname_erase( /*IN*/ classname *handle, /*IN*/ uint2 index);
使用 _erase()
函数可能会在向量字段中留下未使用的元素(“空洞”)。
为此,会为向量字段生成 _pack()
函数以移除“空洞”,从而将已删除元素所占用的空间归还给空闲数据库内存池。 同样,如果应用程序分配了一个非空字符串,然后将字符串值修改为空值,_size()
函数将返回 0,但实际的字符串空间不会自动回收。应用程序需要调用生成的 _pack()
函数将该空间归还给存储池。
MCO_RET classname_pack ( /*IN*/ classname *handle, /*OUT*/ uint4 pages_released );
字符串排序
SmartEDB 核心和 C 应用程序的用户定义函数(UDA)编程接口支持多种排序规则。
排序规则通过一组比较字符集中的字符的规则来实现。字符集是一组具有指定序号的符号,这些序号决定了字符的精确排序顺序。例如,在意大利字母表中,“a, a, b, c, d, e, e, f”
这些字母可以被分配以下序号:a=0,a=1,b=2,c=3,d=4,e=5,e=6,f=7
。这种映射确保了带重音的字母”a“
会排在“a”
之后但在“b”
之前,而带重音的字母“e”
则会排在“e”
之后但在“f”
之前。
在某些字符集中,像“AE”
(在丹麦语和挪威语字母表中表示“labor lapsus”,在古英语中表示“ash”)和“OE”
(德语字母表中“O”的另一种形式或“O-umlaut”)这样的多字符组合被视为单个字母。当需要对包含这些字符组合的字符串进行排序时,这就会带来一些挑战。因此,用于这些字符集的排序算法必须能够一次比较多个字符,以确保正确的排序结果。
大小写处理也是排序规则中的一个重要问题。有时,我们希望字符串以区分大小写的方式进行比较,例如字母“a-z”会跟在大写字母“Z”之后;而在更多情况下,我们更倾向于不区分大小写的比较方式,即“a”跟在“A”之后,“b”跟在“B”之后,等等。这可以通过将每个字母的大写和小写版本视为等效来轻松实现,或者在比较字符串之前将大写转换为小写或反之,也可以在不区分大小写的字符集中为它们分配相同的序号。
SmartEDB 提供了灵活的排序规则支持,允许您使用多种排序规则对字符串进行比较,并且可以在同一个数据库中混合使用不同字符集或排序规则的字符串和字符数组。字符集和排序规则的配置是在应用程序级别完成的,为您提供更大的灵活性和控制力。
数据定义语言和API函数定义排序
SmartEDB 数据定义语言(DDL)为字符串类型字段上的树索引和哈希索引提供了排序规则声明,如下所示:
[unique] tree<string_field_name_1 [collate C1]
[, string_field_name_2 [collate C2]], …> index_name;
hash<string_field_name_1 [collate C1]
[, string_field_name_2 [collate C2]], …> index_name;
如果未为索引组件显式指定排序规则,则使用默认排序规则。根据 DDL 声明,对于每个排序规则,DDL 编译器将为使用此排序规则的树索引和/或哈希索引生成以下比较函数占位符:
int2 collation_name_collation_compare(
mco_collate_h c1,
uint2 len1,
mco_collate_h c2,
uint2 len2 );
{
/* 在此处添加您的实现代码 */
return 0;
}
uint4 collation_name_collation_hash (mco_collate_h c, uint2 len)
{
/* 在此处添加您的实现代码 */
return 0;
}
对于每个已定义的排序规则,都会生成一个单独的 API。比较函数的实际实现,包括字符集的定义,是应用程序的责任。为了便于比较函数的实现,SmartEDB 提供了以下一组函数:
mco_collate_get_char(mco_collate_h s, char *buf, uint2 len);
mco_collate_get_nchar(mco_collate_h s, nchar_t *buf, uint2 len);
mco_collate_get_wchar(mco_collate_h s, wchar_t *buf, uint2 len);
mco_collate_get_char_range(mco_collate_h s, char *buf, uint2 from, uint2 len);
mco_collate_get_nchar_range(mco_collate_h s, nchar_t *buf, uint2 from, uint2 len);
mco_collate_get_wchar_range(mco_collate_h s, wchar_t *buf, uint2 from, uint2 len);
请注意,需要三个不同版本的 mco_collate_get_*char()
和 mco_collate_get_*char_range()
函数,这是因为为了使用相同的排序规则,参数必须与所访问字段的类型相对应。
- 对于类型为
string
和char<n>
的字段,将调用*char
版本(mco_collate_get_char()
); - 对于类型为
nstring
和nchar<n>
的字段,将调用*nchar
版本; - 对于类型为
wstring
和wchar<n>
的字段,将调用*wchar
版本。
C 应用程序通过以下函数注册用户定义的排序:
mco_db_register_collations(dbname, mydb_get_collations());
这个函数必须在mco_db_connect()
或mco_db_connect_ctx()
之前调用,并且对于访问共享内存数据库的每个进程必须调用一次。第二个参数mydb_get_collations()
是一个特定于数据库的函数,类似于mydb_get_dictionary()
,由DDL编译器在文件mydb.h
和mydb.c
中生成。此外,DDL编译器在mydb_col .c
中生成排序比较函数存根。
注意,如果文件mydb_col .c
已经存在,DDL编译器将显示警告并生成mydb_col .c
。新的代替。
有关使用自定义排序的更多详细信息和示例,请参阅自定义排序。
blob支持
当需要保存流数据且没有已知大小限制时,blob字段非常有用。C 语言应用程序使用生成的 _get()
函数将 blob数据复制到应用程序的缓冲区;它允许指定 blob内的起始偏移量。
MCO_RET classname_fieldname_get(
/*IN*/ classname *handle,
/*IN*/ uint4 startOffset,
/*OUT*/ char *buf,
/*IN*/ uint4 bufsz,
/*OUT*/ uint4 *len
);
bufsz
参数是应用程序通过 buf
参数传递的缓冲区大小。len
输出参数是实际复制到缓冲区中的字节数(其值将小于等于 bufsz)。
_size()
函数返回 blob字段的大小。在调用 _get()
函数之前,可以使用此值分配足够的内存来容纳 blob。
MCO_RET classname_fieldname_size( /*IN*/ classname *handle, /*OUT*/ uint4 * result);
_put()
函数会填充一个 blob字段,可能会覆盖先前的内容。它会分配空间并从应用程序的缓冲区复制数据;必须指定 blob的大小。
MCO_RET classname_fieldname_put( /*IN*/ classname *handle, /*IN*/ const void *from,
/*IN*/ uint4 nbytes);
_append()
函数用于向现有的 blob中追加数据。提供此方法是为了让应用程序不必分配一个足够大的缓冲区来容纳整个 blob,而是可以通过分段写入 blob来节省内存。
MCO_RET classname_fieldname_append(
/*IN*/ classname *handle,
/*IN*/ const void * from,
/*IN*/ uint4 nbytes
);
要删除一个 BLOB,请向 _put()
方法传递大小为 0 的值。
二进制数据
虽然blob字段对于大型二进制数据很有用,但它们仅适用于大型数据字段(大于 1KB)。对于字符或二进制数据(小于 64KB),建议使用字符串字段。当字符串字段不用于索引时,它可以保存任意二进制数据(因为索引比较需要空终止符)。但与 blob字段不同的是,二进制(binary)和可变二进制(varbinary)字段可以添加到简单索引和复合索引中。
日期,时间和日期时间字段
参阅这里。