%name VTQL_Parser
//%left EQ LT GT LTE GTE NE IN LIKE.

sql ::= select_statement.
select_statement ::= SELECT(SEL) selectcol_list FRM(F) table_list where_condition order_clause limit_clause end_stmt. { 
if(SEL){
$this->out['select'] = SEL;
}
if(F){
$this->out['from'] = F ;
}
if(SEMI){
$this->out['semi_colon'] = SEMI;
}
if($this->out['select']){
$this->buildSelectStmt($this->out);
}
}
selectcol_list ::= selectcolumn_exp COLUMNNAME(CNAME). { 
$this->out['column_list'][] = CNAME;
}
selectcol_list ::= ASTERISK(A). {
$this->out['column_list'][] = A;
}
selectcol_list ::= COUNT PARENOPEN ASTERISK PARENCLOSE. {
$this->out['column_list'][] = 'count(*)';
}
selectcolumn_exp ::= selectcol_list COMMA. 
selectcolumn_exp ::= . 
table_list ::= TABLENAME(TNAME). {
if(!in_array($this->out["column_list"], "*") && !in_array(array_map(strtolower, $this->out["column_list"]), "count(*)")){
if(!in_array("id",$this->out["column_list"])){
	$this->out["column_list"][] = "id";
}
}
$moduleName = TNAME;
if(!$moduleName){
	$this->syntax_error = true;
	throw new WebServiceException(WebServiceErrorCode::$QUERYSYNTAX, "There is an syntax error in query");
}
global $adb;
$handler = vtws_getModuleHandlerFromName($moduleName,$this->user);
$objectMeta = $handler->getMeta();
$this->out['moduleName'] = $moduleName;
$this->out['tableName'] = implode(',',$objectMeta->getEntityTableList());
}
where_condition ::= WHERE(Wh) condition.
where_condition ::= . 
condition ::= expr_set expr(E).
expr_set ::= condition LOGICAL_AND(LAND). {
$this->out['where_condition']['operators'][] = LAND;
}
expr_set ::= condition LOGICAL_OR(LOR). {
$this->out['where_condition']['operators'][] = LOR;
}
expr_set ::= .
expr ::= COLUMNNAME(ECNAME) logical_term valuelist.{
$this->out['columnDone']=true;
$this->out['where_condition']['column_names'][] = ECNAME;
if(strcmp(ECNAME, 'id')===0){
$prev = $this->out['where_condition']['column_values'][php7_sizeof($this->out['where_condition']['column_values'])-1];
if(is_array($prev)){
$new = array();
foreach($prev as $ind=>$val){
$val = trim($val,'\'"');
$value = vtws_getIdComponents($val);
$new[] = $value[1];
}
$this->out['where_condition']['column_values'][php7_sizeof($this->out['where_condition']['column_values'])-1] = $new;
}else{
$prev = trim($prev,'\'"');
$value = vtws_getIdComponents($prev);
if(strcasecmp($this->out['where_condition']['column_operators'][php7_sizeof($this->out['where_condition']['column_operators'])-1],'like')===0){
$value[1] = "'".$value[1]."'";
}
$this->out['where_condition']['column_values'][php7_sizeof($this->out['where_condition']['column_values'])-1] = $value[1];
}
}
}
valuelist ::= PARENOPEN valueref PARENCLOSE.
valuelist ::= valueref.
valueref ::= value_exp VALUE(VAL).{
$length = php7_sizeof($this->out['where_condition']['column_values']);
$pos = $length - 1;
if($pos < 0){
$pos = 0;
}
if(strcasecmp($this->out['where_condition']['column_operators'][$pos],"in")===0 && 
	!empty($this->out['where_condition']['column_values'][$pos]) && !$this->out['columnDone']){
if(!is_array($this->out['where_condition']['column_values'][$pos])){
$prev = $this->out['where_condition']['column_values'][$pos];
$this->out['where_condition']['column_values'][$pos] = array();
$this->out['where_condition']['column_values'][$pos][] = $prev;
}
$this->out['where_condition']['column_values'][$pos][] = VAL;
}else{
$this->out['columnDone'] = false;
$this->out['where_condition']['column_values'][] = VAL;
}
}
value_exp ::= valueref COMMA.
value_exp ::= .
logical_term ::= EQ. {
$this->out['where_condition']['column_operators'][] = '=';
}
logical_term ::= LT. {
$this->out['where_condition']['column_operators'][] = '<';
}
logical_term ::= GT. {
$this->out['where_condition']['column_operators'][] = '>';
}
logical_term ::= LTE. {
$this->out['where_condition']['column_operators'][] = '<=';
}
logical_term ::= GTE. {
$this->out['where_condition']['column_operators'][] = '>=';
}
logical_term ::= NE. {
$this->out['where_condition']['column_operators'][] = '!=';
}
logical_term ::= IN. {
$this->out['where_condition']['column_operators'][] = 'IN';
}
logical_term ::= LIKE. {
$this->out['where_condition']['column_operators'][] = 'LIKE';
}
order_clause ::= ORDERBY column_group clause. 
order_clause ::= . 
column_group ::= column_list .
column_list ::= column_exp COLUMNNAME(CN). {
$this->out['orderby'][] = CN;
}
column_exp ::= column_list COMMA. 
column_exp ::= .
clause ::= ASC. {
$this->out['sortOrder'] = 'ASC';
}
clause ::= DESC. {
$this->out['sortOrder'] = 'DESC';
}
clause ::= .
limit_clause ::= LIMIT limit_set. 
limit_clause ::= . 
limit_set ::= VALUE(LV). {
$this->out['limit'][] = LV;
}
limit_set ::= VALUE(LV) COMMA VALUE(LV2). {
$this->out['limit'][] = LV;
$this->out['limit'][] = LV2;
}
end_stmt ::= SEMICOLON(SEMI). {
global $adb;
if(!$this->out['meta']){
$module = $this->out['moduleName'];
$handler = vtws_getModuleHandlerFromName($module,$this->user);
$objectMeta = $handler->getMeta();
$this->out['meta'] = $objectMeta;
$meta = $this->out['meta'];
$fieldcol = $meta->getFieldColumnMapping();
$columns = array();
if(in_array($this->out['column_list'],'*')){
$columns = array_values($fieldcol);
}else if( !in_array(array_map(strcmp, $this->out['column_list']),'count(*)')){
foreach($this->out['column_list'] as $ind=>$field){
$columns[] = $fieldcol[$field];
}
}
if($this->out['where_condition']){
foreach($this->out['where_condition']['column_names'] as $ind=>$field){
$columns[] = $fieldcol[$field];
}
}
$tables = $this->getTables($this->out, $columns);
if(!in_array($objectMeta->getEntityBaseTable(), $tables)){
$tables[] = $objectMeta->getEntityBaseTable();
}
$defaultTableList = $objectMeta->getEntityDefaultTableList();
foreach($defaultTableList as $tableName){
if(!in_array($tableName,$tables)){
array_push($tables,$tableName);
}
}
$firstTable = $objectMeta->getEntityBaseTable();
$tabNameIndex = $objectMeta->getEntityTableIndexList();
$firstIndex = $tabNameIndex[$firstTable];
foreach($tables as $ind=>$table){
if($firstTable!=$table){
	if(!isset($tabNameIndex[$table]) && $table == "vtiger_crmentity"){
		$this->out['defaultJoinConditions'] = $this->out['defaultJoinConditions']." LEFT JOIN $table ON $firstTable.$firstIndex=$table.crmid";
	}else{
		$this->out['defaultJoinConditions'] = $this->out['defaultJoinConditions']." LEFT JOIN $table ON $firstTable.$firstIndex=$table.{$tabNameIndex[$table]}";
	}
}else{
	$this->out['tableName'] = $table;
}
}
}
}
%include_class {
/*
add this rule to add parenthesis support.
condition ::= PARENOPEN expr_set expr(E) PARENCLOSE.
sample format(for contacts) for generated sql object 
Array ( 
	[column_list] => c4,c3,c2,c1 
	[tableName] => vtiger_crmentity,vtiger_contactdetails,vtiger_contactaddress,vtiger_contactsubdetails,vtiger_contactscf,vtiger_customerdetails 
	[where_condition] => Array ( 
		[column_operators] => Array ( 
			[0] => = 
			[1] => = 
			[2] => = 
			) 
		[column_names] => Array ( 
			[0] => c1 
			[1] => c2 
			[2] => c3 
			) 
		[column_values] => Array ( 
			[0] => 'llet me' 
			[1] => 45 
			[2] => -1 
			) 
		//TO BE DONE
		[grouping] => Array (
			[0] => Array (
				[0] => 1
				[1] => 2
				)
			)
		[operators] => Array ( 
			[0] => and 
			[1] => or 
			)
		)
	[orderby] => Array ( 
		[0] => c4 
		[1] => c5 
		)
	[select] => SELECT 
	[from] => from 
	[semi_colon] => ; 
)*/
	private $out;
	public $lex;
	private $success ;
	private $query ;
	private $error_msg;
	private $syntax_error;
	private $user;
function __construct($user, $lex,$out){
	if(!is_array($out)){
		$out = array();
	}
	$this->out = &$out;
	$this->lex = $lex;
	$this->success = false;
	$this->error_msg ='';
	$this->query = '';
	$this->syntax_error = false;
	$this->user = $user;
}

function __toString(){
	return $this->value."";
}
function buildSelectStmt($sqlDump){
	$meta = $sqlDump['meta'];
	$fieldcol = $meta->getFieldColumnMapping();
	$columnTable = $meta->getColumnTableMapping();
	$this->query = 'SELECT ';
	if(in_array($sqlDump['column_list'],'*')){
		$i=0;
		foreach($fieldcol as $field=>$col){
			if($i===0){
				$this->query = $this->query.$columnTable[$col].'.'.$col;
				$i++;
			}else{
				$this->query = $this->query.','.$columnTable[$col].'.'.$col;
			}
		}
	}else if(in_array($sqlDump['column_list'],'count(*)')){
		$this->query = $this->query." COUNT(*)";
	}else{
		$i=0;
		foreach($sqlDump['column_list'] as $ind=>$field){
			if(!$fieldcol[$field]){
				throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED, "Permission to access '.$field.' attribute denied.");
			}
			if($i===0){
				$this->query = $this->query.$columnTable[$fieldcol[$field]].'.'.$fieldcol[$field];
				$i++;
			}else{
				$this->query = $this->query.','.$columnTable[$fieldcol[$field]].'.'.$fieldcol[$field];
			}
		}
	}
	$this->query = $this->query.' FROM '.$sqlDump['tableName'].$sqlDump['defaultJoinConditions'];
	$deletedQuery = $meta->getEntityDeletedQuery();
	$accessControlQuery = $meta->getEntityAccessControlQuery();
	$this->query = $this->query.' '.$accessControlQuery;
	if($sqlDump['where_condition']){
		if((php7_sizeof($sqlDump['where_condition']['column_names']) == 
		php7_sizeof($sqlDump['where_condition']['column_values'])) && 
		(php7_sizeof($sqlDump['where_condition']['column_operators']) == php7_sizeof($sqlDump['where_condition']['operators'])+1)){
			$this->query = $this->query.' WHERE (';
			$i=0;
			$referenceFields = $meta->getReferenceFieldDetails();
			$ownerFields = $meta->getOwnerFields();
			for(;$i<php7_sizeof($sqlDump['where_condition']['column_values']);++$i){
				if(!$fieldcol[$sqlDump['where_condition']['column_names'][$i]]){
					throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED, "Permission to access ".$sqlDump['where_condition']['column_names'][$i]." attribute denied.");
				}
				$whereField = $sqlDump['where_condition']['column_names'][$i];
				$whereOperator = $sqlDump['where_condition']['column_operators'][$i];
				$whereValue = $sqlDump['where_condition']['column_values'][$i];
				if(in_array($whereField,array_keys($referenceFields))){
					if(is_array($whereValue)){
						foreach($whereValue as $index=>$value){
							if(strpos($value,'x')===false){
								throw new WebServiceException(WebServiceErrorCode::$INVALIDID,"Id specified is incorrect");
							}
						}
						$whereValue = array_map(array($this, 'getReferenceValue'),$whereValue);
					}else if(strpos($whereValue,'x')!==false){
						$whereValue = $this->getReferenceValue($whereValue);
						if(strcasecmp($whereOperator,'like')===0){
							$whereValue = "'".$whereValue."'";
						}
					}else{
						throw new WebServiceException(WebServiceErrorCode::$INVALIDID,"Id specified is incorrect");
					}
				}else if(in_array($whereField,$ownerFields)){
					if(is_array($whereValue)){
						$groupId = array_map(array($this, 'getOwner'),$whereValue);
					}else{
						$groupId = $this->getOwner($whereValue);
						if(strcasecmp($whereOperator,'like')===0){
							$groupId = "'$groupId'";
						}
					}
					$whereValue = $groupId;
				}
				if(is_array($whereValue)){
					$whereValue = "(".implode(',',$whereValue).")";
				}elseif(strcasecmp($whereOperator, 'in') === 0){
					$whereValue = "($whereValue)";
				}
				$this->query = $this->query.$columnTable[$fieldcol[$whereField]].'.'.
									$fieldcol[$whereField]." ".$whereOperator." ".$whereValue;
				if($i <php7_sizeof($sqlDump['where_condition']['column_values'])-1){
					$this->query = $this->query.' ';
					$this->query = $this->query.$sqlDump['where_condition']['operators'][$i].' ';
				}
			}
		}else{
			throw new WebServiceException(WebServiceErrorCode::$QUERYSYNTAX, "columns data inappropriate");
		}
		$this->query = $this->query.")";
		$nextToken = ' AND ';
	}else{
		if(!empty($deletedQuery)){
			$nextToken = " WHERE ";
		}
	}
	if(strcasecmp('calendar',$this->out['moduleName'])===0){
		$this->query = $this->query." $nextToken activitytype='Task' AND ";
	}elseif(strcasecmp('events',$this->out['moduleName'])===0){
		$this->query = $this->query."$nextToken activitytype!='Emails' AND activitytype!='Task' AND ";
	}else if(strcasecmp('emails',$this->out['moduleName'])===0){
		$this->query = $this->query."$nextToken activitytype='Emails' AND ";
	}elseif(!empty($deletedQuery)){
		$this->query = $this->query.$nextToken;
	}
	
	$this->query = $this->query.' '.$deletedQuery;
	
	if($sqlDump['orderby']){
		$i=0;
		$this->query = $this->query.' ORDER BY ';
		foreach($sqlDump['orderby'] as $ind=>$field){
			if($i===0){
				$this->query = $this->query.$columnTable[$fieldcol[$field]].".".$fieldcol[$field];
				$i++;
			}else{
				$this->query = $this->query.','.$columnTable[$fieldcol[$field]].".".$fieldcol[$field];
			}
		}
		if($sqlDump['sortOrder']) {
			$this->query .= ' '.$sqlDump['sortOrder'];
		}
	}
	if($sqlDump['limit']){
		$i=0;
		$offset =false;
		if(php7_sizeof($sqlDump['limit'])>1){
			$offset = true;
		}
		$this->query = $this->query.' LIMIT ';
		foreach($sqlDump['limit'] as $ind=>$field){
			if(!$offset){
				$field = ($field>100)? 100: $field;
			}
			if($i===0){
				$this->query = $this->query.$field;
				$i++;
				$offset = false;
			}else{
				$this->query = $this->query.','.$field;
			}
		}
	}else{
		$this->query = $this->query.' LIMIT 100';
	}
	$this->query = $this->query.';';
}
function getTables($sqlDump,$columns){
	$meta = $sqlDump['meta'];
	$coltable = $meta->getColumnTableMapping();
	$tables = array();
	foreach($columns as $ind=>$col){
		$tables[$coltable[$col]] = $coltable[$col];
	}
	$tables = array_keys($tables);
	return ($tables);
}
function getReferenceValue($whereValue){
	$whereValue = trim($whereValue,'\'"');
	$whereValue = vtws_getIdComponents($whereValue);
	$whereValue = $whereValue[1];
	return $whereValue;	
}
function getOwner($whereValue){
	$whereValue = trim($whereValue,'\'"');
	$whereValue = vtws_getIdComponents($whereValue);
	$whereValue = $whereValue[1];
	return $whereValue;
}
function isSuccess(){
	return $this->success;
}
function getErrorMsg(){
	return $this->error_msg;
}
function getQuery(){
	return $this->query;
}
function getObjectMetaData(){
	return $this->out['meta'];
}
}
//%token_prefix    VTQL_
%declare_class {class VTQL_Parser}
%parse_accept {
		$this->success = true;
	}

%parse_failure {
	if(!$this->syntax_error){
		throw new WebServiceException(WebServiceErrorCode::$QUERYSYNTAX, "Parsing failed");
	}
}

%stack_overflow {
	throw new WebServiceException(WebServiceErrorCode::$QUERYSYNTAX, "Parser stack overflow");
}

%syntax_error {
	$synMsg = "Syntax Error on line " . $this->lex->linenum . ": token '" .$this->lex->value."' ";
	$expect = array();
	foreach ($this->yy_get_expected_tokens($yymajor) as $token) {
		$expect[] = self::$yyTokenName[$token];
	}
	$synMsg =$synMsg.('Unexpected ' . $this->tokenName($yymajor) . '(' . $TOKEN
		. '), expected one of: ' . implode(',', $expect));
	
	throw new WebServiceException(WebServiceErrorCode::$QUERYSYNTAX, $synMsg);
}

