Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ public static String extractCookieByKey(String cookieKey, List<String> cookies)
.orElse(null);
}

Engine makeEngine(String databaseNickname) throws SQLException, URISyntaxException, Engine.InvalidCredentialsException, Engine.OverwritingUserAndCredentialedJdbcConflictedException, Engine.UnreachableServerException, Engine.InvalidDatabaseNameProbablyException, BackingStoreException, Engine.GenericConnectionException {
public Engine makeEngine(String databaseNickname) throws SQLException, URISyntaxException, Engine.InvalidCredentialsException, Engine.OverwritingUserAndCredentialedJdbcConflictedException, Engine.UnreachableServerException, Engine.InvalidDatabaseNameProbablyException, BackingStoreException, Engine.GenericConnectionException {
var databaseConfig = Arrays.stream(getAllDatabaseConfigs()).filter(d -> d.nickname.equals(databaseNickname)).findFirst().orElse(null);

if (databaseConfig == null) {
Expand Down Expand Up @@ -827,7 +827,7 @@ protected IResponse processIndexPage(IRequest req) throws Exception {
);
}

private JsonValue[][] readRows(Engine engine, Column[] columns, ResultSet rs) throws SQLException {
public JsonValue[][] readRows(Engine engine, Column[] columns, ResultSet rs) throws SQLException {
var rows = new ArrayList<JsonValue[]>();
while (rs.next()) {
var row = new JsonValue[columns.length];
Expand Down
46 changes: 46 additions & 0 deletions core/src/main/java/tanin/backdoor/core/CsvWriter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package tanin.backdoor.core;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class CsvWriter implements AutoCloseable {

FileWriter fileWriter;
BufferedWriter bufferedWriter;
boolean startOfTheLine = true;

public CsvWriter(String filePath) throws IOException {
fileWriter = new java.io.FileWriter(filePath);
bufferedWriter = new java.io.BufferedWriter(fileWriter);
}

public void addValue(String value) throws IOException {
if (startOfTheLine) {
startOfTheLine = false;
} else {
bufferedWriter.write(',');
}

if (value == null || value.isEmpty()) {
bufferedWriter.write("");
} else {
bufferedWriter.write('"' + value.replace("\"", "\"\"") + '"');
}
}

public void newLine() throws IOException {
bufferedWriter.newLine();
startOfTheLine = true;
}

public void flush() throws IOException {
bufferedWriter.flush();
}

@Override
public void close() throws Exception {
bufferedWriter.close();
fileWriter.close();
}
}
2 changes: 1 addition & 1 deletion core/src/main/java/tanin/backdoor/core/Filter.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public enum Operator {
public String value;
public Operator operator;

Filter(String name, String value, Operator operator) {
public Filter(String name, String value, Operator operator) {
this.name = name;
this.value = value;
this.operator = operator;
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/tanin/backdoor/core/Sort.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class Sort {
public String name;
public String direction;

Sort(String name, String direction) {
public Sort(String name, String direction) {
this.name = name;
this.direction = direction;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.eclipsesource.json.JsonValue;
import tanin.backdoor.core.*;

import java.io.IOException;
import java.math.BigDecimal;
import java.net.URISyntaxException;
import java.sql.DriverManager;
Expand All @@ -15,7 +16,10 @@
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;

Expand Down Expand Up @@ -71,7 +75,7 @@ protected void connect(DatabaseConfig config, DatabaseUser overwritingUser) thro
}

@Override
public Column[] getColumns(String table) throws SQLException {
public Column[] getColumns(String table) throws SQLException, IOException {
AtomicReference<String> databaseName = new AtomicReference<>();
executeQuery(
"SELECT currentDatabase();",
Expand Down Expand Up @@ -108,7 +112,7 @@ public Column[] getColumns(String table) throws SQLException {
}

@Override
public String[] getTables() throws SQLException {
public String[] getTables() throws SQLException, IOException {
var tables = new ArrayList<String>();
executeQuery(
"SHOW TABLES;",
Expand Down Expand Up @@ -304,6 +308,7 @@ public JsonValue getJsonValue(ResultSet rs, int columnIndex, Column column) thro
};
}


private JsonValue convertHashmapToJson(HashMap<Object, Object> value) {
var json = new JsonObject();
for (var entry : value.entrySet()) {
Expand Down
56 changes: 43 additions & 13 deletions core/src/main/java/tanin/backdoor/core/engine/Engine.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.eclipsesource.json.JsonValue;
import tanin.backdoor.core.*;

import java.io.IOException;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.DriverManager;
Expand Down Expand Up @@ -90,9 +91,9 @@ protected Engine(DatabaseConfig config, DatabaseUser overwritingUser) throws SQL

protected abstract void connect(DatabaseConfig config, DatabaseUser overwritingUser) throws SQLException, InvalidCredentialsException, URISyntaxException, UnreachableServerException, InvalidDatabaseNameProbablyException, GenericConnectionException;

public abstract Column[] getColumns(String table) throws SQLException;
public abstract Column[] getColumns(String table) throws SQLException, IOException;

public abstract String[] getTables() throws SQLException;
public abstract String[] getTables() throws SQLException, IOException;

public abstract void insert(String table, Column[] columns, Engine.Value[] values) throws Exception;

Expand All @@ -102,7 +103,7 @@ protected Engine(DatabaseConfig config, DatabaseUser overwritingUser) throws SQL

public abstract void rename(String table, String newTableName) throws SQLException;

public abstract BackdoorCoreServer.SqlType getSqlType(String sql) throws SQLException;
public abstract BackdoorCoreServer.SqlType getSqlType(String sql) throws SQLException, IOException;

public abstract Column.ColumnType convertRawType(String rawType);

Expand All @@ -111,7 +112,7 @@ protected Engine(DatabaseConfig config, DatabaseUser overwritingUser) throws SQL
public Stats getStats(
String sql,
Filter[] filters
) throws SQLException {
) throws SQLException, IOException {
var whereClause = makeWhereClause(filters);
var tableStats = new Stats(0);
executeQuery(
Expand All @@ -130,20 +131,26 @@ public String makeLoadTableSql(String table, Column[] columns) {
" FROM " + makeSqlName(table);
}

public void executeQueryWithParams(String sql, Filter[] filters, Sort[] sorts, int offset, int limit, ProcessResultSet processResultSet) throws SQLException {
public String makeQuerySql(String sql, Filter[] filters, Sort[] sorts, int offset, int limit) {
var whereClause = makeWhereClause(filters);

var orderByClause = "";
if (sorts != null && sorts.length > 0) {
orderByClause = " ORDER BY " + String.join(", ", Arrays.stream(sorts).map(s -> makeSqlName(s.name) + " " + s.direction).toArray(String[]::new));
}

var limitClause = limit == -1 ? "" : " LIMIT " + limit;

return "SELECT * FROM (" + sql + ") " +
whereClause +
orderByClause +
limitClause +
" OFFSET " + offset;
}

public void executeQueryWithParams(String sql, Filter[] filters, Sort[] sorts, int offset, int limit, ProcessResultSet processResultSet) throws SQLException, IOException {
executeQuery(
"SELECT * FROM (" + sql + ") " +
whereClause +
orderByClause +
" LIMIT " + limit +
" OFFSET " + offset,
makeQuerySql(sql, filters, sorts, offset, limit),
processResultSet
);
}
Expand Down Expand Up @@ -174,7 +181,7 @@ public String makeWhereClause(Filter[] filters) {
return whereClause;
}

public void select(String tableName, Column column, Filter[] filters, ProcessResultSet processResultSet) throws SQLException {
public void select(String tableName, Column column, Filter[] filters, ProcessResultSet processResultSet) throws SQLException, IOException {
var whereClause = makeWhereClause(filters);
executeQuery(
"SELECT " + makeSqlName(column.name) + " FROM " + makeSqlName(tableName) + whereClause,
Expand All @@ -188,13 +195,13 @@ public void drop(String table, boolean useCascade) throws SQLException {
}

public interface ProcessResultSet {
void process(ResultSet rs) throws SQLException;
void process(ResultSet rs) throws SQLException, IOException;
}

public void executeQuery(
String sql,
ProcessResultSet processResultSet
) throws SQLException {
) throws SQLException, IOException {
logger.info("Executing query: " + sql);
try (var stmt = connection.createStatement()) {
try (var rs = stmt.executeQuery(sql)) {
Expand All @@ -216,4 +223,27 @@ public int executeUpdate(String sql) throws SQLException {
return stmt.executeUpdate(sql);
}
}

public void exportCsv(String path, String sql, Filter[] filters, Sort[] sorts) throws Exception {
try (var writer = new CsvWriter(path)) {
executeQuery(makeQuerySql(sql, filters, sorts, 0, -1), rs -> {
var meta = rs.getMetaData();
var columnCount = meta.getColumnCount();

for (int i = 1; i <= columnCount; i++) {
writer.addValue(meta.getColumnName(i));
}
writer.newLine();

while (rs.next()) {
for (int i = 1; i <= columnCount; i++) {
writer.addValue(rs.getString(i));
}
writer.newLine();
}

writer.flush();
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.postgresql.util.PSQLException;
import tanin.backdoor.core.*;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.DriverManager;
Expand Down Expand Up @@ -101,7 +102,7 @@ protected void connect(DatabaseConfig config, DatabaseUser overwritingUser) thro
}

@Override
public Column[] getColumns(String table) throws SQLException {
public Column[] getColumns(String table) throws SQLException, IOException {
var columns = new ArrayList<>(List.<Column>of());
executeQuery(
"SELECT c.column_name, c.data_type, c.is_nullable, " +
Expand Down Expand Up @@ -138,7 +139,7 @@ public Column[] getColumns(String table) throws SQLException {
}

@Override
public String[] getTables() throws SQLException {
public String[] getTables() throws SQLException, IOException {
var tables = new ArrayList<String>();
executeQuery(
"SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' ORDER BY table_name ASC;",
Expand Down Expand Up @@ -245,7 +246,7 @@ public void close() throws Exception {
}

@Override
public BackdoorCoreServer.SqlType getSqlType(String sql) throws SQLException {
public BackdoorCoreServer.SqlType getSqlType(String sql) throws SQLException, IOException {
var sanitized = sql.toLowerCase().trim();
if (sanitized.startsWith("explain")) {
return BackdoorCoreServer.SqlType.EXPLAIN;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
import com.eclipsesource.json.Json;
import com.eclipsesource.json.JsonValue;
import com.renomad.minum.web.*;
import tanin.backdoor.core.BackdoorCoreServer;
import tanin.backdoor.core.DatabaseConfig;
import tanin.backdoor.core.DatabaseUser;
import tanin.backdoor.core.GlobalSettings;
import tanin.backdoor.core.*;
import tanin.backdoor.desktop.engine.EngineProvider;
import tanin.ejwf.MinumBuilder;

Expand All @@ -15,6 +12,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
Expand Down Expand Up @@ -165,6 +163,47 @@ public FullSystem start() throws Exception {
}
);

wf.registerPath(
POST,
"api/export-file",
req -> {
var json = Json.parse(req.getBody().asString());
var path = json.asObject().get("path").asString();
var database = json.asObject().get("database").asString();
var sql = json.asObject().get("sql").asString().trim();
var filters = json.asObject().get("filters").asArray().values().stream().map(s -> {
var o = s.asObject();
var value = o.get("value");
var operator = Filter.Operator.valueOf(o.get("operator").asString());

return new Filter(o.get("name").asString(), value.asString(), operator);
}).toArray(Filter[]::new);
var sorts = json.asObject().get("sorts").asArray().values().stream().map(s -> {
var o = s.asObject();
var direction = o.get("direction").asString();

if (direction.equalsIgnoreCase("asc") || direction.equalsIgnoreCase("desc")) {
// good
} else {
throw new IllegalStateException("Invalid sort direction: " + direction);
}

return new Sort(o.get("name").asString(), o.get("direction").asString());
}).toArray(Sort[]::new);

try (var engine = makeEngine(database)) {
engine.exportCsv(path, sql, filters, sorts);

return Response.buildResponse(
StatusLine.StatusCode.CODE_200_OK,
Map.of("Content-Type", "application/json"),
Json.object().toString()
);
}

}
);

// We cannot use webview_bind due to the synchronous nature of it. The callback has to be blocked.
// However, if the callback is blocked, then the file dialog which needs to run on the main thread wouldn't show.
wf.registerPath(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import tanin.backdoor.desktop.nativeinterface.Base;
import tanin.backdoor.desktop.nativeinterface.MacOsApi;

import java.io.IOException;
import java.net.URISyntaxException;
import java.sql.DriverManager;
import java.sql.ResultSet;
Expand Down Expand Up @@ -59,7 +60,7 @@ protected void connect(DatabaseConfig config, DatabaseUser overwritingUser) thro
}

@Override
public Column[] getColumns(String table) throws SQLException {
public Column[] getColumns(String table) throws SQLException, IOException {
var columns = new ArrayList<Column>();
executeQuery(
"SELECT name, type, \"notnull\", pk, dflt_value FROM pragma_table_info('" + table + "')",
Expand Down Expand Up @@ -87,7 +88,7 @@ public Column[] getColumns(String table) throws SQLException {
}

@Override
public String[] getTables() throws SQLException {
public String[] getTables() throws SQLException, IOException {
var tables = new ArrayList<String>();
executeQuery(
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'",
Expand Down Expand Up @@ -197,7 +198,7 @@ public void close() throws Exception {
}

@Override
public BackdoorCoreServer.SqlType getSqlType(String sql) throws SQLException {
public BackdoorCoreServer.SqlType getSqlType(String sql) throws SQLException, IOException {
var sanitized = sql.toLowerCase().trim();
if (sanitized.startsWith("explain")) {
return BackdoorCoreServer.SqlType.EXPLAIN;
Expand Down
1 change: 1 addition & 0 deletions desktop/src/main/swift/MacOsApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ public func saveFile(
NSLog("Opening the save dialog")
let savePanel = NSSavePanel()
savePanel.canCreateDirectories = true
savePanel.nameFieldStringValue = "untitled.csv"

savePanel.begin { response in
if response == .OK {
Expand Down
Loading
Loading