001: /*
002: * Copyright (c) 1998 - 2005 Versant Corporation
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * Versant Corporation - initial API and implementation
010: */
011: package com.versant.core.jdbc.fetch;
012:
013: import com.versant.core.util.CharBuf;
014: import com.versant.core.common.Debug;
015: import com.versant.core.common.OID;
016: import com.versant.core.common.Utils;
017: import com.versant.core.metadata.ModelMetaData;
018: import com.versant.core.metadata.ClassMetaData;
019: import com.versant.core.metadata.MDStaticUtils;
020: import com.versant.core.jdbc.sql.SqlDriver;
021: import com.versant.core.jdbc.metadata.JdbcColumn;
022: import com.versant.core.jdbc.metadata.JdbcField;
023: import com.versant.core.jdbc.metadata.JdbcClass;
024: import com.versant.core.jdbc.JdbcUtils;
025: import com.versant.core.jdbc.JdbcOID;
026:
027: import java.io.Serializable;
028: import java.sql.PreparedStatement;
029: import java.sql.SQLException;
030: import java.util.Collection;
031: import java.util.Iterator;
032:
033: import com.versant.core.common.BindingSupportImpl;
034:
035: /**
036: * Buffer holding the SQL for a query. This supports modification of the
037: * SQL to convert nullparameter references into IS NULL, toggling SELECT
038: * FOR UPDATE and other things.
039: */
040: public class SqlBuffer {
041:
042: private static final char[] COUNT_STAR_PRE = "COUNT(".toCharArray();
043: private static final char[] COUNT_STAR_POST = ")".toCharArray();
044: private static final char[] COUNT_STAR_PRE_DISTINCT = "COUNT(DISTINCT("
045: .toCharArray();
046: private static final char[] COUNT_STAR_POST_DISTINCT = "))"
047: .toCharArray();
048: private static final char[] IS_NULL = "IS NULL".toCharArray();
049: private static final char[] IS_NOT_NULL = "IS NOT NULL"
050: .toCharArray();
051:
052: /**
053: * The SQL to execute the query. It is kept in a CharBuf as
054: * it may need to be modified before each execution of the query depending
055: * on which parameters are null.
056: */
057: private CharBuf sqlbuf;
058: /**
059: * Is the query a 'select distinct'?
060: */
061: private boolean distinct;
062: /**
063: * Index of the first character in the select list.
064: */
065: private int selectListStartIndex;
066: /**
067: * Number of characters in the select list.
068: */
069: private int selectListLength;
070: /**
071: * Index of the first column in the select list.
072: */
073: private int selectFirstColStart;
074: /**
075: * Index of the first column in the select list.
076: */
077: private int selectFirstColLength;
078: /**
079: * Index of the start of the 'order by ..' clause or 0 if none.
080: */
081: private int orderByStartIndex;
082: /**
083: * Number of characters in the 'order by ..' clause.
084: */
085: private int orderByLength;
086: /**
087: * The first table or alias in the from clause for the query. This is
088: * required for some databases if the query needs to be converted into a
089: * 'select for update' query (e.g. postgres).
090: */
091: private String firstTableOrAlias;
092: /**
093: * The sql is cached here in string form.
094: */
095: private transient String sql;
096: /**
097: * Is the current sqlbuf 'select for update'?
098: */
099: private boolean sqlForUpdate;
100: /**
101: * What was the original size of the sqlbuf before any modifications for
102: * 'select for update'?
103: */
104: private int sqlbufNotForUpdateSize;
105: /**
106: * Is the current sqlbuf 'select count(*)'?
107: */
108: private boolean sqlForCount;
109: /**
110: * Store for the select list when query is converted to count(*).
111: */
112: private char[] originalSelectList;
113: /**
114: * Store for the first column of the select list when query is converted to
115: * count(*).
116: */
117: private char[] originalSelectListFirstColumn;
118: /**
119: * Store for the order by clause when query is converted to count(*).
120: */
121: private char[] originalOrderByClause;
122: /**
123: * Params in the order that they appear in the SQL string. Each declared
124: * parameter may have several entries in this array if it was used more
125: * than once in the original query.
126: */
127: private Param paramList;
128: /**
129: * Characters used to convert a param into 'is null'.
130: */
131: private char[] isNullChars;
132: /**
133: * Characters used to convert a param into 'is not null'.
134: */
135: private char[] isNotNullChars;
136: /**
137: * Is this an aggregate query.
138: */
139: private boolean aggregate;
140:
141: public SqlBuffer() {
142: sqlbuf = new CharBuf(256);
143: }
144:
145: public SqlBuffer getClone() {
146: SqlBuffer clone = new SqlBuffer();
147: clone.sqlbuf = new CharBuf(sqlbuf);
148: clone.distinct = distinct;
149: clone.selectListStartIndex = selectListStartIndex;
150: clone.selectListLength = selectListLength;
151: clone.orderByStartIndex = orderByStartIndex;
152: clone.orderByLength = orderByLength;
153: clone.firstTableOrAlias = firstTableOrAlias;
154: clone.sql = sql;
155: clone.sqlForUpdate = sqlForUpdate;
156: clone.sqlbufNotForUpdateSize = sqlbufNotForUpdateSize;
157: clone.sqlForCount = sqlForCount;
158: clone.originalSelectList = originalSelectList;
159: clone.originalOrderByClause = originalOrderByClause;
160: clone.paramList = (paramList == null ? null : paramList
161: .getClone());
162: if (isNullChars != null) {
163: clone.isNullChars = new char[isNullChars.length];
164: for (int i = 0; i < isNullChars.length; i++) {
165: clone.isNullChars[i] = isNullChars[i];
166: }
167: }
168: if (isNotNullChars != null) {
169: clone.isNotNullChars = new char[isNotNullChars.length];
170: for (int i = 0; i < isNotNullChars.length; i++) {
171: clone.isNotNullChars[i] = isNotNullChars[i];
172: }
173: }
174: return clone;
175: }
176:
177: public boolean isAggregate() {
178: return aggregate;
179: }
180:
181: public void setAggregate(boolean aggregate) {
182: this .aggregate = aggregate;
183: }
184:
185: /**
186: * Set the range of characters in our buffer that contain all the columns
187: * in the select list.
188: * @param start Index of the first character in the select list (after
189: * 'SELECT ' or 'SELECT DISTINCT ')
190: * @param firstColEnd Index of the last character in the first column + 1
191: * @param end Index of the last character in the list + 1
192: */
193: public void setSelectListRange(boolean distinct, int start,
194: int firstColEnd, int end) {
195: this .distinct = distinct;
196: if (distinct) {
197: selectListStartIndex = start - 9; // "DISTINCT ".length()
198: } else {
199: selectListStartIndex = start;
200: }
201: selectFirstColStart = start;
202: selectListLength = end - selectListStartIndex;
203: selectFirstColLength = firstColEnd - selectFirstColStart;
204: }
205:
206: /**
207: * Set the range of characters in our buffer that contain order by clause
208: * including the 'order by' keywords. The end index is exclusive.
209: */
210: public void setOrderByRange(int start, int end) {
211: orderByStartIndex = start;
212: orderByLength = end - start;
213: }
214:
215: public CharBuf getSqlbuf() {
216: return sqlbuf;
217: }
218:
219: public boolean isDistinct() {
220: return distinct;
221: }
222:
223: public String getFirstTableOrAlias() {
224: return firstTableOrAlias;
225: }
226:
227: public void setFirstTableOrAlias(String firstTableOrAlias) {
228: this .firstTableOrAlias = firstTableOrAlias;
229: }
230:
231: public boolean isSqlForUpdate() {
232: return sqlForUpdate;
233: }
234:
235: public int getSqlbufNotForUpdateSize() {
236: return sqlbufNotForUpdateSize;
237: }
238:
239: public boolean isSqlForCount() {
240: return sqlForCount;
241: }
242:
243: public char[] getOriginalSelectList() {
244: return originalSelectList;
245: }
246:
247: public char[] getOriginalOrderByClause() {
248: return originalOrderByClause;
249: }
250:
251: public Param getParamList() {
252: return paramList;
253: }
254:
255: public void setParamList(Param paramList) {
256: this .paramList = paramList;
257: if (paramList != null)
258: analyzeCharSpans();
259: }
260:
261: private void analyzeCharSpans() {
262: int max = IS_NOT_NULL.length;
263: for (Param p = paramList; p != null; p = p.next) {
264: for (CharSpan s = p.charSpanList; s != null; s = s.next) {
265: if (s.type == CharSpan.TYPE_REMOVE)
266: continue;
267: int len = s.lastCharIndex - s.firstCharIndex;
268: if (len > max)
269: max = len;
270: }
271: }
272: if (max > 0) {
273: isNullChars = new char[max];
274: copyAndPad(IS_NULL, isNullChars, max);
275: isNotNullChars = new char[max];
276: copyAndPad(IS_NOT_NULL, isNotNullChars, max);
277: }
278: }
279:
280: /**
281: * This must create space in the charBuf at the index.
282: */
283: public void createSpace(CharBuf charBuf, int index, int amount) {
284: sql = null;
285: for (SqlBuffer.Param p = paramList; p != null; p = p.next) {
286: if (p.firstCharIndex > index) {
287: //must update
288: p.firstCharIndex += amount;
289: for (CharSpan cs = p.charSpanList; cs != null; cs = cs.next) {
290: cs.firstCharIndex += amount;
291: cs.lastCharIndex += amount;
292: }
293: }
294: }
295:
296: if (orderByStartIndex > index) {
297: orderByStartIndex += amount;
298: }
299: }
300:
301: /**
302: * This must create space in the charBuf at the index.
303: */
304: public void removeSpace(CharBuf charBuf, int index, int amount) {
305: sql = null;
306: for (SqlBuffer.Param p = paramList; p != null; p = p.next) {
307: if (p.firstCharIndex > index) {
308: //must update
309: p.firstCharIndex -= amount;
310: for (CharSpan cs = p.charSpanList; cs != null; cs = cs.next) {
311: cs.firstCharIndex -= amount;
312: cs.lastCharIndex -= amount;
313: }
314: }
315: }
316:
317: if (orderByStartIndex > index) {
318: orderByStartIndex -= amount;
319: }
320: }
321:
322: /**
323: * Update all our Param's for the null/not null state of their parameters
324: * and for 'select for update' or not. Return the updated SQL.
325:
326: * @param forUpdate Generate SELECT FOR UPDATE type query
327: * @param forCount Generate a COUNT(*) query to just count the rows
328: * @param fromIncl Index of first row to return
329: * @param toExcl Index of row after last row to return
330: */
331: public synchronized String getSql(SqlDriver driver,
332: Object[] params, boolean forUpdate, boolean forCount,
333: long fromIncl, long toExcl) {
334: boolean changed = sql == null;
335: if (params != null) {
336: int paramIndex = 0;
337: for (SqlBuffer.Param p = paramList; p != null; p = p.next, paramIndex++) {
338: if (params[p.declaredParamIndex] instanceof Collection) {
339: Collection col = (Collection) params[p.declaredParamIndex];
340: int n = col.size();
341: if (n == 0) {
342: throw BindingSupportImpl.getInstance()
343: .invalidOperation(
344: "The supplied collection param at index "
345: + paramIndex
346: + " may not be empty");
347: }
348: if (p.inListParamCount == 0) {
349: //this is a readOnly char[]. it may not be modified
350: final char[] charsToInsert = driver
351: .getSqlParamStringChars(p.jdbcType);
352: int toInsert = (n == 1 ? charsToInsert.length
353: : (n * charsToInsert.length) + (n - 1));
354: char[] chars = new char[toInsert];
355:
356: int offset = 0;
357: for (int i = 0; i < n; i++) {
358: if (offset > 0) {
359: chars[offset++] = ',';
360: }
361: for (int j = 0; j < charsToInsert.length; j++) {
362: chars[j + offset] = charsToInsert[j];
363: }
364: offset += charsToInsert.length;
365: }
366: createSpace(sqlbuf, p.firstCharIndex, toInsert);
367: sqlbuf.insert(p.firstCharIndex, chars);
368: p.charLength = chars.length;
369: p.inListParamCount = n;
370: changed = true;
371: } else if (p.inListParamCount < n) {
372: //must insert more param's
373: int insertPoint = p.charLength
374: + p.firstCharIndex;
375: //the diff between the required number and what is already there
376: int paramsToInsert = n - p.inListParamCount;
377: final char[] charStamp = driver
378: .getSqlParamStringChars(p.jdbcType);
379: int charsToInsert = (charStamp.length * paramsToInsert)
380: + paramsToInsert;
381: char[] chars = new char[charsToInsert];
382: int offset = 0;
383: for (int i = 0; i < paramsToInsert; i++) {
384: chars[offset++] = ',';
385: for (int j = 0; j < charStamp.length; j++) {
386: chars[offset++] = charStamp[j];
387: }
388: }
389: if (Debug.DEBUG) {
390: if (offset != chars.length) {
391: throw BindingSupportImpl.getInstance()
392: .internal("");
393: }
394: }
395: createSpace(sqlbuf, insertPoint, charsToInsert);
396: sqlbuf.insert(insertPoint, chars);
397: p.charLength += chars.length;
398: p.inListParamCount = n;
399: changed = true;
400: } else if (p.inListParamCount > n) {
401: //must remove some
402: changed = true;
403: int removeStart = p.firstCharIndex;
404: int paramToRemove = p.inListParamCount - n;
405:
406: int charsToRemove = driver
407: .getSqlParamStringChars(p.jdbcType).length
408: * paramToRemove + paramToRemove;
409: int removeTo = charsToRemove + removeStart;
410: sqlbuf.remove(removeStart, removeTo);
411: removeSpace(sqlbuf, p.firstCharIndex, removeTo
412: - removeStart);
413:
414: p.charLength -= (removeTo - removeStart);
415: p.inListParamCount = n;
416: changed = true;
417: }
418:
419: }
420: }
421: }
422:
423: if (params != null) {
424: for (SqlBuffer.Param p = paramList; p != null; p = p.next) {
425: if (p
426: .update(this ,
427: params[p.declaredParamIndex] == null)) {
428: changed = true;
429: }
430: }
431: }
432: if (forUpdate != sqlForUpdate
433: && (!distinct || driver
434: .isSelectForUpdateWithDistinctOk())
435: && (!aggregate || driver
436: .isSelectForUpdateWithAggregateOk())) {
437: char[] a = driver.getSelectForUpdate();
438: if (a != null) {
439: if (forUpdate) {
440: sqlbufNotForUpdateSize = sqlbuf.size();
441: sqlbuf.append(a);
442: if (driver.isSelectForUpdateAppendTable()) {
443: sqlbuf.append(firstTableOrAlias);
444: }
445: } else {
446: sqlbuf.setSize(sqlbufNotForUpdateSize);
447: }
448: sqlForUpdate = forUpdate;
449: changed = true;
450: }
451: }
452: if (forCount != sqlForCount) {
453: if (forCount) {
454: if (originalSelectList == null) {
455: originalSelectList = sqlbuf.toArray(
456: selectListStartIndex, selectListLength);
457: originalSelectListFirstColumn = sqlbuf.toArray(
458: selectFirstColStart, selectFirstColLength);
459: }
460: int start;
461: if (distinct) {
462: sqlbuf.replace(selectListStartIndex,
463: COUNT_STAR_PRE_DISTINCT);
464: start = selectListStartIndex
465: + COUNT_STAR_PRE_DISTINCT.length;
466: } else {
467: sqlbuf
468: .replace(selectListStartIndex,
469: COUNT_STAR_PRE);
470: start = selectListStartIndex
471: + COUNT_STAR_PRE.length;
472: }
473: sqlbuf.replace(start, originalSelectListFirstColumn);
474: start += originalSelectListFirstColumn.length;
475: if (distinct) {
476: sqlbuf.replace(start, COUNT_STAR_POST_DISTINCT);
477: start += COUNT_STAR_POST_DISTINCT.length;
478: } else {
479: sqlbuf.replace(start, COUNT_STAR_POST);
480: start += COUNT_STAR_POST.length;
481: }
482: int n = (selectListStartIndex + selectListLength)
483: - start;
484: if (n > 0)
485: sqlbuf.replace(start, start + n, ' ');
486: if (orderByStartIndex > 0) {
487: if (originalOrderByClause == null) {
488: originalOrderByClause = sqlbuf.toArray(
489: orderByStartIndex, orderByLength);
490: }
491: sqlbuf.replace(orderByStartIndex, orderByStartIndex
492: + orderByLength, ' ');
493: }
494: } else {
495: sqlbuf
496: .replace(selectListStartIndex,
497: originalSelectList);
498: if (orderByStartIndex > 0) {
499: sqlbuf.replace(orderByStartIndex,
500: originalOrderByClause);
501: }
502: }
503: sqlForCount = forCount;
504: changed = true;
505: }
506:
507: if (changed) {
508: sql = sqlbuf.toString();
509: }
510: return sql;
511: }
512:
513: /**
514: * Set all the parameters for this query on ps. This is a NOP if params
515: * is null.
516: */
517: public void setParamsOnPS(ModelMetaData jmd, SqlDriver driver,
518: PreparedStatement ps, Object[] params, String sql) {
519: if (params == null)
520: return;
521: int pos = 1;
522: SqlBuffer.Param p = paramList;
523: Object value = null;
524: try {
525: for (; p != null; p = p.next) {
526: value = params[p.declaredParamIndex];
527: switch (p.mod) {
528: case Param.MOD_NONE:
529: break;
530: case Param.MOD_APPEND_PERCENT:
531: if (value != null)
532: value = value + "%";
533: break;
534: case Param.MOD_PREPEND_PERCENT:
535: if (value != null)
536: value = "%" + value;
537: break;
538: default:
539: throw BindingSupportImpl.getInstance().internal(
540: "Invalid mod: " + p.mod);
541: }
542: if (value == null && p.requiresUpdate())
543: continue;
544: int pci = p.classIndex;
545: if (pci >= 0) {
546: ClassMetaData pcmd = jmd.classes[pci];
547: int pfno = p.fieldNo;
548: if (pfno >= 0) {
549: JdbcField f = ((JdbcClass) pcmd.storeClass).stateFields[pfno];
550: if (value instanceof Collection) {
551: Collection col = (Collection) value;
552: for (Iterator iterator = col.iterator(); iterator
553: .hasNext();) {
554: Object o = iterator.next();
555: if (o instanceof OID) {
556: pos = ((JdbcOID) o).setParams(ps,
557: pos);
558: } else {
559: pos = f.setQueryParam(ps, pos, o);
560: }
561: }
562: } else {
563: pos = f.setQueryParam(ps, pos, value);
564: }
565: } else { // this is an OID param for a link table
566: if (value != null) {
567: pos = ((JdbcOID) value).setParams(ps, pos);
568: } else {
569: JdbcColumn[] pkcols = ((JdbcClass) pcmd.storeClass).table.pkSimpleCols;
570: int nc = pkcols.length;
571: for (int i = 0; i < nc; i++) {
572: ps.setNull(pos++, pkcols[i].jdbcType);
573: }
574: }
575: }
576: } else {
577: if (p.col != null) {
578: p.col.set(ps, pos++, value);
579: } else {
580: int javaTypeCode = p.javaTypeCode;
581: if (javaTypeCode == 0 && value != null) {
582: javaTypeCode = MDStaticUtils
583: .toTypeCode(value.getClass());
584: }
585: JdbcUtils.set(ps, pos++, value, javaTypeCode,
586: p.jdbcType);
587: }
588: }
589: }
590: } catch (Exception e) {
591: throw driver.mapException(e,
592: "Error setting query parameter "
593: + p.getIdentifier()
594: + " = '"
595: + Utils.toString(value)
596: + "' at PreparedStatement index "
597: + pos
598: + " in\n"
599: + JdbcUtils.getPreparedStatementInfo(sql,
600: ps) + "\n" + JdbcUtils.toString(e),
601: false);
602: }
603: }
604:
605: private static void copyAndPad(char[] src, char[] dest, int len) {
606: int n = src.length;
607: System.arraycopy(src, 0, dest, 0, n);
608: for (; n < len;)
609: dest[n++] = ' ';
610: }
611:
612: public char[] getNullChars() {
613: return isNullChars;
614: }
615:
616: public void setNullChars(char[] nullChars) {
617: isNullChars = nullChars;
618: }
619:
620: public char[] getNotNullChars() {
621: return isNotNullChars;
622: }
623:
624: public void setNotNullChars(char[] notNullChars) {
625: isNotNullChars = notNullChars;
626: }
627:
628: /**
629: * A parameter. This tells us the indexes of the first and last characters
630: * of this parameter and its index within the original list of declared
631: * parameters.
632: */
633: public final static class Param implements Serializable {
634:
635: public static final int MOD_NONE = 0;
636: public static final int MOD_PREPEND_PERCENT = 1;
637: public static final int MOD_APPEND_PERCENT = 2;
638:
639: /**
640: * An identifier for this parameter for error messages.
641: */
642: private String identifier;
643: /**
644: * The next Param in the list.
645: */
646: public Param next;
647: /**
648: * The index of this parameter in the original list of declared
649: * parameters for the query or the index in the array of parameter
650: * values used to exec the query (EJBQL).
651: */
652: public int declaredParamIndex;
653: /**
654: * The index of the first character in sql for this Param.
655: * If charSpanList is not null then this is the same as there. It
656: * is used for sorting.
657: */
658: public transient int firstCharIndex;
659: /**
660: * The current amount of chars that the param occupies when used a in list
661: * for collection params
662: */
663: public transient int charLength;
664: /**
665: * The current amount params that this param can take is used as a collection
666: * param.
667: */
668: public transient int inListParamCount;
669: /**
670: * List of character ranges used by this Param. This info is used
671: * to turn '= ?' into 'is null' and so on. It is null if there is
672: * no need for any replacement (e.g. using Sybase).
673: */
674: public CharSpan charSpanList;
675: /**
676: * The classIndex of the class associated with this parameter or
677: * -1 if there is none (e.g. a parameter used in an expression).
678: * @see #fieldNo
679: */
680: public int classIndex;
681: /**
682: * The field number associated with this parameter or -1 if none.
683: * This is used with cls to locate its column(s). It is not used if
684: * the classIndex is -1.
685: * @see #classIndex
686: */
687: public int fieldNo;
688: /**
689: * The java type code of this parameter. This is only set if
690: * classIndex is -1 i.e. this parameter is not for a field.
691: */
692: public int javaTypeCode;
693: /**
694: * The JDBC type (from java.sql.Types) for this parameter. This is
695: * only set if classIndex is -1.
696: * @see java.sql.Types
697: */
698: public int jdbcType;
699: /**
700: * How must the parameter value be modified before being set? This is
701: * used for startsWith and endsWith for databases that do not allow
702: * expressions on the right hand side of a LIKE (e.g. Informix).
703: * @see #MOD_APPEND_PERCENT
704: */
705: public int mod;
706:
707: public transient JdbcColumn col;
708:
709: public Param(String identifier) {
710: this .identifier = identifier;
711: }
712:
713: public String getIdentifier() {
714: return identifier;
715: }
716:
717: /**
718: * Update all our CharSpan's for the null/not null state of the
719: * parameter. This is a NOP if the parameter does not require update.
720: * @return True if changes were made else false
721: * @see #requiresUpdate
722: */
723: public boolean update(SqlBuffer q, boolean newParamIsNull) {
724: boolean ans = false;
725: for (CharSpan cs = charSpanList; cs != null; cs = cs.next) {
726: if (cs.update(q, newParamIsNull))
727: ans = true;
728: }
729: return ans;
730: }
731:
732: /**
733: * Does this need to be updated for null/not null parameter values?
734: * @see #update
735: */
736: public boolean requiresUpdate() {
737: return charSpanList != null;
738: }
739:
740: public Param getClone() {
741: Param clone = new Param(identifier);
742: clone.next = (next == null ? null : next.getClone());
743: clone.declaredParamIndex = declaredParamIndex;
744: clone.firstCharIndex = firstCharIndex;
745: clone.charSpanList = (charSpanList == null ? null
746: : charSpanList.getClone());
747: clone.classIndex = classIndex;
748: clone.fieldNo = fieldNo;
749: clone.javaTypeCode = javaTypeCode;
750: clone.jdbcType = jdbcType;
751: clone.mod = mod;
752: clone.col = col;
753: return clone;
754: }
755: }
756:
757: /**
758: * This specifies a range of characters for a Param in our sql buffer. The
759: * lastCharIndex is the index of the character after the last character
760: * in the range.
761: */
762: public final static class CharSpan implements Serializable {
763:
764: public static final int TYPE_NULL = 1;
765: public static final int TYPE_NOT_NULL = 2;
766: public static final int TYPE_REMOVE = 3;
767:
768: /**
769: * What must be done to this span?
770: */
771: public int type;
772: /**
773: * The index of the first character in sql.
774: */
775: public int firstCharIndex;
776: /**
777: * The index of the character after the last character in the span.
778: */
779: public int lastCharIndex;
780: /**
781: * The next span in the list.
782: */
783: public CharSpan next;
784:
785: /**
786: * The current state of the parameter in sql. If this is true then
787: * the parameter has been replaced with an 'is null' or 'is not null'.
788: */
789: private boolean paramIsNull;
790: /**
791: * The original text from the sql query. This is filled the first
792: * time the param is null and is used to restore the query when it
793: * is not null in future.
794: */
795: private char[] originalSql;
796:
797: public CharSpan getClone() {
798: CharSpan clone = new CharSpan();
799: clone.type = type;
800: clone.firstCharIndex = firstCharIndex;
801: clone.lastCharIndex = lastCharIndex;
802: clone.next = (next == null ? null : next.getClone());
803: clone.paramIsNull = paramIsNull;
804: if (originalSql != null) {
805: clone.originalSql = new char[originalSql.length];
806: for (int i = 0; i < originalSql.length; i++) {
807: clone.originalSql[i] = originalSql[i];
808: }
809: }
810: return clone;
811: }
812:
813: /**
814: * Update the query and our state if the newParamIsNull value differs
815: * from our paramIsNull field.
816: * @return True if changes were made else false
817: */
818: public boolean update(SqlBuffer q, boolean newParamIsNull) {
819: if (newParamIsNull == paramIsNull)
820: return false;
821: CharBuf sql = q.sqlbuf;
822: if (newParamIsNull) {
823: if (originalSql == null) {
824: originalSql = sql.toArray(firstCharIndex,
825: lastCharIndex - firstCharIndex);
826: }
827: if (Debug.DEBUG) {
828: System.out
829: .println("*** CharSpan.update replacing '"
830: + new String(originalSql) + "' "
831: + originalSql.length + " "
832: + q.isNullChars.length);
833: }
834: switch (type) {
835: case TYPE_NULL:
836: sql.replace(firstCharIndex, q.isNullChars);
837: break;
838: case TYPE_NOT_NULL:
839: sql.replace(firstCharIndex, q.isNotNullChars);
840: break;
841: case TYPE_REMOVE:
842: sql.replace(firstCharIndex, lastCharIndex, ' ');
843: break;
844: default:
845: throw BindingSupportImpl.getInstance().internal(
846: "Unknown CharSpan type: " + type);
847: }
848: } else {
849: sql.replace(firstCharIndex, originalSql);
850: }
851: paramIsNull = newParamIsNull;
852: return true;
853: }
854: }
855:
856: }
|