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
007 * "License"); you may not use this file except in compliance
008 * with the License.  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 */
018package org.apache.oozie.util.db;
019
020import java.io.BufferedInputStream;
021import java.io.BufferedOutputStream;
022import java.io.ByteArrayOutputStream;
023import java.io.IOException;
024import java.sql.Blob;
025import java.sql.Connection;
026import java.sql.PreparedStatement;
027import java.sql.ResultSet;
028import java.sql.SQLException;
029import java.sql.Timestamp;
030import java.util.ArrayList;
031import java.util.HashSet;
032import java.util.List;
033import java.util.Map;
034import java.util.Set;
035
036import org.apache.commons.logging.LogFactory;
037import org.apache.oozie.util.XLog;
038import org.apache.oozie.util.db.Schema.Column;
039import org.apache.oozie.util.db.Schema.Table;
040
041/**
042 * The <code>SqlStatement</code> is used to generate SQL Statements. Prepare the generated Statements and also to parse
043 * the resultSets
044 */
045public abstract class SqlStatement {
046
047    private static XLog log = new XLog(LogFactory.getLog(SqlStatement.class));
048    protected boolean forUpdate = false;
049
050    /**
051     * <code>ResultSetReader</code> is used to parse the result set and gives methods for getting appropriate type of
052     * data given the column name
053     */
054    public static class ResultSetReader {
055        final ResultSet rSet;
056
057        private ResultSetReader(ResultSet rSet) {
058            this.rSet = rSet;
059        }
060
061        /**
062         * Move the Result Set to next record
063         *
064         * @return true if there is a next record
065         * @throws SQLException
066         */
067        public boolean next() throws SQLException {
068            return rSet.next();
069        }
070
071        /**
072         * Close the Result Set
073         *
074         * @throws SQLException
075         */
076        public void close() throws SQLException {
077            rSet.close();
078        }
079
080        /**
081         * Get the Column data given its type and name
082         *
083         * @param <T> Type of the column
084         * @param clazz Class of the Type
085         * @param col Column name
086         * @return Column data
087         * @throws SQLException
088         */
089        @SuppressWarnings("unchecked")
090        public <T> T get(Class<T> clazz, Column col) throws SQLException {
091            if (clazz.isAssignableFrom(col.getType())) {
092                return (T) rSet.getObject(col.asLabel());
093            }
094            else {
095                if (String.class.equals(clazz)) {
096                    return (T) ("" + rSet.getObject(col.asLabel()));
097                }
098                else {
099                    throw new RuntimeException("Column Error : Actual Type [" + col.getType() + "]," + " Requested Type ["
100                            + clazz + "] !!");
101                }
102            }
103        }
104
105        /**
106         * Get the data for columns with blob type
107         *
108         * @param col Column name
109         * @return Column data
110         * @throws SQLException
111         */
112        public byte[] getByteArray(Column col) throws SQLException {
113            byte[] bArray = null;
114            if (Blob.class.equals(col.getType())) {
115                BufferedInputStream bStream = new BufferedInputStream(rSet.getBinaryStream(col.asLabel()));
116                byte[] temp = new byte[1024];
117                int num = 0;
118                ByteArrayOutputStream baos = new ByteArrayOutputStream();
119                BufferedOutputStream bOut = new BufferedOutputStream(baos);
120                try {
121                    while ((num = bStream.read(temp)) != -1) {
122                        bOut.write(temp, 0, num);
123                    }
124                    bOut.flush();
125                    bOut.close();
126                }
127                catch (IOException e) {
128                    throw new SQLException(e);
129                }
130                bArray = baos.toByteArray();
131            }
132            else {
133                throw new RuntimeException("Column Error : Actual Type [" + col.getType() + "]," + " Requested Type ["
134                        + Blob.class + "] !!");
135            }
136            return bArray;
137        }
138
139        /**
140         * Get a String Column
141         *
142         * @param col Column Name
143         * @return Column data
144         * @throws SQLException
145         */
146        public String getString(Column col) throws SQLException {
147            return get(String.class, col);
148        }
149
150        /**
151         * Get the Timestamp Column
152         *
153         * @param col Column name
154         * @return Column data
155         * @throws SQLException
156         */
157        public Timestamp getTimestamp(Column col) throws SQLException {
158            return get(Timestamp.class, col);
159        }
160
161        /**
162         * Get the Boolean Column
163         *
164         * @param col Column name
165         * @return Column data
166         * @throws SQLException
167         */
168        public Boolean getBoolean(Column col) throws SQLException {
169            return get(Boolean.class, col);
170        }
171
172        /**
173         * Get the Numeric data
174         *
175         * @param col Column name
176         * @return Column data
177         * @throws SQLException
178         */
179        public Long getLong(Column col) throws SQLException {
180            return get(Long.class, col);
181        }
182
183    }
184
185    /**
186     * Construct the condition statement that will be used in the where clause
187     */
188    public static class Condition {
189
190        final Column column;
191        protected StringBuilder sb = new StringBuilder("( ");
192        protected List<Object> values = new ArrayList<Object>();
193
194        private Condition(Column column) {
195            this.column = column;
196            if (column != null) {
197                sb.append(column);
198            }
199        }
200
201        /**
202         * Return the Condition string
203         */
204        public String toString() {
205            return sb.toString();
206        }
207    }
208
209    /**
210     * NULL/NOT NULL Condition Generator
211     */
212    static class Null extends Condition {
213        Null(boolean isInvert, Column column) {
214            super(column);
215            sb.append(" IS");
216            sb.append(isInvert ? " NOT" : "");
217            sb.append(" NULL ");
218            sb.append(" )");
219        }
220    }
221
222    /**
223     * Generate condition statement for IS NULL
224     *
225     * @param column column name
226     * @return IS NULL condition statement
227     */
228    public static Condition isNull(Column column) {
229        return new Null(false, column);
230    }
231
232    /**
233     * Generate condition statement for IS NOT NULL
234     *
235     * @param column column name
236     * @return IS NOT NULL condition statement
237     */
238    public static Condition isNotNull(Column column) {
239        return new Null(true, column);
240    }
241
242    /**
243     * LIKE/NOT LIKE Condition Generator
244     */
245    static class Like extends Condition {
246        Like(boolean isInvert, Column column, String value) {
247            super(column);
248            sb.append(isInvert ? " NOT" : "").append(" LIKE ").append("?").append(" )");
249            values.add(value);
250        }
251    }
252
253    /**
254     * Generate condition statement for IS LIKE
255     *
256     * @param column column name
257     * @param value value to be checked
258     * @return IS LIKE condition statement
259     */
260    public static Condition isLike(Column column, String value) {
261        return new Like(false, column, value);
262    }
263
264    /**
265     * Generates condition statement for IS NOT LIKE
266     *
267     * @param column column name
268     * @param value value to be checked
269     * @return IS NOT LIKE condition statement
270     */
271    public static Condition isNotLike(Column column, String value) {
272        return new Like(true, column, value);
273    }
274
275    /**
276     * Comparison condition statement generator
277     */
278    static class Compare extends Condition {
279        Compare(String oper, Column column, Object value) {
280            super(column);
281            if (value instanceof Column) {
282                sb.append(oper).append(value).append(" )");
283            }
284            else {
285                sb.append(oper).append("?").append(" )");
286                values.add(value);
287            }
288        }
289    }
290
291    /**
292     * Generate Condition statement for equality check
293     *
294     * @param column
295     * @param value
296     * @return Equality Condition statement
297     */
298    public static Condition isEqual(Column column, Object value) {
299        return new Compare(" = ", column, value);
300    }
301
302    /**
303     * Generate InEquality Condition statement
304     *
305     * @param column
306     * @param value
307     * @return Inequality Condition statement
308     */
309    public static Condition isNotEqual(Column column, Object value) {
310        return new Compare(" <> ", column, value);
311    }
312
313    /**
314     * Generate Condition statement for LESS THAN condition checking
315     *
316     * @param column
317     * @param value
318     * @return less than condition statement
319     */
320    public static Condition lessThan(Column column, Object value) {
321        return new Compare(" < ", column, value);
322    }
323
324    /**
325     * Generate Condition statement for GREATER THAN condition checking
326     *
327     * @param column
328     * @param value
329     * @return greater than condition statement
330     */
331    public static Condition greaterThan(Column column, Object value) {
332        return new Compare(" > ", column, value);
333    }
334
335    /**
336     * Generate Condition statement for LESS THAN OR EQUAL condition checking
337     *
338     * @param column
339     * @param value
340     * @return less than or equal condition statement
341     */
342    public static Condition lessThanOrEqual(Column column, Object value) {
343        return new Compare(" <= ", column, value);
344    }
345
346    /**
347     * Generate Condition statement for GREATER THAN OR EQUAL condition checking
348     *
349     * @param column
350     * @param value
351     * @return greater than or equal condition statement
352     */
353    public static Condition greaterThanOrEqual(Column column, Object value) {
354        return new Compare(" >= ", column, value);
355    }
356
357    /**
358     * IN/NOT IN condition statement generator for checking multiple values and for sub queries
359     */
360    static class In extends Condition {
361        In(boolean isInvert, Column column, Object... values) {
362            super(column);
363            sb.append(isInvert ? " NOT" : "").append(" IN (");
364            for (Object value : values) {
365                sb.append(" ? ").append(",");
366                this.values.add(value);
367            }
368            // remove last comma
369            sb.setLength(sb.length() - 1);
370            sb.append(") )");
371        }
372
373        In(boolean isInvert, Column column, Select select) {
374            super(column);
375            sb.append(isInvert ? " NOT" : "").append(" IN (");
376            for (Object value : select.values) {
377                this.values.add(value);
378            }
379            sb.append(select.toString());
380            sb.append(") )");
381        }
382    }
383
384    /**
385     * IN Condition for checking multiple values
386     *
387     * @param column
388     * @param values
389     * @return In condition statement
390     */
391    public static Condition in(Column column, Object... values) {
392        return new In(false, column, values);
393    }
394
395    /**
396     * NOT IN Condition for checking multiple values
397     *
398     * @param column
399     * @param values
400     * @return not in condition statement
401     */
402    public static Condition notIn(Column column, Object... values) {
403        return new In(true, column, values);
404    }
405
406    /**
407     * Sub query with IN condition
408     *
409     * @param column
410     * @param select
411     * @return Sub query using in
412     */
413    public static Condition in(Column column, Select select) {
414        return new In(false, column, select);
415    }
416
417    /**
418     * Sub query with NOT IN condition
419     *
420     * @param column
421     * @param select
422     * @return sub query using not in
423     */
424    public static Condition notIn(Column column, Select select) {
425        return new In(true, column, select);
426    }
427
428    /**
429     * Generate the Range checking statements
430     */
431    static class Between extends Condition {
432        Between(boolean isInvert, Column column, Object lVal, Object rVal) {
433            super(column);
434            sb.append(isInvert ? " NOT" : "").append(" BETWEEN ");
435            sb.append(" ? ");
436            values.add(lVal);
437            sb.append(" AND ");
438            sb.append(" ? ").append(" )");
439            values.add(rVal);
440        }
441    }
442
443    /**
444     * BETWEEN range checking statement
445     *
446     * @param column
447     * @param lVal min value for range checking
448     * @param rVal max value for range checking
449     * @return between condition statement
450     */
451    public static Condition between(Column column, Object lVal, Object rVal) {
452        return new Between(false, column, lVal, rVal);
453    }
454
455    /**
456     * NOT BETWEEN range checking statement
457     *
458     * @param column
459     * @param lVal min value for range checking
460     * @param rVal max value for range checking
461     * @return not between condition statement
462     */
463    public static Condition notBetween(Column column, Object lVal, Object rVal) {
464        return new Between(true, column, lVal, rVal);
465    }
466
467    /**
468     * Logical AND condition Generator
469     *
470     * @param conds list of conditions for AND
471     * @return AND statement
472     */
473    public static Condition and(Condition... conds) {
474        Condition retVal = new Condition(null);
475        if (conds.length >= 2) {
476            for (int i = 0; i < conds.length; i++) {
477                if (i > 0) {
478                    retVal.sb.append(" AND ");
479                }
480                retVal.sb.append(conds[i].sb);
481                retVal.values.addAll(conds[i].values);
482            }
483        }
484        else {
485            if (conds.length == 1) {
486                return conds[0];
487            }
488        }
489        retVal.sb.append(" )");
490        return retVal;
491    }
492
493    /**
494     * Logical OR condition generator
495     *
496     * @param conds list of conditions for OR
497     * @return OR statement
498     */
499    public static Condition or(Condition... conds) {
500        Condition retVal = new Condition(null);
501        if (conds.length >= 2) {
502            for (int i = 0; i < conds.length; i++) {
503                if (i > 0) {
504                    retVal.sb.append(" OR ");
505                }
506                retVal.sb.append(conds[i].sb);
507                retVal.values.addAll(conds[i].values);
508            }
509        }
510        else {
511            if (conds.length == 1) {
512                return conds[0];
513            }
514        }
515        retVal.sb.append(" )");
516        return retVal;
517    }
518
519    protected StringBuilder sb = new StringBuilder("");
520    protected List<Object> values = new ArrayList<Object>();
521
522    /**
523     * Select Statement generator. Generate the SQL Statement for select statements. Provide methods to add WHERE
524     * clause, ORDER BY clause, FOR UPDATE clause.
525     */
526    public static class Select extends SqlStatement {
527        private Condition condition;
528        private boolean isOdered = false;
529        private Column[] orderby = null;
530        private boolean[] isAscending = null;
531        private boolean isLimitSet = false;
532
533        private Select(Select other) {
534            this.condition = other.condition;
535            this.sb.append(other.sb);
536            this.values.addAll(other.values);
537            this.isOdered = other.isOdered;
538            this.isLimitSet = other.isLimitSet;
539            this.orderby = other.orderby;
540            this.isAscending = other.isAscending;
541            this.forUpdate = other.forUpdate;
542        }
543
544        Select(boolean count, Table... tables) {
545            String temp = count ? "COUNT(*)" : "*";
546            this.sb.append("SELECT " + temp + " FROM");
547            if ((tables != null) && (tables.length > 0)) {
548                for (Table table : tables) {
549                    this.sb.append(" " + table + " ,");
550                }
551                // remove comma
552                this.sb.setLength(sb.length() - 1);
553            }
554            else {
555                throw new RuntimeException("Need atleast 1 Table !!");
556            }
557        }
558
559        Select(Column... columns) {
560            this.sb.append("SELECT");
561            if ((columns != null) && (columns.length > 0)) {
562                Set<Table> tables = new HashSet<Table>();
563                for (Column column : columns) {
564                    tables.add(column.table());
565                    this.sb.append(" " + column + " AS " + column.asLabel() + " ,");
566                }
567                // remove comma
568                this.sb.setLength(sb.length() - 1);
569                this.sb.append(" FROM");
570                for (Table table : tables) {
571                    this.sb.append(" " + table + " ,");
572                }
573                // remove comma
574                this.sb.setLength(sb.length() - 1);
575            }
576            else {
577                throw new RuntimeException("Need atleast 1 Column !!");
578            }
579        }
580
581        /**
582         * Set the condition for where clause
583         *
584         * @param condition condition for where clause
585         * @return <code>Select</code> for cascading
586         */
587        public Select where(Condition condition) {
588            Select retVal = new Select(this);
589            retVal.condition = condition;
590            retVal.values.addAll(condition.values);
591            return retVal;
592        }
593
594        /**
595         * Sets the column to sort and the order of sort
596         *
597         * @param column column to sort
598         * @param order true = ascending
599         * @return <code>Select</code> for cascading
600         */
601        public Select orderBy(Column column, boolean order) {
602            if (!isOdered) {
603                Select retVal = new Select(this);
604                retVal.orderby = new Column[]{column};
605                retVal.isAscending = new boolean[]{order};
606                retVal.isOdered = true;
607                return retVal;
608            }
609            return this;
610        }
611
612        /**
613         * To sort 2 columns
614         *
615         * @param column0 First column to be sorted
616         * @param order0 true = ascending
617         * @param column1 Second column to be sorted
618         * @param order1 true = ascending
619         * @return <code>Select</code> for cascading
620         */
621        public Select orderBy(Column column0, boolean order0, Column column1, boolean order1) {
622            if (!isOdered) {
623                Select retVal = new Select(this);
624                retVal.orderby = new Column[]{column0, column1};
625                retVal.isAscending = new boolean[]{order0, order1};
626                retVal.isOdered = true;
627                return retVal;
628            }
629            return this;
630        }
631
632        /**
633         * Setting the offset and limit for LIMIT clause
634         *
635         * @param offset
636         * @param limit
637         * @return <code>Select</code> for cascading
638         */
639        public Select limit(int offset, int limit) {
640            if (isOdered) {
641                Select retVal = new Select(this);
642                retVal.values.add(limit);
643                retVal.values.add(offset);
644                retVal.isLimitSet = true;
645                return retVal;
646            }
647            return this;
648        }
649
650        /**
651         * Set the "for update" flag to lock the rows for updating
652         *
653         * @return <code>Select</code> for cascading
654         */
655        // TODO Not working for hsql
656        public Select forUpdate() {
657            Select retVal = new Select(this);
658            retVal.forUpdate = true;
659            return retVal;
660        }
661
662        /**
663         * Generate the SQL Select Statement with conditions and other clauses that were set
664         */
665        public String toString() {
666            String oBy = "";
667            if ((orderby != null) && (isAscending != null) && (orderby.length == isAscending.length)) {
668                StringBuffer osb = new StringBuffer(" ORDER BY ");
669                int i = 0;
670                for (Column column : orderby) {
671                    osb.append(column.asLabel()).append(isAscending[i] ? " ASC ," : " DESC ,");
672                }
673                osb.setLength(osb.length() - 1);
674                if (isLimitSet) {
675                    osb.append("LIMIT ").append("?").append(" OFFSET ").append("?").append(" ");
676                }
677                oBy = osb.toString();
678            }
679            return sb.toString() + ((condition != null) ? "WHERE " + condition.toString() : "") + oBy;
680        }
681    }
682
683    /**
684     * SQL Statement generator for DELETE Statements
685     */
686    public static class Delete extends SqlStatement {
687        Condition condition;
688        final Table table;
689
690        Delete(Table table) {
691            this.table = table;
692            this.sb.append("DELETE FROM " + table + " ");
693        }
694
695        private Delete(Delete other) {
696            this.table = other.table;
697            this.condition = other.condition;
698            this.sb.append(other.sb);
699            this.values.addAll(other.values);
700        }
701
702        /**
703         * Set the where clause for DELETE
704         *
705         * @param condition condition for where clause
706         * @return <code>Delete</code> for cascading
707         */
708        public Delete where(Condition condition) {
709            Delete retVal = new Delete(this);
710            retVal.condition = condition;
711            retVal.values.addAll(condition.values);
712            return retVal;
713        }
714
715        /**
716         * Return the DELETE Statement
717         */
718        public String toString() {
719            return sb.toString() + ((condition != null) ? "WHERE " + condition.toString() : "");
720        }
721    }
722
723    /**
724     * UPDATE SQL Statement generator
725     */
726    public static class Update extends SqlStatement {
727        Condition condition;
728        final Table table;
729
730        Update(Table table) {
731            this.table = table;
732            this.sb.append("UPDATE " + table + " SET ");
733        }
734
735        private Update(Update other) {
736            this.table = other.table;
737            this.condition = other.condition;
738            this.sb.append(other.sb);
739            this.values.addAll(other.values);
740        }
741
742        /**
743         * SET clause for update statement
744         *
745         * @param column column name
746         * @param value A temporary place holder which can be replaced while preparing
747         * @return <code>Update</code> for cascading
748         */
749        public Update set(Column column, Object value) {
750            Update retVal = new Update(this);
751            retVal.sb.append((values.isEmpty() ? "" : ", ") + column + " = ? ");
752            retVal.values.add(value);
753            return retVal;
754        }
755
756        /**
757         * Set condition for updating
758         *
759         * @param condition condition for where clause
760         * @return <code>Update</code> for cascading
761         */
762        public Update where(Condition condition) {
763            Update retVal = new Update(this);
764            retVal.condition = condition;
765            retVal.values.addAll(condition.values);
766            return retVal;
767        }
768
769        /**
770         * Return the UPDATE statement
771         */
772        public String toString() {
773            return sb.toString() + ((condition != null) ? " WHERE " + condition.toString() : "");
774        }
775    }
776
777    /**
778     * INSERT Statement generator
779     */
780    public static class Insert extends SqlStatement {
781        StringBuilder sbCol = new StringBuilder("");
782        StringBuilder sbVal = new StringBuilder("");
783        boolean isFirst = true;
784        final Table table;
785
786        Insert(Table table) {
787            this.table = table;
788            this.sbCol.append("INSERT INTO " + table + " ( )");
789            this.sbVal.append("VALUES ( )");
790        }
791
792        private Insert(Insert other) {
793            this.table = other.table;
794            this.sbCol.append(other.sbCol);
795            this.sbVal.append(other.sbVal);
796            this.values.addAll(other.values);
797            if (other.isFirst) {
798                this.isFirst = false;
799            }
800        }
801
802        /**
803         * Set the VALUES that are to be inserted
804         *
805         * @param column
806         * @param value A temporary place holder which will be replaced while preparing
807         * @return
808         */
809        public Insert value(Column column, Object value) {
810            Insert retVal = new Insert(this);
811            retVal.sbCol.setLength(retVal.sbCol.length() - 1);
812            retVal.sbVal.setLength(retVal.sbVal.length() - 1);
813            retVal.values.add(value);
814            retVal.sbCol.append(((isFirst) ? "" : ", ") + column + " )");
815            retVal.sbVal.append(((isFirst) ? "" : ", ") + "? )");
816            retVal.isFirst = false;
817            return retVal;
818        }
819
820        /**
821         * Return the INSERT Statement
822         */
823        public String toString() {
824            return sbCol.toString() + " " + sbVal.toString();
825        }
826    }
827
828    /**
829     * Prepare the SQL Statement that is generated and assign the values to prepared statement. setValues should be
830     * called to set the Real Values for place holders
831     *
832     * @param conn Connection
833     * @return Prepared SQL Statement
834     * @throws SQLException
835     */
836    public PreparedStatement prepareAndSetValues(Connection conn) throws SQLException {
837        String stmt = toString();
838        if (forUpdate && !Schema.isHsqlConnection(conn)) {
839            stmt += " FOR UPDATE";
840        }
841        PreparedStatement pStmt = conn.prepareStatement(stmt);
842        int i = 1;
843        for (Object value : this.values) {
844            pStmt.setObject(i, value);
845            i++;
846        }
847        log.trace(XLog.Info.get().createPrefix() + " Preparing : " + stmt);
848        log.trace(XLog.Info.get().createPrefix() + " Values : " + values);
849        return pStmt;
850    }
851
852    /**
853     * Assign the values to Prepared Statement. setValues should be called to set the Real Values for place holders
854     *
855     * @param pStmt Prepared Statement
856     * @return PreparedStatement with values set
857     * @throws SQLException
858     */
859    public PreparedStatement prepare(PreparedStatement pStmt) throws SQLException {
860        int i = 1;
861        pStmt.clearParameters();
862        for (Object value : this.values) {
863            pStmt.setObject(i, value);
864            i++;
865        }
866        return pStmt;
867    }
868
869    /**
870     * Prepare the SQL Statement. Doesn't set the values.
871     *
872     * @param conn Connection
873     * @return PreparedStatement
874     * @throws SQLException
875     */
876    public PreparedStatement prepare(Connection conn) throws SQLException {
877        String stmt = toString();
878        if (forUpdate && !Schema.isHsqlConnection(conn)) {
879            stmt += " FOR UPDATE";
880        }
881        return conn.prepareStatement(stmt);
882    }
883
884    /**
885     * Preparing Multiple statements for batch execution.
886     *
887     * @param conn Connection
888     * @param values A list of maps that contains the actual values
889     * @return Prepared Statement
890     * @throws SQLException
891     */
892    public PreparedStatement prepareForBatch(Connection conn, List<? extends Map<Object, Object>> values,
893                                             PreparedStatement pStmt) throws SQLException {
894        String stmt = toString();
895        if (forUpdate && !Schema.isHsqlConnection(conn)) {
896            stmt += " FOR UPDATE";
897        }
898        // PreparedStatement pStmt = conn.prepareStatement(stmt);
899        for (Map<Object, Object> map : values) {
900            getNewStatementWithValues(map).prepare(pStmt);
901            pStmt.addBatch();
902        }
903        return pStmt;
904    }
905
906    /**
907     * Replace the place holders with actual values in the sql statement
908     *
909     * @param oldVal Place holder
910     * @param newVal Actual Value
911     * @return SQL Statement with oldVal(place holder) replaced with newVal(actual value)
912     */
913    public SqlStatement setValue(Object oldVal, Object newVal) {
914        ArrayList<Object> temp = new ArrayList<Object>(values);
915        if (values.contains(oldVal)) {
916            int i = temp.indexOf(oldVal);
917            temp.set(i, newVal);
918        }
919        SqlStatement retVal = create(temp);
920        return retVal;
921    }
922
923    /**
924     * Replace the keys(newValues) which are place holders in the sql statements with the corresponding new values. And
925     * Gives back a new SQL Statement so that the actual statement can be re-used
926     *
927     * @param newValues
928     * @return A New SQL Statement object with actual values set in its member
929     */
930    public SqlStatement getNewStatementWithValues(Map<Object, Object> newValues) {
931        ArrayList<Object> temp = new ArrayList<Object>();
932        for (Object value : values) {
933            if (newValues.containsKey(value)) {
934                temp.add(newValues.get(value));
935            }
936            else {
937                temp.add(value);
938            }
939        }
940        SqlStatement retVal = create(temp);
941        return retVal;
942    }
943
944    /**
945     * Create the Appropriate SQL Statement with the given values
946     *
947     * @param temp
948     * @return
949     */
950    private SqlStatement create(ArrayList<Object> temp) {
951        SqlStatement retVal = null;
952        if (this instanceof Select) {
953            retVal = new Select((Select) this);
954        }
955        else {
956            if (this instanceof Insert) {
957                retVal = new Insert((Insert) this);
958            }
959            else {
960                if (this instanceof Update) {
961                    retVal = new Update((Update) this);
962                }
963                else {
964                    if (this instanceof Delete) {
965                        retVal = new Delete((Delete) this);
966                    }
967                }
968            }
969        }
970        retVal.values.clear();
971        retVal.values.addAll(temp);
972        return retVal;
973    }
974
975    /**
976     * Create the <code>ResultSetReader</code> object that has the methods to access the data from the result set
977     *
978     * @param rSet Result Set
979     * @return ResultSet Reader
980     */
981    public static ResultSetReader parse(ResultSet rSet) {
982        return new ResultSetReader(rSet);
983    }
984
985    /**
986     * Return a new Insert Statement
987     *
988     * @param table
989     * @return Insert statement
990     */
991    public static Insert insertInto(Table table) {
992        return new Insert(table);
993    }
994
995    /**
996     * Return a new Update Statement
997     *
998     * @param table
999     * @return Update statement
1000     */
1001    public static Update update(Table table) {
1002        return new Update(table);
1003    }
1004
1005    /**
1006     * Return a new Delete Statement
1007     *
1008     * @param table
1009     * @return Delete Statement
1010     */
1011    public static Delete deleteFrom(Table table) {
1012        return new Delete(table);
1013    }
1014
1015    /**
1016     * Return a Select All Statement
1017     *
1018     * @param tables
1019     * @return Select * statement
1020     */
1021    public static Select selectAllFrom(Table... tables) {
1022        return new Select(false, tables);
1023    }
1024
1025    /**
1026     * Return a Select Statement
1027     *
1028     * @param columns columns to select
1029     * @return select statement
1030     */
1031    public static Select selectColumns(Column... columns) {
1032        return new Select(columns);
1033    }
1034
1035    /**
1036     * Select count(*) Statement generator.
1037     *
1038     * @param tables
1039     * @return "select count(*) from tables" statement
1040     */
1041    public static Select getCount(Table... tables) {
1042        return new Select(true, tables);
1043    }
1044}