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