diff --git a/build.gradle b/build.gradle index 10b588ed..dc6c52ef 100644 --- a/build.gradle +++ b/build.gradle @@ -116,6 +116,30 @@ tasks.register('integrationTest', Test) { } } +tasks.register('jdbcTest', Test) { + description = 'Runs JDBC tests.' + group = 'verification' + + testClassesDirs = sourceSets.test.output.classesDirs + classpath = sourceSets.test.runtimeClasspath + shouldRunAfter test + + useJUnitPlatform() + + environment('TEST_SURREAL_HOST', 'localhost') + environment('TEST_SURREAL_PORT', 8000) + environment('TEST_SURREAL_USERNAME', 'root') + environment('TEST_SURREAL_PASSWORD', 'root') + environment('TEST_SURREAL_NAMESPACE', 'test') + environment('TEST_SURREAL_DATABASE', 'test') + environment('TEST_SURREAL_TOKEN', '') + environment('TEST_SURREAL_SCOPE', 'allusers') + + testLogging { + events "passed" + } +} + checkstyle { toolVersion = "10.9.3" configFile = resources.text.fromUri("https://raw.githubusercontent.com/checkstyle/checkstyle/checkstyle-${toolVersion}/src/main/resources/google_checks.xml").asFile() diff --git a/src/main/java/com/surrealdb/jdbc/SurrealJDBCConnection.java b/src/main/java/com/surrealdb/jdbc/SurrealJDBCConnection.java index f499e55e..366c1555 100644 --- a/src/main/java/com/surrealdb/jdbc/SurrealJDBCConnection.java +++ b/src/main/java/com/surrealdb/jdbc/SurrealJDBCConnection.java @@ -1,5 +1,8 @@ package com.surrealdb.jdbc; +import com.surrealdb.connection.SurrealWebSocketConnection; +import com.surrealdb.driver.AsyncSurrealDriver; +import com.surrealdb.driver.SyncSurrealDriver; import java.sql.Array; import java.sql.Blob; import java.sql.CallableStatement; @@ -20,9 +23,83 @@ import java.util.concurrent.Executor; public class SurrealJDBCConnection implements Connection { + + private final String host; + private final String dbName; + private final int port; + + // Maybe do these non-final to allow switching these while runtime + private final boolean useTls; + private final boolean useAsync; + private final SurrealWebSocketConnection webSocketConnection; + private SyncSurrealDriver syncDriver; + private AsyncSurrealDriver asyncDriver; + + public SurrealJDBCConnection( + String host, + int port, + String dbName, + String namespace, + String user, + String password, + boolean useTls, + boolean useAsync) { + this.host = host; + this.port = port; + this.dbName = dbName; + this.useTls = useTls; + this.useAsync = useAsync; + + webSocketConnection = new SurrealWebSocketConnection(host, port, useTls); + webSocketConnection.connect(60); + + if (useAsync) { + asyncDriver = new AsyncSurrealDriver(webSocketConnection); + asyncDriver.signIn(user, password); + asyncDriver.use(namespace, dbName); + } else { + syncDriver = new SyncSurrealDriver(webSocketConnection); + syncDriver.signIn(user, password); + syncDriver.use(namespace, dbName); + } + + System.out.println("Connection established"); + } + + public SurrealJDBCConnection( + String host, + int port, + String dbName, + String namespace, + String token, + boolean useTls, + boolean useAsync) { + this.host = host; + this.port = port; + this.dbName = dbName; + this.useTls = useTls; + this.useAsync = useAsync; + + webSocketConnection = new SurrealWebSocketConnection(host, port, useTls); + webSocketConnection.connect(5); + + if (useAsync) { + asyncDriver = new AsyncSurrealDriver(webSocketConnection); + asyncDriver.authenticate(token); + asyncDriver.use(namespace, dbName); + } else { + syncDriver = new SyncSurrealDriver(webSocketConnection); + syncDriver.authenticate(token); + syncDriver.use(namespace, dbName); + } + } + @Override public Statement createStatement() throws SQLException { - return new SurrealJDBCStatement(); + if (useAsync) { + return new SurrealJDBCStatement(asyncDriver); + } + return new SurrealJDBCStatement(syncDriver); } @Override @@ -62,6 +139,7 @@ public void rollback() throws SQLException { @Override public void close() throws SQLException { + webSocketConnection.close(); throw new UnsupportedOperationException("connection close is currently unsupported"); } @@ -97,12 +175,14 @@ public String getCatalog() throws SQLException { @Override public void setTransactionIsolation(int level) throws SQLException { - throw new UnsupportedOperationException("connection setTransactionIsolation is unimplemented"); + throw new UnsupportedOperationException( + "connection setTransactionIsolation is unimplemented"); } @Override public int getTransactionIsolation() throws SQLException { - throw new UnsupportedOperationException("connection getTransactionIsolation is unimplemented"); + throw new UnsupportedOperationException( + "connection getTransactionIsolation is unimplemented"); } @Override @@ -116,17 +196,20 @@ public void clearWarnings() throws SQLException { } @Override - public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { + public Statement createStatement(int resultSetType, int resultSetConcurrency) + throws SQLException { throw new UnsupportedOperationException("connection createStatement is unimplemented"); } @Override - public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { + public PreparedStatement prepareStatement( + String sql, int resultSetType, int resultSetConcurrency) throws SQLException { throw new UnsupportedOperationException("connection prepareStatement is unimplemented"); } @Override - public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException { throw new UnsupportedOperationException("connection prepareCall is unimplemneted"); } @@ -147,7 +230,7 @@ public void setHoldability(int holdability) throws SQLException { @Override public int getHoldability() throws SQLException { - throw new UnsupportedOperationException(); + return 10; } @Override @@ -171,22 +254,29 @@ public void releaseSavepoint(Savepoint savepoint) throws SQLException { } @Override - public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { + public Statement createStatement( + int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException { throw new UnsupportedOperationException(); } @Override - public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { + public PreparedStatement prepareStatement( + String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException { throw new UnsupportedOperationException(); } @Override - public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { + public CallableStatement prepareCall( + String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException { throw new UnsupportedOperationException(); } @Override - public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { + public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) + throws SQLException { throw new UnsupportedOperationException(); } @@ -196,7 +286,8 @@ public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throw } @Override - public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { + public PreparedStatement prepareStatement(String sql, String[] columnNames) + throws SQLException { throw new UnsupportedOperationException(); } @@ -262,7 +353,7 @@ public void setSchema(String schema) throws SQLException { @Override public String getSchema() throws SQLException { - throw new UnsupportedOperationException(); + return host; } @Override diff --git a/src/main/java/com/surrealdb/jdbc/SurrealJDBCDriver.java b/src/main/java/com/surrealdb/jdbc/SurrealJDBCDriver.java index 87a373a6..4e6c8db1 100644 --- a/src/main/java/com/surrealdb/jdbc/SurrealJDBCDriver.java +++ b/src/main/java/com/surrealdb/jdbc/SurrealJDBCDriver.java @@ -1,6 +1,7 @@ package com.surrealdb.jdbc; -import java.sql.Connection; +import com.surrealdb.jdbc.exception.SurrealJDBCDriverInitializationException; +import java.net.URI; import java.sql.Driver; import java.sql.DriverPropertyInfo; import java.sql.SQLException; @@ -9,9 +10,51 @@ import java.util.logging.Logger; public class SurrealJDBCDriver implements Driver { + private static final Driver INSTANCE = new SurrealJDBCDriver(); + private static boolean registered; + + public SurrealJDBCDriver() { + // Default constructor + } + @Override - public Connection connect(String url, Properties info) throws SQLException { - throw new UnsupportedOperationException(); + public SurrealJDBCConnection connect(String url, Properties info) + throws SQLException, SurrealJDBCDriverInitializationException { + /* + Note: + I think it would be wise to create a method to parse the information for tls, async, dbName + from the url and not from the Properties object, please give feedback about this. :) + + URL suggestion: jdbc:surrealdb://host:port/dbname;namespace;optionalParam2=SomeValue2; + */ + SurrealJDBCConnection connection; + String host, user, password, dbName, namespace; + boolean useTls, useAsync; + int port; + + if (!url.contains("jdbc")) { + throw new SurrealJDBCDriverInitializationException("Missing 'jdbc' in URI!"); + } + + URI uri = URI.create(url.replace("jdbc:", "")); + + if (!uri.getScheme().equals("surrealdb")) { + throw new SurrealJDBCDriverInitializationException( + "Failed to validate the JDBC-URL: Missing scheme 'surrealdb'"); + } + + host = uri.getHost(); + port = uri.getPort(); + dbName = uri.getPath().split(";")[0]; + namespace = uri.getPath().split(";")[1]; + + user = info.getProperty("user"); + password = info.getProperty("password"); + useTls = info.getProperty("tls").equals("true"); + useAsync = info.getProperty("async").equals("true"); + + return new SurrealJDBCConnection( + host, port, dbName, namespace, user, password, useTls, useAsync); } @Override diff --git a/src/main/java/com/surrealdb/jdbc/SurrealJDBCResultSet.java b/src/main/java/com/surrealdb/jdbc/SurrealJDBCResultSet.java index c82b5cf8..e30fc894 100644 --- a/src/main/java/com/surrealdb/jdbc/SurrealJDBCResultSet.java +++ b/src/main/java/com/surrealdb/jdbc/SurrealJDBCResultSet.java @@ -1,7 +1,13 @@ package com.surrealdb.jdbc; +import com.google.gson.Gson; +import com.google.gson.internal.LinkedTreeMap; +import com.surrealdb.driver.model.QueryResult; import java.io.InputStream; import java.io.Reader; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.math.BigDecimal; import java.net.URL; import java.sql.Array; @@ -19,13 +25,26 @@ import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; -import java.util.Calendar; -import java.util.Map; +import java.util.*; + +public class SurrealJDBCResultSet implements ResultSet { + private List data = new ArrayList<>(); + private int rowIndex = -1; + private Gson gson; + + public SurrealJDBCResultSet(List> data) { + data.forEach( + entry -> { + if (entry.getStatus().equals("OK")) { + this.data.addAll(entry.getResult()); + } + }); + } -public class SurrealJDBCResultSet implements ResultSet { @Override public boolean next() throws SQLException { - throw new UnsupportedOperationException(); + rowIndex++; + return rowIndex < data.size(); } @Override @@ -40,12 +59,12 @@ public boolean wasNull() throws SQLException { @Override public String getString(int columnIndex) throws SQLException { - throw new UnsupportedOperationException(); + throw new SQLException("Value is not a string"); } @Override public boolean getBoolean(int columnIndex) throws SQLException { - throw new UnsupportedOperationException(); + throw new SQLException("Value is not a boolean"); } @Override @@ -215,12 +234,12 @@ public String getCursorName() throws SQLException { @Override public ResultSetMetaData getMetaData() throws SQLException { - throw new UnsupportedOperationException(); + return new SurrealJDBCResultSetMetaData<>(data); } @Override public Object getObject(int columnIndex) throws SQLException { - throw new UnsupportedOperationException(); + return data.get(columnIndex); } @Override @@ -524,17 +543,20 @@ public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException } @Override - public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException { + public void updateAsciiStream(String columnLabel, InputStream x, int length) + throws SQLException { throw new UnsupportedOperationException(); } @Override - public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException { + public void updateBinaryStream(String columnLabel, InputStream x, int length) + throws SQLException { throw new UnsupportedOperationException(); } @Override - public void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException { + public void updateCharacterStream(String columnLabel, Reader reader, int length) + throws SQLException { throw new UnsupportedOperationException(); } @@ -824,7 +846,8 @@ public void updateNCharacterStream(int columnIndex, Reader x, long length) throw } @Override - public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { + public void updateNCharacterStream(String columnLabel, Reader reader, long length) + throws SQLException { throw new UnsupportedOperationException(); } @@ -834,7 +857,8 @@ public void updateAsciiStream(int columnIndex, InputStream x, long length) throw } @Override - public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException { + public void updateBinaryStream(int columnIndex, InputStream x, long length) + throws SQLException { throw new UnsupportedOperationException(); } @@ -844,27 +868,32 @@ public void updateCharacterStream(int columnIndex, Reader x, long length) throws } @Override - public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException { + public void updateAsciiStream(String columnLabel, InputStream x, long length) + throws SQLException { throw new UnsupportedOperationException(); } @Override - public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException { + public void updateBinaryStream(String columnLabel, InputStream x, long length) + throws SQLException { throw new UnsupportedOperationException(); } @Override - public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { + public void updateCharacterStream(String columnLabel, Reader reader, long length) + throws SQLException { throw new UnsupportedOperationException(); } @Override - public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException { + public void updateBlob(int columnIndex, InputStream inputStream, long length) + throws SQLException { throw new UnsupportedOperationException(); } @Override - public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException { + public void updateBlob(String columnLabel, InputStream inputStream, long length) + throws SQLException { throw new UnsupportedOperationException(); } @@ -960,12 +989,24 @@ public void updateNClob(String columnLabel, Reader reader) throws SQLException { @Override public T getObject(int columnIndex, Class type) throws SQLException { - throw new UnsupportedOperationException(); + if (gson == null) { + gson = new Gson(); + } + + if (data.get(columnIndex) instanceof LinkedTreeMap) { + var fieldMap = (LinkedTreeMap) data.get(columnIndex); + + String json = gson.toJson(fieldMap); + + return gson.fromJson(json, type); + } else { + return (T) data.get(columnIndex); + } } @Override public T getObject(String columnLabel, Class type) throws SQLException { - throw new UnsupportedOperationException(); + return null; } @Override @@ -977,4 +1018,5 @@ public T unwrap(Class iface) throws SQLException { public boolean isWrapperFor(Class iface) throws SQLException { throw new UnsupportedOperationException(); } + } diff --git a/src/main/java/com/surrealdb/jdbc/SurrealJDBCResultSetMetaData.java b/src/main/java/com/surrealdb/jdbc/SurrealJDBCResultSetMetaData.java new file mode 100644 index 00000000..8f8e5747 --- /dev/null +++ b/src/main/java/com/surrealdb/jdbc/SurrealJDBCResultSetMetaData.java @@ -0,0 +1,130 @@ +package com.surrealdb.jdbc; + +import java.lang.reflect.Field; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.List; + +public class SurrealJDBCResultSetMetaData implements ResultSetMetaData { + + private List data; + + public SurrealJDBCResultSetMetaData(List data) { + this.data = data; + } + + @Override + public int getColumnCount() throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public String getColumnLabel(int columnIndex) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isAutoIncrement(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isCaseSensitive(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isSearchable(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isCurrency(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public int isNullable(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isSigned(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public int getColumnDisplaySize(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public String getColumnName(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public String getSchemaName(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public int getPrecision(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public int getScale(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public String getTableName(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public String getCatalogName(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public int getColumnType(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public String getColumnTypeName(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isReadOnly(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isWritable(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDefinitelyWritable(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public String getColumnClassName(int column) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public T unwrap(Class iface) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/com/surrealdb/jdbc/SurrealJDBCStatement.java b/src/main/java/com/surrealdb/jdbc/SurrealJDBCStatement.java index f4795bc9..a6adfb10 100644 --- a/src/main/java/com/surrealdb/jdbc/SurrealJDBCStatement.java +++ b/src/main/java/com/surrealdb/jdbc/SurrealJDBCStatement.java @@ -1,20 +1,79 @@ package com.surrealdb.jdbc; +import com.surrealdb.driver.AsyncSurrealDriver; +import com.surrealdb.driver.SyncSurrealDriver; +import com.surrealdb.driver.model.QueryResult; + import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.Statement; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; public class SurrealJDBCStatement implements Statement { + + private SyncSurrealDriver syncDriver; + private AsyncSurrealDriver asyncDriver; + private boolean isAsync; + + public SurrealJDBCStatement() { + throw new UnsupportedOperationException( + "Calling the default constructor on the SurrealJDBCStatement isn't allowed."); + } + + public SurrealJDBCStatement(SyncSurrealDriver driver) { + this.syncDriver = driver; + isAsync = false; + } + + public SurrealJDBCStatement(AsyncSurrealDriver driver) { + throw new UnsupportedOperationException( + "Async driver for JDBC usage is not supported yet."); + // this.asyncDriver = driver; + // isAsync = true; + } + @Override public ResultSet executeQuery(String sql) throws SQLException { - throw new UnsupportedOperationException(); + if (isAsync) { + throw new UnsupportedOperationException( + "Async driver for JDBC usage is not supported yet."); + } + List> data; + if (sql.contains("CREATE")) { + data = syncDriver.query(sql, Collections.emptyMap(), Object.class); + } else { + // indicator for given args is $ + if (sql.contains("$")) { + + // we could assume that we add the args with another keyword for our java api because we cant pass them in the override... + // one suggestion: "select * from person where name.first = $firstName withargs [firstName, "Name"]; + // open for discussion :D + + var argsRaw = sql.split("withargs")[1] + .replace("[", "") + .replace("]", ""); + //.replace(";", ""); + + var args = new HashMap(); + args.put(argsRaw.split(",")[0].trim(), argsRaw.split(",")[1].trim()); + + sql = sql.split("withargs")[0]; + data = syncDriver.query(sql, args, Object.class); + } else { + data = syncDriver.query(sql, null, Object.class); + } + } + return new SurrealJDBCResultSet<>(data); } @Override public int executeUpdate(String sql) throws SQLException { - throw new UnsupportedOperationException(); + var data = syncDriver.query(sql, null, Object.class); + return data.get(0).getResult().size(); } @Override diff --git a/src/main/java/com/surrealdb/jdbc/exception/SurrealJDBCDriverInitializationException.java b/src/main/java/com/surrealdb/jdbc/exception/SurrealJDBCDriverInitializationException.java new file mode 100644 index 00000000..54af86e1 --- /dev/null +++ b/src/main/java/com/surrealdb/jdbc/exception/SurrealJDBCDriverInitializationException.java @@ -0,0 +1,11 @@ +package com.surrealdb.jdbc.exception; + +import com.surrealdb.connection.exception.SurrealException; + +public class SurrealJDBCDriverInitializationException extends SurrealException { + public SurrealJDBCDriverInitializationException() {} + + public SurrealJDBCDriverInitializationException(String message) { + super(message); + } +} diff --git a/src/main/java/com/surrealdb/jdbc/exception/SurrealJDBCInstanceConstructionException.java b/src/main/java/com/surrealdb/jdbc/exception/SurrealJDBCInstanceConstructionException.java new file mode 100644 index 00000000..a42b06d3 --- /dev/null +++ b/src/main/java/com/surrealdb/jdbc/exception/SurrealJDBCInstanceConstructionException.java @@ -0,0 +1,11 @@ +package com.surrealdb.jdbc.exception; + +import com.surrealdb.connection.exception.SurrealException; + +public class SurrealJDBCInstanceConstructionException extends SurrealException { + public SurrealJDBCInstanceConstructionException() {} + + public SurrealJDBCInstanceConstructionException(String message) { + super(message); + } +} diff --git a/src/test/java/com/surrealdb/jdbc/SurrealJDBCConnectionTest.java b/src/test/java/com/surrealdb/jdbc/SurrealJDBCConnectionTest.java index edd955e4..db2f2607 100644 --- a/src/test/java/com/surrealdb/jdbc/SurrealJDBCConnectionTest.java +++ b/src/test/java/com/surrealdb/jdbc/SurrealJDBCConnectionTest.java @@ -11,7 +11,16 @@ @Disabled("Disabled until implementation started") class SurrealJDBCConnectionTest { - private SurrealJDBCConnection jdbcConnection = new SurrealJDBCConnection(); + private SurrealJDBCConnection jdbcConnection = + new SurrealJDBCConnection( + TestUtils.getHost(), + TestUtils.getPort(), + TestUtils.getDatabase(), + TestUtils.getNamespace(), + TestUtils.getUsername(), + TestUtils.getPassword(), + TestUtils.getUseTlsDriver(), + TestUtils.getUseAsyncDriver()); @SneakyThrows @Test diff --git a/src/test/java/com/surrealdb/jdbc/SurrealJDBCDriverTest.java b/src/test/java/com/surrealdb/jdbc/SurrealJDBCDriverTest.java index c7d0482e..2452d99d 100644 --- a/src/test/java/com/surrealdb/jdbc/SurrealJDBCDriverTest.java +++ b/src/test/java/com/surrealdb/jdbc/SurrealJDBCDriverTest.java @@ -2,17 +2,71 @@ import static org.junit.jupiter.api.Assertions.assertThrows; +import com.surrealdb.connection.SurrealWebSocketConnection; +import com.surrealdb.driver.SyncSurrealDriver; +import com.surrealdb.jdbc.model.Person; + +import java.net.URI; import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.SQLException; +import org.junit.Assert; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @Disabled("Disabled until implementation started") class SurrealJDBCDriverTest { private final Driver driver = new SurrealJDBCDriver(); + private SyncSurrealDriver surrealDriver; + private SurrealJDBCConnection jdbcConnection; + + private final static String URL = "jdbc:surrealdb://" + + TestUtils.getHost() + + ":" + + TestUtils.getPort() + + "/" + + TestUtils.getDatabase() + + ";" + + TestUtils.getNamespace(); + + @BeforeEach + public void setup() throws ClassNotFoundException, SQLException { + var uri = URI.create(URL); + + // Initialize SurrealDB + SurrealWebSocketConnection connection = + new SurrealWebSocketConnection( + TestUtils.getHost(), TestUtils.getPort(), false); + connection.connect(5); + + surrealDriver = new SyncSurrealDriver(connection); + + surrealDriver.signIn(TestUtils.getUsername(), TestUtils.getPassword()); + surrealDriver.use(TestUtils.getNamespace(), TestUtils.getDatabase()); + + surrealDriver.create( + "person:1", new Person("Founder & CEO", "Tobie", "Morgan Hitchcock", true)); + surrealDriver.create( + "person:2", new Person("Founder & COO", "Jaime", "Morgan Hitchcock", true)); + + // Initialize the JDBC Driver + Class.forName("com.surrealdb.jdbc.SurrealJDBCDriver"); + DriverManager.registerDriver(new SurrealJDBCDriver()); + jdbcConnection = (SurrealJDBCConnection) DriverManager.getConnection(URL, TestUtils.getDriverProperties()); + } + + @AfterEach + public void teardown() { + surrealDriver.delete("person:1"); + surrealDriver.delete("person:2"); + } @Test - void connect() { - assertThrows(UnsupportedOperationException.class, () -> driver.connect("", null)); + void connect() throws SQLException { + driver.connect(URL, TestUtils.getDriverProperties()); + Assert.assertTrue(TestUtils.getHost().equals(jdbcConnection.getSchema())); } @Test diff --git a/src/test/java/com/surrealdb/jdbc/SurrealJDBCStatementTest.java b/src/test/java/com/surrealdb/jdbc/SurrealJDBCStatementTest.java index 5f7883cb..8c2805dd 100644 --- a/src/test/java/com/surrealdb/jdbc/SurrealJDBCStatementTest.java +++ b/src/test/java/com/surrealdb/jdbc/SurrealJDBCStatementTest.java @@ -2,241 +2,372 @@ import static org.junit.jupiter.api.Assertions.*; -import java.sql.ResultSet; -import java.sql.Statement; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; +import java.net.URI; +import java.sql.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.surrealdb.connection.SurrealWebSocketConnection; +import com.surrealdb.driver.SyncSurrealDriver; +import com.surrealdb.driver.model.QueryResult; +import com.surrealdb.jdbc.model.Person; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.jupiter.api.*; -@Disabled("Disabled until implementation started") class SurrealJDBCStatementTest { - private final Statement statement = new SurrealJDBCStatement(); + private Statement statement; + + private final Driver driver = new SurrealJDBCDriver(); + private SyncSurrealDriver surrealDriver; + private SurrealJDBCConnection jdbcConnection; + + private final static String URL = "jdbc:surrealdb://" + + TestUtils.getHost() + + ":" + + TestUtils.getPort() + + "/" + + TestUtils.getDatabase() + + ";" + + TestUtils.getNamespace(); + + @BeforeEach + public void setup() throws ClassNotFoundException, SQLException { + Assert.assertTrue(TestUtils.getHost() != null); + + var uri = URI.create(URL); + + // Initialize the JDBC Driver + Class.forName("com.surrealdb.jdbc.SurrealJDBCDriver"); + DriverManager.registerDriver(new SurrealJDBCDriver()); + jdbcConnection = (SurrealJDBCConnection) DriverManager.getConnection(URL, TestUtils.getDriverProperties()); + + statement = jdbcConnection.createStatement(); + + // Let's create two objects for testing with the driver + try { + var created = statement.executeQuery(""" +CREATE person:1 CONTENT { + id: 'person:1', + marketing: true, + name: { + first: 'Flip', + last: 'Flopsen' + }, + title: 'NiceDude' + }; +CREATE person:2 CONTENT { + id: 'person:2', + marketing: true, + name: { + first: 'Hugh', + last: 'G' + }, + title: 'Polbrit' + }; +"""); + } catch (SQLException e) { + System.err.println("Failed to create person!"); + } + } + + @AfterEach + public void teardown() { + try { + statement.executeQuery("DELETE person"); + } catch (SQLException e) { + System.err.println("Failed to delete person!"); + } + } @Test void executeQuery() { - assertThrows(UnsupportedOperationException.class, () -> statement.executeQuery("")); + try { + var results = statement.executeQuery("select * from person"); + + var ctr = 0; + while (results.next()) { + var obj = results.getObject(ctr, Person.class); + System.out.println(obj.getName()); + Assert.assertTrue(obj != null); + ctr++; + } + + Assert.assertTrue(ctr == 2); + } catch (SQLException e) { + e.printStackTrace(); + } } @Test - void executeUpdate() { - assertThrows(UnsupportedOperationException.class, () -> statement.executeUpdate("")); + void executeQueryWithArgs() { + try { + var results = statement.executeQuery("select * from person where name.first = $firstName withargs [firstName, Flip]"); + + var ctr = 0; + while (results.next()) { + var obj = results.getObject(ctr, Person.class); + Assert.assertTrue(obj != null); + ctr++; + } + + Assert.assertTrue(ctr == 1); + } catch (SQLException e) { + e.printStackTrace(); + } } @Test + void executeUpdate() { + try { + statement.executeUpdate("UPDATE person SET marketing = false"); + + var results = statement.executeQuery("select * from person"); + + var ctr = 0; + while (results.next()) { + var obj = results.getObject(ctr, Person.class); + Assert.assertFalse(obj.isMarketing()); + ctr++; + } + + Assert.assertTrue(ctr == 2); + ctr = 0; + statement.executeUpdate("UPDATE person:1 SET marketing = true"); + + results = statement.executeQuery("select * from person where marketing = false"); + + while (results.next()) { + var obj = results.getObject(ctr, Person.class); + Assert.assertFalse(obj.isMarketing()); + ctr++; + } + + Assert.assertTrue(ctr == 1); + + } catch (SQLException e) { + e.printStackTrace(); + } + } + + @Disabled void close() { assertThrows(UnsupportedOperationException.class, statement::close); } - @Test + @Disabled void getMaxFieldSize() { assertThrows(UnsupportedOperationException.class, statement::getMaxFieldSize); } - @Test + @Disabled void setMaxFieldSize() { assertThrows(UnsupportedOperationException.class, () -> statement.setMaxFieldSize(0)); } - @Test + @Disabled void getMaxRows() { assertThrows(UnsupportedOperationException.class, statement::getMaxRows); } - @Test + @Disabled void setMaxRows() { assertThrows(UnsupportedOperationException.class, () -> statement.setMaxRows(0)); } - @Test + @Disabled void setEscapeProcessing() { assertThrows( UnsupportedOperationException.class, () -> statement.setEscapeProcessing(false)); } - @Test + @Disabled void getQueryTimeout() { assertThrows(UnsupportedOperationException.class, statement::getQueryTimeout); } - @Test + @Disabled void setQueryTimeout() { assertThrows(UnsupportedOperationException.class, () -> statement.setQueryTimeout(0)); } - @Test + @Disabled void cancel() { assertThrows(UnsupportedOperationException.class, statement::cancel); } - @Test + @Disabled void getWarnings() { assertThrows(UnsupportedOperationException.class, statement::getWarnings); } - @Test + @Disabled void clearWarnings() { assertThrows(UnsupportedOperationException.class, statement::clearWarnings); } - @Test + @Disabled void setCursorName() { assertThrows(UnsupportedOperationException.class, () -> statement.setCursorName("")); } - @Test + @Disabled void execute() { assertThrows(UnsupportedOperationException.class, () -> statement.execute("")); } - @Test + @Disabled void getResultSet() { assertThrows(UnsupportedOperationException.class, statement::getResultSet); } - @Test + @Disabled void getUpdateCount() { assertThrows(UnsupportedOperationException.class, statement::getUpdateCount); } - @Test + @Disabled void getMoreResults() { assertThrows(UnsupportedOperationException.class, statement::getMoreResults); } - @Test + @Disabled void setFetchDirection() { assertThrows( UnsupportedOperationException.class, () -> statement.setFetchDirection(ResultSet.FETCH_FORWARD)); } - @Test + @Disabled void getFetchDirection() { assertThrows(UnsupportedOperationException.class, statement::getFetchDirection); } - @Test + @Disabled void setFetchSize() { assertThrows(UnsupportedOperationException.class, () -> statement.setFetchSize(0)); } - @Test + @Disabled void getFetchSize() { assertThrows(UnsupportedOperationException.class, statement::getFetchSize); } - @Test + @Disabled void getResultSetConcurrency() { assertThrows(UnsupportedOperationException.class, statement::getResultSetConcurrency); } - @Test + @Disabled void getResultSetType() { assertThrows(UnsupportedOperationException.class, statement::getResultSetType); } - @Test + @Disabled void addBatch() { assertThrows(UnsupportedOperationException.class, () -> statement.addBatch("")); } - @Test + @Disabled void clearBatch() { assertThrows(UnsupportedOperationException.class, statement::clearBatch); } - @Test + @Disabled void executeBatch() { assertThrows(UnsupportedOperationException.class, statement::executeBatch); } - @Test + @Disabled void getConnection() { assertThrows(UnsupportedOperationException.class, statement::getConnection); } - @Test + @Disabled void testGetMoreResults() { assertThrows(UnsupportedOperationException.class, statement::getMoreResults); } - @Test + @Disabled void getGeneratedKeys() { assertThrows(UnsupportedOperationException.class, statement::getGeneratedKeys); } - @Test + @Disabled void testExecuteUpdate() { assertThrows(UnsupportedOperationException.class, () -> statement.executeUpdate("")); } - @Test + @Disabled void testExecuteUpdate1() { assertThrows( UnsupportedOperationException.class, () -> statement.executeUpdate("", Statement.RETURN_GENERATED_KEYS)); } - @Test + @Disabled void testExecuteUpdate2() { assertThrows( UnsupportedOperationException.class, () -> statement.executeUpdate("", new String[] {})); } - @Test + @Disabled void testExecute() { assertThrows(UnsupportedOperationException.class, () -> statement.execute("")); } - @Test + @Disabled void testExecute1() { assertThrows(UnsupportedOperationException.class, () -> statement.execute("", new int[10])); } - @Test + @Disabled void testExecute2() { assertThrows( UnsupportedOperationException.class, () -> statement.execute("", Statement.RETURN_GENERATED_KEYS)); } - @Test + @Disabled void getResultSetHoldability() { assertThrows(UnsupportedOperationException.class, statement::getResultSetHoldability); } - @Test + @Disabled void isClosed() { assertThrows(UnsupportedOperationException.class, statement::isClosed); } - @Test + @Disabled void setPoolable() { assertThrows(UnsupportedOperationException.class, () -> statement.setPoolable(false)); } - @Test + @Disabled void isPoolable() { assertThrows(UnsupportedOperationException.class, statement::isPoolable); } - @Test + @Disabled void closeOnCompletion() { assertThrows(UnsupportedOperationException.class, statement::closeOnCompletion); } - @Test + @Disabled void isCloseOnCompletion() { assertThrows(UnsupportedOperationException.class, statement::isCloseOnCompletion); } - @Test + @Disabled void unwrap() { assertThrows(UnsupportedOperationException.class, () -> statement.unwrap(String.class)); } - @Test + @Disabled void isWrapperFor() { assertThrows( UnsupportedOperationException.class, () -> statement.isWrapperFor(String.class)); diff --git a/src/test/java/com/surrealdb/jdbc/TestUtils.java b/src/test/java/com/surrealdb/jdbc/TestUtils.java new file mode 100644 index 00000000..a09e1c5a --- /dev/null +++ b/src/test/java/com/surrealdb/jdbc/TestUtils.java @@ -0,0 +1,79 @@ +package com.surrealdb.jdbc; + +import java.util.Properties; + +/** + * @author Khalid Alharisi + */ +public class TestUtils { + private static final String HOST = System.getenv("TEST_SURREAL_HOST"); + private static final int PORT = Integer.parseInt(System.getenv("TEST_SURREAL_PORT")); + + private static final String USERNAME = System.getenv("TEST_SURREAL_USERNAME"); + private static final String PASSWORD = System.getenv("TEST_SURREAL_PASSWORD"); + + private static final String NAMESPACE = System.getenv("TEST_SURREAL_NAMESPACE"); + private static final String DATABASE = System.getenv("TEST_SURREAL_DATABASE"); + private static final String SCOPE = System.getenv("TEST_SURREAL_SCOPE"); + private static final String JWT_TOKEN = System.getenv("TEST_SURREAL_TOKEN"); + + private static Properties DRIVER_PROPERTIES; + + // These two are for dev and definitely open for discussion :) + private static final boolean USE_TLS_DRIVER = false; + private static final boolean USE_ASYNC_DRIVER = false; + + public static String getHost() { + return HOST; + } + + public static int getPort() { + return PORT; + } + + public static String getUsername() { + return USERNAME; + } + + public static String getPassword() { + return PASSWORD; + } + + public static String getNamespace() { + return NAMESPACE; + } + + public static String getDatabase() { + return DATABASE; + } + + public static String getToken() { + return JWT_TOKEN; + } + + public static String getScope() { + return SCOPE; + } + + public static boolean getUseAsyncDriver() { + return USE_ASYNC_DRIVER; + } + + public static boolean getUseTlsDriver() { + return USE_TLS_DRIVER; + } + + public static Properties getDriverProperties() { + DRIVER_PROPERTIES = new Properties(); + + DRIVER_PROPERTIES.setProperty("host", HOST); + DRIVER_PROPERTIES.setProperty("user", "root"); + DRIVER_PROPERTIES.setProperty("password", "root"); + DRIVER_PROPERTIES.setProperty("port", String.valueOf(PORT)); + DRIVER_PROPERTIES.setProperty("tls", String.valueOf(USE_TLS_DRIVER)); + DRIVER_PROPERTIES.setProperty("async", String.valueOf(USE_ASYNC_DRIVER)); + DRIVER_PROPERTIES.setProperty("dbname", DATABASE); + + return DRIVER_PROPERTIES; + } +} diff --git a/src/test/java/com/surrealdb/jdbc/model/Person.java b/src/test/java/com/surrealdb/jdbc/model/Person.java new file mode 100644 index 00000000..b1a737f4 --- /dev/null +++ b/src/test/java/com/surrealdb/jdbc/model/Person.java @@ -0,0 +1,31 @@ +package com.surrealdb.jdbc.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Khalid Alharisi + */ +@Data +@NoArgsConstructor +public class Person { + + @Data + @AllArgsConstructor + public static class Name { + private String first; + private String last; + } + + private String id; + private String title; + private Name name; + private boolean marketing; + + public Person(String title, String firstName, String lastName, boolean marketing) { + this.title = title; + this.name = new Name(firstName, lastName); + this.marketing = marketing; + } +}