Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed aggregate issue and added unsupported exception for WHERE in JOIN #125

Merged
merged 5 commits into from
Dec 16, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
7 changes: 6 additions & 1 deletion sql-gremlin/README.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ Identifier matching is case-sensitive and identifiers that match a reserved SQL
=== Joins
Currently, the driver only support `INNER JOIN` on two vertices that are connected by an edge. When looking at vertices you will see `<edge_label>_IN_ID` or `<edge_label>_OUT_ID`.

`WHERE` and `HAVING` are not currently supported in conjunction with a `JOIN` clause.
Foreign keys are not generated and not exposed in JDBC metadata at this time.

lyndonbauto marked this conversation as resolved.
Show resolved Hide resolved
Vertices can be joined on columns that have the same edge label and one ends with `IN_ID` while the other ends with an `OUT_ID`.

=== Data Types
Expand All @@ -66,11 +69,13 @@ Supported operators are listed below. Arithmetic, string, conditional and date o

==== Comparison Operators
* `value1 <op> value2` where `op` is one of : `=`, `<>`, `<`, `>`, `<=` or `>=`
* `NOT` in conjunction with a comparison operator is not currently supported

==== Logical Operators
* `boolean1` OR `boolean2`
* `boolean1` AND `boolean2`
* NOT `boolean2`
* NOT `boolean2` is supported only when used with a boolean valued column in `WHERE`/`HAVING` filters
* NOT `boolean2` is supported with expressions and boolean values in `SELECT` clause
lyndonbauto marked this conversation as resolved.
Show resolved Hide resolved

==== Aggregate Functions
* `AVG(numeric)`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinTableBase;
import org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinVertexTable;
import org.twilmes.sql.gremlin.adapter.util.SqlGremlinError;

import java.sql.SQLException;
import java.time.Instant;
import java.util.ArrayList;
Expand Down Expand Up @@ -218,8 +217,14 @@ public String getRenameFromActual(final String actual) {
}

public String getActualColumnName(final GremlinTableBase table, final String column) throws SQLException {
final String actualColumnName = getRenamedColumn(column);
return table.getColumn(actualColumnName).getName();
if (table.hasColumn(column)) {
return table.getColumn(column).getName();
} else if (columnRenameMap.containsKey(column)) {
return table.getColumn(getRenamedColumn(column)).getName();
}
final Optional<String> actualName = columnRenameMap.entrySet().stream().
filter(entry -> entry.getValue().equals(column)).map(Map.Entry::getKey).findFirst();
return table.getColumn(actualName.orElse(column)).getName();
}

public boolean getTableHasColumn(final GremlinTableBase table, final String column) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinTableBase;
import org.twilmes.sql.gremlin.adapter.results.SqlGremlinQueryResult;
import org.twilmes.sql.gremlin.adapter.util.SqlGremlinError;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -100,9 +99,16 @@ public static void addProjection(final List<GremlinSqlIdentifier> gremlinSqlIden
}


public static void applyTraversal(final GraphTraversal<?, ?> graphTraversal,
final GraphTraversal<?, ?> subGraphTraversal) {
graphTraversal.by(subGraphTraversal);
public static void applyTraversal(final boolean apply,
final GraphTraversal graphTraversal,
final GraphTraversal subGraphTraversal) {
lyndonbauto marked this conversation as resolved.
Show resolved Hide resolved
graphTraversal.by((apply ? __.coalesce(subGraphTraversal, __.constant(SqlGremlinQueryResult.NULL_VALUE)) : subGraphTraversal));
}


public static void applyTraversal(final GraphTraversal graphTraversal,
final GraphTraversal subGraphTraversal) {
applyTraversal(false, graphTraversal, subGraphTraversal);
lyndonbauto marked this conversation as resolved.
Show resolved Hide resolved
}

public static void applySqlIdentifier(final GremlinSqlIdentifier sqlIdentifier,
Expand All @@ -115,7 +121,8 @@ public static void applySqlIdentifier(final GremlinSqlIdentifier sqlIdentifier,
graphTraversal.constant(1);
} else if (sqlIdentifier.getNameCount() == 2) {
// With size 2 format of identifier is 'table'.'column' => ['table', 'column']
appendGraphTraversal(sqlIdentifier.getName(0), sqlMetadata.getRenamedColumn(sqlIdentifier.getName(1)), sqlMetadata, graphTraversal);
appendGraphTraversal(sqlIdentifier.getName(0), sqlMetadata.getRenamedColumn(sqlIdentifier.getName(1)),
sqlMetadata, graphTraversal);
} else {
// With size 1, format of identifier is 'column'.
appendGraphTraversal(sqlMetadata.getRenamedColumn(sqlIdentifier.getName(0)), sqlMetadata, graphTraversal);
Expand All @@ -128,7 +135,8 @@ public static void applySqlIdentifier(final GremlinSqlIdentifier sqlIdentifier,
return __.project(firstColumn, remaining);
}

private static void appendColumnSelect(final SqlMetadata sqlMetadata, final String column, final GraphTraversal<?, ?> graphTraversal) {
private static void appendColumnSelect(final SqlMetadata sqlMetadata, final String column,
final GraphTraversal<?, ?> graphTraversal) {
if (sqlMetadata.isDoneFilters()) {
graphTraversal.choose(__.has(column), __.values(column), __.constant(SqlGremlinQueryResult.NULL_VALUE));
} else {
Expand All @@ -151,7 +159,8 @@ private static void appendGraphTraversal(final String table, final String column
}
} else {
// It's this vertex/edge.
if (columnName.toLowerCase(Locale.getDefault()).startsWith(gremlinTableBase.getLabel().toLowerCase(Locale.getDefault()))) {
if (columnName.toLowerCase(Locale.getDefault())
.startsWith(gremlinTableBase.getLabel().toLowerCase(Locale.getDefault()))) {
lyndonbauto marked this conversation as resolved.
Show resolved Hide resolved
graphTraversal.id();
} else {
if (columnName.endsWith(IN_ID)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,18 +187,20 @@ void handleEmbeddedGremlinSqlBasicCall(final GremlinSqlBasicCall gremlinSqlBasic
if (operands.size() != 2) {
throw SqlGremlinError.create(SqlGremlinError.BINARY_AND_PREFIX_OPERAND_COUNT);
}
final GraphTraversal<?, ?>[] graphTraversals = new GraphTraversal[2];
final GraphTraversal[] graphTraversals = new GraphTraversal[2];
for (int i = 0; i < operands.size(); i++) {
graphTraversals[i] = __.unfold();
if (operands.get(i) instanceof GremlinSqlIdentifier) {
GremlinSqlIdentifier gremlinSqlIdentifier = (GremlinSqlIdentifier) operands.get(i);
GraphTraversal subtraversal = __.unfold();
final GremlinSqlIdentifier gremlinSqlIdentifier = (GremlinSqlIdentifier) operands.get(i);
final GraphTraversal subtraversal = __.unfold();
SqlTraversalEngine.applySqlIdentifier(gremlinSqlIdentifier, sqlMetadata, subtraversal);
graphTraversals[i] = __.coalesce(subtraversal,
__.constant(sqlMetadata.getDefaultCoalesceValue(gremlinSqlIdentifier.getColumn())));
} else if (operands.get(i) instanceof GremlinSqlBasicCall) {
GremlinSqlBasicCall gremlinSqlBasicCall = ((GremlinSqlBasicCall) operands.get(i));
final GremlinSqlBasicCall gremlinSqlBasicCall = ((GremlinSqlBasicCall) operands.get(i));
gremlinSqlBasicCall.generateTraversal(graphTraversals[i]);
graphTraversals[i] = __.coalesce(graphTraversals[i],
__.constant(sqlMetadata.getDefaultCoalesceValue(gremlinSqlBasicCall.getActual())));
} else if (operands.get(i) instanceof GremlinSqlLiteral) {
((GremlinSqlLiteral) operands.get(i)).appendTraversal(graphTraversals[i]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,12 @@ protected void applyColumnRetrieval(final GraphTraversal<?, ?> graphTraversal, f
SqlTraversalEngine.getEmptyTraversal(stepDirection, sqlMetadata);
SqlTraversalEngine
.applySqlIdentifier((GremlinSqlIdentifier) gremlinSqlNode, sqlMetadata, subSubGraphTraversal);
SqlTraversalEngine.applyTraversal(subGraphTraversal, subSubGraphTraversal);
SqlTraversalEngine.applyTraversal(true, subGraphTraversal, subSubGraphTraversal);
lyndonbauto marked this conversation as resolved.
Show resolved Hide resolved
} else if (gremlinSqlNode instanceof GremlinSqlBasicCall) {
final GraphTraversal<?, ?> subSubGraphTraversal =
SqlTraversalEngine.getEmptyTraversal(stepDirection, sqlMetadata);
((GremlinSqlBasicCall) gremlinSqlNode).generateTraversal(subSubGraphTraversal);
SqlTraversalEngine.applyTraversal(subGraphTraversal, subSubGraphTraversal);
SqlTraversalEngine.applyTraversal(true, subGraphTraversal, subSubGraphTraversal);
lyndonbauto marked this conversation as resolved.
Show resolved Hide resolved
} else {
throw SqlGremlinError.create(SqlGremlinError.UNKNOWN_NODE_SELECTLIST, gremlinSqlNode.getClass().getName());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ protected void runTraversalExecutor(final GraphTraversal<?, ?> graphTraversal,
graphTraversal = g.E().hasLabel(edgeLabel)
.where(__.inV().hasLabel(inVLabel))
.where(__.outV().hasLabel(outVLabel));
applyWhere(graphTraversal);
applyGroupBy(graphTraversal, edgeLabel, inVRename, outVRename);
applySelectValues(graphTraversal);
applyOrderBy(graphTraversal, edgeLabel, inVRename, outVRename);
Expand Down Expand Up @@ -311,4 +312,11 @@ protected void applyHaving(final GraphTraversal<?, ?> graphTraversal) throws SQL
}
throw SqlGremlinError.createNotSupported(SqlGremlinError.JOIN_HAVING_UNSUPPORTED);
}

protected void applyWhere(final GraphTraversal<?, ?> graphTraversal) throws SQLException {
if (sqlSelect.getWhere() == null) {
return;
}
throw SqlGremlinError.createNotSupported(SqlGremlinError.JOIN_WHERE_UNSUPPORTED);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public enum SqlGremlinError {
CANNOT_GROUP_TABLE,
CANNOT_GROUP_COLUMN,
JOIN_HAVING_UNSUPPORTED,
JOIN_WHERE_UNSUPPORTED,
SINGLE_SELECT_MULTI_RETURN,
SELECT_NO_LIST,
UNEXPECTED_FROM_FORMAT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ CANNOT_GROUP_EDGES=Error: Cannot group by edges.
CANNOT_GROUP_TABLE=Error: Unable to group table %s.
CANNOT_GROUP_COLUMN=Error: Unable to group column %s.
JOIN_HAVING_UNSUPPORTED=Unsupported: HAVING is not currently supported for JOIN.
JOIN_WHERE_UNSUPPORTED=Unsupported: WHERE is not currently supported for JOIN.
SINGLE_SELECT_MULTI_RETURN=Error: Single select has multi-table return.
SELECT_NO_LIST=Error: GremlinSqlSelect expects select list component.
UNEXPECTED_FROM_FORMAT=Unexpected format for FROM.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ public void testWhere() throws SQLException {
rows(r("Tom", 35), r("Juanita", 50)));
}

// TODO: Support NOT on operators
// TODO #127: Support NOT in WHERE filter.
@Test
@Disabled
public void testWhereNot() throws SQLException {
Expand Down Expand Up @@ -385,6 +385,9 @@ void testMultiComparisonOperator() throws SQLException {
runQueryTestResults(
"SELECT NOT age > 0 FROM person",
columns("NOT age > 0"), rows(r(false), r(false), r(false), r(false), r(false), r(false)));
runQueryTestResults(
"SELECT NOT name = 'Tom' FROM person",
columns("NOT name = Tom"), rows(r(false), r(true), r(true), r(true), r(true), r(true)));
}

@Test
Expand All @@ -410,5 +413,8 @@ void testComparisonWithAsOperator() throws SQLException {
runQueryTestResults(
"SELECT NOT age > 0 AS a FROM person",
columns("a"), rows(r(false), r(false), r(false), r(false), r(false), r(false)));
runQueryTestResults(
"SELECT NOT name = 'Tom' AS a FROM person",
columns("a"), rows(r(false), r(true), r(true), r(true), r(true), r(true)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,13 @@ protected void runJoinQueryTestResults(final String query, final List<String> co

protected void runQueryTestThrows(final String query, final SqlGremlinError messageKey,
final Object... formatArgs) {
Throwable t = Assertions.assertThrows(SQLException.class, () -> converter.executeQuery(g, query));
final Throwable t = Assertions.assertThrows(SQLException.class, () -> converter.executeQuery(g, query));
Assert.assertEquals(SqlGremlinError.getMessage(messageKey, formatArgs), t.getMessage());
}

protected void runNotSupportedQueryTestThrows(final String query, final SqlGremlinError messageKey,
final Object... formatArgs) {
Throwable t = Assertions.assertThrows(SQLNotSupportedException.class, () -> converter.executeQuery(g, query));
final Throwable t = Assertions.assertThrows(SQLNotSupportedException.class, () -> converter.executeQuery(g, query));
Assert.assertEquals(SqlGremlinError.getMessage(messageKey, formatArgs), t.getMessage());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,7 @@

package org.twilmes.sql.gremlin.adapter;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.twilmes.sql.gremlin.adapter.util.SqlGremlinError;

import java.math.BigDecimal;
import java.sql.SQLException;

public class GremlinSqlIncompleteSelectTest extends GremlinSqlBaseTest {
Expand All @@ -44,7 +40,8 @@ public void testProject() throws SQLException {

@Test
public void testEdges() throws SQLException {
runQueryTestResults("select * from worksFor where yearsWorked = 9", columns("person_OUT_ID", "company_IN_ID", "yearsWorked", "worksFor_ID"),
runQueryTestResults("select * from worksFor where yearsWorked = 9",
columns("person_OUT_ID", "company_IN_ID", "yearsWorked", "worksFor_ID"),
rows(r(25L, 2L, 9, 61L)));
}

Expand All @@ -66,7 +63,7 @@ public void testOrderWithNull() throws SQLException {
runQueryTestResults("SELECT name, age AS a FROM person ORDER BY a", columns("name", "a"),
rows(r("Patty", 29), r(null, 30), r("Phil", 31), r("Susan", 45), r("Juanita", 50), r("Tom", null)));
runQueryTestResults("SELECT name, age AS a FROM person ORDER BY a DESC", columns("name", "a"),
rows( r("Tom", null), r("Juanita", 50), r("Susan", 45), r("Phil", 31), r(null, 30), r("Patty", 29)));
rows(r("Tom", null), r("Juanita", 50), r("Susan", 45), r("Phil", 31), r(null, 30), r("Patty", 29)));

// ORDER with string column.
runQueryTestResults("SELECT name, age FROM person ORDER BY name", columns("name", "age"),
Expand All @@ -90,66 +87,70 @@ public void testWhereNull() throws SQLException {

// TODO: Support aggregates for null values
@Test
@Disabled
public void testHavingNull() throws SQLException {
// HAVING with aggregate literal.
runQueryTestResults("SELECT MIN(age) AS age FROM person GROUP BY name HAVING MIN(age) > 1",
columns("age"),
rows(r((Object) null), r(29), r(31), r(45), r(50), r(30)));
runQueryTestResults("SELECT wentToSpace, SUM(age) FROM person GROUP BY wentToSpace HAVING SUM(age) > 1000",
columns("wentToSpace", "SUM(age)"),
rows());
columns("wentToSpace", "SUM(age)"),
rows());
runQueryTestResults("SELECT wentToSpace, COUNT(age) FROM person GROUP BY wentToSpace HAVING COUNT(age) < 1000",
columns("wentToSpace", "COUNT(age)"),
rows(r(false, 3L), r(true, 3L)));
runQueryTestResults("SELECT wentToSpace, COUNT(age) FROM person GROUP BY wentToSpace HAVING COUNT(age) <> 3",
rows(r(false, 2L), r(true, 2L), r(null, 1L)));
runQueryTestResults("SELECT wentToSpace, COUNT(age) FROM person GROUP BY wentToSpace HAVING COUNT(age) <> 2",
columns("wentToSpace", "COUNT(age)"),
rows());
rows(r(null, 1L)));
runQueryTestResults("SELECT COUNT(age), SUM(age) FROM person GROUP BY wentToSpace HAVING SUM(age) > 100",
columns("COUNT(age)", "SUM(age)"),
rows(r(3L, 125L)));
rows());
runQueryTestResults("SELECT COUNT(age), SUM(age) FROM person GROUP BY wentToSpace HAVING SUM(age) > 80",
columns("COUNT(age)", "SUM(age)"),
rows(r(2L, 95L)));
runQueryTestResults("SELECT COUNT(age), SUM(age) FROM person GROUP BY wentToSpace HAVING SUM(age) < 100",
columns("COUNT(age)", "SUM(age)"),
rows(r(3L, 95L)));
rows(r(2L, 60L), r(2L, 95L), r(1L, 30L)));
runQueryTestResults("SELECT COUNT(age), SUM(age) FROM person GROUP BY age HAVING SUM(age) <> 1000",
columns("COUNT(age)", "SUM(age)"),
rows(r(1L, 35L), r(1L, 29L), r(1L, 31L), r(1L, 45L), r(1L, 50L), r(1L, 30L)));
runQueryTestResults("SELECT COUNT(age), SUM(age) FROM person GROUP BY age HAVING SUM(age) = 35",
rows(r(0L, null), r(1L, 29L), r(1L, 31L), r(1L, 45L), r(1L, 50L), r(1L, 30L)));
runQueryTestResults("SELECT COUNT(age), SUM(age) FROM person GROUP BY age HAVING SUM(age) = 29",
columns("COUNT(age)", "SUM(age)"),
rows(r(1L, 35L)));
rows(r(1L, 29L)));

// Having with AND.
runQueryTestResults(
"SELECT COUNT(age), SUM(age) FROM person GROUP BY wentToSpace HAVING SUM(age) <> 1000 AND COUNT(age) = 3",
columns("COUNT(age)", "SUM(age)"),
rows(r(3L, 95L), r(3L, 125L)));
rows());
runQueryTestResults(
"SELECT COUNT(age), SUM(age) FROM person GROUP BY wentToSpace HAVING SUM(age) = 125 AND COUNT(age) = 3",
columns("COUNT(age)", "SUM(age)"),
rows(r(3L, 125L)));
rows());
runQueryTestResults(
"SELECT COUNT(age), SUM(age) FROM person GROUP BY wentToSpace HAVING SUM(age) <> 125 AND COUNT(age) = 3",
columns("COUNT(age)", "SUM(age)"),
rows(r(3L, 95L)));
rows());
runQueryTestResults(
"SELECT COUNT(age), SUM(age) FROM person GROUP BY wentToSpace HAVING SUM(age) <> 125 AND COUNT(age) <> 3",
columns("COUNT(age)", "SUM(age)"),
rows());
rows(r(2L, 60L), r(2L, 95L), r(1L, 30L)));

// Having with OR.
runQueryTestResults(
"SELECT COUNT(age), SUM(age) FROM person GROUP BY wentToSpace HAVING SUM(age) = 1000 OR COUNT(age) = 3",
columns("COUNT(age)", "SUM(age)"),
rows(r(3L, 95L), r(3L, 125L)));
rows());
runQueryTestResults(
"SELECT COUNT(age), SUM(age) FROM person GROUP BY wentToSpace HAVING SUM(age) = 1000 OR COUNT(age) <> 3",
columns("COUNT(age)", "SUM(age)"),
rows());
rows(r(2L, 60L), r(2L, 95L), r(1L, 30L)));
runQueryTestResults(
"SELECT COUNT(age), SUM(age) FROM person GROUP BY wentToSpace HAVING SUM(age) = 125 OR COUNT(age) <> 3",
columns("COUNT(age)", "SUM(age)"),
rows(r(3L, 125L)));
rows(r(2L, 60L), r(2L, 95L), r(1L, 30L)));
runQueryTestResults(
"SELECT COUNT(age), SUM(age) FROM person GROUP BY wentToSpace HAVING SUM(age) <> 125 OR COUNT(age) <> 3",
columns("COUNT(age)", "SUM(age)"),
rows(r(3L, 95L)));
rows(r(2L, 60L), r(2L, 95L), r(1L, 30L)));
}

@Test
Expand Down
Loading