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 java.sql.DatabaseMetaData;
041import com.killcoding.log.LoggerFactory;
042import com.killcoding.log.Logger;
043import java.util.concurrent.Executors;
044import com.killcoding.datasource.DriverConnection;
045import com.killcoding.datasource.DriverDataSource;
046import java.util.concurrent.ConcurrentHashMap;
047import com.killcoding.tool.CommonTools;
048import com.killcoding.tool.ConfigProperties;
049import java.io.File;
050import java.text.DateFormat;
051import java.text.SimpleDateFormat;
052import com.killcoding.tool.FileTools;
053import java.io.IOException;
054import java.nio.file.Files;
055import java.util.Date;
056import java.nio.file.Paths;
057import java.nio.file.Path;
058import java.nio.file.StandardCopyOption;
059import com.killcoding.cache.CacheArray;
060import com.killcoding.cache.CacheArrayFilter;
061import com.killcoding.tool.CodeEscape;
062import java.sql.Blob;
063import java.util.stream.Collectors;
064import java.net.URI;
065import java.util.Comparator;
066import java.sql.Timestamp;
067import java.sql.Clob;
068import java.io.InputStream;
069
070/**
071 * This class is execute sql base class.
072 * Support database replication.
073 * Support database multi-activity.
074 * Support database CRUD
075 * */
076public class DriverExecutor {
077
078        protected final static Map<Integer, List<DriverExecutor>> SYNC_EXECUTOR_MARK = new ConcurrentHashMap<Integer, List<DriverExecutor>>();
079        private final static Map<Integer, Long> SYNC_CONN_ERROR_TIME = new ConcurrentHashMap<Integer, Long>();
080        
081        private final static Map<String,String> SQL_LOG_MSG_MAPPING = new ConcurrentHashMap<String,String>();
082        private final static Map<String,Boolean> SQL_LOG_OVERSPEND_MAPPING = new ConcurrentHashMap<String,Boolean>();
083
084        protected Logger log = null;
085
086        public final static String COLUMN_NAME_CASE_UPPER = "UPPER";
087        public final static String COLUMN_NAME_CASE_LOWER = "LOWER";
088        public final static String COLUMN_NAME_CASE_ORIGINAL = "ORIGINAL";
089        public static String COLUMN_NAME_CASE_MODE = COLUMN_NAME_CASE_ORIGINAL;
090
091        protected boolean closed = true;
092        protected Connection connection = null;
093        private static CacheArray sqlLogCacheArray = null;
094
095        /**
096         * New a DriverExecutor object
097         * @param connection - JDBC connection
098         * */
099        public DriverExecutor(Connection connection) {
100                super();
101                log = LoggerFactory.getLogger(this.getClass());
102                this.connection = connection;
103                writeSqlLog("open", 0, "open", "");
104                closed = (this.connection == null);
105        }
106
107        /**
108         * Get current Connection
109         * @return Connection
110         * */
111        public Connection getConnection() {
112                return connection;
113        }
114
115        /**
116         * Get column classes by table name
117         * @exception SQLException
118         * @return Map<String,Object> - column and java type class mapping
119         * @param tableName - table name
120         * */
121        public Map<String, Object> getColumnClasses(String tableName) throws SQLException {
122                Map<String, Object> types = null;
123                ResultSet result = null;
124                PreparedStatement statement = null;
125                String sql = String.format("SELECT * FROM %s WHERE 1 = 0", tableName);
126                statement = connection.prepareStatement(sql);
127                result = statement.executeQuery();
128                final ResultSetMetaData rsmd = result.getMetaData();
129                for (int i = 0; i < rsmd.getColumnCount(); i++) {
130                        if (types == null) {
131                                types = new ResultMap<String, Object>();
132                        }
133                        String cn = rsmd.getColumnClassName(i + 1);
134                        types.put(converCase(rsmd.getColumnLabel(i + 1)), cn);
135                }
136                return types;
137        }
138
139        /**
140         * Get column db data types by table name
141         * @exception SQLException
142         * @return Map<String, Object> - column and db data type mapping
143         * @param tableName - Table name
144         * */
145        public Map<String, Object> getColumnTypes(String tableName) throws SQLException {
146                ResultSet result = null;
147                PreparedStatement statement = null;
148                String sql = String.format("SELECT * FROM %s WHERE 1 = 0", tableName);
149                Map<String, Object> types = null;
150                statement = connection.prepareStatement(sql);
151                result = statement.executeQuery();
152                final ResultSetMetaData rsmd = result.getMetaData();
153                for (int i = 0; i < rsmd.getColumnCount(); i++) {
154                        if (types == null) {
155                                types = new ResultMap<String, Object>();
156                        }
157                        types.put(converCase(rsmd.getColumnLabel(i + 1)), rsmd.getColumnTypeName(i + 1));
158                }
159                return types;
160        }
161
162        /**
163         * Show column data type and java type mapping
164         * @exception SQLException
165         * @return List<Map<String, Object>> - Mapping list
166         * @param tableName - Table name
167         * */
168        public List<Map<String, Object>> desc(String tableName) throws SQLException {
169                List<String> primaryKeys = getPrimaryKeys(tableName);
170                List<Map<String, Object>> results = new ArrayList<Map<String, Object>>();
171                ResultSet result = null;
172                PreparedStatement statement = null;
173                String sql = String.format("SELECT * FROM %s WHERE 1 = 0", tableName);
174                statement = connection.prepareStatement(sql);
175                result = statement.executeQuery();
176                final ResultSetMetaData rsmd = result.getMetaData();
177                for (int i = 0; i < rsmd.getColumnCount(); i++) {
178                        Map<String, Object> types = new ResultMap<String, Object>();
179                        int ci = i + 1;
180                        boolean nullable = rsmd.isNullable(ci) == 1;
181                        String name = converCase(rsmd.getColumnLabel(ci));
182                        String isPk = "UNKNOWN";
183                        if (primaryKeys != null) {
184                                isPk = primaryKeys.contains(name) ? "Y" : "N";
185                        }
186                        types.put("NAME", name);
187                        types.put("PRIMARY_KEY", isPk);
188                        types.put("DATA_TYPE", rsmd.getColumnTypeName(ci));
189                        types.put("JAVA_TYPE", rsmd.getColumnClassName(ci));
190                        types.put("PRECISION", rsmd.getPrecision(ci));
191                        types.put("ALLOW_NULLABLE", nullable ? "Y" : 'N');
192                        results.add(types);
193                }
194                return results;
195        }
196
197        /**
198         * Get primary Keys by table name
199         * @return List<String> - Primary Keys
200         * @param _tableName - Table name
201         * */
202        public List<String> getPrimaryKeys(String _tableName) {
203                try {
204                        List<String> pks = new ArrayList<String>();
205                        DatabaseMetaData meta = connection.getMetaData();
206                        ResultSet tables = meta.getTables(null, null, "%", new String[] { "TABLE" });
207                        while (tables.next()) {
208                                String catalog = tables.getString("TABLE_CAT");
209                                String schema = tables.getString("TABLE_SCHEM");
210                                String tableName = tables.getString("TABLE_NAME");
211                                if (tableName.equalsIgnoreCase(_tableName)) {
212                                        ResultSet primaryKeys = meta.getPrimaryKeys(catalog, schema, tableName);
213                                        while (primaryKeys.next()) {
214                                                pks.add(primaryKeys.getString("COLUMN_NAME"));
215                                        }
216                                        break;
217                                }
218                        }
219                        return pks;
220                } catch (Exception e) {
221                        log.warn(e);
222                        return null;
223                }
224        }
225
226        /**
227         * Get all tables
228         * @return List<Map<String, Object>>
229         * @exception SQLException
230         * */
231        public List<Map<String, Object>> getAllTables() throws SQLException {
232                List<Map<String, Object>> tablesList = new ArrayList<Map<String, Object>>();
233                DatabaseMetaData meta = connection.getMetaData();
234                ResultSet tables = meta.getTables(null, null, "%", new String[] { "TABLE" });
235                while (tables.next()) {
236                        String catalog = tables.getString("TABLE_CAT");
237                        String schema = tables.getString("TABLE_SCHEM");
238                        String tableName = tables.getString("TABLE_NAME");
239                        Map<String, Object> t = new ResultMap<String, Object>();
240                        t.put("TABLE_SCHEMA", schema);
241                        t.put("TABLE_NAME", tableName);
242                        tablesList.add(t);
243                }
244                return tablesList;
245        }
246
247        /**
248         * Get all tables by schema
249         * @return List<Map<String, Object>>
250         * @exception SQLException
251         * */
252        public List<Map<String, Object>> getAllTables(String _schema) throws SQLException {
253                List<Map<String, Object>> tablesList = new ArrayList<Map<String, Object>>();
254                DatabaseMetaData meta = connection.getMetaData();
255                ResultSet tables = meta.getTables(null, null, "%", new String[] { "TABLE" });
256                while (tables.next()) {
257                        String schema = tables.getString("TABLE_SCHEM");
258                        if (schema != null && schema.equalsIgnoreCase(_schema)) {
259                                String catalog = tables.getString("TABLE_CAT");
260                                String tableName = tables.getString("TABLE_NAME");
261                                Map<String, Object> t = new ResultMap<String, Object>();
262                                t.put("TABLE_SCHEMA", schema);
263                                t.put("TABLE_NAME", tableName);
264                                tablesList.add(t);
265                        }
266                }
267                return tablesList;
268        }
269
270        /**
271         * Query first record
272         * @exception SQLException
273         * @return Map<String,Object> - First result
274         * @param sql - Condition use format ':column_name'
275         * @param params
276         * */
277        public Map<String, Object> first(String sql, Map<String, Object> params) throws SQLException {
278                List<Map<String, Object>> list = find(0, 1, sql, params);
279                if (list.size() > 0) {
280                        return list.get(0);
281                }
282                return null;
283        }
284
285        /**
286         * Query first record
287         * @exception SQLException
288         * @return Map<String,Object> - First result
289         * @param sql
290         * */
291        public Map<String, Object> first(String sql) throws SQLException {
292                return first(sql, Arrays.asList(new Object[] {}));
293        }
294
295        /**
296         * Query first record
297         * @exception SQLException
298         * @return Map<String,Object> - First result
299         * @param sql - Condition use format '?'
300         * @param params
301         * */
302        public Map<String, Object> first(String sql, List<Object> params) throws SQLException {
303                List<Map<String, Object>> list = find(0, 1, sql, params);
304                if (list.size() > 0) {
305                        return list.get(0);
306                }
307                return null;
308        }
309
310        /**
311         * Query all matched records
312         * @exception SQLException
313         * @return List<Map<String, Object>>
314         * @param sql 
315         * @param params
316         * */
317        public List<Map<String, Object>> find(String sql) throws SQLException {
318                return find(0, 0, sql, Arrays.asList(new Object[] {}));
319        }
320
321        /**
322         * Query all matched records
323         * @exception SQLException
324         * @return List<Map<String, Object>>
325         * @param cursorStart - JDBC result Cursor start index
326         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000)
327         * @param sql 
328         * @param params
329         * */
330        public List<Map<String, Object>> find(int cursorStart, int maxRows, String sql) throws SQLException {
331                return find(cursorStart, maxRows, sql, Arrays.asList(new Object[] {}));
332        }
333
334        /**
335         * Query all matched records
336         * @exception SQLException
337         * @return List<Map<String, Object>>
338         * @param sql - Condition use format ':column_name'
339         * @param params
340         * */
341        public List<Map<String, Object>> find(String sql, Map<String, Object> params) throws SQLException {
342                return find(0, 0, sql, params);
343        }
344
345        /**
346         * Query all matched records
347         * @exception SQLException
348         * @return List<Map<String, Object>>
349         * @param cursorStart - JDBC result Cursor start index
350         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000)
351         * @param sql - Condition use format ':column_name'
352         * @param params
353         * */
354        public List<Map<String, Object>> find(int cursorStart, int maxRows, String sql, Map<String, Object> params)
355                        throws SQLException {
356                String csql = converSql(sql);
357                List<Object> cparams = converParams(sql, params);
358                return find(cursorStart, maxRows, csql, cparams);
359        }
360
361        /**
362         * Query all matched records
363         * @exception SQLException
364         * @return List<Map<String, Object>>
365         * @param cursorStart - JDBC result Cursor start index
366         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000)
367         * @param sql - Condition use format '?'
368         * @param params
369         * */
370        public List<Map<String, Object>> find(int cursorStart, int maxRows, String sql, List<Object> params)
371                        throws SQLException {
372                long begin = System.currentTimeMillis();
373                boolean allowedLog = writeSqlLog("find", begin,
374                                String.format("%s [cursorStart=%s,maxRows=%s]", sql, cursorStart, maxRows), params);
375
376                PreparedStatement statement = null;
377                Map<String, Object> row = null;
378                ResultSet result = null;
379                final List<Map<String, Object>> rows = new ArrayList<Map<String, Object>>();
380                try {
381                        // ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE
382                        // ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY
383                        statement = connection.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
384                        if (params != null) {
385                                int size = params.size();
386                                for (int i = 0; i < size; i++) {
387                                        int ci = i + 1;
388                                        Object param = params.get(i);
389                                        if (param == null) {
390                                                statement.setNull(ci, Types.VARCHAR);
391                                        } else {
392                                                statement.setObject(ci, param);
393                                        }
394                                }
395                        }
396                        if (maxRows > 0) {
397                                statement.setMaxRows(maxRows);
398                        }
399                        result = statement.executeQuery();
400                        result.absolute(cursorStart);
401                        final ResultSetMetaData rsmd = result.getMetaData();
402                        final int c = rsmd.getColumnCount();
403                        while (result.next()) {
404                                row = new ResultMap<String, Object>();
405                                for (int i = 0; i < c; i++) {
406                                        int ci = i + 1;
407                                        Object value = null;
408                                        Object originValue = result.getObject(ci);
409                                        if (originValue == null) {
410                                                value = originValue;
411                                        } else if (originValue instanceof Blob) {
412                                                Blob blobValue = (Blob) originValue;
413                                                InputStream is = null;
414                                                try {
415                                                        is = blobValue.getBinaryStream();
416                                                        if(is != null) value = is.readAllBytes();
417                                                } catch (IOException e) {
418                                                        throw new SQLException(e.getMessage(), e);
419                                                } finally {
420                                                        if (blobValue != null) {
421                                                                try {
422                                                                        blobValue.free();
423                                                                } catch (SQLException e) {
424                                                                        throw e;
425                                                                }
426                                                        }
427                                                        if (is != null) {
428                                                                try {
429                                                                        is.close();
430                                                                } catch (IOException e) {
431                                                                        throw new SQLException(e.getMessage(), e);
432                                                                }
433                                                        }
434                                                }
435                                        } else if (originValue instanceof Clob) {
436                                                Clob clobValue = (Clob) originValue;
437                                                InputStream is = null;
438                                                try {
439                                                        is = clobValue.getAsciiStream();
440                                                        if(is != null) value = is.readAllBytes();
441                                                } catch (IOException e) {
442                                                        throw new SQLException(e.getMessage(), e);
443                                                } finally {
444                                                        if (clobValue != null) {
445                                                                try {
446                                                                        clobValue.free();
447                                                                } catch (SQLException e) {
448                                                                        throw e;
449                                                                }
450                                                        }
451                                                        if (is != null) {
452                                                                try {
453                                                                        is.close();
454                                                                } catch (IOException e) {
455                                                                        throw new SQLException(e.getMessage(), e);
456                                                                }
457                                                        }
458                                                }
459                                        } else {
460                                                value = originValue;
461                                        }
462                                        row.put(converCase(rsmd.getColumnLabel(ci)), value);
463                                }
464                                rows.add(row);
465                        }
466                        if (allowedLog) {
467                                writeSqlLog("rows", begin, "rows", rows.size());
468                                long spend = System.currentTimeMillis() - begin;
469                                writeSqlLog("spend", begin, "spend", spend);
470                        }
471                        return rows;
472                } catch (SQLException e) {
473                        if (allowedLog)
474                                writeSqlLog("error", begin, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
475
476                        throw e;
477                } finally {
478                        if (result != null)
479                                result.close();
480
481                        if (statement != null)
482                                statement.close();
483                }
484        }
485
486        /**
487         * Execute stored proc (and return boolean) 
488         * @param sql - Query sql 
489         * @exception SQLException 
490         * @return boolean
491         * */
492        public boolean callAndReturnBoolean(String sql) throws SQLException {
493                return callAndReturnBoolean(sql, Arrays.asList(new Object[] {}));
494        }
495
496        /**
497         * Execute stored proc (and return boolean) 
498         * @param sql - Query sql, condition use format ':column_name'
499         * @param params
500         * @exception SQLException 
501         * @return boolean
502         * */
503        public boolean callAndReturnBoolean(String sql, Map<String, Object> params) throws SQLException {
504                String csql = converSql(sql);
505                List<Object> cparams = converParams(sql, params);
506                return callAndReturnBoolean(sql, params);
507        }
508
509        /**
510         * Execute stored proc (and return boolean) 
511         * @param sql - Query sql,condition use format '?'
512         * @param params
513         * @exception SQLException 
514         * @return boolean
515         * */
516        public boolean callAndReturnBoolean(String sql, List<Object> params) throws SQLException {
517
518                if (!checkSqlAvailable(sql))
519                        return false;
520
521                long begin = System.currentTimeMillis();
522                boolean allowedLog = writeSqlLog("call", begin, sql, params);
523
524                PreparedStatement statement = null;
525                try {
526                        statement = connection.prepareCall("{" + sql + "}");
527                        if (params != null) {
528                                int size = params.size();
529                                for (int i = 0; i < size; i++) {
530                                        Object param = params.get(i);
531                                        if (param == null) {
532                                                statement.setNull(i + 1, Types.VARCHAR);
533                                        } else {
534                                                statement.setObject(i + 1, param);
535                                        }
536                                }
537                        }
538
539                        boolean returnResult = statement.execute();
540
541                        if (allowedLog) {
542                                writeSqlLog("return", begin, "return", returnResult);
543                                long spend = System.currentTimeMillis() - begin;
544                                writeSqlLog("spend", begin, "spend", spend);
545                        }
546
547                        callAndReturnBooleanSync(connection, begin, sql, params, returnResult);
548
549                        return returnResult;
550                } catch (SQLException e) {
551                        if (allowedLog)
552                                writeSqlLog("error", begin, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
553
554                        throw e;
555                } finally {
556                        if (statement != null)
557                                statement.close();
558                }
559        }
560
561        /**
562         * Execute stored proc (and return rows) 
563         * @param sql - Query sql
564         * @exception SQLException 
565         * @return int
566         * */
567        public int callAndReturnRows(String sql) throws SQLException {
568                return callAndReturnRows(sql, Arrays.asList(new Object[] {}));
569        }
570
571        /**
572         * Execute stored proc (and return rows) 
573         * @param sql - Query sql,condition use format ':column_name'
574         * @param params
575         * @exception SQLException 
576         * @return int
577         * */
578        public int callAndReturnRows(String sql, Map<String, Object> params) throws SQLException {
579                String csql = converSql(sql);
580                List<Object> cparams = converParams(sql, params);
581                return callAndReturnRows(sql, params);
582        }
583
584        /**
585         * Execute stored proc (and return rows) 
586         * @param sql - Query sql,condition use format '?'
587         * @param params
588         * @exception SQLException 
589         * @return int
590         * */
591        public int callAndReturnRows(String sql, List<Object> params) throws SQLException {
592
593                if (!checkSqlAvailable(sql))
594                        return -1;
595
596                long begin = System.currentTimeMillis();
597                boolean allowedLog = writeSqlLog("call", begin, sql, params);
598
599                PreparedStatement statement = null;
600                try {
601                        statement = connection.prepareCall("{" + sql + "}");
602                        if (params != null) {
603                                int size = params.size();
604                                for (int i = 0; i < size; i++) {
605                                        Object param = params.get(i);
606                                        if (param == null) {
607                                                statement.setNull(i + 1, Types.VARCHAR);
608                                        } else {
609                                                statement.setObject(i + 1, param);
610                                        }
611                                }
612                        }
613                        int row = statement.executeUpdate();
614
615                        if (allowedLog) {
616                                writeSqlLog("return", begin, "return", row);
617                                long spend = System.currentTimeMillis() - begin;
618                                writeSqlLog("spend", begin, "spend", spend);
619                        }
620
621                        callAndReturnRowsSync(connection, begin, sql, params, row);
622
623                        return row;
624                } catch (SQLException e) {
625                        if (allowedLog)
626                                writeSqlLog("error", begin, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
627
628                        throw e;
629                } finally {
630                        if (statement != null)
631                                statement.close();
632                }
633        }
634
635        /**
636         * Execute stored proc (and return List) 
637         * @param sql - Query sql
638         * @exception SQLException 
639         * @return List<Map<String, Object>>
640         * */
641        public List<Map<String, Object>> callAndReturnList(String sql) throws SQLException {
642                return callAndReturnList(0, 0, sql, Arrays.asList(new Object[] {}));
643        }
644
645        /**
646         * Execute stored proc (and return List) 
647         * @param cursorStart - JDBC result Cursor start index
648         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000) 
649         * @param sql - Query sql
650         * @exception SQLException 
651         * @return List<Map<String, Object>>
652         * */
653        public List<Map<String, Object>> callAndReturnList(int cursorStart, int maxRows, String sql) throws SQLException {
654                return callAndReturnList(cursorStart, maxRows, sql, Arrays.asList(new Object[] {}));
655        }
656
657        /**
658         * Execute stored proc (and return List) 
659         * @param cursorStart - JDBC result Cursor start index
660         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000) 
661         * @param sql - Query sql, condition use format ':column_name'
662         * @exception SQLException 
663         * @return List<Map<String, Object>>
664         * */
665        public List<Map<String, Object>> callAndReturnList(int cursorStart, int maxRows, String sql,
666                        Map<String, Object> params) throws SQLException {
667                String csql = converSql(sql);
668                List<Object> cparams = converParams(sql, params);
669                return callAndReturnList(cursorStart, maxRows, sql, params);
670        }
671
672        /**
673         * Execute stored proc (and return List) 
674         * @param cursorStart - JDBC result Cursor start index
675         * @param maxRows - JDBC result max rows (JDBC limited rows 50,000,000) 
676         * @param sql - Query sql, condition use format '?'
677         * @exception SQLException 
678         * @return List<Map<String, Object>>
679         * */
680        public List<Map<String, Object>> callAndReturnList(int cursorStart, int maxRows, String sql, List<Object> params)
681                        throws SQLException {
682
683                if (!checkSqlAvailable(sql))
684                        return null;
685
686                long begin = System.currentTimeMillis();
687                boolean allowedLog = writeSqlLog("call", begin,
688                                String.format("%s [cursorStart=%s,maxRows=%s]", sql, cursorStart, maxRows), params);
689
690                PreparedStatement statement = null;
691                Map<String, Object> row = null;
692                ResultSet result = null;
693                final List<Map<String, Object>> rows = new ArrayList<Map<String, Object>>();
694                try {
695                        statement = connection.prepareCall("{" + sql + "}");
696                        if (params != null) {
697                                int size = params.size();
698                                for (int i = 0; i < size; i++) {
699                                        int ci = i + 1;
700                                        Object param = params.get(i);
701                                        if (param == null) {
702                                                statement.setNull(ci, Types.VARCHAR);
703                                        } else {
704                                                statement.setObject(ci, param);
705                                        }
706                                }
707                        }
708                        if (maxRows > 0) {
709                                statement.setMaxRows(maxRows);
710                        }
711                        result = statement.executeQuery();
712                        final ResultSetMetaData rsmd = result.getMetaData();
713                        final int c = rsmd.getColumnCount();
714                        int rowIndex = 0;
715                        while (result.next()) {
716                                if (rowIndex >= cursorStart) {
717                                        row = new ResultMap<String, Object>();
718                                        for (int i = 0; i < c; i++) {
719                                                int ci = i + 1;
720                                                Object value = null;
721                                                Object originValue = result.getObject(ci);
722                                                if (originValue == null) {
723                                                        value = originValue;
724                                                } else if (originValue instanceof Blob) {
725                                                        Blob blobValue = (Blob) originValue;
726                                                        InputStream is = null;
727                                                        try {
728                                                                is = blobValue.getBinaryStream();
729                                                                if(is != null) value = is.readAllBytes();
730                                                        } catch (IOException e) {
731                                                                throw new SQLException(e.getMessage(), e);
732                                                        } finally {
733                                                                if (blobValue != null) {
734                                                                        try {
735                                                                                blobValue.free();
736                                                                        } catch (SQLException e) {
737                                                                                throw e;
738                                                                        }
739                                                                }
740                                                                if (is != null) {
741                                                                        try {
742                                                                                is.close();
743                                                                        } catch (IOException e) {
744                                                                                throw new SQLException(e.getMessage(), e);
745                                                                        }
746                                                                }
747                                                        }
748                                                } else if (originValue instanceof Clob) {
749                                                        Clob clobValue = (Clob) originValue;
750                                                        InputStream is = null;
751                                                        try {
752                                                                is = clobValue.getAsciiStream();
753                                                                if(is != null) value = is.readAllBytes();
754                                                        } catch (IOException e) {
755                                                                throw new SQLException(e.getMessage(), e);
756                                                        } finally {
757                                                                if (clobValue != null) {
758                                                                        try {
759                                                                                clobValue.free();
760                                                                        } catch (SQLException e) {
761                                                                                throw e;
762                                                                        }
763                                                                }
764                                                                if (is != null) {
765                                                                        try {
766                                                                                is.close();
767                                                                        } catch (IOException e) {
768                                                                                throw new SQLException(e.getMessage(), e);
769                                                                        }
770                                                                }
771                                                        }
772                                                } else {
773                                                        value = originValue;
774                                                }
775                                                row.put(converCase(rsmd.getColumnLabel(ci)), value);
776                                        }
777                                        rows.add(row);
778                                }
779                                rowIndex++;
780                        }
781
782                        if (allowedLog) {
783                                writeSqlLog("rows", begin, "rows", rows);
784                                long spend = System.currentTimeMillis() - begin;
785                                writeSqlLog("spend", begin, "spend", spend);
786                        }
787
788                        callAndReturnListSync(connection, cursorStart, maxRows, sql, params);
789
790                        return rows;
791                } catch (SQLException e) {
792                        if (allowedLog)
793                                writeSqlLog("error", begin, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
794                        throw e;
795                } finally {
796                        if (result != null)
797                                result.close();
798
799                        if (statement != null)
800                                statement.close();
801                }
802        }
803
804        /**
805         * Execute sql
806         * @exception SQLException
807         * @param sql
808         * @return int
809         * */
810        public int execute(String sql) throws SQLException {
811                return execute(sql, Arrays.asList(new Object[] {}));
812        }
813
814        /**
815         * Execute sql
816         * @exception SQLException
817         * @param sql - Condition use format ':column_name'
818         * @param params 
819         * @return int
820         * */
821        public int execute(String sql, Map<String, Object> params) throws SQLException {
822                String csql = converSql(sql);
823                List<Object> cparams = converParams(sql, params);
824                return execute(csql, cparams);
825        }
826
827        /**
828         * Execute sql
829         * @exception SQLException
830         * @param sql - Condition use format '?'
831         * @param params 
832         * @return int
833         * */
834        public int execute(String sql, List<Object> params) throws SQLException {
835                if (!checkSqlAvailable(sql))
836                        return -1;
837
838                long begin = System.currentTimeMillis();
839                boolean allowedLog = writeSqlLog("execute", begin, sql, params);
840
841                PreparedStatement statement = null;
842                try {
843                        statement = connection.prepareStatement(sql);
844                        if (params != null) {
845                                int size = params.size();
846                                for (int i = 0; i < size; i++) {
847                                        Object param = params.get(i);
848                                        if (param == null) {
849                                                statement.setNull(i + 1, Types.VARCHAR);
850                                        } else {
851                                                statement.setObject(i + 1, param);
852                                        }
853                                }
854                        }
855                        int row = statement.executeUpdate();
856
857                        if (allowedLog) {
858                                writeSqlLog("return", begin, "return", row);
859                                long spend = System.currentTimeMillis() - begin;
860                                writeSqlLog("spend", begin, "spend", spend);
861                        }
862
863                        executeSync(connection, begin, sql, params, row);
864
865                        return row;
866                } catch (SQLException e) {
867                        if (allowedLog)
868                                writeSqlLog("error", begin, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
869                        throw e;
870                } finally {
871                        if (statement != null)
872                                statement.close();
873                }
874        }
875
876        /**
877         * Execute batch
878         * @exception SQLException
879         * @param sql - Condition use format ':column_name'
880         * @param records
881         * @return int - Return rows
882         * */
883        public int executeBatch(String sql, List<Map<String, Object>> records) throws SQLException {
884                String csql = converSql(sql);
885                List<List<Object>> crecords = new ArrayList<List<Object>>();
886                for (Map<String, Object> record : records) {
887                        List<Object> crecord = converParams(sql, record);
888                        crecords.add(crecord);
889                }
890                return executeBatchList(csql, crecords);
891        }
892
893        /**
894         * Execute batch
895         * @exception SQLException
896         * @param sql - Condition use format '?'
897         * @param records
898         * @return int - Return rows
899         * */
900        public int executeBatchList(String sql, List<List<Object>> records) throws SQLException {
901                boolean allowedLog = false;
902                if (!checkSqlAvailable(sql))
903                        return -1;
904
905                long begin = System.currentTimeMillis();
906                if (records != null) {
907                        allowedLog = writeSqlLog("batch", begin, sql, String.format("[batchSize=%s]", records.size()));
908                }
909
910                boolean first = true;
911                PreparedStatement statement = null;
912                try {
913                        for (List<Object> params : records) {
914                                if (first) {
915                                        statement = connection.prepareStatement(sql);
916                                        first = false;
917                                }
918
919                                int size = params.size();
920                                for (int i = 0; i < size; i++) {
921                                        Object param = params.get(i);
922                                        if (param == null) {
923                                                statement.setNull(i + 1, Types.VARCHAR);
924                                        } else {
925                                                statement.setObject(i + 1, param);
926                                        }
927                                }
928
929                                statement.addBatch();
930                        }
931
932                        int sumRow = 0;
933                        if (records.size() > 0) {
934                                int[] rows = statement.executeBatch();
935
936                                for (int r : rows) {
937                                        sumRow += r;
938                                }
939
940                                if (allowedLog) {
941                                        long spend = System.currentTimeMillis() - begin;
942                                        writeSqlLog("return", begin, "return", sumRow);
943                                        writeSqlLog("spend", begin, "spend", spend);
944                                }
945
946                                executeBatchListSync(connection, begin, sql, records, sumRow);
947                        }
948
949                        return sumRow;
950                } catch (SQLException e) {
951                        if (allowedLog)
952                                writeSqlLog("error", begin, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
953
954                        throw e;
955                } finally {
956                        if (statement != null)
957                                statement.close();
958                }
959        }
960
961        /**
962         * Check connection is closed
963         * @return boolean
964         * */
965        public boolean isClosed() {
966                try {
967                    if(closed) return closed;
968                    
969                        return (connection == null || connection.isClosed());
970                } catch (Exception e) {
971                        log.warn(e);
972                        return true;
973                }
974        }
975
976        /**
977         * Abort connection
978         * @exception SQLException
979         * */
980        public void abort() throws SQLException {
981                writeSqlLog("aborting", 0, "aborting", "");
982                if (connection != null) {
983                        try {
984                                if (connection instanceof DriverConnection) {
985                                        abortSyncConnection(connection);
986                                        connection.abort(null);
987                                } else {
988                                        connection.abort(Executors.newFixedThreadPool(1));
989                                }
990                                closed = true;
991                        } catch (SQLException e) {
992                                writeSqlLog("error", 0, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
993                                throw e;
994                        }
995                }
996                writeSqlLog("aborted", 0, "aborted", "");
997        }
998
999        /**
1000         * Close connection
1001         * @exception SQLException
1002         * */
1003        public void close() throws SQLException {
1004                writeSqlLog("closing", 0, "closing", "");
1005                if (connection != null) {
1006                        try {
1007                                closeSyncConnection(connection);
1008                                connection.close();
1009                                closed = true;
1010                        } catch (SQLException e) {
1011                                writeSqlLog("error", 0, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
1012                                throw e;
1013                        }
1014                }
1015                writeSqlLog("closed", 0, "closed", "");
1016        }
1017
1018        /**
1019         * Commit connection
1020         * @exception SQLException
1021         * */
1022        public void commit() throws SQLException {
1023                writeSqlLog("committing", 0, "committing", "");
1024                if (connection != null) {
1025                        try {
1026                                commitSyncConnection(connection);
1027                                connection.commit();
1028                        } catch (SQLException e) {
1029                                writeSqlLog("error", 0, "", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
1030                                throw e;
1031                        }
1032                }
1033                writeSqlLog("committed", 0, "committed", "");
1034        }
1035
1036        /**
1037         * Rollback connection
1038         * @exception SQLException
1039         * */
1040        public void rollback() throws SQLException {
1041                writeSqlLog("rollbacking", 0, "rollbacking", "");
1042                if (connection != null) {
1043                        try {
1044                                rollbackSyncConnection(connection);
1045                                connection.rollback();
1046                        } catch (SQLException e) {
1047                                writeSqlLog("error", 0, "error", String.format("%s,%s", e.getErrorCode(), e.getMessage()));
1048                                throw e;
1049                        }
1050                }
1051                writeSqlLog("rollbacked", 0, "rollbacked", "");
1052        }
1053
1054        /**
1055         * Get origin connection hash code
1056         * @return Integer - hash code
1057         * */
1058        private Integer getOriginConnectionHashCode() {
1059                if (connection instanceof DriverConnection) {
1060                        return ((DriverConnection) connection).getOriginConnectionHashCode();
1061                } else {
1062                        return connection.hashCode();
1063                }
1064        }
1065
1066        /**
1067         * Get data source thread name
1068         * @return String - DataSource thread name
1069         * */
1070        private String getDataSourceName() {
1071                if (connection instanceof DriverConnection) {
1072                        return ((DriverConnection) connection).getDriverDataSource().getName();
1073                } else {
1074                        return null;
1075                }
1076        }
1077
1078        /**
1079         * Conver sql from ':column_name' to '?'
1080         * @return String
1081         * */
1082        protected String converSql(String sql) {
1083                return sql.replaceAll(":[a-zA-Z0-9_]+", "?");
1084        }
1085
1086        /**
1087         * Conver param from map to list
1088         * @param sql
1089         * @param map
1090         * @return List<Object>
1091         * */
1092        protected List<Object> converParams(String sql, Map<String, Object> map) {
1093                List<String> paramKeys = new ArrayList<String>();
1094                Pattern pattern = Pattern.compile(":[a-zA-Z0-9_]+");
1095                Matcher matcher = pattern.matcher(sql);
1096                while (matcher.find()) {
1097                        String key = matcher.group().replaceFirst(":", "");
1098                        paramKeys.add(key);
1099                }
1100
1101                List<Object> params = new ArrayList<Object>();
1102                for (String pk : paramKeys) {
1103                        Object pv = map.get(pk);
1104                        if (pv == null) {
1105                                pv = map.get(pk.toLowerCase());
1106                        }
1107                        if (pv == null) {
1108                                pv = map.get(pk.toUpperCase());
1109                        }
1110                        if (pv instanceof java.util.Date) {
1111                                java.util.Date utilDate = (java.util.Date) pv;
1112                                java.sql.Timestamp sqlDate = new java.sql.Timestamp(utilDate.getTime());
1113                                params.add(sqlDate);
1114                        } else {
1115                                params.add(pv);
1116                        }
1117                }
1118
1119                return params;
1120        }
1121
1122        /**
1123         * Covner to upper or lower
1124         * @param s - Column name or table name
1125         * @return String
1126         * */
1127        protected String converCase(String s) {
1128                if (COLUMN_NAME_CASE_MODE.equals(COLUMN_NAME_CASE_UPPER)) {
1129                        return s.toUpperCase();
1130                } else if (COLUMN_NAME_CASE_MODE.equals(COLUMN_NAME_CASE_LOWER)) {
1131                        return s.toLowerCase();
1132                } else {
1133                        return s;
1134                }
1135        }
1136
1137        /**
1138         * Check available sql to write to log
1139         * @return boolean
1140         * @param connection - Connection
1141         * @param sql
1142         * */
1143        protected static boolean checkSqlLogAvailable(Connection connection, String sql) {
1144                if (connection instanceof DriverConnection) {
1145                        DriverConnection dc = (DriverConnection) connection;
1146                        DriverDataSource dds = dc.getDriverDataSource();
1147                        ConfigProperties configProperties = dds.getConfigProperties();
1148                        List<String> sqlAllowed = configProperties.getArray("SqlLogAllowed");
1149                        List<String> sqlIgnored = configProperties.getArray("SqlLogIgnored");
1150                        return checkSqlAvailable(sql, sqlAllowed, sqlIgnored);
1151                }
1152                return false;
1153        }
1154
1155        /**
1156         * Check available sql to execute
1157         * @return boolean
1158         * @param sql
1159         * */
1160        protected boolean checkSqlAvailable(String sql) {
1161                return checkSqlAvailable(connection, sql);
1162        }
1163
1164        /**
1165         * Check available sql to execute
1166         * @return boolean
1167         * @param connection
1168         * @param sql
1169         * */
1170        protected static boolean checkSqlAvailable(Connection connection, String sql) {
1171                if (connection instanceof DriverConnection) {
1172                        DriverConnection dc = (DriverConnection) connection;
1173                        DriverDataSource dds = dc.getDriverDataSource();
1174                        ConfigProperties configProperties = dds.getConfigProperties();
1175                        List<String> sqlAllowed = configProperties.getArray("SqlExecuteAllowed");
1176                        List<String> sqlIgnored = configProperties.getArray("SqlExecuteIgnored");
1177                        return checkSqlAvailable(sql, sqlAllowed, sqlIgnored);
1178                }
1179                return true;
1180        }
1181
1182        /**
1183         * Check available sql to execute
1184         * @return boolean
1185         * @param sql
1186         * @param sqlAllowed - From DataSources.properties
1187         * @param sqlIgnored - From DataSources.properties
1188         * */
1189        private static boolean checkSqlAvailable(String sql, List<String> sqlAllowed, List<String> sqlIgnored) {
1190                boolean matchedAllowed = true;
1191                boolean matchedIgnored = false;
1192                if (sqlAllowed != null) {
1193                        for (String regex : sqlAllowed) {
1194                                if (!CommonTools.isBlank(regex)) {
1195                                        matchedAllowed = sql.matches(regex);
1196
1197                                        if (matchedAllowed)
1198                                                break;
1199                                }
1200                        }
1201                }
1202                if (matchedAllowed && sqlIgnored != null) {
1203                        for (String regex : sqlIgnored) {
1204                                if (!CommonTools.isBlank(regex)) {
1205                                        matchedIgnored = sql.matches(regex);
1206
1207                                        if (matchedIgnored)
1208                                                break;
1209                                }
1210                        }
1211                }
1212
1213                boolean b = matchedAllowed && !matchedIgnored;
1214                if (!b) {
1215                        LoggerFactory.getLogger(DriverExecutor.class).debug("Not available - '{}'", sql);
1216                }
1217                return b;
1218        }
1219
1220        /**
1221         * Write sql log
1222         * @return boolean 
1223         * @param type
1224         * @param seq
1225         * @param sql
1226         * @param params
1227         * */
1228        protected synchronized boolean writeSqlLog(String type, long seq, String sql, Object params) {
1229                return writeSqlLog(this.hashCode(),connection, type, seq, sql, params);
1230        }
1231
1232        /**
1233         * Write sql log
1234         * @return boolean 
1235         * @param connection
1236         * @param type
1237         * @param seq
1238         * @param sql
1239         * @param params
1240         * */
1241        protected synchronized static boolean writeSqlLog(int deHashCode,Connection connection, String type, long seq, String sql,
1242                        Object params) {
1243                if (connection instanceof DriverConnection) {
1244                        if (!checkSqlLogAvailable(connection, sql))
1245                                return false;
1246
1247                        final String header = String.format(
1248                                        "LOG_DATE,LOG_HOUR,LOG_MI,LOG_SEC,LOG_MS,LOG_HOST,LOG_THREAD,LOG_DS,LOG_CONN_ID,LOG_EXEC_ID,LOG_TYPE,LOG_SEQ,LOG_SQL,LOG_PARAMS%s",
1249                                        System.lineSeparator());
1250                        final DateFormat df = new SimpleDateFormat("yyyyMMdd");
1251                        final DateFormat dtf = new SimpleDateFormat("yyyyMMdd,HH,mm,ss,SSS");
1252                        DriverConnection dc = (DriverConnection) connection;
1253                        Integer connHashCode = dc.getOriginConnectionHashCode();
1254                        DriverDataSource dds = dc.getDriverDataSource();
1255                        ConfigProperties configProperties = dds.getConfigProperties();
1256
1257                        boolean logEnable = configProperties.getBoolean("SqlLogEnable", false);
1258                        long overspend = configProperties.getMilliSeconds("SqlLogOverspend",0L);
1259
1260                        if (!logEnable)
1261                                return false;
1262
1263                        final String defaultLogFolderPath = String.format("%s/SqlLog/",
1264                                        CommonTools.getJarPath(DriverExecutor.class));
1265                        final String logFolderPath = configProperties.getString("SqlLogFolder", defaultLogFolderPath);
1266                        final long maxFileSize = configProperties.getFileSize("SqlLogMaxFileSize", 1024 * 1024 * 10L);
1267                        final int archiveDays = configProperties.getInteger("SqlLogArchiveDays", 31);
1268                        final int logParamMaxLength = configProperties.getInteger("SqlLogParamMaxLength", 20);
1269
1270                        if (sqlLogCacheArray == null) {
1271
1272                                sqlLogCacheArray = new CacheArray();
1273                                long sqlLogFilterTimer = configProperties.getLong("SqlLogTimer", 1000L);
1274                                sqlLogCacheArray.filter(new CacheArrayFilter(0L,sqlLogFilterTimer) {
1275                                        @Override
1276                                        public void executeBatch(Integer index, List batch) {
1277                                            final StringBuffer sbf = new StringBuffer();
1278                                            for(Object item : batch){
1279                                                sbf.append(item);
1280                                            }
1281                                                try {
1282                                                        String msg = sbf.toString();
1283                                                        String dateStr = df.format(new Timestamp(System.currentTimeMillis()));
1284                                                        File sqlLogFile = new File(String.format("%s/%s.csv", logFolderPath, dateStr));
1285                                                        File sqlLogFolder = new File(sqlLogFile.getParent());
1286
1287                                                        if (sqlLogFolder.exists()) {
1288                                                                if (!sqlLogFolder.canWrite())
1289                                                                        throw new IOException(String.format("Can not write to log folder '%s'",
1290                                                                                        sqlLogFolder.getAbsolutePath()));
1291                                                        } else {
1292                                                                sqlLogFolder.mkdirs();
1293                                                        }
1294
1295                                                        if (sqlLogFile.exists()) {
1296                                                                if (!sqlLogFile.canWrite())
1297                                                                        throw new IOException(String.format("Can not write to log file '%s'",
1298                                                                                        sqlLogFile.getAbsolutePath()));
1299                                                        }
1300
1301                                                        int suffixIndex = sqlLogFile.getName().lastIndexOf(".");
1302                                                        String logFileNamePrefix = sqlLogFile.getName().substring(0, suffixIndex);
1303                                                        long logSize = FileTools.size(sqlLogFile);
1304                                                        if (!sqlLogFile.exists()) {
1305                                                                FileTools.write(sqlLogFile, header, false);
1306                                                        }
1307                                                        if (logSize < maxFileSize) {
1308                                                                FileTools.write(sqlLogFile, msg, true);
1309                                                        } else {
1310                                                                int logIndex = getLogFileIndex(configProperties, sqlLogFolder, logFileNamePrefix,
1311                                                                                "csv");
1312                                                                if (logIndex == 0) {
1313                                                                        FileTools.write(sqlLogFile, String.format("%s%s", header, msg), false);
1314                                                                } else {
1315                                                                        backupLog(sqlLogFolder, sqlLogFile, logFileNamePrefix, logIndex, header, msg);
1316                                                                }
1317                                                        }
1318                                                        archiveLog(archiveDays, sqlLogFolder);
1319                                                } catch (Exception e) {
1320                                                        LoggerFactory.getLogger(DriverExecutor.class).warn(e.getMessage(), e);
1321                                                }
1322                                        }
1323                                });
1324                        }
1325
1326                        if (logEnable) {
1327                                String hostname = CommonTools.getHostname();
1328                                String dateTimeStr = dtf.format(new Timestamp(System.currentTimeMillis()));
1329                                boolean isOverspend = false;
1330                                boolean isSpendSeq = sql.equals("spend");
1331                                if(isSpendSeq && params != null){
1332                                    long spendValue = Long.parseLong(params + "");
1333                                    isOverspend = (spendValue >= overspend);
1334                                }
1335
1336                String threadId = Thread.currentThread().getName() + "-" + Thread.currentThread().getId();
1337                                String msg = String.format("%s,%s,%s,%s,%s,%s,%s,%s,\"%s\",\"%s\"%s", dateTimeStr, hostname,
1338                                                    threadId, dds.getName(), connHashCode,deHashCode, type, seq,
1339                                                    replaceToSigleLine(sql), handleParams(logParamMaxLength, params), System.lineSeparator());  
1340                                                    
1341                                String key = String.format("%s_%s",deHashCode,connHashCode);
1342                                if(seq <= 0 || overspend <= 0){
1343                                    sqlLogCacheArray.add(msg);
1344                                    SQL_LOG_MSG_MAPPING.remove(key);
1345                                SQL_LOG_OVERSPEND_MAPPING.remove(key);                              
1346                                }
1347                                if(seq > 0 && overspend > 0){
1348                                SQL_LOG_OVERSPEND_MAPPING.put(key,isOverspend);
1349                                String existsMsg = SQL_LOG_MSG_MAPPING.get(key);
1350                                existsMsg = existsMsg == null ? msg : (existsMsg + msg);
1351                                SQL_LOG_MSG_MAPPING.put(key, existsMsg);
1352                                
1353                                boolean seqOverspend = SQL_LOG_OVERSPEND_MAPPING.get(key);
1354                                if(isSpendSeq && seqOverspend){
1355                                    sqlLogCacheArray.add(SQL_LOG_MSG_MAPPING.get(key) + "");
1356                                    SQL_LOG_MSG_MAPPING.remove(key);
1357                                    SQL_LOG_OVERSPEND_MAPPING.remove(key);
1358                                }
1359                                if(isSpendSeq && !seqOverspend){
1360                                    SQL_LOG_MSG_MAPPING.remove(key);
1361                                    SQL_LOG_OVERSPEND_MAPPING.remove(key);
1362                                }
1363                                }
1364                                return true;
1365                        }
1366
1367                }
1368                return false;
1369        }
1370
1371        /**
1372         * Backup log file
1373         * */
1374        private static void backupLog(File logFolder, File logFile, String logFileNamePrefix, int logIndex, String header,
1375                        String msg) throws Exception {
1376                String backupLogFileName = String.format("%s/%s.%s.csv", logFolder.getAbsolutePath(), logFileNamePrefix,
1377                                logIndex);
1378                Path source = Paths.get(logFile.getAbsolutePath());
1379                Path target = Paths.get(backupLogFileName);
1380                Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
1381                FileTools.write(logFile, String.format("%s%s", header, msg), false);
1382        }
1383
1384        /**
1385         * Get log file index
1386         * For backup log file use
1387         * */
1388        private static int getLogFileIndex(ConfigProperties configProperties, File folder, String prefix, String suffix) {
1389                Integer maxBackupIndex = configProperties.getInteger("SqlLogMaxBackupIndex", 10);
1390                for (int i = 1; i <= maxBackupIndex; i++) {
1391                        String logFileName = String.format("%s/%s.%s.%s", folder.getAbsolutePath(), prefix, i, suffix);
1392                        File logFile = new File(logFileName);
1393                        if (!logFile.exists())
1394                                return i;
1395                }
1396                return 0;
1397        }
1398
1399        /**
1400         * Archive Log
1401         * */
1402        private static void archiveLog(int archiveDays, File sqlLogFolder) {
1403                if (archiveDays > 0) {
1404                        try {
1405                                long archiveDaysMs = new Date().getTime() - (archiveDays * 24 * 3600000L);
1406                                deleteFilesOlderThan(sqlLogFolder, archiveDaysMs);
1407                        } catch (Exception e) {
1408                                LoggerFactory.getLogger(DriverExecutor.class).warn(e);
1409                        }
1410                }
1411        }
1412
1413        /**
1414         * Delete old archive logs
1415         * */
1416        private static void deleteFilesOlderThan(File directory, long archiveDaysMs) throws IOException {
1417                if (directory.isDirectory()) {
1418                        File[] files = directory.listFiles();
1419                        if (files != null) {
1420                                for (File file : files) {
1421                                        if (file.isFile()) {
1422                                                boolean isLogFile = file.getName().toLowerCase().endsWith(".csv");
1423                                                if (isLogFile) {
1424                                                        boolean canWrite = file.canWrite();
1425                                                        if (canWrite) {
1426                                                                long lastModified = file.lastModified();
1427                                                                if (lastModified < archiveDaysMs) {
1428                                                                        Files.deleteIfExists(Paths.get(file.toURI()));
1429                                                                }
1430                                                        }
1431                                                }
1432                                        }
1433                                }
1434                        }
1435                }
1436        }
1437
1438        /**
1439         * Replace to sigle line
1440         * For write csv log
1441         * */
1442        private static String replaceToSigleLine(String msg) {
1443                return CodeEscape.escapeToSingleLineForCsv(msg);
1444        }
1445
1446        /**
1447         * Hahdle Params
1448         * For write csv log
1449         * */
1450        private static String handleParams(int paramMaxLength, Object params) {
1451
1452                if (params == null)
1453                        return "null";
1454
1455                StringBuffer paramSbf = new StringBuffer("");
1456                if (params instanceof List) {
1457                        List<Object> listParams = (List<Object>) params;
1458                        int size = listParams.size();
1459                        for (int i = 0; i < size; i++) {
1460                                Object param = listParams.get(i);
1461                                String str = param + "";
1462                                if (str.length() > paramMaxLength) {
1463                                        paramSbf.append(str.substring(0, paramMaxLength) + "...");
1464                                } else {
1465                                        paramSbf.append(str);
1466                                }
1467                                if (i < size - 1) {
1468                                        paramSbf.append(";");
1469                                }
1470                        }
1471                } else {
1472                        paramSbf.append(params);
1473                }
1474                return replaceToSigleLine(paramSbf.toString());
1475        }
1476
1477        /**For Sync DataSource**/
1478
1479        /**
1480         * For database replication
1481         * */
1482        protected static void callAndReturnBooleanSync(Connection masterConn, long seq, String sql, List<Object> params,
1483                        boolean returnResult) throws SQLException {
1484                if (masterConn instanceof DriverConnection) {
1485                        openSyncConnection(masterConn);
1486
1487                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1488                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1489                        ConfigProperties ddscp = masterDriverConn.getDriverDataSource().getConfigProperties();
1490                        boolean connCheck = true;
1491                        boolean returnCheck = true;
1492                        if (ddscp != null) {
1493                                connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1494                                returnCheck = ddscp.getBoolean("SyncReturnCheck", true);
1495                        }
1496                        List<DriverExecutor> deList = SYNC_EXECUTOR_MARK.get(masterConnHashCode);
1497                        if (deList != null) {
1498                                for (DriverExecutor de : deList) {
1499                                        try {
1500                                                boolean resultSync = de.callAndReturnBoolean(sql, params);
1501                                                if (returnCheck) {
1502                                                        if (resultSync != returnResult) {
1503                                                                writeSqlLog(de.hashCode(),masterDriverConn, "diffed", seq, "diffed", String.format("%s-%s",
1504                                                                                de.getDataSourceName(), de.getOriginConnectionHashCode()));
1505                                                                int errorCode = 99906;
1506                                                                String errorMsg = String.format("The returned results are inconsistent '%s-%s'.",
1507                                                                                de.getDataSourceName(), de.getOriginConnectionHashCode());
1508                                                                throw new SQLException(errorMsg) {
1509                                                                        @Override
1510                                                                        public int getErrorCode() {
1511                                                                                return errorCode;
1512                                                                        }
1513                                                                };
1514                                                        }
1515                                                }
1516                                        } catch (SQLException e) {
1517                                                LoggerFactory.getLogger(DriverExecutor.class)
1518                                                                .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1519                                                if (connCheck)
1520                                                        throw e;
1521                                        }
1522                                }
1523                        }
1524                }
1525        }
1526
1527        /**
1528         * For database replication
1529         * */
1530        protected static void callAndReturnListSync(Connection masterConn, int cursorStart, int maxRows, String sql,
1531                        List<Object> params) throws SQLException {
1532                if (masterConn instanceof DriverConnection) {
1533                        openSyncConnection(masterConn);
1534
1535                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1536                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1537
1538                        List<DriverExecutor> deList = SYNC_EXECUTOR_MARK.get(masterConnHashCode);
1539                        if (deList != null) {
1540                                for (DriverExecutor de : deList) {
1541                                        try {
1542                                                de.callAndReturnBoolean(sql, params);
1543                                        } catch (SQLException e) {
1544                                                LoggerFactory.getLogger(DriverExecutor.class)
1545                                                                .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1546                                                ConfigProperties ddscp = masterDriverConn.getDriverDataSource().getConfigProperties();
1547                                                boolean connCheck = true;
1548                                                if (ddscp != null) {
1549                                                        connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1550                                                }
1551                                                if (connCheck)
1552                                                        throw e;
1553                                        }
1554                                }
1555                        }
1556                }
1557        }
1558
1559        /**
1560         * For database replication
1561         * */
1562        protected static void callAndReturnRowsSync(Connection masterConn, long seq, String sql, List<Object> params,
1563                        int returnRows) throws SQLException {
1564                if (masterConn instanceof DriverConnection) {
1565                        openSyncConnection(masterConn);
1566
1567                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1568                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1569                        ConfigProperties ddscp = masterDriverConn.getDriverDataSource().getConfigProperties();
1570                        boolean connCheck = true;
1571                        boolean returnCheck = true;
1572                        if (ddscp != null) {
1573                                connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1574                                returnCheck = ddscp.getBoolean("SyncReturnCheck", true);
1575                        }
1576                        List<DriverExecutor> deList = SYNC_EXECUTOR_MARK.get(masterConnHashCode);
1577                        if (deList != null) {
1578                                for (DriverExecutor de : deList) {
1579                                        try {
1580                                                int rowSync = de.callAndReturnRows(sql, params);
1581                                                if (returnCheck) {
1582                                                        if (rowSync != returnRows) {
1583                                                                writeSqlLog(de.hashCode(),masterDriverConn, "diffed", seq, "diffed", String.format("%s-%s",
1584                                                                                de.getDataSourceName(), de.getOriginConnectionHashCode()));
1585                                                                int errorCode = 99906;
1586                                                                String errorMsg = String.format("The returned results are inconsistent '%s-%s'.",
1587                                                                                de.getDataSourceName(), de.getOriginConnectionHashCode());
1588                                                                throw new SQLException(errorMsg) {
1589                                                                        @Override
1590                                                                        public int getErrorCode() {
1591                                                                                return errorCode;
1592                                                                        }
1593                                                                };
1594                                                        }
1595                                                }
1596                                        } catch (SQLException e) {
1597                                                LoggerFactory.getLogger(DriverExecutor.class)
1598                                                                .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1599                                                if (connCheck)
1600                                                        throw e;
1601                                        }
1602                                }
1603                        }
1604                }
1605        }
1606
1607        /**
1608         * For database replication
1609         * */
1610        protected static void executeBatchListSync(Connection masterConn, long seq, String sql, List<List<Object>> records,
1611                        int returnRows) throws SQLException {
1612                if (masterConn instanceof DriverConnection) {
1613                        openSyncConnection(masterConn);
1614
1615                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1616                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1617                        ConfigProperties ddscp = masterDriverConn.getDriverDataSource().getConfigProperties();
1618                        boolean connCheck = true;
1619                        boolean returnCheck = true;
1620                        if (ddscp != null) {
1621                                connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1622                                returnCheck = ddscp.getBoolean("SyncReturnCheck", true);
1623                        }
1624                        List<DriverExecutor> deList = SYNC_EXECUTOR_MARK.get(masterConnHashCode);
1625                        if (deList != null) {
1626                                for (DriverExecutor de : deList) {
1627                                        try {
1628                                                int rowSync = de.executeBatchList(sql, records);
1629                                                if (returnCheck) {
1630                                                        if (rowSync != returnRows) {
1631                                                                writeSqlLog(de.hashCode(),masterDriverConn, "diffed", seq, "diffed", String.format("%s-%s",
1632                                                                                de.getDataSourceName(), de.getOriginConnectionHashCode()));
1633                                                                int errorCode = 99906;
1634                                                                String errorMsg = String.format("The returned results are inconsistent '%s-%s'.",
1635                                                                                de.getDataSourceName(), de.getOriginConnectionHashCode());
1636                                                                throw new SQLException(errorMsg) {
1637                                                                        @Override
1638                                                                        public int getErrorCode() {
1639                                                                                return errorCode;
1640                                                                        }
1641                                                                };
1642                                                        }
1643                                                }
1644                                        } catch (SQLException e) {
1645                                                LoggerFactory.getLogger(DriverExecutor.class)
1646                                                                .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1647                                                if (connCheck)
1648                                                        throw e;
1649                                        }
1650                                }
1651                        }
1652                }
1653        }
1654
1655        /**
1656         * For database replication
1657         * */
1658        protected static void executeSync(Connection masterConn, long seq, String sql, List<Object> params, int returnRows)
1659                        throws SQLException {
1660                if (masterConn instanceof DriverConnection) {
1661                        openSyncConnection(masterConn);
1662
1663                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1664                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1665                        ConfigProperties ddscp = masterDriverConn.getDriverDataSource().getConfigProperties();
1666                        boolean connCheck = true;
1667                        boolean returnCheck = true;
1668                        if (ddscp != null) {
1669                                connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1670                                returnCheck = ddscp.getBoolean("SyncReturnCheck", true);
1671                        }
1672                        List<DriverExecutor> deList = SYNC_EXECUTOR_MARK.get(masterConnHashCode);
1673                        if (deList != null) {
1674                                for (DriverExecutor de : deList) {
1675                                        try {
1676                                                int rowSync = de.execute(sql, params);
1677                                                if (returnCheck) {
1678                                                        if (rowSync != returnRows) {
1679                                                                writeSqlLog(de.hashCode(),masterDriverConn, "diffed", seq, "diffed", String.format("%s-%s",
1680                                                                                de.getDataSourceName(), de.getOriginConnectionHashCode()));
1681                                                                int errorCode = 99906;
1682                                                                String errorMsg = String.format("The returned results are inconsistent '%s-%s'.",
1683                                                                                de.getDataSourceName(), de.getOriginConnectionHashCode());
1684                                                                throw new SQLException(errorMsg) {
1685                                                                        @Override
1686                                                                        public int getErrorCode() {
1687                                                                                return errorCode;
1688                                                                        }
1689                                                                };
1690                                                        }
1691                                                }
1692                                        } catch (SQLException e) {
1693                                                LoggerFactory.getLogger(DriverExecutor.class)
1694                                                                .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1695                                                if (connCheck)
1696                                                        throw e;
1697                                        }
1698                                }
1699                        }
1700                }
1701        }
1702
1703        /**
1704         * For database replication
1705         * */
1706        protected synchronized static boolean openSyncConnection(Connection masterConn) throws SQLException {
1707                if (masterConn instanceof DriverConnection) {
1708
1709                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1710                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1711
1712                        boolean isExist = SYNC_EXECUTOR_MARK.containsKey(masterConnHashCode);
1713
1714                        if (isExist) {
1715                                return false;
1716                        } else {
1717                                SYNC_EXECUTOR_MARK.put(masterConnHashCode, new ArrayList<DriverExecutor>());
1718                        }
1719
1720                        DriverDataSource dds = masterDriverConn.getDriverDataSource();
1721                        if (dds != null) {
1722                                List<DriverDataSource> sdsl = dds.getSyncDataSourceList(dds.getName());
1723                                if (sdsl != null) {
1724
1725                                        if (sdsl.isEmpty())
1726                                                return false;
1727
1728                                        for (DriverDataSource sds : sdsl) {
1729
1730                                                Integer sdsHashCode = sds.hashCode();
1731                                                if (!SYNC_CONN_ERROR_TIME.containsKey(sdsHashCode)) {
1732                                                        SYNC_CONN_ERROR_TIME.put(sdsHashCode, 0L);
1733                                                }
1734
1735                                                Long syncConnErrorTime = SYNC_CONN_ERROR_TIME.get(sdsHashCode);
1736                                                if (syncConnErrorTime > 0) {
1737                                                        ConfigProperties ddscp = dds.getConfigProperties();
1738                                                        long connCheckMs = 10000L;
1739                                                        if (ddscp != null) {
1740                                                                connCheckMs = ddscp.getLong("SyncConnectionCheckTime", 10000L);
1741                                                        }
1742                                                        boolean isSkipConn = syncConnErrorTime > 0
1743                                                                        && (System.currentTimeMillis() - syncConnErrorTime) <= connCheckMs;
1744                                                        if (isSkipConn) {
1745                                                                continue;
1746                                                        }
1747                                                }
1748
1749                                                String masterFingerprint = dds.getFingerprint();
1750                                                String syncFingerprint = sds.getFingerprint();
1751                                                if (masterFingerprint.equalsIgnoreCase(syncFingerprint)) {
1752                                                        LoggerFactory.getLogger(DriverExecutor.class)
1753                                                                        .warn("Skip sync reason 'same connection fingerprint'.");
1754                                                        continue;
1755                                                }
1756
1757                                                try {
1758                                                        final Connection conn = sds.getConnection();
1759                                                        if (conn == null) {
1760                                                                int errorCode = 99904;
1761                                                                String errorMsg = "Connection is null.";
1762                                                                throw new SQLException(errorMsg) {
1763                                                                        @Override
1764                                                                        public int getErrorCode() {
1765                                                                                return errorCode;
1766                                                                        }
1767                                                                };
1768                                                        }
1769                                                        conn.setAutoCommit(masterConn.getAutoCommit());
1770                                                        SYNC_CONN_ERROR_TIME.put(sdsHashCode, 0L);
1771                                                        SYNC_EXECUTOR_MARK.get(masterConnHashCode).add(new DriverExecutor(conn));
1772                                                } catch (SQLException e) {
1773                                                        SYNC_CONN_ERROR_TIME.put(sdsHashCode, System.currentTimeMillis());
1774                                                        LoggerFactory.getLogger(DriverExecutor.class)
1775                                                                        .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1776                                                        ConfigProperties ddscp = dds.getConfigProperties();
1777                                                        boolean connCheck = true;
1778                                                        if (ddscp != null) {
1779                                                                connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1780                                                        }
1781                                                        if (connCheck)
1782                                                                throw e;
1783                                                }
1784
1785                                        }
1786
1787                                        return true;
1788                                }
1789                        }
1790                }
1791                return false;
1792        }
1793
1794        /**
1795         * For database replication
1796         * */
1797        protected static void closeSyncConnection(Connection masterConn) throws SQLException {
1798                if (masterConn instanceof DriverConnection) {
1799                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1800                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1801
1802                        List<DriverExecutor> deList = SYNC_EXECUTOR_MARK.get(masterConnHashCode);
1803                        if (deList != null) {
1804                                for (DriverExecutor de : deList) {
1805                                        try {
1806                                                de.close();
1807                                        } catch (SQLException e) {
1808                                                LoggerFactory.getLogger(DriverExecutor.class)
1809                                                                .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1810                                                ConfigProperties ddscp = masterDriverConn.getDriverDataSource().getConfigProperties();
1811                                                boolean connCheck = true;
1812                                                if (ddscp != null) {
1813                                                        connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1814                                                }
1815                                                if (connCheck)
1816                                                        throw e;
1817                                        }
1818                                }
1819                                SYNC_EXECUTOR_MARK.remove(masterConnHashCode);
1820                                LoggerFactory.getLogger(DriverExecutor.class).debug("CloseSyncConnection - masterConn '{}' finished.",
1821                                                masterConnHashCode);
1822                        }
1823                }
1824        }
1825
1826        /**
1827         * For database replication
1828         * */
1829        protected static void commitSyncConnection(Connection masterConn) throws SQLException {
1830                if (masterConn instanceof DriverConnection) {
1831                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1832                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1833
1834                        List<DriverExecutor> deList = SYNC_EXECUTOR_MARK.get(masterConnHashCode);
1835                        if (deList != null) {
1836                                for (DriverExecutor de : deList) {
1837                                        try {
1838                                                de.commit();
1839                                        } catch (SQLException e) {
1840                                                LoggerFactory.getLogger(DriverExecutor.class)
1841                                                                .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1842                                                ConfigProperties ddscp = masterDriverConn.getDriverDataSource().getConfigProperties();
1843                                                boolean connCheck = true;
1844                                                if (ddscp != null) {
1845                                                        connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1846                                                }
1847                                                if (connCheck)
1848                                                        throw e;
1849                                        }
1850                                }
1851                                LoggerFactory.getLogger(DriverExecutor.class).debug("CommitSyncConnection - masterConn '{}' finished.",
1852                                                masterConnHashCode);
1853                        }
1854                }
1855        }
1856
1857        /**
1858         * For database replication
1859         * */
1860        protected static void rollbackSyncConnection(Connection masterConn) throws SQLException {
1861                if (masterConn instanceof DriverConnection) {
1862                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1863                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1864
1865                        List<DriverExecutor> deList = SYNC_EXECUTOR_MARK.get(masterConnHashCode);
1866                        if (deList != null) {
1867                                for (DriverExecutor de : deList) {
1868                                        try {
1869                                                de.rollback();
1870                                        } catch (SQLException e) {
1871                                                LoggerFactory.getLogger(DriverExecutor.class)
1872                                                                .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1873                                                ConfigProperties ddscp = masterDriverConn.getDriverDataSource().getConfigProperties();
1874                                                boolean connCheck = true;
1875                                                if (ddscp != null) {
1876                                                        connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1877                                                }
1878                                                if (connCheck)
1879                                                        throw e;
1880                                        }
1881                                }
1882                                LoggerFactory.getLogger(DriverExecutor.class)
1883                                                .debug("RollbackSyncConnection - masterConn '{}' finished.", masterConnHashCode);
1884                        }
1885                }
1886        }
1887
1888        /**
1889         * For database replication
1890         * */
1891        protected static void abortSyncConnection(Connection masterConn) throws SQLException {
1892                if (masterConn instanceof DriverConnection) {
1893                        DriverConnection masterDriverConn = (DriverConnection) masterConn;
1894                        Integer masterConnHashCode = masterDriverConn.getOriginConnectionHashCode();
1895
1896                        List<DriverExecutor> deList = SYNC_EXECUTOR_MARK.get(masterConnHashCode);
1897                        if (deList != null) {
1898                                for (DriverExecutor de : deList) {
1899                                        try {
1900                                                de.abort();
1901                                        } catch (SQLException e) {
1902                                                LoggerFactory.getLogger(DriverExecutor.class)
1903                                                                .warn(String.format("ERROR CODE: (%s) %s", e.getErrorCode(), e.getMessage()));
1904                                                ConfigProperties ddscp = masterDriverConn.getDriverDataSource().getConfigProperties();
1905                                                boolean connCheck = true;
1906                                                if (ddscp != null) {
1907                                                        connCheck = ddscp.getBoolean("SyncConnectionCheck", true);
1908                                                }
1909                                                if (connCheck)
1910                                                        throw e;
1911                                        }
1912                                }
1913                                LoggerFactory.getLogger(DriverExecutor.class).debug("AbortSyncConnection - masterConn '{}' finished.",
1914                                                masterConnHashCode);
1915                        }
1916                }
1917        }
1918
1919}