Skip to content

Instantly share code, notes, and snippets.

@wiky
Created July 26, 2012 17:53
Show Gist options
  • Save wiky/3183494 to your computer and use it in GitHub Desktop.
Save wiky/3183494 to your computer and use it in GitHub Desktop.
NanoDB Design (draft)

#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

nanodb architecture

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment