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
@@ -0,0 +1,73 @@
package com.github.magnusp.libsql.conformance;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

final class LibsqlConformanceGuard {

static final String EXPECTED_DRIVER_NAME = "libSQL JDBC Driver";
static final String EXPECTED_DATABASE_PRODUCT_NAME = "SQLite (libSQL)";
static final String LIBSQL_SOURCE_ID_SUFFIX = "alt1";

private static final String PROBE_URL = "jdbc:sqlite::memory:";

private LibsqlConformanceGuard() {}

static void verify() {
try (Connection connection = DriverManager.getConnection(PROBE_URL)) {
verify(connection);
} catch (SQLException e) {
throw new IllegalStateException(
"Conformance suite could not obtain a JDBC connection from " + PROBE_URL, e);
}
}

static void verify(Connection connection) throws SQLException {
DatabaseMetaData metadata = connection.getMetaData();
String driverName = metadata.getDriverName();
String productName = metadata.getDatabaseProductName();

if (!EXPECTED_DRIVER_NAME.equals(driverName)
|| !EXPECTED_DATABASE_PRODUCT_NAME.equals(productName)) {
throw new AssertionError(
"Conformance suite is not connected to libSQL: expected driver \""
+ EXPECTED_DRIVER_NAME
+ "\" and database product \""
+ EXPECTED_DATABASE_PRODUCT_NAME
+ "\", but resolved driver \""
+ driverName
+ "\" and database product \""
+ productName
+ "\"");
}

String sourceId = readSourceId(connection);
if (sourceId == null || !sourceId.endsWith(LIBSQL_SOURCE_ID_SUFFIX)) {
throw new AssertionError(
"Conformance suite is not connected to a libSQL server: expected SQLite"
+ " source id ending \""
+ LIBSQL_SOURCE_ID_SUFFIX
+ "\", but resolved \""
+ sourceId
+ "\" through driver \""
+ driverName
+ "\"");
}
}

private static String readSourceId(Connection connection) throws SQLException {
try (Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT sqlite_source_id()")) {
if (!resultSet.next()) {
throw new AssertionError(
"Conformance suite could not read a SQLite source id from "
+ "the libSQL connection");
}
return resultSet.getString(1);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.github.magnusp.libsql.conformance;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.Statement;
import org.junit.jupiter.api.Test;

class LibsqlConformanceGuardTest {

@Test
void acceptsALibsqlConnection() throws Exception {
Connection connection =
connection(
"libSQL JDBC Driver",
"SQLite (libSQL)",
"2024-10-21 16:30:22 03a9703e27c44437c39363d0baf82db4ebc94538a0f284"
+ "11c85dda156f82alt1");

LibsqlConformanceGuard.verify(connection);
}

@Test
void rejectsAForeignDriverAndNamesTheResolvedAndExpectedDriver() {
Connection connection = connection("SQLite JDBC", "SQLite", null);

AssertionError failure =
assertThrows(
AssertionError.class, () -> LibsqlConformanceGuard.verify(connection));

assertThat(failure)
.hasMessageContaining("\"SQLite JDBC\"")
.hasMessageContaining("\"libSQL JDBC Driver\"");
}

@Test
void rejectsANonLibsqlServerAndNamesTheResolvedSourceId() {
Connection connection =
connection(
"libSQL JDBC Driver",
"SQLite (libSQL)",
"2024-10-21 16:30:22 03a9703e27c44437c39363d0baf82db4ebc94538a0f284"
+ "11c85dda156f82");

AssertionError failure =
assertThrows(
AssertionError.class, () -> LibsqlConformanceGuard.verify(connection));

assertThat(failure)
.hasMessageContaining("2024-10-21 16:30:22")
.hasMessageContaining("alt1");
}

private static Connection connection(
String driverName, String productName, String sourceId) {
ResultSet resultSet =
proxy(
ResultSet.class,
(proxy, method, args) ->
switch (method.getName()) {
case "next" -> true;
case "getString" -> sourceId;
case "close" -> null;
default -> unsupported(method.getName());
});
Statement statement =
proxy(
Statement.class,
(proxy, method, args) ->
switch (method.getName()) {
case "executeQuery" -> resultSet;
case "close" -> null;
default -> unsupported(method.getName());
});
DatabaseMetaData metadata =
proxy(
DatabaseMetaData.class,
(proxy, method, args) ->
switch (method.getName()) {
case "getDriverName" -> driverName;
case "getDatabaseProductName" -> productName;
default -> unsupported(method.getName());
});
return proxy(
Connection.class,
(proxy, method, args) ->
switch (method.getName()) {
case "getMetaData" -> metadata;
case "createStatement" -> statement;
case "close" -> null;
default -> unsupported(method.getName());
});
}

private static Object unsupported(String methodName) {
throw new UnsupportedOperationException(methodName);
}

@SuppressWarnings("unchecked")
private static <T> T proxy(Class<T> type, InvocationHandler handler) {
return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[] {type}, handler);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public final class XerialLauncherSessionListener implements LauncherSessionListe
@Override
public void launcherSessionOpened(LauncherSession session) {
XerialConformanceDriver.register();
LibsqlConformanceGuard.verify();
}

@Override
Expand Down
Loading