All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.landawn.abacus.jdbc.Jdbc Maven / Gradle / Ivy

There is a newer version: 3.8.5
Show newest version
/*
 * Copyright (c) 2022, Haiyang Li.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.landawn.abacus.jdbc;

import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;

import com.landawn.abacus.annotation.Beta;
import com.landawn.abacus.annotation.SequentialOnly;
import com.landawn.abacus.annotation.Stateful;
import com.landawn.abacus.parser.ParserUtil;
import com.landawn.abacus.parser.ParserUtil.BeanInfo;
import com.landawn.abacus.parser.ParserUtil.PropInfo;
import com.landawn.abacus.type.Type;
import com.landawn.abacus.util.Array;
import com.landawn.abacus.util.ClassUtil;
import com.landawn.abacus.util.DataSet;
import com.landawn.abacus.util.EntityId;
import com.landawn.abacus.util.Fn;
import com.landawn.abacus.util.Fn.Factory;
import com.landawn.abacus.util.Fn.IntFunctions;
import com.landawn.abacus.util.Fn.Suppliers;
import com.landawn.abacus.util.ImmutableList;
import com.landawn.abacus.util.ListMultimap;
import com.landawn.abacus.util.Multimap;
import com.landawn.abacus.util.N;
import com.landawn.abacus.util.NoCachingNoUpdating.DisposableObjArray;
import com.landawn.abacus.util.ObjectPool;
import com.landawn.abacus.util.ParsedSql;
import com.landawn.abacus.util.Seid;
import com.landawn.abacus.util.Strings;
import com.landawn.abacus.util.Throwables;
import com.landawn.abacus.util.Tuple;
import com.landawn.abacus.util.Tuple.Tuple2;
import com.landawn.abacus.util.Tuple.Tuple3;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;

@SuppressWarnings("java:S1192")
public final class Jdbc {

    static final ObjectPool, ColumnGetter> COLUMN_GETTER_POOL = new ObjectPool<>(1024);

    static {
        COLUMN_GETTER_POOL.put(N.typeOf(boolean.class), ColumnGetter.GET_BOOLEAN);
        COLUMN_GETTER_POOL.put(N.typeOf(Boolean.class), ColumnGetter.GET_BOOLEAN);
        COLUMN_GETTER_POOL.put(N.typeOf(byte.class), ColumnGetter.GET_BYTE);
        COLUMN_GETTER_POOL.put(N.typeOf(Byte.class), ColumnGetter.GET_BYTE);
        COLUMN_GETTER_POOL.put(N.typeOf(short.class), ColumnGetter.GET_SHORT);
        COLUMN_GETTER_POOL.put(N.typeOf(Short.class), ColumnGetter.GET_SHORT);
        COLUMN_GETTER_POOL.put(N.typeOf(int.class), ColumnGetter.GET_INT);
        COLUMN_GETTER_POOL.put(N.typeOf(Integer.class), ColumnGetter.GET_INT);
        COLUMN_GETTER_POOL.put(N.typeOf(long.class), ColumnGetter.GET_LONG);
        COLUMN_GETTER_POOL.put(N.typeOf(Long.class), ColumnGetter.GET_LONG);
        COLUMN_GETTER_POOL.put(N.typeOf(float.class), ColumnGetter.GET_FLOAT);
        COLUMN_GETTER_POOL.put(N.typeOf(Float.class), ColumnGetter.GET_FLOAT);
        COLUMN_GETTER_POOL.put(N.typeOf(double.class), ColumnGetter.GET_DOUBLE);
        COLUMN_GETTER_POOL.put(N.typeOf(Double.class), ColumnGetter.GET_DOUBLE);
        COLUMN_GETTER_POOL.put(N.typeOf(BigDecimal.class), ColumnGetter.GET_BIG_DECIMAL);
        COLUMN_GETTER_POOL.put(N.typeOf(String.class), ColumnGetter.GET_STRING);
        COLUMN_GETTER_POOL.put(N.typeOf(java.sql.Date.class), ColumnGetter.GET_DATE);
        COLUMN_GETTER_POOL.put(N.typeOf(java.sql.Time.class), ColumnGetter.GET_TIME);
        COLUMN_GETTER_POOL.put(N.typeOf(java.sql.Timestamp.class), ColumnGetter.GET_TIMESTAMP);
        COLUMN_GETTER_POOL.put(N.typeOf(Object.class), ColumnGetter.GET_OBJECT);
    }

    private Jdbc() {
        // singleton.
    }

    /**
     * The Interface ParametersSetter.
     *
     * @param 
     */
    @FunctionalInterface
    public interface ParametersSetter extends Throwables.Consumer {
        @SuppressWarnings("rawtypes")
        ParametersSetter DO_NOTHING = preparedQuery -> {
            // Do nothing.
        };

        /**
         *
         *
         * @param preparedQuery
         * @throws SQLException
         */
        @Override
        void accept(QS preparedQuery) throws SQLException;
    }

    /**
     * The Interface BiParametersSetter.
     *
     * @param 
     * @param 
     * @see Columns.ColumnOne
     * @see Columns.ColumnTwo
     * @see Columns.ColumnThree
     */
    @FunctionalInterface
    public interface BiParametersSetter extends Throwables.BiConsumer {
        @SuppressWarnings("rawtypes")
        BiParametersSetter DO_NOTHING = (preparedQuery, param) -> {
            // Do nothing.
        };

        /**
         *
         *
         * @param preparedQuery
         * @param param
         * @throws SQLException
         */
        @Override
        void accept(QS preparedQuery, T param) throws SQLException;

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param 
         * @param fieldNameList
         * @param entityClass
         * @return
         */
        @Beta
        static  BiParametersSetter createForArray(final List fieldNameList, final Class entityClass) {
            N.checkArgNotEmpty(fieldNameList, "'fieldNameList' can't be null or empty");
            N.checkArgument(ClassUtil.isBeanClass(entityClass), "{} is not a valid entity class with getter/setter methods", entityClass);

            return new BiParametersSetter<>() {
                private final int len = fieldNameList.size();
                @SuppressWarnings("rawtypes")
                private Type[] fieldTypes = null;

                @Override
                public void accept(PreparedStatement stmt, T[] params) throws SQLException {
                    if (fieldTypes == null) {
                        final BeanInfo entityInfo = ParserUtil.getBeanInfo(entityClass);
                        @SuppressWarnings("rawtypes")
                        final Type[] localFieldTypes = new Type[len];

                        for (int i = 0; i < len; i++) {
                            localFieldTypes[i] = entityInfo.getPropInfo(fieldNameList.get(i)).dbType;
                        }

                        this.fieldTypes = localFieldTypes;
                    }

                    for (int i = 0; i < len; i++) {
                        fieldTypes[i].set(stmt, i + 1, params[i]);
                    }
                }
            };
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param 
         * @param fieldNameList
         * @param entityClass
         * @return
         */
        @Beta
        static  BiParametersSetter> createForList(final List fieldNameList, final Class entityClass) {
            N.checkArgNotEmpty(fieldNameList, "'fieldNameList' can't be null or empty");
            N.checkArgument(ClassUtil.isBeanClass(entityClass), "{} is not a valid entity class with getter/setter methods", entityClass);

            return new BiParametersSetter<>() {
                private final int len = fieldNameList.size();
                @SuppressWarnings("rawtypes")
                private Type[] fieldTypes = null;

                @Override
                public void accept(PreparedStatement stmt, List params) throws SQLException {
                    if (fieldTypes == null) {
                        final BeanInfo entityInfo = ParserUtil.getBeanInfo(entityClass);
                        @SuppressWarnings("rawtypes")
                        final Type[] localFieldTypes = new Type[len];

                        for (int i = 0; i < len; i++) {
                            localFieldTypes[i] = entityInfo.getPropInfo(fieldNameList.get(i)).dbType;
                        }

                        this.fieldTypes = localFieldTypes;
                    }

                    for (int i = 0; i < len; i++) {
                        fieldTypes[i].set(stmt, i + 1, params.get(i));
                    }
                }
            };
        }
    }

    /**
     * The Interface TriParametersSetter.
     *
     * @param 
     * @param 
     */
    @FunctionalInterface
    public interface TriParametersSetter extends Throwables.TriConsumer {
        @SuppressWarnings("rawtypes")
        TriParametersSetter DO_NOTHING = new TriParametersSetter<>() {
            @Override
            public void accept(ParsedSql parsedSql, Object preparedQuery, Object param) throws SQLException {
                // Do nothing.
            }
        };

        /**
         *
         *
         * @param parsedSql
         * @param preparedQuery
         * @param param
         * @throws SQLException
         */
        @Override
        void accept(ParsedSql parsedSql, QS preparedQuery, T param) throws SQLException;
    }

    /**
     * The Interface ResultExtractor.
     *
     * @param 
     */
    @FunctionalInterface
    public interface ResultExtractor extends Throwables.Function {

        ResultExtractor TO_DATA_SET = rs -> {
            if (rs == null) {
                return N.newEmptyDataSet();
            }

            return JdbcUtil.extractData(rs);
        };

        /**
         * In a lot of scenarios, including PreparedQuery/Dao/SQLExecutor, the input {@code ResultSet} will be closed after {@code apply(rs)} call. So don't save/return the input {@code ResultSet}.
         *
         * @param rs
         * @return
         * @throws SQLException
         */
        @Override
        T apply(ResultSet rs) throws SQLException;

        /**
         *
         *
         * @param 
         * @param after
         * @return
         */
        default  ResultExtractor andThen(final Throwables.Function after) {
            N.checkArgNotNull(after);

            return rs -> after.apply(apply(rs));
        }

        /**
         *
         *
         * @return
         */
        default BiResultExtractor toBiResultExtractor() {
            return (rs, columnLabels) -> this.apply(rs);
        }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param keyExtractor
         * @param valueExtractor
         * @return
         */
        static  ResultExtractor> toMap(final RowMapper keyExtractor, final RowMapper valueExtractor) {
            return toMap(keyExtractor, valueExtractor, Suppliers. ofMap());
        }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param 
         * @param keyExtractor
         * @param valueExtractor
         * @param supplier
         * @return
         */
        static > ResultExtractor toMap(final RowMapper keyExtractor, final RowMapper valueExtractor,
                final Supplier supplier) {
            return toMap(keyExtractor, valueExtractor, Fn. throwingMerger(), supplier);
        }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param keyExtractor
         * @param valueExtractor
         * @param mergeFunction
         * @return
         * @see {@link Fn.throwingMerger()}
         * @see {@link Fn.replacingMerger()}
         * @see {@link Fn.ignoringMerger()}
         */
        static  ResultExtractor> toMap(final RowMapper keyExtractor, final RowMapper valueExtractor,
                final BinaryOperator mergeFunction) {
            return toMap(keyExtractor, valueExtractor, mergeFunction, Suppliers. ofMap());
        }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param 
         * @param keyExtractor
         * @param valueExtractor
         * @param mergeFunction
         * @param supplier
         * @return
         * @see {@link Fn.throwingMerger()}
         * @see {@link Fn.replacingMerger()}
         * @see {@link Fn.ignoringMerger()}
         */
        static > ResultExtractor toMap(final RowMapper keyExtractor, final RowMapper valueExtractor,
                final BinaryOperator mergeFunction, final Supplier supplier) {
            N.checkArgNotNull(keyExtractor, "keyExtractor");
            N.checkArgNotNull(valueExtractor, "valueExtractor");
            N.checkArgNotNull(mergeFunction, "mergeFunction");
            N.checkArgNotNull(supplier, "supplier");

            return rs -> {
                final M result = supplier.get();

                while (rs.next()) {
                    Jdbc.merge(result, keyExtractor.apply(rs), valueExtractor.apply(rs), mergeFunction);
                }

                return result;
            };
        }

        /**
         *
         *
         * @param  the key type
         * @param  the value type
         * @param 
         * @param keyExtractor
         * @param valueExtractor
         * @param downstream
         * @return
         * @see #groupTo(RowMapper, RowMapper, Collector)
         * @deprecated replaced by {@code groupTo(RowMapper, RowMapper, Collector)}
         */
        @Deprecated
        static  ResultExtractor> toMap(final RowMapper keyExtractor, final RowMapper valueExtractor,
                final Collector downstream) {
            return toMap(keyExtractor, valueExtractor, downstream, Suppliers. ofMap());
        }

        /**
         *
         *
         * @param  the key type
         * @param  the value type
         * @param 
         * @param 
         * @param keyExtractor
         * @param valueExtractor
         * @param downstream
         * @param supplier
         * @return
         * @see #groupTo(RowMapper, RowMapper, Collector, Supplier)
         * @deprecated replaced by {@code groupTo(RowMapper, RowMapper, Collector, Supplier)}
         */
        @Deprecated
        static > ResultExtractor toMap(final RowMapper keyExtractor, final RowMapper valueExtractor,
                final Collector downstream, final Supplier supplier) {
            return groupTo(keyExtractor, valueExtractor, downstream, supplier);
        }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param keyExtractor
         * @param valueExtractor
         * @return
         */
        static  ResultExtractor> toMultimap(final RowMapper keyExtractor, final RowMapper valueExtractor) {
            return toMultimap(keyExtractor, valueExtractor, Suppliers. ofListMultimap());
        }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param 
         * @param 
         * @param keyExtractor
         * @param valueExtractor
         * @param multimapSupplier
         * @return
         */
        static , M extends Multimap> ResultExtractor toMultimap(final RowMapper keyExtractor,
                final RowMapper valueExtractor, final Supplier multimapSupplier) {
            N.checkArgNotNull(keyExtractor, "keyExtractor");
            N.checkArgNotNull(valueExtractor, "valueExtractor");
            N.checkArgNotNull(multimapSupplier, "multimapSupplier");

            return rs -> {
                final M result = multimapSupplier.get();

                while (rs.next()) {
                    result.put(keyExtractor.apply(rs), valueExtractor.apply(rs));
                }

                return result;
            };
        }

        /**
         *
         *
         * @param  the key type
         * @param  the value type
         * @param keyExtractor
         * @param valueExtractor
         * @return
         */
        static  ResultExtractor>> groupTo(final RowMapper keyExtractor, final RowMapper valueExtractor) {
            return groupTo(keyExtractor, valueExtractor, Suppliers.> ofMap());
        }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param 
         * @param keyExtractor
         * @param valueExtractor
         * @param supplier
         * @return
         */
        static >> ResultExtractor groupTo(final RowMapper keyExtractor,
                final RowMapper valueExtractor, final Supplier supplier) {
            N.checkArgNotNull(keyExtractor, "keyExtractor");
            N.checkArgNotNull(valueExtractor, "valueExtractor");
            N.checkArgNotNull(supplier, "supplier");

            return rs -> {
                final M result = supplier.get();
                K key = null;
                List value = null;

                while (rs.next()) {
                    key = keyExtractor.apply(rs);
                    value = result.get(key);

                    if (value == null) {
                        value = new ArrayList<>();
                        result.put(key, value);
                    }

                    value.add(valueExtractor.apply(rs));
                }

                return result;
            };
        }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param 
         * @param keyExtractor
         * @param valueExtractor
         * @param downstream
         * @return
         */
        static  ResultExtractor> groupTo(final RowMapper keyExtractor, final RowMapper valueExtractor,
                final Collector downstream) {
            return groupTo(keyExtractor, valueExtractor, downstream, Suppliers. ofMap());
        }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param 
         * @param 
         * @param keyExtractor
         * @param valueExtractor
         * @param downstream
         * @param supplier
         * @return
         */
        static > ResultExtractor groupTo(final RowMapper keyExtractor, final RowMapper valueExtractor,
                final Collector downstream, final Supplier supplier) {
            N.checkArgNotNull(keyExtractor, "keyExtractor");
            N.checkArgNotNull(valueExtractor, "valueExtractor");
            N.checkArgNotNull(downstream, "downstream");
            N.checkArgNotNull(supplier, "supplier");

            return rs -> {
                final Supplier downstreamSupplier = (Supplier) downstream.supplier();
                final BiConsumer downstreamAccumulator = (BiConsumer) downstream.accumulator();
                final Function downstreamFinisher = (Function) downstream.finisher();

                final M result = supplier.get();
                final Map tmp = (Map) result;
                K key = null;
                Object container = null;

                while (rs.next()) {
                    key = keyExtractor.apply(rs);
                    container = tmp.get(key);

                    if (container == null) {
                        container = downstreamSupplier.get();
                        tmp.put(key, container);
                    }

                    downstreamAccumulator.accept(container, valueExtractor.apply(rs));
                }

                for (Map.Entry entry : result.entrySet()) {
                    entry.setValue(downstreamFinisher.apply(entry.getValue()));
                }

                return result;
            };
        }

        /**
         *
         *
         * @param 
         * @param rowMapper
         * @return
         */
        static  ResultExtractor> toList(final RowMapper rowMapper) {
            return toList(RowFilter.ALWAYS_TRUE, rowMapper);
        }

        /**
         *
         *
         * @param 
         * @param rowFilter
         * @param rowMapper
         * @return
         */
        static  ResultExtractor> toList(final RowFilter rowFilter, RowMapper rowMapper) {
            N.checkArgNotNull(rowFilter, "rowFilter");
            N.checkArgNotNull(rowMapper, "rowMapper");

            return rs -> {
                final List result = new ArrayList<>();

                while (rs.next()) {
                    if (rowFilter.test(rs)) {
                        result.add(rowMapper.apply(rs));
                    }
                }

                return result;
            };
        }

        /**
         * @param 
         * @param targetClass
         * @return
         */
        static  ResultExtractor> toList(final Class targetClass) {
            N.checkArgNotNull(targetClass, "targetClass");

            return rs -> {
                final BiRowMapper rowMapper = BiRowMapper.to(targetClass);
                final List columnLabels = JdbcUtil.getColumnLabelList(rs);
                final List result = new ArrayList<>();

                while (rs.next()) {
                    result.add(rowMapper.apply(rs, columnLabels));
                }

                return result;
            };

        }

        /**
         * @param 
         * @param targetClass
         * @return
         * @see DataSet#toMergedEntities(Class)
         */
        static  ResultExtractor> toMergedList(final Class targetClass) {
            N.checkArgNotNull(targetClass, "targetClass");

            return rs -> {
                final RowExtractor rowExtractor = RowExtractor.createBy(targetClass);

                return JdbcUtil.extractData(rs, 0, Integer.MAX_VALUE, rowExtractor, false).toMergedEntities(targetClass);
            };
        }

        /**
         * @param 
         * @param targetClass
         * @param idPropNameForMerge
         * @return
         * @see DataSet#toMergedEntities(Collection, Collection, Class)
         */
        static  ResultExtractor> toMergedList(final Class targetClass, final String idPropNameForMerge) {
            N.checkArgNotNull(targetClass, "targetClass");

            return rs -> {
                final RowExtractor rowExtractor = RowExtractor.createBy(targetClass);

                return JdbcUtil.extractData(rs, 0, Integer.MAX_VALUE, rowExtractor, false).toMergedEntities(idPropNameForMerge, targetClass);
            };
        }

        /**
         * @param 
         * @param targetClass
         * @param idPropNamesForMerge
         * @return
         * @see DataSet#toMergedEntities(Collection, Collection, Class)
         */
        static  ResultExtractor> toMergedList(final Class targetClass, Collection idPropNamesForMerge) {
            N.checkArgNotNull(targetClass, "targetClass");

            return rs -> {
                final RowExtractor rowExtractor = RowExtractor.createBy(targetClass);

                return JdbcUtil.extractData(rs, 0, Integer.MAX_VALUE, rowExtractor, false).toMergedEntities(idPropNamesForMerge, targetClass);
            };
        }

        /**
         * @param entityClass
         * @return
         */
        static ResultExtractor toDataSet(final Class entityClass) {
            return rs -> JdbcUtil.extractData(rs, RowExtractor.createBy(entityClass));
        }

        /**
         *
         *
         * @param entityClass
         * @param prefixAndFieldNameMap
         * @return
         */
        static ResultExtractor toDataSet(final Class entityClass, final Map prefixAndFieldNameMap) {
            return rs -> JdbcUtil.extractData(rs, RowExtractor.createBy(entityClass, prefixAndFieldNameMap));
        }

        /**
         *
         *
         * @param rowFilter
         * @return
         */
        static ResultExtractor toDataSet(final RowFilter rowFilter) {
            return rs -> JdbcUtil.extractData(rs, rowFilter);
        }

        /**
         *
         *
         * @param rowExtractor
         * @return
         */
        static ResultExtractor toDataSet(final RowExtractor rowExtractor) {
            return rs -> JdbcUtil.extractData(rs, rowExtractor);
        }

        /**
         *
         *
         * @param rowFilter
         * @param rowExtractor
         * @return
         */
        static ResultExtractor toDataSet(final RowFilter rowFilter, final RowExtractor rowExtractor) {
            return rs -> JdbcUtil.extractData(rs, 0, Integer.MAX_VALUE, rowFilter, rowExtractor, false);
        }

        /**
         *
         *
         * @param 
         * @param after
         * @return
         */
        static  ResultExtractor to(final Throwables.Function after) {
            return rs -> after.apply(TO_DATA_SET.apply(rs));
        }
    }

    /**
     * The Interface BiResultExtractor.
     *
     * @param 
     */
    @FunctionalInterface
    public interface BiResultExtractor extends Throwables.BiFunction, T, SQLException> {

        BiResultExtractor TO_DATA_SET = (rs, columnLabels) -> {
            if (rs == null) {
                return N.newEmptyDataSet();
            }

            return JdbcUtil.extractData(rs);
        };

        /**
         * In a lot of scenarios, including PreparedQuery/Dao/SQLExecutor, the input {@code ResultSet} will be closed after {@code apply(rs)} call. So don't save/return the input {@code ResultSet}.
         *
         * @param rs
         * @param columnLabels
         * @return
         * @throws SQLException
         */
        @Override
        T apply(ResultSet rs, List columnLabels) throws SQLException;

        /**
         *
         *
         * @param 
         * @param after
         * @return
         */
        default  BiResultExtractor andThen(final Throwables.Function after) {
            N.checkArgNotNull(after);

            return (rs, columnLabels) -> after.apply(apply(rs, columnLabels));
        }

        //    /**
        //     *
        //     *
        //     * @param 
        //     * @param resultExtractor
        //     * @return
        //     */
        //    static  BiResultExtractor from(final ResultExtractor resultExtractor) {
        //        N.checkArgNotNull(resultExtractor);
        //
        //        return (rs, columnLabels) -> resultExtractor.apply(rs);
        //    }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param keyExtractor
         * @param valueExtractor
         * @return
         */
        static  BiResultExtractor> toMap(final BiRowMapper keyExtractor, final BiRowMapper valueExtractor) {
            return toMap(keyExtractor, valueExtractor, Suppliers. ofMap());
        }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param 
         * @param keyExtractor
         * @param valueExtractor
         * @param supplier
         * @return
         */
        static > BiResultExtractor toMap(final BiRowMapper keyExtractor,
                final BiRowMapper valueExtractor, final Supplier supplier) {
            return toMap(keyExtractor, valueExtractor, Fn. throwingMerger(), supplier);
        }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param keyExtractor
         * @param valueExtractor
         * @param mergeFunction
         * @return
         * @see {@link Fn.throwingMerger()}
         * @see {@link Fn.replacingMerger()}
         * @see {@link Fn.ignoringMerger()}
         */
        static  BiResultExtractor> toMap(final BiRowMapper keyExtractor, final BiRowMapper valueExtractor,
                final BinaryOperator mergeFunction) {
            return toMap(keyExtractor, valueExtractor, mergeFunction, Suppliers. ofMap());
        }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param 
         * @param keyExtractor
         * @param valueExtractor
         * @param mergeFunction
         * @param supplier
         * @return
         * @see {@link Fn.throwingMerger()}
         * @see {@link Fn.replacingMerger()}
         * @see {@link Fn.ignoringMerger()}
         */
        static > BiResultExtractor toMap(final BiRowMapper keyExtractor,
                final BiRowMapper valueExtractor, final BinaryOperator mergeFunction, final Supplier supplier) {
            N.checkArgNotNull(keyExtractor, "keyExtractor");
            N.checkArgNotNull(valueExtractor, "valueExtractor");
            N.checkArgNotNull(mergeFunction, "mergeFunction");
            N.checkArgNotNull(supplier, "supplier");

            return (rs, columnLabels) -> {
                final M result = supplier.get();

                while (rs.next()) {
                    Jdbc.merge(result, keyExtractor.apply(rs, columnLabels), valueExtractor.apply(rs, columnLabels), mergeFunction);
                }

                return result;
            };
        }

        /**
         *
         *
         * @param  the key type
         * @param  the value type
         * @param 
         * @param keyExtractor
         * @param valueExtractor
         * @param downstream
         * @return
         * @see #groupTo(BiRowMapper, BiRowMapper, Collector)
         * @deprecated replaced by {@code groupTo(BiRowMapper, BiRowMapper, Collector)}
         */
        @Deprecated
        static  BiResultExtractor> toMap(final BiRowMapper keyExtractor, final BiRowMapper valueExtractor,
                final Collector downstream) {
            return toMap(keyExtractor, valueExtractor, downstream, Suppliers. ofMap());
        }

        /**
         *
         *
         * @param  the key type
         * @param  the value type
         * @param 
         * @param 
         * @param keyExtractor
         * @param valueExtractor
         * @param downstream
         * @param supplier
         * @return
         * @see #groupTo(BiRowMapper, BiRowMapper, Collector, Supplier)
         * @deprecated replaced by {@code groupTo(BiRowMapper, BiRowMapper, Collector, Supplier)}
         */
        @Deprecated
        static > BiResultExtractor toMap(final BiRowMapper keyExtractor,
                final BiRowMapper valueExtractor, final Collector downstream, final Supplier supplier) {
            return groupTo(keyExtractor, valueExtractor, downstream, supplier);
        }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param keyExtractor
         * @param valueExtractor
         * @return
         */
        static  BiResultExtractor> toMultimap(final BiRowMapper keyExtractor,
                final BiRowMapper valueExtractor) {
            return toMultimap(keyExtractor, valueExtractor, Suppliers. ofListMultimap());
        }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param 
         * @param 
         * @param keyExtractor
         * @param valueExtractor
         * @param multimapSupplier
         * @return
         */
        static , M extends Multimap> BiResultExtractor toMultimap(final BiRowMapper keyExtractor,
                final BiRowMapper valueExtractor, final Supplier multimapSupplier) {
            N.checkArgNotNull(keyExtractor, "keyExtractor");
            N.checkArgNotNull(valueExtractor, "valueExtractor");
            N.checkArgNotNull(multimapSupplier, "multimapSupplier");

            return (rs, columnLabels) -> {
                final M result = multimapSupplier.get();

                while (rs.next()) {
                    result.put(keyExtractor.apply(rs, columnLabels), valueExtractor.apply(rs, columnLabels));
                }

                return result;
            };
        }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param keyExtractor
         * @param valueExtractor
         * @return
         */
        static  BiResultExtractor>> groupTo(final BiRowMapper keyExtractor, final BiRowMapper valueExtractor) {
            return groupTo(keyExtractor, valueExtractor, Suppliers.> ofMap());
        }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param 
         * @param keyExtractor
         * @param valueExtractor
         * @param supplier
         * @return
         */
        static >> BiResultExtractor groupTo(final BiRowMapper keyExtractor,
                final BiRowMapper valueExtractor, final Supplier supplier) {
            N.checkArgNotNull(keyExtractor, "keyExtractor");
            N.checkArgNotNull(valueExtractor, "valueExtractor");
            N.checkArgNotNull(supplier, "supplier");

            return (rs, columnLabels) -> {
                final M result = supplier.get();
                K key = null;
                List value = null;

                while (rs.next()) {
                    key = keyExtractor.apply(rs, columnLabels);
                    value = result.get(key);

                    if (value == null) {
                        value = new ArrayList<>();
                        result.put(key, value);
                    }

                    value.add(valueExtractor.apply(rs, columnLabels));
                }

                return result;
            };
        }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param 
         * @param keyExtractor
         * @param valueExtractor
         * @param downstream
         * @return
         */
        static  BiResultExtractor> groupTo(final BiRowMapper keyExtractor, final BiRowMapper valueExtractor,
                final Collector downstream) {
            return groupTo(keyExtractor, valueExtractor, downstream, Suppliers. ofMap());
        }

        /**
         *
         * @param  the key type
         * @param  the value type
         * @param 
         * @param 
         * @param keyExtractor
         * @param valueExtractor
         * @param downstream
         * @param supplier
         * @return
         */
        static > BiResultExtractor groupTo(final BiRowMapper keyExtractor,
                final BiRowMapper valueExtractor, final Collector downstream, final Supplier supplier) {
            N.checkArgNotNull(keyExtractor, "keyExtractor");
            N.checkArgNotNull(valueExtractor, "valueExtractor");
            N.checkArgNotNull(downstream, "downstream");
            N.checkArgNotNull(supplier, "supplier");

            return (rs, columnLabels) -> {

                final Supplier downstreamSupplier = (Supplier) downstream.supplier();
                final BiConsumer downstreamAccumulator = (BiConsumer) downstream.accumulator();
                final Function downstreamFinisher = (Function) downstream.finisher();

                final M result = supplier.get();
                final Map tmp = (Map) result;
                K key = null;
                Object container = null;

                while (rs.next()) {
                    key = keyExtractor.apply(rs, columnLabels);
                    container = tmp.get(key);

                    if (container == null) {
                        container = downstreamSupplier.get();
                        tmp.put(key, container);
                    }

                    downstreamAccumulator.accept(container, valueExtractor.apply(rs, columnLabels));
                }

                for (Map.Entry entry : result.entrySet()) {
                    entry.setValue(downstreamFinisher.apply(entry.getValue()));
                }

                return result;
            };
        }

        /**
         *
         *
         * @param 
         * @param rowMapper
         * @return
         */
        static  BiResultExtractor> toList(final BiRowMapper rowMapper) {
            return toList(BiRowFilter.ALWAYS_TRUE, rowMapper);
        }

        /**
         *
         *
         * @param 
         * @param rowFilter
         * @param rowMapper
         * @return
         */
        static  BiResultExtractor> toList(final BiRowFilter rowFilter, final BiRowMapper rowMapper) {
            N.checkArgNotNull(rowFilter, "rowFilter");
            N.checkArgNotNull(rowMapper, "rowMapper");

            return (rs, columnLabels) -> {
                final List result = new ArrayList<>();

                while (rs.next()) {
                    if (rowFilter.test(rs, columnLabels)) {
                        result.add(rowMapper.apply(rs, columnLabels));
                    }
                }

                return result;
            };
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param 
         * @param targetClass
         * @return
         */
        @SequentialOnly
        static  BiResultExtractor> toList(final Class targetClass) {
            N.checkArgNotNull(targetClass, "targetClass");

            return (rs, columnLabels) -> {
                final BiRowMapper rowMapper = BiRowMapper.to(targetClass);
                final List result = new ArrayList<>();

                while (rs.next()) {
                    result.add(rowMapper.apply(rs, columnLabels));
                }

                return result;
            };
        }
    }

    /**
     * Don't use {@code RowMapper} in {@link PreparedQuery#list(RowMapper)} or any place where multiple records will be retrieved by it, if column labels/count are used in {@link RowMapper#apply(ResultSet)}.
     * Consider using {@code BiRowMapper} instead because it's more efficient to retrieve multiple records when column labels/count are used.
     *
     * @param 
     * @see Columns.ColumnOne
     * @see Columns.ColumnTwo
     * @see Columns.ColumnThree
     */
    @FunctionalInterface
    public interface RowMapper extends Throwables.Function {

        /**
         *
         *
         * @param rs
         * @return
         * @throws SQLException
         */
        @Override
        T apply(ResultSet rs) throws SQLException;

        /**
         *
         *
         * @param 
         * @param after
         * @return
         */
        default  RowMapper andThen(final Throwables.Function after) {
            N.checkArgNotNull(after);

            return rs -> after.apply(apply(rs));
        }

        /**
         *
         *
         * @return
         */
        default BiRowMapper toBiRowMapper() {
            return (rs, columnLabels) -> this.apply(rs);
        }

        //    /**
        //     * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
        //     *
        //     * @param 
        //     * @param biRowMapper
        //     * @return
        //     * @deprecated because it's stateful and may be misused easily and frequently
        //     */
        //    @Beta
        //    @Deprecated
        //    @SequentialOnly
        //    @Stateful
        //    static  RowMapper from(final BiRowMapper biRowMapper) {
        //        N.checkArgNotNull(biRowMapper, "biRowMapper");
        //
        //        return new RowMapper<>() {
        //            private List cls = null;
        //
        //            @Override
        //            public T apply(ResultSet rs) throws SQLException {
        //                if (cls == null) {
        //                    cls = JdbcUtil.getColumnLabelList(rs);
        //                }
        //
        //                return biRowMapper.apply(rs, cls);
        //            }
        //        };
        //    }

        /**
         *
         *
         * @param 
         * @param 
         * @param rowMapper1
         * @param rowMapper2
         * @return
         */
        static  RowMapper> combine(final RowMapper rowMapper1, final RowMapper rowMapper2) {
            N.checkArgNotNull(rowMapper1, "rowMapper1");
            N.checkArgNotNull(rowMapper2, "rowMapper2");

            return rs -> Tuple.of(rowMapper1.apply(rs), rowMapper2.apply(rs));
        }

        /**
         *
         *
         * @param 
         * @param 
         * @param 
         * @param rowMapper1
         * @param rowMapper2
         * @param rowMapper3
         * @return
         */
        static  RowMapper> combine(final RowMapper rowMapper1, final RowMapper rowMapper2,
                final RowMapper rowMapper3) {
            N.checkArgNotNull(rowMapper1, "rowMapper1");
            N.checkArgNotNull(rowMapper2, "rowMapper2");
            N.checkArgNotNull(rowMapper3, "rowMapper3");

            return rs -> Tuple.of(rowMapper1.apply(rs), rowMapper2.apply(rs), rowMapper3.apply(rs));
        }

        /**
         *
         *
         * @param columnGetterForAll
         * @return
         */
        @Beta
        @SequentialOnly
        @Stateful
        static RowMapper toArray(final ColumnGetter columnGetterForAll) {
            return new RowMapper<>() {
                private int columnCount = -1;

                @Override
                public Object[] apply(final ResultSet rs) throws SQLException {
                    if (columnCount < 0) {
                        columnCount = JdbcUtil.getColumnCount(rs);
                    }

                    final Object[] result = new Object[columnCount];

                    for (int i = 0; i < columnCount; i++) {
                        result[i] = columnGetterForAll.apply(rs, i + 1);
                    }

                    return result;
                }
            };
        }

        /**
         *
         *
         * @param columnGetterForAll
         * @return
         */
        @Beta
        @SequentialOnly
        @Stateful
        static RowMapper> toList(final ColumnGetter columnGetterForAll) {
            return toCollection(columnGetterForAll, Factory. ofList());
        }

        /**
         *
         *
         * @param 
         * @param columnGetterForAll
         * @param supplier
         * @return
         */
        @Beta
        @SequentialOnly
        @Stateful
        static > RowMapper toCollection(final ColumnGetter columnGetterForAll, final IntFunction supplier) {
            return new RowMapper<>() {
                private int columnCount = -1;

                @Override
                public C apply(final ResultSet rs) throws SQLException {
                    if (columnCount < 0) {
                        columnCount = JdbcUtil.getColumnCount(rs);
                    }

                    final Collection result = (Collection) supplier.apply(columnCount);

                    for (int i = 0; i < columnCount; i++) {
                        result.add(columnGetterForAll.apply(rs, i + 1));
                    }

                    return (C) result;
                }
            };
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @return
         */
        @Beta
        @SequentialOnly
        @Stateful
        static RowMapper toDisposableObjArray() {
            return new RowMapper<>() {
                private DisposableObjArray disposable = null;
                private int columnCount = -1;
                private Object[] output = null;

                @Override
                public DisposableObjArray apply(final ResultSet rs) throws SQLException {
                    if (disposable == null) {
                        columnCount = JdbcUtil.getColumnCount(rs);
                        output = new Object[columnCount];
                        disposable = DisposableObjArray.wrap(output);
                    }

                    for (int i = 0; i < columnCount; i++) {
                        output[i] = JdbcUtil.getColumnValue(rs, i + 1);
                    }

                    return disposable;
                }
            };
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param entityClass used to fetch column/row value from {@code ResultSet} by the type of fields/columns defined in this class.
         * @return
         */
        @Beta
        @SequentialOnly
        @Stateful
        static RowMapper toDisposableObjArray(final Class entityClass) {
            N.checkArgNotNull(entityClass, "entityClass");

            return new RowMapper<>() {
                private DisposableObjArray disposable = null;
                private int columnCount = -1;
                private Object[] output = null;

                private Type[] columnTypes = null;

                @Override
                public DisposableObjArray apply(final ResultSet rs) throws SQLException {
                    if (disposable == null) {
                        final List columnLabels = JdbcUtil.getColumnLabelList(rs);

                        columnCount = columnLabels.size();
                        columnTypes = new Type[columnCount];

                        final BeanInfo entityInfo = ParserUtil.getBeanInfo(entityClass);
                        final Map column2FieldNameMap = JdbcUtil.getColumn2FieldNameMap(entityClass);
                        PropInfo propInfo = null;

                        for (int i = 0; i < columnCount; i++) {
                            propInfo = entityInfo.getPropInfo(columnLabels.get(i));

                            if (propInfo == null) {
                                String fieldName = column2FieldNameMap.get(columnLabels.get(i));

                                if (Strings.isEmpty(fieldName)) {
                                    fieldName = column2FieldNameMap.get(columnLabels.get(i).toLowerCase());
                                }

                                if (Strings.isNotEmpty(fieldName)) {
                                    propInfo = entityInfo.getPropInfo(fieldName);
                                }
                            }

                            if (propInfo == null) {
                                //    throw new IllegalArgumentException(
                                //            "No property in class: " + ClassUtil.getCanonicalClassName(entityClass) + " mapping to column: " + columnLabels.get(i));
                            } else {
                                columnTypes[i] = propInfo.dbType;
                            }
                        }

                        output = new Object[columnCount];
                        disposable = DisposableObjArray.wrap(output);
                    }

                    for (int i = 0; i < columnCount; i++) {
                        output[i] = columnTypes[i] == null ? JdbcUtil.getColumnValue(rs, i + 1) : columnTypes[i].get(rs, i + 1);
                    }

                    return disposable;
                }
            };
        }

        /**
         *
         *
         * @return
         */
        static RowMapperBuilder builder() {
            return builder(ColumnGetter.GET_OBJECT);
        }

        /**
         *
         *
         * @param defaultColumnGetter
         * @return
         */
        static RowMapperBuilder builder(final ColumnGetter defaultColumnGetter) {
            return new RowMapperBuilder(defaultColumnGetter);
        }

        //    static RowMapperBuilder builder(final int columnCount) {
        //        return new RowMapperBuilder(columnCount);
        //    }

        @SequentialOnly
        public static class RowMapperBuilder {
            private final Map> columnGetterMap;

            RowMapperBuilder(final ColumnGetter defaultColumnGetter) {
                N.checkArgNotNull(defaultColumnGetter, "defaultColumnGetter");

                columnGetterMap = new HashMap<>(9);
                columnGetterMap.put(0, defaultColumnGetter);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowMapperBuilder getBoolean(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_BOOLEAN);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowMapperBuilder getByte(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_BYTE);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowMapperBuilder getShort(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_SHORT);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowMapperBuilder getInt(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_INT);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowMapperBuilder getLong(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_LONG);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowMapperBuilder getFloat(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_FLOAT);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowMapperBuilder getDouble(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_DOUBLE);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowMapperBuilder getBigDecimal(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_BIG_DECIMAL);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowMapperBuilder getString(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_STRING);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowMapperBuilder getDate(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_DATE);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowMapperBuilder getTime(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_TIME);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowMapperBuilder getTimestamp(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_TIMESTAMP);
            }

            /**
             *
             * @param columnIndex
             * @return
             * @deprecated default {@link #getObject(int)} if there is no {@code ColumnGetter} set for the target column
             */
            @Deprecated
            public RowMapperBuilder getObject(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_OBJECT);
            }

            /**
             *
             *
             * @param columnIndex
             * @param type
             * @return
             */
            public RowMapperBuilder getObject(final int columnIndex, Class type) {
                return get(columnIndex, ColumnGetter.get(type));
            }

            /**
             *
             *
             * @param columnIndex
             * @param columnGetter
             * @return
             */
            public RowMapperBuilder get(final int columnIndex, final ColumnGetter columnGetter) {
                N.checkArgPositive(columnIndex, "columnIndex");
                N.checkArgNotNull(columnGetter, "columnGetter");

                //        if (columnGetters == null) {
                //            columnGetterMap.put(columnIndex, columnGetter);
                //        } else {
                //            columnGetters[columnIndex] = columnGetter;
                //        }

                columnGetterMap.put(columnIndex, columnGetter);
                return this;
            }

            /**
             *
             * Set column getter function for column[columnIndex].
             *
             * @param columnIndex start from 1.
             * @param columnGetter
             * @return
             * @deprecated replaced by {@link #get(int, ColumnGetter)}
             */
            @Deprecated
            public RowMapperBuilder column(final int columnIndex, final ColumnGetter columnGetter) {
                return get(columnIndex, columnGetter);
            }

            //    /**
            //     * Set default column getter function.
            //     *
            //     * @param columnGetter
            //     * @return
            //     */
            //    public RowMapperBuilder __(final ColumnGetter columnGetter) {
            //        if (columnGetters == null) {
            //            columnGetterMap.put(0, columnGetter);
            //        } else {
            //            columnGetters[0] = columnGetter;
            //        }
            //
            //        return this;
            //    }
            //
            //    /**
            //     *
            //     * Set column getter function for column[columnIndex].
            //     *
            //     * @param columnIndex start from 1.
            //     * @param columnGetter
            //     * @return
            //     */
            //    public RowMapperBuilder __(final int columnIndex, final ColumnGetter columnGetter) {
            //        if (columnGetters == null) {
            //            columnGetterMap.put(columnIndex, columnGetter);
            //        } else {
            //            columnGetters[columnIndex] = columnGetter;
            //        }
            //
            //        return this;
            //    }
            //
            //    /**
            //     * Set column getter function for column[1].
            //     *
            //     * @param columnGetter
            //     * @return
            //     */
            //    public RowMapperBuilder _1(final ColumnGetter columnGetter) {
            //        if (columnGetters == null) {
            //            columnGetterMap.put(1, columnGetter);
            //        } else {
            //            columnGetters[1] = columnGetter;
            //        }
            //
            //        return this;
            //    }
            //
            //    /**
            //     *
            //     * Set column getter function for column[1].
            //     *
            //     * @param columnGetter
            //     * @return
            //     */
            //    public RowMapperBuilder _2(final ColumnGetter columnGetter) {
            //        if (columnGetters == null) {
            //            columnGetterMap.put(2, columnGetter);
            //        } else {
            //            columnGetters[2] = columnGetter;
            //        }
            //
            //        return this;
            //    }
            //
            //    /**
            //     *
            //     * Set column getter function for column[3].
            //     *
            //     * @param columnGetter
            //     * @return
            //     */
            //    public RowMapperBuilder _3(final ColumnGetter columnGetter) {
            //        if (columnGetters == null) {
            //            columnGetterMap.put(3, columnGetter);
            //        } else {
            //            columnGetters[3] = columnGetter;
            //        }
            //
            //        return this;
            //    }
            //
            //    /**
            //     *
            //     * Set column getter function for column[4].
            //     *
            //     * @param columnGetter
            //     * @return
            //     */
            //    public RowMapperBuilder _4(final ColumnGetter columnGetter) {
            //        if (columnGetters == null) {
            //            columnGetterMap.put(4, columnGetter);
            //        } else {
            //            columnGetters[4] = columnGetter;
            //        }
            //
            //        return this;
            //    }
            //
            //    /**
            //     *
            //     * Set column getter function for column[5].
            //     *
            //     * @param columnGetter
            //     * @return
            //     */
            //    public RowMapperBuilder _5(final ColumnGetter columnGetter) {
            //        if (columnGetters == null) {
            //            columnGetterMap.put(5, columnGetter);
            //        } else {
            //            columnGetters[5] = columnGetter;
            //        }
            //
            //        return this;
            //    }
            //
            //    /**
            //     *
            //     * Set column getter function for column[6].
            //     *
            //     * @param columnGetter
            //     * @return
            //     */
            //    public RowMapperBuilder _6(final ColumnGetter columnGetter) {
            //        if (columnGetters == null) {
            //            columnGetterMap.put(6, columnGetter);
            //        } else {
            //            columnGetters[6] = columnGetter;
            //        }
            //
            //        return this;
            //    }
            //
            //    /**
            //     *
            //     * Set column getter function for column[7].
            //     *
            //     * @param columnGetter
            //     * @return
            //     */
            //    public RowMapperBuilder _7(final ColumnGetter columnGetter) {
            //        if (columnGetters == null) {
            //            columnGetterMap.put(7, columnGetter);
            //        } else {
            //            columnGetters[7] = columnGetter;
            //        }
            //
            //        return this;
            //    }
            //
            //    /**
            //     *
            //     * Set column getter function for column[8].
            //     *
            //     * @param columnGetter
            //     * @return
            //     */
            //    public RowMapperBuilder _8(final ColumnGetter columnGetter) {
            //        if (columnGetters == null) {
            //            columnGetterMap.put(8, columnGetter);
            //        } else {
            //            columnGetters[8] = columnGetter;
            //        }
            //
            //        return this;
            //    }
            //
            //    /**
            //     *
            //     * Set column getter function for column[9].
            //     *
            //     * @param columnGetter
            //     * @return
            //     */
            //    public RowMapperBuilder _9(final ColumnGetter columnGetter) {
            //        if (columnGetters == null) {
            //            columnGetterMap.put(9, columnGetter);
            //        } else {
            //            columnGetters[9] = columnGetter;
            //        }
            //
            //        return this;
            //    }

            //    void setDefaultColumnGetter() {
            //        if (columnGetters != null) {
            //            for (int i = 1, len = columnGetters.length; i < len; i++) {
            //                if (columnGetters[i] == null) {
            //                    columnGetters[i] = columnGetters[0];
            //                }
            //            }
            //        }
            //    }

            ColumnGetter[] initColumnGetter(ResultSet rs) throws SQLException { //NOSONAR
                return initColumnGetter(rs.getMetaData().getColumnCount());
            }

            ColumnGetter[] initColumnGetter(final int columnCount) { //NOSONAR
                final ColumnGetter[] rsColumnGetters = new ColumnGetter[columnCount];
                final ColumnGetter defaultColumnGetter = columnGetterMap.get(0);

                for (int i = 0, len = rsColumnGetters.length; i < len; i++) {
                    rsColumnGetters[i] = columnGetterMap.getOrDefault(i + 1, defaultColumnGetter);
                }

                return rsColumnGetters;
            }

            /**
             * Don't cache or reuse the returned {@code RowMapper} instance.
             *
             * @return
             */
            @SequentialOnly
            @Stateful
            public RowMapper toArray() {
                // setDefaultColumnGetter();

                return new RowMapper<>() {
                    private ColumnGetter[] rsColumnGetters = null;
                    private int rsColumnCount = -1;

                    @Override
                    public Object[] apply(ResultSet rs) throws SQLException {
                        if (rsColumnGetters == null) {
                            rsColumnGetters = initColumnGetter(rs);
                            rsColumnCount = rsColumnGetters.length - 1;
                        }

                        final Object[] row = new Object[rsColumnCount];

                        for (int i = 0; i < rsColumnCount; i++) {
                            row[i] = rsColumnGetters[i].apply(rs, i + 1);
                        }

                        return row;
                    }
                };
            }

            /**
             * Don't cache or reuse the returned {@code RowMapper} instance.
             *
             * @return
             */
            @SequentialOnly
            @Stateful
            public RowMapper> toList() {
                // setDefaultColumnGetter();

                return new RowMapper<>() {
                    private ColumnGetter[] rsColumnGetters = null;
                    private int rsColumnCount = -1;

                    @Override
                    public List apply(ResultSet rs) throws SQLException {
                        if (rsColumnGetters == null) {
                            rsColumnGetters = initColumnGetter(rs);
                            rsColumnCount = rsColumnGetters.length - 1;
                        }

                        final List row = new ArrayList<>(rsColumnCount);

                        for (int i = 0; i < rsColumnCount; i++) {
                            row.add(rsColumnGetters[i].apply(rs, i + 1));
                        }

                        return row;
                    }
                };
            }

            /**
             * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
             *
             * @param 
             * @param finisher
             * @return
             */
            @SequentialOnly
            @Stateful
            public  RowMapper to(final Throwables.Function finisher) {
                return new RowMapper<>() {
                    private ColumnGetter[] rsColumnGetters = null;
                    private int rsColumnCount = -1;
                    private Object[] outputRow = null;
                    private DisposableObjArray output;

                    @Override
                    public R apply(ResultSet rs) throws SQLException {
                        if (rsColumnGetters == null) {
                            rsColumnGetters = initColumnGetter(rs);
                            this.rsColumnCount = rsColumnGetters.length - 1;
                            this.outputRow = new Object[rsColumnCount];
                            this.output = DisposableObjArray.wrap(outputRow);
                        }

                        for (int i = 0; i < rsColumnCount; i++) {
                            outputRow[i] = rsColumnGetters[i].apply(rs, i + 1);
                        }

                        return finisher.apply(output);
                    }
                };
            }
        }
    }

    /**
     * The Interface BiRowMapper.
     *
     * @param 
     */
    @FunctionalInterface
    public interface BiRowMapper extends Throwables.BiFunction, T, SQLException> {

        /** The Constant TO_ARRAY. */
        BiRowMapper TO_ARRAY = (rs, columnLabels) -> {
            final int columnCount = columnLabels.size();
            final Object[] result = new Object[columnCount];

            for (int i = 1; i <= columnCount; i++) {
                result[i - 1] = JdbcUtil.getColumnValue(rs, i);
            }

            return result;
        };

        /** The Constant TO_LIST. */
        BiRowMapper> TO_LIST = (rs, columnLabels) -> {
            final int columnCount = columnLabels.size();
            final List result = new ArrayList<>(columnCount);

            for (int i = 1; i <= columnCount; i++) {
                result.add(JdbcUtil.getColumnValue(rs, i));
            }

            return result;
        };

        /** The Constant TO_MAP. */
        BiRowMapper> TO_MAP = (rs, columnLabels) -> {
            final int columnCount = columnLabels.size();
            final Map result = N.newHashMap(columnCount);

            for (int i = 1; i <= columnCount; i++) {
                result.put(columnLabels.get(i - 1), JdbcUtil.getColumnValue(rs, i));
            }

            return result;
        };

        /** The Constant TO_LINKED_HASH_MAP. */
        BiRowMapper> TO_LINKED_HASH_MAP = (rs, columnLabels) -> {
            final int columnCount = columnLabels.size();
            final Map result = N.newLinkedHashMap(columnCount);

            for (int i = 1; i <= columnCount; i++) {
                result.put(columnLabels.get(i - 1), JdbcUtil.getColumnValue(rs, i));
            }

            return result;
        };

        BiRowMapper TO_ENTITY_ID = new BiRowMapper<>() {
            @SuppressWarnings("deprecation")
            @Override
            public EntityId apply(final ResultSet rs, final List columnLabels) throws SQLException {
                final int columnCount = columnLabels.size();
                final Seid entityId = Seid.of(Strings.EMPTY_STRING);

                for (int i = 1; i <= columnCount; i++) {
                    entityId.set(columnLabels.get(i - 1), JdbcUtil.getColumnValue(rs, i));
                }

                return entityId;
            }
        };

        /**
         *
         *
         * @param rs
         * @param columnLabels
         * @return
         * @throws SQLException
         */
        @Override
        T apply(ResultSet rs, List columnLabels) throws SQLException;

        /**
         *
         *
         * @param 
         * @param after
         * @return
         */
        default  BiRowMapper andThen(final Throwables.Function after) {
            N.checkArgNotNull(after);

            return (rs, columnLabels) -> after.apply(apply(rs, columnLabels));
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @return
         * @see RowMapper#toBiRowMapper()
         * @deprecated because it's stateful and may be misused easily and frequently
         */
        @Beta
        @Deprecated
        @Stateful
        default RowMapper toRowMapper() {
            final BiRowMapper biRowMapper = this;

            return new RowMapper<>() {
                private List cls = null;

                @Override
                public T apply(ResultSet rs) throws SQLException {
                    if (cls == null) {
                        cls = JdbcUtil.getColumnLabelList(rs);
                    }

                    return biRowMapper.apply(rs, cls);
                }
            };
        }

        //    /**
        //     *
        //     *
        //     * @param 
        //     * @param rowMapper
        //     * @return
        //     */
        //    static  BiRowMapper from(final RowMapper rowMapper) {
        //        N.checkArgNotNull(rowMapper, "rowMapper");
        //
        //        return (rs, columnLabels) -> rowMapper.apply(rs);
        //    }

        /**
         *
         *
         * @param 
         * @param 
         * @param rowMapper1
         * @param rowMapper2
         * @return
         */
        static  BiRowMapper> combine(final BiRowMapper rowMapper1, final BiRowMapper rowMapper2) {
            N.checkArgNotNull(rowMapper1, "rowMapper1");
            N.checkArgNotNull(rowMapper2, "rowMapper2");

            return (rs, cls) -> Tuple.of(rowMapper1.apply(rs, cls), rowMapper2.apply(rs, cls));
        }

        /**
         *
         *
         * @param 
         * @param 
         * @param 
         * @param rowMapper1
         * @param rowMapper2
         * @param rowMapper3
         * @return
         */
        static  BiRowMapper> combine(final BiRowMapper rowMapper1, final BiRowMapper rowMapper2,
                final BiRowMapper rowMapper3) {
            N.checkArgNotNull(rowMapper1, "rowMapper1");
            N.checkArgNotNull(rowMapper2, "rowMapper2");
            N.checkArgNotNull(rowMapper3, "rowMapper3");

            return (rs, cls) -> Tuple.of(rowMapper1.apply(rs, cls), rowMapper2.apply(rs, cls), rowMapper3.apply(rs, cls));
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param 
         * @param targetClass
         * @return
         */
        @SequentialOnly
        @Stateful
        static  BiRowMapper to(final Class targetClass) {
            return to(targetClass, false);
        }

        /**
         * Don't cache or reuse the returned {@code BiRowMapper} instance. It's stateful.
         *
         * @param 
         * @param targetClass
         * @param ignoreNonMatchedColumns
         * @return
         */
        @SequentialOnly
        @Stateful
        static  BiRowMapper to(final Class targetClass, final boolean ignoreNonMatchedColumns) {
            return to(targetClass, Fn.alwaysTrue(), Fn.identity(), ignoreNonMatchedColumns);
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param 
         * @param targetClass
         * @param columnNameFilter
         * @param columnNameConverter
         * @return
         */
        @SequentialOnly
        @Stateful
        static  BiRowMapper to(final Class targetClass, final Predicate columnNameFilter,
                final Function columnNameConverter) {
            return to(targetClass, columnNameFilter, columnNameConverter, false);
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param 
         * @param targetClass
         * @param columnNameFilter
         * @param columnNameConverter
         * @param ignoreNonMatchedColumns
         * @return
         */
        @SequentialOnly
        @Stateful
        static  BiRowMapper to(final Class targetClass, final Predicate columnNameFilter,
                final Function columnNameConverter, final boolean ignoreNonMatchedColumns) {
            N.checkArgNotNull(targetClass, "targetClass");

            final Predicate columnNameFilterToBeUsed = columnNameFilter == null ? Fn.alwaysTrue() : columnNameFilter;
            final Function columnNameConverterToBeUsed = columnNameConverter == null ? Fn.identity() : columnNameConverter;

            if (Object[].class.isAssignableFrom(targetClass)) {
                if ((columnNameFilter == null || Objects.equals(columnNameFilter, Fn.alwaysTrue()))
                        && (columnNameConverter == null || Objects.equals(columnNameConverter, Fn.identity()))) {
                    return (rs, columnLabelList) -> {
                        final int columnCount = columnLabelList.size();
                        final Object[] a = Array.newInstance(targetClass.getComponentType(), columnCount);

                        for (int i = 0; i < columnCount; i++) {
                            a[i] = JdbcUtil.getColumnValue(rs, i + 1);
                        }

                        return (T) a;
                    };
                } else {
                    return new BiRowMapper<>() {
                        private String[] columnLabels = null;
                        private int columnCount = -1;

                        @Override
                        public T apply(final ResultSet rs, final List columnLabelList) throws SQLException {
                            if (columnLabels == null) {
                                columnCount = columnLabelList.size();

                                columnLabels = columnLabelList.toArray(new String[columnCount]);

                                for (int i = 0; i < columnCount; i++) {
                                    if (columnNameFilterToBeUsed.test(columnLabels[i])) {
                                        columnLabels[i] = columnNameConverterToBeUsed.apply(columnLabels[i]);
                                    } else {
                                        columnLabels[i] = null;
                                    }
                                }
                            }

                            final Object[] a = Array.newInstance(targetClass.getComponentType(), columnCount);

                            for (int i = 0; i < columnCount; i++) {
                                if (columnLabels[i] == null) {
                                    continue;
                                }

                                a[i] = JdbcUtil.getColumnValue(rs, i + 1);
                            }

                            return (T) a;
                        }
                    };
                }
            } else if (List.class.isAssignableFrom(targetClass)) {
                if ((columnNameFilter == null || Objects.equals(columnNameFilter, Fn.alwaysTrue()))
                        && (columnNameConverter == null || Objects.equals(columnNameConverter, Fn.identity()))) {
                    return new BiRowMapper<>() {
                        @Override
                        public T apply(final ResultSet rs, final List columnLabelList) throws SQLException {
                            final int columnCount = columnLabelList.size();
                            @SuppressWarnings("rawtypes")
                            final Collection c = N.newCollection((Class) targetClass, columnCount);

                            for (int i = 0; i < columnCount; i++) {
                                c.add(JdbcUtil.getColumnValue(rs, i + 1));
                            }

                            return (T) c;
                        }
                    };
                } else {
                    return new BiRowMapper<>() {
                        private String[] columnLabels = null;

                        @Override
                        public T apply(final ResultSet rs, final List columnLabelList) throws SQLException {
                            final int columnCount = columnLabelList.size();

                            if (columnLabels == null) {
                                columnLabels = columnLabelList.toArray(new String[columnCount]);

                                for (int i = 0; i < columnCount; i++) {
                                    if (columnNameFilterToBeUsed.test(columnLabels[i])) {
                                        columnLabels[i] = columnNameConverterToBeUsed.apply(columnLabels[i]);
                                    } else {
                                        columnLabels[i] = null;
                                    }
                                }
                            }

                            @SuppressWarnings("rawtypes")
                            final Collection c = N.newCollection((Class) targetClass, columnCount);

                            for (int i = 0; i < columnCount; i++) {
                                if (columnLabels[i] == null) {
                                    continue;
                                }

                                c.add(JdbcUtil.getColumnValue(rs, i + 1));
                            }

                            return (T) c;
                        }
                    };
                }
            } else if (Map.class.isAssignableFrom(targetClass)) {
                if ((columnNameFilter == null || Objects.equals(columnNameFilter, Fn.alwaysTrue()))
                        && (columnNameConverter == null || Objects.equals(columnNameConverter, Fn.identity()))) {
                    return new BiRowMapper<>() {
                        private String[] columnLabels = null;
                        private int columnCount = -1;

                        @Override
                        public T apply(final ResultSet rs, final List columnLabelList) throws SQLException {
                            if (columnLabels == null) {
                                columnCount = columnLabelList.size();
                                columnLabels = columnLabelList.toArray(new String[columnCount]);
                            }

                            @SuppressWarnings("rawtypes")
                            final Map m = N.newMap((Class) targetClass, columnCount);

                            for (int i = 0; i < columnCount; i++) {
                                m.put(columnLabels[i], JdbcUtil.getColumnValue(rs, i + 1));
                            }

                            return (T) m;
                        }
                    };
                } else {
                    return new BiRowMapper<>() {
                        private String[] columnLabels = null;
                        private int columnCount = -1;

                        @Override
                        public T apply(final ResultSet rs, final List columnLabelList) throws SQLException {
                            if (columnLabels == null) {
                                columnCount = columnLabelList.size();
                                columnLabels = columnLabelList.toArray(new String[columnCount]);

                                for (int i = 0; i < columnCount; i++) {
                                    if (columnNameFilterToBeUsed.test(columnLabels[i])) {
                                        columnLabels[i] = columnNameConverterToBeUsed.apply(columnLabels[i]);
                                    } else {
                                        columnLabels[i] = null;
                                    }
                                }
                            }

                            @SuppressWarnings("rawtypes")
                            final Map m = N.newMap((Class) targetClass, columnCount);

                            for (int i = 0; i < columnCount; i++) {
                                if (columnLabels[i] == null) {
                                    continue;
                                }

                                m.put(columnLabels[i], JdbcUtil.getColumnValue(rs, i + 1));
                            }

                            return (T) m;
                        }
                    };
                }
            } else if (ClassUtil.isBeanClass(targetClass)) {
                final BeanInfo entityInfo = ParserUtil.getBeanInfo(targetClass);

                return new BiRowMapper<>() {
                    private String[] columnLabels = null;
                    private PropInfo[] propInfos;
                    private Type[] columnTypes = null;
                    private int columnCount = -1;

                    @Override
                    public T apply(final ResultSet rs, final List columnLabelList) throws SQLException {
                        if (columnLabels == null) {
                            final Map column2FieldNameMap = JdbcUtil.getColumn2FieldNameMap(targetClass);

                            columnCount = columnLabelList.size();
                            columnLabels = columnLabelList.toArray(new String[columnCount]);
                            propInfos = new PropInfo[columnCount];
                            columnTypes = new Type[columnCount];

                            for (int i = 0; i < columnCount; i++) {
                                if (columnNameFilterToBeUsed.test(columnLabels[i])) {
                                    columnLabels[i] = columnNameConverterToBeUsed.apply(columnLabels[i]);

                                    propInfos[i] = entityInfo.getPropInfo(columnLabels[i]);

                                    if (propInfos[i] == null) {
                                        String fieldName = column2FieldNameMap.get(columnLabels[i]);

                                        if (Strings.isEmpty(fieldName)) {
                                            fieldName = column2FieldNameMap.get(columnLabels[i].toLowerCase());
                                        }

                                        if (Strings.isNotEmpty(fieldName)) {
                                            propInfos[i] = entityInfo.getPropInfo(fieldName);
                                        }
                                    }

                                    if (propInfos[i] == null) {
                                        final String newColumnName = Jdbc.checkPrefix(entityInfo, columnLabels[i], null, columnLabelList);
                                        propInfos[i] = JdbcUtil.getSubPropInfo(targetClass, newColumnName);

                                        if (propInfos[i] == null) {
                                            String fieldName = column2FieldNameMap.get(newColumnName);

                                            if (Strings.isEmpty(fieldName)) {
                                                fieldName = column2FieldNameMap.get(newColumnName.toLowerCase());
                                            }

                                            if (Strings.isNotEmpty(fieldName)) {
                                                propInfos[i] = JdbcUtil.getSubPropInfo(targetClass, fieldName);

                                                if (propInfos[i] != null) {
                                                    columnLabels[i] = fieldName;
                                                }
                                            }
                                        } else {
                                            columnLabels[i] = newColumnName;
                                        }

                                        if (propInfos[i] == null) {
                                            if (ignoreNonMatchedColumns) {
                                                columnLabels[i] = null;
                                            } else {
                                                throw new IllegalArgumentException("No property in class: " + ClassUtil.getCanonicalClassName(targetClass)
                                                        + " mapping to column: " + columnLabels[i]);
                                            }
                                        } else {
                                            columnTypes[i] = propInfos[i].dbType;
                                            propInfos[i] = null;
                                        }
                                    } else {
                                        columnTypes[i] = propInfos[i].dbType;
                                    }
                                } else {
                                    columnLabels[i] = null;
                                    propInfos[i] = null;
                                    columnTypes[i] = null;
                                }
                            }
                        }

                        final Object result = entityInfo.createBeanResult();

                        for (int i = 0; i < columnCount; i++) {
                            if (columnLabels[i] == null) {
                                continue;
                            }

                            if (propInfos[i] == null) {
                                entityInfo.setPropValue(result, columnLabels[i], columnTypes[i].get(rs, i + 1));
                            } else {
                                propInfos[i].setPropValue(result, columnTypes[i].get(rs, i + 1));
                            }
                        }

                        return entityInfo.finishBeanResult(result);
                    }
                };
            } else {
                if ((columnNameFilter == null || Objects.equals(columnNameFilter, Fn.alwaysTrue()))
                        && (columnNameConverter == null || Objects.equals(columnNameConverter, Fn.identity()))) {
                    return new BiRowMapper<>() {
                        private final Type targetType = N.typeOf(targetClass);
                        private int columnCount = -1;

                        @Override
                        public T apply(final ResultSet rs, final List columnLabelList) throws SQLException {
                            if (columnCount != 1 && (columnCount = columnLabelList.size()) != 1) {
                                throw new IllegalArgumentException(
                                        "It's not supported to retrieve value from multiple columns: " + columnLabelList + " for type: " + targetClass);
                            }

                            return targetType.get(rs, 1);
                        }
                    };
                } else {
                    throw new IllegalArgumentException(
                            "'columnNameFilter' and 'columnNameConverter' are not supported to convert single column to target type: " + targetClass);
                }
            }
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param 
         * @param entityClass
         * @param prefixAndFieldNameMap
         * @return
         */
        @SequentialOnly
        @Stateful
        static  BiRowMapper to(final Class entityClass, final Map prefixAndFieldNameMap) {
            return to(entityClass, prefixAndFieldNameMap, false);
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param 
         * @param entityClass
         * @param prefixAndFieldNameMap
         * @param ignoreNonMatchedColumns
         * @return
         */
        @SequentialOnly
        @Stateful
        static  BiRowMapper to(final Class entityClass, final Map prefixAndFieldNameMap,
                final boolean ignoreNonMatchedColumns) {
            if (N.isEmpty(prefixAndFieldNameMap)) {
                return to(entityClass, ignoreNonMatchedColumns);
            }

            N.checkArgument(ClassUtil.isBeanClass(entityClass), "{} is not an entity class", entityClass);

            final BeanInfo entityInfo = ParserUtil.getBeanInfo(entityClass);

            return new BiRowMapper<>() {
                private String[] columnLabels = null;
                private PropInfo[] propInfos;
                private Type[] columnTypes = null;
                private int columnCount = -1;

                @Override
                public T apply(final ResultSet rs, final List columnLabelList) throws SQLException {

                    if (columnLabels == null) {
                        final Map column2FieldNameMap = JdbcUtil.getColumn2FieldNameMap(entityClass);

                        columnCount = columnLabelList.size();
                        columnLabels = columnLabelList.toArray(new String[columnCount]);
                        propInfos = new PropInfo[columnCount];
                        columnTypes = new Type[columnCount];

                        for (int i = 0; i < columnCount; i++) {
                            propInfos[i] = entityInfo.getPropInfo(columnLabels[i]);

                            if (propInfos[i] == null) {
                                String fieldName = column2FieldNameMap.get(columnLabels[i]);

                                if (Strings.isEmpty(fieldName)) {
                                    fieldName = column2FieldNameMap.get(columnLabels[i].toLowerCase());
                                }

                                if (Strings.isNotEmpty(fieldName)) {
                                    propInfos[i] = entityInfo.getPropInfo(fieldName);
                                }
                            }

                            if (propInfos[i] == null) {
                                final String newColumnName = Jdbc.checkPrefix(entityInfo, columnLabels[i], prefixAndFieldNameMap, columnLabelList);
                                propInfos[i] = JdbcUtil.getSubPropInfo(entityClass, newColumnName);

                                if (propInfos[i] == null) {
                                    String fieldName = column2FieldNameMap.get(newColumnName);

                                    if (Strings.isEmpty(fieldName)) {
                                        fieldName = column2FieldNameMap.get(newColumnName.toLowerCase());
                                    }

                                    if (Strings.isNotEmpty(fieldName)) {
                                        propInfos[i] = JdbcUtil.getSubPropInfo(entityClass, fieldName);

                                        if (propInfos[i] != null) {
                                            columnLabels[i] = fieldName;
                                        }
                                    }
                                } else {
                                    columnLabels[i] = newColumnName;
                                }

                                if (propInfos[i] == null) {
                                    if (ignoreNonMatchedColumns) {
                                        columnLabels[i] = null;
                                    } else {
                                        throw new IllegalArgumentException("No property in class: " + ClassUtil.getCanonicalClassName(entityClass)
                                                + " mapping to column: " + columnLabels[i]);
                                    }
                                } else {
                                    columnTypes[i] = propInfos[i].dbType;
                                    propInfos[i] = null;
                                }
                            } else {
                                columnTypes[i] = propInfos[i].dbType;
                            }
                        }
                    }

                    final Object result = entityInfo.createBeanResult();

                    for (int i = 0; i < columnCount; i++) {
                        if (columnLabels[i] == null) {
                            continue;
                        }

                        if (propInfos[i] == null) {
                            entityInfo.setPropValue(result, columnLabels[i], columnTypes[i].get(rs, i + 1));
                        } else {
                            propInfos[i].setPropValue(result, columnTypes[i].get(rs, i + 1));
                        }
                    }

                    return entityInfo.finishBeanResult(result);
                }
            };
        }

        //        Map, Tuple2, ? extends Function>> buildFuncMap = new ConcurrentHashMap<>();
        //
        //        static  BiRowMapper toEntityByBuilder(final Class entityClass) {
        //            @SuppressWarnings("rawtypes")
        //            Tuple2, Function> builderFuncs = (Tuple2) buildFuncMap.get(entityClass);
        //
        //            if (builderFuncs == null) {
        //                Class builderClass = null;
        //                Method builderMethod = null;
        //                Method buildMethod = null;
        //
        //                try {
        //                    builderMethod = entityClass.getDeclaredMethod("builder");
        //                } catch (Exception e) {
        //                    // ignore
        //                }
        //
        //                if (builderMethod == null || !(Modifier.isStatic(builderMethod.getModifiers()) && Modifier.isPublic(builderMethod.getModifiers()))) {
        //                    try {
        //                        builderMethod = entityClass.getDeclaredMethod("newBuilder");
        //                    } catch (Exception e) {
        //                        // ignore
        //                    }
        //                }
        //
        //                if (builderMethod == null || !(Modifier.isStatic(builderMethod.getModifiers()) && Modifier.isPublic(builderMethod.getModifiers()))) {
        //                    try {
        //                        builderMethod = entityClass.getDeclaredMethod("createBuilder");
        //                    } catch (Exception e) {
        //                        // ignore
        //                    }
        //                }
        //
        //                if (builderMethod == null || !(Modifier.isStatic(builderMethod.getModifiers()) && Modifier.isPublic(builderMethod.getModifiers()))) {
        //                    throw new IllegalArgumentException("No static builder method found in entity class: " + entityClass);
        //                }
        //
        //                builderClass = builderMethod.getReturnType();
        //
        //                try {
        //                    buildMethod = builderClass.getDeclaredMethod("build");
        //                } catch (Exception e) {
        //                    // ignore
        //                }
        //
        //                if (buildMethod == null || !Modifier.isPublic(buildMethod.getModifiers())) {
        //                    try {
        //                        buildMethod = builderClass.getDeclaredMethod("create");
        //                    } catch (Exception e) {
        //                        // ignore
        //                    }
        //                }
        //
        //                if (buildMethod == null || !Modifier.isPublic(buildMethod.getModifiers())) {
        //                    throw new IllegalArgumentException("No build method found in builder class: " + builderClass);
        //                }
        //
        //                final Method finalBuilderMethod = builderMethod;
        //                final Method finalBuildMethod = buildMethod;
        //
        //                final Supplier builderSupplier = () -> ClassUtil.invokeMethod(finalBuilderMethod);
        //                final Function buildFunc = instance -> ClassUtil.invokeMethod(instance, finalBuildMethod);
        //
        //                builderFuncs = Tuple.of(builderSupplier, buildFunc);
        //
        //                buildFuncMap.put(entityClass, builderFuncs);
        //            }
        //
        //            return toEntityByBuilder(builderFuncs._1, builderFuncs._2);
        //        }
        //
        //        static  BiRowMapper toEntityByBuilder(final Supplier builderSupplier) {
        //            // TODO
        //
        //            return null;
        //        }
        //
        //        static  BiRowMapper toEntityByBuilder(final Supplier builderSupplier, final Function buildFunction) {
        //            // TODO
        //
        //            return null;
        //        }

        /**
         *
         *
         * @param valueFilter
         * @return
         */
        static BiRowMapper> toMap(final Predicate valueFilter) {
            return (rs, columnLabels) -> {
                final int columnCount = columnLabels.size();
                final Map result = N.newHashMap(columnCount);

                Object value = null;

                for (int i = 1; i <= columnCount; i++) {
                    value = JdbcUtil.getColumnValue(rs, i);

                    if (valueFilter.test(value)) {
                        result.put(columnLabels.get(i - 1), value);
                    }
                }

                return result;
            };
        }

        /**
         *
         *
         * @param valueFilter
         * @param mapSupplier
         * @return
         */
        static BiRowMapper> toMap(final BiPredicate valueFilter, final IntFunction> mapSupplier) {
            return (rs, columnLabels) -> {
                final int columnCount = columnLabels.size();
                final Map result = mapSupplier.apply(columnCount);

                String columnName = null;
                Object value = null;

                for (int i = 1; i <= columnCount; i++) {
                    columnName = columnLabels.get(i - 1);
                    value = JdbcUtil.getColumnValue(rs, i);

                    if (valueFilter.test(columnName, value)) {
                        result.put(columnName, value);
                    }
                }

                return result;
            };
        }

        /**
         *
         * @param rowExtractor
         * @param valueFilter
         * @param mapSupplier
         * @return
         */
        @SequentialOnly
        @Stateful
        static BiRowMapper> toMap(final RowExtractor rowExtractor, final BiPredicate valueFilter,
                final IntFunction> mapSupplier) {
            return new BiRowMapper<>() {
                private Object[] outputValuesForRowExtractor = null;

                @Override
                public Map apply(final ResultSet rs, final List columnLabels) throws SQLException {
                    final int columnCount = columnLabels.size();

                    if (outputValuesForRowExtractor == null) {
                        outputValuesForRowExtractor = new Object[columnCount];
                    }

                    rowExtractor.accept(rs, outputValuesForRowExtractor);

                    final Map result = mapSupplier.apply(columnCount);

                    String columnName = null;

                    for (int i = 0; i < columnCount; i++) {
                        columnName = columnLabels.get(i);

                        if (valueFilter.test(columnName, outputValuesForRowExtractor[i])) {
                            result.put(columnName, outputValuesForRowExtractor[i]);
                        }
                    }

                    return result;
                }
            };
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param columnNameConverter
         * @return
         */
        @SequentialOnly
        @Stateful
        static BiRowMapper> toMap(final Function columnNameConverter) {
            return toMap(columnNameConverter, IntFunctions. ofMap());
        }

        /**
         *
         * @param columnNameConverter
         * @param mapSupplier
         * @return
         */
        @SequentialOnly
        @Stateful
        static BiRowMapper> toMap(final Function columnNameConverter,
                final IntFunction> mapSupplier) {
            return new BiRowMapper<>() {
                private String[] keyNames = null;

                @Override
                public Map apply(final ResultSet rs, final List columnLabels) throws SQLException {
                    if (keyNames == null) {
                        keyNames = new String[columnLabels.size()];

                        for (int i = 0, size = columnLabels.size(); i < size; i++) {
                            keyNames[i] = columnNameConverter.apply(columnLabels.get(i));
                        }
                    }

                    final int columnCount = keyNames.length;
                    final Map result = mapSupplier.apply(columnCount);

                    for (int i = 1; i <= columnCount; i++) {
                        result.put(keyNames[i - 1], JdbcUtil.getColumnValue(rs, i));
                    }

                    return result;
                }
            };

        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param rowExtractor
         * @param columnNameConverter
         * @param mapSupplier
         * @return
         */
        @SequentialOnly
        @Stateful
        static BiRowMapper> toMap(final RowExtractor rowExtractor, final Function columnNameConverter,
                final IntFunction> mapSupplier) {
            return new BiRowMapper<>() {
                private Object[] outputValuesForRowExtractor = null;
                private String[] keyNames = null;

                @Override
                public Map apply(final ResultSet rs, final List columnLabels) throws SQLException {
                    final int columnCount = columnLabels.size();

                    if (outputValuesForRowExtractor == null) {
                        outputValuesForRowExtractor = new Object[columnCount];
                        keyNames = new String[columnCount];

                        for (int i = 0; i < columnCount; i++) {
                            keyNames[i] = columnNameConverter.apply(columnLabels.get(i));
                        }
                    }

                    rowExtractor.accept(rs, outputValuesForRowExtractor);

                    final Map result = mapSupplier.apply(columnCount);

                    for (int i = 0; i < columnCount; i++) {
                        result.put(keyNames[i], outputValuesForRowExtractor[i]);
                    }

                    return result;
                }
            };
        }

        /**
         *
         *
         * @param rowExtractor
         * @return
         */
        @SequentialOnly
        @Stateful
        static BiRowMapper> toMap(final RowExtractor rowExtractor) {
            return new BiRowMapper<>() {
                private Object[] outputValuesForRowExtractor = null;

                @Override
                public Map apply(final ResultSet rs, final List columnLabels) throws SQLException {
                    final int columnCount = columnLabels.size();

                    if (outputValuesForRowExtractor == null) {
                        outputValuesForRowExtractor = new Object[columnCount];
                    }

                    rowExtractor.accept(rs, outputValuesForRowExtractor);

                    final Map result = N.newHashMap(columnCount);

                    for (int i = 0; i < columnCount; i++) {
                        result.put(columnLabels.get(i), outputValuesForRowExtractor[i]);
                    }

                    return result;
                }
            };
        }

        /**
         *
         *
         * @param columnGetterForAll
         * @return
         */
        @Beta
        static BiRowMapper toArray(final ColumnGetter columnGetterForAll) {
            return new BiRowMapper<>() {

                @Override
                public Object[] apply(final ResultSet rs, final List columnLabels) throws SQLException {
                    final int columnCount = columnLabels.size();
                    final Object[] result = new Object[columnCount];

                    for (int i = 0; i < columnCount; i++) {
                        result[i] = columnGetterForAll.apply(rs, i + 1);
                    }

                    return result;
                }
            };
        }

        /**
         *
         *
         * @param columnGetterForAll
         * @return
         */
        @Beta
        static BiRowMapper> toList(final ColumnGetter columnGetterForAll) {
            return toCollection(columnGetterForAll, Factory. ofList());
        }

        /**
         *
         *
         * @param 
         * @param columnGetterForAll
         * @param supplier
         * @return
         */
        @Beta
        static > BiRowMapper toCollection(final ColumnGetter columnGetterForAll, final IntFunction supplier) {
            return new BiRowMapper<>() {

                @Override
                public C apply(final ResultSet rs, final List columnLabels) throws SQLException {
                    final int columnCount = columnLabels.size();

                    final Collection result = (Collection) supplier.apply(columnCount);

                    for (int i = 0; i < columnCount; i++) {
                        result.add(columnGetterForAll.apply(rs, i + 1));
                    }

                    return (C) result;
                }
            };
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @return
         */
        @Beta
        @SequentialOnly
        @Stateful
        static BiRowMapper toDisposableObjArray() {
            return new BiRowMapper<>() {
                private DisposableObjArray disposable = null;
                private int columnCount = -1;
                private Object[] output = null;

                @Override
                public DisposableObjArray apply(final ResultSet rs, final List columnLabels) throws SQLException {
                    if (disposable == null) {
                        columnCount = JdbcUtil.getColumnCount(rs);
                        output = new Object[columnCount];
                        disposable = DisposableObjArray.wrap(output);
                    }

                    for (int i = 0; i < columnCount; i++) {
                        output[i] = JdbcUtil.getColumnValue(rs, i + 1);
                    }

                    return disposable;
                }
            };
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param entityClass used to fetch column/row value from {@code ResultSet} by the type of fields/columns defined in this class.
         * @return
         */
        @Beta
        @SequentialOnly
        @Stateful
        static BiRowMapper toDisposableObjArray(final Class entityClass) {
            N.checkArgNotNull(entityClass, "entityClass");

            return new BiRowMapper<>() {
                private DisposableObjArray disposable = null;
                private int columnCount = -1;
                private Object[] output = null;

                private Type[] columnTypes = null;

                @Override
                public DisposableObjArray apply(final ResultSet rs, final List columnLabels) throws SQLException {
                    if (disposable == null) {
                        columnCount = columnLabels.size();
                        columnTypes = new Type[columnCount];

                        final BeanInfo entityInfo = ParserUtil.getBeanInfo(entityClass);
                        final Map column2FieldNameMap = JdbcUtil.getColumn2FieldNameMap(entityClass);
                        PropInfo propInfo = null;

                        for (int i = 0; i < columnCount; i++) {
                            propInfo = entityInfo.getPropInfo(columnLabels.get(i));

                            if (propInfo == null) {
                                String fieldName = column2FieldNameMap.get(columnLabels.get(i));

                                if (Strings.isEmpty(fieldName)) {
                                    fieldName = column2FieldNameMap.get(columnLabels.get(i).toLowerCase());
                                }

                                if (Strings.isNotEmpty(fieldName)) {
                                    propInfo = entityInfo.getPropInfo(fieldName);
                                }
                            }

                            if (propInfo == null) {
                                //    throw new IllegalArgumentException(
                                //            "No property in class: " + ClassUtil.getCanonicalClassName(entityClass) + " mapping to column: " + columnLabels.get(i));
                            } else {
                                columnTypes[i] = propInfo.dbType;
                            }
                        }

                        output = new Object[columnCount];
                        disposable = DisposableObjArray.wrap(output);
                    }

                    for (int i = 0; i < columnCount; i++) {
                        output[i] = columnTypes[i] == null ? JdbcUtil.getColumnValue(rs, i + 1) : columnTypes[i].get(rs, i + 1);
                    }

                    return disposable;
                }
            };
        }

        /**
         *
         *
         * @return
         */
        static BiRowMapperBuilder builder() {
            return builder(ColumnGetter.GET_OBJECT);
        }

        /**
         *
         *
         * @param defaultColumnGetter
         * @return
         */
        static BiRowMapperBuilder builder(final ColumnGetter defaultColumnGetter) {
            return new BiRowMapperBuilder(defaultColumnGetter);
        }

        //    static BiRowMapperBuilder builder(final int columnCount) {
        //        return new BiRowMapperBuilder(columnCount);
        //    }

        @SequentialOnly
        public static class BiRowMapperBuilder {
            private final ColumnGetter defaultColumnGetter;
            private final Map> columnGetterMap;

            BiRowMapperBuilder(final ColumnGetter defaultColumnGetter) {
                this.defaultColumnGetter = defaultColumnGetter;

                columnGetterMap = new HashMap<>(9);
            }

            /**
             *
             *
             * @param columnName
             * @return
             */
            public BiRowMapperBuilder getBoolean(final String columnName) {
                return get(columnName, ColumnGetter.GET_BOOLEAN);
            }

            /**
             *
             *
             * @param columnName
             * @return
             */
            public BiRowMapperBuilder getByte(final String columnName) {
                return get(columnName, ColumnGetter.GET_BYTE);
            }

            /**
             *
             *
             * @param columnName
             * @return
             */
            public BiRowMapperBuilder getShort(final String columnName) {
                return get(columnName, ColumnGetter.GET_SHORT);
            }

            /**
             *
             *
             * @param columnName
             * @return
             */
            public BiRowMapperBuilder getInt(final String columnName) {
                return get(columnName, ColumnGetter.GET_INT);
            }

            /**
             *
             *
             * @param columnName
             * @return
             */
            public BiRowMapperBuilder getLong(final String columnName) {
                return get(columnName, ColumnGetter.GET_LONG);
            }

            /**
             *
             *
             * @param columnName
             * @return
             */
            public BiRowMapperBuilder getFloat(final String columnName) {
                return get(columnName, ColumnGetter.GET_FLOAT);
            }

            /**
             *
             *
             * @param columnName
             * @return
             */
            public BiRowMapperBuilder getDouble(final String columnName) {
                return get(columnName, ColumnGetter.GET_DOUBLE);
            }

            /**
             *
             *
             * @param columnName
             * @return
             */
            public BiRowMapperBuilder getBigDecimal(final String columnName) {
                return get(columnName, ColumnGetter.GET_BIG_DECIMAL);
            }

            /**
             *
             *
             * @param columnName
             * @return
             */
            public BiRowMapperBuilder getString(final String columnName) {
                return get(columnName, ColumnGetter.GET_STRING);
            }

            /**
             *
             *
             * @param columnName
             * @return
             */
            public BiRowMapperBuilder getDate(final String columnName) {
                return get(columnName, ColumnGetter.GET_DATE);
            }

            /**
             *
             *
             * @param columnName
             * @return
             */
            public BiRowMapperBuilder getTime(final String columnName) {
                return get(columnName, ColumnGetter.GET_TIME);
            }

            /**
             *
             *
             * @param columnName
             * @return
             */
            public BiRowMapperBuilder getTimestamp(final String columnName) {
                return get(columnName, ColumnGetter.GET_TIMESTAMP);
            }

            /**
             *
             * @param columnName
             * @return
             * @deprecated default {@link #getObject(String)} if there is no {@code ColumnGetter} set for the target column
             */
            @Deprecated
            public BiRowMapperBuilder getObject(final String columnName) {
                return get(columnName, ColumnGetter.GET_OBJECT);
            }

            /**
             *
             *
             * @param columnName
             * @param type
             * @return
             */
            public BiRowMapperBuilder getObject(final String columnName, Class type) {
                return get(columnName, ColumnGetter.get(type));
            }

            /**
             *
             *
             * @param columnName
             * @param columnGetter
             * @return
             */
            public BiRowMapperBuilder get(final String columnName, final ColumnGetter columnGetter) {
                N.checkArgNotNull(columnName, "columnName");
                N.checkArgNotNull(columnGetter, "columnGetter");

                columnGetterMap.put(columnName, columnGetter);

                return this;
            }

            /**
             *
             * @param columnName
             * @param columnGetter
             * @return
             * @deprecated replaced by {@link #get(String, ColumnGetter)}
             */
            @Deprecated
            public BiRowMapperBuilder column(final String columnName, final ColumnGetter columnGetter) {
                return get(columnName, columnGetter);
            }

            //    /**
            //     * Set default column getter function.
            //     *
            //     * @param columnGetter
            //     * @return
            //     */
            //    public BiRowMapperBuilder __(final ColumnGetter columnGetter) {
            //        defaultColumnGetter = columnGetter;
            //
            //        return this;
            //    }
            //
            //    /**
            //     * Set column getter function for column[columnName].
            //     *
            //     * @param columnGetter
            //     * @return
            //     */
            //    public BiRowMapperBuilder __(final String columnName, final ColumnGetter columnGetter) {
            //        columnGetterMap.put(columnName, columnGetter);
            //
            //        return this;
            //    }

            ColumnGetter[] initColumnGetter(final List columnLabelList) { //NOSONAR
                final int rsColumnCount = columnLabelList.size();
                final ColumnGetter[] rsColumnGetters = new ColumnGetter[rsColumnCount];

                int cnt = 0;
                ColumnGetter columnGetter = null;

                for (int i = 0; i < rsColumnCount; i++) {
                    columnGetter = columnGetterMap.get(columnLabelList.get(i));

                    if (columnGetter != null) {
                        cnt++;
                    }

                    rsColumnGetters[i] = columnGetter == null ? defaultColumnGetter : columnGetter;
                }

                if (cnt < columnGetterMap.size()) {
                    final List tmp = new ArrayList<>(columnGetterMap.keySet());
                    tmp.removeAll(columnLabelList);
                    throw new IllegalArgumentException("ColumnGetters for " + tmp + " are not found in ResultSet columns: " + columnLabelList);
                }

                return rsColumnGetters;
            }

            /**
             * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
             *
             * @param 
             * @param targetClass
             * @return
             */
            @SequentialOnly
            @Stateful
            public  BiRowMapper to(final Class targetClass) {
                return to(targetClass, false);
            }

            /**
             * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
             *
             * @param 
             * @param targetClass
             * @param ignoreNonMatchedColumns
             * @return
             */
            @SequentialOnly
            @Stateful
            public  BiRowMapper to(final Class targetClass, final boolean ignoreNonMatchedColumns) {
                if (Object[].class.isAssignableFrom(targetClass)) {
                    return new BiRowMapper<>() {
                        private ColumnGetter[] rsColumnGetters = null;
                        private int rsColumnCount = -1;

                        @Override
                        public T apply(final ResultSet rs, final List columnLabelList) throws SQLException {
                            if (rsColumnGetters == null) {
                                rsColumnCount = columnLabelList.size();
                                rsColumnGetters = initColumnGetter(columnLabelList);
                            }

                            final Object[] a = Array.newInstance(targetClass.getComponentType(), rsColumnCount);

                            for (int i = 0; i < rsColumnCount; i++) {
                                a[i] = rsColumnGetters[i].apply(rs, i + 1);
                            }

                            return (T) a;
                        }
                    };
                } else if (List.class.isAssignableFrom(targetClass)) {
                    return new BiRowMapper<>() {
                        private ColumnGetter[] rsColumnGetters = null;
                        private int rsColumnCount = -1;

                        @Override
                        public T apply(final ResultSet rs, final List columnLabelList) throws SQLException {
                            if (rsColumnGetters == null) {
                                rsColumnCount = columnLabelList.size();
                                rsColumnGetters = initColumnGetter(columnLabelList);
                            }

                            @SuppressWarnings("rawtypes")
                            final Collection c = N.newCollection((Class) targetClass, rsColumnCount);

                            for (int i = 0; i < rsColumnCount; i++) {
                                c.add(rsColumnGetters[i].apply(rs, i + 1));
                            }

                            return (T) c;
                        }
                    };
                } else if (Map.class.isAssignableFrom(targetClass)) {
                    return new BiRowMapper<>() {
                        private ColumnGetter[] rsColumnGetters = null;
                        private int rsColumnCount = -1;
                        private String[] columnLabels = null;

                        @Override
                        public T apply(final ResultSet rs, final List columnLabelList) throws SQLException {

                            if (rsColumnGetters == null) {
                                rsColumnCount = columnLabelList.size();
                                rsColumnGetters = initColumnGetter(columnLabelList);

                                columnLabels = columnLabelList.toArray(new String[rsColumnCount]);
                            }

                            @SuppressWarnings("rawtypes")
                            final Map m = N.newMap((Class) targetClass, rsColumnCount);

                            for (int i = 0; i < rsColumnCount; i++) {
                                m.put(columnLabels[i], rsColumnGetters[i].apply(rs, i + 1));
                            }

                            return (T) m;
                        }
                    };
                } else if (ClassUtil.isBeanClass(targetClass)) {
                    return new BiRowMapper<>() {
                        private final BeanInfo entityInfo = ParserUtil.getBeanInfo(targetClass);

                        private int rsColumnCount = -1;
                        private ColumnGetter[] rsColumnGetters = null;
                        private String[] columnLabels = null;
                        private PropInfo[] propInfos;

                        @Override
                        public T apply(final ResultSet rs, final List columnLabelList) throws SQLException {
                            if (rsColumnGetters == null) {
                                rsColumnCount = columnLabelList.size();
                                rsColumnGetters = initColumnGetter(columnLabelList);

                                columnLabels = columnLabelList.toArray(new String[rsColumnCount]);
                                final PropInfo[] localPropInfos = new PropInfo[rsColumnCount];

                                final Map column2FieldNameMap = JdbcUtil.getColumn2FieldNameMap(targetClass);

                                for (int i = 0; i < rsColumnCount; i++) {
                                    localPropInfos[i] = entityInfo.getPropInfo(columnLabels[i]);

                                    if (localPropInfos[i] == null) {
                                        String fieldName = column2FieldNameMap.get(columnLabels[i]);

                                        if (Strings.isEmpty(fieldName)) {
                                            fieldName = column2FieldNameMap.get(columnLabels[i].toLowerCase());
                                        }

                                        if (Strings.isNotEmpty(fieldName)) {
                                            localPropInfos[i] = entityInfo.getPropInfo(fieldName);
                                        }
                                    }

                                    if (localPropInfos[i] == null) {
                                        if (ignoreNonMatchedColumns) {
                                            columnLabels[i] = null;
                                        } else {
                                            throw new IllegalArgumentException("No property in class: " + ClassUtil.getCanonicalClassName(targetClass)
                                                    + " mapping to column: " + columnLabels[i]);
                                        }
                                    } else {
                                        if (rsColumnGetters[i] == ColumnGetter.GET_OBJECT) {
                                            rsColumnGetters[i] = ColumnGetter.get(localPropInfos[i].dbType);
                                        }
                                    }
                                }

                                this.propInfos = localPropInfos;
                            }

                            final Object result = entityInfo.createBeanResult();

                            for (int i = 0; i < rsColumnCount; i++) {
                                if (columnLabels[i] == null) {
                                    continue;
                                }

                                propInfos[i].setPropValue(result, rsColumnGetters[i].apply(rs, i + 1));
                            }

                            return entityInfo.finishBeanResult(result);
                        }
                    };
                } else {
                    return new BiRowMapper<>() {
                        private int rsColumnCount = -1;
                        private ColumnGetter[] rsColumnGetters = null;

                        @Override
                        public T apply(final ResultSet rs, final List columnLabelList) throws SQLException {
                            if (rsColumnGetters == null) {
                                rsColumnCount = columnLabelList.size();
                                rsColumnGetters = initColumnGetter(columnLabelList);

                                if (rsColumnGetters[0] == ColumnGetter.GET_OBJECT) {
                                    rsColumnGetters[0] = ColumnGetter.get(N.typeOf(targetClass));
                                }
                            }

                            if (rsColumnCount != 1 && (rsColumnCount = columnLabelList.size()) != 1) {
                                throw new IllegalArgumentException(
                                        "It's not supported to retrieve value from multiple columns: " + columnLabelList + " for type: " + targetClass);
                            }

                            return (T) rsColumnGetters[0].apply(rs, 1);
                        }
                    };
                }
            }
        }
    }

    /**
     * Don't use {@code RowConsumer} in {@link PreparedQuery#forEach(RowConsumer)} or any place where multiple records will be consumed by it, if column labels/count are used in {@link RowConsumer#accept(ResultSet)}.
     * Consider using {@code BiRowConsumer} instead because it's more efficient to consume multiple records when column labels/count are used.
     *
     */
    @FunctionalInterface
    public interface RowConsumer extends Throwables.Consumer {

        RowConsumer DO_NOTHING = rs -> {
        };

        /**
         *
         *
         * @param rs
         * @throws SQLException
         */
        @Override
        void accept(ResultSet rs) throws SQLException;

        /**
         *
         *
         * @param after
         * @return
         */
        default RowConsumer andThen(final Throwables.Consumer after) {
            N.checkArgNotNull(after);

            return rs -> {
                accept(rs);
                after.accept(rs);
            };
        }

        /**
         *
         *
         * @return
         */
        default BiRowConsumer toBiRowConsumer() {
            return (rs, columnLabels) -> this.accept(rs);
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param consumerForAll
         * @return
         */
        @Beta
        @SequentialOnly
        @Stateful
        static RowConsumer create(Throwables.ObjIntConsumer consumerForAll) {
            N.checkArgNotNull(consumerForAll, "consumerForAll");

            return new RowConsumer() {
                private int columnCount = -1;

                @Override
                public void accept(final ResultSet rs) throws SQLException {
                    if (columnCount < 0) {
                        columnCount = JdbcUtil.getColumnCount(rs);
                    }

                    for (int i = 0; i < columnCount; i++) {
                        consumerForAll.accept(rs, i + 1);
                    }
                }
            };
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param consumer
         * @return
         */
        @Beta
        @SequentialOnly
        @Stateful
        static RowConsumer oneOff(final Consumer consumer) {
            N.checkArgNotNull(consumer, "consumer");

            return new RowConsumer() {
                private DisposableObjArray disposable = null;
                private int columnCount = -1;
                private Object[] output = null;

                @Override
                public void accept(final ResultSet rs) throws SQLException {
                    if (disposable == null) {
                        columnCount = JdbcUtil.getColumnCount(rs);
                        output = new Object[columnCount];
                        disposable = DisposableObjArray.wrap(output);
                    }

                    for (int i = 0; i < columnCount; i++) {
                        output[i] = JdbcUtil.getColumnValue(rs, i + 1);
                    }

                    consumer.accept(disposable);
                }
            };
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param entityClass used to fetch column/row value from {@code ResultSet} by the type of fields/columns defined in this class.
         * @param consumer
         * @return
         */
        @Beta
        @SequentialOnly
        @Stateful
        static RowConsumer oneOff(final Class entityClass, final Consumer consumer) {
            N.checkArgNotNull(entityClass, "entityClass");
            N.checkArgNotNull(consumer, "consumer");

            return new RowConsumer() {
                private DisposableObjArray disposable = null;
                private int columnCount = -1;
                private Object[] output = null;

                private Type[] columnTypes = null;

                @Override
                public void accept(final ResultSet rs) throws SQLException {
                    if (disposable == null) {
                        final List columnLabels = JdbcUtil.getColumnLabelList(rs);

                        columnCount = columnLabels.size();
                        columnTypes = new Type[columnCount];

                        final BeanInfo entityInfo = ParserUtil.getBeanInfo(entityClass);
                        final Map column2FieldNameMap = JdbcUtil.getColumn2FieldNameMap(entityClass);
                        PropInfo propInfo = null;

                        for (int i = 0; i < columnCount; i++) {
                            propInfo = entityInfo.getPropInfo(columnLabels.get(i));

                            if (propInfo == null) {
                                String fieldName = column2FieldNameMap.get(columnLabels.get(i));

                                if (Strings.isEmpty(fieldName)) {
                                    fieldName = column2FieldNameMap.get(columnLabels.get(i).toLowerCase());
                                }

                                if (Strings.isNotEmpty(fieldName)) {
                                    propInfo = entityInfo.getPropInfo(fieldName);
                                }
                            }

                            if (propInfo == null) {
                                //    throw new IllegalArgumentException(
                                //            "No property in class: " + ClassUtil.getCanonicalClassName(entityClass) + " mapping to column: " + columnLabels.get(i));
                            } else {
                                columnTypes[i] = propInfo.dbType;
                            }
                        }

                        output = new Object[columnCount];
                        disposable = DisposableObjArray.wrap(output);
                    }

                    for (int i = 0; i < columnCount; i++) {
                        output[i] = columnTypes[i] == null ? JdbcUtil.getColumnValue(rs, i + 1) : columnTypes[i].get(rs, i + 1);
                    }

                    consumer.accept(disposable);
                }
            };
        }
    }

    /**
     * The Interface BiRowConsumer.
     */
    @FunctionalInterface
    public interface BiRowConsumer extends Throwables.BiConsumer, SQLException> {

        BiRowConsumer DO_NOTHING = (rs, cls) -> {
        };

        /**
         *
         *
         * @param rs
         * @param columnLabels
         * @throws SQLException
         */
        @Override
        void accept(ResultSet rs, List columnLabels) throws SQLException;

        /**
         *
         *
         * @param after
         * @return
         */
        default BiRowConsumer andThen(final Throwables.BiConsumer, SQLException> after) {
            N.checkArgNotNull(after);

            return (rs, cls) -> {
                accept(rs, cls);
                after.accept(rs, cls);
            };
        }

        //    /**
        //     *
        //     *
        //     * @param rowConsumer
        //     * @return
        //     */
        //    static BiRowConsumer from(final RowConsumer rowConsumer) {
        //        N.checkArgNotNull(rowConsumer, "rowConsumer");
        //
        //        return (rs, columnLabels) -> rowConsumer.accept(rs);
        //    }

        /**
         *
         *
         * @param consumerForAll
         * @return
         */
        @Beta
        static BiRowConsumer create(Throwables.ObjIntConsumer consumerForAll) {
            N.checkArgNotNull(consumerForAll, "consumerForAll");

            return new BiRowConsumer() {
                @Override
                public void accept(final ResultSet rs, final List columnLabels) throws SQLException {
                    final int columnCount = columnLabels.size();

                    for (int i = 0; i < columnCount; i++) {
                        consumerForAll.accept(rs, i + 1);
                    }
                }
            };
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param consumer
         * @return
         */
        @Beta
        @SequentialOnly
        @Stateful
        static BiRowConsumer oneOff(final BiConsumer, DisposableObjArray> consumer) {
            N.checkArgNotNull(consumer, "consumer");

            return new BiRowConsumer() {
                private DisposableObjArray disposable = null;
                private int columnCount = -1;
                private Object[] output = null;

                @Override
                public void accept(final ResultSet rs, final List columnLabels) throws SQLException {
                    if (disposable == null) {
                        columnCount = columnLabels.size();
                        output = new Object[columnCount];
                        disposable = DisposableObjArray.wrap(output);
                    }

                    for (int i = 0; i < columnCount; i++) {
                        output[i] = JdbcUtil.getColumnValue(rs, i + 1);
                    }

                    consumer.accept(columnLabels, disposable);
                }
            };
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param entityClass used to fetch column/row value from {@code ResultSet} by the type of fields/columns defined in this class.
         * @param consumer
         * @return
         */
        @Beta
        @SequentialOnly
        @Stateful
        static BiRowConsumer oneOff(final Class entityClass, final BiConsumer, DisposableObjArray> consumer) {
            N.checkArgNotNull(entityClass, "entityClass");
            N.checkArgNotNull(consumer, "consumer");

            return new BiRowConsumer() {
                private DisposableObjArray disposable = null;
                private int columnCount = -1;
                private Object[] output = null;

                private Type[] columnTypes = null;

                @Override
                public void accept(final ResultSet rs, final List columnLabels) throws SQLException {
                    if (disposable == null) {
                        columnCount = columnLabels.size();
                        columnTypes = new Type[columnCount];

                        final BeanInfo entityInfo = ParserUtil.getBeanInfo(entityClass);
                        final Map column2FieldNameMap = JdbcUtil.getColumn2FieldNameMap(entityClass);
                        PropInfo propInfo = null;

                        for (int i = 0; i < columnCount; i++) {
                            propInfo = entityInfo.getPropInfo(columnLabels.get(i));

                            if (propInfo == null) {
                                String fieldName = column2FieldNameMap.get(columnLabels.get(i));

                                if (Strings.isEmpty(fieldName)) {
                                    fieldName = column2FieldNameMap.get(columnLabels.get(i).toLowerCase());
                                }

                                if (Strings.isNotEmpty(fieldName)) {
                                    propInfo = entityInfo.getPropInfo(fieldName);
                                }
                            }

                            if (propInfo == null) {
                                //    throw new IllegalArgumentException(
                                //            "No property in class: " + ClassUtil.getCanonicalClassName(entityClass) + " mapping to column: " + columnLabels.get(i));
                            } else {
                                columnTypes[i] = propInfo.dbType;
                            }
                        }

                        output = new Object[columnCount];
                        disposable = DisposableObjArray.wrap(output);
                    }

                    for (int i = 0; i < columnCount; i++) {
                        output[i] = columnTypes[i] == null ? JdbcUtil.getColumnValue(rs, i + 1) : columnTypes[i].get(rs, i + 1);
                    }

                    consumer.accept(columnLabels, disposable);
                }
            };
        }
    }

    /**
     * Generally, the result should be filtered in database side by SQL scripts.
     * Only user {@code RowFilter/BiRowFilter} if there is a specific reason or the filter can't be done by SQL scripts in database server side.
     * Consider using {@code BiRowConsumer} instead because it's more efficient to test multiple records when column labels/count are used.
     *
     */
    @FunctionalInterface
    public interface RowFilter extends Throwables.Predicate {

        /** The Constant ALWAYS_TRUE. */
        RowFilter ALWAYS_TRUE = rs -> true;

        /** The Constant ALWAYS_FALSE. */
        RowFilter ALWAYS_FALSE = rs -> false;

        /**
         *
         *
         * @param rs
         * @return
         * @throws SQLException
         */
        @Override
        boolean test(final ResultSet rs) throws SQLException;

        /**
         *
         *
         * @return
         */
        @Override
        default RowFilter negate() {
            return rs -> !test(rs);
        }

        /**
         *
         *
         * @param other
         * @return
         */
        default RowFilter and(final Throwables.Predicate other) {
            N.checkArgNotNull(other);

            return rs -> test(rs) && other.test(rs);
        }

        /**
         *
         *
         * @return
         */
        default BiRowFilter toBiRowFilter() {
            return (rs, columnLabels) -> this.test(rs);
        }
    }

    /**
     * Generally, the result should be filtered in database side by SQL scripts.
     * Only user {@code RowFilter/BiRowFilter} if there is a specific reason or the filter can't be done by SQL scripts in database server side.
     *
     */
    @FunctionalInterface
    public interface BiRowFilter extends Throwables.BiPredicate, SQLException> {

        /** The Constant ALWAYS_TRUE. */
        BiRowFilter ALWAYS_TRUE = (rs, columnLabels) -> true;

        /** The Constant ALWAYS_FALSE. */
        BiRowFilter ALWAYS_FALSE = (rs, columnLabels) -> false;

        /**
         *
         *
         * @param rs
         * @param columnLabels
         * @return
         * @throws SQLException
         */
        @Override
        boolean test(ResultSet rs, List columnLabels) throws SQLException;

        /**
         *
         *
         * @return
         */
        default BiRowFilter negate() {
            return (rs, cls) -> !test(rs, cls);
        }

        /**
         *
         *
         * @param other
         * @return
         */
        default BiRowFilter and(final Throwables.BiPredicate, SQLException> other) {
            N.checkArgNotNull(other);

            return (rs, cls) -> test(rs, cls) && other.test(rs, cls);
        }

        //    /**
        //     *
        //     *
        //     * @param rowFilter
        //     * @return
        //     */
        //    static BiRowFilter from(final RowFilter rowFilter) {
        //        N.checkArgNotNull(rowFilter, "rowFilter");
        //
        //        return (rs, columnLabels) -> rowFilter.test(rs);
        //    }
    }

    @FunctionalInterface
    public interface RowExtractor extends Throwables.BiConsumer {

        /**
         *
         *
         * @param rs
         * @param outputRow
         * @throws SQLException
         */
        @Override
        void accept(final ResultSet rs, final Object[] outputRow) throws SQLException;

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param entityClassForFetch
         * @return
         */
        @SequentialOnly
        @Stateful
        static RowExtractor createBy(final Class entityClassForFetch) {
            return createBy(entityClassForFetch, null, null);
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param entityClassForFetch
         * @param prefixAndFieldNameMap
         * @return
         */
        @SequentialOnly
        @Stateful
        static RowExtractor createBy(final Class entityClassForFetch, final Map prefixAndFieldNameMap) {
            return createBy(entityClassForFetch, null, prefixAndFieldNameMap);
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param entityClassForFetch
         * @param columnLabels
         * @return
         */
        @SequentialOnly
        @Stateful
        static RowExtractor createBy(final Class entityClassForFetch, final List columnLabels) {
            return createBy(entityClassForFetch, columnLabels, null);
        }

        /**
         * It's stateful. Don't save or cache the returned instance for reuse or use it in parallel stream.
         *
         * @param entityClassForFetch
         * @param columnLabels
         * @param prefixAndFieldNameMap
         * @return
         */
        @SequentialOnly
        @Stateful
        static RowExtractor createBy(final Class entityClassForFetch, final List columnLabels, final Map prefixAndFieldNameMap) {
            N.checkArgument(ClassUtil.isBeanClass(entityClassForFetch), "entityClassForFetch");

            final BeanInfo entityInfo = ParserUtil.getBeanInfo(entityClassForFetch);

            return new RowExtractor() {
                private Type[] columnTypes = null;
                private int columnCount = -1;

                @Override
                public void accept(ResultSet rs, Object[] outputRow) throws SQLException {
                    if (columnTypes == null) {
                        final Map column2FieldNameMap = JdbcUtil.getColumn2FieldNameMap(entityClassForFetch);
                        final List columnLabelList = N.isEmpty(columnLabels) ? JdbcUtil.getColumnLabelList(rs) : columnLabels;
                        columnCount = columnLabelList.size();
                        final String[] columnLabels = columnLabelList.toArray(new String[columnCount]);

                        columnTypes = new Type[columnCount];
                        PropInfo propInfo = null;

                        for (int i = 0; i < columnCount; i++) {
                            propInfo = entityInfo.getPropInfo(columnLabels[i]);

                            if (propInfo == null) {
                                String fieldName = column2FieldNameMap.get(columnLabels[i]);

                                if (Strings.isEmpty(fieldName)) {
                                    fieldName = column2FieldNameMap.get(columnLabels[i].toLowerCase());
                                }

                                if (Strings.isNotEmpty(fieldName)) {
                                    propInfo = entityInfo.getPropInfo(fieldName);
                                }
                            }

                            if (propInfo == null) {
                                final String newColumnName = Jdbc.checkPrefix(entityInfo, columnLabels[i], prefixAndFieldNameMap, columnLabelList);

                                propInfo = JdbcUtil.getSubPropInfo(entityClassForFetch, newColumnName);

                                if (propInfo == null) {
                                    propInfo = JdbcUtil.getSubPropInfo(entityClassForFetch, columnLabels[i]);

                                    if (propInfo == null) {
                                        String fieldName = column2FieldNameMap.get(columnLabels[i]);

                                        if (Strings.isEmpty(fieldName)) {
                                            fieldName = column2FieldNameMap.get(columnLabels[i].toLowerCase());
                                        }

                                        if (Strings.isNotEmpty(fieldName)) {
                                            propInfo = JdbcUtil.getSubPropInfo(entityClassForFetch, fieldName);
                                        }
                                    }
                                }

                                if (propInfo == null) {
                                    columnTypes[i] = null;
                                } else {
                                    columnTypes[i] = propInfo.dbType;
                                }
                            } else {
                                columnTypes[i] = propInfo.dbType;
                            }
                        }
                    }

                    for (int i = 0; i < columnCount; i++) {
                        outputRow[i] = columnTypes[i] == null ? JdbcUtil.getColumnValue(rs, i + 1) : columnTypes[i].get(rs, i + 1);
                    }
                }
            };
        }

        /**
         *
         *
         * @param defaultColumnGetter
         * @return
         */
        static RowExtractorBuilder create(final ColumnGetter defaultColumnGetter) {
            return new RowExtractorBuilder(defaultColumnGetter);
        }

        /**
         *
         *
         * @return
         */
        static RowExtractorBuilder builder() {
            return builder(ColumnGetter.GET_OBJECT);
        }

        /**
         *
         *
         * @param defaultColumnGetter
         * @return
         */
        static RowExtractorBuilder builder(final ColumnGetter defaultColumnGetter) {
            return new RowExtractorBuilder(defaultColumnGetter);
        }

        public static class RowExtractorBuilder {
            private final Map> columnGetterMap;

            RowExtractorBuilder(final ColumnGetter defaultColumnGetter) {
                N.checkArgNotNull(defaultColumnGetter, "defaultColumnGetter");

                columnGetterMap = new HashMap<>(9);
                columnGetterMap.put(0, defaultColumnGetter);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowExtractorBuilder getBoolean(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_BOOLEAN);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowExtractorBuilder getByte(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_BYTE);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowExtractorBuilder getShort(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_SHORT);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowExtractorBuilder getInt(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_INT);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowExtractorBuilder getLong(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_LONG);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowExtractorBuilder getFloat(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_FLOAT);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowExtractorBuilder getDouble(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_DOUBLE);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowExtractorBuilder getBigDecimal(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_BIG_DECIMAL);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowExtractorBuilder getString(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_STRING);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowExtractorBuilder getDate(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_DATE);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowExtractorBuilder getTime(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_TIME);
            }

            /**
             *
             *
             * @param columnIndex
             * @return
             */
            public RowExtractorBuilder getTimestamp(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_TIMESTAMP);
            }

            /**
             *
             * @param columnIndex
             * @return
             * @deprecated default {@link #getObject(int)} if there is no {@code ColumnGetter} set for the target column
             */
            @Deprecated
            public RowExtractorBuilder getObject(final int columnIndex) {
                return get(columnIndex, ColumnGetter.GET_OBJECT);
            }

            /**
             *
             *
             * @param columnIndex
             * @param type
             * @return
             */
            public RowExtractorBuilder getObject(final int columnIndex, Class type) {
                return get(columnIndex, ColumnGetter.get(type));
            }

            /**
             *
             *
             * @param columnIndex
             * @param columnGetter
             * @return
             */
            public RowExtractorBuilder get(final int columnIndex, final ColumnGetter columnGetter) {
                N.checkArgPositive(columnIndex, "columnIndex");
                N.checkArgNotNull(columnGetter, "columnGetter");

                //        if (columnGetters == null) {
                //            columnGetterMap.put(columnIndex, columnGetter);
                //        } else {
                //            columnGetters[columnIndex] = columnGetter;
                //        }

                columnGetterMap.put(columnIndex, columnGetter);
                return this;
            }

            ColumnGetter[] initColumnGetter(ResultSet rs) throws SQLException { //NOSONAR
                return initColumnGetter(rs.getMetaData().getColumnCount());
            }

            ColumnGetter[] initColumnGetter(final int columnCount) { //NOSONAR
                final ColumnGetter[] rsColumnGetters = new ColumnGetter[columnCount];
                final ColumnGetter defaultColumnGetter = columnGetterMap.get(0);

                for (int i = 0, len = rsColumnGetters.length; i < len; i++) {
                    rsColumnGetters[i] = columnGetterMap.getOrDefault(i + 1, defaultColumnGetter);
                }

                return rsColumnGetters;
            }

            /**
             * Don't cache or reuse the returned {@code RowExtractor} instance.
             *
             * @return
             */
            @SequentialOnly
            @Stateful
            public RowExtractor build() {
                return new RowExtractor() {
                    private ColumnGetter[] rsColumnGetters = null;
                    private int rsColumnCount = -1;

                    @Override
                    public void accept(final ResultSet rs, final Object[] outputRow) throws SQLException {
                        if (rsColumnGetters == null) {
                            rsColumnGetters = initColumnGetter(outputRow.length);
                            rsColumnCount = rsColumnGetters.length - 1;
                        }

                        for (int i = 0; i < rsColumnCount; i++) {
                            outputRow[i] = rsColumnGetters[i].apply(rs, i + 1);
                        }
                    }
                };
            }
        }
    }

    @FunctionalInterface
    public interface ColumnGetter {

        ColumnGetter GET_BOOLEAN = ResultSet::getBoolean;

        ColumnGetter GET_BYTE = ResultSet::getByte;

        ColumnGetter GET_SHORT = ResultSet::getShort;

        ColumnGetter GET_INT = ResultSet::getInt;

        ColumnGetter GET_LONG = ResultSet::getLong;

        ColumnGetter GET_FLOAT = ResultSet::getFloat;

        ColumnGetter GET_DOUBLE = ResultSet::getDouble;

        ColumnGetter GET_BIG_DECIMAL = ResultSet::getBigDecimal;

        ColumnGetter GET_STRING = ResultSet::getString;

        ColumnGetter GET_DATE = ResultSet::getDate;

        ColumnGetter