blob: 8860e7abfd2f766edf14a1006b3611df648891da [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/*
* $Id$
*/
/**
* This is used by the SQLDocumentHandler for processing JDBC queries.
* This prepares JDBC PreparedStatement or CallableStatements and the
* input/output of parameters from/to variables.
*
*/
package org.apache.xalan.lib.sql;
import java.util.*;
import java.sql.*;
import org.apache.xpath.objects.*;
import org.apache.xalan.extensions.ExpressionContext;
import org.apache.xml.utils.QName;
import javax.xml.transform.TransformerException;
public class SQLQueryParser
{
/**
* If the parser used inline parser to pull out variables then
* this will be true. The default is not to use the Inline Parser.
*/
private boolean m_InlineVariables = false;
/**
*
*/
private boolean m_IsCallable = false;
/**
*
*/
private String m_OrigQuery = null;
/**
*
*/
private StringBuffer m_ParsedQuery = null;
/**
*
*/
private Vector m_Parameters = null;
/**
*
*/
private boolean m_hasOutput = false;
/**
*
*/
private boolean m_HasParameters;
public static final int NO_OVERRIDE = 0;
public static final int NO_INLINE_PARSER = 1;
public static final int INLINE_PARSER = 2;
/**
* The SQLStatement Parser will be created as a psuedo SINGLETON per
* XConnection. Since we are only caching the Query and its parsed results
* we may be able to use this as a real SINGLETON. It all depends on how
* Statement Caching will play out.
*/
public SQLQueryParser()
{
init();
}
/**
* Constructor, used to create a new parser entry
*/
private SQLQueryParser(String query)
{
m_OrigQuery = query;
}
/**
* On a per Xconnection basis, we will create a SQLStatemenetParser, from
* this parser, individual parsers will be created. The Init method is defined
* to initialize all the internal structures that maintains the pool of parsers.
*/
private void init()
{
// Do nothing for now.
}
/**
* Produce an SQL Statement Parser based on the incomming query.
*
* For now we will just create a new object, in the future we may have this
* interface cache the queries so that we can take advantage of a preparsed
* String.
*
* If the Inline Parser is not enabled in the Options, no action will be
* taken on the parser. This option can be set by the Stylesheet. If the
* option is not set or cleared, a default value will be set determined
* by the way variables were passed into the system.
*/
public SQLQueryParser parse(XConnection xconn, String query, int override)
{
SQLQueryParser parser = new SQLQueryParser(query);
// Try to implement caching here, if we found a parser in the cache
// then just return the instance otherwise
parser.parse(xconn, override);
return parser;
}
/**
* Produce an SQL Statement Parser based on the incomming query.
*
* For now we will just create a new object, in the future we may have this
* interface cache the queries so that we can take advantage of a preparsed
* String.
*
* If the Inline Parser is not enabled in the Options, no action will be
* taken on the parser. This option can be set by the Stylesheet. If the
* option is not set or cleared, a default value will be set determined
* by the way variables were passed into the system.
*/
private void parse(XConnection xconn, int override)
{
// Grab the Feature here. We could maintain it from the Parent Parser
// but that may cause problems if a single XConnection wants to maintain
// both Inline Variable Statemens along with NON inline variable statements.
m_InlineVariables = "true".equals(xconn.getFeature("inline-variables"));
if (override == NO_INLINE_PARSER) m_InlineVariables = false;
else if (override == INLINE_PARSER) m_InlineVariables = true;
if (m_InlineVariables) inlineParser();
}
/**
* If a SQL Statement does not have any parameters, then it can be executed
* directly. Most SQL Servers use this as a performance advantage since no
* parameters need to be parsed then bound.
*/
public boolean hasParameters()
{
return m_HasParameters;
}
/**
* If the Inline Parser is used, the parser will note if this stastement is
* a plain SQL Statement or a Called Procedure. Called Procudures generally
* have output parameters and require special handling.
*
* Called Procudures that are not processed with the Inline Parser will
* still be executed but under the context of a PreparedStatement and
* not a CallableStatement. Called Procudures that have output parameters
* MUST be handled with the Inline Parser.
*/
public boolean isCallable()
{
return m_IsCallable;
}
/**
*
*/
public Vector getParameters()
{
return m_Parameters;
}
/**
* The XConnection will use this method to store the Parameters
* that were supplied by the style sheet in the case where the
* inline parser was not used
*/
public void setParameters(Vector p)
{
m_HasParameters = true;
m_Parameters = p;
}
/**
* Return a copy of the parsed SQL query that will be set to the
* Database system to execute. If the inline parser was not used,
* then the original query will be returned.
*/
public String getSQLQuery()
{
if (m_InlineVariables) return m_ParsedQuery.toString();
else return m_OrigQuery;
}
/**
* The SQL Statement Parser, when an Inline Parser is used, tracks the XSL
* variables used to populate a statement. The data use to popoulate a
* can also be provided. If the data is provided, it will overide the
* populastion using XSL variables. When the Inline PArser is not used, then
* the Data will always be provided.
*
*/
public void populateStatement(PreparedStatement stmt, ExpressionContext ctx)
{
// Set input parameters from variables.
// for ( int indx = returnParm ? 1 : 0 ; indx < m_Parameters.size() ; indx++ )
for ( int indx = 0 ; indx < m_Parameters.size() ; indx++ )
{
QueryParameter parm = (QueryParameter) m_Parameters.elementAt(indx);
try
{
if (m_InlineVariables)
{
XObject value = (XObject)ctx.getVariableOrParam(new QName(parm.getName()));
if (value != null)
{
stmt.setObject(
indx + 1,
value.object(),
parm.getType(), 4); // Currently defaulting scale to 4 - should read this!
}
else
{
stmt.setNull(indx + 1, parm.getType());
}
}
else
{
String value = parm.getValue();
if (value != null)
{
stmt.setObject(
indx + 1,
value,
parm.getType(), 4); // Currently defaulting scale to 4 - should read this!
}
else
{
stmt.setNull(indx + 1, parm.getType());
}
}
}
catch (Exception tx)
{
// if ( ! parm.isOutput() ) throw new SQLException(tx.toString());
}
}
}
public void registerOutputParameters(CallableStatement cstmt) throws SQLException
{
// Register output parameters if call.
if ( m_IsCallable && m_hasOutput )
{
for ( int indx = 0 ; indx < m_Parameters.size() ; indx++ )
{
QueryParameter parm = (QueryParameter) m_Parameters.elementAt(indx);
if ( parm.isOutput() )
{
//System.out.println("chrysalisSQLStatement() Registering output parameter for parm " + indx);
cstmt.registerOutParameter(indx + 1, parm.getType());
}
}
}
}
/**
*
*/
protected void inlineParser()
{
QueryParameter curParm = null;
int state = 0;
StringBuffer tok = new StringBuffer();
boolean firstword = true;
if (m_Parameters == null) m_Parameters = new Vector();
if (m_ParsedQuery == null) m_ParsedQuery = new StringBuffer();
for ( int idx = 0 ; idx < m_OrigQuery.length() ; idx++ )
{
char ch = m_OrigQuery.charAt(idx);
switch ( state )
{
case 0: // Normal
if ( ch == '\'' ) state = 1;
else if ( ch == '?' ) state = 4;
else if ( firstword && (Character.isLetterOrDigit(ch) || ch == '#') )
{
tok.append(ch);
state = 3;
}
m_ParsedQuery.append(ch);
break;
case 1: // In String
if ( ch == '\'' ) state = 0;
else if ( ch == '\\' ) state = 2;
m_ParsedQuery.append(ch);
break;
case 2: // In escape
state = 1;
m_ParsedQuery.append(ch);
break;
case 3: // First word
if ( Character.isLetterOrDigit(ch) || ch == '#' || ch == '_' ) tok.append(ch);
else
{
if ( tok.toString().equalsIgnoreCase("call") )
{
m_IsCallable = true;
if ( curParm != null )
{
// returnParm = true;
curParm.setIsOutput(true);
// hasOutput = true;
}
}
firstword = false;
tok = new StringBuffer();
if ( ch == '\'' ) state = 1;
else if ( ch == '?' ) state = 4;
else state = 0;
}
m_ParsedQuery.append(ch);
break;
case 4: // Get variable definition
if ( ch == '[' ) state = 5;
break;
case 5: // Read variable type.
if ( !Character.isWhitespace(ch) && ch != '=' )
{
tok.append(Character.toUpperCase(ch));
}
else if ( tok.length() > 0 )
{
// OK we have at least one parameter.
m_HasParameters = true;
curParm = new QueryParameter();
curParm.setTypeName(tok.toString());
// curParm.type = map_type(curParm.typeName);
m_Parameters.addElement(curParm);
tok = new StringBuffer();
if ( ch == '=' ) state = 7;
else state = 6;
}
break;
case 6: // Look for '='
if ( ch == '=' ) state = 7;
break;
case 7: // Read variable name.
if ( !Character.isWhitespace(ch) && ch != ']' ) tok.append(ch);
else if ( tok.length() > 0 )
{
curParm.setName(tok.toString());
tok = new StringBuffer();
if ( ch == ']' )
{
//param_output.addElement(new Boolean(false));
state = 0;
}
else state = 8;
}
break;
case 8: // Look for "OUTput.
if ( !Character.isWhitespace(ch) && ch != ']' )
{
tok.append(ch);
}
else if ( tok.length() > 0 )
{
tok.setLength(3);
if ( tok.toString().equalsIgnoreCase("OUT") )
{
curParm.setIsOutput(true);
m_hasOutput = true;
}
tok = new StringBuffer();
if ( ch == ']' )
{
state = 0;
}
}
break;
}
}
// Prepare statement or call.
if ( m_IsCallable )
{
m_ParsedQuery.insert(0, '{');
m_ParsedQuery.append('}');
}
}
}