基于Node.js的NoSQL产品:FileDB V3.0开发完毕

FileDB前两版是基于Java和Servlet容器的,且只能现实简单的Key/Value数据存取。

V3.0版使用了Javascript语言重写代码,并进行了重新设计,运行环境改为了Node.js。

V3.0版功能有所增强,支持建任意多个表,前端还支持简单的SQL语句查询。

使用中建议配合JQuery库一起使用,想test.html中的一样。

数据库相关代码在附件中,感兴趣的同学可以下载下来测试下,也欢迎与我讨论。

数据库代码:

// This class parses the sql statement alike.
// Examples : create table abc; insert into abc(field, field2, field3) values('xxx', 123, 234);
//            update abc set field = 'xxx', field2 = 123 where [id] = 123;
//            select * from abc where [field] = 'xxx' and ([field2] >= 123 or [field3] < 234) order by field desc limit 0, 20
function Query(sql) {
    this.sql = sql.toLowerCase().replace(/\'/g, '"').replace(/;/g, '');
}
Query.prototype.exec = function() {
    if (this.sql.indexOf('create') != -1) {
        return JSON.stringify(this.create());
    } else if (this.sql.indexOf('insert') != -1) {
        return JSON.stringify(this.insert());
    } else if (this.sql.indexOf('update') != -1) {
        return JSON.stringify(this.update());
    } else if (this.sql.indexOf('select') != -1) {
        return JSON.stringify(this.select());
    } else if (this.sql.indexOf('show') != -1) {
        var str = '{';
        for (var tab in App.db.data) {
            str += '"' + tab + '":' + App.db.data[tab].data.length + ',';
        }
        str = (str.length > 1 ? str.slice(0, -1) : str) + '}';
        return str;
    } else {
        return 'false';
    }
};
Query.prototype.create = function() {
    var arr = this.sql.match(/table\s+(\w+)/); // regex is : table \s+ (\w+)
    if (!arr || arr.length != 2) return false;
    if (App.db.get(arr[1])) return false; // already exists.
    var tab = new Table(arr[1]);
    App.db.set(tab);
    return true;
};
Query.prototype.insert = function() {
    // regex is : (\w+) \( ([\w\s,]+) \) \s+ values \( ([\w\s,\"]+) \)
    var arr = this.sql.match(/(\w+)\s*\(([\w\s,]+)\)\s+values\(([\w\s,\"]+)\)/); 
    if (!arr || arr.length != 4) return false;
    var tab = new Table('rs').init(App.db.get(arr[1]));
    var fields = arr[2].split(/\s*,\s*/);
    var values = arr[3].split(/\s*,\s*/);
    var str = '{';
    for (var i = 0; i < fields.length; i++) {
        str += '"' + fields[i] + '":' + values[i] + ',';
    }
    str = str.slice(0, -1) + '}';
    var obj = JSON.parse(str);
    tab.insert(obj);
    Cache.change[arr[1]] = true; File.save();
    return true;
};
Query.prototype.update = function() {
    // regex is : update \s+ (\w+) \s+ set \s+ ([\w\s,=\'\"]+?) \s+ where \s+ ([\w\s=\[\]\'\"]+)
    var arr = this.sql.match(/update\s+(\w+)\s+set\s+([\w\s,=\'\"]+?)\s+where\s+([\w\s=<>\[\]\'\"]+)/);
    if (!arr || arr.length != 4) return false;
    var tab = new Table('rs').init(App.db.get(arr[1]));
    var sets = arr[2].split(/\s*,\s*/);
    var rs = tab.select(arr[3]);
    for (var i = rs.data.length - 1; i >= 0; i--) {
        for (var j = sets.length - 1; j >= 0; j--) {
            var kv = sets[j].split(/\s*=+\s*/);
            tab.update(rs.data[i].id, kv[0], isNaN(kv[1]) ? kv[1] : parseInt(kv[1]));
        }
    }
    Cache.change[arr[1]] = true; File.save();
    return true;
};
Query.prototype.select = function() {
    var limit = this.sql.split(/\s+limit\s+/);
    var order = limit[0].split(/\s+order\s+by\s+/);
    var where = order[0].split(/\s+where\s+/);
    var from = where[0].replace(/.+?from\s+/, '');
    if (!from.match(/[0-9A-z_]+/)) return false;
    if (Cache.change[from]) {
        Cache.change[from] = false;
        Cache.set(this.sql, null);
    }
    if (Cache.get(this.sql)) return Cache.get(this.sql);
    var tab = new Table('rs').init(App.db.get(from));
    tab = tab.select(where[1]).orderby(order[1]).limit(limit[1]);
    Cache.set(this.sql, tab);
    return tab;
};
exports.Query = Query;

function Cache() {
}
Cache.change = {}; // This property preserved the changed tables.
Cache.data = {}; // This property preserverd the cache data.
Cache.set = function(sql, tab) {
    Cache.data[sql] = tab;
}
Cache.get = function(sql) {
    return Cache.data[sql];
}

function DB(name) {
    this.name = name;
    this.data = {};
}
DB.prototype.set = function(tab) {
    this.data[tab.name] = tab;
};
DB.prototype.get = function(name) {
    return this.data[name];
};

function Table(name) {
    this.name = name;
    this.data = [];
}
Table.prototype.init = function(json) {
    this.name = json.name;
    this.data = json.data;
    return this;
};
Table.prototype.insert = function(obj) {
    obj.id = this.data.length;
    this.data.push(obj);
};
Table.prototype.update = function(id, k, v) {
    this.data[id][k] = v;
};
Table.prototype.select = function(con) {
    var rs = new Table('rs');
    if (!con) { rs.data = this.data; return rs; }
    con = con.replace(/and/g, '&&').replace(/or/g, '||').replace(/([^<>!=])=\s*/g, '$1 == ');
    con = con.replace(/\[\"?\'?(\w+)\"?\'?]/g, "this.data[i]['$1']"); // regex is : \[ \"? \'? (\w+) \"? \'? ]
    for (var i = this.data.length - 1; i >= 0; i--) {
        try {
            if (eval(con)) rs.data.push(this.data[i]);
        } catch(e) {
            console.log(new Date() + ' : 查询条件解析错误:' + e);
        }
    }
    return rs;
};
Table.prototype.orderby = function(str) {
    var rs = new Table('rs');
    rs.data = this.data;
    if (!str || rs.data.length < 2) return rs;
    var strs = str.split(/\s+/);
    rs.data.sort(function(a, b) {
        if (!isNaN(a[strs[0]]) && !isNaN(b[strs[0]])) {
            if (strs[1] && strs[1].toLowerCase() == 'desc') {
                return b[strs[0]] - a[strs[0]];
            } else {
                return a[strs[0]] - b[strs[0]];
            }
        } else {
            if (strs[1] && strs[1].toLowerCase() == 'desc') {
                return b[strs[0]].localeCompare(a[strs[0]]);
            } else {
                return a[strs[0]].localeCompare(b[strs[0]]);
            }
        }
    });
    return rs;
};
Table.prototype.limit = function(str) {
    var rs = new Table('rs');
    rs.data = this.data;
    if (!str) return rs;
    var strs = str.split(/,/); // separated by ","
    if (!strs[1]) { strs[1] = strs[0]; strs[0] = 0; }
    rs.data = this.data.slice(parseInt(strs[0]), parseInt(strs[0]) + parseInt(strs[1]));
    return rs;
};

function File() {
}
File.read = function(str) {
    File.time = new Date().getTime();
    var file = new DB('db');
    try {
        var fs = require('fs');
		var dbFile = fs.readFileSync('./' + str + '.txt', 'utf8');
        var dbObj = JSON.parse(dbFile);
        file.name = dbObj.name;
        file.data = dbObj.data;
	} catch (e) {
        console.log(new Date() + ' : Error : File ' + str + '.txt is not successfully loaded!');
	}
	return file;
};
File.write = function(db) {
    var data = JSON.stringify(db);
    var fs = require('fs');
    fs.writeFile('./' + db.name + '.txt', data, function (err) {
        if (err) console.log(new Date() + ' : Error : ' + db.name + ' is not successfully saved!');
    });
};
File.save = function() {
    var curTime = new Date().getTime();
    if (curTime - File.time > 10000) File.write(App.db); // If > 10s, then saved.
    File.time = curTime;
};

function App() {
}
App.run = function() {
    App.db = File.read('db'); // Defined a static property named 'db'.
};
exports.run = App.run;

 数据库代码二:

var http = require('http');
var url = require('url');
var fs = require('fs');
var path = require('path');
var db = require('./filedb3.js');

var mime = {
    "css": "text/css",
    "gif": "image/gif",
    "html": "text/html",
    "ico": "image/x-icon",
    "jpeg": "image/jpeg",
    "jpg": "image/jpeg",
    "js": "text/javascript",
    "json": "application/json",
    "pdf": "application/pdf",
    "png": "image/png",
    "svg": "image/svg+xml",
    "swf": "application/x-shockwave-flash",
    "tiff": "image/tiff",
    "txt": "text/plain",
    "wav": "audio/x-wav",
    "wma": "audio/x-ms-wma",
    "wmv": "video/x-ms-wmv",
    "xml": "text/xml"
};

var server = http.createServer(function(req, res) {
    if (req.url !== '/favicon.ico') {
        var urlObj = url.parse(req.url, true);
        var pathname = urlObj.pathname;
        if (pathname !== '/db.php') {
            var realPath = pathname.slice(1);
            path.exists(realPath, function (exists) {
                if (!exists) {
                    res.writeHead(404, {'Content-Type': 'text/plain'});
                    res.write('This request URL ' + pathname + ' was not found on this server.');
                    res.end();
                } else {
                    fs.readFile(realPath, 'binary', function (err, file) {
                        if (err) {
                            res.writeHead(500, {'Content-Type': 'text/plain'});
                            res.end(err);
                        } else {
                            var ext = path.extname(realPath);
                            ext = ext ? ext.slice(1) : 'unknown';
                            var contentType = mime[ext] || "text/plain";
                            res.writeHead(200, {'Content-Type': contentType});
                            res.write(file, "binary");
                            res.end();
                        }
                    });
                }
            });
        } else {
            var query = urlObj.query;
            res.writeHead(200, {'Content-Type': 'application/json'});
            var str = new db.Query(query.sql).exec();
            res.write(query.callback + '(' + str + ')');
            res.end();
        }
    } else {
        //res.writeHead(404, {'Content-Type': 'text/plain'});
    }
});
server.on('error', function(err) { 
    console.log(new Date() + ' : ' + err);
});
db.run();
server.listen(8080);
console.log(new Date() + ' : Server running at http://localhost:8080/');

测试代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>FileDB V3.0 测试页面</title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
	$('input[type="button"]').click(function() {
		var sql = $.trim($(this).prev().val()); //alert(sql);
		$.getJSON('http://localhost:8080/db.php?callback=?', {sql : sql}, function(data) {
			alert(JSON.stringify(data));
			//$('#result').html(JSON.stringify(data));
		});
	});
});
</script>
</head>

<body>
<h1>FileDB V3.0 测试页面</h1>
<form id="form1" name="form1" method="post" action="">
	<p>
		<input name="textfield" type="text" value="create table abc" />
		<input type="button" name="Submit" value="执行语句" />
	</p>
	<p>
		<input name="textfield2" type="text" value="insert into abc(name, age, job) values('yfs', 30, 'developer')" size="80" />
		<input type="button" name="Submit2" value="执行语句" />
</p>
	<p>
		<input name="textfield3" type="text" value="insert into abc(name, age, job) values('why', 28, 'developer')" size="80" />
		<input type="button" name="Submit3" value="执行语句" />
	</p>
	<p>
		<input name="textfield4" type="text" value="insert into abc(name, age, job) values('liyan', 27, 'developer')" size="80" />
		<input type="button" name="Submit4" value="执行语句" />
	</p>
	<p>
		<input name="textfield5" type="text" value="insert into abc(name, age, job) values('lipeng', 31, 'designer')" size="80" />
		<input type="button" name="Submit5" value="执行语句" />
	</p>
	<p>
		<input name="textfield6" type="text" value="select * from abc where [job] = 'developer' and ([age] &gt;= 30 or [age] &lt; 28) order by age desc limit 0, 1" size="120" />
		<input type="button" name="Submit6" value="执行语句" />
	</p>
	<p>
		<input name="textfield7" type="text" value="update abc set name = 'xxx', age = 123 where [name] = 'yfs' or [age] &lt;= 28" size="80" />
		<input type="button" name="Submit7" value="执行语句" />
	</p>
	<p>
		<input name="textfield8" type="text" value="show status" />
		<input type="button" name="Submit8" value="执行语句" />
	</p>
</form>
<p id="result"></p>
<p>PS :此NoSQL数据库仅支持基本的SQL语句关键字,不支持联合查询和SQL函数及in、like关键字。</p>
<p>PS2:where子句字段名需用中括号[]括起来,update语句必须带where子句。</p>
</body>
</html>
 

猜你喜欢

转载自treemp3.iteye.com/blog/1491682