JScript快速开发框架Edk:js2sql

简介: 我们已经对 ORM 的概念耳熟能详了,而且使用动态语言一般都能比较轻松地写一套 ORM 方案,网上比比皆是,有的参照 Ruby on Rail 的 ActiveRecord,有的参照 iBatis 的 SQLTemplate 模式,还有的采用微软 Linq 的方案,无论哪一种思想和模式都力求把数据的增、删、改、查的操作映射(Mapping)为一套 API,用面向对象(Object-Oriented)的方式将这些对象组织起来,代替繁复的 SQL 来实现系统业务逻辑的过程,当然,最终仍要转换到数据库可以解析的 SQL 语句。

我们已经对 ORM 的概念耳熟能详了,而且使用动态语言一般都能比较轻松地写一套 ORM 方案,网上比比皆是,有的参照 Ruby on Rail 的 ActiveRecord,有的参照 iBatis 的 SQLTemplate 模式,还有的采用微软 Linq 的方案,无论哪一种思想和模式都力求把数据的增、删、改、查的操作映射(Mapping)为一套 API,用面向对象(Object-Oriented)的方式将这些对象组织起来,代替繁复的 SQL 来实现系统业务逻辑的过程,当然,最终仍要转换到数据库可以解析的 SQL 语句。“映射、业务对象、数据库”这三者之间,映射负责业务逻辑到数据持久化的过程,并且可以想像映射居于业务对象与数据库的“中间位置”。业务对象不但不会与数据库直接打交道,而且其结果就是要让业务对象与数据库(数据库)的分离。中间层的映射、如何映射于是显得相当重要,直接影响到整体架构上的解耦。这里又提到了 ORM 的一点好处:“解耦”。大多分离的目的便是为了解耦,看怎么“解”。如果深入更多的 ORM 好处是什么就让大家去感受了,或者开发过程中大家都明白的,熟悉的,天天打交道的,本文则不想罗嗦复述。一言蔽之,当为称道的地方乃解放了生产力——此说法可谓最适用不过。不过,看官会不会觉得行文至此写得比较笼统?为表示笔者做了功课,还是贴一帖人家总结的现象,说明 ORM 带来的好处:

目前大多数项目或产品都使用关系型数据库实现业务数据的存储,这样在开发过程中,常常有一些业务逻辑需要直接用写SQL语句实现,但这样开发的结果是:遍地布满SQL语句。这些高藕合的SQL语句给系统的改造和升级带来很多无法预计的障碍。为了提高项目的灵活性,特别是快速开发,ORM是一个不错的选择。举个简单的例子:在使用ORM的系统中,当数据库模型改变时,不再需要理会逻辑代码和SQL语句中涉及到该模型的所有改动,只需要将该模型映射的对象稍作改动,甚至不做改动就可以满足要求。(1)

如果 NoSql、对象数据库流行后,那么则可能不需要存在映射层。当前的 ORM 实质可被认为是介乎关系模型和对象模型之间的一种混合协调机制。至于什么“O/R 阻抗失衡(O/R Impedance Mismatch)”、“OO 数据库”、“使用图模型分析很多对象间复杂关系的设计合理性”,俺水平所限,不能也不可能深究,总之简单的说,不直接写 SQL 就对了。

我们知道虽然数据库都支持标准 SQL 语句,但不同的数据库之间还是会存着着不少差异,这导致我们在操作不同的数据库时不能统一 SQL 语句, 一旦更换了数据库,那意味着 SQL 要重写。为了避免灾难性的后果,我们就需要对数据进行封装。 封装的意义就在于不管数据库如何变化,我们都只需要使用相同的代码去获取和构建我们需要的数据结构,而不用关心不同数据库之间对于 SQL 的支持不同而造成的差异。

下面是我尝试用 JS 写的动态生成 SQL 类。

$$.sql.Parser = function(){

	var data = this.data;
	if(!data) {
		data = this.data = {};
	}
	
	data.mainTable			= '';
	
	data.top				= {};
	data.top.value			= 0;
	data.top.sql			= '';
	
	data.field				= {};
	data.field.value		= [];
	data.field.sql			= '';
	
	data.subQuery			= {};
	data.subQuery.value		= [];
	data.subQuery.sql		= '';
	
	data.join				= {};
	data.join.value			= [];
	data.join.sql			= '';	
	
	data.filter				= {};
	data.filter.value		= [];
	data.filter.sql			= '';	
	
	data.orderBy			= {};
	data.orderBy.value		= [];
	data.orderBy.sql		= '';	
				
	data.noConflict			= {};
	data.noConflict.sql		= '';
	
	// for Top(First)
	this.setTop = function(num){
		this.data.top.value = num
		this.data.top.sql   = "TOP " + num + ' ';
		return this;
	}
	
	this.setField = function(field){
		var _field = this.data.field;
		// 如果前面有字符串却没有 分号,则自动加上一个。
		if(_field.value.length && /(?!,)\s?$/.test(_field.sql)){
			_field.sql += ',';
		}
		_field.value.push(field);
		_field.sql += ' '+ field + ' ';
		return this;
	}
	
	this.addField = function(){
		var fields = [];
		for(var i = 0, j = arguments.length; i < j; i++){
			fields.push(arguments[i].getSql().alias);
		}
		fields = fields.join(', ');
		
		this.setField(fields);
	}
	
	// for fields
	this.setSubQuery = function(queristInstance, token){
		this.data.subQuery.value.push(queristInstance);
		this.data.subQuery.sql += ',(' + queristInstance.getQuerySql() + ') AS ' + token;
		return this;
	}
	
	/**
	 * 设置主题表格。
	 * @param {String} mainTable
	 */
	this.setMainTable = function(mainTable){
		this.data.mainTable = mainTable;
		return this; 
	}
	
	this.setTable = this.setMainTable; // shorthand
	
	/**
	 * for tables
	 * 生成下面的语句:
	 * USER] INNER JOIN (news INNER JOIN newsClass ON news.ClassID = newsClass.ID) ON USER.ID = news.SubmitterID;
	 * 有两个关系:
	 * student(s#,sname,d#),即学生这个关系有三个属性:学号,姓名,所在系别
	 * dep(d#,dname),即院系有两个属性:系号、系名则s#、d#是主键,也是各自所在关系的唯一候选键,d#是student的外键。
	 */
	this.setJoin = (function(){
		var  tpl = "({1} INNER JOIN {0} ON {3}.{4} = {1}.{2})";
		
		/**
		 * @param  {String}	mainTable
		 * @param  {Array}	tables
		 * @return {String}
		 */
		function makeJoin(mainTable, tables){
			var  
			 join
			,table 
			,inner		// 内面一层的join
			,out	= []; // Is this useful? @todo
	
			for(var i = 0, j = tables.length; i < j; i++){
				
				 table = tables[i]
				,inner = (i == 0) ? mainTable : out[i - 1]
				,join  = tpl.format(
					 inner
					,table.tableName
					,table.primaryKey
					,mainTable
					,table.foreignKey
				);
				
				out.push(join);
				
				// SELECT fields, noConflict
				if(table.noConflict){
					var noConflict = new String(' ');
					for(var q = 0, p = table.noConflict.length; q < p; q++){
						noConflict += ',' + table.noConflict[q] + ' ';
					}
					this.data.noConflict.sql = this.data.noConflict.sql + noConflict;
				}
			}
			return join;
		}
		return function(mainTable, tables){
			var thisJoin = this.data.join;
			
			// 保存value,就是保存參數
			thisJoin.value.push([ mainTable, tables]);
			// 不用join,沒有tables
			if(!tables){
				thisJoin.sql = mainTable;
				return thisJoin.sql;
			}
			
			// 如果有sql值存在,表示這是第二次調用join(),則mainTable為fn.sql
			if(thisJoin.sql && thisJoin.sql != ''){
				mainTable = thisJoin.sql;
			}
			
			thisJoin.sql = makeJoin.call(this, mainTable, tables); // 每次join操作都会破坏旧字符串
			return thisJoin.sql;
		}
	})()
	
	this.addJoin = function(join){
		var tmpJoin = this.data.tmpJoin;
		if(!tmpJoin){
			tmpJoin	= this.data.tmpJoin = [];
		}
		tmpJoin.push(join);
		return this;
	}
	
	// for where
	this.setFilter = function(fieldObj, operator, value){
		switch(fieldObj.type){
			case String:
				value = String(value);
				value = "'" + $$.trim(value) + "'";
			break;
		}
		var filterSql = fieldObj.toString({fullFieldName : true}) + ' ' +operator + ' ' + value;
		 
		this.data.filter.value.push(filterSql);
		this.data.filter.sql += ' '+ filterSql + ' ';
		return this;
	}

	// for order
	this.orderBy = function(fieldObj, sort){
		var sql = ' ORDER BY {0} {1} ';
		this.data.orderBy.sql = sql.format(fieldObj.toString({fullFieldName : true}), sort);
		return this;
	}

	// for page
	this.limit = function(){
		return emptyStr;
		return this;
	}	
	
	this.getQuerySql = function(isForPage){
		var data = this.data;

		// 先有 tables,然后才有 data.noConflict
		var tables;													// 关联表
		// 为防止影响data.join.sql,所以先有一判断
		tables = data.join.sql || this.setJoin(data.mainTable, data.tmpJoin);	
		
		var fields;													// 字段
		if(isForPage){
			fields  = this.data.mainTable + '.uid';
		}else{
			fields  = '';
			fields += data.field.sql;
			fields += data.subQuery.sql;
			fields += data.noConflict.sql;
			fields  = fields || '*';
		}
		
		var filter = data.filter.sql;								// 查询条件
		if( filter && isForPage != false){/* 分页之后 filter 无效 */
			filter = ' WHERE ' + filter;
		}else{
			filter = '';
		}
		
		var order = data.orderBy.sql;								// 排序
		var top	= data.top.sql;										// first
		return "SELECT " + top + fields + ' FROM ' + tables + filter + order;
	}
	
	// For write action
	
	/**
	 * @param  {Object} entityObj
	 * @param  {Action} action
	 * @return {String} SQL语句。
	 */
	function write(entityObj, action){
		var sql;
		
		switch(action){
			case $$.data.INITIALIZE: // 新建表格。
				var insertData = entityObj.eachFields(entity2insert, [ [], [] ]);
				
		        sql = sqlTpl_Insert.format(
			         entityObj.tableName
			        ,insertData[0].join(',')
			        ,json2sql(insertData[1].join(','))
		        ); 
			break;
			case $$.data.CREATE: 	// 执行SQL INSERT指令。
				var insertData = entityObj.eachFields(entity2insert, [ [], [] ]);
				
		        sql = sqlTpl_Insert.format(
			         entityObj.tableName
			        ,insertData[0].join(',')
			        ,json2sql(insertData[1].join(','))
		        ); 
			break;
			case $$.data.UPDATE: 	// 

			break;
			case $$.data.DELETE: 	// 输入记录的id参数,删除记录。
				sql = sqlTpl_Delete.format(entityObj.tableName, entityObj.id);
			break;
			default:
				// warn
			break;
		}
		
		return sql;
	}
	
	/**
	 * @this {Array}
	 */
	function entity2insert(field){
		this[0].push(field.key);
		this[1].push(field.getSqlValue());
	}
	


	/**
	 * 送入一个JSON值,根据其类型返回符合SQL存储的字符串。
	 * @private
	 * @param 	{Any}	 v 	值
	 * @return 	{String} 	符合sQL存储的字符串。
	 */
	function json2sql(v){
	    switch(typeof(v)){
	        case 'boolean':
	        	break;
	        case 'string':
	            v = v.replace(/\'/g, "''").replace(/\r/g,"").replace(/(wh)(ere)/ig, "$1'+'$2");
			    v = "'"+ v +"'";
			break;
			case 'number':
				/*
				 * Access 双精度数字需要转换
				 * @dep
				if(false){
					v = " Format(" + v + ",'#0.0000') ";
				}*/
			break;
//				if (value && (typeof value.getFullYear == 'function')) {// 日期类型转换
//					value = "#" + value.format("Y-m-d h:i:s") + "#";
//				}
			case 'object':
	            if(v.toString() === new Date(v).toString()){ // 日期类型
	               v = (
	                $$.cfg.edk_dbType == 'mysql'
	                ? "'" + v.format("yyyy-mm-dd HH:MM:ss") + "'"
	                : "#" + v.format("yyyy-mm-dd hh:MM:ss") + "#"
	               );
	               break; 
	            }
			default : 
	            throw "unknow type!";
	    }
	    return v;
	}
	
	function json2sql(type, value){
		var str;
		
		switch(type){
			case Boolean:
			case Number:
				var isAccesss = true; // @todo get this config from &&.cfg
				// Access 双精度数字需要转换
				if(String(value).length > 10 && isAccesss){
					value = " Format({0},'#0.0000') ".format(value);	
				}
				str = value;
			break;
			case String:
				str = String(value + '');
	            str = str.replace(/\'/g, "''"); // @todo is this necessary ?.replace(/\r/g,"").replace(/(wh)(ere)/ig, "$1'+'$2");
			    str = "'"+ str +"'";
			break;
//			case Array:
//			break;
//			case Function:
//			break;
//			case Object:
//			break;
			case Date:
	             // mysql "'" + v.format("yyyy-mm-dd HH:MM:ss") + "'"
				str = "#" + value.format("yyyy-MM-dd hh:mm:ss") + "#"
			break;
//			case RegExp:
//			break;
			case 'undefined':
			case null:
				str = 'NULL';
			break;
			default:
				throw '未解析的字段';
		}
		
		return str;
	}

	/**
	 * 这里一定要转换,因为COM日期类型到JS中很奇怪。
	 * 原理ADO Types -->JS Types
	 * 以字符出现date字眼为判断!
	 * @param 	{String}	key
	 * @param	{COM/COM+}	v
	 * @return	{Date}
	 */
	function getPrimitiveDate(key, v){
	    if(/date/i.test(key)){
	        v = new Date(v);
	    }
	    return v;
	}
	
	var 
	 insertSql = 'INSERT INTO {0} ({1}) VALUES ({2})'
	,updateSql = 'UPDATE {0} SET {1} WHERE uid = {2}'
	,deleteSql = 'DELETE FROM {0} WHERE uid = {1}';
	
	
	/**
	 * 返回执行 SQL CREATE 指令。
	 * @param {Object} data
	 * @return {String}
	 */
	this.getInsertSql = function(data){
		var fields = [], values = [];

		for(var i in data){
			fields.push(i);
			values.push(json2sql(data[i][0], data[i][1]));
		}
	    
		fields = fields.join(', ');
		values = values.join(', ');
		
	    var sql = insertSql.format(this.data.mainTable, fields, values);
	    return sql;
	};
	
	/**
	 * 返回执行 SQL UPDATE 指令。
	 * @param {Number} uid
	 * @param {Object} data
	 * @return {String}
	 */
	this.getUpdateSql = function(uid, data){
		var sql = [];
		
		for(var i in data){
			sql.push(i + ' = ' + json2sql(data[i][0], data[i][1]));
		}
	    
	    sql = sql.join(',');
	    sql = updateSql.format(this.data.mainTable, sql, uid.toString());
	    return sql;
	}
	
	this.getDelSql	  = function(entry){
		
	};
	
	/**
	 * 新建表格。
	 * @param 	{Object} 	entityObj
	 * @param 	{Boolean} 	isDropTable 	创建表格之前是否删除表格。
	 * @return 	{Boolean} 					True表示创建成功。
	 */
	this.getCreateTableSql = function(entityObj, isDropTable){
		var  
		 sql  = 'CREATE TABLE ' + tableName
		,meta = $$.Model.meta
		,fieldName
		,SQLType
		,arr  = [];
			
		for(fieldName in model){
			SQLType =  meta.getMeta.call(model[fieldName], meta.SQLType);
			switch(SQLType){
				case 'BOOLEAN':
					SQLType = ($$.cfg.edk_dbType == 'access') ? 'BIT' : SQLType;
				break;
				case 'CHAR':
					SQLType = 'CHAR({0})'.format(meta.getMeta.call(model[fieldName], meta.Length))
				break;
			}
			arr.push(fieldName + ' ' + SQLType + (fieldName == "ID" ? " NOT NULL PRIMARY KEY" : ""));
		}
		sql += '('+ arr.join('\n,') + ")";
		
		if(isDropTable){
			 	// Access不支持 IF EXIST……
				$$.sql.execute('DROP TABLE {0}'.format(tableName));
		}
		//进行数据库操作
		return sql;
	}
};

后来,我了解了 ROR,于是也学着搞点 ActiveRecord。
预备知识:ActiveRecord 模式

数据层采用 ActiveRecord 模式关联数据(Associations)。 最经典的 ActiveRecord 应用是出现在知名 Web 框架 Ruby on Rails (RoR)1上,它使得 RoR 的 ORM2 建模方案有快速、灵活等的优点。关于  ActiveRecord 模式深度了解,可以参阅权威文献“Active Record(http://www.martinfowler.com/eaaCatalog/activeRecord.html)”,作者 Martin Fowler。
怎么认识 ActiveRecord 模式呢?它是 ORM 方案中的一种,使得我们可以以对象的方式处理关系型数据库为“行、列”为单位的处理方式。这里不打算深入探讨背景的机制,就简单地讲一下基本认识。

在分析的时候,要定义不同对象之间的关系是非常自然的。比如说,在一个食谱数据库中,一条食谱可能会有多条评论,多条评论又可能为同一作者所写,而作者又可以创造多条食谱。透过定义这种链接到一起的关系可允许你通过更为直观的、更强大的方式去操纵数据,——而这种方式,就是 ActiveRecord 的关系链接来支持。
首先是 belongTo 关联。何谓“belongTo”,我们不妨这样说(“属于”这里表示特定的关系):

  • 公司数据库中,账单 accout 属于 公司 company;
  • 论坛程序中,帖子 thread 属于论坛 forum,也属于分类 cateory;
  • 一个画册中,缩略图 thumbnail 属于picture。

如果 bar 属于foo,也就是说它们之间的关系 belongTo,那么一般情况下,关系型数据库物理表中,bar 表会有一称作 foo_id 的字段,作为外键(foreign_key)出现。相对地,与 belongs_to 关联相对应地就是 hasMany 关联,也就是“多对一” v.s “一对多”之间的区别;

也许我们可以借助“父-子”的概念去理解,例如父母可有多个孩子,孩子只有一对父母。账单相当于公司是“子级别”,而公司相当于账单是“父级别”。

当前可支持 belongTo(多对一)、hasMany(一对多)、多对多。而 hasOne 关系实际上包含在 belongTo 关系中。

目录
相关文章
|
3月前
|
SQL 监控 安全
Flask 框架防止 SQL 注入攻击的方法
通过综合运用以上多种措施,Flask 框架可以有效地降低 SQL 注入攻击的风险,保障应用的安全稳定运行。同时,持续的安全评估和改进也是确保应用长期安全的重要环节。
205 71
|
2月前
|
SQL 安全 Java
除了Flask框架,还有哪些框架能防止SQL注入攻击?
这些框架都在安全方面有着较好的表现,通过它们的内置机制和安全特性,可以有效地降低 SQL 注入攻击的风险。然而,无论使用哪个框架,开发者都需要具备良好的安全意识,正确配置和使用框架提供的安全功能,以确保应用的安全可靠。同时,持续关注安全更新和漏洞修复也是非常重要的。
125 60
|
2月前
|
数据采集 人工智能 自然语言处理
Midscene.js:AI 驱动的 UI 自动化测试框架,支持自然语言交互,生成可视化报告
Midscene.js 是一款基于 AI 技术的 UI 自动化测试框架,通过自然语言交互简化测试流程,支持动作执行、数据查询和页面断言,提供可视化报告,适用于多种应用场景。
548 1
Midscene.js:AI 驱动的 UI 自动化测试框架,支持自然语言交互,生成可视化报告
|
2月前
|
SQL 存储 人工智能
Vanna:开源 AI 检索生成框架,自动生成精确的 SQL 查询
Vanna 是一个开源的 Python RAG(Retrieval-Augmented Generation)框架,能够基于大型语言模型(LLMs)为数据库生成精确的 SQL 查询。Vanna 支持多种 LLMs、向量数据库和 SQL 数据库,提供高准确性查询,同时确保数据库内容安全私密,不外泄。
396 7
Vanna:开源 AI 检索生成框架,自动生成精确的 SQL 查询
|
1月前
|
SQL 分布式计算 Java
Spark SQL向量化执行引擎框架Gluten-Velox在AArch64使能和优化
本文摘自 Arm China的工程师顾煜祺关于“在 Arm 平台上使用 Native 算子库加速 Spark”的分享,主要内容包括以下四个部分: 1.技术背景 2.算子库构成 3.算子操作优化 4.未来工作
|
2月前
|
Web App开发 JavaScript 前端开发
Node.js开发
Node.js开发
78 13
|
3月前
|
存储 JavaScript 前端开发
深入浅出Node.js后端开发
在数字化时代的浪潮中,后端开发作为连接用户与数据的桥梁,扮演着至关重要的角色。本文将以Node.js为例,深入探讨其背后的哲学思想、核心特性以及在实际项目中的应用,旨在为读者揭示Node.js如何优雅地处理高并发请求,并通过实践案例加深理解。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的视角和思考。
|
3月前
|
Web App开发 开发框架 JavaScript
深入浅出Node.js后端开发
本文将带你领略Node.js的魅力,从基础概念到实践应用,一步步深入理解并掌握Node.js在后端开发中的运用。我们将通过实例学习如何搭建一个基本的Web服务,探讨Node.js的事件驱动和非阻塞I/O模型,以及如何利用其强大的生态系统进行高效的后端开发。无论你是前端开发者还是后端新手,这篇文章都会为你打开一扇通往全栈开发的大门。
|
3月前
|
Web App开发 开发框架 JavaScript
深入浅出Node.js后端开发
在这篇文章中,我们将一起探索Node.js的奇妙世界。无论你是刚接触后端开发的新手,还是希望深化理解的老手,这篇文章都适合你。我们将从基础概念开始,逐步深入到实际应用,最后通过一个代码示例来巩固所学知识。让我们一起开启这段旅程吧!
|
2月前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js后端开发
本文将带领读者从零基础开始,一步步深入到Node.js后端开发的精髓。我们将通过通俗易懂的语言和实际代码示例,探索Node.js的强大功能及其在现代Web开发中的应用。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的见解和技巧,让你的后端开发技能更上一层楼。

热门文章

最新文章