Skip to content

Commit

Permalink
ISSUE-1026 Add support for @subselect (#1038)
Browse files Browse the repository at this point in the history
* Manager transacton manually

* Add readonly

* some rework

* use getTimeDimension()

* change exception

* ISSUE-1026 Add support for @subselect

* Address comments
  • Loading branch information
hellohanchen authored and aklish committed Oct 22, 2019
1 parent 7165d60 commit 8c0cbc1
Show file tree
Hide file tree
Showing 16 changed files with 498 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import com.yahoo.elide.utils.coerce.CoerceUtil;

import com.google.common.base.Preconditions;

import org.hibernate.jpa.QueryHints;

import lombok.Getter;
Expand All @@ -48,7 +47,6 @@
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Table;

/**
* QueryEngine for SQL backed stores.
Expand Down Expand Up @@ -181,7 +179,7 @@ protected SQLQuery toSQL(Query query, SQLSchema schema) {
(predicate) -> { return generateHavingClauseColumnReference(predicate, query); }));
}

if (! query.getDimensions().isEmpty()) {
if (!query.getDimensions().isEmpty()) {
builder.groupByClause(extractGroupBy(query));

joinPredicates.addAll(extractPathElements(query
Expand Down Expand Up @@ -298,25 +296,20 @@ private String extractJoin(Path.PathElement pathElement) {
//TODO - support joins where either side owns the relationship.
//TODO - Support INNER and RIGHT joins.
//TODO - Support toMany joins.
String relationshipName = pathElement.getFieldName();
Class<?> relationshipClass = pathElement.getFieldType();
String relationshipAlias = FilterPredicate.getTypeAlias(relationshipClass);
Class<?> entityClass = pathElement.getType();
String entityAlias = FilterPredicate.getTypeAlias(entityClass);

Table tableAnnotation = dictionary.getAnnotation(relationshipClass, Table.class);

String relationshipTableName = (tableAnnotation == null)
? dictionary.getJsonAliasFor(relationshipClass)
: tableAnnotation.name();

Class<?> relationshipClass = pathElement.getFieldType();
String relationshipAlias = FilterPredicate.getTypeAlias(relationshipClass);
String relationshipName = pathElement.getFieldName();
String relationshipIdField = getColumnName(relationshipClass, dictionary.getIdFieldName(relationshipClass));
String relationshipColumnName = getColumnName(entityClass, relationshipName);

return String.format("LEFT JOIN %s AS %s ON %s.%s = %s.%s",
relationshipTableName,
SQLSchema.getTableOrSubselect(dictionary, relationshipClass),
relationshipAlias,
entityAlias,
getColumnName(entityClass, relationshipName),
relationshipColumnName,
relationshipAlias,
relationshipIdField);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@
import com.yahoo.elide.datastores.aggregation.schema.metric.Aggregation;
import com.yahoo.elide.datastores.aggregation.schema.metric.Metric;

import org.hibernate.annotations.Subselect;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;

import java.util.List;
import javax.persistence.Column;
import javax.persistence.JoinColumn;
import javax.persistence.Table;

/**
* A subclass of Schema that supports additional metadata to construct the FROM clause of a SQL query.
Expand Down Expand Up @@ -116,15 +119,15 @@ protected Metric constructMetric(String metricField, Class<?> cls, EntityDiction
/**
* Maps a logical entity attribute into a physical SQL column name.
* @param entityDictionary The dictionary for this elide instance.
* @param clazz The entity class.
* @param cls The entity class.
* @param fieldName The entity attribute.
* @return The physical SQL column name.
*/
public static String getColumnName(EntityDictionary entityDictionary, Class<?> clazz, String fieldName) {
Column[] column = entityDictionary.getAttributeOrRelationAnnotations(clazz, Column.class, fieldName);
public static String getColumnName(EntityDictionary entityDictionary, Class<?> cls, String fieldName) {
Column[] column = entityDictionary.getAttributeOrRelationAnnotations(cls, Column.class, fieldName);

// this would only be valid for dimension columns
JoinColumn[] joinColumn = entityDictionary.getAttributeOrRelationAnnotations(clazz,
JoinColumn[] joinColumn = entityDictionary.getAttributeOrRelationAnnotations(cls,
JoinColumn.class, fieldName);

if (column == null || column.length == 0) {
Expand All @@ -138,14 +141,36 @@ public static String getColumnName(EntityDictionary entityDictionary, Class<?> c
}
}

/**
* Maps an entity class to a physical table of subselect query, if neither {@link Table} nor {@link Subselect}
* annotation is present on this class, use the class alias as default.
*
* @param entityDictionary The dictionary for this elide instance.
* @param cls The entity class.
* @return The physical SQL table or subselect query.
*/
public static String getTableOrSubselect(EntityDictionary entityDictionary, Class<?> cls) {
Subselect subselectAnnotation = entityDictionary.getAnnotation(cls, Subselect.class);

if (subselectAnnotation == null) {
Table tableAnnotation = entityDictionary.getAnnotation(cls, Table.class);

return (tableAnnotation == null)
? entityDictionary.getJsonAliasFor(cls)
: tableAnnotation.name();
} else {
return "(" + subselectAnnotation.value() + ")";
}
}

/**
* Returns the physical database column name of an entity field.
* @param clazz The entity which owns the field.
* @param cls The entity which owns the field.
* @param fieldName The field name to lookup
* @return
* @return physical database column name of an entity field
*/
private String getColumnName(Class<?> clazz, String fieldName) {
return getColumnName(entityDictionary, clazz, fieldName);
private String getColumnName(Class<?> cls, String fieldName) {
return getColumnName(entityDictionary, cls, fieldName);
}

private String getJoinColumn(Path path) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.yahoo.elide.datastores.aggregation.example.Player;
import com.yahoo.elide.datastores.aggregation.example.PlayerStats;
import com.yahoo.elide.datastores.aggregation.example.PlayerStatsView;
import com.yahoo.elide.datastores.aggregation.example.SubCountry;
import com.yahoo.elide.datastores.aggregation.example.VideoGame;

public class AggregationDataStoreTestHarness implements DataStoreTestHarness {
Expand All @@ -28,6 +29,7 @@ public DataStore getDataStore() {
public void populateEntityDictionary(EntityDictionary dictionary) {
dictionary.bindEntity(PlayerStats.class);
dictionary.bindEntity(Country.class);
dictionary.bindEntity(SubCountry.class);
dictionary.bindEntity(PlayerStatsView.class);
dictionary.bindEntity(Player.class);
dictionary.bindEntity(VideoGame.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

/**
* A root level entity for testing AggregationDataStore.
*/
@Data
@Entity
@Include(rootLevel = true)
@Table(name = "countries")
@Cardinality(size = CardinalitySize.SMALL)
public class Country {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@
import com.yahoo.elide.datastores.aggregation.annotation.Cardinality;
import com.yahoo.elide.datastores.aggregation.annotation.CardinalitySize;
import com.yahoo.elide.datastores.aggregation.annotation.FriendlyName;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

/**
* A root level entity for testing AggregationDataStore.
*/
@Entity
@Include(rootLevel = true)
@Table(name = "players")
@Cardinality(size = CardinalitySize.MEDIUM)
@Data
public class Player {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@
import lombok.ToString;

import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.ManyToOne;

/**
* A root level entity for testing AggregationDataStore.
Expand Down Expand Up @@ -65,11 +66,21 @@ public class PlayerStats {
*/
private Country country;

/**
* A subselect dimension.
*/
private SubCountry subCountry;

/**
* A dimension field joined to this table.
*/
private String countryIsoCode;

/**
* A dimension field joined to this table.
*/
private String subCountryIsoCode;

/**
* A table dimension.
*/
Expand Down Expand Up @@ -115,7 +126,7 @@ public void setOverallRating(final String overallRating) {
this.overallRating = overallRating;
}

@OneToOne
@ManyToOne
@JoinColumn(name = "country_id")
public Country getCountry() {
return country;
Expand All @@ -125,7 +136,17 @@ public void setCountry(final Country country) {
this.country = country;
}

@OneToOne
@ManyToOne
@JoinColumn(name = "sub_country_id")
public SubCountry getSubCountry() {
return subCountry;
}

public void setSubCountry(final SubCountry subCountry) {
this.subCountry = subCountry;
}

@ManyToOne
@JoinColumn(name = "player_id")
public Player getPlayer() {
return player;
Expand Down Expand Up @@ -157,4 +178,15 @@ public String getCountryIsoCode() {
public void setCountryIsoCode(String isoCode) {
this.countryIsoCode = isoCode;
}


@JoinTo(path = "subCountry.isoCode")
@Column(updatable = false, insertable = false) // subselect field should be read-only
public String getSubCountryIsoCode() {
return subCountryIsoCode;
}

public void setSubCountryIsoCode(String isoCode) {
this.subCountryIsoCode = isoCode;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
@Entity
@Include(rootLevel = true)
@Data
@FromSubquery(sql = "SELECT stats.highScore, stats.player_id, c.name as countryName FROM playerStats AS stats LEFT JOIN country AS c ON stats.country_id = c.id WHERE stats.overallRating = 'Great'")
@FromSubquery(sql = "SELECT stats.highScore, stats.player_id, c.name as countryName FROM playerStats AS stats LEFT JOIN countries AS c ON stats.country_id = c.id WHERE stats.overallRating = 'Great'")
public class PlayerStatsView {

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2019, Yahoo Inc.
* Licensed under the Apache License, Version 2.0
* See LICENSE file in project root for terms.
*/
package com.yahoo.elide.datastores.aggregation.example;

import com.yahoo.elide.annotation.Include;
import com.yahoo.elide.datastores.aggregation.annotation.Cardinality;
import com.yahoo.elide.datastores.aggregation.annotation.CardinalitySize;
import com.yahoo.elide.datastores.aggregation.annotation.FriendlyName;

import org.hibernate.annotations.Subselect;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.Id;

/**
* A root level entity for testing AggregationDataStore with @Subselect annotation
*/
@Data
@Entity
@Include(rootLevel = true)
@Subselect(value = "select * from countries")
@Cardinality(size = CardinalitySize.SMALL)
public class SubCountry {

private String id;

private String isoCode;

private String name;

@Id
public String getId() {
return id;
}

public void setId(final String id) {
this.id = id;
}

public String getIsoCode() {
return isoCode;
}

public void setIsoCode(final String isoCode) {
this.isoCode = isoCode;
}

@FriendlyName
public String getName() {
return name;
}

public void setName(final String name) {
this.name = name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

/**
* A root level entity for testing AggregationDataStore.
*/
@Entity
@Include(rootLevel = true)
@FromTable(name = "videoGame")
@Table(name = "videoGames")
@FromTable(name = "videoGames")
public class VideoGame {

@Id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.yahoo.elide.datastores.aggregation.example.Country;
import com.yahoo.elide.datastores.aggregation.example.Player;
import com.yahoo.elide.datastores.aggregation.example.PlayerStats;
import com.yahoo.elide.datastores.aggregation.example.SubCountry;
import com.yahoo.elide.datastores.aggregation.schema.Schema;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -49,6 +50,7 @@ public static void setupEntityDictionary() {
entityDictionary = new EntityDictionary(Collections.emptyMap());
entityDictionary.bindEntity(PlayerStats.class);
entityDictionary.bindEntity(Country.class);
entityDictionary.bindEntity(SubCountry.class);
entityDictionary.bindEntity(Player.class);
schema = new Schema(PlayerStats.class, entityDictionary);
splitFilterExpressionVisitor = new SplitFilterExpressionVisitor(schema);
Expand Down
Loading

0 comments on commit 8c0cbc1

Please sign in to comment.