001/*******************************************************************************
002The MIT License (MIT)
003
004Copyright (c) 2024 KILLCODING.COM
005
006Permission is hereby granted, free of charge, to any person obtaining a copy
007of this software and associated documentation files (the "Software"), to deal
008in the Software without restriction, including without limitation the rights
009to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010copies of the Software, and to permit persons to whom the Software is
011furnished to do so, subject to the following conditions:
012
013The above copyright notice and this permission notice shall be included in
014all copies or substantial portions of the Software.
015
016THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
022THE SOFTWARE.
023*****************************************************************************/
024package com.killcoding.datasource;
025
026import java.sql.Connection;
027import java.sql.PreparedStatement;
028import java.util.List;
029import java.sql.SQLException;
030import java.sql.Types;
031import java.util.Map;
032import java.sql.ResultSet;
033import java.sql.ResultSetMetaData;
034import java.util.HashMap;
035import java.util.ArrayList;
036import java.util.regex.Pattern;
037import java.util.regex.Matcher;
038import java.util.Arrays;
039import com.killcoding.tool.ResultMap;
040import com.killcoding.log.LoggerFactory;
041import com.killcoding.log.Logger;
042import com.killcoding.cache.CacheArray;
043import java.io.IOException;
044import java.sql.Blob;
045import java.sql.Clob;
046import java.io.InputStream;
047import java.sql.CallableStatement;
048
049/**
050 * This class uses a 'CacheArray' to process the asynchronous processing method of the data query result set.
051 * If you choose to use the mode 'DiskCache', temporary files will be generated. 
052 * If you choose to use the mode 'MemoryCache', temporary files will not be generated but the physical memory size must be considered.
053 * */
054public final class CacheDriverExecutor extends DriverExecutor {
055
056        public static long READ_TIMER = 10L;
057
058        /**
059         * New a object CacheDriverExecutor
060         * @param connection - it is jdbc connection
061         * */
062        public CacheDriverExecutor(Connection connection) {
063                super(connection);
064        }
065
066        /**
067         * This is full table query function
068         * @param sql - Query sql 
069         * @param params - Query params 
070         * @param rows - CacheArray object
071         * @exception SQLException - if query failed
072         * */
073        public void find(String sql, List<Object> params, final CacheArray rows) throws SQLException {
074                find(0, 0, sql, params, rows);
075        }
076
077        /**
078         * This is full table query function
079         * @param sql - Query sql 
080         * @param params - Query params 
081         * @param rows - CacheArray object
082         * @exception SQLException - if query failed
083         * */
084        public void find(String sql, Map<String, Object> params, final CacheArray rows)
085                        throws SQLException {
086                String csql = converSql(sql);
087                List<Object> cparams = converParams(sql, params);
088                find(0, 0, csql, cparams, rows);
089        }
090
091        /**
092         * This is full table query function
093         * @param sql - Query sql 
094         * @param rows - CacheArray object
095         * @exception SQLException - if query failed
096         * */
097        public void find(String sql, final CacheArray rows) throws SQLException {
098                find(0, 0, sql, Arrays.asList(new Object[] {}), rows);
099        }
100
101        /**
102         * This is limited rows query function
103         * @param cursorStart - JDBC result Cursor start index
104         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000)
105         * @param sql - Query sql 
106         * @param rows - CacheArray object
107         * @exception SQLException - if query failed
108         * */
109        public void find(int cursorStart, int maxRows, String sql, final CacheArray rows)
110                        throws SQLException {
111                find(cursorStart, maxRows, sql, Arrays.asList(new Object[] {}), rows);
112        }
113
114        /**
115         * This is limited rows query function (use Map param mode)
116         * @param cursorStart - JDBC result Cursor start index
117         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000)
118         * @param sql - Query sql 
119         * @param rows - CacheArray object
120         * @exception SQLException - if query failed
121         * */
122        public void find(int cursorStart, int maxRows, String sql, Map<String, Object> params,
123                        final CacheArray rows) throws SQLException {
124                String csql = converSql(sql);
125                List<Object> cparams = converParams(sql, params);
126                find(cursorStart, maxRows, csql, cparams, rows);
127        }
128
129        /**
130         * This is limited rows query function (use List param mode)
131         * @param cursorStart - JDBC result Cursor start index
132         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000)
133         * @param sql - Query sql 
134         * @param rows - CacheArray object
135         * @exception SQLException - if query failed
136         * */
137        public void find(int cursorStart, int maxRows, String sql, List<Object> params,
138                        final CacheArray rows) throws SQLException {
139                long begin = System.currentTimeMillis();
140                boolean allowedLog = writeSqlLog("find", begin,
141                                String.format("%s [cursorStart=%s,maxRows=%s]", sql, cursorStart, maxRows), params);
142                PreparedStatement statement = null;
143                Map<String, Object> row = null;
144
145                ResultSet result = null;
146                try {
147                        // ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE
148                        // ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY
149                        statement = connection.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
150                        if (params != null) {
151                                int size = params.size();
152                                for (int i = 0; i < size; i++) {
153                                        int ci = i + 1;
154                                        Object param = params.get(i);
155                                        if (param == null) {
156                                                statement.setNull(ci, Types.VARCHAR);
157                                        } else {
158                                                statement.setObject(ci, param);
159                                        }
160                                }
161                        }
162                        if (maxRows > 0) {
163                                statement.setMaxRows(maxRows);
164                        }
165                        result = statement.executeQuery();
166                        result.absolute(cursorStart);
167                        final ResultSetMetaData rsmd = result.getMetaData();
168                        final int c = rsmd.getColumnCount();
169                        while (result.next()) {
170                                row = new ResultMap<String, Object>();
171                                for (int i = 0; i < c; i++) {
172                                        int ci = i + 1;
173                                        Object value = null;
174                                        Object originValue = result.getObject(ci);
175                                        if (originValue == null) {
176                                                value = originValue;
177                                        } else if (originValue instanceof Blob) {
178                                                Blob blobValue = (Blob) originValue;
179                                                InputStream is = null;
180                                                try {
181                                                        is = blobValue.getBinaryStream();
182                                                        if(is != null) value = is.readAllBytes();
183                                                } catch (IOException e) {
184                                                        throw new SQLException(e.getMessage(), e);
185                                                } finally {
186                                                        if (blobValue != null) {
187                                                                try {
188                                                                        blobValue.free();
189                                                                } catch (SQLException e) {
190                                                                        throw e;
191                                                                }
192                                                        }
193                                                        if (is != null) {
194                                                                try {
195                                                                        is.close();
196                                                                } catch (IOException e) {
197                                                                        throw new SQLException(e.getMessage(), e);
198                                                                }
199                                                        }
200                                                }
201                                        } else if (originValue instanceof Clob) {
202                                                Clob clobValue = (Clob) originValue;
203                                                InputStream is = null;
204                                                try {
205                                                        is = clobValue.getAsciiStream();
206                            if(is != null) value = is.readAllBytes();
207                                                } catch (IOException e) {
208                                                        throw new SQLException(e.getMessage(), e);
209                                                } finally {
210                                                        if (clobValue != null) {
211                                                                try {
212                                                                        clobValue.free();
213                                                                } catch (SQLException e) {
214                                                                        throw e;
215                                                                }
216                                                        }
217                                                        if (is != null) {
218                                                                try {
219                                                                        is.close();
220                                                                } catch (IOException e) {
221                                                                        throw new SQLException(e.getMessage(), e);
222                                                                }
223                                                        }
224                                                }
225                                        } else {
226                                                value = originValue;
227                                        }
228                                        row.put(converCase(rsmd.getColumnLabel(ci)), value);
229                                }
230                                try {
231                                        rows.add(row,getReadTimer());
232                                } catch (Exception e) {
233                                        log.debug(e);
234                                        continue;
235                                }
236                        }
237                        rows.add(null);
238
239                        if (allowedLog) {
240                                writeSqlLog("rows", begin, "rows", rows.size() - 1);
241                                long spend = System.currentTimeMillis() - begin;
242                                writeSqlLog("spend", begin, "spend", spend);
243                        }
244                } catch (SQLException e) {
245                    if(rows != null) rows.add(null);
246                    
247                        if (allowedLog)
248                                writeSqlLog("error", begin, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
249                        throw e;
250                } finally {
251                        if (result != null)
252                                result.close();
253
254                        if (statement != null)
255                                statement.close();
256
257                }
258        }
259
260        /**
261         * Execute stored proc(and return result to CacheArray) 
262         * @param cursorStart - JDBC result Cursor start index
263         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000)
264         * @param sql - Query sql 
265         * @param rows - CacheArray object
266         * @exception SQLException - if query failed 
267         * */
268        public void callAndReturnList(int cursorStart, int maxRows, String sql, final CacheArray rows)
269                        throws SQLException {
270                callAndReturnList(cursorStart, maxRows, sql, Arrays.asList(new Object[] {}), rows);
271        }
272
273        /**
274         * Execute stored proc(and return result) 
275         * @param cursorStart - JDBC result Cursor start index
276         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000)
277         * @param sql - Query sql 
278         * @param params - Use Map param mode (use ':column_name' to mapping)
279         * @param rows - CacheArray object
280         * @exception SQLException - if query failed 
281         * */
282        public void callAndReturnList(int cursorStart, int maxRows, String sql, Map<String, Object> params,
283                        final CacheArray rows) throws SQLException {
284                String csql = converSql(sql);
285                List<Object> cparams = converParams(sql, params);
286                callAndReturnList(cursorStart, maxRows, csql, cparams, rows);
287        }
288
289        /**
290         * Execute stored proc(and return result) 
291         * @param cursorStart - JDBC result Cursor start index
292         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000)
293         * @param sql - Query sql  (use '?' to mapping)
294         * @param params - Use List param mode
295         * @param rows - CacheArray object
296         * @exception SQLException - if query failed 
297         * */
298        public void callAndReturnList(int cursorStart, int maxRows, String sql, List<Object> params,
299                        final CacheArray rows) throws SQLException {
300
301                if (!checkSqlAvailable(sql))
302                        return;
303
304                long begin = System.currentTimeMillis();
305                boolean allowedLog = writeSqlLog("call", begin,
306                                String.format("%s [cursorStart=%s,maxRows=%s]", sql, cursorStart, maxRows), params);
307
308                CallableStatement statement = null;
309                Map<String, Object> row = null;
310                ResultSet result = null;
311                try {
312                        statement = connection.prepareCall("{" + sql + "}");
313                        if (params != null) {
314                                int size = params.size();
315                                for (int i = 0; i < size; i++) {
316                                        int ci = i + 1;
317                                        Object param = params.get(i);
318                                        if (param == null) {
319                                                statement.setNull(ci, Types.VARCHAR);
320                                        } else {
321                                                statement.setObject(ci, param);
322                                        }
323                                }
324                        }
325                        if (maxRows > 0) {
326                                statement.setMaxRows(maxRows);
327                        }
328                        result = statement.executeQuery();
329                        callAndReturnListSync(connection, cursorStart, maxRows, sql, params);
330                        final ResultSetMetaData rsmd = result.getMetaData();
331                        final int c = rsmd.getColumnCount();
332                        int rowIndex = 0;
333                        while (result.next()) {
334                                if (rowIndex >= cursorStart) {
335                                        row = new ResultMap<String, Object>();
336                                        for (int i = 0; i < c; i++) {
337                                                int ci = i + 1;
338                                                Object value = null;
339                                                Object originValue = result.getObject(ci);
340                                                if (originValue == null) {
341                                                        value = originValue;
342                                                } else if (originValue instanceof Blob) {
343                                                        Blob blobValue = (Blob) originValue;
344                                                        InputStream is = null;
345                                                        try {
346                                                                is = blobValue.getBinaryStream();
347                                                                if(is != null) value = is.readAllBytes();
348                                                        } catch (IOException e) {
349                                                                throw new SQLException(e.getMessage(), e);
350                                                        } finally {
351                                                                if (blobValue != null) {
352                                                                        try {
353                                                                                blobValue.free();
354                                                                        } catch (SQLException e) {
355                                                                                throw e;
356                                                                        }
357                                                                }
358                                                                if (is != null) {
359                                                                        try {
360                                                                                is.close();
361                                                                        } catch (IOException e) {
362                                                                                throw new SQLException(e.getMessage(), e);
363                                                                        }
364                                                                }
365                                                        }
366                                                } else if (originValue instanceof Clob) {
367                                                        Clob clobValue = (Clob) originValue;
368                                                        InputStream is = null;
369                                                        try {
370                                                                is = clobValue.getAsciiStream();
371                                                                if(is != null) value = is.readAllBytes();
372                                                        } catch (IOException e) {
373                                                                throw new SQLException(e.getMessage(), e);
374                                                        } finally {
375                                                                if (clobValue != null) {
376                                                                        try {
377                                                                                clobValue.free();
378                                                                        } catch (SQLException e) {
379                                                                                throw e;
380                                                                        }
381                                                                }
382                                                                if (is != null) {
383                                                                        try {
384                                                                                is.close();
385                                                                        } catch (IOException e) {
386                                                                                throw new SQLException(e.getMessage(), e);
387                                                                        }
388                                                                }
389                                                        }
390                                                } else {
391                                                        value = originValue;
392                                                }
393                                                row.put(converCase(rsmd.getColumnLabel(ci)), value);
394                                        }
395                                        try {
396                                                rows.add(row,getReadTimer());
397                                        } catch (Exception e) {
398                                                log.debug(e);
399                                                continue;
400                                        }
401                                }
402                                rowIndex++;
403                        }
404                        rows.add(null);
405
406                        if (allowedLog) {
407                                writeSqlLog("rows", begin, "rows", rows.size() - 1);
408                                long spend = System.currentTimeMillis() - begin;
409                                writeSqlLog("spend", begin, "spend", spend);
410                        }
411                } catch (SQLException e) {
412                    if(rows != null) rows.add(null);
413                    
414                        if (allowedLog)
415                                writeSqlLog("error", begin, "error", e.getErrorCode());
416                        throw e;
417                } finally {
418                        if (result != null)
419                                result.close();
420
421                        if (statement != null)
422                                statement.close();
423                
424                }
425        }
426        
427        private long getReadTimer(){
428            return READ_TIMER;
429        }
430
431}