Skip to content

Commit

Permalink
Use JDK collections for geode bind variables when sending query to se…
Browse files Browse the repository at this point in the history
…rver

Useful for OQLs of type `select * form /region where id in $1` (where `$1` is a guava collection)

By default, Geode can't deserialize Guava collections (and other guava classes) unless guava library
is on server classpath (which is not guaranteed). Bind variables in OQL are serialized using standard
java serialization.
  • Loading branch information
asereda-gs committed Dec 6, 2019
1 parent 0bb7faa commit 7cc1fb8
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 2 deletions.
@@ -0,0 +1,66 @@
/*
* Copyright 2019 Immutables Authors and Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.immutables.criteria.geode;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
* This function is used for OQLs of type
* {@code select * form /region where id in $1} (where {@code $1} is a guava collection).
* Helpful in cases when client has guava library but server has not.
*
* <p>By default, Geode can't deserialize Guava collections (and other guava classes) since guava might
* not be on servers classpath. Bind variables in OQL are serialized using standard java serialization.
*
* <p>Manually "clone" collections into a standard java collection implementation
* like {@link ArrayList} or {@link HashSet}.
*/
class BindVariableConverter implements Function<Object, Object> {

@Override
public Object apply(Object value) {
if (!(value instanceof Iterable)) {
return value;
}

if (value instanceof HashSet || value instanceof ArrayList || value instanceof TreeSet) {
// don't convert java collections (assume no guava implementations exists inside it)
return value;
}

// transform to java collections
Collector<Object, ?, ? extends Iterable<Object>> collector;
if (value instanceof SortedSet) {
collector = Collectors.toCollection(TreeSet::new);
} else if (value instanceof Set) {
collector = Collectors.toCollection(HashSet::new);
} else {
collector = Collectors.toList();
}

return StreamSupport.stream(((Iterable<Object>) value).spliterator(), false).map(this::apply).collect(collector);
}

}
Expand Up @@ -33,12 +33,12 @@
import java.util.stream.Collectors;

class SyncSelect implements Callable<Iterable<Object>> {


/**
* Convert Geode specific {@link QueryService#UNDEFINED} value to null
*/
private static final Function<Object, Object> UNDEFINED_TO_NULL = value -> QueryService.UNDEFINED.equals(value) ? null : value;
private static final Function<Object, Object> BIND_VARIABLE_CONVERTER = new BindVariableConverter();

private final GeodeBackend.Session session;
private final StandardOperations.Select operation;
Expand Down Expand Up @@ -81,9 +81,13 @@ public Iterable<Object> call() throws Exception {
} else {
tupleFn = Function.identity();
}

// convert existing collections to JDK-only implementations (eg. ImmutableList -> ArrayList)
Object[] variables = oql.variables().stream().map(BIND_VARIABLE_CONVERTER).toArray(Object[]::new);
@SuppressWarnings("unchecked")
Iterable<Object> result = (Iterable<Object>) session.queryService.newQuery(oql.oql()).execute(oql.variables().toArray(new Object[0]));
Iterable<Object> result = (Iterable<Object>) session.queryService.newQuery(oql.oql()).execute(variables);
// lazy transform
return Iterables.transform(result, tupleFn::apply);
}

}
@@ -0,0 +1,105 @@
/*
* Copyright 2019 Immutables Authors and Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.immutables.criteria.geode;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import static org.immutables.check.Checkers.check;

@SuppressWarnings("unchecked")
class BindVariableConverterTest {

private final BindVariableConverter converter = new BindVariableConverter();

@Test
void generic() {
check(converter.apply(null)).isNull();
check(converter.apply("a")).is("a");
check(converter.apply(1)).is(1);
check(converter.apply(true)).is(true);
}

@Test
void list() {
List<?> l0 = (List<?>) converter.apply(ImmutableList.of());
check(l0).isEmpty();
check(l0).isA(ArrayList.class);

List<String> l1 = (List<String>) converter.apply(ImmutableList.of("a"));
check(l1).isOf("a");
check(l1).isA(ArrayList.class);

List<String> l2 = (List<String>) converter.apply(ImmutableList.of("a", "b"));
check(l2).isOf("a", "b");
check(l2).isA(ArrayList.class);

check((List<?>) converter.apply(Collections.emptyList())).isEmpty();

}

@Test
void set() {
check((Set<?>) converter.apply(Collections.emptySet())).isEmpty();

Set<?> s0 = (Set<?>) converter.apply(ImmutableSet.of());
check(s0).isEmpty();
check(s0).isA(HashSet.class);

Set<String> s1 = (Set<String>) converter.apply(ImmutableSet.of("a"));
check(s1).isOf("a");
check(s1).isA(HashSet.class);

Set<String> s2 = (Set<String>) converter.apply(ImmutableSet.of("a", "b"));
check(s2).isOf("a", "b");
check(s2).isA(HashSet.class);
}

@Test
void iterable() {
Iterable<?> i0 = (Iterable<?>) converter.apply(new CustomIterable<>(Collections.emptySet()));
check(i0).isEmpty();
check(i0.getClass().getPackage().getName()).startsWith("java.");

Iterable<String> i1 = (Iterable<String>) converter.apply(new CustomIterable<>(Arrays.asList("a")));
check(i1).isOf("a");
check(i1.getClass().getPackage().getName()).startsWith("java.");
}

private static class CustomIterable<T> implements Iterable<T> {

private final Iterable<T> delegate;

private CustomIterable(Iterable<T> delegate) {
this.delegate = delegate;
}

@Override
public Iterator<T> iterator() {
return delegate.iterator();
}
}
}

0 comments on commit 7cc1fb8

Please sign in to comment.