View Javadoc

1   ////////////////////////////////////////////////////////////////////////////////
2   // MillScript: an Open Spice interpreter and batch website creation tool
3   // Copyright (C) 2001-2004 Open World Ltd
4   // Copyright (C) 2005 Kevin Rogers
5   //
6   // This file is part of MillScript.
7   //
8   // MillScript is free software; you can redistribute it and/or modify it under
9   // the terms of the GNU General Public License as published by the Free
10  // Software Foundation; either version 2 of the License, or (at your option)
11  // any later version.
12  //
13  // MillScript is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
16  // more details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // MillScript; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA  02111-1307  USA
21  ////////////////////////////////////////////////////////////////////////////////
22  package org.millscript.millscript.functions;
23  
24  import org.millscript.millscript.alert.Alerts;
25  import org.millscript.millscript.datatypes.DatabaseSource;
26  import org.millscript.millscript.vm.Machine;
27  
28  import java.sql.Connection;
29  import java.sql.PreparedStatement;
30  import java.sql.SQLException;
31  
32  /**
33   * Thsi class implements a millscript function to execute an SQL update through
34   * the JDBC interface. The update is done using a prepared statement, which
35   * should improve efficiency for multiple updates. The generated function
36   * requires at least one argument, which will be the DataSource for the update.
37   * Any subsequent arguments are treated as parameters for the prepared
38   * statement.
39   * <pre>
40   * f( datasource, arg1, arg2, arg3, ... )
41   * </pre>
42   * The function returns the number of rows changed.
43   *
44   * @since 9.6.4
45   */
46  public class JdbcExecuteUpdateFunction extends Function {
47  
48      /**
49       * The SQL update statement this function will prepare and execute.
50       */
51      private final String sql;
52  
53      /**
54       * The Connection, cached when the function is first appplied, or the
55       * DataSource changes.
56       */
57      private Connection cachedConn;
58  
59      /**
60       * The PreparedStatement, cached when the function is first applied, or the
61       * DataSource changes.
62       */
63      private PreparedStatement cachedPrep;
64  
65      /**
66       * Construct a new function which will execute the specifed SQL as a
67       * prepared statement.
68       *
69       * @param    sqlString  the string containing SQL to prepare and execute
70       */
71      public JdbcExecuteUpdateFunction( final String sqlString ) {
72          this.sql = sqlString;
73      }
74  
75      /**
76       * @see org.millscript.millscript.functions.Function#apply(org.millscript.millscript.vm.Machine, int)
77       */
78      @Override
79      public void apply( final Machine mc, final int nargs ) {
80  
81          // Check that we received at least one argument
82          this.checkNargsGT( mc, 1, nargs );
83  
84          // Get any prepared statement arguments
85          final Object[] prepArgs = mc.popArgsArray( nargs - 1 );
86  
87          // Pop the DataSource we will use for executing the prepared statement
88          final DatabaseSource source = mc.popDatabaseSource();
89  
90          try {
91  
92              // Obtain a connection from the supplied DataSource
93              Connection conn = source.getConnection();
94  
95              // First check if the supplied DataSource is the same as the one we
96              // have cached from previous applications of this function.
97              if ( conn != cachedConn ) {
98                  // Close the existing prepared statement and connection.
99                  if ( cachedPrep != null ) {
100                     cachedPrep.close();
101                 }
102 
103                 // Using the new connection, prepare the SQL statement
104                 cachedPrep = conn.prepareStatement( sql );
105 
106                 // Now we're fully initialised, store the supplied DataSource.
107                 cachedConn = conn;
108             }
109 
110             // Initialise the parameters for the prepared statement by iterating
111             // through each one and setting it.
112             for ( int i = 0; i < prepArgs.length; i++ ) {
113                 this.cachedPrep.setObject( i + 1, prepArgs[ i ] );
114             }
115 
116             // Execute the update
117             int rowsChanged = this.cachedPrep.executeUpdate();
118 
119             // Push the results from the execution of the statement.
120             mc.pushObject( new Integer( rowsChanged ) );
121 
122             // Clear any parameters from the prepared statement.
123             this.cachedPrep.clearParameters();
124 
125         } catch ( SQLException ex ) {
126             // We would want to report the SQL state and the PostgreSQL vendor
127             // error code as commented out below, except they don't provide it.
128             // culprit( "SQLState", ex.getSQLState() ),
129             // culprit( "PostgreSQL error code", String.valueOf( ex.getErrorCode() ) ),
130             throw(
131                 Alerts.eval(
132                     "SQL update failure",
133                     ex.getMessage()
134                 ).culprit( "SQL", sql ).mishap()
135             );
136         }
137 
138     }
139 
140     /**
141      * Returns a string representation of this function. It shows that it
142      * requires one or more arguments.
143      *
144      * @return    a string representation of this function
145      */
146     @Override
147     public String toString() {
148         String name = getName();
149         return (
150             name == null ?
151             "<function (1+)>" :
152             "<function " + name + "(1+)>"
153         );
154     }
155 
156 }