From 95dec00085e2b4665690032b9128b36be2988e79 Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Sun, 27 Oct 2024 21:06:53 +0100 Subject: [PATCH] dbeaver/dbeaver#23361 Statement execution + client --- .../META-INF/MANIFEST.MF | 3 +- com.dbeaver.jdbc.driver.libsql/pom.xml | 5 + .../jdbc/driver/libsql/LSqlConnection.java | 22 +++- .../jdbc/driver/libsql/LSqlDriver.java | 11 +- .../jdbc/driver/libsql/LSqlResultSet.java | 2 +- .../driver/libsql/LSqlResultSetMetaData.java | 6 +- .../jdbc/driver/libsql/LSqlStatement.java | 3 +- .../jdbc/driver/libsql/client/LSqlClient.java | 100 ++++++++++++++++++ .../libsql/client/LSqlExecutionResult.java | 10 +- .../jdbc/upd/driver/test/LSqlDriverTest.java | 7 +- 10 files changed, 153 insertions(+), 16 deletions(-) create mode 100644 com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/client/LSqlClient.java diff --git a/com.dbeaver.jdbc.driver.libsql/META-INF/MANIFEST.MF b/com.dbeaver.jdbc.driver.libsql/META-INF/MANIFEST.MF index 689075f..ec96ce6 100644 --- a/com.dbeaver.jdbc.driver.libsql/META-INF/MANIFEST.MF +++ b/com.dbeaver.jdbc.driver.libsql/META-INF/MANIFEST.MF @@ -8,6 +8,7 @@ Bundle-RequiredExecutionEnvironment: JavaSE-17 Export-Package: com.dbeaver.jdbc.libsql Require-Bundle: com.dbeaver.jdbc.api, com.dbeaver.rpc, - org.jkiss.utils + org.jkiss.utils, + com.google.gson Bundle-Vendor: DBeaver Corp Automatic-Module-Name: com.dbeaver.jdbc.driver.libsql diff --git a/com.dbeaver.jdbc.driver.libsql/pom.xml b/com.dbeaver.jdbc.driver.libsql/pom.xml index 20c61b6..5dac68d 100644 --- a/com.dbeaver.jdbc.driver.libsql/pom.xml +++ b/com.dbeaver.jdbc.driver.libsql/pom.xml @@ -16,6 +16,11 @@ eclipse-plugin + + com.google.code.gson + gson + compile + com.dbeaver.common org.jkiss.utils diff --git a/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlConnection.java b/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlConnection.java index 9e26d69..8a46134 100644 --- a/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlConnection.java +++ b/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlConnection.java @@ -16,9 +16,13 @@ */ package com.dbeaver.jdbc.driver.libsql; +import com.dbeaver.jdbc.driver.libsql.client.LSqlClient; import com.dbeaver.jdbc.model.AbstractJdbcConnection; import org.jkiss.code.NotNull; +import org.jkiss.utils.CommonUtils; +import java.io.IOException; +import java.net.URL; import java.sql.*; import java.util.Map; @@ -26,19 +30,31 @@ public class LSqlConnection extends AbstractJdbcConnection { @NotNull private final LSqlDriver driver; + private final LSqlClient client; @NotNull private String url; @NotNull - private Map driverProperties; + private Map driverProperties; public LSqlConnection( @NotNull LSqlDriver driver, @NotNull String url, - @NotNull Map driverProperties + @NotNull Map driverProperties ) throws SQLException { this.driver = driver; this.url = url; this.driverProperties = driverProperties; + + try { + String token = CommonUtils.toString(driverProperties.get("password")); + this.client = new LSqlClient(new URL(url), token); + } catch (IOException e) { + throw new SQLException(e); + } + } + + public LSqlClient getClient() { + return client; } @NotNull @@ -47,7 +63,7 @@ public String getUrl() { } @NotNull - public Map getDriverProperties() { + public Map getDriverProperties() { return driverProperties; } diff --git a/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlDriver.java b/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlDriver.java index e64d06f..e269ad9 100644 --- a/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlDriver.java +++ b/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlDriver.java @@ -18,6 +18,9 @@ import java.sql.*; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Properties; import java.util.logging.Logger; import java.util.regex.Matcher; @@ -34,8 +37,14 @@ public Connection connect(String url, Properties info) throws SQLException { "Invalid connection URL: " + url + ".\nExpected URL format: " + LSqlConstants.CONNECTION_URL_EXAMPLE); } + String targetUrl = matcher.group(1); - throw new SQLFeatureNotSupportedException(); + Map props = new LinkedHashMap<>(); + for (Enumeration pne = info.propertyNames(); pne.hasMoreElements(); ) { + String propName = pne.toString(); + props.put(propName, info.get(propName)); + } + return new LSqlConnection(this, targetUrl, props); /* UPDEndpoint endpoint = new UPDEndpoint(); endpoint.setServerAddress(matcher.group(1)); diff --git a/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlResultSet.java b/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlResultSet.java index dfc1143..2832572 100644 --- a/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlResultSet.java +++ b/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlResultSet.java @@ -56,7 +56,7 @@ public LSqlExecutionResult getResult() { private int getColumnIndex(String columnLabel) throws LSqlException { if (nameMap == null) { nameMap = new HashMap<>(); - List columns = result.getColumnNames(); + List columns = result.getColumns(); for (int i = 0; i < columns.size(); i++) { nameMap.put(columns.get(i).toUpperCase(Locale.ENGLISH), i + 1); } diff --git a/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlResultSetMetaData.java b/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlResultSetMetaData.java index ce53eb0..7861a48 100644 --- a/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlResultSetMetaData.java +++ b/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlResultSetMetaData.java @@ -34,7 +34,7 @@ public LSqlResultSetMetaData(@NotNull LSqlResultSet resultSet) throws SQLExcepti @Override public int getColumnCount() throws SQLException { - return resultSet.getResult().getColumnNames().size(); + return resultSet.getResult().getColumns().size(); } @Override @@ -74,12 +74,12 @@ public int getColumnDisplaySize(int column) throws SQLException { @Override public String getColumnLabel(int column) throws SQLException { - return resultSet.getResult().getColumnNames().get(column); + return resultSet.getResult().getColumns().get(column - 1); } @Override public String getColumnName(int column) throws SQLException { - return resultSet.getResult().getColumnNames().get(column); + return resultSet.getResult().getColumns().get(column - 1); } @Override diff --git a/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlStatement.java b/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlStatement.java index eba2f3a..2eda932 100644 --- a/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlStatement.java +++ b/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/LSqlStatement.java @@ -36,7 +36,8 @@ public LSqlStatement(@NotNull LSqlConnection connection) throws SQLException { @Override public ResultSet executeQuery(String sql) throws SQLException { - throw new SQLFeatureNotSupportedException(); + LSqlExecutionResult result = connection.getClient().execute(sql); + return new LSqlResultSet(result); } @Override diff --git a/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/client/LSqlClient.java b/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/client/LSqlClient.java new file mode 100644 index 0000000..2603072 --- /dev/null +++ b/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/client/LSqlClient.java @@ -0,0 +1,100 @@ +package com.dbeaver.jdbc.driver.libsql.client; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.Strictness; +import com.google.gson.ToNumberPolicy; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.sql.SQLException; + +/** + * The entry point to LibSQL client API. + */ +public class LSqlClient { + public static final String DEFAULT_ISO_TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + + private URL url; + private String authToken; + private final Gson gson = new GsonBuilder() + .setStrictness(Strictness.LENIENT) + .setDateFormat(DEFAULT_ISO_TIMESTAMP_FORMAT) + .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .create(); + + public LSqlClient(URL url, String authToken) { + this.url = url; + this.authToken = authToken; + } + + /** + * Execute a single SQL statement. + * + * @return The result set. + */ + public LSqlExecutionResult execute(String stmt) throws SQLException { + return batch(new String[]{stmt})[0]; + } + + /** + * Execute a batch of SQL statements. + * + * @param stmts The SQL statements. + * @return The result sets. + */ + public LSqlExecutionResult[] batch(String[] stmts) throws SQLException { + try { + HttpURLConnection conn = openConnection(); + conn.setRequestMethod("POST"); + conn.setDoOutput(true); + + try (OutputStream os = conn.getOutputStream()) { + query(stmts, os); + } + try (InputStreamReader in = new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)) { + Response[] response = gson.fromJson(in, Response[].class); + LSqlExecutionResult[] resultSets = new LSqlExecutionResult[response.length]; + for (int i = 0; i < response.length; i++) { + resultSets[i] = response[i].results; + } + return resultSets; + } + } catch (Exception e) { + throw new SQLException(e); + } + + } + + private HttpURLConnection openConnection() throws IOException { + HttpURLConnection conn = (HttpURLConnection) this.url.openConnection(); + if (authToken != null) { + conn.setRequestProperty("Authorization", "Bearer " + authToken); + } + return conn; + } + + private void query(String[] stmts, OutputStream os) throws IOException { + JsonWriter jsonWriter = new JsonWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8)); + jsonWriter.beginObject(); + jsonWriter.name("statements"); + jsonWriter.beginArray(); + for (String stmt : stmts) { + jsonWriter.value(stmt); + } + jsonWriter.endArray(); + jsonWriter.endObject(); + jsonWriter.flush(); + } + + public static class Response { + public LSqlExecutionResult results; + } + +} \ No newline at end of file diff --git a/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/client/LSqlExecutionResult.java b/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/client/LSqlExecutionResult.java index c4e4158..2c72069 100644 --- a/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/client/LSqlExecutionResult.java +++ b/com.dbeaver.jdbc.driver.libsql/src/main/java/com/dbeaver/jdbc/driver/libsql/client/LSqlExecutionResult.java @@ -20,16 +20,16 @@ public class LSqlExecutionResult { - private List columnNames; + private List columns; private List rows; private long updateCount; - public List getColumnNames() { - return columnNames; + public List getColumns() { + return columns; } - public void setColumnNames(List columnNames) { - this.columnNames = columnNames; + public void setColumns(List columns) { + this.columns = columns; } public List getRows() { diff --git a/com.dbeaver.jdbc.driver.libsql/src/test/java/com/dbeaver/jdbc/upd/driver/test/LSqlDriverTest.java b/com.dbeaver.jdbc.driver.libsql/src/test/java/com/dbeaver/jdbc/upd/driver/test/LSqlDriverTest.java index 11494d0..2b14d17 100644 --- a/com.dbeaver.jdbc.driver.libsql/src/test/java/com/dbeaver/jdbc/upd/driver/test/LSqlDriverTest.java +++ b/com.dbeaver.jdbc.driver.libsql/src/test/java/com/dbeaver/jdbc/upd/driver/test/LSqlDriverTest.java @@ -28,7 +28,12 @@ public static void main(String[] args) throws Exception { try { LSqlDriver driver = new LSqlDriver(); Properties props = new Properties(); - try (Connection connection = driver.connect("jdbc:dbeaver.libsql:http://localhost:9999", props)) { + try (Connection connection = driver.connect("jdbc:dbeaver:libsql:" + args[0], props)) { + try (Statement dbStat = connection.createStatement()) { + try (ResultSet dbResult = dbStat.executeQuery("select * from testme")) { + printResultSet(dbResult); + } + } DatabaseMetaData metaData = connection.getMetaData(); System.out.println("Driver: " + metaData.getDriverName()); System.out.println("Database: " + metaData.getDatabaseProductName() + " " + metaData.getDatabaseProductVersion());