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

Provide gRPC api for input tables #1440

Merged
merged 13 commits into from
Nov 8, 2021
2 changes: 2 additions & 0 deletions Base/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
dependencies {
compile depLog4j, depTrove3, depAnnotations, depCommonsCompress

compile depCommonsLang3

compile 'io.deephaven:hash:0.1.0'

testCompile fileTree(dir: "${rootDir}/test-libs", include: ['*.jar'])
Expand Down
86 changes: 25 additions & 61 deletions Base/src/main/java/io/deephaven/base/ClassUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,91 +4,55 @@

package io.deephaven.base;

import org.apache.log4j.Logger;
import org.apache.commons.lang3.ClassUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public final class ClassUtil {
public static String getBaseName(final String s) {
int i = s.lastIndexOf(".");
return (i == -1) ? s : s.substring(i + 1);
}

public static void dumpFinals(final Logger log, final String prefix, final Object p) {
final Class c = p.getClass();
Field[] fields = c.getDeclaredFields();
final int desiredMods = (Modifier.PUBLIC | Modifier.FINAL);
for (Field f : fields) {
if ((f.getModifiers() & desiredMods) == 0)
continue;
try {
final String tName = f.getType().getName();
final String name = f.getName();
final Object value = f.get(p);
log.info(prefix
+ tName
+ " " + name
+ " = " + value.toString());
} catch (Exception ignored) {
}
}
}

public static <T> Class<T> generify(Class c) {
return c;
}

private static final Map<String, Class<?>> classMap = new HashMap<>();
public static Map<String, Class<?>> primitives = new HashMap<>();

static {
primitives.put("boolean", boolean.class);
primitives.put("int", int.class);
primitives.put("double", double.class);
primitives.put("long", long.class);
primitives.put("byte", byte.class);
primitives.put("short", short.class);
primitives.put("char", char.class);
primitives.put("float", float.class);
}
private static final Map<String, Class<?>> classMap = new ConcurrentHashMap<>();

private static Class<?> getJavaType(String selectedType) throws ClassNotFoundException {
int arrayCount = 0;
while (selectedType.endsWith("[]")) {
selectedType = selectedType.substring(0, selectedType.length() - 2);
++arrayCount;
}
Class<?> result = primitives.get(selectedType);
if (result == null && selectedType.startsWith("java.lang.")) {
result = primitives.get(selectedType.substring("java.lang.".length()));
niloc132 marked this conversation as resolved.
Show resolved Hide resolved
}
if (result == null) {
result = Class.forName(selectedType.split("<")[0]);
}
if (arrayCount > 0) {
final int[] dimensions = new int[arrayCount];
result = Array.newInstance(result, dimensions).getClass();
niloc132 marked this conversation as resolved.
Show resolved Hide resolved
}
return result;
// Given string might have generics, remove those before delegating to
// commons-lang3 for lookup implementation. Greedily match from first
// '<' to last '>' and remove all, so that the two types of array
// notation are retained.
String noGenerics = selectedType.replaceAll("<.*>", "");

return ClassUtils.getClass(noGenerics, false);
}

/**
* Finds and caches Class instances based on name. This implementation can handle the strings created by
* {@link Class#getName()} and {@link Class#getCanonicalName()}, and some mixture of the two. JNI names are not
* supported.
*
* @param name the name of the class to lookup.
* @return A class instance
* @throws ClassNotFoundException if the class cannot be found
*/
public static Class<?> lookupClass(final String name) throws ClassNotFoundException {
Class<?> result = classMap.get(name);
if (result == null) {
try {
result = getJavaType(name);
classMap.put(name, result);
} catch (ClassNotFoundException e) {
classMap.put(name, ClassUtil.class);
// Note that this prevents some runtime fix to the classpath and retrying
classMap.put(name, FailedToResolve.class);
throw e;
}
} else if (result == ClassUtil.class) {
} else if (result == FailedToResolve.class) {
throw new ClassNotFoundException(name);
}
return result;
}

private static class FailedToResolve {
}
}
116 changes: 116 additions & 0 deletions Base/src/test/java/io/deephaven/base/ClassUtilTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package io.deephaven.base;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;

@RunWith(Parameterized.class)
public class ClassUtilTest {
@Parameterized.Parameters
public static List<Function<Class<?>, String>[]> naming() {
return Arrays.asList(
new Function[] {(Function<Class<?>, String>) Class::getName},
new Function[] {(Function<Class<?>, String>) Class::getCanonicalName});
}

private final Function<Class<?>, String> namer;

public ClassUtilTest(Function<Class<?>, String> namer) {
this.namer = namer;
}

private Class<?> lookup(Class<?> clazz) throws ClassNotFoundException {
String string = namer.apply(clazz);
System.out.println(string);
return ClassUtil.lookupClass(string);
}

private void assertRoundTrip(Class<?> clazz) throws ClassNotFoundException {
assertSame(clazz, lookup(clazz));
}

@Test
public void lookupClassSimple() throws ClassNotFoundException {
assertRoundTrip(String.class);
assertRoundTrip(ClassUtilTest.class);
}

@Test
public void lookupClassPrimitive() throws ClassNotFoundException {
assertRoundTrip(int.class);
assertRoundTrip(Integer.class);
assertRoundTrip(char.class);

// assertRoundTrip(void.class);
assertRoundTrip(Void.class);
}

@Test
public void lookupClassArray() throws ClassNotFoundException {
assertRoundTrip(String[].class);
assertRoundTrip(String[][][][][][].class);
assertRoundTrip(ClassUtilTest[][][][][][].class);

assertRoundTrip(int[].class);
}

@Test
public void lookupClassInner() throws ClassNotFoundException {
assertRoundTrip(StaticInnerClass.class);
assertRoundTrip(InnerClass.class);
assertRoundTrip(StaticInnerClass.StaticInnerStaticInnerClass.class);
assertRoundTrip(StaticInnerClass.InnerStaticInnerClass.class);
assertRoundTrip(InnerClass.InnerInnerClass.class);
assertRoundTrip(Outer.class);
}

@Test
public void lookupClassWithClinit() throws ClassNotFoundException {
assertRoundTrip(VerifyNotInitialized.class);
assertFalse(verifyNotInitializedWasNotInitialized);
}

@Test
public void testGenericStrings() throws ClassNotFoundException {
assertSame(Map.Entry.class, ClassUtil.lookupClass(namer.apply(Map.Entry.class) + "<String, String>"));
// note that this name isn't quite legal for getName(), will try a few iterations to make sure we DWIM
assertSame(Map.Entry[].class, ClassUtil.lookupClass(namer.apply(Map.Entry.class) + "<String, String>[]"));
assertSame(Map.Entry[].class, ClassUtil.lookupClass(namer.apply(Map.Entry[].class) + "<String, String>"));
assertSame(Map.Entry[].class, ClassUtil.lookupClass("[L" + namer.apply(Map.Entry.class) + "<String, String>;"));
}

public static class StaticInnerClass {

public class StaticInnerStaticInnerClass {
}
public static class InnerStaticInnerClass {
}
}

public class InnerClass {
public class InnerInnerClass {
}
}

// Do not reset this by hand, that won't cause the class to be re-initialized
public static boolean verifyNotInitializedWasNotInitialized = false;
}


class Outer {
}


class VerifyNotInitialized {
static {
ClassUtilTest.verifyNotInitializedWasNotInitialized = true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ public String getColumnNamesAsString() {
*
* @param other the other definition
* @return {@code this} table definition, but in the the column order of {@code other}
* @throws IncompatibleTableDefinitionException if the definitions are not compatible
*/
public TableDefinition checkMutualCompatibility(@NotNull final TableDefinition other) {
TableDefinition result = checkCompatibility(other, false);
Expand Down
87 changes: 72 additions & 15 deletions DB/src/main/java/io/deephaven/db/util/config/MutableInputTable.java
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
package io.deephaven.db.util.config;

import io.deephaven.db.exceptions.ArgumentException;
import io.deephaven.db.tables.ColumnDefinition;
import io.deephaven.db.tables.Table;
import io.deephaven.db.tables.TableDefinition;
import io.deephaven.db.v2.utils.Index;
import io.deephaven.web.shared.data.InputTableDefinition;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
* A minimal interface for mutable tables that can be changed over the OpenAPI.
* A minimal interface for mutable shared tables, providing the ability to write to the table instance this is attached
* to. MutableInputTable instances are set on the table as an attribute.
*
* Implementations of this interface will make their own guarantees about how atomically changes will be applied and
* what operations they support.
*/
public interface MutableInputTable extends InputTableRowSetter, InputTableEnumGetter {

/**
* Get the key and value columns names for this table.
* Gets the names of the key columns.
*
* @return the InputTableDefinition.
* @return a list with the names of the key columns of this input table
*/
InputTableDefinition getDefinition();
List<String> getKeyNames();

/**
* Get the names of the key columns
* Gets the names of the value columns. By default, any column not marked as a key column is a value column.
*
* @return an array with the names of our key columns
* @return a list with the names of the value columns of this input table
*/
default String[] getKeyNames() {
return getDefinition().getKeys();
default List<String> getValueNames() {
List<String> keyNames = getKeyNames();
return getTableDefinition().getColumnNames().stream()
.filter(colName -> !keyNames.contains(colName))
.collect(Collectors.toList());
}

/**
Expand All @@ -36,7 +46,50 @@ default String[] getKeyNames() {
TableDefinition getTableDefinition();

/**
* Write newData to this table.
* Helper to check if a table is compatible with this table, so that it could be added as contents.
*
* @param tableToApply the table to check if it can used to add or modify this input table
* @throws TableDefinition.IncompatibleTableDefinitionException if the definitions are not compatible
*/
default void validateAddOrModify(final Table tableToApply) {
getTableDefinition().checkMutualCompatibility(tableToApply.getDefinition());
}

/**
* Validates that the given table definition is suitable to be passed to {@link #delete(Table)}.
*
* @param tableToDelete the definition of the table to delete
* @throws UnsupportedOperationException if this table does not support deletes
* @throws io.deephaven.db.exceptions.ArgumentException if the given definition isn't compatible to be used to
* delete
*/
default void validateDelete(Table tableToDelete) {
final TableDefinition keyDefinition = tableToDelete.getDefinition();
final TableDefinition thisDefinition = getTableDefinition();
final StringBuilder error = new StringBuilder();
final List<String> keyNames = getKeyNames();
for (String keyColumn : keyNames) {
final ColumnDefinition<?> colDef = keyDefinition.getColumn(keyColumn);
final ColumnDefinition<?> thisColDef = thisDefinition.getColumn(keyColumn);
if (colDef == null) {
error.append("Key Column \"").append(keyColumn).append("\" does not exist.\n");
} else if (!colDef.isCompatible(thisColDef)) {
error.append("Key Column \"").append(keyColumn).append("\" is not compatible.\n");
}
}
final List<String> extraKeys = keyDefinition.getColumnNames().stream().filter(kd -> !keyNames.contains(kd))
.collect(Collectors.toList());
if (!extraKeys.isEmpty()) {
error.append("Unknown key columns: ").append(extraKeys);
niloc132 marked this conversation as resolved.
Show resolved Hide resolved
}
if (error.length() > 0) {
throw new ArgumentException("Invalid Key Table Definition: " + error.toString());
}
}

/**
* Write newData to this table. This method will block until the rows are added. Added rows with keys that match
* existing rows will instead replace those rows, if supported.
*
* @param newData the data to write to this table
*
Expand All @@ -50,6 +103,7 @@ default String[] getKeyNames() {
*
* @param table The rows to delete.
* @throws IOException If a problem occurred while deleting the rows.
* @throws UnsupportedOperationException if this table does not support deletes
*/
default void delete(Table table) throws IOException {
delete(table, table.getIndex());
Expand All @@ -60,9 +114,12 @@ default void delete(Table table) throws IOException {
* deleted.
*
* @param table The rows to delete.
* @throws IOException If a problem occurred while deleting the rows.
* @throws IOException if a problem occurred while deleting the rows
* @throws UnsupportedOperationException if this table does not support deletes
*/
void delete(Table table, Index index) throws IOException;
default void delete(Table table, Index index) throws IOException {
throw new UnsupportedOperationException("Table does not support deletes");
}

/**
* Return a user-readable description of this MutableInputTable.
Expand All @@ -86,7 +143,7 @@ default void delete(Table table) throws IOException {
* @return true if columnName is a key column, false otherwise
*/
default boolean isKey(String columnName) {
return Arrays.asList(getDefinition().getKeys()).contains(columnName);
return getKeyNames().contains(columnName);
}

/**
Expand All @@ -97,7 +154,7 @@ default boolean isKey(String columnName) {
* @return true if columnName exists in this MutableInputTable
*/
default boolean hasColumn(String columnName) {
return isKey(columnName) || Arrays.asList(getDefinition().getValues()).contains(columnName);
return getTableDefinition().getColumnNames().contains(columnName);
}

/**
Expand Down
Loading