package io.github.lucaseasedup.logit.storage; import io.github.lucaseasedup.logit.LogItCore; import io.github.lucaseasedup.logit.logging.CustomLevel; import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.LinkedList; import java.util.List; public final class PostgreSqlStorage implements Storage { public PostgreSqlStorage(String host, String user, String password) { if (host == null || user == null || password == null) throw new IllegalArgumentException(); if (!host.startsWith("jdbc:postgresql://")) { host = "jdbc:postgresql://" + host; } this.host = host; this.user = user; this.password = password; } @Override public void connect() throws IOException { try { Class.forName("org.postgresql.Driver"); connection = DriverManager.getConnection(host, user, password); statement = connection.createStatement(); } catch (ClassNotFoundException | SQLException ex) { throw new IOException(ex); } } @Override public boolean isConnected() throws IOException { try { return connection != null && !connection.isClosed(); } catch (SQLException ex) { throw new IOException(ex); } } @Override public void ping() throws IOException { try { statement.execute("SELECT 1"); } catch (SQLException ex) { throw new IOException(ex); } } @Override public void close() throws IOException { if (connection != null) { try { connection.close(); } catch (SQLException ex) { throw new IOException(ex); } finally { connection = null; } } if (statement != null) { try { statement.close(); } catch (SQLException ex) { throw new IOException(ex); } finally { statement = null; } } } @Override public List<String> getUnitNames() throws IOException { List<String> units = new LinkedList<>(); String sql = "SELECT table_schema, table_name FROM information_schema.tables" + " WHERE table_type = 'BASE TABLE'" + " AND table_schema NOT IN ('pg_catalog', 'information_schema');"; try (ResultSet rs = executeQuery(sql)) { while (rs.next()) { units.add(rs.getString("table_name")); } } catch (SQLException ex) { throw new IOException(ex); } return units; } @Override public UnitKeys getKeys(String unit) throws IOException { UnitKeys keys = new UnitKeys(); String sql = "SELECT COLUMN_NAME, UDT_NAME FROM INFORMATION_SCHEMA.COLUMNS" + " WHERE TABLE_NAME = '" + SqlUtils.escapeQuotes(unit, "'", true) + "';"; try (ResultSet tableInfo = executeQuery(sql)) { while (tableInfo.next()) { String name = tableInfo.getString("COLUMN_NAME"); DataType type = SqlUtils.decodeType(tableInfo.getString("UDT_NAME")); keys.put(name, type); } } catch (SQLException ex) { throw new IOException(ex); } return keys; } @Override public String getPrimaryKey(String unit) throws IOException { String sql = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE" + " WHERE TABLE_NAME = '" + SqlUtils.escapeQuotes(unit, "'", true) + "';"; try (ResultSet rs = executeQuery(sql)) { if (rs.next()) { return rs.getString("COLUMN_NAME"); } } catch (SQLException ex) { throw new IOException(ex); } return null; } @Override public List<StorageEntry> selectEntries(String unit) throws IOException { String sql = "SELECT * FROM \"" + SqlUtils.escapeQuotes(unit, "\"", true) + "\";"; try { return SqlUtils.copyResultSet(executeQuery(sql)); } catch (SQLException ex) { throw new IOException(ex); } } @Override public List<StorageEntry> selectEntries(String unit, Selector selector) throws IOException { String sql = "SELECT * FROM \"" + SqlUtils.escapeQuotes(unit, "\"", true) + "\"" + " WHERE " + SqlUtils.translateSelector(selector, "\"", "'") + ";"; try { return SqlUtils.copyResultSet(executeQuery(sql)); } catch (SQLException ex) { throw new IOException(ex); } } @Override public List<StorageEntry> selectEntries(String unit, List<String> keys) throws IOException { String sql = "SELECT " + SqlUtils.translateKeyList(keys, "\"") + " FROM \"" + SqlUtils.escapeQuotes(unit, "\"", true) + "\";"; try { return SqlUtils.copyResultSet(executeQuery(sql)); } catch (SQLException ex) { throw new IOException(ex); } } @Override public List<StorageEntry> selectEntries( String unit, List<String> keys, Selector selector ) throws IOException { String sql = "SELECT " + SqlUtils.translateKeyList(keys, "\"") + " FROM \"" + SqlUtils.escapeQuotes(unit, "\"", true) + "\"" + " WHERE " + SqlUtils.translateSelector(selector, "\"", "'") + ";"; try { return SqlUtils.copyResultSet(executeQuery(sql)); } catch (SQLException ex) { throw new IOException(ex); } } @Override public void createUnit(String unit, UnitKeys keys, String primaryKey) throws IOException { String sql = "CREATE TABLE IF NOT EXISTS" + " \"" + SqlUtils.escapeQuotes(unit, "\"", true) + "\"" + " (" + SqlUtils.translateKeyTypeList(keys, primaryKey, "\"") + ");"; try { executeStatement(sql); } catch (SQLException ex) { throw new IOException(ex); } } @Override public void renameUnit(String unit, String newName) throws IOException { String sql = "ALTER TABLE \"" + SqlUtils.escapeQuotes(unit, "\"", true) + "\"" + " RENAME TO \"" + SqlUtils.escapeQuotes(newName, "\"", true) + "\";"; try { executeStatement(sql); } catch (SQLException ex) { throw new IOException(ex); } } @Override public void eraseUnit(String unit) throws IOException { String sql = "TRUNCATE \"" + SqlUtils.escapeQuotes(unit, "\"", true) + "\";"; try { executeStatement(sql); } catch (SQLException ex) { throw new IOException(ex); } } @Override public void removeUnit(String unit) throws IOException { String sql = "DROP TABLE \"" + SqlUtils.escapeQuotes(unit, "\"", true) + "\";"; try { executeStatement(sql); } catch (SQLException ex) { throw new IOException(ex); } } @Override public void addKey(String unit, String key, DataType type) throws IOException { String sql = "ALTER TABLE \"" + SqlUtils.escapeQuotes(unit, "\"", true) + "\"" + " ADD COLUMN \"" + SqlUtils.escapeQuotes(key, "\"", true) + "\" " + SqlUtils.encodeType(type) + ";"; try { executeStatement(sql); } catch (SQLException ex) { throw new IOException(ex); } } @Override public void addEntry(String unit, StorageEntry entry) throws DuplicateEntryException, IOException { String sql = "INSERT INTO \"" + SqlUtils.escapeQuotes(unit, "\"", true) + "\"" + " (" + SqlUtils.translateEntryNames(entry, "\"") + ")" + " VALUES (" + SqlUtils.translateEntryValues(entry, "'") + ");"; try { executeStatement(sql); } catch (SQLException ex) { if ("23000".equals(ex.getSQLState())) { throw new DuplicateEntryException(); } else { throw new IOException(ex); } } } @Override public void updateEntries( String unit, StorageEntry entrySubset, Selector selector ) throws IOException { String sql = "UPDATE \"" + SqlUtils.escapeQuotes(unit, "\"", true) + "\"" + " SET " + SqlUtils.translateEntrySubset(entrySubset, "\"", "'") + " WHERE " + SqlUtils.translateSelector(selector, "\"", "'") + ";"; try { executeStatement(sql); } catch (SQLException ex) { throw new IOException(ex); } } @Override public void removeEntries(String unit, Selector selector) throws IOException { String sql = "DELETE FROM \"" + SqlUtils.escapeQuotes(unit, "\"", true) + "\"" + " WHERE " + SqlUtils.translateSelector(selector, "\"", "'") + ";"; try { executeStatement(sql); } catch (SQLException ex) { throw new IOException(ex); } } @Override public boolean isAutobatchEnabled() { return autobatch; } @Override public void setAutobatchEnabled(boolean status) { autobatch = status; } @Override public void executeBatch() throws IOException { try { statement.executeBatch(); statement.clearBatch(); } catch (SQLException ex) { throw new IOException(ex); } } @Override public void clearBatch() throws IOException { try { statement.clearBatch(); } catch (SQLException ex) { throw new IOException(ex); } } private ResultSet executeQuery(String sql) throws SQLException { LogItCore.getInstance().log(CustomLevel.INTERNAL, "(Q) " + sql); return statement.executeQuery(sql); } private boolean executeStatement(String sql) throws SQLException { if (!isAutobatchEnabled()) { LogItCore.getInstance().log(CustomLevel.INTERNAL, "(S) " + sql); return statement.execute(sql); } addBatch(sql); return false; } private void addBatch(String sql) throws SQLException { LogItCore.getInstance().log(CustomLevel.INTERNAL, "(BS) " + sql); statement.addBatch(sql); } private final String host; private final String user; private final String password; private Connection connection; private Statement statement; private boolean autobatch = false; }