@teamnet/ic-orm
Version: 
Database Management System for Total.js v4 and standalone
991 lines (835 loc) • 26.8 kB
JavaScript
const Database = require('mysql2');
const REG_PARAMS = /\$\d/g;
const EMPTYARRAY = [];
const BLACKLIST = { dbms: 1 };
const ISOP = { '+': 1, '-': 1, '*': 1, '/': 1, '=': 1, '!': 1, '#': 1 };
const CANSTATS = global.F ? (global.F.stats && global.F.stats.performance && global.F.stats.performance.dbrm != null) : false;
const CACHEOPT = {};
const POOLS = {};
var ESCAPE = global.MYSQL_ESCAPE = function(value) {
	if (value == null)
		return 'null';
	var type = typeof(value);
	if (type === 'function') {
		value = value();
		if (value == null)
			return 'null';
		type = typeof(value);
	}
	if (type === 'boolean')
		return value === true ? 'true' : 'false';
	if (type === 'number')
		return value.toString();
	if (type === 'string')
		return Database.escape(value);
	if (value instanceof Array)
		return Database.escape(value.join(','));
	if (value instanceof Date)
		return Database.escape(dateToString(value));
	return Database.escape(value.toString());
};
function select(client, cmd) {
	var builder = cmd.builder;
	var opt = builder.options;
	var params = [];
	var q = 'SELECT ' + FIELDS(builder) + ' FROM ' + opt.table + WHERE(builder, null, null, params);
	F.$events.dbms && EMIT('dbms', 'select', opt.table, opt.db, builder);
	builder.db.$debug && builder.db.$debug(q);
	if (CANSTATS)
		F.stats.performance.dbrm++;
	client.query(q, params, function(err, response) {
		builder.db.busy = false;
		err && client.$opt.onerror && client.$opt.onerror(err, q, builder);
		var rows = response ? response : EMPTYARRAY;
		if (opt.first)
			rows = rows.length ? rows[0] : null;
		// checks joins
		if (!err && builder.$joins) {
			client.$dbms._joins(rows, builder);
			setImmediate(builder.db.$next);
		} else
			builder.$callback(err, rows);
	});
}
function check(client, cmd) {
	var builder = cmd.builder;
	var opt = builder.options;
	var params = [];
	var q = 'SELECT 1 FROM ' + opt.table + WHERE(builder, null, null, params);
	F.$events.dbms && EMIT('dbms', 'select', opt.table, opt.db, builder);
	if (CANSTATS)
		F.stats.performance.dbrm++;
	builder.db.$debug && builder.db.$debug(q);
	client.query(q, params, function(err, response) {
		builder.db.busy = false;
		err && client.$opt.onerror && client.$opt.onerror(err, q, builder);
		var is = response ? response[0] != null : false;
		builder.$callback(err, is);
	});
}
function query(client, cmd) {
	var builder = cmd.builder;
	var opt = builder.options;
	if (!cmd.value && builder.options.params)
		cmd.value = [];
	var q = cmd.query + WHERE(builder, null, null, cmd.value);
	builder.db.$debug && builder.db.$debug(q);
	if (CANSTATS)
		F.stats.performance.dbrm++;
	F.$events.dbms && EMIT('dbms', 'query', cmd.query, opt.db, builder);
	client.query(q, cmd.value, function(err, response) {
		builder.db.busy = false;
		err && client.$opt.onerror && client.$opt.onerror(err, q, builder);
		var rows = response || EMPTYARRAY;
		if (opt.first)
			rows = rows.length ? rows[0] : null;
		builder.$callback(err, rows);
	});
}
function command(client, sql, cmd) {
	cmd.db.$debug && cmd.db.$debug(sql);
	F.$events.dbms && EMIT('dbms', 'query', sql, cmd.db);
	if (CANSTATS)
		F.stats.performance.dbrm++;
	client.query(sql, function(err) {
		cmd.db.busy = false;
		err && client.$opt.onerror && client.$opt.onerror(err, sql);
		cmd.db.$next(err);
	});
}
function list(client, cmd) {
	var builder = cmd.builder;
	var opt = builder.options;
	var params = [];
	var query =  WHERE(builder, true, null, params);
	var q;
	if (cmd.improved && builder.skip) {
		q = 'SELECT ' + FIELDS(builder) + ' FROM ' + opt.table + query + OFFSET(builder);
		F.$events.dbms && EMIT('dbms', 'select', opt.table, opt.db, builder);
		builder.db.$debug && builder.db.$debug(q);
		builder.db.busy = true;
		if (CANSTATS)
			F.stats.performance.dbrm++;
		client.query(q, params, function(err, response) {
			builder.db.busy = false;
			var rows = response || [];
			if (!err && builder.$joins) {
				client.$dbms._joins(rows, builder, rows.length);
				setImmediate(builder.db.$next);
			} else
				builder.$callback(err, rows, rows.length);
		});
	} else {
		q = 'SELECT COUNT(1) as dbmsvalue FROM ' + opt.table + query;
		builder.db.$debug && builder.db.$debug(q);
		F.$events.dbms && EMIT('dbms', 'select', opt.table, opt.db, builder);
		if (CANSTATS)
			F.stats.performance.dbrm++;
		client.query(q, params, function(err, response) {
			builder.db.busy = false;
			err && client.$opt.onerror && client.$opt.onerror(err, q, builder);
			var count = err ? 0 : response ? response[0].dbmsvalue : 0;
			var fn = function(err, response) {
				builder.db.busy = false;
				var rows = response || [];
				// checks joins
				// client.$dbms._joins(rows, builder);
				if (!err && builder.$joins) {
					client.$dbms._joins(rows, builder, count);
					setImmediate(builder.db.$next);
				} else
					builder.$callback(err, rows, count);
			};
			if (count) {
				builder.db.busy = true;
				q = 'SELECT ' + FIELDS(builder) + ' FROM ' + opt.table + query + OFFSET(builder);
				builder.db.$debug && builder.db.$debug(q);
				F.$events.dbms && EMIT('dbms', 'select', opt.table, opt.db, builder);
				if (CANSTATS)
					F.stats.performance.dbrm++;
				client.query(q, params, fn);
			} else
				fn(err, null);
		});
	}
}
function scalar(client, cmd) {
	var builder = cmd.builder;
	var opt = builder.options;
	var params = [];
	var q;
	switch (cmd.scalar) {
		case 'avg':
		case 'min':
		case 'sum':
		case 'max':
		case 'count':
			q = 'SELECT ' + cmd.scalar.toUpperCase() + (cmd.scalar !== 'count' ? ('(' + (cmd.field || cmd.name) + ')') : '(1)') + ' as dbmsvalue FROM ' + opt.table;
			break;
		case 'group':
			q = 'SELECT ' + cmd.name + ', ' + (cmd.field ? ('SUM(' + cmd.field + ')') : 'COUNT(1)') + ' as count FROM ' + opt.table;
			break;
	}
	q = q + WHERE(builder, false, cmd.scalar === 'group' ? cmd.name : null, params);
	builder.db.$debug && builder.db.$debug(q);
	F.$events.dbms && EMIT('dbms', 'select', opt.table, opt.db, builder);
	if (CANSTATS)
		F.stats.performance.dbrm++;
	client.query(q, params, function(err, response) {
		builder.db.busy = false;
		err && client.$opt.onerror && client.$opt.onerror(err, q, builder);
		var rows = response || EMPTYARRAY;
		if (!cmd.field && cmd.scalar !== 'group')
			rows = rows.length ? (rows[0].dbmsvalue || 0) : 0;
		builder.$callback(err, rows);
	});
}
function abortcommands(client, builder) {
	while (builder.db.$commands.length) {
		var c = builder.db.$commands.shift();
		if (c && c.type === 'commit') {
			c.type = 'rollback';
			builder.db.$commands.unshift(c);
			break;
		}
	}
}
function insert(client, cmd) {
	var builder = cmd.builder;
	cmd.builder.options.transform && cmd.builder.options.transform(cmd.builder.value, cmd.builder.db.$output, cmd.builder.db.$lastoutput);
	var keys = Object.keys(builder.value);
	var params = [];
	var fields = [];
	var values = [];
	var opt = builder.options;
	for (var i = 0; i < keys.length; i++) {
		var key = keys[i];
		var val = builder.value[key];
		if (val === undefined || BLACKLIST[key])
			continue;
		if (builder.options.fields && builder.options.fields.length) {
			var skip = true;
			for (var j = 0; j < builder.options.fields.length; j++) {
				var field = builder.options.fields[j];
				if (field[0] === '-') {
					field = field.substring(1);
					if (field === key || (ISOP[key[0]] && field === key.substring(1))) {
						skip = true;
						break;
					}
					skip = false;
				} else if (field === key || (ISOP[key[0]] && field === key.substring(1))) {
					skip = false;
					break;
				} else
					skip = false;
			}
			if (skip)
				continue;
		}
		var raw = false;
		switch (key[0]) {
			case '-':
			case '+':
			case '*':
			case '/':
			case '>':
			case '<':
				key = key.substring(1);
				break;
			case '=':
				key = key.substring(1);
				raw = true;
				break;
			case '#':
				continue;
			case '!':
				// toggle
				key = key.substring(1);
				if (val)
					val = true;
				else
					val = false;
				break;
		}
		fields.push('`' + key + '`');
		if (raw) {
			values.push(val);
		} else {
			values.push('?');
			params.push(val == null ? null : typeof(val) === 'function' ? val(builder.value) : val);
		}
	}
	var q = 'INSERT INTO ' + opt.table + ' (' + fields.join(',') + ') VALUES(' + values.join(',') + ')';// + (builder.$primarykey ? (' RETURNING ' + builder.$primarykey) : '');
	builder.db.$debug && builder.db.$debug(q);
	F.$events.dbms && EMIT('dbms', 'insert', opt.table, opt.db, builder);
	if (CANSTATS)
		F.stats.performance.dbwm++;
	client.query(q, params, function(err, response) {
		// Transaction is aborted
		if (err && client.$dbmstransaction) {
			client.$dbmstransaction = true;
			abortcommands(client, builder);
		}
		builder.db.busy = false;
		err && client.$opt.onerror && client.$opt.onerror(err, q, builder);
		builder.$callback(err, err == null ? (response && response.length ? response[0][builder.$primarykey] : 1) : 0);
	});
}
function insertexists(client, cmd) {
	var builder = cmd.builder;
	var opt = builder.options;
	var q = 'SELECT 1 as dbmsvalue FROM ' + opt.table + WHERE(builder);
	builder.db.$debug && builder.db.$debug(q);
	F.$events.dbms && EMIT('dbms', 'select', opt.table, opt.db, builder);
	if (CANSTATS)
		F.stats.performance.dbrm++;
	client.query(q, function(err, response) {
		builder.db.busy = false;
		err && client.$opt.onerror && client.$opt.onerror(err, q, builder);
		var rows = response || EMPTYARRAY;
		if (rows.length)
			builder.$callback(err, 0);
		else
			insert(client, cmd);
	});
}
function modify(client, cmd) {
	cmd.builder.options.transform && cmd.builder.options.transform(cmd.builder.value, cmd.builder.db.$output, cmd.builder.db.$lastoutput);
	var keys = Object.keys(cmd.builder.value);
	var fields = [];
	var params = [];
	for (var i = 0; i < keys.length; i++) {
		var key = keys[i];
		var val = cmd.builder.value[key];
		if (val === undefined || BLACKLIST[key])
			continue;
		if (cmd.builder.options.equal && cmd.builder.options.equal.indexOf(key) !== -1)
			continue;
		if (cmd.builder.options.fields && cmd.builder.options.fields.length) {
			var skip = true;
			for (var j = 0; j < cmd.builder.options.fields.length; j++) {
				var field = cmd.builder.options.fields[j];
				if (field[0] === '-') {
					field = field.substring(1);
					if (field === key || (ISOP[key[0]] && field === key.substring(1))) {
						skip = true;
						break;
					}
					skip = false;
				} else if (field === key || (ISOP[key[0]] && field === key.substring(1))) {
					skip = false;
					break;
				} else
					skip = false;
			}
			if (skip)
				continue;
		}
		var c = key[0];
		var type;
		if (typeof(val) === 'function')
			val = val(cmd.builder.value);
		switch (c) {
			case '-':
			case '+':
			case '*':
			case '/':
				params.push(val ? val : 0);
				key = key.substring(1);
				type = '`' + key + '`=COALESCE(' + key + ',0)' + c + '?';
				break;
			case '>':
			case '<':
				params.push(val ? val : 0);
				key = key.substring(1);
				type = '`' + key + '`=' + (c === '>' ? 'GREATEST' : 'LEAST') + '(COALESCE(' + key + ',0),?)';
				break;
			case '!':
				// toggle
				key = key.substring(1);
				type = '`' + key + '`=NOT ' + key;
				break;
			case '=':
			case '#':
				// raw
				type = '`' + key.substring(1) + '`=' + val;
				break;
			default:
				params.push(val);
				type = '`' + key + '`=?';
				break;
		}
		type && fields.push(type);
	}
	var builder = cmd.builder;
	var opt = builder.options;
	if (opt.equal) {
		for (var i = 0; i < opt.equal.length; i++)
			cmd.builder.where(opt.equal[i], builder.value[opt.equal[i]]);
	}
	var q = 'UPDATE ' + opt.table + ' SET ' + fields + WHERE(builder, true, null, params);
	builder.db.$debug && builder.db.$debug(q);
	F.$events.dbms && EMIT('dbms', 'update', opt.table, opt.db, cmd.builder);
	if (CANSTATS)
		F.stats.performance.dbwm++;
	client.query(q, params, function(err, response) {
		// Transaction is aborted
		if (err && client.$dbmstransaction) {
			client.$dbmstransaction = true;
			abortcommands(client, builder);
		}
		builder.db.busy = false;
		err && client.$opt.onerror && client.$opt.onerror(err, q, builder);
		var rows = response || EMPTYOBJECT;
		rows = rows.affectedRows || 0;
		if (!rows && cmd.insert) {
			if (cmd.insert !== true)
				cmd.builder.value = cmd.insert;
			cmd.builder.options.insert && cmd.builder.options.insert(cmd.builder.value, cmd.builder.options.insertparams);
			insert(client, cmd);
		} else
			builder.$callback(err, rows);
	});
}
function remove(client, cmd) {
	var builder = cmd.builder;
	var opt = builder.options;
	var params = [];
	var q = 'DELETE FROM ' + opt.table + WHERE(builder, true, null, params);
	builder.db.$debug && builder.db.$debug(q);
	F.$events.dbms && EMIT('dbms', 'delete', opt.table, opt.db, builder);
	if (CANSTATS)
		F.stats.performance.dbwm++;
	client.query(q, params, function(err, response) {
		// Transaction is aborted
		if (err && client.$dbmstransaction) {
			client.$dbmstransaction = true;
			abortcommands(client, builder);
		}
		builder.db.busy = false;
		err && client.$opt.onerror && client.$opt.onerror(err, q, builder);
		var rows = response || EMPTYOBJECT;
		rows = rows.affectedRows || 0;
		builder.$callback(err, rows);
	});
}
function destroy(conn) {
	if (conn) {
		if (conn.$pool) {
			if (conn.$dbmstransaction)
				conn.$dbmstransaction = false;
			conn.$pool && conn.$pool.releaseConnection(conn);
			conn.$pool = null;
		} else
			conn.destroy();
	}
}
function clientcommand(cmd, client, self) {
	switch (cmd.type) {
		case 'transaction':
			client.$dbmstransaction = true;
			command(client, 'BEGIN', cmd);
			break;
		case 'end':
			cmd.type = self.$eb ? self.$errors.items.length ? 'ROLLBACK' : self.$errors.length ? 'ROLLBACK' : 'COMMIT' : 'COMMIT';
			command(client, cmd.type, cmd);
			break;
		case 'commit':
		case 'rollback':
			client.$dbmstransaction = false;
			command(client, cmd.type.toUpperCase(), cmd);
			break;
		case 'find':
		case 'read':
			select(client, cmd);
			break;
		case 'diff':
			var cb = cmd.builder.$callback;
			cmd.builder.$callback = function(err, response) {
				cb.call(cmd.builder, err, err ? EMPTYOBJECT : DIFFARR(cmd.key, response, cmd.form));
			};
			select(client, cmd);
			break;
		case 'modify2':
			var cb = cmd.builder.$callback;
			cmd.builder.$callback = function(err, response) {
				cmd.builder.options.fields = null;
				if (err) {
					cb.call(cmd.builder, err, 0);
				} else if (response) {
					cmd.builder.db.busy = true;
					var mod = cmd.fn(response, cmd.builder.db.$output, cmd.builder.db.$outputall);
					cmd.builder.db.busy = false;
					if (mod) {
						cmd.builder.value = mod;
						cmd.builder.$callback = cb;
						if (cmd.builder.value.$clean)
							cmd.builder.value = cmd.builder.value.$clean();
						modify(client, cmd);
					} else
						cb.call(cmd.builder, err, 0);
				} else {
					if (cmd.insert) {
						cmd.builder.db.busy = true;
						mod = cmd.fn(null, cmd.builder.db.$output, cmd.builder.db.$outputall);
						cmd.builder.db.busy = false;
						if (mod) {
							cmd.builder.value = mod;
							cmd.builder.$callback = cb;
							insert(client, cmd);
						} else
							cb.call(cmd.builder, err, 0);
					} else {
						cb.call(cmd.builder, err, 0);
					}
				}
			};
			select(client, cmd);
			break;
		case 'check':
			check(client, cmd);
			break;
		case 'list':
			list(client, cmd);
			break;
		case 'scalar':
			scalar(client, cmd);
			break;
		case 'insert':
			if (cmd.unique)
				insertexists(client, cmd);
			else
				insert(client, cmd);
			break;
		case 'update':
		case 'modify':
			modify(client, cmd);
			break;
		case 'remove':
			remove(client, cmd);
			break;
		case 'query':
			query(client, cmd);
			break;
		default:
			cmd.builder.$callback(new Error('Operation "' + cmd.type + '" not found'));
			break;
	}
}
exports.run = function(opt, self, cmd, repeated) {
	var conn = self.$conn[opt.id];
	if (conn) {
		clientcommand(cmd, conn, self);
		return;
	}
	if (opt.options.pooling) {
		conn = POOLS[opt.id];
		if (!conn) {
			var optconn = CLONE(opt.options);
			if (optconn.max) {
				optconn.connectionLimit = optconn.max;
				delete optconn.max;
			}
			delete optconn.min;
			delete optconn.pooling;
			conn = POOLS[opt.id] = Database.createPool(optconn);
		}
	} else {
		var optconn = CACHEOPT[opt.id];
		if (!optconn) {
			optconn = CACHEOPT[opt.id] = CLONE(opt.options);
			if (optconn.max) {
				optconn.connectionLimit = optconn.max;
				delete optconn.max;
			}
			delete optconn.min;
			delete optconn.pooling;
		}
		conn = self.$conn[opt.id] = Database.createConnection(optconn);
		conn.$dbms = self;
		conn.$$destroy = destroy;
	}
	self.$op = null;
	self.busy = true;
	if (opt.options.pooling) {
		conn.getConnection(function(err, connection) {
			if (err) {
				self.busy = false;
				opt.onerror && opt.onerror(err);
				if ((!repeated || repeated < 3) && err.toString().indexOf('Too many connections') !== -1) {
					// try again
					setTimeout(exports.run, 200, opt, self, cmd, (repeated || 0) + 1);
					return;
				}
				if (cmd.builder)
					cmd.builder.$callback(err);
				else
					cmd.db.$next(err);
			} else {
				self.$conn[opt.id] = connection;
				connection.$dbms = self;
				connection.$opt = opt;
				connection.$pool = conn;
				connection.$$destroy = destroy;
				clientcommand(cmd, connection, self);
			}
		});
	} else
		clientcommand(cmd, conn, self);
};
function prepare_owner(cmd, condition) {
	var tmp = [];
	if (cmd.member) {
		for (var i = 0; i < cmd.member.length; i++)
			tmp.push(ESCAPE(cmd.member[i]));
	}
	var addcondition = [];
	if (cmd.condition) {
		addcondition.push('');
		if (typeof(cmd.condition) === 'string') {
			addcondition.push(val);
		} else {
			for (var key in cmd.condition) {
				var val = cmd.condition[key];
				addcondition.push(key + (val == null ? ' IS ' : '=') + ESCAPE(val));
			}
		}
	}
	// e.g. userid=ID OR (userid IN (ARR) (AND condition))
	condition.push('(' + cmd.name + '=' + ESCAPE(cmd.value) + (tmp.length ? (' OR (' + cmd.name + ' IN (' + tmp.join(',') + ')' + (addcondition.length ? addcondition.join(' AND ') : '') + ')') : '') + ')');
}
function WHERE(builder, scalar, group, params) {
	var condition = [];
	var sort = [];
	var tmp;
	var op = 'AND';
	var opuse = false;
	var current; // temporary for "query" + "cmd.value" and "replace" method because of performance
	var replace = builder.options.params ? function(text) {
		var indexer = (+text.substring(1)) - 1;
		params.push(current[indexer]);
		return '?';
	} : null;
	for (var i = 0; i < builder.$commands.length; i++) {
		var cmd = builder.$commands[i];
		if (builder.options.islanguage && cmd.name && cmd.name[cmd.name.length - 1] === '§')
			cmd.name = cmd.name.substring(0, cmd.name.length - 1) + (builder.options.language || '');
		switch (cmd.type) {
			case 'where':
				if (cmd.value === undefined) {
					opuse && condition.length && condition.push(op);
					condition.push(cmd.name);
				} else {
					tmp = ESCAPE(cmd.value);
					opuse && condition.length && condition.push(op);
					condition.push(cmd.name + ((cmd.value == null || tmp == 'null') && cmd.compare === '=' ? ' IS ' : cmd.compare) + tmp);
				}
				break;
			case 'owner':
				opuse && condition.length && condition.push(op);
				prepare_owner(cmd, condition);
				break;
			case 'custom':
				cmd.fn.call(builder, builder, builder.db.$output, builder.db.$lastoutput);
				break;
			case 'in':
				if (typeof(cmd.value) === 'function')
					cmd.value = cmd.value();
				if (cmd.value instanceof Array) {
					tmp = [];
					for (var j = 0; j < cmd.value.length; j++) {
						var val = cmd.value[j];
						if (val && cmd.field)
							val = val[cmd.field];
						tmp.push(ESCAPE(val));
					}
					opuse && condition.length && condition.push(op);
					condition.push(cmd.name + ' IN (' + (tmp.length ? tmp.join(',') : 'NULL') + ')');
				} else {
					opuse && condition.length && condition.push(op);
					condition.push(cmd.name + '=' + ESCAPE(cmd.field ? cmd.value[cmd.field] : cmd.value));
				}
				break;
			case 'notin':
				if (typeof(cmd.value) === 'function')
					cmd.value = cmd.value();
				if (cmd.value instanceof Array) {
					tmp = [];
					for (var j = 0; j < cmd.value.length; j++) {
						var val = cmd.value[j];
						if (val && cmd.field)
							val = val[cmd.field];
						tmp.push(ESCAPE(val));
					}
					opuse && condition.length && condition.push(op);
					condition.push(cmd.name + ' NOT IN (' + (tmp.length ? tmp.join(',') : 'NULL') + ')');
				} else {
					opuse && condition.length && condition.push(op);
					condition.push(cmd.name + '<>' + ESCAPE(cmd.field ? cmd.value[cmd.field] : cmd.value));
				}
				break;
			case 'between':
				opuse && condition.length && condition.push(op);
				condition.push('(' + cmd.name + '>=' + ESCAPE(cmd.a) + ' AND ' + cmd.name + '<=' + ESCAPE(cmd.b) + ')');
				break;
			case 'search':
				tmp = ESCAPE((!cmd.compare || cmd.compare === '*' ? ('%' + cmd.value + '%') : (cmd.compare === 'beg' ? ('%' + cmd.value) : (cmd.value + '%'))));
				opuse && condition.length && condition.push(op);
				condition.push(cmd.name + ' LIKE ' + tmp);
				break;
			case 'searchfull':
				// tmp = ESCAPE('%' + cmd.value.toLowerCase().replace(/y/g, 'i') + '%');
				// opuse && condition.length && condition.push(op);
				// condition.push('REPLACE(LOWER(to_tsvector(' + builder.options.table + '::text)::text), \'y\', \'i\') LIKE ' + tmp);
				break;
			case 'searchall':
				tmp = '';
				for (var j = 0; j < cmd.value.length; j++)
					tmp += (tmp ? ' AND ' : '') + cmd.name + ' LIKE ' + ESCAPE('%' + cmd.value[j] + '%');
				opuse && condition.length && condition.push(op);
				condition.push('(' + (tmp || '0=1') + ')');
				break;
			case 'fulltext':
				tmp = ESCAPE('%' + cmd.value.toLowerCase() + '%');
				opuse && condition.length && condition.push(op);
				condition.push('LOWER(' + cmd.name + ') LIKE ' + tmp);
				break;
			case 'contains':
				opuse && condition.length && condition.push(op);
				condition.push('LENGTH(' + cmd.name +')>0');
				break;
			case 'query':
				opuse && condition.length && condition.push(op);
				if (cmd.value)
					current = cmd.value;
				condition.push('(' + (current == undefined ? cmd.query : cmd.query.replace(REG_PARAMS, replace)) + ')');
				break;
			case 'permit':
				opuse && condition.length && condition.push(op);
				if (cmd.must)
					condition.push('(' + ((cmd.useridfield ? ('`' + cmd.useridfield + '`=' + Database.escape(cmd.userid) + ' OR ') : '') + '`' + cmd.name + '` && $' + params.push([cmd.value])) + ')');
				else
					condition.push('(' + ((cmd.useridfield ? ('`' + cmd.useridfield + '`=' + Database.escape(cmd.userid) + ' OR ') : '') + 'array_length(`' + cmd.name + '`,1) IS NULL OR `' + cmd.name + '` && $' + params.push([cmd.value])) + ')');
				break;
			case 'empty':
				opuse && condition.length && condition.push(op);
				condition.push('(' + cmd.name + ' IS NULL OR LENGTH(' + cmd.name + ')=0)');
				break;
			case 'month':
			case 'year':
			case 'day':
			case 'hour':
			case 'minute':
				opuse && condition.length && condition.push(op);
				condition.push(cmd.type + '(`' + cmd.name + '`)' + cmd.compare + ESCAPE(cmd.value));
				break;
			case 'date':
				opuse && condition.length && condition.push(op);
				condition.push('CONVERT(`' + cmd.name + '`,date)' + cmd.compare + (cmd.value instanceof Date ? ('CONVERT(\'' + cmd.value.format('yyyy-MM-dd') + '\',date)') : 'null'));
				break;
			case 'or':
				opuse && condition.length && condition.push(op);
				op = 'OR';
				opuse = false;
				condition.push('(');
				continue;
			case 'end':
				condition.push(')');
				op = 'AND';
				break;
			case 'and':
				opuse && condition.length && condition.push(op);
				op = 'AND';
				break;
			case 'sort':
				sort.push(cmd.name + ' ' + (cmd.desc ? 'DESC' : 'ASC'));
				break;
			case 'regexp':
				tmp = cmd.value.toString().substring(1);
				var g = '~';
				if (tmp[tmp.length - 1] === 'i') {
					tmp = tmp.substring(0, tmp.length - 2);
					g = '~*';
				} else
					tmp = tmp.substring(0, tmp.length - 1);
				opuse && condition.length && condition.push(op);
				condition.push(cmd.name + g + '\'' + tmp + '\'');
				break;
		}
		opuse = true;
	}
	var query = (condition.length ? (' WHERE ' + condition.join(' ')) : '') + (group ? (' GROUP BY ' + group) : '');
	if (scalar) {
		builder.options.sort = sort;
	} else {
		if (sort.length)
			query += ' ORDER BY ' + sort.join(',');
		if (builder.options.skip && builder.options.take)
			query += ' LIMIT ' + builder.options.take + ' OFFSET ' + builder.options.skip;
		else if (builder.options.take)
			query += ' LIMIT ' + builder.options.take;
		else if (builder.options.skip)
			query += ' OFFSET ' + builder.options.skip;
	}
	return query;
}
function OFFSET(builder) {
	var query = '';
	var sort = builder.options.sort || EMPTYARRAY;
	if (sort.length)
		query += ' ORDER BY ' + sort.join(',');
	if (builder.options.skip && builder.options.take)
		query += ' LIMIT ' + builder.options.take + ' OFFSET ' + builder.options.skip;
	else if (builder.options.take)
		query += ' LIMIT ' + builder.options.take;
	else if (builder.options.skip)
		query += ' OFFSET ' + builder.options.skip;
	return query;
}
function FIELDS(builder) {
	var output = '';
	var plus = '';
	var fields = builder.options.fields;
	if (fields && fields.length) {
		for (var i = 0; i < fields.length; i++) {
			var field = fields[i];
			if (field[0] === '-') {
				if (builder.options.fieldsrem)
					builder.options.fieldsrem.push(field.substring(1));
				else
					builder.options.fieldsrem = [field.substring(1)];
				continue;
			}
			if (builder.options.islanguage) {
				if (field[field.length - 1] === '§') {
					field = field.substring(0, field.length - 1);
					field = (field + builder.options.language) + ' AS ' + field;
				}
			}
			output += (output ? ',' : '') + field;
		}
		if (output && builder.$joinmeta)
			output += ',' + builder.$joinmeta.a;
	}
	fields = builder.options.subquery;
	if (fields && fields.length) {
		for (var i = 0; i < fields.length; i++)
			plus += ',' + (fields[i].name ? ('(' + fields[i].query + ') AS ' + fields[i].name) : fields[i].query);
	}
	return (output ? output : '*') + plus;
}
function dateToString(dt) {
	var arr = [];
	arr.push(dt.getFullYear().toString());
	arr.push((dt.getMonth() + 1).toString());
	arr.push(dt.getDate().toString());
	arr.push(dt.getHours().toString());
	arr.push(dt.getMinutes().toString());
	arr.push(dt.getSeconds().toString());
	for (var i = 1, length = arr.length; i < length; i++) {
		if (arr[i].length === 1)
			arr[i] = '0' + arr[i];
	}
	return arr[0] + '-' + arr[1] + '-' + arr[2] + ' ' + arr[3] + ':' + arr[4] + ':' + arr[5];
}