1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.millscript.millscript.functions;
23
24 import org.millscript.commons.util.list.ELinkedList;
25 import org.millscript.millscript.alert.Alerts;
26 import org.millscript.millscript.datatypes.DatabaseRecord;
27 import org.millscript.millscript.datatypes.DatabaseSource;
28 import org.millscript.millscript.vm.Machine;
29
30 import java.sql.Connection;
31 import java.sql.Date;
32 import java.sql.PreparedStatement;
33 import java.sql.ResultSet;
34 import java.sql.ResultSetMetaData;
35 import java.sql.SQLException;
36 import java.util.GregorianCalendar;
37
38 /**
39 * This class implements a millscript function to execute an SQL query through
40 * the JDBC interface. The query is done using a prepared statement, which
41 * should improve efficiency for multiple queries. The generated function
42 * requires at least one argument, which will be the DataSource for the query.
43 * Any subsequent arguments are treated as parameters for the prepared
44 * statement.
45 * <pre>
46 * f( datasource, arg1, arg2, arg3, ... )
47 * </pre>
48 * The function returns a list-of-maps.
49 *
50 * @since 9.6.4
51 */
52 public class JdbcExecuteQueryFunction extends Function {
53
54 /**
55 * The SQL statement this function will prepare and execute.
56 */
57 private final String sql;
58
59 /**
60 * The Connection, cached when the function is first appplied, or the
61 * DataSource changes.
62 */
63 private Connection cachedConn;
64
65 /**
66 * The PreparedStatement, cached when the function is first applied, or the
67 * DataSource changes.
68 */
69 private PreparedStatement cachedPrep;
70
71 /**
72 * Construct a new function which will execute the specifed SQL as a
73 * prepared statement.
74 *
75 * @param s the string containing SQL to prepare and execute
76 */
77 public JdbcExecuteQueryFunction( final String s ) {
78 this.sql = s;
79 }
80
81 /**
82 * @see org.millscript.millscript.functions.Function#apply(org.millscript.millscript.vm.Machine, int)
83 */
84 @Override
85 public void apply( final Machine mc, final int nargs ) {
86
87
88 this.checkNargsGT( mc, 1, nargs );
89
90
91 final Object[] prepArgs = mc.popArgsArray( nargs - 1 );
92
93
94 final DatabaseSource source = mc.popDatabaseSource();
95
96 try {
97
98
99 Connection conn = source.getConnection();
100
101
102
103 if ( conn != cachedConn ) {
104
105 if ( cachedPrep != null ) {
106 cachedPrep.close();
107 }
108
109
110 cachedPrep = conn.prepareStatement( sql );
111
112
113 cachedConn = conn;
114 }
115
116
117
118 for ( int i = 0; i < prepArgs.length; i++ ) {
119 this.cachedPrep.setObject( i + 1, prepArgs[ i ] );
120 }
121
122 ResultSet rs = this.cachedPrep.executeQuery();
123
124
125 mc.pushObject( changeResultSet( rs ) );
126
127
128 rs.close();
129
130
131 this.cachedPrep.clearParameters();
132
133 } catch ( SQLException ex ) {
134
135
136
137
138 throw(
139 Alerts.eval(
140 "SQL query failure",
141 ex.getMessage()
142 ).culprit( "SQL", sql ).mishap()
143 );
144 }
145
146 }
147
148 /**
149 * Changes the supplied ResultSet into a LinkedList of database records.
150 *
151 * @param rs the result set containing the results to change
152 * @return a LinkedList of database records
153 */
154 private ELinkedList changeResultSet( final ResultSet rs ) throws SQLException {
155
156
157 final ResultSetMetaData meta = rs.getMetaData();
158
159
160 final ELinkedList< DatabaseRecord > results = new ELinkedList< DatabaseRecord >();
161
162
163 final int columnCount = meta.getColumnCount();
164
165
166 while ( rs.next() ) {
167
168
169
170 final DatabaseRecord record = new DatabaseRecord();
171
172
173
174 record.unlock();
175
176
177 for ( int index = 1; index <= columnCount; index++ ) {
178
179
180 final String columnName = meta.getColumnName( index );
181
182
183
184 switch ( meta.getColumnType( index ) ) {
185
186 case java.sql.Types.DATE:
187
188 final GregorianCalendar cal = new GregorianCalendar();
189 Date date = rs.getDate( index, cal );
190
191 if ( date != null ) {
192 cal.setTime( date );
193 }
194
195 record.insert( columnName, cal );
196 break;
197
198 default:
199 record.insert( columnName, rs.getObject( index ) );
200
201 }
202
203 }
204
205
206
207 record.lock();
208
209
210 results.addLast( record );
211
212 }
213
214 int size = results.size();
215
216 System.out.println( "Received " + size + " record" + ( size == 1 ? "" : "s" ) );
217
218
219 return results;
220 }
221
222 /**
223 * Returns a string representation of this function. It shows that it
224 * requires one or more arguments.
225 *
226 * @return a string representation of this function
227 */
228 @Override
229 public String toString() {
230 String name = getName();
231 return (
232 name == null ?
233 "<function (1+)>" :
234 "<function " + name + "(1+)>"
235 );
236 }
237
238 }