Skip to content

Commit

Permalink
feat: Introduce @Fulltext annotation for creating fulltext indexes. (
Browse files Browse the repository at this point in the history
…#1183)

Co-authored-by: Michael Simons <michael@simons.ac>
  • Loading branch information
shanon84 and michael-simons committed Jan 2, 2024
1 parent 802be95 commit 09a6454
Show file tree
Hide file tree
Showing 12 changed files with 173 additions and 6 deletions.
2 changes: 1 addition & 1 deletion docs/modules/ROOT/pages/appendix.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ TIP: Our recommended approach is to use `javac` directly and script it's invocat

=== Additional annotations

We offer a set of additional annotations - `@Unique` and `@Required` that can be used standalone or together with SDN6 _or_ OGM to specify constraints on classes. Please check the JavaDoc of those annotations about their usage. The module as shown below has no dependencies, neither on Neo4j-Migrations, nor SDN6 or OGM. While it works excellent with SDN6 for specifying additional information, all annotations offer a way to define labels and relationship types.
We offer a set of additional annotations - `@Unique`, `@Required` and `@Fulltext` that can be used standalone or together with SDN6 _or_ OGM to specify constraints on classes. Please check the JavaDoc of those annotations about their usage. The module as shown below has no dependencies, neither on Neo4j-Migrations, nor SDN6 or OGM. While it works excellent with SDN6 for specifying additional information, all annotations offer a way to define labels and relationship types.

[source,xml,subs="verbatim,attributes"]
.Annotation processor as Maven dependency
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2020-2023 the original author or authors.
*
* 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
*
* https://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 ac.simons.neo4j.migrations.annotations.catalog;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @author shanon84
* @since 2.8.2
*/
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE, ElementType.FIELD})
@Documented
public @interface Fulltext {
/**
* If this is not {@literal null} it has precedence over an implicit label (either no class annotations or one without
* a dedicated label) but not over OGM or SDN6 annotations specifying the label or type explicitly.
* Its use must be consistent throughout the class.
*
* @return The target label
*/
String label() default "";

/**
* Use this if you want to define composite, fulltext index when using {@link Fulltext} on the class level.
* Leave it empty when using on field level, otherwise an exception will be thrown.
*
* @return The list of properties to include in the composite.
*/
String[] properties() default {};

/**
* Use this if you want to use a different fulltext analyzer for your index.
* Be aware to activate the option to allow further options on index creation.
*
* @return name of the fulltext analyzer
*/
String analyzer() default "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ final class Attributes {
static final String PROPERTIES = "properties";
static final String PROPERTY = "property";
static final String UNIQUE = "unique";
static final String ANALYZER = "analyzer";

static Optional<ExecutableElement> get(TypeElement annotation, String name) {
if (annotation == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
FullyQualifiedNames.CATALOG_REQUIRED,
FullyQualifiedNames.CATALOG_UNIQUE,
FullyQualifiedNames.CATALOG_UNIQUE_PROPERTIES,
FullyQualifiedNames.CATALOG_FULLTEXT
})
@SupportedOptions({
CatalogGeneratingProcessor.OPTION_NAME_GENERATOR_CATALOG,
Expand Down Expand Up @@ -326,6 +327,12 @@ private void processCatalogAnnotations(RoundEnvironment roundEnv) {
.addAll(processCatalogAnnotation(enclosingElement, element, catalog.required(), null, existingLabelsAndTypes.computeIfAbsent(enclosingElement, ignore -> new HashSet<>())));
}

for (Element element : roundEnv.getElementsAnnotatedWith(catalog.fulltextIndex())) {
Element enclosingElement = enclosingOrSelf.apply(element);
items.computeIfAbsent(enclosingElement, ignored -> new LinkedHashSet<>())
.addAll(processCatalogAnnotation(enclosingElement, element, catalog.fulltextIndex(), null, existingLabelsAndTypes.computeIfAbsent(enclosingElement, ignore -> new HashSet<>())));
}

items.values().forEach(catalogItems::addAll);
}

Expand Down Expand Up @@ -488,6 +495,18 @@ private CatalogItem<?> processCatalogAnnotation0(
return target == Target.REL ?
Constraint.forRelationship(firstIdentifier).named(name).exists(propertyName) :
Constraint.forNode(firstIdentifier).named(name).exists(propertyName);
} else if (annotationType == catalog.fulltextIndex()) {
String name = this.indexNameGenerator.generateName(Index.Type.FULLTEXT, properties);
Index index = target == Target.REL ?
Index.forRelationship(firstIdentifier).named(name).fulltext(propertyNames.toArray(String[]::new)) :
Index.forNode(firstIdentifier).named(name).fulltext(propertyNames.toArray(String[]::new));
AnnotationValue annotationAnalyzerType =
Attributes.get(annotationType, Attributes.ANALYZER).map(attributes::get).orElse(null);
if (annotationAnalyzerType != null) {
String analyzer = (String) annotationAnalyzerType.getValue();
index = index.withOptions("indexConfig: +{ `fulltext.analyzer`:\"" + analyzer + "\" }");
}
return index;
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
record ElementsCatalog(
TypeElement required,
TypeElement unique,
TypeElement uniqueWrapper) {
TypeElement uniqueWrapper,
TypeElement fulltextIndex) {

static Optional<ElementsCatalog> of(Elements elements) {
TypeElement catalogRequired = elements.getTypeElement(FullyQualifiedNames.CATALOG_REQUIRED);
Expand All @@ -39,7 +40,8 @@ static Optional<ElementsCatalog> of(Elements elements) {
}
TypeElement catalogUnique = elements.getTypeElement(FullyQualifiedNames.CATALOG_UNIQUE);
TypeElement catalogUniqueWrapper = elements.getTypeElement(FullyQualifiedNames.CATALOG_UNIQUE_PROPERTIES);
TypeElement catalogFulltextIndex = elements.getTypeElement(FullyQualifiedNames.CATALOG_FULLTEXT);

return Optional.of(new ElementsCatalog(catalogRequired, catalogUnique, catalogUniqueWrapper));
return Optional.of(new ElementsCatalog(catalogRequired, catalogUnique, catalogUniqueWrapper, catalogFulltextIndex));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ final class FullyQualifiedNames {
static final String CATALOG_REQUIRED = "ac.simons.neo4j.migrations.annotations.catalog.Required";
static final String CATALOG_UNIQUE = "ac.simons.neo4j.migrations.annotations.catalog.Unique";
static final String CATALOG_UNIQUE_PROPERTIES = "ac.simons.neo4j.migrations.annotations.catalog.UniqueProperties";
static final String CATALOG_FULLTEXT = "ac.simons.neo4j.migrations.annotations.catalog.Fulltext";

private FullyQualifiedNames() {
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2020-2023 the original author or authors.
*
* 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
*
* https://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 ac.simons.neo4j.migrations.annotations.proc.catalog.invalid;

import ac.simons.neo4j.migrations.annotations.catalog.Fulltext;

/**
* @author Michael J. Simons
*/
@Fulltext(properties = {"nameA"})
public class ContradictingLabels3 {

@Fulltext(label = "foo")
public String name;

public String nameA;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2020-2023 the original author or authors.
*
* 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
*
* https://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 ac.simons.neo4j.migrations.annotations.proc.catalog.invalid;

import ac.simons.neo4j.migrations.annotations.catalog.Fulltext;

/**
* @author Michael J. Simons
*/
public class FulltextOnFieldWithProperties {

@Fulltext(properties = {"a", "b"})
public String name;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.util.UUID;

import ac.simons.neo4j.migrations.annotations.catalog.Fulltext;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.Property;

Expand All @@ -28,6 +29,7 @@
*/
@NodeEntity("CBOGM")
@Unique(properties = {"a", "b", "c"})
@Fulltext(properties = {"a", "b"})
public class CoffeeBeanOGM {

@Unique
Expand All @@ -36,6 +38,12 @@ public class CoffeeBeanOGM {
@Required
public String name;

@Fulltext
public String text;

@Fulltext(analyzer = "whitespace")
public String textB;

@Required(property = "theName")
public String nameA;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ static Stream<Arguments> shouldPreventInvalidStandaloneAnnotations() {
Arguments.of("ConflictingAliasesOGM", "Different @AliasFor or @ValueFor mirror values for annotation [org.neo4j.ogm.annotation.Property]"),
Arguments.of("ContradictingLabels1", "Contradicting labels found: `foo`, `ContradictingLabels1`"),
Arguments.of("ContradictingLabels2", "Contradicting labels found: `ContradictingLabels2`, `foo`"),
Arguments.of("ContradictingLabels3", "Contradicting labels found: `ContradictingLabels3`, `foo`"),
Arguments.of("ContradictingPropertiesOGM", "Contradicting properties: (bar) vs foo"),
Arguments.of("ContradictingPropertiesSDN", "Contradicting properties: (bar) vs foo"),
Arguments.of("MixingSDNAndOgm", "Mixing SDN and OGM annotations on the same class is not supported"),
Expand All @@ -125,7 +126,8 @@ static Stream<Arguments> shouldPreventInvalidStandaloneAnnotations() {
Arguments.of("UniqueOnRelOGM", "Unique constraints on relationships are not supported"),
Arguments.of("UniqueOnRelSDN", "Unique constraints on relationships are not supported"),
Arguments.of("WrongOverwritingDefaultOGM1", "Overwriting explicit type with a label is not supported"),
Arguments.of("WrongOverwritingDefaultOGM2", "Overwriting explicit label with a type is not supported")
Arguments.of("WrongOverwritingDefaultOGM2", "Overwriting explicit label with a type is not supported"),
Arguments.of("FulltextOnFieldWithProperties", "Please annotate the class and not a field for composite constraints")
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,28 @@
<migration xmlns="https://michael-simons.github.io/neo4j-migrations">
<!-- This file was generated by Neo4j-Migrations at 2022-11-17T21:21:00+01:00. -->
<catalog>
<indexes/>
<indexes>
<index name="ac_simons_neo4j_migrations_annotations_proc_catalog_valid_coffeebeanogm_a_b_fulltext" type="fulltext">
<label>CBOGM</label>
<properties>
<property>a</property>
<property>b</property>
</properties>
</index>
<index name="ac_simons_neo4j_migrations_annotations_proc_catalog_valid_coffeebeanogm_text_fulltext" type="fulltext">
<label>CBOGM</label>
<properties>
<property>text</property>
</properties>
</index>
<index name="ac_simons_neo4j_migrations_annotations_proc_catalog_valid_coffeebeanogm_textB_fulltext" type="fulltext">
<label>CBOGM</label>
<properties>
<property>textB</property>
</properties>
<options>indexConfig: +{ `fulltext.analyzer`:"whitespace" }</options>
</index>
</indexes>
<constraints>
<constraint name="ac_simons_neo4j_migrations_annotations_proc_catalog_valid_coffeebeanmultiple1_unique_unique" type="unique">
<label>CoffeeBeanMultiple1</label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
* autoconfiguration.
*
* @author Michael J. Simons
* @since 2.8.1
* @since 2.8.2
*/
@FunctionalInterface
public interface ConfigBuilderCustomizer {
Expand Down

0 comments on commit 09a6454

Please sign in to comment.