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

io.lightlink.sql.SQLHandler Maven / Gradle / Ivy

package io.lightlink.sql;

/*
 * #%L
 * lightlink-core
 * %%
 * Copyright (C) 2015 Vitaliy Shevchuk
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */


import io.lightlink.autostop.AutoStopQuery;
import io.lightlink.core.RunnerContext;
import io.lightlink.facades.SQLFacade;
import io.lightlink.output.ResponseStream;
import io.lightlink.translator.ScriptTranslator;
import io.lightlink.types.*;
import io.lightlink.utils.Utils;
import jdk.nashorn.api.scripting.JSObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.script.ScriptException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.*;

public class SQLHandler {

    public static final Logger LOG = LoggerFactory.getLogger(SQLHandler.class);

    PreparedStatement batchPS;
    CallableStatement batchCS;
    List batchArgInfos;
    private String batchSQL;

    private SQLFacade facade;

    public SQLHandler(SQLFacade facade) {
        this.facade = facade;
        this.facade = facade;
    }

    public void query(boolean addBatch, String resultSetName, RunnerContext runnerContext, String sql, JSObject rowHandler)
            throws SQLException, IOException, ScriptException {


        LOG.info("SQL:" + sql);

        if (sql == null || Utils.isBlank(sql)) {

            if (!addBatch && batchPS != null) {
                batchPS.addBatch();

                int[] updateCounts = batchPS.executeBatch();

                ArrayList updateCountList = new ArrayList(updateCounts.length);
                for (int updateCount : updateCounts) {
                    updateCountList.add(updateCount);
                }

                facade.setUpdateCount(updateCountList);
                batchPS = batchCS = null;
            }
            return;
        }


        Connection conn = facade.getConnection();

        // guess IN/OUT params

        List argInfos = new ArrayList();

        sql = guessArgs(runnerContext, sql, argInfos);
        try {
            prepareInArgsValues(conn, runnerContext, argInfos);


            // prepare/callable statement
            boolean outParam = false;
            for (ArgInfo argInfo : argInfos) {
                outParam = outParam || argInfo.isOut();
            }
            PreparedStatement ps;
            CallableStatement cs;

            if (outParam) {
                if (batchCS == null && batchPS != null) {
                    throw new RuntimeException("Cannot mix PreparedStatement and CallableStatement in  a batch");
                } else if (batchCS != null) {
                    ps = cs = batchCS;
                    checkSqlAndAddBatch(sql, ps);
                } else {
                    ps = cs = conn.prepareCall(sql);
                }
            } else {
                if (batchPS != null) {
                    ps = batchPS;
                    cs = batchCS;
                    checkSqlAndAddBatch(sql, ps);
                } else {
                    ps = conn.prepareStatement(sql);
                    cs = null;
                }
            }

            // inject in params & declare out params
            for (int i = 0; i < argInfos.size(); i++) {
                ArgInfo argInfo = argInfos.get(i);
                Integer sqlType = argInfo.findOutSqlType();
                if (argInfo.isIn()) {
                    Object value = argInfo.getValue();

                    AbstractConverter converter = argInfo.getConverter();
                    if (value == null) {
                        if (sqlType == null)
                            ps.setObject(i + 1, null);
                        else if (converter != null && converter.getCustomSQLTypeName() != null)
                            ps.setNull(i + 1, sqlType, converter.getCustomSQLTypeName());
                        else
                            ps.setNull(i + 1, sqlType);
                    } else if (value instanceof ByteArrayInputStream) {
                        int bytes = ((ByteArrayInputStream) value).available();
                        System.out.println("sql = " + sql);
                        System.out.println("bytes = " + bytes);
                        ps.setBinaryStream(i + 1, (ByteArrayInputStream) value, bytes);
                    } else if (value instanceof InputStream) {
                        System.out.println("IS sql = " + sql);
                        System.out.println("IS bytes = " + ((InputStream) value).available());
                        ps.setBinaryStream(i + 1, (InputStream) value);
                    } else if (sqlType != null) {
                        ps.setObject(i + 1, value, sqlType);
                    } else {
                        ps.setObject(i + 1, value);
                    }
                }
                if (argInfo.isOut() && cs != null) {
                    if (sqlType == null)
                        sqlType = Types.VARCHAR;

                    String typeName = argInfo.findOutSqlTypeName();
                    if (typeName != null)
                        cs.registerOutParameter(i + 1, sqlType, typeName);
                    else
                        cs.registerOutParameter(i + 1, sqlType);
                }

            }

            if (facade.getQueryTimeout() != null)
                ps.setQueryTimeout(facade.getQueryTimeout());
            ps.setFetchSize(facade.getFetchSize());
            if (facade.getMaxRows() != null)
                ps.setMaxRows(facade.getMaxRows());

            if (addBatch) {
                batchPS = ps;
                batchCS = cs;
                batchArgInfos = argInfos;
                batchSQL = sql;
                // save references so that they will be reused and executed for next queries
            } else {
                preparedStatementExecute(resultSetName, runnerContext, rowHandler, argInfos, ps, cs);
                batchPS = batchCS = null;
            }
        } catch (SQLException e) {
            throw new SQLException("SQL:" + sql + " \n" + e.toString(), e);
        }


    }

    private void checkSqlAndAddBatch(String sql, PreparedStatement ps) throws SQLException {
        // remove all whitespaces and compare SQLs
        if (!sql.replaceAll("\\s", "").equalsIgnoreCase(batchSQL.replaceAll("\\s", ""))) {
            // if different warn developer
            throw new RuntimeException("SQL statement: \n" + sql + "\n does not match to the initial one on a batch:\n" + batchSQL);
        }
        ps.addBatch();
    }

    private void preparedStatementExecute(String resultSetName, RunnerContext runnerContext, JSObject rowHandler, List argInfos, PreparedStatement ps, CallableStatement cs) throws SQLException, IOException {
        // execute
        boolean resultsAvailable;
        try {
            AutoStopQuery.getInstance().register(runnerContext, ps);
            resultsAvailable = ps.execute();
        } finally {
            AutoStopQuery.getInstance().unregister(runnerContext, ps);
        }

        loadResultSets(resultSetName, runnerContext, ps, resultsAvailable, rowHandler);

        if (cs != null) // if out params present
            loadOutData(argInfos, runnerContext, cs, rowHandler);
    }

    private void loadResultSets(String resultSetName, RunnerContext runnerContext, PreparedStatement ps, boolean resultsAvailable, JSObject rowHandler) throws SQLException, IOException {


        List updateCount = new ArrayList();


        while (true) {
            if (!resultsAvailable) {
                int uc = ps.getUpdateCount();
                if (uc == -1) {
                    break;
                } else {
                    updateCount.add(uc);
                }
            } else {
                ResultSet rs = ps.getResultSet();

                loadResultSet(runnerContext, makeUniqueResultSetName(runnerContext, resultSetName), rs, rowHandler);
            }

            resultsAvailable = ps.getMoreResults();
        }
        facade.setUpdateCount(updateCount);

    }

    private String makeUniqueResultSetName(RunnerContext runnerContext, String resultSetName) {

        if (resultSetName == null || Utils.isBlank(resultSetName))
            resultSetName = "resultSet";

        Set usedNames = runnerContext.getUsedResultSetNames();

        String newResultSetName = resultSetName;
        int counter = 2;

        while (usedNames.contains(newResultSetName)) {
            newResultSetName = resultSetName + counter;
            counter++;
        }
        usedNames.add(newResultSetName);
        return newResultSetName;

    }

    private void loadResultSet(RunnerContext runnerContext, String rsName, ResultSet rs, JSObject rowHandler) throws SQLException, IOException {

        ResponseStream resp = runnerContext.getResponseStream();

        resp.writePropertyArrayStart(rsName);

        rs.setFetchSize(facade.getFetchSize());

        try {
            ResultSetMetaData metaData = rs.getMetaData();
            int cnt = metaData.getColumnCount();
            String[] cols = new String[cnt];
            String[] outNames = new String[cnt];
            AbstractConverter[] convertors = new AbstractConverter[cnt];

            for (int i = 0; i < cols.length; i++) {
                String label = metaData.getColumnLabel(i + 1);
                cols[i] = label;
                if (label.startsWith("(")) {
                    ArgInfo argInfo = new ArgInfo(label);
                    applyDirectivesToArgInfo(runnerContext, argInfo);
                    convertors[i] = argInfo.getConverter();
                    outNames[i] = label.substring(label.lastIndexOf(")") + 1);
                } else {
                    outNames[i] = label;
                }
            }

            JSObject line = (rowHandler == null)
                    ? null // not needed without rowHandler
                    : runnerContext.newJSObject();

            int index = 0;
            while (rs.next()) {
                if (rowHandler != null) {

                    for (int i = 0; i < cols.length; i++) {

                        Object value = (convertors[i] != null)
                                ? convertors[i].readFromResultSet(rs, i + 1, runnerContext, cols[i])
                                : rs.getObject(i + 1);

                        line.setMember(outNames[i], value);
                    }
                    Object res = rowHandler.call(rowHandler, line, index, rsName);
                    if (res instanceof Map)
                        res = new LinkedHashMap((Map) res);
                    else if (res instanceof List)
                        res = new ArrayList((List) res);
                    // todo : real deep copy


                    resp.writeFullObjectToArray(genericConvertFromJdbc(runnerContext, res));

                } else {

                    resp.writeObjectStart();
                    for (int i = 0; i < cols.length; i++) {
                        Object value = (convertors[i] != null)
                                ? convertors[i].readFromResultSet(rs, i + 1, runnerContext, cols[i])
                                : rs.getObject(i + 1);

                        resp.writeProperty(outNames[i], genericConvertFromJdbc(runnerContext, value));
                    }
                    resp.writeObjectEnd();

                }
                index++;

            }


        } finally {
            resp.writePropertyArrayEnd();
            rs.close();
        }
    }


    protected void loadOutData(List args, RunnerContext runnerContext, CallableStatement cs, JSObject rowHandler)
            throws SQLException, IOException {


        int pos = 0;
        for (ArgInfo arg : args) {
            pos++;

            if (arg.isOut()) {
                Object value;


                value = cs.getObject(pos);

                String name = arg.getName();
                if (name.contains(")"))
                    name = name.substring(name.lastIndexOf(')') + 1);
                if (name.startsWith("p."))
                    name = name.substring(2);

                if (arg.getConverter() != null) {
                    value = arg.getConverter().readFromCallableStatement(cs, pos, runnerContext, name);
                } else {
                    value = genericConvertFromJdbc(runnerContext, value);
                }

                if (value instanceof ResultSet) {
                    ResultSet rs = ((ResultSet) value);
                    loadResultSet(runnerContext, name, rs, rowHandler);
                } else
                    runnerContext.getResponseStream().writeProperty(name, value);
            }
        }
    }


    protected Map processMap(RunnerContext runnerContext, Map map) throws SQLException {
// todo : why not called ?
        for (Map.Entry entry : map.entrySet()) {
            entry.setValue(genericConvertFromJdbc(runnerContext, entry.getValue()));
        }
        return map;
    }

    public static Object genericConvertFromJdbc(RunnerContext runnerContext, Object value) throws SQLException {

        if (value instanceof Array) {
            Object[] array = (Object[]) ((Array) value).getArray();
            for (int i = 0; i < array.length; i++) {
                Object el = array[i];
                array[i] = genericConvertFromJdbc(runnerContext, el);
            }
            value = array;
        } else if (value instanceof Struct) {
            Struct struct = (Struct) value;
            Object[] attributes = struct.getAttributes();
            for (int i = 0; i < attributes.length; i++) {
                Object el = attributes[i];
                attributes[i] = genericConvertFromJdbc(runnerContext, el);
            }
            value = attributes;
        } else if (value instanceof Clob) {
            Clob clob = (Clob) value;
            value = clob.getCharacterStream();
        } else if (value instanceof java.util.Date && runnerContext != null) {
            value = runnerContext.getTypesFacade().dateToString((java.util.Date) value);
        }
        // any convertions will be done here
        return value;
    }


    private void prepareInArgsValues(Connection connection, RunnerContext runnerContext, List argInfos) throws IOException, SQLException {
        for (ArgInfo argInfo : argInfos) {
            if (argInfo.isIn()) {
                String name = argInfo.getName();
                Object value = null;
                try {
                    value = runnerContext.getParam(name);
                    if (LOG.isDebugEnabled())
                        LOG.debug("for " + name + " value:" + value);

                } catch (ScriptException e) {
                    throw new IllegalArgumentException("Cannot evaluate the value of parameter:");
                }

                AbstractConverter convertor = argInfo.getConverter();
                if (convertor != null) {
                    value = Utils.tryConvertToJavaCollections(value);
                    Object newValue = convertor.convertToJdbc(connection, runnerContext, name, value);
                    argInfo.setValue(newValue);
                } else {
                    argInfo.setValue(value);
                }
            }
        }
    }

    public static String guessArgs(RunnerContext runnerContext, String sql, List args) {
        List usedArgs = new ArrayList();
        StringBuilder sb = new StringBuilder(sql);
        int pos = sb.indexOf(":");
        while (pos != -1) {
            if (pos != 0 && sb.charAt(pos - 1) != ':' && pos < sb.length() - 1 && ScriptTranslator.isBindingExpressionChar(sb.charAt(pos), sb.charAt(pos + 1), "")) {
                int pos2 = pos + 1;
                while (pos2 < sb.length() && ScriptTranslator.isBindingExpressionChar(sb.charAt(pos2 - 1), sb.charAt(pos2)
                        , sb.substring(pos, pos2)))
                    pos2++;
                String arg = sb.substring(pos + 1, pos2).trim();
                sb.replace(pos, pos2, "?");
                pos -= arg.length();

                usedArgs.add(arg);
            }
            pos = sb.indexOf(":", pos + 1);
        }
        sql = sb.toString().replaceAll("::", ":");

        for (String fullName : usedArgs) {

            ArgInfo argInfo = new ArgInfo(fullName);
            args.add(argInfo);

            applyDirectivesToArgInfo(runnerContext, argInfo);
        }
        return sql;
    }

    public static void applyDirectivesToArgInfo(RunnerContext runnerContext, ArgInfo argInfo) {
        List directives = findDirectives(argInfo.getName());
        for (String directive : directives) {
            String directiveLower = directive.toLowerCase();
            if (directive.equalsIgnoreCase("inout")) {
                argInfo.setIn(true);
                argInfo.setOut(true);
                continue;
            } else if (directive.equalsIgnoreCase("out")) {
                argInfo.setIn(false);
                argInfo.setOut(true);
                continue;
            }
            String parts[] = directive.split("\\.", 2);

            AbstractConverter converter;

            if (directiveLower.startsWith("blob")) {
                converter = BlobConverter.getInstance();
            } else if (directiveLower.startsWith("array"))
                converter = ArrayConverter.getInstance();
            else if (directiveLower.startsWith("json"))
                converter = JSONConverter.getInstance();
            else if (directive.equalsIgnoreCase("number") || directive.equalsIgnoreCase("numeric"))
                converter = NumberConverter.getInstance();
            else if (directive.equalsIgnoreCase("date"))
                converter = DateConverter.getInstance();
            else
                converter = runnerContext.getTypesFacade().getCustomConverter(parts[0]);

            if (converter != null && parts.length > 1) {
                converter.setConfig(parts[1]);
            }

            argInfo.setConverter(converter);

        }

        if (!argInfo.isOut())
            argInfo.setIn(true); // default direction even if (in) directive is missing
    }

    private static List findDirectives(String fullName) {
        ArrayList directives = new ArrayList();
        while (fullName.length() > 1 && fullName.charAt(0) == '(' && fullName.contains(")")) {
            int pos2 = fullName.indexOf(')');
            directives.add(fullName.substring(1, pos2));
            fullName = fullName.substring(pos2 + 1);
        }
        return directives;
    }


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy