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

impl.H2DatabaseConnection Maven / Gradle / Ivy

package impl;

import api.Column;
import api.Table;
import api.sql.SQLDatabaseConnection;
import api.sql.SQLSchema;
import api.sql.SQLTable;
import api.sql.constraint.Constraint;
import api.sql.constraint.NotNull;
import api.sql.constraint.PrimaryKey;

import java.sql.*;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public abstract class H2DatabaseConnection implements SQLDatabaseConnection {
    private static class H2DatabaseColumn implements Column{
        private final String name;
        private final String dataType;

        private H2DatabaseColumn(String name, String dataType) {
            this.name = name;
            this.dataType = dataType;
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public String getDatatype() {
            return dataType;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            H2DatabaseColumn that = (H2DatabaseColumn) o;
            return Objects.equals(name, that.name) &&
                    Objects.equals(dataType, that.dataType);
        }

        @Override
        public int hashCode() {
            return Objects.hash(name, dataType);
        }

        @Override
        public String toString() {
            return "H2DatabaseColumn{" +
                    "name='" + name + '\'' +
                    ", dataType='" + dataType + '\'' +
                    '}';
        }
    }

    private static class H2DatabaseTable implements SQLTable{
        private final Set columns = new HashSet<>();
        private final Set constraints = new HashSet<>();

        private final String name;

        private H2DatabaseTable(String name) {
            assert name != null;
            this.name = name;
        }

        @Override
        public Set getConstraints() {
            return constraints;
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public Set getColumns() {
            return columns;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            H2DatabaseTable that = (H2DatabaseTable) o;
            return Objects.equals(columns, that.columns) &&
                    Objects.equals(constraints, that.constraints) &&
                    Objects.equals(name, that.name);
        }

        @Override
        public int hashCode() {
            return Objects.hash(columns, constraints, name);
        }

        @Override
        public String toString() {
            return "H2DatabaseTable{" +
                    "columns=" + columns +
                    ", constraints=" + constraints +
                    ", name='" + name + '\'' +
                    '}';
        }
    }

    protected abstract String buildDatabaseUrl();

    /**
     * Initializes the database according to the given schema. Tables which are mentioned in the
     * schema will be dropped if they already exist. All other tables will not be touched by this
     * commmand.
     * @param schemas The schema that the database have to fulfill
     */
    @Override
    public void init(Collection schemas) {
        try(Connection connection = DriverManager.getConnection(buildDatabaseUrl())) {
            schemas.forEach(sqlSchema -> initDatabase(sqlSchema, connection));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private void initDatabase(SQLSchema schema, Connection connection) {
        schema.getTables().forEach(table -> {
            try(Statement statement = connection.createStatement()){
                statement.execute(String.format("DROP TABLE IF EXISTS %s CASCADE", table.getName()));
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }

            List columns = new ArrayList<>(table.getColumns());
            StringBuilder columnDefinition = new StringBuilder();
            for (int i = 0; i < columns.size(); i++){
                if (i > 0){
                    columnDefinition.append(",");
                }
                columnDefinition.append(String.format("%s %s", columns.get(i).getName(), columns.get(i).getDatatype()));
                appendConstraints(table, columnDefinition, columns.get(i));
            }
            try(Statement statement = connection.createStatement()){
                String queryString = String.format("CREATE TABLE %s (%s)", table.getName(), columnDefinition);
                statement.execute(queryString);
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        });
    }

    @Override
    public void insert(Row data, Collection> predicates) {
        try(Connection connection = DriverManager.getConnection(buildDatabaseUrl())){
            queryTableNames(connection).forEach(tableName -> {
                try {
                    Table table = queryTableDefinition(connection, tableName);

                    if (predicates.stream().allMatch(tablePredicate -> tablePredicate.test(table))){
                        StringBuilder columnNames = new StringBuilder();
                        StringBuilder valuesHolder = new StringBuilder();
                        List values = new ArrayList<>(data.getColumns().size());
                        final boolean[] isFirstRow = {true};
                        data.getColumns().forEach(column -> {
                            if (!isFirstRow[0]){
                                columnNames.append(",");
                                valuesHolder.append(",");
                            }
                            columnNames.append(column.getName());
                            valuesHolder.append("?");
                            Optional value = data.get(column);
                            assert value.isPresent();
                            values.add(value.get());
                            isFirstRow[0] = false;
                        });
                        String query = String.format("INSERT INTO %s (%s) VALUES (%s)", table.getName(), columnNames, valuesHolder);
                        try(PreparedStatement statement = connection.prepareStatement(query)){
                            for (int i = 0; i < values.size(); i++){
                                statement.setString(i+1, values.get(i).get());
                            }
                            statement.execute();
                        }
                    }
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            });
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Stream read(Collection> predicates) {
        try(Connection connection = DriverManager.getConnection(buildDatabaseUrl())) {
            List tables = queryTableNames(connection);
            if (tables.isEmpty()){
                return Stream.empty();
            }

            List rows = new LinkedList<>();

            tables.parallelStream().forEach(tableName -> {
                H2DatabaseTable table;
                try{
                    table = queryTableDefinition(connection, tableName);
                } catch (SQLException e){
                    throw new RuntimeException(e);
                }

                try(Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) {
                    ResultSet constraintQueryResult = statement.executeQuery(String.format("SELECT * FROM %s", tableName));
                    if (constraintQueryResult.last()){
                        constraintQueryResult.beforeFirst();
                        while (constraintQueryResult.next()){
                            Map columnValues = new HashMap<>();
                            table.columns.forEach(column -> {
                                try {
                                    String valueString = constraintQueryResult.getString(column.getName());
                                    Value value = new Value("");
                                    if (valueString != null){
                                        value = new Value(valueString);
                                    }
                                    columnValues.put(column, value);

                                } catch (SQLException e) {
                                    throw new RuntimeException(e);
                                }
                            });
                            if (columnValues.entrySet().stream().allMatch(columnValueEntry -> {
                                final boolean[] result = {true};
                                predicates.forEach(tableColumnPredicate -> {
                                    if (!tableColumnPredicate.test(new TableColumn(
                                            table, columnValueEntry.getKey(), columnValueEntry.getValue())
                                    )){
                                        result[0] = false;
                                    }
                                });
                                return result[0];
                            })){
                                rows.add(new Row(columnValues));
                            }
                        }
                    }
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            });

            return rows.stream();
        } catch (SQLException e){
            throw new RuntimeException(e);
        }
    }

    private H2DatabaseTable queryTableDefinition(Connection connection, String tableName) throws SQLException{
        H2DatabaseTable table = new H2DatabaseTable(tableName);
        try(Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) {
            ResultSet constraintQueryResult = statement.executeQuery(String.format(
                    "select * from information_schema.columns where table_name = '%s';", tableName));
            if (constraintQueryResult.last()){
                //columns have been found for the current table
                constraintQueryResult.beforeFirst();
                while (constraintQueryResult.next()){
                    String columnName = constraintQueryResult.getString("COLUMN_NAME");
                    String[] dataTypeParts = constraintQueryResult.getString("COLUMN_TYPE").split(" ");
                    String dataType = dataTypeParts[0];
                    table.columns.add(new H2DatabaseColumn(columnName, dataType));
                }
            }
        }
        return table;
    }

    private List queryTableNames(Connection connection) throws SQLException {
        List tables = new LinkedList<>();
        try (Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) {
            ResultSet tablesQueryResult = statement.executeQuery("SHOW TABLES");

            if (!tablesQueryResult.last()) {
                return Collections.emptyList();
            }

            tablesQueryResult.beforeFirst();
            while (tablesQueryResult.next()) {
                tables.add(tablesQueryResult.getString(1));
            }
        }
        return tables;
    }

    /**
     * Appends the constraint string to the given column definition string
     * @param table The table containing the constraints
     * @param columnDefinition The column definition string the constraints shall be appended to
     * @param column The column the constraints refer to
     */
    private void appendConstraints(SQLTable table, StringBuilder columnDefinition, Column column) {
        Set constraints = table.getConstraints().stream().filter(
                constraint -> Objects.equals(constraint.getConstrainedColumn(), column)
        ).collect(Collectors.toSet());
        constraints.forEach(constraint -> {
            if (constraint instanceof PrimaryKey){
                columnDefinition.append(" PRIMARY KEY");
            }
            if (constraint instanceof NotNull){
                columnDefinition.append(" NOT NULL");
            }
        });
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy