From 1b106be9b12d73752a50043a850e639ce150a1b2 Mon Sep 17 00:00:00 2001 From: mswertz Date: Mon, 22 Jan 2024 09:56:47 +0100 Subject: [PATCH] feat(graphql): can expand into the fields of subclasses --- .../graphql/GraphqlTableFieldFactory.java | 2 +- .../TestTableQueriesWithInheritance.java | 58 +++++++++++++++++++ .../java/org/molgenis/emx2/sql/SqlQuery.java | 13 +++-- .../java/org/molgenis/emx2/TableMetadata.java | 39 +++++++++++++ .../org/molgenis/emx2/TestTableMetadata.java | 30 ++++++++++ 5 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 backend/molgenis-emx2-graphql/src/test/java/org/molgenis/emx2/graphql/TestTableQueriesWithInheritance.java create mode 100644 backend/molgenis-emx2/src/test/java/org/molgenis/emx2/TestTableMetadata.java diff --git a/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlTableFieldFactory.java b/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlTableFieldFactory.java index c36e201c30..47679b6783 100644 --- a/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlTableFieldFactory.java +++ b/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlTableFieldFactory.java @@ -142,7 +142,7 @@ private GraphQLNamedOutputType createTableObjectType(TableMetadata table) { tableTypes.put(tableObjectType, GraphQLTypeReference.typeRef(tableObjectType)); // build the object GraphQLObjectType.Builder tableBuilder = GraphQLObjectType.newObject().name(tableObjectType); - for (Column col : table.getColumnsWithoutHeadings()) { + for (Column col : table.getColumnsIncludingSubclassesExcludingHeadings()) { String id = col.getIdentifier(); switch (col.getColumnType().getBaseType()) { case HEADING: diff --git a/backend/molgenis-emx2-graphql/src/test/java/org/molgenis/emx2/graphql/TestTableQueriesWithInheritance.java b/backend/molgenis-emx2-graphql/src/test/java/org/molgenis/emx2/graphql/TestTableQueriesWithInheritance.java new file mode 100644 index 0000000000..09c89f9cb1 --- /dev/null +++ b/backend/molgenis-emx2-graphql/src/test/java/org/molgenis/emx2/graphql/TestTableQueriesWithInheritance.java @@ -0,0 +1,58 @@ +package org.molgenis.emx2.graphql; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.molgenis.emx2.Column.column; +import static org.molgenis.emx2.Row.row; +import static org.molgenis.emx2.TableMetadata.table; +import static org.molgenis.emx2.graphql.GraphqlApiFactory.convertExecutionResultToJson; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import graphql.GraphQL; +import java.io.IOException; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.molgenis.emx2.ColumnType; +import org.molgenis.emx2.Database; +import org.molgenis.emx2.MolgenisException; +import org.molgenis.emx2.Schema; +import org.molgenis.emx2.sql.TestDatabaseFactory; +import org.molgenis.emx2.tasks.TaskService; +import org.molgenis.emx2.tasks.TaskServiceInMemory; + +public class TestTableQueriesWithInheritance { + private static final String schemaName = TestTableQueriesWithInheritance.class.getSimpleName(); + private static GraphQL grapql; + private static Database database; + private static TaskService taskService; + private static Schema schema; + + @BeforeAll + public static void setup() { + database = TestDatabaseFactory.getTestDatabase(); + schema = database.dropCreateSchema(schemaName); + schema.create(table("Person", column("name").setPkey())); + schema.create( + table("Employee", column("salary").setType(ColumnType.INT)).setInheritName("Person")); + schema.getTable("Employee").insert(row("name", "pooky", "salary", 1000)); + taskService = new TaskServiceInMemory(); + grapql = new GraphqlApiFactory().createGraphqlForSchema(schema, taskService); + } + + @Test + public void testQueriesIncludingSubclassColumns() throws IOException { + JsonNode result = + execute( + "{Person{name,salary}}"); // note, Person.salary doesn't exist, but Employee.salary does + assertEquals(1000, result.at("/Person/0/salary").asInt()); + } + + private JsonNode execute(String query) throws IOException { + String result = convertExecutionResultToJson(grapql.execute(query)); + JsonNode node = new ObjectMapper().readTree(result); + if (node.get("errors") != null) { + throw new MolgenisException(node.get("errors").get(0).get("message").asText()); + } + return new ObjectMapper().readTree(result).get("data"); + } +} diff --git a/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/SqlQuery.java b/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/SqlQuery.java index 82fb83f14f..98aaaa8be1 100644 --- a/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/SqlQuery.java +++ b/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/SqlQuery.java @@ -835,7 +835,6 @@ else if (col.getColumnType().isArray()) { } private static Table tableWithInheritanceJoin(TableMetadata table) { - Table result = table.getJooqTable(); TableMetadata inheritedTable = table.getInheritedTable(); // root and intermediate levels have mg_tableclass column @@ -851,6 +850,12 @@ private static Table tableWithInheritanceJoin(TableMetadata tab mg_tableclass = inheritedTable.getLocalColumn(MG_TABLECLASS); } } + // join subclass tables also (todo: can we make this less expensive?) + for (TableMetadata subclassTable : table.getSubclassTables()) { + List> using = subclassTable.getPrimaryKeyFields(); + result = result.join(subclassTable.getJooqTable()).using(using.toArray(new Field[0])); + } + return result; } @@ -1381,10 +1386,10 @@ private static Column getColumnByName(TableMetadata table, String columnName) { return new Column(table, searchColumnName(table.getTableName())); } // is scalar column - Column column = table.getColumn(columnName); + Column column = table.getColumnIncludingSubclasses(columnName); if (column == null) { // is reference? - for (Column c : table.getColumns()) { + for (Column c : table.getColumnsIncludingSubclasses()) { if (c.isReference()) { for (Reference ref : c.getReferences()) { // can also request composite reference columns, can only be used on row level queries @@ -1395,7 +1400,7 @@ private static Column getColumnByName(TableMetadata table, String columnName) { } } // is file? - for (Column c : table.getColumns()) { + for (Column c : table.getColumnsIncludingSubclasses()) { if (c.isFile() && columnName.startsWith(c.getName()) && (columnName.equals(c.getName()) diff --git a/backend/molgenis-emx2/src/main/java/org/molgenis/emx2/TableMetadata.java b/backend/molgenis-emx2/src/main/java/org/molgenis/emx2/TableMetadata.java index 06388c7c25..4f9bbc14ae 100644 --- a/backend/molgenis-emx2/src/main/java/org/molgenis/emx2/TableMetadata.java +++ b/backend/molgenis-emx2/src/main/java/org/molgenis/emx2/TableMetadata.java @@ -583,4 +583,43 @@ public String getDescription() { return null; } } + + public List getColumnsIncludingSubclasses() { + // get all tables in current schema that inherit this + List result = new ArrayList<>(); + result.addAll(this.getColumns()); + result.addAll(getColumnsFromSubclasses()); + return result; + } + + public List getColumnsIncludingSubclassesExcludingHeadings() { + return getColumnsIncludingSubclasses().stream().filter(c -> !c.isHeading()).toList(); + } + + private List getColumnsFromSubclasses() { + List result = new ArrayList<>(); + for (TableMetadata table : getSubclassTables()) { + result.addAll(table.getLocalColumns()); + result.addAll(table.getColumnsFromSubclasses()); + } + return result; + } + + public Column getColumnIncludingSubclasses(String columnName) { + return getColumnsIncludingSubclasses().stream() + .filter(c -> c.getName().equals(columnName)) + .findFirst() + .orElseGet(() -> null); + } + + public List getSubclassTables() { + List result = new ArrayList(); + for (TableMetadata table : getSchema().getTables()) { + if (this.getTableName().equals(table.getInheritName())) { + result.add(table); + result.addAll(table.getSubclassTables()); + } + } + return result; + } } diff --git a/backend/molgenis-emx2/src/test/java/org/molgenis/emx2/TestTableMetadata.java b/backend/molgenis-emx2/src/test/java/org/molgenis/emx2/TestTableMetadata.java new file mode 100644 index 0000000000..7d53844eda --- /dev/null +++ b/backend/molgenis-emx2/src/test/java/org/molgenis/emx2/TestTableMetadata.java @@ -0,0 +1,30 @@ +package org.molgenis.emx2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.molgenis.emx2.Column.column; +import static org.molgenis.emx2.TableMetadata.table; + +import java.util.List; +import org.junit.jupiter.api.Test; + +public class TestTableMetadata { + @Test + void testGetColumnsFromSubclasses() { + SchemaMetadata s = + new SchemaMetadata() + .create( + table("Person", column("name")), + table("Employee", column("details").setType(ColumnType.HEADING), column("salary")) + .setInheritName("Person")); + + List result = s.getTableMetadata("Person").getColumnsIncludingSubclasses(); + assertEquals(3, result.size()); + + Column salary = result.get(2); + assertEquals("salary", salary.getName()); + assertEquals("Employee", salary.getTableName()); + + result = s.getTableMetadata("Person").getColumnsIncludingSubclassesExcludingHeadings(); + assertEquals(2, result.size()); + } +}