001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 /*
019 * $Id: SQLQueryParser.java 468638 2006-10-28 06:52:06Z minchau $
020 */
021
022 /**
023 * This is used by the SQLDocumentHandler for processing JDBC queries.
024 * This prepares JDBC PreparedStatement or CallableStatements and the
025 * input/output of parameters from/to variables.
026 *
027 */
028
029 package org.apache.xalan.lib.sql;
030
031 import java.util.*;
032 import java.sql.*;
033 import org.apache.xpath.objects.*;
034 import org.apache.xalan.extensions.ExpressionContext;
035 import org.apache.xml.utils.QName;
036 import javax.xml.transform.TransformerException;
037
038
039
040 public class SQLQueryParser
041 {
042 /**
043 * If the parser used inline parser to pull out variables then
044 * this will be true. The default is not to use the Inline Parser.
045 */
046 private boolean m_InlineVariables = false;
047
048 /**
049 *
050 */
051 private boolean m_IsCallable = false;
052
053 /**
054 *
055 */
056 private String m_OrigQuery = null;
057
058 /**
059 *
060 */
061 private StringBuffer m_ParsedQuery = null;
062
063 /**
064 *
065 */
066 private Vector m_Parameters = null;
067
068 /**
069 *
070 */
071 private boolean m_hasOutput = false;
072
073 /**
074 *
075 */
076 private boolean m_HasParameters;
077
078 public static final int NO_OVERRIDE = 0;
079 public static final int NO_INLINE_PARSER = 1;
080 public static final int INLINE_PARSER = 2;
081
082 /**
083 * The SQLStatement Parser will be created as a psuedo SINGLETON per
084 * XConnection. Since we are only caching the Query and its parsed results
085 * we may be able to use this as a real SINGLETON. It all depends on how
086 * Statement Caching will play out.
087 */
088 public SQLQueryParser()
089 {
090 init();
091 }
092
093 /**
094 * Constructor, used to create a new parser entry
095 */
096 private SQLQueryParser(String query)
097 {
098 m_OrigQuery = query;
099 }
100
101 /**
102 * On a per Xconnection basis, we will create a SQLStatemenetParser, from
103 * this parser, individual parsers will be created. The Init method is defined
104 * to initialize all the internal structures that maintains the pool of parsers.
105 */
106 private void init()
107 {
108 // Do nothing for now.
109 }
110
111 /**
112 * Produce an SQL Statement Parser based on the incomming query.
113 *
114 * For now we will just create a new object, in the future we may have this
115 * interface cache the queries so that we can take advantage of a preparsed
116 * String.
117 *
118 * If the Inline Parser is not enabled in the Options, no action will be
119 * taken on the parser. This option can be set by the Stylesheet. If the
120 * option is not set or cleared, a default value will be set determined
121 * by the way variables were passed into the system.
122 */
123 public SQLQueryParser parse(XConnection xconn, String query, int override)
124 {
125 SQLQueryParser parser = new SQLQueryParser(query);
126
127 // Try to implement caching here, if we found a parser in the cache
128 // then just return the instance otherwise
129 parser.parse(xconn, override);
130
131 return parser;
132 }
133
134
135
136 /**
137 * Produce an SQL Statement Parser based on the incomming query.
138 *
139 * For now we will just create a new object, in the future we may have this
140 * interface cache the queries so that we can take advantage of a preparsed
141 * String.
142 *
143 * If the Inline Parser is not enabled in the Options, no action will be
144 * taken on the parser. This option can be set by the Stylesheet. If the
145 * option is not set or cleared, a default value will be set determined
146 * by the way variables were passed into the system.
147 */
148 private void parse(XConnection xconn, int override)
149 {
150 // Grab the Feature here. We could maintain it from the Parent Parser
151 // but that may cause problems if a single XConnection wants to maintain
152 // both Inline Variable Statemens along with NON inline variable statements.
153
154 m_InlineVariables = "true".equals(xconn.getFeature("inline-variables"));
155 if (override == NO_INLINE_PARSER) m_InlineVariables = false;
156 else if (override == INLINE_PARSER) m_InlineVariables = true;
157
158 if (m_InlineVariables) inlineParser();
159
160 }
161
162 /**
163 * If a SQL Statement does not have any parameters, then it can be executed
164 * directly. Most SQL Servers use this as a performance advantage since no
165 * parameters need to be parsed then bound.
166 */
167 public boolean hasParameters()
168 {
169 return m_HasParameters;
170 }
171
172 /**
173 * If the Inline Parser is used, the parser will note if this stastement is
174 * a plain SQL Statement or a Called Procedure. Called Procudures generally
175 * have output parameters and require special handling.
176 *
177 * Called Procudures that are not processed with the Inline Parser will
178 * still be executed but under the context of a PreparedStatement and
179 * not a CallableStatement. Called Procudures that have output parameters
180 * MUST be handled with the Inline Parser.
181 */
182 public boolean isCallable()
183 {
184 return m_IsCallable;
185 }
186
187
188 /**
189 *
190 */
191 public Vector getParameters()
192 {
193 return m_Parameters;
194 }
195
196 /**
197 * The XConnection will use this method to store the Parameters
198 * that were supplied by the style sheet in the case where the
199 * inline parser was not used
200 */
201 public void setParameters(Vector p)
202 {
203 m_HasParameters = true;
204 m_Parameters = p;
205 }
206
207 /**
208 * Return a copy of the parsed SQL query that will be set to the
209 * Database system to execute. If the inline parser was not used,
210 * then the original query will be returned.
211 */
212 public String getSQLQuery()
213 {
214 if (m_InlineVariables) return m_ParsedQuery.toString();
215 else return m_OrigQuery;
216 }
217
218
219 /**
220 * The SQL Statement Parser, when an Inline Parser is used, tracks the XSL
221 * variables used to populate a statement. The data use to popoulate a
222 * can also be provided. If the data is provided, it will overide the
223 * populastion using XSL variables. When the Inline PArser is not used, then
224 * the Data will always be provided.
225 *
226 */
227 public void populateStatement(PreparedStatement stmt, ExpressionContext ctx)
228 {
229 // Set input parameters from variables.
230 // for ( int indx = returnParm ? 1 : 0 ; indx < m_Parameters.size() ; indx++ )
231
232 for ( int indx = 0 ; indx < m_Parameters.size() ; indx++ )
233 {
234 QueryParameter parm = (QueryParameter) m_Parameters.elementAt(indx);
235
236 try
237 {
238
239 if (m_InlineVariables)
240 {
241 XObject value = (XObject)ctx.getVariableOrParam(new QName(parm.getName()));
242
243 if (value != null)
244 {
245 stmt.setObject(
246 indx + 1,
247 value.object(),
248 parm.getType(), 4); // Currently defaulting scale to 4 - should read this!
249 }
250 else
251 {
252 stmt.setNull(indx + 1, parm.getType());
253 }
254 }
255 else
256 {
257 String value = parm.getValue();
258
259 if (value != null)
260 {
261 stmt.setObject(
262 indx + 1,
263 value,
264 parm.getType(), 4); // Currently defaulting scale to 4 - should read this!
265 }
266 else
267 {
268 stmt.setNull(indx + 1, parm.getType());
269 }
270 }
271 }
272 catch (Exception tx)
273 {
274 // if ( ! parm.isOutput() ) throw new SQLException(tx.toString());
275 }
276 }
277
278 }
279
280 public void registerOutputParameters(CallableStatement cstmt) throws SQLException
281 {
282 // Register output parameters if call.
283 if ( m_IsCallable && m_hasOutput )
284 {
285 for ( int indx = 0 ; indx < m_Parameters.size() ; indx++ )
286 {
287 QueryParameter parm = (QueryParameter) m_Parameters.elementAt(indx);
288 if ( parm.isOutput() )
289 {
290 //System.out.println("chrysalisSQLStatement() Registering output parameter for parm " + indx);
291 cstmt.registerOutParameter(indx + 1, parm.getType());
292 }
293 }
294 }
295 }
296
297 /**
298 *
299 */
300 protected void inlineParser()
301 {
302 QueryParameter curParm = null;
303 int state = 0;
304 StringBuffer tok = new StringBuffer();
305 boolean firstword = true;
306
307 if (m_Parameters == null) m_Parameters = new Vector();
308
309 if (m_ParsedQuery == null) m_ParsedQuery = new StringBuffer();
310
311 for ( int idx = 0 ; idx < m_OrigQuery.length() ; idx++ )
312 {
313 char ch = m_OrigQuery.charAt(idx);
314 switch ( state )
315 {
316
317 case 0: // Normal
318 if ( ch == '\'' ) state = 1;
319 else if ( ch == '?' ) state = 4;
320 else if ( firstword && (Character.isLetterOrDigit(ch) || ch == '#') )
321 {
322 tok.append(ch);
323 state = 3;
324 }
325 m_ParsedQuery.append(ch);
326 break;
327
328 case 1: // In String
329 if ( ch == '\'' ) state = 0;
330 else if ( ch == '\\' ) state = 2;
331 m_ParsedQuery.append(ch);
332 break;
333
334 case 2: // In escape
335 state = 1;
336 m_ParsedQuery.append(ch);
337 break;
338
339 case 3: // First word
340 if ( Character.isLetterOrDigit(ch) || ch == '#' || ch == '_' ) tok.append(ch);
341 else
342 {
343 if ( tok.toString().equalsIgnoreCase("call") )
344 {
345 m_IsCallable = true;
346 if ( curParm != null )
347 {
348 // returnParm = true;
349 curParm.setIsOutput(true);
350 // hasOutput = true;
351 }
352 }
353 firstword = false;
354 tok = new StringBuffer();
355 if ( ch == '\'' ) state = 1;
356 else if ( ch == '?' ) state = 4;
357 else state = 0;
358 }
359
360 m_ParsedQuery.append(ch);
361 break;
362
363 case 4: // Get variable definition
364 if ( ch == '[' ) state = 5;
365 break;
366
367 case 5: // Read variable type.
368 if ( !Character.isWhitespace(ch) && ch != '=' )
369 {
370 tok.append(Character.toUpperCase(ch));
371 }
372 else if ( tok.length() > 0 )
373 {
374 // OK we have at least one parameter.
375 m_HasParameters = true;
376
377 curParm = new QueryParameter();
378
379 curParm.setTypeName(tok.toString());
380 // curParm.type = map_type(curParm.typeName);
381 m_Parameters.addElement(curParm);
382 tok = new StringBuffer();
383 if ( ch == '=' ) state = 7;
384 else state = 6;
385 }
386 break;
387
388 case 6: // Look for '='
389 if ( ch == '=' ) state = 7;
390 break;
391
392 case 7: // Read variable name.
393 if ( !Character.isWhitespace(ch) && ch != ']' ) tok.append(ch);
394 else if ( tok.length() > 0 )
395 {
396 curParm.setName(tok.toString());
397 tok = new StringBuffer();
398 if ( ch == ']' )
399 {
400 //param_output.addElement(new Boolean(false));
401 state = 0;
402 }
403 else state = 8;
404 }
405 break;
406
407 case 8: // Look for "OUTput.
408 if ( !Character.isWhitespace(ch) && ch != ']' )
409 {
410 tok.append(ch);
411 }
412 else if ( tok.length() > 0 )
413 {
414 tok.setLength(3);
415 if ( tok.toString().equalsIgnoreCase("OUT") )
416 {
417 curParm.setIsOutput(true);
418 m_hasOutput = true;
419 }
420
421 tok = new StringBuffer();
422 if ( ch == ']' )
423 {
424 state = 0;
425 }
426 }
427 break;
428 }
429 }
430
431
432 // Prepare statement or call.
433 if ( m_IsCallable )
434 {
435 m_ParsedQuery.insert(0, '{');
436 m_ParsedQuery.append('}');
437 }
438
439 }
440
441 }
442