All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.novartis.opensource.yada.util.QueryUtils Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2016 Novartis Institutes for BioMedical Research Inc.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.novartis.opensource.yada.util;

import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.YADAMarkupParameter;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.ValuesList;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import com.novartis.opensource.yada.ConnectionFactory;
import com.novartis.opensource.yada.Finder;
import com.novartis.opensource.yada.Parser;
import com.novartis.opensource.yada.QueryManager;
import com.novartis.opensource.yada.YADAConnectionException;
import com.novartis.opensource.yada.YADAParserException;
import com.novartis.opensource.yada.YADAQuery;
import com.novartis.opensource.yada.YADARequest;
import com.novartis.opensource.yada.YADAResourceException;
import com.novartis.opensource.yada.YADAUnsupportedAdaptorException;
import com.novartis.opensource.yada.adaptor.Adaptor;
import com.novartis.opensource.yada.adaptor.FileSystemAdaptor;
import com.novartis.opensource.yada.adaptor.JDBCAdaptor;
import com.novartis.opensource.yada.adaptor.RESTAdaptor;
import com.novartis.opensource.yada.adaptor.SOAPAdaptor;
import com.zaxxer.hikari.HikariDataSource;

/**
 * Utilities for query string manipulation and accounting.
 * 
 * @since 4.0.0
 * @author David Varon
 * 
 */
public class QueryUtils
{

	/**
	 * Local logger handle
	 */
	private static Logger l = Logger.getLogger(QueryUtils.class);
	/**
	 * A constant equal to: {@value}
	 * @since 9.1.0
	 */
	public static final String KEY_ADAPTOR = "adaptor";
	/**
   * A constant equal to: {@value}
   * @since 9.1.0
   */
  public static final String KEY_EXTENSION = "extn";
	/**
	 * A constant equal to: {@code com.novartis.opensource.yada.adaptor.JDBCAdaptor}
	 */
	public static final String JDBC_ADAPTOR_CLASS_NAME = JDBCAdaptor.class.getName();
	/**
	 * A constant equal to the class {@code com.novartis.opensource.yada.adaptor.JDBCAdaptor}
	 */
	public static final Class JDBC_ADAPTOR_CLASS = JDBCAdaptor.class;
	/**
	 * A constant equal to: {@code com.novartis.opensource.yada.adaptor.SOAPAdaptor}
	 */
	public static final String SOAP_ADAPTOR_CLASS_NAME = SOAPAdaptor.class.getName();
	/**
	 * A constant equal to the class {@code com.novartis.opensource.yada.adaptor.SOAPAdaptor}
	 */
	public static final Class SOAP_ADAPTOR_CLASS = SOAPAdaptor.class;
	/**
	 * A constant equal to: {@code com.novartis.opensource.yada.adaptor.RESTAdaptor}
	 */
	public static final String REST_ADAPTOR_CLASS_NAME = RESTAdaptor.class.getName();
	/**
	 * A constant equal to the class {@code com.novartis.opensource.yada.adaptor.RESTAdaptor}
	 */
	public static final Class REST_ADAPTOR_CLASS = RESTAdaptor.class;
	/**
	 * A constant equal to: {@code com.novartis.opensource.yada.adaptor.FileSystemAdaptor}
	 */
	public static final String FILESYSTEM_ADAPTOR_CLASS_NAME = FileSystemAdaptor.class.getName();
	/**
	 * A constant equal to the class {@code com.novartis.opensource.yada.adaptor.FileSystemAdaptor}
	 */
	public static final Class FILESYSTEM_ADAPTOR_CLASS = FileSystemAdaptor.class;
	/**
	 * A constant equal to: {@value}
	 */
	public static final String RX_FILE = "^file:.+$";
	/**
	 * A constant equal to: {@value}
	 * @since 8.0.0
	 */
	public static final String RX_JDBC_JNDI = "^java:.+/jdbc/.+$";
	/**
   * A constant equal to: {@value}. {@code (?s)} means "dot matches newline for rest of regex.
   * @since 8.0.0
   */
  public static final String RX_JDBC_CONF = "(?s).*jdbcUrl=jdbc:.+";
	/**
   * A constant equal to: {@value}
   */
  public static final String RX_JDBC = "^jdbc:.+$";
	/**
	 * A constant equal to: {@value}
	 */
	public static final String RX_SOAP = "^soaps?:.+$";
	/**
	 * A constant equal to: {@value}
	 */
	public static final String RX_CALLABLE = "^call .+\\(.*\\)\\s*$";
	/**
	 * A constant equal to: {@value}
	 */
	public static final String RX_SELECT = "^(?:WITH.+)?SELECT.+";
	/**
	 * A constant equal to: {@value}
	 */
	public static final String RX_INSERT = "^INSERT.+";
	/**
	 * A constant equal to: {@value}
	 */
	public static final String RX_UPDATE = "^UPDATE.+";
	/**
	 * A constant equal to: {@value}
	 */
	public static final String RX_DELETE = "^DELETE.+";
	/**
	 * A constant equal to: {@code ^([^<]+)((<{1,2})((?!rm|mkdir).+))*$ } 
	 */
	public final static String RX_FILE_URI = "^([^<]+)((<{1,2})((?!rm|mkdir).+))?$";
	/**
   * A constant equal to: {@code ^[^<]+ getAdaptorClass(String source, String version) throws YADAResourceException, YADAUnsupportedAdaptorException
	{
		String driverName = "";
		String className = REST_ADAPTOR_CLASS_NAME;
		l.debug("JNDI source is [" + source + "]");
		if (source.matches(RX_JDBC_JNDI))
		{
			Context ctx;
			try
			{
				ctx = new InitialContext();
			} 
			catch (NamingException e)
			{
				String msg = "Could not create context.";
				throw new YADAResourceException(msg, e);
			}
			DataSource ds;
			try
			{
				ds = (DataSource)ctx.lookup(source);
			} 
			catch (NamingException e)
			{
				String msg = "Could not find data source at " + source;
				throw new YADAResourceException(msg, e);
			}
			
			//TODO add integration tests for multiple containers after breaking tomcat dbcp dependency
			//     http://docs.oracle.com/javase/1.5.0/docs/api/java/sql/DriverManager.html?is-external=true
			driverName = ((HikariDataSource)ds).getDriverClassName();
			l.debug("JDBC driver is [" + driverName + "]");
			className = Finder.getEnv("adaptor/" + driverName + version);
		} 
		else if (source.matches(RX_SOAP))
		{
			className = SOAP_ADAPTOR_CLASS_NAME;
		} 
		else if (source.matches(RX_FILE))
		{
			className = FILESYSTEM_ADAPTOR_CLASS_NAME;
		}
		l.debug("JDBCAdaptor class is [" + className + "]");

		Class adaptorClass;
		try
		{
			adaptorClass = (Class)Class.forName(className);
		} 
		catch (ClassNotFoundException e)
		{
			String msg = "Could not find appropriate adaptor class";
			throw new YADAUnsupportedAdaptorException(msg, e);
		} 
		catch (NoClassDefFoundError e)
		{
			String msg = "Could not find appropriate adaptor class";
			throw new YADAUnsupportedAdaptorException(msg, e);
		}
		return adaptorClass;
	}

	/**
	 * 
	 * @param app the query's APP code
	 * @return Class of type Adaptor mapped to the {@code source}
	 * @throws YADAResourceException when {@code source} can't be found, or there is an issue with the application context
	 * @throws YADAUnsupportedAdaptorException when the adaptor class mapped to {@code source} can't be found
	 * @throws YADAConnectionException if a new datasource connection pool cannot be established or stored
	 */
	@SuppressWarnings("unchecked")
	public Class getAdaptorClass(String app) throws YADAResourceException, YADAUnsupportedAdaptorException, YADAConnectionException
	{
	  String driverName = "";
    String className = REST_ADAPTOR_CLASS_NAME;
    ConnectionFactory factory = ConnectionFactory.getConnectionFactory(); 
    String type = factory.getAppConnectionType(app);
    if(type == null)
    {
      factory.createDataSources();
      type = factory.getAppConnectionType(app);
    }
    
    if(type.equals(ConnectionFactory.TYPE_JDBC))
    {
      HikariDataSource ds = factory.getDataSourceMap().get(app);
      driverName = ds.getDriverClassName();
      className = Finder.getEnv("adaptor/" + driverName);
    }
    else if(type.equals(ConnectionFactory.TYPE_URL))
    {
      Properties props = ConnectionFactory.getConnectionFactory().getWsSourceMap().get(app);    
      String url  = props.getProperty(ConnectionFactory.YADA_CONF_SOURCE); 
      if (url.matches(RX_SOAP))
      {
        className = SOAP_ADAPTOR_CLASS_NAME;
      } 
      else if (url.matches(RX_FILE))
      {
        // interrogate url and or configuration object to obtain adaptor class or file extn
        if(props.containsKey(KEY_ADAPTOR))
        {
          className = props.getProperty(KEY_ADAPTOR);
        }
        else if(props.containsKey(KEY_EXTENSION))
        {
          
          className = Finder.getEnv(props.getProperty(KEY_EXTENSION.toLowerCase()));
        }
        else
        {
          className = FILESYSTEM_ADAPTOR_CLASS_NAME;
        }
      }
      // else default is REST, set up top
    }
    
    l.debug("JDBCAdaptor class is [" + className + "]");
    
    Class adaptorClass;
    try
    {
      adaptorClass = (Class)Class.forName(className);
    } 
    catch (ClassNotFoundException e)
    {
      String msg = "Could not find appropriate adaptor class";
      throw new YADAUnsupportedAdaptorException(msg, e);
    } 
    catch (NoClassDefFoundError e)
    {
      String msg = "Compiled adaptor class could not be loaded";
      throw new YADAUnsupportedAdaptorException(msg, e);
    }
    return adaptorClass;
	}

	/**
	 * 
	 * @param query the query object to process
	 * @return Class of type JDBCAdaptor mapped to the source string stored in the YADAQuery object
	 * @throws YADAResourceException when the source stored in query can't be found, or there is an issue with the application context
	 * @throws YADAUnsupportedAdaptorException when the adaptor class mapped to the source stored in the query can't be found
	 */
	public Class getAdaptorClass(YADAQuery query) throws YADAResourceException, YADAUnsupportedAdaptorException
	{
		return getAdaptorClass(query.getSource(), query.getVersion());
	}

	/**
	 * Returns an instance of {@code adaptorClass} using the "YADARequest"
	 * constructor.
	 * 
	 * @param adaptorClass Class of type JDBCAdaptor mapped to the source string stored in
	 *          the YADAQuery object
	 * @param yadaReq request config
	 * @return an adaptor instance of the provided class, with including the
	 *         service parameters
	 * @throws YADAUnsupportedAdaptorException when {@code adaptorClass} cannot be instantiated
	 */
	public Adaptor getAdaptor(Class adaptorClass, YADARequest yadaReq) throws YADAUnsupportedAdaptorException
	{
		try
		{
			return adaptorClass.getConstructor(YADARequest.class)
													.newInstance(yadaReq);
		} catch (InstantiationException e)
		{
			String msg = "Error instanting adaptor for class " + adaptorClass.getName();
			throw new YADAUnsupportedAdaptorException(msg, e);
		} catch (IllegalAccessException e)
		{
			String msg = "Error instanting adaptor for class " + adaptorClass.getName();
			throw new YADAUnsupportedAdaptorException(msg, e);
		} 
		catch (IllegalArgumentException e)
		{
			String msg = "Error instanting adaptor for class " + adaptorClass.getName();
			throw new YADAUnsupportedAdaptorException(msg, e);
		} 
		catch (SecurityException e)
		{
			String msg = "Error instanting adaptor for class " + adaptorClass.getName();
			throw new YADAUnsupportedAdaptorException(msg, e);
		} 
		catch (InvocationTargetException e)
		{
			String msg = "Error instanting adaptor for class " + adaptorClass.getName();
			throw new YADAUnsupportedAdaptorException(msg, e);
		} 
		catch (NoSuchMethodException e)
		{
			String msg = "Error instanting adaptor for class  s" + adaptorClass.getName();
			throw new YADAUnsupportedAdaptorException(msg, e);
		}

	}

	/**
	 * Returns an instance of {@code adaptorClass} using the no-arg constructor.
	 * 
	 * @param adaptorClass the class name of the YADA adaptor associated to the query
	 * @return an instance of the provided class
	 * @throws YADAUnsupportedAdaptorException when the {@code adaptorClass} cannot be instantiated
	 */
	public Adaptor getAdaptor(Class adaptorClass) throws YADAUnsupportedAdaptorException
	{
		try
		{
			return adaptorClass.getDeclaredConstructor().newInstance();
		} catch (InstantiationException|NoSuchMethodException|InvocationTargetException e)
		{
			String msg = "Error instantiating adaptor for class" + adaptorClass.getName();
			throw new YADAUnsupportedAdaptorException(msg, e);
		} catch (IllegalAccessException e)
		{
			String msg = "Error instantiating adaptor for class" + adaptorClass.getName();
			throw new YADAUnsupportedAdaptorException(msg, e);
		}
	}

	/**
	 * Calls {@link #getAdaptorClass(String)} to get the class, then
	 * {@link #getAdaptor(Class)} to get an instance, using the no-arg
	 * constructor.
	 * @param app the YADA app assigned to the datasource
	 * @return an instance of the adaptor class mapped to {@code app}
	 * @throws YADAResourceException when {@code source} is not mapped to an adaptor
	 * @throws YADAUnsupportedAdaptorException when the adaptor class cannot be instantiated
	 * @throws YADAConnectionException when the connection pool or string cannot be established
	 */
	public Adaptor getAdaptor(String app) throws YADAResourceException, YADAUnsupportedAdaptorException, YADAConnectionException
	{
		Class execClass = getAdaptorClass(app);
		return getAdaptor(execClass);
	}

	/**
	 * Returns {@code true} if the adaptor class is {@link #SOAP_ADAPTOR_CLASS}
	 * 
	 * @param adaptorClass
	 *          the class name of the YADA adaptor associated to the query
	 * @return {@code true} if the adaptor class is {@link #SOAP_ADAPTOR_CLASS}
	 */
	public boolean isSoap(Class adaptorClass)
	{
		return SOAP_ADAPTOR_CLASS.isAssignableFrom(adaptorClass);
	}

	/**
	 * Returns {@code true} if the adaptor class is {@link #REST_ADAPTOR_CLASS}
	 * 
	 * @param adaptorClass
	 *          the class name of the YADA adaptor associated to the query
	 * @return {@code true} if the adaptor class is {@link #REST_ADAPTOR_CLASS}
	 */
	public boolean isRest(Class adaptorClass)
	{
		return REST_ADAPTOR_CLASS.isAssignableFrom(adaptorClass);
	}

	/**
	 * Returns {@code true} if the adaptor class is
	 * {@link #FILESYSTEM_ADAPTOR_CLASS}
	 * 
	 * @param adaptorClass
	 *          the class name of the YADA adaptor associated to the query
	 * @return {@code true} if the adaptor class is
	 *         {@link #FILESYSTEM_ADAPTOR_CLASS}
	 */
	public boolean isFileSystem(Class adaptorClass)
	{
		return FILESYSTEM_ADAPTOR_CLASS.isAssignableFrom(adaptorClass);
	}
	/**
	 * @since 4.0.0
	 * @param adaptorClass
	 *          the class name of the YADA adaptor associated to the query
	 * @return boolean true if the Class of the JDBCAdaptor param is a
	 *         JDBCAdaptor, otherwise false
	 */
	public boolean isJdbc(Class adaptorClass)
	{
		return JDBC_ADAPTOR_CLASS.isAssignableFrom(adaptorClass);
	}

	/**
	 * Utility wrapper method to manage {@link Parser} instantiation and parsing.
	 * 
	 * @param yq
	 *          The query object containing the SQL to parse
	 * @throws YADAParserException when the parser fails
	 * @see com.novartis.opensource.yada.QueryManager
	 */

	private void processJDBCStatement(YADAQuery yq) throws YADAParserException
	{
	  // new method
		Parser parser = new Parser();
		
		parser.parseDeparse(yq.getYADACode());
		
		yq.setStatement(parser.getStatement());
		yq.setType(parser.getStatementType());
    yq.setColumnList(parser.getColumnList());
    yq.setInList(parser.getInColumnList());
    yq.setValuesList(parser.getValuesList());
    yq.setValuesColumns(parser.getValuesColumns());
    yq.setParameterizedColumnList(parser.getJdbcColumnList());
    yq.setInExpressionMap(parser.getInExpressionMap());
	}

	/**
	 * Initiates the parse/deparse process for a statement, and 
	 * recovers gracefully if {@link CCJSqlParserManager#parse(java.io.Reader)} throws a
	 * {@link JSQLParserException} in which case it will use a regular expression to infer 
	 * the query type.
	 * 
	 * @param yq the query object containing the code to parse
	 * @throws YADAUnsupportedAdaptorException when the adaptor can't be found or instantiated
	 */

	public void processStatement(YADAQuery yq) throws YADAUnsupportedAdaptorException
	{
		String         code         = getConformedCode(yq.getYADACode());
		Class adaptorClass = yq.getAdaptorClass();
		if (isJdbc(yq.getAdaptorClass()))
		{
			try
			{
			  // Attempts to parse the JDBC statement
				processJDBCStatement(yq); 
			} 
			catch (YADAParserException e)
			{
				l.warn("Attempting to qualify previously unparsable statement");
				if (isCallable(code))
					yq.setType(Parser.CALL);
				else if (isSelect(code))
					yq.setType(Parser.SELECT);
				else if (isUpdate(code))
					yq.setType(Parser.UPDATE);
				else if (isInsert(code))
					yq.setType(Parser.INSERT);
				else if (isDelete(code))
					yq.setType(Parser.DELETE);
			}
		} 
		else if (isSoap(adaptorClass))
		{
			yq.setType(Parser.SOAP);
		} 
		else if (isRest(adaptorClass))
		{
			yq.setType(Parser.REST);
		} 
		else if (isFileSystem(adaptorClass))
		{
			if (isRead(code))
				yq.setType(READ);
			else if (isWrite(code))
				yq.setType(WRITE);
			else if (isAppend(code))
        yq.setType(APPEND);
			else if (isRm(code))
        yq.setType(RM);
			else if (isMkdir(code))
        yq.setType(MKDIR);
		} 
		else
		{
			String msg = "The query you are attempting to execute requires a protocol or class that is not supported.  This could be a configuration issue.";
			throw new YADAUnsupportedAdaptorException(msg);
		}
	}

	/**
	 * Interrogates {@code yq} for the adaptor class and sets the protocol
	 * attribute accordingly.
	 * 
	 * @param yq the query object in which to set the protocol attribute
	 * @throws YADAUnsupportedAdaptorException when the adaptor class cannot be found
	 */
	public void setProtocol(YADAQuery yq) throws YADAUnsupportedAdaptorException
	{
		Class adaptorClass = yq.getAdaptorClass();
		if (isJdbc(adaptorClass))
		{
			yq.setProtocol(Parser.JDBC);
		}
		else if (isSoap(adaptorClass))
		{
			yq.setProtocol(Parser.SOAP);
		} 
		else if (isRest(adaptorClass))
		{
			yq.setProtocol(Parser.REST);
		} 
		else if (isFileSystem(adaptorClass))
		{
			yq.setProtocol(Parser.FILE);
		}
		else
		{
			String msg = "The query you are attempting to execute requires a protocol or class that is not supported.  This could be a configuration issue.";
			throw new YADAUnsupportedAdaptorException(msg);
		}
		yq.addParam(YADARequest.PS_PROTOCOL, yq.getProtocol());
	}

	/**
	 * Returns {@code true} if the query content matches an SQL callable statement
	 * syntax (see {@link #RX_CALLABLE}.
	 * 
	 * @param coreSql
	 *          stored code (with YADA markup)
	 * @return {@code true} if the query content matches an SQL callable statement
	 *         syntax
	 */
	public boolean isCallable(String coreSql)
	{
		Matcher matcher = Pattern.compile(RX_CALLABLE, Pattern.CASE_INSENSITIVE).matcher(coreSql);
		return matcher.matches();
	}

	/**
	 * Returns {@code true} if the query content matches an SQL SELECT statement
	 * syntax (see {@link #RX_SELECT}.
	 * 
	 * @param code
	 *          stored code (with YADA markup)
	 * @return {@code true} if the query content matches an SQL SELECT statement
	 *         syntax
	 */
	public boolean isSelect(String code)
	{
		Matcher matcher = Pattern.compile(RX_SELECT,
																			Pattern.DOTALL | Pattern.CASE_INSENSITIVE)
															.matcher(code);
		return matcher.matches();
	}
	/**
	 * Returns {@code true} if the query content matches an SQL UPDATE statement
	 * syntax (see {@link #RX_UPDATE}.
	 * 
	 * @param code
	 *          stored code (with YADA markup)
	 * @return {@code true} if the query content matches an SQL UPDATe statement
	 *         syntax
	 */
	public boolean isUpdate(String code)
	{
		Matcher matcher = Pattern.compile(RX_UPDATE,
																			Pattern.DOTALL | Pattern.CASE_INSENSITIVE)
															.matcher(code);
		return matcher.matches();
	}
	/**
	 * Returns {@code true} if the query type matches {@link Parser#UPDATE}.
	 * 
	 * @param yq
	 *          the query object to check
	 * @return {@code true} if the query type is {@link Parser#UPDATE}. s
	 */
	public boolean isUpdate(YADAQuery yq)
	{
		return yq.getType().equals(Parser.UPDATE);
	}
	/**
	 * Returns {@code true} if the query content matches an SQL INSERT statement
	 * syntax (see {@link #RX_INSERT}.
	 * 
	 * @param code
	 *          stored code (with YADA markup)
	 * @return {@code true} if the query content matches an SQL INSERT statement
	 *         syntax
	 */
	public boolean isInsert(String code)
	{
		Matcher matcher = Pattern.compile(RX_INSERT,
																			Pattern.DOTALL | Pattern.CASE_INSENSITIVE)
															.matcher(code);
		return matcher.matches();
	}
	/**
	 * Returns {@code true} if the query type matches {@link Parser#INSERT}.
	 * 
	 * @param yq
	 *          the query object to check
	 * @return {@code true} if the query type is {@link Parser#INSERT}.
	 */
	public boolean isInsert(YADAQuery yq)
	{
		return yq.getType().equals(Parser.INSERT);
	}
	/**
	 * Returns {@code true} if the query content matches an SQL DELETE statement
	 * syntax (see {@link #RX_DELETE}.
	 * 
	 * @param code
	 *          stored code (with YADA markup)
	 * @return {@code true} if the query content matches an SQL DELETE statement
	 *         syntax
	 */
	public boolean isDelete(String code)
	{
		Matcher matcher = Pattern.compile(RX_DELETE,
																			Pattern.DOTALL | Pattern.CASE_INSENSITIVE)
															.matcher(code);
		return matcher.matches();
	}
	/**
	 * Returns {@code true} if the query type matches {@link Parser#DELETE}.
	 * 
	 * @param yq
	 *          the query object to check
	 * @return {@code true} if the query type is {@link Parser#DELETE}.
	 */
	public boolean isDelete(YADAQuery yq)
	{
		return yq.getType().equals(Parser.DELETE);
	}

	/**
	 * Returns {@code true} if the query content matches an SQL DELETE statement
	 * syntax (see {@link #RX_DELETE}.
	 * 
	 * @param code
	 *          stored code (with YADA markup)
	 * @return {@code true} if the query content matches an SQL DELETE statement
	 *         syntax
	 * @since PROVISIONAL
	 */
	public boolean isRead(String code)
	{
		Matcher m1 = Pattern.compile(RX_FILE_URI).matcher(code);
		if (m1.matches())
		{
			if (m1.groupCount() > 1 && m1.group(3) != null)
			{
				return false;
			}
			return true;
		}
		return false;
	}
	/**
	 * Returns {@code true} if the query content matches an the {@link #RX_FILE_URI} regex
	 * 
	 * @param code
	 *          stored code (with YADA markup)
	 * @return {@code true} if the query content matches
	 *         
	 */
	public boolean isWrite(String code)
	{
		Matcher m1 = Pattern.compile(RX_FILE_URI).matcher(code);
		if (m1.matches())
		{
			if (m1.groupCount() > 1 && m1.group(3) != null && m1.group(3)
																													.equals(WRITE))
			{
				return true;
			}
			return false;
		}
		return false;
	}
	
	/**
   * Returns {@code true} if the query content matches an the {@link #RX_FILE_RM} regex
   * 
   * @param code
   *          stored code (with YADA markup)
   * @return {@code true} if the query content matches
   * @since 9.0.3        
   */
  public boolean isRm(String code)
  {
    Matcher m1 = Pattern.compile(RX_FILE_RM).matcher(code);
    return m1.matches();
  }
  
  /**
   * Returns {@code true} if the query content matches an the {@link #RX_FILE_MKDIR} regex
   * 
   * @param code
   *          stored code (with YADA markup)
   * @return {@code true} if the query content matches
   * @since 9.0.3        
   */
  public boolean isMkdir(String code)
  {
    Matcher m1 = Pattern.compile(RX_FILE_MKDIR).matcher(code);
    return m1.matches();
  }

	/**
	 * Test if the query requires a stored connection
	 * 
	 * @param yq
	 *          the query to evaluate
	 * @return {@code true} if the query's protocol is {@link Parser#JDBC} or
	 *         {@link Parser#SOAP}
	 */
	public boolean requiresConnection(YADAQuery yq)
	{
		return (yq.getProtocol().equals(Parser.JDBC) // TODO this is now redundant to YADAQuery.getType -- perhaps this could be refactored 
		    || yq.getProtocol().equals(Parser.SOAP));
	}

	/**
	 * Returns {@code true} if the {@link YADARequest#PS_COMMITQUERY} parameter is
	 * not {@code null}, has a length > 0, equals {@code true}, and the query type
	 * is equal to {@link Parser#INSERT}, {@link Parser#UPDATE}, or
	 * {@link Parser#DELETE}
	 * 
	 * @param yq
	 *          the query to commit
	 * @return {@code true} if the {@link YADARequest#PS_COMMITQUERY} parameter is
	 *         not {@code null}, has a length > 0, equals {@code true}, and the
	 *         query type is equal to {@link Parser#INSERT}, {@link Parser#UPDATE}
	 *         , or {@link Parser#DELETE}
	 * @since 4.1.0
	 */
	public boolean isCommitQuery(YADAQuery yq)
	{
		return yq.getYADAQueryParamValue(YADARequest.PS_COMMITQUERY) != null 
				&& yq.getYADAQueryParamValue(YADARequest.PS_COMMITQUERY).length > 0 
				&& Boolean.parseBoolean(yq.getYADAQueryParamValue(YADARequest.PS_COMMITQUERY)[0]) 
				&& (this.isInsert(yq) || this.isUpdate(yq) || this.isDelete(yq));
	}

	/**
	 * Returns {@code true} if the query content matches an SQL DELETE statement
	 * syntax (see {@link #RX_DELETE}.
	 * 
	 * @param code
	 *          stored code (with YADA markup)
	 * @return {@code true} if the query content matches an SQL DELETE statement
	 *         syntax
	 * @since PROVISIONAL
	 */
	public boolean isAppend(String code)
	{
		Matcher m1 = Pattern.compile(RX_FILE_URI).matcher(code);
		if (m1.matches())
		{
			if (m1.groupCount() > 1 && m1.group(3) != null && m1.group(3)
																													.equals(APPEND))
			{
				return true;
			}
			return false;
		}
		return false;
	}

	/**
	 * Returns the soap query that is passed as an argument. This method does
	 * nothing.
	 * 
	 * @param xmlStr
	 *          the soap query
	 * @return the soap query
	 */
	public String getSoap(String xmlStr)
	{
		return xmlStr;
	}

	/**
	 * Returns a {@link URL} built from the provided {@code urlStr}
	 * 
	 * @param urlStr
	 *          the url string to convert to an object
	 * @return a {@link URL} object
	 */
	public URL getUrl(String urlStr)
	{
		URL url = null;
		try
		{
			url = new URL(urlStr);
		} 
		catch (MalformedURLException e)
		{
			e.printStackTrace();
			l.error(e.getMessage());
		}
		return url;
	}

	/**
	 * Creates and returns a {@link java.sql.CallableStatement} from the
	 * {@code sql} on the {@code conn}.
	 * 

* NOTE: not sure why this method isn't throwing an * exception. *

* * @param sql * conformed code (without YADA markup) * @param conn * connection object derived from YADA query's source attribute * @return the desired statement object */ public CallableStatement getCallableStatement(String sql, Connection conn) { CallableStatement c = null; try { c = conn.prepareCall(sql); } catch (SQLException e) { l.error(e.getMessage()); } return c; } /** * Creates and returns a {@link java.sql.PreparedStatement} from {@code sql} * on the {@code conn} * * @param sql conformed code (without YADA markup) * @param conn connection object derived from YADA query's source attribute * @return the desired statement object * @throws YADAConnectionException when the connection cannot deliver the statement */ public PreparedStatement getPreparedStatement(String sql, Connection conn) throws YADAConnectionException { PreparedStatement pstmt = null; try { pstmt = conn.prepareStatement(sql); } catch (SQLException e) { String msg = "Unable to create or configure the PreparedStatementfor the requested query in the YADA Index."; throw new YADAConnectionException(msg, e); } return pstmt; } /** * Removes all YADA data type symbols from source code. * * @param code * stored code with YADA markup * @return the transformed code */ public String getConformedCode(String code) { String c = code.replaceAll("\\?[nvdti]", "?"); return c; } /** * Parses the source code, generating a {@code char} array of data types in * order of occurrence. * * @param sql * stored code with YADA markup * @return a {@code char} array of data types */ public char[] getDataTypes(String sql) { int count = sql.split("\\?(?=[vindt])").length - 1; char[] dataTypes = new char[count]; int idx = 0; for (int i = 0; i < count; i++) { idx = sql.indexOf("?", idx) + 1; if(String.valueOf(sql.charAt(idx)).matches("[vindt]")) { dataTypes[i] = sql.charAt(idx); l.debug("data type of param [" + String.valueOf(i + 1) + "] = " + dataTypes[i]); } } return dataTypes; } /** * This method creates a list for positional-indexed storage of data values, * then iterates over the list of jdbc-parameterized columns, extracting the * corresponding values from the data map. It then stores the value in the * list at it's proper positional index. Finally, the indexed value list is * added to the list of value lists in the query {@code yq}, at position {@code row}. * * This method uses the value returned by {@link YADAQuery#getParameterizedColumnList()} * instead of {@link YADAQuery#getParameterizedColumns()}, and * supercedes {@link #setValsInPosition(YADAQuery, int)} * * @param yq the query containing the data to process * @param row the index of the data list * @since 7.1.0 */ public void setPositionalParameterValues(YADAQuery yq, int row) { // the indexed positional value list for the current "row" List valsInPosition = new ArrayList<>(); // any subtables in values claused previously processed in the loop below List processedValuesAliases = new ArrayList<>(); // only process if there's data if (yq.getData().size() > 0) { // get the columns corresponding to jdbc parameters List paramColumns = yq.getParameterizedColumnList(); // get the data for the current "row" Map data = yq.getDataRow(row); // iterate over parameterized columns in the query // for (int paramColIndex = 0; paramColIndex < paramColumms.size(); paramColIndex++) int paramColIndex = 0; for(Column column : paramColumns) { // String paramColName = paramColumns.get(paramColIndex).getColumnName(); String paramColName = column.getColumnName(); // get table name Table table = column.getTable(); String tabName = ""; if(table.getAlias() != null && !table.getAlias().getName().contentEquals("")) { tabName = table.getAlias().getName().trim(); } else if(table.getName() != null) { tabName = table.getName().trim(); } if(data.containsKey(tabName)) { paramColName = tabName; } else if(data.containsKey(tabName.toUpperCase())) { paramColName = tabName.toUpperCase(); } // standard params or deliberate names, i.e., REST params else if(data.containsKey(YADA_COLUMN + (paramColIndex + 1))) paramColName = YADA_COLUMN + (paramColIndex + 1); // named columns in json params else if (data.containsKey(paramColName.toUpperCase())) paramColName = paramColName.toUpperCase(); // named columns with quotes else if(data.containsKey(paramColName.replaceAll("\"", "").toUpperCase())) paramColName = paramColName.replaceAll("\"", "").toUpperCase(); // obtain the values for the column String[] valsForColumn; valsForColumn = data.get(paramColName); if(!processedValuesAliases.contains(tabName)) { // handle VALUE columns if(yq.getValuesList() != null && yq.getValuesList().getAlias() != null && yq.getValuesList().getAlias().getName().trim().contentEquals(tabName)) { String alias = yq.getValuesList().getAlias().getName(); if(valsForColumn != null && valsForColumn[0].contains(",")) { valsForColumn = String.join(",", Arrays.asList(valsForColumn)).split(","); } /* * A query such as: * * select * from tab a join (values (?v, ?v)) vals(v,w) on a.col1 = vals.v and a.col2 = ?i * * with standard paramters would contain the following: * * YADA_1 = [(A,1),(B,2),(Z,3)] * * YADA_3 = 1 * * with JSONParams, data would be * * {"vals":[(A,1),(B,2),(Z,3)],"cols2":1} * * valsInPosition must result in [A,1,B,2,C,3,1] * */ // if we haven't processed the values alias yet, do them all for(int valIndex = 0; valIndex < valsForColumn.length; valIndex++) { String val = valsForColumn[valIndex]; String cleanVal = ""; Matcher m_paren = RX_VALUES_PARAM_SINGLE.matcher(val); Matcher m_left = RX_VALUES_PARAM_LEFT.matcher(val); Matcher m_right = RX_VALUES_PARAM_RIGHT.matcher(val); if(m_paren.matches()) { cleanVal = m_paren.group(1); } else if(m_left.matches()) { cleanVal = m_left.group(1); } else if(m_right.matches()) { cleanVal = m_right.group(1); } else { // middle of value set cleanVal = val; } valsInPosition.add(cleanVal); } processedValuesAliases.add(alias); } else { // add the values in the correct order for (String val : valsForColumn) { l.debug("Column [" + String.valueOf(paramColIndex + 1) + ": " + paramColumns.get(paramColIndex) + "] has value [" + val + "]"); valsInPosition.add(val); } } paramColIndex++; } } } yq.addVals(row, valsInPosition); } /** * This method creates a list for positional-indexed storage of data values, * then iterates over the list of jdbc-parameterized columns, extracting the * corresponding values from the data map. It then stores the value in the * list at it's proper positional index. Finally, the indexed value list is * added to the list of value lists in the query {@code yq}, at position * {@code row}. * * @param yq * the query containing the data to process * @param row * the index of the data list */ public void setValsInPosition(YADAQuery yq, int row) { List valsInPosition = new ArrayList<>(); if (yq.getData().size() > 0) { String[] columns = yq.getParameterizedColumns(); Map data = yq.getDataRow(row); for (int j = 0; j < columns.length; j++) { String colName = columns[j]; if(data.containsKey(YADA_COLUMN + (j + 1))) colName = YADA_COLUMN + (j + 1); else if (data.containsKey(colName.toUpperCase())) colName = colName.toUpperCase(); String[] valsForColumn; valsForColumn = data.get(colName); for (String val : valsForColumn) { l.debug("Column [" + String.valueOf(j + 1) + ": " + columns[j] + "] has value [" + val + "]"); valsInPosition.add(val); } } } yq.addVals(row, valsInPosition); } /** * While processing JDBC statements, this method handles mapping of data * values to JDBC positional parameters. * * @param yq * the query containing the statement * @param row * the index of the value list stored in the query */ public void setQueryParameters(YADAQuery yq, int row) { int jdbcParamCount = yq.getParamCount(row); char[] dataTypes = yq.getDataTypes(row); List valsInPosition = yq.getVals(row); PreparedStatement pstmt = yq.getPstmt(row); for (int j = 0; j < jdbcParamCount; j++) { int position = j + 1; char dt = dataTypes[j]; String val = valsInPosition.get(j); setQueryParameter(pstmt, position, dt, val); } } /** * Calls the appropriate setter method for {@code type} in the {@code pstmt}, * performing the appropriate type conversion or syntax change as needed * (e.g., for {@link java.sql.Date}s) * * @param pstmt * the statement to which to assign the parameter values * @param index * the position of the parameter * @param type * the data type of the parameter * @param val * the value to assign */ private void setQueryParameter(PreparedStatement pstmt, int index, char type, String val) { String idx = (index < 10) ? " " + String.valueOf(index) : String.valueOf(index); l.debug("Setting param [" + idx + "] of type [" + String.valueOf(type) + "] to: " + val); try { switch (type) { case DATE : try { if ("".equals(val) || val == null) { pstmt.setNull(index, java.sql.Types.DATE); } else { SimpleDateFormat sdf = new SimpleDateFormat(STANDARD_DATE_FMT); ParsePosition pp = new ParsePosition(0); Date dateVal = sdf.parse(val, pp); if (dateVal == null) { sdf = new SimpleDateFormat(ORACLE_DATE_FMT); dateVal = sdf.parse(val, pp); } if (dateVal != null) { long t = dateVal.getTime(); java.sql.Date sqlDateVal = new java.sql.Date(t); pstmt.setDate(index, sqlDateVal); } } } catch (Exception e) { l.error("Error: " + e.getMessage()); } break; case INTEGER : try { int ival = Integer.parseInt(val); pstmt.setInt(index, ival); } catch (NumberFormatException nfe) { l.error("Error: " + nfe.getMessage()); l.debug("Setting param [" + String.valueOf(index) + "] of type [" + String.valueOf(type) + "] to: null"); pstmt.setNull(index, java.sql.Types.INTEGER); } catch (NullPointerException npe) { l.error("Error: " + npe.getMessage()); l.debug("Setting param [" + String.valueOf(index) + "] of type [" + String.valueOf(type) + "] to: null"); pstmt.setNull(index, java.sql.Types.INTEGER); } catch (Exception sqle) { l.error("Error: " + sqle.getMessage()); l.debug("Setting param [" + String.valueOf(index) + "] of type [" + String.valueOf(type) + "] to: 0"); pstmt.setNull(index, java.sql.Types.INTEGER); } break; case NUMBER : try { float fval = Float.parseFloat(val); pstmt.setFloat(index, fval); } catch (NumberFormatException nfe) { l.error("Error: " + nfe.getMessage()); l.debug("Setting param [" + String.valueOf(index) + "] of type [" + String.valueOf(type) + "] to: null"); pstmt.setNull(index, java.sql.Types.INTEGER); } catch (NullPointerException npe) { l.error("Error: " + npe.getMessage()); l.debug("Setting param [" + String.valueOf(index) + "] of type [" + String.valueOf(type) + "] to: null"); pstmt.setNull(index, java.sql.Types.INTEGER); } catch (Exception sqle) { l.error("Error: " + sqle.getMessage()); l.debug("Setting param [" + String.valueOf(index) + "] of type [" + String.valueOf(type) + "] to: null"); pstmt.setNull(index, java.sql.Types.INTEGER); } break; case OUTPARAM_DATE : ((CallableStatement)pstmt).registerOutParameter(index, java.sql.Types.DATE); break; case OUTPARAM_INTEGER : ((CallableStatement)pstmt).registerOutParameter(index, java.sql.Types.INTEGER); break; case OUTPARAM_NUMBER : ((CallableStatement)pstmt).registerOutParameter(index, java.sql.Types.FLOAT); break; case OUTPARAM_VARCHAR : ((CallableStatement)pstmt).registerOutParameter(index, java.sql.Types.VARCHAR); break; default : // VARCHAR2 pstmt.setString(index, val); break; } } catch (SQLException e) { e.printStackTrace(); l.error(e.getMessage()); } } /** * Uses the metadata collected during {@link Parser#parseDeparse(String)} to * modify the {@link Statement} by appending positional parameters to IN clause * expression lists, dynamically, based on the data passed in the request. * * Also stores the transformed data types, param counts, and SQL corresponding to the row * so the return value is for illustrative or logging purposes only, in all likelihood. * @param yq the query to process * @param row the index of the data array passed for processing * @return the modified YADA SQL * @throws YADAParserException if parsing or deparsing the query encounters a non-conforming state * @since 7.1.0 */ public String processInList(YADAQuery yq, int row) throws YADAParserException { Parser parser = new Parser(); // Are there "in" columns if(yq.getInList().size() > 0) { //TODO there should be a better way than reparsing to get access to the parser. // like aren't the incols and expression map already in the query at this point? // Probably just need to store the statement object in the yq try { parser.parseDeparse(yq.getYADACode()); } catch (YADAParserException e) { String msg = "Unable to reparse statement for IN clause processing."; throw new YADAParserException(msg, e); } List inColumns = parser.getInColumnList(); Map inExprs = parser.getInExpressionMap(); Map dataForRow = yq.getDataRow(row); // iterate inColumns list for(int colIndex=0; colIndex inColumns.size()) // more values { StringBuilder inVals = new StringBuilder(); for(int i=colIndex+1;i<=dataForRow.keySet().size();i++) { if(i > colIndex+1) inVals.append(","); String name = YADA_COLUMN + i; int j=0; while(j 0) inVals.append(","); inVals.append(dataForRow.get(name)[j++]); } } dataForRow.put(colName, inVals.toString().split(",")); dataLen = dataForRow.get(colName).length; } // amend the in clause with the additional markup InExpression inExpr = inExprs.get(inColumn); ItemsList rightItemsList = inExpr.getRightItemsList(); List rightItemsExpressionList = ((ExpressionList)rightItemsList).getExpressions(); String dataType = String.valueOf(((YADAMarkupParameter)rightItemsExpressionList.get(0)).getType()); for(int i=0;i newData = new LinkedHashMap<>(); // to be i.e., YADA_1:[],YADA_2:[] if (inColumns.length > 0) { String[] columns = yq.getParameterizedColumns(); Map data = yq.getDataRow(row); char[] dataTypes = yq.getDataTypes(row); Matcher matcher; l.debug("Processing inColumns [" + StringUtils.join(inColumns, ",") + "]"); for (String in : inColumns) { int colIndex = -1, j = 0; String inCol = in.toUpperCase(); // get the index of the 'incolumn' in the 'JDBCcolumns' array l.debug("Looking for column [" + inCol + "] in columns array " + ArrayUtils.toString(columns)); while (j < columns.length && colIndex != j) { if (inCol.contains(columns[j])) { colIndex = j; l.debug("Found column [" + inCol + "] at index [" + String.valueOf(colIndex) + "] of columns array."); break; } j++; } // get the value list associated to the column in the data hash String colName = ""; String[] inData = null; int inLen = 0; if (data.containsKey(columns[colIndex])) // JSONParams { colName = columns[colIndex]; if (data.get(colName).length == 1) { inData = data.get(colName)[0].split(","); for (int m = 0; m < columns.length; m++) { if (columns[m].equals(colName)) { // add the new data for the column newData.put(colName, inData); } else { // add the existing data for the column newData.put(columns[m], data.get(columns[m])); } // add data row yq.getData().set(row, newData); } yq.getData().set(row, newData); } else inData = data.get(colName); l.debug("Splitting in args [" + data.get(colName) + "]"); } else // Standard Params { // Get an array of keys to compare and potentially manipulate String[] colNames = new String[data.size()]; int k = 0; for (String col : data.keySet()) { colNames[k] = col; k++; } // if colNames and columns array are of equal size, // then there is no param value manipulation required if (colNames.length == columns.length) { colName = QueryUtils.YADA_COLUMN + (colIndex + 1); inData = data.get(colName); } else // there is a length discrepancy { for (int m = colIndex; m < colNames.length; m++) { if (m == colIndex) // it's the first index inData = data.get(colNames[m]); else // further indexes must build aggregate array inData = (String[])ArrayUtils.addAll( inData, data.get(colNames[m])); } for (int m = 0; m < columns.length; m++) { if (m == columns.length - 1) { // it's the last index, so add the aggregrate inData array newData.put(colNames[m], inData); } else { // not the last index, add the existing array newData.put(colNames[m], data.get(colNames[m])); } // add data row yq.getData().set(row, newData); } } l.debug("Setting IN args [" + ArrayUtils.toString(inData) + "]"); } if (inData != null) { inLen = inData.length; } if (inLen > 1) // there's an aggregate of multiple values { l.debug("Length of value list [" + String.valueOf(inLen) + "]"); l.debug("Getting data type of [" + columns[colIndex] + "]"); char dt = dataTypes[colIndex]; String dtStr = "?" + String.valueOf(dt); // generate the new parameter string with data type markers String[] pList = new String[inLen]; for (int k = 0; k < inLen; k++) { pList[k] = dtStr; } String pListStr = StringUtils.join(pList, ","); l.debug("New parameter list [" + pListStr + "]"); // add additional parameters to coreSql String rx = "(.+)(" + inCol + "\\s+in\\s+\\(\\" + dtStr + "\\))(.*)"; String repl = inCol + " IN (" + pListStr + ")"; String sql = coreSql.replaceAll(NEWLINE, " "); l.debug("Attempting to replace part of [" + sql + "] with [" + repl + "]"); matcher = Pattern.compile(rx, Pattern.CASE_INSENSITIVE).matcher(sql); if (matcher.matches()) { coreSql = matcher.group(1) + repl + matcher.group(3); } l.debug("Matched clause in coreSql [" + matcher.toString() + "]"); } // end current incolumn processing } // end all incolumn processing } // reset datatype and param count with new coreSql yq.addDataTypes(row, this.getDataTypes(coreSql)); yq.addParamCount(row, yq.getDataTypes(row).length); return coreSql; } /** * Replaces YADA markup in {@code VALUES} clause in {@code SELECT} statement with * corresponding parameter values. Called internally by {@link QueryManager} * during {@link QueryManager#prepQueryForExecution} processing. * @param yq * the query being processed * @param row * the index of the list of value lists in the query containing the * data to evaluate * @return the {@code SQL} string with transformed {@code VALUES} clause * @throws YADAParserException when statement parsing fails */ public String processValuesList(YADAQuery yq, int row) throws YADAParserException { // Reparsing happens for each iteration of the data. This is // necessary as it's a tradeoff between iterating over the data // then or now, and also parsing the query Parser parser = new Parser(); /* * What are all the different scenarios: * * - join (values(?v)) vals(v) = 1 column * - join (values(?v,?v) vals(v,w) = >1 column * - join (values(?v,'x') vals(v,w) = >1 column with literal */ if(yq.getValuesList() != null) { try { parser.parseDeparse(yq.getYADACode()); } catch (YADAParserException e) { String msg = "Unable to reparse statement for VALUES JOIN clause processing."; throw new YADAParserException(msg, e); } ValuesList valuesList = parser.getValuesList(); List valColumns = valuesList.getColumnNames(); Map dataForRow = yq.getDataRow(row); int colIndex = 0; String colName = valColumns.get(colIndex); String tabName = valuesList.getAlias().getName(); if(colName != null) { if(dataForRow.containsKey(tabName)) { colName = tabName; } else if(dataForRow.containsKey(tabName.toUpperCase())) { colName = tabName.toUpperCase(); } else if(dataForRow.containsKey(YADA_COLUMN + (colIndex+1))) { // standard params // TODO I think this means data at end of params list colName = YADA_COLUMN + (colIndex + 1); } else if(dataForRow.containsKey(colName.toUpperCase())) { // json params upper case colName = colName.toUpperCase(); } else if(dataForRow.containsKey(colName.replaceAll("\"", "").toUpperCase())) { // json params, quoted column nams, e.g., postgres names with spaces or mixed case colName = colName.replaceAll("\"", "").toUpperCase(); } // length of value array for valColumn. // all we want here are the sets of parens Matcher m = RX_VALUES_PARAM_STRING.matcher(String.join(",",dataForRow.get(colName))); int dataLen = 0; while(m.find()) { dataLen++; } // int dataLen = dataForRow.get(colName).length; // special case of comma-separated strings, e.g., ["A,B,C"] (instead of ["A","B","C"]) if(dataLen == 1) { dataForRow.put(colName, dataForRow.get(colName)[0].split(",")); dataLen = dataForRow.get(colName).length; } // special case of standard params without brackets e.g., p=1,2,3,4 if(colName.startsWith(YADA_COLUMN) && colIndex == (valColumns.size() - 1) // last index && dataForRow.keySet().size() > valColumns.size()) // more values { StringBuilder valuesVals = new StringBuilder(); for(int i=colIndex+1;i<=dataForRow.keySet().size();i++) { if(i > colIndex+1) valuesVals.append(","); String name = YADA_COLUMN + i; int j=0; while(j 0) valuesVals.append(","); valuesVals.append(dataForRow.get(name)[j++]); } } dataForRow.put(colName, valuesVals.toString().split(",")); dataLen = dataForRow.get(colName).length; } //TODO this will limit use cases to YADA queries that contain VALUES clauses containing only a single expression list // i.e., (VALUES (?v,?v,?v...)) vals(x,y,z...) will work // but (VALUES (?v,?v,?v...),(?v,?v,?v...)) vals(x,y,z...) wont work // column list: [v, w, x] // multiExpressionList = (?v, ?v, 'xyz') // I assume the multiexpressionlist is just a container for the "expressionlist list" // and entries would take the form (x1,y1,z1),(xn,yn,zn) // so what we're doing here is creating new expression lists and adding them to the // multiExpressionList.exprList.expressions: [?v, ?v, 'xyz'] MultiExpressionList mel = valuesList.getMultiExpressionList(); ExpressionList el = mel.getExprList().get(0); List exprList = el.getExpressions(); // All we need to do here is create a new expression list for each entry in the data row. // TODO BUT, there is a complication with how to pass the data and handle the parameter value mapping for(int i=0; i neo = new ArrayList<>(exprList); mel.addExpressionList(neo); } } yq.addDataTypes(row, getDataTypes(parser.getStatement().toString())); yq.addParamCount(row, yq.getDataTypes(row).length); yq.addCoreCode(row, parser.getStatement().toString()); } return parser.getStatement().toString(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy