thinkjs/lib/Lib/Core/Model.js
2014-01-26 10:48:34 +08:00

945 lines
32 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

var util = require('util');
var querystring = require('querystring');
//数据库配置
var dbConfigs = [];
//数据库实例化对象
var dbInstances = [];
//数据表的字段信息
var tableFields = {}
/**
* Model类
* @type {[type]}
*/
var Model = module.exports = Class(function(){
return {
initAttr: function(){
// 当前数据库操作对象
this.db = null;
// 主键名称
this.pk = "id";
// 数据库连接序号
this.linkNum = 0;
// 数据表前缀
this.tablePrefix = "";
// 模型名称
this.name = "";
// 数据库名称
this.dbName = "";
// 数据库配置
this.connection = "";
// 数据表名(不包含表前缀)
this.tableName = "";
// 实际数据表名(包含表前缀)
this.trueTableName = "";
// 字段信息
this.fields = {};
// 数据信息
this._data = {};
// 参数
this._options = {};
// 是否自动检测数据表字段信息
this.autoCheckFields = true;
// 初始化的promise
this.promise = null;
},
/**
* 取得DB类的实例对象 字段检查
* @access public
* @param string $name 模型名称
* @param string $tablePrefix 表前缀
* @param mixed $connection 数据库连接信息
*/
init: function(name, tablePrefix, connection){
this.initAttr();
// 获取模型名称
if (name) {
if (name.indexOf(".") > -1) {
name = name.split(".");
this.dbName = name[0];
this.name = name[1];
}else{
this.name = name;
}
}else if(!this.name){
this.getModelName();
}
// 设置表前缀
this.tablePrefix = tablePrefix === undefined ? (this.tablePrefix || C("db_prefix")) : tablePrefix;
//子类的init方法
this._init && this._init();
// 数据库初始化操作
// 获取数据库操作对象
// 当前模型有独立的数据库连接信息
this.promise = this.initDb(0, connection || this.connection);
},
/**
* 初始化数据库连接
* @access public
* @param integer $linkNum 连接序号
* @param mixed $config 数据库连接信息
* @param array $params 模型参数
* @return Model
*/
initDb: function(linkNum, config, params){
linkNum = linkNum | 0;
this.linkNum = linkNum;
if (!linkNum && this.db) {
return getPromise(this.db);
};
if (!dbInstances[linkNum] || config && dbConfigs[linkNum] != config) {
if (config && isString(config) && config.indexOf("/") == -1) {
config = C(config);
};
dbInstances[linkNum] = thinkRequire("Db").getInstance(config);
}
if (params) {
extend(this, params);
};
// 记录连接信息
dbConfigs[linkNum] = config;
this.db = dbInstances[linkNum];
if (this.name && this.autoCheckFields) {
return this.checkTableInfo();
};
return getPromise(this);
},
/**
* 获取模型名
* @access public
* @return string
*/
getModelName: function(){
if (this.name) {
return this.name;
};
var filename = this.__filename || __filename;
var name = filename.split("/").pop();
this.name = name.substr(0, name.length - 8);
return this.name;
},
/**
* 获取表名
* @return {[type]} [description]
*/
getTableName: function(){
if (!this.trueTableName) {
var tableName = this.tablePrefix || "";
tableName += this.tableName || parseName(this.getModelName());
this.trueTableName = tableName.toLowerCase();
};
var tableName = (this.dbName ? this.dbName + "." : "") + this.trueTableName;
return tableName;
},
/**
* 获取缓存数据表字段信息的缓存文件
* @return {[type]} [description]
*/
// getFieldsCacheFile: function(){
// var db = this.dbName || C('db_name');
// return '_fields/' + db + "." + this.getModelName().toLowerCase();
// },
/**
* 自动检测数据表信息
* @access protected
* @return void
*/
checkTableInfo: function(){
if (isEmpty(this.fields)) {
if (C('db_fields_cache')) {
var fields = tableFields[this.getTableName()];
if (fields) {
this.fields = fields;
return getPromise(fields);
};
};
// 每次都会读取数据表信息
return this.flushFields();
};
return getPromise(this.fields);
},
/**
* 刷新数据表字段信息
* @return promise [description]
*/
flushFields: function(){
this.db.setModel(this.name);
var self = this;
return this.getTableFields(this.getTableName(), true).then(function(fields){
self.fields = fields;
if (C('db_fields_cache')) {
tableFields[self.getTableName()] = fields;
};
return fields;
})
},
/**
* 获取数据表的字段
* @param {[type]} table
* @param {[type]} all
* @return {[type]}
*/
getTableFields: function(table, all){
if (table === true) {
table = undefined;
all = true;
};
if (table) {
return this.db.getFields(table).then(function(data){
var fields = {
"_field": Object.keys(data || {}),
"_autoinc": false,
"_unique": []
};
var types = {};
for(var key in data){
var val = data[key];
types[key] = val.type;
if (val.primary) {
fields['_pk'] = key;
if (val.autoinc) {
fields['_autoinc'] = true;
};
}else if (val.unique) {
fields._unique.push(key);
};
}
fields['_type'] = types;
return all ? fields : fields["_field"];
})
};
if (!isEmpty(this.fields)) {
return getPromise(all ? this.fields : this.fields["_field"]);
};
return this.getTableFields(this.getTableName(), all);
},
/**
* 获取类型为唯一的字段
* @return {[type]} [description]
*/
getUniqueField: function(data){
var unqiueFileds = this.fields._unique;
var unqiue = "";
unqiueFileds.some(function(item){
if (!data || data[item]) {
return unqiue = item;
};
});
return unqiue;
},
/**
* 获取上一次操作的sql
* @return {[type]} [description]
*/
getLastSql: function(){
return this.db.getLastSql();
},
/**
* 获取主键名称
* @access public
* @return string
*/
getPk: function(){
return this.fields['_pk'] || this.pk;
},
/**
* 缓存
* @param {[type]} key [description]
* @param {[type]} expire [description]
* @param {[type]} type [description]
* @return {[type]} [description]
*/
cache: function(key, expire, type){
if (key === undefined) {
return this;
};
if (isObject(key)) {
this._options.cache = key;
}else{
//如果没有key则根据sql语句自动生成
if (isNumber(key)) {
type = expire;
expire = key;
key = "";
};
this._options.cache = {
key: key,
expire: expire,
type: type
};
}
return this;
},
/**
* 指定查询数量
* @param {[type]} offset [description]
* @param {[type]} length [description]
* @return {[type]} [description]
*/
limit: function(offset, length){
this._options.limit = length === undefined ? offset : offset + "," + length;
return this;
},
/**
* 指定分页
* @return {[type]} [description]
*/
page: function(page, listRows){
this._options.page = listRows === undefined ? page : page + "," + listRows;
return this;
},
/**
* where条件
* @return {[type]} [description]
*/
where: function(where){
if (where && isString(where)) {
where = {_string: where};
};
this._options.where = extend(this._options.where || {}, where);
return this;
},
/**
* 要查询的字段
* @param {[type]} field [description]
* @param {[type]} reverse [description]
* @return {[type]} [description]
*/
field: function(field, reverse){
if (isArray(field)) {
field = field.join(",")
}else if (!field) {
field = "*";
};
this._options.field = field;
this._options.fieldReverse = reverse;
return this;
},
/**
* 联合查询
* @return {[type]} [description]
*/
union: function(union){
if (!this._options.union) {
this._options.union = [];
};
this._options.union.push(union);
return this;
},
/**
* 联合查询
* @param {[type]} join [description]
* @return {[type]} [description]
*/
join: function(join){
if (isArray(join)) {
this._options.join = join;
}else{
if (!this._options.join) {
this._options.join = [];
};
this._options.join.push(join);
}
return this;
},
/**
* 生成查询SQL 可用于子查询
* @param {[type]} options [description]
* @return {[type]} [description]
*/
buildSql: function(options){
var self = this;
return this.parseOptions(options).then(function(options){
return "( " + self.db.buildSelectSql(options) + " )";
})
},
/**
* 解析参数在this.promise后执行
* @param {[type]} options [description]
* @return promise [description]
*/
parseOptions: function(options, extraOptions){
var self = this;
options = this.parseWhereOptions(options);
options = extend(this._options, options, extraOptions);
// 查询过后清空sql表达式组装 避免影响下次查询
this._options = {};
var promise = null;
//获取数据表下的字段信息
if (options.table) {
promise = this.getTableFields(options.table);
}else{
options.table = this.getTableName();
if (isEmpty(self.fields["_field"])) {
promise = this.promise.then(function(){
return self.fields["_field"];
});
}else{
promise = getPromise(self.fields["_field"]);
}
}
//数据表别名
if (options.alias) {
options.table += " " + options.alias;
};
options.model = this.name;
return promise.then(function(fields){
// 字段类型验证
if (options.where && isObject(options.where) && !isEmpty(fields)) {
// 对数组查询条件进行字段类型检查
for(var key in options.where){
var val = options.where[key];
key = key.trim();
if (fields.indexOf(key) > -1) {
if (isScalar(val)) {
options.where = self.parseType(options.where, key);
};
}else if(key.substr(0, 1) !== "_" && !(/[\.\|\&]/.test(key))){
delete options.where[key];
}
}
};
//field反选
if (options.field && options.fieldReverse) {
var optionsField = options.field.split(',');
options.field = fields.filter(function(item){
if (optionsField.indexOf(item) > -1) {
return;
};
return item;
}).join(',');
};
options = self._optionsFilter(options, fields);
return options;
});
},
/**
* 选项过滤器
* 具体的Model类里进行实现
* @param {[type]} options [description]
* @return {[type]} [description]
*/
_optionsFilter: function(options, fields){
return options;
},
/**
* 数据类型检测
* @param {[type]} data [description]
* @param {[type]} key [description]
* @return {[type]} [description]
*/
parseType: function(data, key){
var fieldType = this.fields["_type"][key] || "";
if (fieldType.indexOf("bigint") === -1 && fieldType.indexOf("int") > -1) {
data[key] = parseInt(data[key], 10) || 0;
}else if(fieldType.indexOf("double") > -1 || fieldType.indexOf("float") > -1){
data[key] = parseFloat(data[key]) || 0.0;
}else if(fieldType.indexOf('bool') > -1){
data[key] = !! data[key];
}
return data;
},
/**
* 对插入到数据库中的数据进行处理要在parseOptions后执行
* @param {[type]} data [description]
* @return {[type]} [description]
*/
parseData: function(data){
data = extend({}, data);
if (!isEmpty(this.fields)) {
for(var key in data){
var val = data[key];
if (this.fields["_field"].indexOf(key) === -1) {
delete data[key];
}else if(isScalar(val)){
data = this.parseType(data, key);
}
}
};
//安全过滤
if (typeof this._options.filter == 'function') {
for(var key in data){
data[key] = this._options.filter.call(this, key, data[key]);
}
delete this._options.filter;
};
data = this._dataFilter(data);
return data;
},
/**
* 数据过滤器
* 具体的Model类里进行实现
* @param {[type]} data [description]
* @return {[type]} [description]
*/
_dataFilter: function(data){
return data;
},
/**
* 数据插入之前操作可以返回一个promise
* @param {[type]} data [description]
* @param {[type]} options [description]
* @return {[type]} [description]
*/
_beforeAdd: function(data, options){
return data;
},
/**
* 数据插入之后操作可以返回一个promise
* @param {[type]} data [description]
* @param {[type]} options [description]
* @return {[type]} [description]
*/
_afterAdd: function(data, options){
return data;
},
/**
* 添加一条数据
* @param {[type]} data [description]
* @param {[type]} options [description]
* @param int 返回插入的id
*/
add: function(data, options, replace){
//copy data
data = extend({}, data);
if (isEmpty(data)) {
if (this._data) {
data = this._data;
this._data = {};
}else{
return getPromise(L("_DATA_TYPE_INVALID_"), true);
}
};
var self = this;
//解析后的选项
var parsedOptions = {};
//解析后的数据
var parsedData = {};
return this.parseOptions(options).then(function(options){
parsedOptions = options;
return self._beforeAdd(data, parsedOptions);
}).then(function(data){
parsedData = data;
data = self.parseData(data);
return self.db.insert(data, parsedOptions, replace);
}).then(function(){
parsedData[self.getPk()] = self.db.getLastInsertId();
return self._afterAdd(parsedData, parsedOptions);
}).then(function(){
return parsedData[self.getPk()];
});
},
/**
* 插入多条数据
* @param {[type]} data [description]
* @param {[type]} options [description]
* @param {[type]} replace [description]
*/
addAll: function(data, options, replace){
if (!isArray(data) || !isObject(data[0])) {
return getPromise(L("_DATA_TYPE_INVALID_"), true);
};
var self = this;
return this.parseOptions(options).then(function(options){
return self.db.insertAll(data, options, replace);
}).then(function(){
return self.db.getLastInsertId();
})
},
/**
* 删除后续操作
* @return {[type]} [description]
*/
_afterDelete: function(data, options){
return data;
},
/**
* 删除数据
* @return {[type]} [description]
*/
delete: function(options){
var self = this;
var parsedOptions = {};
return this.parseOptions(options).then(function(options){
parsedOptions = options;
return self.db.delete(options);
}).then(function(affectedRows){
return self._afterDelete(parsedOptions.where || {}, parsedOptions);
});
},
/**
* 更新前置操作
* @param {[type]} data [description]
* @param {[type]} options [description]
* @return {[type]} [description]
*/
_beforeUpdate: function(data, options){
return data;
},
/**
* 更新后置操作
* @param {[type]} data [description]
* @param {[type]} options [description]
* @return {[type]} [description]
*/
_afterUpdate: function(data, options){
return data;
},
/**
* 更新数据
* @return {[type]} [description]
*/
update: function(data, options){
data = extend({}, data);
if (isEmpty(data)) {
if (this._data) {
data = this._data;
this._data = {};
}else{
return getPromise(L("_DATA_TYPE_INVALID_"), true);
}
};
var self = this;
var pk = self.getPk();
var parsedOptions = {};
var parsedData = {};
var affectedRows = 0;
return this.parseOptions(options).then(function(options){
parsedOptions = options;
return self._beforeUpdate(data, options);
}).then(function(data){
parsedData = data;
data = self.parseData(data);
if (isEmpty(parsedOptions.where) || isEmpty(parsedOptions.where[pk])) {
// 如果存在主键数据 则自动作为更新条件
if (!isEmpty(data[pk])) {
parsedOptions.where = getObject(pk, data[pk]);
delete data[pk];
}else{
return getPromise(L("_OPERATION_WRONG_"), true);
}
}else{
parsedData[pk] = parsedOptions.where[pk];
}
return self.db.update(data, parsedOptions);
}).then(function(data){
affectedRows = data.affectedRows;
return self._afterUpdate(parsedData, parsedOptions);
}).then(function(){
return affectedRows;
})
},
/**
* 更新多个数据,自动用主键作为查询条件
* @param {[type]} dataList [description]
* @return {[type]} [description]
*/
updateAll: function(dataList){
if (!isArray(dataList) || !isObject(dataList[0])) {
return getPromise(L("_DATA_TYPE_INVALID_"), true);
};
var promises = [];
var self = this;
dataList.forEach(function(data){
promises.push(self.update(data));
});
return Promise.all(promises);
},
/**
* 更新某个字段的值
* @param {[type]} field [description]
* @param {[type]} value [description]
* @return {[type]} [description]
*/
updateField: function(field, value){
var data = {};
if (isObject(field)) {
data = field;
}else{
data[field] = value;
}
return this.update(data);
},
/**
* 字段值增长
* @return {[type]} [description]
*/
updateInc: function(field, step){
step = parseInt(step, 10) || 1;
return this.updateField(field, ["exp", field + "+" + step]);
},
/**
* 字段值减少
* @return {[type]} [description]
*/
updateDec: function(field, step){
step = parseInt(step, 10) || 1;
return this.updateField(field, ["exp", field + "-" + step]);
},
/**
* 解析options中简洁的where条件
* @return {[type]} [description]
*/
parseWhereOptions: function(options){
if (isNumber(options) || isString(options)) {
var pk = this.getPk();
options += "";
var where = {};
if (options.indexOf(",") > -1) {
where[pk] = ["IN", options];
}else{
where[pk] = options;
}
options = {
where: where
};
}
return options || {};
},
/**
* find查询后置操作
* @return {[type]} [description]
*/
_afterFind: function(result, options){
return result;
},
/**
* 查询一条数据
* @return 返回一个promise
*/
find: function(options){
var self = this;
var parsedOptions = {};
return this.parseOptions(options, {
limit: 1
}).then(function(options){
parsedOptions = options;
return self.db.select(options);
}).then(function(data){
return self._afterFind(data[0] || {}, parsedOptions);
})
},
/**
* 查询后置操作
* @param {[type]} result [description]
* @param {[type]} options [description]
* @return {[type]} [description]
*/
_afterSelect: function(result, options){
return result;
},
/**
* 查询数据
* @return 返回一个promise
*/
select: function(options){
var self = this;
var parsedOptions = {};
return this.parseOptions(options).then(function(options){
parsedOptions = options;
return self.db.select(options);
}).then(function(result){
return self._afterSelect(result, parsedOptions);
});
},
selectAdd: function(fields, table, options){
var self = this;
return this.parseOptions(options).then(function(options){
fields = fields || options.field;
table = table || self.getTableName();
return self.db.selectInsert(fields, table, options);
});
},
/**
* 获取一条记录的某个字段值
* @return {[type]} [description]
*/
getField: function(field, sepa){
field = field.trim();
var self = this;
var multi = false;
return this.parseOptions({
"field": field
}).then(function(options){
if (field.indexOf(",") > -1) {
if (options.limit === undefined && isNumber(sepa)) {
options.limit = sepa;
};
multi = true;
}else{
options.limit = isNumber(sepa) ? sepa : 1;
}
return self.db.select(options);
}).then(function(data){
if (multi) {
var length = field.split(",").length;
var field = Object.keys(data[0] || {});
var key = field.shift();
var key2 = field.shift();
var cols = {};
data.forEach(function(item){
var name = item[key];
if (length === 2) {
cols[name] = item[key2];
}else{
cols[name] = isString(sepa) ? item.join(sepa) : item;
}
});
return cols;
}else{
if (sepa !== true && options.limit == 1) {
return data[0];
};
return Object.values(data[0] || {})[0];
}
})
},
/**
* 根据某个字段值获取一条数据
* @param {[type]} name [description]
* @param {[type]} value [description]
* @return {[type]} [description]
*/
getBy: function(name, value){
var where = getObject(name, value);
return this.where(where).find();
},
/**
* SQL查询
* @return {[type]} [description]
*/
query: function(sql, parse){
if (parse !== undefined && !isBoolean(parse) && !isArray(parse)) {
parse = [].slice.call(arguments);
parse.shift();
};
var self = this;
return this.parseSql(sql, parse).then(function(sql){
return self.db.query(sql);
})
},
/**
* 执行SQL语法非查询类的SQL语句返回值为影响的行数
* @param {[type]} sql [description]
* @param {[type]} parse [description]
* @return {[type]} [description]
*/
execute: function(sql, parse){
if (parse !== undefined && !isBoolean(parse) && !isArray(parse)) {
parse = [].slice.call(arguments);
parse.shift();
};
var self = this;
return this.parseSql(sql, parse).then(function(sql){
return self.db.execute(sql);
})
},
/**
* 解析SQL语句
* @return promise [description]
*/
parseSql: function(sql, parse){
var promise = null;
var self = this;
if (parse === true) {
promise = this.parseOptions().then(function(options){
return self.db.parseSql(options);
})
}else{
parse = isArray(parse) ? parse : [parse];
parse.unshift(sql);
sql = util.format.apply(null, parse);
var map = {
"__TABLE__": this.getTableName(),
"__PREFIX__": C('db_prefix')
};
sql = sql.replace(/__[A-Z]+__/g, function(a){
return map[a] || a;
})
promise = getPromise(sql);
}
this.db.setModel(this.name);
return promise;
},
/**
* 设置数据对象值
* @return {[type]} [description]
*/
data: function(data){
if (data === true) {
return this._data;
};
if (isString(data)) {
data = querystring.parse(data);
};
this._data = data;
return this;
},
/**
* 设置操作选项
* @param {[type]} options [description]
* @return {[type]} [description]
*/
options: function(options){
if (options === true) {
return this._options;
};
this._options = options;
return this;
},
/**
* 关闭数据库连接
* @return {[type]} [description]
*/
close: function(){
delete dbInstances[this.linkNum];
delete dbConfigs[this.linkNum];
this.db && this.db.close();
}
}
}).extend(function(){
//追加的方法
var methods = {};
// 链操作方法列表
var methodNameList = [
'table','order','alias','having','group',
'lock','distinct','auto','filter','validate'
];
methodNameList.forEach(function(item){
methods[item] = function(data){
this._options[item] = data;
return this;
}
});
['count','sum','min','max','avg'].forEach(function(item){
methods[item] = function(data){
var field = data || "*";
return this.getField(item.toUpperCase() + "(" + field + ") AS thinkjs_" + item, true);
}
});
//方法别名
var aliasMethodMap = {
update: "save",
updateField: "setField",
updateInc: "setInc",
updateDec: "setDec"
};
Object.keys(aliasMethodMap).forEach(function(key){
var value = aliasMethodMap[key];
methods[value] = function(){
return this[key].apply(this, arguments);
}
})
return methods;
});
/**
* 关闭所有的数据库连接
* @return {[type]} [description]
*/
Model.close = function(){
dbInstances.forEach(function(db){
db.close();
});
dbInstances.length = 0;
dbConfigs.length = 0;
}