import com;
import string.list;
import time.ole;

class sqlServer {

	ctor( param,...){
		var connectionString;
		if( type(param) == type.table ){
			var tList = ..string.list(,";","=")
			tList.mixin(param) 
			
			if( !tList.find("DSN") ){
				if( !tList.find("Provider") )  tList.Provider="SQLOLEDB.1";
				if( !tList.find("Data Source","Server") )  tList["Data Source"]=".";
				if( !tList.find("Integrated Security","Password","PWD") ){
					tList["Integrated Security"]="SSPI"; //windows身份验证模式 
				}
			};
			
			connectionString = tostring( tList );
		}
		else { 
			if( !param ) error("请指定数据库连接串",2) 
			connectionString = ..string.format( param,... ); 
		}

		var conn = ..com.CreateObject("ADODB.Connection")
		conn.ConnectionString = connectionString;
		var ok,err = call(conn.open,conn);
		if(!ok) return null,"打开数据库连接失败:" +err;
		
		this.connection = conn;
		this._tables = {@{_weak="kv"}};
		this.codepage = 0;
		
		..table.gc(this,"close");
	}; 
	getCatalog = function(){
		if( this.catalog ) return this.catalog;
		this.catalog = ..com.CreateObject("ADOX.Catalog")  
		this.catalog.ActiveConnection = this.connection; 
		return this.catalog;	
	};
	eachTableObject = function(){
		var cat = this.getCatalog();
	 
		var next,items,idx = ..com.each(cat.Tables);  
		var tbl;
		return function(){
			idx,tbl = invoke(next,items,idx) 
			if( tbl ) return tbl.getName(),tbl.getType(),tbl;
		}  
	}; 
	findTableObject = function(name,cache=true){
		if(cache && this._tables[name] ) return this._tables[name];
		
		var cat = this.getCatalog();
		cat.Tables.Refresh();
		for index,tbl in ..com.each(cat.Tables) {
			if( tbl.Type = "TABLE" and tbl.Name = name ){
				if(cache) this._tables[name] = tbl;
				return tbl;
			}
		}	
	};
	existsTable = function(name){ 
		return !!this.findTableObject(name);
	}; 
	findProcedureObject = function(name){	
		var cat = this.getCatalog();
		cat.Procedures.Refresh();
		
		if(!..string.find(name,"\;\d+$") ) name = name + ";1"
		for index,tbl in ..com.each(cat.Procedures) {
			if( tbl.Name = name ){
				return tbl;
			}
		}	
	};
	existsProcedure = function(name){ 
		return !!this.findProcedureObject(name);
	}; 
	exec = function(sql,...){ 
		if( ... )sql = format(sql,...); 
		var ret,err = call(this.connection.Execute,this.connection,sql ); 
		if(!ret) error(err,2);
		return ret;
	}; 
	openRecordSet = function(sql,cursorType,lockType,...){
		var rs = ..com.CreateObject("ADODB.RecordSet"); 
		if( ... )sql = format(sql,...);
		var ok,err = call(rs.Open,rs,sql,this.connection,cursorType,lockType);
		if(!ok) return null,err;
		
		return rs;
	};
	getRows = function(sql,...){
		var rs,err = owner.openRecordSet(sql,,,...);
		if(!rs) error(err,2);
		 
		var recordCount = 0;
		var eof = rs.eof;
		var data = eof ? { @{} } : rs.GetRows();  
		
		var fields = {};
		var name; 
		for( i=1;rs.Fields.Count ){
			var name = rs.Fields(i-1).Name;
			if(#name){
				fields[i]  = name;
				data[ name ] = data[ i ]; 
			}
			
			if(!eof){
				var _,len = ..table.range(data[ i ]);
				recordCount = ..math.max(len,recordCount);
			}
		} 
		
		rs.close();
		
		data@.recordCount = recordCount;
		data@.fields = fields; 
		return data;
	};
	getTable = function(sql,...){
		var data,err = this.getRows(sql,...);
		if(!data) error(err,2);
		
        var result = {}; 
        var v; 
        var rowCount = data@.recordCount;
        var trim = ..string.trimright; 
        var fields = data@.fields;
        for(c=1;#data){
            var col = data[c];
            var name = fields[c];
            if(!#name) name = c;
            
            if(c>1){
            	for(r=1;rowCount){
        			v = col[r];
        			if( type( v ) == "string" ) v = trim(v);
        			elseif( ..time.istime(v) ) v = ..time.ole(v);
        		
        			result[r][name]  = v;
        		} 
        	}
        	else {
        		for(r=1;rowCount){
        			v = col[r];
        			if( type( v ) == "string" ) v = trim(v);
        			elseif( ..time.istime(v) ) v = ..time.ole(v);
        			
        			result[r] = { [name]  = v };
        		} 
        	}
        }
        
        result.fields = fields;
		return result;
	};
	stepQuery = function(...){
		return this.getTable(...)[[1]];
	};
	rowCount = function(){
		return this.stepQuery("SELECT @@rowcount")[[1]];
	};
	each = function(sql,...){  
        var rs,err = this.openRecordSet(sql,,,...);
       	var first = true;
       	if(!rs)error(err,2);

        return function(){ 
        	if(!first) rs.movenext(); 
        	else first = false;
   
            if( rs.eof )   return;
 			return rs;
 			
        },function(){ rs.close();   } ;
    }; 
	createCommand = function(sql,...){
		var cmd = ..com.CreateObject("ADODB.Command") 
		cmd.activeConnection = this.connection;
		cmd.commandType = ..string.find(sql,"\s\S") ? 1/*adCmdText*/ : 4/*adCmdStoredProc*/;
		
		if( ... )sql = format(sql,...);
		cmd.commandText = sql;
		cmd.namedParameters = true;
		
		var getParameterType = function(value){
			var adType,adSize; 
			var t = type(value);
			
			select(t) {
				case "string" {
					if(..string.isUnicode(value)){
						adType = 203/*adLongVarWChar*/
						adSize = #value/2;	
					}
					else {
						adType = 129 /*adChar*/;
						adSize = #..string.fromto(value,65001,this.codepage);
					} 
				}
				case "number" {
					adType = 3/*adInteger*/
					adSize = 8;
				}
				case "buffer" {
					adType = 204/*adVarBinaryr*/
					adSize = #value;
				}
				case "boolean" {
					adType = 11/*adBoolean*/
					adSize = 1;
				}
				case "null"{
					adType = 3/*adInteger*/;
					adSize = 0;
					value = ..com.Variant(null);
				}
				else {
					if( ..time.istime(value) ){
						adType = 7/*adDate*/;
						adSize = 8;
						value = tonumber(..time.ole(value));
					}
					else { 
						error("自动识别类型遇到错误")
					}
					
				}
			}
			
			return adType,adSize,value;
		}
		
		cmd[["addParameter"]] = function(name,value,opt=1/*_adParamInput*/,adType,adSize){
			var adType,adSize,value = getParameterType(value);  
			var param = cmd.CreateParameter("@"+name,adType,opt,adSize,value);
			cmd.Parameters.Append(param);  
			return param;	
		}; 
		cmd[["bind"]] = function(parameters){ 
			if( type(parameters) == type.table ){
				
				if( cmd.parameters.Count ){
					var p,n,v;
					for(i=1;cmd.parameters.Count;1){
						p = cmd.parameters(i-1);
						n = ..string.right(p.name,-2) 
						v = parameters[n];
						
						if(v===null){
							p.Value = ..com.Variant(null);
							continue;
						}
						
						if(cmd.commandType != 4){
							p.Type,p.Size,v = getParameterType(v);   
						}
						p.Value = v; 
					}
					return;
				}
					
				if( cmd.commandType == 4 ){
					for(name,value in parameters){
						cmd.addParameter(name,value); 
					}
					return cmd;
				}
				
				cmd.CommandText = ..string.replace(sql,"[\@\?](<:+>|<\w+>)",
						function(name){
							cmd.addParameter(name,parameters[name]); 
							return "?"; //OLEDB不支持命名参数
						}
					)	
				return cmd;
			}
			
			//已废弃,勿用	
			return {
				parameters = function(tParam){ return cmd.bind(tParam); }
			}
		}; 
		
		cmd["executeNoRecords"] = function(ra,parameters){
			cmd.execute(ra,parameters,cmd.adExecuteNoRecords);
		};
		
		cmd["executeStream"] = function(ra,parameters){
			cmd.execute(ra,parameters,cmd.adExecuteStream);
		};
		cmd[["getRows"]] = function(...){
			var rs,err = cmd.execute(...); 
			if(!rs) error(err,2);
		 	
			var recordCount = 0;
			var closed = (rs.State == rs.AdStateClosed);
			var eof = closed || rs.eof;
			var data = eof ? { @{} } : rs.GetRows();  
			
			var fields = {};
			var name; 
			for( i=1;rs.Fields.Count ){
				var name = rs.Fields(i-1).Name;
				if(#name){
					fields[i]  = name;
					data[ name ] = data[ i ]; 
				}
				
				if(!eof){
					var _,len = ..table.range(data[ i ]);
					recordCount = ..math.max(len,recordCount);
				}
			} 
			
			if(!closed)rs.close();
			data@.recordCount = recordCount;
			data@.fields = fields; 
			return data;
		};
		cmd[["getTable"]] = function(...){
			var data,err = cmd.getRows(...);
			if(!data) error(err,2);
			
        	var result = {}; 
        	var v; 
        	var rowCount = data@.recordCount;
        	var trim = ..string.trimright; 
        	var fields = data@.fields;
        	for(c=1;#data){
            	var col = data[c];
            	var name = fields[c];
            	if(!#name) name = c;
            	
            	if(c>1){
            		for(r=1;rowCount){
        				v = col[r];
        				if( type( v ) == "string" ) v = trim(v);
        				elseif( ..time.istime(v) ) v = ..time.ole(v);
        			
        				result[r][name]  = v;
        			} 
        		}
        		else {
        			for(r=1;rowCount){
        				v = col[r];
        				if( type( v ) == "string" ) v = trim(v);
        				elseif( ..time.istime(v) ) v = ..time.ole(v);
        				
        				result[r] = { [name]  = v };
        			} 
        		}
        	}
        	
        	result.fields = fields;
			return result;
		};
		cmd[["stepQuery"]] = function(...){
			return cmd.getTable(...)[[1]];
		};
		cmd["rowCount"] = function(){
			return this.stepQuery("SELECT @@rowcount")[[1]];
		};
		cmd[["each"]] = function(...){  
        	var rs,err = cmd.execute(...); 
       		var first = true;
       		if(!rs)error(err,2);
       		
        	return function(){ 
        		if(!first) rs.movenext(); 
        		else first = false;
   	
            	if( rs.eof )   return;
 				return rs;
 				
        	},function(){ rs.close();   } ;
    	}; 
    	
		return cmd;
	}; 
	beginTrans = function(){
		return this.connection.BeginTrans();
	};
	commitTrans = function(){
		return this.connection.CommitTrans();
	};
	rollbackTrans = function () {
		return this.connection.RollbackTrans();
	};
	close = function(){
		if(this.connection){
			this.connection.close(); 
			this.connection = null;
		}
	};
	@_meta;
} 

namespace sqlServer {

	_meta = {
		_get = function(k){
			if( ..string.cmp(k,"ad",2) == 0 ){
				owner[[k]] = owner.connection[k];
				return owner[[k]];	
			};
		}
	};
	
	escapeId = function(id){
		if(type(id)==="table"){
			if(#id){
				var ret = {}
				for(i=1;#id;1){
					..table.push(ret,escapeId(id[i])); 
				}
				return ..string.join(ret,",");  
			}
			else {
				var ret = {}
				for(k,v in id){
					if(type(v)!="table")
						..table.push(ret,escapeId(k) + " = " + escape(v));
					else
						..table.push(ret,escapeId(k) + " IN " + escape(v,true));
				} 
				return ..string.join(ret," AND ");
			}  
		}

		return '[' + ..string.replace(id,"\.", '].[')+ ']' 
	};
	
	escape = function(v,nested){
		var t  = type(v);
			 	
		if( t = "string" ){
			return "'" +  ..string.replace(v,"'","''") + "'";
		}
		elseif( t = "number" ){
			return tostring(v);
		}
		elseif( t = "boolean" ){
			return v ? "1" : "0";
		}
		elseif( t = "table" ) {
			if(..time.istime(v)){
				var f = v.format;
				v.format = "%Y-%m-%d %H:%M:%S";
				var ret  = "'" +  tostring(v) + "'";
				v.format = f;
				return ret;
			}
			elseif(#v){
				var ret = {}
				for(i=1;#v;1){
					..table.push(ret,escape(v[i],true)); 
				}
				ret = ..string.join(ret,",");
				if(nested) ret = "(" + ret + ")";
				return ret;
			} 
			elseif( v@[["_toSqlString"]] ){
				return invoke(v@[["_toSqlString"]],v,'mssql')
			}
			else {
				var ret = {}
				for(k,vv in v){
					..table.push(ret,escapeId(k) + " = " + escape(vv));
				} 
				return ..string.join(ret,",");
			}
		} 
		elseif( t=='buffer'){
			return ..string.concat("0x",..string.hex(v,"") );
		} 
		return ..string.replace(tostring(v),"\p",""); 
	};
	
	format = function(sql,...) { 
		if( type( ... ) == "table" ){
			var param = ...;
			if(#param){
				var i = 0;
				sql =  ..string.replace(sql,"\?\?*",
					function(name) {
						i++;
						if( name == "?" ) return escape(param[i]);
						else return escapeId(param[i])
					}
				); 
			}	
			
			return (..string.replace(sql,"\@(<:+>|<\w+>)",
				lambda(name) escape(param[name])
			)); 
		}
		return ..string.format(sql,...);
	}
	
	formatSqlParameters =  format;
}

/**intellisense()
sqlServer( 连接参数表 ) = @sqlServer( \n	["Data Source"] = "127.0.0.1,7788";\n	["Database"] = "数据库名__";\n	["User ID"] = "用户名";\n	["Password"] = "密码"; \n)
sqlServer( "数据库连接语句",其他格式化参数 ) = @sqlServer("Provider=SQLOLEDB.1;Data Source=.\SQLEXPRESS;Database=数据库名;User ID=登录名;Password=登录密码");
打开数据库连接,\n如果指定的数据库不存在将自动创建数据库文件\n如果存在格式化参数，则调用string.fomat格式化连接串
sqlServer() = !sqlServer.
sqlServer.escape(__) = 转义参数指定值为用于 SQL 查询语句的参数化值
sqlServer.escapeId(__) = 如果传入参数是文本则转换为 SQL 标识符,\n返回文本首尾会添加中括号,\n\n如果传入参数是表, 则格式化为 SQL 键值对并以 AND 为分隔符,\n如果表中的值为数组,则格式化为 IN 语句
sqlServer.format(.(SQL语句,格式化参数->->->) = 格式化 SQL 查询语句。\n所有需要格式化 SQL 语句的函数调用此函数格式化 SQL语句,\n如果格式化参数不是表则调用 string.format 格式化,否则按以下规则格式化:\n\nSQL语句中?或??占位符使用表的数组元素格式化,\n@字符开始的命名参数使用表的名值对元素格式化,\n其中??格式化为标识符,其他占位符格式化为参数值。\n\n字符串转为SQL安全转义字符串,buffer转为 0x4D7953514C 格式,\n数组则自动展开为列表,例如{'a', 'b'}格式化为'a', 'b'\n嵌套数组则格式化为分组列表,例如{{'a', 'b'}, {'c', 'd'}} 格式化为 ('a', 'b'), ('c', 'd')\n非数组的命名表，则格式化为 SQL 键值对,默认以逗号为分隔符,\n??占位符格式化 SQL 键值对则以 AND 为分隔符,并将数组值转换为IN语句
end intellisense**/

/**intellisense(!sqlServer)
codepage = 非 Unicode 字符的默认编码
? = 可直接使用对象的成员获取ADO组件提供的COM常量\n这些常量名的前缀都是 ad
close() = 关闭数据库连接
getTable(.("select * from [表名]",其他格式化参数) = 返回查询结果集数组\n每个元素代表一行,每行由N个键值对列组成\n返回值的fields成员记录了所有数据列的字段名\n结果集为空也会返回空数组,使用#操作符获取结果集长度
getRows() = 查询并返回包含结果数据的数组\n此函数返回的数据是先列后行,与getTable相反,\n数组由所有列的数据组成,每个列都是由多个行组成的数组\n可以使用 返回数据[ 列号 ][ 行号 ] 获取对应的值,\n也可以使用 数据.字段名[列号] 获取对应的值\n\n返回数组的元表中使用recordCount记录了总行数,\n结果集为空也会返回空数组,可使用 @.recordCount判断记录集的行数\n元表中的fields字段则是记录了所有字段名的数组.\n\n所有值后面的占位空白没有去除\n可使用getTable函数获取处理过空白占位符的数据,\n或参考该函数源码自行处理尾部空白
stepQuery(.("select * from [表名]",其他格式化参数) = 返回查询结果集第一行的数据,由键值对列组成,\n\n格式化规则请参考 sqlServer.format 函数说明,\n格式化参数可以是一个表参数,用于替换SQL中占位符指定的参数,\nSQL语句用中@前缀标明命名参数使用表参数中的名值对格式化,\nSQL语句中的?或??占位符使用表参数中的数组值格式化
rowCount() = 返回上次执行增、删、改、查等SQL影响的记录行数
openRecordSet(.(sql,游标类型,锁定方法,其他格式化SQL参数) = 打开记录集,游标类型如下:\n0:默认值,启动一个只能向前移动的游标\n1:打开键集类型游标,允许任意移动，并且允许更改记录集\n2:打开动态类型游标,许所有操作，其他用户对记录集的添加、删除、更改在此记录集中\n3:打开静态类型游标\n\n锁定方法如下：\n1:默认值,不能改变数据\n2:悲观锁,编辑时立即锁定数据源的记录\n3:乐观锁,只在调用Update 方法时才锁定记\n4: 乐观批更新
exec(.(sql,其他格式化SQL参数) =  执条SQL语句\n格式化参数规则请参考 sqlServer.format 函数说明,\n格式化参数可以是一个表参数,用于替换SQL中占位符指定的参数,\nSQL语句用中@前缀标明命名参数使用表参数中的名值对格式化,\nSQL语句中的?或??占位符使用表参数中的数组值格式化
createCommand(.(sql,其他格式化SQL参数) = 创建命令对象\n格式化规则请参考 sqlServer.format 函数说明,\n格式化参数可以是一个表参数,用于替换SQL中占位符指定的参数,\nSQL语句用中@前缀标明命名参数使用表参数中的名值对格式化,\nSQL语句中的?或??占位符使用表参数中的数组值格式化
existsTable(.("表名") = 判断数据库中是否存在指定表
existsProcedure(.("存储过程名") = 判断数据库中是否存在指定存储过程
each(sql,其他格式化参数) = @for(rs in ??.each("select * from [表名]") ){ 
	io.print( rs("字段名").value );__
}
each() = !sqlServer_rs.
createCommand() = !sqlServer_cmd.
connection = 连接对象
 
connection.ConnectionString = 连接串
connection.Errors = 错误集合
connection.Errors.count = 错误数
connection.Errors(.(0) = 参数指定错误索引
connection.Errors() = !ado_conn_err.

findTableObject() = !ADOX_Catalog_tbl.
findTableObject(.("表名") = 返回表对象
findProcedureObject(.("存储过程名") = 返回存储过程对象
eachTableObject = @for(tbName,tbType,tbObj in ??.eachTableObject() ){
	
}
eachTableObject() = !ADOX_Catalog_tbl.
beginTrans() = 启动事务,返回嵌套层数\n注意如果未调用此函数开启事务，默认每条独立SQL语句仍然会自动创建独立事务
commitTrans() = 提交事务
rollbackTrans() = 回滚事务
end intellisense**/

/**intellisense(!sqlServer_rs)
close() = 关闭记录集 
eof = 是否移动到尾部 
end intellisense**/

/**intellisense(!sqlServer_cmd)
commandText = 要执行的SQL命令语句
commandType = 命令类型\n普通SQL为1，存储过程为4
execute(.(ra,parameters.option) = 执行查询,所有参数可选\n参数ra 返回受查询影响的记录的数目 \n参数parameters 用 SQL 语句传递的参数值 \n参数options 一个或者多个 CommandTypeEnum 或 ExecuteOptionEnum 值 
executeStream(.(ra,parameters) = 执行查询返回流,所有参数可选\n参数ra 返回受查询影响的记录的数目 \n参数parameters 用 SQL 语句传递的参数值
executeNoRecords(.(ra,parameters) = 执行查询不返回记录集,所有参数可选\n参数ra 返回受查询影响的记录的数目 \n参数parameters 用 SQL 语句传递的参数值
cancel() = 取消执行
getTable() = 执行SQL命令返回查询结果集数组\n每个元素代表一行,每行由N个键值对列组成\n返回值的fields成员记录了所有数据列的字段名\n结果集为空也会返回空数组,使用#操作符获取结果集长度
getRows() = 查询并返回包含结果数据的SafeArray数组\n此函数返回的数据是先列后行,与getTable相反,\n数组由所有列的数据组成,每个列都是由多个行组成的数组\n可以使用 返回数据[ 列号 ][ 行号 ] 获取对应的值,\n也可以使用 数据.字段名[列号] 获取对应的值\n\n返回数组的元表中使用recordCount记录了总行数,\n结果集为空也会返回空数组,可使用 @.recordCount判断记录集的行数\n元表中的fields字段则是记录了所有字段名的数组.\n\n所有值后面的占位空白没有去除\n可使用getTable函数获取处理过空白占位符的数据,\n或参考该函数源码自行处理尾部空白
stepQuery() = 执行SQL命令\n返回查询结果集第一行的数据,由键值对列组成\n无数据返回null
rowCount() = 返回上次执行增、删、改、查等SQL影响的记录行数
each() = @for(rs in ??.each() ){ 
	io.print( rs(0).value );____/*执行SQL命令并遍历结果集\nrs为 ADODB.Recordset 对象，即ADO 表记录集\n可以使用 rs("字段名").value自记录集中提取数据,\n更多用法请参考 ADO  文档。*/
}
createParameter(.(名字,字段类型,参数类型,字段大小,参数值) = 创建参数\n参数中用到的常量请使用COM对象的成员常量获取,\n例如cmd.adLongVarWChar\n这个函数创建的参数还需要调用parameters.append添加到执行参数中\n\n不建议直接调用此函数，可以使用更简单的addParameter或bind函数添加参数
parameters = 参数列表
parameters.append(.(参数对象) = 添加参数\n注意access的参数对象虽然必须设置正确的名字,\n但实际上他只按添加顺序执行
parameters.count() = 返回参数长度
parameters.delete(.(索引) = 删除参数
parameters.refresh() = 刷新参数
parameters("__/*参数名*/") = 返回已添加的参数
bind(__) = 参数是一个表,\n可使用多个键值对批量指定SQL命令对象的多个参数,\n此函数会按参数在SQL中出现的顺序逐个添加参数,\nSQL语句中所有参数名前面必须额外添加@前缀,\n此函数返回命令对象自身
addParameter( = 调用createParameter创建参数对象,\n设置值并添加到SQL执行参数中\n注意如果直接使用这个函数添加参数,\nSQL语句中应当用?号作为参数占位符,\n参数只按添加顺序匹配SQL中的参数
addParameter(.(名字,值,参数类型,字段类型,字段大小) = 直接添加SQL执行参数\n除名字，值以外的其他参数不必要设置\n返回创建的参数对象\n\n\n注意access的参数对象虽然必须设置正确的名字,\n但实际上他只按添加顺序执行
bind() = !sqlServer_cmd.
addParameter() = !sqlServer_parameter.
parameters() = !sqlServer_parameter.
end intellisense**/

/**intellisense(!sqlServer_parameter) 
value = 参数值
direction = 参数的类型，默认是adParamInput
name = 参数名称
size  = 数据长度
end intellisense**/

/**intellisense(!ado_conn_err)
Description = 错误描述
Number = 错误代码
Source = 错误对象
SQLState = 数据源 
NativeError = 数据源错误代码
end intellisense**/

/**intellisense(!ADOX_Catalog_tbl)
Columns = 列集合
Columns.Count = 总数
Columns(.("列名") = 参数也可以写数值索引
Columns() = !ADOX_Catalog_column.
end intellisense**/

/**intellisense(!ADOX_Catalog_column)
Name = 列名
Type = 类型
DefinedSize = 字段大小 

end intellisense**/