diff --git a/rxrepo-apt/src/main/java/com/slimgears/rxrepo/apt/FiltersExtension.java b/rxrepo-apt/src/main/java/com/slimgears/rxrepo/apt/FiltersExtension.java index 3a854b0c..16ef6ca7 100644 --- a/rxrepo-apt/src/main/java/com/slimgears/rxrepo/apt/FiltersExtension.java +++ b/rxrepo-apt/src/main/java/com/slimgears/rxrepo/apt/FiltersExtension.java @@ -1,6 +1,7 @@ package com.slimgears.rxrepo.apt; import com.google.auto.service.AutoService; +import com.slimgears.apt.data.AnnotationInfo; import com.slimgears.apt.data.TypeInfo; import com.slimgears.util.autovalue.apt.Context; import com.slimgears.util.autovalue.apt.PropertyInfo; @@ -8,6 +9,7 @@ import javax.annotation.processing.SupportedAnnotationTypes; import java.util.Collection; +import java.util.function.Predicate; import java.util.stream.Collectors; @AutoService(Extension.class) @@ -17,13 +19,27 @@ public class FiltersExtension implements Extension { public String generateClassBody(Context context) { Collection filterableProperties = context.properties() .stream() - .filter(p -> p.annotations() - .stream() - .anyMatch(a -> a.type().equals(TypeInfo.of("com.slimgears.rxrepo.annotations.Filterable")))) + .filter(hasAnnotationOfType("com.slimgears.rxrepo.annotations.Filterable")) .collect(Collectors.toList()); - return context.evaluatorForResource("filter-body.java.vm") + boolean isSearchable = context.properties() + .stream() + .anyMatch(hasAnnotationOfType("com.slimgears.rxrepo.annotations.Searchable")); + + return (!filterableProperties.isEmpty() || isSearchable) + ? context.evaluatorForResource("filter-body.java.vm") .variable("filterableProperties", filterableProperties) - .evaluate(); + .variable("isSearchable", isSearchable) + .evaluate() + : ""; + } + + private Predicate isAnnotationOfType(String typeName) { + TypeInfo typeInfo = TypeInfo.of(typeName); + return annotationInfo -> annotationInfo.type().equals(typeInfo); + } + + private Predicate hasAnnotationOfType(String typeName) { + return propertyInfo -> propertyInfo.annotations().stream().anyMatch(isAnnotationOfType(typeName)); } } diff --git a/rxrepo-apt/src/main/resources/expressions-body.java.vm b/rxrepo-apt/src/main/resources/expressions-body.java.vm index 82c17faa..aeaea1ba 100644 --- a/rxrepo-apt/src/main/resources/expressions-body.java.vm +++ b/rxrepo-apt/src/main/resources/expressions-body.java.vm @@ -13,7 +13,7 @@ #end } - public static class Expressions<__S#foreach ($tp in $sourceClass.typeParams()), $tp.fullDeclaration()#end> { + public static class Expressions<__S#foreach ($tp in $sourceClass.typeParams()), $tp.fullDeclaration()#end> implements $[com.slimgears.rxrepo.expressions.ObjectExpression]<__S, $targetClass.simpleName()#typeParams($sourceClass)> { private final $[com.slimgears.rxrepo.expressions.ObjectExpression]<__S, $targetClass.simpleName()#typeParams($sourceClass)> self; private final Meta#typeParams($sourceClass) meta = #if ($sourceClass.typeParams().isEmpty())new Meta()#{else}new Meta<>()#end; @@ -45,6 +45,16 @@ #end } + @Override + public Type type() { + return this.self.type(); + } + + @Override + public $[com.slimgears.util.reflect.TypeToken] objectType() { + return this.self.objectType(); + } + static #typeParamsDeclaration($sourceClass) Expressions<$targetClass.simpleName()#typeParams($sourceClass)#foreach ($tp in $sourceClass.typeParams()), $tp.typeName()#end> create() { return new Expressions<>(ObjectExpression.arg(new $[com.slimgears.util.reflect.TypeToken]<$targetClass.simpleName()#typeParams($sourceClass)>(){})); } diff --git a/rxrepo-apt/src/main/resources/filter-body.java.vm b/rxrepo-apt/src/main/resources/filter-body.java.vm index 43df8f9e..bd987c11 100644 --- a/rxrepo-apt/src/main/resources/filter-body.java.vm +++ b/rxrepo-apt/src/main/resources/filter-body.java.vm @@ -15,21 +15,33 @@ #end## @$[com.google.auto.value.AutoValue] - public static abstract class Filter#typeParamsDeclaration($sourceClass) implements com.slimgears.rxrepo.filters.Filter<${targetClass.simpleName()}#typeParams($sourceClass)>, $[com.slimgears.rxrepo.filters.TextFilter] { + public static abstract class Filter#typeParamsDeclaration($sourceClass) implements com.slimgears.rxrepo.filters.Filter<${targetClass.simpleName()}#typeParams($sourceClass)>## +#if ($isSearchable)## +, $[com.slimgears.rxrepo.filters.SearchableFilter]## +#end { #foreach ($p in $filterableProperties) @$[javax.annotation.Nullable] public abstract #filterType($p) ${p.name()}(); #end +#if ($isSearchable) @Override @$[javax.annotation.Nullable] public abstract String searchText(); - +#end @Override public <__S> $[java.util.Optional]<$[com.slimgears.rxrepo.expressions.BooleanExpression]<__S>> toExpression($[com.slimgears.rxrepo.expressions.ObjectExpression]<__S, $targetClass.simpleName()#typeParams($sourceClass)> arg) { Expressions<__S#foreach ($tp in $sourceClass.typeParams()), $tp.name()#end> self = new Expressions<>(arg); - return $[com.slimgears.rxrepo.filters.Filters].combineExpressions( + return $[com.slimgears.rxrepo.filters.Filters].combineExpressions(## +#if ($isSearchable) + $[com.slimgears.rxrepo.filters.Filters].fromTextFilter(this, arg)## #foreach ($p in $filterableProperties)## , $[java.util.Optional].ofNullable(${p.name()}()).flatMap(f -> f.toExpression(self.${p.name()})) #end## +#else + #foreach ($p in $filterableProperties)## + + $[java.util.Optional].ofNullable(${p.name()}()).flatMap(f -> f.toExpression(self.${p.name()}))#if ($foreach.hasNext),#end + #end## +#end ); } @@ -59,7 +71,9 @@ #foreach ($p in $filterableProperties) Builder#typeParams($sourceClass) ${p.name()}(#filterType($p) ${p.name()}); #end +#if ($isSearchable) Builder#typeParams($sourceClass) searchText(String searchText); +#end Filter#typeParams($sourceClass) build(); } } diff --git a/rxrepo-apt/src/test/java/com/slimgears/rxrepo/queries/TestEntityPrototype.java b/rxrepo-apt/src/test/java/com/slimgears/rxrepo/queries/TestEntityPrototype.java index 4f8fdc1d..29a23b11 100644 --- a/rxrepo-apt/src/test/java/com/slimgears/rxrepo/queries/TestEntityPrototype.java +++ b/rxrepo-apt/src/test/java/com/slimgears/rxrepo/queries/TestEntityPrototype.java @@ -2,6 +2,7 @@ import com.slimgears.rxrepo.annotations.Filterable; import com.slimgears.rxrepo.annotations.Indexable; +import com.slimgears.rxrepo.annotations.Searchable; import com.slimgears.rxrepo.annotations.UseFilters; import com.slimgears.util.autovalue.annotations.AutoValuePrototype; import com.slimgears.util.autovalue.annotations.Key; @@ -14,8 +15,8 @@ @UseFilters @UseCopyAnnotator public interface TestEntityPrototype { - @Key TestKey key(); - @Indexable @Filterable String text(); + @Key @Searchable TestKey key(); + @Indexable @Filterable @Searchable String text(); @Indexable @Filterable int number(); @Reference @Filterable TestRefEntity refEntity(); Collection refEntities(); diff --git a/rxrepo-core/src/main/java/com/slimgears/rxrepo/annotations/Indexable.java b/rxrepo-core/src/main/java/com/slimgears/rxrepo/annotations/Indexable.java index 7bde0d5f..20fc861a 100644 --- a/rxrepo-core/src/main/java/com/slimgears/rxrepo/annotations/Indexable.java +++ b/rxrepo-core/src/main/java/com/slimgears/rxrepo/annotations/Indexable.java @@ -8,4 +8,5 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Indexable { + boolean unique() default false; } diff --git a/rxrepo-core/src/main/java/com/slimgears/rxrepo/expressions/ExpressionVisitor.java b/rxrepo-core/src/main/java/com/slimgears/rxrepo/expressions/ExpressionVisitor.java index ecda88bd..db32fbba 100644 --- a/rxrepo-core/src/main/java/com/slimgears/rxrepo/expressions/ExpressionVisitor.java +++ b/rxrepo-core/src/main/java/com/slimgears/rxrepo/expressions/ExpressionVisitor.java @@ -22,15 +22,15 @@ public _R visit(Expression expression, _T arg) { } } - protected abstract _R reduceBinary(Expression.Type type, _R first, _R second); - protected abstract _R reduceUnary(Expression.Type type, _R first); + protected abstract _R reduceBinary(ObjectExpression expression, Expression.Type type, _R first, _R second); + protected abstract _R reduceUnary(ObjectExpression expression, Expression.Type type, _R first); protected abstract _R visitOther(ObjectExpression expression, _T arg); protected _R visitComposition(ComposedExpression expression, _T arg) { _R resSrc = this.visit(expression.source(), arg); _R resExp = this.visit(expression.expression(), arg); - return reduceBinary(expression.type(), resSrc, resExp); + return reduceBinary(expression, expression.type(), resSrc, resExp); } protected _R visitConstant(ConstantExpression constantExpression, _T arg) { @@ -38,15 +38,15 @@ protected _R visitConstant(ConstantExpression constantExpression, _ } protected _R visitProperty(PropertyExpression expression, _T arg) { - return reduceBinary(expression.type(), visit(expression.target(), arg), visitProperty(expression.property(), arg)); + return reduceBinary(expression, expression.type(), visit(expression.target(), arg), visitProperty(expression.property(), arg)); } protected _R visitBinaryOperator(BinaryOperationExpression expression, _T arg) { - return reduceBinary(expression.type(), visit(expression.left(), arg), visit(expression.right(), arg)); + return reduceBinary(expression, expression.type(), visit(expression.left(), arg), visit(expression.right(), arg)); } protected _R visitUnaryOperator(UnaryOperationExpression expression, _T arg) { - return reduceUnary(expression.type(), visit(expression.operand(), arg)); + return reduceUnary(expression, expression.type(), visit(expression.operand(), arg)); } protected _R visitArgument(ArgumentExpression expression, _T arg) { diff --git a/rxrepo-core/src/main/java/com/slimgears/rxrepo/expressions/ObjectExpression.java b/rxrepo-core/src/main/java/com/slimgears/rxrepo/expressions/ObjectExpression.java index 486e97b2..31774034 100644 --- a/rxrepo-core/src/main/java/com/slimgears/rxrepo/expressions/ObjectExpression.java +++ b/rxrepo-core/src/main/java/com/slimgears/rxrepo/expressions/ObjectExpression.java @@ -115,12 +115,8 @@ default CollectionExpression ref(CollectionPropertyExpression return PropertyExpression.ofCollection(this, expression.property()); } - default BooleanExpression searchText(ObjectExpression pattern) { - return BooleanBinaryOperationExpression.create(Type.SearchText, this, pattern); - } - default BooleanExpression searchText(String pattern) { - return searchText(ConstantExpression.of(pattern)); + return BooleanBinaryOperationExpression.create(Type.SearchText, this, ConstantExpression.of(pattern)); } static ObjectExpression arg(TypeToken type) { diff --git a/rxrepo-core/src/main/java/com/slimgears/rxrepo/filters/Filters.java b/rxrepo-core/src/main/java/com/slimgears/rxrepo/filters/Filters.java index 22969817..cce937cf 100644 --- a/rxrepo-core/src/main/java/com/slimgears/rxrepo/filters/Filters.java +++ b/rxrepo-core/src/main/java/com/slimgears/rxrepo/filters/Filters.java @@ -7,7 +7,7 @@ import java.util.Optional; public class Filters { - public static Optional> fromTextFilter(TextFilter filter, ObjectExpression arg) { + public static Optional> fromTextFilter(SearchableFilter filter, ObjectExpression arg) { return Optional.ofNullable(filter.searchText()).map(arg::searchText); } diff --git a/rxrepo-core/src/main/java/com/slimgears/rxrepo/filters/TextFilter.java b/rxrepo-core/src/main/java/com/slimgears/rxrepo/filters/SearchableFilter.java similarity index 75% rename from rxrepo-core/src/main/java/com/slimgears/rxrepo/filters/TextFilter.java rename to rxrepo-core/src/main/java/com/slimgears/rxrepo/filters/SearchableFilter.java index 66c7a0ed..8388aa24 100644 --- a/rxrepo-core/src/main/java/com/slimgears/rxrepo/filters/TextFilter.java +++ b/rxrepo-core/src/main/java/com/slimgears/rxrepo/filters/SearchableFilter.java @@ -2,6 +2,6 @@ import javax.annotation.Nullable; -public interface TextFilter { +public interface SearchableFilter { @Nullable String searchText(); } diff --git a/rxrepo-core/src/main/java/com/slimgears/rxrepo/util/ExpressionTextGenerator.java b/rxrepo-core/src/main/java/com/slimgears/rxrepo/util/ExpressionTextGenerator.java index a1c1661d..8dd79771 100644 --- a/rxrepo-core/src/main/java/com/slimgears/rxrepo/util/ExpressionTextGenerator.java +++ b/rxrepo-core/src/main/java/com/slimgears/rxrepo/util/ExpressionTextGenerator.java @@ -17,6 +17,7 @@ import java.util.concurrent.Callable; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import static com.slimgears.util.stream.Optionals.ofType; @@ -31,23 +32,27 @@ private static T[] requireArgs(T[] args, int count) { return args; } + public interface GenericInterceptor { + String onVisit(Function, String> visitor, E expression, Supplier visitSupplier); + } + public interface Interceptor { - String onVisit(ObjectExpression exp, String visitedResult); + String onVisit(Function, String> visitor, ObjectExpression exp, Supplier visitedResult); default Interceptor combineWith(Interceptor other) { - return (exp, visitedResult) -> other.onVisit(exp, this.onVisit(exp, visitedResult)); + return (visitor, exp, visitedResult) -> other.onVisit(visitor, exp, () -> this.onVisit(visitor, exp, visitedResult)); } static Interceptor empty() { - return (exp, visitedResult) -> visitedResult; + return (visitor, exp, visitedResult) -> visitedResult.get(); } - static > Interceptor ofType(Class expressionType, BiFunction interceptor) { - return (exp, visitedResult) -> Optional + static Interceptor ofType(Class expressionType, GenericInterceptor interceptor) { + return (visitor, exp, visitedResult) -> Optional .of(exp) .flatMap(Optionals.ofType(expressionType)) - .map(e -> interceptor.apply(e, visitedResult)) - .orElse(visitedResult); + .map(e -> interceptor.onVisit(visitor, e, visitedResult)) + .orElseGet(visitedResult); } static InterceptorBuilder builder() { @@ -80,43 +85,43 @@ public Interceptor build() { Map byOpType = interceptorsByOpType.build(); Map byOpValueType = interceptorsByValueType.build(); - return (exp, visitedResult) -> Optionals.or( + return (visitor, exp, visitedResult) -> Optionals.or( () -> Optional.ofNullable(byType.get(exp.type())), () -> Optional.ofNullable(byOpType.get(exp.type().operationType())), () -> Optional.ofNullable(byOpValueType.get(exp.type().valueType()))) .orElseGet(Interceptor::empty) - .onVisit(exp, visitedResult); + .onVisit(visitor, exp, visitedResult); } } public interface Reducer { - String reduce(String... parts); + String reduce(ObjectExpression expression, String... parts); static Reducer fromFormat(String format) { - return args -> String.format(format, (Object[])args); + return (exp, args) -> String.format(format, (Object[])args); } static Reducer fromBinary(BiFunction reducer) { - return args -> reducer.apply(requireArgs(args, 2)[0], args[1]); + return (exp, args) -> reducer.apply(requireArgs(args, 2)[0], args[1]); } static Reducer fromUnary(Function reducer) { - return args -> reducer.apply(requireArgs(args, 1)[0]); + return (exp, args) -> reducer.apply(requireArgs(args, 1)[0]); } static Reducer just(String str) { - return args -> str; + return (exp, args) -> str; } static Reducer join(String delimiter) { - return args -> Arrays + return (exp, args) -> Arrays .stream(args) .filter(a -> !a.isEmpty()) .collect(Collectors.joining(delimiter)); } default Reducer andThen(Function postProcess) { - return args -> postProcess.apply(this.reduce(args)); + return (exp, args) -> postProcess.apply(this.reduce(exp, args)); } } @@ -188,10 +193,14 @@ public String generate(ObjectExpression expression, ObjectExpressio } private String generate(ObjectExpression expression, String arg) { - Visitor visitor = new Visitor(); + Visitor visitor = createVisitor(); return visitor.visit(expression, arg); } + protected Visitor createVisitor() { + return new Visitor(); + } + private Reducer toReducer(Expression.Type type) { return Optionals.or( () -> reducerFromExpressionType(type), @@ -212,29 +221,29 @@ private Optional reducerFromOperationType(Expression.OperationType type return Optional.ofNullable(opTypeToReducer.get(type)); } - class Visitor extends ExpressionVisitor { + protected class Visitor extends ExpressionVisitor { @Override public String visit(Expression expression, String arg) { - String visitedStr = super.visit(expression, arg); + Supplier visited = () -> super.visit(expression, arg); return Optional.of(expression) .flatMap(ofType(ObjectExpression.class)) - .map(objExp -> scopedInterceptor.current().onVisit(objExp, visitedStr)) - .orElse(visitedStr); + .map(objExp -> scopedInterceptor.current().onVisit(exp -> visit(exp, arg), objExp, visited)) + .orElseGet(visited); } @Override - protected String reduceBinary(Expression.Type type, String first, String second) { - return toReducer(type).reduce(first, second); + protected String reduceBinary(ObjectExpression expression, Expression.Type type, String first, String second) { + return toReducer(type).reduce(expression, first, second); } @Override - protected String reduceUnary(Expression.Type type, String first) { - return toReducer(type).reduce(first); + protected String reduceUnary(ObjectExpression expression, Expression.Type type, String first) { + return toReducer(type).reduce(expression, first); } @Override protected String visitOther(ObjectExpression expression, String context) { - return toReducer(expression.type()).reduce(); + return toReducer(expression.type()).reduce(expression); } @Override @@ -254,14 +263,14 @@ protected String visitArgument(TypeToken argType, String context) { @Override protected String visitConstant(Expression.Type type, V value, String context) { - if (value instanceof String) { - return reduceUnary(Expression.Type.StringConstant, (String)value); - } else if (value == null) { - return reduceUnary(Expression.Type.NullConstant, null); + if (value == null) { + return reduceUnary(null, Expression.Type.NullConstant, null); + } else if (value instanceof String) { + return reduceUnary(null, Expression.Type.StringConstant, (String)value); } else if (value instanceof Number) { - return reduceUnary(Expression.Type.NumericConstant, String.valueOf(value)); + return reduceUnary(null, Expression.Type.NumericConstant, String.valueOf(value)); } else { - return reduceUnary(type, String.valueOf(value)); + return reduceUnary(null, type, String.valueOf(value)); } } } diff --git a/rxrepo-core/src/main/java/com/slimgears/rxrepo/util/Expressions.java b/rxrepo-core/src/main/java/com/slimgears/rxrepo/util/Expressions.java index ca86c315..02ef7df4 100644 --- a/rxrepo-core/src/main/java/com/slimgears/rxrepo/util/Expressions.java +++ b/rxrepo-core/src/main/java/com/slimgears/rxrepo/util/Expressions.java @@ -33,18 +33,22 @@ public static io.reactivex.functions.Function compileRx(ObjectExpre return compile(exp)::apply; } + @SuppressWarnings("unchecked") private static Function fromUnary(Function func) { return funcs -> val -> func.apply((T)funcs[0].apply(val)); } + @SuppressWarnings("unchecked") private static Function fromNumericUnary(Function func) { return funcs -> val -> func.apply((T)funcs[0].apply(val)); } + @SuppressWarnings("unchecked") private static Function fromBinary(BiFunction func) { return funcs -> val -> func.apply((T1)funcs[0].apply(val), (T2)funcs[1].apply(val)); } + @SuppressWarnings("unchecked") private static Function fromNumericBinary(BiFunction func) { return funcs -> val -> func.apply((T)funcs[0].apply(val), (T)funcs[1].apply(val)); } @@ -63,6 +67,7 @@ private static Function notSupported() { } private static class InternalVisitor extends ExpressionVisitor { + @SuppressWarnings("unchecked") private final static ImmutableMap> expressionTypeReducersMap = ImmutableMap.>builder() .put(Expression.Type.AsString, fromUnary(Object::toString)) .put(Expression.Type.Add, fromNumericBinary(GenericMath::add)) @@ -109,12 +114,12 @@ private static Function reduce(Expression.Type type, Function... functions) { } @Override - protected Function reduceBinary(Expression.Type type, Function first, Function second) { + protected Function reduceBinary(ObjectExpression expression, Expression.Type type, Function first, Function second) { return reduce(type, first, second); } @Override - protected Function reduceUnary(Expression.Type type, Function first) { + protected Function reduceUnary(ObjectExpression expression, Expression.Type type, Function first) { return reduce(type, first); } diff --git a/rxrepo-orientdb/src/main/java/com/slimgears/rxrepo/orientdb/OrientDbSchemaProvider.java b/rxrepo-orientdb/src/main/java/com/slimgears/rxrepo/orientdb/OrientDbSchemaProvider.java index 9f1bd124..2c418322 100644 --- a/rxrepo-orientdb/src/main/java/com/slimgears/rxrepo/orientdb/OrientDbSchemaProvider.java +++ b/rxrepo-orientdb/src/main/java/com/slimgears/rxrepo/orientdb/OrientDbSchemaProvider.java @@ -4,9 +4,12 @@ import com.orientechnologies.orient.core.metadata.schema.OClass; import com.orientechnologies.orient.core.metadata.schema.OProperty; import com.orientechnologies.orient.core.metadata.schema.OType; +import com.slimgears.rxrepo.annotations.Indexable; +import com.slimgears.rxrepo.annotations.Searchable; import com.slimgears.rxrepo.sql.SchemaProvider; import com.slimgears.util.autovalue.annotations.HasMetaClass; import com.slimgears.util.autovalue.annotations.HasMetaClassWithKey; +import com.slimgears.util.autovalue.annotations.Key; import com.slimgears.util.autovalue.annotations.MetaClass; import com.slimgears.util.autovalue.annotations.MetaClassWithKey; import com.slimgears.util.autovalue.annotations.MetaClasses; @@ -43,6 +46,7 @@ private OClass ensureClass(MetaClass metaClass) { return classMap.computeIfAbsent(toClassName(metaClass.objectClass()), name -> createClass(metaClass)); } + @SuppressWarnings("unchecked") private OClass createClass(MetaClass metaClass) { String className = toClassName(metaClass.objectClass()); OClass oClass = dbSession.get().createClassIfNotExist(className); @@ -51,17 +55,12 @@ private OClass createClass(MetaClass metaClass) { if (metaClass instanceof MetaClassWithKey) { MetaClassWithKey metaClassWithKey = (MetaClassWithKey) metaClass; - PropertyMeta keyProperty = metaClassWithKey.keyProperty(); - if (!oClass.areIndexed(keyProperty.name())) { - oClass.createIndex(className + "." + keyProperty.name() + "Index", OClass.INDEX_TYPE.UNIQUE, keyProperty.name()); - } + addIndex(oClass, metaClassWithKey.keyProperty(), true); } -// String[] textFields = Streams -// .fromIterable(metaClass.properties()) -// .filter(p -> p.type().asClass() == String.class) -// .map(PropertyMeta::name) -// .toArray(String[]::new); + Streams.fromIterable(metaClass.properties()) + .filter(p -> p.hasAnnotation(Indexable.class) && !p.hasAnnotation(Key.class)) + .forEach(p -> addIndex(oClass, p, p.getAnnotation(Indexable.class).unique())); Streams.fromIterable(metaClass.properties()) .filter(p -> p.type().is(HasMetaClassWithKey.class::isAssignableFrom)) @@ -69,13 +68,27 @@ private OClass createClass(MetaClass metaClass) { .map(OrientDbSchemaProvider::toMetaClass) .forEach(this::ensureClass); -// if (textFields.length > 0) { -// oClass.createIndex(className + ".textIndex", "FULLTEXT", null, null, "LUCENE", textFields); -// } + String[] textFields = Streams + .fromIterable(metaClass.properties()) + .filter(p -> p.hasAnnotation(Searchable.class)) + .map(PropertyMeta::name) + .toArray(String[]::new); + + if (textFields.length > 0) { + oClass.createIndex(className + ".textIndex", "FULLTEXT", null, null, "LUCENE", textFields); + } return oClass; } + private static void addIndex(OClass oClass, PropertyMeta propertyMeta, boolean unique) { + String className = toClassName(propertyMeta.declaringType().objectClass()); + OClass.INDEX_TYPE indexType = unique ? OClass.INDEX_TYPE.UNIQUE : OClass.INDEX_TYPE.NOTUNIQUE; + if (!oClass.areIndexed(propertyMeta.name())) { + oClass.createIndex(className + "." + propertyMeta.name() + "Index", indexType, propertyMeta.name()); + } + } + private static > MetaClass toMetaClass(TypeToken typeToken) { //noinspection unchecked return MetaClasses.forToken((TypeToken)typeToken); @@ -117,11 +130,11 @@ static OType toOType(TypeToken token) { : OType.ANY); } - private static String toClassName(TypeToken cls) { + static String toClassName(TypeToken cls) { return toClassName(cls.asClass()); } - private static String toClassName(Class cls) { + static String toClassName(Class cls) { return cls.getSimpleName(); } diff --git a/rxrepo-orientdb/src/main/java/com/slimgears/rxrepo/orientdb/OrientDbSqlExpressionGenerator.java b/rxrepo-orientdb/src/main/java/com/slimgears/rxrepo/orientdb/OrientDbSqlExpressionGenerator.java index a6760d98..d23b87f3 100644 --- a/rxrepo-orientdb/src/main/java/com/slimgears/rxrepo/orientdb/OrientDbSqlExpressionGenerator.java +++ b/rxrepo-orientdb/src/main/java/com/slimgears/rxrepo/orientdb/OrientDbSqlExpressionGenerator.java @@ -1,13 +1,46 @@ package com.slimgears.rxrepo.orientdb; +import com.slimgears.rxrepo.expressions.ConstantExpression; import com.slimgears.rxrepo.expressions.Expression; +import com.slimgears.rxrepo.expressions.ObjectExpression; +import com.slimgears.rxrepo.expressions.internal.BooleanBinaryOperationExpression; import com.slimgears.rxrepo.sql.DefaultSqlExpressionGenerator; import com.slimgears.rxrepo.util.ExpressionTextGenerator; +import com.slimgears.util.reflect.TypeToken; + +import java.util.Arrays; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; public class OrientDbSqlExpressionGenerator extends DefaultSqlExpressionGenerator { + private final ExpressionTextGenerator.Interceptor searchTextInterceptor = ExpressionTextGenerator.Interceptor.builder() + .intercept(Expression.Type.SearchText, ExpressionTextGenerator.Interceptor.ofType(BooleanBinaryOperationExpression.class, this::onVisitSearchTextExpression)) + .build(); + @Override protected ExpressionTextGenerator.Builder createBuilder() { return super.createBuilder() .add(Expression.Type.AsString, "%s.asString()"); } + + @Override + protected ExpressionTextGenerator.Interceptor createInterceptor() { + return searchTextInterceptor; + } + + @SuppressWarnings("unchecked") + private String onVisitSearchTextExpression(Function, String> visitor, BooleanBinaryOperationExpression expression, Supplier visitedExpression) { + TypeToken argType = expression.left().objectType(); + String searchText = ((ConstantExpression)expression.right()).value(); + String wildcard = searchTextToWildcard(searchText); + return String.format("(search_index('" + OrientDbSchemaProvider.toClassName(argType) + ".textIndex', %s) = true)", visitor.apply(ConstantExpression.of(wildcard))); + } + + private String searchTextToWildcard(String searchText) { + return Arrays + .stream(searchText.split("\\s")) + .map(t -> "+" + t) + .collect(Collectors.joining(" ")); + } } diff --git a/rxrepo-orientdb/src/test/java/com/slimgears/rxrepo/orientdb/InventoryPrototype.java b/rxrepo-orientdb/src/test/java/com/slimgears/rxrepo/orientdb/InventoryPrototype.java index c646c6b8..97202c36 100644 --- a/rxrepo-orientdb/src/test/java/com/slimgears/rxrepo/orientdb/InventoryPrototype.java +++ b/rxrepo-orientdb/src/test/java/com/slimgears/rxrepo/orientdb/InventoryPrototype.java @@ -1,5 +1,7 @@ package com.slimgears.rxrepo.orientdb; +import com.slimgears.rxrepo.annotations.Filterable; +import com.slimgears.rxrepo.annotations.Searchable; import com.slimgears.util.autovalue.annotations.AutoValuePrototype; import com.slimgears.util.autovalue.annotations.Key; import com.slimgears.rxrepo.annotations.UseExpressions; @@ -9,6 +11,6 @@ @AutoValuePrototype @UseExpressions public interface InventoryPrototype { - @Key int id(); - @Nullable String name(); + @Key @Searchable @Filterable int id(); + @Nullable @Searchable @Filterable String name(); } diff --git a/rxrepo-orientdb/src/test/java/com/slimgears/rxrepo/orientdb/OrientDbQueryProviderTest.java b/rxrepo-orientdb/src/test/java/com/slimgears/rxrepo/orientdb/OrientDbQueryProviderTest.java index 8ada7c05..260bf368 100644 --- a/rxrepo-orientdb/src/test/java/com/slimgears/rxrepo/orientdb/OrientDbQueryProviderTest.java +++ b/rxrepo-orientdb/src/test/java/com/slimgears/rxrepo/orientdb/OrientDbQueryProviderTest.java @@ -206,6 +206,32 @@ public void testInsertThenRetrieve() throws InterruptedException { .assertValueCount(1); } + @Test + @UseLogLevel(UseLogLevel.Level.FINEST) + public void testInsertThenSearch() throws InterruptedException { + EntitySet productSet = repository.entities(Product.metaClass); + Iterable products = createProducts(100); + Stopwatch stopwatch = Stopwatch.createUnstarted(); + productSet + .update(products) + .doOnSubscribe(d -> stopwatch.start()) + .doFinally(stopwatch::stop) + .test() + .await() + .assertNoErrors(); + + //noinspection unchecked + productSet + .query() + .where(Product.$.searchText("Product 31")) + .select() + .retrieve(Product.$.id, Product.$.name, Product.$.price, Product.$.inventory.id, Product.$.inventory.name) + .test() + .await() + .assertNoErrors() + .assertValueCount(1); + } + @Test public void testInsertThenUpdate() throws InterruptedException { EntitySet productSet = repository.entities(Product.metaClass); diff --git a/rxrepo-orientdb/src/test/java/com/slimgears/rxrepo/orientdb/ProductPrototype.java b/rxrepo-orientdb/src/test/java/com/slimgears/rxrepo/orientdb/ProductPrototype.java index d2c23111..e3f0a4b4 100644 --- a/rxrepo-orientdb/src/test/java/com/slimgears/rxrepo/orientdb/ProductPrototype.java +++ b/rxrepo-orientdb/src/test/java/com/slimgears/rxrepo/orientdb/ProductPrototype.java @@ -1,5 +1,7 @@ package com.slimgears.rxrepo.orientdb; +import com.slimgears.rxrepo.annotations.Filterable; +import com.slimgears.rxrepo.annotations.Searchable; import com.slimgears.util.autovalue.annotations.AutoValuePrototype; import com.slimgears.util.autovalue.annotations.Key; import com.slimgears.util.autovalue.annotations.Reference; @@ -12,8 +14,8 @@ @UseExpressions @UseCopyAnnotator public interface ProductPrototype { - @Key int id(); - @Nullable String name(); - @Reference @Nullable Inventory inventory(); - int price(); + @Key @Searchable int id(); + @Nullable @Filterable @Searchable String name(); + @Reference @Filterable @Nullable Inventory inventory(); + @Searchable int price(); } diff --git a/rxrepo-sql-core/src/main/java/com/slimgears/rxrepo/sql/DefaultSqlExpressionGenerator.java b/rxrepo-sql-core/src/main/java/com/slimgears/rxrepo/sql/DefaultSqlExpressionGenerator.java index d3b7ce62..4bb63d8f 100644 --- a/rxrepo-sql-core/src/main/java/com/slimgears/rxrepo/sql/DefaultSqlExpressionGenerator.java +++ b/rxrepo-sql-core/src/main/java/com/slimgears/rxrepo/sql/DefaultSqlExpressionGenerator.java @@ -8,6 +8,8 @@ import java.util.List; import java.util.concurrent.Callable; +import java.util.function.Function; +import java.util.function.Supplier; public class DefaultSqlExpressionGenerator implements SqlExpressionGenerator { private final Lazy sqlGenerator; @@ -60,7 +62,7 @@ protected ExpressionTextGenerator.Builder createBuilder() { } public T withParams(List params, Callable callable) { - return sqlGenerator.get().withInterceptor(paramsInterceptor(params), callable); + return sqlGenerator.get().withInterceptor(createInterceptor().combineWith(paramsInterceptor(params)), callable); } public String toSqlExpression(ObjectExpression expression) { @@ -80,18 +82,22 @@ public String toSqlExpression(ObjectExpression expression, ObjectEx } protected static ExpressionTextGenerator.Reducer notSupported() { - return str -> { + return (exp, str) -> { throw new IllegalArgumentException("Not supported expression"); }; } + protected ExpressionTextGenerator.Interceptor createInterceptor() { + return ExpressionTextGenerator.Interceptor.empty(); + } + private static ExpressionTextGenerator.Reducer formatAndFixQuotes(String format) { return ExpressionTextGenerator.Reducer.fromFormat(format).andThen(str -> str.replaceAll("' \\+ '", "")); } private static ExpressionTextGenerator.Interceptor paramsInterceptor(List params) { - return ExpressionTextGenerator.Interceptor.ofType(ConstantExpression.class, (exp, visited) -> { - params.add(exp.value()); + return ExpressionTextGenerator.Interceptor.ofType(ConstantExpression.class, (visitor, expression, visitSupplier) -> { + params.add(expression.value()); return "?"; }); }