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;
047
048/**
049 * This class uses a 'CacheArray' to process the asynchronous processing method of the data query result set.
050 * If you choose to use the mode 'DiskCache', temporary files will be generated. 
051 * If you choose to use the mode 'MemoryCache', temporary files will not be generated but the physical memory size must be considered.
052 * */
053public final class CacheDriverExecutor extends DriverExecutor {
054
055        public static long READ_TIMER = 10L;
056
057        /**
058         * New a object CacheDriverExecutor
059         * @param connection - it is jdbc connection
060         * */
061        public CacheDriverExecutor(Connection connection) {
062                super(connection);
063        }
064
065        /**
066         * This is full table query function
067         * @param sql - Query sql 
068         * @param params - Query params 
069         * @param rows - CacheArray object
070         * @exception SQLException - if query failed
071         * */
072        public void find(String sql, List<Object> params, final CacheArray<Map<String, Object>> rows) throws SQLException {
073                find(0, 0, sql, params, rows);
074        }
075
076        /**
077         * This is full table query function
078         * @param sql - Query sql 
079         * @param params - Query params 
080         * @param rows - CacheArray object
081         * @exception SQLException - if query failed
082         * */
083        public void find(String sql, Map<String, Object> params, final CacheArray<Map<String, Object>> rows)
084                        throws SQLException {
085                String csql = converSql(sql);
086                List<Object> cparams = converParams(sql, params);
087                find(0, 0, csql, cparams, rows);
088        }
089
090        /**
091         * This is full table query function
092         * @param sql - Query sql 
093         * @param rows - CacheArray object
094         * @exception SQLException - if query failed
095         * */
096        public void find(String sql, final CacheArray<Map<String, Object>> rows) throws SQLException {
097                find(0, 0, sql, Arrays.asList(new Object[] {}), rows);
098        }
099
100        /**
101         * This is limited rows query function
102         * @param cursorStart - JDBC result Cursor start index
103         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000)
104         * @param sql - Query sql 
105         * @param rows - CacheArray object
106         * @exception SQLException - if query failed
107         * */
108        public void find(int cursorStart, int maxRows, String sql, final CacheArray<Map<String, Object>> rows)
109                        throws SQLException {
110                find(cursorStart, maxRows, sql, Arrays.asList(new Object[] {}), rows);
111        }
112
113        /**
114         * This is limited rows query function (use Map param mode)
115         * @param cursorStart - JDBC result Cursor start index
116         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000)
117         * @param sql - Query sql 
118         * @param rows - CacheArray object
119         * @exception SQLException - if query failed
120         * */
121        public void find(int cursorStart, int maxRows, String sql, Map<String, Object> params,
122                        final CacheArray<Map<String, Object>> rows) throws SQLException {
123                String csql = converSql(sql);
124                List<Object> cparams = converParams(sql, params);
125                find(cursorStart, maxRows, csql, cparams, rows);
126        }
127
128        /**
129         * This is limited rows query function (use List param mode)
130         * @param cursorStart - JDBC result Cursor start index
131         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000)
132         * @param sql - Query sql 
133         * @param rows - CacheArray object
134         * @exception SQLException - if query failed
135         * */
136        public void find(int cursorStart, int maxRows, String sql, List<Object> params,
137                        final CacheArray<Map<String, Object>> rows) throws SQLException {
138                long begin = System.currentTimeMillis();
139                boolean allowedLog = writeSqlLog("find", begin,
140                                String.format("%s [cursorStart=%s,maxRows=%s]", sql, cursorStart, maxRows), params);
141                PreparedStatement statement = null;
142                Map<String, Object> row = null;
143
144                ResultSet result = null;
145                try {
146                        // ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE
147                        // ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY
148                        statement = connection.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
149                        if (params != null) {
150                                int size = params.size();
151                                for (int i = 0; i < size; i++) {
152                                        int ci = i + 1;
153                                        Object param = params.get(i);
154                                        if (param == null) {
155                                                statement.setNull(ci, Types.VARCHAR);
156                                        } else {
157                                                statement.setObject(ci, param);
158                                        }
159                                }
160                        }
161                        if (maxRows > 0) {
162                                statement.setMaxRows(maxRows);
163                        }
164                        result = statement.executeQuery();
165                        result.absolute(cursorStart);
166                        final ResultSetMetaData rsmd = result.getMetaData();
167                        final int c = rsmd.getColumnCount();
168                        while (result.next()) {
169                                row = new ResultMap<String, Object>();
170                                for (int i = 0; i < c; i++) {
171                                        int ci = i + 1;
172                                        Object value = null;
173                                        Object originValue = result.getObject(ci);
174                                        if (originValue == null) {
175                                                value = originValue;
176                                        } else if (originValue instanceof Blob) {
177                                                Blob blobValue = (Blob) originValue;
178                                                InputStream is = null;
179                                                try {
180                                                        is = blobValue.getBinaryStream();
181                                                        if(is != null) value = is.readAllBytes();
182                                                } catch (IOException e) {
183                                                        throw new SQLException(e.getMessage(), e);
184                                                } finally {
185                                                        if (blobValue != null) {
186                                                                try {
187                                                                        blobValue.free();
188                                                                } catch (SQLException e) {
189                                                                        throw e;
190                                                                }
191                                                        }
192                                                        if (is != null) {
193                                                                try {
194                                                                        is.close();
195                                                                } catch (IOException e) {
196                                                                        throw new SQLException(e.getMessage(), e);
197                                                                }
198                                                        }
199                                                }
200                                        } else if (originValue instanceof Clob) {
201                                                Clob clobValue = (Clob) originValue;
202                                                InputStream is = null;
203                                                try {
204                                                        is = clobValue.getAsciiStream();
205                            if(is != null) value = is.readAllBytes();
206                                                } catch (IOException e) {
207                                                        throw new SQLException(e.getMessage(), e);
208                                                } finally {
209                                                        if (clobValue != null) {
210                                                                try {
211                                                                        clobValue.free();
212                                                                } catch (SQLException e) {
213                                                                        throw e;
214                                                                }
215                                                        }
216                                                        if (is != null) {
217                                                                try {
218                                                                        is.close();
219                                                                } catch (IOException e) {
220                                                                        throw new SQLException(e.getMessage(), e);
221                                                                }
222                                                        }
223                                                }
224                                        } else {
225                                                value = originValue;
226                                        }
227                                        row.put(converCase(rsmd.getColumnLabel(ci)), value);
228                                }
229                                rows.add(row);
230                                try {
231                                        Thread.sleep(getReadTimer());
232                                } catch (InterruptedException 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 (allowedLog)
246                                writeSqlLog("error", begin, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
247                        throw e;
248                } finally {
249                        if (result != null)
250                                result.close();
251
252                        if (statement != null)
253                                statement.close();
254
255                }
256        }
257
258        /**
259         * Execute stored proc(and return result to CacheArray) 
260         * @param cursorStart - JDBC result Cursor start index
261         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000)
262         * @param sql - Query sql 
263         * @param rows - CacheArray object
264         * @exception SQLException - if query failed 
265         * */
266        public void callAndReturnList(int cursorStart, int maxRows, String sql, final CacheArray<Map<String, Object>> rows)
267                        throws SQLException {
268                callAndReturnList(cursorStart, maxRows, sql, Arrays.asList(new Object[] {}), rows);
269        }
270
271        /**
272         * Execute stored proc(and return result) 
273         * @param cursorStart - JDBC result Cursor start index
274         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000)
275         * @param sql - Query sql 
276         * @param params - Use Map param mode (use ':column_name' to mapping)
277         * @param rows - CacheArray object
278         * @exception SQLException - if query failed 
279         * */
280        public void callAndReturnList(int cursorStart, int maxRows, String sql, Map<String, Object> params,
281                        final CacheArray<Map<String, Object>> rows) throws SQLException {
282                String csql = converSql(sql);
283                List<Object> cparams = converParams(sql, params);
284                callAndReturnList(cursorStart, maxRows, csql, cparams, rows);
285        }
286
287        /**
288         * Execute stored proc(and return result) 
289         * @param cursorStart - JDBC result Cursor start index
290         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000)
291         * @param sql - Query sql  (use '?' to mapping)
292         * @param params - Use List param mode
293         * @param rows - CacheArray object
294         * @exception SQLException - if query failed 
295         * */
296        public void callAndReturnList(int cursorStart, int maxRows, String sql, List<Object> params,
297                        final CacheArray<Map<String, Object>> rows) throws SQLException {
298
299                if (!checkSqlAvailable(sql))
300                        return;
301
302                long begin = System.currentTimeMillis();
303                boolean allowedLog = writeSqlLog("call", begin,
304                                String.format("%s [cursorStart=%s,maxRows=%s]", sql, cursorStart, maxRows), params);
305
306                PreparedStatement statement = null;
307                Map<String, Object> row = null;
308                ResultSet result = null;
309                try {
310                        statement = connection.prepareCall("{" + sql + "}");
311                        if (params != null) {
312                                int size = params.size();
313                                for (int i = 0; i < size; i++) {
314                                        int ci = i + 1;
315                                        Object param = params.get(i);
316                                        if (param == null) {
317                                                statement.setNull(ci, Types.VARCHAR);
318                                        } else {
319                                                statement.setObject(ci, param);
320                                        }
321                                }
322                        }
323                        if (maxRows > 0) {
324                                statement.setMaxRows(maxRows);
325                        }
326                        result = statement.executeQuery();
327                        callAndReturnListSync(connection, cursorStart, maxRows, sql, params);
328                        final ResultSetMetaData rsmd = result.getMetaData();
329                        final int c = rsmd.getColumnCount();
330                        int rowIndex = 0;
331                        while (result.next()) {
332                                if (rowIndex >= cursorStart) {
333                                        row = new ResultMap<String, Object>();
334                                        for (int i = 0; i < c; i++) {
335                                                int ci = i + 1;
336                                                Object value = null;
337                                                Object originValue = result.getObject(ci);
338                                                if (originValue == null) {
339                                                        value = originValue;
340                                                } else if (originValue instanceof Blob) {
341                                                        Blob blobValue = (Blob) originValue;
342                                                        InputStream is = null;
343                                                        try {
344                                                                is = blobValue.getBinaryStream();
345                                                                if(is != null) value = is.readAllBytes();
346                                                        } catch (IOException e) {
347                                                                throw new SQLException(e.getMessage(), e);
348                                                        } finally {
349                                                                if (blobValue != null) {
350                                                                        try {
351                                                                                blobValue.free();
352                                                                        } catch (SQLException e) {
353                                                                                throw e;
354                                                                        }
355                                                                }
356                                                                if (is != null) {
357                                                                        try {
358                                                                                is.close();
359                                                                        } catch (IOException e) {
360                                                                                throw new SQLException(e.getMessage(), e);
361                                                                        }
362                                                                }
363                                                        }
364                                                } else if (originValue instanceof Clob) {
365                                                        Clob clobValue = (Clob) originValue;
366                                                        InputStream is = null;
367                                                        try {
368                                                                is = clobValue.getAsciiStream();
369                                                                if(is != null) value = is.readAllBytes();
370                                                        } catch (IOException e) {
371                                                                throw new SQLException(e.getMessage(), e);
372                                                        } finally {
373                                                                if (clobValue != null) {
374                                                                        try {
375                                                                                clobValue.free();
376                                                                        } catch (SQLException e) {
377                                                                                throw e;
378                                                                        }
379                                                                }
380                                                                if (is != null) {
381                                                                        try {
382                                                                                is.close();
383                                                                        } catch (IOException e) {
384                                                                                throw new SQLException(e.getMessage(), e);
385                                                                        }
386                                                                }
387                                                        }
388                                                } else {
389                                                        value = originValue;
390                                                }
391                                                row.put(converCase(rsmd.getColumnLabel(ci)), value);
392                                        }
393                                        rows.add(row);
394                                        try {
395                                                Thread.sleep(getReadTimer());
396                                        } catch (InterruptedException e) {
397                                                log.debug(e);
398                                                continue;
399                                        }
400                                }
401                                rowIndex++;
402                        }
403                        rows.add(null);
404
405                        if (allowedLog) {
406                                writeSqlLog("rows", begin, "rows", rows.size() - 1);
407                                long spend = System.currentTimeMillis() - begin;
408                                writeSqlLog("spend", begin, "spend", spend);
409                        }
410                } catch (SQLException e) {
411                        if (allowedLog)
412                                writeSqlLog("error", begin, "error", e.getErrorCode());
413                        throw e;
414                } finally {
415                        if (result != null)
416                                result.close();
417
418                        if (statement != null)
419                                statement.close();
420                
421                }
422        }
423        
424        private long getReadTimer(){
425            return READ_TIMER;
426        }
427
428}