#NanoDB Design (draft)
定位:跨应用,跨平台
##Creating Database
####Memory Database
创建内存数据库,只需要提供库名(此名字用于标识数据库在内存中的名字)和数据源(数据源为空则是一个空数据库)。数据源的来源不关心,只要是完整的JSON数据对象即可。
nano.db('dbtest'); // Empty database
// database with 2 Datasheets
nano.db('mydb', {
users: [],
books: []
});
内存数据库,顾名思义数据只在页面内存中,没有任何与后台交互或本地存储,刷新页面后数据数据将丢失。
如果需要数据的各个操作的同步,需要引入相应的数据驱动。
####Data driver
数据驱动,即是监听数据的操作,触发相应的同步动作,保证数据的存储,至于存储方式是什么,就要取决于配置进来的数据驱动类型。
引入驱动文件,比如本地存储:
<script src="/path-to-your-scripts/driver/local.js" type="text/javascript"></script>
这个时候按第一步创建出来的数据库,就会同步到本地存储:
nano.db('mydb');
除此之外,还可以根据需要配置其他驱动。
注:把数据驱动部分从core中独立出来,分本地存储、异步请求、文件读写(server端),根据使用者需要,导入相应的文件。
除了使用项目提供的数据驱动,用户还可以自己写驱动。参考自定义数据驱动。
##Collection
新建或获取一个数据集(相当于数据表):
var users = mydb.collection('users');
新建一个规定结构(Schema)的数据集:
var books = mydb.collection('books', {
'name': 'book',
'properties': {
'title': {
'type': 'string',
'required': true
}
}
});
例子中新建了books表,要求数据记录必须有一个string类型的必选属性title。增改数据记录时,按这个结构的数据才是有效数据。
##Schema
规范了数据记录的结构,规则: http://tools.ietf.org/html/draft-zyp-json-schema-03
TODO: 提供更详细的规则描述和例子。
##indexes
...
##Inserting
对于未规定结构的数据表,插入一条记录和多条记录:
// insert one record
users.insert({name: 'Neo', age: 24, gender: 'male'}, function (err) {
// result handle
});
// insert multiple records
users.insert([
{name: 'Kar', age: 25, gender: 'male'},
{name: 'Mio', age: 20, gender: 'female'}
], function (err) {
// result handle
});
对于严格规定了数据结构的数据表,插入数据格式必须符合要求,同样支持单条或多条记录:
books.insert({title: 'NanoDB Guide'}, function (err) {
// result handle
});
// malformed
books.insert({name: 'NanoDB Guide'}, function (err) {
// result handle
});
TODO: 多数据插入,部分失败的处理方式(全部回滚 or 只返回失败)。
##Querying
查询条件是以基于对象的查询。
基础查询:
users.find({gender: 'male'}, function (err, rs) {
// result handle
});
高级查询:
// find which the age > 12 && age < 20
users.find({
age: {
$gt: 12,
$lt: 20
}
}, function (err, rs) {
// result handle
});
####Conditional Operators
高级查询支持的所有操作符:
Operator Description
$all all values in the array must be matched
$and &&
$exists exists or not
$gt >
$gte >=
$has has the provided value in an array
$in within a provided array
$is ===
$length matches object's length
$lt <
$lte <=
$ne !=
$nin value not in a provided array
$or ||
$regex regular expression match
$startWith matches a string is start with a given value
$endWith matches a string is end of value
$same matches object same with another
$type matches object where the property is of a given type
TODO:补充或摒弃一些操作符,评估使用频率比较高的;添加更详细的描述和例子。
####基于方法链的查询
跟基于对象的查询的区别是,把操作符函数化。比如:
var youth = users.find('age').gt(12).lt(20);
TODO: 分析可行性。实现上感觉相对比较复杂,回调不好处理。使用起来的话,像一些习惯jq的会容易理解。
##Updating
update操作时,获取所要更新的数据与query的方式相同。update可分两种方式:合并更新和自操作更新。
合并更新,把指定的新值merge到查询出来的数据中:
users.update({name: 'Kar'}, {age: 21}, function (err) {
// ...
});
多结果即等同于批量更新。
自操作更新:
users.update({name: 'Kar'}, function (records) {
records[0].age++;
}, function (err) {
// ...
});
注意:第一个function是查询结果的,发生在查询后,这个时候还没有更新,这个方法正是指定如何更新。
第二个function是更新后的回调。
自操作更新对查询出来的记录所作修改,能直接影响到源数据,能适应更多复杂情况。
books.insert({title: 'Computer Science', category: ['science', 'computer']});
books.update({title: 'Computer Science'}, function (records) {
records[0].category.push('reference');
}, function (err) {
// ...
});
TODO: 接口感觉还是有点奇怪,有些人可能不好理解。
##Deleting
删除动作相对简单,直接删除查询出来的数据:
users.remove({age: {$gt: 12}}, function (err, rs) {
// ...
});
也可以整表删除:
users.remove();
TODO: 为了避免调用remove的时候,误操作没传查询条件,考虑把清除操作由clear方法代替,待定。
##Cursor Methods
Cursor Methods提供对查询结果集(数组)的常用操作的一系列方法,包括排序、过滤、计数等。
因为增删改等操作,都涉及到与数据源同步,大多数同步行为是异步的,而查询只操作内存数据,不涉及异步操作,所以也不一定要使用回调的方式处理查询结果。这样就可以让查询支持方法链形式。
var count = users.find({age: {$gt: 12}}).sort().limit(10).count();
Cursor Methods
Method Description
count() number of objects matching the query specified
filter() filter data from result data
limit(num)
skip(num)
sort(obj) sort by conditions
toArray()
##Reference
Reference用于建立相引用的数据表之间的关系。
// if now users data is [{name: 'Kar', _id:'bmFub2l0ZW1z69556912398376284'}]
books.insert({
title: 'cookbook',
author: {
$ref: 'users#bmFub2l0ZW1z69556912398376284',
}
}, function () {
var cookbook = books.find({title: 'cookbook'});
// result: {title: 'cookbook', author: {name: 'Kar', _id:'bmFub2l0ZW1z69556912398376284'}}
});
当引用的对象数据发生改变时候,所有引用这个对象数据的值也会更新。
$ref 操作符号语法
当前数据库,指定数据表名和id值或查询条件
{$ref: 'collectionName#id'} // reference with collection name and id value
{$ref: 'collectionName?condition=value'} // reference with collection name and condition
{$ref: 'collectionName[index]'} // reference with collection name an index
跨数据库的语法,待定
$dbName.collectionName#id
$dbName.collectionName?condition=value
$ref
的指向结果是唯一的,不必要支持复杂条件的指向,没有太多现实意义。
##Event subscriptions
数据库的所有操作,都会抛出相应的发生前和发生后事件。提供这一机制主要是为了解决,使用不同数据来源(本地存储、服务器端、文件流)的相同操作的行为触发。通过事件监听来实现与核心文件的解耦。
####事件监听
NanoDB支持的事件包括:insert
, update
, remove
, find
, clear
。每个事件均有before和after状态,分别表示事件触发前和事件触发后。
比如监听mydb数据库的insert
事件:
mydb.on('before:insert', function (ev) {
// ...
return true; // if return true, the event will stop
});
mydb.on('after:insert', function (ev) {
// ...
});
var scope = mydb; // event context
mydb.on('after:insert', function (ev) {
// ...
}, scope);
对于事件的before状态,可以通过return true
来中断后续动作的进行。
除了监听整个数据库,还可以只监听某个数据表,如:
users.on('after:insert', function (ev) {
// ...
});
等同于
mydb.on('after:users.insert', function (ev) {
// ...
});
####事件回调参数
事件回调的参数,第一个ev
,后续参数于事件所对应的方法的参数相同,比如insert
方法:
books.insert({title: 'NanoDB Guide'}, function (err) {
// result handle
});
books.on('after:insert', function (ev) {
console.log(arguments); // ['books.insert', {title: 'NanoDB Guide'}, function(err){}]
});
##自定义数据驱动
自定义驱动基于事件订阅。
function CustomDriver (host) {
this.init(host);
}
CustomDriver.prototype = {
init: function (host) {
host.on('after:insert', function () {
this.save(arguments..slice(1));
}, this);
// other event subscript
},
save: function () {
// do something to save data;
}
};
##Architecture