View Javadoc

1   /*
2    * Copyright 2004-2007 Brian McCallister
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.skife.jdbi.v2;
18  
19  import org.antlr.runtime.ANTLRStringStream;
20  import org.antlr.runtime.Token;
21  import org.skife.jdbi.v2.exceptions.UnableToCreateStatementException;
22  import org.skife.jdbi.v2.exceptions.UnableToExecuteStatementException;
23  import org.skife.jdbi.v2.tweak.Argument;
24  import org.skife.jdbi.v2.tweak.RewrittenStatement;
25  import org.skife.jdbi.v2.tweak.StatementRewriter;
26  import org.skife.jdbi.rewriter.colon.ColonStatementLexer;
27  
28  import static org.skife.jdbi.rewriter.colon.ColonStatementLexer.*;
29  
30  import java.sql.PreparedStatement;
31  import java.sql.SQLException;
32  import java.util.ArrayList;
33  import java.util.List;
34  
35  /**
36   * Statement rewriter which replaces named parameter tokens of the form :tokenName
37   * <p/>
38   * This is the default statement rewriter
39   */
40  public class ColonPrefixNamedParamStatementRewriter implements StatementRewriter
41  {
42      /**
43       * Munge up the SQL as desired. Responsible for figuring out ow to bind any
44       * arguments in to the resultant prepared statement.
45       *
46       * @param sql The SQL to rewrite
47       * @param params contains the arguments which have been bound to this statement.
48       * @param ctx The statement context for the statement being executed
49       * @return somethign which can provde the actual SQL to prepare a statement from
50       *         and which can bind the correct arguments to that prepared statement
51       */
52      public RewrittenStatement rewrite(String sql, Binding params, StatementContext ctx)
53      {
54          StringBuilder b = new StringBuilder();
55          ParsedStatement stmt = new ParsedStatement();
56          ColonStatementLexer lexer = new ColonStatementLexer(new ANTLRStringStream(sql));
57          try {
58              Token t = lexer.nextToken();
59              while (t.getType() != ColonStatementLexer.EOF) {
60                  switch (t.getType()) {
61                      case LITERAL:
62                          b.append(t.getText());
63                          break;
64                      case NAMED_PARAM:
65                          stmt.addNamedParamAt(t.getText().substring(1, t.getText().length()));
66                          b.append("?");
67                          break;
68                      case QUOTED_TEXT:
69                          b.append(t.getText());
70                          break;
71                      case DOUBLE_QUOTED_TEXT:
72                          b.append(t.getText());
73                          break;
74                      case POSITIONAL_PARAM:
75                          b.append("?");
76                          stmt.addPositionalParamAt();
77                          break;
78                  }
79                  t = lexer.nextToken();
80              }
81          }
82          catch (IllegalArgumentException e) {
83              throw new UnableToCreateStatementException("Exception parsing for named parameter replacement", e, ctx);
84          }
85  
86          return new MyRewrittenStatement(b.toString(), stmt, ctx);
87      }
88  
89      private static class MyRewrittenStatement implements RewrittenStatement
90      {
91          private final String sql;
92          private final ParsedStatement stmt;
93          private final StatementContext context;
94  
95          public MyRewrittenStatement(String sql, ParsedStatement stmt, StatementContext ctx)
96          {
97              this.context = ctx;
98              this.sql = sql;
99              this.stmt = stmt;
100         }
101 
102         public void bind(Binding params, PreparedStatement statement) throws SQLException
103         {
104             if (stmt.positionalOnly) {
105                 // no named params, is easy
106                 boolean finished = false;
107                 for (int i = 0; !finished; ++i) {
108                     final Argument a = params.forPosition(i);
109                     if (a != null) {
110                         try {
111                         a.apply(i + 1, statement, this.context);
112                         }
113                         catch (SQLException e) {
114                             throw new UnableToExecuteStatementException(
115                                     String.format("Excpetion while binding positional param at (0 based) position %d",
116                                                   i), e, context);
117                         }
118                     }
119                     else {
120                         finished = true;
121                     }
122                 }
123             }
124             else {
125                 //List<String> named_params = stmt.params;
126                 int i = 0;
127                 for (String named_param : stmt.params) {
128                     if ("*".equals(named_param)) continue;
129                     Argument a = params.forName(named_param);
130                     if (a == null) {
131                         a = params.forPosition(i);
132                     }
133 
134                     if (a == null) {
135                         String msg = String.format("Unable to execute, no named parameter matches " +
136                                                    "\"%s\" and no positional param for place %d (which is %d in " +
137                                                    "the JDBC 'start at 1' scheme) has been set.",
138                                                    named_param, i, i + 1);
139                         throw new UnableToExecuteStatementException(msg, context);
140                     }
141 
142                     try {
143                         a.apply(i + 1, statement, this.context);
144                     }
145                     catch (SQLException e) {
146                         throw new UnableToCreateStatementException(String.format("Exception while binding '%s'",
147                                                                                  named_param), e, context);
148                     }
149                     i++;
150                 }
151             }
152         }
153 
154         public String getSql()
155         {
156             return sql;
157         }
158     }
159 
160     private static class ParsedStatement
161     {
162 
163         private boolean positionalOnly = true;
164         private List<String> params = new ArrayList<String>();
165 
166         public void addNamedParamAt(String name)
167         {
168             positionalOnly = false;
169             params.add(name);
170         }
171 
172         public void addPositionalParamAt()
173         {
174             params.add("*");
175         }
176     }
177 }