Skip to content

Commit

Permalink
refactor: Align metadata with previous version (#247)
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-simons authored Feb 27, 2024
1 parent d2108e9 commit da4ea02
Show file tree
Hide file tree
Showing 2 changed files with 316 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
Expand All @@ -40,6 +42,7 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException;

@Testcontainers(disabledWithoutDocker = true)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
Expand Down Expand Up @@ -758,6 +761,288 @@ void getColumnsWithLists() throws SQLException {
}
}

@Test
void getDatabaseVersionShouldBeOK() throws SQLException {

assertThat(this.connection.getMetaData().getDatabaseProductVersion()).isNotNull();
assertThat(this.connection.getMetaData().getDatabaseMajorVersion()).isGreaterThanOrEqualTo(5);
assertThat(this.connection.getMetaData().getDatabaseMajorVersion()).isGreaterThanOrEqualTo(0);
assertThat(this.connection.getMetaData().getUserName()).isEqualTo("neo4j");
}

@Test
void getTablesWithNull() throws SQLException {

try (Statement statement = this.connection.createStatement()) {
statement.execute("MATCH (n) DETACH DELETE n");
statement.execute("CREATE (a:A {one:1, two:2})");
statement.execute("CREATE (b:B {three:3, four:4})");
}

ResultSet labels = this.connection.getMetaData().getTables(null, null, null, null);

assertThat(labels).isNotNull();
assertThat(labels.next()).isTrue();
assertThat(labels.getString("TABLE_NAME")).isEqualTo("A");
assertThat(labels.next()).isTrue();
assertThat(labels.getString("TABLE_NAME")).isEqualTo("B");
assertThat(labels.next()).isFalse();
}

@Test
void getTablesWithStrictPattern() throws SQLException {

try (Statement statement = this.connection.createStatement()) {
statement.execute("create (a:Test {one:1, two:2})");
statement.execute("create (b:Testa {three:3, four:4})");
}

ResultSet labels = this.connection.getMetaData().getTables(null, null, "Test", null);

assertThat(labels).isNotNull();
List<String> tableNames = new ArrayList<>();

while (labels.next()) {
tableNames.add(labels.getString("TABLE_NAME"));
}

assertThat(tableNames).containsExactly("Test");
}

@Test
void getTablesWithPattern() throws SQLException {

try (Statement statement = this.connection.createStatement()) {
statement.execute("create (a:Test {one:1, two:2})");
statement.execute("create (b:Testa {three:3, four:4})");
}

ResultSet labels = this.connection.getMetaData().getTables(null, null, "Test%", null);

assertThat(labels).isNotNull();
List<String> tableNames = new ArrayList<>();

while (labels.next()) {
tableNames.add(labels.getString("TABLE_NAME"));
}

assertThat(tableNames).containsExactlyInAnyOrder("Test", "Testa");
}

@Test
void getTablesWithWildcard() throws SQLException {

try (Statement statement = this.connection.createStatement()) {
statement.execute("create (a:Foo {one:1, two:2})");
statement.execute("create (b:Bar {three:3, four:4})");
}

ResultSet labels = this.connection.getMetaData().getTables(null, null, "%", null);

assertThat(labels).isNotNull();
List<String> tableNames = new ArrayList<>();

while (labels.next()) {
tableNames.add(labels.getString("TABLE_NAME"));
}

assertThat(tableNames).containsExactlyInAnyOrder("Foo", "Bar");
}

@Test
void getColumnWithNull() throws SQLException {

try (Statement statement = this.connection.createStatement()) {
statement.execute("create (a:A {one:1, two:2})");
statement.execute("create (b:B {three:3, four:4})");
}

ResultSet columns = this.connection.getMetaData().getColumns(null, null, null, null);

assertThat(columns).isNotNull();
List<String> columnNames = new ArrayList<>();

while (columns.next()) {
columnNames.add(columns.getString("COLUMN_NAME"));
}

assertThat(columnNames).containsExactlyInAnyOrder("one", "two", "three", "four");
}

@Test
void getColumnWithTablePattern() throws SQLException {

try (Statement statement = this.connection.createStatement()) {
statement.execute("create (a:Test {one:1, two:2})");
statement.execute("create (b:Test2 {three:3, four:4})");
}

ResultSet columns = this.connection.getMetaData().getColumns(null, null, "Test", null);

assertThat(columns).isNotNull();
List<String> columnNames = new ArrayList<>();

while (columns.next()) {
columnNames.add(columns.getString("COLUMN_NAME"));
}

assertThat(columnNames).containsExactlyInAnyOrder("one", "two");
}

@Test
void getColumnWithColumnPattern() throws SQLException {

try (Statement statement = this.connection.createStatement()) {
statement.execute("create (a:Test {one:1, two:2})");
statement.execute("create (b:Test2 {three:3, four:4})");
}

ResultSet columns = this.connection.getMetaData().getColumns(null, null, "Test", "t%");

assertThat(columns).isNotNull();
List<String> columnNames = new ArrayList<>();

while (columns.next()) {
columnNames.add(columns.getString("COLUMN_NAME"));
}

assertThat(columnNames).containsExactly("two");
}

@Test
void classShouldWorkIfTransactionIsAlreadyOpened() throws SQLException {
try (var localConnection = getConnection()) {
localConnection.setAutoCommit(false);
assertThatNoException().isThrownBy(localConnection::getMetaData);
}
}

@Test
void getSystemFunctions() throws SQLException {
String systemFunctions = this.connection.getMetaData().getSystemFunctions();

assertThat(systemFunctions).isNotNull();
String[] split = systemFunctions.split(",");
List<String> functionsList = Arrays.asList(split);

assertThat(functionsList)
.containsAll(List.of("date", "date.truncate", "time", "time.truncate", "duration", "duration.between"));
}

@Test
void getIndexInfoWithConstraint() throws Exception {
String constrName = "bar_uuid";

try (var statement = this.connection.createStatement()) {
statement
.execute("CREATE CONSTRAINT " + constrName + " IF NOT EXISTS FOR (f:Bar) REQUIRE (f.uuid) IS UNIQUE");
}

try (ResultSet resultSet = this.connection.getMetaData().getIndexInfo(null, null, "Bar", true, false)) {

assertThat(resultSet.next()).isTrue();
assertThat(resultSet.getString("TABLE_NAME")).isEqualTo("Bar");
assertThat(resultSet.getBoolean("NON_UNIQUE")).isFalse();
assertThat(resultSet.getString("INDEX_NAME")).isEqualTo(constrName);
assertThat(resultSet.getString("INDEX_QUALIFIER")).isEqualTo(constrName);
assertThat(resultSet.getInt("TYPE")).isEqualTo(3);
assertThat(resultSet.getInt("ORDINAL_POSITION")).isOne();
assertThat(resultSet.getString("COLUMN_NAME")).isEqualTo("uuid");
assertThat(resultSet.getObject("TABLE_CAT")).isNull();
assertThat(resultSet.getObject("TABLE_SCHEM")).isEqualTo("public");
assertThat(resultSet.getObject("ASC_OR_DESC")).isEqualTo("A");
assertThat(resultSet.getObject("CARDINALITY")).isNull();
assertThat(resultSet.getObject("PAGES")).isNull();
assertThat(resultSet.getObject("FILTER_CONDITION")).isNull();
assertThat(resultSet.next()).isFalse();
}
finally {
try (var statement = this.connection.createStatement()) {
statement.execute("DROP CONSTRAINT " + constrName + " IF EXISTS");
}
}
}

@Test
void getIndexInfoWithBacktickLabels() throws Exception {
String constrName = "barExt_uuid";
try (var statement = this.connection.createStatement()) {
statement.execute("CREATE CONSTRAINT " + constrName + " FOR (f:`Bar Ext`) REQUIRE (f.uuid) IS UNIQUE");
}

try (ResultSet resultSet = this.connection.getMetaData().getIndexInfo(null, null, "Bar Ext", true, false)) {

assertThat(resultSet.next()).isTrue();
assertThat(resultSet.getString("TABLE_NAME")).isEqualTo("Bar Ext");
assertThat(resultSet.getBoolean("NON_UNIQUE")).isFalse();
assertThat(resultSet.getString("INDEX_NAME")).isEqualTo(constrName);
assertThat(resultSet.getString("INDEX_QUALIFIER")).isEqualTo(constrName);
assertThat(resultSet.getInt("TYPE")).isEqualTo(3);
assertThat(resultSet.getInt("ORDINAL_POSITION")).isOne();
assertThat(resultSet.getString("COLUMN_NAME")).isEqualTo("uuid");
assertThat(resultSet.getObject("TABLE_CAT")).isNull();
assertThat(resultSet.getObject("TABLE_SCHEM")).isEqualTo("public");
assertThat(resultSet.getObject("ASC_OR_DESC")).isEqualTo("A");
assertThat(resultSet.getObject("CARDINALITY")).isNull();
assertThat(resultSet.getObject("PAGES")).isNull();
assertThat(resultSet.getObject("FILTER_CONDITION")).isNull();
assertThat(resultSet.next()).isFalse();
}
finally {
try (var statement = this.connection.createStatement()) {
statement.execute("DROP CONSTRAINT " + constrName + " IF EXISTS");
}
}
}

@Test
void getIndexInfoWithConstraintWrongLabel() throws Exception {
try (var statement = this.connection.createStatement()) {
statement.execute("CREATE CONSTRAINT bar_uuid IF NOT EXISTS FOR (f:Bar) REQUIRE (f.uuid) IS UNIQUE");
}

try (ResultSet resultSet = this.connection.getMetaData().getIndexInfo(null, null, "Foo", true, false)) {
assertThat(resultSet.next()).isFalse();
}
finally {
try (var statement = this.connection.createStatement()) {
statement.execute("DROP CONSTRAINT bar_uuid IF EXISTS");
}
}
}

@Test
void getIndexInfoWithIndex() throws Exception {
String indexName = "bar_uuid";
try (var statement = this.connection.createStatement()) {
statement.execute("CREATE INDEX " + indexName + " IF NOT EXISTS FOR (b:Bar) ON (b.uuid)");
}

try (ResultSet resultSet = this.connection.getMetaData().getIndexInfo(null, null, "Bar", false, false)) {

assertThat(resultSet.next()).isTrue();
assertThat(resultSet.getString("TABLE_NAME")).isEqualTo("Bar");
assertThat(resultSet.getBoolean("NON_UNIQUE")).isTrue();
assertThat(resultSet.getString("INDEX_NAME")).isEqualTo(indexName);
assertThat(resultSet.getString("INDEX_QUALIFIER")).isEqualTo(indexName);
assertThat(resultSet.getInt("TYPE")).isEqualTo(3);
assertThat(resultSet.getInt("ORDINAL_POSITION")).isOne();
assertThat(resultSet.getString("COLUMN_NAME")).isEqualTo("uuid");
assertThat(resultSet.getObject("TABLE_CAT")).isNull();
assertThat(resultSet.getObject("TABLE_SCHEM")).isEqualTo("public");
assertThat(resultSet.getObject("ASC_OR_DESC")).isEqualTo("A");
assertThat(resultSet.getObject("CARDINALITY")).isNull();
assertThat(resultSet.getObject("PAGES")).isNull();
assertThat(resultSet.getObject("FILTER_CONDITION")).isNull();
assertThat(resultSet.next()).isFalse();
}
finally {
try (var statement = this.connection.createStatement()) {
statement.execute("DROP INDEX " + indexName + " IF EXISTS");
}
}
}

record IndexInfo(String tableName, boolean nonUnique, String indexName, int type, int ordinalPosition,
String columnName, String ascOrDesc) {
IndexInfo(ResultSet resultset) throws SQLException {
Expand Down
52 changes: 31 additions & 21 deletions neo4j-jdbc/src/main/java/org/neo4j/jdbc/DatabaseMetadataImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,14 @@ public String getStringFunctions() {
}

@Override
public String getSystemFunctions() {
return "";
public String getSystemFunctions() throws SQLException {
var functions = new ArrayList<String>();
try (var rs = getFunctions(null, null, null)) {
while (rs.next()) {
functions.add(rs.getString("FUNCTION_NAME"));
}
}
return String.join(",", functions);
}

@Override
Expand Down Expand Up @@ -826,20 +832,23 @@ public ResultSet getTables(String catalog, String schemaPattern, String tableNam
assertSchemaIsPublicOrNull(schemaPattern);
assertCatalogIsNullOrEmpty(catalog);

if (tableNamePattern != null && !tableNamePattern.equals("%")) {
return doQueryForResultSet("""
CALL db.labels() YIELD label AS TABLE_NAME WHERE TABLE_NAME=$name RETURN "" as TABLE_CAT,
"public" AS TABLE_SCHEM, TABLE_NAME, "LABEL" as TABLE_TYPE, "" as REMARKS,
"" AS TYPE_CAT, "" AS TYPE_SCHEM, "" AS TYPE_NAME, "" AS SELF_REFERENCES_COL_NAME,
"" AS REF_GENERATION""", Map.of("name", tableNamePattern));
}
else {
return doQueryForResultSet("""
CALL db.labels() YIELD label AS TABLE_NAME RETURN "" as TABLE_CAT,
"public" AS TABLE_SCHEM, TABLE_NAME, "TABLE" as TABLE_TYPE, NULL as REMARKS,
NULL AS TYPE_CAT, NULL AS TYPE_SCHEM, NULL AS TYPE_NAME, NULL AS SELF_REFERENCES_COL_NAME,
NULL AS REF_GENERATION""", Map.of());
}
Map<String, Object> args = new HashMap<>();
args.put("name", (tableNamePattern != null) ? tableNamePattern.replace("%", ".*") : null);
return doQueryForResultSet("""
CALL db.labels() YIELD label AS TABLE_NAME
WHERE ($name IS NULL OR TABLE_NAME =~ $name) AND EXISTS {MATCH (n) WHERE TABLE_NAME IN labels(n)}
RETURN
"" as TABLE_CAT,
"public" AS TABLE_SCHEM,
TABLE_NAME,
"TABLE" as TABLE_TYPE,
NULL as REMARKS,
NULL AS TYPE_CAT,
NULL AS TYPE_SCHEM,
NULL AS TYPE_NAME,
NULL AS SELF_REFERENCES_COL_NAME,
NULL AS REF_GENERATION
""", args);
}

@Override
Expand Down Expand Up @@ -924,14 +933,15 @@ public ResultSet getColumns(String catalog, String schemaPattern, String tableNa
CALL db.schema.nodeTypeProperties()
YIELD nodeLabels, propertyName, propertyTypes
WITH *
WHERE ($name IS NULL OR $name = '%' OR $name IN nodeLabels)
AND ($column_name IS NULL OR $column_name = '%' OR propertyName = $column_name)
WHERE ($name IS NULL OR $name = '%' OR ANY (label IN nodeLabels WHERE label =~ $name))
AND ($column_name IS NULL OR propertyName =~ $column_name)
RETURN *
""";

var queryParams = new HashMap<String, Object>();
queryParams.put("name", tableNamePattern);
queryParams.put("column_name", columnNamePattern);
queryParams.put("name", (tableNamePattern != null) ? tableNamePattern.replace("%", ".*") : tableNamePattern);
queryParams.put("column_name",
(columnNamePattern != null) ? columnNamePattern.replace("%", ".*") : columnNamePattern);

var pullResponse = doQueryForPullResponse(query, queryParams);
var records = pullResponse.records();
Expand Down Expand Up @@ -1212,7 +1222,7 @@ WITH result, range(0, size(result.properties) - 1) AS ordinal_positions
"public" AS TABLE_SCHEM,
result.tableName AS TABLE_NAME,
result.owningConstraint IS NULL AS NON_UNIQUE,
NULL AS INDEX_QUALIFIER,
result.name AS INDEX_QUALIFIER,
result.name AS INDEX_NAME,
$type AS TYPE,
CASE WHEN result.properties[ORDINAL_POSITION] IS NULL THEN NULL ELSE ORDINAL_POSITION + 1 END AS ORDINAL_POSITION,
Expand Down

0 comments on commit da4ea02

Please sign in to comment.