结构体、数组和字符串
概述
以下 SmartESQL 示例代码片段说明了如何使用 Field、Struct 和 Array 类来访问结构中的元素。
示例代码使用了以下模式定义:
declare database structuresdb;
struct aPoint
{
int4 x;
int4 y;
};
struct aLine
{
aPoint begin;
aPoint end;
};
struct aPolygon
{
vector<aPoint> points;
};
class aRecord
{
string s;
int2 i2;
aPoint p;
int1 i1;
int8 i8;
float f;
double d;
aLine l;
int1 ai1[100];
vector<aPolygon> vp;
aLine lines[10];
int4 i4;
hash<i4> by_i4[1000];
list;
autoid[1000];
};
在类 aRecord 中定义的嵌入式结构 aPoint、aLine 以及向量或 aPolygon 结构。这些结构在以下代码片段的开头处由 C++ 结构定义进行了映射。
const char * db_name = "structuresdb";
…
// 定义全局 SQL 引擎
using namespace McoSql;
McoSqlEngine engine;
// 定义与数据库表相对应的结构
struct _Point
{
int x;
int y;
};
struct _Line
{
_Point begin;
_Point end;
};
struct _Polygon
{
Array* points;
};
struct _Record
{
char* s;
short i2;
_Point p;
char i1;
int64_t i8;
float f;
double d;
_Line l;
Array* ai1;
Array* vp;
Array* lines;
int i4;
};
…
int main( int argc, char* argv[] )
{
…
engine.open( db_name, structuresdb_get_dictionary(), DATABASE_SIZE, MEMORY_PAGE_SIZE);
// 插入记录
insertRecords();
// 显示内容(不含数组)
showRecords();
// 添加ints, Polygons和Lines数组至Records
addArrays();
// 使用数组显示内容
showRecordArrays();
engine.close();
sample_pause_end("\n\nPress any key to continue . . . ");
return 0;
}
void insertRecords()
{
…
for (int i = 1; i <= nRecords; i++)
{
_Record r;
char buf[256];
sprintf(buf, "Record %d", i);
r.s = buf;
r.i2 = i * 10;
r.p.x = i + 1;
r.p.y = i + 2;
r.i1 = i % 10;
r.i8 = i * 100;
r.f = (float)i / 10;
r.d = (double)i / 100;
r.l.begin.x = i + 2;
r.l.begin.y = i + 2;
r.l.end.x = i + 3;
r.l.end.y = i + 4;
r.i4 = i;
r.ai1 = NULL;
r.vp = NULL;
r.lines = NULL;
engine.executeStatement("insert into aRecord %r", &r);
}
}
在上述代码片段insertRecords
中需要注意的一些编程要点:
_Record
对象的数组元素初始化为NULL
。这些值在 addArrays() 函数中被赋予。- _Line 元素 l 的起始点和终点是通过简单赋值设置的标量整数值。
void addArrays()
{
int i,j, k;
...
for ( i = 1; i <= nRecords; i++ )
{
// 获取记录 i 以进行更新
QueryResult result(engine.executeQuery( "select * from aRecord where i4=%i for update", i ) );
Cursor* cursor = result->records();
Record* rec = cursor->next();
// 将字段值放入字段变量中
Field* vp = result->findField("vp");
Field* points = vp->element()->findComponent("points");
Field* x = points->element()->findComponent("x");
Field* y = points->element()->findComponent("y");
Field* lines = result->findField("lines");
Field* beg = lines->element()->findComponent("begin");
Field* end = lines->element()->findComponent("end");
// 将记录的当前内容获取到结构体 _Record 中
_Record r;
result->extract(rec, &r, sizeof(r));
// 设置字节数组的值
memset( byteArray, 0, nBytes );
for ( j=0; j < nBytes; j++ )
byteArray[j] = ( i * nBytes) + j;
r.ai1->setBody( byteArray, 0, nBytes );
// 设置nPolygons
r.vp->setSize( nPolygons );
for ( j = 0; j < nPolygons; j++ )
{
Struct* poly = (Struct*)r.vp->updateAt( j );
Array* pa = (Array*)points->update( poly );
pa->setSize(nPoints);
for ( k = 0; k < nPoints; k++)
{
Struct* point = (Struct*)pa->updateAt(k);
x->set( point, new IntValue( (i * 100) + j + k + 1 ) );
y->set( point, new IntValue( (i * 100) - j - k - 1 ) );
}
}
// 设置 lines
for ( j = 0; j < nLines; j++ )
{
Struct* line = (Struct*)r.lines->updateAt(j);
Struct* bp = (Struct*)beg->update(line);
Struct* ep = (Struct*)end->update(line);
x->set( bp, new IntValue( (i * 100) + j + 1 ) );
y->set( bp, new IntValue( (i * 100) - j - 1 ) );
x->set( ep, new IntValue( (i * 100) + j + 3 ) );
y->set( ep, new IntValue( (i * 100) - j - 3 ) );
}
// 更新数据库记录
rec->updateRecord();
}
}
}
在上述代码片段 addArray
中需要注意的一些编程要点:
- McoSql 类的 Record(变量
rec
)用于定位游标中的每条记录,然后初始化其数组字段。 - McoSql 类的 Field 用于获取结构字段
vp
、aPolygon.points
和lines
以及标量字段x
、y
、begin
和end
的指针。然后使用这些指针来初始化数组和结构值。 - 通过调用 Array 类的
setBody()
方法来设置 Arrayail
(数据库模式中的字节数组)。 - McoSql 类的 Struct(变量
poly
)初始化为指向向量vp
中的下一个 aPolygon 元素,然后 Array 变量pa
初始化为指向 aPolygon 结构中的 aPoints 数组。 - 调用 Array 方法
setSize()
来确定 aPoints 数组的大小。 - 再次使用 Struct 类(变量
point
)指向 Array 中的每个aPoint
结构。 - 使用 Field 变量
x
和y
通过调用set()
方法并使用IntValue
构造函数实例化值参数来设置aPoint
值。 - 以类似的方式使用 Struct 和 Field 类设置
aRecord
的lines
数组字段。
void showRecords()
{
printf("\n\tRecord table contents:" );
for ( int i=1; i <= nRecords; i++ )
{
_Record r;
QueryResult result( engine.executeQuery( "select * from aRecord where i4=%i", i ) );
Cursor* cursor = result->records();
if ( cursor->hasNext() )
{
Record* rec = cursor->next();
result->extract( rec, &r, sizeof(r) );
printf( "\n\t%d) %s: i2=%d, i4=%d, x=%d, y=%d, f=%f, d=%f, \n\t\t"
"line(x1,y1,x2,y2)=%d,%d,%d,%d",
i, r.s, r.i2, r.i4, r.p.x, r.p.y, r.f, r.d,
r.l.begin.x, r.l.begin.y, r.l.end.x, r.l.end.y );
}
}
}
void showRecordArrays()
{
int i,j,k;
printf("\n\n\tRecord Array contents:" );
for ( i=1; i <= nRecords; i++ )
{
// 获取记录 i
QueryResult result(engine.executeQuery( "select * from aRecord where i4=%i", i ) );
Cursor* cursor = result->records();
Record* rec = cursor->next();
_Record r;
result->extract(rec, &r, sizeof(r));
printf( "\n\n\t%d) %s: ", i, r.s );
// 显示字节数组
printf( "\n\t\tArray of %d 'i1' values: ", nBytes );
r.ai1->getBody( byteArray, 0, nBytes );
for ( j=0; j < nBytes; j++)
{
printf( "%d%s", byteArray[j], ( j == nBytes-1 ? "" : ", " ) );
}
// 显示多边形顶点
printf( "\n\t\tPoints for %d Polygons : ", nPolygons );
for ( j=0; j < nPolygons; j++ )
{
Struct* poly = (Struct*)r.vp->getAt(j);
Array* points = (Array*)poly->get(0);
printf( "\n\t\t\t Polygon %d:", j+1 );
for ( k=0; k < nPoints; k++ )
{
Struct* point = (Struct*)points->getAt(k);
printf( "\n\t\t\t\t%d) x=%d, y=%d ", k+1, (int)point->get(0)->intValue(), (int)point->get(1)->intValue() );
}
}
// 显示线条端点
printf( "\n\t\tEnd points for %d Lines : ", nLines );
for ( j=0; j < nLines; j++ )
{
Struct* line = (Struct*)r.lines->getAt(j);
Struct* beg = (Struct*)line->get(0);
Struct* end = (Struct*)line->get(1);
printf( "\n\t\t\t\t%d) begin=(%d,%d) end=(%d,%d)", j+1, (int)beg->get(0)->intValue(), (int)beg->get(1)->intValue(),
(int)end->get(0)->intValue(), (int)end->get(1)->intValue() );
}
}
}
在上述代码片段“showRecord
”和“showRecordArrays
”中需要注意的一些编程要点:
- 对于 aRecord 对象的元素点“
p
”,其坐标可以直接从 aRecord 结构中通过r.p.x
和r.p.y
来访问。 - 向量元素
vp
内的 aPolygon 结构中的点的坐标必须通过 Struct 和 Array 类来访问;首先通过方法getAt()
将 Struct 对象poly
初始化为vp
的元素j
,然后通过 Struct 方法get(0)
将数组points
设置为aPolygons.points
,接着将每个point
初始化为指向数组points
的元素k
,最后通过 Struct 方法get(0)->intValue()
提取并转换为整数类型point.x
的值,以及get(1)->intValue()
提取point.y
的值。 - 同样地,
lines
数组的坐标begin
和end
通过 Struct 方法get(n)->IntValue()
来访问并转换为整数类型。
// 从 aRecord 中选择一个结构体元素和字符串字段
void showStruct( int x )
{
printf("\n\n\tExtract p.y and s fields from aRecord with p.x=%d:", x );
QueryResult result(engine.executeQuery( "select p.y, s from aRecord where p.x=%i", x ) );
Cursor* cursor = result->records();
Record* rec = cursor->next();
// 使用 Struct::get(int index) 方法通过索引(从 0 开始)引用结果列
int2 y = (int2)rec->get(0)->intValue();
printf( "\n\t\tUsing Struct::get(int index): y=%d, s='%s'", y, rec->get(1)->pointer() );
// 使用字段描述符
printf( "\n\t\tUsing QueryResult::Fields iterator:" );
int iField = 0;
Iterator <Field> * fields = result->fields();
Field * f;
while ( f = fields->next() )
{
switch ( iField )
{
case 0:
printf( "\n\t\t\tField %s=%d", f->name()->cstr(), (int)f->get(rec)->intValue() );
break;
case 1:
printf( "\n\t\t\tField %s='%s'", f->name()->cstr(), f->get(rec)->pointer() );
}
iField++;
}
// 使用 DataSource.extract() 方法。注意:所使用的结构必须与 select 语句中指定的字段数量相同。
struct
{
int y;
char * s;
} row;
result->extract( rec, &row, sizeof( row ) );
printf( "\n\t\tUsing DataSource::extract(): y=%d, s='%s'\n", row.y, row.s );
}
在上述代码片段“showStruct()
”中需要注意的一些编程要点:
select
语句中的条件指定了 "where p.x=%i
",用于根据嵌入式 Point 结构体中某个元素的值进行筛选。有三种不同的方法用于提取结果集的值。特别要注意
struct
元素p.y
的值是如何管理的:- 使用
Struct::get(int index)
方法通过索引引用结果行中的列; - 使用
QueryResult::Fields
迭代器来遍历结果行中的列; - 使用
DataSource::extract()
方法提取一个与结果行中的列数完全对应的struct
。
- 使用
数组及用法
SmartESQL 可以接受 SmartEDB 数组(固定长度)和向量(动态长度)作为记录的组成部分。与 SmartEDB 一样,多维数组不受支持。SmartESQL 提供了一组专门的构造来处理数组和向量(以下统称为“数组”):
- 可以通过使用
length()
函数获取数组中的元素数量。 - 可以使用
[]
运算符获取数组元素。如果索引表达式超出数组范围,则会引发异常。 - 可以使用
in
运算符检查数组是否包含由左操作数指定的值。此操作仅适用于原子类型的数组;即布尔型、数值型、引用型或字符串型的数组。 - 通过
exists
运算符可以遍历数组元素。在exists
关键字之后指定的变量可以在由exists
量词引导的表达式中用作数组的索引。此索引变量将遍历所有可能的数组索引值,直到表达式的值为真或索引超出数组范围。
例如,给定以下数据库模式:
class Company
{
string location;
autoid[1000];
};
class Contract
{
autoid_t<Company> company;
unsigned<8> quantity;
date delivery;
autoid[1000];
};
class Detail
{
string name;
vector<autoid_t> contract;
};
查询语句如下可用于查询所有在美国公司的Detail记录:
select * from Detail where exists i: (contract[i].company.location = 'US');
若选择所有美国以外公司的详细记录,查询语句如下:
not exists i: (contract[i].company.location = 'US')
阵列与向量
SmartESQL 支持所有 SmartEDB 数据类型,包括向量(动态数组)类型。数组通过以下方式创建:
McoSql::Array *arr = McoSql::Array::create(allocator, McoSql::tpInt8, 0, nElem);
固定大小的项目可以通过指向内部缓冲区的指针进行分配,如下所示
McoSql::Array * arr = McoSql::Array::create(allocator,
McoSql::tpInt8, 0, nElem);
int64_t * carr = (int64_t *)arr->pointer();
for (Py_ssize_t j = 0; j < nelem; j++)
{
int64_t v = PyInt_AsLong(pitem);
carr[j] = v;
}
或者,为单个元素赋值的更安全的方法如下:
int64_t val = 0;
arr->setAt(j, &val);
SmartEDB 还支持可为空的数组。通过调用 Array 方法 makeNullable()
可以创建可为空的数组。例如:
McoSql::Array *someArray;
...
McoSql::Array *nullableArray = someArray->makeNullable();
新创建的 nullableArray
是一个 NullableArray 类的实例,其内容为原始数组的内容以及对应的 nullBitmap。nullBitmap 是一个 McoSql::Array 类型的实例,其元素类型为 uint64_t
,其中每个设置的位都表示数组中对应位置的元素为 null。可以使用 getNullBitmap()
方法获取此位图。例如:
McoSql::Array *nullBitmap = nullableArray->getNullBitmap();
请注意,应用程序无需直接检查或修改此 nullBitmap
。可以通过将指向 McoSql::Null
对象的指针传递给 setAt()
方法来设置一个空元素。例如:
nullableArray->setAt(0, &McoSql::Null);
方法 getAt()
和 getCopyAt()
将为 NullableArray
中的空值项返回指向 McoSql::Null
对象的指针。
字符串
所有适用于“数组”的操作也适用于“字符串”,字符串也有其自身的一套操作。例如,可以使用标准的关系运算符对字符串进行相互比较。
like
这种结构可用于将字符串与包含特殊通配符字符“%
”和“_
”的模式进行匹配。字符“_
”匹配任何单个字符,而字符“%
”匹配任意数量的字符(包括 0 个)。带有转义部分的 like
运算符的扩展形式仅在特殊转义字符出现在模式中的“%
”和“_
”之前时,才能将它们作为普通字符处理,该转义字符在 escape 关键字之后指定。
例如,
select * from T where name like '#%x%' escape '#'
将选择所有名称字段以“%x
”开头的记录。
可以使用 in
运算符在字符串中搜索子字符串。因此,表达式 'blue' in color
对于所有颜色字段包含字符串 'blue'
的记录都为真。例如,
select * from car where 'blue' in color;
将选择颜色为“深蓝色”、“蓝色”、“白蓝相间”、“浅蓝色”……的汽车对象。
字符串可以通过使用 +
或 ||
运算符进行连接(+ 和 || 可以互换使用)。SmartESQL 在表达式中不支持隐式转换为 string
类型,因此已重新定义了字符串的 + 运算符的语义。换句话说,在许多 SQL 实现中,可以这样写:
1+'1'
结果将是 2(这里从字符串到整数进行了隐式转换)。结果为
1||'1'
将是“11”。
SmartESQL 不允许字符串的隐式转换,因此 1 + '1' 的结果将是 '11',与 1 || '1' 的结果相同。
应当注意,从基类 Value 继承的每个类都实现了一个名为 String *stringValue
的方法。在该方法的所有实现中(String 类除外),都会创建一个类型为 *String
的新对象,并且在使用完毕后应当将其删除。而 String 类的方法则返回 this
。因此,如果应用程序删除了字符串对象,然后又删除了当前对象所继承自的对象,那么该对象就会被删除两次!而且一旦删除,也无法再使用父对象了。
因此,为了避免混淆,建议不要使用 String *stringValue
方法将 Value 转换为 string
,而应使用 Ref<String> stringRef(Allocator* allocator)
方法。此方法会监控对象的生命周期,并在适当的时候将其移除。使用此方法有两种方式:
Ref<String> strval(v->stringRef(allocator));
或
Ref<String> str = v->stringRef(allocator);
引用的作用域与使用如下:
{
Ref<String> strval(v->stringRef(allocator));
/* 将引用作为本地对象字符串使用*/
printf("%s", strval.cstr());
} /* Ref在此处会自动删除。 */
或
{
Ref<String> str = v->stringRef(allocator);
/* 将引用作为指向字符串对象的指针 */
printf("%s", str->cstr());
} /* Ref is automatically deleted here */
字符串常量
StringLiteral
构造函数有一个大小为 1 的“chars
”成员变量。这说明了具有可变大小的结构的标准分配方式。当分配此结构时,必须将对象的固定部分和可变部分的大小相加。在 C++ 中,可以使用带有额外参数的 new
运算符来实现。然而,这样的运算符并未预先定义,您需要自行定义(就像在 DynamicObject
类中所做的那样):
inline void* operator new(size_t fixed, size_t var)
{
return ::new char[fixed + var];
}
现在可以按如下方式创建字符串常量:
char const* str = "Hello World";
size_t length = strlen(str);
Value* value = ::new (length) StringLiteral(str, length);
有两种选择:
使用 StringRef 类,其构造函数接受指向字符串的指针,并且当字符串不再需要时,由程序员负责释放该字符串。
定义一个继承自 String 类的自定义类:
class UserString : public String
{
public:
virtual int size();
virtual char* body();
virtual char* cstr();
private:
const int length;
char* chars;
public:
UserString(char const* s, int l);
~UserString();
};
int UserString::size()
{
return length;
}
UserString::UserString(char const* s, int l) : length(l)
{
chars = new char[l+1];
memcpy(chars, s, l);
chars[l] = '\0';
}
UserString::~UserString()
{
delete[] chars;
}
char* UserString::body()
{
return chars;
}
char* UserString::cstr()
{
return chars;
}
二元数据
可能包含零值的 二进制 和 可变二进制 数据可以存储在 SmartESQL 的 byte
数组或类型为 binary
或 varbinary
的字段中。要将包含嵌入零值的二进制数据插入到类型为 byte
数组的字段中或从该字段中检索数据,请使用 Array 的 putBody()
或 getBody()
方法。例如,在模式定义中:
class t
{
signed<1> b[16];
list;
};
字段“b
”是一个字节数组,在应用程序代码中将作为数组进行访问 - 使用 Array::getBody()
方法获取数组的内容。
如果需要在二进制数据上建立索引,则该字段必须为 binary
或 varbinary
类型。例如,在模式定义中:
class bin_array
{
unsigned<4> idx;
binary<16> bin_array;
userdef tree<bin_array> binkey;
kdtree <idx, bin_array> binkey_kd;
patricia<bin_array> binkey_p;
};
class bin_vector
{
unsigned<4> idx;
varbinary bin_vector;
userdef tree<idx, bin_vector> binkey;
kdtree <idx, bin_vector> binkey_kd;
patricia<bin_vector> binkey_p;
};
C++ 类 Binary 用于访问二进制字段。以下代码片段演示了如何实现二进制数据访问:
struct _bin_cls
{
int idx;
Value *bin;
}
...
_bin_cls bin_cls;
// 将新数据放入数据库
char buf_in[4096];
...
// 假设 buf_in 包含重要数据
// 创建二元对象并复制
Binary* b = Binary::create(engine.getAllocator(), sizeof(buf_in));
memcpy(b->body(), buf_in, sizeof(buf_in));
bin_cls.idx = 1;
bin_cls.bin = (Value *)b;
engine.executeStatement("insert into bin_cls %r", _bin_cls);
DELETE_OBJ(engine.getAllocator(), b);
...
// 获取查询结果
QueryResult result( engine.executeQuery( "select * from bin_cls" ) );
Cursor* cursor = result->records();
Record* rec = cursor->next();
McoSql::ValueRef eref(rec->get(1));
McoSql::Binary *bin = eref.as<McoSql::Binary>(); // will be deleted automatically by resultSet's allocator
size_t len = bin->length;
char buf_out[len];
memcpy(buf_out, bin->body(), len);
或者,占位符 %v
可用于将 binary
或 varbinary
值替换到 SQL 字符串中:
// 将新数据放入数据库
char buf_in[4096];
// 假设 buf_in 包含重要数据
// 创建二元对象并复制
Binary* b = Binary::create(engine.getAllocator(), sizeof(buf_in));
memcpy(b->body(), buf_in, sizeof(buf_in));
engine.executeStatement("insert into bin_cls values (%i, %v)", 1, b);
DELETE_OBJ(engine.getAllocator(), b);
从 SQL 的角度来看,有两种数据类型:binary(n)
映射到存储类型 binary<n>
,以及 varbinary
映射到存储类型 varbinary
。(从 SQL C++ API 类 Value
的角度来看,这两种类型在内部都映射到 tpBinary
类型)。二进制字段的数据可以在 SQL 中以十六进制格式的字符串形式传递。例如:
create table t (idx int, bin binary(4));
insert into t values(1, '0A0D0A0D' );
还可以通过 SQL 创建 binary
和 varbinary
类型的数组和向量。例如:
create table t (idx int, bin array(binary(4), 10)); // 二维数组
create table t (idx int, bin array(varbinary)); // 二进制变长字符向量
Dynamic objects
现在,new
运算符已重载为使用静态的 StdAllocator,并且所有动态对象都必须将分配器作为参数传递给 new
运算符。而在 SmartESQL 6.5 之前的版本中,动态对象是通过如下代码创建的:
int val = 1;
...
return new IntValue(val);
现在,动态对象必须在如下代码中使用 StdAllocator:
Allocator * allocator = engine.getAllocator();
int val = 1;
...
return new (allocator) IntValue(val);
或者在代码中像下面这样使用静态的 create()
方法:
Allocator * allocator = engine.getAllocator();
int val = 1;
...
return IntValue::create(allocator,val);
请注意,在任何需要分配器的 API 调用(如 new)中,可以从 McoSqlEngine 获取分配器的指针,如上述示例代码所示,或者从任何容器子类(如 Record、DataSource、Array、Blob)获取。使用 McoSqlEngine 分配器分配的任何对象都不会被隐式删除;也就是说,除非显式删除,否则它们将一直保留在内存中,直到引擎本身被销毁。但是,如果在查询内部操作,则可以从 QueryResult 对象获取分配器;并且此分配器以及使用它分配的所有对象将在查询完成时被销毁。
若要显式删除使用 new 运算符创建的动态对象,请使用在公共头文件 basedef.h
中提供的 DELETE
或 DESTROY
宏。
使用引用
在 SmartESQL 中,通常通过使用 references
来实现表之间的 SQL“外键”关系,从而通过 AUTOID
快速直接地访问记录。引用字段也可以被索引,并在 ORDER BY
子句中使用。引用可以通过与访问结构组件相同的点符号进行解引用。
例如,以下数据库模式:
class Address
{
string city;
autoid[1000];
hash<city> city_index[1000];
};
class Company
{
autoid_t<Address> address;
autoid[10000];
hash<address> address_index[10000];
};
class Contract
{
autoid_t<Company> company;
autoid[10000];
hash<company> company_index[10000];
};
查询语句:
select * from Contract where company.address.city = 'Chicago';
将检索出所引用的Company引用的Address的city
字段等于“Chicago”的“合同”记录。此请求的查询执行计划如下:
- 使用
city_index (city = 'Chicago')
对 Address 表执行索引搜索。 - 对于所有选定的Address记录,使用
address_index
查找引用这些地址的Company记录。 - 对于所有选定的Company记录,使用
company_index
查找引用这些Company记录的Contract记录。
可以通过 is null
或 is not null
这两个谓词来检查引用是否为 null
。它们还可以相互比较是否相等,以及与特殊的 null
关键字进行比较。当对 null
引用进行解引用时,SmartESQL 会引发异常。
以下示例模式图展示了可用于对“客户 - 订单”应用程序进行建模的三个表:
declare database referencesdb;
class Address
{
int4 zipcode;
string city;
string street;
hash<zipcode> iaddr[100000];
autoid[100000];
list;
};
class Company
{
autoid_t<Address> location;
string name;
hash<name> iname[100000];
hash<location> iaddr[100000];
autoid[100000];
list;
};
class Orders
{
autoid_t<Company> company;
date shipment;
uint8 amount;
string description;
hash<company> icomp[100000];
list;
};
请注意,Company 表和 Orders 表之间存在一对多的关系,即多个 Order 类的实例可能具有相同的 autoid
字段company
的值。此字段company
是对一个 Company 对象的“引用”,从而实现最佳查找时间。同样,Company 表和 Address 表之间的关系通过引用字段location
来实现。以下示例代码片段展示了如何在 SmartESQL 中使用这些引用字段:
const char * db_name = "joindb";
// 定义全局 SQL 引擎
using namespace McoSql;
McoSqlEngine engine;
// 定义与数据库表相对应的结构
struct _Address
{
int4 zipcode;
char const* city;
char const* street;
};
int main()
{
…
engine.open( db_name, referencesdb_get_dictionary(), DATABASE_SIZE, MEMORY_PAGE_SIZE);
// 插入记录
insertRecords();
// 按邮政编码搜索地址
searchAddresses();
// 显示Company-Addresses
showJoin1();
// 显示Company-Orders
showJoin2();
// 显示公司订单,其中描述字段包含“1”
showJoin3();
// 释放
deleteRecords();
engine.close();
sample_pause_end("\n\nPress any key to continue . . . ");
return 0;
}
void showJoin1()
{
printf("\n\n\tSELECT C.name FROM Address A,Company C ...");
for (int i = 1; i <= nRecords; i++)
{
QueryResult result( engine.executeQuery( "SELECT C.name FROM Address A,Company C "
"WHERE A.zipcode=%i AND
A.autoid=C.location", i ) );
Cursor* iterator = result->records();
assert(iterator->hasNext());
Record* rec = iterator->next();
String * pName = (String*)rec->get(0);
printf( "\n\t\t%d) %s", i, pName->body() );
}
}
void showJoin2()
{
printf("\n\n\tSELECT C.name, O.description FROM Address A,Company C,Orders O ...");
for (int i = 1; i <= nRecords; i++)
{
QueryResult result( engine.executeQuery( "SELECT C.name, O.description "
"FROM Address A,Company C,Orders O "
"WHERE A.zipcode=%i AND A.autoid=C.location"
" AND C.autoid=O.company", i ) );
Cursor* iterator = result->records();
assert(iterator->hasNext());
Record* rec = iterator->next();
String * pName = (String*)rec->get(0);
String * pDesc = (String*)rec->get(1);
printf( "\n\t\t%d) %s: %s", i, pName->body(), pDesc->body() );
}
}
void showJoin3()
{
printf("\n\n\tSELECT C.name, O.description ... WHERE O.description like '%%1%%' ...");
for (int i = 1; i <= nRecords; i++)
{
QueryResult result( engine.executeQuery( "SELECT C.name, O.description "
"FROM Address A,Company C,Orders O "
"WHERE O.description like '%1%' AND "
"A.zipcode=%i AND A.autoid=C.location "
"AND C.autoid=O.company", i ) );
Cursor* iterator = result->records();
while ( iterator->hasNext() )
{
Record* rec = iterator->next();
String * pName = (String*)rec->get(0);
String * pDesc = (String*)rec->get(1);
printf( "\n\t\t%d) %s: %s", i, pName->body(), pDesc->body() );
}
}
}
在上述代码片段 showJoin1
、showJoin2
和 showJoin3
中需要注意的一些编程要点:
- 通过调用方法
get(n)
,McoSQL 类中的 String 用于访问Company.Name
和Order.Description
字段。 - 通过调用
String
方法 "body()
" 来打印出string
值。
在向量或数组中搜索
可以使用 SQL 的 IN
运算符在向量或数组中进行搜索,例如:
select * from T where ? in T.vector;
上述语句适用于 标量
类型的向量,但对于复合类型的向量,则必须使用 SmartEDB 特定的表达式。例如,使用以下模式定义:
struct E
{
uint4 q1;
uint4 q2;
vector<D> q3;
};
class R
{
uint4 r1;
E r2;
tree <r2.q1> by_r2e1;
};
一个恰当的 select
语句应该如下所示:
select * from R where exists i: (exists j: (r2.q3[i].d3[j].c1=1));
请注意,这样的查询需要对所有可能的索引值进行顺序搜索和二次迭代,因此速度会相当慢(所以不建议这样做)。