aurasphere/gomorra-sql

View on GitHub
src/main/java/co/aurasphere/gomorrasql/GomorraSqlInterpreter.java

Summary

Maintainability
A
1 hr
Test Coverage
A
98%
package co.aurasphere.gomorrasql;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.StringTokenizer;
import java.util.stream.Collectors;

import co.aurasphere.gomorrasql.model.CaggiaFaException;
import co.aurasphere.gomorrasql.model.GomorraSqlQueryResult;
import co.aurasphere.gomorrasql.model.QueryInfo;
import co.aurasphere.gomorrasql.model.WhereCondition;
import co.aurasphere.gomorrasql.states.AbstractState;
import co.aurasphere.gomorrasql.states.InitialState;

/**
 * Interpreter for the GomorraSQL.
 * 
 * @author Donato Rimenti
 *
 */
public class GomorraSqlInterpreter {

    private Connection connection;

    public GomorraSqlInterpreter(Connection connection) {
        this.connection = connection;
    }

    /**
     * Converts a GomorraSQL query into a standard SQL query.
     * 
     * @param gomorraQuery the query to convert
     * @return an equivalent SQL query
     */
    public static String toSqlQuery(String gomorraQuery) {
        QueryInfo info = parseQuery(gomorraQuery);
        return buildSqlQuery(info);
    }

    private static QueryInfo parseQuery(String query) {
        AbstractState currentState = new InitialState();
        // TODO: bug: whitespaces inside quotes should be ignored
        StringTokenizer tokenizer = new StringTokenizer(query, ", ", true);
        while (tokenizer.hasMoreTokens()) {
            String nextToken = tokenizer.nextToken().trim();
            if (!nextToken.isEmpty()) {
                currentState = currentState.transitionToNextState(nextToken);
            }
        }

        if (!currentState.isFinalState()) {
            throw new CaggiaFaException("Unexpected end of query");
        }

        return currentState.getQueryInfo();
    }

    private static String buildSqlQuery(QueryInfo queryInfo) {
        switch (queryInfo.getType()) {
        case SELECT:
            return buildSelectQuery(queryInfo);
        case INSERT:
            return buildInsertQuery(queryInfo);
        case UPDATE:
            return buildUpdateQuery(queryInfo);
        case DELETE:
            return buildDeleteQuery(queryInfo);
        case BEGIN_TRANSACTION:
            return "START_TRANSACTION";
        case COMMIT:
            return "COMMIT";
        case ROLLBACK:
            return "ROLLBACK";
        }
        throw new CaggiaFaException("Unrecognised query");
    }

    private static String buildDeleteQuery(QueryInfo queryInfo) {
        return "DELETE FROM " + queryInfo.getTableName() + buildWhereClause(queryInfo);
    }

    private static String buildWhereClause(QueryInfo queryInfo) {
        List<WhereCondition> whereConditions = queryInfo.getWhereConditions();
        List<String> whereConditionsJoinOperators = queryInfo.getWhereConditionsJoinOperators();
        if (whereConditions.isEmpty()) {
            return "";
        }
        StringBuilder whereClause = new StringBuilder(" WHERE ");
        for (int i = 0; i < whereConditions.size(); i++) {
            WhereCondition whereCondition = whereConditions.get(i);
            whereClause.append(whereCondition.getField()).append(" ").append(whereCondition.getOperator()).append(" ")
                    .append(whereCondition.getValue());
            if (whereConditionsJoinOperators.size() >= i + 1) {
                whereClause.append(" ").append(whereConditionsJoinOperators.get(i)).append(" ");
            }
        }
        return whereClause.toString();
    }

    private static String buildUpdateQuery(QueryInfo queryInfo) {
        StringBuilder query = new StringBuilder("UPDATE ").append(queryInfo.getTableName()).append(" SET ");
        for (int i = 0; i < queryInfo.getColumnNames().size(); i++) {
            query.append(queryInfo.getColumnNames().get(i)).append(" = ").append(queryInfo.getValues().get(i))
                    .append(", ");
        }
        query.delete(query.length() - 2, query.length());
        return query.toString() + buildWhereClause(queryInfo);
    }

    private static String buildInsertQuery(QueryInfo queryInfo) {
        StringBuilder query = new StringBuilder("INSERT INTO ").append(queryInfo.getTableName());
        if (!queryInfo.getColumnNames().isEmpty()) {
            query.append(" ( ").append(queryInfo.getColumnNames().stream().collect(Collectors.joining(", ")))
                    .append(" )");
        }
        query.append(" VALUES ( ").append(queryInfo.getValues().stream().collect(Collectors.joining(", ")))
                .append(" )");
        return query.toString();
    }

    private static String buildSelectQuery(QueryInfo queryInfo) {
        String query = "SELECT ";

        // Column names
        query += queryInfo.getColumnNames().stream().collect(Collectors.joining(", "));

        // Table name
        query += " FROM " + queryInfo.getTableName();

        // Joins
        List<String> joinedTables = queryInfo.getJoinedTables();
        if (!joinedTables.isEmpty()) {
            query += " INNER JOIN " + joinedTables.stream().collect(Collectors.joining(" INNER JOIN "));
        }
        // Where
        query += buildWhereClause(queryInfo);

        return query;
    }

    /**
     * Executes the given GomorraSQL query in the connected database.
     * 
     * @param gomorraSqlQuery the query to execute
     * @return the result of the query
     */
    public GomorraSqlQueryResult execute(String gomorraSqlQuery) {
        QueryInfo queryInfo = parseQuery(gomorraSqlQuery);
        String sqlQuery = buildSqlQuery(queryInfo);
        GomorraSqlQueryResult result = new GomorraSqlQueryResult();

        // Executes the query
        try {
            Statement statement = connection.createStatement();
            switch (queryInfo.getType()) {
            case SELECT:
                ResultSet resultSet = statement.executeQuery(sqlQuery);
                result.setResultSet(resultSet);
                break;
            case BEGIN_TRANSACTION:
                connection.setAutoCommit(false);
                break;
            case COMMIT:
                connection.commit();
                connection.setAutoCommit(true);
                break;
            case DELETE:
            case UPDATE:
            case INSERT:
                int updatedRows = statement.executeUpdate(sqlQuery);
                result.setAffectedRows(updatedRows);
                break;
            case ROLLBACK:
                connection.rollback();
                connection.setAutoCommit(true);
                break;
            }
        } catch (SQLException e) {
            throw new CaggiaFaException(e);
        }
        return result;
    }

}