// jsSQLi rel 1.0
//
// JavaScript Structured Query Language Interpreter
//
// - jsSQLi (JavaScript Structured Query Language Interpreter)
//
// written in JavaScript1.2
//
// conceived and first realized by:
//
//    Dieter Siebeck
//    Munich - Germany
//    dsiebeck@talkabout.de
//
//
// Author notes:
//
// jsSQLi  This is the interpreter of a simplified version of the standard SQL language,
//         it is a SQL interface to the jsSQL Framework and its extension. 
//
// Versions:
//
// - jsSQLi rel 1.0 (juni 2002)
// 
///////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////
//                                 mare nostrum                                  //
///////////////////////////////////////////////////////////////////////////////////

//TO-DOs

//optimizing join??
//more than 1 join in chain

//global vars
var jsSQL_I_Version = 1.0;
var jsSQL_Engine_Version_Minmum = 1.4;
var jsSQL_I_DEBUG = false;
//version check
if (parseFloat(jsSQL_ver.substring(3)) < jsSQL_Engine_Version_Minmum){
	alert ("This JSQL-Interpreter requires JSQL-Engine Version " + jsSQL_Engine_Version_Minmum + ". Actual version is " + jsSQL_ver);
}
//*************************************************************
//non-oop-interface to the Interpreter
//usage:
//aResult = jsSQL("Select x from aTable where ...");
function jsSQL(strSQL)
{
	var objInterpreter = new jsSQL_Interpreter();
	if (! objInterpreter.evaluate(strSQL)){
		alert(objInterpreter.getErrstr());
		return [];
	}
	return objInterpreter.getResult();
	
}
//*************************************************************
//these functions are methods in most of the jsSQLi-Objects:
//obj.getErrstr()
function jsSQL_GetErrstr(){
	return this.errstr;
}
function jsSQL_SetErrstr( msg ){
	this.error = true;
	this.errstr = msg + "\n" +this.errstr;
	if(jsSQL_I_DEBUG){alert(msg)}
}
//obj.getResult()
function jsSQL_GetResult(){
	return this.resultset;
}
//*******************************************************
/* INTERPRETER - OBJECT
synopsis:
strSQL = "SELECT ......";
bSuccess = objInterpreter.evaluate(strSQL);
if(!bSuccess){
	//do some error-handling, e.g.
	alert(objInterpreter.getErrstr());
}
aResult =  objInterpreter.getResult();
//aResult is now a jsSQL-Table with the result of the query:
PrintTable(result);

Main-Methods:
 objInterpreter.evaluate();
*/
function jsSQL_Interpreter()
{
	this.sql = "";
	this.errstr = "";
	this.error = false;
	this.resultset = [];
	this.evaluate = jsSQL_I_Evaluate;
	this.getErrstr = jsSQL_I_GetErrstr;
	this.setErrstr = jsSQL_SetErrstr;
	this.clearErrors = jsSQL_ClearErrors;
	this.getResult = jsSQL_GetResult;
	this.setDebugMode = jsSQL_setDebugMode;
	this.runInDebugMode = jsSQL_RunInDebugMode;
}
//**********************
//special errorhandling for the interpreter:
function jsSQL_I_GetErrstr(){
	var errstr = "error while evaluating sql-statement '"+this.sql+"'.\n" + this.errstr;
	return errstr;
}
function jsSQL_ClearErrors(){
	this.error = false;
	this.errstr = "";
}
//**********************
//special methods for debugging
function jsSQL_setDebugMode(flag){
	jsSQL_I_DEBUG = flag;
}
function jsSQL_RunInDebugMode(strSQL){
	alert("debugmode");
	this.clearErrors();
	this.sql = strSQL;
	var result = [];
	this.setDebugMode(true);
	var ret = this.evaluate(strSQL);
	this.setDebugMode(false);
	if(!ret){	
		alert(this.getErrstr())
	}else{
		result = this.getResult();
	}
	return result;
	
}
//**********************
//main method:
function jsSQL_I_Evaluate(strSQL)
{
	this.clearErrors();
	this.sql = strSQL;
	var objJSQL_Parser = new jsSQL_Parser();
	//Parsing - Error?
	if(! objJSQL_Parser.parse(strSQL)){
		this.setErrstr( objJSQL_Parser.getErrstr());
		this.setErrstr("error while parsing!");
		return;
	}
	var strCommand = objJSQL_Parser.getNextToken();
	var aTokens = objJSQL_Parser.getTokens();
	var objStatement;
	switch( strCommand.toUpperCase() ) {
		case "SELECT":
			objStatement = new jsSQL_SelectStatement(aTokens);
			break;
		default:
			this.setErrstr("unknown or not implemented SQL-Command: " + strCommand);
			return;
	}
	if (! objStatement.executeQuery()){
		this.setErrstr(objStatement.getErrstr());
		this.setErrstr("error while executing the query.");
		return;	
	}		
	this.resultset = objStatement.getResult();
	return true;
}
//End of Interpreter-Object
//***************************************************************
//SELECT - OBJECT
/*The core of the jsSQLi-Interpreter
Synopsis:
aTokens = objJSQL_Parser.getTokens()// Token-Array of a given SQL-Select-Command, see Parser-Object below
objSelect = new jsSQL_SelectStatement(aTokens);
bSuccess = objStatement.executeQuery();
if(!bSuccess){//error-handling goes here...}
aResult = objSelect.getResult(); //aResult is a jsSQL-Table
*/
function jsSQL_SelectStatement(aTokens){
	//init
	this.tokens = aTokens;
	//strings
	this.orderfield = "";
	this.ordermode = "";
	this.errstr = "";
	this.joinmode = "";

	//arrays
	
	this.tables = [];
	this.tableNames = [];
	this.resultset = [];
	this.fields = [];
	this.renamedFields = [];
	this.alias_names = [];
	this.joinfields = [];
	this.subqueries = [];
	this.limit = [];
	this.groupfields = [];
	this.function_to_call = [];
	//condition-object:
	this.rootCondition;
	this.havingCondition;
	//flags
	this.selectAll = false;
	this.isFunctionCall = false;
	this.isCondition = false;
	this.isHaving = false;
	this.isOrderBy = false;
	this.isGroupBy = false;
	this.isLimited = false;
	this.isSingleTableCall = false;
	this.isJoin = false;
	this.isDistinct = false;
	this.error = false;
	//methods
	this.executeQuery = jsSQL_S_Execute;
	//build-methods:
	this.buildQuery = jsSQL_S_BuildQuery;
	this.buildFields = jsSQL_S_BuildFields;
	this.buildFunction = jsSQL_S_BuildFunction;
	this.buildTables = jsSQL_S_BuildTables;
	this.buildJoin = jsSQL_S_BuildJoin;
	this.buildCondition = jsSQL_S_BuildCondition;
	this.buildOrderBy = jsSQL_S_BuildOrderBy;
	this.buildGroupBy = jsSQL_S_BuildGroupBy;
	this.newSubQuery = jsSQL_S_NewSubQuery;
	
	//evaluating methods:
	this.performQuery = jsSQL_S_PerformQuery;
	this.doJoin = jsSQL_S_DoJoin;
	this.evalSubquery = jsSQL_S_EvalSubQuery;
	//help-functions:
	this.renameField = jsSQL_S_RenameField;
	this.renameAmbiguousFieldNames = jsSQL_S_RenameAmbiguosFieldNames;
	this.setLimit = jsSQL_S_SetLimit;
	this.setAlias = jsSQL_S_SetAlias;
	this.setTable = jsSQL_S_SetTable;
	this.getField = jsSQL_S_GetField;
	this.getFieldFromAlias_or_Original = jsSQL_S_GetFieldFromAlias_or_Original;
	this.getTable = jsSQL_S_GetTable; 
	this.getTheTableFromItsName = jsSQL_GetTheTableFromItsName;
	this.getFirstTable = jsSQL_S_GetFirstTable;
	this.getJoinfield = jsSQL_S_GetJoinfield;
	this.setJoinfield = jsSQL_S_SetJoinfield;
	this.getFieldstring = jsSQL_GetFieldstring;
	//error-handling	
	this.getErrstr = jsSQL_GetErrstr;
	this.setErrstr = jsSQL_SetErrstr;
	//result
	this.getResult = jsSQL_GetResult;
}
//************************************
//objSelect.executeQuery()
/*main method
calls first buildQuery() and second performQuery()
*/
//************************************
function jsSQL_S_Execute( )
{
	if(jsSQL_I_DEBUG){alert("jsSQL_S_Execute")}
	if(! this.buildQuery()){return;}
	if(! this.performQuery()){return;}
	return true;
}
//************************************
//objSelect.executeQuery()
/*main evaluating method
defines the order, in which the (now prepared) query is evaluated
*/
//************************************
function jsSQL_S_PerformQuery()
{
	var i,strField;
	if(jsSQL_I_DEBUG){alert("jsSQL_S_PerformQuery")}
	//init the resultset of this select-object::
	if(this.isSingleTableCall ){
		//take first and only table:
		this.resultset = this.getFirstTable();
	}else{
		if( !this.isJoin ){this.setErrstr ("more than one table, but no join specified!");return;}
		this.renameAmbiguousFieldNames();
		//lets do the join:
		if(! this.doJoin()){this.setErrstr ("error while executing the JOIN");return;}
		//this.resultset is now set from doJoin()
	}
	//evaluate the WHERE - condition(s):
	if(this.isCondition){
		if(typeof(this.rootCondition) != "object"){this.setErrstr ("error: WHERE-condition has not been build!" );return;}
		this.rootCondition.init();
		this.resultset = this.rootCondition.evaluate(this);
		if(this.error){this.setErrstr ("error while evaluating WHERE-condition");return;}
	}
	//conditions are now evaluated
	if(this.isFunctionCall){
		var strFunc = this.function_to_call[0];
		var strFuncArg = this.function_to_call[1];
		var strFuncCall;
		if(this.isGroupBy){
			//groupfields can be original or aliasnames:
			
			var aGroupFields = [];
			for(i=0;i<this.groupfields.length;i++){
				strField = this.groupfields[i];
				strField = this.getFieldFromAlias_or_Original(strField);
				aGroupFields.push(strField);
			}
			if(jsSQL_I_DEBUG){alert("Groupfields are: "+aGroupFields)}
			this.resultset = GroupBy( this.resultset, aGroupFields, strFunc, strFuncArg );
		}else{
			strFuncCall = strFunc;
			if(strFuncArg != ""){
				//example: Max('fieldname',aTable)
				strFuncCall += "( '" + strFuncArg + "', this.resultset)";
			}else{
				//example: Count(aTable);
				strFuncCall += "( this.resultset )";
			}
			//call the function! 
			if(jsSQL_I_DEBUG){alert("Function-Call: "+strFuncCall)}
			this.resultset = eval(strFuncCall);
		}
	}
	
	//evaluate the HAVING-condition(s):
	if(this.isHaving){
		if(typeof(this.havingCondition) != "object"){this.setErrstr ("error: HAVING - condition has not been build!" );return;}
		this.havingCondition.init();
		this.resultset = this.havingCondition.evaluate(this);
		if(this.error){this.setErrstr ("error while evaluating HAVING - condition");return;}
	}
	//OrderBy: orderfield can be alias-name or original name
	if(this.isOrderBy){
		this.orderfield = this.getFieldFromAlias_or_Original(this.orderfield);
		this.resultset = OrderBy( this.resultset , this.orderfield, this.ordermode );
	}
	
	//field-selection
	if(!this.isFunctionCall){
		//"normal" field-select
		//distinct?
		if (this.isDistinct){
			var distinctfields = [];
			for(i=0;i<this.fields.length;i++){
				strField = this.fields[i];
				strField = this.getField(strField);
				distinctfields.push(strField);
			}
			if(jsSQL_I_DEBUG){alert("Distinct " + distinctfields)}
			this.resultset = Distinct(distinctfields,this.resultset)
		}else{
			//normal fields
			if(!this.selectAll){
				//build the fieldstring
				var fieldstring = this.getFieldstring();
				if(! fieldstring ){
					this.setErrstr ("error while building fieldstring");
					return;//exact Error-Message must be set in getFieldstring()!
				}
				var func_call = "Select( this.resultset , " +fieldstring + ");"
				if(jsSQL_I_DEBUG){alert("calling: " + func_call)}
				this.resultset = eval ( func_call);
			}
			//(when selectAll, nothing must be done)	
		}
	}
	//Limits?
	if(this.isLimited){
		if(this.limit.length>1){
			var startRow = this.limit[0];
			var maxRows = this.limit[1];
		}else{
			var startRow = 0;
			var maxRows = this.limit[0];
		}
		if(jsSQL_I_DEBUG){alert("Limit from "+ startRow + " to " + maxRows)}
		this.resultset = Limit( this.resultset ,startRow, maxRows);
	}
	//Alias-Names?
	var old_name, new_name;
	for (old_name in this.alias_names) {
		new_name = this.alias_names[old_name];
		old_name = this.getField(old_name);
		if(jsSQL_I_DEBUG){alert("renaming "+ old_name + " to " + new_name)}
        //call the engine: 
		As( old_name , new_name , this.resultset);
	}
	//all work is done:
	return true;
}
//************************************
//objSelect.doJoin()
/*
performes the JOIN between table_1 and table_2 according to the given joinfields and joinmode 
in the moment, there is only the JOIN of 2 tables possible
*/
//************************************
function jsSQL_S_DoJoin(){
	if(jsSQL_I_DEBUG){alert("jsSQL_S_DoJoin")}
	var strTableName_1 = this.tableNames[0];
	var strTableName_2 = this.tableNames[1];
	var table_1 = this.getTable(strTableName_1);
	var field_1 = this.getJoinfield(strTableName_1);
	var table_2 = this.getTable(strTableName_2);
	var field_2 = this.getJoinfield(strTableName_2);
	if ( typeof (table_1) != "object" || typeof (table_2) != "object" ){
		this.setErrstr("Could not retrieve 2 tables for JOIN");return;
	}
	if (typeof(field_1) == "undefined" || typeof(field_2) == "undefined"){
		this.setErrstr("Could not retrieve 2 joinfields for JOIN");return;
	}
	field_1 = this.getField(field_1);
	field_2 = this.getField(field_2);
	if(jsSQL_I_DEBUG){alert("joinfields are now: "+strTableName_1+": "+field_1 + ", "+strTableName_2+": "+ field_2)}
	if(jsSQL_I_DEBUG){alert("Now Doing "+ this.joinmode + "-Join")}
	//call engine:
	switch(this.joinmode ){
		case "I":
			this.resultset = InnerJoin(table_1 , table_2, field_1, field_2);
			break;
		case "L":
			this.resultset = LeftJoin(table_1 , table_2, field_1, field_2);
			break;
		case "R":
			this.resultset = RightJoin(table_1 , table_2, field_1, field_2);
			break;
		default:
			this.setErrstr ("Internal Error: WRONG JOINMODE!");return;
	}
	return true;
}
//************************************
//objSelect.buildQuery()
/* main build-method
1. parses the token-stream and splits it in appropriate slices
2. calls all succeeding build-functions
*/
//************************************
function jsSQL_S_BuildQuery(  )
{
	if(jsSQL_I_DEBUG){alert("jsSQL_S_BuildQuery")}
	var i,strToken,strNextToken;
	var blFromIsSet = false;
	var strPart = "FIELDS";
	var blIsSubQuery = false;
	var iCountSubOpenBackets = 0;
	var iCountSubCloseBackets = 0;
	var iSubQueryIndex;
	var aCurrentSubQuery;
	//the token-array-slices:
	var aFieldTokens = [];
	var aTableTokens = [];
	var aWhereTokens = [];
	var aHavingTokens = [];
	var aOrderByTokens = [];
	var aGroupByTokens = [];
	var aJoinTokens = [];
	for(i=0;i<this.tokens.length; i++){
		strToken = this.tokens[i];
		if(blIsSubQuery){
			switch (strToken){
				case "(":
					iCountSubOpenBackets++;
					aCurrentSubQuery.push(strToken);
					break;
				case ")":
					iCountSubCloseBackets++;//no break, except:
					if(iCountSubOpenBackets==iCountSubCloseBackets){
						//end of subquery
						//return to THIS query
						blIsSubQuery = false;
						if(jsSQL_I_DEBUG){alert("SubQuery completed: SELECT "+ aCurrentSubQuery.join(" "))}
						break;
					}
				default:
					aCurrentSubQuery.push(strToken);
			}
		}else{
		//we are not in a subquery
			switch (strToken.toUpperCase()){
				case "SELECT":
					//begin of a subquery:
					blIsSubQuery = true;
					iCountSubOpenBackets = 1;
					iSubQueryIndex = this.newSubQuery();
					aCurrentSubQuery = this.subqueries[iSubQueryIndex];
					var array;
					if(strPart == "WHERE" ){
						array = aWhereTokens;
					}else{
						if(strPart =="FROM" ){
							array = aTableTokens;
						}else{
							this.setErrstr("SUB-Select not in WHERE-or-FROM-Part!");return;
						}
					}
					array.pop();//remove open bracket
					array.push("SQ");
					array.push(""+iSubQueryIndex+"");
					if(jsSQL_I_DEBUG){alert("Begin of Sub-Query: "+iSubQueryIndex)}
					break;
				
				case "FIELDS":
					strPart = "FIELDS";
					break;
				case "TOP":
					if(strPart == "FIELDS"){
						strPart = "TOP";
					}
					break;
				case "FROM" : 
					strPart = "FROM";
					blFromIsSet = true;
					break;
				case "WHERE":
					strPart = "WHERE";
					this.isCondition = true;
					break;
				case "HAVING":
					strPart = "HAVING";
					this.isHaving = true;
					break;
				case "ON":
					if(strPart=="FROM"){
						strPart = "ON";
						this.isJoin = true;
					}
					break;	
				case "GROUP":
					strNextToken = this.tokens[i+1];
					if(strNextToken.toUpperCase()=="BY"){
						strPart ="GROUP";
						this.isGroupBy = true;
						i++;//one step ahead
					}
					break;
				case "ORDER":
					strNextToken = this.tokens[i+1];
					if(strNextToken.toUpperCase()=="BY"){
						strPart = "ORDER";
						this.isOrderBy = true;
						i++;//one step ahead
					}
					break;
				case "LIMIT":
					strPart = "LIMIT";
					break;
				default:
					switch (strPart){
						case "FIELDS":
							aFieldTokens.push(strToken);
							break;
						case "FROM":
							aTableTokens.push(strToken);
							break;
						case "WHERE":
							aWhereTokens.push(strToken);
							break;
						case "HAVING":
							aHavingTokens.push(strToken);
							break;
						case "ORDER":
							aOrderByTokens.push(strToken);
							break;
						case "GROUP":
							aGroupByTokens.push(strToken);
							break;
						case "ON":
							aJoinTokens.push(strToken);
							break;
						case "TOP":
							if ( ! this.setLimit(strToken)){this.setErrstr("BuildQuery: error in setlimit "+strToken);return};
							strPart = "FIELDS"; //back to Fields-Processing!
							break;
						case "LIMIT":
							if (strToken!="," && ! this.setLimit(strToken)){this.setErrstr("BuildQuery: error in setlimit "+strToken);return};
							break;
						default: 
							this.setErrstr("Either there is an error in your SELECT-Statement or this feature '"+strPart+"' is not implemented yet!");
							return;
					}//end switch 2
			}//end switch 1
		}//end if isSubquery
	}//end foreach token
	//Errors?
	if(! blFromIsSet ){
		this.setErrstr("Missing keyword FROM in your statement!");
		return;
	}
	//buildFields
	if (!  this.buildFields( aFieldTokens )){return}
	//buildTables()
	if ( ! this.buildTables( aTableTokens )){return};
	//buildCondition()
	if(this.isCondition){
		if ( ! (this.rootCondition = this.buildCondition( aWhereTokens ))){
			this.setErrstr("WHERE - Condition can't be build");return;
		}
	}
	//buildJoin must be called after buildTables and buildCondition
	if( this.isJoin ){
		if ( ! this.buildJoin( aJoinTokens )){return};
	}
	//buildOrderBy()
	if(this.isOrderBy){
		if ( ! this.buildOrderBy( aOrderByTokens )){return};
	}
	//buildGroupBy()
	if(this.isGroupBy){
		if ( ! this.buildGroupBy( aGroupByTokens )){return};
	}
	if(this.isHaving){
		if ( ! (this.havingCondition = this.buildCondition( aHavingTokens ))){
			this.setErrstr("HAVING - Condition can't be build");return;
		}
	}
	//job is done
	return true;
}
//************************************
//objSelect.setLimit()
//************************************
function jsSQL_S_SetLimit(lLimit){
	if(jsSQL_I_DEBUG){alert("jsSQL_S_SetLimit"+lLimit)}
	lLimit = parseInt(lLimit);
	if(isNaN(lLimit)){
		this.setErrstr("Limit must be a Integer!");
		return;
	}
	this.limit.push(lLimit);
	this.isLimited = true;
	return true;
}
//************************************
//objSelect.buildOrderBy()
//************************************
function jsSQL_S_BuildOrderBy( aOrderByTokens )
{
	if(jsSQL_I_DEBUG){alert("jsSQL_S_BuildOrderBy")}
	this.orderfield = aOrderByTokens[0];
	if(aOrderByTokens.length>1 && aOrderByTokens[1].toUpperCase() == "DESC"){
		this.ordermode = "DESC";
	}else{this.ordermode = "ASC"}
	return true;
}
//************************************
//objSelect.buildGroupBy()
//************************************
function jsSQL_S_BuildGroupBy( aGroupByTokens )
{
	if(jsSQL_I_DEBUG){alert("jsSQL_S_BuildGroupBy")}
	if(!this.isFunctionCall){this.setErrstr("You must specify an aggregat-function for groub-by-clause");return;}
	var i;for(i=0;i<aGroupByTokens.length;i+=2){this.groupfields.push(aGroupByTokens[i])}//ommit the comma
	return true;
}
//************************************
//objSelect.buildJoin()
/*
*/
//************************************
function jsSQL_S_BuildJoin( aJoinTokens ){
	if(jsSQL_I_DEBUG){alert("jsSQL_S_BuildJoin")}
	if ( aJoinTokens.length == 0){
		if (typeof (this.rootCondition) != "object"){
			this.setErrstr("Inner Join must have a ON- or a WHERE-Clause");return;
		}
		if(jsSQL_I_DEBUG){alert("getting Jointokens from Condition")}
		this.rootCondition.setJoinFields(this);
		if (this.error){
			this.setErrstr("Error while retrieving Joinfields from Condition");return;
		}else{
			return true;
		}
	}
	if (aJoinTokens.length != 3 ||  aJoinTokens[1] != "="){
		this.setErrstr("Syntax-Error in ON-Clause: '"+ aJoinTokens.join(" ")+"'.");return;
	}
	//first joinfield
	var joinfield = aJoinTokens[0];
	var tmpArray = joinfield.split(".");
	if (! tmpArray.length == 2){
		this.setErrstr("You must specify table and fieldname in first JOIN-field");return;
	}
	var strTable = tmpArray[0];
	this.setJoinfield(strTable, joinfield);
	//second joinfield
	joinfield = aJoinTokens[2];
	tmpArray = joinfield.split(".");
	if (! tmpArray.length == 2){
		this.setErrstr("You must specify table and fieldname in second JOIN-field");return;
	}
	strTable = tmpArray[0];
	this.setJoinfield(strTable, joinfield);
	return true;
}
//************************************
//objSelect.setJoinfield()
//************************************
function jsSQL_S_SetJoinfield(strTable,strField){
	this.joinfields[strTable] = strField;
}
//************************************
//objSelect.getJoinfield()
//************************************
function jsSQL_S_GetJoinfield(strTable){
	if(jsSQL_I_DEBUG){alert("jsSQL_S_GetJoinfield for table" + strTable)}
	var field = this.joinfields[strTable];
	if (typeof(field) == "undefined"){
		this.setErrstr("can not find joinfield for table '"+strTable+"'");return
	}
	if(jsSQL_I_DEBUG){alert("field:"+field)}
	return field;
}
//************************************
//objSelect.buildCondition()
//************************************
function jsSQL_S_BuildCondition( aConditionTokens )
{
	if(jsSQL_I_DEBUG){alert("jsSQL_S_BuildCondition:" + aConditionTokens.join(" ") )}
	var i,strToken;
	var lOpenBrackets = 0;
	var lClosedBrackets = 0;
	var objRootCondition = new jsSQL_Condition();
	objRootCondition.isComposite = true;//Root-Condition is always composite
	
	var objActualCondition = objRootCondition;
	//flag for distinguishing the Between-AND and the logical AND
	var isBetween = false;
	for (i=0; i<aConditionTokens.length;i++){
		strToken = aConditionTokens[i];
		if(jsSQL_I_DEBUG){alert(strToken)}
		switch ( strToken.toUpperCase() ) {
			
			case "(":
				lOpenBrackets ++;
				if(objActualCondition.isComposite){
					objActualCondition = objActualCondition.setChild();
					objActualCondition.isComposite = true;
				}else{
					objActualCondition.isComposite = true;
				}
				break;
			case ")":
				objActualCondition = objActualCondition.getParent();
				lClosedBrackets ++;
				break;
			
			case "OR":
				//1 up
				objActualCondition = objActualCondition.getParent();
				objActualCondition.setOperator("O");
				//1 down
				objActualCondition = objActualCondition.setChild();
				break;
			case "AND":
				//is it the 'normal' operator 'AND' or ae we in a between-statement?
				if(! isBetween ){
					//1 up
					if(! (objActualCondition = objActualCondition.getParent())){
						this.setErrstr("Error in WHERE-Clause near AND!");
						return;
					}
					objActualCondition.setOperator("A");
					//1 down
					objActualCondition = objActualCondition.setChild();
					break;
				}
				//if between, fall through:
			case "BETWEEN":
				if(isBetween){
					//fallen through from above:
					isBetween = false;//the and of the between-statement
				}else{
					isBetween = true;
				} 
				//no break here: 
				//flag isBetween is necessary to distinct the "AND" of the BETWEEN-statement from the operator "AND"
			default:
				//build the (rest of the) statement
				if(objActualCondition.isComposite){
					if(jsSQL_I_DEBUG){alert("go 1 down, because composite")}
					objActualCondition = objActualCondition.setChild();
				}
				objActualCondition.statement.push(strToken);
		}
	}
	//Errors?
	if ( lOpenBrackets > lClosedBrackets ){
		this.setErrstr("Error in WHERE-Clause: Too much open brackets!");
		return;
	}else{
		if ( lOpenBrackets < lClosedBrackets ){
			this.setErrstr("Error in WHERE-Clause: Too much closing brackets!");
			return;
		}
	}
	return objRootCondition;
}
//************************************
//objSelect.buildFunction()
//************************************
function jsSQL_S_BuildFunction(strFunc,aFuncArgs)
{
	if(jsSQL_I_DEBUG){alert("jsSQL_S_BuildFunction: "+strFunc+"("+ aFuncArgs+")")}
	strFuncArg = aFuncArgs.shift();
	var blAll = false;
	var blDistinct = false;
	switch (strFuncArg.toUpperCase()){
		case "ALL":
			blAll = true;
			strFuncArg = aFuncArgs.shift();
			break;
		case "DISTINCT":
			blDistinct = true;
			strFuncArg = aFuncArgs.shift();
			break;
		default:
			//nothing
	}
	if(aFuncArgs.length>0){this.setErrstr("More than one argument in function "+strFunc+": "+aFuncArgs);return;}

	switch (strFunc.toUpperCase()){
		case "COUNT":
			if(blAll){
				strFunc = "CountAll";
			}else{
				if(blDistinct){
					strFunc = "CountDistinct";
				}else{
					strFunc = "Count";
					strFuncArg = "";
				}
			}
			break;
		case "MAX":
			strFunc = "Max";
			break;
		case "MIN":
			strFunc = "Min";
			break;
		case "AVG":
			if(blDistinct){
				strFunc = "AvgDistinct";
			}else{
				strFunc = "Avg";
			}
			break;
		case "SUM":
			if(blDistinct){
				strFunc = "SumDistinct";
			}else{
				strFunc = "Sum";
			}
			break;
		default:
			this.setErrstr("Sorry: Function '"+strFunc +"' is not supported");
			return;	
	}
	//alert("FuncArg: "+strFuncArg);
	//arguments remaining?
	if(aFuncArgs.length>0){
		this.setErrstr("To much arguents for function "+strFunc+": "+aFuncArgs);return;
	}
	this.function_to_call = [strFunc, strFuncArg];
	
	return true;
}
//************************************
//objSelect.buildFields()
//************************************
function jsSQL_S_BuildFields( aFieldTokens ){
	if(jsSQL_I_DEBUG){alert("jsSQL_S_BuildFields")}
	if (aFieldTokens[0] == "*"){
		this.selectAll = true;
		return true;
	}
	var i,strToken,strField;
	var blAs = false;
	var blFunction = false;
	var strFunc,strFuncArg;
	//var strFieldstring = "'";
	for (i=0;i<aFieldTokens.length;i++){
		strToken = aFieldTokens[i];
		if(blFunction){
			if(strToken ==")"){
				if(!this.buildFunction(strFunc,aFuncArgs)){
					this.setErrstr("Error while building Function "+strFunc+"("+aFuncArgs+")");return;
				}
				blFunction = false;
			}else{
				aFuncArgs.push(strToken);
			}
		}else{
			switch (strToken.toUpperCase()){
				case "(":
					//should be Function-Call:
					if(this.isFunctionCall){this.setErrstr("More than one function in FIELDS-Part!");return}
					this.isFunctionCall = true;
					blFunction = true;
					strFunc = strField;
					strField = strFunc.toLowerCase();
					aFuncArgs = [];
					break;
				case ")":
					this.setErrstr("Unmotivated closing bracket in FIELDS-Part!");return;
					break;
				case "DISTINCT":
					if(this.isDistinct){this.setErrstr("DISTINCT can't be called twice in FIELDS-Part");return;}
					this.isDistinct = true;
					break;
				case ",":
					this.fields.push(strField)
					break;
				case "AS":
					blAs = true;
					break;
				default:
					if(blAs){
						this.setAlias(strField,strToken);
						blAs = false;
					}else{
						strField = strToken;
					}
			}//end switch
		}//end if(blFunction)
	}//end for
	this.fields.push(strField);
	return true;
}
//************************************
//objSelect.getFieldstring()
/*returns the fieldstring for calling the Select-Function of the engine
*/
//************************************
function jsSQL_GetFieldstring(){
	if(jsSQL_I_DEBUG){alert("jsSQL_GetFieldstring")}
	var i,field;
	var fieldstring = "'";
	var f_length = this.fields.length;
	for(i=0;i<f_length;i++){
		field = this.fields[i];
		field = this.getField(field);
		if(!field){
			return;
		}
		fieldstring += field + "'";
		if(i <f_length -1){//all except the last
			fieldstring += ",'";
		}
	}
	return fieldstring;
}
//************************************
//objSelect.renameAmbiguosFieldNames()()
/*in case of more than one table, all ambiguous fieldnames 
calling method objSelect.renameField()
*/
//************************************
function jsSQL_S_RenameAmbiguosFieldNames(){
	//alert("Renaming ambiguos fieldnames"+strTableName_1);
	var i,j,x,aTable_1,aTable_2,fieldnames_of_table_1,field,strTableName;
	for(i=0;i<this.tableNames.length-1;i++){
		strTableName_1 = this.tableNames[i];
		if(jsSQL_I_DEBUG){
			alert("renaming ambiguos fieldnames in "+strTableName_1);
		}
		aTable_1 = this.getTable(strTableName_1);
		fieldnames_of_table_1 = sys_getColNames(aTable_1);
		for(j=i+1;j<this.tableNames.length;j++){
			strTableName_2 = this.tableNames[j];
			if(jsSQL_I_DEBUG){
				alert("searching for ambiguos fieldnames in "+strTableName_2);
			}
			aTable_2 = this.getTable(strTableName_2);
			for(x=0;x<fieldnames_of_table_1.length;x++){
				field = fieldnames_of_table_1[x];
				//alert(field);
				if(sys_ColNameIsIn(aTable_2,field)){
					this.renameField(strTableName_1,field);
					this.renameField(strTableName_2,field);
				}
			}
		}
	}
}
//************************************
//objSelect.renameField()()
/*
renames a field, fully qualifying it with its tablename
stores fullname and former name in the array this.renamedFields
*/
//************************************
function jsSQL_S_RenameField(strTable,strField){
	var strFullFieldName;
	strFullFieldName = strTable + "." + strField;
	if(jsSQL_I_DEBUG){
		alert("renaming " + strField + " to "+ strFullFieldName + " in Table "+ strTable)
	}
	As( strField , strFullFieldName , this.getTable(strTable));
	this.renamedFields[strFullFieldName] = strField;
}
//************************************
//objSelect.getField()
/*
returns the actual name of a strGivenField in the current resultset
*/
//************************************
function jsSQL_S_GetField(strGivenField){
       var strFullField;
	   var strRetField;
	   var blFound = false;
	   if(strGivenField.indexOf(".")>0){
	   		//fully qualified field, e.g. agenda.surname
	   		for (strFullField in this.renamedFields){
		   		if(strFullField == strGivenField){
					//FIELD HAS BEEN RENAMED
					blFound = true;
					break;
				}
		   }
		   if ( ! blFound ){
		   		//field has not been renamed, so return the not-qualified field:
				//TO-DO: Check, if qualifer is correct and the field exists
		   		var tmpArray = strGivenField.split(".");
				strRetField = tmpArray[1];
		   }else{
		   		strRetField = strGivenField;//field has been renamed, so strGivenField is the right column-name
		   }
	   }else{
	   		//no fully qualified field, e.g. surname
			
			var intCount = 0;
		   for (strFullField in this.renamedFields){
		   		if(this.renamedFields[strFullField] == strGivenField){
					//FIELD HAS BEEN RENAMED, so return the NEW (full-)fieldname:
					strRetField = strFullField;
					intCount++;
				}
		   }
		   if ( intCount ==0 ){
		   		//field has not been renamed, so return the given Input:
		   		strRetField = strGivenField;
		   }else{
		   		if(intCount > 1){
					//more than one fields found (e.g. ID)
					strRetField = false;
					this.setErrstr("Ambiguous fieldname "+strGivenField+ " must be fully qualifed!");
				}
		   }
	    }
	return strRetField;
}
//************************************
//objSelect.setAlias()
//************************************
function jsSQL_S_SetAlias(strField,strAlias)
{
	this.alias_names[ strField ] = strAlias;
}
//************************************
//objSelect.getFieldFromAlias_or_Original()
//************************************
function jsSQL_S_GetFieldFromAlias_or_Original(strGivenField)
{
	var strRetField;
	var strOrigField;
	var blFound = false;
	//is strGivenField an alias?
	for(strOrigField in this.alias_names){
		if(this.alias_names[strOrigField] == strGivenField){
			blFound = true;
			//yes: return original (maybe fully qualified) field 
			strRetField = this.getField( strOrigField );
			break;
		}
	}
	if(!blFound){//strGivenField is not an alias
		strRetField = this.getField( strGivenField );
	}
	return strRetField;
}
//************************************
//objSelect.buildTables()
//************************************
function jsSQL_S_BuildTables( aTableTokens )
{
	if(jsSQL_I_DEBUG){alert("jsSQL_S_BuildTables")}

	var i,strToken;
	var blAS = false;
	var blSQ = false;//Subquery-Flags
	var strTableName = "";
	var strTable = "";
	var blPutTable = false;
	var aJoinTokens = [];
	for (i=0; i< aTableTokens.length; i++ ){
		strToken = aTableTokens[i];
		switch ( strToken.toUpperCase() ){
			case "SQ":
				blSQ = true;//Subquery
				break;
			case "AS":
				blAS = true;
				break;
			case ",":
				blPutTable = true;
				break;
			case "RIGHT":
				this.joinmode = "R";
				break;
			case "LEFT":
				this.joinmode = "L";
				break;
			case "INNER":
				this.joinmode = "I";
				break;
			case "JOIN":
				blPutTable = true;
				break;
			default:
				if(this.tableNames.length > 2){
					this.setErrstr("You can not specify more than 2 tables in this version of the interpreter");
					return;
				}
				if ( blAS ){
					//an alias is specified:
					if (strTableName != ""){
						this.setErrstr("Error in Table-Specification: more than 1 word after AS specfied (missing komma?)");
						return;
					}
					strTableName = strToken;
					if(jsSQL_I_DEBUG){alert("Alias: "+strTableName)}
				}else{
					//this should be the name of the table (= the name of the table-array)
					if (strTable != ""){
						this.setErrstr("Error in Table-Specification: more than 1 word before ',' or AS (missing komma?)");
						return;
					}
					strTable = strToken;
					if(jsSQL_I_DEBUG){alert("Table: "+strTable)}
				}
		}//end switch
		if(i == aTableTokens.length-1){
			//last token
			if (strTable != ""){
				//there is a not-yet-put table 
				blPutTable = true;
			}
		}
		//put the table:
		if (blPutTable){
			//there should be a table found, else cry:
			if (strTable == ""){this.setErrstr("Error in BuildTable: Missing table after " + strToken);return }
			//adding the found table (and its alias) to objSelect.tables
			if (strTableName == ""){strTableName = strTable} //no alias
			if (! this.setTable(strTable,strTableName,blSQ)){ this.setErrstr("Error in BuildTable");return }
			strTableName = "";
			strTable = "";
			blPutTable = false;
			blAS = false;
			blSQ = false;
		}
	}//end for
	if( this.tableNames.length == 1 ){
		this.isSingleTableCall = true;
		if(jsSQL_I_DEBUG){alert("Single-Table-Call")}
	}else{
		//more than 1 table:
		if (! this.isJoin  ){ //INNER Join without ON
			this.isJoin = true;
			this.joinmode = "I";
		}
	}
	return true;
}
//************************************
//objSelect.setTable(strTable,strTableName)
//************************************
function jsSQL_S_SetTable(strTable,strTableName,blIsSubquery)
{
	if(jsSQL_I_DEBUG){alert("jsSQL_S_SetTable "+strTable)}
	//Make a copy and store it in tables-array
	var aTable;
	if(blIsSubquery){
		aTable = this.evalSubquery(strTable);//strTable is in this case the subquery-index
		//alert(aTable);
	}else{
		aTable = this.getTheTableFromItsName(strTable);
	}
	
	if (typeof(aTable) != "object" ){this.setErrstr("Error in SetTable!");return}
	this.tables[strTableName] = From(aTable);
	this.tableNames.push(strTableName);
	//this.renamedFields[strTable] = [];
	return true;
}
//************************************
//objSelect.getFirstTable()
//************************************
function jsSQL_S_GetFirstTable(){
	return this.getTable(this.tableNames[0]);
}
//************************************
//objSelect.getTable()
//************************************
function jsSQL_S_GetTable(strTable){
	///if(jsSQL_I_DEBUG){alert("Get Table " + strTable)}
	if(typeof(this.tables[strTable]) != "object"){
		this.setErrstr("Table '" +  strTable + "' was not specfied in the FROM - Part"); 
		return;
	}
	return this.tables[strTable];
}
//************************************
//objSelect.getTheTableFromItsName()
/* returns the Array-Object specifed with  strTableName*/
//************************************
function jsSQL_GetTheTableFromItsName ( strTableName )
{
	var aTable;
	//alert(strTableName);
	if(typeof (window[strTableName]) != "object" ){
		this.setErrstr("Unknown table: '" + strTableName + "'");
		return;
	}else{
		aTable = window[strTableName];
		return aTable;
	}
}

//************************************
//objSelect.newSubQuery()
//initialises a new subquery by creating a empty array as new entry in the this.subqueries-array
//returns the index of the new subquery
//************************************
function jsSQL_S_NewSubQuery(){
	var new_index = this.subqueries.length;
	this.subqueries[new_index] = [];
	return new_index;
}
//************************************
//objSelect.evalSubQuery()
//evaluates a complete subquery by creating a new Select-Object
//************************************
function jsSQL_S_EvalSubQuery(index){
	var aTokens = this.subqueries[index];
	var objSelect = new jsSQL_SelectStatement(aTokens);
	if(jsSQL_I_DEBUG){alert("evaluate subquery: "+ aTokens.join(" "))}	
	if (! objSelect.executeQuery()){
		this.setErrstr(objSelect.getErrstr());
		this.setErrstr("error while executing subquery:"+ aTokens.join(" "));
		return [];	
	}		
	return objSelect.getResult();
}
//END OF SELECT-OBJECT
//****************************************************************************//
// CONDITION - OBJECT
/*the conditon-objects are node-like structures, designed to build a tree. 
Conditions can be either composite or not - I call the others 'statement-conditions'. 
A composite condition has one ore more children, which are connected (in a left-to-right-chain) 
of Operators (AND or OR). Each statement-condition contains (only) one 'real' statement, 
e.g. "surname = 'Myers'". A statement-condition can not have childs.
There must be one root-Condition, which is per default composite. The most simple condition-tree
consists therefore of one root -condition, which has a single child, the statement-condition.
The tree is build by the SELECT-Object. The SELECT-Object calls then objRootCondition->evaluate(this),
which means, that every evaluate-method knows the SELECT-Object, which is used for 
setting Resultset and Errstring of the Select-Object. This means, that each statement is executed on the
resultset of the Select-Obj, which can be a single table or a joined table.
*/
function jsSQL_Condition(objParent)
{
	this.children = []; 
	this.operators  = [];
	this.isCleared = false;
	this.statement = [];
	this.isComposite = false;
	this.parent = null;
	this.isInit = false;
	//init
	if(objParent != null){
		this.parent = objParent;
	}
	//methods:
	this.init = jsSQL_C_Init;
	this.setOperator = jsSQL_C_SetOp;
	this.setChild = jsSQL_C_SetChild;
	this.getParent = jsSQL_C_GetParent;
	this.evaluate = jsSQL_C_Evaluate;
	this.evaluateStatement = jsSQL_C_EvaluateStatement;
	this.setJoinFields = jsSQL_C_SetJoinFields;
}
//************************************
//objCondition.init()
//************************************
function jsSQL_C_Init(){
	this.isInit = true;
}
//************************************
//objCondition.setJoinFields()
//search recursively the condition tree and find join-fields
//set them directly in the given SELECT-Obj
//************************************
function jsSQL_C_SetJoinFields(objSelect){
	if(jsSQL_I_DEBUG){alert("jsSQL_C_SetJoinFields")}
	if(!this.isComposite){
		if (this.statement[1] != "="){return}
		var field1 = this.statement[0];
		var field2 = this.statement[2];
		if (field1.indexOf(".")<1 || field2.indexOf(".")<1 ){return}
		var tmpAr1 = field1.split(".");
		var tmpAr2 = field2.split(".");
		if (tmpAr1[0] == tmpAr2[0]){return}
		//todo: check tablenames, if they exist!?
		//joinfields found:
		objSelect.setJoinfield(tmpAr1[0], field1);
		objSelect.setJoinfield(tmpAr2[0], field2);
		if(jsSQL_I_DEBUG){alert("join-fields found: '"+this.statement.join(" ")+"'")}
		this.statement = [];
		this.isCleared = true;
		return;
	}
	//Statement is Composite
	var i;
	for (i=0;i<this.children.length;i++){
		this.children[i].setJoinFields(objSelect);
	}
}
//************************************
//objCondition.evaluate()
//************************************
function jsSQL_C_Evaluate(objSelect)
{
	if(jsSQL_I_DEBUG){
		var testString = (this.isComposite)? "composite": "statement";
		alert("evaluate: " + testString);
	}
	//Statement is not composite?
	if(! this.isComposite){
		return this.evaluateStatement(objSelect);
	}
	//Statement should be composite:
	//Errors?
	if(this.operators.length != this.children.length - 1){
		objSelect.setErrstr("error in partial-statement, beginning with '"+this.children[0]+"' and ending with '"+this.children[this.children.length-1]+"': To less or to many operators!"); 
		return [];
	}
	//recursive tree-processing!
	var i,objCond,strOp,aResult;
	
	//when init, initialize the result with the resultset from objSelect
	//and intersect it with he first condition
	//otherwise, we can't get JOINed-Results to be evaluated
	
	if( this.isInit ){
		aResult = objSelect.getResult();
	}
	//looking for the first child-statement 
	for (i=0;i<this.children.length;i++){
		if (this.children[i].isCleared){
			continue;
		}else{
			objCond = this.children[i];
			aResult = objCond.evaluate(objSelect);
			break;
		}
	}
	i++;
	for (i=i;i<this.children.length;i++){
		objCond = this.children[i];
		if(objCond.isCleared){continue};
		strOp = this.operators[i-1];
		switch (strOp){
			case "A":
				if(jsSQL_I_DEBUG){alert("AND")};
				aResult = Intersect( aResult, objCond.evaluate(objSelect));
				break;
			case "O":
				if(jsSQL_I_DEBUG){alert("OR")};
				aResult = Union(  aResult, objCond.evaluate(objSelect));
				break;
			default:
				objSelect.setErrstr("unknown operator: " + strOp);
				return [];
		}
	}
	return aResult;
}
//************************************
//objCondition.evaluateStatement()
//************************************
function jsSQL_C_EvaluateStatement(objSelect)
{
	if( this.statement.length < 3){
		objSelect.setErrstr("length of statement too small: " + this.statement.join(" "));
		return [];
	}
	if(jsSQL_I_DEBUG){alert("evaluate statement: "+ this.statement.join(" ") )};
	var leftVal,rightVal,op;
	var aTable;
	var aResult = [];
	var index = 0;
	
	aTable = objSelect.getResult(); //should be either single table or the result from a previous join
	leftVal = this.statement[index++];
	
	//leftVal can be either an alias or the original (maybe fully qualified) fieldname
	leftVal = objSelect.getFieldFromAlias_or_Original( leftVal );
	
	op = this.statement[index++].toUpperCase();
	if(op == "IS"){
		if(this.statement[index].toUpperCase() =="NOT"){
			//"IS NOT NULL"
			op += " " + this.statement[index++].toUpperCase() + " " + this.statement[index++].toUpperCase();
			
		}else{
			//"IS NULL"
			op += " " + this.statement[index++].toUpperCase();
		}
	}else{
		if(op == "NOT"){
			//NOT LIKE, NOT BETWEEN, NOT IN
			op += " " + this.statement[index++].toUpperCase();
		}
	}
	if(op == "IN" || op == "NOT IN"){
		//right val is a table:
		var sq_index,sq_table;
		if(jsSQL_I_DEBUG){alert("evaluating subquery")}

		var strTable = this.statement[index++]
		if(strTable== "SQ"){
			//Sub-SELECT
			sq_index = this.statement[index++];
			sq_table = objSelect.evalSubquery(sq_index);
		}else{
			//REAL TABLLE
			if(jsSQL_I_DEBUG){alert("subquery on table " + strTable)}
			sq_table = jsSQL_GetTheTableFromItsName(strTable);
		}
		aResult = Where (aTable, leftVal, op, sq_table);
		return aResult;
	}
	
	//Normal cases (right val is a value, not a table):
	
	rightVal = this.statement[index++];
	//alert(rightVal);
	//are there more than one right value?
	if(this.statement.length > index+1 ){
		if(op == "BETWEEN" || op == "NOT BETWEEN"){
			index++;//ommit the "AND"
			aResult = Where (aTable, leftVal, op, rightVal, this.statement[index++]);
			if(this.statement.length > index+1 ){
				objSelect.setErrstr("statement has still remaining token: "+this.statement[index]);
			}
		}else{
			
			objSelect.setErrstr("statement not correct: '" + this.statement.join(" ") + "'.\nMaybe wong operator '" + op + "'");
			return [];
		}
	}else{
		//normal case
		if((op == "LIKE" || op == "NOT LIKE") && rightVal.indexOf("/") !=0){
			//convenience: add slashes
			rightVal = "/" + rightVal + "/";
		}
		if(typeof(rightVal) =="undefined"){rightVal = ""}
		if(jsSQL_I_DEBUG){alert("call engine: Where (aTable, '"+leftVal+"','"+op+"','"+rightVal+"')")}
		aResult = Where (aTable, leftVal, op, rightVal);
	}
	return aResult;
}
function jsSQL_C_SetOp(strOp){
	//if(jsSQL_I_DEBUG){alert ("set Op: "+strOp)}
	this.operators.push(strOp);
}
function jsSQL_C_GetParent()
{
	return this.parent;
}
function jsSQL_C_SetChild(){
	var objChild = new jsSQL_Condition(this);
	this.children.push(objChild);
	return objChild;
}

//End Condition-Object
//****************************************************************************//
//PARSER-OBJECT
function jsSQL_Parser(){
	this.aTokens = [];
	this.SQL_String = "";
	this.errstr = "";
	this.parse = jsSQL_P_Parse;
	this.addToken = jsSQL_P_AddToken;
	this.addToLastOperator = jsSQL_P_AddOperator;
	this.getTokens = jsSQL_P_GetTokens;
	this.getNextToken = jsSQL_P_GetNextToken;
	this.getCharCode = jsSQL_P_GetCharCode;
	this.getErrstr = jsSQL_GetErrstr;
	this.setErrstr = jsSQL_SetErrstr;
}

function jsSQL_P_AddToken(strToken){
	//alert(strToken);
	if(typeof(strToken) != "undefined" && strToken != ""){
		this.aTokens.push(strToken);
	}
}
function jsSQL_P_GetErrstr(){
	return this.errstr;
}
function jsSQL_P_GetTokens(){
	return this.aTokens;
}
function jsSQL_P_GetNextToken(){
	return this.aTokens.shift();
}
function jsSQL_P_AddOperator(strChar){
	this.aTokens[this.aTokens.length-1] += strChar;
}
function jsSQL_P_Parse(SQL_String)
{
	this.SQL_String = SQL_String;
	var strChar, strActualQuotingChar, intCharCode;
	var i;
	var strToken  = "";
	var bl_IsQuoted = false;
	var bl_IsEscape = false;
	//step through the chars of te input string
	for (i=0;i<SQL_String.length; i++){
		strChar = SQL_String.charAt(i);
		
		//Are we inside a Quoted String?
		if(bl_IsQuoted){
			//another quoting-char:
			if(strChar == strActualQuotingChar && !bl_IsEscape){
				//escaped?
				if(SQL_String.charAt(i+1)== strActualQuotingChar){
					//then continue 
					bl_IsEscape = true;
					continue;
				}
				//end of quoted string:
				bl_IsQuoted = false;
				this.addToken(strToken);
				strToken  = "";
			}else{
				bl_IsEscape = false;
				strToken += strChar;
			}
		}else{
			//Not inside a quoted string
			intCharCode = this.getCharCode(strChar);
			switch(intCharCode){
				case 1: 		// whitespace or komma
					this.addToken(strToken);
					strToken  = "";
					break;
				case 2: 		//brackets
					this.addToken(strToken);
					this.addToken(strChar);
					strToken  = "";
					break;
				case 3: 		//operators
					if(this.getCharCode(strLastChar) == 3){ //last char was operator too
						this.addToLastOperator(strChar);
					}else{
						this.addToken(strToken);
						this.addToken(strChar);
						strToken  = "";
					}
					break;
				case 4:   		//quoting
					bl_IsQuoted = true;
					strActualQuotingChar = strChar;
					break;
				default: 		//letters, numbers and the rest
					strToken += strChar;	
			}//end switch
		}//end if isQuoted
		strLastChar = strChar;	
	}//End for
	this.addToken(strToken);//last one
	return true;
}//end jsSQL_P_Parse

function jsSQL_P_GetCharCode(strChar){
	if(strChar == " " ){ // whitespace
		return 1;
	}
	if(strChar == "(" || strChar == ")" || strChar == ","){//brackets or kommas
		return 2;
	}
	if(strChar == "<" || strChar ==">" || strChar == "=") {//operators
		return 3;
	}
	if(strChar == "'" || strChar =='"') {//quoting
		return 4;
	}
	return 12;//rest
}
//END PARSER OBJECT
///////////////////////////////////////////////////////////////////////////////////
//                                 hic sunt leones                               //
///////////////////////////////////////////////////////////////////////////////////
