Repository: neo4j-contrib/cypher-dsl Branch: main Commit: ef1860afa69b Files: 793 Total size: 2.9 MB Directory structure: gitextract_i8wbytku/ ├── .git-blame-ignore-revs ├── .github/ │ ├── CODEOWNERS │ ├── ISSUE_TEMPLATE/ │ │ └── config.yml │ ├── dependabot.yml │ └── workflows/ │ ├── build.yml │ ├── publish_docs.yml │ └── release.yml ├── .gitignore ├── .mvn/ │ ├── extensions.xml │ ├── jvm.config │ └── wrapper/ │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.adoc ├── LICENSE.txt ├── README.adoc ├── bin/ │ ├── extract-version.sh │ ├── prepare-release.sh │ ├── remove-shaded-modules.sh │ └── runtests.java ├── docs/ │ ├── appendix/ │ │ ├── 2020.0.adoc │ │ ├── 2020.1.adoc │ │ ├── 2021.0.adoc │ │ ├── 2021.1.adoc │ │ ├── 2021.2.adoc │ │ ├── 2021.3.adoc │ │ ├── 2021.4.adoc │ │ ├── 2022.0.adoc │ │ ├── 2022.1.adoc │ │ ├── 2022.10.adoc │ │ ├── 2022.11.adoc │ │ ├── 2022.2.adoc │ │ ├── 2022.3.adoc │ │ ├── 2022.4.adoc │ │ ├── 2022.5.adoc │ │ ├── 2022.6.adoc │ │ ├── 2022.7.adoc │ │ ├── 2022.8.adoc │ │ ├── 2022.9.adoc │ │ ├── 2023.0.adoc │ │ ├── 2023.1.adoc │ │ ├── 2023.2.adoc │ │ ├── 2023.3.adoc │ │ ├── 2023.4.adoc │ │ ├── 2023.5.adoc │ │ ├── 2023.6.adoc │ │ ├── 2023.7.adoc │ │ ├── 2023.8.adoc │ │ ├── 2023.9.adoc │ │ ├── 2024.0.adoc │ │ ├── 2024.1.adoc │ │ ├── 2024.2.adoc │ │ ├── 2024.3.adoc │ │ ├── 2024.4.adoc │ │ ├── 2024.5.adoc │ │ ├── 2024.6.adoc │ │ ├── 2024.7.adoc │ │ ├── 2025.0.adoc │ │ ├── 2025.1.adoc │ │ ├── 2025.2.adoc │ │ ├── building.adoc │ │ ├── changes.adoc │ │ ├── index.adoc │ │ └── query-dsl-support.adoc │ ├── cypher-parser/ │ │ ├── cypher-parser.adoc │ │ └── index.adoc │ ├── functions/ │ │ ├── arbitrary-procedures-and-functions.adoc │ │ ├── index.adoc │ │ ├── lists.adoc │ │ └── mathematical.adoc │ ├── getting-started/ │ │ ├── getting-started.adoc │ │ └── index.adoc │ ├── index.adoc │ ├── introduction-and-preface/ │ │ ├── index.adoc │ │ └── introduction.adoc │ ├── properties/ │ │ └── index.adoc │ └── static-meta-model/ │ ├── concepts.adoc │ ├── index.adoc │ ├── ogm-annotation-processor.adoc │ ├── possible-usage.adoc │ └── sdn6-annotation-processor.adoc ├── etc/ │ ├── architecture/ │ │ ├── api.adoc │ │ ├── index.adoc │ │ ├── naming.adoc │ │ └── structure.adoc │ ├── changelog.tpl │ ├── checkstyle/ │ │ ├── config.xml │ │ └── suppressions.xml │ ├── index.tpl │ ├── license.tpl │ └── recipes/ │ └── rewrite.yml ├── mvnw ├── mvnw.cmd ├── neo4j-cypher-dsl/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ ├── module-info.java │ │ │ └── org/ │ │ │ └── neo4j/ │ │ │ └── cypherdsl/ │ │ │ └── core/ │ │ │ ├── AbstractCase.java │ │ │ ├── AbstractClause.java │ │ │ ├── AbstractNode.java │ │ │ ├── AbstractPropertyContainer.java │ │ │ ├── AbstractStatement.java │ │ │ ├── Aliased.java │ │ │ ├── AliasedExpression.java │ │ │ ├── Arguments.java │ │ │ ├── Asterisk.java │ │ │ ├── BooleanFunctionCondition.java │ │ │ ├── BooleanLiteral.java │ │ │ ├── BuiltInFunctions.java │ │ │ ├── Case.java │ │ │ ├── Clause.java │ │ │ ├── Clauses.java │ │ │ ├── ClausesBasedStatement.java │ │ │ ├── CollectExpression.java │ │ │ ├── Comparison.java │ │ │ ├── CompoundCondition.java │ │ │ ├── Condition.java │ │ │ ├── Conditions.java │ │ │ ├── ConflictingParametersException.java │ │ │ ├── ConstantCondition.java │ │ │ ├── CountExpression.java │ │ │ ├── Create.java │ │ │ ├── Cypher.java │ │ │ ├── DecoratedQuery.java │ │ │ ├── DefaultLoadCSVStatementBuilder.java │ │ │ ├── DefaultStatementBuilder.java │ │ │ ├── Delete.java │ │ │ ├── DistinctExpression.java │ │ │ ├── DurationLiteral.java │ │ │ ├── ExistentialSubquery.java │ │ │ ├── ExposesAndThen.java │ │ │ ├── ExposesCall.java │ │ │ ├── ExposesCreate.java │ │ │ ├── ExposesFinish.java │ │ │ ├── ExposesHints.java │ │ │ ├── ExposesLoadCSV.java │ │ │ ├── ExposesLogicalOperators.java │ │ │ ├── ExposesMatch.java │ │ │ ├── ExposesMerge.java │ │ │ ├── ExposesPatternLengthAccessors.java │ │ │ ├── ExposesProperties.java │ │ │ ├── ExposesRelationships.java │ │ │ ├── ExposesReturning.java │ │ │ ├── ExposesSubqueryCall.java │ │ │ ├── ExposesUnwind.java │ │ │ ├── ExposesWhere.java │ │ │ ├── ExposesWith.java │ │ │ ├── Expression.java │ │ │ ├── ExpressionCondition.java │ │ │ ├── ExpressionList.java │ │ │ ├── Expressions.java │ │ │ ├── Finish.java │ │ │ ├── Foreach.java │ │ │ ├── ForeignAdapter.java │ │ │ ├── ForeignAdapterFactory.java │ │ │ ├── FunctionInvocation.java │ │ │ ├── Functions.java │ │ │ ├── HasLabelCondition.java │ │ │ ├── Hint.java │ │ │ ├── IdentifiableElement.java │ │ │ ├── ImportingWith.java │ │ │ ├── InTransactions.java │ │ │ ├── InternalNodeImpl.java │ │ │ ├── InternalPropertyImpl.java │ │ │ ├── InternalRelationshipImpl.java │ │ │ ├── KeyValueMapEntry.java │ │ │ ├── LabelExpression.java │ │ │ ├── Labels.java │ │ │ ├── Limit.java │ │ │ ├── ListComprehension.java │ │ │ ├── ListExpression.java │ │ │ ├── ListLiteral.java │ │ │ ├── ListOperator.java │ │ │ ├── ListPredicate.java │ │ │ ├── Literal.java │ │ │ ├── LiteralBase.java │ │ │ ├── LoadCSVStatementBuilder.java │ │ │ ├── MapExpression.java │ │ │ ├── MapLiteral.java │ │ │ ├── MapProjection.java │ │ │ ├── Match.java │ │ │ ├── Merge.java │ │ │ ├── MergeAction.java │ │ │ ├── MessageKeys.java │ │ │ ├── MultiPartElement.java │ │ │ ├── MultiPartQuery.java │ │ │ ├── Named.java │ │ │ ├── NamedPath.java │ │ │ ├── Neo4jVersion.java │ │ │ ├── NestedExpression.java │ │ │ ├── Node.java │ │ │ ├── NodeBase.java │ │ │ ├── NodeLabel.java │ │ │ ├── NullLiteral.java │ │ │ ├── NumberLiteral.java │ │ │ ├── OngoingListBasedPredicateFunction.java │ │ │ ├── OngoingListBasedPredicateFunctionWithList.java │ │ │ ├── Operation.java │ │ │ ├── Operations.java │ │ │ ├── Operator.java │ │ │ ├── Order.java │ │ │ ├── Parameter.java │ │ │ ├── ParameterCollectingVisitor.java │ │ │ ├── ParameterLiteral.java │ │ │ ├── Pattern.java │ │ │ ├── PatternComprehension.java │ │ │ ├── PatternElement.java │ │ │ ├── PatternExpression.java │ │ │ ├── PatternExpressionImpl.java │ │ │ ├── PatternSelector.java │ │ │ ├── PeriodLiteral.java │ │ │ ├── Predicates.java │ │ │ ├── ProcedureCall.java │ │ │ ├── ProcedureCallImpl.java │ │ │ ├── Properties.java │ │ │ ├── Property.java │ │ │ ├── PropertyAccessor.java │ │ │ ├── PropertyContainer.java │ │ │ ├── PropertyLookup.java │ │ │ ├── QuantifiedPathPattern.java │ │ │ ├── QueryDSLAdapter.java │ │ │ ├── RawLiteral.java │ │ │ ├── ReadingClause.java │ │ │ ├── Reduction.java │ │ │ ├── Relationship.java │ │ │ ├── RelationshipBase.java │ │ │ ├── RelationshipChain.java │ │ │ ├── RelationshipPattern.java │ │ │ ├── Remove.java │ │ │ ├── RendererBridge.java │ │ │ ├── ResultStatement.java │ │ │ ├── Return.java │ │ │ ├── ReturnBody.java │ │ │ ├── Set.java │ │ │ ├── SinglePartQuery.java │ │ │ ├── Skip.java │ │ │ ├── SortItem.java │ │ │ ├── Statement.java │ │ │ ├── StatementBuilder.java │ │ │ ├── StatementCatalog.java │ │ │ ├── StatementCatalogBuildingVisitor.java │ │ │ ├── StatementContext.java │ │ │ ├── StringLiteral.java │ │ │ ├── Subquery.java │ │ │ ├── SubqueryExpression.java │ │ │ ├── SubqueryExpressionBuilder.java │ │ │ ├── SymbolicName.java │ │ │ ├── TemporalLiteral.java │ │ │ ├── TreeNode.java │ │ │ ├── UnionPart.java │ │ │ ├── UnionQueryImpl.java │ │ │ ├── Unwind.java │ │ │ ├── UpdatingClause.java │ │ │ ├── Use.java │ │ │ ├── UseClauseImpl.java │ │ │ ├── Where.java │ │ │ ├── With.java │ │ │ ├── annotations/ │ │ │ │ ├── CheckReturnValue.java │ │ │ │ └── package-info.java │ │ │ ├── ast/ │ │ │ │ ├── EnterResult.java │ │ │ │ ├── ProvidesAffixes.java │ │ │ │ ├── TypedSubtree.java │ │ │ │ ├── Visitable.java │ │ │ │ ├── Visitor.java │ │ │ │ ├── VisitorWithResult.java │ │ │ │ └── package-info.java │ │ │ ├── internal/ │ │ │ │ ├── CaseElse.java │ │ │ │ ├── CaseWhenThen.java │ │ │ │ ├── ConstantParameterHolder.java │ │ │ │ ├── DefaultStatementContext.java │ │ │ │ ├── Distinct.java │ │ │ │ ├── FixedNamesStrategy.java │ │ │ │ ├── GeneratedNamesStrategy.java │ │ │ │ ├── HandlerException.java │ │ │ │ ├── LoadCSV.java │ │ │ │ ├── NameResolvingStrategy.java │ │ │ │ ├── Namespace.java │ │ │ │ ├── ProcedureName.java │ │ │ │ ├── ReflectiveVisitor.java │ │ │ │ ├── RelationshipLength.java │ │ │ │ ├── RelationshipPatternCondition.java │ │ │ │ ├── RelationshipTypes.java │ │ │ │ ├── SchemaNamesBridge.java │ │ │ │ ├── ScopingStrategy.java │ │ │ │ ├── UsingPeriodicCommit.java │ │ │ │ ├── YieldItems.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── querydsl/ │ │ │ │ ├── CypherContext.java │ │ │ │ ├── CypherTemplates.java │ │ │ │ ├── ToCypherFormatStringVisitor.java │ │ │ │ ├── UnsupportedOperatorException.java │ │ │ │ └── package-info.java │ │ │ ├── renderer/ │ │ │ │ ├── ConfigurableRenderer.java │ │ │ │ ├── Configuration.java │ │ │ │ ├── DefaultVisitor.java │ │ │ │ ├── Dialect.java │ │ │ │ ├── GeneralizedRenderer.java │ │ │ │ ├── Neo4j523SubqueryVisitor.java │ │ │ │ ├── Neo4j5ComparisonVisitor.java │ │ │ │ ├── Neo4j5FunctionInvocationVisitor.java │ │ │ │ ├── Neo4j5Pre26LabelsVisitor.java │ │ │ │ ├── PatternSelectorVisitorPreNeo4j521.java │ │ │ │ ├── PrettyPrintingVisitor.java │ │ │ │ ├── Renderer.java │ │ │ │ ├── RenderingVisitor.java │ │ │ │ ├── SchemaEnforcementFailedException.java │ │ │ │ ├── Symbols.java │ │ │ │ └── package-info.java │ │ │ └── utils/ │ │ │ ├── Assertions.java │ │ │ ├── LRUCache.java │ │ │ ├── Strings.java │ │ │ └── package-info.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── native-image/ │ │ │ └── org.neo4j/ │ │ │ └── neo4j-cypher-dsl/ │ │ │ ├── native-image.properties │ │ │ └── resources-config.json │ │ └── org/ │ │ └── neo4j/ │ │ └── cypherdsl/ │ │ └── core/ │ │ └── messages.properties │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── neo4j/ │ │ └── cypherdsl/ │ │ └── core/ │ │ ├── BooleanLiteralTests.java │ │ ├── BuiltInFunctionsTests.java │ │ ├── ClausesTests.java │ │ ├── ComparisonTests.java │ │ ├── Cypher5IT.java │ │ ├── CypherIT.java │ │ ├── CypherTests.java │ │ ├── DefaultStatementBuilderTests.java │ │ ├── DeleteTests.java │ │ ├── DialectIT.java │ │ ├── DurationLiteralTests.java │ │ ├── ExpressionTests.java │ │ ├── ExpressionsIT.java │ │ ├── ForeignAdapterFactoryTests.java │ │ ├── FunctionInvocationTests.java │ │ ├── FunctionsIT.java │ │ ├── FunctionsTests.java │ │ ├── HintsIT.java │ │ ├── IdentifiableExpressionsIT.java │ │ ├── InternalNodeImplTests.java │ │ ├── InternalPropertyImplTests.java │ │ ├── InternalRelationshipImplTests.java │ │ ├── IssueRelatedIT.java │ │ ├── LabelsTests.java │ │ ├── LoadCSVIT.java │ │ ├── PackageAndAPIStructureTests.java │ │ ├── ParameterIT.java │ │ ├── ParameterLiteralTests.java │ │ ├── PredicatesTests.java │ │ ├── ProcedureCallsIT.java │ │ ├── RawLiteralTests.java │ │ ├── RelationshipChainTests.java │ │ ├── RelationshipTests.java │ │ ├── StatementCatalogBuildingVisitorTests.java │ │ ├── StringLiteralTests.java │ │ ├── SubqueriesGQLIT.java │ │ ├── SubqueriesIT.java │ │ ├── SymbolicNameTests.java │ │ ├── TemporalLiteralTests.java │ │ ├── TestUtils.java │ │ ├── ToStringSmokeTests.java │ │ ├── TreeNodeTests.java │ │ ├── UseIT.java │ │ ├── internal/ │ │ │ ├── LoadCSVTests.java │ │ │ └── ReflectiveVisitorTests.java │ │ ├── querydsl/ │ │ │ ├── Person.java │ │ │ ├── Place.java │ │ │ ├── QueryDSLAdapterTests.java │ │ │ ├── Stuff.java │ │ │ └── UnsupportedOperatorExceptionTests.java │ │ └── renderer/ │ │ ├── ConfigurableRendererTests.java │ │ ├── ConfigurationTests.java │ │ ├── DefaultVisitorTests.java │ │ └── PrettyPrintingVisitorTests.java │ └── resources/ │ └── mockito-extensions/ │ └── org.mockito.plugins.MockMaker ├── neo4j-cypher-dsl-bom/ │ └── pom.xml ├── neo4j-cypher-dsl-build/ │ ├── annotations/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ ├── module-info.java │ │ └── org/ │ │ └── neo4j/ │ │ └── cypherdsl/ │ │ └── build/ │ │ └── annotations/ │ │ ├── RegisterForReflection.java │ │ └── package-info.java │ ├── pom.xml │ └── processor/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── neo4j/ │ │ │ └── cypherdsl/ │ │ │ └── build/ │ │ │ └── processor/ │ │ │ ├── Entry.java │ │ │ ├── RegisterForReflectionProcessor.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── javax.annotation.processing.Processor │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── neo4j/ │ │ └── cypherdsl/ │ │ └── build/ │ │ └── processor/ │ │ └── RegisterForReflectionProcessorTests.java │ └── resources/ │ └── test_classes/ │ ├── Class0.java │ ├── Class1.java │ ├── Class2.java │ └── Class3.java ├── neo4j-cypher-dsl-codegen/ │ ├── neo4j-cypher-dsl-codegen-core/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ ├── module-info.java │ │ │ └── org/ │ │ │ └── neo4j/ │ │ │ └── cypherdsl/ │ │ │ └── codegen/ │ │ │ └── core/ │ │ │ ├── AbstractClassNameGenerator.java │ │ │ ├── AbstractMappingAnnotationProcessor.java │ │ │ ├── AbstractModelBuilder.java │ │ │ ├── ClassNameGenerator.java │ │ │ ├── Configuration.java │ │ │ ├── FieldNameGenerator.java │ │ │ ├── Identifiers.java │ │ │ ├── ModelBuilder.java │ │ │ ├── NodeImplBuilder.java │ │ │ ├── NodeModelBuilder.java │ │ │ ├── NodeNameGenerator.java │ │ │ ├── PropertyDefinition.java │ │ │ ├── RelationshipFactoryDefinition.java │ │ │ ├── RelationshipImplBuilder.java │ │ │ ├── RelationshipModelBuilder.java │ │ │ ├── RelationshipNameGenerator.java │ │ │ ├── RelationshipPropertyDefinition.java │ │ │ └── package-info.java │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── neo4j/ │ │ └── cypherdsl/ │ │ └── codegen/ │ │ └── core/ │ │ ├── ConfigurationTests.java │ │ ├── ConstantFieldNamingStrategyTests.java │ │ ├── ModelBuilderTests.java │ │ ├── NodeImplBuilderTests.java │ │ ├── NodeNameGeneratorTests.java │ │ └── RelationshipNameGeneratorTests.java │ ├── neo4j-cypher-dsl-codegen-ogm/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── neo4j/ │ │ │ │ └── cypherdsl/ │ │ │ │ └── codegen/ │ │ │ │ └── ogm/ │ │ │ │ ├── OGMAnnotationProcessor.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── javax.annotation.processing.Processor │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── neo4j/ │ │ │ └── cypherdsl/ │ │ │ └── codegen/ │ │ │ └── ogm/ │ │ │ ├── OGMAnnotationProcessorTests.java │ │ │ └── models/ │ │ │ ├── abstract_rels/ │ │ │ │ ├── Actor.java │ │ │ │ ├── Movie.java │ │ │ │ └── Person.java │ │ │ ├── different_properties_for_rel_type/ │ │ │ │ ├── Movie.java │ │ │ │ ├── MovieAppearance.java │ │ │ │ ├── Person.java │ │ │ │ ├── Play.java │ │ │ │ ├── TheaterAppearance.java │ │ │ │ └── package-info.java │ │ │ ├── enums_and_inner_classes/ │ │ │ │ ├── AnotherConverter.java │ │ │ │ ├── ConnectorTransport.java │ │ │ │ ├── InnerInnerClassConverter.java │ │ │ │ └── OtherEnum.java │ │ │ ├── ids/ │ │ │ │ ├── ExternalGeneratedId.java │ │ │ │ ├── ExternalGeneratedIdImplicit.java │ │ │ │ ├── InternalGeneratedId.java │ │ │ │ └── InternalGeneratedPrimitiveLongId.java │ │ │ ├── labels/ │ │ │ │ └── NodesWithDifferentLabelAnnotations.java │ │ │ ├── primitives/ │ │ │ │ └── Connector.java │ │ │ ├── records/ │ │ │ │ ├── NodeWithRecordProperties.java │ │ │ │ └── RecordTarget.java │ │ │ ├── same_properties_for_rel_type/ │ │ │ │ ├── Movie.java │ │ │ │ ├── MovieAppearance.java │ │ │ │ ├── Person.java │ │ │ │ ├── Play.java │ │ │ │ ├── TheaterAppearance.java │ │ │ │ └── package-info.java │ │ │ ├── same_rel_different_source/ │ │ │ │ ├── Book.java │ │ │ │ ├── Movie.java │ │ │ │ └── Person.java │ │ │ ├── same_rel_different_target/ │ │ │ │ ├── Book.java │ │ │ │ ├── Movie.java │ │ │ │ ├── Person.java │ │ │ │ └── package-info.java │ │ │ ├── same_rel_mixed/ │ │ │ │ ├── Book.java │ │ │ │ ├── Movie.java │ │ │ │ └── Person.java │ │ │ ├── same_rel_mixed_different_directions/ │ │ │ │ ├── Book.java │ │ │ │ ├── Movie.java │ │ │ │ └── Person.java │ │ │ ├── self_referential/ │ │ │ │ └── Example.java │ │ │ └── simple/ │ │ │ ├── Actor.java │ │ │ ├── Movie.java │ │ │ ├── Person.java │ │ │ └── package-info.java │ │ └── resources/ │ │ ├── abstract_rels/ │ │ │ ├── Directed_.java │ │ │ ├── Movie_.java │ │ │ └── Person_.java │ │ ├── different_properties_for_rel_type/ │ │ │ ├── ActedInMovie_.java │ │ │ ├── ActedInPlay_.java │ │ │ ├── Movie_.java │ │ │ ├── Person_.java │ │ │ └── Play_.java │ │ ├── enums_and_inner_classes/ │ │ │ └── ConnectorTransport_.java │ │ ├── ids/ │ │ │ ├── ExternalGeneratedIdImplicit_.java │ │ │ ├── ExternalGeneratedId_.java │ │ │ ├── InternalGeneratedId_.java │ │ │ └── InternalGeneratedPrimitiveLongId_.java │ │ ├── labels/ │ │ │ └── nodeswithdifferentlabelannotations/ │ │ │ ├── LabelOnNode1_.java │ │ │ └── LabelOnNode2_.java │ │ ├── org/ │ │ │ └── neo4j/ │ │ │ └── cypherdsl/ │ │ │ └── codegen/ │ │ │ └── ogm/ │ │ │ └── models/ │ │ │ └── related_classes_not_on_cp_like_in_reallife/ │ │ │ ├── Movie.java │ │ │ └── Person.java │ │ ├── primitives/ │ │ │ └── Connector_.java │ │ ├── records/ │ │ │ ├── NodeWithRecordProperties_.java │ │ │ ├── RecordAsRelationship_.java │ │ │ └── RecordTarget_.java │ │ ├── related_classes_not_on_cp_like_in_reallife/ │ │ │ ├── Movie_.java │ │ │ └── Person_.java │ │ ├── same_properties_for_rel_type/ │ │ │ ├── ActedIn_.java │ │ │ ├── Movie_.java │ │ │ ├── Person_.java │ │ │ └── Play_.java │ │ ├── same_rel_different_source/ │ │ │ ├── Book_.java │ │ │ ├── Movie_.java │ │ │ ├── Person_.java │ │ │ └── Wrote_.java │ │ ├── same_rel_different_target/ │ │ │ ├── Book_.java │ │ │ ├── Movie_.java │ │ │ ├── Person_.java │ │ │ └── Wrote_.java │ │ ├── same_rel_mixed/ │ │ │ ├── Book_.java │ │ │ ├── Movie_.java │ │ │ ├── Person_.java │ │ │ └── Wrote_.java │ │ ├── same_rel_mixed_different_directions/ │ │ │ ├── Book_.java │ │ │ ├── Movie_.java │ │ │ ├── Person_.java │ │ │ └── Wrote_.java │ │ ├── self_referential/ │ │ │ └── Example_.java │ │ └── simple/ │ │ ├── ActedIn_.java │ │ ├── Directed_.java │ │ ├── Follows_.java │ │ ├── Movie_.java │ │ ├── Person_.java │ │ └── Produced_.java │ ├── neo4j-cypher-dsl-codegen-sdn6/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── neo4j/ │ │ │ │ └── cypherdsl/ │ │ │ │ └── codegen/ │ │ │ │ └── sdn6/ │ │ │ │ ├── SDN6AnnotationProcessor.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── javax.annotation.processing.Processor │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── neo4j/ │ │ │ └── cypherdsl/ │ │ │ └── codegen/ │ │ │ └── sdn6/ │ │ │ ├── SDN6AnnotationProcessorTests.java │ │ │ └── models/ │ │ │ ├── abstract_rels/ │ │ │ │ ├── Actor.java │ │ │ │ ├── Movie.java │ │ │ │ └── Person.java │ │ │ ├── bidi/ │ │ │ │ ├── Element.java │ │ │ │ └── Point.java │ │ │ ├── different_properties_for_rel_type/ │ │ │ │ ├── Movie.java │ │ │ │ ├── MovieAppearance.java │ │ │ │ ├── Person.java │ │ │ │ ├── Play.java │ │ │ │ ├── TheaterAppearance.java │ │ │ │ └── package-info.java │ │ │ ├── enums_and_inner_classes/ │ │ │ │ ├── ConnectorTransport.java │ │ │ │ ├── InnerInnerClassConverter.java │ │ │ │ ├── OtherEnum.java │ │ │ │ └── SpringBasedConverter.java │ │ │ ├── ids/ │ │ │ │ ├── ExternalGeneratedId.java │ │ │ │ ├── ExternalGeneratedIdImplicit.java │ │ │ │ ├── InternalGeneratedId.java │ │ │ │ ├── InternalGeneratedIdWithSpringId.java │ │ │ │ └── InternalGeneratedPrimitiveLongId.java │ │ │ ├── labels/ │ │ │ │ └── NodesWithDifferentLabelAnnotations.java │ │ │ ├── primitives/ │ │ │ │ └── Connector.java │ │ │ ├── records/ │ │ │ │ ├── NodeWithRecordProperties.java │ │ │ │ └── RecordTarget.java │ │ │ ├── same_properties_for_rel_type/ │ │ │ │ ├── Movie.java │ │ │ │ ├── MovieAppearance.java │ │ │ │ ├── Person.java │ │ │ │ ├── Play.java │ │ │ │ ├── TheaterAppearance.java │ │ │ │ └── package-info.java │ │ │ ├── same_rel_different_package/ │ │ │ │ ├── application/ │ │ │ │ │ ├── CompanyModel.java │ │ │ │ │ └── PlaceModel.java │ │ │ │ └── domain/ │ │ │ │ ├── Company.java │ │ │ │ ├── DomainEntity.java │ │ │ │ ├── DomainEntity2.java │ │ │ │ └── Place.java │ │ │ ├── same_rel_different_source/ │ │ │ │ ├── Book.java │ │ │ │ ├── Movie.java │ │ │ │ └── Person.java │ │ │ ├── same_rel_different_target/ │ │ │ │ ├── Book.java │ │ │ │ ├── Movie.java │ │ │ │ ├── Person.java │ │ │ │ └── package-info.java │ │ │ ├── same_rel_mixed/ │ │ │ │ ├── Book.java │ │ │ │ ├── Movie.java │ │ │ │ └── Person.java │ │ │ ├── same_rel_mixed_different_directions/ │ │ │ │ ├── Book.java │ │ │ │ ├── Movie.java │ │ │ │ └── Person.java │ │ │ ├── self_referential/ │ │ │ │ └── Example.java │ │ │ └── simple/ │ │ │ ├── Actor.java │ │ │ ├── Bridge.java │ │ │ ├── Edge.java │ │ │ ├── Kante.java │ │ │ ├── Movie.java │ │ │ ├── Person.java │ │ │ ├── Src.java │ │ │ ├── Target.java │ │ │ ├── Target2.java │ │ │ ├── Target3.java │ │ │ ├── TotallyIgnored.java │ │ │ └── package-info.java │ │ └── resources/ │ │ ├── abstract_rels/ │ │ │ ├── Directed_.java │ │ │ ├── Movie_.java │ │ │ └── Person_.java │ │ ├── bidi/ │ │ │ ├── Element_.java │ │ │ ├── HasPathPoints_.java │ │ │ └── Point_.java │ │ ├── different_properties_for_rel_type/ │ │ │ ├── ActedInMovie_.java │ │ │ ├── ActedInPlay_.java │ │ │ ├── Movie_.java │ │ │ ├── Person_.java │ │ │ └── Play_.java │ │ ├── enums_and_inner_classes/ │ │ │ ├── ConnectorTransportWithGlobalConverter_.java │ │ │ └── ConnectorTransport_.java │ │ ├── ids/ │ │ │ ├── ExternalGeneratedIdImplicit_.java │ │ │ ├── ExternalGeneratedId_.java │ │ │ ├── InternalGeneratedIdWithSpringId_.java │ │ │ ├── InternalGeneratedId_.java │ │ │ └── InternalGeneratedPrimitiveLongId_.java │ │ ├── labels/ │ │ │ └── nodeswithdifferentlabelannotations/ │ │ │ ├── LabelOnNode1_.java │ │ │ ├── LabelOnNode2_.java │ │ │ ├── LabelOnNode3_.java │ │ │ ├── MultipleLabels1_.java │ │ │ ├── MultipleLabels2_.java │ │ │ └── MultipleLabels3_.java │ │ ├── org/ │ │ │ └── neo4j/ │ │ │ └── cypherdsl/ │ │ │ └── codegen/ │ │ │ └── sdn6/ │ │ │ └── models/ │ │ │ └── related_classes_not_on_cp_like_in_reallife/ │ │ │ ├── Movie.java │ │ │ └── Person.java │ │ ├── primitives/ │ │ │ └── Connector_.java │ │ ├── records/ │ │ │ ├── NodeWithRecordProperties_.java │ │ │ ├── RecordAsRelationship_.java │ │ │ └── RecordTarget_.java │ │ ├── related_classes_not_on_cp_like_in_reallife/ │ │ │ ├── Directed_.java │ │ │ ├── Movie_.java │ │ │ └── Person_.java │ │ ├── same_properties_for_rel_type/ │ │ │ ├── ActedIn_.java │ │ │ ├── Movie_.java │ │ │ ├── Person_.java │ │ │ └── Play_.java │ │ ├── same_rel_different_package/ │ │ │ ├── Abuses1_.java │ │ │ ├── CompanyModel_.java │ │ │ ├── Company_.java │ │ │ ├── DomainEntity2_.java │ │ │ ├── DomainEntity_.java │ │ │ ├── In1_.java │ │ │ ├── In2_.java │ │ │ ├── PlaceModel_.java │ │ │ ├── Place_.java │ │ │ ├── Uses1_.java │ │ │ └── Uses2_.java │ │ ├── same_rel_different_source/ │ │ │ ├── Book_.java │ │ │ ├── Movie_.java │ │ │ ├── Person_.java │ │ │ └── Wrote_.java │ │ ├── same_rel_different_target/ │ │ │ ├── Book_.java │ │ │ ├── Movie_.java │ │ │ ├── Person_.java │ │ │ └── Wrote_.java │ │ ├── same_rel_mixed/ │ │ │ ├── Book_.java │ │ │ ├── Movie_.java │ │ │ ├── Person_.java │ │ │ └── Wrote_.java │ │ ├── same_rel_mixed_different_directions/ │ │ │ ├── Book_.java │ │ │ ├── Movie_.java │ │ │ ├── Person_.java │ │ │ └── Wrote_.java │ │ ├── self_referential/ │ │ │ ├── BelongsTo_.java │ │ │ └── Example_.java │ │ └── simple/ │ │ ├── ActedIn_.java │ │ ├── Directed_.java │ │ ├── Follows_.java │ │ ├── Movie_.java │ │ ├── Person_.java │ │ ├── Produced_.java │ │ ├── Rel21_.java │ │ ├── Rel22_.java │ │ ├── Src_.java │ │ └── Target2_.java │ └── pom.xml ├── neo4j-cypher-dsl-examples/ │ ├── README.adoc │ ├── neo4j-cypher-dsl-examples-core/ │ │ ├── pom.xml │ │ └── src/ │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── neo4j/ │ │ └── cypherdsl/ │ │ └── examples/ │ │ ├── core/ │ │ │ ├── ArbitraryProceduresAndFunctionsTests.java │ │ │ ├── CypherDSLExamplesTests.java │ │ │ ├── FunctionsListTests.java │ │ │ ├── IssuesExamplesTests.java │ │ │ └── PropertiesTests.java │ │ └── model/ │ │ ├── AbstractNodeDefinition.java │ │ ├── AbstractRelationshipDefinition.java │ │ ├── ActedIn.java │ │ ├── BelongsTo.java │ │ ├── Department.java │ │ ├── Directed.java │ │ ├── Division.java │ │ ├── Movie.java │ │ ├── Person.java │ │ ├── StaticModelIT.java │ │ └── UnboundRelation.java │ ├── neo4j-cypher-dsl-examples-ogm-quarkus/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── neo4j/ │ │ │ │ └── cypherdsl/ │ │ │ │ └── examples/ │ │ │ │ └── ogm/ │ │ │ │ ├── books/ │ │ │ │ │ ├── Book.java │ │ │ │ │ ├── BookGenre.java │ │ │ │ │ ├── UserDetails.java │ │ │ │ │ ├── UserPreferences.java │ │ │ │ │ ├── UserSuggestionActivity.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── misc/ │ │ │ │ │ ├── Example.java │ │ │ │ │ └── package-info.java │ │ │ │ └── movies/ │ │ │ │ ├── Actor.java │ │ │ │ ├── Movie.java │ │ │ │ ├── MovieRepository.java │ │ │ │ ├── MovieResource.java │ │ │ │ ├── PeopleRepository.java │ │ │ │ ├── PeopleResource.java │ │ │ │ ├── Person.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── neo4j/ │ │ │ └── cypherdsl/ │ │ │ └── examples/ │ │ │ └── ogm/ │ │ │ ├── Neo4jOgmResourcesIT.java │ │ │ ├── UsageTests.java │ │ │ └── books/ │ │ │ └── ScopingTests.java │ │ └── resources/ │ │ └── movies.cypher │ ├── neo4j-cypher-dsl-examples-parser/ │ │ ├── pom.xml │ │ └── src/ │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── neo4j/ │ │ └── cypherdsl/ │ │ └── examples/ │ │ └── parser/ │ │ ├── ConditionExtractingMatchFactory.java │ │ ├── CypherDSLParserExamplesTests.java │ │ └── StatementCatalogBuildingVisitorViaParserTests.java │ ├── neo4j-cypher-dsl-examples-sdn6/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── neo4j/ │ │ │ │ └── cypherdsl/ │ │ │ │ └── examples/ │ │ │ │ └── sdn6/ │ │ │ │ ├── Application.java │ │ │ │ ├── books/ │ │ │ │ │ ├── Book.java │ │ │ │ │ ├── BookGenre.java │ │ │ │ │ ├── UserDetails.java │ │ │ │ │ ├── UserPreferences.java │ │ │ │ │ ├── UserSuggestionActivity.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── misc/ │ │ │ │ │ ├── Example.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── movies/ │ │ │ │ │ ├── Actor.java │ │ │ │ │ ├── Genre.java │ │ │ │ │ ├── GenreRepository.java │ │ │ │ │ ├── Movie.java │ │ │ │ │ ├── MovieRepository.java │ │ │ │ │ ├── MovieService.java │ │ │ │ │ ├── MoviesController.java │ │ │ │ │ ├── NewPersonCmd.java │ │ │ │ │ ├── PeopleController.java │ │ │ │ │ ├── PeopleRepository.java │ │ │ │ │ ├── PeopleService.java │ │ │ │ │ ├── Person.java │ │ │ │ │ ├── PersonDetails.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── application.properties │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── neo4j/ │ │ │ └── cypherdsl/ │ │ │ └── examples/ │ │ │ └── sdn6/ │ │ │ ├── ApplicationIT.java │ │ │ ├── UsageTests.java │ │ │ └── books/ │ │ │ └── ScopingTests.java │ │ └── resources/ │ │ ├── logback-test.xml │ │ └── movies.cypher │ └── pom.xml ├── neo4j-cypher-dsl-native-tests/ │ ├── README.adoc │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── neo4j/ │ │ └── cypherdsl/ │ │ └── graalvm/ │ │ ├── Application.java │ │ └── package-info.java │ └── test/ │ └── java/ │ └── org/ │ └── neo4j/ │ └── cypherdsl/ │ └── graalvm/ │ └── NativeApplicationIT.java ├── neo4j-cypher-dsl-parser/ │ ├── NOTICE.txt │ ├── README.adoc │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ ├── module-info.java │ │ └── org/ │ │ └── neo4j/ │ │ └── cypherdsl/ │ │ └── parser/ │ │ ├── CyperDslParseException.java │ │ ├── CypherDslASTExceptionFactory.java │ │ ├── CypherDslASTFactory.java │ │ ├── CypherParser.java │ │ ├── DatabaseName.java │ │ ├── EntityType.java │ │ ├── ExpressionAsPatternElementWrapper.java │ │ ├── ExpressionCreatedEventType.java │ │ ├── InfinityLiteral.java │ │ ├── InputPosition.java │ │ ├── InvocationCreatedEventType.java │ │ ├── LabelParsedEventType.java │ │ ├── MatchDefinition.java │ │ ├── NaNLiteral.java │ │ ├── NodeAtom.java │ │ ├── Options.java │ │ ├── ParenthesizedPathPatternAtom.java │ │ ├── PathAtom.java │ │ ├── PathLength.java │ │ ├── PatternAtom.java │ │ ├── PatternElementAsExpressionWrapper.java │ │ ├── PatternElementCreatedEventType.java │ │ ├── PatternElementFunctions.java │ │ ├── ReturnDefinition.java │ │ ├── Statements.java │ │ ├── TypeParsedEventType.java │ │ ├── UnsupportedCypherException.java │ │ └── package-info.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── neo4j/ │ │ └── cypherdsl/ │ │ └── parser/ │ │ ├── ComparisonIT.java │ │ ├── CypherChallengeTests.java │ │ ├── CypherDslASTFactoryTests.java │ │ ├── CypherParserTests.java │ │ ├── ExtractionIT.java │ │ ├── IdentifiableExpressionsTests.java │ │ ├── OptionsTests.java │ │ ├── ParserIssuesIT.java │ │ ├── PathLengthTests.java │ │ ├── QPPPrimerIT.java │ │ ├── RewriteTests.java │ │ ├── StatementsTests.java │ │ ├── TckTests.java │ │ └── UnsupportedCypherExceptionTests.java │ └── resources/ │ ├── cypher-challenge.csv │ └── qpp-primer.csv ├── neo4j-cypher-dsl-schema-name-support/ │ ├── README.adoc │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ ├── module-info.java │ │ └── org/ │ │ └── neo4j/ │ │ └── cypherdsl/ │ │ └── support/ │ │ └── schema_name/ │ │ ├── SchemaNames.java │ │ └── package-info.java │ └── test/ │ └── java/ │ └── org/ │ └── neo4j/ │ └── cypherdsl/ │ └── support/ │ └── schema_name/ │ ├── SchemaNamesIT.java │ └── SchemaNamesTests.java ├── neo4j-cypher-dsl-test-results/ │ └── pom.xml └── pom.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .git-blame-ignore-revs ================================================ 8f35f73471d06957498b5fc09dd83ceb49fd342b ================================================ FILE: .github/CODEOWNERS ================================================ /.github/ @michael-simons @meistermeier ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: true ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: maven directory: "/" schedule: interval: weekly time: "04:00" open-pull-requests-limit: 10 ================================================ FILE: .github/workflows/build.yml ================================================ name: build on: push: branches: - main pull_request: jobs: build: name: Neo4j Cypher-DSL runs-on: ubuntu-latest outputs: revision: ${{ steps.revision_step.outputs.revision }} steps: - uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v4 with: distribution: zulu java-version: 25 - name: Enable Sonar for local PRs not from Dependabot if: ${{ github.event.sender.login != 'dependabot[bot]' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} run: echo "USE_SONAR=sonar" >> $GITHUB_ENV - name: Disable Sonar for foreign PRs or from Dependabot if: ${{ github.event.sender.login == 'dependabot[bot]' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) }} run: echo "USE_SONAR=-sonar" >> $GITHUB_ENV - name: Determine revision id: revision_step run: | REVISION=$(./bin/extract-version.sh) echo "REVISION=$REVISION" >> $GITHUB_ENV echo "OLD_VERSION=$(./bin/extract-version.sh latest)" >> $GITHUB_ENV echo "::set-output name=revision::$REVISION" - name: Cache SonarCloud packages uses: actions/cache@v3 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - name: Cache Maven packages uses: actions/cache@v3 with: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-m2 - name: Run Maven build env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: > ./mvnw --no-transfer-progress clean deploy -DSCHEMA_NAMES_TEST_ALL_VERSIONS=false -P$USE_SONAR -Drevision=$REVISION -Dsha1=-$GITHUB_SHA -Dchangelist= -Dcypher-dsl.version.old=$OLD_VERSION -DaltDeploymentRepository=releases::default::file://$GITHUB_WORKSPACE/target/repository -pl !org.neo4j:neo4j-cypher-dsl-native-tests -pl !org.neo4j:neo4j-cypher-dsl-examples-core -pl !org.neo4j:neo4j-cypher-dsl-examples-parser -pl !org.neo4j:neo4j-cypher-dsl-examples-ogm-quarkus -pl !org.neo4j:neo4j-cypher-dsl-examples-sdn6 - name: Archive artifacts uses: actions/upload-artifact@v4 with: name: artifacts path: target/repository test_native: name: Verify compilation on GraalVM native runs-on: ubuntu-latest needs: build steps: - uses: graalvm/setup-graalvm@v1 with: distribution: 'graalvm-community' java-version: 25 github-token: ${{ secrets.GITHUB_TOKEN }} - name: Determine revision run: echo "REVISION=${{ needs.build.outputs.revision }}" >> $GITHUB_ENV - uses: actions/checkout@v4 - name: Download artifacts uses: actions/download-artifact@v4 with: name: artifacts path: repository - name: Install dependencies run: "mkdir -p ~/.m2 && mv $GITHUB_WORKSPACE/repository ~/.m2" - name: Run Maven build run: > ./mvnw --no-transfer-progress clean verify -Djqassistant.skip=true -Drevision=$REVISION -Dsha1=-$GITHUB_SHA -Dchangelist= -pl org.neo4j:neo4j-cypher-dsl-native-tests build_examples: name: Verify examples with ${{ matrix.java }} runs-on: ubuntu-latest strategy: matrix: java: [ '17', '21', '25' ] needs: build steps: - name: Determine revision run: echo "REVISION=${{ needs.build.outputs.revision }}" >> $GITHUB_ENV - uses: actions/checkout@v4 - name: Download artifacts uses: actions/download-artifact@v4 with: name: artifacts path: repository - name: Install dependencies run: "mkdir -p $HOME/.m2 && mv $GITHUB_WORKSPACE/repository $HOME/.m2/" - uses: actions/setup-java@v4 with: distribution: zulu java-version: ${{ matrix.java }} - name: Run Maven build run: > ./mvnw --no-transfer-progress clean verify -Djqassistant.skip=true -Drevision=$REVISION -Dsha1=-$GITHUB_SHA -Dchangelist= -pl org.neo4j:neo4j-cypher-dsl-examples -pl org.neo4j:neo4j-cypher-dsl-examples-core -pl org.neo4j:neo4j-cypher-dsl-examples-parser -pl org.neo4j:neo4j-cypher-dsl-examples-ogm-quarkus -pl org.neo4j:neo4j-cypher-dsl-examples-sdn6 ================================================ FILE: .github/workflows/publish_docs.yml ================================================ name: publish_docs on: push: branches: - main create: tags: - '*' jobs: publish_docs: if: github.event_name == 'push' || (github.event_name == 'create' && github.event.ref_type == 'tag') runs-on: ubuntu-latest steps: - name: Prepare branch name run: > echo "refName=${GITHUB_REF##*/}" >> $GITHUB_ENV - name: Checkout relevant branch uses: actions/checkout@v4 with: ref: ${{ env.refName }} - name: Checkout gh-pages uses: actions/checkout@v4 with: ref: gh-pages path: target/gh-pages - name: Set up JDK uses: actions/setup-java@v4 with: distribution: zulu java-version: 25 - name: Run docs generation run: > ./mvnw --no-transfer-progress install -pl neo4j-cypher-dsl-bom && ./mvnw --no-transfer-progress asciidoctor:process-asciidoc@generate-docs -pl org.neo4j:neo4j-cypher-dsl-parent -Dproject.build.docs=target/gh-pages/${refName} -Duse-latest-version-for-docs=`[ $refName = "main" ] && echo "0" || echo "1"` && ./mvnw --no-transfer-progress site -pl org.neo4j:neo4j-cypher-dsl-build-proc -pl org.neo4j:neo4j-cypher-dsl -am && rm -rf target/gh-pages/${refName}/project-info && mv neo4j-cypher-dsl/target/site target/gh-pages/${refName}/project-info - name: Update index if: (github.event_name == 'create' && github.event.ref_type == 'tag') run: sed -e "s/\${current}/${refName}/g" ./etc/index.tpl > ./target/gh-pages/index.html - name: Commit to gh-pages working-directory: ./target/gh-pages run: | git config --local user.email "action@github.com" git config --local user.name "GitHub Action" git add . git commit -m "Update GH-Pages." - name: Push changes uses: ad-m/github-push-action@v0.6.0 with: directory: target/gh-pages branch: gh-pages github_token: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/release.yml ================================================ name: release on: workflow_dispatch: create: tags: - '*' jobs: release: if: (github.event_name == 'create' && github.event.ref_type == 'tag') || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest steps: - name: 'Set up JDK' uses: actions/setup-java@v4 with: distribution: zulu java-version: 25 - name: 'Prepare git' run: git config --global core.autocrlf false - name: 'Prepare branch name' if: (github.event_name == 'create' && github.event.ref_type == 'tag') run: > echo "refName=${GITHUB_REF##*/}" >> $GITHUB_ENV - name: 'Checkout relevant branch' uses: actions/checkout@v4 with: ref: ${{ env.refName }} fetch-depth: 0 - name: Determine revision id: revision_step run: | REVISION=$(./bin/extract-version.sh latest) echo "REVISION=$REVISION" >> $GITHUB_ENV - name: 'Create release' env: JRELEASER_GITHUB_TOKEN: ${{ secrets.JRELEASER_TOKEN }} JRELEASER_SLACK_WEBHOOK: ${{ secrets.JRELEASER_SLACK_WEBHOOK }} run: ./mvnw --no-transfer-progress -Djreleaser -Drevision=$REVISION -Dsha1= -Dchangelist= jreleaser:announce -pl org.neo4j:neo4j-cypher-dsl-parent ================================================ FILE: .gitignore ================================================ target/ !.mvn/wrapper/maven-wrapper.jar ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans .sts4-cache ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr .gradle ### NetBeans ### /nbproject/private/ /nbbuild/ /dist/ /nbdist/ /.nb-gradle/ /build/ ### Visual Studio Code ### .vscode ### Misc ### .DS_Store .flattened-pom.xml dependency-reduced-pom.xml ================================================ FILE: .mvn/extensions.xml ================================================ org.torquebox.mojo mavengem-wagon 1.0.3 ================================================ FILE: .mvn/jvm.config ================================================ -XX:+IgnoreUnrecognizedVMOptions -Dguice_custom_class_loading=CHILD --enable-native-access=ALL-UNNAMED ================================================ FILE: .mvn/wrapper/maven-wrapper.properties ================================================ wrapperVersion=3.3.4 distributionType=bin distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at admins@neo4j.com. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. ================================================ FILE: CONTRIBUTING.adoc ================================================ = Contributing to the Neo4j Ecosystem :sectanchors: At http://neo4j.com/[Neo4j], we develop our software in the open at GitHub. This provides transparency for you, our users, and allows you to fork the software to make your own additions and enhancements. We also provide areas specifically for community contributions, in particular the https://github.com/neo4j-contrib[neo4j-contrib] space. There's an active https://community.neo4j.com/[Neo4j Online Community] where we work directly with the community. If you're not already a member, sign up! We love our community and wouldn't be where we are without you. Please remember: Many things are contributions, among them issues, code, documentation and examples. == Building and compiling the Neo4j-Cypher-DSL // tag::building-manual[] === Requirements For the full project, including examples and native tests: * https://www.graalvm.org/release-notes/JDK_24/[GraalVM for JDK 24] For the project, including examples but skipping native tests: * JDK 17+ (Can be https://openjdk.java.net[OpenJDK] or https://www.oracle.com/technetwork/java/index.html[Oracle JDK]) Maven 3.8.4 is our build tool of choice. We provide the Maven wrapper, see `mvnw` respectively `mvnw.cmd` in the project root; the wrapper downloads the appropriate Maven version automatically. The build requires a local copy of the project: [source,console,subs="verbatim,attributes"] [[clone-cypher-dsl]] .Clone the Neo4j Cypher-DSL ---- $ git clone git@github.com:neo4j-contrib/cypher-dsl.git ---- === Fast build NOTE: This is useful if you want to just have an installation of a snapshot version. No tests are run, no verification is done. [source,console,subs="verbatim,attributes"] [[build-fast-bash]] .Fast build (only compiling and producing packages) ---- $ ./mvnw -Dfast package ---- For a local install - maybe to try out a future release - you can also specify the version number: [source,console,subs="verbatim,attributes"] [[build-fast-and-install-bash]] .Fast build (locally installed, with an artificial version number) ---- $ ./mvnw -Dfast -Drevision=1337 -Dchangelist= install ---- === Full build (including examples and native tests) Before you proceed, verify your locally installed JDK version. The output should be similar: [source,console,subs="verbatim,attributes"] [[verify-jdk]] .Verify your JDK ---- $ java -version openjdk version "24" 2025-03-18 OpenJDK Runtime Environment GraalVM CE 24+36.1 (build 24+36-jvmci-b01) OpenJDK 64-Bit Server VM GraalVM CE 24+36.1 (build 24+36-jvmci-b01, mixed mode, sharing) ---- Check whether GraalVM `native-image` is present with: [source,console,subs="verbatim,attributes"] [[verify-native-image]] .Check for the present of `native-image` ---- $ which native-image /Users/msimons/.sdkman/candidates/java/24-graalce/bin/native-image ---- You should see `native-image` in the list. If not, you are most likely on a GraalVM version we don't support with this project. After that, use `./mvnw` on a Unix-like operating system to build the Cypher-DSL: [source,console,subs="verbatim,attributes"] [[build-default-bash]] .Build with default settings on Linux / macOS ---- $ ./mvnw clean verify ---- On a Windows machine, use [source,console,subs="verbatim,attributes"] [[build-default-windows]] .Build with default settings on Windows ---- $ mvnw.cmd clean verify ---- === Skipping native tests On a plain JDK 17 or higher, run the following to skip the native tests: [source,console,subs="verbatim,attributes"] [[build-skip-native-bash]] .Skipping native tests ---- $ ./mvnw clean verify -pl \!org.neo4j:neo4j-cypher-dsl-native-tests ---- === Build only the core module The core module can be built on plain JDK 17 with: [source,console,subs="verbatim,attributes"] [[build-only-core-bash]] .Build only the core module ---- $ ./mvnw clean verify -am -pl org.neo4j:neo4j-cypher-dsl ---- === CI-friendly version numbers We use CI-friendly version numbers, the current build will always identify itself as 9999-SNAPSHOT. If you need to create a specific version you can specify the revision, the changelist and an optional hash like this: [source,console,subs="verbatim,attributes"] .Specifying revision and changelist ---- $ ./mvnw clean package -pl org.neo4j:neo4j-cypher-dsl -Drevision=2022.1.0 -Dchangelist=-SNAPSHOT ---- === Releasing (Only relevant for the current maintainers) Prepare a release with: [source,console,subs="verbatim,attributes"] ---- ./mvnw exec:exec@prepare-release -pl :neo4j-cypher-dsl-parent -Drevision=2020.0.1 -Dchangelist= -Dcypher-dsl.version.next=2020.0.2-SNAPSHOT ---- and then let do Teamcity the rest, but chose the same version number there, too. // end::building-manual[] == Tasks === Keep the build descriptor (`pom.xml`) sorted [source,bash] ---- ./mvnw sortpom:sort ---- === Formatting sources / adding headers When you add new files, you can run [source,bash] ---- ./mvnw license:format ---- to add required headers automatically. We use https://github.com/spring-io/spring-javaformat[spring-javaformat] to format the source files. [source,bash] ---- ./mvnw spring-javaformat:apply ---- TIP: The Spring Developers write: "The source formatter does not fundamentally change your code. For example, it will not change the order of import statements. It is effectively limited to adding or removing whitespace and line feeds." This means the following checkstyle check might still fail. Some common errors include the wrong import order. There are plugins for https://github.com/spring-io/spring-javaformat#eclipse[Eclipse] and https://github.com/spring-io/spring-javaformat#intellij-idea[IntelliJ IDEA] and the Checkstyle settings https://github.com/spring-io/spring-javaformat#checkstyle-idea-plugin[can be imported as well]. We took those "as is" and just disabled the lambda check (requiring even single parameters to have parenthesis). Public classes do require an author tag. Please add yourself as an `@author` to the `.java` files you added or that modified substantially (more than cosmetic changes). == General considerations === Need to raise an issue? Where you raise an issue depends largely on the nature of the problem. Firstly, if you are an Enterprise customer, you might want to head over to our http://support.neo4j.com/[Customer Support Portal]. There are plenty of public channels available too, though. If you simply want to get started or have a question on how to use a particular feature, ask a question in https://community.neo4j.com/[Neo4j Online Community]. If you think you might have hit a bug in our software (it happens occasionally!) or you have specific feature request then use the issue feature on the relevant GitHub repository. Check first though as someone else may have already raised something similar. http://stackoverflow.com/questions/tagged/neo4j[StackOverflow] also hosts a ton of questions and might already have a discussion around your problem. Make sure you have a look there too. Include as much information as you can in any request you make: * Which versions of our products are you using? * Which language (and which version of that language) are you developing with? * What operating system are you on? * Are you working with a cluster or on a single machine? * What code are you running? * What errors are you seeing? * What solutions have you tried already? === Want to contribute? It's easier for all of us if you try to follow these steps before creating a pull request: * Do all your work in a personal fork of the original repository * https://github.com/edx/edx-platform/wiki/How-to-Rebase-a-Pull-Request[Rebase], don't merge (we prefer to keep our history clean) * Create a branch (with a useful name) for your contribution * Make sure you're familiar with the appropriate coding style (this varies by language so ask if you're in doubt) * Include unit tests if appropriate (obviously not necessary for documentation changes) NOTE: Small things that doesn't change the public API or documented behaviour and of course bug fixes usually go in quickly. If you want to add new features with public API changes or additions or want to customize or change a feature, please do reach out to us on one of the available channels, preferable by creating a https://github.com/neo4j-contrib/cypher-dsl/issues/new[new issue] first in which we can discuss the proposed changes. We can't guarantee that we'll accept pull requests and may ask you to make some changes before they go in. Occasionally, we might also have logistical, commercial, or legal reasons why we can't accept your work, but we'll try to find an alternative way for you to contribute in that case. Remember that many community members have become regular contributors and some are now even Neo employees! === Further reading If you want to find out more about how you can contribute, head over to our website for http://neo4j.com/developer/contributing-code/[more information]. == Got an idea for a new project? If you have an idea for a new tool or library, start by talking to other people in the community. Chances are that someone has a similar idea or may have already started working on it. The best software comes from getting like minds together to solve a problem. And we'll do our best to help you promote and co-ordinate your Neo ecosystem projects. ================================================ FILE: LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================ FILE: README.adoc ================================================ = The Neo4j Cypher-DSL :sectanchors: // tag::properties[] :groupId: org.neo4j :artifactId: neo4j-cypher-dsl // This will be next version and also the one that will be put into the manual for the main branch :neo4j-cypher-dsl-version: 2025.2.8-SNAPSHOT // This is the latest released version, used only in the readme :neo4j-cypher-dsl-version-latest: 2025.2.7 // end::properties[] image:https://github.com/neo4j-contrib/cypher-dsl/workflows/build/badge.svg[link=https://github.com/neo4j-contrib/cypher-dsl/actions] image:https://sonarcloud.io/api/project_badges/measure?project=org.neo4j%3Aneo4j-cypher-dsl-parent&metric=coverage[link=https://sonarcloud.io/summary/new_code?id=org.neo4j%3Aneo4j-cypher-dsl-parent] image:https://sonarcloud.io/api/project_badges/measure?project=org.neo4j%3Aneo4j-cypher-dsl-parent&metric=alert_status[link=https://sonarcloud.io/dashboard?id=org.neo4j%3Aneo4j-cypher-dsl-parent] image:https://maven-badges.herokuapp.com/maven-central/org.neo4j/neo4j-cypher-dsl/badge.svg[Maven Central,link=http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.neo4j%22%20AND%20a%3A%22neo4j-cypher-dsl%22] [abstract] -- The Neo4j Cypher-DSL in its current form is a spin-off from Spring Data Neo4j 6+ (né Spring Data Neo4j⚡️RX), where it is used to generate all Cypher queries. We thank all contributors to all branches prior to 2020.0 for their effort in creating the previous versions. -- The primary goal of this project is to have a type safe way of creating Cypher queries targeted at https://neo4j.com[Neo4j 4.0+]. Most of the constructs used here are modelled after https://www.opencypher.org[openCypher], but we include several constructs specific to Neo4j. The core module of the Neo4j Cypher-DSL has no required runtime dependencies. == Versioning This rebooted version of the Neo4j Cypher-DSL uses https://calver.org[CalVer] in the same way Spring does since early 2020 (see https://spring.io/blog/2020/04/30/updates-to-spring-versions[Updates to Spring Versions]), starting at _2020.0.0_. The year digit is treated in a semver fashion for the core module. That means that you won't experience any breaking changes when staying in a release line. However, the first or the latest release in a given year does not necessarily reflect the current calendar year. It's kind of a trade-off, but one that we think is valuable, otherwise we would need to postpone the first release in a year until we need todo some breaking changes. == Manual For a gentle introduction and some getting started guides, please use our https://neo4j.github.io/cypher-dsl[Manual]. == Getting Started === Adding the necessary dependencies First, include the dependency to the Neo4j Cypher-DSL under the following coordinates: `{groupId}:{artifactId}`: ==== Maven configuration [source,xml,subs="verbatim,attributes"] .Inclusion of the Neo4j Cypher-DSL in a Maven project ---- {groupId} {artifactId} {neo4j-cypher-dsl-version-latest} ---- ==== Gradle configuration [source,groovy,subs="verbatim,attributes"] .Inclusion of the Neo4j Cypher-DSL in a Gradle project ---- dependencies { compile '{groupId}:{artifactId}:{neo4j-cypher-dsl-version-latest}' } ---- === A simple example With the Cypher-DSL, you can build your queries starting with the static methods provided through `org.neo4j.cypherdsl.core.Cypher`. Static imports for those packages should be allowed: [source,java,tabsize=4] ---- import static org.neo4j.cypherdsl.core.Cypher.*; import org.neo4j.cypherdsl.core.Cypher; class SimpleExample { public static void main(String... a) { var m = node("Movie").named("m"); var statement = Cypher.match(m) .returning(m) .build(); System.out.println(statement.getCypher()); // Prints MATCH (m:`Movie`) RETURN m } } ---- === Required Java Version From version 2023.0.0 onwards, the minimal required Java version to use and build the Cypher-DSL is *Java 17*. If you need a version that is compatible with Java 8, please use 2022.9.x. == Licensing The Cypher-DSL itself is licenced under the https://www.apache.org/licenses/LICENSE-2.0[Apache License 2.0]. ================================================ FILE: bin/extract-version.sh ================================================ #!/usr/bin/env bash set -euo pipefail DIR="$(dirname "$(realpath "$0")")" if test -n "${1-}"; then sed -n 's/\(:neo4j-cypher-dsl-version-'"${1}"':\) \(.*\)/\2/p' $DIR/../README.adoc else sed -n 's/\(:neo4j-cypher-dsl-version:\) \(.*\)/\2/p' $DIR/../README.adoc fi ================================================ FILE: bin/prepare-release.sh ================================================ #!/usr/bin/env bash set -euo pipefail DIR="$(dirname "$(realpath "$0")")" sed -i .bak 's/\(:neo4j-cypher-dsl-version-latest:\) \(.*\)/\1 '"${1}"'/g' $DIR/../README.adoc if test -n "${2-}"; then sed -i .bak 's/\(:neo4j-cypher-dsl-version:\) \(.*\)/\1 '"${2}"'/g' $DIR/../README.adoc fi rm $DIR/../README.adoc.bak git add README.adoc git commit -sm "Prepare release." ================================================ FILE: bin/remove-shaded-modules.sh ================================================ #!/usr/bin/env bash set -euo pipefail DIR="$(dirname "$(realpath "$0")")" mkdir -p $DIR/../neo4j-cypher-dsl/target/modules sed 's/requires org.neo4j.cypherdsl.support.schema_name;$//g' $DIR/../neo4j-cypher-dsl/src/main/java/module-info.java > $DIR/../neo4j-cypher-dsl/target/modules/module-info.java ================================================ FILE: bin/runtests.java ================================================ ///usr/bin/env jbang "$0" "$@" ; exit $? //DEPS org.junit.platform:junit-platform-console-standalone:1.8.1 //DEPS org.assertj:assertj-core:3.22.0 //DEPS org.mockito:mockito-junit-jupiter:3.6.0 import org.junit.platform.console.ConsoleLauncher; /** * This script is mainly used to execute our tests standalone in CI with JDK 8. */ public class runtests { public static void main(String... a) { ConsoleLauncher.main(a); } } ================================================ FILE: docs/appendix/2020.0.adoc ================================================ == 2020.0 === 2020.0.1 This is the first patch release for the rebooted Cypher-DSL project. ==== 🚀 Features * GH-64 - Add function invocation for builtin point function. * GH-65 - Add support for defining calls to stored procedures. * `Cypher.literalOf` accepts now `boolean` values as well ==== 🧹 Housekeeping * Improvements to the manual and Java Docs. Thanks to @Andy2003 for contributing to this release. === 2020.0.0 This is the first version of the rebooted Neo4j Cypher-DSL project. This version has been extracted from https://github.com/neo4j/sdn-rx[SDN-RX]. It's a completely revamped API and we use it in all places in SDN/RX for generating Cypher-Queries. We use https://calver.org[CalVer] in the same way Spring does since early 2020 (see https://spring.io/blog/2020/04/30/updates-to-spring-versions[Updates to Spring Versions]) from this release onwards. You'll find the manual of the latest release version under http://neo4j-contrib.github.io/cypher-dsl/current/ and the current development version - or _main_ - under http://neo4j-contrib.github.io/cypher-dsl/main/. ================================================ FILE: docs/appendix/2020.1.adoc ================================================ == 2020.1 === 2020.1.6 No new features. Re-released 2020.1.5 due downtime and staging issues of oss.sonatype.org. === 2020.1.5 ==== 🚀 Features * GH-116 - Add support for creating calls to Neo4j reduce(). * GH-119 - Add support for passing symbolic names to nodes and relationships functions. * GH-117 - Introduce mutating operator. === 2020.1.4 ==== 🚀 Features * GH-114 - Support chained properties. * GH-115 - Support named items in YIELD and symbolic names as NamedPath reference. === 2020.1.3 ==== 🚀 Features * GH-111 - Provide a programmatic way of creating an optional match. ==== 🐛 Bug Fixes * GH-110 - Fix collapsing of empty conditions. === 2020.1.2 ==== 🚀 Features * GH-88 - Add support for Neo4j 4.0 subqueries. * GH-104 - Add support for merge actions. * GH-101 - Introduce asFunction on an ongoing call definition. ==== 🐛 Bug Fixes * GH-106 - Escape symbolic names, property lookups, aliases and map keys. ==== 🧹 Housekeeping * GH-105 - Remove ::set-env from GH-Actions ci. ==== Further improvements: * Add support for EXPLAIN and PROFILE keywords. * Qualify a yield call (only relevant for JDK15+) * Fix wrong offsets in the documentation. * Improve JavaDoc and document internal API. * Allow `WITH` clause after `YIELD`. * Improve reusability of fragments. * Make ORDER clause buildable. * Remove parts of an experimental API. We do publish the Project info now: http://neo4j-contrib.github.io/cypher-dsl/current/project-info/project-info.html[Project info], including the http://neo4j-contrib.github.io/cypher-dsl/current/project-info/apidocs/index.html[Java API]. === 2020.1.1 ==== 🚀 Features * List comprehensions can now be build based on named paths. === 2020.1.0 ==== 🚀 Features * GH-74 - Automatically generate symbolic name if required: `Node` and `Relationship` objects generate a symbolic name if required and not set * Added several new functions ** GH-77 `properties()` ** GH-81 `relationships()` ** GH-83 `startNode()`, `endNode()`, ** GH-89 All temporal functions * GH-76 - Added the list operator (`[]` for accessing sub lists and indexes). ==== 🐛 Bug Fixes * GH-82 - Expose all necessary interfaces for `call` * GH-84 - Fix rendering of nested sub trees. * GH-95 - NPE during the creation of map projections * GH-96 - Make sure aliased expressions are not rendered multiple times. ==== 🧹 Housekeeping * GH-67 - Improvements in regards of Java generics. * GH-68 - Clean up the Functions api. * GH-69 - Avoid star and static imports. * GH-72 - Some release cleanup * GH-75 - Move Assert to internal utils package. * GH-89 - `RelationshipDetails` is now internal API. * GH-93 - Ensure compatibility with GraalVM native. * GH-94 - Bring back SymbolicName#concat. ================================================ FILE: docs/appendix/2021.0.adoc ================================================ == 2021.0 === 2021.0.2 WARNING: This will already be the last release of the 2021.0 line. 2021.1 will be API compatible but not ABI compatible, as some classes have been changed into interfaces. That means it is not a drop in replacement, but your application needs to be recompiled. ==== 🚀 Features * GH-157 - Provide a method to turn a Java map into an expression. * GH-158 - Improve pretty printing of subqueries. * Allow the use of raw cypher as expressions. * Allow symbolic names to be used as aliases. * Cache some symbolic names. * Add support for the keys() function. ==== 📖 Documentation * GH-152 - Document usage of PatterElement in tests. ==== 🐛 Bug Fixes * GH-149 - Avoid possible stackoverflow exception during visitor traversal. * GH-159 - Fix missing labels for nodes after `WITH`. ==== 🧹 Housekeeping * GH-148 - Add jQAssistant rules and improve building documentation. * Add Maven PMD plugin. Thanks https://github.com/Andy2003[Andy] for the improvements of the pretty printer. === 2021.0.1 ==== 🚀 Features * GH-147 - Configuration infrastructure for renderer. First use case being a simple, pretty printing renderer. The feature looks like this: [source,java] ---- var c = node("Configuration").named("c"); var d = node("Cypher-DSL").named("d"); var mergeStatement = merge(c.relationshipTo(d, "CONFIGURES")) .onCreate() .set( d.property("version").to(literalOf("2021.0.1")), c.property("prettyPrint").to(literalTrue()) ) .onMatch().set(c.property("indentStyle").to(literalOf("TAB"))) .returning(d).build(); var renderer = Renderer.getRenderer(Configuration.prettyPrinting()); System.out.println(renderer.render(mergeStatement)); ---- and gives you: [source,cypher] ---- MERGE (c:Configuration)-[:CONFIGURES]->(d:`Cypher-DSL`) ON CREATE SET d.version = '2021.0.1', c.prettyPrint = true ON MATCH SET c.indentStyle = 'TAB' RETURN d ---- === 2021.0.0 2021.0.0 comes with a lot of new features. Thanks to https://github.com/Andy2003[Andy] for his contributions! Andy is one of our first users outside https://github.com/spring-projects/spring-data-neo4j[Spring Data Neo4j 6]. He started to use the Cypher-DSL in https://github.com/neo4j-graphql/neo4j-graphql-java[Neo4j GraphQL Java]. Neo4j GraphQL Java is a library to translate GraphQL based schemas and queries to Cypher and execute those statements with the Neo4j database. It can be used from a wide variety of frameworks. We are happy and proud to be part of this and even more so about the input and contribution we got back from Andy. Of course thanks for your input in form of tickets and discussions go out to @utnaf, @aaramg, @K-Lovelace and @maximelovino as well! ==== Noteworthy Two things should be mentioned: The https://github.com/neo4j-contrib/cypher-dsl/commit/2d0c98af853c72d4cd61099c9d8f3209b7e4c7c6[bugfix for GH-121] might change behavior for some users: The changes prevents the forced rendering of an alias for objects when the original object - the one that has been aliased - is passed down to the DSL after an alias has been created. The original intention for that behaviour was related to Map projection, in which the alias is actually rendered before the object. So now the use of an aliased expression the first time triggers `a AS b` respectively `b: a` in a map projection. All further calls will just render `b`. If the *original* object is used again, `a` will be rendered. If that is not desired in your query and you rely on the alias, make sure you use the aliased expression returned from `.as("someAlias")`. The other thing are the combined features GH-135 and GH-146. The `Statement` class has become a fully fledged accessor to the Cypher String and the parameters used and if provided, the values for those. The following shows a small example: [source,java] ---- var person = Cypher.node("Person").named("p"); var statement = Cypher .match(person) .where(person.property("nickname").isEqualTo(Cypher.parameter("nickname"))) .set( person.property("firstName").to(Cypher.parameter("firstName").withValue("Thomas")), person.property("name").to(Cypher.parameter("name", "Anderson")) ) .returning(person) .build(); assertThat(statement.getCypher()) .isEqualTo("MATCH (p:`Person`) WHERE p.nickname = $nickname SET p.firstName = $firstName, p.name = $name RETURN p"); Collection parameterNames = statement.getParameterNames(); assertThat(parameterNames).containsExactlyInAnyOrder("nickname", "firstName", "name"); Map parameters = statement.getParameters(); assertThat(parameters).hasSize(2); assertThat(parameters).containsEntry("firstName", "Thomas"); assertThat(parameters).containsEntry("name", "Anderson"); ---- ==== 🚀 Features * GH-122 - Add support for index hints. * GH-123 - Expose nested building of nested properties as public API. * GH-124 - Add support for Neo4j's mathematical functions. * GH-127 - Allow dynamic property lookup. * GH-128 - Provide asConditions for RelationshipPatterns. * GH-129 - Allow Expressions as Parameter for Skip and Limit. * GH-131 - Add support for projections on symbolic names. * GH-133 - Allow symbolic names to be used as condition. * GH-135 - Collect parameters defined on a statement. * GH-141 - Provide a property function on all expressions. * GH-142 - Provide a point function accepting generic expressions as parameter. * GH-146 - Allow a statement to render itself. ==== 📖 Documentation * GH-126 - Document how to call arbitrary functions and procedures. ==== 🐛 Bug Fixes * Prevent double rendering of Node content when using generated names. * GH-121 - Don't force rendering of aliases when the original object is used. * GH-137 - Fix grouping of nested conditions. ==== 🧹 Housekeeping * Switch to GraalVM 20.3.0. * GH-125 - Use GraalVM image from ghcr.io. * GH-139 - Ensure indention via tabs. * GH-140 - Provide editorconfig. * GH-143 - Remove union types. ================================================ FILE: docs/appendix/2021.1.adoc ================================================ == 2021.1 === 2021.1.2 This release comes with two notable things: It uses a couple of annotations on the API to guide developers using it correctly. IDEs like IDEA will now issue warnings if you don't use a returned builder, or a new instance of an object while wrongly assuming you mutated state. In the light of that we discovered that the `RelationshipChain` pattern was mutable and returning a mutated instance while it should have returned a fresh one. _Warning_ This might be a breaking change for users having code like this: ``` var pattern = Cypher.node("Start").named("s") .relationshipTo(Cypher.anyNode()) .relationshipTo(Cypher.node("End").named("e")); pattern.named("x"); ``` Prior to 2021.1.2 this would give the pattern the name `x` and modify it in place. From 2021.1.2 onwards you *must* use the returned value: ``` pattern = pattern.named("x"); ``` We think that this change is crucial and necessary as all other patterns are immutable as intended and in sum, they build up truly immutable statements. One pattern that is mutable like the above invalides the whole guarantee about the statement. ==== 🚀 Features * Add `named(SymbolicName s)` to RelationshipChain. * Generate $TYPE field containing the relationship type. [SDN 6 Annotation Processor] * Introduce some optional annotations for guidance along the api. ==== 📖 Documentation * GH-173 - Improve documentation. [A collection of small improvements] ==== 🐛 Bug Fixes * GH-174 - Extract types via the visitor API and avoid casting element types. [SDN 6 Annotation Processor] * Ensure immutability of `RelationshipChain`. ==== 🧹 Housekeeping * Remove unnecessary close (will be taken care of via `@Container`). [Only test related] * Run tests on JDK 16 === 2021.1.1 This is a drop-in replacement for <>. Introducing the interface for `Property` broke the `mutate` operation, for which no test was in place. This and the bug has been fixed. ==== 🐛 Bug Fixes * GH-168 - Fix mutating containers by properties. [[v2021.1.0]] === 2021.1.0 2021.1.0 comes with a ton of new features and a handful of breaking changes. Fear not, the breaking changes are resolvable by recompiling your application. We turned `Node`, `Relationship` and `Property` into interfaces and provide now `NodeBase` and `RelationshipBase` so that you can use them to build a static meta-model of your application. A `PropertyBase` might follow. Find out everything about the new possibility to define a static meta model in <>. The manual also includes a major part about the two new modules we offer: `{groupId}:neo4j-cypher-dsl-codegen-core` and `{groupId}:neo4j-cypher-dsl-codegen-sdn6`. `neo4j-cypher-dsl-codegen-core` provides the infrastructure necessary to build code generators for creating a domain model following our recommendation and `neo4j-cypher-dsl-codegen-sdn6` is a first implementation of that: A Java annotation processor that can be added to any Spring Data Neo4j 6 project in version 6.0.6 or higher. It will find your annotated domain classes and turn them into a model you can use to build queries. Last but not least: We added support for some expressions of the more generic http://www.querydsl.com[QueryDSL]. This will require `com.querydsl:querydsl-core` on the class path but only if you decide to call `Cypher#adapt(foreignExpression)`. This is a feature that is driven by Spring Data Neo4j 6.1 in which we build upon this to provide a `QuerydslPredicateExecutor`. Find more in <>. ==== 🚀 Features * GH-154 - Make Node and Relationship extendable. * GH-155 - Provide infrastructure for generating a static meta model. * GH-156 - Create an annotation processor for Spring Data Neo4j 6. * GH-167 - Add support for some Query-DSL expressions. * Introduce a statement context for allowing anonymous parameters (use `Cypher#anonParameter()` to define a parameter with a value but without a name. The name will be accessible on the statement after rendering). * Make rendering of constants as parameters configurable. * Allow specification of the direction while creating a sort item. * Introduce an interface for Property. ==== 📖 Documentation * GH-152 - Document usage of PatterElement in tests. * GH-164 - Improve public extendable API and add documentation. ==== 🐛 Bug Fixes * Fix SymbolicName#toString. * Clear visited name cache after single queries. ==== 🧹 Housekeeping * GH-165 - Simplify poms. * GH-166 - Improve Cypher.literalOf. * Exclude all example projects from release. ================================================ FILE: docs/appendix/2021.2.adoc ================================================ == 2021.2 === 2021.2.4 2021.2.4 is a pure bug fix release. ==== 🐛 Bug Fixes * GH-203 - Introduce a scope for the `PatternComprehension`. === 2021.2.3 2021.2.3 is a rather big release as it contains many small improvements and API functionality required by our next major release. Those are brought in now so that they can be benefial to others without bumping a major version. ==== 🚀 Features * GH-195 - Add collection parameter support for `ExposesReturning`. * Introduce a `ExposesPatternLengthAccessors` for uniform access to relationships and chains thereof. [improvement] * Allow creating instances of `FunctionInvocation` directly. [improvement] * Provide factory methods of `MapProjection` and `KeyValueMapEntry` as public API. * Provide `set` and `remove` labels operation as public API. * Provide `set` and `mutate` of expressions as public API. * Provide factory methods of `Hints` as public API. * GH-200 - Provide an API to define named paths based on a `Node` pattern. * Provide an option to escape names only when necessary. [improvement] ==== 📖 Documentation * Add documentation for escaping names. * GH-198 - Fix spelling errors in JavaDoc and documentation. ==== 🐛 Bug Fixes * Make `Case` an interface and let it extend `Expression`. [bug] * GH-197 - Fix eagerly resolved symbolic names in negated pattern conditions. * GH-197 - Clear name cache after leaving a statement. * GH-199 - Bring back `with(Named… vars)`. ==== 🧹 Housekeeping * Don't use fixed driver versions in doc. * Pass builder as constructor argument. * Improve `Return` and `With` internals. * Update Driver, SDN integration and Spring Boot example dependencies. * GH-202 - Make API Guardian an optional / provided dependency. Thanks to https://github.com/meistermeier[@meistermeier] and https://github.com/aldrinm[@aldrinm] for their contributions. === 2021.2.2 ==== 🚀 Features * Allow all expresions to be used as conditions. [improvement] * Add support for unary minus and plus operations. [new-feature] * Add support for generatic dynamic distinct aggregating function calls. [new-feature] * GH-190 - Introduce a union type for named things and aliased expressions. * Provide means to pass additional types to the relationship base class. [new-feature] * GH-193 - Allow MATCH after YIELD. * GH-189 - Provide an alternate api for methods consuming collections via vargs. ==== 📖 Documentation * Improve inheritance example. [static-model, codegen] ==== 🐛 Bug Fixes * Fix parameter collector when running as GraalVM native image * GH-192 - Don't introduce new symbolic names in conditional pattern expressions. ==== 🧹 Housekeeping * GH-178 - Upgrade SDN 6 examples to Spring Boot 2.5 final. Thanks to https://github.com/meistermeier[@meistermeier] for the contribution of the API improvements in regard to collections. === 2021.2.1 ==== 🚀 Features * Distinguish between statements and result statements: The Cypher-DSL knows whether a statement would actually return data or not * Provide optional integration with the Neo4j-Java-Driver to execute statements. * Allow to register Spring converters with the annotation processor. [codegen] * GH-182 - Add support for scalar converter functions. * GH-183 - Add trim function. * GH-184 - Add split function. * GH-180 - Add support for LOAD CSV and friends. * GH-187 - Add `returningRaw` for returning arbitrary (aliased) Cypher fragments (bot as part of a statement or as a general `RETURN xxx` clause without preceding query) * Resolve named parameters in raw literals: You can mix now the expression placeholder `$E` and named parameters in raw Cypher literals giving you much more flexibility in regards what to pass to the raw litera. ==== 🐛 Bug Fixes * GH-177 - Create a valid loadable and instantiable name when working on nested, inner classes. [codegen] * GH-186 - Pretty print subqueries and fix double rendering of Labels after subquery. ==== 🧹 Housekeeping * Remove unnecessary subpackage 'valid'. [codegen] (test code only) * Upgrade to GraalVM 21.1.0. * Update Spring dependencies for codegen. Thanks to https://github.com/Andy2003[@Andy2003] for contributing to this release. === 2021.2.0 2021.2 doesn't bring any new features apart from being now a Java library supporting the Java module system not only with automatic module names but also with a correct `module-info.java` when running on JDK 11+ on the module path. The Cypher-DSL uses the technique of https://openjdk.java.net/jeps/238[JEP 238: Multi-Release JAR Files] to provide a `module-info.java` for projects being on JDK 11+. The MR-Jar allows us to compile for JDK 8 but also support JDK 11 (we choose 11 as it is the current LTS release as time of writing). To use the Cypher-DSL in a modular application you would need to require the following modules: [source,java] ---- module org.neo4j.cypherdsl.examples.core { requires org.neo4j.cypherdsl.core; } ---- This release comes with a small catch: We do support using some https://github.com/querydsl/querydsl[QueryDSL] constructs. Query-DSL will have correct automatic module names in their 5.x release and we asked them to backport those to the 4.x line on which the Cypher-DSL *optionally* depends (See https://github.com/querydsl/querydsl/pull/2805[2805]). Until then we statically require (that is "optional" in module speak) Query-DSL via the artifact name. This can cause errors when the artifact (`querydsl-core.jar`) is renamed via the build process or similar. We are gonna improve that as soon as we can depend on fixed automatic module names. Apart from this big change there is no change in any public API. This release should be a drop-in replacement for the prior release. A big thank you to https://github.com/sormuras[@sormuras] for his invaluable lessons about the Java module system. ================================================ FILE: docs/appendix/2021.3.adoc ================================================ == 2021.3 === 2021.3.4 2021.3.4 is a pure bug fix release. ==== 🐛 Bug Fixes * GH-252 - Use a namespace for the message bundle. === 2021.3.3 2021.3.3 is a pure housekeeping release, however a release we are proud of. We do analyze this project now with https://www.sonarqube.org[SonarQube] vie https://sonarcloud.io[Sonarcloud] and are happy to announce that we have a quadruple A rating: image::20211027_sonarcoud.jpg[] In addition, we finally invited https://dependabot.com[Dependabot] taking care of at least creating the PRs. ==== 🧹 Housekeeping * Fix reliability and security issues * Fix a minor amounts of remaining code smells * Bump several dependencies (only test and build related) === 2021.3.2 ==== 🚀 Features * Add support for QueryDSL's `equalsIgnoreCase` operator * GH-204 - Provide access to identifiable expressions (See <>) ==== 🧹 Housekeeping * Fix several (compile) warnings * Fix several spelling errors in api docs * Upgrade Spring Data Neo4j to 6.1.5 (In module `org.neo4j.cypherdsl.codegen.sdn6`) * Upgrade Neo4j Cypher Parser to 4.3.4 (In module `neo4j-cypher-dsl-parser`). * Verify examples on JDK 17 === 2021.3.1 2021.3.1 is a pure bug fix release. API Guardian cannot be an optional dependency, otherwise compiling programs with `-Werror` will fail, as the `@API`-annotation has runtime and not class retention. ==== 🐛 Bug Fixes * GH-203 - Introduce a scope for the `PatternComprehension`. * Revert "GH-202 - Make API Guardian an optional / provided dependency." * Support empty BooleanBuilder in QueryDSL adapter. === 2021.3.0 ==== 🚀 New module: The Cypher-DSL-Parser 2021.3 builds straight upon <<2021.2.3>>, with few additions to the existing API, that didn't quite fit with a patch release, but belong conceptually into this release, which brings a completely new module: The `neo4j-cypher-dsl-parser`. What's behind that name? A Cypher-Parser build on the official Neo4j 4.3 parser frontend and creating a Cypher-DSL-AST or single expressions usable in the context of the Cypher-DSL. The module lives under the following coordinates `org.neo4j:neo4j-cypher-dsl-parser` and requires JDK 11+ (the same version like Neo4j does). We created a couple of <>, but we are sure you will have tons of more ideas and therefore a looking for your feedback. Here's a sneak preview. It shows you can add a user supplied Cypher fragment to something you are building using the DSL. [source,java] ---- var userProvidedCypher = "MATCH (this)-[:LINK]-(o:Other) RETURN o as result"; var userStatement = CypherParser.parse(userProvidedCypher); var node = Cypher.node("Node").named("node"); var result = Cypher.name("result"); var cypher = Cypher .match(node) .call( userStatement, node.as("this") ) .returning(result.project("foo", "bar")) .build() .getCypher(); ---- For this release a big thank you goes out to the Cypher-operations team at Neo4j, listening to our requests and ideas! ================================================ FILE: docs/appendix/2021.4.adoc ================================================ == 2021.4 === 2021.4.2 ==== 🐛 Bug Fixes * GH-252 - Use a namespace for the message bundle. Thanks to @Andy2003 for spotting this. ==== 🧹 Housekeeping * Tons of dependency upgrades in test scope * The parser module now uses the Neo4j 4.3.7 parser * Bump apiguardian-api from 1.1.1 to 1.1.2 (#250) === 2021.4.1 ==== 🚀 Features GH-230 - Add a way for a programmatic sort definition on expressions. ==== 🧹 Housekeeping * Tons of dependency upgrades in test scope * Upgrade to Neo4j-Java-Driver 4.4.1. (a provided dependency) * The parser module now uses the Neo4j 4.3.6 parser === 2021.4.0 2021.4.0 updates the optional dependency to https://github.com/querydsl/querydsl[Querydsl] to 5.0.0. While this is API not a breaking change, it can be when the Cypher-DSL is run together with Querydsl on the Java Module path. Querydsl maintainer finally introduced automatic module names for all their module on which we can no reliable depend. As that module name is however different from the generated one, it will be a breaking change on the module path. Therefore we bump our version, too. ==== 🧹 Housekeeping * Upgrade Querydsl to 5.0.0 ================================================ FILE: docs/appendix/2022.0.adoc ================================================ == 2022.0 === 2022.0.0 Starting with the 2022 release line, all current experimental warnings have been removed, and we consider our API stable. ==== Noteworthy As we have marked the API as stable we do enforce semantic versioning in our builds now. The parser module `neo4j-cypher-dsl-parser` has been updated to the Neo4j 4.4 parser and therefore doesn't bring in Scala dependencies anymore. And last but not least, we added the _Contributor Covenant Code of Conduct_. ==== 🚀 Features * Indent `CREATE` in subqueries. (#254) ==== 🐛 Bug Fixes * Fix broken asciidoc includes. * Give messages constant a better name (The bundle name we used might clash with other bundle names). ==== 🧹 Housekeeping * Tons of dependency upgrades: ** Bump reactor-bom from 2020.0.13 to 2020.0.14 (#265) ** Bump checker-qual from 3.20.0 to 3.21.0 (#264) ** Bump mockito.version from 4.1.0 to 4.2.0 (#263) ** Bump neo4j-cypher-javacc-parser from 4.4.0 to 4.4.2 (#262) ** Bump checker-qual from 3.19.0 to 3.20.0 (#261) ** Bump neo4j-java-driver from 4.4.1 to 4.4.2 (#260) ** Bump spring-boot-starter-parent from 2.5.6 to 2.6.1 (#259) ** Bump checkstyle from 9.1 to 9.2 (#256) ** Bump junit-bom from 5.8.1 to 5.8.2 (#257) ** Bump mockito.version from 4.0.0 to 4.1.0 (#255) Thanks again to https://github.com/Andy2003[Andy] for his contributions and feedback. ================================================ FILE: docs/appendix/2022.1.adoc ================================================ == 2022.1 === 2022.1.0 *No breaking changes*. The minor version has been incremented to notify about new default methods in our interfaces. Those shouldn't concern you as a user though, as they are not meant to be implemented by you. ==== Noteworthy Our integration tests on GitHub now uses the official GraalVM action: https://github.com/marketplace/actions/github-action-for-graalvm. Thanks, https://github.com/meistermeier[Gerrit], for integrating it. ==== 🚀 Features * Add `size` and `hasSize` on expressions. (#267) ==== 🧹 Housekeeping * Some polishing (mainly working on getting a "warning free" build in all the tools) * Tons of dependency upgrades: ** Bump testcontainers.version from 1.16.2 to 1.16.3 (#289) ** Bump spring-boot-starter-parent from 2.6.2 to 2.6.3 (#290) ** Bump mockito.version from 4.2.0 to 4.3.1 (#291) ** Bump slf4j.version from 1.7.33 to 1.7.35 (#292) ** Bump japicmp-maven-plugin from 0.15.4 to 0.15.6 (#293) ** Bump neo4j-java-driver from 4.4.2 to 4.4.3 (#294) ** Bump checkstyle from 9.2.1 to 9.3 (#295) ** Bump asciidoctor-maven-plugin from 2.2.1 to 2.2.2 (#296) ** Bump asciidoctorj from 2.5.2 to 2.5.3 (#285) ** Bump maven-jar-plugin from 3.2.1 to 3.2.2 (#284) ** Bump spring-data-neo4j from 6.2.0 to 6.2.1 (#283) ** Bump reactor-bom from 2020.0.14 to 2020.0.15 (#282) ** Bump slf4j.version from 1.7.32 to 1.7.33 (#281) ** Bump neo4j-cypher-javacc-parser from 4.4.2 to 4.4.3 (#280) ** Bump maven-jar-plugin from 3.2.0 to 3.2.1 (#277) ** Bump checker-qual from 3.21.0 to 3.21.1 (#276) ** Bump assertj-core from 3.21.0 to 3.22.0 (#272) ** Bump maven-site-plugin from 3.9.1 to 3.10.0 (#270) ** Bump maven-deploy-plugin from 3.0.0-M1 to 3.0.0-M2 (#271) ** Bump checkstyle from 9.2 to 9.2.1 (#269) ** Bump spring-boot-starter-parent from 2.6.1 to 2.6.2 (#268) ** Bump Maven to 3.8.4. ================================================ FILE: docs/appendix/2022.10.adoc ================================================ == 2022.10 === 2022.10.0 Welcome @ali-ince to the list of contributors to Cypher-DSL. We are glad that you and your team are users of this module. ==== 🚀 Features * Allow retrieving parameter names (#1071) * Allow chainable foreach. (#988) * Add builder methods for `FOREACH`. (#740) ==== 🧹 Housekeeping * Dependency upgrades: ** Update Neo4j from 4.4.29 to 4.4.37 ** Update Neo4j Java Driver from 4.4.15 to 4.4.18 ================================================ FILE: docs/appendix/2022.11.adoc ================================================ == 2022.11 === 2022.11.0 ==== 🚀 Features * Allow to call raw cypher from top level entry point (#1073) (#1074) ================================================ FILE: docs/appendix/2022.2.adoc ================================================ == 2022.2 === 2022.2.1 *No breaking changes*. ==== 🚀 Features * Add randomUUID to predefined functions. * Support additional mutate expression types. (#312) ==== 🐛 Bug Fixes * Don't create empty `WITH` clause without renames. (#320) * Fix rendering of nested FOREACH statements. (#318) * Check for field type too when computing internalId usage. ==== 📝 Documentation * Add example how to merge-find things via Springs `CypherdslStatementExecutor`. ==== 🧹 Housekeeping * Remove Awaitility test-dependency. * Dependency upgrades: ** Bump spring-data-neo4j from 6.2.2 to 6.2.3 (#332) ** Bump neo4j-cypher-javacc-parser from 4.4.4 to 4.4.5 (#330) ** Bump checkstyle from 10.0 to 10.1 (#329) ** Bump jna from 5.10.0 to 5.11.0 (#331) ** Bump spring-boot-starter-parent from 2.6.4 to 2.6.5 (#333) ** Bump native-maven-plugin from 0.9.10 to 0.9.11 (#334) ** Bump neo4j-java-driver from 4.4.3 to 4.4.5 (#328) ** Bump reactor-bom from 2020.0.16 to 2020.0.17 (#327) ** Bump mockito.version from 4.3.1 to 4.4.0 (#325) ** Bump checkstyle from 9.3 to 10.0 (#323) ** Bump guava from 31.0.1-jre to 31.1-jre (#324) ** Bump checker-qual from 3.21.2 to 3.21.3 (#322) ** Bump awaitility from 4.1.1 to 4.2.0 (#321) ** Bump japicmp-maven-plugin from 0.15.6 to 0.15.7 (#313) ** Bump spring-boot-starter-parent from 2.6.3 to 2.6.4 (#314) === 2022.2.0 *No breaking changes*. The minor version has been incremented to notify about a couple of new methods in the parser module, allowing for more and different types of parsing events to be emitted. Thanks to @ikwattro for his input and feedback in this release. ==== 🚀 Features * Emit pattern created event on merge clauses. * Add callbacks for a "pattern element created event". (#303) ==== 📝 Documentation * Add an example how to track changed properties to nodes. * Add rewrite example. * Add examples how to extract modified labels for the Cypher parser. ==== 🛠 Build * Fix surefire settings. * Add a 'fast' profile. * Reorder module-info.java creation before shading so that javadoc wont fail on vanilla JDK. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump maven-site-plugin from 3.10.0 to 3.11.0 (#311) ** Bump native-maven-plugin from 0.9.9 to 0.9.10 (#310) ** Bump maven-pmd-plugin from 3.15.0 to 3.16.0 (#309) ** Bump spring-data-neo4j from 6.2.1 to 6.2.2 (#308) ** Bump reactor-bom from 2020.0.15 to 2020.0.16 (#307) ** Bump slf4j.version from 1.7.35 to 1.7.36 (#306) ** Bump maven-javadoc-plugin from 3.3.1 to 3.3.2 (#305) ** Bump neo4j-cypher-javacc-parser from 4.4.3 to 4.4.4 (#304) ** Bump checker-qual from 3.21.1 to 3.21.2 (#298) ================================================ FILE: docs/appendix/2022.3.adoc ================================================ == 2022.3 === 2022.3.0 *No breaking changes*. The minor version has been incremented due to the following changes: * Changes in the `ExposesSubqueryCall` (new methods to expose `callInTransactions`, but that interface is not meant for external implementations anyway) * Added a new type `Dialect` and a new default method `enterWithResult` on the `Visitor` interface (have a look at the JavaDoc for the rationale behind it). ==== 🚀 Features * Add support for dialects. * Add support for toString(Expression). (#344) * Support `CALL {} IN TRANSACTIONS`. * Add parameter callbacks to the parser. (#336) ==== 🐛 Bug Fixes * Prevent `ClassCastException` when using `String` arguments to import variables into a subquery. * Make generated static model usable with self referential associations. (#337, Thanks to @ChristophB for his input on #335). * Fix tag of CypherParser entry point. (docs) ==== 📝 Documentation * Add information about GraalVM 21.3.0 and `org.graalvm.buildtools:native-maven-plugin` to CONTRIBUTING.adoc. ==== 🛠 Build * Fix publish_docs workflow. * Add support for registering `allDeclaredConstructors`. (#342) * Add `RegisterForReflection` and processor replacing static reflection-config.json. (#341) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump jackson-bom from 2.13.2 to 2.13.2.20220328 (#346) ** Bump asm from 9.2 to 9.3 (#347) ** Bump jacoco-maven-plugin from 0.8.7 to 0.8.8 (#345) ** Update managed version of error_prone_annotations to 2.12.1, avoiding compilation issues in IDEA. ** Bump spring-boot-starter-parent from 2.6.5 to 2.6.6 (#340) ** Bump checker-qual from 3.21.3 to 3.21.4 (#339) ** Bump maven-shade-plugin from 3.2.4 to 3.3.0 (#338) ================================================ FILE: docs/appendix/2022.4.adoc ================================================ == 2022.4 === 2022.4.0 * Added `withoutResults` to both in-statement and standalone call-builders so that one can use procedures without results inside a pipeline. This won't break anything, as the corresponding interface is not meant to implemented by downstream libraries * Compound conditions are now correctly immutable (as stated by the contract and its JavaDoc). This might break things if you have them changed inflight. Thanks to @Andy2003 for his input on this release. ==== 🚀 Features * Allow procedure calls without results after a match clause. (#361) ==== 🐛 Bug Fixes * Make `CompoundCondition` immutable obliging the interfaces contract. (#365) * Don't skip symbolic names if present and already in scope. (#363) ==== 🛠 Build * Update github-push-action to 0.6.0. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump testcontainers.version from 1.16.3 to 1.17.1 (#352) ** Bump reactor-bom from 2020.0.17 to 2020.0.18 (#353) ** Bump mockito.version from 4.4.0 to 4.5.1 (#354) ** Bump checkstyle from 10.1 to 10.2 (#355) ** Bump spring-boot-starter-parent from 2.6.6 to 2.6.7 (#356) ** Bump maven-javadoc-plugin from 3.3.2 to 3.4.0 (#357) ** Bump maven-site-plugin from 3.11.0 to 3.12.0 (#358) ** Bump spring-data-neo4j from 6.2.3 to 6.2.4 (#359) ** Bump neo4j-cypher-javacc-parser from 4.4.5 to 4.4.6 (#360) ** Bump checker-qual from 3.21.4 to 3.22.0 (#364) ================================================ FILE: docs/appendix/2022.5.adoc ================================================ == 2022.5 === 2022.5.0 *No breaking changes*, the minor version has been bumped due to new default methods of internal interfaces. This release is - again - a safe drop-in replacement for the prior (2022.4.0) one. Thanks to @hindog, @bhspencer, @Hardu2203 and @irene221b for their input on this release. ==== 🚀 Features * Add explicit `set` operation to `PropertyContainer`. (#394) * Support "WITH *, " by handling the 'returnAll' flag received from parser (#367) ==== 🔄️ Refactorings * refactor: Remove superfluous whitespaces before `MapExpression` in pretty printer. (#401) ==== 📝 Documentation * Add an example how to use Cypher parameters with`CypherdslStatementExecutor`. (#395) * Improve JavaDoc of `TemporalLiteral`. * Add correction method description. ==== 🛠 Build * Use latest Neo4j 4.4 for integration tests. * Add a CODEOWNERS declaration. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump jna from 5.11.0 to 5.12.0 (#399) ** Bump spring-boot-starter-parent from 2.7.0 to 2.7.1 (#398) ** Bump spring-data-neo4j from 6.3.0 to 6.3.1 (#397) ** Bump native-maven-plugin from 0.9.11 to 0.9.12 (#396) ** Bump reactor-bom from 2020.0.19 to 2020.0.20 (#392) ** Bump checker-qual from 3.22.1 to 3.22.2 (#390) ** Bump neo4j-cypher-javacc-parser from 4.4.7 to 4.4.8 (#391) ** Bump maven-enforcer-plugin from 3.0.0 to 3.1.0 (#386) ** Bump joda-time from 2.10.10 to 2.10.14 (#387) ** Bump asciidoctorj from 2.5.3 to 2.5.4 (#380) ** Bump assertj-core from 3.22.0 to 3.23.1 (#383) ** Bump checker-qual from 3.22.0 to 3.22.1 (#382) ** Bump mockito.version from 4.6.0 to 4.6.1 (#381) ** Bump neo4j-java-driver from 4.4.5 to 4.4.6 (#379) ** Bump maven-pmd-plugin from 3.16.0 to 3.17.0 (#378) ** Bump asciidoctorj-diagram from 2.2.1 to 2.2.3 (#377) ** Bump mockito.version from 4.5.1 to 4.6.0 (#376) ** Bump checkstyle from 10.2 to 10.3 (#375) ** Bump neo4j-cypher-javacc-parser from 4.4.6 to 4.4.7 (#373) ** Bump testcontainers.version from 1.17.1 to 1.17.2 (#371) ** Bump spring-data-neo4j from 6.2.4 to 6.3.0 (#368) ** Bump jackson-bom from 2.13.2.20220328 to 2.13.3 (#370) ** Bump reactor-bom from 2020.0.18 to 2020.0.19 (#369) ================================================ FILE: docs/appendix/2022.6.adoc ================================================ == 2022.6 === 2022.6.1 ==== 🐛 Bug Fixes * Include aliased expression in local scopes. (#420) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump neo4j-cypher-javacc-parser from 4.4.8 to 4.4.9 (#418) ** Bump maven-install-plugin from 3.0.0-M1 to 3.0.1 (#417) ** Bump spring-boot-starter-parent from 2.7.1 to 2.7.2 (#416) ** Bump maven-deploy-plugin from 3.0.0-M2 to 3.0.0 (#415) ** Bump exec-maven-plugin from 3.0.0 to 3.1.0 (#414) ** Bump native-maven-plugin from 0.9.12 to 0.9.13 (#413) ** Bump spring-data-neo4j from 6.3.1 to 6.3.2 (#412) ** Bump reactor-bom from 2020.0.20 to 2020.0.21 (#411) ** Bump checker-qual from 3.22.2 to 3.23.0 (#410) === 2022.6.0 `Functions.internalId()` has been deprecated to accomodate for Neo4j 5 later this year. We are unsure if we will do this to `id()`, too (The method with the same name will be deprecated in Neo4j 5 and eventually be replaced by `elementId()`). ==== 🚀 Features * Add fallback from `elementId` to `id` on older Neo4j versions. (#407) * Add calls for `elementId()`. (#406) ==== 🔄️ Refactorings * Deprecate `internalId` in favor of `elementId` on nodes. (#408) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump testcontainers.version from 1.17.2 to 1.17.3 (#403) ** Bump checkstyle from 10.3 to 10.3.1 (#404) ** Bump jna from 5.12.0 to 5.12.1 (#405) ================================================ FILE: docs/appendix/2022.7.adoc ================================================ == 2022.7 === 2022.7.3 ==== 🚀 Features * Add `point.withinBBox` and convenience methods for cartesian points and coordinates. (#475) ==== 🔄️ Refactorings * Remove superfluous field. ==== 🛠 Build * Replace jQAssistant with easier to maintain Archunit test. (#466) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump jackson-bom from 2.13.4 to 2.13.4.20221013 (#479) ** Bump neo4j-cypher-javacc-parser from 4.4.11 to 4.4.12 (#478) ** Bump reactor-bom from 2020.0.23 to 2020.0.24 (#477) ** Bump joda-time from 2.11.2 to 2.12.0 (#476) ** Bump archunit from 0.23.1 to 1.0.0 (#471) ** Bump neo4j-java-driver from 4.4.6 to 4.4.9 (#474) ** Bump testcontainers.version from 1.17.3 to 1.17.5 (#470) ** Bump checker-qual from 3.25.0 to 3.26.0 (#472) ** Bump asm from 9.3 to 9.4 (#468) ** Bump joda-time from 2.11.1 to 2.11.2 (#465) ** Bump spring-boot-starter-parent from 2.7.3 to 2.7.4 (#464) ** Bump junit-bom from 5.9.0 to 5.9.1 (#463) ** Bump asciidoctorj from 2.5.5 to 2.5.6 (#462) ** Bump checkstyle from 10.3.3 to 10.3.4 (#461) ** Bump native-maven-plugin from 0.9.13 to 0.9.14 (#460) ** Bump spring-data-neo4j from 6.3.2 to 6.3.3 (#459) ** Bump jqassistant.plugin.git from 1.8.0 to 1.9.0 (#458) ** Bump maven-jar-plugin from 3.2.2 to 3.3.0 (#457) ** Bump reactor-bom from 2020.0.22 to 2020.0.23 (#456) ** Bump maven-shade-plugin from 3.3.0 to 3.4.0 (#455) ** Bump maven-pmd-plugin from 3.18.0 to 3.19.0 (#454) ** Bump neo4j-cypher-javacc-parser from 4.4.10 to 4.4.11 (#452) ** Bump mockito.version from 4.7.0 to 4.8.0 (#451) === 2022.7.2 *No breaking changes*. This adds a new module - `neo4j-cypher-dsl-schema-name-support` - that contains only one class, dedicated to sanitise and quote names that are meant to be used as labels and types. We are using it internally for all our quoting needs and if you have the need in your application to create dynamic queries that deal with the modification of labels and types, you might want to have a look at that module. It is dependency free and safe to shade. The background to do label and type manipulation is this: Cypher does not support them as parameters, you need to concatenate your query for this. In all other cases, please use proper parameter, but especially for string values. Thanks to @AzuObs @AlexNeo4J and @robsdedude for their feedback on this work and also to @harshitp-fens for their inspiration of the `ON_DELETE_ITEM` parser callback. ==== 🚀 Features * Provide `ON_DELETE_ITEM` event type. (#449) * Introduce standalone schema-name support module. (#445) ==== 🛠 Build * Fix the build on a restricted TeamCity instance. (#450) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump checker-qual from 3.24.0 to 3.25.0 (#448) ** Bump japicmp-maven-plugin from 0.15.7 to 0.16.0 (#447) ** Bump jackson-bom from 2.13.3 to 2.13.4 (#446) ** Bump checkstyle from 10.3.2 to 10.3.3 (#444) ** Bump maven-checkstyle-plugin from 3.1.2 to 3.2.0 (#443) ** Bump maven-pmd-plugin from 3.17.0 to 3.18.0 (#442) ** Bump joda-time from 2.11.0 to 2.11.1 (#441) === 2022.7.1 *No breaking changes*. This is an important bug-fix release and a safe drop-in replacement for 2022.7.0 and all 2022.6 releases. If you can life with some deprecation warnings, it can be used as a drop-in for the 2022.5 and 2022.4 series, too. ==== 🐛 Bug Fixes * Escape escaped Unicode 0060 (backtick) proper. (#436) ==== 🔄️ Refactorings * Don't double escape already escaped backticks. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump mockito.version from 4.6.1 to 4.7.0 (#434): ** Bump reactor-bom from 2020.0.21 to 2020.0.22 (#433): ** Bump joda-time from 2.10.14 to 2.11.0 (#432): ** Bump neo4j-cypher-javacc-parser from 4.4.9 to 4.4.10 (#431): ** Bump maven-javadoc-plugin from 3.4.0 to 3.4.1 (#430): ** Bump spring-boot-starter-parent from 2.7.2 to 2.7.3 (#439) ** Bump flatten-maven-plugin from 1.2.7 to 1.3.0 (#437): === 2022.7.0 *No breaking changes*, the minor version has been bumped due to new default methods of internal interfaces. This release is - again - a safe drop-in replacement for the prior (2022.6.1) one. Thanks to https://github.com/AakashSorathiya[@AakashSorathiya] and https://github.com/nmervaillie[Nicolas Mervaillie] for their input on this release. ==== 🚀 Features * Add support for `includesAll` and `includesAny` operations on expressions for list properties * Support `org.neo4j.cypher.internal.ast.factory.ASTExpressionFactory#ands` ==== 🔄️ Refactorings * Add cause to unsupported to `UnsupportedCypherException`. ==== 🛠 Build * Use current JBang action to verify on JDK 8. (#421) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump maven-site-plugin from 3.12.0 to 3.12.1 (#428) ** Bump checker-qual from 3.23.0 to 3.24.0 (#429) ** Bump checkstyle from 10.3.1 to 10.3.2 (#425) ** Bump junit-bom from 5.8.2 to 5.9.0 (#424) ** Bump maven-resources-plugin from 3.2.0 to 3.3.0 (#423) ** Bump asciidoctorj from 2.5.4 to 2.5.5 (#422) ================================================ FILE: docs/appendix/2022.8.adoc ================================================ == 2022.8 === 2022.8.5 ==== 🐛 Bug Fixes * Apply prefixes after potential separator. (#606) ==== 🔄️ Refactorings * Ensure getting the type of relationships without type is safe. ==== 🛠 Build * Use `inputEncoding` for configuring checkstyle encoding. === 2022.8.4 ==== 🐛 Bug Fixes * Correctly track identifiable elements. (#579) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump checker-qual from 3.29.0 to 3.30.0 (#601) ** Bump maven-enforcer-plugin from 3.1.0 to 3.2.1 (#600) ** Bump checkstyle from 10.6.0 to 10.7.0 (#598) ** Bump asciidoctorj-diagram from 2.2.3 to 2.2.4 (#597) ** Bump jackson-bom from 2.14.1 to 2.14.2 (#594) ** Bump assertj-core from 3.24.1 to 3.24.2 (#576) ** Bump maven-checkstyle-plugin from 3.2.0 to 3.2.1 (#564) ** Bump junit-bom from 5.9.1 to 5.9.2 (#563) ** Bump reactor-bom from 2022.0.1 to 2022.0.2 (#559) ** Bump mockito.version from 4.11.0 to 5.0.0 (#558) ** Bump annotations from 23.1.0 to 24.0.0 (#557) ** Bump jna from 5.12.1 to 5.13.0 (#556) === 2022.8.3 ==== 🔄️ Refactorings * Allow `yield *` for standalone calls with arguments, too. (#545, thanks to @zakjan taking the time and report this) ==== 🧹 Housekeeping * Extend license header to 2023. * Dependency upgrades: ** Bump assertj-core from 3.23.1 to 3.24.1 (#549) ** Bump checker-qual from 3.28.0 to 3.29.0 (#548) ** Bump checkstyle from 10.5.0 to 10.6.0 (#537) ** Bump mockito.version from 4.10.0 to 4.11.0 (#536) === 2022.8.2 Thanks to @ikwattro from @graphaware for investing his time and creating valuable tickets for this release. ==== 🐛 Bug Fixes * Allow `match` after unwind as defined by OpenCypher. (#531) ==== 📝 Documentation * Make clear that pretty printing does not always escape names ==== 🛠 Build * Upgrade various actions to non-deprecated versions. (#519) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump annotations from 23.0.0 to 23.1.0 (#521) ** Bump compile-testing from 0.20 to 0.21.0 (#526) ** Bump reactor-bom from 2022.0.0 to 2022.0.1 (#527) ** Bump mockito.version from 4.9.0 to 4.10.0 (#528) === 2022.8.1 ==== 🔄️ Refactorings * Apply learnings from full JDK 17 migrations. * Prevent usage of `REMOVE` item inside `SET` clause (during RT). (#506) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump checker-qual from 3.27.0 to 3.28.0 (#517) ** Bump compile-testing from 0.19 to 0.20 (#516) ** Bump native-maven-plugin from 0.9.18 to 0.9.19 (#515) ** Bump joda-time from 2.12.1 to 2.12.2 (#514) ** Bump jackson-bom from 2.14.0 to 2.14.1 (#513) ** Bump archunit from 1.0.0 to 1.0.1 (#512) ** Bump native-maven-plugin from 0.9.17 to 0.9.18 (#511) ** Bump checkstyle from 10.4 to 10.5.0 (#510) === 2022.8.0 ==== 🚀 Features * Add `yield *` for standalone calls. (#497) ==== 📝 Documentation * Add missing value to `sanitize` JavaDoc. (#496) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump testcontainers.version from 1.17.5 to 1.17.6 (#502) ** Bump maven-install-plugin from 3.0.1 to 3.1.0 (#501) ** Bump japicmp-maven-plugin from 0.16.0 to 0.17.1 (#499) ** Bump mockito.version from 4.8.1 to 4.9.0 (#498) ** Bump jackson-bom from 2.13.4.20221013 to 2.14.0 (#492) ** Bump checker-qual from 3.26.0 to 3.27.0 (#493) ** Bump reactor-bom from 2020.0.24 to 2022.0.0 (#495) ** Bump native-maven-plugin from 0.9.16 to 0.9.17 (#491) ** Bump maven-shade-plugin from 3.4.0 to 3.4.1 (#487) ** Bump checkstyle from 10.3.4 to 10.4 (#488) ** Bump joda-time from 2.12.0 to 2.12.1 (#486) ** Bump spring-boot-starter-parent from 2.7.4 to 2.7.5 (#485) ** Bump asciidoctorj from 2.5.6 to 2.5.7 (#483) ** Bump native-maven-plugin from 0.9.14 to 0.9.16 (#482) ** Bump mockito.version from 4.8.0 to 4.8.1 (#481) ================================================ FILE: docs/appendix/2022.9.adoc ================================================ == 2022.9 === 2022.9.2 ==== 🚀 Features * Allow calling of raw Cypher strings in sub queries. (Backport from 2023.x) ==== 🐛 Bug Fixes * Driving symbolic names for list predicate function must not be scoped. (#905) ==== 🔄️ Refactorings * Allow unit-subqueries (Backport from 2023.x) ==== 🧰 Tasks * Extend license header to 2024. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump spring-boot-starter-parent from 2.7.5 to 2.7.18 ** Update Spring Data Neo4j from 6.3.14 to 6.3.18 ** Bump reactor-bom from 2022.0.2 to 2022.0.15 ** Update Neo4j from 4.4.23 to 4.4.29 ** Update Neo4j Java Driver from 4.4.12 to 4.4.13 ** Update Neo4j Java Driver from 4.4.13 to 4.4.15 ** Bump testcontainers.version from 1.17.6 to 1.19.7 ==== 🛠 Build * Update various build related plugins. === 2022.9.1 ==== 🐛 Bug Fixes * Apply the correct includesAll and includesAny semantics (#819) === 2022.9.0 ==== 🚀 Features * Add callbacks for function and procedure invocations. (#758) ==== 🧹 Housekeeping * Dependency upgrades: ** Update Spring Data Neo4j from 6.3.5 to 6.3.14 ** Update Neo4j from 4.4.12 to 4.4.23 ** Update Neo4j Java Driver from 4.4.9 to 4.4.12 ================================================ FILE: docs/appendix/2023.0.adoc ================================================ == 2023.0 === 2023.0.5 ==== 🐛 Bug Fixes * Apply the correct includesAll and includesAny semantics (#819) === 2023.0.4 2023.0.4 is a bug fix release and fully compatible with 2023.0.3. ==== 🐛 Bug Fixes * Correctly shadow visible nodes in a subquery. (#616) * Parse Node pattern predicates correctly. (#615) * Ensure getting the type of relationships without type is safe. * Apply prefixes after potential separator. (#606) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump neo4j-java-driver from 5.5.0 to 5.6.0 (#621) ** Bump spring-boot-starter-parent from 3.0.2 to 3.0.3 (#619) ** Bump checkstyle from 10.7.0 to 10.8.0 (#620) ** Bump spring-data-neo4j from 7.0.0 to 7.0.2 (#614) ** Bump maven-surefire-plugin from 3.0.0-M8 to 3.0.0-M9 (#613) ** Bump maven-failsafe-plugin from 3.0.0-M8 to 3.0.0-M9 (#612) ** Bump checker-qual from 3.30.0 to 3.31.0 (#611) ** Bump reactor-bom from 2022.0.1 to 2022.0.3 (#610) ** Bump native-maven-plugin from 0.9.19 to 0.9.20 (#608) ** Bump maven-javadoc-plugin from 3.4.1 to 3.5.0 (#607) ** Bump neo4j-cypher-javacc-parser from 5.4.0 to 5.5.0 (#609) === 2023.0.3 Thanks to @Andy2003 for their input on several bugs! ==== 🐛 Bug Fixes * Resolve symbolic names when looking for visited items. (#602) * Open implicit scope when entering a `UNION` clause. (#590) * Move resolved symbolic names into `StatementContext`. (#588) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump maven-surefire-plugin from 3.0.0-M7 to 3.0.0-M8 (#555) ** Bump maven-failsafe-plugin from 3.0.0-M7 to 3.0.0-M8 (#560) ** Bump checker-qual from 3.29.0 to 3.30.0 (#601) ** Bump maven-enforcer-plugin from 3.1.0 to 3.2.1 (#600) ** Bump mockito.version from 4.11.0 to 5.1.1 (#599) ** Bump checkstyle from 10.6.0 to 10.7.0 (#598) ** Bump asciidoctorj-diagram from 2.2.3 to 2.2.4 (#597) ** Bump jackson-bom from 2.14.1 to 2.14.2 (#594) ** Bump neo4j-java-driver from 5.3.1 to 5.5.0 (#592) ** Bump neo4j-cypher-javacc-parser from 5.3.0 to 5.4.0 (#593) === 2023.0.2 Thanks to @ikwattro, @lukaseder and @bonelli for their input! ==== 🚀 Features * Add missing string functions. (#584) * Add support for rewriting the `MATCH` clause after parsing. (#580) ==== 🐛 Bug Fixes * Add support for label expressions. (#582) * Correctly track identifiable elements. (#579) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump spring-boot-starter-parent from 3.0.1 to 3.0.2 (#577) ** Bump assertj-core from 3.24.1 to 3.24.2 (#576) ==== 🛠 Build * Move the fixed `module-info.java` somewhere out of IDEAs reach. === 2023.0.1 This patch releases adds a build-in `length` function for paths (thanks @Lukasmp3 for the request) and fixes issues when running Cypher-DSL on the module path (see c7747ca35 on main for more information). ==== 🚀 Features * Add `length()` function. (#569) ==== 🔄️ Refactorings * Replace `requires static transitive` with `requires static`. === 2023.0.0 Welcome to 2023, welcome Java 17, welcome Neo4j 5 clauses. This is the first release of the Cypher-DSL requiring Java 17. This is in line with Neo4j itself, Spring Data Neo4j 7 and several other frameworks out there. This allows for more concise code (which is nice for us) as well as using the Neo4j 5 parser in `neo4j-cypher-dsl-parser` module. Bumping the JDK warrants a major upgrade already. Apart from that we have been very reluctant on breaking changes. As a matter of fact, close to none has been necessary. One of the few improvements that might need changes on your side is #551 (Commit 10080df) in which we improved the `WITH` clause: You might see ambiguous method errors and the fix can be seen https://github.com/neo4j-contrib/cypher-dsl/commit/10080df4c537742218584d80bf4682dd74088a15#diff-dd86a606c4e4fe3151d8d7bb6af70b1aeecb25b5a32bcc9e9862483ca666a261[here] for example: Either use JDK 17 reserved name `var` for local variable type-inference or use explicit `IdentifiableElement`. There's a lot of new stuff as well: You can now use `Expressions.count` to build new Neo4j 5 `COUNT` expressions and we do support the `USE` clause for composite database queries as well. Please fear not if you are still on JDK 8: We will maintain the 2022.8.x branch at least as long as Spring Data Neo4j 6.3 is maintained, as the latter is build on top of the Cypher-DSL and is JDK 8, too. Thanks a lot to our friend @ikwattro from @graphaware for his continuous and well appreciated feedback and input to this project. ==== 🚀 Features * Add support for the `COUNT {}` sub-query expressions. (#546) * Pretty print `USE` clause proper. (#543, thanks to @ikwattro for contributing this) * Add support for the `USE` clause in the DSL. (#542) ==== 🐛 Bug Fixes * Allow `match` after unwind as defined by OpenCypher. (#531) ==== 🔄️ Refactorings * Improve `returning` and `with`. (#551) * Allow `yield *` for standalone calls with arguments, too. (#545, thanks to @zakjan taking the time and report this) * Upgrade the parser module to use the new Neo4j 5 parser. (#503) * Migrate the project to Java 17. (#518) * Prevent usage of `REMOVE` item inside `SET` clause (during RT). (#506) ==== 📖 Documentation * Update changelog. * Add section about dialect support. * Make clear that pretty printing does not always escape names. * Document correct Java version in `README.adoc`. ==== 🧰 Tasks * Extend license header to 2023. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump assertj-core from 3.23.1 to 3.24.1 (#549) ** Bump checker-qual from 3.28.0 to 3.29.0 (#548) ** Bump neo4j-java-driver from 5.3.0 to 5.3.1 (#535) ** Bump spring-boot-starter-parent from 3.0.0 to 3.0.1 (#534) ** Bump checkstyle from 10.5.0 to 10.6.0 (#537) ** Bump mockito.version from 4.10.0 to 4.11.0 (#536) ** Bump neo4j-cypher-javacc-parser from 5.2.0 to 5.3.0 (#529) ** Bump annotations from 23.0.0 to 23.1.0 (#521) ** Bump compile-testing from 0.20 to 0.21.0 (#526) ** Bump reactor-bom from 2022.0.0 to 2022.0.1 (#527) ** Bump mockito.version from 4.9.0 to 4.10.0 (#528) ** Bump spring-boot-starter-parent from 2.7.5 to 3.0.0 (#509) ** Bump neo4j-java-driver from 4.4.9 to 5.3.0 (#508) ** Bump checker-qual from 3.27.0 to 3.28.0 (#517) ** Bump compile-testing from 0.19 to 0.20 (#516) ** Bump native-maven-plugin from 0.9.18 to 0.9.19 (#515) ** Bump joda-time from 2.12.1 to 2.12.2 (#514) ** Bump jackson-bom from 2.14.0 to 2.14.1 (#513) ** Bump archunit from 1.0.0 to 1.0.1 (#512) ** Bump native-maven-plugin from 0.9.17 to 0.9.18 (#511) ** Bump checkstyle from 10.4 to 10.5.0 (#510) ==== 🛠 Build * Add more tests for GH-547. * Define JaCoCo config in plugin-management. (#541) * Add `license-maven-plugin` for checking Apache 2 compatible license and header formatting. * Fix quality gate. * Verify examples on Java LTS and next version. * Fix docs build. * Upgrade various actions to non-deprecated versions. (#519) ================================================ FILE: docs/appendix/2023.1.adoc ================================================ == 2023.1 === 2023.1.0 2023.1.0 is the first feat release after 2023.0.0 and contains several ideas and improvements that stem from https://github.com/neo4j-contrib/sql2cypher[sql2cypher] and from input by @lukaseder. Thank you! Additionally, we worked again with @ikwattro from https://graphaware.com[Graph Aware] on building the catalog feature. Each statement - regardless of being built or parsed with the Cypher-DSL - can be analyzed via it's catalog now. The catalog will contain labels, types and properties used in a statement and the filters created based on those tokens. You can access the catalog like this: [source,java] ---- var input = """ MATCH (m:`Movie` {title: 'The Matrix'})<-[a:`ACTED_IN`]-(p:`Person`) WHERE p.born >= $born RETURN p """; var statement = CypherParser.parse(input); var catalog = statement.getCatalog(); ---- Also: All AST elements will now render themselves to Cypher-Fragments when used in `toString()` scenarios. Apart from that, all bug fixes and dependency upgrades from 2022.8.5 and 2023.0.4 are included: ==== 🚀 Features * Add overloads for `count` and `exists` taking in a statement and optional imports. (#623) * Add label existences conditions to catalog. (#622) * Provide a catalog for identifiable items in a statement. (#617) * Allow retrieving parameter names * Add missing string functions. (#584) * Add support for rewriting the `MATCH` clause after parsing. (#580) * Add `length()` function. (#569) * Allow direct rendering of `Visitable` objects. (#554) ==== 🐛 Bug Fixes * Correctly shadow visible nodes in a subquery. (#616) * Parse Node pattern predicates correctly. (#615) * Ensure getting the type of relationships without type is safe. * Apply prefixes after potential separator. (#606) * Resolve symbolic names when looking for visited items. (#602) * Open implicit scope when entering a `UNION` clause. (#590) * Move resolved symbolic names into `StatementContext`. (#586) * Add support for label expressions. (#583) * Correctly track identifiable elements. (#579) ==== 🔄️ Refactorings * Replace `requires static transitive` with `requires static`. * Allow covariant collection overloads for `PatternElement` and `Expression` where sensible. (#566) ==== 📖 Documentation * Fix title. * Improve JavaDoc. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump neo4j-java-driver from 5.5.0 to 5.6.0 (#621) ** Bump spring-boot-starter-parent from 3.0.2 to 3.0.3 (#619) ** Bump checkstyle from 10.7.0 to 10.8.0 (#620) ** Bump spring-data-neo4j from 7.0.1 to 7.0.2 (#614) ** Bump maven-surefire-plugin from 3.0.0-M8 to 3.0.0-M9 (#613) ** Bump maven-failsafe-plugin from 3.0.0-M8 to 3.0.0-M9 (#612) ** Bump checker-qual from 3.30.0 to 3.31.0 (#611) ** Bump reactor-bom from 2022.0.2 to 2022.0.3 (#610) ** Bump native-maven-plugin from 0.9.19 to 0.9.20 (#608) ** Bump maven-javadoc-plugin from 3.4.1 to 3.5.0 (#607) ** Bump neo4j-cypher-javacc-parser from 5.4.0 to 5.5.0 (#609) ** Bump maven-deploy-plugin from 3.0.0 to 3.1.0 (#603) ** Bump checker-qual from 3.29.0 to 3.30.0 (#601) ** Bump maven-enforcer-plugin from 3.1.0 to 3.2.1 (#600) ** Bump mockito.version from 5.0.0 to 5.1.1 (#599) ** Bump checkstyle from 10.6.0 to 10.7.0 (#598) ** Bump asciidoctorj-diagram from 2.2.3 to 2.2.4 (#597) ** Bump jackson-bom from 2.14.1 to 2.14.2 (#594) ** Bump neo4j-java-driver from 5.4.0 to 5.5.0 (#592) ** Bump neo4j-cypher-javacc-parser from 5.3.0 to 5.4.0 (#593) ** Bump spring-boot-starter-parent from 3.0.1 to 3.0.2 (#577) ** Bump assertj-core from 3.24.1 to 3.24.2 (#576) ** Bump maven-checkstyle-plugin from 3.2.0 to 3.2.1 (#564) ** Bump junit-bom from 5.9.1 to 5.9.2 (#563) ** Bump maven-failsafe-plugin from 3.0.0-M7 to 3.0.0-M8 (#560) ** Bump reactor-bom from 2022.0.1 to 2022.0.2 (#559) ** Bump mockito.version from 4.11.0 to 5.0.0 (#558) ** Bump annotations from 23.1.0 to 24.0.0 (#557) ** Bump jna from 5.12.1 to 5.13.0 (#556) ** Bump maven-surefire-plugin from 3.0.0-M7 to 3.0.0-M8 (#555) ** Bump spring-data-neo4j from 7.0.0 to 7.0.1 (#562) ** Bump neo4j-java-driver from 5.3.1 to 5.4.0 (#561) ==== 🛠 Build * Improve configuration of the license plugin. * Move the fixed `module-info.java` somewhere out of IDEAs reach. ================================================ FILE: docs/appendix/2023.2.adoc ================================================ == 2023.2 === 2023.2.1 ==== 🚀 Features * Add adapter for Neo4j driver value. * Add support for `Duration` and `Period` literals. ==== 🔄️ Refactorings * Use `StringBuilder` with `Matcher#appendReplacement`. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump neo4j-cypher-javacc-parser from 5.5.0 to 5.6.0 (#657) ** Bump maven-install-plugin from 3.1.0 to 3.1.1 (#664) ** Bump joda-time from 2.12.2 to 2.12.4 (#663) ** Bump spring-boot-starter-parent from 3.0.4 to 3.0.5 (#662) ** Bump asm from 9.4 to 9.5 (#661) ** Bump checkstyle from 10.9.2 to 10.9.3 (#660) ** Bump maven-deploy-plugin from 3.1.0 to 3.1.1 (#659) ** Bump spring-data-neo4j from 7.0.3 to 7.0.4 (#658) ** Bump maven-resources-plugin from 3.3.0 to 3.3.1 (#656) === 2023.2.0 Thanks to @Andy2003 for his input on the 2023.2.0 release. The main topic of this release is https://github.com/neo4j-contrib/cypher-dsl/issues/596[adding support for semantic comparison]. The Cypher-DSLs builder method creates an AST, so that is in theory an excellent and doable request. The AST has originally been created as good enough means of rendering Cypher proper when we invented the Cypher-DSL for Spring Data Neo4j 6 back in 2019. Good enough here means that it has optimizing potential and a full-blown analysis is - at least at the moment - out of scope. Instead, we went with another approach: We added ways of normalizing * Variable names (identifiers for entities (nodes and relationships) and variables of lists and map comprehensions) * Parameter names * Aliases into generated names and optionally make the parsing of literal Cypher maps constant (maps sorted alphabetically and not by order of key appearance). This allows now for a "poor man's" semantic comparison. Imagine 2 Cypher-DSL `Statement` objects that you either created using the builder or parsed through our parser module. You can compare them like this: [source,java] ---- static boolean areSemanticallyEquivalent(Statement statement1, Map args1, Statement statement2, Map args2) { if (!areSemanticallyEquivalent(statement1, statement2)) { return false; } var mapping1 = statement1.getCatalog().getRenamedParameters(); var mapping2 = statement2.getCatalog().getRenamedParameters(); for (Map.Entry entry : mapping1.entrySet()) { String key1 = entry.getKey(); String mapped = entry.getValue(); String key2 = mapping2.entrySet().stream().filter(e -> e.getValue().equals(mapped)) .map(Map.Entry::getKey).findFirst().orElseThrow(); if (!args1.get(key1).equals(args2.get(key2))) { return false; } } return true; } ---- The catalog featured added in 2023.1.0 has been enhanced so that it can return now the mapping of the renamed parameters as well, allowing for inspection of parameters from different sources. Also thanks to @hindog for contributing map literals in #642 and to @sathishkumar294 for inspiring the new dedicated overloads for `type` and `labels` that now work with symbolic names, too. ==== 🚀 Features * Allow map literals to be parsed into sorted maps. (#644) * Add support for Map literals. (#642) * Use generated names for aliases too if possible. (#640) * Make the `Asterisk` proper identifiable. (#641) * Add `Cypher.withAll` to create a with clause importing all (`*`) variables. (#639) * Add overloads of `Functions.type` and `Functions.labels` taking in a symbolic name. (#633) * Add extended meta data and the ability to use generated variables. (#631) ==== 🔄️ Refactorings * Replace identifiers in list / pattern comprehensions, too. (#647) * Use scope for generated names. (#646) * Some general housekeeping. (#643 and #632) * Optimize structure of `UNWIND`. ==== 📖 Documentation * Add a list comprehension example. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump checkstyle from 10.8.1 to 10.9.2 (#653) ** Bump reactor-bom from 2022.0.4 to 2022.0.5 (#652) ** Bump asciidoctor-maven-plugin from 2.2.2 to 2.2.3 (#651) ** Bump maven-failsafe-plugin from 3.0.0-M9 to 3.0.0 (#650) ** Bump maven-surefire-plugin from 3.0.0-M9 to 3.0.0 (#649) ** Bump flatten-maven-plugin from 1.3.0 to 1.4.0 (#648) ** Bump moditect-maven-plugin from 1.0.0.RC2 to 1.0.0.RC3 (#637) ** Bump checkstyle from 10.8.0 to 10.8.1 (#638) ** Bump mockito.version from 5.1.1 to 5.2.0 (#636) ** Bump spring-boot-starter-parent from 3.0.3 to 3.0.4 (#629) ** Bump annotations from 24.0.0 to 24.0.1 (#628) ** Bump checker-qual from 3.31.0 to 3.32.0 (#627) ** Bump spring-data-neo4j from 7.0.2 to 7.0.3 (#626) ** Bump reactor-bom from 2022.0.3 to 2022.0.4 (#625) ** Bump japicmp-maven-plugin from 0.17.1 to 0.17.2 (#624) ==== 🛠 Build * Use correct version numbers for tags. * Replace symlink in gh-pages with static href. ================================================ FILE: docs/appendix/2023.3.adoc ================================================ == 2023.3 === 2023.3.2 ==== 🐛 Bug Fixes * Relationship chains didn't explicitly enter their elements. (#718) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump spring-boot-starter-parent from 3.0.6 to 3.1.0 (#711) ** Bump neo4j-cypher-javacc-parser from 5.7.0 to 5.8.0 (#709) ** Bump guava from 31.1-jre to 32.0.0-jre (#714) ** Bump checkstyle from 10.11.0 to 10.12.0 (#717) ** Bump asciidoctor-maven-plugin from 2.2.3 to 2.2.4 (#716) ** Bump maven-checkstyle-plugin from 3.2.2 to 3.3.0 (#715) ** Bump neo4j-java-driver from 5.8.0 to 5.9.0 (#713) ** Bump jackson-bom from 2.15.0 to 2.15.1 (#710) ** Bump maven-source-plugin from 3.2.1 to 3.3.0 (#708) ** Bump testcontainers.version from 1.18.0 to 1.18.1 (#704) ** Bump native-maven-plugin from 0.9.21 to 0.9.22 (#707) ** Bump reactor-bom from 2022.0.6 to 2022.0.7 (#706) ** Bump asciidoctorj-diagram from 2.2.7 to 2.2.8 (#705) ** Bump checkstyle from 10.10.0 to 10.11.0 (#703) ** Bump flatten-maven-plugin from 1.4.1 to 1.5.0 (#702) ** Bump spring-data-neo4j from 7.0.5 to 7.1.0 (#701) === 2023.3.1 ==== 📖 Documentation * Add an example combining existential sub-queries and custom procedure calls (#694) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump maven-failsafe-plugin from 3.0.0 to 3.1.0 (#698) ** Bump checker-qual from 3.33.0 to 3.34.0 (#697) ** Bump maven-surefire-plugin from 3.0.0 to 3.1.0 (#696) ** Bump moditect-maven-plugin from 1.0.0.RC3 to 1.0.0.Final (#695) ** Bump checkstyle from 10.9.3 to 10.10.0 (#692) ** Bump junit-bom from 5.9.2 to 5.9.3 (#691) ** Bump neo4j-java-driver from 5.7.0 to 5.8.0 (#690) ** Bump jacoco-maven-plugin from 0.8.9 to 0.8.10 (#689) ** Bump neo4j-cypher-javacc-parser from 5.6.0 to 5.7.0 (#685) ** Bump jackson-bom from 2.14.2 to 2.15.0 (#688) ** Bump spring-boot-starter-parent from 3.0.5 to 3.0.6 (#687) ** Bump maven-checkstyle-plugin from 3.2.1 to 3.2.2 (#686) ** Bump mockito.version from 5.3.0 to 5.3.1 (#684) === 2023.3.0 ==== 🚀 Features * Allow retrieval of relationship details from the catalog. ==== 🐛 Bug Fixes * Collect `LabelExpression`'s into the catalog, too. (#676) ==== 🔄️ Refactorings * Deprecate `Functions.id()` in favor of `elementId()`. (#678) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump native-maven-plugin from 0.9.20 to 0.9.21 (#683) ** Bump reactor-bom from 2022.0.5 to 2022.0.6 (#682) ** Bump asciidoctorj from 2.5.7 to 2.5.8 (#681) ** Bump mockito.version from 5.2.0 to 5.3.0 (#680) ** Bump spring-data-neo4j from 7.0.4 to 7.0.5 (#679) ** Bump jacoco-maven-plugin from 0.8.8 to 0.8.9 (#672) ** Bump testcontainers.version from 1.17.6 to 1.18.0 (#671) ** Bump maven-enforcer-plugin from 3.2.1 to 3.3.0 (#673) ** Bump asciidoctorj-diagram from 2.2.4 to 2.2.7 (#670) ** Bump checker-qual from 3.32.0 to 3.33.0 (#669) ** Bump flatten-maven-plugin from 1.4.0 to 1.4.1 (#668) ** Bump joda-time from 2.12.4 to 2.12.5 (#667) ** Bump neo4j-java-driver from 5.6.0 to 5.7.0 (#666) ==== 🛠 Build * Skip driver IT without docker. (#665) ================================================ FILE: docs/appendix/2023.4.adoc ================================================ == 2023.4 === 2023.4.0 2023.4.0 comes with a whole list of new features. As we deprecated two things (the `DEFAULT` dialect and `org.neo4j.cypherdsl.core.Cypher.use(org.neo4j.cypherdsl.core.SymbolicName, org.neo4j.cypherdsl.core.Statement)`), your project might break depending on your warning settings. The `DEFAULT` dialect is now `org.neo4j.cypherdsl.core.renderer.Dialect.NEO4J_4` (which keeps on being the default) and the `use` method has a new overload taking in an expression. You might need to explicitly cast here until we remove the deprecated method for good. This change was necessary to be able to put all functions in the `graph.*` namespace to use. Thanks to our reporters, contributors and supporters @xdelox, @ikwattro, @nmervaillie and Rohan Kharwar. ==== 🚀 Features * Introduce event to capture parsed literals. (#742) * Allow retrieval of literals. (#741) * Add builder methods for `FOREACH`. (#740) * Provide all functions in the `graph.*` namespaces. (#734) * Provide a way to fill parsed parameters with values. (#732) ==== 🐛 Bug Fixes * Strip off octal prefix. ==== 🔄️ Refactorings * Replace `DEFAULT` dialect with explicit `NEO4J_4` dialect. (#736) * Use sorted sets everywhere to keep orders of identifiables in the catalog close to users expectations. (#733) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump neo4j-cypher-javacc-parser from 5.8.0 to 5.9.0 (#743) ** Bump reactor-bom from 2022.0.7 to 2022.0.8 (#748) ** Bump mockito.version from 5.3.1 to 5.4.0 (#747) ** Bump spring-data-neo4j from 7.1.0 to 7.1.1 (#746) ** Bump native-maven-plugin from 0.9.22 to 0.9.23 (#745) ** Bump maven-shade-plugin from 3.4.1 to 3.5.0 (#744) ** Bump guava from 32.0.0-jre to 32.0.1-jre (#726) ** Bump maven-surefire-plugin from 3.1.0 to 3.1.2 (#725) ** Bump maven-failsafe-plugin from 3.1.0 to 3.1.2 (#724) ** Bump asciidoctorj-diagram from 2.2.8 to 2.2.9 (#723) ** Bump jackson-bom from 2.15.1 to 2.15.2 (#722) ** Bump testcontainers.version from 1.18.1 to 1.18.3 (#721) ** Bump checker-qual from 3.34.0 to 3.35.0 (#720) ** Bump asciidoctorj from 2.5.8 to 2.5.10 (#719) ================================================ FILE: docs/appendix/2023.5.adoc ================================================ == 2023.5 === 2023.5.0 Wait what, another minor? Yes, we added new methods to some builders for #753 and a new method in #756 that takes in the direction of a relationship. While these are not interfaces for you to implemented and the methods are defaulted, semver requires a minor bump nevertheless. Thanks to @israelstmz and ss with almost every release this year, to @ikwattro, for your input! ==== 🚀 Features * Provide non-builder method for creating relationships. (#756) * Allow `REMOVE` being used after `MERGE`. (#753) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump guava from 32.0.1-jre to 32.1.1-jre (#755) ** Bump neo4j-java-driver from 5.9.0 to 5.10.0 (#754) ** Bump checkstyle from 10.12.0 to 10.12.1 (#752) ** Bump spring-boot-starter-parent from 3.1.0 to 3.1.1 (#751) ================================================ FILE: docs/appendix/2023.6.adoc ================================================ == 2023.6 === 2023.6.1 ==== 🚀 Features * Add `isEmpty()`. (#784) ==== 🐛 Bug Fixes * Use unmodifiable maps instead of copyOf to allow `null` parameters. (#789) ==== 🔄️ Refactorings * Add tests demonstrating prevention of function calls. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#788) ** Bump org.neo4j.driver:neo4j-java-driver (#787) ** Bump org.checkerframework:checker-qual (#786) ** Bump org.springframework.boot:spring-boot-starter-parent (#783) ** Bump com.puppycrawl.tools:checkstyle (#782) ** Bump mockito.version from 5.4.0 to 5.5.0 (#781) ** Bump org.graalvm.buildtools:native-maven-plugin (#780) ** Bump testcontainers.version from 1.18.3 to 1.19.0 (#779) ** Bump org.apache.maven.plugins:maven-enforcer-plugin (#778) ** Bump org.springframework.data:spring-data-neo4j (#775) ** Bump org.neo4j:neo4j-cypher-javacc-parser (#774) ** Bump io.projectreactor:reactor-bom (#773) ** Bump com.tngtech.archunit:archunit from 1.0.1 to 1.1.0 (#772) ** Bump org.asciidoctor:asciidoctorj-diagram (#771) ** Bump org.graalvm.buildtools:native-maven-plugin (#770) Thanks to @zakjan for a great bug-report again and ofc @ikwattro for your ongoing support and feedback. === 2023.6.0 ==== 🚀 Features * Add callbacks for function and procedure invocations. (#764, #758, also backported as 2022.9.0, thanks to @ClemDoum for your input here) ==== 🔄️ Refactorings * Make TreeNode pretty printing prettier. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump com.google.guava:guava (#769) ** Bump org.checkerframework:checker-qual (#768) ** Bump com.puppycrawl.tools:checkstyle (#767) ** Bump org.asciidoctor:asciidoctorj-diagram (#766) ** Bump org.neo4j.driver:neo4j-java-driver (#765) ** Bump org.neo4j:neo4j-cypher-javacc-parser from 5.9.0 to 5.10.0 (#761) ** Bump checker-qual from 3.35.0 to 3.36.0 (#757) ** Bump spring-data-neo4j from 7.1.1 to 7.1.2 (#759) ** Bump reactor-bom from 2022.0.8 to 2022.0.9 (#760) ** Bump org.junit:junit-bom from 5.9.3 to 5.10.0 (#762) ** Bump org.springframework.boot:spring-boot-starter-parent (#763) ================================================ FILE: docs/appendix/2023.7.adoc ================================================ == 2023.7 === 2023.7.1 Thanks to @jrsperry for a great bug-report and the fix for `includesAll` and `includesAny`. ==== 🐛 Bug Fixes * Apply the correct includesAll and includesAny semantics (#819) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump mockito.version from 5.5.0 to 5.6.0 (#812) ** Bump org.springframework.data:spring-data-neo4j (#817) ** Bump com.google.guava:guava (#816) ** Bump org.checkerframework:checker-qual (#809) ** Bump net.bytebuddy:byte-buddy-parent from 1.14.8 to 1.14.9 (#818) ** Bump io.projectreactor:reactor-bom (#815) ** Bump com.fasterxml.jackson:jackson-bom (#814) ** Bump org.neo4j.driver:neo4j-java-driver (#811) ** Bump testcontainers.version from 1.19.0 to 1.19.1 (#810) ** Bump com.puppycrawl.tools:checkstyle (#807) ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#808) ** Bump org.ow2.asm:asm from 9.5 to 9.6 (#806) ** Bump com.mycila:license-maven-plugin from 4.2.rc2 to 4.3 (#805) ** Bump org.springframework.boot:spring-boot-starter-parent (#804) ** Bump org.apache.maven.plugins:maven-shade-plugin (#803) ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#802) ** Bump org.graalvm.buildtools:native-maven-plugin (#800) ** Bump io.projectreactor:reactor-bom (#799) ** Bump org.neo4j:neo4j-cypher-javacc-parser (#798) ** Bump org.springframework.data:spring-data-neo4j (#797) ** Bump org.sonarsource.scanner.maven:sonar-maven-plugin (#796) ** Bump org.asciidoctor:asciidoctorj-diagram (#795) ** Bump org.apache.maven.plugins:maven-javadoc-plugin (#794) ** Bump com.opencsv:opencsv from 5.7.1 to 5.8 (#793) ** Bump org.apache.maven.plugins:maven-enforcer-plugin (#792) ** Bump org.graalvm.buildtools:native-maven-plugin (#791) ==== 🛠 Build * Upgrade Maven wrapper to ASF wrapper 3.2.0 and Maven 3.9.4 * Build and release with Java 21 targeting Java 17. (#801) === 2023.7.0 ==== 🚀 Features * Add basic schema enforcement when rendering statements. * Add basic support for parsing negated types. * Add basic support for parenthesized path patterns. ================================================ FILE: docs/appendix/2023.8.adoc ================================================ == 2023.8 === 2023.8.1 ==== 🐛 Bug Fixes * Including aliasing in scoping strategy, too. (#839) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.moditect:moditect-maven-plugin (#843) ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#845) ** Bump com.tngtech.archunit:archunit from 1.1.0 to 1.2.0 (#846) ** Bump org.checkerframework:checker-qual (#844) ** Bump mockito.version from 5.6.0 to 5.7.0 (#842) ** Bump org.junit:junit-bom from 5.10.0 to 5.10.1 (#841) === 2023.8.0 This minor release is drop-in compatible with 2023.7, but it adds support for using `COLLECT {}` sub-queries, which required enhancing some interfaces (that only we should implement, but still, it's a minor upgrade then). The price for finding the most bugs in the scoping strategy applied for sub-queries in this release goes to @Andy2003, thank you! ==== 🚀 Features * Add support for `COLLECT` subqueries. (#831) * Make fieldname generator configurable. (#830) ==== 🐛 Bug Fixes * Make sure local scope is cleared after leaving subquery expressions. (#837) * Recognize entities defined in sub-queries correctly. (#827) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.apache.maven.plugins:maven-surefire-plugin (#836) ** Bump org.apache.maven.plugins:maven-failsafe-plugin (#835) ** Bump org.apache.maven.plugins:maven-checkstyle-plugin (#834) ** Bump org.neo4j.driver:neo4j-java-driver (#833) ** Bump org.neo4j:neo4j-cypher-javacc-parser from 5.12.0 to 5.13.0 (#821) ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#823) ** Bump org.graalvm.buildtools:native-maven-plugin (#824) ** Bump org.jacoco:jacoco-maven-plugin from 0.8.10 to 0.8.11 (#822) ** Bump org.springframework.boot:spring-boot-starter-parent (#820) ==== 🛠 Build * Remove explicit management of bytebuddy. (#828) ================================================ FILE: docs/appendix/2023.9.adoc ================================================ == 2023.9 === 2023.9.8 ==== 🐛 Bug Fixes * Don’t introduce new names in pattern expressions. (#1017) === 2023.9.7 Not everything goes as planned ;) Another 2023.9 release, enjoy. ==== 🐛 Bug Fixes * Use proper generics when generating extensible models. (#974) * Treat the asterisk correctly in an importing with. (#973) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.neo4j.driver:neo4j-java-driver (#978) ** Bump org.apache.maven.plugins:maven-shade-plugin (#977) ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#976) ** Bump com.puppycrawl.tools:checkstyle (#975) ** Bump neo4j.version from 5.18.1 to 5.19.0 (#965) ** Bump com.tngtech.archunit:archunit from 1.2.1 to 1.3.0 (#966) ** Bump org.springframework.data:spring-data-neo4j (#967) ** Bump io.projectreactor:reactor-bom (#968) ** Bump org.springframework.boot:spring-boot-starter-parent (#971) ** Bump org.apache.maven.plugins:maven-jar-plugin (#972) ==== 🛠 Build * Fix doc generation. === 2023.9.6 **Heads up** this is the last _planned_ release in the 2023.x series. The next release will be 2024.0.0, in which all deprecations apart from `internalId` on nodes and relationships will be removed. With that change, the Cypher-DSL will have one single entry point for all operations: `org.neo4j.cypherdsl.core.Cypher`. Nothing will change in terms of JDK compatibility. Cypher-DSL 2024 will still require JDK 17, and will run just fine on JDK 21 and higher. ==== 🚀 Features * Allow calling of raw Cypher strings in sub queries. (#961) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.jacoco:jacoco-maven-plugin from 0.8.11 to 0.8.12 (#960) ** Bump org.neo4j.driver:neo4j-java-driver (#959) ** Bump org.apache.maven.plugins:maven-source-plugin (#958) ** Bump com.puppycrawl.tools:checkstyle (#957) ** Bump org.moditect:moditect-maven-plugin (#956) ** Bump neo4j.version from 5.18.0 to 5.18.1 (#955) ** Bump org.ow2.asm:asm from 9.6 to 9.7 (#954) ** Bump org.springframework.boot:spring-boot-starter-parent (#953) === 2023.9.5 ==== 🚀 Features * Add a bom project ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.springframework.data:spring-data-neo4j ** Bump neo4j.version from 5.17.0 to 5.18.0 (#944) ** Bump com.google.guava:guava (#952) ** Bump io.projectreactor:reactor-bom (#951) ** Bump org.asciidoctor:asciidoctorj from 2.5.11 to 2.5.12 (#950) ** Bump com.fasterxml.jackson:jackson-bom (#949) ** Bump org.sonarsource.scanner.maven:sonar-maven-plugin (#947) ** Bump org.moditect:moditect-maven-plugin (#946) ** Bump com.puppycrawl.tools:checkstyle (#945) ** Bump testcontainers.version from 1.19.6 to 1.19.7 (#942) ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#941) ** Bump com.fasterxml.jackson:jackson-bom (#940) ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#938) ** Bump org.neo4j.driver:neo4j-java-driver (#937) ** Bump com.puppycrawl.tools:checkstyle (#936) ** Bump mockito.version from 5.10.0 to 5.11.0 (#935) === 2023.9.4 Change parser license to The Apache Software License, Version 2.0 (same as the Neo4j JavaCC based parser, which we use under the hoods). Thanks a lot @hindog, @fbiville and @Andy2003 for agreeing to relicense your contributions, too. ==== 📖 Documentation * Use `compile` scope in Gradle dependencies. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump neo4j.version from 5.16.0 to 5.17.0 (#933) ** Bump testcontainers.version from 1.19.5 to 1.19.6 (#934) ** Bump org.apache.maven.plugins:maven-shade-plugin (#932) ** Bump org.springframework.boot:spring-boot-starter-parent (#931) ** Bump org.graalvm.buildtools:native-maven-plugin (#930) ** Bump org.codehaus.mojo:exec-maven-plugin (#929) ** Bump org.asciidoctor:asciidoctor-maven-plugin (#927) ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#928) ** Bump org.springframework.data:spring-data-neo4j (#926) ** Bump io.projectreactor:reactor-bom (#925) ** Bump org.asciidoctor:asciidoctorj-diagram (#924) ** Bump org.graalvm.buildtools:native-maven-plugin (#919) ** Bump org.assertj:assertj-core from 3.25.2 to 3.25.3 (#918) ** Bump org.asciidoctor:asciidoctorj-diagram (#916) ** Bump org.junit:junit-bom from 5.10.1 to 5.10.2 (#915) ** Bump testcontainers.version from 1.19.4 to 1.19.5 (#923) ** Bump org.asciidoctor:asciidoctor-maven-plugin (#922) ** Bump org.neo4j.driver:neo4j-java-driver (#920) ** Bump joda-time:joda-time from 2.12.6 to 2.12.7 (#917) === 2023.9.3 ==== 🚀 Features * Add a parser option to unify the direction of relationships. (#906) ==== 🐛 Bug Fixes * Driving symbolic names for list predicate function must not be scoped. (#905) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump neo4j.version from 5.15.0 to 5.16.0 (#910) ** Bump org.assertj:assertj-core from 3.25.1 to 3.25.2 (#914) ** Bump com.querydsl:querydsl-core from 5.0.0 to 5.1.0 (#913) ** Bump mockito.version from 5.9.0 to 5.10.0 (#912) ** Bump com.puppycrawl.tools:checkstyle (#911) ** Bump testcontainers.version from 1.19.3 to 1.19.4 (#909) ** Bump org.springframework.boot:spring-boot-starter-parent (#904) === 2023.9.2 *Please read the updated stance wrt calver/semver in the README. This release is current and the first one in 2024, including some new, additive and non-breaking features contributed by @Andy2003* ==== 🚀 Features * Add possibility to define inheritance for the static model. (#894) ==== 🔄️ Refactorings * Allow unit-subqueries. (#895) ==== 📖 Documentation * Clarify calver and semver wording. ==== 🧰 Tasks * Extend license header to 2024. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.asciidoctor:asciidoctor-maven-plugin (#902) ** Bump org.apache.maven.plugins:maven-failsafe-plugin (#901) ** Bump mockito.version from 5.8.0 to 5.9.0 (#900) ** Bump org.codehaus.mojo:flatten-maven-plugin (#899) ** Bump org.springframework.data:spring-data-neo4j (#898) ** Bump io.projectreactor:reactor-bom (#897) ** Bump org.apache.maven.plugins:maven-surefire-plugin (#896) ** Bump org.assertj:assertj-core from 3.25.0 to 3.25.1 (#893) ** Bump org.neo4j.driver:neo4j-java-driver (#892) ** Bump joda-time:joda-time from 2.12.5 to 2.12.6 (#891) ** Bump org.asciidoctor:asciidoctorj-diagram (#890) ** Bump com.fasterxml.jackson:jackson-bom (#887) ** Bump org.assertj:assertj-core from 3.24.2 to 3.25.0 (#889) ** Bump com.puppycrawl.tools:checkstyle (#888) ** Bump org.asciidoctor:asciidoctorj from 2.5.10 to 2.5.11 (#886) ** Bump com.google.guava:guava (#885) ** Bump org.springframework.boot:spring-boot-starter-parent (#884) === 2023.9.1 ==== 🐛 Bug Fixes * for #840 add missing casts in constructor super calls for relations with generic start or / and end node (#866) ==== 🔄️ Refactorings * Officially allow label value to be accessed. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump neo4j.version from 5.14.0 to 5.15.0 (#880) ** Bump org.checkerframework:checker-qual (#883) ** Bump io.projectreactor:reactor-bom (#882) ** Bump org.springframework.data:spring-data-neo4j (#881) ** Bump org.apache.maven.plugins:maven-surefire-plugin (#879) ** Bump org.apache.maven.plugins:maven-failsafe-plugin (#878) ** Bump com.puppycrawl.tools:checkstyle (#876) ** Bump net.java.dev.jna:jna from 5.13.0 to 5.14.0 (#877) ** Bump org.checkerframework:checker-qual (#875) ** Bump org.apache.maven.plugins:maven-javadoc-plugin (#874) ** Bump org.neo4j.driver:neo4j-java-driver (#873) ** Bump com.tngtech.archunit:archunit from 1.2.0 to 1.2.1 (#872) ** Bump mockito.version from 5.7.0 to 5.8.0 (#871) ** Bump neo4j.version from 5.13.0 to 5.14.0 (#868) ** Bump testcontainers.version from 1.19.2 to 1.19.3 (#867) === 2023.9.0 2023.9 contains several new features: It brings support for parsing and rendering https://neo4j.com/docs/cypher-manual/current/patterns/concepts/#quantified-path-patterns[Quantified Path Patterns (QPP)], shifts to a single, easy to find main entry point to the DSL via just `Cypher` and makes the static code generator a bit more powerful. While QPP are a powerful feature (have a look at https://medium.com/neo4j/getting-from-denmark-hill-to-gatwick-airport-with-quantified-path-patterns-bed38da27ca1["Getting From Denmark Hill to Gatwick Airport With Quantified Path Patterns"]) to see what you can do with them, I find them hard to read, with all the parentheses and I did not expect them to really fit in well with our builder. However, it turned out that the elements we need to provide in our own AST to render what we parsed do work well: If you decide to build QPP with Cypher-DSL, you can now quantify relationship patterns as a whole or only the relationship, making up already for many uses cases. The single entry point to our API makes the whole system a lot more discoverable. @lukaseder did create a ticket for that in the beginning of 2023 and if someone knows the importance of that, he is that someone as the creator of jOOQ. Thank you, Lukas and of course earlier this week, @Andy2003 for actually doing the work of adding all those methods to `Cypher`. If you don't care about deprecation warnings, 2023.9.0 will be a drop-in replacement. The existing entry points won't go away until the next major release, in which they will be made package private. Until then, they are deprecated. It my sound like a broken record by now, but again: Thank you, @zakjan and @ikwattro for your input on QPP, now we are waiting for your bug-reports. ==== 🚀 Features * Provide a single DSL API entry point. (#862) * Allow parsing of `collect` expression. (#861) * Add support for quantified path patterns. (#860) * Add support for predicates inside pattern elements. (#859) * Add ability to add additional factory methods for relationship models to a node in the static model (#840) ==== 📖 Documentation * Add example how to access properties of a list element. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump spring-boot-starter-parent from 3.1.5 to 3.2.0 ** Bump auto-common to 1.2.2 ** Bump errorprone from 2.12.1 to 2.23.0 ** Bump sortpom from 2.15.0 to 3.3.0 ** Bump com.opencsv:opencsv from 5.8 to 5.9 ** Bump testcontainers.version from 1.19.2 to 1.19.3 ** Bump testcontainers.version from 1.19.1 to 1.19.2 (#857) ** Bump org.codehaus.mojo:exec-maven-plugin (#856) ** Bump io.projectreactor:reactor-bom (#855) ** Bump com.puppycrawl.tools:checkstyle (#854) ** Bump com.fasterxml.jackson:jackson-bom (#853) ** Bump org.jetbrains:annotations from 24.0.1 to 24.1.0 (#852) ** Bump org.springframework.data:spring-data-neo4j (#851) ** Bump org.apache.maven.plugins:maven-surefire-plugin (#850) ** Bump org.apache.maven.plugins:maven-failsafe-plugin (#849) ** Bump org.apache.maven.plugins:maven-javadoc-plugin (#848) ==== 🛠 Build * Address several warnings appearing in the build. (#847) ================================================ FILE: docs/appendix/2024.0.adoc ================================================ == 2024.0 === 2024.0.3 This is mostly a release that upgrades dependency, with the noteworthy exception of having now a unified property accessor. Thanks to @fbiville for his contribution to the documentation, @loveleif for his support and making the latest Neo4j Cypher parser and its improvements work on the module path again and to @Andy2003 for his latest suggestion of the unified property accessor. ==== 🚀 Features * Add a common interface `PropertyAccessor` (#908) ==== 📖 Documentation * Update latest version supporting JDK 8 in README. (#1041) * Fix documentation link in README (#1026) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump com.google.guava:guava (#1049) ** Bump org.junit:junit-bom from 5.10.3 to 5.11.0 (#1048) ** Bump org.apache.maven.plugins:maven-failsafe-plugin (#1047) ** Bump org.codehaus.mojo:exec-maven-plugin (#1046) ** Bump org.springframework.data:spring-data-neo4j (#1045) ** Bump io.projectreactor:reactor-bom (#1044) ** Bump org.apache.maven.plugins:maven-surefire-plugin (#1043) ** Bump org.apache.maven.plugins:maven-site-plugin (#1042) ** Bump org.codehaus.mojo:exec-maven-plugin (#1040) ** Bump org.checkerframework:checker-qual (#1039) ** Bump testcontainers.version from 1.20.0 to 1.20.1 (#1038) ** Bump neo4j.version from 5.20.0 to 5.22.0 (#1036) ** Bump org.neo4j.driver:neo4j-java-driver (#1037) ** Bump org.springframework.boot:spring-boot-starter-parent (#1035) ** Bump testcontainers.version from 1.19.8 to 1.20.0 (#1034) ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#1033) ** Bump org.apache.maven.plugins:maven-javadoc-plugin (#1032) ** Bump org.apache.maven.plugins:maven-surefire-plugin (#1030) ** Bump org.apache.maven.plugins:maven-failsafe-plugin (#1029) ** Bump org.springframework.data:spring-data-neo4j (#1028) ** Bump org.assertj:assertj-core from 3.26.0 to 3.26.3 (#1027) === 2024.0.2 🎉 The big party release! 🎉 With this release we move the repository from http://github.com/neo4j-contrib/[github.com/neo4j-contrib] to https://github.com/neo4j-contrib[github.com/neo4j] with Neo4j adding Cypher-DSL to the list of supported modules. What we have now on our todo list is to incorporate our http://neo4j.github.io/cypher-dsl[documentation] into the official Neo4j docs, but apart from that, little will change immediate. Even our Maven coordinates will stay the same. You can however rely on the fact that Cypher-DSL is not going anywhere anytime soon. Thanks to @stumoore for supporting this! ==== 🐛 Bug Fixes * Don’t introduce new names in pattern expressions. (#1017) ==== 🧹 Housekeeping * Dependency upgrades ** Bump io.projectreactor:reactor-bom (#1025) ** Bump org.checkerframework:checker-qual (#1024) ** Bump com.fasterxml.jackson:jackson-bom (#1023) ** Bump org.moditect:moditect-maven-plugin (#1019) ** Bump org.asciidoctor:asciidoctorj-diagram (#1021) ** Bump org.junit:junit-bom from 5.10.2 to 5.10.3 (#1020) ** Bump org.neo4j.driver:neo4j-java-driver (#1018) ** Bump org.springframework.boot:spring-boot-starter-parent (#1016) ** Bump org.apache.maven.plugins:maven-jar-plugin (#1015) ==== 🛠 Build * Change license for examples that depend on the parser to Apache 2.0, too. === 2024.0.1 This is a pure bug-fix release. Thanks to @Andy2003 for spotting yet another scoping issue. ==== 🐛 Bug Fixes * Include implicit new variables in seed for name generator. (#999) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump io.projectreactor:reactor-bom (#1013) ** Bump org.springframework.data:spring-data-neo4j (#1012) ** Bump org.apache.maven.plugins:maven-failsafe-plugin (#1011) ** Bump org.apache.maven.plugins:maven-surefire-plugin (#1010) === 2024.0.0 We're finally going 2024 with this release. The biggest new feature in this release is that we now allow chaining statements that end with a `YIELD` clause, which lets you compose complex queries in a nicer way. We also removed all deprecated constructs and methods we accumulated until now. If you ignored the warnings until now, you cannot do any longer. The latest SDN release is prepared for this Cypher-DSL release already, as we did the necessary changes over there already (See this https://github.com/spring-projects/spring-data-neo4j/commit/2861e771333d8b9443026669763ddccd5be7659d[commit] for the necessary changes for example). ==== 🚀 Features * Allow chaining statements to yielding calls. (#964) * Allow chainable foreach. (#988) ==== 🐛 Bug Fixes * Actually make `sortOrderDefaultExpression` test what it is supposed to test. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.apache.maven.plugins:maven-shade-plugin (#1006) ** Bump org.neo4j.driver:neo4j-java-driver (#1005) ** Bump org.sonarsource.scanner.maven:sonar-maven-plugin (#1004) ** Bump com.google.guava:guava (#1003) ** Bump org.apache.maven.plugins:maven-enforcer-plugin (#1002) ** Bump org.apache.maven.plugins:maven-javadoc-plugin (#1001) ** Bump com.github.ekryd.sortpom:sortpom-maven-plugin (#992) ** Bump org.assertj:assertj-core from 3.25.3 to 3.26.0 (#998) ** Bump org.springframework.boot:spring-boot-starter-parent (#997) ** Bump com.puppycrawl.tools:checkstyle (#996) ** Bump org.codehaus.mojo:exec-maven-plugin (#995) ** Bump org.asciidoctor:asciidoctorj from 2.5.12 to 2.5.13 (#993) ** Bump org.springframework.data:spring-data-neo4j (#991) ** Bump io.projectreactor:reactor-bom (#990) ** Bump org.graalvm.buildtools:native-maven-plugin (#989) ** Bump com.mycila:license-maven-plugin from 4.3 to 4.5 (#987) ** Bump mockito.version from 5.11.0 to 5.12.0 (#986) ** Bump testcontainers.version from 1.19.7 to 1.19.8 (#985) ** Bump com.google.guava:guava (#982) ** Bump org.checkerframework:checker-qual (#984) ** Bump org.apache.maven.plugins:maven-install-plugin (#983) ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#981) ** Bump com.fasterxml.jackson:jackson-bom (#980) ** Bump org.apache.maven.plugins:maven-deploy-plugin (#979) ================================================ FILE: docs/appendix/2024.1.adoc ================================================ == 2024.1 === 2024.1.0 This new minor release adds a third dialect: Neo4j 5.23, catering for Neo4j >= 5.23. The initial release will rewrite sub-query `CALL` statements with importing `WITH` into sub-queries with variable scoped `CALL` clause. While the former is still available in Neo4j 5.23, it will cause deprecation warnings, hence, if you want to get rid of those, change the dialect accordingly. No need to rewrite any query on your own. ==== 🚀 Features * Add a Neo4j 5.23 dialect. (#1069) ==== 🐛 Bug Fixes * Unwrap `PatternList` proper if unique. ==== 📖 Documentation * Add an example of building a stand-alone `WHERE` clause. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.checkerframework:checker-qual (#1072) ** Bump actions/download-artifact from 1 to 4.1.7 in /.github/workflows (#1070) ** Bump org.apache.maven.plugins:maven-surefire-plugin (#1068) ** Bump mockito.version from 5.12.0 to 5.13.0 (#1067) ** Bump org.apache.maven.plugins:maven-failsafe-plugin (#1066) ** Bump org.neo4j.driver:neo4j-java-driver (#1065) ** Bump com.puppycrawl.tools:checkstyle (#1064) ** Bump org.apache.maven.plugins:maven-javadoc-plugin (#1063) ** Bump neo4j.version from 5.22.0 to 5.23.0 (#1053) ** Bump org.apache.maven.plugins:maven-install-plugin (#1057) ** Bump org.asciidoctor:asciidoctorj from 2.5.13 to 3.0.0 (#1056) ** Bump org.springframework.boot:spring-boot-starter-parent (#1055) ** Bump org.apache.maven.plugins:maven-deploy-plugin (#1054) ** Bump com.puppycrawl.tools:checkstyle (#1052) ** Bump org.apache.maven.plugins:maven-checkstyle-plugin (#1051) ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#1050) ================================================ FILE: docs/appendix/2024.2.adoc ================================================ == 2024.2 === 2024.2.0 A new minor release so shortly after the last? We changed the behaviour of the renderer when using generated names. Before and upto including 2024.1.0 we didn't allow aliases to be reused. If in the original query alias `x` would have been legally reused, we would not have reused them with generated names, i.e. we would have usd `v0` and then `v1`. I think this is wrong, and we changed this behaviour, hence a new minor is due. If you are using generated names, you can opt out of this behaviour like this: [source,java] ---- var generatedNamesConfig = EnumSet.complementOf(EnumSet.of(Configuration.GeneratedNames.REUSE_ALIASES)); var renderer = Renderer.getRenderer(Configuration.newConfig() .withGeneratedNames(generatedNamesConfig) .build()); ---- Apart from that, this is a drop-in replacement for 2024.1 and 2024.0. Congratulations to @ali-ince for contributing his first feature. And last but not least, @Andy2003 gave our AST factory and the scoping mechanism a real good test run, and we have been able to fix several bugs again. Thank you! ==== 🚀 Features * Apply sorting of maps when parsing to projections, too. (#1085) * Add call raw cypher to top level entry point (#1073) ==== 🐛 Bug Fixes * Don’t introduce new aliases on for each alias used. (#1084) * Export return variables from unions into scope proper. (#1075) * Correctly compute imports. (#1076) ==== 📖 Documentation * Add example test for `LabelExpression`. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump neo4j.version from 5.23.0 to 5.24.0 (#1092) ** Bump com.google.guava:guava (#1095) ** Bump com.mycila:license-maven-plugin from 4.5 to 4.6 (#1090) ** Bump mockito.version from 5.13.0 to 5.14.0 (#1094) ** Bump org.neo4j.driver:neo4j-java-driver (#1093) ** Bump com.fasterxml.jackson:jackson-bom (#1091) ** Bump com.puppycrawl.tools:checkstyle (#1089) ** Bump org.jetbrains:annotations from 24.1.0 to 25.0.0 (#1088) ** Bump org.junit:junit-bom from 5.11.0 to 5.11.1 (#1087) ** Bump org.springframework.boot:spring-boot-starter-parent (#1083) ** Bump net.java.dev.jna:jna from 5.14.0 to 5.15.0 (#1082) ** Bump joda-time:joda-time from 2.12.7 to 2.13.0 (#1081) ** Bump io.projectreactor:reactor-bom (#1080) ** Bump org.springframework.data:spring-data-neo4j (#1079) ** Bump org.graalvm.buildtools:native-maven-plugin (#1078) ================================================ FILE: docs/appendix/2024.3.adoc ================================================ == 2024.3 === 2024.3.2 ==== 🐛 Bug Fixes * Restore wrongly deleted `module-info.java` ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.springframework.data:spring-data-neo4j (#1150) ** Bump io.projectreactor:reactor-bom (#1149) ** Bump com.puppycrawl.tools:checkstyle (#1148) === 2024.3.1 ==== 🐛 Bug Fixes * `$` must be escaped in future Cypher versions. === 2024.3.0 Thanks to @nk-coding for the idea of supporting label expressions as conditions. ==== 🚀 Features * Add `hasLabels(LabelExpression labels)` for nodes. (#1146) ==== 📖 Documentation * Fix typo (#1127) * Add another example for list comprehensions. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump neo4j.version from 5.25.1 to 5.26.0 (#1142) ** Bump org.checkerframework:checker-qual (#1144) ** Bump org.graalvm.buildtools:native-maven-plugin (#1145) ** Bump org.apache.maven.plugins:maven-javadoc-plugin (#1143) ** Bump com.puppycrawl.tools:checkstyle (#1140) ** Bump com.fasterxml.jackson:jackson-bom (#1139) ** Bump org.neo4j.driver:neo4j-java-driver (#1138) ** Bump testcontainers.version from 1.20.3 to 1.20.4 (#1137) ** Bump org.springframework.data:spring-data-neo4j from 7.3.5 to 7.4.0 (#1133) ** Bump io.projectreactor:reactor-bom (#1136) ** Bump org.neo4j.driver:neo4j-java-driver (#1135) ** Bump org.asciidoctor:asciidoctor-maven-plugin (#1134) ** Bump org.sonarsource.scanner.maven:sonar-maven-plugin (#1132) ** Bump org.neo4j.driver:neo4j-java-driver (#1131) ** Bump org.apache.maven.plugins:maven-site-plugin (#1130) ** Bump org.springframework.boot:spring-boot-starter-parent (#1129) ** Bump com.puppycrawl.tools:checkstyle (#1128) ** Bump neo4j.version from 5.24.2 to 5.25.1 (#1117) ** Bump org.apache.maven.plugins:maven-surefire-plugin (#1121) ** Bump com.puppycrawl.tools:checkstyle (#1120) ** Bump org.apache.maven.plugins:maven-checkstyle-plugin (#1119) ** Bump org.asciidoctor:asciidoctor-maven-plugin (#1122) ** Bump testcontainers.version from 1.20.2 to 1.20.3 (#1123) ** Bump com.fasterxml.jackson:jackson-bom (#1124) ** Bump org.apache.maven.plugins:maven-javadoc-plugin (#1125) ** Bump org.apache.maven.plugins:maven-failsafe-plugin (#1126) ** Bump org.checkerframework:checker-qual (#1118) ** Bump com.puppycrawl.tools:checkstyle (#1114) ** Bump org.neo4j.driver:neo4j-java-driver (#1112) ** Bump org.junit:junit-bom from 5.11.2 to 5.11.3 (#1116) ** Bump org.codehaus.mojo:exec-maven-plugin (#1115) ** Bump io.projectreactor:reactor-bom (#1111) ** Bump org.jetbrains:annotations from 25.0.0 to 26.0.1 (#1110) ** Bump org.springframework.data:spring-data-neo4j (#1109) ** Bump neo4j.version from 5.24.1 to 5.24.2 (#1108) ** Bump mockito.version from 5.14.1 to 5.14.2 (#1107) ** Bump org.checkerframework:checker-qual (#1105) ** Bump org.junit:junit-bom from 5.11.1 to 5.11.2 (#1104) ** Bump org.apache.maven.plugins:maven-javadoc-plugin (#1103) ** Bump org.apache.maven.plugins:maven-failsafe-plugin (#1102) ** Bump org.apache.maven.plugins:maven-surefire-plugin (#1101) ** Bump mockito.version from 5.14.0 to 5.14.1 (#1100) ** Bump neo4j.version from 5.24.0 to 5.24.1 (#1099) ** Bump org.checkerframework:checker-qual (#1098) ** Bump testcontainers.version from 1.20.1 to 1.20.2 (#1097) ** Bump org.ow2.asm:asm from 9.7 to 9.7.1 (#1096) ================================================ FILE: docs/appendix/2024.4.adoc ================================================ == 2024.4 === 2024.4.0 Thanks to @meistermeier for both the bugfix in scoping of existential subqueries and kicking off the support for the `FINISH` clause, to finish of 2024 in style. Happy holidays and all the best for 2025, we are looking forward helping more projects with Cypher-DSL 2025 next year. ==== 🚀 Features * Add support for creating and parsing the `FINISH` clause. ==== 🐛 Bug Fixes * Fix scoping issue with existential subquery (#1151) === 2024.4.1 ==== 🧰 Tasks * Extend license header to 2025. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump neo4j.version from 5.26.1 to 5.26.2 (#1176) ** Bump org.checkerframework:checker-qual (#1177) ** Bump org.neo4j.driver:neo4j-java-driver (#1175) ** Bump joda-time:joda-time from 2.13.0 to 2.13.1 (#1174) ** Bump org.graalvm.buildtools:native-maven-plugin (#1173) ** Bump org.springframework.boot:spring-boot-starter-parent (#1172) ** Bump com.puppycrawl.tools:checkstyle (#1171) ** Bump org.jetbrains:annotations from 26.0.1 to 26.0.2 (#1170) ** Bump org.assertj:assertj-core from 3.27.2 to 3.27.3 (#1168) ** Bump io.projectreactor:reactor-bom (#1167) ** Bump org.springframework.data:spring-data-neo4j (#1166) ** Bump neo4j.version from 5.26.0 to 5.26.1 (#1165) ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#1164) ** Bump com.opencsv:opencsv from 5.9 to 5.10 (#1163) ** Bump com.puppycrawl.tools:checkstyle (#1159) ** Bump org.springframework.boot:spring-boot-starter-parent (#1154) ** Bump net.java.dev.jna:jna from 5.15.0 to 5.16.0 (#1155) ** Bump org.junit:junit-bom from 5.11.3 to 5.11.4 (#1157) ** Bump com.google.guava:guava (#1158) ** Bump org.checkerframework:checker-qual (#1160) ** Bump mockito.version from 5.14.2 to 5.15.2 (#1161) ** Bump org.assertj:assertj-core from 3.26.3 to 3.27.2 (#1162) ==== 🛠 Build * Add announcement notifier. ================================================ FILE: docs/appendix/2024.5.adoc ================================================ == 2024.5 === 2024.5.2 Take note that this release deprecates direct integrations with the Neo4j Java Driver. They have never been used widely and can be easily added when needed in calling code. Removing them from Cypher-DSL avoid unnessary complexity in dependency management. ==== 🔄️ Refactorings refactor: Deprecate all driver integrations from Cypher-DSL side. (#1211) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.checkerframework:checker-qual (#1233) ** Bump com.puppycrawl.tools:checkstyle (#1232) ** Bump org.springframework.boot:spring-boot-starter-parent (#1231) ** Bump com.google.guava:guava (#1230) ** Bump io.projectreactor:reactor-bom (#1229) ** Bump org.junit:junit-bom from 5.12.1 to 5.12.2 (#1228) ** Bump org.jacoco:jacoco-maven-plugin from 0.8.12 to 0.8.13 (#1226) ** Bump org.asciidoctor:asciidoctorj-diagram (#1223) ** Bump mockito.version from 5.16.1 to 5.17.0 (#1222) ** Bump neo4j.version from 5.26.4 to 5.26.5 (#1221) ** Bump org.apache.maven.plugins:maven-failsafe-plugin (#1220) ** Bump org.asciidoctor:asciidoctor-maven-plugin (#1219) ** Bump org.apache.maven.plugins:maven-surefire-plugin (#1218) ** Bump org.springframework.boot:spring-boot-starter-parent (#1217) ** Bump com.google.guava:guava (#1216) ** Bump com.puppycrawl.tools:checkstyle (#1215) ** Bump org.sonarsource.scanner.maven:sonar-maven-plugin (#1214) ** Bump joda-time:joda-time from 2.13.1 to 2.14.0 (#1213) ** Bump org.ow2.asm:asm from 9.7.1 to 9.8 (#1212) ** Bump testcontainers.version from 1.20.5 to 1.20.6 (#1207) ** Bump org.junit:junit-bom from 5.12.0 to 5.12.1 (#1209) ** Bump io.projectreactor:reactor-bom (#1210) ** Bump org.graalvm.buildtools:native-maven-plugin (#1208) ** Bump net.java.dev.jna:jna from 5.16.0 to 5.17.0 (#1206) ** Bump org.springframework.data:spring-data-neo4j (#1205) ** Bump com.mycila:license-maven-plugin from 4.6 to 5.0.0 (#1204) ** Bump com.google.guava:guava (#1203) ** Bump neo4j.version from 5.26.3 to 5.26.4 (#1202) ** Bump mockito.version from 5.16.0 to 5.16.1 (#1201) === 2024.5.1 ==== 🐛 Bug Fixes * Use a `Pattern` typed subtree for a list of patterns in an existential subquery. (#1200) ==== 📖 Documentation * Add an example for setting multiple labels. (#1199) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.neo4j.driver:neo4j-java-driver (#1198) ** Bump com.puppycrawl.tools:checkstyle (#1197) ** Bump org.checkerframework:checker-qual (#1195) ** Bump mockito.version from 5.15.2 to 5.16.0 (#1194) ** Bump org.codehaus.mojo:flatten-maven-plugin (#1192) ** Bump org.apache.maven.plugins:maven-deploy-plugin (#1191) ** Bump com.fasterxml.jackson:jackson-bom (#1190) ** Bump neo4j.version from 5.26.2 to 5.26.3 (#1189) ** Bump org.apache.maven.plugins:maven-install-plugin (#1188) ** Bump org.jreleaser:jreleaser-maven-plugin (#1187) === 2024.5.0 This new minor release does not have any breaking changes. A new dialect dubbed 5.26 has been introduced. When configured, the renderer will prefix all queries with `CYPHER 5`, which is compatible with Neo4j 5.26 as well as the main line 2025.x. It ensures that a future version of Neo4j will treat all queries generated with Cypher-DSL as Cypher 5, even when newer Cypher versions become available. ==== 🚀 Features * Introduce Dialect 5.26 (#1185) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump testcontainers.version from 1.20.4 to 1.20.5 (#1184) ** Bump org.springframework.boot:spring-boot-starter-parent (#1183) ** Bump org.junit:junit-bom from 5.11.4 to 5.12.0 (#1182) ** Bump com.puppycrawl.tools:checkstyle (#1181) ** Bump io.projectreactor:reactor-bom (#1180) ** Bump org.springframework.data:spring-data-neo4j (#1179) ** Bump com.tngtech.archunit:archunit from 1.3.0 to 1.4.0 (#1178) ==== 🛠 Build * fix: The default of `structuredMessage` is `false`. (Internal release announcer) ================================================ FILE: docs/appendix/2024.6.adoc ================================================ == 2024.6 === 2024.6.1 ==== 🐛 Bug Fixes * Don't change simple case to generic case. ==== 🔄️ Refactorings * Add better error messages when attempting to continue subquery without `with`. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.neo4j.driver:neo4j-java-driver (#1242) ** Bump org.springframework.data:spring-data-neo4j (#1239) ** Bump com.opencsv:opencsv from 5.10 to 5.11 (#1237) ** Bump com.tngtech.archunit:archunit from 1.4.0 to 1.4.1 (#1243) ** Bump org.jreleaser:jreleaser-maven-plugin (#1241) ** Bump com.fasterxml.jackson:jackson-bom (#1240) ** Bump testcontainers.version from 1.20.6 to 1.21.0 (#1238) ** Bump neo4j.version from 5.26.5 to 5.26.6 (#1236) === 2024.6.0 Take note that this release deprecates direct integrations with the Neo4j Java Driver. They have never been used widely and can be easily added when needed in calling code. Removing them from Cypher-DSL avoid unnessary complexity in dependency management. ==== 🔄️ Refactorings * refactor: Deprecate all driver integrations from Cypher-DSL side. (#1211) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.checkerframework:checker-qual (#1233) ** Bump com.puppycrawl.tools:checkstyle (#1232) ** Bump org.springframework.boot:spring-boot-starter-parent (#1231) ** Bump com.google.guava:guava (#1230) ** Bump io.projectreactor:reactor-bom (#1229) ** Bump org.junit:junit-bom from 5.12.1 to 5.12.2 (#1228) ** Bump org.jacoco:jacoco-maven-plugin from 0.8.12 to 0.8.13 (#1226) ** Bump org.asciidoctor:asciidoctorj-diagram (#1223) ** Bump mockito.version from 5.16.1 to 5.17.0 (#1222) ** Bump neo4j.version from 5.26.4 to 5.26.5 (#1221) ** Bump org.apache.maven.plugins:maven-failsafe-plugin (#1220) ** Bump org.asciidoctor:asciidoctor-maven-plugin (#1219) ** Bump org.apache.maven.plugins:maven-surefire-plugin (#1218) ** Bump org.springframework.boot:spring-boot-starter-parent (#1217) ** Bump com.google.guava:guava (#1216) ** Bump com.puppycrawl.tools:checkstyle (#1215) ** Bump org.sonarsource.scanner.maven:sonar-maven-plugin (#1214) ** Bump joda-time:joda-time from 2.13.1 to 2.14.0 (#1213) ** Bump org.ow2.asm:asm from 9.7.1 to 9.8 (#1212) ** Bump testcontainers.version from 1.20.5 to 1.20.6 (#1207) ** Bump org.junit:junit-bom from 5.12.0 to 5.12.1 (#1209) ** Bump io.projectreactor:reactor-bom (#1210) ** Bump org.graalvm.buildtools:native-maven-plugin (#1208) ** Bump net.java.dev.jna:jna from 5.16.0 to 5.17.0 (#1206) ** Bump org.springframework.data:spring-data-neo4j (#1205) ** Bump com.mycila:license-maven-plugin from 4.6 to 5.0.0 (#1204) ** Bump com.google.guava:guava (#1203) ** Bump neo4j.version from 5.26.3 to 5.26.4 (#1202) ** Bump mockito.version from 5.16.0 to 5.16.1 (#1201) ================================================ FILE: docs/appendix/2024.7.adoc ================================================ == 2024.7 === 2024.7.4 ==== 🚀 Features * Add support for `ORDER BY` after match. === 2024.7.3 ==== 🐛 Bug Fixes * Improve rendering of the importing `CALL` clause. * Clearing the scoping strategy could lead to a premature resolution of symbolic names. * Add back a test-class deleted by accident. === 2024.7.2 This is a dependency upgrade release only. The next release will most likely be the first 2025.x release with some new features, removal of deprecations and some of the meta-annotations that we use. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump com.opencsv:opencsv from 5.11.2 to 5.12.0 (#1290) ** Bump org.springframework.boot:spring-boot-starter-parent (#1289) ** Bump org.junit:junit-bom from 5.13.3 to 5.13.4 (#1288) ** Bump org.neo4j.driver:neo4j-java-driver (#1280) ** Bump com.fasterxml.jackson:jackson-bom (#1285) ** Bump org.springframework.data:spring-data-neo4j (#1284) ** Bump org.graalvm.buildtools:native-maven-plugin (#1283) ** Bump io.projectreactor:reactor-bom (#1282) ** Bump org.apache.maven.plugins:maven-enforcer-plugin (#1281) ** Bump org.moditect:moditect-maven-plugin (#1279) ** Bump org.neo4j.driver:neo4j-java-driver from 5.28.5 to 5.28.8 (#1277) ** Bump org.junit:junit-bom from 5.13.1 to 5.13.3 (#1274) ** Bump org.checkerframework:checker-qual (#1272) ** Bump neo4j.version from 5.26.8 to 5.26.9 (#1278) ** Bump org.apache.maven.plugins:maven-enforcer-plugin (#1276) ** Bump org.jreleaser:jreleaser-maven-plugin (#1275) ** Bump testcontainers.version from 1.21.2 to 1.21.3 (#1269) ** Bump com.puppycrawl.tools:checkstyle (#1268) === 2024.7.1 ==== 📖 Documentation * Fix copyright date. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump testcontainers.version from 1.21.1 to 1.21.2 (#1263) ** Bump com.opencsv:opencsv from 5.11.1 to 5.11.2 (#1266) ** Bump org.asciidoctor:asciidoctorj-diagram (#1265) ** Bump org.springframework.boot:spring-boot-starter-parent (#1264) ** Bump com.puppycrawl.tools:checkstyle (#1262) ** Bump io.projectreactor:reactor-bom (#1261) ** Bump org.springframework.data:spring-data-neo4j (#1260) ** Bump com.fasterxml.jackson:jackson-bom (#1259) ** Bump org.codehaus.mojo:flatten-maven-plugin (#1267) ** Bump neo4j.version from 5.26.7 to 5.26.8 (#1258) ** Bump org.checkerframework:checker-qual (#1257) ** Bump org.junit:junit-bom from 5.13.0 to 5.13.1 (#1256) ** Bump com.opencsv:opencsv from 5.11 to 5.11.1 (#1254) ** Bump org.codehaus.mojo:exec-maven-plugin (#1255) ** Bump neo4j.version from 5.26.6 to 5.26.7 (#1253) ** Bump org.junit:junit-bom from 5.12.2 to 5.13.0 (#1252) ** Bump com.puppycrawl.tools:checkstyle (#1251) ** Bump testcontainers.version from 1.21.0 to 1.21.1 (#1250) === 2024.7.0 This is a feature release that is fully compatible with the previous 2024.6 and earlier series. ==== 🚀 Features * Add support for `SHORTEST k`, `SHORTEST k GROUPS`, `ALL SHORTEST` and `ANY` path selectors. (#1247) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump mockito.version from 5.17.0 to 5.18.0 (#1249) ** Bump com.puppycrawl.tools:checkstyle (#1248) ** Bump org.springframework.data:spring-data-neo4j from 7.4.5 to 7.5.0 (#1245) ** Bump io.projectreactor:reactor-bom (#1246) ================================================ FILE: docs/appendix/2025.0.adoc ================================================ == 2025.0 === 2025.0.3 ==== 🚀 Features * Add support for `ORDER BY` after match. * Make dynamic labels configurable even on latest dialects. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.springframework.boot:spring-boot-starter-parent (#1349) ** Bump org.apache.maven.plugins:maven-javadoc-plugin (#1348) ** Bump quarkus.platform.version from 3.26.3 to 3.26.4 (#1347) ** Bump org.neo4j:neo4j-ogm-quarkus from 3.16.0 to 3.16.1 (#1346) ** Bump mockito.version from 5.19.0 to 5.20.0 (#1344) ** Bump org.assertj:assertj-core from 3.27.4 to 3.27.5 (#1343) ** Bump org.codehaus.mojo:flatten-maven-plugin (#1342) === 2025.0.2 ==== 🚀 Features * Add support for rendering some label expressions for older Neo4j versions. * Deduplicate parameters while collecting. * Add support for dynamic label expression in the `REMOVE` clause. (#1325) * Add support for dynamic label expression in the `SET` clause. (#1324) * Add support for dynamic label expressions. (#1323) ==== 🐛 Bug Fixes * Extract parameters from Label expressions, too. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump Neo4j-OGM and OGM-Quarkus example to 4.0.20. ** Bump org.checkerframework:checker-qual (#1338) ** Bump com.google.testing.compile:compile-testing (#1336) ** Bump org.apache.maven.plugins:maven-failsafe-plugin (#1335) ** Bump quarkus.platform.version from 3.26.2 to 3.26.3 (#1334) ** Bump org.springframework.data:spring-data-neo4j (#1333) ** Bump org.apache.maven.plugins:maven-surefire-plugin (#1332) ** Bump org.apache.maven.plugins:maven-shade-plugin (#1331) ** Bump com.google.testing.compile:compile-testing from 0.21.0 to 0.22.0 (#1328) ** Bump quarkus.platform.version from 3.26.1 to 3.26.2 (#1330) ** Bump neo4j.version from 5.26.11 to 5.26.12 (#1329) * Restore `QueryDSLAdapterTests` once again. === 2025.0.1 ==== 🔄️ Refactorings * Existential subqueries in latest Neo4j do work with multiple matches, too. (#1322) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.checkerframework:checker-qual (#1321) ** Bump quarkus.platform.version from 3.25.4 to 3.26.1 (#1320) ** Bump neo4j.version from 5.26.10 to 5.26.11 (#1319) ** Bump org.sonarsource.scanner.maven:sonar-maven-plugin (#1318) ** Bump org.jreleaser:jreleaser-maven-plugin (#1317) ** Bump com.fasterxml.jackson:jackson-bom (#1316) ** Bump com.puppycrawl.tools:checkstyle from 11.0.0 to 11.0.1 (#1315) ** Bump quarkus.platform.version from 3.25.3 to 3.25.4 (#1312) ** Bump org.springframework.boot:spring-boot-starter-parent (#1311) === 2025.0.0 It's about time to create a 2025 major release of Cypher-DSL. First things first: We removed the deprecated, direct integration of the Neo4j Java driver. That integration used to allow making all Cypher-DSL statements "executable", that is: You were able to pass a query executor to them and get results back. It has been used in https://github.com/spring-projects/spring-data-neo4j[Spring Data Neo4j] testing code, but not much anywhere else. We removed it because it started to became costly: The common Neo4j Java driver and it's dependency will change a lot in their 6.x series, and especially the dependencies will drag us down. Cypher-DSL is a project that is used by Spring Data Neo4j, https://github.com/neo4j/neo4j-jdbc[Neo4j JDBC Driver] and many more. All of them need to keep an eye on dependency management, hence we prefer to be more lightweight here again. You won't notice a difference in most use-cases. With this major we welcome and thank @Shinigami92 as first-time contributor to the ecosystem. They took inspiration of the Spring Data Neo4j annotation processor and started off a similar processor for https://github.com/neo4j/neo4j-ogm[Neo4j-OGM], so that you can now enjoy a static metamodel based on Cypher-DSL for Neo4j-OGM as well. A usecase might be in a Quarkus application. ==== 🚀 Features * Enhance old dialects to render `ALL SHORTEST` and `SHORTEST 1` as legacy functions. * Add an annotation processor for Neo4j-OGM annotations. (#1287) ==== 🐛 Bug Fixes * Improve rendering of the importing `CALL` clause. * Clearing the scoping strategy could lead to a premature resolution of symbolic names. ==== 🔄️ Refactorings * Make Neo4j 5 the default dialect, promote the usage of a dialect without Cypher prefix. * Remove the remaining bits of driver dependencies. * Use Spring-Java-Formatter and corresponding checkstyle config. (#1308) * Remove JetBrains annotations. (#1294) * Remove deprecated features and unnecessary suppressions. (#1292) ==== 🧹 Housekeeping * Remove superfluous .editorconfig * Dependency upgrades: ** Bump org.springframework.data:spring-data-neo4j (#1307) ** Bump quarkus.platform.version from 3.24.5 to 3.25.3 (#1306) ** Bump io.projectreactor:reactor-bom (#1305) ** Bump mockito.version from 5.18.0 to 5.19.0 (#1304) ** Bump org.apache.maven.plugins:maven-javadoc-plugin (#1303) ** Bump neo4j.version from 5.26.9 to 5.26.10 (#1302) ** Bump com.puppycrawl.tools:checkstyle from 10.26.1 to 11.0.0 (#1301) ** Bump org.assertj:assertj-core from 3.27.3 to 3.27.4 (#1299) ** Bump org.codehaus.mojo:flatten-maven-plugin (#1297) ================================================ FILE: docs/appendix/2025.1.adoc ================================================ == 2025.1 === 2025.1.0 ==== 🚀 Features * Add option `org.neo4j.cypherdsl.codegen.excludes` for excluding types from processing. (#1363, codegen) ==== 🐛 Bug Fixes * Generate relationship meta model always per package and in the package of the start node. (codegen) ==== 🧹 Housekeeping * Dependency upgrades: ** Upgrade to latest OGM and OGM Quarkus including driver 6. ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#1367) ** Bump org.ow2.asm:asm from 9.8 to 9.9 (#1372) ** Bump com.puppycrawl.tools:checkstyle from 11.1.0 to 12.0.1 (#1371) ** Bump org.codehaus.mojo:exec-maven-plugin (#1370) ** Bump neo4j.version from 5.26.12 to 5.26.13 (#1369) ** Bump quarkus.platform.version from 3.28.2 to 3.28.3 (#1368) ** Bump org.jacoco:jacoco-maven-plugin from 0.8.13 to 0.8.14 (#1366) ** Bump org.junit:junit-bom from 5.13.4 to 6.0.0 (#1365) ** Bump org.checkerframework:checker-qual (#1357) ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#1362) ** Bump org.apache.maven.plugins:maven-enforcer-plugin (#1361) ** Bump quarkus.platform.version from 3.26.4 to 3.28.2 (#1356) ** Bump org.assertj:assertj-core from 3.27.5 to 3.27.6 (#1355) ** Bump com.puppycrawl.tools:checkstyle from 11.0.1 to 11.1.0 (#1354) ** Bump org.graalvm.buildtools:native-maven-plugin (#1352) ================================================ FILE: docs/appendix/2025.2.adoc ================================================ == 2025.2 === 2025.2.7 ==== 🧹 Housekeeping * Dependency upgrades: ** Bump com.puppycrawl.tools:checkstyle from 13.4.0 to 13.4.2 (#1508) ** Bump org.testcontainers:testcontainers-bom (#1502) ** Bump org.graalvm.buildtools:native-maven-plugin (#1503) ** Bump neo4j.version from 5.26.24 to 5.26.25 (#1504) ** Bump org.springframework.boot:spring-boot-starter-parent (#1506) ** Bump org.sonarsource.scanner.maven:sonar-maven-plugin (#1507) ** Bump org.checkerframework:checker-qual (#1509) ** Bump quarkus.platform.version from 3.34.5 to 3.35.1 (#1510) ** Bump com.fasterxml.jackson:jackson-bom (#1511) ** Bump org.neo4j.driver:neo4j-java-driver (#1512) ** Bump slf4j to 2.0.17. ** Bump com.puppycrawl.tools:checkstyle from 13.3.0 to 13.4.0 (#1489) ** Bump neo4j.version from 5.26.23 to 5.26.24 (#1491) ** Bump org.checkerframework:checker-qual (#1495) ** Bump org.springframework.data:spring-data-neo4j (#1496) ** Bump quarkus.platform.version from 3.32.4 to 3.34.5 (#1497) ** Bump org.neo4j.driver:neo4j-java-driver (#1498) ** Bump com.tngtech.archunit:archunit from 1.4.1 to 1.4.2 (#1499) ** Bump com.google.guava:guava (#1500) ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#1501) === 2025.2.6 ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.neo4j:neo4j-ogm-quarkus from 4.2.4 to 4.2.5 ** Bump org.neo4j:neo4j-ogm-core from 5.0.3 to 5.0.4 (#1476) ** Bump neo4j.version from 5.26.22 to 5.26.23 (#1477) ** Bump org.asciidoctor:asciidoctorj-diagram (#1478) ** Bump com.fasterxml.jackson:jackson-bom (#1480) ** Bump org.springframework.data:spring-data-neo4j (#1481) ** Bump quarkus.platform.version from 3.32.2 to 3.32.4 (#1482) ** Bump org.testcontainers:testcontainers-bom (#1483) ** Bump org.neo4j.driver:neo4j-java-driver (#1485) ** Bump org.graalvm.buildtools:native-maven-plugin (#1486) ** Bump org.springframework.boot:spring-boot-starter-parent (#1487) === 2025.2.5 ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.neo4j:neo4j-ogm-quarkus from 4.2.3 to 4.2.4 (#1475) ** Bump neo4j.version from 5.26.21 to 5.26.22 (#1474) ** Bump mockito.version from 5.22.0 to 5.23.0 (#1473) ** Bump org.checkerframework:checker-qual (#1472) ** Bump org.asciidoctor:asciidoctorj-diagram (#1471) ** Bump joda-time:joda-time from 2.14.0 to 2.14.1 (#1470) ** Bump org.graalvm.buildtools:native-maven-plugin (#1469) ** Bump org.apache.maven.plugins:maven-resources-plugin (#1468) ** Bump quarkus.platform.version from 3.32.1 to 3.32.2 (#1467) ** Bump org.apache.maven.plugins:maven-shade-plugin (#1466) ** Bump org.jreleaser:jreleaser-maven-plugin (#1465) ** Bump quarkus.platform.version from 3.31.4 to 3.32.1 (#1464) ** Bump com.puppycrawl.tools:checkstyle from 13.2.0 to 13.3.0 (#1463) ** Bump mockito.version from 5.21.0 to 5.22.0 (#1462) ** Bump org.neo4j:neo4j-ogm-core from 5.0.2 to 5.0.3 (#1461) ** Bump org.apache.maven.plugins:maven-surefire-plugin (#1460) ** Bump org.apache.maven.plugins:maven-failsafe-plugin (#1459) ** Bump com.fasterxml.jackson:jackson-bom (#1458) ** Bump org.neo4j:neo4j-ogm-quarkus from 4.2.2 to 4.2.3 (#1457) ** Bump org.springframework.boot:spring-boot-starter-parent (#1456) ** Bump quarkus.platform.version from 3.31.3 to 3.31.4 (#1455) ** Bump org.neo4j.driver:neo4j-java-driver (#1454) === 2025.2.4 ==== 🐛 Bug Fixes * Don’t generate meta model that cannot be initialized. (#1450) ==== 🔄️ Refactorings * Don’t use date parts for duration literals. (#1449) ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.springframework.data:spring-data-neo4j (#1453) ** Bump quarkus.platform.version from 3.31.1 to 3.31.3 (#1452) ** Bump org.junit:junit-bom from 6.0.2 to 6.0.3 (#1451) ** Bump org.checkerframework:checker-qual (#1448) ** Bump com.puppycrawl.tools:checkstyle from 13.1.0 to 13.2.0 (#1447) ** Bump neo4j.version from 5.26.20 to 5.26.21 (#1445) ** Bump com.puppycrawl.tools:checkstyle from 13.0.0 to 13.1.0 (#1444) ** Bump quarkus.platform.version from 3.30.8 to 3.31.1 (#1443) ** Bump org.springframework.boot:spring-boot-starter-parent (#1442) ** Bump org.assertj:assertj-core from 3.27.6 to 3.27.7 (#1441) ** Bump quarkus.platform.version from 3.30.6 to 3.30.8 (#1440) ** Bump neo4j.version from 5.26.19 to 5.26.20 (#1439) ** Bump org.graalvm.buildtools:native-maven-plugin (#1438) ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#1437) ** Bump org.springframework.data:spring-data-neo4j (#1435) ** Bump com.fasterxml.jackson:jackson-bom (#1434) ** Bump org.neo4j:neo4j-ogm-core and quarkus integration to latest. ==== 🛠 Build * Disable testing of all Neo4j versions for schema names on GitHub. === 2025.2.3 Happy new year. This first Cypher-DSL release of 2026 just contains updated dependencies. ==== 🧰 Tasks * Extend license header to 2026. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump com.puppycrawl.tools:checkstyle from 12.3.0 to 13.0.0 (#1429) ** Bump org.springframework.data:spring-data-neo4j (#1431) ** Bump org.junit:junit-bom from 6.0.1 to 6.0.2 (#1430) ** Bump org.checkerframework:checker-qual (#1428) ** Bump org.jreleaser:jreleaser-maven-plugin (#1427) ** Bump quarkus.platform.version from 3.30.4 to 3.30.6 (#1426) ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#1425) ** Bump org.ow2.asm:asm from 9.9 to 9.9.1 (#1424) ** Bump quarkus.platform.version from 3.30.2 to 3.30.4 (#1423) ** Bump org.testcontainers:testcontainers-bom (#1422) ** Bump org.codehaus.mojo:exec-maven-plugin (#1421) ** Bump com.puppycrawl.tools:checkstyle from 12.2.0 to 12.3.0 (#1420) ** Bump neo4j.version from 5.26.18 to 5.26.19 (#1419) ** Bump org.springframework.boot:spring-boot-starter-parent (#1418) ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#1417) ** Bump mockito.version from 5.20.0 to 5.21.0 (#1416) ** Bump org.apache.maven.plugins:maven-resources-plugin (#1415) ==== 🛠 Build * Require Java 25 for the build consistently, release with 17. (#1432) * Ignore `.git-blame-ignore-revs` during license check. * Add shims to start Maven warning free on JDK 25. * Update required Maven version and Maven wrapper to 3.9.12. === 2025.2.2 🎅🏻 Thanks for another year of Cypher-DSL, with good feedback, excellent bug-reports and more. Merry Christmas and happy holidays. This release contains only updated dependencies. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.neo4j:neo4j-ogm-core from 5.0.0 to 5.0.1 (#1414) ** Bump org.checkerframework:checker-qual (#1411) ** Bump neo4j.version from 5.26.17 to 5.26.18 (#1405) ** Bump org.testcontainers:testcontainers-bom (#1406) ** Bump org.asciidoctor:asciidoctorj-diagram (#1407) ** Bump quarkus.platform.version from 3.29.4 to 3.30.2 (#1409) ** Bump org.apache.maven.plugins:maven-source-plugin (#1410) ** Bump org.sonarsource.scanner.maven:sonar-maven-plugin (#1412) ** Bump com.puppycrawl.tools:checkstyle from 12.1.2 to 12.2.0 (#1413) === 2025.2.1 No breaking changes. The updated Spring Boot bits only do affect our own testing. Cypher-DSL does not use anything, either from Spring, Spring Boot or Quarkus directly, hence you can use this release equally with Spring Boot 3 or 4. The same is true for Spring Data Neo4j and OGM, in case you use the Cypher-DSL annotation processor. None of the annotations Cypher-DSL understands has changed in their major releases. Thanks to @pecollet for finding another—and quite some nasty— bug in the way Cypher-DSL traverses Label expressions. ==== 🚀 Features ==== 🐛 Bug Fixes * Leave the `Labels` expression in all cases and handlers if a delegation had been made. ==== 🔄️ Refactorings * Adapt to changes in Spring Boot 4. ==== 🧰 Tasks * Configure ignored blame revs. ==== 🧹 Housekeeping * Dependency upgrades: ** Bump org.neo4j:neo4j-ogm-quarkus from 4.1.0 to 4.2.0 ** Bump org.springframework.data:spring-data-neo4j ** Bump org.springframework.boot:spring-boot-starter-parent ** Bump org.sonarsource.scanner.maven:sonar-maven-plugin (#1402) ** Bump com.puppycrawl.tools:checkstyle from 12.1.1 to 12.1.2 (#1401) ** Bump quarkus.platform.version from 3.29.2 to 3.29.4 (#1399) ** Bump neo4j.version from 5.26.16 to 5.26.17 (#1397) ** Bump org.apache.maven.plugins:maven-jar-plugin (#1396) ** Bump org.graalvm.buildtools:native-maven-plugin (#1395) ** Bump com.github.siom79.japicmp:japicmp-maven-plugin (#1394) ** Bump org.asciidoctor:asciidoctorj from 3.0.0 to 3.0.1 (#1393) ** Bump org.neo4j.driver:neo4j-java-driver (#1392) ** Bump org.checkerframework:checker-qual (#1391) ** Bump quarkus.platform.version from 3.29.0 to 3.29.2 (#1390) ** Bump neo4j.version from 5.26.15 to 5.26.16 (#1389) ** Bump com.fasterxml.jackson:jackson-bom (#1388) ** Bump org.neo4j:neo4j-ogm-quarkus from 4.0.0 to 4.1.0 (#1387) ** Bump quarkus.platform.version from 3.28.5 to 3.29.0 (#1386) ** Bump neo4j.version from 5.26.14 to 5.26.15 (#1385) ** Bump org.junit:junit-bom from 6.0.0 to 6.0.1 (#1384) ** Bump org.jreleaser:jreleaser-maven-plugin (#1383) === 2025.2.0 🎃 No breaking changes in this release (as most of the time), only a small new feature needed for the https://github.com/neo4j/neo4j-jdbc[Neo4j JDBC Driver] which you definitely check out. ==== 🚀 Features * Add support for parameter inside literal maps. (#1382) ==== 🔄️ Refactorings * Prevent anonymous parameters from being used in literals. ==== 🧹 Housekeeping * Dependency upgrades: ** Upgrade testcontainers.version to 2.0.1 ** Bump org.graalvm.buildtools:native-maven-plugin (#1377) ** Bump org.springframework.data:spring-data-neo4j (#1376) ** Bump org.codehaus.mojo:exec-maven-plugin (#1375) ** Bump com.puppycrawl.tools:checkstyle from 12.0.1 to 12.1.1 (#1380) ** Bump quarkus.platform.version from 3.28.3 to 3.28.5 (#1381) ** Bump neo4j.version from 5.26.13 to 5.26.14 (#1379) ** Bump org.springframework.boot:spring-boot-starter-parent (#1378) ================================================ FILE: docs/appendix/building.adoc ================================================ == Building the Neo4j Cypher-DSL include::{manualIncludeDir}../CONTRIBUTING.adoc[tags=building-manual] ================================================ FILE: docs/appendix/changes.adoc ================================================ == Change log :leveloffset: +1 include::2025.2.adoc[] include::2025.1.adoc[] include::2025.0.adoc[] include::2024.7.adoc[] include::2024.6.adoc[] include::2024.5.adoc[] include::2024.4.adoc[] include::2024.3.adoc[] include::2024.2.adoc[] include::2024.1.adoc[] include::2024.0.adoc[] include::2023.9.adoc[] include::2023.8.adoc[] include::2023.7.adoc[] include::2023.6.adoc[] include::2023.5.adoc[] include::2023.4.adoc[] include::2023.3.adoc[] include::2023.2.adoc[] include::2023.1.adoc[] include::2023.0.adoc[] include::2022.11.adoc[] include::2022.10.adoc[] include::2022.9.adoc[] include::2022.8.adoc[] include::2022.7.adoc[] include::2022.6.adoc[] include::2022.5.adoc[] include::2022.4.adoc[] include::2022.3.adoc[] include::2022.2.adoc[] include::2022.1.adoc[] include::2022.0.adoc[] include::2021.4.adoc[] include::2021.3.adoc[] include::2021.2.adoc[] include::2021.1.adoc[] include::2021.0.adoc[] include::2020.1.adoc[] include::2020.0.adoc[] :leveloffset: -1 ================================================ FILE: docs/appendix/index.adoc ================================================ [[appendix]] = Appendix :numbered!: include::query-dsl-support.adoc[] include::building.adoc[] include::../../etc/architecture/index.adoc[] include::changes.adoc[] ================================================ FILE: docs/appendix/query-dsl-support.adoc ================================================ [[query-dsl-support]] == Query-DSL support The Neo4j Cypher-DSL has some support for http://www.querydsl.com[Query-DSL]. It can * turn instances of `com.querydsl.core.types.Predicate` into `org.neo4j.cypherdsl.core.Condition`, * turn instances of `com.querydsl.core.types.Expression` into `org.neo4j.cypherdsl.core.Expression`, * create `org.neo4j.cypherdsl.core.Node` instances from `com.querydsl.core.types.Path` and also * create `org.neo4j.cypherdsl.core.SymbolicName` With this, many static meta models based on Query-DSL can be used to create Queries and match on nodes. Most operations supported by Query-DSL are translated into Cypher that is understood by Neo4j 4.0+, so that most predicates should work - at least from a syntactic point of view - out of the box. Expressions are most useful to address properties in return statements and the like. Here's one example on how to use it: [[appendix.query-dsl-support.query-dsl-simple]] [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/querydsl/QueryDSLAdapterTests.java[tags=query-dsl-simple] ---- <.> This makes use of a "Q"-class generated by Query-DSL APT (using the general processor). `alias` and class based paths works, too. Please make sure to name the instance accordingly when you use it as node (see next step) <.> Adapt the "Q"-class into a node. Please note it must be named accordingly, otherwise your query won't return the expected results <.> Create some Query-DSL predicate based on the properties and adapt it as condition <.> Return some Query-DSL properties and adapt it as expression The `Statement` offers a way to render all constants as parameters, so that they don't bust the query cache: [[appendix.query-dsl-support.query-dsl-simple-avoid-constants]] [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/querydsl/QueryDSLAdapterTests.java[tags=query-dsl-simple-avoid-constants] ---- <.> Set this to true before accessing parameters of the statement or rendering the statement <.> Access the statements parameters for generated parameter names <.> Compare the statement to the first listening. Constants are gone now The Neo4j Cypher-DSL will collect all parameters defined via Query-DSL for you: [[appendix.query-dsl-support.query-dsl-parameters]] [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/querydsl/QueryDSLAdapterTests.java[tags=query-dsl-parameters] ---- <.> Basically the same predicate as above, but with parameters, which will be turned into correct placeholders <.> Access the parameter names via the `Statement` object === Required dependencies The Query-DSL support in the Neo4j Cypher-DSL is optional, and the Query-DSL dependency is only in the `provided` scope. To make use of `Cypher.adapt()`, you must add the following dependency in addition to the Cypher-DSL: ==== Maven [source,xml] ---- com.querydsl querydsl-core 4.4.0 provided ---- ==== Gradle [source,groovy] ---- dependencies { implementation 'com.querydsl:query-dsl-core:4.4.0' } ---- In case you want to use an annotation processor, you have to add additional dependencies and depending on your Java environment. We use the following in our tests: [source,xml] ---- com.querydsl querydsl-apt 4.4.0 general javax.annotation javax.annotation-api 1.3.2 ---- ================================================ FILE: docs/cypher-parser/cypher-parser.adoc ================================================ == Introduction The `cypher-dsl-parser` module ins an optional add-on to the Cypher-DSL that takes your existing Cypher - either whole statements or fragments like clauses or expressions - and turn them into Cypher-DSL statements or expressions. Those fragments can be used to add custom Cypher to your generated statements without resorting to raw String literals. It allows sanitizing user input, add additional filters for labels and types to rewrite queries and more. The parser itself is based on Neo4j's official Cypher-Parser, thus supporting the same constructs as Neo4j itself. However, while we could theoretically parse all expressions that Neo4j {neo4j-version} supports, we might cannot translate all of them into elements of the Cypher-DSL. In such cases an `UnsupportedOperationException` will be thrown. == Getting started === Add additional dependencies ==== Maven [source,xml,subs="verbatim,attributes"] .Cypher-DSL parser added via Maven ---- {groupId} neo4j-cypher-dsl-parser {the-version} ---- ==== Gradle [source,groovy,subs="verbatim,attributes"] .Gradle variant for additional dependencies ---- dependencies { implementation '{groupId}:neo4j-cypher-dsl-parser:{the-version}' } ---- === Minimum JDK version The Cypher-Parser requires JDK 11 to run which is the same version that Neo4j {neo4j-version} requires. === Main entry point The main entry point to parsing Cypher strings into Cypher-DSL objects is [[main-entry-pint]] .Cypher Parser [source, java,indent=0,tabsize=4] ---- import org.neo4j.cypherdsl.parser.CypherParser; ---- It provides a list of static methods: [cols=2*,options=header] |=== |Method |What it does |`parseNode` |Parses a pattern like `(n:Node {withOrWithout: 'properties'})` into a `Node` |`parseRelationship` |Parses a pattern like `(m)-[{a: 'b', c: 'd'}]->(n)` into a `RelationshipPattern`. The pattern might be a `Relationship` or `RelationshipChain`. |`parseExpression` |Parses an arbitrary expression. |`parseClause` |Parses a full clause like `MATCH (m:Movie)` or `DELETE n` etc. These clauses might be modified via callbacks and then passed on into `org.neo4j.cypherdsl.core.Statement.of`. This will take them in order and create a whole statement out of it. It is your responsibility to make sure those clauses are meaningful in the given order. |`parseStatement` |Parses a whole statement. The result can be rendered or used in a union or subquery call. |`parse` |An alias for `parseStatement`. |=== The `README` for the parser module itself contains not only our whole TCK for the parser, but also several examples of calling it. Have a look here: https://github.com/neo4j-contrib/cypher-dsl/blob/main/neo4j-cypher-dsl-parser/README.adoc[neo4j-cypher-dsl-parser/README.adoc]. All the methods mention above provide an overload taking in an additional `org.neo4j.cypherdsl.parser.Option` instance allowing to interact with the parser. Please have a look at the JavaAPI for information about the options class. The following examples show some ways of using it. Most of the configurable options represent ways to provide filters for labels or types or are callbacks when certain expressions are created. [[cypher-parser-examples]] == Examples === Parsing user input and call in a subquery This is helpful when you create an outer query that maybe enriched by a user. Here we assume the user does the right thing and don't modify the query any further: [[example-using-input]] .Just using the user supplied input [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-parser/src/test/java/org/neo4j/cypherdsl/examples/parser/CypherDSLParserExamplesTests.java[tag=example-using-input] ---- <.> A valid standalone query that is also a valid subquery <.> Just parse it into a `Statement` object <.> Use the Cypher-DSL as explained throughout the docs <.> Use the overload of `call` that takes a `Statement` and a collection of expression that should be imported into the subquery <.> Notice how to `WITH` clauses are generated: The first one is the importing one, the second one the aliasing one <.> This is the original query === Ensure an alias for the return clause We are going to work with the same test as in <>, so this is not repeated. Here we make sure the query supplied by the user returns something with a required alias. [[example-required-alias]] .Using a callback to make sure that a query has an alias [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-parser/src/test/java/org/neo4j/cypherdsl/examples/parser/CypherDSLParserExamplesTests.java[tag=example-required-alias] ---- <.> This is a `Function` that receives an expressions and returns a new one. It checks if the provided expressions obeys to some criteria: Here being something that is aliased or not <.> We start building new options <.> The callback from step one is passed as callback to the event `ON_RETURN_ITEM` and will be called for every item <.> The final option instance will be applied to the parser. The statement will render to the same result as <>. === Preventing certain things Callbacks can of course be used to prevent things. Any exception thrown will halt the parsing. <> shows how: [[example-preventing-things]] .Preventing input that deletes properties [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-parser/src/test/java/org/neo4j/cypherdsl/examples/parser/CypherDSLParserExamplesTests.java[tag=example-preventing-things] ---- <.> Create a callback that just throws an unchecked exception <.> Configure it for the event that should be prevented <.> Parsing will not be possible === Shape the return clause the way you want The parser provides `ReturnDefinition` as value object. It contains information to be passed to the `Clauses` factory to shape a `RETURN` clause the way you need: [[example-example-shape-the-return-clause]] .Shaping the return clause [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-parser/src/test/java/org/neo4j/cypherdsl/examples/parser/CypherDSLParserExamplesTests.java[tag=example-shape-the-return-clause] ---- <.> Create a factory method that takes in a definition and uses its information to build the `RETURN`. Or examples filters the attributes being returned and enforces an alias. It also adds some arbitrary sorting and keeps sort and limit values from the original <.> It than is parsed to the options <.> The statement has the new `RETURN` clause. === Enforcing labels The parser can enforce labels to be present or absent with filters. This can be individually achieved when parsing node patterns, setting or removing labels with a `BiFunction` like the following: [[a-label-enforcing-functions]] .Shaping the return clause [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-parser/src/test/java/org/neo4j/cypherdsl/parser/RewriteTests.java[tag=enforcing-labels-function] ---- <.> Decide on the event type what is supposed to happen Putting this function in action involves the `Options` class again: [[enforcing-on-parse]] .Enforcing a label is always set on the pattern [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-parser/src/test/java/org/neo4j/cypherdsl/parser/RewriteTests.java[tag=enforcing-on-parse] ---- This can safely be used to match only nodes spotting such a label for example. [[enforcing-on-set]] .Enforcing that a new collection of labels always contains a specific [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-parser/src/test/java/org/neo4j/cypherdsl/parser/RewriteTests.java[tag=enforcing-on-set] ---- Of course, we can prevent a label to be removed: [[enforcing-on-remove]] .Preventing a specific label to be removed [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-parser/src/test/java/org/neo4j/cypherdsl/parser/RewriteTests.java[tag=enforcing-on-remove] ---- Changing relationship types via a filter is possible as well, but as relationships might only have one type, the number of usecases is smaller. === Combining the parser with SDN's `CypherdslConditionExecutor` Spring Data Neo4j 6 provides `CypherdslConditionExecutor`. This is a fragment that adds the capability to execute statements with added conditions to a `Neo4jRepository`. Given the following repository: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/PeopleRepository.java[tag=additional-fragments] ---- <.> Allows to just add conditions to our generated queries <.> Provides an alternative to using @Query with strings One possible use case is presented in this service: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/PeopleService.java[tag=using-parser-with-spring] ---- <.> The condition that only people born later than 1980 is hard coded in the service. An arbitrary String is than parsed into a condition and attached via `AND`. Thus, only valid cypher can go in there and with filters and callbacks, preconditions of that Cypher can be asserted. The downside to the above solution is that the query fragment passed to the service and eventually the repository must know the root node (which is `n` in case of SDN 6) and the caller code might look like this: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/test/java/org/neo4j/cypherdsl/examples/sdn6/ApplicationIT.java[tag=exchange1] ---- Notice `n.name` etc. We could change the service method slightly and apply a callback like this: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/PeopleService.java[tag=using-parser-with-spring2] ---- <.> Create a function that takes the value of a variable created in the fragment and use it to look up a property on the SDN root node. <.> Create an instance of parsing options. It's probably a good idea todo this once and store them away in an instance variable. Options are thread safe. <.> Apply them when calling the corresponding parse method Now it's enough to pass `"name contains \"Ricci\" OR name ends with 'Hirsch'"` into the exchange presented above and things will work out of the box. Further validation and sanitiy checks are of course up to you. ================================================ FILE: docs/cypher-parser/index.adoc ================================================ [[cypher-parser]] = Parsing existing Cypher include::cypher-parser.adoc[] ================================================ FILE: docs/functions/arbitrary-procedures-and-functions.adoc ================================================ == Calling arbitrary procedures and functions Neo4j has plethora of built-in procedures and functions. The Cypher-DSL does cover a lot of them already, and they can be called in a typesafe way on many `Expression`-instances taking in various expressions such as the results of other calls, literals or parameters. However, there will probably always be a gap between what the Cypher-DSL includes and what the Neo4j database brings. More so, there are fantastic libraries out there like https://neo4j.com/labs/apoc/[APOC]. APOC has so many procedures and functions in so many categories that it is rather futile to add shims for all of them consistently. Probably the most important aspect of all: many Neo4j users bring their knowledge to the database themselves, in the form of their stored procedures. Those should be callable as well. The Cypher-DSL is flexible enough to call all those procedures and functions. === Calling custom procedures Procedures are called via the Cypher https://neo4j.com/docs/cypher-manual/current/clauses/call/[CALL-Clause]. The CALL-Clause can appear as a https://s3.amazonaws.com/artifacts.opencypher.org/M15/railroad/StandaloneCall.html[StandAlone call] and as an https://s3.amazonaws.com/artifacts.opencypher.org/M15/railroad/InQueryCall.html[InQuery call]. Both are supported by the Cypher-DSL. ==== Standalone procedure calls Standalone calls are particular useful for `VOID` procedures. A VOID procedure is a procedure that does not declare any result fields and returns no result records and that has explicitly been declared as `VOID`. Let's take the first example https://neo4j.com/docs/cypher-manual/current/clauses/call/#call-call-a-procedure-using-call[from here] [source,java,indent=0] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/ArbitraryProceduresAndFunctionsTests.java[tag=standalone-call] ---- <.> `Cypher.call` returns a buildable statement that can be rendered. `Cypher.call` can be used with separate namespace and procedure name as shown here or <.> with a single argument equal to the name of the procedure. Of course, arguments can be specified as expressions. Expressions can be literals as in <>, Cypher parameters (via `Cypher.parameter`) that bind your input or nested calls: [[standalone-call-with-args]] [source,java,indent=0] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/ArbitraryProceduresAndFunctionsTests.java[tag=standalone-call-with-args] ---- Last but not least, the Cypher-DSL can of course `YIELD` the results from a standalone call: [[standalone-call-yielding]] [source,java,indent=0] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/ArbitraryProceduresAndFunctionsTests.java[tag=standalone-call-yielding] ---- <.> Yielded items can be specified via string… <.> …or with symbolic names created earlier A standalone call can spot a `WHERE` clause as well: [[standalone-call-where]] [source,java,indent=0] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/ArbitraryProceduresAndFunctionsTests.java[tag=standalone-call-where] ---- ==== In-query procedure calls In-query calls are only possible with non-void procedures. An In-query call happens inside the flow of a normal query. The mechanics to construct those calls via the Cypher-DSL are identical to standalone calls: [[in-query-calls]] [source,java,indent=0] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/ArbitraryProceduresAndFunctionsTests.java[tag=in-query-calls] ---- A `CALL` can be used after a `MATCH`, a `WITH` and also a `WHERE` clause. === Use stored-procedure-calls as expressions (Calling custom functions) All the mechanics described and shown above - how to define a custom call statement, supply it with arguments etc. - doesn't distinguish between procedures and functions. Every stored procedure can be treated as a function - as long as the stored procedure returns a single value. It doesn't matter if the single value returns a scalar or a list of objects. A list of objects is still a single value, in contrast to a stream of objects returned by a non-void procedure. So the question is not how to call a stored custom function, but how to turn a call statement into an expression that can be used in any place in a query where an expression is valid. This is where `asFunction` comes in. [[as-function]] [source,java,indent=0] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/ArbitraryProceduresAndFunctionsTests.java[tag=as-function] ---- <.> First we define a call as seen earlier and turn it into an expression <.> This expression is than used as any other expression Of course, arguments to those functions can be expressed as well, either as literals or expressions. [[as-function-with-args]] [source,java,indent=0] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/ArbitraryProceduresAndFunctionsTests.java[tag=as-function-with-args] ---- <.> Same as before <.> A call to APOC's `camelCase` function, taking in the literal of `first name`. <.> A call to another APOC function to which a parameter is passed. You find that corresponding placeholder as `$nameParam` in the following assert === Summary Through `Cypher.call` any procedure or function can be called in case one of your favorite procedures is missing in `org.neo4j.cypherdsl.core.Functions`. All clauses, including `YIELD` and `WHERE` on procedures are supported. All procedures can be turned into functions. The Cypher-DSL however does not check if the procedure that is used as a function is actually eligible to do so. If the Cypher-DSL misses an important builtin Neo4j function, please raise a https://github.com/neo4j-contrib/cypher-dsl/issues/new[ticket]. ================================================ FILE: docs/functions/index.adoc ================================================ [[functions]] = Functions NOTE: There are many more functions implemented in `org.neo4j.cypherdsl.core.Functions`. Not all of them are already documented here. include::lists.adoc[] include::mathematical.adoc[] include::arbitrary-procedures-and-functions.adoc[] ================================================ FILE: docs/functions/lists.adoc ================================================ == Lists [[functions-list-range]] === range() Creates an invocation of https://neo4j.com/docs/cypher-manual/current/functions/list/#functions-range[`range()`]. Given the following imports [source,java,indent=0] ---- import org.neo4j.cypherdsl.core.Cypher; ---- [source,java,indent=0] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/FunctionsListTests.java[tag=functions-list-range] ---- Gives you `range(0,10)`. The step size can be specified as well: [source,java,indent=0] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/FunctionsListTests.java[tag=functions-list-range-step] ---- This gives you `range(0,10,1)`. Both variants of `range` also take a `NumberLiteral` for the start and end index and the step size. === Inserting a list operator The _list operator_ `[]` selects a specific value of a list or a range of values from a list. A range can either be closed or open. Find examples to select a value at a given index, a sublist based on a closed range and sublist based on open ranges below. While the examples use <>, the list operator doesn't put any restrictions on the expression at the moment. [source,java,indent=0] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/FunctionsListTests.java[tag=functions-list-operator] ---- ================================================ FILE: docs/functions/mathematical.adoc ================================================ == Mathematical functions The Cypher-DSL supports all mathematical functions as of Neo4j 4.2. Please find their description in the Neo4j cypher manual: * https://neo4j.com/docs/cypher-manual/current/functions/mathematical-numeric/[Numeric] * https://neo4j.com/docs/cypher-manual/current/functions/mathematical-logarithmic/[Logarithmic] * https://neo4j.com/docs/cypher-manual/current/functions/mathematical-trigonometric/[Trigonometric] ================================================ FILE: docs/getting-started/getting-started.adoc ================================================ == Prepare dependencies Please use a dependency management system. We recommend either Maven or Gradle. === Maven configuration [source,xml,subs="verbatim,attributes"] .Inclusion of the Neo4j Cypher-DSL in a Maven project ---- {groupId} {artifactId} {the-version} ---- === Gradle configuration [source,groovy,subs="verbatim,attributes"] .Inclusion of the Neo4j Cypher-DSL in a Gradle project ---- dependencies { implementation '{groupId}:{artifactId}:{the-version}' } ---- [[how-to-use-it]] == How to use it You use the Cypher-DSL as you would write Cypher: it allows to write down even complex Cypher queries from top to bottom in a type safe, compile time checked way. The examples to follow are using JDK 11. We find the `var` keyword especially appealing in such a DSL as the types returned by the DSL are much less important than the further building methods they offer. IMPORTANT: The AST parts and intermediate build steps are immutable. That is, the methods create new intermediate steps. For example, you cannot reuse an `ExposesLimit` step, but have to use the returned object from its `skip` method. An instance of a `org.neo4j.cypherdsl.core.Statement` is provided at the end of every query building step. This `Statement` needs to be rendered into a string or passed to methods supporting it as input. Please get an instance of the default renderer via `org.neo4j.cypherdsl.renderer.Renderer#getDefaultRenderer()`. The renderer provides a single method `render` for rendering the AST into a string representation. Furthermore, the `Statement` will collect parameter names and if provided, parameter values. Parameter names and values are available after the statement has been built and can for example be used directly with the Neo4j-Java-Driver. [[how-to-use-it.examples]] === Examples The following examples are 1:1 copies of the queries you will find in the Neo4j browser after running `:play movies`. They use the following imports: .Imports needed for the examples to compile [source, java] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/CypherDSLExamplesTests.java[tag=cypher-dsl-imports] ---- To match and return all the movie, build your statement like this: .Simple match [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/CypherDSLExamplesTests.java[tag=cypher-dsl-e1] ---- <.> Declare a variable storing your node labeled `Movie` and named `m`, so that you can <.> reuse it in both the match and the return part. <.> The `build` method becomes only available when a compilable Cypher statement can be rendered. ==== Find Match all nodes with a given set of properties: .Find the actor named "Tom Hanks"... [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/CypherDSLExamplesTests.java[tag=cypher-dsl-e2] ---- Limit the number of returned things and return only one attribute .Find 10 people... [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/CypherDSLExamplesTests.java[tag=cypher-dsl-e4] ---- Create complex conditions .Find movies released in the 1990s... [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/CypherDSLExamplesTests.java[tag=cypher-dsl-e5] ---- ==== Query Build relationships .List all Tom Hanks movies... [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/CypherDSLExamplesTests.java[tag=cypher-dsl-e6] ---- .Who directed "Cloud Atlas"? [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/CypherDSLExamplesTests.java[tag=cypher-dsl-e7] ---- .Tom Hanks' co-actors... [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/CypherDSLExamplesTests.java[tag=cypher-dsl-e8] ---- .How people are related to "Cloud Atlas"... [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/CypherDSLExamplesTests.java[tag=cypher-dsl-e9] ---- ==== Solve .Movies and actors up to 4 "hops" away from Kevin Bacon [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/CypherDSLExamplesTests.java[tag=cypher-dsl-bacon] ---- ==== Recommend .Extend Tom Hanks co-actors, to find co-co-actors who haven't worked with Tom Hanks... [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/CypherDSLExamplesTests.java[tag=cypher-dsl-r] ---- === More features [[retrieving-parameters]] ==== Retrieving parameters being defined A placeholder for a parameter can be defined via `Cypher.parameter("param")`. This placeholder will be rendered as `$param` and must be filled with the appropriate means of the environment you're working with. In addition, an arbitrary value can be bound to the name via `Cypher.parameter("param", "a value")` or `Cypher.parameter("param").withValue("a value")`. `NULL` is a valid value. The Cypher-DSL will not use those values, but collect them for you. The following example shows how to access them and how to use it: [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/CypherDSLExamplesTests.java[tag=collecting-params] ---- <.> The names contain all placeholders, also those without a value <.> The parameter map contains only parameters with defined values If you define a parameter with conflicting values, a `ConflictingParametersException` will be thrown the moment you try to retrieve the collected parameters. ==== Using the default renderer A statement can render itself as well: [source, java] ---- var statement = Cypher.returning(literalTrue().as("t")).build(); var cypher = this.statement.getCypher(); assertThat(cypher).isEqualTo("RETURN true AS t"); ---- This, together with <>, makes the statement a complete accessor for a Cypher-statement and its parameters. [[retrieving-identifiable-expressions]] ==== Retrieving identifiable expressions The `Statement` as well as the intermediate build steps after defining a `WITH` or `RETURN` clause allow to retrieve identifiable expressions via `getIdentifiableExpressions()`. All expressions identifiable via a name such as named nodes and relationships, symbolic names or aliased expressions are included. In addition, properties are also available. Those information can be used when dynamically building a query to verify the presence of required expressions or use them for further refinement. Statements parsed via the optional <> are also able to return their identifiable expressions. The use case here might be evaluating a statement defined by a user being parsed and then checked if everything required is returned. ==== Generating formatted Cypher The Cypher-DSL can also format the generated Cypher to some extend. The `Renderer` offers the overload `Renderer getRenderer(Configuration configuration)`, taking in an instance of `org.neo4j.cypherdsl.core.renderer.Configuration`. Instances of `Configuration` are thread-safe and reusable. The class offers a couple of static convenience methods for retrieving some variants. [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/CypherDSLExamplesTests.java[tag=pretty-printing-examle] ---- <.> Get a "pretty printing" instance of the renderer configuration and retrieve a renderer based on it <.> Enjoy formatted Cypher. ==== Escaping names The default renderer in its default configuration will escape all names (labels and relationship types) by default. So `Movie` becomes `++`Movie`++` and `ACTED_IN` becomes `++`ACTED_IN`++`. If you don't want this, you can either create a dedicated configuration for a renderer with that setting turned off or use the pretty printing renderer: [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/CypherDSLExamplesTests.java[tag=escaping] ---- ==== Inserting raw Cypher `Cypher.raw` allows for creating arbitrary expressions from raw String literals. Users discretion is advised: [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/CypherDSLExamplesTests.java[tag=raw-cypher] ---- <.> The Cypher-DSL doesn't support a dynamic lookup of properties on expression at the moment. We use a raw Cypher string with the placeholder `$E` which resolves to the symbolic name also passed to the `raw` function. [[dialect-support]] == Dialect support There is limited dialect support. The default dialect `org.neo4j.cypherdsl.core.renderer.Dialect.DEFAULT` is Neo4j 4.4 and earlier whereas `org.neo4j.cypherdsl.core.renderer.Dialect.NEO4J_5` is designated to work with Neo4j 5. The Neo4j 5 dialect will render some things differently: * `org.neo4j.cypherdsl.core.Functions.distance` will be rendered as `point.distance` instead of just `distance` * `elementId` will be rendered "as is" * `EXISTS(n.property)` will be rendered as `n.property IS NOT NULL` Additional features might be added. NOTE: It is especially noteworthy that `elementId(node)` will be rendered as `toString(id(node))` with the default dialect. This is helpful for building queries that must work a) without deprecation warnings and b) on both Neo4j 4 and 5. Of course this is not portable and the returned values must only be used for comparison inside one statement. But Neo4j advices strongly against making the internal entity ids available outside a transaction (See https://neo4j.com/docs/cypher-manual/current/functions/scalar/#functions-elementid[elementID]: "Outside of the scope of a single transaction, no guarantees are given about the mapping between ID values and elements.") Here is one example how to use a dialect: [source, java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/DialectIT.java[tag=dialect-example] ---- [[catalog-support]] == Catalog support The Cypher-DSL offers a catalog view on statements. After a statement has been build, a catalog can be retrieved like this: .Using the statement catalog from a constructed statement [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/StatementCatalogBuildingVisitorTests.java[tag=catalog-example] ---- In the above example the catalog contains all labels and types used (`Movie`, `Person`, `ACTED_IN`) and all conditions that involves a property of an entity combining the labels or types into a node or relationship. In addition, the catalog contains the identifiable elements. This becomes especially powerful when combining with the optional <>: .Using the statement catalog from a parsed statement [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-parser/src/test/java/org/neo4j/cypherdsl/examples/parser/StatementCatalogBuildingVisitorViaParserTests.java[tag=catalog-example] ---- ================================================ FILE: docs/getting-started/index.adoc ================================================ [[getting-started]] = Getting started include::getting-started.adoc[] ================================================ FILE: docs/index.adoc ================================================ = The Neo4j Cypher-DSL Gerrit Meier ; Michael Simons :toc: :doctype: book :lang: en :listing-caption: Listing :source-highlighter: coderay :icons: font :sectlink: true :sectanchors: true :numbered: true :xrefstyle: short :use-latest-version-for-docs: 0 ifndef::manualIncludeDir[] :manualIncludeDir: ../ endif::[] include::{manualIncludeDir}/README.adoc[tags=properties] ifeval::[{use-latest-version-for-docs} == 1] :the-version: {neo4j-cypher-dsl-version-latest} endif::[] ifeval::[{use-latest-version-for-docs} == 0] :the-version: {neo4j-cypher-dsl-version} endif::[] :copyright: 2020-2025 Neo4j, Inc., :gh-base: https://github.com/neo4j/sdn-rx :java-driver-starter-href: https://github.com/neo4j/neo4j-java-driver-spring-boot-starter :springVersion: 5.2.0.RELEASE :spring-framework-docs: https://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference :spring-framework-javadoc: https://docs.spring.io/spring/docs/{springVersion}/javadoc-api (C) {copyright} [abstract] -- This is the Neo4j Cypher-DSL manual version {the-version}. -- _Who should read this?_ This manual is written for people interested in creating Cypher queries in a typesafe way on the JVM. NOTE: All public classes inside `org.neo4j.cypherdsl` still subject to breaking changes are annotated with `@API(status = EXPERIMENTAL)`. include::introduction-and-preface/index.adoc[leveloffset=+1] include::getting-started/index.adoc[leveloffset=+1] include::properties/index.adoc[leveloffset=+1] include::functions/index.adoc[leveloffset=+1] include::cypher-parser/index.adoc[leveloffset=+1] include::static-meta-model/index.adoc[leveloffset=+1] include::appendix/index.adoc[leveloffset=+1] ================================================ FILE: docs/introduction-and-preface/index.adoc ================================================ [[introduction]] = Introduction include::introduction.adoc[] ================================================ FILE: docs/introduction-and-preface/introduction.adoc ================================================ == Purpose The Cypher-DSL has been developed with the needs of Spring Data Neo4j. We wanted to avoid string concatenations in our query generation and decided do go with a builder approach, much like we find with https://www.jooq.org[jOOQ] or in the relational module of https://github.com/spring-projects/spring-data-jdbc/tree/1.1.6.RELEASE/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql[Spring Data JDBC], but for Cypher. What we don't have - and don't need for our mapping purpose - at the moment is a code generator that reads the database schema and generates static classes representing labels and relationship types. That is still up to the mapping framework (in our case SDN). We however have a type safe API for Cypher that allows only generating valid Cypher constructs. We worked closely with the https://www.opencypher.org[OpenCypher spec] here and you find a lot of these concepts in the API. The Cypher-DSL can also be seen in the same area as the https://docs.spring.io/spring-data/mongodb/docs/current/api/org/springframework/data/mongodb/core/query/Criteria.html[Criteria API] of Spring Data Mongo. == Where to use it The Cypher-DSL creates an https://en.wikipedia.org/wiki/Abstract_syntax_tree[Abstract Syntax Tree (AST)] representing your Cypher-Statements. An instance of a `org.neo4j.cypherdsl.core.Statement` representing that AST is provided at the end of query building step. A `Renderer` is then used to create literal Java-Strings. Those can be used in any context supporting String-based queries, for example with the https://github.com/neo4j/neo4j-java-driver[Neo4j Java driver] or inside embedded procedures and of course with Spring Data's https://github.com/spring-projects/spring-data-neo4j/blob/master/src/main/java/org/springframework/data/neo4j/core/Neo4jClient.java[Neo4j-Client]. Parameters in the generated queries will use the `$form` and as such be compatible with all current versions of Neo4j. Users of SDN 6+ can use the generated `org.neo4j.cypherdsl.core.Statement` directly with the `Neo4jTemplate` or the `ReactiveNeo4jTemplate`. Both the imperative and the reactive variants allow the retrieval and counting of entities without rendering a String first, for example through `Neo4jTemplate#findAll(Statement, Class)`. == Java API Find the Java-API and a generated project info here: link:project-info/apidocs/index.html[API] and link:project-info/index.html[project info]. ================================================ FILE: docs/properties/index.adoc ================================================ [[properties]] = Properties Nodes and Relationships expose properties. This reflects directly in the Cypher-DSL: [[properties-on-nodes-and-rel]] [source,java,indent=0] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/PropertiesTests.java[tag=properties-on-nodes-and-rel] ---- <.> Create a property expression from a `Node` <.> Create a property expression from a `Relationship` Both Node and Relationship should be named as in the example. The Cypher-DSL generates names if they are not named, to refer to them in the statements. Without the explicit names, the generated statement would look like this: [source,cypher] ---- MATCH (geIcWNUD000:`Person`)-[TqfqBNcc001:`RATED`]->(:`Movie`) RETURN geIcWNUD000.name, TqfqBNcc001.rating ---- The name is of course random. The `Cypher` class exposes the `property` method, too. This methods takes in one name (as symbolic name or as string literal) OR one expression and at least one further string, referring to the name of the property. Passing in a symbolic name would lead to a similar result like in <>, an expression can refer to the results of functions etc.: [[properties-on-expressions]] [source,java,indent=0] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/PropertiesTests.java[tag=properties-on-expressions] ---- <.> Here an expression, the `datetime()` function is passed in and the `epocheSeconds` property is dereferenced. Nested properties are of course possible as well, either directly on nodes and relationships or via the static builder: [[nested-properties]] [source,java,indent=0] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/PropertiesTests.java[tag=nested-properties] ---- ================================================ FILE: docs/static-meta-model/concepts.adoc ================================================ == Concepts === A static meta model is optional First let's stress this: a static meta model is optional for the Cypher-DSL. It is completely OK to use the Cypher-DSL as shown in the <>: all labels, types and properties can be named as you go. The Cypher-DSL will still give you type-safety in regard to the Cypher's syntax. This fits nicely with Neo4j's capabilities: Neo4j is a schemaless database. Nodes, their relationships between each other, their labels and properties can be changed as you go but all of this information can still be queried. In a schemaless database or a database with dynamic scheme the scheme is often defined by the application. This definition takes many forms: It can be through an object mapper like https://github.com/neo4j/neo4j-ogm[Neo4j-OGM] or https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#reference[Spring Data Neo4j 6+], or maybe in form of Graph-QL schemes. Another source may be the information we can retrieve from the database itself via https://neo4j.com/docs/operations-manual/current/reference/procedures/#procedure_db_schema_nodetypeproperties[`db.schema.nodeTypeProperties`] and https://neo4j.com/docs/operations-manual/current/reference/procedures/#procedure_db_schema_reltypeproperties[`db.schema.relTypeProperties`]. [[static-meta-model.building-blocks]] === Building blocks The Cypher DSL offers the following building blocks as part of the public API: * Two pattern elements: ** Nodes via `Node` (and its default implementation `NodeBase`) ** Relationships via `Relationship` (and its default implementation `RelationshipBase`) * `Property`, which is a holder for properties When you use Cypher-DSL like this: [source,java] ---- var m = Cypher.node("Movie").named("m"); var statement = Cypher.match(m).returning(this.m).build(); ---- `m` will be a `Node` instance having the label `Movie` and an alias of `m`. `m` can be used everywhere where a pattern element can be used according to the openCypher spec. You don't have to care about its type. That's why we vouched for the JDK11+ local type inference here and omitted the declaration of the type `Node`: it just reads better. === A very simple, static model Both `NodeBase` and `RelationshipBase` are meant to be extended to put your static model into something that is usable with the Cypher-DSL. ==== Nodes Start by extending `NodeBase` like this: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/Movie.java[tag=simple-model] ---- <.> Extend from `NodeBase` and specify your class as a "self" type-argument <.> Optional: Create one static instance of your model <.> This is where you specify one or more label <.> This constructor is optional, it is used in the next two steps <.> `named` must be overridden and must return new copies of the node, with the changed symbolic name. It must be overridden to guarantee type integrity. <.> Same as above With that in place, you can already use it like this: [source,java,indent=0,tabsize=4] [[static-movie]] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/StaticModelIT.java[tag=simple-model] ---- and it would generate a Cypher string like this: `"MATCH (jwKyXzwS000:`Movie`) RETURN jwKyXzwS000"`, with generated variable names. If you don't like them, you can just rename one instance of the movie-model like this: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/StaticModelIT.java[tag=simple-model-renamed] ---- Of course, properties belong into a model as well. You add them like this: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/Movie.java[tag=add-properties] ---- <.> Same class before, extending from `NodeBase`. <.> Use `this` and the `property` method to create a new `Property` instance, stored on the given instance A possible usage scenario looks like this: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/StaticModelIT.java[tag=add-properties] ---- <.> Make sure to use the renamed instance everywhere. Here: For accessing the property. Alternatively, don't rename. ==== Relationships Relationships are a bit more complicated. Relationships of the same type can be used between nodes with different labels. We have these scenarios: * `(s:LabelA) - (r:SomeType) -> (e:LabelB)`: * `(s:LabelA) - (r:SomeType) -> (e)` * `(s) - (r:SomeType) -> (e)` We either have a type that is used only between the same set of labels, or a type is used always with one fixed label or a type is used between arbitrary labels. To accommodate for that, the default relationship implementation, `RelationshipBase`, spots three type parameters: [source,java] ---- public class RelationshipBase, E extends NodeBase, SELF extends RelationshipBase> { } ---- `S` is the type of a start node, `E` of an end node and `SELF` is the concrete implementation itself. The public API of `RelationshipBase` enforces a direction from start to end (`LTR`, left to right). We just have a look at the first case to make the concepts clear. We model the `ACTED_IN` relationship of the movie graph. It exists between people and movies (going from person to movie) and has an attribute roles: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/ActedIn.java[tag=simple-model] ---- <.> The base class, with 3 concrete types. A `Person`, the `Movie` from <> and the type itself. <.> Same idea with as with nodes: Store all properties as final attributes. <.> There is no default constructor for a relationship: Start and end node must be specified. The type is fixed. <.> Copy constructor for the following two methods <.> Required to rename this relationship <.> Required for querying properties In contrast to the movie, we don't need a static attribute allowing access to the relationship. This is stored at the owner. In this case, the `Person`: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/Person.java[tag=simple-model] ---- <.> We create the relationship properties with `this` concrete instance pointing to another, concrete instance. === Summary While there are possible other ways to define a static meta model with the Cypher-DSL, this is the way we create them with the `neo4j-cypher-dsl-codegen` models. ================================================ FILE: docs/static-meta-model/index.adoc ================================================ [[static-meta-model]] = Building a static metamodel include::concepts.adoc[] include::possible-usage.adoc[] include::sdn6-annotation-processor.adoc[] include::ogm-annotation-processor.adoc[] ================================================ FILE: docs/static-meta-model/ogm-annotation-processor.adoc ================================================ [[ogm-annotation-processor]] == The Neo4j-OGM annotation processor We provide a Java annotation processor for https://github.com/neo4j/neo4j-ogm[Neo4j-OGM] under the following coordinates: [source,subs="verbatim,attributes",indent=0,tabsize=4] .Coordinates of the Neo4j-OGM annotation processor ---- {groupId}:neo4j-cypher-dsl-codegen-ogm:{the-version} ---- The annotation processor understands classes annotated with `@NodeEntity` and `@RelationshipEntity`. Inside those classes `@Relationship`, `@Property`, `@StartNode` and `@EndNode` are read. The processor generates a static metamodel for each annotated class found in the same package with an underscore (`_`) added to the name. The processor needs Neo4j-OGM 4 and the Cypher-DSL in version `2025.0.0` or later on it's classpath. We recommend using it explicitly on the separate annotation processor classpath (via `--processor-path` to `javac`). The processor does *not* support nested classes used as `@NodeEntity` or `@RelationshipEntity`. This processor is a great choice for applications built on https://quarkus.io[Quarkus] using https://github.com/neo4j/neo4j-ogm-quarkus[Neo4j-OGM-Quarkus] but also for https://github.com/neo4j/neo4j-ogm-spring[Neo4j-OGM-Spring], our Spring Data Neo4j 5 work that supports Spring Boot >= 3 and the latest Spring Data generation with the behaviour of SDN5 and OGM. === Configure your build ==== Maven As a Maven user, please configure the build as follows: [source,xml,subs="verbatim,attributes",indent=0,tabsize=4] ---- org.apache.maven.plugins maven-compiler-plugin {groupId} neo4j-cypher-dsl-codegen-ogm {neo4j-cypher-dsl.version} ---- ==== Gradle In a Gradle project, please add the following: [source,groovy,subs="verbatim,attributes"] ---- dependencies { annotationProcessor '{groupId}:neo4j-cypher-dsl-codegen-ogm:{the-version}' } ---- ==== As dependency on the classpath NOTE: We recommend the annotation processor path for two reasons: The processor needs Neo4j-OGM as dependency. While Neo4j-OGM is already on the classpath, it might not fit the one we build the annotation processor against exactly. While the processor is lenient in that regard, your dependency setup might not. Furthermore: Why should you have the annotation processor as a dependency in your final artifact? This would be unnecessary. If you insist on having the Neo4j-OGM annotation processor on the standard class path, please include with your Neo4j-OGM application like as follows to avoid dependency conflicts: [source,xml,subs="verbatim,attributes",indent=0,tabsize=4] ---- {groupId} neo4j-cypher-dsl-codegen-ogm {neo4j-cypher-dsl.version} true org.neo4j neo4j-ogm-core ---- === Usage The processor supports the same arguments as the <> except for `org.neo4j.cypherdsl.codegen.sdn.custom_converter_classes`. The usage of the generated classes is identical. ================================================ FILE: docs/static-meta-model/possible-usage.adoc ================================================ == Possible Usage Please assume we did model the "Movie graph" (`:play movies` in Neo4j-Browser) with the following scheme: image::movie-graph.png[] and these classes, which have been generated with the <> (the required functions have been omitted for brevity): [source,java,indent=0,tabsize=4] ---- final class Movie extends NodeBase { public static final Movie MOVIE = new Movie(); public final Property TAGLINE = this.property("tagline"); public final Property TITLE = this.property("title"); public final Property RELEASED = this.property("released"); public Movie() { super("Movie"); } } final class Person extends NodeBase { public static final Person PERSON = new Person(); public final Property NAME = this.property("name"); public final Property FIRST_NAME = this.property("firstName"); public final Directed DIRECTED = new Directed<>(this, Movie.MOVIE); public final ActedIn ACTED_IN = new ActedIn(this, Movie.MOVIE); public final Property BORN = this.property("born"); public final Property DOB = this.property("dob"); public Person() { super("Person"); } } final class ActedIn extends RelationshipBase { public static final String $TYPE = "ACTED_IN"; public final Property ROLE = this.property("role"); protected ActedIn(Person start, Movie end) { super(start, $TYPE, end); } } final class Directed> extends RelationshipBase> { public static final String $TYPE = "DIRECTED"; protected Directed(Person start, E end) { super(start, $TYPE, end); } } ---- === Work with properties Properties can be used like normal objects: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/StaticModelIT.java[tag=work-with-properties] ---- Of course, new properties can be derived: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/StaticModelIT.java[tag=deriving-new-properties] ---- === Query nodes or relationships by properties Use `withProperties` (and `named` you like) to model your queries as needed. Applicable to properties of nodes such as the title: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/StaticModelIT.java[tag=query-node-by-properties] ---- and relationships: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/StaticModelIT.java[tag=query-rel-by-properties] ---- Note that the query will look like this, as we didn't rename the objects and they used generated names: [source,cypher] ---- `MATCH (dZVpwHhe000:`Person`)-[JcVKsSrn001:`ACTED_IN` {role: 'Neo'}]->(cDWeUJSI002:`Movie`) RETURN cDWeUJSI002`` as we didn't specify aliases) ---- === Work with relationships Relationships can be worked with like with properties: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/StaticModelIT.java[tag=multiple-relationships] ---- They are quite flexible together with the `inverse` method. The following example also shows how to include non-static parts: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/StaticModelIT.java[tag=chaining-relationships] ---- <.> Using a non-static fragment ================================================ FILE: docs/static-meta-model/sdn6-annotation-processor.adoc ================================================ [[sdn6-annotation-processor]] == The Spring Data Neo4j 6 annotation processor We provide a Java annotation processor for https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#reference[Spring Data Neo4j] under the following coordinates: [source,subs="verbatim,attributes",indent=0,tabsize=4] .Coordinates of the SDN 6 annotation processor ---- {groupId}:neo4j-cypher-dsl-codegen-sdn6:{the-version} ---- The annotation processor understands classes annotated with `@Node` and `@RelationshipProperties`. Inside those classes `@Relationship` and `@Property` are read. The processor generates a static metamodel for each annotated class found in the same package with an underscore (`_`) added to the name. The processor needs Spring Data Neo4j 6 and the Cypher-DSL in version `2021.1.0` or later on it's classpath. We recommend using it explicitly on the separate annotation processor classpath (via `--processor-path` to `javac`). TIP: Please make sure to use `@Relationship` when you use the annotation processor. While we do our best to detect possible, implicit associations during annotation processing, we can't load classes that are being processed that very moment to check if the Spring infrastructure would provide a custom converter for them and make them a simple property. We won't generate fields when in doubt but relationships if we find the corresponding class. The processor does *not* support nested classes used as `@Node` or `@RelationshipProperties` entities. === Configure your build ==== Maven As a Maven user, please configure the build as follows: [source,xml,subs="verbatim,attributes",indent=0,tabsize=4] ---- org.apache.maven.plugins maven-compiler-plugin {groupId} neo4j-cypher-dsl-codegen-sdn6 {neo4j-cypher-dsl.version} ---- ==== Gradle In a Gradle project, please add the following: [source,groovy,subs="verbatim,attributes"] ---- dependencies { annotationProcessor '{groupId}:neo4j-cypher-dsl-codegen-sdn6:{the-version}' } ---- ==== As dependency on the classpath NOTE: We recommend the annotation processor path for two reasons: The processor needs SDN as dependency. While SDN is already on the classpath, it might not fit the one we build the annotation processor against exactly. While the processor is lenient in that regard, your dependency setup might not. Furthermore: Why should you have the annotation processor as a dependency in your final artifact? This would be unnecessary. If you insist on having the SDN 6 annotation processor on the standard class path, please include with your Spring Data Neo4j 6 application like as follows to avoid dependency conflicts: [source,xml,subs="verbatim,attributes",indent=0,tabsize=4] ---- {groupId} neo4j-cypher-dsl-codegen-sdn6 {neo4j-cypher-dsl.version} true org.slf4j slf4j-simple org.springframework.data spring-data-neo4j ---- === Usage The processor supports the following arguments: |=== |Name | Meaning | org.neo4j.cypherdsl.codegen.prefix, | An optional prefix for the generated classes | org.neo4j.cypherdsl.codegen.suffix | An optional suffix for the generated classes | org.neo4j.cypherdsl.codegen.indent_style | The indent style (Use `TAB` for tabs, `SPACE` for spaces) | org.neo4j.cypherdsl.codegen.indent_size | The number of whitespaces for the indent style `SPACE` | org.neo4j.cypherdsl.codegen.timestamp | An optional timestamp in ISO_OFFSET_DATE_TIME format for the generated classes. Defaults to the time of generation. | org.neo4j.cypherdsl.codegen.add_at_generated | A flag whether `@Generated` should be added | org.neo4j.cypherdsl.codegen.sdn.custom_converter_classes | A comma separated list of custom Spring converter classes (Can be `Converter`, `GenericConverter`, `ConverterAware`, pretty much everything you can register with `org.springframework.data.neo4j.core.convert.Neo4jConversions` ). However, these converters must have a default-non-args constructor in the context of the annotation processor. | org.neo4j.cypherdsl.codegen.excludes | A comma separated list of fully qualified names of types that should be ignored (i.e. despite being annotated as `Node` or `RelationshipProperties`) |=== The generated classes can be used in a variety of places: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/MovieService.java[tag=as-property] ---- <.> Pass the name of the property `TITLE` to Spring Data's sort facility Spring Data Neo4j 6.1 has two additional query fragments, that makes working with the Cypher-DSL very efficient: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/PeopleRepository.java[tag=additional-fragments] ---- <.> Allows to just add conditions to our generated queries <.> Provides an alternative to using @Query with strings Both interfaces are *independent* of each other, they can be used together like in this example or separately (just picking the one you need). You can read more about them in the Spring Data Neo4j 6.1 documentation, chapter https://docs.spring.io/spring-data/neo4j/docs/6.1.0-M5/reference/html/#sdn-mixins[Spring Data Neo4j Extensions]. The repository above can be used like this: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/PeopleService.java[tag=using-person-repo] ---- <.> Using literals <.> Using an optional parameter <.> or when it's not filled, an empty condition <.> Project a person onto `PersonDetails`, using a complex query. Here is another complex example. The `MovieRepository` is also a `CypherdslStatementExecutor`: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/MovieService.java[tag=more-examples] ---- <.> Here we use an anonymous parameter to store the `name` value The full example is in `neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6`. An example of using a mixture of anonymous and named parameters is shown in the following listing: [source,java,indent=0,tabsize=4] ---- include::../../neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/PeopleService.java[tag=using-temporals] ---- <.> An anonymous parameter with a simple `String`-value <.> A named parameter with an `Integer` value extracted from a complex datatype <.> An anonymous parameter again, but one that holds a complex datatype. Such datatype can be anything that is understood by the Neo4j-Java-Driver, such as in this case, a temporal but also maps and lists. It will always be passed as a value, never as a literal. The statement above will be rendered like this: [source,console] ---- [http-nio-auto-1-exec-1] 2022-06-21 11:40:57,732 DEBUG org.springframework.data.neo4j.cypher: 313 - Executing: MERGE (p:`Person` {name: $pcdsl01}) ON CREATE SET p.born = $arbitraryName, p.dob = $pcdsl02 RETURN p [http-nio-auto-1-exec-1] 2022-06-21 11:40:57,735 TRACE org.springframework.data.neo4j.cypher: 334 - with parameters: :param arbitraryName => 1990 :param pcdsl01 => 'Liv Lisa Fries' :param pcdsl02 => 1990-10-31T22:42Z[UTC] ---- ================================================ FILE: etc/architecture/api.adoc ================================================ [[arch-rules.api]] [role=group,includesConstraints="arch-rules.api:*"] ==== API ===== General considerations We use https://github.com/apiguardian-team/apiguardian[@API Guardian] to keep track of what we expose as public or internal API. To keep things both clear and concise, we restrict the usage of those annotations to interfaces, classes (only public methods and constructors: and annotations. [[arch-rules.api:api-guardian-usage]] [source,java,indent=0,tabsize=4] .@API Guardian annotations must not be used on fields ---- include::../../neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/PackageAndAPIStructureTests.java[tag=arch-rules.structure:core-must-not-depend-on-renderer] ---- ===== Internal API While we are pretty clear about the intended use of our classes (being experimental, public API or strictly internal), we want to make sure that no-one can coincidentally inherit from internal classes that we couldn't restrict to default package visibility. There should not be any internal, public API inside the core package. Everything that needs to be public for reasons (like being used in the renderer), must go into the `internal` package. This package won't be exported via `module-info.java`. [[arch-rules.api:internal]] [source,java,indent=0,tabsize=4] .Non abstract, public classes that are only part of internal API must be final ---- include::../../neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/PackageAndAPIStructureTests.java[tag=arch-rules.api:internal] ---- ================================================ FILE: etc/architecture/index.adoc ================================================ [[arch-rules]] [role=group,includesGroups="arch-rules.naming,arch-rules.api,arch-rules.structure"] == Architecture The Neo4j-Cypher-DSL consists of one main module: `org.neo4j.cypherdsl.core`. The coordinates of that module `{groupId}:{artifactId}`, the JDK module name is `org.neo4j.cypherdsl.core`. The rendering feature is part of the core module. All other modules depend on the core. As the core reflects the Cypher language, it is not meant to be extendable. Therefore, there is little to know API to do so with the AST visitor being the exception. We document our rules structure with https://www.archunit.org[ArchUnit] within a single unittest named `org.neo4j.cypherdsl.core.PackageAndAPIStructureTests`. === Coding rules include::naming.adoc[] include::api.adoc[] === Structure include::structure.adoc[] ================================================ FILE: etc/architecture/naming.adoc ================================================ [[arch-rules.naming]] [role=group,includesConstraints="arch-rules.naming:*"] ==== Consistent naming The following naming conventions are used throughout the project: [[arch-rules.naming:TypeNameMustBeginWithGroupId]] [source,java,indent=0,tabsize=4] .All Java types must be located in packages that start with `org.neo4j.cypherdsl`. ---- include::../../neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/PackageAndAPIStructureTests.java[tag=arch-rules.naming:TypeNameMustBeginWithGroupId] ---- ================================================ FILE: etc/architecture/structure.adoc ================================================ [[arch-rules.structure]] [role=group,includesConstraints="arch-rules.structure:*"] ==== Neo4j-Cypher-DSL Core The core of the Cypher-DSL is consist of a set of classes that loosely reassemble the https://www.opencypher.org[openCypher spec], especially in the https://s3.amazonaws.com/artifacts.opencypher.org/M15/railroad/Cypher.html[railroad diagrams]. The main package of the core module is `org.neo4j.cypherdsl.core` which also reflects in the JDK module name: `org.neo4j.cypherdsl.core`. Part of the Cypher-DSL core is also the `renderer` package as the main goal of the core is to render Cypher. The `renderer` package is a sub-package of `core` as it is an essential part of it and in addition, the above mentioned JDK module name should reflect exactly one package. So while all other subpackages in `core` can be used freely from the `core` classes themselves, we don't want to access the `renderer` package apart from one exception: The `AbstractStatement` class can be used to invoke the rendering process without explicitly specifying a renderer: [[arch-rules.structure:core-must-not-depend-on-renderer]] [source,java,indent=0,tabsize=4] .The Cypher-DSL core package must not depend on the rendering infrastructure ---- include::../../neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/PackageAndAPIStructureTests.java[tag=arch-rules.structure:core-must-not-depend-on-renderer] ---- The `renderer` package is not only free to use the whole `core`, it must do so to fulfill its purpose. The `ast` and `utils` packages however should not have dependencies outside their own: [[arch-rules.structure:supporting-packages-are-dependency-free]] [source,java,indent=0,tabsize=4] .Supporting packages must not depend on anything from the outside ---- include::../../neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/PackageAndAPIStructureTests.java[tag=arch-rules.structure:supporting-packages-are-dependency-free] ---- ================================================ FILE: etc/changelog.tpl ================================================ ==== 🚀 Features ==== 🐛 Bug Fixes ==== 🔄️ Refactorings ==== 📖 Documentation ==== 🧰 Tasks ==== 🧹 Housekeeping ==== 🛠 Build ================================================ FILE: etc/checkstyle/config.xml ================================================ ================================================ FILE: etc/checkstyle/suppressions.xml ================================================ ================================================ FILE: etc/index.tpl ================================================ Redirecting...

Please follow this link.

================================================ FILE: etc/license.tpl ================================================ Copyright (c) 2019-${year} "Neo4j," Neo4j Sweden AB [https://neo4j.com] This file is part of Neo4j. 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. ================================================ FILE: etc/recipes/rewrite.yml ================================================ # # Copyright (c) 2019-2026 "Neo4j," # Neo4j Sweden AB [https://neo4j.com] # # This file is part of Neo4j. # # 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. # # Run with # ./mvnw org.openrewrite.maven:rewrite-maven-plugin:run \ # -Drewrite.configLocation=/full/path/to/cypher-dsl/etc/recipes/rewrite.yml \ # -Drewrite.activeRecipes=cypher-dsl.rewriteIdCalls --- type: specs.openrewrite.org/v1beta/recipe name: cypher-dsl.rewriteIdCalls displayName: Change calls to Functions.id to Functions.elementId recipeList: - org.openrewrite.java.ChangeMethodName: methodPattern: org.neo4j.cypherdsl.core.Functions id(..) newMethodName: elementId ignoreDefinition: true ================================================ FILE: mvnw ================================================ #!/bin/sh # ---------------------------------------------------------------------------- # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you 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. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Apache Maven Wrapper startup batch script, version 3.3.4 # # Required ENV vars: # ------------------ # JAVA_HOME - location of a JDK home dir # # Optional ENV vars # ----------------- # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 # MAVEN_SKIP_RC - flag to disable loading of mavenrc files # ---------------------------------------------------------------------------- if [ -z "$MAVEN_SKIP_RC" ]; then if [ -f /usr/local/etc/mavenrc ]; then . /usr/local/etc/mavenrc fi if [ -f /etc/mavenrc ]; then . /etc/mavenrc fi if [ -f "$HOME/.mavenrc" ]; then . "$HOME/.mavenrc" fi fi # OS specific support. $var _must_ be set to either true or false. cygwin=false darwin=false mingw=false case "$(uname)" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true ;; Darwin*) darwin=true # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then JAVA_HOME="$(/usr/libexec/java_home)" export JAVA_HOME else JAVA_HOME="/Library/Java/Home" export JAVA_HOME fi fi ;; esac if [ -z "$JAVA_HOME" ]; then if [ -r /etc/gentoo-release ]; then JAVA_HOME=$(java-config --jre-home) fi fi # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin; then [ -n "$JAVA_HOME" ] \ && JAVA_HOME=$(cygpath --unix "$JAVA_HOME") [ -n "$CLASSPATH" ] \ && CLASSPATH=$(cygpath --path --unix "$CLASSPATH") fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw; then [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \ && JAVA_HOME="$( cd "$JAVA_HOME" || ( echo "cannot cd into $JAVA_HOME." >&2 exit 1 ) pwd )" fi if [ -z "$JAVA_HOME" ]; then javaExecutable="$(which javac)" if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. readLink=$(which readlink) if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then if $darwin; then javaHome="$(dirname "$javaExecutable")" javaExecutable="$(cd "$javaHome" && pwd -P)/javac" else javaExecutable="$(readlink -f "$javaExecutable")" fi javaHome="$(dirname "$javaExecutable")" javaHome=$(expr "$javaHome" : '\(.*\)/bin') JAVA_HOME="$javaHome" export JAVA_HOME fi fi fi if [ -z "$JAVACMD" ]; then if [ -n "$JAVA_HOME" ]; then if [ -x "$JAVA_HOME/jre/sh/java" ]; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi else JAVACMD="$( \unset -f command 2>/dev/null \command -v java )" fi fi if [ ! -x "$JAVACMD" ]; then echo "Error: JAVA_HOME is not defined correctly." >&2 echo " We cannot execute $JAVACMD" >&2 exit 1 fi if [ -z "$JAVA_HOME" ]; then echo "Warning: JAVA_HOME environment variable is not set." >&2 fi # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { if [ -z "$1" ]; then echo "Path not specified to find_maven_basedir" >&2 return 1 fi basedir="$1" wdir="$1" while [ "$wdir" != '/' ]; do if [ -d "$wdir"/.mvn ]; then basedir=$wdir break fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then wdir=$( cd "$wdir/.." || exit 1 pwd ) fi # end of workaround done printf '%s' "$( cd "$basedir" || exit 1 pwd )" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then # Remove \r in case we run on Windows within Git Bash # and check out the repository with auto CRLF management # enabled. Otherwise, we may read lines that are delimited with # \r\n and produce $'-Xarg\r' rather than -Xarg due to word # splitting rules. tr -s '\r\n' ' ' <"$1" fi } log() { if [ "$MVNW_VERBOSE" = true ]; then printf '%s\n' "$1" fi } BASE_DIR=$(find_maven_basedir "$(dirname "$0")") if [ -z "$BASE_DIR" ]; then exit 1 fi MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} export MAVEN_PROJECTBASEDIR log "$MAVEN_PROJECTBASEDIR" trim() { # MWRAPPER-139: # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. # Needed for removing poorly interpreted newline sequences when running in more # exotic environments such as mingw bash on Windows. printf "%s" "${1}" | tr -d '[:space:]' } ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" if [ -r "$wrapperJarPath" ]; then log "Found $wrapperJarPath" else log "Couldn't find $wrapperJarPath, downloading it ..." if [ -n "$MVNW_REPOURL" ]; then wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" else wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" fi while IFS="=" read -r key value; do case "$key" in wrapperUrl) wrapperUrl=$(trim "${value-}") break ;; esac done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" log "Downloading from: $wrapperUrl" if $cygwin; then wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") fi if command -v wget >/dev/null; then log "Found wget ... using wget" [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then wget ${QUIET:+"$QUIET"} "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" else wget ${QUIET:+"$QUIET"} --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" fi elif command -v curl >/dev/null; then log "Found curl ... using curl" [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then curl ${QUIET:+"$QUIET"} -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" else curl ${QUIET:+"$QUIET"} --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" fi else log "Falling back to using Java to download" javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" # For Cygwin, switch paths to Windows format before running javac if $cygwin; then javaSource=$(cygpath --path --windows "$javaSource") javaClass=$(cygpath --path --windows "$javaClass") fi if [ -e "$javaSource" ]; then if [ ! -e "$javaClass" ]; then log " - Compiling MavenWrapperDownloader.java ..." ("$JAVA_HOME/bin/javac" "$javaSource") fi if [ -e "$javaClass" ]; then log " - Running MavenWrapperDownloader.java ..." ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" fi fi fi fi ########################################################################################## # End of extension ########################################################################################## # If specified, validate the SHA-256 sum of the Maven wrapper jar file wrapperSha256Sum="" while IFS="=" read -r key value; do case "$key" in wrapperSha256Sum) wrapperSha256Sum=$(trim "${value-}") break ;; esac done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" if [ -n "$wrapperSha256Sum" ]; then wrapperSha256Result=false if command -v sha256sum >/dev/null; then if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c - >/dev/null 2>&1; then wrapperSha256Result=true fi elif command -v shasum >/dev/null; then if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then wrapperSha256Result=true fi else echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2 exit 1 fi if [ $wrapperSha256Result = false ]; then echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 exit 1 fi fi MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then [ -n "$JAVA_HOME" ] \ && JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") [ -n "$CLASSPATH" ] \ && CLASSPATH=$(cygpath --path --windows "$CLASSPATH") [ -n "$MAVEN_PROJECTBASEDIR" ] \ && MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") fi # Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain # shellcheck disable=SC2086 # safe args exec "$JAVACMD" \ $MAVEN_OPTS \ $MAVEN_DEBUG_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" ================================================ FILE: mvnw.cmd ================================================ @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @REM distributed with this work for additional information @REM regarding copyright ownership. The ASF licenses this file @REM to you under the Apache License, Version 2.0 (the @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM @REM http://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @REM KIND, either express or implied. See the License for the @REM specific language governing permissions and limitations @REM under the License. @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- @REM Apache Maven Wrapper startup batch script, version 3.3.4 @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files @REM ---------------------------------------------------------------------------- @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' @echo off @REM set title of command window title %0 @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* :skipRcPre @setlocal set ERROR_CODE=0 @REM To isolate internal variables from possible post scripts, we use another setlocal @setlocal @REM ==== START VALIDATION ==== if not "%JAVA_HOME%" == "" goto OkJHome echo. >&2 echo Error: JAVA_HOME not found in your environment. >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. >&2 goto error :OkJHome if exist "%JAVA_HOME%\bin\java.exe" goto init echo. >&2 echo Error: JAVA_HOME is set to an invalid directory. >&2 echo JAVA_HOME = "%JAVA_HOME%" >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. >&2 goto error @REM ==== END VALIDATION ==== :init @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". @REM Fallback to current working directory if not found. set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir set EXEC_DIR=%CD% set WDIR=%EXEC_DIR% :findBaseDir IF EXIST "%WDIR%"\.mvn goto baseDirFound cd .. IF "%WDIR%"=="%CD%" goto baseDirNotFound set WDIR=%CD% goto findBaseDir :baseDirFound set MAVEN_PROJECTBASEDIR=%WDIR% cd "%EXEC_DIR%" goto endDetectBaseDir :baseDirNotFound set MAVEN_PROJECTBASEDIR=%EXEC_DIR% cd "%EXEC_DIR%" :endDetectBaseDir IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig @setlocal EnableExtensions EnableDelayedExpansion for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% :endReadAdditionalConfig SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM This allows using the maven wrapper in projects that prohibit checking in binary data. if exist %WRAPPER_JAR% ( if "%MVNW_VERBOSE%" == "true" ( echo Found %WRAPPER_JAR% ) ) else ( if not "%MVNW_REPOURL%" == "" ( SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... echo Downloading from: %WRAPPER_URL% ) powershell -Command "&{"^ "$webclient = new-object System.Net.WebClient;"^ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ "}"^ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ "}" if "%MVNW_VERBOSE%" == "true" ( echo Finished downloading %WRAPPER_JAR% ) ) @REM End of extension @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file SET WRAPPER_SHA_256_SUM="" FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B ) IF NOT %WRAPPER_SHA_256_SUM%=="" ( powershell -Command "&{"^ "Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ " Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ " Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ " Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ " exit 1;"^ "}"^ "}" if ERRORLEVEL 1 goto error ) @REM Provide a "standardized" way to retrieve the CLI args that will @REM work with both Windows and non-Windows executions. set MAVEN_CMD_LINE_ARGS=%* %MAVEN_JAVA_EXE% ^ %JVM_CONFIG_MAVEN_PROPS% ^ %MAVEN_OPTS% ^ %MAVEN_DEBUG_OPTS% ^ -classpath %WRAPPER_JAR% ^ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end :error set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' if "%MAVEN_BATCH_PAUSE%"=="on" pause if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% cmd /C exit /B %ERROR_CODE% ================================================ FILE: neo4j-cypher-dsl/pom.xml ================================================ 4.0.0 org.neo4j neo4j-cypher-dsl-parent ${revision}${sha1}${changelist} neo4j-cypher-dsl Neo4j Cypher DSL (Core) The core module of the Cypher DSL, including all supported elements and the default renderer. ${basedir}/../${aggregate.report.dir} org.neo4j neo4j-cypher-dsl-bom ${project.version} pom import org.apiguardian apiguardian-api org.neo4j neo4j-cypher-dsl-schema-name-support com.querydsl querydsl-core provided true org.neo4j neo4j-cypher-dsl-build-annotations ${project.version} provided true cglib cglib test com.tngtech.archunit archunit test org.assertj assertj-core test org.junit.jupiter junit-jupiter test org.mockito mockito-core test org.slf4j slf4j-simple test org.codehaus.mojo exec-maven-plugin ${exec-maven-plugin.version} remove-shaded-modules exec generate-sources bin/remove-shaded-modules.sh org.apache.maven.plugins maven-surefire-plugin @{argLine} -Xverify:all --add-modules com.querydsl.core --add-reads org.neo4j.cypherdsl.core=com.querydsl.core --add-opens java.base/java.lang=ALL-UNNAMED org.apache.maven.plugins maven-compiler-plugin default-compile -Aorg.neo4j.cypherdsl.build.native_config_dir=${project.groupId}/${project.artifactId} -Xlint:all,-options,-path,-processing,-exports -Werror org.neo4j neo4j-cypher-dsl-build-proc ${project.version} default-testCompile -Aquerydsl.generatedAnnotationClass=com.querydsl.core.annotations.Generated -Xlint:all,-options,-path,-processing,-exports,-missing-explicit-ctor,-classfile -Werror -implicit:class com.querydsl querydsl-apt ${querydsl.version} general javax.annotation javax.annotation-api ${javax.annotation-api.version} org.apache.maven.plugins maven-shade-plugin shade package org.neo4j:neo4j-cypher-dsl-schema-name-support org.neo4j.cypherdsl.support.schema_name org.neo4j.cypherdsl.core.internal true org.neo4j:neo4j-cypher-dsl-schema-name-support **/SchemaNames*class module-info.class org.moditect moditect-maven-plugin add-module-infos add-module-info package true ${project.build.directory}/modules/module-info.java com.github.siom79.japicmp japicmp-maven-plugin com\.querydsl\.core\..* org\.neo4j\.cypherdsl\.core\.internal\.LiteralBase org\.neo4j\.cypherdsl\.core\.internal\.StatementContext ================================================ FILE: neo4j-cypher-dsl/src/main/java/module-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * @author Michael J. Simons * @since 2023.0.0 */ @SuppressWarnings({"requires-automatic", "requires-transitive-automatic"}) module org.neo4j.cypherdsl.core { requires static com.querydsl.core; requires static java.sql; requires static org.neo4j.cypherdsl.build.annotations; requires transitive org.apiguardian.api; requires org.neo4j.cypherdsl.support.schema_name; exports org.neo4j.cypherdsl.core; exports org.neo4j.cypherdsl.core.ast; exports org.neo4j.cypherdsl.core.renderer; exports org.neo4j.cypherdsl.core.annotations; } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/AbstractCase.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.internal.CaseElse; import org.neo4j.cypherdsl.core.internal.CaseWhenThen; /** * Abstract base class for a {@link Case}. * * @author Gerrit Meier * @author Michael J. Simons * @since 2021.2.3 */ abstract class AbstractCase implements Case { private final List caseWhenThens; private CaseElse caseElse; private Optional prefix = Optional.empty(); private Optional suffix = Optional.empty(); AbstractCase(List caseWhenThens) { this.caseWhenThens = new ArrayList<>(caseWhenThens); } abstract Expression getCaseExpression(); @Override public String toString() { return RendererBridge.render(this); } void setCaseElse(CaseElse caseElse) { this.caseElse = caseElse; } /** * Creates a new case/when expression with an additional {@code WHEN} block. * @param nextExpression the next expression to use. * @return an ongoing when builder. */ @Override @CheckReturnValue public OngoingWhenThen when(Expression nextExpression) { return new DefaultOngoingWhenThen(nextExpression); } @Override public Optional getPrefix() { return this.prefix; } @Override public Optional getSuffix() { return this.suffix; } @Override public Property property(String... names) { this.prefix = Optional.of("("); this.suffix = Optional.of(")"); return Case.super.property(names); } @Override public void accept(Visitor visitor) { visitor.enter(this); if (getCaseExpression() != null) { getCaseExpression().accept(visitor); } this.caseWhenThens.forEach(caseWhenThen -> caseWhenThen.accept(visitor)); if (this.caseElse != null) { this.caseElse.accept(visitor); } visitor.leave(this); } static class SimpleCaseImpl extends AbstractCase implements SimpleCase { private final Expression caseExpression; SimpleCaseImpl(Expression caseExpression) { this(caseExpression, Collections.emptyList()); } SimpleCaseImpl(Expression caseExpression, List caseWhenThens) { super(caseWhenThens); this.caseExpression = caseExpression; } @Override Expression getCaseExpression() { return this.caseExpression; } /** * The renderable implementation of {@link SimpleCase}. */ static final class EndingSimpleCase extends SimpleCaseImpl implements CaseEnding { private EndingSimpleCase(Expression caseExpression, List caseWhenThens) { super(caseExpression, caseWhenThens); } @Override public Case elseDefault(Expression defaultExpression) { this.setCaseElse(new CaseElse(defaultExpression)); return this; } } } static class GenericCaseImpl extends AbstractCase implements GenericCase { GenericCaseImpl() { this(Collections.emptyList()); } GenericCaseImpl(List caseWhenThens) { super(caseWhenThens); } @Override Expression getCaseExpression() { return null; } /** * The renderable implementation of {@link GenericCase}. */ static final class EndingGenericCase extends GenericCaseImpl implements CaseEnding { private EndingGenericCase(List caseWhenThens) { super(caseWhenThens); } @Override public Case elseDefault(Expression defaultExpression) { this.setCaseElse(new CaseElse(defaultExpression)); return this; } } } private final class DefaultOngoingWhenThen implements OngoingWhenThen { final Expression whenExpression; private DefaultOngoingWhenThen(Expression whenExpression) { this.whenExpression = whenExpression; } /** * Ends this {@code WHEN} block with an expression. * @param expression the expression for the ongoing {@code WHEN} block. * @return an ongoing when builder. */ @Override @CheckReturnValue public CaseEnding then(Expression expression) { CaseWhenThen caseWhenThen = new CaseWhenThen(this.whenExpression, expression); AbstractCase.this.caseWhenThens.add(caseWhenThen); if (getCaseExpression() != null) { return new SimpleCaseImpl.EndingSimpleCase(AbstractCase.this.getCaseExpression(), AbstractCase.this.caseWhenThens); } else { return new GenericCaseImpl.EndingGenericCase(AbstractCase.this.caseWhenThens); } } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/AbstractClause.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; abstract class AbstractClause implements Clause { @Override public final String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/AbstractNode.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.INTERNAL; /** * Abstract base class for the {@link NodeBase node implementation} to avoid default * interface methods to be overridable in inheritors. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = INTERNAL, since = "2021.1.0") abstract class AbstractNode extends AbstractPropertyContainer implements Node { @Deprecated(forRemoval = true) @Override @SuppressWarnings("removal") public final Condition hasLabels(LabelExpression labels) { return hasLabels(Labels.of(labels)); } @Override public final Condition hasLabels(String... labelsToQuery) { return HasLabelCondition.create( this.getSymbolicName() .orElseThrow(() -> new IllegalStateException("Cannot query a node without a symbolic name.")), labelsToQuery); } @Override public final Condition hasLabels(Labels labels) { return HasLabelCondition.create( this.getSymbolicName() .orElseThrow(() -> new IllegalStateException("Cannot query a node without a symbolic name.")), labels); } @Override public final Condition isEqualTo(Node otherNode) { return this.getRequiredSymbolicName().isEqualTo(otherNode.getRequiredSymbolicName()); } @Override public final Condition isNotEqualTo(Node otherNode) { return this.getRequiredSymbolicName().isNotEqualTo(otherNode.getRequiredSymbolicName()); } @Override public final Condition isNull() { return this.getRequiredSymbolicName().isNull(); } @Override public final Condition isNotNull() { return this.getRequiredSymbolicName().isNotNull(); } @Override public final SortItem descending() { return this.getRequiredSymbolicName().descending(); } @Override public final SortItem ascending() { return this.getRequiredSymbolicName().ascending(); } @Override public final AliasedExpression as(String alias) { return this.getRequiredSymbolicName().as(alias); } @Override @SuppressWarnings("deprecation") // IDEA is stupid. public final FunctionInvocation internalId() { return Functions.id(this); } @Override public final FunctionInvocation labels() { return Functions.labels(this); } @Override public final Relationship relationshipTo(Node other, String... types) { return new InternalRelationshipImpl(null, this, Relationship.Direction.LTR, null, other, types); } @Override public final Relationship relationshipFrom(Node other, String... types) { return new InternalRelationshipImpl(null, this, Relationship.Direction.RTL, null, other, types); } @Override public final Relationship relationshipBetween(Node other, String... types) { return new InternalRelationshipImpl(null, this, Relationship.Direction.UNI, null, other, types); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/AbstractPropertyContainer.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.INTERNAL; /** * Abstract base class for all property containers to avoid default interface methods to * be overridable in inheritors. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = INTERNAL, since = "2021.1.0") abstract class AbstractPropertyContainer implements PropertyContainer { static IllegalStateException noNameException() { return new IllegalStateException(Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_REQUIRES_NAME_FOR_MUTATION)); } @Override public final Property property(String name) { return InternalPropertyImpl.create(this, name); } @Override public final Property property(String... names) { return InternalPropertyImpl.create(this, names); } @Override public final Property property(Expression lookup) { return InternalPropertyImpl.create(this, lookup); } @Override public final Operation mutate(Parameter parameter) { return Operations.mutate(this.getSymbolicName().orElseThrow(AbstractPropertyContainer::noNameException), parameter); } @Override public final Operation mutate(MapExpression properties) { return Operations.mutate(this.getSymbolicName().orElseThrow(AbstractPropertyContainer::noNameException), properties); } @Override public final Operation set(Parameter parameter) { return Operations.set(this.getSymbolicName().orElseThrow(AbstractPropertyContainer::noNameException), parameter); } @Override public final Operation set(MapExpression properties) { return Operations.set(this.getSymbolicName().orElseThrow(AbstractPropertyContainer::noNameException), properties); } @Override public final MapProjection project(List entries) { return project(entries.toArray()); } @Override public final MapProjection project(Object... entries) { return getRequiredSymbolicName().project(entries); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/AbstractStatement.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.internal.DefaultStatementContext; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.apiguardian.api.API.Status.INTERNAL; /** * The abstract statement provides possible state shared across various statement * implementations. Use cases are collecting bound parameter values, possible * configuration of the renderer and similar. * * @author Michael J. Simons * @author Andreas Berger * @since 2021.0.0 */ @API(status = INTERNAL, since = "2021.0.0") abstract class AbstractStatement implements Statement { /** * Provides context during visiting of this statement. */ private final StatementContext context = new DefaultStatementContext(); /** * A flag that indicates whether parameters coming from QueryDSL integration should be * rendered as Cypher {@link Literal literals} or as actual parameters. */ private boolean renderConstantsAsParameters = false; /** * The rendered Cypher statement. */ private volatile String cypher; /** * The catalog for this statement, will be lazily available and needs refreshment if * parameter rendering changes. */ @SuppressWarnings("squid:S3077") private volatile StatementCatalog statementCatalog; @Override public StatementContext getContext() { return this.context; } @Override public synchronized boolean isRenderConstantsAsParameters() { return this.renderConstantsAsParameters; } @Override public void setRenderConstantsAsParameters(boolean renderConstantsAsParameters) { synchronized (this) { this.renderConstantsAsParameters = renderConstantsAsParameters; this.cypher = null; this.statementCatalog = null; } } @Override public String getCypher() { String result = this.cypher; if (result == null) { synchronized (this) { result = this.cypher; if (result == null) { this.cypher = Renderer.getDefaultRenderer().render(this); result = this.cypher; } } } return result; } @Override public StatementCatalog getCatalog() { StatementCatalog result = this.statementCatalog; if (result == null) { synchronized (this) { result = this.statementCatalog; if (result == null) { this.statementCatalog = getCatalog0(); result = this.statementCatalog; } } } return result; } private StatementCatalog getCatalog0() { var catalogBuildingVisitor = new StatementCatalogBuildingVisitor(getContext(), isRenderConstantsAsParameters()); this.accept(catalogBuildingVisitor); return catalogBuildingVisitor.getResult(); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Aliased.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * An element with an alias. An alias has a subtle difference to a symbolic name in * cypher. Nodes and relationships can have symbolic names which in turn can be aliased as * well. *

* Therefore, the Cypher generator needs both {@code Named} and {@code Aliased}. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface Aliased { /** * {@return the alias} */ String getAlias(); /** * Turns this alias into a symbolic name that can be used as an {@link Expression}. * @return a new symbolic name */ default SymbolicName asName() { return SymbolicName.of(this.getAlias()); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/AliasedExpression.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * An aliased expression, that deals with named expressions when accepting visitors. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class AliasedExpression implements Aliased, Expression, IdentifiableElement { private final Expression delegate; private final String alias; AliasedExpression(Expression delegate, String alias) { this.delegate = delegate; this.alias = alias; } @Override public String getAlias() { return this.alias; } /** * This takes the originally aliased expression and re-aliases it. Aliases are not * nested. * @param newAlias the new alias to use * @return a new aliased, expression. */ @Override public AliasedExpression as(String newAlias) { Assertions.hasText(newAlias, "The alias may not be null or empty."); return new AliasedExpression(this.delegate, newAlias); } @Override public void accept(Visitor visitor) { visitor.enter(this); Expressions.nameOrExpression(this.delegate).accept(visitor); visitor.leave(this); } Expression getDelegate() { return this.delegate; } @Override public Expression asExpression() { return this; } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Arguments.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Optional; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.ProvidesAffixes; import org.neo4j.cypherdsl.core.ast.TypedSubtree; import org.neo4j.cypherdsl.core.ast.Visitable; import static org.apiguardian.api.API.Status.INTERNAL; /** * Specialized list of expressions that represents the arguments of a procedure call. * * @author Michael J. Simons * @since 2020.0.1 */ @API(status = INTERNAL, since = "2020.0.1") final class Arguments extends TypedSubtree implements ProvidesAffixes { Arguments(Expression... children) { super(children); } @Override protected Visitable prepareVisit(Expression child) { return Expressions.nameOrExpression(child); } @Override public Optional getPrefix() { return Optional.of("("); } @Override public Optional getSuffix() { return Optional.of(")"); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Asterisk.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * The {@code *}. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class Asterisk extends LiteralBase implements IdentifiableElement { /** * The single instance of the {@code *}. */ public static final Asterisk INSTANCE = new Asterisk(); private Asterisk() { super("*"); } @Override public String asString() { return this.content; } @Override public Expression asExpression() { return this; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/BooleanFunctionCondition.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; /** * This wraps a function into a condition so that it can be used in a where clause. The * function is supposed to return a boolean value. * * @author Michael J. Simons * @since 1.0 */ @API(status = INTERNAL, since = "1.0") final class BooleanFunctionCondition implements Condition { private final FunctionInvocation delegate; BooleanFunctionCondition(FunctionInvocation delegate) { this.delegate = delegate; } @Override public void accept(Visitor visitor) { this.delegate.accept(visitor); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/BooleanLiteral.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * The boolean literal. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class BooleanLiteral extends LiteralBase { static final BooleanLiteral TRUE = new BooleanLiteral(true); static final BooleanLiteral FALSE = new BooleanLiteral(false); private BooleanLiteral(boolean content) { super(content); } static Literal of(Boolean value) { if (value != null && value) { return TRUE; } else { return FALSE; } } @Override public String asString() { return getContent().toString(); } @Override public Boolean getContent() { return this.content; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/BuiltInFunctions.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.apiguardian.api.API.Status; import org.neo4j.cypherdsl.core.FunctionInvocation.FunctionDefinition; /** * A (non-exhaustive) list of builtin functions. * * @author Michael J. Simons * @since 2020.1.0 */ @API(status = Status.INTERNAL, since = "2020.1.0") final class BuiltInFunctions { private BuiltInFunctions() { } enum Predicates implements FunctionDefinition { ALL("all"), ANY("any"), EXISTS("exists"), NONE("none"), SINGLE("single"), IS_EMPTY("isEmpty"); private final String implementationName; Predicates(String implementationName) { this.implementationName = implementationName; } @Override public String getImplementationName() { return this.implementationName; } } enum Graph implements FunctionDefinition { NAMES("names"), PROPERTIES_BY_NAME("propertiesByName"), BY_NAME("byName"); private final String implementationName; Graph(String implementationName) { this.implementationName = "graph." + implementationName; } @Override public String getImplementationName() { return this.implementationName; } } enum Scalars implements FunctionDefinition { COALESCE("coalesce"), END_NODE("endNode"), HEAD("head"), ID("id"), ELEMENT_ID("elementId"), LAST("last"), LENGTH("length"), PROPERTIES("properties"), SHORTEST_PATH("shortestPath"), SIZE("size"), START_NODE("startNode"), TYPE("type"), TO_STRING("toString"), TO_INTEGER("toInteger"), TO_FLOAT("toFloat"), TO_BOOLEAN("toBoolean"); private final String implementationName; Scalars(String implementationName) { this.implementationName = implementationName; } @Override public String getImplementationName() { return this.implementationName; } } enum Strings implements FunctionDefinition { LEFT("left"), LTRIM("ltrim"), REPLACE("replace"), REVERSE("reverse"), RIGHT("right"), RTRIM("rtrim"), SPLIT("split"), SUBSTRING("substring"), TO_LOWER("toLower"), TO_STRING("toString"), TO_STRING_OR_NULL("toStringOrNull"), TO_UPPER("toUpper"), TRIM("trim"); private final String implementationName; Strings(String implementationName) { this.implementationName = implementationName; } @Override public String getImplementationName() { return this.implementationName; } } enum Spatials implements FunctionDefinition { POINT("point"), DISTANCE("distance"); private final String implementationName; Spatials(String implementationName) { this.implementationName = implementationName; } @Override public String getImplementationName() { return this.implementationName; } } enum Aggregates implements FunctionDefinition { AVG("avg"), COLLECT("collect"), COUNT("count"), MAX("max"), MIN("min"), PERCENTILE_CONT("percentileCont"), PERCENTILE_DISC("percentileDisc"), ST_DEV("stDev"), ST_DEV_P("stDevP"), SUM("sum"); private final String implementationName; Aggregates(String implementationName) { this.implementationName = implementationName; } @Override public String getImplementationName() { return this.implementationName; } @Override public boolean isAggregate() { return true; } } enum Lists implements FunctionDefinition { KEYS("keys"), LABELS("labels"), NODES("nodes"), RANGE("range"), REDUCE("reduce"), RELATIONSHIPS("relationships"); private final String implementationName; Lists(String implementationName) { this.implementationName = implementationName; } @Override public String getImplementationName() { return this.implementationName; } } enum Temporals implements FunctionDefinition { DATE("date"), DATETIME("datetime"), LOCALDATETIME("localdatetime"), LOCALTIME("localtime"), TIME("time"), DURATION("duration"); private final String implementationName; Temporals(String implementationName) { this.implementationName = implementationName; } @Override public String getImplementationName() { return this.implementationName; } } enum MathematicalFunctions implements FunctionDefinition { ABS("abs"), CEIL("ceil"), FLOOR("floor"), RAND("rand", 0, 0), ROUND("round", 1, 3), SIGN("sign"), E("e", 0, 0), EXP("exp"), LOG("log"), LOG10("log10"), SQRT("sqrt"), ACOS("acos"), ASIN("asin"), ATAN("atan"), ATAN2("atan2", 2, 2), COS("cos"), COT("cot"), DEGREES("degrees"), HAVERSIN("haversin"), PI("pi", 0, 0), RADIANS("radians"), SIN("sin"), TAN("tan"); private final String implementationName; private final int minArgs; private final int maxArgs; MathematicalFunctions(String implementationName) { this(implementationName, 1, 1); } MathematicalFunctions(String implementationName, int minArgs, int maxArgs) { this.implementationName = implementationName; this.minArgs = minArgs; this.maxArgs = maxArgs; } int getMinArgs() { return this.minArgs; } int getMaxArgs() { return this.maxArgs; } @Override public String getImplementationName() { return this.implementationName; } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Case.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import org.neo4j.cypherdsl.core.ast.ProvidesAffixes; import org.neo4j.cypherdsl.core.internal.CaseWhenThen; import static org.apiguardian.api.API.Status.STABLE; /** * See CaseExpression. * * @author Gerrit Meier * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface Case extends Expression, ProvidesAffixes { /** * Creates a new {@link Case} {@link Expression}. * @param expression starting expression for the simple case * @return the new expression */ static Case create(Expression expression) { return (expression != null) ? new AbstractCase.SimpleCaseImpl(expression) : new AbstractCase.GenericCaseImpl(); } /** * Creates a new case/when expression with an additional {@code WHEN} block. * @param nextExpression the next expression to use. * @return an ongoing when builder. */ @CheckReturnValue OngoingWhenThen when(Expression nextExpression); /** * Extension the {@link Case} interface to support simple case with an initial * expression / condition. */ @API(status = STABLE, since = "1.0") interface SimpleCase extends Case { } /** * Extension of the {@link Case} interface to support generic case. */ @API(status = STABLE, since = "1.0") interface GenericCase extends Case { } /** * Specification for a renderable, complete CASE statement. */ @API(status = STABLE, since = "1.0") interface CaseEnding extends Case { /** * Adds a new {@code WHEN} block. * @param expression a2 new when expression. * @return an ongoing when builder. */ @CheckReturnValue OngoingWhenThen when(Expression expression); /** * Ends this case expression with a default expression to evaluate. * @param defaultExpression the new default expression * @return an ongoing when builder. */ @CheckReturnValue Case elseDefault(Expression defaultExpression); } /** * Helper class to collect `when` expressions and create {@link CaseWhenThen} * instances when the `then` is provided. */ @API(status = STABLE, since = "1.0") interface OngoingWhenThen { /** * Ends this {@code WHEN} block with an expression. * @param expression the expression for the ongoing {@code WHEN} block. * @return an ongoing when builder. */ @CheckReturnValue CaseEnding then(Expression expression); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Clause.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import static org.apiguardian.api.API.Status.STABLE; /** * This is a marker interface for top level clauses. * * @author Michael J. Simons * @since 2021.3.0 */ @API(status = STABLE, since = "2021.3.0") public interface Clause extends Visitable { } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Clauses.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.net.URI; import java.util.Collections; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.internal.LoadCSV; import org.neo4j.cypherdsl.core.internal.ProcedureName; import org.neo4j.cypherdsl.core.internal.YieldItems; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * Builder / factory for various {@link Clause clauses}. It's mostly useful for building a * Cypher-DSL AST outside the fluent API. * * @author Michael J. Simons * @since 2021.3.0 */ @API(status = STABLE, since = "2021.3.0") public final class Clauses { /** * Not to be instantiated. */ private Clauses() { } /** * Builds a {@code MATCH} clause. The result can be safely cast to a {@link Match} if * needed. * @param optional should this be an optional match? * @param patternElements the pattern elements to match * @param optionalWhere an optional where sub-clause * @param optionalHints optional hints to be used * @return an immutable match clause * @since 2022.0.0 */ public static Clause match(boolean optional, List patternElements, Where optionalWhere, List optionalHints) { return new Match(optional, Pattern.of(patternElements), optionalWhere, optionalHints); } /** * Builds a {@code DELETE} clause. * @param detach should this be an detach delete? * @param expressions the expressions pointing to the things to be deleted * @return an immutable delete clause */ public static Clause delete(boolean detach, List expressions) { return new Delete(new ExpressionList(expressions), detach); } /** * Builds a {@code RETURN} clause. * @param distinct should this be a distinct return * @param expressions the expressions to be returned * @param optionalSortItems an optional list of sort items * @param optionalSkip an optional {@link NumberLiteral} of how many items to skip * @param optionalLimit an optional {@link NumberLiteral} of how many items to be * returned * @return an immutable return clause */ public static Return returning(boolean distinct, List expressions, List optionalSortItems, Expression optionalSkip, Expression optionalLimit) { DefaultStatementBuilder.OrderBuilder orderBuilder = new DefaultStatementBuilder.OrderBuilder(); orderBuilder.orderBy(optionalSortItems); orderBuilder.skip(optionalSkip); orderBuilder.limit(optionalLimit); return Return.create(false, distinct, expressions, orderBuilder); } /** * Builds a {@code CREATE} clause. * @param patternElements the pattern elements to create * @return an immutable create clause */ public static Clause create(List patternElements) { return new Create(Pattern.of(patternElements)); } /** * Builds a {@code MERGE} clause. * @param patternElements the pattern elements to merge * @param mergeActions an optional list of {@link MergeAction merge actions} * @return an immutable merge clause */ public static Clause merge(List patternElements, List mergeActions) { return new Merge(Pattern.of(patternElements), (mergeActions != null) ? mergeActions : Collections.emptyList()); } /** * Retrofits an existing {@link Return return clause} into an equivalent {@link With * with clause}, optionally adding a {@link Where where}. * @param returnClause the return clause that defines the fields, order and limit of * what the with clause should return * @param optionalWhere an optional {@literal WHERE }expression to define a where * clause. * @return an immutable with clause * @since 2022.0.0 */ public static Clause with(Return returnClause, Where optionalWhere) { return new With(returnClause, optionalWhere); } /** * Creates a {@link Remove remove clause}, removing labels or properties. * @param expressions expressions pointing to a list of properties or labels that * shall be removed * @return an immutable remove clause */ public static Clause remove(List expressions) { return new Remove(new ExpressionList(expressions)); } /** * Creates a {@link Set remove clause}, setting labels or properties. * @param expressions expressions pointing to a list of properties or labels that * shall be set * @return an immutable set clause */ public static Clause set(List expressions) { return new Set(new ExpressionList(expressions)); } /** * Creates an {@link Unwind unwind clause}. * @param expression the expression to unwind * @param name the name on which to unwind * @return an immutable unwind clause */ public static Clause unwind(Expression expression, SymbolicName name) { return new Unwind(expression, name.getValue()); } /** * Creates an {@link LoadCSV LOAD CSV clause}. * @param withHeaders set to true to render the WITH HEADERS options * @param uri the source to load from * @param alias the alias for the lines * @param fieldTerminator the field terminator * @return an immutable clause */ public static Clause loadCSV(boolean withHeaders, StringLiteral uri, SymbolicName alias, String fieldTerminator) { return new LoadCSV(URI.create(uri.getContent().toString()), withHeaders, alias.getValue()) .withFieldTerminator(fieldTerminator); } /** * Creates a {@literal CALL} clause. * @param namespace an optional namespace, maybe empty * @param name the name of the stored procedure to call * @param arguments the arguments, maybe null or empty * @param resultItems the result items, maybe null or empty * @param optionalWhere an optional where * @return an immutable clause * @since 2022.0.0 */ public static Clause callClause(List namespace, String name, List arguments, List resultItems, Where optionalWhere) { return ProcedureCallImpl.create(ProcedureName.from(namespace, name), new Arguments((arguments != null) ? arguments.toArray(new Expression[0]) : new Expression[0]), (resultItems != null) ? YieldItems.yieldAllOf(resultItems.toArray(new Expression[0])) : null, optionalWhere); } /** * Creates a {@literal CALL {}} sub-query clause. No checking is done whether the * statement passed in returns anything meaningful so that the resulting clause will * be runnable or not. * @param statement a statement to be used inside the sub-query. * @return an immutable sub-query clause. */ public static Clause callClause(Statement statement) { return Subquery.call(statement); } /** * Creates a literal for each clause. * @param v the name of the variable that should be available in the list of updating * clauses * @param list the list on which to iterate * @param updatingClauses the updating clauses * @return an immutable foreach clause */ public static Clause forEach(SymbolicName v, Expression list, List updatingClauses) { Assertions.isTrue(updatingClauses.stream().allMatch(UpdatingClause.class::isInstance), "Only updating clauses SET, REMOVE, CREATE, MERGE, DELETE, and FOREACH are allowed as clauses applied inside FOREACH."); return new Foreach(v, list, updatingClauses.stream().map(UpdatingClause.class::cast).toList()); } /** * Creates a standalone ORDER BY clause. * @param sortItems the items to sort by * @param skip a literal number item specifying the skipped items, may be * {@literal null} * @param limit a literal number item specifying the total limit of items, may be * {@literal null} * @return an immutable order by clause * @since 2024.7.4 */ public static Clause orderBy(List sortItems, Expression skip, Expression limit) { return new OrderByClause(new Order(sortItems), (skip != null) ? Skip.create(skip) : null, (limit != null) ? Limit.create(limit) : null); } static final class OrderByClause extends AbstractClause { private final Order order; private final Skip skip; private final Limit limit; OrderByClause(Order order, Skip skip, Limit limit) { this.order = order; this.skip = skip; this.limit = limit; } @Override public void accept(Visitor visitor) { this.order.accept(visitor); Visitable.visitIfNotNull(this.skip, visitor); Visitable.visitIfNotNull(this.limit, visitor); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ClausesBasedStatement.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.internal.UsingPeriodicCommit; import static org.apiguardian.api.API.Status.INTERNAL; /** * This variant of a {@link Statement} takes in a plain list of clauses without any * further checks. During rendering they are visited in the same order as originally * provided. * * @author Michael J. Simons * @since 2021.3.0 */ @API(status = INTERNAL, since = "2021.3.0") class ClausesBasedStatement extends AbstractStatement { private final UsingPeriodicCommit optionalPeriodicCommit; private final List clauses; ClausesBasedStatement(List clauses, UsingPeriodicCommit optionalPeriodicCommit) { this.optionalPeriodicCommit = optionalPeriodicCommit; this.clauses = List.copyOf(clauses); } @Override public void accept(Visitor visitor) { visitor.enter(this); Visitable.visitIfNotNull(this.optionalPeriodicCommit, visitor); this.clauses.forEach(c -> c.accept(visitor)); visitor.leave(this); } @Override public boolean doesReturnOrYield() { if (this.clauses.isEmpty()) { return false; } Clause lastClause = this.clauses.get(this.clauses.size() - 1); return lastClause instanceof Return; } @API(status = INTERNAL) List getClauses() { return this.clauses; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/CollectExpression.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.STABLE; /** * Implements * COLLECT * subqueries. * * @author Michael J. Simons * @since 2023.8.0 */ @API(status = STABLE, since = "2023.0.0") @Neo4jVersion(minimum = "5.6") public final class CollectExpression implements SubqueryExpression { private final ImportingWith optionalWith; private final Statement resultStatement; private CollectExpression(ImportingWith optionalWith, Statement resultStatement) { this.optionalWith = optionalWith; this.resultStatement = resultStatement; } static CollectExpression collect(Statement resultStatement) { return new CollectExpression(new ImportingWith(), resultStatement); } static CollectExpression collect(With optionalWith, Statement resultStatement) { return new CollectExpression(new ImportingWith(optionalWith, null), resultStatement); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.optionalWith.accept(visitor); this.resultStatement.accept(visitor); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Comparison.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.EnterResult; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * A concrete condition representing a comparision between two expressions. * * @author Michael J. Simons * @author Gerrit Meier * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class Comparison implements Condition { private final Expression left; private final Operator comparator; private final Expression right; private Comparison(Expression left, Operator operator, Expression right) { this.left = nestedIfCondition(left); this.comparator = operator; this.right = nestedIfCondition(right); } static Comparison create(Operator operator, Expression expression) { Assertions.isTrue(operator.isUnary(), "Operator must be unary."); Assertions.notNull(expression, "Expression must not be null."); return switch (operator.getType()) { case PREFIX -> new Comparison(null, operator, expression); case POSTFIX -> new Comparison(expression, operator, null); default -> throw new IllegalArgumentException("Invalid operator type " + operator.getType()); }; } static Comparison create(Expression lhs, Operator operator, Expression rhs) { Assertions.notNull(lhs, "Left expression must not be null."); Assertions.notNull(operator, "Operator must not be empty."); Assertions.notNull(rhs, "Right expression must not be null."); return new Comparison(lhs, operator, rhs); } private static Expression nestedIfCondition(Expression expression) { return (expression instanceof Condition) ? new NestedExpression(expression) : expression; } @Override public void accept(Visitor visitor) { EnterResult result = visitor.enterWithResult(this); if (result == EnterResult.CONTINUE) { if (this.left != null) { Expressions.nameOrExpression(this.left).accept(visitor); } this.comparator.accept(visitor); if (this.right != null) { Expressions.nameOrExpression(this.right).accept(visitor); } } visitor.leave(this); } @API(status = INTERNAL, since = "2024.6.1") public Expression getLeft() { return this.left; } @API(status = INTERNAL, since = "2024.6.1") public Operator getComparator() { return this.comparator; } @API(status = INTERNAL, since = "2024.6.1") public Expression getRight() { return this.right; } @Override public Condition not() { if (this.comparator == Operator.IS_NULL) { return new Comparison(this.left, Operator.IS_NOT_NULL, this.right); } else if (this.comparator == Operator.IS_NOT_NULL) { return new Comparison(this.left, Operator.IS_NULL, this.right); } return Condition.super.not(); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/CompoundCondition.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import java.util.Optional; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.ProvidesAffixes; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.INTERNAL; /** * A condition that consists of one or two {@link Condition conditions} connected by a * Logical connective * (operator). * * @author Michael J. Simons * @since 1.0 */ @API(status = INTERNAL, since = "1.0") final class CompoundCondition implements Condition, ProvidesAffixes { static final EnumSet VALID_OPERATORS = EnumSet.of(Operator.AND, Operator.OR, Operator.XOR); /** * The empty, compound condition. */ private static final CompoundCondition EMPTY_CONDITION = new CompoundCondition(null); private final Operator operator; private final List conditions; private CompoundCondition(Operator operator) { this.operator = operator; this.conditions = new ArrayList<>(); } static CompoundCondition create(Condition left, Operator operator, Condition right) { Assertions.isTrue(VALID_OPERATORS.contains(operator), "Operator " + operator + " is not a valid operator for a compound condition."); Assertions.notNull(left, "Left hand side condition is required."); Assertions.notNull(operator, "Operator is required."); Assertions.notNull(right, "Right hand side condition is required."); return new CompoundCondition(operator).add(operator, left).add(operator, right); } static CompoundCondition copyOf(CompoundCondition other) { CompoundCondition result = new CompoundCondition(other.operator); result.conditions.addAll(other.conditions); return result; } static CompoundCondition empty() { return EMPTY_CONDITION; } private static void acceptVisitorWithOperatorForChildCondition(Visitor visitor, Operator operator, Condition condition) { Visitable.visitIfNotNull(operator, visitor); condition.accept(visitor); } @Override public Condition and(Condition condition) { return this.add(Operator.AND, condition); } @Override public Condition or(Condition condition) { return this.add(Operator.OR, condition); } @Override public Condition xor(Condition condition) { return this.add(Operator.XOR, condition); } private CompoundCondition add(Operator chainingOperator, Condition condition) { if (this == EMPTY_CONDITION) { return new CompoundCondition(chainingOperator).add(chainingOperator, condition); } if (condition instanceof CompoundCondition compoundCondition && !compoundCondition.hasConditions()) { return this; } if (condition instanceof CompoundCondition compoundCondition) { CompoundCondition target; if (this.operator == chainingOperator && chainingOperator == compoundCondition.operator) { target = CompoundCondition.copyOf(this); } else { CompoundCondition inner = new CompoundCondition(chainingOperator); if (this.hasConditions()) { inner.conditions.add(this); } target = inner; } if (compoundCondition.canBeFlattenedWith(chainingOperator)) { target.conditions.addAll(compoundCondition.conditions); } else { target.conditions.add(compoundCondition); } return target; } if (this.operator == chainingOperator) { CompoundCondition target = CompoundCondition.copyOf(this); target.conditions.add(condition); return target; } return CompoundCondition.create(this, chainingOperator, condition); } boolean hasConditions() { return !(this == EMPTY_CONDITION || this.conditions.isEmpty()); } /** * Returns true if this operation can be flattened into a single with another using * the given operator. * @param operatorBefore the operator that is to be used before this condition * @return true if all conditions in this condition are either simple or compound * annotation with the same boolean operator as {@code operatorBefore} */ private boolean canBeFlattenedWith(Operator operatorBefore) { if (this.operator != operatorBefore) { return false; } for (Condition c : this.conditions) { if (c instanceof CompoundCondition compoundCondition && compoundCondition.operator != operatorBefore) { return false; } } return true; } @Override public void accept(Visitor visitor) { // There is nothing to visit here if (this.conditions.isEmpty()) { return; } // Fold single condition boolean hasManyConditions = this.conditions.size() > 1; if (hasManyConditions) { visitor.enter(this); } // The first nested condition does not need an operator acceptVisitorWithOperatorForChildCondition(visitor, null, this.conditions.get(0)); // All others do if (hasManyConditions) { for (Condition condition : this.conditions.subList(1, this.conditions.size())) { acceptVisitorWithOperatorForChildCondition(visitor, this.operator, condition); } visitor.leave(this); } } @Override public Optional getPrefix() { return Optional.of("("); } @Override public Optional getSuffix() { return Optional.of(")"); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Condition.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.internal.RelationshipPatternCondition; import static org.apiguardian.api.API.Status.STABLE; /** * Shared interface for all conditions. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface Condition extends Expression { /** * Adds a condition to this condition with an {@literal AND}. * @param condition the new condition to add, must not be {@literal null}. * @return a new condition. */ default Condition and(Condition condition) { return CompoundCondition.create(this, Operator.AND, condition); } /** * Adds a condition to this condition with an {@literal OR}. * @param condition the new condition to add, must not be {@literal null}. * @return a new condition. */ default Condition or(Condition condition) { return CompoundCondition.create(this, Operator.OR, condition); } /** * Adds a condition to this condition with a {@literal XOR}. * @param condition the new condition to add, must not be {@literal null}. * @return a new condition. */ default Condition xor(Condition condition) { return CompoundCondition.create(this, Operator.XOR, condition); } /** * Adds a condition based on a path pattern to this condition with an {@literal AND}. * See Using * path patterns in WHERE. * @param pathPattern the path pattern to add to the where clause. This path pattern * must not be {@literal null} and must not introduce new variables not available in * the match. * @return a new condition. * @since 1.0.1 */ default Condition and(RelationshipPattern pathPattern) { return CompoundCondition.create(this, Operator.AND, RelationshipPatternCondition.of(pathPattern)); } /** * Adds a condition based on a path pattern to this condition with an {@literal OR}. * See Using * path patterns in WHERE. * @param pathPattern the path pattern to add to the where clause. This path pattern * must not be {@literal null} and must not introduce new variables not available in * the match. * @return a new condition. * @since 1.0.1 */ default Condition or(RelationshipPattern pathPattern) { return CompoundCondition.create(this, Operator.OR, RelationshipPatternCondition.of(pathPattern)); } /** * Adds a condition based on a path pattern to this condition with a {@literal XOR}. * See Using * path patterns in WHERE. * @param pathPattern the path pattern to add to the where clause. This path pattern * must not be {@literal null} and must not introduce new variables not available in * the match. * @return a new condition. * @since 1.0.1 */ default Condition xor(RelationshipPattern pathPattern) { return CompoundCondition.create(this, Operator.XOR, RelationshipPatternCondition.of(pathPattern)); } /** * Negates this condition. * @return a new condition. */ default Condition not() { return Comparison.create(Operator.NOT, this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Conditions.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.neo4j.cypherdsl.core.internal.RelationshipPatternCondition; import org.neo4j.cypherdsl.core.utils.Assertions; /** * Builder for various conditions. * * @author Michael J. Simons * @author Gerrit Meier * @author Aakash Sorathiya * @since 1.0 */ final class Conditions { /** * Not to be instantiated. */ private Conditions() { } /** * Creates a condition that checks whether the {@code lhs} includes all elements * present in {@code rhs}. * @param lhs argument that is tested whether it contains all values in {@code rhs} or * not * @param rhs the reference collection * @return an "includesAll" comparison * @since 2022.7.0 */ static Condition includesAll(Expression lhs, Expression rhs) { SymbolicName x = SymbolicName.of("x"); return Predicates.all(x).in(rhs).where(x.in(lhs)); } /** * Creates a condition that checks whether the {@code lhs} includes any element * present in {@code rhs}. * @param lhs argument that is tested whether it contains any values in {@code rhs} or * not * @param rhs the reference collection * @return a "not_includes" comparison * @since 2022.7.0 */ static Condition includesAny(Expression lhs, Expression rhs) { SymbolicName x = SymbolicName.of("x"); return Predicates.any(x).in(rhs).where(x.in(lhs)); } /** * Creates a condition that checks whether this matches a given relationship pattern. * @param relationshipPattern the pattern being evaluated in a condition * @return a new condition matching the given pattern */ static Condition matching(RelationshipPattern relationshipPattern) { return RelationshipPatternCondition.of(relationshipPattern); } /** * Creates a condition that matches if the right hand side is a regular expression * that matches the left hand side via {@code =~}. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return a "matches" comparison */ static Condition matches(Expression lhs, Expression rhs) { return Comparison.create(lhs, Operator.MATCHES, rhs); } /** * Creates a condition that matches if both expressions are equals according to * {@code =}. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return an "equals" comparison */ static Condition isEqualTo(Expression lhs, Expression rhs) { return Comparison.create(lhs, Operator.EQUALITY, rhs); } /** * Creates a condition that matches if both expressions are equals according to * {@code <>}. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return a "not equals" comparison */ static Condition isNotEqualTo(Expression lhs, Expression rhs) { return Comparison.create(lhs, Operator.INEQUALITY, rhs); } /** * Creates a condition that matches if the left hand side is less than the right hand * side. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return an "less than" comparison */ static Condition lt(Expression lhs, Expression rhs) { return Comparison.create(lhs, Operator.LESS_THAN, rhs); } /** * Creates a condition that matches if the left hand side is less than or equal the * right hand side. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return a "less than or equal" comparison */ static Condition lte(Expression lhs, Expression rhs) { return Comparison.create(lhs, Operator.LESS_THAN_OR_EQUAL_TO, rhs); } /** * Creates a condition that matches if the left hand side is greater than or equal the * right hand side. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return a "greater than or equal" comparison */ static Condition gte(Expression lhs, Expression rhs) { return Comparison.create(lhs, Operator.GREATER_THAN_OR_EQUAL_TO, rhs); } /** * Creates a condition that matches if the left hand side is greater than the right * hand side. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return an "greater than" comparison */ static Condition gt(Expression lhs, Expression rhs) { return Comparison.create(lhs, Operator.GREATER_THAN, rhs); } /** * Negates the given condition. * @param condition the condition to negate. Must not be null. * @return the negated condition. */ static Condition not(Condition condition) { Assertions.notNull(condition, "Condition to negate must not be null."); return condition.not(); } /** * Negates the given pattern element: The pattern must not matched to be included in * the result. * @param pattern the pattern to negate. Must not be null. * @return a condition that evaluates to true when the pattern does not match. */ static Condition not(RelationshipPattern pattern) { return RelationshipPatternCondition.not(pattern); } /** * Creates a condition that checks whether the {@code lhs} starts with the * {@code rhs}. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return a new condition. */ static Condition startsWith(Expression lhs, Expression rhs) { return Comparison.create(lhs, Operator.STARTS_WITH, rhs); } /** * Creates a condition that checks whether the {@code lhs} contains with the * {@code rhs}. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return a new condition. */ static Condition contains(Expression lhs, Expression rhs) { return Comparison.create(lhs, Operator.CONTAINS, rhs); } /** * Creates a condition that checks whether the {@code lhs} ends with the {@code rhs}. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return a new condition. */ static Condition endsWith(Expression lhs, Expression rhs) { return Comparison.create(lhs, Operator.ENDS_WITH, rhs); } /** * Creates a placeholder condition which is not rendered in the final statement but is * useful while chaining conditions together. * @return a placeholder condition. */ static Condition noCondition() { return CompoundCondition.empty(); } /** * Creates a condition that checks whether the {@code expression} is {@literal null}. * @param expression the expression to check for {@literal null} * @return a new condition. */ static Condition isNull(Expression expression) { return Comparison.create(Operator.IS_NULL, expression); } /** * Creates a condition that checks whether the {@code expression} is not * {@literal null}. * @param expression the expression to check for {@literal null} * @return a new condition. */ static Condition isNotNull(Expression expression) { return Comparison.create(Operator.IS_NOT_NULL, expression); } /** * {@return a condition that is always true} */ static Condition isTrue() { return ConstantCondition.TRUE; } /** * {@return a condition that is always false} */ static Condition isFalse() { return ConstantCondition.FALSE; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ConflictingParametersException.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.io.Serial; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * Exception thrown when extracting parameters from a statement leads to one parameter * with a given name appearing with different values. * * @author Andreas Berger * @author Michael J. Simons * @since 2021.0.0 */ @API(status = STABLE, since = "2021.0.0") public final class ConflictingParametersException extends RuntimeException { @Serial private static final long serialVersionUID = -45456411835790492L; private final transient Map> erroneousParameters; ConflictingParametersException(Map> erroneousParameters) { super(createMessage(erroneousParameters)); this.erroneousParameters = new HashMap<>(erroneousParameters.size()); erroneousParameters.forEach((k, v) -> this.erroneousParameters.put(k, new HashSet<>(v))); } private static String createMessage(Map> errors) { StringBuilder sb = new StringBuilder(); String prefix; if (errors.size() > 1) { sb.append("There are conflicting parameter values:"); prefix = "\n\t"; } else { prefix = ""; } errors.forEach((param, values) -> { sb.append(prefix); sb.append("Parameter '").append(param).append("' is defined multiple times with different bound values: "); sb.append(values.stream() .map(o -> (o != Parameter.NO_VALUE) ? Objects.toString(o) : "(UNDEFINED VALUE)") .collect(Collectors.joining(" != "))); }); return sb.toString(); } /** * {@return the conflicting parameters} */ public Map> getErroneousParameters() { return Collections.unmodifiableMap(this.erroneousParameters); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ConstantCondition.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * A constant condition that is either always {@literal true} or {@literal false}. * * @author Michael J. Simons * @author Andreas Berger * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class ConstantCondition extends ExpressionCondition { static final ConstantCondition TRUE = new ConstantCondition(BooleanLiteral.TRUE); static final ConstantCondition FALSE = new ConstantCondition(BooleanLiteral.FALSE); private ConstantCondition(BooleanLiteral value) { super(value); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/CountExpression.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.STABLE; /** * Added for supporting the Neo4j v5 parser. * * @author Michael J. Simons * @since 2023.0.0 */ @API(status = STABLE, since = "2023.0.0") @Neo4jVersion(minimum = "5.0") public final class CountExpression implements SubqueryExpression, ExposesWhere { private final ImportingWith importingWith; private final List fragments; private final Where innerWhere; private CountExpression(ImportingWith optionalWith, List fragments, Where innerWhere) { var patternOrUnion = (fragments.size() != 1) ? null : fragments.get(0); if (patternOrUnion instanceof Statement.UnionQuery && innerWhere != null) { throw new IllegalArgumentException("Cannot use a UNION with a WHERE clause inside a COUNT {} expression"); } this.importingWith = optionalWith; var imports = optionalWith.imports(); if (imports != null && patternOrUnion instanceof Pattern pattern) { this.fragments = List.of(new Match(false, pattern, innerWhere, null)); this.innerWhere = null; } else if (imports != null && patternOrUnion instanceof Match match) { this.fragments = List.of(new Match(false, match.pattern, innerWhere, null)); this.innerWhere = null; } else { this.fragments = List.copyOf(fragments); this.innerWhere = innerWhere; } } static CountExpression count(Statement statement, IdentifiableElement... imports) { return new CountExpression(ImportingWith.of(imports), List.of(statement), null); } static CountExpression count(Visitable patternOrUnion) { return new CountExpression(new ImportingWith(), List.of(patternOrUnion), null); } static CountExpression count(With optionalWith, Visitable patternOrUnion) { return new CountExpression(new ImportingWith(optionalWith, null), List.of(patternOrUnion), null); } static CountExpression count(List patternElements, Where innerWhere) { return new CountExpression(new ImportingWith(), patternElements, innerWhere); } /** * Creates a new {@link CountExpression count expression} with additional conditions. * @param condition the condition to apply in the count expression * @return a new {@link CountExpression} */ public CountExpression where(Condition condition) { if (this.fragments.size() == 1 && this.fragments.get(0) instanceof Statement) { throw new IllegalArgumentException( "This count expression is build upon a full statement, adding a condition to it is not supported."); } return new CountExpression(this.importingWith, this.fragments, new Where(condition)); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.importingWith.accept(visitor); this.fragments.forEach(v -> v.accept(visitor)); Visitable.visitIfNotNull(this.innerWhere, visitor); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Create.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.STABLE; /** * See Create. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class Create extends AbstractClause implements UpdatingClause { private final Pattern pattern; Create(Pattern pattern) { this.pattern = pattern; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.pattern.accept(visitor); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Cypher.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.lang.reflect.Array; import java.net.URI; import java.time.Duration; import java.time.Period; import java.time.temporal.TemporalAccessor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.ResourceBundle; import java.util.TimeZone; import java.util.function.BiConsumer; import java.util.function.Consumer; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ListComprehension.OngoingDefinitionWithVariable; import org.neo4j.cypherdsl.core.Literal.UnsupportedLiteralException; import org.neo4j.cypherdsl.core.PatternComprehension.OngoingDefinitionWithPattern; import org.neo4j.cypherdsl.core.Statement.SingleQuery; import org.neo4j.cypherdsl.core.Statement.UnionQuery; import org.neo4j.cypherdsl.core.Statement.UseStatement; import org.neo4j.cypherdsl.core.StatementBuilder.OngoingStandaloneCallWithoutArguments; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * The main entry point into the Cypher DSL. The Cypher Builder API is intended for * framework usage to produce Cypher statements required for database operations. * * @author Michael J. Simons * @author Gerrit Meier * @author Andreas Berger * @author Ali Ince * @since 1.0 */ @SuppressWarnings("unused") @API(status = STABLE, since = "1.0") public final class Cypher { static final ResourceBundle MESSAGES = ResourceBundle.getBundle("org.neo4j.cypherdsl.core.messages"); /** * The foreign adapter factory. Can only be used when `com.querydsl:querydsl-core` is * on the class path. The object won't be modified after initialisation. */ @SuppressWarnings("squid:S3077") private static volatile ForeignAdapterFactory foreignAdapterFactory; /** * Not to be instantiated. */ private Cypher() { } /** * Create a new Node representation with at least one label, the "primary" label. This * is required. All other labels are optional. * @param primaryLabel the primary label this node is identified by. * @param additionalLabels additional labels * @return a new node representation */ public static Node node(String primaryLabel, String... additionalLabels) { return new InternalNodeImpl(primaryLabel, additionalLabels); } /** * Create a new Node representation with at least one label, the "primary" label. This * is required. All other labels are optional. * @param primaryLabel the primary label this node is identified by. * @param additionalLabels additional labels * @return a new node representation */ public static Node node(String primaryLabel, List additionalLabels) { return new InternalNodeImpl(primaryLabel, additionalLabels.toArray(new String[] {})); } /** * Create a new Node representation with at least one label, the "primary" label. This * is required. All other labels are optional. This method also takes a map of * properties. This allows the returned node object to be used in a {@code MATCH} or * {@code MERGE} statement. * @param primaryLabel the primary label this node is identified by. * @param properties the properties expected to exist on the node. * @param additionalLabels additional labels * @return a new node representation */ public static Node node(String primaryLabel, MapExpression properties, String... additionalLabels) { return new InternalNodeImpl(null, primaryLabel, properties, additionalLabels); } /** * Create a new Node representation with at least one label, the "primary" label. This * is required. All other labels are optional. This method also takes a map of * properties. This allows the returned node object to be used in a {@code MATCH} or * {@code MERGE} statement. * @param primaryLabel the primary label this node is identified by. * @param properties the properties expected to exist on the node. * @param additionalLabels additional labels * @return a new node representation * @since 2021.2.2 */ public static Node node(String primaryLabel, MapExpression properties, Collection additionalLabels) { return node(primaryLabel, properties, additionalLabels.toArray(new String[] {})); } /** * {@return a node matching any node} */ public static Node anyNode() { return new InternalNodeImpl(); } /** * Creates an expression that matches the given {@code label} exactly. Can be used as * starting point to add conditions via {@link Labels#and(Labels)} or * {@link Labels#or(Labels)}. * @param label the label to match * @return a label expression */ public static Labels exactlyLabel(String label) { return Labels.exactly(label); } /** * Creates a dynamic label expression matching all labels to which {@code expression} * resolves. * @param expression the expression that must resolve to a string or a list of strings * @return a dynamic label expression * @since 2025.1.0 */ public static Labels allLabels(Expression expression) { return Labels.all(expression); } /** * Creates a dynamic label expression matching any label to which {@code expression} * resolves. * @param expression the expression that must resolve to a string or a list of strings * @return a dynamic label expression * @since 2025.1.0 */ public static Labels anyLabel(Expression expression) { return Labels.any(expression); } /** * Creates a new {@literal Node} object. * @param labelExpression required expression * @return a node matching a label expression * @since 2023.0.2 * @deprecated use {@link #node(Labels)} */ @SuppressWarnings("removal") @Deprecated(forRemoval = true) public static Node node(LabelExpression labelExpression) { return node(Labels.of(labelExpression)); } /** * Creates a new {@literal Node} object. * @param labels required expression * @return a node matching the given labels * @since 2025.1.0 */ public static Node node(Labels labels) { return new InternalNodeImpl(Objects.requireNonNull(labels), null); } /** * {@return the '*' wildcard literal} */ public static Asterisk asterisk() { return Asterisk.INSTANCE; } /** * Creates a new unlabeled {@literal Node} object. * @param symbolicName the new symbolic name * @return a node matching any node with the symbolic the given {@code symbolicName}. */ public static Node anyNode(String symbolicName) { return new InternalNodeImpl().named(symbolicName); } /** * Creates a new unlabeled {@literal Node} object. * @param symbolicName the new symbolic name * @return a node matching any node with the symbolic the given {@code symbolicName}. */ public static Node anyNode(SymbolicName symbolicName) { return new InternalNodeImpl().named(symbolicName); } /** * Dereferences a property for a symbolic name, most likely pointing to a property * container like a node or a relationship. * @param containerName the symbolic name of a property container * @param names the names of the properties to dereference. More than one name does * create a nested property like {@code containerName.name1.name2}. * @return a new property */ public static Property property(String containerName, String... names) { return property(name(containerName), names); } /** * Dereferences a property for a symbolic name, most likely pointing to a property * container like a node or a relationship. * @param containerName the symbolic name of a property container * @param names the names of the properties to dereference. More than one name does * create a nested property like {@code containerName.name1.name2}. * @return a new property * @since 2021.2.2 */ public static Property property(String containerName, Collection names) { return property(name(containerName), names.toArray(new String[] {})); } /** * Dereferences a property on an arbitrary expression. * @param expression the expression that describes some sort of accessible map * @param names the names of the properties to dereference. More than one name does * create a nested property like {@code expression.name1.name2}. * @return a new property. */ public static Property property(Expression expression, String... names) { return InternalPropertyImpl.create(expression, names); } /** * Dereferences a property on a arbitrary expression. * @param expression the expression that describes some sort of accessible map * @param names the names of the properties to dereference. More than one name does * create a nested property like {@code expression.name1.name2}. * @return a new property. * @since 2021.2.2 */ public static Property property(Expression expression, Collection names) { return property(expression, names.toArray(new String[] {})); } /** * Creates a dynamic lookup of a property for a symbolic name, most likely pointing to * a property container like a node or a relationship. A dynamic property will be * rendered as {@code p[expression]}. * @param containerName the symbolic name of a property container * @param lookup an expression to use as a dynamic lookup for properties of the * container with the given name * @return a new property * @since 2021.0.0 */ public static Property property(String containerName, Expression lookup) { return property(name(containerName), lookup); } /** * Creates a dynamic lookup of a property on an arbitrary expression. A dynamic * property will be rendered as {@code p[expression]}. * @param expression the expression that describes some sort of accessible map * @param lookup an expression to use as a dynamic lookup for properties of the * container the expression resolved to * @return a new property. * @since 2021.0.0 */ public static Property property(Expression expression, Expression lookup) { return InternalPropertyImpl.create(expression, lookup); } /** * Starts defining a named path by indicating a name. * @param name the name of the new path * @return an ongoing definition of a named path * @since 1.1 */ public static NamedPath.OngoingDefinitionWithName path(String name) { return NamedPath.named(name); } /** * Starts defining a named path by indicating a name. * @param name the name of the new path * @return an ongoing definition of a named path * @since 1.1 */ public static NamedPath.OngoingDefinitionWithName path(SymbolicName name) { return NamedPath.named(name); } /** * Returns an ongoing definition of a named path, returning the k shortest paths. * @param k the number of shortest groups to return * @return an ongoing definition of a named path, returning the k shortest paths. * @since 2024.7.0 */ public static NamedPath.OngoingShortestDefinition shortestK(int k) { return NamedPath.shortest(k); } /** * Returns an ongoing definition of a named path, returning the k shortest groups of * points. * @param k the number of shortest groups to return * @return an ongoing definition of a named path, returning the k shortest groups of * paths. * @since 2024.7.0 */ public static NamedPath.OngoingShortestDefinition shortestKGroups(int k) { return NamedPath.shortestKGroups(k); } /** * Returns an ongoing definition of a named path, returning any shortest path. * @return an ongoing definition of a named path, returning any shortest path * @since 2024.7.0 */ public static NamedPath.OngoingShortestDefinition anyShortest() { return NamedPath.any(); } /** * Returns an ongoing definition of a named path, returning all shortest paths. * @return an ongoing definition of a named path, returning all shortest paths * @since 2024.7.0 */ public static NamedPath.OngoingShortestDefinition allShortest() { return NamedPath.allShortest(); } /** * Creates a new symbolic name. * @param value the value of the symbolic name * @return a new symbolic name */ public static SymbolicName name(String value) { return SymbolicName.of(value); } /** * Creates a new parameter placeholder. Existing $-signs will be removed. * @param name the name of the parameter, must not be null * @return the new parameter */ public static Parameter parameter(String name) { return Parameter.create(name); } /** * Creates a new parameter with the given {@code name} and a value bound to it. The * value can be retrieved from the final statement build. * @param name the name of the parameter, must not be null * @param value the value of the parameter. * @param type of the new parameter * @return the new parameter * @since 2021.0.0 */ public static Parameter parameter(String name, T value) { return Parameter.create(name, value); } /** * Creates a new anonymous parameter with a value bound to it. The value can be * retrieved from the final statement build. The name will be available as soon as the * statement has been rendered. * @param value the value of the parameter. * @param type of the new parameter * @return the new parameter * @since 2021.1.0 */ public static Parameter anonParameter(T value) { return Parameter.anon(value); } /** * Prepares an optional match statement. * @param pattern the patterns to match * @return an ongoing match that is used to specify an optional where and a required * return clause */ public static StatementBuilder.OngoingReadingWithoutWhere optionalMatch(PatternElement... pattern) { return Statement.builder().optionalMatch(pattern); } /** * Prepares an optional match statement. * @param pattern the patterns to match * @return an ongoing match that is used to specify an optional where and a required * return clause * @since 2021.2.2 */ public static StatementBuilder.OngoingReadingWithoutWhere optionalMatch( Collection pattern) { return optionalMatch(pattern.toArray(new PatternElement[] {})); } /** * Starts building a statement based on a match clause. Use * {@link Cypher#node(String, String...)} and related to retrieve a node or a * relationship, which both are pattern elements. * @param pattern the patterns to match * @return an ongoing match that is used to specify an optional where and a required * return clause */ public static StatementBuilder.OngoingReadingWithoutWhere match(PatternElement... pattern) { return Statement.builder().match(pattern); } /** * Starts building a statement based on a match clause. Use * {@link Cypher#node(String, String...)} and related to retrieve a node or a * relationship, which both are pattern elements. * @param pattern the patterns to match * @return an ongoing match that is used to specify an optional where and a required * return clause * @since 2021.2.2 */ public static StatementBuilder.OngoingReadingWithoutWhere match(Collection pattern) { return match(pattern.toArray(new PatternElement[] {})); } /** * Starts building a statement based on a match clause. Use * {@link Cypher#node(String, String...)} and related to retrieve a node or a * relationship, which both are pattern elements. * @param optional a flag whether the {@code MATCH} clause includes the * {@code OPTIONAL} keyword. * @param pattern the patterns to match * @return an ongoing match that is used to specify an optional where and a required * return clause * @since 2020.1.3 */ public static StatementBuilder.OngoingReadingWithoutWhere match(boolean optional, PatternElement... pattern) { return Statement.builder().match(optional, pattern); } /** * Starts building a statement based on a match clause. Use * {@link Cypher#node(String, String...)} and related to retrieve a node or a * relationship, which both are pattern elements. * @param optional a flag whether the {@code MATCH} clause includes the * {@code OPTIONAL} keyword. * @param pattern the patterns to match * @return an ongoing match that is used to specify an optional where and a required * return clause * @since 2021.2.2 */ public static StatementBuilder.OngoingReadingWithoutWhere match(boolean optional, Collection pattern) { return match(optional, pattern.toArray(new PatternElement[] {})); } /** * Starts building a statement based on a {@code CREATE} clause. * @param pattern the patterns to create * @return an ongoing {@code CREATE} that can be used to specify {@code WITH} and * {@code RETURNING} etc. */ public static StatementBuilder.OngoingUpdate create(PatternElement... pattern) { return Statement.builder().create(pattern); } /** * Starts building a statement based on a {@code CREATE} clause. * @param pattern the patterns to create * @return an ongoing {@code CREATE} that can be used to specify {@code WITH} and * {@code RETURNING} etc. * @since 2021.2.2 */ public static StatementBuilder.OngoingUpdate create(Collection pattern) { return create(pattern.toArray(new PatternElement[] {})); } /** * Starts a statement with a leading {@code WITH}. Those are useful for passing on * lists of various type that can be unwound later on etc. A leading {@code WITH} * obviously cannot be used with patterns and needs its arguments to have an alias. * @param variables one ore more variables. * @return an ongoing with clause. * @since 2020.1.2 */ public static StatementBuilder.OrderableOngoingReadingAndWithWithoutWhere with(String... variables) { return Statement.builder().with(variables); } /** * Starts a statement with a leading {@code WITH}. Those are useful for passing on * lists of various type that can be unwound later on etc. A leading {@code WITH} * cannot be used with patterns obviously and needs its arguments to have an alias. * @param elements one ore more variables. * @return an ongoing with clause. * @since 2020.1.2 */ public static StatementBuilder.OrderableOngoingReadingAndWithWithoutWhere with(IdentifiableElement... elements) { return Statement.builder().with(elements); } /** * Start building a new sub-query expression by importing variables into the scope * with a {@literal WITH} clause. * @param identifiableElements the identifiable elements to import * @return a builder for creating the concrete sub-query * @since 2023.9.0 */ public static SubqueryExpressionBuilder subqueryWith(String... identifiableElements) { return subqueryWith(Arrays.stream(identifiableElements).map(SymbolicName::of).toArray(SymbolicName[]::new)); } /** * Start building a new sub-query expression by importing variables into the scope * with a {@literal WITH} clause. * @param identifiableElements the identifiable elements to import * @return a builder for creating the concrete sub-query * @since 2023.9.0 */ public static SubqueryExpressionBuilder subqueryWith(IdentifiableElement... identifiableElements) { return Expressions.with(identifiableElements); } /** * Starts a statement with a leading {@code WITH}. Those are useful for passing on * lists of various type that can be unwound later on etc. A leading {@code WITH} * cannot be used with patterns obviously and needs its arguments to have an alias. *

* This method takes both aliased and non-aliased expression. The later will produce * only valid Cypher when used in combination with a correlated subquery via * {@link Cypher#call(Statement)}. * @param elements one ore more expressions. * @return an ongoing with clause. * @since 2021.2.2 */ public static StatementBuilder.OrderableOngoingReadingAndWithWithoutWhere with( Collection elements) { return Statement.builder().with(elements); } /** * Starts building a statement based on a {@code MERGE} clause. * @param pattern the patterns to merge * @return an ongoing {@code MERGE} that can be used to specify {@code WITH} and * {@code RETURNING} etc. */ public static StatementBuilder.OngoingMerge merge(PatternElement... pattern) { return Statement.builder().merge(pattern); } /** * Starts building a statement based on a {@code MERGE} clause. * @param pattern the patterns to merge * @return an ongoing {@code MERGE} that can be used to specify {@code WITH} and * {@code RETURNING} etc. * @since 2021.2.2 */ public static StatementBuilder.OngoingMerge merge(Collection pattern) { return merge(pattern.toArray(new PatternElement[] {})); } /** * Starts building a statement starting with an {@code UNWIND} clause. The expression * needs to be an expression evaluating to a list, otherwise the query will fail. * @param expression the expression to unwind * @return an ongoing {@code UNWIND}. */ public static StatementBuilder.OngoingUnwind unwind(Expression expression) { return Statement.builder().unwind(expression); } /** * Starts building a statement starting with an {@code UNWIND} clause. The expressions * passed will be turned into a list expression * @param expressions expressions to unwind * @return a new instance of {@link StatementBuilder.OngoingUnwind} */ public static StatementBuilder.OngoingUnwind unwind(Expression... expressions) { return Statement.builder().unwind(Cypher.listOf(expressions)); } /** * Starts building a statement starting with an {@code UNWIND} clause. The expressions * passed will be turned into a list expression * @param expressions expressions to unwind * @return a new instance of {@link StatementBuilder.OngoingUnwind} * @since 2021.2.2 */ public static StatementBuilder.OngoingUnwind unwind(Collection expressions) { return unwind(expressions.toArray(new Expression[] {})); } /** * Creates a new {@link SortItem} to be used as part of an {@link Order}. * @param expression the expression by which things should be sorted * @return a sort item, providing means to specify ascending or descending order */ public static SortItem sort(Expression expression) { return SortItem.create(expression, null); } /** * Creates a new {@link SortItem} to be used as part of an {@link Order}. * @param expression the expression by which things should be sorted * @param direction the direction to sort by. Defaults to * {@link SortItem.Direction#UNDEFINED}. * @return a sort item * @since 2021.1.0 */ public static SortItem sort(Expression expression, SortItem.Direction direction) { return SortItem.create(expression, direction); } /** * Creates a map of expression from a list of key/value pairs. * @param keysAndValues a list of key and values. Must be an even number, with * alternating {@link String} and {@link Expression} * @return a new map expression. */ public static MapExpression mapOf(Object... keysAndValues) { return MapExpression.create(false, keysAndValues); } /** * Creates an alphabetically sorted map of expression from a list of key/value pairs. * @param keysAndValues a list of key and values. Must be an even number, with * alternating {@link String} and {@link Expression} * @return a new map expression. */ public static MapExpression sortedMapOf(Object... keysAndValues) { return MapExpression.create(true, keysAndValues); } /** * Creates a map of expression from a Java Map. * @param map a map to be turned into a MapExpression * @return a new map expression. * @since 2021.1.0 */ public static MapExpression asExpression(Map map) { return MapExpression.create(map); } /** * Creates a {@link ListExpression list-expression} from several expressions. * @param expressions expressions to get combined into a list * @return a new instance of {@link ListExpression} */ public static ListExpression listOf(Expression... expressions) { return ListExpression.create(expressions); } /** * Creates a {@link ListExpression list-expression} from several expressions. * @param expressions expressions to get combined into a list * @return a new instance of {@link ListExpression} * @since 2021.2.2 */ public static ListExpression listOf(Collection expressions) { return Cypher.listOf(expressions.toArray(new Expression[0])); } /** * Creates a new {@link Literal Literal<?>} from the given {@code object}. * @param object the object to represent. * @param the type of the literal returned * @return a new {@link Literal Literal<?>}. * @throws UnsupportedLiteralException when the object cannot be represented as a * literal */ @SuppressWarnings("unchecked") public static Literal literalOf(Object object) { if (object == null) { return (Literal) NullLiteral.INSTANCE; } if (object instanceof Literal) { return (Literal) object; } if (object instanceof CharSequence charSequence) { return (Literal) new StringLiteral(charSequence); } if (object instanceof Character) { return (Literal) new StringLiteral(String.valueOf(object)); } if (object instanceof Number number) { return (Literal) new NumberLiteral(number); } if (object instanceof TemporalAccessor temporalAccessor) { return (Literal) new TemporalLiteral(temporalAccessor); } if (object instanceof Duration duration) { return (Literal) DurationLiteral.of(duration); } if (object instanceof Period period) { return (Literal) PeriodLiteral.of(period); } if (object instanceof Parameter parameter) { return (Literal) ParameterLiteral.of(parameter); } if (object instanceof Iterable || object.getClass().isArray()) { List> elements = new ArrayList<>(); Consumer handleElement = element -> { if (element instanceof Literal) { elements.add((Literal) element); } else { try { elements.add(Cypher.literalOf(element)); } catch (UnsupportedLiteralException ex) { throw new UnsupportedLiteralException("Unsupported literal type in iterable.", element); } } }; if (object.getClass().isArray()) { for (int i = 0; i < Array.getLength(object); i++) { handleElement.accept(Array.get(object, i)); } } else { ((Iterable) object).forEach(handleElement); } ListLiteral listLiteral = new ListLiteral(elements); return (Literal) listLiteral; } if (object instanceof Map) { Map> map = new LinkedHashMap<>(); BiConsumer handleEntry = (key, value) -> { if (!(key instanceof CharSequence || key instanceof Character)) { throw new UnsupportedLiteralException("Unsupported literal map key (not a string/char type).", key); } if (value instanceof Literal) { map.put(key.toString(), (Literal) value); } else { try { map.put(key.toString(), Cypher.literalOf(value)); } catch (UnsupportedLiteralException ex) { throw new UnsupportedLiteralException("Unsupported literal type in map.", value); } } }; ((Map) object).forEach(handleEntry); MapLiteral mapLiteral = new MapLiteral(map); return (Literal) mapLiteral; } if (object instanceof Boolean b) { return (Literal) BooleanLiteral.of(b); } throw new UnsupportedLiteralException(object); } /** * {@return the `TRUE` literal} */ public static Literal literalTrue() { return BooleanLiteral.TRUE; } /** * {@return the `FALSE` literal} */ public static Literal literalFalse() { return BooleanLiteral.FALSE; } /** * {@return the `NULL` literal} */ public static Literal literalNull() { return NullLiteral.INSTANCE; } /** * Creates a {@code UNION} statement from several other statements. No checks are * applied for matching return types. * @param statements the statements to union. * @return a union statement. */ public static UnionQuery union(Statement... statements) { return unionImpl(false, statements); } /** * Creates a {@code UNION} statement from several other statements. No checks are * applied for matching return types. * @param statements the statements to union. * @return a union statement. * @since 2021.2.2 */ public static UnionQuery union(Collection statements) { return union(statements.toArray(new Statement[] {})); } /** * Creates a {@code UNION ALL} statement from several other statements. No checks are * applied for matching return types. * @param statements the statements to union. * @return a union statement. */ public static Statement unionAll(Statement... statements) { return unionImpl(true, statements); } /** * Creates a {@code UNION ALL} statement from several other statements. No checks are * applied for matching return types. * @param statements the statements to union. * @return a union statement. * @since 2021.2.2 */ public static Statement unionAll(Collection statements) { return unionAll(statements.toArray(new Statement[] {})); } /** * A {@literal RETURN} statement without a previous match. * @param expressions the elements to return * @return a buildable statement * @since 1.0.1 */ public static StatementBuilder.OngoingReadingAndReturn returning(Expression... expressions) { return Statement.builder().returning(expressions); } /** * A {@literal RETURN} statement without a previous match. * @param expressions the expressions to return * @return a buildable statement * @since 2021.2.2 */ public static StatementBuilder.OngoingReadingAndReturn returning(Collection expressions) { return Statement.builder().returning(expressions); } /** * Creates a list comprehension starting with a {@link Relationship} or a * {@link RelationshipChain chain of relationships}. * @param relationshipPattern the relationship pattern on which the new list * comprehension is based on. * @return an ongoing definition. * @since 2020.0.0 */ public static OngoingDefinitionWithPattern listBasedOn(RelationshipPattern relationshipPattern) { return PatternComprehension.basedOn(relationshipPattern); } /** * Creates a list comprehension starting with a {@link NamedPath named path}. * @param namedPath the named path on which the new list comprehension is based on. * @return an ongoing definition. * @since 2020.1.1 */ public static OngoingDefinitionWithPattern listBasedOn(NamedPath namedPath) { return PatternComprehension.basedOn(namedPath); } /** * Starts defining a {@link ListComprehension list comprehension}. * @param variable the variable to which each element of the list is assigned. * @return an ongoing definition of a list comprehension * @since 1.0.1 */ public static OngoingDefinitionWithVariable listWith(SymbolicName variable) { return ListComprehension.with(variable); } /** * Escapes and quotes the {@code unquotedString} for safe usage in Neo4j-Browser and * Shell. * @param unquotedString an unquoted string * @return a quoted string with special chars escaped. */ public static String quote(String unquotedString) { return literalOf(unquotedString).asString(); } /** * {@return generic case expression start} */ public static Case caseExpression() { return Case.create(null); } /** * Starts building a {@literal CASE} expression. * @param expression initial expression for the simple case statement * @return simple case expression start */ public static Case caseExpression(Expression expression) { return Case.create(expression); } /** * Starts defining a procedure call of the procedure with the given * {@literal procedureName}. That procedure name might be fully qualified - that is, * including a namespace - or just a simple name. * @param procedureName the procedure name of the procedure to call. Might be fully * qualified. * @return an ongoing definition of a call */ public static OngoingStandaloneCallWithoutArguments call(String procedureName) { Assertions.hasText(procedureName, "The procedure name must not be null or empty."); return call(procedureName.split("\\.")); } /** * Starts defining a procedure call of the procedure with the given qualified name. * @param namespaceAndProcedure the procedure name of the procedure to call. * @return an ongoing definition of a call */ public static OngoingStandaloneCallWithoutArguments call(String... namespaceAndProcedure) { return Statement.call(namespaceAndProcedure); } /** * Starts defining a procedure call of the procedure with the given qualified name. * @param namespaceAndProcedure the procedure name of the procedure to call. * @return an ongoing definition of a call * @since 2021.2.2 */ public static OngoingStandaloneCallWithoutArguments call(Collection namespaceAndProcedure) { return call(namespaceAndProcedure.toArray(new String[] {})); } /** * Starts building a statement based on one subquery. * @param subquery the statement representing the subquery * @return a new ongoing read without any further conditions or returns. * @neo4j.version 4.0.0 * @since 2020.1.2 * @see ExposesSubqueryCall#call(Statement) */ @Neo4jVersion(minimum = "4.0.0") public static StatementBuilder.OngoingReadingWithoutWhere call(Statement subquery) { return Statement.builder().call(subquery); } /** * Creates a closed range with given boundaries. * @param targetExpression the target expression for the range * @param start the inclusive start * @param end the exclusive end * @return a range literal. * @since 2020.1.0 */ public static Expression subList(Expression targetExpression, Integer start, Integer end) { return ListOperator.subList(targetExpression, Cypher.literalOf(start), Cypher.literalOf(end)); } /** * Creates a closed range with given boundaries. * @param targetExpression the target expression for the range * @param start the inclusive start * @param end the exclusive end * @return a range literal. * @since 2020.1.0 */ public static Expression subList(Expression targetExpression, Expression start, Expression end) { return ListOperator.subList(targetExpression, start, end); } /** * Creates an open range starting at {@code start}. * @param targetExpression the target expression for the range * @param start the inclusive start * @return a range literal. * @since 2020.1.0 */ public static Expression subListFrom(Expression targetExpression, Integer start) { return ListOperator.subListFrom(targetExpression, Cypher.literalOf(start)); } /** * Creates an open range starting at {@code start}. * @param targetExpression the target expression for the range * @param start the inclusive start * @return a range literal. * @since 2020.1.0 */ public static Expression subListFrom(Expression targetExpression, Expression start) { return ListOperator.subListFrom(targetExpression, start); } /** * Creates an open range starting at {@code start}. * @param targetExpression the target expression for the range * @param end the exclusive end * @return a range literal. * @since 2020.1.0 */ public static Expression subListUntil(Expression targetExpression, Integer end) { return ListOperator.subListUntil(targetExpression, Cypher.literalOf(end)); } /** * Creates an open range starting at {@code start}. * @param targetExpression the target expression for the range * @param end the exclusive end * @return a range literal. * @since 2020.1.0 */ public static Expression subListUntil(Expression targetExpression, Expression end) { return ListOperator.subListUntil(targetExpression, end); } /** * Creates a single valued range at {@code index}. * @param targetExpression the target expression for the range * @param index the index of the range * @return a range literal. * @since 2020.1.0 */ public static ListOperator valueAt(Expression targetExpression, Integer index) { return valueAt(targetExpression, Cypher.literalOf(index)); } /** * Creates a single valued range at {@code index}. * @param targetExpression the target expression for the range * @param index the index of the range * @return a range literal. * @since 2020.1.0 */ public static ListOperator valueAt(Expression targetExpression, Expression index) { return ListOperator.valueAt(targetExpression, index); } /** * Creates an expression from a raw string fragment. No validation is performed on it. * If it is used as expression, you must make sure to define something that works as * expression. *

* This method expects exactly one placeholder in the form of {@literal $E} for any * argument passed with {@code mixedArgs}. *

* To use exactly the term {@literal $E} escape it like this: {@literal \$E} * @param format a raw Cypher string * @param mixedArgs args to the Cypher string * @return an expression to reuse with the builder. * @since 2021.0.2 */ public static Expression raw(String format, Object... mixedArgs) { return RawLiteral.create(format, mixedArgs); } /** * Starts building a statement from a raw Cypher string that might also have arguments * as supported through {@link Cypher#raw(String, Object...)}. Use this method as your * own risk and be aware that no checks are done on the Cypher. * @param rawCypher the raw Cypher statement to call * @param args optional args that replace placeholders in the {@code rawCypher} * @return ongoing sub-query definition based on the raw Cypher statement. * @since 2024.2.0 */ public static ExposesSubqueryCall.BuildableSubquery callRawCypher(String rawCypher, Object... args) { return Statement.builder().callRawCypher(rawCypher, args); } /** * Creates a {@code RETURN} clause from a raw Cypher expression created via * {@link Cypher#raw(String, Object...)}. The expression maybe aliased but it must * resolve to a raw element * @param rawExpression must be a plain raw or an aliased raw expression. To * eventually render as valid Cypher, it must contain the {@code RETURN} keyword. * @return a match that can be build now * @since 2021.2.1 */ public static StatementBuilder.OngoingReadingAndReturn returningRaw(Expression rawExpression) { return Statement.builder().returningRaw(rawExpression); } /** * Provides access to the foreign DSL adapter. Please make sure you have the necessary * runtime dependencies on the class path, otherwise you will see some kind of * {@link ClassNotFoundException} along various classes related to the foreign DSL. * @param expression the expression that should be adapted * @param the type of the expression * @return a foreign adapter * @throws IllegalArgumentException in case the object cannot be adapter * @since 2021.1.0 */ public static ForeignAdapter adapt(FE expression) { ForeignAdapterFactory initializedForeignAdapterFactory = foreignAdapterFactory; if (initializedForeignAdapterFactory == null) { synchronized (Cypher.class) { initializedForeignAdapterFactory = foreignAdapterFactory; if (initializedForeignAdapterFactory == null) { foreignAdapterFactory = new ForeignAdapterFactory(); initializedForeignAdapterFactory = foreignAdapterFactory; } } } return initializedForeignAdapterFactory.getAdapterFor(expression); } /** * Starts building a {@code LOAD CSV} clause by using a periodic commit. The default * rate of the database will be used. * @return an ongoing definition of a {@code LOAD CSV} clause * @since 2021.2.1 */ public static ExposesLoadCSV usingPeriodicCommit() { return usingPeriodicCommit(null); } /** * Starts building a {@code LOAD CSV} clause by using a periodic commit. * @param rate the rate to be used. No checks are done on the rate, the database will * verify valid values. * @return an ongoing definition of a {@code LOAD CSV} clause * @since 2021.2.1 */ public static ExposesLoadCSV usingPeriodicCommit(Integer rate) { return LoadCSVStatementBuilder.usingPeriodicCommit(rate); } /** * Starts building a {@code LOAD CSV}. No headers are assumed. * @param from the URI to load data from. Any uri that is resolvable by the database * itself is valid. * @return an ongoing definition of a {@code LOAD CSV} clause * @since 2021.2.1 */ public static LoadCSVStatementBuilder.OngoingLoadCSV loadCSV(URI from) { return loadCSV(from, false); } /** * Starts building a {@code LOAD CSV}. * @param from the URI to load data from. Any uri that is resolvable by the database * itself is valid. * @param withHeaders set to {@literal true} if the csv file contains header * @return an ongoing definition of a {@code LOAD CSV} clause */ public static LoadCSVStatementBuilder.OngoingLoadCSV loadCSV(URI from, boolean withHeaders) { return LoadCSVStatementBuilder.loadCSV(from, withHeaders); } private static UnionQuery unionImpl(boolean unionAll, Statement... statements) { Assertions.isTrue(statements != null && statements.length >= 2, "At least two statements are required!"); int i = 0; UnionQueryImpl existingUnionQuery = null; @SuppressWarnings("squid:S2259") // Really, we asserted it 4 lines above this one. // Thank you, sonar. boolean isUnionQuery = statements[0] instanceof UnionQueryImpl; if (isUnionQuery) { existingUnionQuery = (UnionQueryImpl) statements[0]; Assertions.isTrue(existingUnionQuery.isAll() == unionAll, "Cannot mix union and union all!"); i = 1; } List listOfQueries = new ArrayList<>(); do { Assertions.isTrue(statements[i] instanceof SingleQuery || statements[i] instanceof ClausesBasedStatement, "Can only union single queries!"); listOfQueries.add(statements[i]); } while (++i < statements.length); if (existingUnionQuery == null) { return UnionQueryImpl.create(unionAll, listOfQueries); } else { return existingUnionQuery.addAdditionalQueries(listOfQueries); } } /** * Tries to format this expression into something human-readable. Not all expressions * are supported * @param expression an expression to format * @return a human-readable string * @throws IllegalArgumentException when the expression cannot be formatted * @since 2021.3.2 */ public static String format(Expression expression) { return Expressions.format(expression); } /** * Decorates the given statement by prepending a static {@literal USE} clause. * @param target the target. This might be a single database or a constituent of a * composite database. This value will be escaped if necessary. If it contains a * {@literal .}, both the first and second part will be escaped individually. * @param statement the statement to decorate * @return the new buildable statement * @since 2023.0.0 */ public static UseStatement use(String target, Statement statement) { return DecoratedQuery.decorate(statement, UseClauseImpl.of(target)); } /** * Decorates the given statement by prepending a dynamic {@literal USE} clause. A * dynamic {@literal USE} clause will utilize {@code graph.byName} to resolve the * target database. * @param target a parameter that must resolve to a Cypher string. * @param statement the statement to decorate * @return the new buildable statement * @since 2023.0.0 */ public static UseStatement use(Parameter target, Statement statement) { return DecoratedQuery.decorate(statement, UseClauseImpl.of(target)); } /** * Decorates the given statement by prepending a dynamic {@literal USE} clause. A * dynamic {@literal USE} clause will utilize {@code graph.byName} to resolve the * target database. * @param target a string expression * @param statement the statement to decorate * @return the new buildable statement * @since 2023.0.0 */ public static UseStatement use(StringLiteral target, Statement statement) { return DecoratedQuery.decorate(statement, UseClauseImpl.of(target)); } /** * Decorates the given statement by prepending a dynamic {@literal USE} clause. A * dynamic {@literal USE} clause will utilize {@code graph.byName} to resolve the * target database unless {@link Cypher#graphByName(Expression)} has already been * used. * @param target the name of a variable pointing to the graph or constituent * @param statement the statement to decorate * @return the new buildable statement * @since 2023.4.0 */ public static UseStatement use(Expression target, Statement statement) { return DecoratedQuery.decorate(statement, UseClauseImpl.of(target)); } /** * Creates a condition that checks whether the {@code lhs} includes all elements * present in {@code rhs}. * @param lhs argument that is tested whether it contains all values in {@code rhs} or * not * @param rhs the reference collection * @return an "includesAll" comparison * @since 2023.9.0 */ public static Condition includesAll(Expression lhs, Expression rhs) { return Conditions.includesAll(lhs, rhs); } /** * Creates a condition that checks whether the {@code lhs} includes any element * present in {@code rhs}. * @param lhs argument that is tested whether it contains any values in {@code rhs} or * not * @param rhs the reference collection * @return a "not_includes" comparison * @since 2023.9.0 */ public static Condition includesAny(Expression lhs, Expression rhs) { return Conditions.includesAny(lhs, rhs); } /** * A condition testing if the given relationship pattern matches. * @param relationshipPattern the pattern being evaluated in a condition * @return a new condition matching the given pattern * @since 2023.9.0 */ public static Condition matching(RelationshipPattern relationshipPattern) { return Conditions.matching(relationshipPattern); } /** * Creates a condition that matches if the right hand side is a regular expression * that matches the left hand side via {@code =~}. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return a "matches" comparison * @since 2023.9.0 */ public static Condition matches(Expression lhs, Expression rhs) { return Conditions.matches(lhs, rhs); } /** * Creates a condition that matches if both expressions are equals according to * {@code =}. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return an "equals" comparison * @since 2023.9.0 */ public static Condition isEqualTo(Expression lhs, Expression rhs) { return Conditions.isEqualTo(lhs, rhs); } /** * Creates a condition that matches if both expressions are equals according to * {@code <>}. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return a "not equals" comparison * @since 2023.9.0 */ public static Condition isNotEqualTo(Expression lhs, Expression rhs) { return Conditions.isNotEqualTo(lhs, rhs); } /** * Creates a condition that matches if the left hand side is less than the right hand * side. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return a "less than" comparison * @since 2023.9.0 */ public static Condition lt(Expression lhs, Expression rhs) { return Conditions.lt(lhs, rhs); } /** * Creates a condition that matches if the left hand side is less than or equal the * right hand side. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return a "less than or equal" comparison * @since 2023.9.0 */ public static Condition lte(Expression lhs, Expression rhs) { return Conditions.lte(lhs, rhs); } /** * Creates a condition that matches if the left hand side is greater than or equal the * right hand side. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return a "greater than or equal" comparison * @since 2023.9.0 */ public static Condition gte(Expression lhs, Expression rhs) { return Conditions.gte(lhs, rhs); } /** * Creates a condition that matches if the left hand side is greater than the right * hand side. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return a "greater than" comparison * @since 2023.9.0 */ public static Condition gt(Expression lhs, Expression rhs) { return Conditions.gt(lhs, rhs); } /** * Negates the given condition. * @param condition the condition to negate. Must not be null. * @return the negated condition. * @since 2023.9.0 */ public static Condition not(Condition condition) { return Conditions.not(condition); } /** * Negates the given pattern element: The pattern must not matche to be included in * the result. * @param pattern the pattern to negate. Must not be null. * @return a condition that evaluates to true when the pattern does not match. * @since 2023.9.0 */ public static Condition not(RelationshipPattern pattern) { return Conditions.not(pattern); } /** * Creates a condition that checks whether the {@code lhs} starts with the * {@code rhs}. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return a new condition. * @since 2023.9.0 */ public static Condition startsWith(Expression lhs, Expression rhs) { return Conditions.startsWith(lhs, rhs); } /** * Creates a condition that checks whether the {@code lhs} contains with the * {@code rhs}. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return a new condition. * @since 2023.9.0 */ public static Condition contains(Expression lhs, Expression rhs) { return Conditions.contains(lhs, rhs); } /** * Creates a condition that checks whether the {@code lhs} ends with the {@code rhs}. * @param lhs the left hand side of the comparison * @param rhs the right hand side of the comparison * @return a new condition. * @since 2023.9.0 */ public static Condition endsWith(Expression lhs, Expression rhs) { return Conditions.endsWith(lhs, rhs); } /** * Creates a placeholder condition which is not rendered in the final statement but is * useful while chaining conditions together. * @return a placeholder condition. * @since 2023.9.0 */ public static Condition noCondition() { return Conditions.noCondition(); } /** * Creates a condition that checks whether the {@code expression} is {@literal null}. * @param expression the expression to check for {@literal null} * @return a new condition. * @since 2023.9.0 */ public static Condition isNull(Expression expression) { return Conditions.isNull(expression); } /** * Creates a condition that checks whether the {@code expression} is not * {@literal null}. * @param expression the expression to check for {@literal null} * @return a new condition. * @since 2023.9.0 */ public static Condition isNotNull(Expression expression) { return Conditions.isNotNull(expression); } /** * Creates a new condition based on a function invocation for the {@code isEmpty()} * function. See isEmpty. *

* The argument {@code e} must refer to an expression that evaluates to a list for * {@code isEmpty()} to work * @param expression an expression referring to a list * @return a function call for {@code isEmpty()} for a list * @since 2023.9.0 */ public static Condition isEmpty(Expression expression) { return Predicates.isEmpty(expression); } /** * Creates a |{@literal true} condition. * @return a condition that is always true. * @since 2023.9.0 */ public static Condition isTrue() { return Conditions.isTrue(); } /** * Creates a |{@literal false} condition. * @return a condition that is always false. * @since 2023.9.0 */ public static Condition isFalse() { return Conditions.isFalse(); } /** * Checks if a given object has the given labels or types. * @param symbolicName reference to the entity that should be checked for labels or * types * @param labelsOrTypes the list of labels or types to check for * @return a condition that checks whether a node has a set of given labels or a * relationship a set of given types. * @since 2023.9.0 */ public static Condition hasLabelsOrType(SymbolicName symbolicName, String... labelsOrTypes) { return HasLabelCondition.create(symbolicName, labelsOrTypes); } /** * Checks if a given object has the given labels or types. * @param symbolicName reference to the entity that should be checked for labels or * types * @param labels the expression of labels or types to check for * @return a condition that checks whether a node has a set of given labels or a * relationship a set of given types. * @since 2025.1.0 */ public static Condition hasLabelsOrType(SymbolicName symbolicName, Labels labels) { return HasLabelCondition.create(symbolicName, labels); } /** * Creates a {@literal COUNT} sub-query expressions from at least one pattern. * @param requiredPattern one pattern is required * @param patternElement optional pattern * @return the immutable {@link CountExpression} * @since 2023.9.0 */ public static CountExpression count(PatternElement requiredPattern, PatternElement... patternElement) { return Expressions.count(requiredPattern, patternElement); } /** * Creates a {@literal COUNT} with an inner {@literal UNION} sub-query. * @param union the union that will be the source of the {@literal COUNT} sub-query * @return the immutable {@link CountExpression} * @since 2023.9.0 */ public static CountExpression count(UnionQuery union) { return Expressions.count(union); } /** * Creates a {@literal COUNT} from a full statement, including its filters and * conditions. The statement may or may not have a {@literal RETURN} clause. It must * however not contain any updates. While it would render syntactically correct * Cypher, Neo4j does not support updates inside counting sub-queries. * @param statement the statement to be passed to {@code count{}} * @param imports optional imports to be used in the statement (will be imported with * {@literal WITH}) * @return a counting sub-query. * @since 2023.9.0 */ public static CountExpression count(Statement statement, IdentifiableElement... imports) { return Expressions.count(statement, imports); } /** * Creates a {@literal COUNT} expression based on a list of pattern. * @param pattern the list of patterns that shall be counted * @param where an optional where-clause * @return a count expression. * @since 2023.9.0 */ public static CountExpression count(List pattern, Where where) { return Expressions.count(pattern, where); } /** * Creates a {@literal COLLECT} subquery from a statement, including its filters and * conditions. The statement must return exactly one column. It must however not * contain any updates. While it would render syntactically correct Cypher, Neo4j does * not support updates inside counting sub-queries. * @param statement the statement to be passed to {@code COLLECT{}} * @return a collecting sub-query. * @since 2023.9.0 */ public static Expression collect(Statement statement) { return Expressions.collect(statement); } /** * Returns the name of the given expression or the expression itself if it isn't * named. * @param the tyoe of the expression * @param expression possibly named with a non-empty symbolic name. * @return the name of the expression if the expression is named or the expression * itself. * @since 2023.9.0 */ public static Expression nameOrExpression(T expression) { return Expressions.nameOrExpression(expression); } public static SymbolicName[] createSymbolicNames(String[] variables) { return Expressions.createSymbolicNames(variables); } public static SymbolicName[] createSymbolicNames(Named[] variables) { return Expressions.createSymbolicNames(variables); } /** * Creates a function invocation for {@code elementId{}}. * @param node the node for which the element id should be retrieved * @return a function call for {@code elementId()} on a node. * @since 2023.9.0 */ @Neo4jVersion(minimum = "5.0.0") public static FunctionInvocation elementId(Node node) { return Functions.elementId(node); } /** * Creates a function invocation for {@code elementId{}}. * @param relationship the relationship for which the element id should be retrieved * @return a function call for {@code elementId()} on a relationship. * @since 2023.9.0 */ @Neo4jVersion(minimum = "5.0.0") public static FunctionInvocation elementId(Relationship relationship) { return Functions.elementId(relationship); } /** * Creates a function invocation for {@code keys{}}. See keys. * @param node the node which keys should be returned. * @return a function call for {@code keys()} on an expression. * @since 2023.9.0 */ public static FunctionInvocation keys(Node node) { return Functions.keys(node); } /** * Creates a function invocation for {@code keys{}}. See keys. * @param relationship the relationship which keys should be returned. * @return a function call for {@code keys()} on an expression. * @since 2023.9.0 */ public static FunctionInvocation keys(Relationship relationship) { return Functions.keys(relationship); } /** * Creates a function invocation for {@code keys{}}. See keys. * @param expression the expressions which keys should be returned. Must resolve to a * node, relationship or map. * @return a function call for {@code keys()} on an expression. * @since 2023.9.0 */ public static FunctionInvocation keys(Expression expression) { return Functions.keys(expression); } /** * Creates a function invocation for {@code labels{}}. See labels. * @param node the node for which the labels should be retrieved * @return a function call for {@code labels()} on a node. * @since 2023.9.0 */ public static FunctionInvocation labels(Node node) { return Functions.labels(node); } /** * Creates a function invocation for {@code labels{}}. The {@link SymbolicName * symbolic name} {@code node} must point to a node. This can't be checked during * compile time, so please make sure of that. *

* See labels. * @param node the node for which the labels should be retrieved * @return a function call for {@code labels()} on a node. * @since 2023.9.0 */ public static FunctionInvocation labels(SymbolicName node) { return Functions.labels(node); } /** * Creates a function invocation for {@code type{}}. See type. * @param relationship the relationship for which the type should be retrieved * @return a function call for {@code type()} on a relationship. * @since 2023.9.0 */ public static FunctionInvocation type(Relationship relationship) { return Functions.type(relationship); } /** * Creates a function invocation for {@code type{}}. The {@link SymbolicName symbolic * name} {@code relationship} must point to a relationship. This can't be checked * during compile time, so please make sure of that. *

* See type. * @param relationship the relationship for which the type should be retrieved * @return a function call for {@code type()} on a relationship. * @since 2023.9.0 */ public static FunctionInvocation type(SymbolicName relationship) { return Functions.type(relationship); } /** * Creates an instance of the {@code count} function. * @param node the named node to be counted * @return a function call for {@code count()} for one named node * @since 2023.9.0 * @see #count(Expression) */ public static FunctionInvocation count(Node node) { return Functions.count(node); } /** * Creates a function invocation for the {@code count()} function. See count. * @param expression an expression describing the things to count. * @return a function call for {@code count()} for an expression like * {@link Cypher#asterisk()} etc. * @since 2023.9.0 */ public static FunctionInvocation count(Expression expression) { return Functions.count(expression); } /** * Creates a function invocation for a {@code count()} function with {@code DISTINCT} * added. * @param node the named node to be counted * @return a function call for {@code count()} for one named node * @since 2023.9.0 * @see #countDistinct(Expression) */ public static FunctionInvocation countDistinct(Node node) { return Functions.countDistinct(node); } /** * Creates a function invocation for a {@code count()} function with {@code DISTINCT} * added. See count. * @param expression an expression describing the things to count. * @return a function call for {@code count()} for an expression like * {@link Cypher#asterisk()} etc. * @since 2023.9.0 */ public static FunctionInvocation countDistinct(Expression expression) { return Functions.countDistinct(expression); } /** * Creates a function invocation for {@code properties())} on nodes. * @param node the node who's properties should be returned. * @return a function call for {@code properties())} * @since 2023.9.0 */ public static FunctionInvocation properties(Node node) { return Functions.properties(node); } /** * Creates a function invocation for {@code properties())} on relationships. * @param relationship the relationship who's properties should be returned. * @return a function call for {@code properties())} * @since 2023.9.0 */ public static FunctionInvocation properties(Relationship relationship) { return Functions.properties(relationship); } /** * Creates a function invocation for {@code properties())} on maps. * @param map the map whose properties should be returned. * @return a function call for {@code properties())} * @since 2023.9.0 */ public static FunctionInvocation properties(MapExpression map) { return Functions.properties(map); } /** * Creates a function invocation for the {@code coalesce()} function. See coalesce. * @param expressions one or more expressions to be coalesced * @return a function call for {@code coalesce}. * @since 2023.9.0 */ public static FunctionInvocation coalesce(Expression... expressions) { return Functions.coalesce(expressions); } /** * Creates a function invocation for the {@code left()} function. See left. * @param expression an expression resolving to a string * @param length desired length * @return a function call for {@code left()} * @since 2023.9.0 */ public static FunctionInvocation left(Expression expression, Expression length) { return Functions.left(expression, length); } /** * Creates a function invocation for the {@code ltrim()} function. See ltrim. * @param expression an expression resolving to a string * @return a function call for {@code ltrim()} * @since 2023.9.0 */ public static FunctionInvocation ltrim(Expression expression) { return Functions.ltrim(expression); } /** * Creates a function invocation for the {@code replace()} function. See replace. * @param original an expression that returns a string * @param search an expression that specifies the string to be replaced in * {@code original}. * @param replace an expression that specifies the replacement string. * @return a function call for {@code replace()} * @since 2023.9.0 */ public static FunctionInvocation replace(Expression original, Expression search, Expression replace) { return Functions.replace(original, search, replace); } /** * Creates a function invocation for the {@code reverse()} function. See reverse. * @param original an expression that returns a string * @return a function call for {@code reverse()} * @since 2023.9.0 */ public static FunctionInvocation reverse(Expression original) { return Functions.reverse(original); } /** * Creates a function invocation for the {@code right()} function. See right. * @param expression an expression resolving to a string * @param length desired length * @return a function call for {@code right()} * @since 2023.9.0 */ public static FunctionInvocation right(Expression expression, Expression length) { return Functions.right(expression, length); } /** * Creates a function invocation for the {@code rtrim()} function. See rtrim. * @param expression an expression resolving to a string * @return a function call for {@code rtrim()} * @since 2023.9.0 */ public static FunctionInvocation rtrim(Expression expression) { return Functions.rtrim(expression); } /** * Creates a function invocation for the {@code substring()} function. See rtrim. * @param original an expression resolving to a string * @param start an expression that returns a positive integer, denoting the position * at which the substring will begin. * @param length an expression that returns a positive integer, denoting how many * characters of original will be returned. * @return a function call for {@code substring()} * @since 2023.9.0 */ public static FunctionInvocation substring(Expression original, Expression start, Expression length) { return Functions.substring(original, start, length); } /** * Creates a function invocation for the {@code toLower()} function. See toLower. * @param expression an expression resolving to a string * @return a function call for {@code toLower()} for one expression * @since 2023.9.0 */ public static FunctionInvocation toLower(Expression expression) { return Functions.toLower(expression); } /** * Creates a function invocation for the {@code toUpper()} function. See toUpper. * @param expression an expression resolving to a string * @return a function call for {@code toLower()} for one expression * @since 2023.9.0 */ public static FunctionInvocation toUpper(Expression expression) { return Functions.toUpper(expression); } /** * Creates a function invocation for the {@code trim()} function. See trim. * @param expression an expression resolving to a string * @return a function call for {@code trim()} for one expression * @since 2023.9.0 */ public static FunctionInvocation trim(Expression expression) { return Functions.trim(expression); } /** * Creates a function invocation for the {@code split()} function. See split. * @param expression an expression resolving to a string that should be split * @param delimiter the delimiter on which to split * @return a function call for {@code split()} * @since 2023.9.0 */ public static FunctionInvocation split(Expression expression, Expression delimiter) { return Functions.split(expression, delimiter); } /** * Creates a function invocation for the {@code split()} function. See split. * @param expression an expression resolving to a string that should be split * @param delimiter the delimiter on which to split * @return a function call for {@code split()} * @since 2023.9.0 */ public static FunctionInvocation split(Expression expression, String delimiter) { return Functions.split(expression, delimiter); } /** * Creates a function invocation for the {@code size()} function. {@code size} can be * applied to *

* @param expression the expression who's size is to be returned * @return a function call for {@code size()} for one expression * @since 2023.9.0 */ public static FunctionInvocation size(Expression expression) { return Functions.size(expression); } /** * Creates a function invocation for the {@code size()} function. {@code size} can be * applied to * * @param pattern the pattern for which {@code size()} should be invoked. * @return a function call for {@code size()} for a pattern * @since 2023.9.0 */ public static FunctionInvocation size(RelationshipPattern pattern) { return Functions.size(pattern); } /** * Creates a function invocation for the {@code exists()} function. See exists. * @param expression the expression whose existence is to be evaluated * @return a function call for {@code exists()} for one expression * @since 2023.9.0 */ public static FunctionInvocation exists(Expression expression) { return Functions.exists(expression); } /** * Creates a function invocation for the {@code distance()} function. See exists. * Both points need to be in the same coordinate system. * @param point1 point 1 * @param point2 point 2 * @return a function call for {@code distance()} * @since 2023.9.0 */ public static FunctionInvocation distance(Expression point1, Expression point2) { return Functions.distance(point1, point2); } /** * Creates a function invocation for the {@code point()} function. See point. * @param parameterMap the map of parameters for {@code point()} * @return a function call for {@code point()} * @since 2023.9.0 */ public static FunctionInvocation point(MapExpression parameterMap) { return Functions.point(parameterMap); } /** * Creates a function invocation for the {@code point()} function. See point. *

* This generic expression variant is useful for referencing a point inside a * parameter or another map. * @param expression an expression resolving to a valid map of parameters for * {@code point()} * @return a function call for {@code point()} * @since 2023.9.0 */ public static FunctionInvocation point(Expression expression) { return Functions.point(expression); } /** * Creates a function invocation for the {@code point()} function. See point. * @param parameter a parameter referencing a {@code point()} * @return a function call for {@code point()} * @since 2023.9.0 */ public static FunctionInvocation point(Parameter parameter) { return Functions.point(parameter); } /** * Convenience method for creating a 2d cartesian point. * @param x the x coordinate * @param y the y coordinate * @return a function call for {@code point()} * @since 2023.9.0 */ public static FunctionInvocation cartesian(double x, double y) { return Functions.cartesian(x, y); } /** * Convenience method for creating a 2d coordinate in the WGS 84 coordinate system. * @param longitude the longitude * @param latitude the latitude * @return a function call for {@code point()} * @since 2023.9.0 */ public static FunctionInvocation coordinate(double longitude, double latitude) { return Functions.coordinate(longitude, latitude); } /** * Creates a function invocation for the {@code point.withinBBox} function. See * point.withinBBox. * @param point the point to check * @param lowerLeft the lower left point of the bounding box (south-west coordinate) * @param upperRight the upper right point of the bounding box (north-east coordinate) * @return a function call for {@code point.withinBBox} * @since 2023.9.0 */ public static FunctionInvocation withinBBox(Expression point, Expression lowerLeft, Expression upperRight) { return Functions.withinBBox(point, lowerLeft, upperRight); } /** * Creates a function invocation for the {@code avg()} function. See avg. * @param expression the things to average * @return a function call for {@code avg()} * @since 2023.9.0 */ public static FunctionInvocation avg(Expression expression) { return Functions.avg(expression); } /** * Creates a function invocation for the {@code avg()} function with {@code DISTINCT} * added. See avg. * @param expression the things to average * @return a function call for {@code avg()} * @since 2023.9.0 */ public static FunctionInvocation avgDistinct(Expression expression) { return Functions.avgDistinct(expression); } /** * Creates a function invocation for the {@code collect()} function. * @param variable the named thing to collect * @return a function call for {@code collect()} * @since 2023.9.0 * @see #collect(Expression) */ public static FunctionInvocation collect(Named variable) { return Functions.collect(variable); } /** * Creates a function invocation for the {@code collect()} function with * {@code DISTINCT} added. * @param variable the named thing to collect * @return a function call for {@code collect()} * @since 2023.9.0 * @see #collect(Expression) */ public static FunctionInvocation collectDistinct(Named variable) { return Functions.collectDistinct(variable); } /** * Creates a function invocation for the {@code collect()} function. See collect. * @param expression the things to collect * @return a function call for {@code collect()} * @since 2023.9.0 */ public static FunctionInvocation collect(Expression expression) { return Functions.collect(expression); } /** * Creates a function invocation for the {@code collect()} function with * {@code DISTINCT} added. See collect. * @param expression the things to collect * @return a function call for {@code collect()} * @since 2023.9.0 */ public static FunctionInvocation collectDistinct(Expression expression) { return Functions.collectDistinct(expression); } /** * Creates a function invocation for the {@code max()} function. See max. * @param expression a list from which the maximum element value is returned * @return a function call for {@code max()} * @since 2023.9.0 */ public static FunctionInvocation max(Expression expression) { return Functions.max(expression); } /** * Creates a function invocation for the {@code max()} function with {@code DISTINCT} * added. See max. * @param expression a list from which the maximum element value is returned * @return a function call for {@code max()} * @since 2023.9.0 */ public static FunctionInvocation maxDistinct(Expression expression) { return Functions.maxDistinct(expression); } /** * Creates a function invocation for the {@code min()} function. See min. * @param expression a list from which the minimum element value is returned * @return a function call for {@code min()} * @since 2023.9.0 */ public static FunctionInvocation min(Expression expression) { return Functions.min(expression); } /** * Creates a function invocation for the {@code min()} function with {@code DISTINCT} * added. See min. * @param expression a list from which the minimum element value is returned * @return a function call for {@code min()} * @since 2023.9.0 */ public static FunctionInvocation minDistinct(Expression expression) { return Functions.minDistinct(expression); } /** * Creates a function invocation for the {@code percentileCont()} function. See * percentileCont. * @param expression a numeric expression * @param percentile a numeric value between 0.0 and 1.0 * @return a function call for {@code percentileCont()} * @since 2023.9.0 */ public static FunctionInvocation percentileCont(Expression expression, Number percentile) { return Functions.percentileCont(expression, percentile); } /** * Creates a function invocation for the {@code percentileCont()} function with * {@code DISTINCT} added. See percentileCont. * @param expression a numeric expression * @param percentile a numeric value between 0.0 and 1.0 * @return a function call for {@code percentileCont()} * @since 2023.9.0 */ public static FunctionInvocation percentileContDistinct(Expression expression, Number percentile) { return Functions.percentileContDistinct(expression, percentile); } /** * Creates a function invocation for the {@code percentileDisc()} function. See * percentileDisc. * @param expression a numeric expression * @param percentile a numeric value between 0.0 and 1.0 * @return a function call for {@code percentileDisc()} * @since 2023.9.0 */ public static FunctionInvocation percentileDisc(Expression expression, Number percentile) { return Functions.percentileDisc(expression, percentile); } /** * Creates a function invocation for the {@code percentileDisc()} function with * {@code DISTINCT} added. See percentileDisc. * @param expression a numeric expression * @param percentile a numeric value between 0.0 and 1.0 * @return a function call for {@code percentileDisc()} * @since 2023.9.0 */ public static FunctionInvocation percentileDiscDistinct(Expression expression, Number percentile) { return Functions.percentileDiscDistinct(expression, percentile); } /** * Creates a function invocation for the {@code stDev()} function. See stDev. * @param expression a numeric expression * @return a function call for {@code stDev()} * @since 2023.9.0 */ public static FunctionInvocation stDev(Expression expression) { return Functions.stDev(expression); } /** * Creates a function invocation for the {@code stDev()} function with * {@code DISTINCT} added. See stDev. * @param expression a numeric expression * @return a function call for {@code stDev()} * @since 2023.9.0 */ public static FunctionInvocation stDevDistinct(Expression expression) { return Functions.stDevDistinct(expression); } /** * Creates a function invocation for the {@code stDevP()} function. See stDevP. * @param expression a numeric expression * @return a function call for {@code stDevP()} * @since 2023.9.0 */ public static FunctionInvocation stDevP(Expression expression) { return Functions.stDevP(expression); } /** * Creates a function invocation for the {@code stDevP()} function with * {@code DISTINCT} added. See stDevP. * @param expression a numeric expression * @return a function call for {@code stDevP()} * @since 2023.9.0 */ public static FunctionInvocation stDevPDistinct(Expression expression) { return Functions.stDevPDistinct(expression); } /** * Creates a function invocation for the {@code sum()} function. See sum. * @param expression an expression returning a set of numeric values * @return a function call for {@code sum()} * @since 2023.9.0 */ public static FunctionInvocation sum(Expression expression) { return Functions.sum(expression); } /** * Creates a function invocation for the {@code sum()} function with {@code DISTINCT} * added. See sum. * @param expression an expression returning a set of numeric values * @return a function call for {@code sum()} * @since 2023.9.0 */ public static FunctionInvocation sumDistinct(Expression expression) { return Functions.sumDistinct(expression); } /** * Creates a function invocation for the {@code range()} function. * @param start the range's start * @param end the range's end * @return a function call for {@code range()} * @since 2023.9.0 * @see #range(Expression, Expression) */ public static FunctionInvocation range(Integer start, Integer end) { return Functions.range(start, end); } /** * Creates a function invocation for the {@code range()} function. * @param start the range's start * @param end the range's end * @return a function call for {@code range()} * @since 2023.9.0 * @see #range(Expression, Expression, Expression) */ public static FunctionInvocation range(Expression start, Expression end) { return Functions.range(start, end); } /** * Creates a function invocation for the {@code range()} function. See range. * @param start the range's start * @param end the range's end * @param step the range's step * @return a function call for {@code range()} * @since 2023.9.0 * @see #range(Expression, Expression, Expression) */ public static FunctionInvocation range(Integer start, Integer end, Integer step) { return Functions.range(start, end, step); } /** * Creates a function invocation for the {@code range()} function. See range. * @param start the range's start * @param end the range's end * @param step the range's step * @return a function call for {@code range()} * @since 2023.9.0 */ public static FunctionInvocation range(Expression start, Expression end, Expression step) { return Functions.range(start, end, step); } /** * Creates a function invocation for the {@code head()} function. See head. * @param expression a list from which the head element is returned * @return a function call for {@code head()} * @since 2023.9.0 */ public static FunctionInvocation head(Expression expression) { return Functions.head(expression); } /** * Creates a function invocation for the {@code last()} function. See last. * @param expression a list from which the last element is returned * @return a function call for {@code last()} * @since 2023.9.0 */ public static FunctionInvocation last(Expression expression) { return Functions.last(expression); } /** * Creates a function invocation for {@code nodes{}}. See nodes. * @param path the path for which the number of nodes should be retrieved * @return a function call for {@code nodes()} on a path. * @since 2023.9.0 */ public static FunctionInvocation nodes(NamedPath path) { return Functions.nodes(path); } /** * Creates a function invocation for {@code nodes{}}. See nodes. * @param symbolicName the symbolic name of a path for which the number of nodes * should be retrieved * @return a function call for {@code nodes{}} on a path represented by a symbolic * name. * @since 2023.9.0 */ public static FunctionInvocation nodes(SymbolicName symbolicName) { return Functions.nodes(symbolicName); } /** * Creates a function invocation for {@code relationships{}}. See relationships. * @param path the path for which the relationships should be retrieved * @return a function call for {@code relationships()} on a path. * @since 2023.9.0 */ public static FunctionInvocation relationships(NamedPath path) { return Functions.relationships(path); } /** * Creates a function invocation for {@code relationships{}}. See relationships. * @param symbolicName the symbolic name of a path for which the relationships should * be retrieved * @return a function call for {@code relationships()} on a path represented by a * symbolic name. * @since 2023.9.0 */ public static FunctionInvocation relationships(SymbolicName symbolicName) { return Functions.relationships(symbolicName); } /** * Creates a function invocation for {@code startNode{}}. See startNode. * @param relationship the relationship for which the start node be retrieved * @return a function call for {@code startNode()} on a path. * @since 2023.9.0 */ public static FunctionInvocation startNode(Relationship relationship) { return Functions.startNode(relationship); } /** * Creates a function invocation for {@code endNode{}}. See endNode. * @param relationship the relationship for which the end node be retrieved * @return a function call for {@code endNode()} on a path. * @since 2023.9.0 */ public static FunctionInvocation endNode(Relationship relationship) { return Functions.endNode(relationship); } /** * Creates a function invocation for {@code date()}. See date. * This is the most simple form. * @return a function call for {@code date()}. * @since 2023.9.0 */ public static FunctionInvocation date() { return Functions.date(); } /** * Creates a function invocation for {@code date({})}. See date. * @param year the year * @param month the month * @param day the day * @return a function call for {@code date({})}. * @since 2023.9.0 */ public static FunctionInvocation calendarDate(Integer year, Integer month, Integer day) { return Functions.calendarDate(year, month, day); } /** * Creates a function invocation for {@code date({})}. See date. * @param year the year * @param week the optional week * @param dayOfWeek the optional day of the week * @return a function call for {@code date({})}. * @since 2023.9.0 */ public static FunctionInvocation weekDate(Integer year, Integer week, Integer dayOfWeek) { return Functions.weekDate(year, week, dayOfWeek); } /** * Creates a function invocation for {@code date({})}. See date. * @param year the year * @param quarter the optional week * @param dayOfQuarter the optional day of the week * @return a function call for {@code date({})}. * @since 2023.9.0 */ public static FunctionInvocation quarterDate(Integer year, Integer quarter, Integer dayOfQuarter) { return Functions.quarterDate(year, quarter, dayOfQuarter); } /** * Creates a function invocation for {@code date({})}. See date. * @param year the year * @param ordinalDay the ordinal day of the year. * @return a function call for {@code date({})}. * @since 2023.9.0 */ public static FunctionInvocation ordinalDate(Integer year, Integer ordinalDay) { return Functions.ordinalDate(year, ordinalDay); } /** * Creates a function invocation for {@code date({})}. See date. * This is the most generic form. * @param components the map to pass to {@code date({})} * @return a function call for {@code date({})}. * @since 2023.9.0 */ public static FunctionInvocation date(MapExpression components) { return Functions.date(components); } /** * Creates a function invocation for {@code date({})}. See date. * This creates a date from a string. * @param temporalValue a string representing a temporal value. * @return a function call for {@code date({})}. * @since 2023.9.0 */ public static FunctionInvocation date(String temporalValue) { return Functions.date(temporalValue); } /** * Creates a function invocation for {@code date({})}. See date. * This creates a date from a string. * @param temporalValue an expression representing a temporal value. * @return a function call for {@code date({})}. * @since 2023.9.0 */ public static FunctionInvocation date(Expression temporalValue) { return Functions.date(temporalValue); } /** * Creates a function invocation for {@code datetime({})}. See datetime. * @return a function call for {@code datetime({})}. * @since 2023.9.0 */ public static FunctionInvocation datetime() { return Functions.datetime(); } /** * Creates a function invocation for {@code datetime({})}. See datetime. * @param timeZone the timezone to use when creating the temporal instance * @return a function call for {@code datetime({})}. * @since 2023.9.0 */ public static FunctionInvocation datetime(TimeZone timeZone) { return Functions.datetime(timeZone); } /** * Creates a function invocation for {@code datetime({})}. See datetime. * This is the most generic form. * @param components the map to pass to {@code datetime({})} * @return a function call for {@code datetime({})}. * @since 2023.9.0 */ public static FunctionInvocation datetime(MapExpression components) { return Functions.datetime(components); } /** * Creates a function invocation for {@code datetime({})}. See datetime. * This creates a datetime from a string. * @param temporalValue a string representing a temporal value. * @return a function call for {@code datetime({})}. * @since 2023.9.0 */ public static FunctionInvocation datetime(String temporalValue) { return Functions.datetime(temporalValue); } /** * Creates a function invocation for {@code datetime({})}. See datetime. * This creates a datetime from a string. * @param temporalValue an expression representing a temporal value. * @return a function call for {@code date({})}. * @since 2023.9.0 */ public static FunctionInvocation datetime(Expression temporalValue) { return Functions.datetime(temporalValue); } /** * Creates a function invocation for {@code localdatetime({})}. See localdatetime. * @return a function call for {@code localdatetime({})}. * @since 2023.9.0 */ public static FunctionInvocation localdatetime() { return Functions.localdatetime(); } /** * Creates a function invocation for {@code localdatetime({})}. See localdatetime. * @param timeZone the timezone to use when creating the temporal instance * @return a function call for {@code localdatetime({})}. * @since 2023.9.0 */ public static FunctionInvocation localdatetime(TimeZone timeZone) { return Functions.localdatetime(timeZone); } /** * Creates a function invocation for {@code localdatetime({})}. See localdatetime. * This is the most generic form. * @param components the map to pass to {@code localdatetime({})} * @return a function call for {@code localdatetime({})}. * @since 2023.9.0 */ public static FunctionInvocation localdatetime(MapExpression components) { return Functions.localdatetime(components); } /** * Creates a function invocation for {@code localdatetime({})}. See localdatetime. * This creates a localdatetime from a string. * @param temporalValue a string representing a temporal value. * @return a function call for {@code localdatetime({})}. * @since 2023.9.0 */ public static FunctionInvocation localdatetime(String temporalValue) { return Functions.localdatetime(temporalValue); } /** * Creates a function invocation for {@code localdatetime({})}. See localdatetime. * This creates a localdatetime from a string. * @param temporalValue an expression representing a temporal value. * @return a function call for {@code localdatetime({})}. * @since 2023.9.0 */ public static FunctionInvocation localdatetime(Expression temporalValue) { return Functions.localdatetime(temporalValue); } /** * Creates a function invocation for {@code localtime({})}. See localtime. * @return a function call for {@code localtime({})}. * @since 2023.9.0 */ public static FunctionInvocation localtime() { return Functions.localtime(); } /** * Creates a function invocation for {@code localtime({})}. See localtime. * @param timeZone the timezone to use when creating the temporal instance * @return a function call for {@code localtime({})}. * @since 2023.9.0 */ public static FunctionInvocation localtime(TimeZone timeZone) { return Functions.localtime(timeZone); } /** * Creates a function invocation for {@code localtime({})}. See localtime. * This is the most generic form. * @param components the map to pass to {@code localtime({})} * @return a function call for {@code localtime({})}. * @since 2023.9.0 */ public static FunctionInvocation localtime(MapExpression components) { return Functions.localtime(components); } /** * Creates a function invocation for {@code localtime({})}. See localtime. * This creates a localtime from a string. * @param temporalValue a string representing a temporal value. * @return a function call for {@code localtime({})}. * @since 2023.9.0 */ public static FunctionInvocation localtime(String temporalValue) { return Functions.localtime(temporalValue); } /** * Creates a function invocation for {@code localtime({})}. See localtime. * This creates a localtime from a string. * @param temporalValue an expression representing a temporal value. * @return a function call for {@code localtime({})}. * @since 2023.9.0 */ public static FunctionInvocation localtime(Expression temporalValue) { return Functions.localtime(temporalValue); } /** * Creates a function invocation for {@code time({})}. See time. * @return a function call for {@code time({})}. * @since 2023.9.0 */ public static FunctionInvocation time() { return Functions.time(); } /** * Creates a function invocation for {@code time({})}. See time. * @param timeZone the timezone to use when creating the temporal instance * @return a function call for {@code time({})}. * @since 2023.9.0 */ public static FunctionInvocation time(TimeZone timeZone) { return Functions.time(timeZone); } /** * Creates a function invocation for {@code time({})}. See time. * This is the most generic form. * @param components the map to pass to {@code time({})} * @return a function call for {@code time({})}. * @since 2023.9.0 */ public static FunctionInvocation time(MapExpression components) { return Functions.time(components); } /** * Creates a function invocation for {@code time({})}. See time. * This creates a time from a string. * @param temporalValue a string representing a temporal value. * @return a function call for {@code time({})}. * @since 2023.9.0 */ public static FunctionInvocation time(String temporalValue) { return Functions.time(temporalValue); } /** * Creates a function invocation for {@code time({})}. See time. * This creates a time from a string. * @param temporalValue an expression representing a temporal value. * @return a function call for {@code time({})}. * @since 2023.9.0 */ public static FunctionInvocation time(Expression temporalValue) { return Functions.time(temporalValue); } /** * Creates a function invocation for {@code duration({})}. See duration. * This is the most generic form. * @param components the map to pass to {@code duration({})} * @return a function call for {@code duration({})}. * @since 2023.9.0 */ public static FunctionInvocation duration(MapExpression components) { return Functions.duration(components); } /** * Creates a function invocation for {@code duration({})}. See duration. * This creates a duration from a string. * @param temporalAmount a string representing a temporal amount. * @return a function call for {@code duration({})}. * @since 2023.9.0 */ public static FunctionInvocation duration(String temporalAmount) { return Functions.duration(temporalAmount); } /** * Creates a function invocation for {@code duration({})}. See duration. * This creates a duration from a string. * @param temporalAmount an expression representing a temporal amount. * @return a function call for {@code duration({})}. * @since 2023.9.0 */ public static FunctionInvocation duration(Expression temporalAmount) { return Functions.duration(temporalAmount); } /** * Starts building a function invocation for {@code reduce({})}. * @param variable the closure will have a variable introduced in its context. We * decide here which variable to use. * @return an ongoing definition for a function call to {@code reduce({})}. * @since 2023.9.0 */ public static Reduction.OngoingDefinitionWithVariable reduce(SymbolicName variable) { return Functions.reduce(variable); } /** * Creates a function invocation for {@code abs({})}. See abs. * @param expression the value to pass to the function. * @return a function call for {@code abs({})}. * @since 2023.9.0 */ public static FunctionInvocation abs(Expression expression) { return Functions.abs(expression); } /** * Creates a function invocation for {@code ceil({})}. See ceil. * @param expression the value to pass to the function. * @return a function call for {@code ceil({})}. * @since 2023.9.0 */ public static FunctionInvocation ceil(Expression expression) { return Functions.ceil(expression); } /** * Creates a function invocation for {@code floor({})}. See floor. * @param expression the value to pass to the function. * @return a function call for {@code floor({})}. * @since 2023.9.0 */ public static FunctionInvocation floor(Expression expression) { return Functions.floor(expression); } /** * Creates a function invocation for {@code rand({})}. See rand. * @return a function call for {@code rand({})}. * @since 2023.9.0 */ public static FunctionInvocation rand() { return Functions.rand(); } /** * Creates a function invocation for {@code round({})}. See round. * @param value the value to round * @param expression additional parameters, length must be 0, 1 or 2: First entry is * the precision, second is the rounding mode * @return a function call for {@code round({})}. * @since 2023.9.0 */ public static FunctionInvocation round(Expression value, Expression... expression) { return Functions.round(value, expression); } /** * Creates a function invocation for {@code sign({})}. See sign. * @param expression the value to pass to the function. * @return a function call for {@code sign({})}. * @since 2023.9.0 */ public static FunctionInvocation sign(Expression expression) { return Functions.sign(expression); } /** * Creates a function invocation for {@code e({})}. See e. * @return a function call for {@code e({})}. * @since 2023.9.0 */ public static FunctionInvocation e() { return Functions.e(); } /** * Creates a function invocation for {@code exp({})}. See exp. * @param expression the value to pass to the function. * @return a function call for {@code exp({})}. * @since 2023.9.0 */ public static FunctionInvocation exp(Expression expression) { return Functions.exp(expression); } /** * Creates a function invocation for {@code log({})}. See log. * @param expression the value to pass to the function. * @return a function call for {@code log({})}. * @since 2023.9.0 */ public static FunctionInvocation log(Expression expression) { return Functions.log(expression); } /** * Creates a function invocation for {@code log10({})}. See log10. * @param expression the value to pass to the function. * @return a function call for {@code log10({})}. * @since 2023.9.0 */ public static FunctionInvocation log10(Expression expression) { return Functions.log10(expression); } /** * Creates a function invocation for {@code sqrt({})}. See sqrt. * @param expression the value to pass to the function. * @return a function call for {@code sqrt({})}. * @since 2023.9.0 */ public static FunctionInvocation sqrt(Expression expression) { return Functions.sqrt(expression); } /** * Creates a function invocation for {@code acos({})}. See acos. * @param expression the value to pass to the function. * @return a function call for {@code acos({})}. * @since 2023.9.0 */ public static FunctionInvocation acos(Expression expression) { return Functions.acos(expression); } /** * Creates a function invocation for {@code asin({})}. See asin. * @param expression the value to pass to the function. * @return a function call for {@code asin({})}. * @since 2023.9.0 */ public static FunctionInvocation asin(Expression expression) { return Functions.asin(expression); } /** * Creates a function invocation for {@code atan({})}. See atan. * @param expression the value to pass to the function. * @return a function call for {@code atan({})}. * @since 2023.9.0 */ public static FunctionInvocation atan(Expression expression) { return Functions.atan(expression); } /** * Creates a function invocation for {@code atan2({})}. See atan2. * @param y the y value of a point * @param x the x value of a point * @return a function call for {@code atan2({})}. * @since 2023.9.0 */ public static FunctionInvocation atan2(Expression y, Expression x) { return Functions.atan2(y, x); } /** * Creates a function invocation for {@code cos({})}. See cos. * @param expression the value to pass to the function. * @return a function call for {@code cos({})}. * @since 2023.9.0 */ public static FunctionInvocation cos(Expression expression) { return Functions.cos(expression); } /** * Creates a function invocation for {@code cot({})}. See cot. * @param expression the value to pass to the function. * @return a function call for {@code cot({})}. * @since 2023.9.0 */ public static FunctionInvocation cot(Expression expression) { return Functions.cot(expression); } /** * Creates a function invocation for {@code degrees({})}. See degrees. * @param expression the value to pass to the function. * @return a function call for {@code degrees({})}. * @since 2023.9.0 */ public static FunctionInvocation degrees(Expression expression) { return Functions.degrees(expression); } /** * Creates a function invocation for {@code haversin({})}. See haversin. * @param expression the value to pass to the function. * @return a function call for {@code haversin({})}. * @since 2023.9.0 */ public static FunctionInvocation haversin(Expression expression) { return Functions.haversin(expression); } /** * Creates a function invocation for {@code pi({})}. See pi. * @return a function call for {@code pi({})}. * @since 2023.9.0 */ public static FunctionInvocation pi() { return Functions.pi(); } /** * Creates a function invocation for {@code radians({})}. See radians. * @param expression the value to pass to the function. * @return a function call for {@code radians({})}. * @since 2023.9.0 */ public static FunctionInvocation radians(Expression expression) { return Functions.radians(expression); } /** * Creates a function invocation for {@code sin({})}. See sin. * @param expression the value to pass to the function. * @return a function call for {@code sin({})}. * @since 2023.9.0 */ public static FunctionInvocation sin(Expression expression) { return Functions.sin(expression); } /** * Creates a function invocation for {@code tan({})}. See tan. * @param expression the value to pass to the function. * @return a function call for {@code tan({})}. * @since 2023.9.0 */ public static FunctionInvocation tan(Expression expression) { return Functions.tan(expression); } /** * Creates a function invocation for {@code toInteger({})}. See toInteger. * @param expression the value to pass to the function. * @return a function call for {@code toInteger({})}. * @since 2023.9.0 */ public static FunctionInvocation toInteger(Expression expression) { return Functions.toInteger(expression); } /** * Creates a function invocation for {@code toString({})}. See toString. * @param expression the value to pass to the function. * @return a function call for {@code toString({})}. * @since 2023.9.0 */ public static FunctionInvocation toString(Expression expression) { return Functions.toString(expression); } /** * Creates a function invocation for {@code toStringOrNull({})}. See toStringOrNull. * @param expression the value to pass to the function. * @return a function call for {@code toStringOrNull({})}. * @since 2023.9.0 */ public static FunctionInvocation toStringOrNull(Expression expression) { return Functions.toStringOrNull(expression); } /** * Creates a function invocation for {@code toFloat({})}. See toFloat. * @param expression the value to pass to the function. * @return a function call for {@code toFloat({})}. * @since 2023.9.0 */ public static FunctionInvocation toFloat(Expression expression) { return Functions.toFloat(expression); } /** * Creates a function invocation for {@code toBoolean({})}. See toBoolean. * @param expression the value to pass to the function. * @return a function call for {@code toBoolean({})}. * @since 2023.9.0 */ public static FunctionInvocation toBoolean(Expression expression) { return Functions.toBoolean(expression); } /** * Creates a function invocation for {@code linenumber({})}. Only applicable inside an * {@code LOAD CSV} clause. * @return a function call for {@code linenumber({})}. * @since 2023.9.0 */ public static FunctionInvocation linenumber() { return Functions.linenumber(); } /** * Creates a function invocation for {@code file({})}. Only applicable inside an * {@code LOAD CSV} clause. * @return a function call for {@code file({})}. * @since 2023.9.0 */ public static FunctionInvocation file() { return Functions.file(); } /** * Creates a function invocation for {@code randomUUID({})}. Only applicable inside an * {@code LOAD CSV} clause. * @return a function call for {@code randomUUID({})}. * @since 2023.9.0 */ public static FunctionInvocation randomUUID() { return Functions.randomUUID(); } /** * Creates a function invocation for {@code length()}. See length. * @param path the path for which the length should be retrieved * @return a function call for {@code length()} on a path. * @since 2023.9.0 */ public static FunctionInvocation length(NamedPath path) { return Functions.length(path); } /** * Creates a function invocation for {@code graph.names()}. See graph.names. * @return a function call for {@code graph.names()}. * @since 2023.9.0 */ @Neo4jVersion(minimum = "5.0.0") public static FunctionInvocation graphNames() { return Functions.graphNames(); } /** * Creates a function invocation for {@code graph.propertiesByName()}. See graph.propertiesByName. * @param name the name of the graph * @return a function call for {@code graph.propertiesByName()}. * @since 2023.9.0 */ @Neo4jVersion(minimum = "5.0.0") public static FunctionInvocation graphPropertiesByName(Expression name) { return Functions.graphPropertiesByName(name); } /** * Creates a function invocation for {@code graph.byName()}. See graph.byName. * @param name the name of the graph * @return a function call for {@code graph.byName()}. * @since 2023.9.0 */ @Neo4jVersion(minimum = "5.0.0") public static FunctionInvocation graphByName(Expression name) { return Functions.graphByName(name); } /** * Create a new map projection with the given, mixed content. * @param name the symbolic name of this project * @param content the projected content * @return a new map projection * @since 2023.9.0 */ public static MapProjection createProjection(SymbolicName name, Object... content) { return MapProjection.create(name, content); } /** * Creates an unary minus operation. * @param e the expression to which the unary minus should be applied. We don't check * if it's a numeric expression, but in hindsight to generate semantically correct * Cypher, it's recommended that is one. * @return an unary minus operation. * @since 2023.9.0 */ public static Operation minus(Expression e) { return Operations.minus(e); } /** * Creates an unary plus operation. * @param e the expression to which the unary plus should be applied. We don't check * if it's a numeric expression, but in hindsight to generate semantically correct * Cypher, it's recommended that is one. * @return an unary plus operation. * @since 2023.9.0 */ public static Expression plus(Expression e) { return Operations.plus(e); } public static Operation concat(Expression op1, Expression op2) { return Operations.concat(op1, op2); } public static Operation add(Expression op1, Expression op2) { return Operations.add(op1, op2); } public static Operation subtract(Expression op1, Expression op2) { return Operations.subtract(op1, op2); } public static Operation multiply(Expression op1, Expression op2) { return Operations.multiply(op1, op2); } public static Operation divide(Expression op1, Expression op2) { return Operations.divide(op1, op2); } public static Operation remainder(Expression op1, Expression op2) { return Operations.remainder(op1, op2); } public static Operation pow(Expression op1, Expression op2) { return Operations.pow(op1, op2); } /** * Creates a {@code =} operation. The left hand side should resolve to a property or * to something which has labels or types to modify and the right hand side should * either be new properties or labels. * @param target the target that should be modified * @param value the new value of the target * @return a new operation. * @since 2023.9.0 */ public static Operation set(Expression target, Expression value) { return Operations.set(target, value); } /** * Creates a {@code +=} operation. The left hand side must resolve to a container * (either a node or a relationship) of properties and the right hand side must be a * map of new or updated properties * @param target the target container that should be modified * @param value the new properties * @return a new operation. * @since 2023.9.0 */ public static Operation mutate(Expression target, MapExpression value) { return Operations.mutate(target, value); } /** * Creates a {@code +=} operation. The left hand side must resolve to a container * (either a node or a relationship) of properties and the right hand side must be a * map of new or updated properties * @param target the target container that should be modified * @param value the new properties * @return a new operation. * @since 2023.9.0 */ public static Operation mutate(Expression target, Expression value) { return Operations.mutate(target, value); } /** * Creates an operation adding one or more labels to a given {@link Node node}. * @param target the target of the new labels * @param label the labels to be added * @return a set operation * @since 2023.9.0 */ public static Operation setLabel(Node target, String... label) { return Operations.set(target, label); } /** * Creates an operation adding dynamic labels to a {@link Node node}. * @param target the target of the new labels * @param label the labels to be added * @return a set operation * @since 2025.1.0 */ public static Operation setLabel(Node target, Labels label) { return Operations.set(target, label); } /** * Creates an operation removing one or more labels from a given {@link Node node}. * @param target the target of the remove operation * @param label the labels to be removed * @return a remove operation * @since 2023.9.0 */ public static Operation removeLabel(Node target, String... label) { return Operations.remove(target, label); } /** * Creates a new condition based on a function invocation for the {@code exists()} * function. See exists. * @param property the property to be passed to {@code exists()} * @return a function call for {@code exists()} for one property * @since 2023.9.0 */ public static Condition exists(Property property) { return Predicates.exists(property); } /** * Creates a new condition based on a function invocation for the {@code exists()} * function. See exists. * @param pattern the pattern to be passed to {@code exists()} * @return a function call for {@code exists()} for one pattern * @since 2023.9.0 */ public static Condition exists(RelationshipPattern pattern) { return Predicates.exists(pattern); } /** * Creates a new condition via an existential * sub-query. The statement may or may not have a {@literal RETURN} clause. It * must however not contain any updates. While it would render syntactically correct * Cypher, Neo4j does not support updates inside existential sub-queries. * @param statement the statement to be passed to {@code exists{}} * @param imports optional imports to be used in the statement (will be imported with * {@literal WITH}) * @return an existential sub-query. * @since 2023.9.0 */ public static Condition exists(Statement statement, IdentifiableElement... imports) { return Predicates.exists(statement, imports); } /** * Creates a new condition via an existential * sub-query based on the list of patterns. * @param pattern the pattern that must exists * @return an existential sub-query. * @since 2023.9.0 */ public static Condition exists(PatternElement pattern) { return Predicates.exists(pattern); } /** * Creates a new condition via an existential * sub-query based on the list of patterns. * @param pattern the list of patterns that must exists * @return an existential sub-query. * @since 2023.9.0 */ public static Condition exists(List pattern) { return Predicates.exists(pattern); } /** * Creates a new condition via an existential * sub-query based on the list of patterns and an optional {@link Where * where-clause}. * @param pattern the list of patterns that must exists * @param where an optional where-clause * @return an existential sub-query. * @since 2023.9.0 */ public static Condition exists(List pattern, Where where) { return Predicates.exists(pattern, where); } /** * Starts building an {@literal ALL} predicate. * @param variable the variable referring to elements of a list * @return a builder for the {@code all()} predicate function * @since 2023.9.0 * @see #all(SymbolicName) */ public static OngoingListBasedPredicateFunction all(String variable) { return Predicates.all(variable); } /** * Starts building a new condition based on a function invocation for the * {@code all()} function. See exists. * @param variable the variable referring to elements of a list * @return a builder for the {@code all()} predicate function * @since 2023.9.0 */ public static OngoingListBasedPredicateFunction all(SymbolicName variable) { return Predicates.all(variable); } /** * Starts building a {@literal ANY} predicate. * @param variable the variable referring to elements of a list * @return a builder for the {@code any()} predicate function * @since 2023.9.0 * @see #any(SymbolicName) */ public static OngoingListBasedPredicateFunction any(String variable) { return Predicates.any(variable); } /** * Starts building a new condition based on a function invocation for the * {@code any()} function. See exists. * @param variable the variable referring to elements of a list * @return a builder for the {@code any()} predicate function * @since 2023.9.0 */ public static OngoingListBasedPredicateFunction any(SymbolicName variable) { return Predicates.any(variable); } /** * Starts building a {@literal NONE} predicate. * @param variable the variable referring to elements of a list * @return a builder for the {@code none()} predicate function * @since 2023.9.0 * @see #none(SymbolicName) */ public static OngoingListBasedPredicateFunction none(String variable) { return Predicates.none(variable); } /** * Starts building a new condition based on a function invocation for the * {@code none()} function. See exists. * @param variable the variable referring to elements of a list * @return a builder for the {@code none()} predicate function * @since 2023.9.0 */ public static OngoingListBasedPredicateFunction none(SymbolicName variable) { return Predicates.none(variable); } /** * Starts building a {@literal SINGLE} predicate. * @param variable the variable referring to elements of a list * @return a builder for the {@code single()} predicate function * @since 2023.9.0 * @see #single(SymbolicName) */ public static OngoingListBasedPredicateFunction single(String variable) { return Predicates.single(variable); } /** * Starts building a new condition based on a function invocation for the * {@code single()} function. See single. * @param variable the variable referring to elements of a list * @return a builder for the {@code single()} predicate function * @since 2023.9.0 */ public static OngoingListBasedPredicateFunction single(SymbolicName variable) { return Predicates.single(variable); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/DecoratedQuery.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Statement.UseStatement; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; /** * A decorated statement. Used for EXPLAIN and PROFILE'd * queries. * * @author Michael J. Simons * @since 2020.1.2 */ @API(status = INTERNAL, since = "2020.1.2") sealed class DecoratedQuery extends AbstractStatement implements UseStatement { private final Visitable decoration; private final Statement target; private DecoratedQuery(Statement target, Visitable decoration) { this.decoration = decoration; this.target = target; } static DecoratedQuery explain(Statement target) { return DecoratedQuery.decorate(target, Decoration.EXPLAIN); } static DecoratedQuery profile(Statement target) { return DecoratedQuery.decorate(target, Decoration.PROFILE); } private static DecoratedQuery decorate(Statement target, Decoration decoration) { if (target instanceof DecoratedQuery decoratedQuery && !(decoratedQuery.decoration instanceof Use)) { throw new IllegalArgumentException("Cannot explain an already explained or profiled query."); } if (target instanceof ResultStatement && Decoration.PROFILE.equals(decoration)) { return new DecoratedQueryWithResult(target); } return new DecoratedQuery(target, decoration); } static DecoratedQuery decorate(Statement target, Use use) { if (target instanceof DecoratedQuery decoratedQuery) { String message; if (decoratedQuery.decoration instanceof Decoration decoration) { message = decoration.name() + (decoration.name().endsWith("E") ? "'" : "'e") + "d statements are not supported inside USE clauses"; } else { // Right now the only other decoration is the Use clause. message = "Nested USE clauses are not supported"; } throw new IllegalArgumentException(message); } return new DecoratedQuery(target, use); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.decoration.accept(visitor); this.target.accept(visitor); visitor.leave(this); } @Override public boolean doesReturnOrYield() { if (this.decoration instanceof Use) { return this.target.doesReturnOrYield(); } return super.doesReturnOrYield(); } private enum Decoration implements Visitable { EXPLAIN, PROFILE; @Override public String toString() { return RendererBridge.render(this); } } /** * Only profiled queries can have a result statement. */ static final class DecoratedQueryWithResult extends DecoratedQuery implements ResultStatement { private DecoratedQueryWithResult(Statement target) { super(target, Decoration.PROFILE); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/DefaultLoadCSVStatementBuilder.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.net.URI; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.internal.LoadCSV; import org.neo4j.cypherdsl.core.internal.UsingPeriodicCommit; import static org.apiguardian.api.API.Status.INTERNAL; /** * A builder for the {@literal LOAD CSV} statement. * * @author Michael J. Simons * @since 2021.2.1 */ @API(status = INTERNAL, since = "2021.2.1") final class DefaultLoadCSVStatementBuilder extends DefaultStatementBuilder implements LoadCSVStatementBuilder { private final UsingPeriodicCommit usingPeriodicCommit; private final LoadCSV loadCSV; private DefaultLoadCSVStatementBuilder(UsingPeriodicCommit usingPeriodicCommit, LoadCSV loadCSV) { super(usingPeriodicCommit, loadCSV); this.usingPeriodicCommit = usingPeriodicCommit; this.loadCSV = loadCSV; } private DefaultLoadCSVStatementBuilder(DefaultStatementBuilder source, UsingPeriodicCommit usingPeriodicCommit, LoadCSV loadCSV) { super(source, usingPeriodicCommit, loadCSV); this.usingPeriodicCommit = usingPeriodicCommit; this.loadCSV = loadCSV; } static DefaultLoadCSVStatementBuilder create(PrepareLoadCSVStatementImpl config, String alias, DefaultStatementBuilder source) { // It should be reasonable safe to keep that immutable object around final LoadCSV loadCSV = new LoadCSV(config.uri, config.withHeaders, alias); return (source != null) ? new DefaultLoadCSVStatementBuilder(source, config.usingPeriodicCommit, loadCSV) : new DefaultLoadCSVStatementBuilder(config.usingPeriodicCommit, loadCSV); } @Override public StatementBuilder withFieldTerminator(String fieldTerminator) { return new DefaultStatementBuilder(this.usingPeriodicCommit, this.loadCSV.withFieldTerminator(fieldTerminator)); } static final class PrepareLoadCSVStatementImpl implements ExposesLoadCSV, OngoingLoadCSV { private final UsingPeriodicCommit usingPeriodicCommit; private final DefaultStatementBuilder source; private URI uri; private boolean withHeaders; PrepareLoadCSVStatementImpl(Integer rate) { this.usingPeriodicCommit = new UsingPeriodicCommit(rate); this.source = null; } PrepareLoadCSVStatementImpl(URI uri, boolean withHeaders) { this(uri, withHeaders, null); } PrepareLoadCSVStatementImpl(URI uri, boolean withHeaders, DefaultStatementBuilder source) { this.usingPeriodicCommit = null; this.uri = uri; this.withHeaders = withHeaders; this.source = source; } @Override public LoadCSVStatementBuilder as(String alias) { return DefaultLoadCSVStatementBuilder.create(this, alias, this.source); } @Override public OngoingLoadCSV loadCSV(URI newUri, boolean newWithHeaders) { this.uri = newUri; this.withHeaders = newWithHeaders; return this; } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/DefaultStatementBuilder.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.StatementBuilder.BuildableMatchAndUpdate; import org.neo4j.cypherdsl.core.StatementBuilder.BuildableOngoingMergeAction; import org.neo4j.cypherdsl.core.StatementBuilder.OngoingMatchAndUpdate; import org.neo4j.cypherdsl.core.StatementBuilder.OngoingMerge; import org.neo4j.cypherdsl.core.StatementBuilder.OngoingReadingWithWhere; import org.neo4j.cypherdsl.core.StatementBuilder.OngoingReadingWithoutWhere; import org.neo4j.cypherdsl.core.StatementBuilder.OngoingUpdate; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.internal.ProcedureName; import org.neo4j.cypherdsl.core.internal.YieldItems; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.INTERNAL; /** * Default implementation of the {@link StatementBuilder} and related operations. * * @author Michael J. Simons * @author Gerrit Meier * @author Romain Rossi * @since 1.0 */ @API(status = INTERNAL, since = "2021.2.1") class DefaultStatementBuilder implements StatementBuilder, OngoingUpdate, OngoingMerge, OngoingReadingWithWhere, OngoingReadingWithoutWhere, OngoingMatchAndUpdate, BuildableMatchAndUpdate, BuildableOngoingMergeAction, ExposesSubqueryCall.BuildableSubquery, StatementBuilder.VoidCall, StatementBuilder.Terminal { private static final EnumSet MERGE_OR_CREATE = EnumSet.of(UpdateType.CREATE, UpdateType.MERGE); private static final EnumSet SET = EnumSet.of(UpdateType.SET, UpdateType.MUTATE); /** * Current list of reading or update clauses to be generated. */ private final List currentSinglePartElements = new ArrayList<>(); /** * A list of already build withs. */ private final List multiPartElements = new ArrayList<>(); /** * The latest ongoing match. */ private MatchBuilder currentOngoingMatch; /** * The latest ongoing update to be built. */ private DefaultStatementWithUpdateBuilder currentOngoingUpdate; /** * Default constructor. Builder can be preloaded with visitables. * @param visitables a set of visitables. {@literal NULL} values will be skipped. */ DefaultStatementBuilder(Visitable... visitables) { addVisitables(visitables); } /** * A copy constructor. * @param source the source * @param visitables a set of additional visitables. {@literal NULL} values will be * skipped. */ DefaultStatementBuilder(DefaultStatementBuilder source, Visitable... visitables) { this.currentSinglePartElements.addAll(source.currentSinglePartElements); this.currentOngoingMatch = source.currentOngoingMatch; this.currentOngoingUpdate = source.currentOngoingUpdate; this.multiPartElements.addAll(source.multiPartElements); addVisitables(visitables); } /** * Creates a builder for an UPDATE clause. The vargs is list of pattern or * expressions. In case {@code updateType} is of {@link UpdateType#MERGE} or * {@link UpdateType#CREATE} they will be treated as pattern, otherwise as expression. * @param updateType the update type to create * @param patternOrExpressions a list of pattern or expression * @param the type of {@code patternOrExpressions} * @return ongoing builder */ @SafeVarargs @SuppressWarnings("varargs") // WTH IDEA? private static UpdatingClauseBuilder getUpdatingClauseBuilder(UpdateType updateType, T... patternOrExpressions) { boolean mergeOrCreate = MERGE_OR_CREATE.contains(updateType); String message = mergeOrCreate ? "At least one pattern is required." : "At least one modifying expressions is required."; Assertions.notNull(patternOrExpressions, message); Assertions.notEmpty(patternOrExpressions, message); if (mergeOrCreate) { final List patternElements = Arrays.stream(patternOrExpressions) .map(PatternElement.class::cast) .toList(); if (updateType == UpdateType.CREATE) { return new AbstractUpdatingClauseBuilder.CreateBuilder(patternElements); } else { return new AbstractUpdatingClauseBuilder.MergeBuilder(patternElements); } } else { List expressions = Arrays.stream(patternOrExpressions).map(Expression.class::cast).toList(); ExpressionList expressionList = new ExpressionList( SET.contains(updateType) ? prepareSetExpressions(updateType, expressions) : expressions); return switch (updateType) { case DETACH_DELETE -> () -> new Delete(expressionList, true); case DELETE -> () -> new Delete(expressionList, false); case SET, MUTATE -> () -> new Set(expressionList); case REMOVE -> () -> new Remove(expressionList); default -> throw new IllegalArgumentException("Unsupported update type " + updateType); }; } } /** * Utility method to prepare a list of expression to work with the set clause. * @param updateType which kind of update is used while creating the {@literal SET} * operation * @param possibleSetOperations a mixed list of expressions (property and list * operations) * @return a reified list of expressions that all target properties */ private static List prepareSetExpressions(UpdateType updateType, List possibleSetOperations) { List propertyOperations = new ArrayList<>(); List listOfExpressions = new ArrayList<>(); for (Expression possibleSetOperation : possibleSetOperations) { if (possibleSetOperation instanceof Operation) { propertyOperations.add(possibleSetOperation); } else { listOfExpressions.add(possibleSetOperation); } } if (listOfExpressions.size() % 2 != 0) { throw new IllegalArgumentException("The list of expression to set must be even."); } if (updateType == UpdateType.SET) { for (int i = 0; i < listOfExpressions.size(); i += 2) { propertyOperations.add(Operations.set(listOfExpressions.get(i), listOfExpressions.get(i + 1))); } } else if (updateType == UpdateType.MUTATE) { if (!(listOfExpressions.isEmpty() || propertyOperations.isEmpty())) { throw new IllegalArgumentException( "A mutating SET must be build through a single operation or through a pair of expression, not both."); } if (listOfExpressions.isEmpty()) { for (Expression operation : propertyOperations) { if (((Operation) operation).getOperator() != Operator.MUTATE) { throw new IllegalArgumentException("Only property operations based on the " + Operator.MUTATE + " are supported inside a mutating SET."); } } } else { for (int i = 0; i < listOfExpressions.size(); i += 2) { Expression rhs = listOfExpressions.get(i + 1); if (rhs instanceof Parameter) { propertyOperations.add(Operations.mutate(listOfExpressions.get(i), rhs)); } else if (rhs instanceof MapExpression mapExpression) { propertyOperations.add(Operations.mutate(listOfExpressions.get(i), mapExpression)); } else { throw new IllegalArgumentException( "A mutating SET operation can only be used with a named parameter or a map expression."); } } } } if (updateType != UpdateType.REMOVE && propertyOperations.stream() .anyMatch(e -> e instanceof Operation op && op.getOperator() == Operator.REMOVE_LABEL)) { throw new IllegalArgumentException("REMOVE operations are not supported in a SET clause"); } return propertyOperations; } private static Collection extractIdentifiablesFromReturnList(List returnList) { return returnList.stream() .filter(IdentifiableElement.class::isInstance) .map(IdentifiableElement.class::cast) .map(IdentifiableElement::asExpression) .collect(Collectors.toSet()); } private void addVisitables(Visitable[] visitables) { for (Visitable visitable : visitables) { if (visitable != null) { this.currentSinglePartElements.add(visitable); } } } @Override public final OngoingReadingWithoutWhere match(boolean optional, PatternElement... pattern) { Assertions.notNull(pattern, "Patterns to match are required."); Assertions.notEmpty(pattern, "At least one pattern to match is required."); this.closeCurrentOngoingMatch(); this.currentOngoingMatch = new MatchBuilder(optional); this.currentOngoingMatch.patternList.addAll(Arrays.asList(pattern)); return this; } @Override public final OngoingUpdate create(PatternElement... pattern) { return update(UpdateType.CREATE, pattern); } @Override public final OngoingUpdate create(Collection pattern) { return create(pattern.toArray(new PatternElement[] {})); } @Override public final OngoingMerge merge(PatternElement... pattern) { return update(UpdateType.MERGE, pattern); } @Override public final OngoingMergeAction onCreate() { return ongoingOnAfterMerge(MergeAction.Type.ON_CREATE); } @Override public final OngoingMergeAction onMatch() { return ongoingOnAfterMerge(MergeAction.Type.ON_MATCH); } private OngoingMergeAction ongoingOnAfterMerge(MergeAction.Type type) { Assertions.notNull(this.currentOngoingUpdate, "MERGE must have been invoked before defining an event."); Assertions.isTrue(this.currentOngoingUpdate.builder instanceof SupportsActionsOnTheUpdatingClause, "MERGE must have been invoked before defining an event."); return new OngoingMergeAction() { @Override public BuildableOngoingMergeAction set(Node node, String... labels) { return this.set(Operations.set(node, labels)); } @Override public BuildableOngoingMergeAction set(Node node, Collection labels) { return this.set(Operations.set(node, labels.toArray(new String[0]))); } @Override public BuildableOngoingMergeAction set(Node node, Labels labels) { return this.set(Operations.set(node, labels)); } @Override public BuildableOngoingMergeAction mutate(Expression target, Expression properties) { ((SupportsActionsOnTheUpdatingClause) DefaultStatementBuilder.this.currentOngoingUpdate.builder) .on(type, UpdateType.MUTATE, target, properties); return DefaultStatementBuilder.this; } @Override public BuildableOngoingMergeAction set(Expression... expressions) { ((SupportsActionsOnTheUpdatingClause) DefaultStatementBuilder.this.currentOngoingUpdate.builder) .on(type, UpdateType.SET, expressions); return DefaultStatementBuilder.this; } @Override public BuildableOngoingMergeAction set(Collection expressions) { return set(expressions.toArray(new Expression[] {})); } }; } @Override public final OngoingUnwind unwind(Expression expression) { closeCurrentOngoingMatch(); return new DefaultOngoingUnwind(expression); } private DefaultStatementBuilder update(UpdateType updateType, Object[] pattern) { Assertions.notNull(pattern, "Patterns to create are required."); Assertions.notEmpty(pattern, "At least one pattern to create is required."); this.closeCurrentOngoingMatch(); this.closeCurrentOngoingUpdate(); if (pattern.getClass().getComponentType() == PatternElement.class) { this.currentOngoingUpdate = new DefaultStatementWithUpdateBuilder(updateType, (PatternElement[]) pattern); } else if (Expression.class.isAssignableFrom(pattern.getClass().getComponentType())) { this.currentOngoingUpdate = new DefaultStatementWithUpdateBuilder(updateType, (Expression[]) pattern); } return this; } @Override public final OngoingReadingAndReturn returning(Collection elements) { return returning(false, false, elements); } @Override public final OngoingReadingAndReturn returningDistinct(Collection elements) { return returning(false, true, elements); } @Override public StatementBuilder.OngoingReadingAndReturn returningRaw(Expression rawExpression) { return new DefaultStatementWithReturnBuilder(rawExpression); } private OngoingReadingAndReturn returning(boolean raw, boolean distinct, Collection elements) { DefaultStatementWithReturnBuilder ongoingMatchAndReturn = new DefaultStatementWithReturnBuilder(raw, distinct); ongoingMatchAndReturn.addExpressions(elements); return ongoingMatchAndReturn; } @Override public final OrderableOngoingReadingAndWithWithoutWhere with(Collection elements) { return with(false, elements); } @Override public OrderableOngoingReadingAndWithWithoutWhere withDistinct(Collection elements) { return with(true, elements); } private OrderableOngoingReadingAndWithWithoutWhere with(boolean distinct, Collection elements) { DefaultStatementWithWithBuilder ongoingMatchAndWith = new DefaultStatementWithWithBuilder(distinct); ongoingMatchAndWith.addElements(elements); return ongoingMatchAndWith; } @Override public final OngoingUpdate delete(Expression... expressions) { return update(UpdateType.DELETE, expressions); } @Override public final OngoingUpdate delete(Collection expressions) { return delete(expressions.toArray(new Expression[] {})); } @Override public final OngoingUpdate detachDelete(Expression... expressions) { return update(UpdateType.DETACH_DELETE, expressions); } @Override public final OngoingUpdate detachDelete(Collection expressions) { return detachDelete(expressions.toArray(new Expression[] {})); } @Override public final BuildableMatchAndUpdate set(Expression... expressions) { DefaultStatementWithUpdateBuilder result = new DefaultStatementWithUpdateBuilder(UpdateType.SET, expressions); this.closeCurrentOngoingUpdate(); return result; } @Override public final BuildableMatchAndUpdate set(Collection expressions) { return set(expressions.toArray(new Expression[] {})); } @Override public final BuildableMatchAndUpdate set(Node named, String... labels) { this.closeCurrentOngoingUpdate(); return new DefaultStatementWithUpdateBuilder(UpdateType.SET, Operations.set(named, labels)); } @Override public final BuildableMatchAndUpdate set(Node named, Collection labels) { return set(named, labels.toArray(new String[] {})); } @Override public BuildableMatchAndUpdate set(Node node, Labels labels) { this.closeCurrentOngoingUpdate(); return new DefaultStatementWithUpdateBuilder(UpdateType.SET, Operations.set(node, labels)); } @Override public final BuildableMatchAndUpdate mutate(Expression target, Expression properties) { DefaultStatementWithUpdateBuilder result = new DefaultStatementWithUpdateBuilder(UpdateType.MUTATE, Operations.mutate(target, properties)); this.closeCurrentOngoingUpdate(); return result; } @Override public final BuildableMatchAndUpdate remove(Property... properties) { this.closeCurrentOngoingUpdate(); return new DefaultStatementWithUpdateBuilder(UpdateType.REMOVE, properties); } @Override public final BuildableMatchAndUpdate remove(Collection properties) { return remove(properties.toArray(new Property[] {})); } @Override public final BuildableMatchAndUpdate remove(Node named, String... labels) { this.closeCurrentOngoingUpdate(); return new DefaultStatementWithUpdateBuilder(UpdateType.REMOVE, Operations.remove(named, labels)); } @Override public final BuildableMatchAndUpdate remove(Node named, Collection labels) { return remove(named, labels.toArray(new String[] {})); } @Override public BuildableMatchAndUpdate remove(Node node, Labels labels) { this.closeCurrentOngoingUpdate(); return new DefaultStatementWithUpdateBuilder(UpdateType.REMOVE, Operations.remove(node, labels)); } @Override public final OngoingReadingWithWhere where(Condition newCondition) { if (this.currentOngoingMatch == null) { if (!this.currentSinglePartElements.isEmpty() && this.currentSinglePartElements .get(this.currentSinglePartElements.size() - 1) instanceof Subquery) { throw new IllegalArgumentException( "A CALL{} clause requires to WITH before you can add further conditions"); } throw new IllegalArgumentException("Cannot adda WHERE clause at this point"); } this.currentOngoingMatch.conditionBuilder.where(newCondition); return this; } @Override public final OngoingReadingWithWhere and(Condition additionalCondition) { this.currentOngoingMatch.conditionBuilder.and(additionalCondition); return this; } @Override public final OngoingReadingWithWhere or(Condition additionalCondition) { this.currentOngoingMatch.conditionBuilder.or(additionalCondition); return this; } @Override public Statement build() { return buildImpl(null); } protected final Statement buildImpl(Clause returnOrFinish) { SinglePartQuery singlePartQuery = SinglePartQuery.create(buildListOfVisitables(false), returnOrFinish); if (this.multiPartElements.isEmpty()) { return singlePartQuery; } else { return MultiPartQuery.create(this.multiPartElements, singlePartQuery); } } protected final List buildListOfVisitables(boolean clearAfter) { List visitables = new ArrayList<>(this.currentSinglePartElements); if (this.currentOngoingMatch != null) { visitables.add(this.currentOngoingMatch.buildMatch()); } if (this.currentOngoingUpdate != null) { visitables.add(this.currentOngoingUpdate.builder.build()); } if (clearAfter) { this.currentOngoingMatch = null; this.currentOngoingUpdate = null; this.currentSinglePartElements.clear(); } return visitables; } @SuppressWarnings("OptionalUsedAsFieldOrParameterType") protected final DefaultStatementBuilder addWith(Optional optionalWith) { optionalWith .ifPresent(with -> this.multiPartElements.add(new MultiPartElement(buildListOfVisitables(true), with))); return this; } protected final void addUpdatingClause(UpdatingClause updatingClause) { // Close current match closeCurrentOngoingMatch(); this.currentSinglePartElements.add(updatingClause); } @Override public BuildableSubquery call(Statement statement, IdentifiableElement... imports) { this.closeCurrentOngoingMatch(); this.closeCurrentOngoingUpdate(); this.currentSinglePartElements.add(Subquery.call(statement, imports)); return this; } @Override public BuildableSubquery callRawCypher(String rawCypher, Object... args) { this.closeCurrentOngoingMatch(); this.closeCurrentOngoingUpdate(); this.currentSinglePartElements.add(Subquery.raw(rawCypher, args)); return this; } @Override public BuildableSubquery callInTransactions(Statement statement, Integer rows, IdentifiableElement... imports) { this.closeCurrentOngoingMatch(); this.closeCurrentOngoingUpdate(); this.currentSinglePartElements.add(Subquery.call(statement, imports).inTransactionsOf(rows)); return this; } private void closeCurrentOngoingMatch() { if (this.currentOngoingMatch == null) { return; } this.currentSinglePartElements.add(this.currentOngoingMatch.buildMatch()); this.currentOngoingMatch = null; } private void closeCurrentOngoingUpdate() { if (this.currentOngoingUpdate == null) { return; } this.currentSinglePartElements.add(this.currentOngoingUpdate.builder.build()); this.currentOngoingUpdate = null; } @Override public final Condition asCondition() { var matches = new ArrayList(); if (!this.currentSinglePartElements.isEmpty()) { for (var visitable : this.currentSinglePartElements) { if (visitable instanceof Match match) { matches.add(match); } } } if (this.currentOngoingMatch != null) { matches.add(this.currentOngoingMatch.buildMatch()); } if (matches.isEmpty()) { throw new IllegalArgumentException("Only MATCH statements can be used as existential subqueries."); } return ExistentialSubquery.exists(matches); } @Override public final OngoingReadingWithoutWhere usingIndex(Property... properties) { this.currentOngoingMatch.hints.add(Hint.useIndexFor(false, properties)); return this; } @Override public final OngoingReadingWithoutWhere usingIndexSeek(Property... properties) { this.currentOngoingMatch.hints.add(Hint.useIndexFor(true, properties)); return this; } @Override public final OngoingReadingWithoutWhere usingScan(Node node) { this.currentOngoingMatch.hints.add(Hint.useScanFor(node)); return this; } @Override public final OngoingReadingWithoutWhere usingJoinOn(SymbolicName... names) { this.currentOngoingMatch.hints.add(Hint.useJoinOn(names)); return this; } @Override public ForeachSourceStep foreach(SymbolicName variable) { this.closeCurrentOngoingMatch(); this.closeCurrentOngoingUpdate(); return new ForeachBuilder(variable); } @Override public Terminal finish() { return new DefaultStatementWithFinishBuilder(); } @Override public InQueryCallBuilder call(String... namespaceAndProcedure) { Assertions.notEmpty(namespaceAndProcedure, "The procedure namespace and name must not be null or empty."); closeCurrentOngoingMatch(); return new InQueryCallBuilder(ProcedureName.from(namespaceAndProcedure)); } /** * A private enum for distinguishing updating clauses. */ enum UpdateType { DELETE, DETACH_DELETE, SET, MUTATE, REMOVE, CREATE, MERGE } private interface UpdatingClauseBuilder { UpdatingClause build(); } interface SupportsActionsOnTheUpdatingClause { SupportsActionsOnTheUpdatingClause on(MergeAction.Type type, UpdateType updateType, Expression... expressions); } abstract static class ReturnListWrapper { protected final List returnList = new ArrayList<>(); protected final void addElements(Collection elements) { Assertions.notNull(elements, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSIONS_REQUIRED)); var filteredElements = elements.stream() .filter(Objects::nonNull) .map(IdentifiableElement::asExpression) .toList(); Assertions.isTrue(!filteredElements.isEmpty(), Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_AT_LEAST_ONE_EXPRESSION_REQUIRED)); this.returnList.addAll(filteredElements); } protected final void addExpressions(Collection expressions) { Assertions.notNull(expressions, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSIONS_REQUIRED)); Assertions.isTrue(!expressions.isEmpty() && expressions.stream().noneMatch(Objects::isNull), Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_AT_LEAST_ONE_EXPRESSION_REQUIRED)); this.returnList.addAll(expressions); } } /** * Infrastructure for building {@link UpdatingClause updating clauses}. * * @param the type of the updating clause */ private abstract static class AbstractUpdatingClauseBuilder implements UpdatingClauseBuilder { protected final List patternElements; AbstractUpdatingClauseBuilder(List patternElements) { this.patternElements = patternElements; } abstract Function getUpdatingClauseProvider(); @Override public T build() { return getUpdatingClauseProvider().apply(Pattern.of(this.patternElements)); } static class CreateBuilder extends AbstractUpdatingClauseBuilder { CreateBuilder(List patternElements) { super(patternElements); } @Override Function getUpdatingClauseProvider() { return Create::new; } } static class MergeBuilder extends AbstractUpdatingClauseBuilder implements SupportsActionsOnTheUpdatingClause { private final List mergeActions = new ArrayList<>(); MergeBuilder(List patternElements) { super(patternElements); } @Override Function getUpdatingClauseProvider() { return pattern -> new Merge(pattern, this.mergeActions); } @Override public SupportsActionsOnTheUpdatingClause on(MergeAction.Type type, UpdateType updateType, Expression... expressions) { ExpressionList expressionList = new ExpressionList( prepareSetExpressions(updateType, Arrays.asList(expressions))); this.mergeActions.add(MergeAction.of(type, new Set(expressionList))); return this; } } } static final class MatchBuilder { private final List patternList = new ArrayList<>(); private final List hints = new ArrayList<>(); private final ConditionBuilder conditionBuilder = new ConditionBuilder(); private final boolean optional; MatchBuilder(boolean optional) { this.optional = optional; } Match buildMatch() { return (Match) Clauses.match(this.optional, this.patternList, Where.from(this.conditionBuilder.buildCondition().orElse(null)), this.hints); } } abstract static class AbstractCallBuilder { protected final ProcedureName procedureName; protected final DefaultStatementBuilder.ConditionBuilder conditionBuilder = new DefaultStatementBuilder.ConditionBuilder(); protected Expression[] arguments; AbstractCallBuilder(ProcedureName procedureName) { this(procedureName, null); } AbstractCallBuilder(ProcedureName procedureName, Expression[] arguments) { this.procedureName = procedureName; this.arguments = arguments; } Arguments createArgumentList() { Arguments argumentsList = null; if (this.arguments != null && this.arguments.length > 0) { argumentsList = new Arguments(this.arguments); } return argumentsList; } } static final class StandaloneCallBuilder extends AbstractCallBuilder implements OngoingStandaloneCallWithoutArguments, OngoingStandaloneCallWithArguments { StandaloneCallBuilder(ProcedureName procedureName) { super(procedureName); } @Override public StandaloneCallBuilder withArgs(Expression... arguments) { super.arguments = arguments; return this; } @Override public OngoingStandaloneCallWithReturnFields yield(Asterisk asterisk) { return new YieldingStandaloneCallBuilder(this.procedureName, this.arguments, asterisk); } @Override public DefaultStatementBuilder.YieldingStandaloneCallBuilder yield(SymbolicName... resultFields) { return new YieldingStandaloneCallBuilder(this.procedureName, this.arguments, resultFields); } @Override public DefaultStatementBuilder.YieldingStandaloneCallBuilder yield(AliasedExpression... aliasedResultFields) { return new YieldingStandaloneCallBuilder(this.procedureName, this.arguments, aliasedResultFields); } @Override public Expression asFunction(boolean distinct) { if (super.arguments == null || super.arguments.length == 0) { return FunctionInvocation.create(this.procedureName::getQualifiedName); } if (distinct) { return FunctionInvocation.createDistinct(this.procedureName::getQualifiedName, super.arguments); } else { return FunctionInvocation.create(this.procedureName::getQualifiedName, super.arguments); } } @Override public VoidCall withoutResults() { return new DefaultStatementBuilder(this.build()); } @Override public ProcedureCall build() { return ProcedureCallImpl.create(this.procedureName, createArgumentList(), null, this.conditionBuilder.buildCondition().map(Where::new).orElse(null)); } } static final class YieldingStandaloneCallBuilder extends AbstractCallBuilder implements ExposesWhere, ExposesReturning, ExposesFinish, OngoingStandaloneCallWithReturnFields { private final YieldItems yieldItems; private DefaultStatementBuilder delegate; YieldingStandaloneCallBuilder(ProcedureName procedureName, Expression[] arguments, SymbolicName... resultFields) { super(procedureName, arguments); this.yieldItems = YieldItems.yieldAllOf(resultFields); } YieldingStandaloneCallBuilder(ProcedureName procedureName, Expression[] arguments, Asterisk asterisk) { super(procedureName, arguments); this.yieldItems = YieldItems.yieldAllOf(asterisk); } YieldingStandaloneCallBuilder(ProcedureName procedureName, Expression[] arguments, AliasedExpression... aliasedResultFields) { super(procedureName, arguments); this.yieldItems = YieldItems.yieldAllOf(aliasedResultFields); } @Override public StatementBuilder.OngoingReadingAndReturn returning(Collection expressions) { return new DefaultStatementBuilder(this.buildCall()).returning(expressions); } @Override public StatementBuilder.OngoingReadingAndReturn returningDistinct( Collection expressions) { return new DefaultStatementBuilder(this.buildCall()).returningDistinct(expressions); } @Override public StatementBuilder.OngoingReadingAndReturn returningRaw(Expression rawExpression) { return new DefaultStatementBuilder(this.buildCall()).returningRaw(rawExpression); } @Override public StatementBuilder.OngoingReadingWithWhere where(Condition newCondition) { this.conditionBuilder.where(newCondition); return new DefaultStatementBuilder(this.buildCall()); } @Override public StatementBuilder.OrderableOngoingReadingAndWithWithoutWhere with( Collection elements) { return new DefaultStatementBuilder(this.buildCall()).with(elements); } @Override public StatementBuilder.OrderableOngoingReadingAndWithWithoutWhere withDistinct( Collection elements) { return new DefaultStatementBuilder(this.buildCall()).withDistinct(elements); } @Override public BuildableSubquery call(Statement statement, IdentifiableElement... imports) { return new DefaultStatementBuilder(this.buildCall()).call(statement, imports); } @Override public BuildableSubquery callRawCypher(String rawCypher, Object... args) { return new DefaultStatementBuilder(this.buildCall()).callRawCypher(rawCypher, args); } @Override public BuildableSubquery callInTransactions(Statement statement, Integer rows, IdentifiableElement... imports) { return new DefaultStatementBuilder(this.buildCall()).callInTransactions(statement, rows, imports); } @Override public Statement build() { if (this.delegate != null) { return this.delegate.build(); } return ProcedureCallImpl.create(this.procedureName, createArgumentList(), this.yieldItems, this.conditionBuilder.buildCondition().map(Where::new).orElse(null)); } Statement buildCall() { return build(); } @Override public StatementBuilder.OngoingReadingWithoutWhere match(boolean optional, PatternElement... pattern) { return new DefaultStatementBuilder(this.buildCall()).match(optional, pattern); } @Override public ExposesAndThen andThen(Statement statement) { if (this.delegate == null) { this.delegate = new DefaultStatementBuilder(this.buildCall()); } this.delegate.currentSinglePartElements.add(statement); return this; } @Override public StatementBuilder.Terminal finish() { return new DefaultStatementBuilder(this.buildCall()); } } // Static builder and support classes static final class ConditionBuilder { private Condition condition; void where(Condition newCondition) { Assertions.notNull(newCondition, "The new condition must not be null."); this.condition = newCondition; } void and(Condition additionalCondition) { this.condition = this.condition.and(additionalCondition); } void or(Condition additionalCondition) { this.condition = this.condition.or(additionalCondition); } private boolean hasCondition() { return this.condition != null && (!(this.condition instanceof CompoundCondition compoundCondition) || compoundCondition.hasConditions()); } Optional buildCondition() { return hasCondition() ? Optional.of(this.condition) : Optional.empty(); } } static final class OrderBuilder { final List sortItemList = new ArrayList<>(); SortItem lastSortItem; Skip skip; Limit limit; void reset() { this.sortItemList.clear(); this.lastSortItem = null; this.skip = null; this.limit = null; } void orderBy(SortItem... sortItem) { this.sortItemList.addAll(Arrays.asList(sortItem)); } void orderBy(Collection sortItems) { if (sortItems != null) { this.sortItemList.addAll(sortItems); } } void orderBy(Expression expression) { this.lastSortItem = Cypher.sort(expression); } void and(Expression expression) { orderBy(expression); } void descending() { this.sortItemList.add(this.lastSortItem.descending()); this.lastSortItem = null; } void ascending() { this.sortItemList.add(this.lastSortItem.ascending()); this.lastSortItem = null; } void skip(Expression expression) { if (expression != null) { this.skip = Skip.create(expression); } } void limit(Expression expression) { if (expression != null) { this.limit = Limit.create(expression); } } Optional buildOrder() { if (this.lastSortItem != null) { this.sortItemList.add(this.lastSortItem); } Optional result = this.sortItemList.isEmpty() ? Optional.empty() : Optional.of(new Order(this.sortItemList)); this.sortItemList.clear(); this.lastSortItem = null; return result; } Skip getSkip() { return this.skip; } Limit getLimit() { return this.limit; } } final class ForeachBuilder implements ForeachSourceStep, ForeachUpdateStep { private final SymbolicName variable; private Expression list; ForeachBuilder(SymbolicName variable) { this.variable = variable; } @Override public ForeachUpdateStep in(Expression newVariableList) { this.list = Objects.requireNonNull(newVariableList); return this; } @Override public OngoingUpdate apply(UpdatingClause... updatingClauses) { if (Arrays.stream(updatingClauses).anyMatch(Foreach.class::isInstance)) { throw new IllegalArgumentException("FOREACH clauses may not be nested"); } DefaultStatementBuilder.this .addUpdatingClause(new Foreach(this.variable, this.list, Arrays.asList(updatingClauses))); return DefaultStatementBuilder.this; } } protected class DefaultStatementWithReturnBuilder extends ReturnListWrapper implements OngoingReadingAndReturn, TerminalOngoingOrderDefinition, OngoingMatchAndReturnWithOrder { private final OrderBuilder orderBuilder = new OrderBuilder(); protected boolean rawReturn; protected boolean distinct; protected DefaultStatementWithReturnBuilder(Expression rawReturnExpression) { this.distinct = false; this.rawReturn = true; this.returnList.add(rawReturnExpression); } protected DefaultStatementWithReturnBuilder(boolean rawReturn, boolean distinct) { this.distinct = distinct; this.rawReturn = rawReturn; } @Override public Collection getIdentifiableExpressions() { return extractIdentifiablesFromReturnList(this.returnList); } @Override public final OngoingMatchAndReturnWithOrder orderBy(SortItem... sortItem) { this.orderBuilder.orderBy(sortItem); return this; } @Override public final OngoingMatchAndReturnWithOrder orderBy(Collection sortItem) { return orderBy(sortItem.toArray(new SortItem[] {})); } @Override public final TerminalOngoingOrderDefinition orderBy(Expression expression) { this.orderBuilder.orderBy(expression); return this; } @Override public final TerminalOngoingOrderDefinition and(Expression expression) { this.orderBuilder.and(expression); return this; } @Override public final DefaultStatementWithReturnBuilder descending() { this.orderBuilder.descending(); return this; } @Override public final DefaultStatementWithReturnBuilder ascending() { this.orderBuilder.ascending(); return this; } @Override public final OngoingReadingAndReturn skip(Number number) { return skip((number != null) ? new NumberLiteral(number) : null); } @Override public final OngoingReadingAndReturn skip(Expression expression) { this.orderBuilder.skip(expression); return this; } @Override public final OngoingReadingAndReturn limit(Number number) { return limit((number != null) ? new NumberLiteral(number) : null); } @Override public final OngoingReadingAndReturn limit(Expression expression) { this.orderBuilder.limit(expression); return this; } @Override public ResultStatement build() { Return returning = Return.create(this.rawReturn, this.distinct, this.returnList, this.orderBuilder); return (ResultStatement) DefaultStatementBuilder.this.buildImpl(returning); } } protected final class DefaultStatementWithFinishBuilder implements Terminal { @Override public Statement build() { return DefaultStatementBuilder.this.buildImpl(Finish.create()); } } /** * Helper class aggregating a couple of interface, collecting conditions and returned * objects. */ protected final class DefaultStatementWithWithBuilder extends ReturnListWrapper implements OngoingOrderDefinition, OrderableOngoingReadingAndWithWithoutWhere, OrderableOngoingReadingAndWithWithWhere, OngoingReadingAndWithWithWhereAndOrder, OngoingReadingAndWithWithSkip { private final ConditionBuilder conditionBuilder = new ConditionBuilder(); private final OrderBuilder orderBuilder = new OrderBuilder(); private final boolean distinct; private DefaultStatementWithWithBuilder(boolean distinct) { this.distinct = distinct; } private Optional buildWith() { if (this.returnList.isEmpty()) { return Optional.empty(); } ExpressionList returnItems = new ExpressionList(this.returnList); Where where = this.conditionBuilder.buildCondition().map(Where::new).orElse(null); Optional returnedWith = Optional .of(new With(this.distinct, returnItems, this.orderBuilder.buildOrder().orElse(null), this.orderBuilder.getSkip(), this.orderBuilder.getLimit(), where)); this.returnList.clear(); this.orderBuilder.reset(); return returnedWith; } @Override public Collection getIdentifiableExpressions() { return extractIdentifiablesFromReturnList(this.returnList); } @Override public OngoingReadingAndReturn returning(Collection expressions) { return DefaultStatementBuilder.this.addWith(buildWith()).returning(expressions); } @Override public OngoingReadingAndReturn returningDistinct(Collection expressions) { return DefaultStatementBuilder.this.addWith(buildWith()).returningDistinct(expressions); } @Override public OngoingReadingAndReturn returningRaw(Expression rawExpression) { return DefaultStatementBuilder.this.addWith(buildWith()).returningRaw(rawExpression); } @Override public OngoingUpdate delete(Expression... expressions) { return DefaultStatementBuilder.this.addWith(buildWith()).delete(expressions); } @Override public OngoingUpdate delete(Collection expressions) { return delete(expressions.toArray(new Expression[] {})); } @Override public OngoingUpdate detachDelete(Expression... expressions) { return DefaultStatementBuilder.this.addWith(buildWith()).detachDelete(expressions); } @Override public OngoingUpdate detachDelete(Collection expressions) { return detachDelete(expressions.toArray(new Expression[] {})); } @Override public BuildableMatchAndUpdate set(Expression... expressions) { return DefaultStatementBuilder.this.addWith(buildWith()).set(expressions); } @Override public BuildableMatchAndUpdate set(Collection expressions) { return set(expressions.toArray(new Expression[] {})); } @Override public BuildableMatchAndUpdate set(Node node, String... labels) { return DefaultStatementBuilder.this.addWith(buildWith()).set(node, labels); } @Override public BuildableMatchAndUpdate set(Node node, Collection labels) { return set(node, labels.toArray(new String[] {})); } @Override public BuildableMatchAndUpdate set(Node node, Labels labels) { return DefaultStatementBuilder.this.addWith(buildWith()).set(node, labels); } @Override public BuildableMatchAndUpdate mutate(Expression target, Expression properties) { return DefaultStatementBuilder.this.addWith(buildWith()).mutate(target, properties); } @Override public BuildableMatchAndUpdate remove(Node node, String... labels) { return DefaultStatementBuilder.this.addWith(buildWith()).remove(node, labels); } @Override public BuildableMatchAndUpdate remove(Node node, Collection labels) { return remove(node, labels.toArray(new String[] {})); } @Override public BuildableMatchAndUpdate remove(Node node, Labels labels) { return DefaultStatementBuilder.this.addWith(buildWith()).remove(node, labels); } @Override public BuildableMatchAndUpdate remove(Property... properties) { return DefaultStatementBuilder.this.addWith(buildWith()).remove(properties); } @Override public BuildableMatchAndUpdate remove(Collection properties) { return remove(properties.toArray(new Property[] {})); } @Override public OrderableOngoingReadingAndWithWithoutWhere with(Collection elements) { return DefaultStatementBuilder.this.addWith(buildWith()).with(elements); } @Override public OrderableOngoingReadingAndWithWithoutWhere withDistinct(Collection elements) { return DefaultStatementBuilder.this.addWith(buildWith()).withDistinct(elements); } @Override public OrderableOngoingReadingAndWithWithWhere where(Condition newCondition) { this.conditionBuilder.where(newCondition); return this; } @Override public OrderableOngoingReadingAndWithWithWhere and(Condition additionalCondition) { this.conditionBuilder.and(additionalCondition); return this; } @Override public OrderableOngoingReadingAndWithWithWhere or(Condition additionalCondition) { this.conditionBuilder.or(additionalCondition); return this; } @Override public OngoingReadingWithoutWhere match(boolean optional, PatternElement... pattern) { return DefaultStatementBuilder.this.addWith(buildWith()).match(optional, pattern); } @Override public OngoingUpdate create(PatternElement... pattern) { return DefaultStatementBuilder.this.addWith(buildWith()).create(pattern); } @Override public OngoingUpdate create(Collection pattern) { return create(pattern.toArray(new PatternElement[] {})); } @Override public OngoingMerge merge(PatternElement... pattern) { return DefaultStatementBuilder.this.addWith(buildWith()).merge(pattern); } @Override public OngoingUnwind unwind(Expression expression) { return DefaultStatementBuilder.this.addWith(buildWith()).unwind(expression); } @Override public BuildableSubquery call(Statement statement, IdentifiableElement... imports) { return DefaultStatementBuilder.this.addWith(buildWith()).call(statement, imports); } @Override public BuildableSubquery callRawCypher(String rawCypher, Object... args) { return DefaultStatementBuilder.this.addWith(buildWith()).callRawCypher(rawCypher, args); } @Override public BuildableSubquery callInTransactions(Statement statement, Integer rows, IdentifiableElement... imports) { return DefaultStatementBuilder.this.addWith(buildWith()).callInTransactions(statement, rows, imports); } @Override public InQueryCallBuilder call(String... namespaceAndProcedure) { return DefaultStatementBuilder.this.addWith(buildWith()).call(namespaceAndProcedure); } @Override public OrderableOngoingReadingAndWithWithWhere orderBy(SortItem... sortItem) { this.orderBuilder.orderBy(sortItem); return this; } @Override public OrderableOngoingReadingAndWithWithWhere orderBy(Collection sortItem) { return orderBy(sortItem.toArray(new SortItem[] {})); } @Override public OngoingOrderDefinition orderBy(Expression expression) { this.orderBuilder.orderBy(expression); return this; } @Override public OngoingOrderDefinition and(Expression expression) { this.orderBuilder.and(expression); return this; } @Override public OngoingReadingAndWithWithWhereAndOrder descending() { this.orderBuilder.descending(); return this; } @Override public OngoingReadingAndWithWithWhereAndOrder ascending() { this.orderBuilder.ascending(); return this; } @Override public OngoingReadingAndWithWithSkip skip(Number number) { return skip((number != null) ? new NumberLiteral(number) : null); } @Override public OngoingReadingAndWithWithSkip skip(Expression expression) { this.orderBuilder.skip(expression); return this; } @Override public OngoingReadingAndWith limit(Number number) { return limit((number != null) ? new NumberLiteral(number) : null); } @Override public OngoingReadingAndWith limit(Expression expression) { this.orderBuilder.limit(expression); return this; } @Override public LoadCSVStatementBuilder.OngoingLoadCSV loadCSV(URI from, boolean withHeaders) { DefaultStatementBuilder this0 = DefaultStatementBuilder.this.addWith(buildWith()); return new DefaultLoadCSVStatementBuilder.PrepareLoadCSVStatementImpl(from, withHeaders, this0); } @Override public ForeachSourceStep foreach(SymbolicName variable) { return DefaultStatementBuilder.this.addWith(buildWith()).foreach(variable); } @Override public StatementBuilder.Terminal finish() { return DefaultStatementBuilder.this; } } protected final class DefaultStatementWithUpdateBuilder implements BuildableMatchAndUpdate { final UpdatingClauseBuilder builder; private DefaultStatementWithUpdateBuilder(UpdateType updateType, PatternElement... pattern) { this.builder = getUpdatingClauseBuilder(updateType, pattern); } private DefaultStatementWithUpdateBuilder(UpdateType updateType, Expression... expressions) { this.builder = getUpdatingClauseBuilder(updateType, expressions); } @Override public OngoingReadingAndReturn returning(Collection expressions) { DefaultStatementBuilder.this.addUpdatingClause(this.builder.build()); DefaultStatementWithReturnBuilder delegate = new DefaultStatementWithReturnBuilder(false, false); delegate.addExpressions(expressions); return delegate; } @Override public OngoingReadingAndReturn returningDistinct(Collection elements) { DefaultStatementWithReturnBuilder delegate = (DefaultStatementWithReturnBuilder) returning(elements); delegate.distinct = true; return delegate; } @Override public OngoingReadingAndReturn returningRaw(Expression rawExpression) { DefaultStatementBuilder.this.addUpdatingClause(this.builder.build()); return new DefaultStatementWithReturnBuilder(rawExpression); } @Override public OngoingUpdate delete(Expression... deletedExpressions) { return delete(false, deletedExpressions); } @Override public OngoingUpdate delete(Collection deletedExpressions) { return delete(deletedExpressions.toArray(new Expression[] {})); } @Override public OngoingUpdate detachDelete(Expression... deletedExpressions) { return delete(true, deletedExpressions); } @Override public OngoingUpdate detachDelete(Collection deletedExpressions) { return detachDelete(deletedExpressions.toArray(new Expression[] {})); } @Override public OngoingMerge merge(PatternElement... pattern) { DefaultStatementBuilder.this.addUpdatingClause(this.builder.build()); return DefaultStatementBuilder.this.merge(pattern); } private OngoingUpdate delete(boolean nextDetach, Expression... deletedExpressions) { DefaultStatementBuilder.this.addUpdatingClause(this.builder.build()); return DefaultStatementBuilder.this.update(nextDetach ? UpdateType.DETACH_DELETE : UpdateType.DELETE, deletedExpressions); } @Override public BuildableMatchAndUpdate set(Expression... keyValuePairs) { DefaultStatementWithUpdateBuilder result = DefaultStatementBuilder.this.new DefaultStatementWithUpdateBuilder( UpdateType.SET, keyValuePairs); DefaultStatementBuilder.this.addUpdatingClause(this.builder.build()); return result; } @Override public BuildableMatchAndUpdate set(Collection keyValuePairs) { return set(keyValuePairs.toArray(new Expression[] {})); } @Override public BuildableMatchAndUpdate set(Node node, String... labels) { DefaultStatementWithUpdateBuilder result = DefaultStatementBuilder.this.new DefaultStatementWithUpdateBuilder( UpdateType.SET, Operations.set(node, labels)); DefaultStatementBuilder.this.addUpdatingClause(this.builder.build()); return result; } @Override public BuildableMatchAndUpdate set(Node node, Collection labels) { return set(node, labels.toArray(new String[] {})); } @Override public BuildableMatchAndUpdate set(Node node, Labels labels) { DefaultStatementWithUpdateBuilder result = DefaultStatementBuilder.this.new DefaultStatementWithUpdateBuilder( UpdateType.SET, Operations.set(node, labels)); DefaultStatementBuilder.this.addUpdatingClause(this.builder.build()); return result; } @Override public BuildableMatchAndUpdate mutate(Expression target, Expression properties) { DefaultStatementWithUpdateBuilder result = DefaultStatementBuilder.this.new DefaultStatementWithUpdateBuilder( UpdateType.MUTATE, Operations.mutate(target, properties)); DefaultStatementBuilder.this.addUpdatingClause(this.builder.build()); return result; } @Override public BuildableMatchAndUpdate remove(Node node, String... labels) { DefaultStatementWithUpdateBuilder result = DefaultStatementBuilder.this.new DefaultStatementWithUpdateBuilder( UpdateType.REMOVE, Operations.set(node, labels)); DefaultStatementBuilder.this.addUpdatingClause(this.builder.build()); return result; } @Override public BuildableMatchAndUpdate remove(Node node, Collection labels) { return remove(node, labels.toArray(new String[] {})); } @Override public BuildableMatchAndUpdate remove(Node node, Labels labels) { DefaultStatementWithUpdateBuilder result = DefaultStatementBuilder.this.new DefaultStatementWithUpdateBuilder( UpdateType.REMOVE, Operations.remove(node, labels)); DefaultStatementBuilder.this.addUpdatingClause(this.builder.build()); return result; } @Override public BuildableMatchAndUpdate remove(Property... properties) { DefaultStatementWithUpdateBuilder result = DefaultStatementBuilder.this.new DefaultStatementWithUpdateBuilder( UpdateType.REMOVE, properties); DefaultStatementBuilder.this.addUpdatingClause(this.builder.build()); return result; } @Override public BuildableMatchAndUpdate remove(Collection properties) { return remove(properties.toArray(new Property[] {})); } @Override public OrderableOngoingReadingAndWithWithoutWhere with(Collection returnedExpressions) { return this.with(false, returnedExpressions); } @Override public OrderableOngoingReadingAndWithWithoutWhere withDistinct(Collection elements) { return this.with(true, elements); } @Override public OngoingUpdate create(PatternElement... pattern) { DefaultStatementBuilder.this.addUpdatingClause(this.builder.build()); return DefaultStatementBuilder.this.create(pattern); } @Override public OngoingUpdate create(Collection pattern) { return create(pattern.toArray(new PatternElement[] {})); } private OrderableOngoingReadingAndWithWithoutWhere with(boolean distinct, Collection elements) { DefaultStatementBuilder.this.addUpdatingClause(this.builder.build()); return DefaultStatementBuilder.this.with(distinct, elements); } @Override public Statement build() { DefaultStatementBuilder.this.addUpdatingClause(this.builder.build()); return DefaultStatementBuilder.this.buildImpl(null); } @Override public ForeachSourceStep foreach(SymbolicName variable) { DefaultStatementBuilder.this.addUpdatingClause(this.builder.build()); return DefaultStatementBuilder.this.foreach(variable); } @Override public StatementBuilder.Terminal finish() { DefaultStatementBuilder.this.addUpdatingClause(this.builder.build()); return new DefaultStatementWithFinishBuilder(); } } final class DefaultOngoingUnwind implements OngoingUnwind { private final Expression expressionToUnwind; DefaultOngoingUnwind(Expression expressionToUnwind) { this.expressionToUnwind = expressionToUnwind; } @Override public OngoingReading as(String variable) { DefaultStatementBuilder.this.currentSinglePartElements.add(new Unwind(this.expressionToUnwind, variable)); return DefaultStatementBuilder.this; } } final class InQueryCallBuilder extends AbstractCallBuilder implements OngoingInQueryCallWithoutArguments, OngoingInQueryCallWithArguments, OngoingInQueryCallWithReturnFields { private YieldItems yieldItems; InQueryCallBuilder(ProcedureName procedureName) { super(procedureName); } Statement buildCall() { return ProcedureCallImpl.create(this.procedureName, createArgumentList(), this.yieldItems, this.conditionBuilder.buildCondition().map(Where::new).orElse(null)); } @Override public InQueryCallBuilder withArgs(Expression... arguments) { super.arguments = arguments; return this; } @Override public InQueryCallBuilder yield(SymbolicName... resultFields) { this.yieldItems = YieldItems.yieldAllOf(resultFields); return this; } @Override public InQueryCallBuilder yield(AliasedExpression... aliasedResultFields) { this.yieldItems = YieldItems.yieldAllOf(aliasedResultFields); return this; } @Override public OngoingReadingWithWhere where(Condition newCondition) { this.conditionBuilder.where(newCondition); DefaultStatementBuilder.this.currentSinglePartElements.add(this.buildCall()); return DefaultStatementBuilder.this; } @Override public OngoingReadingAndReturn returning(Collection expressions) { DefaultStatementBuilder.this.currentSinglePartElements.add(this.buildCall()); return DefaultStatementBuilder.this.returning(expressions); } @Override public OngoingReadingAndReturn returningDistinct(Collection expressions) { DefaultStatementBuilder.this.currentSinglePartElements.add(this.buildCall()); return DefaultStatementBuilder.this.returningDistinct(expressions); } @Override public OngoingReadingAndReturn returningRaw(Expression rawExpression) { DefaultStatementBuilder.this.currentSinglePartElements.add(this.buildCall()); return DefaultStatementBuilder.this.returningRaw(rawExpression); } @Override public OrderableOngoingReadingAndWithWithoutWhere with(Collection elements) { DefaultStatementBuilder.this.currentSinglePartElements.add(this.buildCall()); return DefaultStatementBuilder.this.with(elements); } @Override public OrderableOngoingReadingAndWithWithoutWhere withDistinct(Collection elements) { DefaultStatementBuilder.this.currentSinglePartElements.add(this.buildCall()); return DefaultStatementBuilder.this.withDistinct(elements); } @Override public BuildableSubquery call(Statement statement, IdentifiableElement... imports) { DefaultStatementBuilder.this.currentSinglePartElements.add(this.buildCall()); return DefaultStatementBuilder.this.call(statement, imports); } @Override public BuildableSubquery callRawCypher(String rawCypher, Object... args) { DefaultStatementBuilder.this.currentSinglePartElements.add(this.buildCall()); return DefaultStatementBuilder.this.callRawCypher(rawCypher, args); } @Override public BuildableSubquery callInTransactions(Statement statement, Integer rows, IdentifiableElement... imports) { DefaultStatementBuilder.this.currentSinglePartElements.add(this.buildCall()); return DefaultStatementBuilder.this.callInTransactions(statement, rows, imports); } @Override public StatementBuilder.OngoingReadingWithoutWhere match(boolean optional, PatternElement... pattern) { DefaultStatementBuilder.this.currentSinglePartElements.add(this.buildCall()); return DefaultStatementBuilder.this.match(optional, pattern); } @Override public VoidCall withoutResults() { DefaultStatementBuilder.this.currentSinglePartElements.add(this.buildCall()); return DefaultStatementBuilder.this; } @Override public ForeachSourceStep foreach(SymbolicName variable) { DefaultStatementBuilder.this.currentSinglePartElements.add(this.buildCall()); return DefaultStatementBuilder.this.foreach(variable); } @Override public StatementBuilder.Terminal finish() { return DefaultStatementBuilder.this; } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Delete.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * See Delete. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class Delete extends AbstractClause implements UpdatingClause { private final ExpressionList deleteItems; private final boolean detach; Delete(ExpressionList deleteItems, boolean detach) { this.deleteItems = deleteItems; this.detach = detach; } /** * Creates a {@literal DELETE} clause deleting the given items. * @param toBeDeleted the item to be deleted * @param more more items to be deleted * @return a {@link Delete} clause * @since 2023.4.0 */ static Delete delete(Expression toBeDeleted, Expression... more) { return delete(false, toBeDeleted, more); } /** * Creates a {@literal DELETE} clause deleting the given items. * @param detach set to {@literal true} for {@literal DELETE} clause detaching all * items * @param toBeDeleted the item to be deleted * @param more more items to be deleted * @return a {@link Delete} clause * @since 2023.4.0 */ static Delete delete(boolean detach, Expression toBeDeleted, Expression... more) { if (more == null || more.length == 0) { return new Delete(new ExpressionList(List.of(toBeDeleted)), detach); } List finalExpressionList = new ArrayList<>(); finalExpressionList.add(toBeDeleted); Collections.addAll(finalExpressionList, more); return new Delete(new ExpressionList(finalExpressionList), detach); } /** * Creates a detaching {@literal DELETE} clause deleting the given items. * @param toBeDeleted the item to be deleted * @param more more items to be deleted * @return a {@link Delete} clause * @since 2023.4.0 */ static Delete detachDelete(Expression toBeDeleted, Expression... more) { return delete(true, toBeDeleted, more); } /** * {@return true, if the `DETACH` keyword needs to be included} */ @API(status = INTERNAL) public boolean isDetach() { return this.detach; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.deleteItems.accept(visitor); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/DistinctExpression.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.internal.Distinct; /** * AST representation of the {@literal DISTINCT} keyword. * * @author Michael J. Simons * @since 1.0 */ final class DistinctExpression implements Expression { private final Expression delegate; DistinctExpression(Expression delegate) { this.delegate = delegate; } @Override public void accept(Visitor visitor) { visitor.enter(this); Distinct.INSTANCE.accept(visitor); this.delegate.accept(visitor); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/DurationLiteral.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.time.Duration; /** * A literal representing a duration value to be formatted in a way that Neo4j's Cypher * understands it. * * @author Michael J. Simons * @since 2023.2.1 */ final class DurationLiteral extends LiteralBase { private DurationLiteral(Duration content) { super(content); } static Literal of(Duration duration) { return new DurationLiteral(duration); } @Override public Duration getContent() { return this.content; } @Override public String asString() { var result = new StringBuilder(); result.append("duration('P"); var hours = this.content.toHours(); var minutes = this.content.toMinutesPart(); var seconds = this.content.toSecondsPart(); var nanos = this.content.toNanosPart(); if (hours != 0 || minutes != 0 || seconds != 0 || nanos != 0) { result.append("T"); } if (hours != 0) { result.append(hours).append("H"); } if (minutes != 0) { result.append(minutes).append("M"); } if (seconds != 0 || nanos != 0) { if (nanos == 0) { result.append(seconds); } else { result.append(seconds + nanos / 1_000_000_000.0); } result.append("S"); } result.append("')"); return result.toString(); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExistentialSubquery.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.STABLE; /** * An existential sub-query can only be used in a where clause. The sub-query must consist * only of a match statement which may have a {@code WHERE} clause on its own but is not * allowed to return anything. * * @author Michael J. Simons * @neo4j.version 4.0.0 * @since 2020.1.2 */ @API(status = STABLE, since = "2020.1.2") @Neo4jVersion(minimum = "4.0.0") public final class ExistentialSubquery implements SubqueryExpression, Condition { private final ImportingWith importingWith; private final List fragments; private final Where innerWhere; ExistentialSubquery(List fragments, Where innerWhere) { this.fragments = List.of(Pattern.of(fragments)); this.importingWith = new ImportingWith(); this.innerWhere = innerWhere; } ExistentialSubquery(List fragments) { this.fragments = List.copyOf(fragments); this.importingWith = new ImportingWith(); this.innerWhere = null; } ExistentialSubquery(Statement statement, IdentifiableElement... imports) { this.fragments = List.of(statement); this.importingWith = ImportingWith.of(imports); this.innerWhere = null; } static ExistentialSubquery exists(List fragments) { return new ExistentialSubquery(fragments); } static Condition exists(Statement statement, IdentifiableElement... imports) { return new ExistentialSubquery(statement, imports); } static Condition exists(List patternElements, Where innerWhere) { return new ExistentialSubquery(patternElements, innerWhere); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.importingWith.accept(visitor); this.fragments.forEach(v -> v.accept(visitor)); Visitable.visitIfNotNull(this.innerWhere, visitor); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExposesAndThen.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; /** * Allows chaining of statements to some other builders. * * @param the source type of the builder that ultimately builds the statement ("SELF") * @param the result type of the statement * @author Michael J. Simons */ public interface ExposesAndThen, R extends Statement> extends StatementBuilder.BuildableStatement { ExposesAndThen andThen(Statement statement); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExposesCall.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Arrays; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import static org.apiguardian.api.API.Status.STABLE; /** * Entrypoint for building procedure calls. * * @param the type of the returned builder * @author Michael J. Simons * @since 2020.0.1 */ @API(status = STABLE, since = "2020.0.1") public interface ExposesCall { /** * Starts defining a procedure call of the procedure with the given qualified name. * @param namespaceAndProcedure the procedure name of the procedure to call. * @return an ongoing definition of a call */ @CheckReturnValue T call(String... namespaceAndProcedure); /** * Used to provide arguments to procedure calls. * * @param the type of the next step */ interface ExposesWithArgs { /** * Adds the given arguments to the ongoing call and proceeds. * @param arguments the list of new arguments, might be null or empty. * @return an ongoing standalone call on which yielded arguments might be * configured. */ @CheckReturnValue T withArgs(Expression... arguments); } /** * Interface to allow creating an expression instead of a statement from an ongoing * definition. To make this generate valid Cypher the stored procedure in question * must be a valid function. * * @since 2020.1.2 */ interface AsFunction { /** * Returns a function invocation that can be used as an expression, for example as * a property or inside a condition. * @return a function invocation that can be used as an expression */ default Expression asFunction() { return asFunction(false); } /** * Returns a function invocation that can be used as an expression, for example as * a property or inside a condition. * @param distinct set to true for adding the {@code DISTINCT} for any of the * aggregating functions. * @return a distinct function invocation that can be used as an expression, for * example as a property or inside a condition. * @since 2021.2.2 */ Expression asFunction(boolean distinct); } /** * Used to yield procedure result fields. There are no checks involved whether the * procedure being called actually returns items with the given names. * * @param the type of the next step */ interface ExposesYield { /** * Adds the given items to the {@literal YIELD} clause of the generated call. * @param yieldedItems the list of items to be yielded. * @return the ongoing standalone call to be configured. */ @CheckReturnValue default T yield(String... yieldedItems) { SymbolicName[] names = new SymbolicName[0]; if (yieldedItems != null) { names = Arrays.stream(yieldedItems).map(SymbolicName::of).toArray(SymbolicName[]::new); } return this.yield(names); } /** * Adds the given items to the {@literal YIELD} clause of the generated call. * @param yieldedItems the list of named items to be yielded. * @return the ongoing standalone call to be configured. * @since 2020.1.4 */ @CheckReturnValue default T yield(Named... yieldedItems) { SymbolicName[] names = new SymbolicName[0]; if (yieldedItems != null) { names = Arrays.stream(yieldedItems).map(Named::getRequiredSymbolicName).toArray(SymbolicName[]::new); } return this.yield(names); } /** * Adds the given items to the {@literal YIELD} clause of the generated call. * @param resultFields the list of result fields to be returned. * @return the ongoing standalone call to be configured. */ @CheckReturnValue T yield(SymbolicName... resultFields); /** * Adds the given items to the {@literal YIELD} clause of the generated call and * uses new aliases in the generated call. * @param aliasedResultFields the list of result fields to be returned with new * aliases given. * @return the ongoing standalone call to be configured. */ @CheckReturnValue T yield(AliasedExpression... aliasedResultFields); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExposesCreate.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Collection; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import static org.apiguardian.api.API.Status.STABLE; /** * A step exposing a {@link #create(PatternElement...)} method. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface ExposesCreate { /** * Starts building a {@code CREATE} clause. * @param pattern patterns to create * @return an ongoing merge * @see Cypher#create(PatternElement...) */ @CheckReturnValue StatementBuilder.OngoingUpdate create(PatternElement... pattern); /** * Starts building a {@code CREATE} clause. * @param pattern patterns to create * @return an ongoing merge * @since 2021.2.2 * @see Cypher#create(Collection) */ @CheckReturnValue StatementBuilder.OngoingUpdate create(Collection pattern); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExposesFinish.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import static org.apiguardian.api.API.Status.STABLE; /** * A step to finish the query without the need to return anything. * * @author Gerrit Meier */ @API(status = STABLE, since = "2024.4.0") public interface ExposesFinish { @CheckReturnValue StatementBuilder.Terminal finish(); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExposesHints.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Arrays; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * A step exposing a several {@code using} methods that are provide entry points of adding * advanced query hints. Read more about that topic here: * Planner * hints and the USING keyword. Note that those hints are specific to Neo4j and not * part of openCypher. * * @author Michael J. Simons * @since 2021.0.0 */ @API(status = STABLE, since = "2021.0.0") public interface ExposesHints { /** * Applies an INDEX hint for one or more properties. *

* Index hints are used to specify which index, if any, the planner should use as a * starting point. This can be beneficial in cases where the index statistics are not * accurate for the specific values that the query at hand is known to use, which * would result in the planner picking a non-optimal index. *

* Read more about SCAN hints here. * @param properties one or properties that makes up the index. The properties must * belong to the same node. * @return a statement using an INDEX hint. */ @CheckReturnValue StatementBuilder.OngoingReadingWithoutWhere usingIndex(Property... properties); /** * Applies an INDEX SEEL hint for one or more properties. *

* Index hints are used to specify which index, if any, the planner should use as a * starting point. This can be beneficial in cases where the index statistics are not * accurate for the specific values that the query at hand is known to use, which * would result in the planner picking a non-optimal index. *

* Read more about SCAN hints here. * @param properties one or properties that makes up the index. The properties must * belong to the same node. * @return a statement using an INDEX SEEK hint. */ @CheckReturnValue StatementBuilder.OngoingReadingWithoutWhere usingIndexSeek(Property... properties); /** * Applies a SCAN hint on a node. *

* If your query matches large parts of an index, it might be faster to scan the label * and filter out nodes that do not match. *

* Read more about SCAN hints here. * @param node the node that should be scanned * @return a statement using a SCAN hint. */ @CheckReturnValue StatementBuilder.OngoingReadingWithoutWhere usingScan(Node node); /** * Applies a JOIN hint on one or more nodes. *

* Join hints are the most advanced type of hints, and are not used to find starting * points for the query execution plan, but to enforce that joins are made at * specified points. This implies that there has to be more than one starting point * (leaf) in the plan, in order for the query to be able to join the two branches * ascending from these leaves. *

* Read more about JOIN hints here. * @param nodes the nodes on which a join should be started. * @return a statement using a JOIN hint. */ @CheckReturnValue default StatementBuilder.OngoingReadingWithoutWhere usingJoinOn(Node... nodes) { Assertions.notEmpty(nodes, "At least one node is required to define a JOIN hint."); return this.usingJoinOn(Arrays.stream(nodes).map(Node::getRequiredSymbolicName).toArray(SymbolicName[]::new)); } /** * Applies a JOIN hint on one or more nodes identified by their names. *

* Join hints are the most advanced type of hints, and are not used to find starting * points for the query execution plan, but to enforce that joins are made at * specified points. This implies that there has to be more than one starting point * (leaf) in the plan, in order for the query to be able to join the two branches * ascending from these leaves. *

* Read more about JOIN hints here. * @param names the symbolic names identifying the nodes. * @return a statement using a JOIN hint. */ @CheckReturnValue StatementBuilder.OngoingReadingWithoutWhere usingJoinOn(SymbolicName... names); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExposesLoadCSV.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.net.URI; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * Exposes methods to configure a {@code LOAD CSV} clause. * * @author Michael J. Simons * @since 2020.2.1 */ @API(status = STABLE, since = "2020.2.1") public interface ExposesLoadCSV { /** * Starts building a {@code LOAD CSV}. * @param from the {@link URI} to load data from. Any uri that is resolvable by the * database itself is valid. * @return an ongoing definition of a {@code LOAD CSV} clause */ default LoadCSVStatementBuilder.OngoingLoadCSV loadCSV(URI from) { return loadCSV(from, false); } /** * Starts building a {@code LOAD CSV}. * @param from the {@link URI} to load data from. Any uri that is resolvable by the * database itself is valid. * @param withHeaders set to {@literal true} if the csv file contains header * @return an ongoing definition of a {@code LOAD CSV} clause */ LoadCSVStatementBuilder.OngoingLoadCSV loadCSV(URI from, boolean withHeaders); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExposesLogicalOperators.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import org.neo4j.cypherdsl.core.internal.RelationshipPatternCondition; /** * A step exposing logical operators {@code and} and {@code or} after a {@code where} * clause. * * @param the type being returned after the new condition has been chained * @author Michael J. Simons * @since 1.1 */ public interface ExposesLogicalOperators { /** * Adds a condition to the existing conditions, connected by an {@code AND}. Existing * conditions will be logically grouped by using {@code ()} in the statement if * previous conditions used another logical operator. * @param condition an additional condition * @return the ongoing definition of a match */ @CheckReturnValue T and(Condition condition); /** * Adds a condition based on a path pattern to the existing conditions, connected by * an {@code AND}. Existing conditions will be logically grouped by using {@code ()} * in the statement if previous conditions used another logical operator. * @param pathPattern an additional pattern to include in the conditions * @return the ongoing definition of a match */ @CheckReturnValue default T and(RelationshipPattern pathPattern) { return this.and(RelationshipPatternCondition.of(pathPattern)); } /** * Adds a condition to the existing conditions, connected by an {@code OR}. Existing * conditions will be logically grouped by using {@code ()} in the statement if * previous conditions used another logical operator. * @param condition an additional condition * @return the ongoing definition of a match */ @CheckReturnValue T or(Condition condition); /** * Adds a condition based on a path pattern to the existing conditions, connected by * an {@code OR}. Existing conditions will be logically grouped by using {@code ()} in * the statement if previous conditions used another logical operator. * @param pathPattern an additional pattern to include in the conditions * @return the ongoing definition of a match */ @CheckReturnValue default T or(RelationshipPattern pathPattern) { return this.or(RelationshipPatternCondition.of(pathPattern)); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExposesMatch.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Collection; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import static org.apiguardian.api.API.Status.STABLE; /** * A step exposing a {@link #match(PatternElement...)} method. This is one of the main * entry points of most Cypher queries. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface ExposesMatch { /** * Adds (another) {@code MATCH} clause. * @param pattern the patterns to match * @return an ongoing match that is used to specify an optional where and a required * return clause */ @CheckReturnValue default StatementBuilder.OngoingReadingWithoutWhere match(PatternElement... pattern) { return this.match(false, pattern); } /** * Adds (another) {@code MATCH} clause. * @param pattern the patterns to match * @return an ongoing match that is used to specify an optional where and a required * return clause * @since 2021.2.2 */ @CheckReturnValue default StatementBuilder.OngoingReadingWithoutWhere match(Collection pattern) { return this.match(pattern.toArray(new PatternElement[] {})); } /** * Adds (another) optional {@code MATCH} clause. * @param pattern the patterns to match * @return an ongoing match that is used to specify an optional where and a required * return clause */ @CheckReturnValue default StatementBuilder.OngoingReadingWithoutWhere optionalMatch(PatternElement... pattern) { return this.match(true, pattern); } /** * Adds (another) optional {@code MATCH} clause. * @param pattern the patterns to match * @return an ongoing match that is used to specify an optional where and a required * return clause * @since 2021.2.2 */ @CheckReturnValue default StatementBuilder.OngoingReadingWithoutWhere optionalMatch(Collection pattern) { return this.optionalMatch(pattern.toArray(pattern.toArray(new PatternElement[] {}))); } /** * Adds (another) {@code MATCH} clause. * @param optional a flag whether the {@code MATCH} clause includes the * {@code OPTIONAL} keyword. * @param pattern the patterns to match * @return an ongoing match that is used to specify an optional where and a required * return clause * @since 2020.1.3 */ @CheckReturnValue StatementBuilder.OngoingReadingWithoutWhere match(boolean optional, PatternElement... pattern); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExposesMerge.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import static org.apiguardian.api.API.Status.STABLE; /** * A step exposing a {@link #merge(PatternElement...)} method. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface ExposesMerge { /** * Starts building a {@code MERGE} clause. * @param pattern patterns to merge * @return an ongoing merge * @see Cypher#merge(PatternElement...) */ @CheckReturnValue StatementBuilder.OngoingMerge merge(PatternElement... pattern); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExposesPatternLengthAccessors.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * This interface is used to derive new relationship patterns from existing * {@link Relationship relationships} or {@link RelationshipChain chains of relationships} * with new lengths (min, max or unbounded) configured. * * @param the type of the patterns whose lengths can be adjusted. * @author Michael J. Simons * @since 2021.2.3 */ @API(status = STABLE, since = "2021.2.3") public interface ExposesPatternLengthAccessors { /** * Creates a new relationship pattern with an unbound length minimum length. * @return the new relationship * @since 1.1.1 */ T unbounded(); /** * Creates a new relationship pattern with a new minimum length. * @param minimum the new minimum * @return the new relationship */ T min(Integer minimum); /** * Creates a new relationship pattern with a new maximum length. * @param maximum the new maximum * @return the new relationship */ T max(Integer maximum); /** * Creates a new relationship pattern with a new length. * @param minimum the new minimum * @param maximum the new maximum * @return the new relationship */ T length(Integer minimum, Integer maximum); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExposesProperties.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Map; /** * A container that exposes methods to add properties with values to nodes or * relationships. * * @param type of the object holding the specified properties * @author Michael J. Simons * @since 1.1 */ public interface ExposesProperties & PropertyContainer> { /** * Creates a copy of this property container with additional properties. Creates a * property container without properties when no properties are passed to this method. * @param newProperties the new properties (can be {@literal null} to remove exiting * properties). * @return the new property container. */ T withProperties(MapExpression newProperties); /** * Creates a copy of this property container with additional properties. Creates a * property container without properties when no properties are passed to this method. * @param keysAndValues a list of key and values. Must be an even number, with * alternating {@link String} and {@link Expression}. * @return the new property container. */ T withProperties(Object... keysAndValues); /** * Creates a copy of this property container with additional properties. * @param newProperties a map with the new properties * @return the new property container. */ T withProperties(Map newProperties); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExposesRelationships.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * A marker interface for things that expose methods to create new relationships to other * elements. * * @param the type of the resulting {@link RelationshipPattern} * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface ExposesRelationships> { /** * Starts building an outgoing relationship to the {@code other} {@link Node node}. * @param other the other end of the outgoing relationship * @param types the types to match * @return an ongoing relationship definition, that can be used to specify the type */ T relationshipTo(Node other, String... types); /** * Starts building an incoming relationship starting at the {@code other} {@link Node * node}. * @param other the source of the incoming relationship * @param types the types to match * @return an ongoing relationship definition, that can be used to specify the type */ T relationshipFrom(Node other, String... types); /** * Starts building an undirected relationship between this {@link Node node} and the * {@code other}. * @param other the other end of the relationship * @param types the types to match * @return an ongoing relationship definition, that can be used to specify the type */ T relationshipBetween(Node other, String... types); /** * A convenience method for creating relationships between nodes avoiding going * through the fluent API by allowing to pass in the type directly. * @param other the other end of the relationship * @param direction the direction of the relationship, seen from {@code this} node * @param types the type of the relationship to create or the types to match * @return an ongoing relationship definition, that can be used to specify details of * the relationship * @since 2023.5.0 */ default T relationshipWith(Node other, Relationship.Direction direction, String... types) { return switch (direction) { case LTR -> this.relationshipTo(other, types); case RTL -> this.relationshipFrom(other, types); case UNI -> this.relationshipBetween(other, types); }; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExposesReturning.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Arrays; import java.util.Collection; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import static org.apiguardian.api.API.Status.STABLE; /** * Return part of a statement. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface ExposesReturning { /** * Creates the {@code RETURN} clause. All variables passed via {@code variables} must * be valid {@link SymbolicName symbolic names}. * {@link Expression#property(String...)} must be used to return single properties. * @param variables the named things to return * @return a build step with a defined list of things to return. */ @CheckReturnValue default StatementBuilder.OngoingReadingAndReturn returning(String... variables) { return returning(Expressions.createSymbolicNames(variables)); } /** * Creates the {@code RETURN} clause. * @param variables the named things to return * @return a build step with a defined list of things to return. */ @CheckReturnValue default StatementBuilder.OngoingReadingAndReturn returning(Named... variables) { return returning(Expressions.createSymbolicNames(variables)); } /** * Create a match that returns one or more expressions. * @param expressions the expressions to be returned. Must not be null and be at least * one expression. * @return a match that can be build now */ @CheckReturnValue default StatementBuilder.OngoingReadingAndReturn returning(Expression... expressions) { return returning((expressions != null) ? Arrays.asList(expressions) : null); } /** * Create a match that returns one or more expressions. * @param expressions the expressions to be returned. Must not be null and be at least * one expression. * @return a match that can be build now */ @CheckReturnValue StatementBuilder.OngoingReadingAndReturn returning(Collection expressions); /** * Creates a {@code RETURN} clause containing the {@code DISTINCT} keyword. All * variables passed via {@code variables} must be valid {@link SymbolicName symbolic * names}. {@link Expression#property(String...)} must be used to return single * properties. * @param variables the variables to return * @return a build step with a defined list of things to return. */ @CheckReturnValue default StatementBuilder.OngoingReadingAndReturn returningDistinct(String... variables) { return returningDistinct(Expressions.createSymbolicNames(variables)); } /** * Creates a {@code RETURN} clause containing the {@code DISTINCT} keyword. * @param variables the named things to return * @return a build step with a defined list of things to return. */ @CheckReturnValue default StatementBuilder.OngoingReadingAndReturn returningDistinct(Named... variables) { return returningDistinct(Expressions.createSymbolicNames(variables)); } /** * Creates a {@code RETURN} clause returning the distinct set of one or more * expressions. * @param expressions the expressions to be returned. Must not be null and be at least * one expression. * @return a match that can be build now */ @CheckReturnValue default StatementBuilder.OngoingReadingAndReturn returningDistinct(Expression... expressions) { return returningDistinct((expressions != null) ? Arrays.asList(expressions) : null); } /** * Creates a {@code RETURN} clause returning the distinct set of one or more * expressions. * @param expressions the expressions to be returned. Must not be null and be at least * one expression. * @return a match that can be build now */ @CheckReturnValue StatementBuilder.OngoingReadingAndReturn returningDistinct(Collection expressions); /** * Creates a {@code RETURN} clause from a raw Cypher expression created via * {@link Cypher#raw(String, Object...)}. The expression maybe aliased, but it must * resolve to a raw element * @param rawExpression must be a plain raw or an aliased raw expression. To * eventually render as valid Cypher, it must contain the {@code RETURN} keyword. * @return a match that can be build now * @since 2021.2.1 */ @CheckReturnValue StatementBuilder.OngoingReadingAndReturn returningRaw(Expression rawExpression); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExposesSubqueryCall.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Arrays; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.StatementBuilder.BuildableStatement; import org.neo4j.cypherdsl.core.StatementBuilder.OngoingReadingWithoutWhere; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import static org.apiguardian.api.API.Status.STABLE; /** * This exposes a call method taking in a statement that represents a valid, correlated * subquery. * * @author Michael J. Simons * @neo4j.version 4.0.0 * @since 2020.1.2 */ @API(status = STABLE, since = "2020.1.2") @Neo4jVersion(minimum = "4.0.0") public interface ExposesSubqueryCall { /** * The {@link Statement subquery} parameter must be a valid subquery. *

    *
  • can be a unit statement
  • *
  • cannot refer to variables from the enclosing query
  • *
  • cannot return variables with the same names as variables in the enclosing * query
  • *
  • All variables that are returned from a subquery are afterwards available in the * enclosing query
  • *
* @param statement the statement representing the subquery. * @return an ongoing reading */ @CheckReturnValue default BuildableSubquery call(Statement statement) { if (statement instanceof MultiPartQuery multiPartQuery) { var parts = multiPartQuery.getParts(); if (!parts.isEmpty() && parts.get(0).isImporting()) { return call(multiPartQuery.stripFirst(), parts.get(0).getImports()); } } return call(statement, new IdentifiableElement[0]); } /** * The {@link Statement subquery} parameter must be a valid subquery. *
    *
  • must end with a RETURN clause
  • *
  • cannot refer to variables from the enclosing query
  • *
  • cannot return variables with the same names as variables in the enclosing * query
  • *
  • All variables that are returned from a subquery are afterwards available in the * enclosing query
  • *
* @param statement the statement representing the subquery. * @param imports additional things that should be imported into the subquery. * @return an ongoing reading * @since 2021.3.0 */ @CheckReturnValue default BuildableSubquery call(Statement statement, String... imports) { return call(statement, Arrays.stream(imports).map(SymbolicName::of).toArray(SymbolicName[]::new)); } /** * The {@link Statement subquery} parameter must be a valid sub-query. *
    *
  • must end with a RETURN clause
  • *
  • cannot refer to variables from the enclosing query
  • *
  • cannot return variables with the same names as variables in the enclosing * query
  • *
  • All variables that are returned from a subquery are afterwards available in the * enclosing query
  • *
* @param statement the statement representing the sub-query. * @param imports additional things that should be imported into the sub-query. * {@link AliasedExpression aliased expressions} will automatically be importe twice * (once as {@code WITH a}, then {code WITH a AS alias}). * @return an ongoing reading * @since 2021.3.0 */ @CheckReturnValue BuildableSubquery call(Statement statement, IdentifiableElement... imports); /** * Starts building a new sub-query from a {@code CALL ... IN TRANSACTIONS} clause * @param statement the sub-query to be called in transactions * @return ongoing sub-query definition */ @CheckReturnValue default BuildableSubquery callInTransactions(Statement statement) { return callInTransactions(statement, (Integer) null); } /** * Starts building a new sub-query from a raw Cypher string that might also have * arguments as supported through {@link Cypher#raw(String, Object...)}. Use this * method as your own risk and be aware that no checks are done on the Cypher. * @param rawCypher the raw Cypher statement to call * @param args optional args that replace placeholders in the {@code rawCypher} * @return ongoing sub-query definition * @since 2023.10.0 */ @CheckReturnValue BuildableSubquery callRawCypher(String rawCypher, Object... args); /** * Creates a subquery running in its own transactions. The statement won't be checked * for a {@literal RETURN} or {@literal YIELD} clause to accommodate for * {@literal CREATE} or other clauses that are void and work in a subquery if the * outer query is void, too. * @param statement the statement representing the subquery. * @param rows the number of rows per transactional batch, leave {@literal NULL} for * the default value * @return an ongoing reading, that is also buildable for outer queries that are void. * @since 2022.3.0 */ @CheckReturnValue default BuildableSubquery callInTransactions(Statement statement, Integer rows) { if (statement instanceof MultiPartQuery multiPartQuery) { var parts = multiPartQuery.getParts(); if (!parts.isEmpty() && parts.get(0).isImporting()) { return callInTransactions(multiPartQuery.stripFirst(), rows, parts.get(0).getImports()); } } return callInTransactions(statement, rows, new IdentifiableElement[0]); } /** * Creates a subquery running in its own transactions. The statement won't be checked * for a {@literal RETURN} or {@literal YIELD} clause to accommodate for * {@literal CREATE} or other clauses that are void and work in a subquery if the * outer query is void, too. * @param statement the statement representing the subquery. * @param imports additional things that should be imported into the subquery. * @return an ongoing reading, that is also buildable for outer queries that are void. * @since 2022.3.0 */ @CheckReturnValue default BuildableSubquery callInTransactions(Statement statement, String... imports) { return callInTransactions(statement, null, Arrays.stream(imports).map(SymbolicName::of).toArray(SymbolicName[]::new)); } /** * Creates a subquery running in its own transactions. The statement won't be checked * for a {@literal RETURN} or {@literal YIELD} clause to accommodate for * {@literal CREATE} or other clauses that are void and work in a subquery if the * outer query is void, too. * @param statement the statement representing the subquery. * @param rows the number of rows per transactional batch, leave {@literal NULL} for * the default value * @param imports additional things that should be imported into the subquery. * @return an ongoing reading, that is also buildable for outer queries that are void. * @since 2022.3.0 */ @CheckReturnValue default BuildableSubquery callInTransactions(Statement statement, Integer rows, String... imports) { return callInTransactions(statement, rows, Arrays.stream(imports).map(SymbolicName::of).toArray(SymbolicName[]::new)); } /** * Creates a subquery running in its own transactions. The statement won't be checked * for a {@literal RETURN} or {@literal YIELD} clause to accommodate for * {@literal CREATE} or other clauses that are void and work in a subquery if the * outer query is void, too. * @param statement the statement representing the subquery. * @param imports additional things that should be imported into the subquery. * {@link AliasedExpression aliased expressions} will automatically imported twice * (once as WITH a, then WITH a AS alias). * @return an ongoing reading, that is also buildable for outer queries that are void. * @since 2022.3.0 */ default BuildableSubquery callInTransactions(Statement statement, IdentifiableElement... imports) { return callInTransactions(statement, null, imports); } /** * Creates a subquery running in its own transactions. The statement won't be checked * for a {@literal RETURN} or {@literal YIELD} clause to accommodate for * {@literal CREATE} or other clauses that are void and work in a subquery if the * outer query is void, too. * @param statement the statement representing the subquery. * @param rows the number of rows per transactional batch, leave {@literal NULL} for * the default value * @param imports additional things that should be imported into the subquery. * {@link AliasedExpression aliased expressions} will automatically imported twice * (once as WITH a, then WITH a AS alias). * @return an ongoing reading, that is also buildable for outer queries that are void. * @since 2022.3.0 */ @CheckReturnValue BuildableSubquery callInTransactions(Statement statement, Integer rows, IdentifiableElement... imports); /** * Subqueries can be valid without any further return statement (i.e when the subquery * is a {@literal void} ({@literal unit}) one, meaning it doesn't yield or return its * results. The Cypher-DSL doesn't do any checking here, so please be careful when * using that construct. * * @since 2022.3.0 */ interface BuildableSubquery extends OngoingReadingWithoutWhere, BuildableStatement { } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExposesUnwind.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import static org.apiguardian.api.API.Status.STABLE; /** * A step exposing a {@link #unwind(Expression...)},{@link #unwind(Expression)}, * {@link #unwind(String)} and method. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface ExposesUnwind { /** * Starts building a new {@code UNWIND} clause. * @param expressions the things to unwind. * @return an ongoing definition of an unwind. */ @CheckReturnValue default StatementBuilder.OngoingUnwind unwind(Expression... expressions) { return unwind(Cypher.listOf(expressions)); } /** * Starts building a new {@code UNWIND} clause. * @param variable the thing to unwind. * @return an ongoing definition of an unwind. */ @CheckReturnValue default StatementBuilder.OngoingUnwind unwind(String variable) { return unwind(Cypher.name(variable)); } /** * Starts building a new {@code UNWIND} clause. * @param expression the things to unwind. * @return an ongoing definition of an unwind. */ StatementBuilder.OngoingUnwind unwind(Expression expression); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExposesWhere.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import org.neo4j.cypherdsl.core.internal.RelationshipPatternCondition; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * A step exposing a several {@code where} methods that are provide entry points of adding * conditions. * * @param the type of the owner exposing the {@literal WHERE} clause * @author Michael J. Simons * @since 2020.0.1 */ @API(status = STABLE, since = "2020.0.1") public interface ExposesWhere { /** * Adds a where clause to this fragement. * @param condition the new condition, must not be {@literal null} * @return a match or call restricted by a where clause with no return items yet. */ @CheckReturnValue T where(Condition condition); /** * Adds a where clause based on a path pattern to this match. See Using * path patterns in WHERE. * @param pathPattern the path pattern to add to the where clause. This path pattern * must not be {@literal null} and must not introduce new variables not available in * the match. * @return a match or a call restricted by a where clause with no return items yet. * @since 1.0.1 */ @CheckReturnValue default T where(RelationshipPattern pathPattern) { Assertions.notNull(pathPattern, "The path pattern must not be null."); return this.where(RelationshipPatternCondition.of(pathPattern)); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExposesWith.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Arrays; import java.util.Collection; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.StatementBuilder.OrderableOngoingReadingAndWithWithoutWhere; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import static org.apiguardian.api.API.Status.STABLE; /** * A step that exposes the {@code WITH} clause. This interface used to be part of the * {@link StatementBuilder} and moved out of it to unify the {@literal WITH} clause taking * in {@link IdentifiableElement identifable elements}. * * @author Michael J. Simons * @since 2023.0.0 */ @API(status = STABLE, since = "2023.0.0") public interface ExposesWith { /** * Starts a with clause by passing variables to it. * @param variables the variables to pass on to the next part * @return a match that can be build now */ @CheckReturnValue default OrderableOngoingReadingAndWithWithoutWhere with(String... variables) { return with(Expressions.createSymbolicNames(variables)); } /** * Create a match that returns one or more identifiable elements. * @param elements the variables to pass on to the next part * @return a match that can be build now */ @CheckReturnValue default OrderableOngoingReadingAndWithWithoutWhere with(IdentifiableElement... elements) { return with(Arrays.asList(elements)); } /** * Create a match that returns one or more identifiable elements. * @param elements the expressions to be returned. Must not be null and be at least * one expression. * @return a match that can be build now */ @CheckReturnValue OrderableOngoingReadingAndWithWithoutWhere with(Collection elements); /** * Create a match that returns the distinct set of one or more identifiable elements. * @param variables the variables to pass on to the next part * @return a match that can be build now * @see #withDistinct(IdentifiableElement...) */ @CheckReturnValue default OrderableOngoingReadingAndWithWithoutWhere withDistinct(String... variables) { return withDistinct(Expressions.createSymbolicNames(variables)); } /** * Create a match that returns the distinct set of one or more identifiable elements. * @param elements the variables to pass on to the next part * @return a match that can be build now * @see #withDistinct(IdentifiableElement...) */ @CheckReturnValue default OrderableOngoingReadingAndWithWithoutWhere withDistinct(IdentifiableElement... elements) { return withDistinct(Arrays.asList(elements)); } /** * Create a match that returns the distinct set of one or more expressions. * @param expressions the expressions to be returned. Must not be null and be at least * one expression. * @return a match that can be build now */ @CheckReturnValue OrderableOngoingReadingAndWithWithoutWhere withDistinct(Collection expressions); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Expression.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * An expression can be used in many places, i.e. in return statements, pattern elements * etc. * * @author Michael J. Simons * @author Aakash Sorathiya * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface Expression extends Visitable, PropertyAccessor { /** * Creates a condition that checks whether this {@code expression} includes all * elements of {@code rhs}. * @param rhs the other collection to compare to, must evaluate into a list during * runtime. * @return a new condition * @since 2022.7.0 */ default Condition includesAll(Expression rhs) { return Conditions.includesAll(this, rhs); } /** * Creates a condition that checks whether this {@code expression} includes any * element of {@code rhs}. * @param rhs the other collection to compare to, must evaluate into a list during * runtime. * @return a new condition * @since 2022.7.0 */ default Condition includesAny(Expression rhs) { return Conditions.includesAny(this, rhs); } /** * Creates an expression with an alias. This expression does not track which or how * many aliases have been created. * @param alias the alias to use * @return an aliased expression. */ default AliasedExpression as(String alias) { Assertions.hasText(alias, "The alias may not be null or empty."); return new AliasedExpression(this, alias); } /** * This creates a {@literal size(e)} expression from this expression. The Cypher * output will semantically only be valid when this refers to a list (see size(list)) * or when the expression is a string (see size() * applied to string). *

* Any other expression will produce Cypher that is either deprecated in Neo4j ≥ * 4.4 or not supported at all. * @return the size of this expression (Either the number of items in a list or the * number of characters in a string expression). * @since 2022.1.0 */ default Expression size() { return Functions.size(this); } /** * Takes the {@link #size()} expresssions and compares it for equality with the * parameter {@code expectedSize}. The same restrictions as with {@link #size()} * apply. * @param expectedSize the expected size * @return a condition * @since 2022.1.0 * @see #size() */ default Condition hasSize(Expression expectedSize) { return Functions.size(this).isEqualTo(expectedSize); } /** * Reuse an existing symbolic name to alias this expression. * @param alias a symbolic name * @return an aliased expression. * @since 2021.0.2 */ default AliasedExpression as(SymbolicName alias) { Assertions.notNull(alias, "The alias may not be null."); return as(alias.getValue()); } /** * Transform this expression into a condition. * @return this expression as a condition. Will return the same instance if it is * already a condition. * @since 2021.2.2 */ default Condition asCondition() { return (this instanceof Condition condition) ? condition : new ExpressionCondition(this); } /** * Creates a {@code lhs = rhs} condition. * @param rhs the right hand side of the condition * @return a new condition */ default Condition isEqualTo(Expression rhs) { return Conditions.isEqualTo(this, rhs); } /** * An alias for {@link #isEqualTo(Expression)}. * @param rhs the right hand side of the condition * @return a new condition */ default Condition eq(Expression rhs) { return isEqualTo(rhs); } /** * Creates a {@code lhs <> rhs} condition. * @param rhs the right hand side of the condition * @return a new condition */ default Condition isNotEqualTo(Expression rhs) { return Conditions.isNotEqualTo(this, rhs); } /** * An alias for {@link #isNotEqualTo(Expression)}. * @param rhs the right hand side of the condition * @return a new condition */ default Condition ne(Expression rhs) { return isNotEqualTo(rhs); } /** * Creates a {@code lhs < rhs} condition. * @param rhs the right hand side of the condition * @return a new condition */ default Condition lt(Expression rhs) { return Conditions.lt(this, rhs); } /** * Creates a {@code lhs <= rhs} condition. * @param rhs the right hand side of the condition * @return a new condition */ default Condition lte(Expression rhs) { return Conditions.lte(this, rhs); } /** * Creates a {@code lhs > rhs} condition. * @param rhs the right hand side of the condition * @return a new condition */ default Condition gt(Expression rhs) { return Conditions.gt(this, rhs); } /** * Creates a {@code lhs >= rhs} condition. * @param rhs the right hand side of the condition * @return a new condition */ default Condition gte(Expression rhs) { return Conditions.gte(this, rhs); } /** * Creates a condition that checks whether this {@code expression} is {@literal true}. * @return a new condition */ default Condition isTrue() { return Conditions.isEqualTo(this, Cypher.literalTrue()); } /** * Creates a condition that checks whether this {@code expression} is * {@literal false}. * @return a new condition */ default Condition isFalse() { return Conditions.isEqualTo(this, Cypher.literalFalse()); } /** * Creates a condition that checks whether this {@code expression} matches that * {@code expression}. * @param expression the expression to match against. Must evaluate into a string * during runtime. * @return a new condition. */ default Condition matches(Expression expression) { return Conditions.matches(this, expression); } /** * Creates a condition that checks whether this {@code expression} matches the given * {@code pattern}. * @param pattern the pattern to match * @return a new condition. */ default Condition matches(String pattern) { return Conditions.matches(this, Cypher.literalOf(pattern)); } /** * Creates a condition that checks whether this {@code expression} starts with that * {@code expression}. * @param expression the expression to match against. Must evaluate into a string * during runtime. * @return a new condition. */ default Condition startsWith(Expression expression) { return Conditions.startsWith(this, expression); } /** * Creates a condition that checks whether this {@code expression} contains that * {@code expression}. * @param expression the expression to match against. Must evaluate into a string * during runtime. * @return a new condition. */ default Condition contains(Expression expression) { return Conditions.contains(this, expression); } /** * Creates a condition that checks whether this {@code expression} ends with that * {@code expression}. * @param expression the expression to match against. Must evaluate into a string * during runtime. * @return a new condition. */ default Condition endsWith(Expression expression) { return Conditions.endsWith(this, expression); } /** * Creates an expression concatenating two string or list expressions. * @param expression the expression to concat to this expression. * @return a new expression. */ default Operation concat(Expression expression) { return Operations.concat(this, expression); } /** * Creates a {@code +} operation of this (the augend) and the {@code addend}. * @param addend the addend * @return a new operation. * @since 1.0.1 */ default Operation add(Expression addend) { return Operations.add(this, addend); } /** * Creates a {@code -} operation of this (the minuend) and the {@code subtrahend}. * @param subtrahend the subtrahend * @return a new operation. * @since 1.0.1 */ default Operation subtract(Expression subtrahend) { return Operations.subtract(this, subtrahend); } /** * Creates a {@code *} operation of this (the multiplier) and the * {@code multiplicand}. * @param multiplicand the multiplicand * @return a new operation. * @since 1.0.1 */ default Operation multiply(Expression multiplicand) { return Operations.multiply(this, multiplicand); } /** * Creates a {@code /} operation of this (the divisor) and the {@code dividend}. * @param dividend the dividend * @return a new operation. * @since 1.0.1 */ default Operation divide(Expression dividend) { return Operations.divide(this, dividend); } /** * Returns the remainder of this value and the {@code dividend}. * @param dividend the dividend * @return a new operation. */ default Operation remainder(Expression dividend) { return Operations.remainder(this, dividend); } /** * Returns the power of n of this value. * @param n power to raise this {@code Expression} to. * @return a new operation. */ default Operation pow(Expression n) { return Operations.pow(this, n); } /** * Creates a {@code IS NULL} operation for this {@code expression}. The expression * does not track the condition created here. * @return a condition based on this expression that evaluates to true when this * expression is null. */ default Condition isNull() { return Conditions.isNull(this); } /** * Creates a {@code IS NOT NULL} operation for this {@code expression}. The expression * does not track the condition created here. * @return a condition based on this expression that evaluates to true when this * expression is not null. */ default Condition isNotNull() { return Conditions.isNotNull(this); } /** * Creates a {@code IN} operation for this expression and that {@code expression}. The * expression does not track the condition created here. * @param haystack the expression to search for this expression * @return a new condition. */ default Condition in(Expression haystack) { return Comparison.create(this, Operator.IN, haystack); } /** * Creates a condition that evaluates to true if this expression is empty. * @return a new condition. */ default Condition isEmpty() { return Functions.size(this).isEqualTo(Cypher.literalOf(0L)); } /** * The property does not track the sort items created here. * @return a sort item for this property in descending order */ default SortItem descending() { return SortItem.create(this, SortItem.Direction.DESC); } /** * The property does not track the sort items created here. * @return a sort item for this property in ascending order */ default SortItem ascending() { return SortItem.create(this, SortItem.Direction.ASC); } /** * Creates a new sort item with the given direction. * @param direction the direction to sort * @return a new sort item. * @since 2021.4.1 */ default SortItem sorted(SortItem.Direction direction) { return SortItem.create(this, direction); } @Override default Property property(String... names) { return InternalPropertyImpl.create(this, names); } /** * Creates a new {@link Property} associated with this property container. This * property can be used as a lookup in other expressions. It does not add a value to * the property. *

* The new {@link Property} object is a dynamic lookup, based on the * {@code expression} passed to this method. The expression can be example another * property, a function result or a Cypher parameter. A property defined in such a way * will render as {@code p[expression]}. *

* Note: The property container does not track property creation and there is no * possibility to enumerate all properties that have been created for this property * container. * @param lookup the expression that is evaluated to lookup this property. * @return a new {@link Property} associated with this named container * @since 2024.1.0 */ @Override default Property property(Expression lookup) { return InternalPropertyImpl.create(this, lookup); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExpressionCondition.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; /** * A condition that uses its bound expression. * * @author Andreas Berger * @since 2021.0.0 */ @API(status = INTERNAL, since = "2021.0.0") class ExpressionCondition implements Condition { private final Expression value; ExpressionCondition(Expression value) { this.value = value; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.value.accept(visitor); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ExpressionList.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import org.neo4j.cypherdsl.core.ast.TypedSubtree; import org.neo4j.cypherdsl.core.ast.Visitable; /** * Represents a list of expressions. When visited, the expressions are treated as named * expression if they have declared a symbolic name as variable or as unnamed expression * when nameless. *

* Not to be mixed up with the actual {@link ListExpression}, which itself is an * expression. * * @author Michael J. Simons * @since 1.0 */ class ExpressionList extends TypedSubtree { ExpressionList(List returnItems) { super(returnItems); } ExpressionList(Expression... children) { super(children); } @Override protected Visitable prepareVisit(Expression child) { return Expressions.nameOrExpression(child); } boolean isEmpty() { return super.children.isEmpty(); } @Override protected List getChildren() { return super.getChildren(); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Expressions.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Arrays; import java.util.List; import org.neo4j.cypherdsl.core.Statement.UnionQuery; /** * Utility methods for dealing with expressions. * * @author Michael J. Simons * @since 1.0 */ final class Expressions { private Expressions() { } /** * Creates a {@literal COUNT} sub-query expressions from at least one pattern. * @param requiredPattern one pattern is required * @param patternElement optional pattern * @return the immutable {@link CountExpression} * @since 2023.0.0 */ static CountExpression count(PatternElement requiredPattern, PatternElement... patternElement) { return CountExpression.count(Pattern.of(requiredPattern, patternElement)); } /** * Creates a {@literal COUNT} with an inner {@literal UNION} sub-query. * @param union the union that will be the source of the {@literal COUNT} sub-query * @return the immutable {@link CountExpression} * @since 2023.0.0 */ static CountExpression count(UnionQuery union) { return CountExpression.count(union); } /** * Creates a {@literal COUNT} from a full statement, including its filters and * conditions. The statement may or may not have a {@literal RETURN} clause. It must * however not contain any updates. While it would render syntactically correct * Cypher, Neo4j does not support updates inside counting sub-queries. * @param statement the statement to be passed to {@code count{}} * @param imports optional imports to be used in the statement (will be imported with * {@literal WITH}) * @return a counting sub-query. * @since 2023.1.0 */ static CountExpression count(Statement statement, IdentifiableElement... imports) { return CountExpression.count(statement, imports); } /** * Creates a {@literal COUNT} expression based on a list of pattern. * @param pattern the list of patterns that shall be counted * @param where an optional where-clause * @return a count expression. * @since 2023.9.0 */ static CountExpression count(List pattern, Where where) { return CountExpression.count(pattern, where); } /** * Start building a new sub-query expression by importing variables into the scope * with a {@literal WITH} clause. * @param identifiableElements the identifiable elements to import * @return a builder for creating the concrete sub-query * @since 2023.0.0 */ static SubqueryExpressionBuilder with(IdentifiableElement... identifiableElements) { var returnItems = new ExpressionList( Arrays.stream(identifiableElements).map(IdentifiableElement::asExpression).toList()); var with = new With(false, returnItems, null, null, null, null); return new SubqueryExpressionBuilder() { @Override public CountExpression count(PatternElement requiredPattern, PatternElement... patternElement) { return CountExpression.count(with, Pattern.of(requiredPattern, patternElement)); } @Override public CountExpression count(UnionQuery union) { return CountExpression.count(with, union); } @Override public CollectExpression collect(Statement statement) { return CollectExpression.collect(with, statement); } }; } /** * Creates a {@literal COLLECT} subquery from a statement, including its filters and * conditions. The statement must return exactly one column. It must however not * contain any updates. While it would render syntactically correct Cypher, Neo4j does * not support updates inside counting sub-queries. * @param statement the statement to be passed to {@code COLLECT{}} * @return a collecting sub-query. * @since 2023.8.0 */ static Expression collect(Statement statement) { if (!statement.doesReturnOrYield()) { throw new IllegalArgumentException( "The final RETURN clause in a subquery used with COLLECT is mandatory and the RETURN clause must return exactly one column."); } return CollectExpression.collect(statement); } /** * Returns the name of the expression if the expression is named or the expression * itself. * @param expression possibly named with a non-empty symbolic name. * @param the type being returned * @return the name of the expression if the expression is named or the expression * itself */ static Expression nameOrExpression(T expression) { if (expression instanceof Named named) { return named.getSymbolicName().map(Expression.class::cast).orElse(expression); } else { return expression; } } static SymbolicName[] createSymbolicNames(String[] variables) { return Arrays.stream(variables).map(SymbolicName::of).toArray(SymbolicName[]::new); } static SymbolicName[] createSymbolicNames(Named[] variables) { return Arrays.stream(variables).map(Named::getRequiredSymbolicName).toArray(SymbolicName[]::new); } static String format(Expression expression) { if (expression instanceof Named named) { return named.getRequiredSymbolicName().getValue(); } else if (expression instanceof AliasedExpression aliasedExpression) { return aliasedExpression.getAlias(); } else if (expression instanceof SymbolicName symbolicName) { return symbolicName.getValue(); } else if (expression instanceof Property) { StringBuilder ref = new StringBuilder(); expression.accept(segment -> { if (segment instanceof SymbolicName symbolicName) { if (!ref.isEmpty()) { ref.append("."); } ref.append(symbolicName.getValue()); } }); return ref.toString(); } throw new IllegalArgumentException("Cannot format expression " + expression.toString()); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Finish.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.STABLE; /** * Representation of the {@code FINISH} clause. * * @author Gerrit Meier * @author Michael J. Simons */ @API(status = STABLE, since = "2024.4.0") public final class Finish implements Clause { private static final Expression FINISH_EXPRESSION = new RawLiteral.RawElement("FINISH"); private Finish() { } public static Finish create() { return new Finish(); } @Override public void accept(Visitor visitor) { visitor.enter(this); FINISH_EXPRESSION.accept(visitor); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Foreach.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Collection; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.STABLE; /** * Represents the {@literal FOREACH} clause and is currently only producible via the * Cypher-Parser. * * @author Michael J. Simons * @since 2021.3.0 */ @API(status = STABLE, since = "2021.3.0") public final class Foreach extends AbstractClause implements UpdatingClause { private final SymbolicName variable; private final Expression list; private final Collection updatingClauses; Foreach(SymbolicName v, Expression list, Collection updatingClauses) { this.variable = v; this.list = list; this.updatingClauses = updatingClauses; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.variable.accept(visitor); Operator.IN.accept(visitor); this.list.accept(visitor); Operator.PIPE.accept(visitor); this.updatingClauses.forEach(clause -> clause.accept(visitor)); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ForeignAdapter.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * Represents an adapter that allows to turn foreign expressions into Cypher-DSL * {@link Expression expressions}. * * @param the type of the foreign expression. * @author Michael J. Simons * @since 2021.1.0 */ @API(status = STABLE, since = "2021.1.0") public interface ForeignAdapter { /** * Adapts a foreign expression into a Cypher-DSL {@link Condition}. The memoized * expression should be something that can be evaluated into something boolean. * @return a condition * @throws IllegalArgumentException if the expression doesn't resolve into something * boolean */ Condition asCondition(); /** * Adapts a foreign expression into a Cypher-DSL {@link Expression}. * @return a native expression */ Expression asExpression(); /** * Adapts a foreign expression into a Cypher-DSL {@link Node}, that allows to address * it further down in queries. * @return a node * @throws IllegalArgumentException if the expression doesn't describe something that * can be used to describe a node */ Node asNode(); /** * Adapts a foreign expression into a Cypher-DSL {@link Relationship}, that allows to * address it further down in queries. * @return a node * @throws IllegalArgumentException if the expression doesn't describe something that * can be used to describe a node */ Relationship asRelationship(); /** * Adapts a foreign expression into a Cypher-DSL {@link SymbolicName}. The memoized * expression should ideally be something that is named or resolves to an alias. * @return a symbolic name * @throws IllegalArgumentException if a name cannot be derived from the expression. */ SymbolicName asName(); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ForeignAdapterFactory.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.INTERNAL; /** * This factory is meant to decouple the instantiating respectively concrete usage of * classes on the provided path as much as possible, to avoid eager loading by some JVM * and in turn, a class not found exception. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = INTERNAL, since = "2021.1.0") final class ForeignAdapterFactory { private final Map, Constructor>> adapterCache = new HashMap<>(); private static IllegalArgumentException newCannotAdaptException(Class k, Exception cause) { var msg = "Cannot adapt expressions of type " + k.getName() + " to Cypher-DSL expressions."; return new IllegalArgumentException(msg, cause); } private static Set getInterfaces(Class type) { if (type == null || type == Object.class) { return Set.of(); } return Stream .concat(getInterfaces(type.getSuperclass()).stream(), Arrays.stream(type.getInterfaces()) .flatMap(i -> Stream.concat(Stream.of(i.getName()), getInterfaces(i).stream()))) .collect(Collectors.toSet()); } @SuppressWarnings("unchecked") // We do check the type of expression ForeignAdapter getAdapterFor(FE expression) { if (expression == null) { throw new IllegalArgumentException("Cannot adapt literal NULL expressions."); } var constructor = this.adapterCache.computeIfAbsent(expression.getClass(), k -> { var interfaces = getInterfaces(k); String adapterName; if (interfaces.stream().anyMatch("com.querydsl.core.types.Expression"::equals)) { adapterName = "org.neo4j.cypherdsl.core.QueryDSLAdapter"; } else { throw newCannotAdaptException(k, null); } try { return (Constructor>) Class.forName(adapterName).getDeclaredConstructors()[0]; } catch (ClassNotFoundException ex) { throw newCannotAdaptException(k, ex); } }); try { return (ForeignAdapter) constructor.newInstance(expression); } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) { throw newCannotAdaptException(expression.getClass(), ex); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/FunctionInvocation.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.text.MessageFormat; import java.util.Arrays; import java.util.List; import java.util.function.Predicate; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.TypedSubtree; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * See FunctionInvocation. * * @author Gerrit Meier * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class FunctionInvocation implements Expression { private static final MessageFormat MESSAGE_FMT_EXP_REQUIRED = new MessageFormat( Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_FOR_FUNCTION_REQUIRED)); private static final MessageFormat MESSAGE_FMT_PATTERN_REQUIRED = new MessageFormat( Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_PATTERN_FOR_FUNCTION_REQUIRED)); private static final MessageFormat MESSAGE_FMT_ARG_REQUIRED = new MessageFormat( Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_AT_LEAST_ONE_ARG_REQUIRED)); private final String functionName; private final Visitable arguments; private FunctionInvocation(String functionName, Expression... arguments) { this.functionName = functionName; this.arguments = new ExpressionList(arguments); } private FunctionInvocation(String functionName, Visitable arguments) { this.functionName = functionName; this.arguments = arguments; } /** * Creates a {@link FunctionInvocation} based on a simple definition without any * arguments. * @param definition the definition of a function * @return the invocation (a valid expression) * @since 2021.2.3 */ public static FunctionInvocation create(FunctionDefinition definition) { return new FunctionInvocation(definition.getImplementationName()); } /** * Creates a {@link FunctionInvocation} based on a simple definition with arguments. * @param definition the definition of a function * @param expressions the arguments to the function * @return the invocation (a valid expression) * @since 2021.2.3 */ public static FunctionInvocation create(FunctionDefinition definition, Expression... expressions) { String message = MESSAGE_FMT_EXP_REQUIRED.format(new Object[] { definition.getImplementationName() }); Assertions.notEmpty(expressions, message); Assertions.notNull(expressions[0], message); return new FunctionInvocation(definition.getImplementationName(), expressions); } /** * Creates a {@link FunctionInvocation} based on a simple definition with arguments * and adds the {@code distinct} operator to it. This is only supported with * {@link FunctionDefinition#isAggregate()} returning {@literal true}. * @param definition the definition of a function * @param expressions the arguments to the function * @return the invocation (a valid expression) * @since 2021.2.3 */ public static FunctionInvocation createDistinct(FunctionDefinition definition, Expression... expressions) { Assertions.isTrue(definition.isAggregate(), Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_CORRECT_USAGE_OF_DISTINCT)); String message = MESSAGE_FMT_EXP_REQUIRED.format(new Object[] { definition.getImplementationName() }); Assertions.notEmpty(expressions, message); Assertions.notNull(expressions[0], message); Expression[] newExpressions = new Expression[expressions.length]; newExpressions[0] = new DistinctExpression(expressions[0]); System.arraycopy(expressions, 1, newExpressions, 1, expressions.length - 1); return new FunctionInvocation(definition.getImplementationName(), newExpressions); } /** * Creates a new function invocation for a pattern element. * @param definition the definition of the function * @param pattern the argument to the function * @return a function invocation * @since 2021.2.3 */ public static FunctionInvocation create(FunctionDefinition definition, PatternElement pattern) { String message = MESSAGE_FMT_PATTERN_REQUIRED.format(new Object[] { definition.getImplementationName() }); Assertions.notNull(pattern, message); Predicate isShortestPath = d -> BuiltInFunctions.Scalars.SHORTEST_PATH .getImplementationName() .equals(d.getImplementationName()) || "allShortestPaths".equals(d.getImplementationName()); return new FunctionInvocation(definition.getImplementationName(), isShortestPath.test(definition) ? Pattern.of(List.of(pattern)) : new PatternExpressionImpl(pattern)); } static FunctionInvocation create(FunctionDefinition definition, TypedSubtree arguments) { Assertions.notNull(arguments, MESSAGE_FMT_ARG_REQUIRED.format(new Object[] { definition.getImplementationName() })); return new FunctionInvocation(definition.getImplementationName(), arguments); } /** * {@return the name of this function} */ @API(status = INTERNAL) public String getFunctionName() { return this.functionName; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.arguments.accept(visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } /** * Defines metadata for a function. */ @API(status = STABLE, since = "2020.1.0") public interface FunctionDefinition { /** * {@return the Cypher implementation name} */ String getImplementationName(); /** * {@return true if this is an aggregating function} */ default boolean isAggregate() { return Arrays.stream(BuiltInFunctions.Aggregates.values()) .map(BuiltInFunctions.Aggregates::getImplementationName) .anyMatch(v -> v.equalsIgnoreCase(getImplementationName())); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Functions.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.TimeZone; import org.neo4j.cypherdsl.core.BuiltInFunctions.Aggregates; import org.neo4j.cypherdsl.core.BuiltInFunctions.Lists; import org.neo4j.cypherdsl.core.BuiltInFunctions.Predicates; import org.neo4j.cypherdsl.core.BuiltInFunctions.Scalars; import org.neo4j.cypherdsl.core.BuiltInFunctions.Spatials; import org.neo4j.cypherdsl.core.BuiltInFunctions.Strings; import org.neo4j.cypherdsl.core.utils.Assertions; /** * Factory methods for creating instances of {@link FunctionInvocation functions}. * * @author Michael J. Simons * @author Gerrit Meier * @author Romain Rossi * @since 1.0 */ final class Functions { private Functions() { } /** * Creates a function invocation for {@code id{}}. See id. * @param node the node for which the internal id should be retrieved * @return a function call for {@code id()} on a node. * @deprecated see {@link #elementId(Node)} for a replacement. Neo4j the database will * remove support for {@code id(n)} at some point. */ @Deprecated(since = "2023.3.0") @SuppressWarnings({ "squid:S1133" }) // Yes, I promise, this will be removed at some // point, but not yet. static FunctionInvocation id(Node node) { Assertions.notNull(node, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_NODE_REQUIRED)); return FunctionInvocation.create(Scalars.ID, node.getRequiredSymbolicName()); } /** * Creates a function invocation for {@code id{}}. See id. * @param relationship the relationship for which the internal id should be retrieved * @return a function call for {@code id()} on a relationship. * @deprecated see {@link #elementId(Relationship)} for a replacement. Neo4j the * database will remove support for {@code id(n)} at some point. */ @Deprecated(since = "2023.3.0") @SuppressWarnings({ "squid:S1133" }) // Yes, I promise, this will be removed at some // point, but not yet. static FunctionInvocation id(Relationship relationship) { Assertions.notNull(relationship, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_RELATIONSHIP_REQUIRED)); return FunctionInvocation.create(Scalars.ID, relationship.getRequiredSymbolicName()); } /** * Creates a function invocation for {@code elementId{}}. * @param node the node for which the element id should be retrieved * @return a function call for {@code elementId()} on a node. */ @Neo4jVersion(minimum = "5.0.0") static FunctionInvocation elementId(Node node) { Assertions.notNull(node, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_NODE_REQUIRED)); return FunctionInvocation.create(Scalars.ELEMENT_ID, node.getRequiredSymbolicName()); } /** * Creates a function invocation for {@code elementId{}}. * @param relationship the relationship for which the element id should be retrieved * @return a function call for {@code elementId()} on a relationship. */ @Neo4jVersion(minimum = "5.0.0") static FunctionInvocation elementId(Relationship relationship) { Assertions.notNull(relationship, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_RELATIONSHIP_REQUIRED)); return FunctionInvocation.create(Scalars.ELEMENT_ID, relationship.getRequiredSymbolicName()); } /** * Creates a function invocation for {@code keys{}}. See keys. * @param node the node which keys should be returned. * @return a function call for {@code keys()} on an expression. * @since 2021.0.2 */ static FunctionInvocation keys(Node node) { Assertions.notNull(node, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_NODE_REQUIRED)); return keys(node.getRequiredSymbolicName()); } /** * Creates a function invocation for {@code keys{}}. See keys. * @param relationship the relationship which keys should be returned. * @return a function call for {@code keys()} on an expression. * @since 2021.0.2 */ static FunctionInvocation keys(Relationship relationship) { Assertions.notNull(relationship, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_RELATIONSHIP_REQUIRED)); return keys(relationship.getRequiredSymbolicName()); } /** * Creates a function invocation for {@code keys{}}. See keys. * @param expression the expressions which keys should be returned. Must resolve to a * node, relationship or map. * @return a function call for {@code keys()} on an expression. * @since 2021.0.2 */ static FunctionInvocation keys(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); Expression param = (expression instanceof Named named) ? named.getRequiredSymbolicName() : expression; return FunctionInvocation.create(Lists.KEYS, param); } /** * Creates a function invocation for {@code labels{}}. See labels. * @param node the node for which the labels should be retrieved * @return a function call for {@code labels()} on a node. */ static FunctionInvocation labels(Node node) { Assertions.notNull(node, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_NODE_REQUIRED)); return labels(node.getRequiredSymbolicName()); } /** * Creates a function invocation for {@code labels{}}. The {@link SymbolicName * symbolic name} {@code node} must point to a node. This can't be checked during * compile time, so please make sure of that. *

* See labels. * @param node the node for which the labels should be retrieved * @return a function call for {@code labels()} on a node. * @since 2023.2.0 */ static FunctionInvocation labels(SymbolicName node) { Assertions.notNull(node, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_NODE_REQUIRED)); return FunctionInvocation.create(Lists.LABELS, node); } /** * Creates a function invocation for {@code type{}}. See type. * @param relationship the relationship for which the type should be retrieved * @return a function call for {@code type()} on a relationship. */ static FunctionInvocation type(Relationship relationship) { Assertions.notNull(relationship, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_RELATIONSHIP_REQUIRED)); return type(relationship.getRequiredSymbolicName()); } /** * Creates a function invocation for {@code type{}}. The {@link SymbolicName symbolic * name} {@code relationship} must point to a relationship. This can't be checked * during compile time, so please make sure of that. *

* See type. * @param relationship the relationship for which the type should be retrieved * @return a function call for {@code type()} on a relationship. * @since 2023.2.0 */ static FunctionInvocation type(SymbolicName relationship) { Assertions.notNull(relationship, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_RELATIONSHIP_REQUIRED)); return FunctionInvocation.create(Scalars.TYPE, relationship); } /** * Returns a function call for {@code count()} for one named node. * @param node the named node to be counted * @return a function call for {@code count()} for one named node * @see #count(Expression) */ static FunctionInvocation count(Node node) { Assertions.notNull(node, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_NODE_REQUIRED)); return FunctionInvocation.create(Aggregates.COUNT, node.getRequiredSymbolicName()); } /** * Creates a function invocation for the {@code count()} function. See count. * @param expression an expression describing the things to count. * @return a function call for {@code count()} for an expression like * {@link Cypher#asterisk()} etc. */ static FunctionInvocation count(Expression expression) { return FunctionInvocation.create(Aggregates.COUNT, expression); } /** * Creates a function invocation for a {@code count()} function with {@code DISTINCT} * added. * @param node the named node to be counted * @return a function call for {@code count()} for one named node * @see #countDistinct(Expression) */ static FunctionInvocation countDistinct(Node node) { Assertions.notNull(node, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_NODE_REQUIRED)); return FunctionInvocation.createDistinct(Aggregates.COUNT, node.getRequiredSymbolicName()); } /** * Creates a function invocation for a {@code count()} function with {@code DISTINCT} * added. See count. * @param expression an expression describing the things to count. * @return a function call for {@code count()} for an expression like * {@link Cypher#asterisk()} etc. */ static FunctionInvocation countDistinct(Expression expression) { return FunctionInvocation.createDistinct(Aggregates.COUNT, expression); } /** * Creates a function invocation for {@code properties())} on nodes. * @param node the node who's properties should be returned. * @return a function call for {@code properties())} */ static FunctionInvocation properties(Node node) { Assertions.notNull(node, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_NODE_REQUIRED)); return FunctionInvocation.create(Scalars.PROPERTIES, node.getRequiredSymbolicName()); } /** * Creates a function invocation for {@code properties())} on relationships. * @param relationship the relationship who's properties should be returned. * @return a function call for {@code properties())} */ static FunctionInvocation properties(Relationship relationship) { Assertions.notNull(relationship, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_RELATIONSHIP_REQUIRED)); return FunctionInvocation.create(Scalars.PROPERTIES, relationship.getRequiredSymbolicName()); } /** * Creates a function invocation for {@code properties())} on maps. * @param map the map who's properties should be returned. * @return a function call for {@code properties())} */ static FunctionInvocation properties(MapExpression map) { return FunctionInvocation.create(Scalars.PROPERTIES, map); } /** * Creates a function invocation for the {@code coalesce()} function. See coalesce. * @param expressions one or more expressions to be coalesced * @return a function call for {@code coalesce}. */ static FunctionInvocation coalesce(Expression... expressions) { return FunctionInvocation.create(Scalars.COALESCE, expressions); } /** * Creates a function invocation for the {@code left()} function. See left. * @param expression an expression resolving to a string * @param length desired length * @return a function call for {@code left()} * @since 2023.0.2 */ static FunctionInvocation left(Expression expression, Expression length) { if (expression != null && length == null) { throw new IllegalArgumentException("length might not be null when the expression is not null"); } return FunctionInvocation.create(Strings.LEFT, expressionOrNullLit(expression), expressionOrNullLit(length)); } /** * Creates a function invocation for the {@code ltrim()} function. See ltrim. * @param expression an expression resolving to a string * @return a function call for {@code ltrim()} * @since 2023.0.2 */ static FunctionInvocation ltrim(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(Strings.LTRIM, expressionOrNullLit(expression)); } /** * Creates a function invocation for the {@code replace()} function. See replace. * @param original an expression that returns a string * @param search an expression that specifies the string to be replaced in * {@code original}. * @param replace an expression that specifies the replacement string. * @return a function call for {@code replace()} * @since 2023.0.2 */ static FunctionInvocation replace(Expression original, Expression search, Expression replace) { return FunctionInvocation.create(Strings.REPLACE, expressionOrNullLit(original), expressionOrNullLit(search), expressionOrNullLit(replace)); } /** * Creates a function invocation for the {@code reverse()} function. See reverse. * @param original an expression that returns a string * @return a function call for {@code reverse()} * @since 2023.0.2 */ static FunctionInvocation reverse(Expression original) { Assertions.notNull(original, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(Strings.REVERSE, expressionOrNullLit(original)); } /** * Creates a function invocation for the {@code right()} function. See right. * @param expression an expression resolving to a string * @param length desired length * @return a function call for {@code right()} * @since 2023.0.2 */ static FunctionInvocation right(Expression expression, Expression length) { if (expression != null && length == null) { throw new IllegalArgumentException("length might not be null when the expression is not null"); } return FunctionInvocation.create(Strings.RIGHT, expressionOrNullLit(expression), expressionOrNullLit(length)); } /** * Creates a function invocation for the {@code rtrim()} function. See rtrim. * @param expression an expression resolving to a string * @return a function call for {@code rtrim()} * @since 2023.0.2 */ static FunctionInvocation rtrim(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(Strings.RTRIM, expressionOrNullLit(expression)); } /** * Creates a function invocation for the {@code substring()} function. See rtrim. * @param original an expression resolving to a string * @param start an expression that returns a positive integer, denoting the position * at which the substring will begin. * @param length an expression that returns a positive integer, denoting how many * characters of original will be returned. * @return a function call for {@code substring()} * @since 2023.0.2 */ static FunctionInvocation substring(Expression original, Expression start, Expression length) { Assertions.notNull(start, "start is required"); if (length != null) { return FunctionInvocation.create(Strings.SUBSTRING, expressionOrNullLit(original), start, length); } return FunctionInvocation.create(Strings.SUBSTRING, expressionOrNullLit(original), start); } private static Expression expressionOrNullLit(Expression expression) { return (expression != null) ? expression : Cypher.literalNull(); } /** * Creates a function invocation for the {@code toLower()} function. See toLower. * @param expression an expression resolving to a string * @return a function call for {@code toLower()} for one expression */ static FunctionInvocation toLower(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(Strings.TO_LOWER, expressionOrNullLit(expression)); } /** * Creates a function invocation for the {@code toUpper()} function. See toUpper. * @param expression an expression resolving to a string * @return a function call for {@code toLower()} for one expression * @since 2023.0.2 */ static FunctionInvocation toUpper(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(Strings.TO_UPPER, expressionOrNullLit(expression)); } /** * Creates a function invocation for the {@code trim()} function. See trim. * @param expression an expression resolving to a string * @return a function call for {@code trim()} for one expression * @since 2021.2.1 */ static FunctionInvocation trim(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(Strings.TRIM, expression); } /** * Creates a function invocation for the {@code split()} function. See split. * @param expression an expression resolving to a string that should be split * @param delimiter the delimiter on which to split * @return a function call for {@code split()} * @since 2021.2.1 */ static FunctionInvocation split(Expression expression, Expression delimiter) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); Assertions.notNull(delimiter, "The delimiter is required."); return FunctionInvocation.create(Strings.SPLIT, expression, delimiter); } /** * Creates a function invocation for the {@code split()} function. See split. * @param expression an expression resolving to a string that should be split * @param delimiter the delimiter on which to split * @return a function call for {@code split()} * @since 2021.2.1 */ static FunctionInvocation split(Expression expression, String delimiter) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); Assertions.notNull(delimiter, "The delimiter is required."); return split(expression, Cypher.literalOf(delimiter)); } /** * Creates a function invocation for the {@code size()} function. {@code size} can be * applied to *

* @param expression the expression who's size is to be returned * @return a function call for {@code size()} for one expression */ static FunctionInvocation size(Expression expression) { return FunctionInvocation.create(Scalars.SIZE, expression); } /** * Creates a function invocation for the {@code size()} function. {@code size} can be * applied to * * @param pattern the pattern for which {@code size()} should be invoked. * @return a function call for {@code size()} for a pattern */ static FunctionInvocation size(RelationshipPattern pattern) { return FunctionInvocation.create(Scalars.SIZE, pattern); } /** * Creates a function invocation for the {@code exists()} function. See exists. * @param expression the expression who's existence is to be evaluated * @return a function call for {@code exists()} for one expression */ static FunctionInvocation exists(Expression expression) { return FunctionInvocation.create(Predicates.EXISTS, expression); } /** * Creates a function invocation for the {@code distance()} function. See exists. * Both points need to be in the same coordinate system. * @param point1 point 1 * @param point2 point 2 * @return a function call for {@code distance()} */ static FunctionInvocation distance(Expression point1, Expression point2) { Assertions.notNull(point1, "The distance function requires two points."); Assertions.notNull(point2, "The distance function requires two points."); return FunctionInvocation.create(Spatials.DISTANCE, point1, point2); } /** * Creates a function invocation for the {@code point()} function. See point. * @param parameterMap the map of parameters for {@code point()} * @return a function call for {@code point()} */ static FunctionInvocation point(MapExpression parameterMap) { return point((Expression) parameterMap); } /** * Creates a function invocation for the {@code point()} function. See point. *

* This generic expression variant is useful for referencing a point inside a * parameter or another map. * @param expression an expression resolving to a valid map of parameters for * {@code point()} * @return a function call for {@code point()} * @since 2021.0.0 */ static FunctionInvocation point(Expression expression) { return FunctionInvocation.create(Spatials.POINT, expression); } /** * Creates a function invocation for the {@code point()} function. See point. * @param parameter a parameter referencing a {@code point()} * @return a function call for {@code point()} * @since 2022.7.3 */ static FunctionInvocation point(Parameter parameter) { return FunctionInvocation.create(Spatials.POINT, parameter); } /** * Convenience method for creating a 2d cartesian point. * @param x the x coordinate * @param y the y coordinate * @return a function call for {@code point()} * @since 2022.7.3 */ static FunctionInvocation cartesian(double x, double y) { return point(Cypher.mapOf("x", Cypher.literalOf(x), "y", Cypher.literalOf(y))); } /** * Convenience method for creating a 2d coordinate in the WGS 84 coordinate system. * @param longitude the longitude * @param latitude the latitude * @return a function call for {@code point()} * @since 2022.7.3 */ static FunctionInvocation coordinate(double longitude, double latitude) { return point(Cypher.mapOf("longitude", Cypher.literalOf(longitude), "latitude", Cypher.literalOf(latitude))); } /** * Creates a function invocation for the {@code point.withinBBox} function. See * point.withinBBox. * @param point the point to check * @param lowerLeft the lower left point of the bounding box (south-west coordinate) * @param upperRight the upper right point of the bounding box (north-east coordinate) * @return a function call for {@code point.withinBBox} * @since 2022.7.3 */ static FunctionInvocation withinBBox(Expression point, Expression lowerLeft, Expression upperRight) { return FunctionInvocation.create(() -> "point.withinBBox", point, lowerLeft, upperRight); } /** * Creates a function invocation for the {@code avg()} function. See avg. * @param expression the things to average * @return a function call for {@code avg()} */ static FunctionInvocation avg(Expression expression) { return FunctionInvocation.create(Aggregates.AVG, expression); } /** * Creates a function invocation for the {@code avg()} function with {@code DISTINCT} * added. See avg. * @param expression the things to average * @return a function call for {@code avg()} */ static FunctionInvocation avgDistinct(Expression expression) { return FunctionInvocation.createDistinct(Aggregates.AVG, expression); } /** * Creates a function invocation for the {@code collect()} function. * @param variable the named thing to collect * @return a function call for {@code collect()} * @see #collect(Expression) */ static FunctionInvocation collect(Named variable) { Assertions.notNull(variable, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_VARIABLE_REQUIRED)); return FunctionInvocation.create(Aggregates.COLLECT, variable.getRequiredSymbolicName()); } /** * Creates a function invocation for the {@code collect()} function with * {@code DISTINCT} added. * @param variable the named thing to collect * @return a function call for {@code collect()} * @see #collect(Expression) */ static FunctionInvocation collectDistinct(Named variable) { Assertions.notNull(variable, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_VARIABLE_REQUIRED)); return FunctionInvocation.createDistinct(Aggregates.COLLECT, variable.getRequiredSymbolicName()); } /** * Creates a function invocation for the {@code collect()} function. See collect. * @param expression the things to collect * @return a function call for {@code collect()} */ static FunctionInvocation collect(Expression expression) { return FunctionInvocation.create(Aggregates.COLLECT, expression); } /** * Creates a function invocation for the {@code collect()} function with * {@code DISTINCT} added. See collect. * @param expression the things to collect * @return a function call for {@code collect()} */ static FunctionInvocation collectDistinct(Expression expression) { return FunctionInvocation.createDistinct(Aggregates.COLLECT, expression); } /** * Creates a function invocation for the {@code max()} function. See max. * @param expression a list from which the maximum element value is returned * @return a function call for {@code max()} */ static FunctionInvocation max(Expression expression) { return FunctionInvocation.create(Aggregates.MAX, expression); } /** * Creates a function invocation for the {@code max()} function with {@code DISTINCT} * added. See max. * @param expression a list from which the maximum element value is returned * @return a function call for {@code max()} */ static FunctionInvocation maxDistinct(Expression expression) { return FunctionInvocation.createDistinct(Aggregates.MAX, expression); } /** * Creates a function invocation for the {@code min()} function. See min. * @param expression a list from which the minimum element value is returned * @return a function call for {@code min()} */ static FunctionInvocation min(Expression expression) { return FunctionInvocation.create(Aggregates.MIN, expression); } /** * Creates a function invocation for the {@code min()} function with {@code DISTINCT} * added. See min. * @param expression a list from which the minimum element value is returned * @return a function call for {@code min()} */ static FunctionInvocation minDistinct(Expression expression) { return FunctionInvocation.createDistinct(Aggregates.MIN, expression); } private static void assertPercentileArguments(Aggregates builtIn, Expression expression, Number percentile) { Assertions.notNull(expression, "The numeric expression for " + builtIn.getImplementationName() + " is required."); Assertions.notNull(percentile, "The percentile for " + builtIn.getImplementationName() + " is required."); final double p = percentile.doubleValue(); Assertions.isTrue(p >= 0D && p <= 1D, "The percentile for " + builtIn.getImplementationName() + " must be between 0.0 and 1.0."); } /** * Creates a function invocation for the {@code percentileCont()} function. See * percentileCont. * @param expression a numeric expression * @param percentile a numeric value between 0.0 and 1.0 * @return a function call for {@code percentileCont()} */ static FunctionInvocation percentileCont(Expression expression, Number percentile) { assertPercentileArguments(Aggregates.PERCENTILE_CONT, expression, percentile); return FunctionInvocation.create(Aggregates.PERCENTILE_CONT, expression, new NumberLiteral(percentile)); } /** * Creates a function invocation for the {@code percentileCont()} function with * {@code DISTINCT} added. See percentileCont. * @param expression a numeric expression * @param percentile a numeric value between 0.0 and 1.0 * @return a function call for {@code percentileCont()} */ static FunctionInvocation percentileContDistinct(Expression expression, Number percentile) { assertPercentileArguments(Aggregates.PERCENTILE_CONT, expression, percentile); return FunctionInvocation.createDistinct(Aggregates.PERCENTILE_CONT, expression, new NumberLiteral(percentile)); } /** * Creates a function invocation for the {@code percentileDisc()} function. See * percentileDisc. * @param expression a numeric expression * @param percentile a numeric value between 0.0 and 1.0 * @return a function call for {@code percentileDisc()} */ static FunctionInvocation percentileDisc(Expression expression, Number percentile) { assertPercentileArguments(Aggregates.PERCENTILE_DISC, expression, percentile); return FunctionInvocation.create(Aggregates.PERCENTILE_DISC, expression, new NumberLiteral(percentile)); } /** * Creates a function invocation for the {@code percentileDisc()} function with * {@code DISTINCT} added. See percentileDisc. * @param expression a numeric expression * @param percentile a numeric value between 0.0 and 1.0 * @return a function call for {@code percentileDisc()} */ static FunctionInvocation percentileDiscDistinct(Expression expression, Number percentile) { assertPercentileArguments(Aggregates.PERCENTILE_DISC, expression, percentile); return FunctionInvocation.createDistinct(Aggregates.PERCENTILE_DISC, expression, new NumberLiteral(percentile)); } /** * Creates a function invocation for the {@code stDev()} function. See stDev. * @param expression a numeric expression * @return a function call for {@code stDev()} */ static FunctionInvocation stDev(Expression expression) { return FunctionInvocation.create(Aggregates.ST_DEV, expression); } /** * Creates a function invocation for the {@code stDev()} function with * {@code DISTINCT} added. See stDev. * @param expression a numeric expression * @return a function call for {@code stDev()} */ static FunctionInvocation stDevDistinct(Expression expression) { return FunctionInvocation.createDistinct(Aggregates.ST_DEV, expression); } /** * Creates a function invocation for the {@code stDevP()} function. See stDevP. * @param expression a numeric expression * @return a function call for {@code stDevP()} */ static FunctionInvocation stDevP(Expression expression) { return FunctionInvocation.create(Aggregates.ST_DEV_P, expression); } /** * Creates a function invocation for the {@code stDevP()} function with * {@code DISTINCT} added. See stDevP. * @param expression a numeric expression * @return a function call for {@code stDevP()} */ static FunctionInvocation stDevPDistinct(Expression expression) { return FunctionInvocation.createDistinct(Aggregates.ST_DEV_P, expression); } /** * Creates a function invocation for the {@code sum()} function. See sum. * @param expression an expression returning a set of numeric values * @return a function call for {@code sum()} */ static FunctionInvocation sum(Expression expression) { return FunctionInvocation.create(Aggregates.SUM, expression); } /** * Creates a function invocation for the {@code sum()} function with {@code DISTINCT} * added. See sum. * @param expression an expression returning a set of numeric values * @return a function call for {@code sum()} */ static FunctionInvocation sumDistinct(Expression expression) { return FunctionInvocation.createDistinct(Aggregates.SUM, expression); } /** * Returns a function call for {@code range()}. * @param start the range's start * @param end the range's end * @return a function call for {@code range()} * @see #range(Expression, Expression) */ static FunctionInvocation range(Integer start, Integer end) { return range(Cypher.literalOf(start), Cypher.literalOf(end)); } /** * Return a function call for {@code range()}. * @param start the range's start * @param end the range's end * @return a function call for {@code range()} * @see #range(Expression, Expression, Expression) */ static FunctionInvocation range(Expression start, Expression end) { return range(start, end, null); } /** * Creates a function invocation for the {@code range()} function. See range. * @param start the range's start * @param end the range's end * @param step the range's step * @return a function call for {@code range()} * @see #range(Expression, Expression, Expression) */ static FunctionInvocation range(Integer start, Integer end, Integer step) { return range(Cypher.literalOf(start), Cypher.literalOf(end), Cypher.literalOf(step)); } /** * Creates a function invocation for the {@code range()} function. See range. * @param start the range's start * @param end the range's end * @param step the range's step * @return a function call for {@code range()} */ static FunctionInvocation range(Expression start, Expression end, Expression step) { Assertions.notNull(start, "The expression for range is required."); Assertions.notNull(end, "The expression for range is required."); if (step == null) { return FunctionInvocation.create(Lists.RANGE, start, end); } else { return FunctionInvocation.create(Lists.RANGE, start, end, step); } } /** * Creates a function invocation for the {@code head()} function. See head. * @param expression a list from which the head element is returned * @return a function call for {@code head()} */ static FunctionInvocation head(Expression expression) { return FunctionInvocation.create(Scalars.HEAD, expression); } /** * Creates a function invocation for the {@code last()} function. See last. * @param expression a list from which the last element is returned * @return a function call for {@code last()} */ static FunctionInvocation last(Expression expression) { return FunctionInvocation.create(Scalars.LAST, expression); } /** * Creates a function invocation for {@code nodes{}}. See nodes. * @param path the path for which the number of nodes should be retrieved * @return a function call for {@code nodes()} on a path. * @since 1.1 */ static FunctionInvocation nodes(NamedPath path) { Assertions.notNull(path, "The path for nodes is required."); return FunctionInvocation.create(Lists.NODES, path.getSymbolicName() .orElseThrow(() -> new IllegalArgumentException( Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_NAMED_PATH_REQUIRED)))); } /** * Creates a function invocation for {@code nodes{}}. See nodes. * @param symbolicName the symbolic name of a path for which the number of nodes * should be retrieved * @return a function call for {@code nodes{}} on a path represented by a symbolic * name. * @since 2020.1.5 */ static FunctionInvocation nodes(SymbolicName symbolicName) { Assertions.notNull(symbolicName, "The symbolic name of the path for nodes is required."); return FunctionInvocation.create(Lists.NODES, symbolicName); } /** * Creates a function invocation for {@code relationships{}}. See relationships. * @param path the path for which the relationships should be retrieved * @return a function call for {@code relationships()} on a path. * @since 2020.0.2 */ static FunctionInvocation relationships(NamedPath path) { Assertions.notNull(path, "The path for relationships is required."); return FunctionInvocation.create(Lists.RELATIONSHIPS, path.getSymbolicName() .orElseThrow(() -> new IllegalArgumentException( Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_NAMED_PATH_REQUIRED)))); } /** * Creates a function invocation for {@code relationships{}}. See relationships. * @param symbolicName the symbolic name of a path for which the relationships should * be retrieved * @return a function call for {@code relationships()} on a path represented by a * symbolic name. * @since 2020.1.5 */ static FunctionInvocation relationships(SymbolicName symbolicName) { Assertions.notNull(symbolicName, "The symbolic name of the path for relationships is required."); return FunctionInvocation.create(Lists.RELATIONSHIPS, symbolicName); } /** * Creates a function invocation for {@code startNode{}}. See startNode. * @param relationship the relationship for which the start node be retrieved * @return a function call for {@code startNode()} on a path. * @since 2020.0.2 */ static FunctionInvocation startNode(Relationship relationship) { Assertions.notNull(relationship, "The relationship for endNode is required."); return FunctionInvocation.create(Scalars.START_NODE, relationship.getSymbolicName() .orElseThrow(() -> new IllegalArgumentException("The relationship needs to be named!"))); } /** * Creates a function invocation for {@code endNode{}}. See endNode. * @param relationship the relationship for which the end node be retrieved * @return a function call for {@code endNode()} on a path. * @since 2020.0.2 */ static FunctionInvocation endNode(Relationship relationship) { Assertions.notNull(relationship, "The relationship for endNode is required."); return FunctionInvocation.create(Scalars.END_NODE, relationship.getSymbolicName() .orElseThrow(() -> new IllegalArgumentException("The relationship needs to be named!"))); } /** * Creates a function invocation for {@code date()}. See date. * This is the most simple form. * @return a function call for {@code date()}. * @since 2020.1.0 */ static FunctionInvocation date() { return FunctionInvocation.create(BuiltInFunctions.Temporals.DATE); } /** * Creates a function invocation for {@code date({})}. See date. * @param year the year * @param month the month * @param day the day * @return a function call for {@code date({})}. * @since 2020.1.0 */ static FunctionInvocation calendarDate(Integer year, Integer month, Integer day) { Assertions.notNull(year, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_YEAR_REQUIRED)); Assertions.notNull(month, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_MONTH_REQUIRED)); Assertions.notNull(day, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_DAY_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.DATE, Cypher.mapOf("year", Cypher.literalOf(year), "month", Cypher.literalOf(month), "day", Cypher.literalOf(day))); } /** * Creates a function invocation for {@code date({})}. See date. * @param year the year * @param week the optional week * @param dayOfWeek the optional day of the week * @return a function call for {@code date({})}. * @since 2020.1.0 */ static FunctionInvocation weekDate(Integer year, Integer week, Integer dayOfWeek) { Assertions.notNull(year, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_YEAR_REQUIRED)); Object[] parameters = new Object[2 + ((week != null) ? 2 : 0) + ((dayOfWeek != null) ? 2 : 0)]; int i = 0; parameters[i++] = "year"; parameters[i++] = Cypher.literalOf(year); if (week != null) { parameters[i++] = "week"; parameters[i++] = Cypher.literalOf(week); } if (dayOfWeek != null) { if (week == null) { throw new IllegalArgumentException("week is required when using dayOfWeek."); } parameters[i++] = "dayOfWeek"; parameters[i] = Cypher.literalOf(dayOfWeek); } return FunctionInvocation.create(BuiltInFunctions.Temporals.DATE, Cypher.mapOf(parameters)); } /** * Creates a function invocation for {@code date({})}. See date. * @param year the year * @param quarter the optional week * @param dayOfQuarter the optional day of the week * @return a function call for {@code date({})}. * @since 2020.1.0 */ static FunctionInvocation quarterDate(Integer year, Integer quarter, Integer dayOfQuarter) { Assertions.notNull(year, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_YEAR_REQUIRED)); Object[] parameters = new Object[2 + ((quarter != null) ? 2 : 0) + ((dayOfQuarter != null) ? 2 : 0)]; int i = 0; parameters[i++] = "year"; parameters[i++] = Cypher.literalOf(year); if (quarter != null) { parameters[i++] = "quarter"; parameters[i++] = Cypher.literalOf(quarter); } if (dayOfQuarter != null) { parameters[i++] = "dayOfQuarter"; parameters[i] = Cypher.literalOf(dayOfQuarter); } return FunctionInvocation.create(BuiltInFunctions.Temporals.DATE, Cypher.mapOf(parameters)); } /** * Creates a function invocation for {@code date({})}. See date. * @param year the year * @param ordinalDay the ordinal day of the year. * @return a function call for {@code date({})}. * @since 2020.1.0 */ static FunctionInvocation ordinalDate(Integer year, Integer ordinalDay) { Assertions.notNull(year, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_YEAR_REQUIRED)); Object[] parameters = new Object[2 + ((ordinalDay != null) ? 2 : 0)]; int i = 0; parameters[i++] = "year"; parameters[i++] = Cypher.literalOf(year); if (ordinalDay != null) { parameters[i++] = "ordinalDay"; parameters[i] = Cypher.literalOf(ordinalDay); } return FunctionInvocation.create(BuiltInFunctions.Temporals.DATE, Cypher.mapOf(parameters)); } /** * Creates a function invocation for {@code date({})}. See date. * This is the most generic form. * @param components the map to pass to {@code date({})} * @return a function call for {@code date({})}. * @since 2020.1.0 */ static FunctionInvocation date(MapExpression components) { Assertions.notNull(components, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_COMPONENTS_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.DATE, components); } /** * Creates a function invocation for {@code date({})}. See date. * This creates a date from a string. * @param temporalValue a string representing a temporal value. * @return a function call for {@code date({})}. * @since 2020.1.0 */ static FunctionInvocation date(String temporalValue) { Assertions.hasText(temporalValue, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_TEMPORAL_VALUE_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.DATE, Cypher.literalOf(temporalValue)); } /** * Creates a function invocation for {@code date({})}. See date. * This creates a date from a string. * @param temporalValue an expression representing a temporal value. * @return a function call for {@code date({})}. * @since 2020.1.0 */ static FunctionInvocation date(Expression temporalValue) { Assertions.notNull(temporalValue, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_TEMPORAL_VALUE_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.DATE, temporalValue); } /** * Creates a function invocation for {@code datetime({})}. See datetime. * @return a function call for {@code datetime({})}. * @since 2020.1.0 */ static FunctionInvocation datetime() { return FunctionInvocation.create(BuiltInFunctions.Temporals.DATETIME); } /** * Creates a function invocation for {@code datetime({})}. See datetime. * @param timeZone the timezone to use when creating the temporal instance * @return a function call for {@code datetime({})}. * @since 2020.1.0 */ static FunctionInvocation datetime(TimeZone timeZone) { Assertions.notNull(timeZone, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_TZ_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.DATETIME, timezoneMapLiteralOf(timeZone)); } /** * Creates a function invocation for {@code datetime({})}. See datetime. * This is the most generic form. * @param components the map to pass to {@code datetime({})} * @return a function call for {@code datetime({})}. * @since 2020.1.0 */ static FunctionInvocation datetime(MapExpression components) { Assertions.notNull(components, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_COMPONENTS_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.DATETIME, components); } /** * Creates a function invocation for {@code datetime({})}. See datetime. * This creates a datetime from a string. * @param temporalValue a string representing a temporal value. * @return a function call for {@code datetime({})}. * @since 2020.1.0 */ static FunctionInvocation datetime(String temporalValue) { Assertions.hasText(temporalValue, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_TEMPORAL_VALUE_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.DATETIME, Cypher.literalOf(temporalValue)); } /** * Creates a function invocation for {@code datetime({})}. See datetime. * This creates a datetime from a string. * @param temporalValue an expression representing a temporal value. * @return a function call for {@code date({})}. * @since 2020.1.0 */ static FunctionInvocation datetime(Expression temporalValue) { Assertions.notNull(temporalValue, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_TEMPORAL_VALUE_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.DATETIME, temporalValue); } /** * Creates a function invocation for {@code localdatetime({})}. See localdatetime. * @return a function call for {@code localdatetime({})}. * @since 2020.1.0 */ static FunctionInvocation localdatetime() { return FunctionInvocation.create(BuiltInFunctions.Temporals.LOCALDATETIME); } /** * Creates a function invocation for {@code localdatetime({})}. See localdatetime. * @param timeZone the timezone to use when creating the temporal instance * @return a function call for {@code localdatetime({})}. * @since 2020.1.0 */ static FunctionInvocation localdatetime(TimeZone timeZone) { Assertions.notNull(timeZone, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_TZ_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.LOCALDATETIME, timezoneMapLiteralOf(timeZone)); } /** * Creates a function invocation for {@code localdatetime({})}. See localdatetime. * This is the most generic form. * @param components the map to pass to {@code localdatetime({})} * @return a function call for {@code localdatetime({})}. * @since 2020.1.0 */ static FunctionInvocation localdatetime(MapExpression components) { Assertions.notNull(components, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_COMPONENTS_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.LOCALDATETIME, components); } /** * Creates a function invocation for {@code localdatetime({})}. See localdatetime. * This creates a localdatetime from a string. * @param temporalValue a string representing a temporal value. * @return a function call for {@code localdatetime({})}. * @since 2020.1.0 */ static FunctionInvocation localdatetime(String temporalValue) { Assertions.hasText(temporalValue, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_TEMPORAL_VALUE_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.LOCALDATETIME, Cypher.literalOf(temporalValue)); } /** * Creates a function invocation for {@code localdatetime({})}. See localdatetime. * This creates a localdatetime from a string. * @param temporalValue an expression representing a temporal value. * @return a function call for {@code localdatetime({})}. * @since 2020.1.0 */ static FunctionInvocation localdatetime(Expression temporalValue) { Assertions.notNull(temporalValue, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_TEMPORAL_VALUE_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.LOCALDATETIME, temporalValue); } /** * Creates a function invocation for {@code localtime({})}. See localtime. * @return a function call for {@code localtime({})}. * @since 2020.1.0 */ static FunctionInvocation localtime() { return FunctionInvocation.create(BuiltInFunctions.Temporals.LOCALTIME); } /** * Creates a function invocation for {@code localtime({})}. See localtime. * @param timeZone the timezone to use when creating the temporal instance * @return a function call for {@code localtime({})}. * @since 2020.1.0 */ static FunctionInvocation localtime(TimeZone timeZone) { Assertions.notNull(timeZone, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_TZ_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.LOCALTIME, timezoneMapLiteralOf(timeZone)); } /** * Creates a function invocation for {@code localtime({})}. See localtime. * This is the most generic form. * @param components the map to pass to {@code localtime({})} * @return a function call for {@code localtime({})}. * @since 2020.1.0 */ static FunctionInvocation localtime(MapExpression components) { Assertions.notNull(components, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_COMPONENTS_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.LOCALTIME, components); } /** * Creates a function invocation for {@code localtime({})}. See localtime. * This creates a localtime from a string. * @param temporalValue a string representing a temporal value. * @return a function call for {@code localtime({})}. * @since 2020.1.0 */ static FunctionInvocation localtime(String temporalValue) { Assertions.hasText(temporalValue, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_TEMPORAL_VALUE_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.LOCALTIME, Cypher.literalOf(temporalValue)); } /** * Creates a function invocation for {@code localtime({})}. See localtime. * This creates a localtime from a string. * @param temporalValue an expression representing a temporal value. * @return a function call for {@code localtime({})}. * @since 2020.1.0 */ static FunctionInvocation localtime(Expression temporalValue) { Assertions.notNull(temporalValue, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_TEMPORAL_VALUE_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.LOCALTIME, temporalValue); } /** * Creates a function invocation for {@code time({})}. See time. * @return a function call for {@code time({})}. * @since 2020.1.0 */ static FunctionInvocation time() { return FunctionInvocation.create(BuiltInFunctions.Temporals.TIME); } /** * Creates a function invocation for {@code time({})}. See time. * @param timeZone the timezone to use when creating the temporal instance * @return a function call for {@code time({})}. * @since 2020.1.0 */ static FunctionInvocation time(TimeZone timeZone) { Assertions.notNull(timeZone, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_TZ_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.TIME, timezoneMapLiteralOf(timeZone)); } private static Expression timezoneMapLiteralOf(TimeZone timeZone) { return Cypher.mapOf("timezone", Cypher.literalOf(timeZone.getID())); } /** * Creates a function invocation for {@code time({})}. See time. * This is the most generic form. * @param components the map to pass to {@code time({})} * @return a function call for {@code time({})}. * @since 2020.1.0 */ static FunctionInvocation time(MapExpression components) { Assertions.notNull(components, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_COMPONENTS_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.TIME, components); } /** * Creates a function invocation for {@code time({})}. See time. * This creates a time from a string. * @param temporalValue a string representing a temporal value. * @return a function call for {@code time({})}. * @since 2020.1.0 */ static FunctionInvocation time(String temporalValue) { Assertions.hasText(temporalValue, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_TEMPORAL_VALUE_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.TIME, Cypher.literalOf(temporalValue)); } /** * Creates a function invocation for {@code time({})}. See time. * This creates a time from a string. * @param temporalValue an expression representing a temporal value. * @return a function call for {@code time({})}. * @since 2020.1.0 */ static FunctionInvocation time(Expression temporalValue) { Assertions.notNull(temporalValue, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_TEMPORAL_VALUE_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.TIME, temporalValue); } /** * Creates a function invocation for {@code duration({})}. See duration. * This is the most generic form. * @param components the map to pass to {@code duration({})} * @return a function call for {@code duration({})}. * @since 2020.1.0 */ static FunctionInvocation duration(MapExpression components) { Assertions.notNull(components, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_COMPONENTS_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.Temporals.DURATION, components); } /** * Creates a function invocation for {@code duration({})}. See duration. * This creates a duration from a string. * @param temporalAmount a string representing a temporal amount. * @return a function call for {@code duration({})}. * @since 2020.1.0 */ static FunctionInvocation duration(String temporalAmount) { Assertions.hasText(temporalAmount, "The temporalAmount is required."); return FunctionInvocation.create(BuiltInFunctions.Temporals.DURATION, Cypher.literalOf(temporalAmount)); } /** * Creates a function invocation for {@code duration({})}. See duration. * This creates a duration from a string. * @param temporalAmount an expression representing a temporal amount. * @return a function call for {@code duration({})}. * @since 2020.1.0 */ static FunctionInvocation duration(Expression temporalAmount) { Assertions.notNull(temporalAmount, "The temporalAmount is required."); return FunctionInvocation.create(BuiltInFunctions.Temporals.DURATION, temporalAmount); } /** * Starts building a function invocation for {@code reduce({})}. * @param variable the closure will have a variable introduced in its context. We * decide here which variable to use. * @return an ongoing definition for a function call to {@code reduce({})}. * @since 2020.1.5 */ static Reduction.OngoingDefinitionWithVariable reduce(SymbolicName variable) { return Reduction.of(variable); } /** * Creates a function invocation for {@code abs({})}. See abs. * @param expression the value to pass to the function. * @return a function call for {@code abs({})}. * @since 2021.0.0 */ static FunctionInvocation abs(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.ABS, expression); } /** * Creates a function invocation for {@code ceil({})}. See ceil. * @param expression the value to pass to the function. * @return a function call for {@code ceil({})}. * @since 2021.0.0 */ static FunctionInvocation ceil(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.CEIL, expression); } /** * Creates a function invocation for {@code floor({})}. See floor. * @param expression the value to pass to the function. * @return a function call for {@code floor({})}. * @since 2021.0.0 */ static FunctionInvocation floor(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.FLOOR, expression); } /** * Creates a function invocation for {@code rand({})}. See rand. * @return a function call for {@code rand({})}. * @since 2021.0.0 */ static FunctionInvocation rand() { return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.RAND); } /** * Creates a function invocation for {@code round({})}. See round. * @param value the value to round * @param expression additional parameters, length must be 0, 1 or 2: First entry is * the precision, second is the rounding mode * @return a function call for {@code round({})}. * @since 2021.0.0 */ static FunctionInvocation round(Expression value, Expression... expression) { if (expression == null || expression.length == 0) { return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.ROUND, value); } else if (expression.length == 1) { return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.ROUND, value, expression[0]); } else if (expression.length == 2) { return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.ROUND, value, expression[0], expression[1]); } else { throw new IllegalArgumentException( "round() must be called with 1, 2 or 3 arguments (value, value + precision or value + precision + rounding mode."); } } /** * Creates a function invocation for {@code sign({})}. See sign. * @param expression the value to pass to the function. * @return a function call for {@code sign({})}. * @since 2021.0.0 */ static FunctionInvocation sign(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.SIGN, expression); } /** * Creates a function invocation for {@code e({})}. See e. * @return a function call for {@code e({})}. * @since 2021.0.0 */ static FunctionInvocation e() { return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.E); } /** * Creates a function invocation for {@code exp({})}. See exp. * @param expression the value to pass to the function. * @return a function call for {@code exp({})}. * @since 2021.0.0 */ static FunctionInvocation exp(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.EXP, expression); } /** * Creates a function invocation for {@code log({})}. See log. * @param expression the value to pass to the function. * @return a function call for {@code log({})}. * @since 2021.0.0 */ static FunctionInvocation log(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.LOG, expression); } /** * Creates a function invocation for {@code log10({})}. See log10. * @param expression the value to pass to the function. * @return a function call for {@code log10({})}. * @since 2021.0.0 */ static FunctionInvocation log10(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.LOG10, expression); } /** * Creates a function invocation for {@code sqrt({})}. See sqrt. * @param expression the value to pass to the function. * @return a function call for {@code sqrt({})}. * @since 2021.0.0 */ static FunctionInvocation sqrt(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.SQRT, expression); } /** * Creates a function invocation for {@code acos({})}. See acos. * @param expression the value to pass to the function. * @return a function call for {@code acos({})}. * @since 2021.0.0 */ static FunctionInvocation acos(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.ACOS, expression); } /** * Creates a function invocation for {@code asin({})}. See asin. * @param expression the value to pass to the function. * @return a function call for {@code asin({})}. * @since 2021.0.0 */ static FunctionInvocation asin(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.ASIN, expression); } /** * Creates a function invocation for {@code atan({})}. See atan. * @param expression the value to pass to the function. * @return a function call for {@code atan({})}. * @since 2021.0.0 */ static FunctionInvocation atan(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.ATAN, expression); } /** * Creates a function invocation for {@code atan2({})}. See atan2. * @param y the y value of a point * @param x the x value of a point * @return a function call for {@code atan2({})}. * @since 2021.0.0 */ static FunctionInvocation atan2(Expression y, Expression x) { Assertions.notNull(y, "y is required."); Assertions.notNull(x, "x is required."); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.ATAN2, y, x); } /** * Creates a function invocation for {@code cos({})}. See cos. * @param expression the value to pass to the function. * @return a function call for {@code cos({})}. * @since 2021.0.0 */ static FunctionInvocation cos(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.COS, expression); } /** * Creates a function invocation for {@code cot({})}. See cot. * @param expression the value to pass to the function. * @return a function call for {@code cot({})}. * @since 2021.0.0 */ static FunctionInvocation cot(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.COT, expression); } /** * Creates a function invocation for {@code degrees({})}. See degrees. * @param expression the value to pass to the function. * @return a function call for {@code degrees({})}. * @since 2021.0.0 */ static FunctionInvocation degrees(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.DEGREES, expression); } /** * Creates a function invocation for {@code haversin({})}. See haversin. * @param expression the value to pass to the function. * @return a function call for {@code haversin({})}. * @since 2021.0.0 */ static FunctionInvocation haversin(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.HAVERSIN, expression); } /** * Creates a function invocation for {@code pi({})}. See pi. * @return a function call for {@code pi({})}. * @since 2021.0.0 */ static FunctionInvocation pi() { return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.PI); } /** * Creates a function invocation for {@code radians({})}. See radians. * @param expression the value to pass to the function. * @return a function call for {@code radians({})}. * @since 2021.0.0 */ static FunctionInvocation radians(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.RADIANS, expression); } /** * Creates a function invocation for {@code sin({})}. See sin. * @param expression the value to pass to the function. * @return a function call for {@code sin({})}. * @since 2021.0.0 */ static FunctionInvocation sin(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.SIN, expression); } /** * Creates a function invocation for {@code tan({})}. See tan. * @param expression the value to pass to the function. * @return a function call for {@code tan({})}. * @since 2021.0.0 */ static FunctionInvocation tan(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(BuiltInFunctions.MathematicalFunctions.TAN, expression); } /** * Creates a function invocation for {@code toInteger({})}. See toInteger. * @param expression the value to pass to the function. * @return a function call for {@code toInteger({})}. * @since 2021.2.1 */ static FunctionInvocation toInteger(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(Scalars.TO_INTEGER, expression); } /** * Creates a function invocation for {@code toString({})}. See toString. * @param expression the value to pass to the function. * @return a function call for {@code toString({})}. * @since 2022.3.0 */ static FunctionInvocation toString(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(Scalars.TO_STRING, expression); } /** * Creates a function invocation for {@code toStringOrNull({})}. See toStringOrNull. * @param expression the value to pass to the function. * @return a function call for {@code toStringOrNull({})}. * @since 2023.0.2 */ static FunctionInvocation toStringOrNull(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(Strings.TO_STRING_OR_NULL, expressionOrNullLit(expression)); } /** * Creates a function invocation for {@code toFloat({})}. See toFloat. * @param expression the value to pass to the function. * @return a function call for {@code toFloat({})}. * @since 2021.2.1 */ static FunctionInvocation toFloat(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(Scalars.TO_FLOAT, expression); } /** * Creates a function invocation for {@code toBoolean({})}. See toBoolean. * @param expression the value to pass to the function. * @return a function call for {@code toBoolean({})}. * @since 2021.2.1 */ static FunctionInvocation toBoolean(Expression expression) { Assertions.notNull(expression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_EXPRESSION_REQUIRED)); return FunctionInvocation.create(Scalars.TO_BOOLEAN, expression); } /** * Creates a function invocation for {@code linenumber({})}. Only applicable inside an * {@code LOAD CSV} clause. * @return a function call for {@code linenumber({})}. * @since 2021.2.1 */ static FunctionInvocation linenumber() { return FunctionInvocation.create(() -> "linenumber"); } /** * Creates a function invocation for {@code file({})}. Only applicable inside an * {@code LOAD CSV} clause. * @return a function call for {@code file({})}. * @since 2021.2.1 */ static FunctionInvocation file() { return FunctionInvocation.create(() -> "file"); } /** * Creates a function invocation for {@code randomUUID({})}. Only applicable inside an * {@code LOAD CSV} clause. * @return a function call for {@code randomUUID({})}. * @since 2022.2.1 */ static FunctionInvocation randomUUID() { return FunctionInvocation.create(() -> "randomUUID"); } /** * Creates a function invocation for {@code length()}. See length. * @param path the path for which the length should be retrieved * @return a function call for {@code length()} on a path. * @since 2023.0.1 */ static FunctionInvocation length(NamedPath path) { Assertions.notNull(path, "The path for length is required."); return FunctionInvocation.create(Scalars.LENGTH, path.getSymbolicName() .orElseThrow(() -> new IllegalArgumentException( Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_NAMED_PATH_REQUIRED)))); } /** * Creates a function invocation for {@code graph.names()}. See graph.names. * @return a function call for {@code graph.names()}. * @since 2023.4.0 */ @Neo4jVersion(minimum = "5.0.0") static FunctionInvocation graphNames() { return FunctionInvocation.create(BuiltInFunctions.Graph.NAMES); } /** * Creates a function invocation for {@code graph.propertiesByName()}. See graph.propertiesByName. * @param name the name of the graph * @return a function call for {@code graph.propertiesByName()}. * @since 2023.4.0 */ @Neo4jVersion(minimum = "5.0.0") static FunctionInvocation graphPropertiesByName(Expression name) { return FunctionInvocation.create(BuiltInFunctions.Graph.PROPERTIES_BY_NAME, name); } /** * Creates a function invocation for {@code graph.byName()}. See graph.byName. * @param name the name of the graph * @return a function call for {@code graph.byName()}. * @since 2023.4.0 */ @Neo4jVersion(minimum = "5.0.0") static FunctionInvocation graphByName(Expression name) { return FunctionInvocation.create(BuiltInFunctions.Graph.BY_NAME, name); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/HasLabelCondition.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.TypedSubtree; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * A condition checking for the presence of labels on nodes or types on relationships. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class HasLabelCondition implements Condition { private final SymbolicName nodeName; private final Visitable labels; private HasLabelCondition(SymbolicName nodeName, List nodeLabels) { this.nodeName = nodeName; this.labels = new TypedSubtree<>(nodeLabels) { @Override public String separator() { return ""; } }; } private HasLabelCondition(SymbolicName nodeName, Labels labels) { this.nodeName = nodeName; this.labels = labels; } static HasLabelCondition create(SymbolicName nodeName, String... labels) { Assertions.notNull(nodeName, "A symbolic name for the node is required."); Assertions.notNull(labels, "Labels to query are required."); Assertions.notEmpty(labels, "At least one label to query is required."); final List nodeLabels = new ArrayList<>(labels.length); for (String label : labels) { nodeLabels.add(new NodeLabel(label)); } return new HasLabelCondition(nodeName, nodeLabels); } static HasLabelCondition create(SymbolicName nodeName, Labels labels) { Assertions.notNull(nodeName, "A symbolic name for the node is required."); Assertions.notNull(labels, "Labels to query are required."); return new HasLabelCondition(nodeName, labels); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.nodeName.accept(visitor); this.labels.accept(visitor); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Hint.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; import org.neo4j.cypherdsl.core.ast.ProvidesAffixes; import org.neo4j.cypherdsl.core.ast.TypedSubtree; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; /** * Visitable implementing hints. See {@link ExposesHints}. * * @author Michael J. Simons * @since 2021.0.0 */ public final class Hint implements Visitable { private final Type type; private final IndexReferences indexReferences; private final IndexProperties optionalProperties; private Hint(Type type, List indexReferences, IndexProperties optionalProperties) { this.type = type; this.indexReferences = new IndexReferences(indexReferences); this.optionalProperties = optionalProperties; } /** * Creates an index hint. Mostly useful when building elements outside the fluent DSL. * @param seek set to true to use the index for seeks only * @param properties the properties to use in the index, must know their container * @return a hint * @since 2021.2.3 */ public static Hint useIndexFor(boolean seek, Property... properties) { Assertions.notEmpty(properties, "Cannot use an index without properties!"); List deferencedProperties = new ArrayList<>(); IndexReference indexReference = null; for (Property property : properties) { Named container = property.getContainer(); Assertions.notNull(container, "Cannot use a property without a reference to a container inside an index hint."); Assertions.isTrue(property.getNames().size() == 1, "One single property is required. Nested properties are not supported."); NodeLabel label; if (container instanceof Node node) { List labels = node.getLabels(); Assertions.isTrue(labels.size() == 1, "Exactly one label is required to define the index."); label = labels.get(0); } else if (container instanceof Relationship relationship) { List types = relationship.getDetails().getTypes(); Assertions.isTrue(types.size() == 1, "Exactly one type is required to define the index."); label = new NodeLabel(types.get(0)); } else { throw new IllegalArgumentException("A property index can only be used for Nodes or Relationships."); } SymbolicName symbolicName = container.getRequiredSymbolicName(); if (indexReference == null) { indexReference = new IndexReference(symbolicName, label); } else if (!indexReference.pointsToSameContainer(symbolicName, label)) { throw new IllegalStateException( "If you want to use more than one index on different nodes you must use multiple `USING INDEX` statements."); } deferencedProperties.add(property.getNames().get(0).getPropertyKeyName()); } return new Hint(seek ? Type.INDEX_SEEK : Type.INDEX, Collections.singletonList(indexReference), new IndexProperties(deferencedProperties)); } /** * Creates an index scan hint. Mostly useful when building elements outside the fluent * DSL. * @param node the node who's label and name should be used to define the scan hint * @return a hint * @since 2021.2.3 */ public static Hint useScanFor(Node node) { Assertions.notNull(node, "Cannot apply a SCAN hint without a node."); List labels = node.getLabels(); Assertions.isTrue(labels.size() == 1, "Exactly one label is required for a SCAN hint."); return new Hint(Type.SCAN, Collections.singletonList(new IndexReference(node.getRequiredSymbolicName(), labels.get(0))), null); } /** * Creates a join hint on one or more symbolic names. * @param name the names that are supposed to provide the join point * @return a hint * @since 2021.2.3 */ public static Hint useJoinOn(SymbolicName... name) { Assertions.notEmpty(name, "At least one name is required to define a JOIN hint."); return new Hint(Type.JOIN_ON, Arrays.stream(name).map(IndexReference::new).toList(), null); } @Override public String toString() { return RendererBridge.render(this); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.type.accept(visitor); this.indexReferences.accept(visitor); Visitable.visitIfNotNull(this.optionalProperties, visitor); visitor.leave(this); } private enum Type implements Visitable { INDEX, INDEX_SEEK, SCAN, JOIN_ON; @Override public String toString() { return RendererBridge.render(this); } } private static final class IndexReference implements Visitable { private final SymbolicName symbolicName; private final NodeLabel optionalLabel; IndexReference(SymbolicName symbolicName) { this(symbolicName, null); } IndexReference(SymbolicName symbolicName, NodeLabel optionalLabel) { this.symbolicName = symbolicName; this.optionalLabel = optionalLabel; } boolean pointsToSameContainer(SymbolicName otherSymbolicName, NodeLabel otherLabel) { return this.symbolicName.equals(otherSymbolicName) && Objects.equals(this.optionalLabel, otherLabel); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.symbolicName.accept(visitor); Visitable.visitIfNotNull(this.optionalLabel, visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } } private static final class IndexReferences extends TypedSubtree { IndexReferences(List indexReferences) { super(indexReferences); } } /** * Internal helper class to wrap up the properties used inside an index. */ private static final class IndexProperties extends TypedSubtree implements ProvidesAffixes { IndexProperties(List properties) { super(properties); } @Override public Optional getPrefix() { return Optional.of("("); } @Override public Optional getSuffix() { return Optional.of(")"); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/IdentifiableElement.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; /** * This interface represents an element that can be for example an identifiable part of * the {@code WITH} clause. It has been introduced to circumvent the absence of union * types in Java and to avoid an overload of {@link StatementBuilder#with(String...)} or * other expressions with an {@code Object...} parameter. This type here allows passing * {@link Named named things}. {@link AliasedExpression aliased expression}, * {@link SymbolicName symbolic names} into a pipeline. *

* There should be no need to implement this on your own. * * @author Michael J. Simons * @since 2021.2.2 */ public sealed interface IdentifiableElement permits AliasedExpression, Asterisk, Named, Property, SymbolicName { /** * Transform this element into an expression. * @return this element as an expression. Will return the same instance if it is * already an expression. * @since 2021.2.2 */ Expression asExpression(); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ImportingWith.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Arrays; import java.util.Objects; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; /** * This type is used in sub-queries, both for sub-queries with an implicit scope such as * {@literal EXISTS{}} and {@literal COUNT{}} and full sub-queries. In the latter case, it * will create and visite multiple {@link With with-instances} so that the import actually * shadows the outer scope. * * @author Michael J. Simons * @param imports the imported expressions * @param renames the renamed expressions, shadowing the outer scope * @since 2023.1.0 */ @API(status = INTERNAL, since = "2023.1.0") record ImportingWith(With imports, With renames) implements Visitable { ImportingWith() { this(null, null); } static ImportingWith of(IdentifiableElement... imports) { With optionalImports; With optionalRenames; ExpressionList returnItems = new ExpressionList(Arrays.stream(imports).map(i -> { if (i instanceof AliasedExpression aliasedExpression) { var delegate = aliasedExpression.getDelegate(); if (delegate instanceof Literal) { return null; } return delegate; } else { return i.asExpression(); } }).filter(Objects::nonNull).toList()); optionalImports = returnItems.isEmpty() ? null : new With(false, returnItems, null, null, null, null); returnItems = new ExpressionList(Arrays.stream(imports) .filter(AliasedExpression.class::isInstance) .map(Expression.class::cast) .toList()); optionalRenames = returnItems.isEmpty() ? null : new With(false, returnItems, null, null, null, null); return new ImportingWith(optionalImports, optionalRenames); } @Override public void accept(Visitor visitor) { Visitable.visitIfNotNull(this.imports, visitor); Visitable.visitIfNotNull(this.renames, visitor); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/InTransactions.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * Used for subqueries {@code IN TRANSACTIONS}. * * @author Michael J. Simons * @since 2022.3.0 */ @API(status = STABLE, since = "2022.3.0") public final class InTransactions implements Visitable { private final Subquery subquery; private final Integer rows; @API(status = INTERNAL) InTransactions(Subquery subquery, Integer rows) { this.subquery = subquery; this.rows = rows; } /** * {@return number of rows in this transaction} */ @API(status = INTERNAL) public Integer getRows() { return this.rows; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.subquery.accept(visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/InternalNodeImpl.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.INTERNAL; /** * An internal implementation of the {@link NodeBase}. It's primary purpose is to have * {@link NodeBase#named(SymbolicName)} and {@link NodeBase#withProperties(MapExpression)} * abstract method to enforce the correct return type. Otherwise one could extend * {@link NodeBase} without overriding those, ignoring unchecked casts and eventually * running into a {@link ClassCastException}. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = INTERNAL, since = "2021.1.0") final class InternalNodeImpl extends NodeBase { InternalNodeImpl() { } InternalNodeImpl(Labels labelExpression, Where innerPredicate) { super(null, null, labelExpression, null, innerPredicate); } InternalNodeImpl(String primaryLabel, String... additionalLabels) { super(primaryLabel, additionalLabels); } InternalNodeImpl(SymbolicName symbolicName, List labels, Labels labelExpression, Properties properties, Where innerPredicate) { super(symbolicName, labels, labelExpression, properties, innerPredicate); } InternalNodeImpl(SymbolicName symbolicName, String primaryLabel, MapExpression properties, String... additionalLabels) { super(symbolicName, primaryLabel, properties, additionalLabels); } @Override public InternalNodeImpl named(SymbolicName newSymbolicName) { Assertions.notNull(newSymbolicName, "Symbolic name is required."); return new InternalNodeImpl(newSymbolicName, this.staticLabels, this.dynamicLabels, this.properties, this.innerPredicate); } @Override public InternalNodeImpl withProperties(MapExpression newProperties) { return new InternalNodeImpl(this.getSymbolicName().orElse(null), this.staticLabels, this.dynamicLabels, Properties.create(newProperties), this.innerPredicate); } @Override public Node where(Expression predicate) { if (predicate == null) { return this; } return new InternalNodeImpl(this.getSymbolicName().orElse(null), this.staticLabels, this.dynamicLabels, this.properties, Where.from(predicate)); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/InternalPropertyImpl.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.INTERNAL; /** * An internal implementation of a {@link Property}. * * @author Michael J. Simons * @since 2021.1.10 */ @API(status = INTERNAL, since = "2021.1.0") final class InternalPropertyImpl implements Property { /** * The reference to the container itself is optional. */ private final Named container; /** * The expression pointing to the {@link #container} above is not. */ private final Expression containerReference; /** * The name of this property. */ private final List names; /** * An optional, external (as in external to the graph) reference. */ private final String externalReference; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") InternalPropertyImpl(Optional container, Expression containerReference, List names, String externalReference) { this.container = container.orElse(null); this.containerReference = containerReference; this.names = names; this.externalReference = externalReference; } static Property create(Named parentContainer, String... names) { SymbolicName requiredSymbolicName = extractRequiredSymbolicName(parentContainer); return new InternalPropertyImpl(Optional.of(parentContainer), requiredSymbolicName, createListOfChainedNames(names), null); } static Property create(Expression containerReference, String... names) { Assertions.notNull(containerReference, "The property container is required."); return new InternalPropertyImpl(Optional.empty(), containerReference, createListOfChainedNames(names), null); } static Property create(Named parentContainer, Expression lookup) { SymbolicName requiredSymbolicName = extractRequiredSymbolicName(parentContainer); return new InternalPropertyImpl(Optional.of(parentContainer), requiredSymbolicName, Collections.singletonList(PropertyLookup.forExpression(lookup)), null); } static Property create(Expression containerReference, Expression lookup) { return new InternalPropertyImpl(Optional.empty(), containerReference, Collections.singletonList(PropertyLookup.forExpression(lookup)), null); } private static List createListOfChainedNames(String... names) { Assertions.notEmpty(names, "The properties name is required."); if (names.length == 1) { return Collections.singletonList(PropertyLookup.forName(names[0])); } else { return Arrays.stream(names) .map(PropertyLookup::forName) .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); } } private static SymbolicName extractRequiredSymbolicName(Named parentContainer) { try { return parentContainer.getRequiredSymbolicName(); } catch (IllegalStateException ex) { throw new IllegalArgumentException( "A property derived from a node or a relationship needs a parent with a symbolic name."); } } @Override public List getNames() { return this.names; } @Override public Named getContainer() { return this.container; } @Override public Expression getContainerReference() { return this.containerReference; } @Override public String getName() { return (this.externalReference != null) ? this.externalReference : this.names.stream() .map(PropertyLookup::getPropertyKeyName) .map(SymbolicName::getValue) .collect(Collectors.joining(".")); } @Override public Property referencedAs(String newReference) { return new InternalPropertyImpl(Optional.ofNullable(this.container), this.containerReference, this.names, newReference); } @Override public Operation to(Expression expression) { return Operations.set(this, expression); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.containerReference.accept(visitor); this.names.forEach(name -> name.accept(visitor)); visitor.leave(this); } @Override public Expression asExpression() { return this; } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/InternalRelationshipImpl.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.INTERNAL; /** * An internal implementation of the {@link RelationshipBase}. It's primary purpose is to * have {@link RelationshipBase#named(SymbolicName)} and * {@link RelationshipBase#withProperties(MapExpression)} abstract method to enforce the * correct return type. Otherwise, one could extend {@link RelationshipBase} without * overriding those, ignoring unchecked casts and eventually running into a * {@link ClassCastException}. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = INTERNAL, since = "2021.1.0") final class InternalRelationshipImpl extends RelationshipBase, NodeBase, InternalRelationshipImpl> { InternalRelationshipImpl(SymbolicName symbolicName, Node left, Direction direction, QuantifiedPathPattern.Quantifier quantifier, Node right, String... types) { super(symbolicName, left, direction, quantifier, right, types); } InternalRelationshipImpl(SymbolicName symbolicName, Node left, Direction direction, Properties properties, QuantifiedPathPattern.Quantifier quantifier, Node right, String... types) { super(symbolicName, left, direction, properties, quantifier, right, types); } InternalRelationshipImpl(Node left, Details details, QuantifiedPathPattern.Quantifier quantifier, Node right) { super(left, details, quantifier, right); } @Override public InternalRelationshipImpl named(SymbolicName newSymbolicName) { return new InternalRelationshipImpl(this.left, this.details.named(newSymbolicName), this.quantifier, this.right); } @Override public InternalRelationshipImpl withProperties(MapExpression newProperties) { return new InternalRelationshipImpl(this.left, this.details.with(Properties.create(newProperties)), this.quantifier, this.right); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/KeyValueMapEntry.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * Helper class, only for internal use. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class KeyValueMapEntry implements Expression { private final String key; private final Expression value; private KeyValueMapEntry(String key, Expression value) { this.key = key; this.value = value; } /** * Create a new {@link KeyValueMapEntry}. This is hardly useful in direct usage, but * might be handy to compose clauses outside the fluent api. * @param key the key of this entry * @param value the value of this entry * @return a new, immutable map entry. * @since 2021.2.3 */ @API(status = STABLE, since = "2021.2.3") public static KeyValueMapEntry create(String key, Expression value) { Assertions.notNull(key, "Key is required."); Assertions.notNull(value, "Value is required."); return new KeyValueMapEntry(key, value); } /** * {@return the key of this entry} */ @API(status = INTERNAL) public String getKey() { return this.key; } /** * {@return the value of this entry} */ @API(status = INTERNAL) public Expression getValue() { return this.value; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.value.accept(visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/LabelExpression.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import org.neo4j.cypherdsl.core.ast.Visitable; /** * This class is not used or supported anymore and will be removed in the next major * version of Cypher-DSL. * * @author Michael J. Simons * @param type whether this is a leaf or another node * @param negated a flag if this subtree is negated * @param value a single value or a list of value for {@link Type#COLON_DISJUNCTION} or * {@link Type#COLON_CONJUNCTION} * @param lhs the left hand site of this tree * @param rhs the right hand site of this tree * @since 2023.0.2 * @deprecated Use {@link Labels} accessible via {@link Cypher#exactlyLabel(String)}, * {@link Cypher#allLabels(Expression)} or {@link Cypher#anyLabel(Expression)} */ @Deprecated(forRemoval = true) public record LabelExpression(Type type, boolean negated, List value, LabelExpression lhs, LabelExpression rhs) implements Visitable { /** * Creates an immutable label expression. * @param type whether this is a leaf or another node * @param negated a flag if this subtree is negated * @param value a single value or a list of value for {@link Type#COLON_DISJUNCTION} * or {@link Type#COLON_CONJUNCTION} * @param lhs the left hand site of this tree * @param rhs the right hand site of this tree */ public LabelExpression { value = (value != null) ? List.copyOf(value) : null; } /** * Creates a leaf expression from a string. * @param value the leaf value */ public LabelExpression(String value) { this(Type.LEAF, false, List.of(value), null, null); } /** * Create a conjunction. * @param next the expression to add * @return a new expression */ public LabelExpression and(LabelExpression next) { return new LabelExpression(Type.CONJUNCTION, false, null, this, next); } /** * Create a disjunction. * @param next the expression to add * @return a new expression */ public LabelExpression or(LabelExpression next) { return new LabelExpression(Type.DISJUNCTION, false, null, this, next); } /** * Negates this expression. * @return a new expression */ public LabelExpression negate() { return new LabelExpression(this.type, !this.negated, this.value, this.lhs, this.rhs); } /** * Type of this expression. * * @deprecated No replacement, see {@link Labels} and its associated type */ @Deprecated(forRemoval = true) public enum Type { /** * A leaf. */ LEAF(""), /** * A list of values, conjugated. */ COLON_CONJUNCTION(":"), /** * A list of values, disjoined. */ COLON_DISJUNCTION(":"), /** * A conjunction. */ CONJUNCTION("&"), /** * A disjunction. */ DISJUNCTION("|"); private final String value; Type(String value) { this.value = value; } /** * {@return a representation of this type} */ public String getValue() { return this.value; } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Labels.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.Collection; import java.util.EnumSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.neo4j.cypherdsl.core.ast.TypedSubtree; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; /** * Represents label expressions, that can be in some way combined and may contain dynamic * expressions as described here. * Labels are essentially modelled as tree, so that they can be properly rendered.
* We are using the term Labels here in accordance with GQL, in which a Label is a quality * for both a node and a relationship element. * * @author Michael J. Simons * @since 2025.1.0 */ public final class Labels implements Visitable { @SuppressWarnings("removal") @Deprecated(forRemoval = true) static Labels of(LabelExpression labelExpression) { if (labelExpression == null) { return null; } var type = Type.valueOf(labelExpression.type().name()); var value = labelExpression.value(); List adaptedValue = (value != null) ? List.of(new Value(Modifier.STATIC, new TypedSubtree<>(value.stream().map(NodeLabel::new).toList()) { @Override public String separator() { return ""; } })) : List.of(); return new Labels(type, labelExpression.negated(), adaptedValue, Labels.of(labelExpression.lhs()), Labels.of(labelExpression.rhs())); } static Labels exactly(String label) { return new Labels(Modifier.STATIC, new NodeLabel(label)); } /** * Returns a new dynamic label expression matching all labels. * @param expression the labels to match * @return a new dynamic label expression matching all labels */ static Labels all(Expression expression) { return new Labels(Modifier.ALL, expression); } /** * Returns a new dynamic label expression matching any labels. * @param expression the labels to match * @return a new dynamic label expression matching any labels */ static Labels any(Expression expression) { return new Labels(Modifier.ANY, expression); } /** * Creates a colon conjunction of all values. * @param values the values for the conjunction * @return a new labels expression */ public static Labels colonConjunction(Collection values) { return new Labels(Type.COLON_CONJUNCTION, false, (values != null) ? List.copyOf(values) : List.of(), null, null); } /** Whether this is a leaf or another node. */ private final Type type; /** A flag if this subtree is negated. */ private final boolean negated; /** The actual value. */ private final List value; /** * Optional left hand site of this tree. */ private final Labels lhs; /** * Optional right hand site of this tree. */ private final Labels rhs; Labels(Modifier modifier, Visitable labels) { this(Type.LEAF, false, List.of(new Value(modifier, labels)), null, null); } Labels(Type type, boolean negated, List value, Labels lhs, Labels rhs) { this.lhs = lhs; this.type = type; this.negated = negated; this.value = value; this.rhs = rhs; } /** * Create a conjunction. * @param next the expression to add * @return a new expression */ public Labels and(Labels next) { return new Labels(Type.CONJUNCTION, false, null, this, next); } /** * Create a disjunction. * @param next the expression to add * @return a new expression */ public Labels or(Labels next) { return new Labels(Type.DISJUNCTION, false, null, this, next); } /** * Negates this expression. * @return a new expression */ public Labels negate() { return new Labels(this.type, !this.negated, this.value, this.lhs, this.rhs); } /** * Creates a colon based conjunction. * @param other the other labels to create a conjunction with * @return a new labels expression */ public Labels conjunctionWith(Labels other) { return colonJunction(other, Type.COLON_CONJUNCTION); } public Labels disjunctionWith(Labels other) { return colonJunction(other, Type.COLON_DISJUNCTION); } private Labels colonJunction(Labels other, Type colonDisjunction) { var hlp = new ArrayList(this.value.size() + other.getValue().size()); hlp.addAll(this.value); hlp.addAll(other.value); return new Labels(colonDisjunction, false, List.copyOf(hlp), null, null); } /** * {@return the optional left hand side of this node} */ public Labels getLhs() { return this.lhs; } /** * {@return true if this is a negated expression} */ public boolean isNegated() { return this.negated; } /** * {@return the optional right hand side of this node} */ public Labels getRhs() { return this.rhs; } public Type getType() { return this.type; } public List getValue() { return this.value; } /** * {@return the list of all static labels} */ public Collection getStaticValues() { var staticValues = new LinkedHashSet(); collectLabels(this, staticValues); return staticValues; } private static void collectLabels(Labels l, Set labels) { if (l == null) { return; } var current = l.getType(); collectLabels(l.getLhs(), labels); if (current == Labels.Type.LEAF || (l.getLhs() == null && l.getRhs() == null && EnumSet.of(Type.COLON_CONJUNCTION, Type.COLON_DISJUNCTION).contains(l.getType()))) { l.getValue().forEach(v -> v.accept(segment -> { if (segment instanceof NodeLabel label) { labels.add(label.getValue()); } })); } collectLabels(l.getRhs(), labels); } boolean canBeUsedInUpdate() { var b = this.lhs == null && this.rhs == null && EnumSet.of(Type.LEAF, Type.COLON_CONJUNCTION).contains(this.type); if (b) { b = !this.value.isEmpty() && this.value.get(0).modifier == Modifier.ALL; } return b; } public boolean isEmpty() { return (this.value == null || this.value.isEmpty()) && (this.lhs == null || this.lhs.isEmpty()) && (this.rhs == null || this.rhs.isEmpty()); } /** * An enum describing whether a {@link Labels} should match all or any labels the * expression resolves to. */ public enum Modifier { /** Dynamically matching all labels. */ ALL, /** Dynamically matching any label. */ ANY, /** Static label expression. */ STATIC } /** * The content of this expression. Depending on the modifier, the expression can be * dynamically or statically matched. The actual {@code value} must resolve to an * expression that either is *

    *
  • A single string
  • *
  • A list of strings
  • *
  • A parameter holding either a single or a list of strings
  • *
* * @param modifier modifier for the given content * @param visitable the actual value */ public record Value(Modifier modifier, Visitable visitable) implements Visitable { @Override public void accept(Visitor visitor) { visitor.enter(this); this.visitable.accept(visitor); visitor.leave(this); } } /** * Type of this expression. */ public enum Type { /** * A leaf. */ LEAF(""), /** * A list of values, conjugated. */ COLON_CONJUNCTION(":"), /** * A list of values, disjoined. */ COLON_DISJUNCTION(":"), /** * A conjunction. */ CONJUNCTION("&"), /** * A disjunction. */ DISJUNCTION("|"); private final String value; Type(String value) { this.value = value; } /** * {@return a representation of this type} */ public String getValue() { return this.value; } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Limit.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * Representation of the {@code LIMIT} clause. * * @author Gerrit Meier * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class Limit implements Visitable { private final Expression limitExpression; private Limit(Expression limitExpression) { this.limitExpression = limitExpression; } static Limit create(Expression value) { Assertions.notNull(value, "A limit cannot have a null value."); return new Limit(value); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.limitExpression.accept(visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ListComprehension.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * See ListComprehension * and the * corresponding cypher manual entry. * * @author Michael J. Simons * @since 1.0.1 */ @API(status = STABLE, since = "1.0.1") public final class ListComprehension implements Expression { // Modelling from the FilterExpression: // https://s3.amazonaws.com/artifacts.opencypher.org/M14/railroad/FilterExpression.html // */ /** * The variable for the where part. */ private final SymbolicName variable; /** * The list expression. No further assertions are taken to check beforehand if it is * actually a Cypher List atm. */ private final Expression listExpression; /** * Filtering on the list. */ private final Where where; /** * The new list to be returned. */ private final Expression listDefinition; private ListComprehension(SymbolicName variable, Expression listExpression, Where where, Expression listDefinition) { this.variable = variable; this.listExpression = listExpression; this.where = where; this.listDefinition = listDefinition; } static OngoingDefinitionWithVariable with(SymbolicName variable) { Assertions.notNull(variable, "A variable is required"); return new Builder(variable); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.variable.accept(visitor); Operator.IN.accept(visitor); this.listExpression.accept(visitor); Visitable.visitIfNotNull(this.where, visitor); if (this.listDefinition != null) { Operator.PIPE.accept(visitor); this.listDefinition.accept(visitor); } visitor.leave(this); } /** * {@link #in(Expression)} must be used to define the source list. */ public interface OngoingDefinitionWithVariable { /** * Create a list comprehension past on a literal list. * @param list the source list. * @return an ongoing definition */ @CheckReturnValue OngoingDefinitionWithList in(Expression list); } /** * Allows to add a where clause into the definition of the list. */ public interface OngoingDefinitionWithList extends OngoingDefinitionWithoutReturn { /** * Adds a {@code WHERE} clause to this comprehension. * @param condition the condition to start the {@code WHERE} clause with. * @return an ongoing definition */ @CheckReturnValue OngoingDefinitionWithoutReturn where(Condition condition); } /** * Provides the final step of defining a list comprehension. */ public interface OngoingDefinitionWithoutReturn { /** * Defines the {@code RETURN} clause. * @param variables the elements to be returned from the list * @return the final definition of the list comprehension * @see #returning(Expression...) */ default ListComprehension returning(Named... variables) { return returning(Expressions.createSymbolicNames(variables)); } /** * Defines the {@code RETURN} clause. * @param listDefinition defines the elements to be returned from the pattern * @return the final definition of the list comprehension */ ListComprehension returning(Expression... listDefinition); /** * Defines an empty {@code RETURN} clause. * @return returns the list comprehension as is, without a {@literal WHERE} and * returning each element of the original list */ ListComprehension returning(); } private static final class Builder implements OngoingDefinitionWithVariable, OngoingDefinitionWithList { private final SymbolicName variable; private Expression listExpression; private Where where; private Builder(SymbolicName variable) { this.variable = variable; } @Override public OngoingDefinitionWithList in(Expression list) { this.listExpression = list; return this; } @Override public OngoingDefinitionWithoutReturn where(Condition condition) { this.where = new Where(condition); return this; } @Override public ListComprehension returning() { return new ListComprehension(this.variable, this.listExpression, this.where, null); } @Override public ListComprehension returning(Expression... expressions) { return new ListComprehension(this.variable, this.listExpression, this.where, ListExpression.listOrSingleExpression(expressions)); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ListExpression.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * Represents a list expression as in * {@code [expression1, expression2, ..., expressionN]}. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class ListExpression implements Expression { private final ExpressionList content; private ListExpression(ExpressionList content) { this.content = content; } static Expression listOrSingleExpression(Expression... expressions) { Assertions.notNull(expressions, "Expressions are required."); Assertions.notEmpty(expressions, "At least one expression is required."); if (expressions.length == 1) { return expressions[0]; } else { return ListExpression.create(expressions); } } static ListExpression create(Expression... expressions) { return new ListExpression(new ExpressionList(expressions)); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.content.accept(visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ListLiteral.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * A list of literals. * * @author Gerrit Meier * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class ListLiteral extends LiteralBase>> { ListLiteral(Iterable> content) { super(content); } @Override public String asString() { return StreamSupport.stream(this.content.spliterator(), false) .map(Literal::asString) .collect(Collectors.joining(", ", "[", "]")); } @Override public Iterable> getContent() { return super.content; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ListOperator.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Optional; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.ProvidesAffixes; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.INTERNAL; /** * Represents a range literal applied to another expression. * * @author Michael J. Simons * @since 2020.1.0 */ @API(status = API.Status.EXPERIMENTAL, since = "2020.1.0") public final class ListOperator implements Expression, Visitable { /** * A literal for the dots. */ static final Literal DOTS = new LiteralBase<>("..") { @Override public String asString() { return this.content; } }; /** * The target expression to which the literal should be applied. */ private final Expression targetExpression; /** * The actual operator's details. */ private final Details details; private ListOperator(Expression targetExpression, Expression optionalStart, Literal dots, Expression optionalEnd) { this.targetExpression = targetExpression; this.details = new Details(optionalStart, dots, optionalEnd); } /** * Creates a closed range with given boundaries. * @param targetExpression the target expression for the range * @param start the inclusive start * @param end the exclusive end * @return a range literal. */ static ListOperator subList(Expression targetExpression, Expression start, Expression end) { Assertions.notNull(targetExpression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_RANGE_TARGET_REQUIRED)); Assertions.notNull(start, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_RANGE_START_REQUIRED)); Assertions.notNull(end, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_RANGE_END_REQUIRED)); return new ListOperator(targetExpression, start, DOTS, end); } /** * Creates an open range starting at {@code start}. * @param targetExpression the target expression for the range * @param start the inclusive start * @return a range literal. */ static ListOperator subListFrom(Expression targetExpression, Expression start) { Assertions.notNull(targetExpression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_RANGE_TARGET_REQUIRED)); Assertions.notNull(start, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_RANGE_START_REQUIRED)); return new ListOperator(targetExpression, start, DOTS, null); } /** * Creates an open range starting at {@code start}. * @param targetExpression the target expression for the range * @param end the exclusive end * @return a range literal. */ static ListOperator subListUntil(Expression targetExpression, Expression end) { Assertions.notNull(targetExpression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_RANGE_TARGET_REQUIRED)); Assertions.notNull(end, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_RANGE_END_REQUIRED)); return new ListOperator(targetExpression, null, DOTS, end); } /** * Creates a single valued range at {@code index}. * @param targetExpression the target expression for the range * @param index the index of the range * @return a range literal. */ static ListOperator valueAt(Expression targetExpression, Expression index) { Assertions.notNull(targetExpression, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_RANGE_TARGET_REQUIRED)); Assertions.notNull(index, Cypher.MESSAGES.getString(MessageKeys.ASSERTIONS_RANGE_INDEX_REQUIRED)); return new ListOperator(targetExpression, index, null, null); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.targetExpression.accept(visitor); this.details.accept(visitor); visitor.leave(this); } @API(status = INTERNAL, since = "1.0") static final class Details implements Visitable, ProvidesAffixes { /** * An optional start for the range (inclusive if given). */ private final Expression optionalStart; /** * Optional dots between the start and end. */ private final Literal dots; /** * An optional end for the range (exclusive if given). */ private final Expression optionalEnd; Details(Expression optionalStart, Literal dots, Expression optionalEnd) { this.optionalStart = optionalStart; this.dots = dots; this.optionalEnd = optionalEnd; } @Override public void accept(Visitor visitor) { visitor.enter(this); Visitable.visitIfNotNull(this.optionalStart, visitor); Visitable.visitIfNotNull(this.dots, visitor); Visitable.visitIfNotNull(this.optionalEnd, visitor); visitor.leave(this); } @Override public Optional getPrefix() { return Optional.of("["); } @Override public Optional getSuffix() { return Optional.of("]"); } @Override public String toString() { return RendererBridge.render(this); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ListPredicate.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; /** * A list predicate. * * @author Michael J. Simons * @since 1.1 / */ @API(status = INTERNAL, since = "1.1") final class ListPredicate implements Expression { /** * The variable for the where part. */ private final SymbolicName variable; /** * The list expression. No further assertions are taken to check beforehand if it is * actually a Cypher List atm. */ private final Expression listExpression; /** * Filtering on the list. */ private final Where where; ListPredicate(SymbolicName variable, Expression listExpression, Where where) { this.variable = variable; this.listExpression = listExpression; this.where = where; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.variable.accept(visitor); Operator.IN.accept(visitor); this.listExpression.accept(visitor); this.where.accept(visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Literal.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * Represents a literal with an optional content. * * @param type of content * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface Literal extends Expression { /** * The string representation should be designed in such a way the a renderer can use * it correctly in the given context of the literal, i.e. a literal containing a * string should quote that string and escape all reserved characters. * @return a string representation to be used literally in a cypher statement. */ String asString(); /** * Retrieves the actual content of this literal, might not be supported by all * literals. * @return the actual content of this literal * @since 2023.4.0 */ default T getContent() { throw new UnsupportedOperationException("Retrieving content not supported"); } /** * Thrown when a given object cannot be used as a Cypher-DSL-Literal. * * @since 2021.1.0 */ @API(status = STABLE, since = "2021.1.0") final class UnsupportedLiteralException extends IllegalArgumentException { private static final long serialVersionUID = 864563506445498829L; /** * Value holding the unsupported type. */ private final Class unsupportedType; UnsupportedLiteralException(String message, Object unsupportedObject) { super(message); this.unsupportedType = unsupportedObject.getClass(); } UnsupportedLiteralException(Object unsupportedObject) { super("Unsupported literal type: " + unsupportedObject.getClass()); this.unsupportedType = unsupportedObject.getClass(); } /** * {@return the type that wasn't supported as literal} */ public Class getUnsupportedType() { return this.unsupportedType; } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/LiteralBase.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Objects; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.INTERNAL; /** * Represents a literal with an optional content. * * @param type of content * @author Michael J. Simons * @since 2021.1.0 */ @API(status = INTERNAL, since = "2021.1.0") abstract class LiteralBase implements Literal { /** * A literal for the blank. */ static final Literal BLANK = new LiteralBase<>(" ") { @Override public String asString() { return this.content; } }; /** * The content of this literal. */ protected final T content; /** * Creates a new literal from the given content. * @param content the content of the new literal */ protected LiteralBase(T content) { this.content = content; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } LiteralBase that = (LiteralBase) o; return this.content.equals(that.content); } @Override public int hashCode() { return Objects.hash(this.content); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/LoadCSVStatementBuilder.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.net.URI; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.DefaultLoadCSVStatementBuilder.PrepareLoadCSVStatementImpl; import static org.apiguardian.api.API.Status.STABLE; /** * A builder dedicated to the creation of the {@code LOAD CSV} statement. * * @author Michael J. Simons * @since 2021.2.1 */ @API(status = STABLE, since = "2021.2.1") public interface LoadCSVStatementBuilder extends StatementBuilder { /** * Starts building a {@code LOAD CSV} clause by using a periodic commit. * @param rate the rate to be used. No checks are done on the rate, the database will * verify valid values. * @return an ongoing definition of a {@code LOAD CSV} clause */ static ExposesLoadCSV usingPeriodicCommit(Integer rate) { return new PrepareLoadCSVStatementImpl(rate); } /** * Starts building a {@code LOAD CSV}. * @param from the {@link URI} to load data from. Any uri that is resolvable by the * database itself is valid. * @param withHeaders set to {@literal true} if the csv file contains header * @return an ongoing definition of a {@code LOAD CSV} clause */ static OngoingLoadCSV loadCSV(URI from, boolean withHeaders) { return new PrepareLoadCSVStatementImpl(from, withHeaders); } /** * Configure a field terminator in case the fields aren't separated with the default * {@code ,}. * @param fieldTerminator a new field terminator * @return a statement builder supporting all available clauses */ StatementBuilder withFieldTerminator(String fieldTerminator); /** * An instance of this interface will be provided after pointing the database to a * valid {@link URI} of a CSV resource. */ interface OngoingLoadCSV { /** * Configure the alias for each line contained in the CSV resource. * @param alias the alias for each line * @return a statement builder supporting all available clauses plus an option to * configure the field terminator */ default LoadCSVStatementBuilder as(SymbolicName alias) { return as(alias.getValue()); } /** * Configure the alias for each line contained in the CSV resource. * @param alias the alias for each line * @return a statement builder supporting all available clauses plus an option to * configure the field terminator */ LoadCSVStatementBuilder as(String alias); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/MapExpression.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.TypedSubtree; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * A dedicated map expression. *

* Most of the comparison methods on this expression will not result in a sensible query * fragment. A {@link MapExpression} is be useful as a concrete parameter to functions or * as properties on {@link Node nodes} or {@link Relationship relationships}. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class MapExpression extends TypedSubtree implements Expression { private MapExpression(List children) { super(children); } static MapExpression create(Map map) { Object[] args = new Object[map.size() * 2]; int i = 0; for (Map.Entry entry : map.entrySet()) { Object value = entry.getValue(); args[i++] = entry.getKey(); args[i++] = (value instanceof Expression) ? value : Cypher.literalOf(value); } return create(false, args); } static MapExpression create(boolean sort, Object... input) { Assertions.isTrue(input.length % 2 == 0, "Need an even number of input parameters"); List newContent = new ArrayList<>(input.length / 2); Set knownKeys = new HashSet<>(); for (int i = 0; i < input.length; i += 2) { Object keyCandidate = input[i]; String key; if (keyCandidate instanceof String v) { key = v; } else if (keyCandidate instanceof Property property) { List names = property.getNames(); if (names.size() != 1) { throw new IllegalArgumentException("Nested properties are not supported in a map expression"); } key = names.get(0).getPropertyKeyName().getValue(); } else { throw new IllegalStateException("Key needs to be of type String or Property."); } Assertions.isInstanceOf(Expression.class, input[i + 1], "Value needs to be of type Expression."); Assertions.isTrue(!knownKeys.contains(input[i]), "Duplicate key '" + input[i] + "'"); final KeyValueMapEntry entry = KeyValueMapEntry.create(key, (Expression) input[i + 1]); newContent.add(entry); knownKeys.add(entry.getKey()); } if (sort) { newContent.sort(Comparator.comparing(o -> ((KeyValueMapEntry) o).getKey())); } return new MapExpression(newContent); } static MapExpression withEntries(List entries) { return new MapExpression(entries); } MapExpression addEntries(List entries) { List newContent = new ArrayList<>(super.children.size() + entries.size()); newContent.addAll(super.children); newContent.addAll(entries); return new MapExpression(newContent); } @Override protected Visitable prepareVisit(Expression child) { return Expressions.nameOrExpression(child); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/MapLiteral.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Map; import java.util.stream.Collectors; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * A map of literals. * * @author Aaron Hiniker * @since 2023.2.0 */ @API(status = STABLE, since = "2023.2.0") public final class MapLiteral extends LiteralBase>> { MapLiteral(Map> content) { super(content); } @Override public String asString() { return this.content.entrySet() .stream() .map(entry -> entry.getKey() + ": " + entry.getValue().asString()) .collect(Collectors.joining(", ", "{", "}")); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/MapProjection.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * Represents a map projection as described here. * * @author Michael J. Simons */ @API(status = STABLE, since = "1.0") public final class MapProjection implements Expression { private final SymbolicName name; private final MapExpression map; MapProjection(SymbolicName name, MapExpression map) { this.name = name; this.map = map; } /** * Create a new map projection with the given, mixed content. * @param name the symbolic name of this project * @param content the projected content * @return a new map projection * @since 2021.2.3 */ @API(status = INTERNAL, since = "2023.9.0") public static MapProjection create(SymbolicName name, Object... content) { return new MapProjection(name, MapExpression.withEntries(createNewContent(false, content))); } /** * Create a new map projection with the given, mixed content. * @param name the symbolic name of this project * @param content the projected content * @return a new map projection * @since 2024.1.1 */ @API(status = INTERNAL, since = "2024.1.1") public static MapProjection sorted(SymbolicName name, Object... content) { return new MapProjection(name, MapExpression.withEntries(createNewContent(true, content))); } private static Object contentAt(Object[] content, int i) { Object currentObject = content[i]; if (currentObject instanceof Expression expression) { return Expressions.nameOrExpression(expression); } else if (currentObject instanceof Named named) { return named.getSymbolicName().map(Object.class::cast).orElse(currentObject); } return currentObject; } private static List createNewContent(boolean sort, Object... content) { final List newContent = new ArrayList<>(content.length); final Set knownKeys = new HashSet<>(); String lastKey = null; Expression lastExpression = null; int i = 0; while (i < content.length) { Object next; if (i + 1 >= content.length) { next = null; } else { next = contentAt(content, i + 1); } Object current = contentAt(content, i); if (current instanceof String stringValue) { if (next instanceof Expression expression) { lastKey = stringValue; lastExpression = expression; i += 2; } else { lastExpression = PropertyLookup.forName((String) current); i += 1; } } else if (current instanceof Expression expression) { lastExpression = expression; i += 1; } if (lastExpression instanceof Asterisk) { lastExpression = PropertyLookup.wildcard(); } if (lastKey != null) { Assertions.isTrue(!knownKeys.contains(lastKey), "Duplicate key '" + lastKey + "'"); newContent.add(KeyValueMapEntry.create(lastKey, lastExpression)); knownKeys.add(lastKey); } else if (lastExpression instanceof SymbolicName || lastExpression instanceof PropertyLookup) { newContent.add(lastExpression); } else if (lastExpression instanceof Property property) { List names = property.getNames(); if (names.size() > 1) { throw new IllegalArgumentException("Cannot project nested properties!"); } newContent.addAll(names); } else if (lastExpression instanceof AliasedExpression aliasedExpression) { newContent.add(KeyValueMapEntry.create(aliasedExpression.getAlias(), aliasedExpression)); } else if (lastExpression instanceof KeyValueMapEntry) { newContent.add(lastExpression); } else if (lastExpression == null) { throw new IllegalArgumentException("Could not determine an expression from the given content!"); } else { throw new IllegalArgumentException(lastExpression + " of type " + lastExpression.getClass() + " cannot be used with an implicit name as map entry."); } lastKey = null; lastExpression = null; } if (sort) { newContent.sort((o1, o2) -> { if (o1 instanceof KeyValueMapEntry kvm1 && o2 instanceof KeyValueMapEntry kvm2) { return kvm1.getKey().compareTo(kvm2.getKey()); } else if (o1 instanceof PropertyLookup pl1 && o2 instanceof PropertyLookup pl2) { if (pl1 == PropertyLookup.wildcard()) { return -1; } else if (pl2 == PropertyLookup.wildcard()) { return 1; } return pl1.getPropertyKeyName().getValue().compareTo(pl2.getPropertyKeyName().getValue()); } else if (o1 instanceof PropertyLookup) { return 1; } return -1; }); } return newContent; } /** * Adds additional content. The current projection is left unchanged and a new one is * returned. * @param content the additional content for a new projection. * @return a new map projection with additional content. */ public MapProjection and(Object... content) { return new MapProjection(this.name, this.map.addEntries(createNewContent(false, content))); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.name.accept(visitor); this.map.accept(visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Match.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * See Match. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class Match extends AbstractClause implements ReadingClause { final Pattern pattern; private final boolean optional; /** * A Neo4j extension to the match clause that allows to specify hints via the * {@code USING} clause. */ private final List hints; private final Where optionalWhere; Match(boolean optional, Pattern pattern, Where optionalWhere, List optionalHints) { this.optional = optional; this.pattern = pattern; this.optionalWhere = optionalWhere; this.hints = (optionalHints != null) ? new ArrayList<>(optionalHints) : Collections.emptyList(); } /** * {@return true if this is an optional match} */ @API(status = INTERNAL) public boolean isOptional() { return this.optional; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.pattern.accept(visitor); this.hints.forEach(value -> value.accept(visitor)); Visitable.visitIfNotNull(this.optionalWhere, visitor); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Merge.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * See Create. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class Merge extends AbstractClause implements UpdatingClause { private final Pattern pattern; private final List onCreateOrMatchEvents; Merge(Pattern pattern, List mergeActions) { this.pattern = pattern; this.onCreateOrMatchEvents = new ArrayList<>(); this.onCreateOrMatchEvents.add(LiteralBase.BLANK); this.onCreateOrMatchEvents.addAll(mergeActions); } /** * {@return true if there are any events defined for the merge statement} */ @API(status = INTERNAL) public boolean hasEvents() { return !this.onCreateOrMatchEvents.isEmpty(); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.pattern.accept(visitor); this.onCreateOrMatchEvents.forEach(s -> s.accept(visitor)); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/MergeAction.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * An action or event that happens after a {@code MERGE} clause. It can either be one of * two types: {@link Type#ON_CREATE} or {@link Type#ON_MATCH}. * *

* Both events support the setting of properties, but not removing or adding labels. * Multiple properties should be set in one action, but Cypher and openCypher * allow for multiple {@link MergeAction merge actions}, with the same or different types. * * @author Michael J. Simons * @since 2020.1.2 */ @API(status = STABLE, since = "2020.1.2") public final class MergeAction implements Visitable { private final Type type; private final UpdatingClause set; private MergeAction(Type type, UpdatingClause set) { this.type = type; this.set = set; } /** * Creates a new merge action. Mostly useful when building the AST outside the fluent * DSL. * @param type the type of the action * @param set the corresponding updating clause * @return an immutable action * @since 2021.3.0 */ public static MergeAction of(Type type, Set set) { return new MergeAction(type, set); } @Override public String toString() { return RendererBridge.render(this); } /** * {@return event type of this action} */ @API(status = INTERNAL) public Type getType() { return this.type; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.set.accept(visitor); visitor.leave(this); } /** * The type of the action. */ public enum Type { /** * Triggered when a pattern has been created. */ ON_CREATE, /** * Triggered when a pattern has been fully matched. */ ON_MATCH } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/MessageKeys.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; /** * The message keys available in the {@code messages}-bundle. * * @author Michael J. Simons */ final class MessageKeys { static final String ASSERTIONS_EXPRESSION_REQUIRED = "assertions.expression-required"; static final String ASSERTIONS_EXPRESSIONS_REQUIRED = "assertions.expressions-required"; static final String ASSERTIONS_AT_LEAST_ONE_EXPRESSION_REQUIRED = "assertions.at-least-one-expression-required"; static final String ASSERTIONS_NODE_REQUIRED = "assertions.node-required"; static final String ASSERTIONS_RELATIONSHIP_REQUIRED = "assertions.relationship-required"; static final String ASSERTIONS_VARIABLE_REQUIRED = "assertions.variable-required"; static final String ASSERTIONS_COMPONENTS_REQUIRED = "assertions.components-required"; static final String ASSERTIONS_TEMPORAL_VALUE_REQUIRED = "assertions.temporal-value-required"; static final String ASSERTIONS_YEAR_REQUIRED = "assertions.year-required"; static final String ASSERTIONS_MONTH_REQUIRED = "assertions.month-required"; static final String ASSERTIONS_DAY_REQUIRED = "assertions.day-required"; static final String ASSERTIONS_TZ_REQUIRED = "assertions.tz-required"; static final String ASSERTIONS_RANGE_TARGET_REQUIRED = "assertions.range-target-required"; static final String ASSERTIONS_RANGE_INDEX_REQUIRED = "assertions.range-index-required"; static final String ASSERTIONS_RANGE_START_REQUIRED = "assertions.range-start-required"; static final String ASSERTIONS_RANGE_END_REQUIRED = "assertions.range-end-required"; static final String ASSERTIONS_EXPRESSION_FOR_FUNCTION_REQUIRED = "assertions.expression-for-function-required"; static final String ASSERTIONS_PATTERN_FOR_FUNCTION_REQUIRED = "assertions.pattern-for-function-required"; static final String ASSERTIONS_AT_LEAST_ONE_ARG_REQUIRED = "assertions.at-least-one-arg-required"; static final String ASSERTIONS_CORRECT_USAGE_OF_DISTINCT = "assertions.correct-usage-of-distinct"; static final String ASSERTIONS_NAMED_PATH_REQUIRED = "assertions.named-path-required"; static final String ASSERTIONS_REQUIRES_NAME_FOR_MUTATION = "assertions.requires-name-for-mutation"; private MessageKeys() { } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/MultiPartElement.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; /** * Connects several visitables into one element. * * @author Michael J. Simons * @since 1.0 */ class MultiPartElement implements Visitable { private final List precedingClauses; private final With with; MultiPartElement(List precedingClauses, With with) { if (precedingClauses == null || precedingClauses.isEmpty()) { this.precedingClauses = Collections.emptyList(); } else { this.precedingClauses = new ArrayList<>(precedingClauses); } this.with = with; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.precedingClauses.forEach(c -> c.accept(visitor)); this.with.accept(visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } boolean isImporting() { return this.precedingClauses.isEmpty() && !this.with.getItems().isEmpty() && this.with.getItems().stream().allMatch(IdentifiableElement.class::isInstance); } IdentifiableElement[] getImports() { return this.with.getItems().stream().map(IdentifiableElement.class::cast).toArray(IdentifiableElement[]::new); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/MultiPartQuery.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; /** * See MultiPartQuery. * * @author Michael J. Simons * @since 1.0 */ @API(status = INTERNAL, since = "1.0") class MultiPartQuery extends AbstractStatement implements Statement.SingleQuery { private final List parts; private final SinglePartQuery remainder; private MultiPartQuery(List parts, SinglePartQuery remainder) { this.parts = new ArrayList<>(parts); this.remainder = remainder; } static MultiPartQuery create(List parts, SinglePartQuery remainder) { if (remainder instanceof ResultStatement) { return new MultiPartQueryWithResult(parts, remainder); } else { return new MultiPartQuery(parts, remainder); } } List getParts() { return this.parts; } Statement stripFirst() { return new MultiPartQuery((this.parts.size() > 1) ? this.parts.subList(1, this.parts.size()) : List.of(), this.remainder); } @Override public void accept(Visitor visitor) { this.parts.forEach(p -> p.accept(visitor)); this.remainder.accept(visitor); } static final class MultiPartQueryWithResult extends MultiPartQuery implements ResultStatement { private MultiPartQueryWithResult(List parts, SinglePartQuery remainder) { super(parts, remainder); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Named.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Optional; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * A named thing exposes {@link #getSymbolicName()}, making the thing identifiable. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public non-sealed interface Named extends IdentifiableElement { /** * {@return an optional symbolic name} */ Optional getSymbolicName(); /** * Return a symbolic name. * @return a symbolic name * @throws IllegalStateException if this has not been named yet. */ default SymbolicName getRequiredSymbolicName() { return getSymbolicName().orElseThrow(() -> new IllegalStateException("No name present.")); } @Override default Expression asExpression() { return getRequiredSymbolicName(); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/NamedPath.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Optional; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.FunctionInvocation.FunctionDefinition; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * Represents a named path. A named path can be either a {@link RelationshipPattern} that * has been assigned to a variable as in {@code p := (a)-->(b)}, a call to functions known * to return paths or an existing, symbolic name that might come from an arbitrary * procedure returning path elements.
* Note: We cannot check a value that has been yielded from a procedure upfront to * verify that it is a named path. This is up to the caller. * * @author Michael J. Simons * @since 1.1 */ @API(status = STABLE, since = "1.1") public final class NamedPath implements PatternElement, Named { /** * The name of this path expression. */ private final SymbolicName name; /** * An optional {@code SHORTEST} keyword element. */ private final PatternSelector optionalPatternSelector; /** * The pattern defining this path. */ private final Visitable optionalPattern; private NamedPath(SymbolicName name) { this.name = name; this.optionalPatternSelector = null; this.optionalPattern = null; } private NamedPath(SymbolicName name, PatternSelector optionalPatternSelector, PatternElement optionalPattern) { this.name = name; this.optionalPatternSelector = optionalPatternSelector; this.optionalPattern = optionalPattern; } private NamedPath(SymbolicName name, FunctionInvocation algorithm) { this.name = name; this.optionalPatternSelector = null; this.optionalPattern = algorithm; } static OngoingDefinitionWithName named(String name) { return named(SymbolicName.of(name)); } static OngoingDefinitionWithName named(SymbolicName name) { Assertions.notNull(name, "A name is required"); return new Builder(name); } static OngoingShortestPathDefinitionWithName named(String name, FunctionDefinition algorithm) { return new ShortestPathBuilder(SymbolicName.of(name), algorithm); } static OngoingShortestPathDefinitionWithName named(SymbolicName name, FunctionDefinition algorithm) { Assertions.notNull(name, "A name is required"); return new ShortestPathBuilder(name, algorithm); } static OngoingShortestDefinition shortest(int k) { return new ShortestBuilder(PatternSelector.shortestK(k)); } static OngoingShortestDefinition allShortest() { return new ShortestBuilder(PatternSelector.allShortest()); } static OngoingShortestDefinition shortestKGroups(int k) { return new ShortestBuilder(PatternSelector.shortestKGroups(k)); } static OngoingShortestDefinition any() { return new ShortestBuilder(PatternSelector.any()); } @API(status = INTERNAL, since = "2024.7.0") public static NamedPath select(PatternSelector patternSelector, PatternElement patternElement) { if (patternElement instanceof NamedPath namedPath && namedPath.optionalPattern instanceof PatternElement target) { return new NamedPath(namedPath.name, patternSelector, target); } return new NamedPath(SymbolicName.unresolved(), patternSelector, patternElement); } @Override public Optional getSymbolicName() { return Optional.of(this.name); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.name.accept(visitor); if (this.optionalPattern != null) { Operator.ASSIGMENT.accept(visitor); if (this.optionalPatternSelector != null) { visitor.enter(this.optionalPatternSelector); } this.optionalPattern.accept(visitor); if (this.optionalPatternSelector != null) { visitor.leave(this.optionalPatternSelector); } } visitor.leave(this); } /** * Partial path that has a name, introduced as a superinterface for * {@link OngoingDefinitionWithName} to avoid dragging * {@link OngoingDefinitionWithName#get()}. */ public interface OngoingNamedDefinition { /** * Create a new named path based on a {@link PatternElement}. * @param pattern the pattern to be matched for the named path. * @return a named path. */ NamedPath definedBy(PatternElement pattern); } /** * Partial path that has a name ({@code p = }). */ public interface OngoingDefinitionWithName extends OngoingNamedDefinition { /** * Create a new named path that references a given, symbolic name. No checks are * done if the referenced name actually points to a path. * @return a named path. * @since 2020.1.4 */ NamedPath get(); } /** * Partial path that has a name ({@code p = }) and is based on a graph algorithm * function. */ public interface OngoingShortestPathDefinitionWithName { /** * Create a new named path based on a single relationship. * @param relationship the relationship to be passed to {@code shortestPath}. * @return a named path. */ NamedPath definedBy(Relationship relationship); } /** * Partial path with the number of paths to match. */ public interface OngoingShortestDefinition { default OngoingNamedDefinition named(String name) { return named(Cypher.name(name)); } OngoingNamedDefinition named(SymbolicName name); } private record Builder(SymbolicName name) implements OngoingDefinitionWithName { @Override public NamedPath definedBy(PatternElement pattern) { if (pattern instanceof NamedPath namedPath) { return namedPath; } return new NamedPath(this.name, null, pattern); } @Override public NamedPath get() { return new NamedPath(this.name); } } private record ShortestPathBuilder(SymbolicName name, FunctionDefinition algorithm) implements OngoingShortestPathDefinitionWithName { @Override public NamedPath definedBy(Relationship relationship) { return new NamedPath(this.name, FunctionInvocation.create(this.algorithm, relationship)); } } private static final class ShortestBuilder implements OngoingShortestDefinition, OngoingNamedDefinition { private final PatternSelector shortest; private SymbolicName name; private ShortestBuilder(PatternSelector shortest) { this.shortest = shortest; } @Override public OngoingNamedDefinition named(SymbolicName newName) { this.name = newName; return this; } @Override public NamedPath definedBy(PatternElement pattern) { return new NamedPath(this.name, this.shortest, pattern); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Neo4jVersion.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; 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; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * This annotation provides information which Neo4j version is required to be able to * successfully run a query containing a fragment generated via a method annotated with * it. * * @author Michael J. Simons * @since 2020.1.2 */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) @Documented @API(status = STABLE, since = "2020.1.2") public @interface Neo4jVersion { /** * {@return the minimum version of Neo4j required to run the annotated construct} */ String minimum(); /** * {@return the last version of Neo4j that supports running the annotated construct} */ String last() default ""; } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/NestedExpression.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.STABLE; /** * A nested expression. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class NestedExpression implements Expression { private final Expression delegate; NestedExpression(Expression delegate) { this.delegate = delegate; } @Override public void accept(Visitor visitor) { visitor.enter(this); Expressions.nameOrExpression(this.delegate).accept(visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Node.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * See NodePattern. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface Node extends PatternElement, PropertyContainer, ExposesProperties, ExposesRelationships { /** * {@return the labels associated with this Node} */ List getLabels(); /** * Creates a copy of this node with a new symbolic name. * @param newSymbolicName the new symbolic name. * @return the new node. */ Node named(String newSymbolicName); /** * Creates a copy of this node with a new symbolic name. * @param newSymbolicName the new symbolic name. * @return the new node. */ Node named(SymbolicName newSymbolicName); /** * A condition that checks for the presence of labels on a node. * @param labelsToQuery a list of labels to query * @return a condition that checks whether this node has all the labels to query */ Condition hasLabels(String... labelsToQuery); /** * Returns a condition that checks for the presence of a label expression on a node. * @param labels the labels to check * @return a condition that checks whether this node has all the labels to query * @since 2024.3.0 * @deprecated use {@link #hasLabels(Labels)} */ @SuppressWarnings("removal") @Deprecated(forRemoval = true) Condition hasLabels(LabelExpression labels); /** * Returns a condition that checks for the presence of a label expression on a node. * @param labels the labels to check * @return a condition that checks whether this node has all the labels to query * @since 2025.1.0 */ Condition hasLabels(Labels labels); /** * Creates a new condition whether this node is equal to {@literal otherNode}. * @param otherNode the node to compare this node to. * @return a condition. */ Condition isEqualTo(Node otherNode); /** * Creates a new condition whether this node is not equal to {@literal otherNode}. * @param otherNode the node to compare this node to. * @return a condition. */ Condition isNotEqualTo(Node otherNode); /** * Creates a new condition based on this node whether it is null. * @return a condition. */ Condition isNull(); /** * Creates a new condition based on this node whether it is not null. * @return a condition. */ Condition isNotNull(); /** * Creates a new sort item of this node in descending order. * @return a sort item. */ SortItem descending(); /** * Creates a new sort item of this node in ascending order. * @return a sort item. */ SortItem ascending(); /** * Creates an alias for this node. * @param alias the alias to use. * @return the aliased expression. */ AliasedExpression as(String alias); /** * Returns a new function invocation returning the internal id of this node. * @return a new function invocation returning the internal id of this node * @deprecated Use {@link #elementId} */ @Deprecated(since = "2022.6.0") // The deprecation warning on any client code calling this is actually the point. @SuppressWarnings({ "DeprecatedIsStillUsed", "squid:S1133" }) FunctionInvocation internalId(); /** * Returns a new function invocation returning the element id of this node. * @return a new function invocation returning the element id of this node * @since 2022.6.0 */ default FunctionInvocation elementId() { return Functions.elementId(this); } /** * {@return a new function invocation returning the labels of this node} */ FunctionInvocation labels(); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/NodeBase.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * This is the base class for all nodes. It can be used with generics, specifying a valid * type. This is useful when using it as a base class for a static metamodel. * * @param the type of this node * @author Michael J. Simons * @since 2021.1.0 */ @API(status = STABLE, since = "2021.1.0") @SuppressWarnings("deprecation") // IDEA is stupid. public abstract class NodeBase extends AbstractNode implements Node { final List staticLabels; final Labels dynamicLabels; final Properties properties; final Where innerPredicate; @SuppressWarnings("squid:S3077") // Symbolic name is unmodifiable private volatile SymbolicName symbolicName; // ------------------------------------------------------------------------ // Public API to be used by the static meta model. // Non-final methods are ok to be overwritten. // ------------------------------------------------------------------------ /** * Creates a new base object from a set of labels. * @param primaryLabel the primary label * @param additionalLabels an optional list of additional ones. */ protected NodeBase(String primaryLabel, String... additionalLabels) { this(null, primaryLabel, null, additionalLabels); } /** * Creates a new base object from a {@link SymbolicName} name, a list of labels and a * set of properties. * @param symbolicName the symbolic name for this node object * @param staticLabels the list of labels, no primary is given * @param properties a set of properties */ protected NodeBase(SymbolicName symbolicName, List staticLabels, Properties properties) { this(symbolicName, new ArrayList<>(staticLabels), null, properties, null); } NodeBase() { this(null, Collections.emptyList(), null); } NodeBase(SymbolicName symbolicName, String primaryLabel, MapExpression properties, String... additionalLabels) { this(symbolicName, assertLabels(primaryLabel, additionalLabels), Properties.create(properties)); } NodeBase(SymbolicName symbolicName, List staticLabels, Labels dynamicLabels, Properties properties, Where innerPredicate) { this.symbolicName = symbolicName; this.staticLabels = staticLabels; this.dynamicLabels = dynamicLabels; this.properties = properties; this.innerPredicate = innerPredicate; } private static List assertLabels(String primaryLabel, String[] additionalLabels) { Assertions.hasText(primaryLabel, "A primary label is required."); if (additionalLabels != null) { for (String additionalLabel : additionalLabels) { Assertions.hasText(additionalLabel, "An empty label is not allowed."); } } List labels = new ArrayList<>(); labels.add(new NodeLabel(primaryLabel)); if (additionalLabels != null) { labels.addAll(Arrays.stream(additionalLabels).map(NodeLabel::new).toList()); } return labels; } @Override public final SELF named(String newSymbolicName) { Assertions.hasText(newSymbolicName, "Symbolic name is required."); return named(SymbolicName.of(newSymbolicName)); } /** * This method needs to be implemented to provide new, type safe instances of this * node. * @param newSymbolicName the new symbolic name. * @return a new node */ @Override // This is overridden to make sure we allow a covariant return type @SuppressWarnings("squid:S3038") public abstract SELF named(SymbolicName newSymbolicName); @Override public final SELF withProperties(Object... keysAndValues) { MapExpression newProperties = null; if (keysAndValues != null && keysAndValues.length != 0) { newProperties = MapExpression.create(false, keysAndValues); } return withProperties(newProperties); } /** * A new object with a new set of properties. * @param newProperties a map with the new properties * @return a new object */ @Override public final SELF withProperties(Map newProperties) { return withProperties(MapExpression.create(newProperties)); } /** * This method needs to be implemented to provide new, type safe instances of this * node. * @param newProperties the new properties (can be {@literal null} to remove exiting * properties). * @return a new node */ @Override // This is overridden to make sure we allow a covariant return type @SuppressWarnings("squid:S3038") public abstract SELF withProperties(MapExpression newProperties); // ------------------------------------------------------------------------ // Internal API. // ------------------------------------------------------------------------ /** * {@return set of properties for this node} */ protected final Properties getProperties() { return this.properties; } @Override public final List getLabels() { return (this.staticLabels != null) ? List.copyOf(this.staticLabels) : List.of(); } @Override public final Optional getSymbolicName() { return Optional.ofNullable(this.symbolicName); } @Override public final SymbolicName getRequiredSymbolicName() { SymbolicName requiredSymbolicName = this.symbolicName; if (requiredSymbolicName == null) { synchronized (this) { requiredSymbolicName = this.symbolicName; if (requiredSymbolicName == null) { this.symbolicName = SymbolicName.unresolved(); requiredSymbolicName = this.symbolicName; } } } return requiredSymbolicName; } @Override public final void accept(Visitor visitor) { visitor.enter(this); this.getSymbolicName().ifPresent(s -> s.accept(visitor)); if (this.staticLabels != null) { this.staticLabels.forEach(label -> label.accept(visitor)); } Visitable.visitIfNotNull(this.dynamicLabels, visitor); Visitable.visitIfNotNull(this.properties, visitor); Visitable.visitIfNotNull(this.innerPredicate, visitor); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/NodeLabel.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import static org.apiguardian.api.API.Status.STABLE; /** * Expression for a single Node label. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class NodeLabel implements Visitable { private final String value; NodeLabel(String value) { this.value = value; } /** * {@return the actual value of this label. Needs to be escaped before rendering} */ public String getValue() { return this.value; } @Override public String toString() { return "NodeLabel{" + "value='" + this.value + '\'' + '}'; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/NullLiteral.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * Represents the literal value {@literal null}. * * @author Gerrit Meier * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class NullLiteral extends LiteralBase { static final NullLiteral INSTANCE = new NullLiteral(); private NullLiteral() { super(null); } @Override public String asString() { return "NULL"; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/NumberLiteral.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * Representation of a numeric literal. * * @author Gerrit Meier * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class NumberLiteral extends LiteralBase { NumberLiteral(Number content) { super(content); } @Override public String asString() { return String.valueOf(getContent()); } @Override public Number getContent() { return this.content; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/OngoingListBasedPredicateFunction.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; /** * Allows to define the source of the list predicate. * * @author Michael J. Simons * @since 2024.0.0 */ public interface OngoingListBasedPredicateFunction { /** * Returns a builder to specify the where condition for the list based predicate. * @param list a list expression * @return a builder to specify the where condition for the list based predicate */ @CheckReturnValue OngoingListBasedPredicateFunctionWithList in(Expression list); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/OngoingListBasedPredicateFunctionWithList.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; /** * Allows to specify the where condition for the list based predicate. * * @author Michael J. Simons * @since 2024.0.0 */ public interface OngoingListBasedPredicateFunctionWithList { /** * Returns the final list based predicate function. * @param condition the condition for the list based predicate. * @return the final list based predicate function */ Condition where(Condition condition); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Operation.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Arrays; import java.util.EnumSet; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.TypedSubtree; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * A binary operation. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class Operation implements Expression { /** * A set of operators triggering operations on labels. */ private static final EnumSet LABEL_OPERATORS = EnumSet.of(Operator.SET_LABEL, Operator.REMOVE_LABEL); private static final EnumSet NEEDS_GROUPING_BY_TYPE = EnumSet .complementOf(EnumSet.of(Operator.Type.PROPERTY, Operator.Type.LABEL)); private static final EnumSet DONT_GROUP = EnumSet.of(Operator.EXPONENTIATION, Operator.PIPE, Operator.UNARY_MINUS, Operator.UNARY_PLUS); private final Expression left; private final Operator operator; private final Visitable right; Operation(Expression left, Operator operator, Expression right) { this.left = left; this.operator = operator; this.right = right; } Operation(Expression left, Operator operator, Visitable right) { this.left = left; this.operator = operator; this.right = right; } static Operation create(Operator operator, Expression expression) { Assertions.notNull(operator, "Operator must not be null."); Assertions.isTrue(operator.isUnary(), "Operator must be unary."); Assertions.notNull(expression, "The expression must not be null."); return switch (operator.getType()) { case PREFIX -> new Operation(null, operator, expression); case POSTFIX -> new Operation(expression, operator, (Expression) null); default -> throw new IllegalArgumentException("Invalid operator type " + operator.getType()); }; } static Operation create(Expression op1, Operator operator, Expression op2) { Assertions.notNull(op1, "The first operand must not be null."); Assertions.notNull(operator, "Operator must not be null."); Assertions.notNull(op2, "The second operand must not be null."); return new Operation(op1, operator, op2); } static Operation create(Node op1, Operator operator, String... nodeLabels) { Assertions.notNull(op1, "The first operand must not be null."); Assertions.isTrue(op1.getSymbolicName().isPresent(), "The node must have a name."); Assertions.isTrue(LABEL_OPERATORS.contains(operator), String.format("Only operators %s can be used to modify labels", LABEL_OPERATORS)); Assertions.notEmpty(nodeLabels, "The labels cannot be empty."); var listOfNodeLabels = new TypedSubtree<>(Arrays.stream(nodeLabels).map(NodeLabel::new).toList()) { @Override public String separator() { return ""; } }; return new Operation(op1.getRequiredSymbolicName(), operator, listOfNodeLabels); } static Operation create(Node op1, Operator operator, Labels labels) { Assertions.notNull(op1, "The first operand must not be null."); Assertions.isTrue(op1.getSymbolicName().isPresent(), "The node must have a name."); Assertions.isTrue(LABEL_OPERATORS.contains(operator), String.format("Only operators %s can be used to modify labels", LABEL_OPERATORS)); Assertions.notNull(labels, "The labels cannot be empty."); Assertions.isTrue(labels.canBeUsedInUpdate(), "Only a single dynamic label expression or a set of static labels might be used in an updating clause"); return new Operation(op1.getRequiredSymbolicName(), operator, labels); } @Override public void accept(Visitor visitor) { visitor.enter(this); if (this.left != null) { Expressions.nameOrExpression(this.left).accept(visitor); } this.operator.accept(visitor); Visitable.visitIfNotNull(this.right, visitor); visitor.leave(this); } /** * Checks, whether this operation needs grouping. * @return true, if this operation needs grouping. */ public boolean needsGrouping() { return NEEDS_GROUPING_BY_TYPE.contains(this.operator.getType()) && !DONT_GROUP.contains(this.operator); } @API(status = INTERNAL) Operator getOperator() { return this.operator; } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Operations.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Set; import org.neo4j.cypherdsl.core.utils.Assertions; /** * A set of operations. * * @author Michael J. Simons * @since 1.0 be accessible. */ final class Operations { private static final java.util.Set> VALID_MUTATORS = Set.of(MapExpression.class, Parameter.class, MapProjection.class, SymbolicName.class, FunctionInvocation.class); /** * Not to be instantiated. */ private Operations() { } /** * Creates a unary minus operation. * @param e the expression to which the unary minus should be applied. We don't check * if it's a numeric expression, but in hindsight to generate semantically correct * Cypher, it's recommended that is one. * @return an unary minus operation. * @since 2021.2.3 */ static Operation minus(Expression e) { return Operation.create(Operator.UNARY_MINUS, e); } /** * Creates an unary plus operation. * @param e the expression to which the unary plus should be applied. We don't check * if it's a numeric expression, but in hindsight to generate semantically correct * Cypher, it's recommended that is one. * @return an unary plus operation. * @since 2021.2.3 */ static Expression plus(Expression e) { return Operation.create(Operator.UNARY_PLUS, e); } static Operation concat(Expression op1, Expression op2) { return Operation.create(op1, Operator.CONCAT, op2); } static Operation add(Expression op1, Expression op2) { return Operation.create(op1, Operator.ADDITION, op2); } static Operation subtract(Expression op1, Expression op2) { return Operation.create(op1, Operator.SUBTRACTION, op2); } static Operation multiply(Expression op1, Expression op2) { return Operation.create(op1, Operator.MULTIPLICATION, op2); } static Operation divide(Expression op1, Expression op2) { return Operation.create(op1, Operator.DIVISION, op2); } static Operation remainder(Expression op1, Expression op2) { return Operation.create(op1, Operator.MODULO_DIVISION, op2); } static Operation pow(Expression op1, Expression op2) { return Operation.create(op1, Operator.EXPONENTIATION, op2); } /** * Creates a {@code =} operation. The left hand side should resolve to a property or * to something which has labels or types to modify and the right hand side should * either be new properties or labels. * @param target the target that should be modified * @param value the new value of the target * @return a new operation. * @since 2021.2.3 */ static Operation set(Expression target, Expression value) { return Operation.create(target, Operator.SET, value); } /** * Creates a {@code +=} operation. The left hand side must resolve to a container * (either a node or a relationship) of properties and the right hand side must be a * map of new or updated properties * @param target the target container that should be modified * @param value the new properties * @return a new operation. * @since 2020.1.5 */ static Operation mutate(Expression target, MapExpression value) { return Operation.create(target, Operator.MUTATE, value); } /** * Creates a {@code +=} operation. The left hand side must resolve to a container * (either a node or a relationship) of properties and the right hand side must be a * map of new or updated properties * @param target the target container that should be modified * @param value the new properties * @return a new operation. * @since 2020.1.5 */ static Operation mutate(Expression target, Expression value) { Assertions.notNull(value, "New properties value must not be null"); Assertions.isTrue( Property.class.isAssignableFrom(value.getClass()) || VALID_MUTATORS.contains(value.getClass()), "A property container can only be mutated by a map, or a parameter or property pointing to a map."); return Operation.create(target, Operator.MUTATE, value); } /** * Creates an operation adding one or more labels from a given {@link Node node}. * @param target the target of the new labels * @param label the labels to be added * @return a set operation * @since 2021.2.3 */ static Operation set(Node target, String... label) { return Operation.create(target, Operator.SET_LABEL, label); } /** * Creates an operation adding one or more labels from a given {@link Node node}. * @param target the target of the new labels * @param labels the labels to be added * @return a set operation * @since 2025.1.0 */ static Operation set(Node target, Labels labels) { return Operation.create(target, Operator.SET_LABEL, labels); } /** * Creates an operation removing one or more labels from a given {@link Node node}. * @param target the target of the remove operation * @param label the labels to be removed * @return a remove operation * @since 2021.2.3 */ static Operation remove(Node target, String... label) { return Operation.create(target, Operator.REMOVE_LABEL, label); } /** * Creates an operation removing one or more labels from a given {@link Node node}. * @param node the node from which the labels should be removed * @param labels the labels to be removed * @return a remove operation * @since 2025.1.0 */ static Expression remove(Node node, Labels labels) { return Operation.create(node, Operator.REMOVE_LABEL, labels); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Operator.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * An operator. See Operators. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public enum Operator implements Visitable { // Mathematical operators /** * Standard addition operator. */ ADDITION("+"), /** * Standard subtraction operator. */ SUBTRACTION("-"), /** * Unary minus operator. */ UNARY_MINUS("-", Type.PREFIX), /** * Unary plus operator. */ UNARY_PLUS("+", Type.PREFIX), /** * Standard multiplication operator. */ MULTIPLICATION("*"), /** * Standard division operator. */ DIVISION("/"), /** * Standard modulo operator. */ MODULO_DIVISION("%"), /** * Operator for exponentiation. */ EXPONENTIATION("^"), // Comparison operators /** * Comparison for equality. */ EQUALITY("="), /** * Comparison for inequality. */ INEQUALITY("<>"), /** * < comparison. */ LESS_THAN("<"), /** * > comparison. */ GREATER_THAN(">"), /** * ≤ comparison. */ LESS_THAN_OR_EQUAL_TO("<="), /** * ≥ comparison. */ GREATER_THAN_OR_EQUAL_TO(">="), /** * {@code IS NULL} comparison. */ IS_NULL("IS NULL", Type.POSTFIX), /** * {@code IS NOT NULL} comparison. */ IS_NOT_NULL("IS NOT NULL", Type.POSTFIX), /** * String operator for {@code STARTS WITH}. */ STARTS_WITH("STARTS WITH"), /** * String operator for {@code ENDS WITH}. */ ENDS_WITH("ENDS WITH"), /** * String operator for {@code CONTAINS}. */ CONTAINS("CONTAINS"), // Boolean operators /** * The AND operator. */ AND("AND"), /** * The OR operator. */ OR("OR"), /** * The XOR operator. */ XOR("XOR"), /** * The NOT operator. */ NOT("NOT", Type.PREFIX), // String operators /** * The string concatenating operator. */ CONCAT("+"), /** * The string matching operator. */ MATCHES("=~"), // List operators /** * {@code IN} operator. */ IN("IN"), // Property operators /** * Property operator for assigning properties. */ SET("=", Type.PROPERTY), /** * Property operator for retrieving properties. */ GET(".", Type.PROPERTY), /** * Property operator for modifying properties. */ MUTATE("+=", Type.PROPERTY), // Node operators /** * The label operator adding labels. */ SET_LABEL("", Type.LABEL), /** * The label operator removing labels. */ REMOVE_LABEL("", Type.LABEL), // Misc /** * The assigment operator (Read as in `p := (a)-->(b)`). */ ASSIGMENT("="), /** * The pipe operator. */ PIPE("|"); private final String representation; private final Type type; Operator(String representation) { this(representation, Type.BINARY); } Operator(String representation, Type type) { this.representation = representation; this.type = type; } /** * {@return the operators textual representation} */ @API(status = INTERNAL) public String getRepresentation() { return this.representation; } /** * {@return true if this is a unary operator} */ boolean isUnary() { return this.type != Type.BINARY; } /** * {@return the type of this operator} */ @API(status = INTERNAL) public Type getType() { return this.type; } @Override public String toString() { return RendererBridge.render(this); } /** * {@link Operator} type. * * @since 1.0 */ public enum Type { /** * Describes a binary operator (An operator with to operands). */ BINARY, /** * Describes a unary prefix operator (An operator with one operand after the * operator). */ PREFIX, /** * Describes a unary postfix operator (An operator with one operand before the * operator). */ POSTFIX, /** * Describes an operator working with properties of entities. */ PROPERTY, /** * The binary operator modifying labels of nodes. */ LABEL } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Order.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.TypedSubtree; import static org.apiguardian.api.API.Status.STABLE; /** * Represents the list of sort items that make up the order of records in a result set. * * @author Gerrit Meier * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class Order extends TypedSubtree { Order(List sortItems) { super(sortItems); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Parameter.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Objects; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * Represents a named parameter inside a Cypher statement. * * @param the type of the parameter. Defaults to {@link Object} for a parameter * without a value from which to derive the actual type. * @author Michael J. Simons * @author Andreas Berger * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class Parameter implements Expression { static final Object NO_VALUE = new Object(); private final String name; private final T value; private Parameter(String name, T value) { this.name = name; this.value = value; } static Parameter create(String name) { return create(name, NO_VALUE); } static Parameter create(String name, T value) { Assertions.hasText(name, "The name of the parameter is required!"); if (name.startsWith("$")) { return create(name.substring(1), value); } return new Parameter<>(name, value); } static Parameter anon(T value) { return new Parameter<>(null, value); } /** * Query method to check if this is an anonymous parameter. * @return true if this is an anonymous parameter * @since 2021.1.0 */ @API(status = STABLE, since = "2021.0.0") public boolean isAnon() { return this.name == null || this.name.trim().isEmpty(); } /** * Returns the name of this parameter. * @return the name of this parameter * @since 2023.1.0 */ @API(status = STABLE, since = "2023.1.0") public String getName() { return this.name; } /** * Returns a new parameter with a bound value. * @param newValue the new value that should be bound by this parameter * @return a new parameter with a bound value * @since 2021.0.0 */ @API(status = STABLE, since = "2021.0.0") public Parameter withValue(Object newValue) { return create(this.name, newValue); } /** * {@return the value bound to this parameter} */ @API(status = INTERNAL, since = "2021.1.0") public T getValue() { return this.value; } /** * {@return true if the Parameter has a bound value} */ boolean hasValue() { return !Objects.equals(this.value, NO_VALUE); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Parameter parameter = (Parameter) o; if (this.isAnon() && parameter.isAnon()) { return Objects.deepEquals(this.value, parameter.value); } else if (this.isAnon() != parameter.isAnon()) { return false; } return Objects.equals(this.name, parameter.name); } @Override public int hashCode() { return this.isAnon() ? Objects.hashCode(this.value) : Objects.hash(this.name); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ParameterCollectingVisitor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.internal.ConstantParameterHolder; import org.neo4j.cypherdsl.core.internal.NameResolvingStrategy; import org.neo4j.cypherdsl.core.internal.ReflectiveVisitor; /** * This is an implementation of a visitor to the Cypher AST created by the Cypher builder * based on the {@link ReflectiveVisitor reflective visitor}. *

* It collects all parameters with bound value into a map of used parameters. *

* * @author Andreas Berger * @author Michael J. Simons * @since 2021.0.0 */ final class ParameterCollectingVisitor implements Visitor { final Map parameterMapping = new TreeMap<>(); private final StatementContext statementContext; private final boolean renderConstantsAsParameters; private final Set parameterNames = new TreeSet<>(); private final Map parameterValues = new TreeMap<>(); private final Map> erroneousParameters = new TreeMap<>(); private final NameResolvingStrategy nameGenerator; ParameterCollectingVisitor(StatementContext statementContext, boolean renderConstantsAsParameters) { this.statementContext = statementContext; this.nameGenerator = NameResolvingStrategy.useGeneratedParameterNames(statementContext); this.renderConstantsAsParameters = renderConstantsAsParameters; } @Override public void enter(Visitable segment) { if (!(segment instanceof Parameter parameter)) { return; } String parameterName = this.statementContext.getParameterName(parameter); Object newValue = parameter.getValue(); if (newValue instanceof ConstantParameterHolder constantParameterHolder) { if (!this.renderConstantsAsParameters) { return; } newValue = constantParameterHolder.getValue(); } boolean knownParameterName = !this.parameterNames.add(parameterName); if (!(knownParameterName || parameter.isAnon())) { this.parameterMapping.put(parameterName, this.nameGenerator.resolve(parameter)); } Object oldValue = (knownParameterName && this.parameterValues.containsKey(parameterName)) ? this.parameterValues.get(parameterName) : Parameter.NO_VALUE; if (parameter.hasValue()) { this.parameterValues.put(parameterName, newValue); } if (knownParameterName && !Objects.equals(oldValue, newValue)) { Set conflictingObjects = this.erroneousParameters.computeIfAbsent(parameterName, s -> { HashSet list = new HashSet<>(); list.add(oldValue); return list; }); conflictingObjects.add(newValue); } } ParameterInformation getResult() { if (!this.erroneousParameters.isEmpty()) { throw new ConflictingParametersException(this.erroneousParameters); } return new ParameterInformation(this.parameterNames, this.parameterValues, this.parameterMapping); } static final class ParameterInformation { final Set names; final Map values; final Map renames; ParameterInformation(Set names, Map values, Map renames) { this.names = Collections.unmodifiableSet(names); this.values = Collections.unmodifiableMap(values); this.renames = Collections.unmodifiableMap(renames); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ParameterLiteral.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; /** * Representation of a parameter literal. For internal use only. * * @author Michael J. Simons * @since 2025.2.0 */ final class ParameterLiteral extends LiteralBase> { private ParameterLiteral(Parameter content) { super(content); } static Literal> of(Parameter content) { if (content.isAnon()) { throw new IllegalArgumentException("Anonymous parameters cannot be used as parameter literals"); } return new ParameterLiteral(content); } @Override public String asString() { return "$" + content.getName(); } @Override public Parameter getContent() { return this.content; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Pattern.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.TypedSubtree; import static org.apiguardian.api.API.Status.INTERNAL; /** * A pattern is something that can be matched. It consists of one or more pattern * elements. Those can be nodes or chains of nodes and relationships. *

* See Pattern. * * @author Michael J. Simons * @since 1.0 */ @API(status = INTERNAL, since = "1.0") final class Pattern extends TypedSubtree { private Pattern(List patternElements) { super(patternElements); } static Pattern of(PatternElement requiredPattern, PatternElement... patternElement) { List elements; if (patternElement == null || patternElement.length == 0) { elements = List.of(requiredPattern); } else { elements = new ArrayList<>(); elements.add(requiredPattern); elements.addAll(Arrays.asList(patternElement)); } return Pattern.of(elements); } static Pattern of(Collection elements) { return new Pattern(elements.stream().map(PatternElement.class::cast).toList()); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/PatternComprehension.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.internal.RelationshipPatternCondition; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * See PatternComprehension * and the * corresponding cypher manual entry. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class PatternComprehension implements Expression { private final PatternElement pattern; private final Where where; private final Expression listDefinition; private PatternComprehension(PatternElement pattern, Where where, Expression listDefinition) { this.pattern = pattern; this.where = where; this.listDefinition = listDefinition; } static OngoingDefinitionWithPattern basedOn(RelationshipPattern pattern) { Assertions.notNull(pattern, "A pattern is required"); return new Builder(pattern); } static OngoingDefinitionWithPattern basedOn(NamedPath pattern) { Assertions.notNull(pattern, "A pattern is required"); return new Builder(pattern); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.pattern.accept(visitor); Visitable.visitIfNotNull(this.where, visitor); Operator.PIPE.accept(visitor); this.listDefinition.accept(visitor); visitor.leave(this); } /** * Provides the final step of defining a pattern comprehension. */ public interface OngoingDefinitionWithoutReturn { /** * Defines the returning named items. * @param variables the elements to be returned from the pattern * @return the final definition of the pattern comprehension * @see #returning(Expression...) */ default PatternComprehension returning(Named... variables) { return returning(Expressions.createSymbolicNames(variables)); } /** * Defines the returning exoression items. * @param listDefinition defines the elements to be returned from the pattern * @return the final definition of the pattern comprehension */ PatternComprehension returning(Expression... listDefinition); } /** * Allows to add a where clause into the definition of the pattern. */ public interface OngoingDefinitionWithPattern extends OngoingDefinitionWithoutReturn { /** * Adds a {@code WHERE} clause to the inner statement of the pattern * comprehension. * @param condition an initial condition to be used with {@code WHERE} * @return an ongoing definition of a pattern comprehension for furhter * modification */ @CheckReturnValue OngoingDefinitionWithPatternAndWhere where(Condition condition); /** * Adds a where clause based on a path pattern to the ongoing definition. * @param pathPattern the path pattern to add to the where clause. This path * pattern must not be {@literal null} and must not introduce new variables not * available in the match. * @return a match or a call restricted by a where clause with no return items * yet. * @since 2020.1.4 */ @CheckReturnValue default OngoingDefinitionWithPatternAndWhere where(RelationshipPattern pathPattern) { Assertions.notNull(pathPattern, "The path pattern must not be null."); return this.where(RelationshipPatternCondition.of(pathPattern)); } } /** * Intermediate step that allows expressing additional, logical operators. */ public interface OngoingDefinitionWithPatternAndWhere extends OngoingDefinitionWithoutReturn, ExposesLogicalOperators { } /** * Ongoing definition of a pattern comprehension. Can be defined without a * where-clause now. */ private static final class Builder implements OngoingDefinitionWithPattern, OngoingDefinitionWithPatternAndWhere { private final PatternElement pattern; private final DefaultStatementBuilder.ConditionBuilder conditionBuilder = new DefaultStatementBuilder.ConditionBuilder(); private Builder(PatternElement pattern) { this.pattern = pattern; } @Override public OngoingDefinitionWithPatternAndWhere where(Condition condition) { this.conditionBuilder.where(condition); return this; } @Override public OngoingDefinitionWithPatternAndWhere and(Condition condition) { this.conditionBuilder.and(condition); return this; } @Override public OngoingDefinitionWithPatternAndWhere or(Condition condition) { this.conditionBuilder.or(condition); return this; } @Override public PatternComprehension returning(Expression... expressions) { Where where = this.conditionBuilder.buildCondition().map(Where::new).orElse(null); return new PatternComprehension(this.pattern, where, ListExpression.listOrSingleExpression(expressions)); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/PatternElement.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import static org.apiguardian.api.API.Status.STABLE; /** * See PatternElement. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface PatternElement extends Visitable { /** * Creates a new {@link PatternElement} which including an additional filter. Returns * {@code this} pattern. when {@code predicate} is literal {@code null}. *

* The pattern might be a {@link Node node pattern} or a {@link RelationshipPattern * relationship pattern}. *

* A {@code WHERE} on a pattern is only supported from Neo4j 5.0 onwards. * @param predicate the predicate to filter on * @return a new pattern element or this instance if the predicate to this method was * literal {@code null} * @throws UnsupportedOperationException in cases the underlying element does not * support a {@code WHERE} clause * @since 2023.9.0 */ @Neo4jVersion(minimum = "5.0") default PatternElement where(Expression predicate) { throw new UnsupportedOperationException(); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/PatternExpression.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; /** * A marker interface used for pattern appearing in {@code exists} or {@code size} * statements. * * @author Michael J. Simons * @since 2023.9.8 */ public sealed interface PatternExpression permits PatternExpressionImpl { } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/PatternExpressionImpl.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; final class PatternExpressionImpl implements PatternExpression, Visitable { private final Pattern pattern; PatternExpressionImpl(PatternElement patternElement) { this.pattern = Pattern.of(List.of(patternElement)); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.pattern.accept(visitor); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/PatternSelector.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import static org.apiguardian.api.API.Status.STABLE; /** * A type resenting the {@code SHORTEST} keyword and it's variants. * * @author Michael J. Simons * @since 2024.7.0 * */ @API(status = STABLE, since = "2024.7.0") public sealed interface PatternSelector extends Visitable { // Yes, we could use an enum here, but that would lead to two instances with a k, two // without, // hence, sealed classes are better as they model what we want to express, and in the // visitor // we can use switch with pattern matching on classes at some point in the future for // sure. static PatternSelector shortestK(int k) { if (k <= 0) { throw new IllegalArgumentException("The path count needs to be greater than 0."); } return new ShortestK(k); } static PatternSelector allShortest() { return new AllShortest(); } static PatternSelector shortestKGroups(int k) { if (k <= 0) { throw new IllegalArgumentException("The path count needs to be greater than 0."); } return new ShortestKGroups(k); } static PatternSelector any() { return new Any(); } /** * Representing {@code SHORTEST K}. */ final class ShortestK implements PatternSelector { private final int k; public ShortestK(Integer k) { this.k = k; } public int getK() { return this.k; } } /** * Representing {@code ALL SHORTEST}. */ final class AllShortest implements PatternSelector { private AllShortest() { } } /** * Representing {@code ANY}. */ final class Any implements PatternSelector { private Any() { } } /** * Representing {@code SHORTEST K GROUPS}. */ final class ShortestKGroups implements PatternSelector { private final int k; ShortestKGroups(Integer k) { this.k = k; } public int getK() { return this.k; } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/PeriodLiteral.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.time.Period; /** * A literal representing a period value to be formatted in a way that Neo4j's Cypher * understands it. * * @author Michael J. Simons * @since 2023.2.1 */ final class PeriodLiteral extends LiteralBase { private PeriodLiteral(Period content) { super(content); } static Literal of(Period duration) { return new PeriodLiteral(duration); } @Override public Period getContent() { return this.content; } @Override public String asString() { var result = new StringBuilder(); result.append("duration('P"); if (this.content.getYears() != 0) { result.append(this.content.getYears()).append("Y"); } if (this.content.getMonths() != 0) { result.append(this.content.getMonths()).append("M"); } if (this.content.getDays() != 0) { result.append(this.content.getDays()).append("D"); } result.append("')"); return result.toString(); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Predicates.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import org.neo4j.cypherdsl.core.utils.Assertions; /** * Factory methods for creating predicates. * * @author Michael J. Simons * @since 1.0 be accessible. */ final class Predicates { private Predicates() { } /** * Creates a new condition based on a function invocation for the {@code exists()} * function. See exists. * @param property the property to be passed to {@code exists()} * @return a function call for {@code exists()} for one property */ static Condition exists(Property property) { return new BooleanFunctionCondition(FunctionInvocation.create(BuiltInFunctions.Predicates.EXISTS, property)); } /** * Creates a new condition based on a function invocation for the {@code exists()} * function. See exists. * @param pattern the pattern to be passed to {@code exists()} * @return a function call for {@code exists()} for one pattern */ static Condition exists(RelationshipPattern pattern) { return new BooleanFunctionCondition(FunctionInvocation.create(BuiltInFunctions.Predicates.EXISTS, pattern)); } /** * Creates a new condition via an existential * sub-query. The statement may or may not have a {@literal RETURN} clause. It * must however not contain any updates. While it would render syntactically correct * Cypher, Neo4j does not support updates inside existential sub-queries. * @param statement the statement to be passed to {@code exists{}} * @param imports optional imports to be used in the statement (will be imported with * {@literal WITH}) * @return an existential sub-query. * @since 2023.1.0 */ static Condition exists(Statement statement, IdentifiableElement... imports) { return ExistentialSubquery.exists(statement, imports); } /** * Creates a new condition via an existential * sub-query based on the list of patterns. * @param pattern the pattern that must exists * @return an existential sub-query. * @since 2023.9.0 */ static Condition exists(PatternElement pattern) { return ExistentialSubquery.exists(List.of(pattern), null); } /** * Creates a new condition via an existential * sub-query based on the list of patterns. * @param pattern the list of patterns that must exists * @return an existential sub-query. * @since 2023.9.0 */ static Condition exists(List pattern) { return ExistentialSubquery.exists(pattern, null); } /** * Creates a new condition via an existential * sub-query based on the list of patterns and an optional {@link Where * where-clause}. * @param pattern the list of patterns that must exists * @param where an optional where-clause * @return an existential sub-query. * @since 2023.9.0 */ static Condition exists(List pattern, Where where) { return ExistentialSubquery.exists(pattern, where); } /** * Creates the {@literal ALL} predicate. * @param variable the variable referring to elements of a list * @return a builder for the {@code all()} predicate function * @since 1.1 * @see #all(SymbolicName) */ static OngoingListBasedPredicateFunction all(String variable) { return all(SymbolicName.of(variable)); } /** * Starts building a new condition based on a function invocation for the * {@code all()} function. See exists. * @param variable the variable referring to elements of a list * @return a builder for the {@code all()} predicate function * @since 1.1 */ static OngoingListBasedPredicateFunction all(SymbolicName variable) { return new Builder(BuiltInFunctions.Predicates.ALL, variable); } /** * Creates the {@literal ANY} predicate. * @param variable the variable referring to elements of a list * @return a builder for the {@code any()} predicate function * @since 1.1 * @see #any(SymbolicName) */ static OngoingListBasedPredicateFunction any(String variable) { return any(SymbolicName.of(variable)); } /** * Starts building a new condition based on a function invocation for the * {@code any()} function. See exists. * @param variable the variable referring to elements of a list * @return a builder for the {@code any()} predicate function * @since 1.1 */ static OngoingListBasedPredicateFunction any(SymbolicName variable) { return new Builder(BuiltInFunctions.Predicates.ANY, variable); } /** * Creates the {@literal NONE} predicate. * @param variable the variable referring to elements of a list * @return a builder for the {@code none()} predicate function * @since 1.1 * @see #none(SymbolicName) */ static OngoingListBasedPredicateFunction none(String variable) { return none(SymbolicName.of(variable)); } /** * Starts building a new condition based on a function invocation for the * {@code none()} function. See exists. * @param variable the variable referring to elements of a list * @return a builder for the {@code none()} predicate function * @since 1.1 */ static OngoingListBasedPredicateFunction none(SymbolicName variable) { return new Builder(BuiltInFunctions.Predicates.NONE, variable); } /** * Creates the {@literal SINGLE} predicate. * @param variable the variable referring to elements of a list * @return a builder for the {@code single()} predicate function * @since 1.1 * @see #single(SymbolicName) */ static OngoingListBasedPredicateFunction single(String variable) { return single(SymbolicName.of(variable)); } /** * Starts building a new condition based on a function invocation for the * {@code single()} function. See exists. * @param variable the variable referring to elements of a list * @return a builder for the {@code single()} predicate function * @since 1.1 */ static OngoingListBasedPredicateFunction single(SymbolicName variable) { return new Builder(BuiltInFunctions.Predicates.SINGLE, variable); } /** * Creates a new condition based on a function invocation for the {@code isEmpty()} * function. See isEmpty. *

* The argument {@code e} must refer to an expression that evaluates to a list for * {@code isEmpty()} to work * @param e an expression referring to a list * @return a function call for {@code isEmpty()} for a list * @since 2023.6.1 */ static Condition isEmpty(Expression e) { return new BooleanFunctionCondition(FunctionInvocation.create(BuiltInFunctions.Predicates.IS_EMPTY, e)); } private static class Builder implements OngoingListBasedPredicateFunction, OngoingListBasedPredicateFunctionWithList { private final BuiltInFunctions.Predicates predicate; private final SymbolicName name; private Expression listExpression; Builder(BuiltInFunctions.Predicates predicate, SymbolicName name) { Assertions.notNull(predicate, "The predicate is required"); Assertions.notNull(name, "The name is required"); this.predicate = predicate; this.name = name; } @Override public OngoingListBasedPredicateFunctionWithList in(Expression list) { Assertions.notNull(list, "The list expression is required"); this.listExpression = list; return this; } @Override public Condition where(Condition condition) { Assertions.notNull(condition, "The condition is required"); return new BooleanFunctionCondition(FunctionInvocation.create(this.predicate, new ListPredicate(this.name, this.listExpression, new Where(condition)))); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ProcedureCall.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * See StandaloneCall. * * @author Michael J. Simons * @since 2020.0.1 */ @API(status = STABLE, since = "2020.0.1") public interface ProcedureCall extends Statement, Clause { } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ProcedureCallImpl.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.internal.ProcedureName; import org.neo4j.cypherdsl.core.internal.YieldItems; import static org.apiguardian.api.API.Status.INTERNAL; /** * An internal implementation of a {@link ProcedureCall} to distinguish between calls that * return ("yield") a set of things and those who don't. * * @author Michael J. Simons * @since 2021.2.1 */ @API(status = INTERNAL, since = "2021.2.1") class ProcedureCallImpl extends AbstractStatement implements ProcedureCall { private final ProcedureName name; private final Arguments arguments; private final YieldItems yieldItems; private final Where optionalWhere; private ProcedureCallImpl(ProcedureName name, Arguments arguments, YieldItems yieldItems, Where optionalWhere) { this.name = name; this.arguments = (arguments != null) ? arguments : new Arguments(); this.yieldItems = yieldItems; this.optionalWhere = optionalWhere; } static ProcedureCall create(ProcedureName name, Arguments arguments, YieldItems yieldItems, Where optionalWhere) { if (yieldItems != null) { return new ProcedureCallImplWithResult(name, arguments, yieldItems, optionalWhere); } else { return new ProcedureCallImpl(name, arguments, null, optionalWhere); } } @Override public void accept(Visitor visitor) { visitor.enter(this); this.name.accept(visitor); Visitable.visitIfNotNull(this.arguments, visitor); Visitable.visitIfNotNull(this.yieldItems, visitor); Visitable.visitIfNotNull(this.optionalWhere, visitor); visitor.leave(this); } static final class ProcedureCallImplWithResult extends ProcedureCallImpl implements ResultStatement { private ProcedureCallImplWithResult(ProcedureName name, Arguments arguments, YieldItems yieldItems, Where optionalWhere) { super(name, arguments, yieldItems, optionalWhere); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Properties.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.STABLE; /** * Represents the properties of a {@link Node node} or a {@link Relationship relationship} * when used as part of the whole pattern (inside a {@code MATCH}, {@code CREATE} or * {@code MERGE} clause as {@code {p1: v1, p2: v2, pn: vn}}. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class Properties implements Visitable { private final MapExpression value; private Properties(MapExpression value) { this.value = value; } /** * Wraps an expression into a {@link Properties} node. * @param expression nullable expression * @return a properties expression */ public static Properties create(MapExpression expression) { return (expression != null) ? new Properties(expression) : null; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.value.accept(visitor); visitor.leave(this); } public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Property.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * A property. A property might belong to a container such as a {@link Node} or * {@link Relationship}, but it's not uncommon to extract single properties from maps or * from various datatypes such as a duration returned from stored procedures. The * container can be retrieved via {@link #getContainer()} in case the property belongs to * a node or relationship. *

* A property has always a reference to the name of the object it was extracted from. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public non-sealed interface Property extends Expression, IdentifiableElement { /** * Returns the concatenated names of the property or the external reference (See * {@link #referencedAs(String)}) if set. * @return a name to reference the property under in an external application */ @API(status = STABLE, since = "2021.1.0") String getName(); /** * Returns the actual property being looked up. The order matters, so this will return * a list, not a random collection. * @return the actual property being looked up */ List getNames(); /** * {@return the container "owning" this property} */ Named getContainer(); /** * {@return a reference to the container owning this property} */ @API(status = INTERNAL, since = "2023.1.0") default Expression getContainerReference() { if (getContainer() == null) { throw new UnsupportedOperationException(); } return getContainer().getRequiredSymbolicName(); } /** * Creates a new property with an external reference. * @param newReference an arbitrary, external reference * @return a new property */ @API(status = STABLE, since = "2021.1.0") Property referencedAs(String newReference); /** * Creates an {@link Operation} setting this property to a new value. The property * does not track the operations created with this method. * @param expression expression describing the new value * @return a new operation. */ Operation to(Expression expression); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/PropertyAccessor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * This interface represents an element that has properties. * * @author Andreas Berger * @author Michael J. Simons * @since 2024.1.0 */ @API(status = STABLE, since = "2024.1.0") public interface PropertyAccessor { /** * Creates a new {@link Property} associated with this element. This property can be * used as a lookup in other expressions. It does not add a value to the property. *

* Note: The element does not track property creation and there is no possibility to * enumerate all properties that have been created for this property container. * @param name property name, must not be {@literal null} or empty. * @return a new {@link Property} associated with this element */ default Property property(String name) { return property(new String[] { name }); } /** * Returns a new {@link Property} associated with this element. * @param names a list of nested property names * @return a new {@link Property} associated with this element * @see #property(String) */ Property property(String... names); /** * Creates a new {@link Property} associated with this element. This property can be * used as a lookup in other expressions. It does not add a value to the property. *

* The new {@link Property} object is a dynamic lookup, based on the * {@code expression} passed to this method. The expression can be example another * property, a function result or a Cypher parameter. A property defined in such a way * will render as {@code p[expression]}. *

* Note: The element does not track property creation and there is no possibility to * enumerate all properties that have been created for this property container. * @param lookup the expression that is evaluated to lookup this property. * @return a new {@link Property} associated with this element */ Property property(Expression lookup); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/PropertyContainer.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * A container having properties. A property container must be {@link Named} with a non * empty name to be able to refer to properties. * * @author Andreas Berger * @author Michael J. Simons * @since 1.1 */ @API(status = STABLE, since = "1.1") public interface PropertyContainer extends Named, PropertyAccessor { /** * Creates an {@link Operation} mutating the properties of this container to a new * value. The container does not track the operations created with this method. * @param parameter the new properties * @return a new operation. * @since 2020.1.5 */ Operation mutate(Parameter parameter); /** * Creates an {@link Operation} mutating the properties of this container to a new * value. The container does not track the operations created with this method. * @param properties the new properties * @return a new operation. * @since 2020.1.5 */ Operation mutate(MapExpression properties); /** * Creates an {@link Operation SET operation} setting the properties of this container * to a new value. The container does not track the operations created with this * method. * @param parameter the new properties * @return a new operation. * @since 2022.5.0 */ Operation set(Parameter parameter); /** * Creates an {@link Operation SET operation} setting the properties of this container * to a new value. The container does not track the operations created with this * method. * @param properties the new properties * @return a new operation. * @since 2022.5.0 */ Operation set(MapExpression properties); /** * Unwraps the list of entries into an array before creating a projection out of it. * @param entries a list of entries for the projection * @return a map projection. * @see SymbolicName#project(List) */ MapProjection project(List entries); /** * Creates a map projection based on this container. The container needs a symbolic * name for this to work. * @param entries a list of entries for the projection * @return a map projection. * @see SymbolicName#project(Object...) */ MapProjection project(Object... entries); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/PropertyLookup.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * See PropertyLookup. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class PropertyLookup implements Expression { private static final PropertyLookup WILDCARD = new PropertyLookup(Asterisk.INSTANCE, false); private final Expression propertyKeyName; /** This flag is set to true for dynamic lookups via `p['x']` notation. */ private final boolean dynamicLookup; private PropertyLookup(Expression propertyKeyName, boolean dynamicLookup) { this.propertyKeyName = propertyKeyName; this.dynamicLookup = dynamicLookup; } /** * This creates a property lookup for a given name. It is mostly usable when building * an AST outside the fluent API. If you need to create property lookup for a * {@link SymbolicName symbolic name}, most likely you can just use the symbolic name. * @param name the name to lookup * @return a property lookup * @since 2021.3.0 */ public static PropertyLookup forName(String name) { Assertions.hasText(name, "The property's name is required."); return new PropertyLookup(SymbolicName.unsafe(name), false); } static PropertyLookup forExpression(Expression expression) { Assertions.notNull(expression, "The expression is required"); return new PropertyLookup(expression, true); } static PropertyLookup wildcard() { return WILDCARD; } @API(status = INTERNAL) SymbolicName getPropertyKeyName() { Assertions.isTrue(this != WILDCARD, "The wildcard property lookup does not reference a specific property!"); return (SymbolicName) this.propertyKeyName; } /** * {@return true if this is a dynamic property} */ @API(status = INTERNAL) public boolean isDynamicLookup() { return this.dynamicLookup; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.propertyKeyName.accept(visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/QuantifiedPathPattern.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.STABLE; /** * Representation of quantified path patterns. * * @author Michael J. Simons * @since 2023.9.0 */ @Neo4jVersion(minimum = "5.9") @API(status = STABLE, since = "2023.9.0") public final class QuantifiedPathPattern implements PatternElement { private final TargetPattern delegate; private final Quantifier quantifier; private QuantifiedPathPattern(TargetPattern delegate, Quantifier quantifier) { this.delegate = delegate; this.quantifier = quantifier; } /** * Creates an interval quantifier. * @param lowerBound lower bound, must be greater than or equal to 0 * @param upperBound upper bound, must be greater than or equal to the lower bound * @return a quantifier */ public static Quantifier interval(Integer lowerBound, Integer upperBound) { return new IntervalQuantifier(lowerBound, upperBound); } /** * {@return the + quantifier} */ public static Quantifier plus() { return PlusQuantifier.INSTANCE; } /** * {@return the * quantifier} */ public static Quantifier star() { return StarQuantifier.INSTANCE; } static QuantifiedPathPattern of(PatternElement patternElement, Quantifier quantifier) { var delegate = (patternElement instanceof TargetPattern ppp) ? ppp : new TargetPattern(patternElement, null); return new QuantifiedPathPattern(delegate, quantifier); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.delegate.accept(visitor); Visitable.visitIfNotNull(this.quantifier, visitor); visitor.leave(this); } @Override public PatternElement where(Expression predicate) { if (predicate == null) { return this; } return of(this.delegate.where(predicate), this.quantifier); } /** * Specialized quantifier for 1 or more iterations ({@literal +} quantifier). */ @SuppressWarnings("squid:S6548") // I do like enums as singletons, deal with it, private enum PlusQuantifier implements Quantifier { INSTANCE; @Override public String toString() { return "+"; } } /** * Specialized quantifier for 0 or more iterations ({@literal *} quantifier). */ @SuppressWarnings("squid:S6548") // I do like enums as singletons, deal with it, private enum StarQuantifier implements Quantifier { INSTANCE; @Override public String toString() { return "*"; } } /** * Quantifier for path patterns. */ public sealed interface Quantifier extends Visitable { } /** * Synthetic element for the Cypher-DSL AST. */ @API(status = API.Status.INTERNAL) public static final class TargetPattern implements PatternElement { private final PatternElement delegate; private final Where innerPredicate; private TargetPattern(PatternElement delegate, Where innerPredicate) { this.delegate = delegate; this.innerPredicate = innerPredicate; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.delegate.accept(visitor); Visitable.visitIfNotNull(this.innerPredicate, visitor); visitor.leave(this); } @Override public PatternElement where(Expression predicate) { if (predicate == null) { return this; } return new TargetPattern(this.delegate, Where.from(predicate)); } } /** * Qualifier for an interval. * * @param lowerBound the lower bound to use * @param upperBound the upper bound to use */ private record IntervalQuantifier(Integer lowerBound, Integer upperBound) implements Quantifier { public IntervalQuantifier { if (lowerBound != null && lowerBound < 0) { throw new IllegalArgumentException("Lower bound must be greater than or equal to zero"); } if (upperBound != null && upperBound <= 0) { throw new IllegalArgumentException("Upper bound must be greater than zero"); } if (lowerBound != null && upperBound != null && upperBound < lowerBound) { throw new IllegalArgumentException("Upper bound must be greater than or equal to " + lowerBound); } } @Override public String toString() { var result = "{"; result += ((lowerBound() == null) ? "0" : lowerBound()); result += ","; if (upperBound() != null) { result += upperBound(); } result += "}"; return result; } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/QueryDSLAdapter.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.Path; import com.querydsl.core.types.Predicate; import org.apiguardian.api.API; import org.neo4j.cypherdsl.build.annotations.RegisterForReflection; import org.neo4j.cypherdsl.core.querydsl.CypherContext; import org.neo4j.cypherdsl.core.querydsl.ToCypherFormatStringVisitor; import static org.apiguardian.api.API.Status.INTERNAL; /** * This is a utility class to turn a several Query-DSL * {@link com.querydsl.core.types.Expression expressions} into something the Cypher-DSL * can understand. It can only be used when `com.querydsl:querydsl-core` is on the * classpath. While we try our best to translate as many expression as possible into * syntactically correct Cypher, we don't provide any guarantees that the expressions and * conditions generated are semantically correct in the context of the final query * generated. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = INTERNAL, since = "2021.1.0") @SuppressWarnings("unused") @RegisterForReflection(allDeclaredConstructors = true) final class QueryDSLAdapter implements ForeignAdapter> { private final com.querydsl.core.types.Expression expression; QueryDSLAdapter(com.querydsl.core.types.Expression expression) { this.expression = expression; } @Override public Condition asCondition() { if (!(this.expression instanceof Predicate)) { throw new IllegalArgumentException("Only Query-DSL predicates can be turned into Cypher-DSL's predicates."); } if (this.expression instanceof BooleanBuilder booleanBuilder && !booleanBuilder.hasValue()) { return Conditions.noCondition(); } CypherContext context = new CypherContext(); String formatString = this.expression.accept(ToCypherFormatStringVisitor.INSTANCE, context); return new ExpressionCondition(Cypher.raw(formatString, (Object[]) context.getExpressions())); } @Override public Expression asExpression() { CypherContext context = new CypherContext(); String formatString = this.expression.accept(ToCypherFormatStringVisitor.INSTANCE, context); return Cypher.raw(formatString, (Object[]) context.getExpressions()); } @Override public Node asNode() { if (!(this.expression instanceof Path entityPath)) { throw new IllegalArgumentException("Only Query-DSL paths can be turned into nodes."); } return Cypher.node(entityPath.getRoot().getType().getSimpleName()).named(entityPath.getMetadata().getName()); } @Override public Relationship asRelationship() { throw new UnsupportedOperationException("Not yet implemented."); } @Override public SymbolicName asName() { if (!(this.expression instanceof Path entityPath)) { throw new IllegalArgumentException("Only Query-DSL paths can be turned into names."); } return Cypher.name(entityPath.getMetadata().getName()); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/RawLiteral.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apiguardian.api.API; import org.apiguardian.api.API.Status; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; /** * Generates a raw cypher literal. The factory method is able to replace {@code $E} * placeholders with expressions passed to it. To use a {@literal $E} escape it as * {$literal \$E}. * * @author Michael J. Simons * @since 2021.0.2 */ @API(status = Status.INTERNAL) final class RawLiteral implements Expression { private static final Pattern EXPRESSION_PATTERN = Pattern.compile("((\\\\?\\$(\\w+))(?:\\s*|$))"); private static final String EXPRESSION_PLACEHOLDER = "$E"; private final List content; RawLiteral(List content) { this.content = content; } static RawLiteral create(String format, Object... mixedArgs) { Assertions.hasText(format, "Cannot create a raw literal without a format."); Map> parameters = new HashMap<>(); List all = new ArrayList<>(); for (Object mixedArg : mixedArgs) { if (mixedArg instanceof Parameter) { Parameter parameter = (Parameter) mixedArg; if (!parameter.isAnon()) { parameters.put(parameter.getName(), parameter); } } all.add(mixedArg); } Matcher m; if (!parameters.isEmpty()) { m = EXPRESSION_PATTERN.matcher(format); // "Reserve" all placeholders that match actual parameter names by removing // them from the all // args list so that they are taken with precedence. while (m.find()) { if (parameters.containsKey(m.group(3))) { all.remove(parameters.get(m.group(3))); } } } List content = new ArrayList<>(); m = EXPRESSION_PATTERN.matcher(format); int i = 0; int cnt = 0; while (m.find()) { if (EXPRESSION_PLACEHOLDER.equals(m.group(2))) { content.add(new RawElement(format.substring(i, m.start(2)))); if (cnt >= all.size()) { throw new IllegalArgumentException( "Too few arguments for the raw literal format `" + format + "`."); } content.add(getMixedArg(all.get(cnt++))); i = m.end(2); } else if (parameters.containsKey(m.group(3))) { Parameter e = parameters.get(m.group(3)); content.add(new RawElement(format.substring(i, m.start(2)))); content.add(e); i = m.end(2); all.remove(e); } else { content.add(new RawElement(format.substring(i, m.end()))); i = m.end(); } } if (cnt < all.size()) { throw new IllegalArgumentException("Too many arguments for the raw literal format `" + format + "`."); } content.add(new RawElement(format.substring(i))); return new RawLiteral(Collections.unmodifiableList(content)); } private static Expression getMixedArg(Object argument) { if (argument instanceof Expression expression) { return expression; } return Cypher.literalOf(argument); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.content.forEach(expression -> expression.accept(visitor)); visitor.leave(this); } static class RawElement extends LiteralBase { RawElement(String content) { super(unescapeEscapedPlaceholders(content)); } private static String unescapeEscapedPlaceholders(String content) { return content.replace("\\$E", "$E"); } @Override public String asString() { return this.content; } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ReadingClause.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; /** * A reading clause does not do any updates. * * @author Michael J. Simons * @since 1.0 */ interface ReadingClause extends Clause { } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Reduction.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import org.neo4j.cypherdsl.core.ast.TypedSubtree; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * A typed subtree representing the arguments of a call to the {@code reduce()} function. * * @author Gerrit Meier * @author Michael J. Simons * @since 2020.1.5 * @see Cypher#reduce(SymbolicName) */ @API(status = STABLE, since = "2020.1.5") public final class Reduction extends TypedSubtree { private Reduction(Visitable... children) { super(children); } /** * Step 1: Define the variable of the reduction. * @param variable the closure will have a variable introduced in its context. We * decide here which variable to use. * @return an ongoing definition */ static OngoingDefinitionWithVariable of(SymbolicName variable) { Assertions.notNull(variable, "A variable is required"); return new Builder(variable); } /** * Step 2: Define the list. */ public interface OngoingDefinitionWithVariable { /** * Returns an ongoing definition. * @param list the list that is the subject of the reduction * @return an ongoing definition */ @CheckReturnValue OngoingDefinitionWithList in(Expression list); } /** * Step 3: Define the map expression. */ public interface OngoingDefinitionWithList { /** * Return an ongoing definition. * @param mapper this expression will run once per value in the list, and produce * the result value. * @return an ongoing definition */ @CheckReturnValue OngoingDefinitionWithReducer map(Expression mapper); } /** * Step 4a: Define the accumulator. */ public interface OngoingDefinitionWithReducer { /** * Return an ongoing definition. * @param accumulator a variable that will hold the result and the partial results * as the list is iterated. * @return an ongoing definition */ @CheckReturnValue OngoingDefinitionWithInitial accumulateOn(Expression accumulator); } /** * Step 4b: Define the initial value. */ public interface OngoingDefinitionWithInitial { /** * Return an ongoing definition. * @param initialValue an expression that runs once to give a starting value to * the accumulator. * @return an ongoing definition */ FunctionInvocation withInitialValueOf(Expression initialValue); } private static final class Builder implements OngoingDefinitionWithVariable, OngoingDefinitionWithList, OngoingDefinitionWithInitial, OngoingDefinitionWithReducer { private final SymbolicName variable; private Expression accumulatorExpression; private Expression listExpression; private Expression mapExpression; private Builder(SymbolicName variable) { this.variable = variable; } @Override public OngoingDefinitionWithList in(Expression list) { this.listExpression = list; return this; } @Override public OngoingDefinitionWithReducer map(Expression mapper) { this.mapExpression = mapper; return this; } @Override public OngoingDefinitionWithInitial accumulateOn(Expression accumulator) { this.accumulatorExpression = accumulator; return this; } @Override public FunctionInvocation withInitialValueOf(Expression initialValue) { Expression accumulatorAssignment = this.accumulatorExpression.isEqualTo(initialValue); ReductionPipeline reductionPipeline = new ReductionPipeline(this.variable, this.listExpression, this.mapExpression); Reduction reduction = new Reduction(accumulatorAssignment, reductionPipeline); return FunctionInvocation.create(BuiltInFunctions.Lists.REDUCE, reduction); } } private static final class ReductionPipeline implements Visitable { private final SymbolicName variable; private final Expression list; private final Expression expression; ReductionPipeline(SymbolicName variable, Expression list, Expression expression) { this.variable = variable; this.list = list; this.expression = expression; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.variable.accept(visitor); Operator.IN.accept(visitor); this.list.accept(visitor); Operator.PIPE.accept(visitor); this.expression.accept(visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Relationship.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Arrays; import java.util.List; import java.util.Optional; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.internal.RelationshipLength; import org.neo4j.cypherdsl.core.internal.RelationshipTypes; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * See RelationshipPattern. * * @author Michael J. Simons * @author Philipp Tölle * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface Relationship extends RelationshipPattern, PropertyContainer, ExposesProperties, ExposesPatternLengthAccessors { /** * {@return the left-hand side of this relationship} */ Node getLeft(); /** * The details containing the types, properties and cardinality. * @return a wrapper around the details of this relationship. */ Details getDetails(); /** * {@return the right-hand side of this relationship} */ Node getRight(); /** * {@return the quantifier of this relationship if any} */ QuantifiedPathPattern.Quantifier getQuantifier(); /** * Creates a copy of this relationship with a new symbolic name. * @param newSymbolicName the new symbolic name. * @return the new relationship. */ Relationship named(String newSymbolicName); /** * Creates a copy of this relationship with a new symbolic name. * @param newSymbolicName the new symbolic name. * @return the new relationship. */ Relationship named(SymbolicName newSymbolicName); /** * Creates a new relationship, inverting the direction but keeping the semantics * intact ({@code (a) --> (b)} becomes {@code (b) <-- (a)}). A symbolic name will be * removed from this relationship if any, as the it wouldn't be the same pattern to * match against. * @return the new relationship */ Relationship inverse(); /** * While the direction in the schema package is centered around the node, the * direction here is the direction between two nodes. * * @since 1.0 */ @API(status = INTERNAL, since = "1.0") enum Direction { /** * Left to right. */ LTR("-", "->"), /** * Right to left. */ RTL("<-", "-"), /** * None. */ UNI("-", "-"); private final String symbolLeft; private final String symbolRight; Direction(String symbolLeft, String symbolRight) { this.symbolLeft = symbolLeft; this.symbolRight = symbolRight; } /** * {@return the symbol to render on the left side of the relationship types} */ @API(status = INTERNAL) public String getSymbolLeft() { return this.symbolLeft; } /** * {@return the symbol to render on the right side of the relationship types} */ @API(status = INTERNAL) public String getSymbolRight() { return this.symbolRight; } } /** * See RelationshipDetail. */ @API(status = STABLE, since = "1.0") final class Details implements Visitable { /** * The direction between the nodes of the relationship. */ private final Direction direction; private final RelationshipTypes types; private final RelationshipLength length; private final Properties properties; private final Where innerPredicate; @SuppressWarnings("squid:S3077") // Symbolic name is unmodifiable private volatile SymbolicName symbolicName; private Details(Direction direction, SymbolicName symbolicName, RelationshipTypes types, RelationshipLength length, Properties properties, Where innerPredicate) { this.direction = Optional.ofNullable(direction).orElse(Direction.UNI); this.symbolicName = symbolicName; this.types = types; this.length = length; this.properties = properties; this.innerPredicate = innerPredicate; } static Details create(Direction direction, SymbolicName symbolicName, String... types) { List listOfTypes = Arrays.stream(types).filter(type -> !(type == null || type.isEmpty())).toList(); return create(direction, symbolicName, listOfTypes.isEmpty() ? null : RelationshipTypes.of(types)); } static Details create(Direction direction, SymbolicName symbolicName, RelationshipTypes types) { return new Details(direction, symbolicName, types, null, null, null); } /** * Internal helper method indicating whether the details have content or not. * @return true if any of the details are filled */ public boolean hasContent() { return this.symbolicName != null || this.types != null || this.length != null || this.properties != null; } Details named(SymbolicName newSymbolicName) { Assertions.notNull(newSymbolicName, "Symbolic name is required."); return new Details(this.direction, newSymbolicName, this.types, this.length, this.properties, this.innerPredicate); } Details with(Properties newProperties) { return new Details(this.direction, this.symbolicName, this.types, this.length, newProperties, this.innerPredicate); } Details unbounded() { return new Details(this.direction, this.symbolicName, this.types, RelationshipLength.unbounded(), this.properties, this.innerPredicate); } Details inverse() { if (this.direction == Direction.UNI) { return this; } return new Details((this.direction == Direction.LTR) ? Direction.RTL : Direction.LTR, null, this.types, this.length, this.properties, this.innerPredicate); } Details where(Expression predicate) { return new Details(this.direction, this.symbolicName, this.types, this.length, this.properties, Where.from(predicate)); } Details min(Integer minimum) { if (minimum == null && (this.length == null || this.length.getMinimum() == null)) { return this; } RelationshipLength newLength = Optional.ofNullable(this.length) .map(l -> RelationshipLength.of(minimum, l.getMaximum())) .orElseGet(() -> RelationshipLength.of(minimum, null)); return new Details(this.direction, this.symbolicName, this.types, newLength, this.properties, this.innerPredicate); } Details max(Integer maximum) { if (maximum == null && (this.length == null || this.length.getMaximum() == null)) { return this; } RelationshipLength newLength = Optional.ofNullable(this.length) .map(l -> RelationshipLength.of(l.getMinimum(), maximum)) .orElseGet(() -> RelationshipLength.of(null, maximum)); return new Details(this.direction, this.symbolicName, this.types, newLength, this.properties, this.innerPredicate); } /** * {@return the direction of the relationship} */ @API(status = INTERNAL) public Direction getDirection() { return this.direction; } Optional getSymbolicName() { return Optional.ofNullable(this.symbolicName); } SymbolicName getRequiredSymbolicName() { SymbolicName requiredSymbolicName = this.symbolicName; if (requiredSymbolicName == null) { synchronized (this) { requiredSymbolicName = this.symbolicName; if (requiredSymbolicName == null) { this.symbolicName = SymbolicName.unresolved(); requiredSymbolicName = this.symbolicName; } } } return requiredSymbolicName; } /** * {@return the relationship types being matched} */ @API(status = INTERNAL) public List getTypes() { return (this.types == null) ? List.of() : List.copyOf(this.types.getValues()); } /** * {@return the properties of this relationship} */ @API(status = INTERNAL) public Properties getProperties() { return this.properties; } @Override public void accept(Visitor visitor) { visitor.enter(this); Visitable.visitIfNotNull(this.symbolicName, visitor); Visitable.visitIfNotNull(this.types, visitor); Visitable.visitIfNotNull(this.length, visitor); Visitable.visitIfNotNull(this.properties, visitor); Visitable.visitIfNotNull(this.innerPredicate, visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/RelationshipBase.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Map; import java.util.Optional; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.internal.RelationshipPatternCondition; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * This is the base class for all relationships. It can be used with generics, specifying * valid start and end nodes. This is useful when using it as a base class for a static * metamodel. * * @param the type at the start of the relationship * @param the type at the pointy end of the relationship * @param the type of the persistent relationship itself * @author Michael J. Simons * @since 2021.1.0 */ @API(status = STABLE, since = "2021.1.0") public abstract class RelationshipBase, E extends NodeBase, SELF extends RelationshipBase> extends AbstractPropertyContainer implements Relationship { final Node left; final Node right; final Details details; final QuantifiedPathPattern.Quantifier quantifier; // ------------------------------------------------------------------------ // Public API to be used by the static metamodel. // Non-final methods are ok to be overwritten. // ------------------------------------------------------------------------ /** * Always creates a relationship from start to end (left to right). * @param start start node * @param end end node * @param type type of the relationship * @param additionalTypes additional types to add to the relationship */ protected RelationshipBase(S start, String type, E end, String... additionalTypes) { this(null, start, Direction.LTR, null, end, mergeTypesIfNecessary(type, additionalTypes)); } /** * Always creates a relationship from start to end (left to right). * @param symbolicName an optional symbolic name * @param start start node * @param properties the properties for the relationship * @param end end node * @param type type of the relationship * @param additionalTypes additional types to be added to the relationship, only * meaningful when the object is used for querying, when used in a {@code CREATE} or * {@code MERGE} clause the runtime will throw an exception. */ protected RelationshipBase(SymbolicName symbolicName, Node start, String type, Properties properties, Node end, String... additionalTypes) { this(symbolicName, start, Direction.LTR, properties, null, end, mergeTypesIfNecessary(type, additionalTypes)); } /** * Always creates a relationship from start to end (left to right). * @param symbolicName an optional symbolic name * @param start start node * @param properties the properties for the relationship * @param end end node * @param type type of the relationship */ protected RelationshipBase(SymbolicName symbolicName, String type, Node start, Properties properties, Node end) { this(symbolicName, start, Direction.LTR, properties, null, end, type); } RelationshipBase(SymbolicName symbolicName, Node left, Direction direction, QuantifiedPathPattern.Quantifier quantifier, Node right, String... types) { this(symbolicName, left, direction, null, quantifier, right, types); } RelationshipBase(SymbolicName symbolicName, Node left, Direction direction, Properties properties, QuantifiedPathPattern.Quantifier quantifier, Node right, String... types) { this(left, Details.create(direction, symbolicName, types).with(properties), quantifier, right); } RelationshipBase(Node left, Details details, QuantifiedPathPattern.Quantifier quantifier, Node right) { Assertions.notNull(left, "Left node is required."); Assertions.notNull(details, "Details are required."); Assertions.notNull(right, "Right node is required."); this.left = left; this.right = right; this.details = details; this.quantifier = quantifier; } private static String[] mergeTypesIfNecessary(String type, String... additionalTypes) { if (additionalTypes != null && additionalTypes.length > 0) { String[] result = new String[1 + additionalTypes.length]; result[0] = type; System.arraycopy(additionalTypes, 0, result, 1, additionalTypes.length); return result; } return new String[] { type }; } @Override public final SELF named(String newSymbolicName) { Assertions.hasText(newSymbolicName, "Symbolic name is required."); return named(SymbolicName.of(newSymbolicName)); } /** * This method needs to be implemented to provide new, type safe instances of this * relationship. * @param newSymbolicName the new symbolic name. * @return a new relationship */ @Override public abstract SELF named(SymbolicName newSymbolicName); @Override public final SELF withProperties(Object... keysAndValues) { MapExpression newProperties = null; if (keysAndValues != null && keysAndValues.length != 0) { newProperties = MapExpression.create(false, keysAndValues); } return withProperties(newProperties); } @Override public final SELF withProperties(Map newProperties) { return withProperties(MapExpression.create(newProperties)); } /** * This method needs to be implemented to provide new, type safe instances of this * relationship. * @param newProperties the new properties (can be {@literal null} to remove exiting * properties). * @return a new relationship */ @Override public abstract SELF withProperties(MapExpression newProperties); @Override public final Node getLeft() { return this.left; } @Override public final Node getRight() { return this.right; } @Override public final QuantifiedPathPattern.Quantifier getQuantifier() { return this.quantifier; } @Override public final Details getDetails() { return this.details; } @Override public final Relationship unbounded() { return new InternalRelationshipImpl(this.left, this.details.unbounded(), this.quantifier, this.right); } @Override public final Relationship min(Integer minimum) { return new InternalRelationshipImpl(this.left, this.details.min(minimum), this.quantifier, this.right); } @Override public final Relationship max(Integer maximum) { return new InternalRelationshipImpl(this.left, this.details.max(maximum), this.quantifier, this.right); } @Override public final Relationship length(Integer minimum, Integer maximum) { return new InternalRelationshipImpl(this.left, this.details.min(minimum).max(maximum), this.quantifier, this.right); } @Override public final Relationship inverse() { return new InternalRelationshipImpl(this.right, this.details.inverse(), this.quantifier, this.left); } @Override public final Optional getSymbolicName() { return this.details.getSymbolicName(); } @Override public final SymbolicName getRequiredSymbolicName() { return this.details.getRequiredSymbolicName(); } @Override public final RelationshipChain relationshipTo(Node other, String... types) { return RelationshipChain.create(this).add(this.right.relationshipTo(other, types)); } // ------------------------------------------------------------------------ // Internal API. // ------------------------------------------------------------------------ @Override public final RelationshipChain relationshipFrom(Node other, String... types) { return RelationshipChain.create(this).add(this.right.relationshipFrom(other, types)); } @Override public final RelationshipChain relationshipBetween(Node other, String... types) { return RelationshipChain.create(this).add(this.right.relationshipBetween(other, types)); } @Override public final Condition asCondition() { return RelationshipPatternCondition.of(this); } @Override public final void accept(Visitor visitor) { visitor.enter(this); this.left.accept(visitor); this.details.accept(visitor); Visitable.visitIfNotNull(this.quantifier, visitor); this.right.accept(visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } @Override public Relationship where(Expression predicate) { if (predicate == null) { return this; } return new InternalRelationshipImpl(this.left, this.details.where(predicate), this.quantifier, this.right); } @Override public RelationshipPattern quantifyRelationship(QuantifiedPathPattern.Quantifier newQuantifier) { if (newQuantifier == null) { return this; } return new InternalRelationshipImpl(this.left, this.details, newQuantifier, this.right); } @Override public QuantifiedPathPattern quantify(QuantifiedPathPattern.Quantifier newQuantifier) { return QuantifiedPathPattern.of(this, newQuantifier); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/RelationshipChain.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.LinkedList; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.internal.RelationshipPatternCondition; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * Represents a chain of relationships. The chain is meant to be in order and the right * node of an element is related to the left node of the next element. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class RelationshipChain implements RelationshipPattern, ExposesPatternLengthAccessors { private final LinkedList relationships; private RelationshipChain(Relationship firstElement) { this.relationships = new LinkedList<>(); this.relationships.add(firstElement); } private RelationshipChain(List firstElements, Relationship lastElement) { this.relationships = new LinkedList<>(firstElements); this.relationships.add(lastElement); } private RelationshipChain(List elements) { this.relationships = new LinkedList<>(elements); } static RelationshipChain create(Relationship firstElement) { return new RelationshipChain(firstElement); } RelationshipChain add(Relationship element) { Assertions.notNull(element, "Elements of a relationship chain must not be null."); return new RelationshipChain(this.relationships, element); } RelationshipChain replaceLast(Relationship element) { Assertions.notNull(element, "Elements of a relationship chain must not be null."); RelationshipChain newChain = new RelationshipChain(this.relationships); newChain.relationships.removeLast(); newChain.relationships.add(element); return newChain; } @Override public RelationshipChain relationshipTo(Node other, String... types) { return this.add(this.relationships.getLast().getRight().relationshipTo(other, types)); } @Override public RelationshipChain relationshipFrom(Node other, String... types) { return this.add(this.relationships.getLast().getRight().relationshipFrom(other, types)); } @Override public RelationshipChain relationshipBetween(Node other, String... types) { return this.add(this.relationships.getLast().getRight().relationshipBetween(other, types)); } /** * Replaces the last element of this chains with a copy of the relationship with the * new symbolic name. * @param newSymbolicName the new symbolic name to use * @return a new chain */ @Override public RelationshipChain named(String newSymbolicName) { Relationship lastElement = this.relationships.getLast(); return this.replaceLast(lastElement.named(newSymbolicName)); } /** * Replaces the last element of this chains with a copy of the relationship with the * new symbolic name. * @param newSymbolicName the new symbolic name to use * @return a new chain * @since 2021.1.1 */ @Override public RelationshipChain named(SymbolicName newSymbolicName) { Relationship lastElement = this.relationships.getLast(); return this.replaceLast(lastElement.named(newSymbolicName)); } @Override public RelationshipChain where(Expression predicate) { if (predicate == null) { return this; } var lastElement = this.relationships.getLast(); return this.replaceLast((Relationship) lastElement.where(predicate)); } @Override public RelationshipPattern quantifyRelationship(QuantifiedPathPattern.Quantifier quantifier) { if (quantifier == null) { return this; } var lastElement = this.relationships.getLast(); return this.replaceLast((Relationship) lastElement.quantifyRelationship(quantifier)); } @Override public QuantifiedPathPattern quantify(QuantifiedPathPattern.Quantifier newQuantifier) { return QuantifiedPathPattern.of(this, newQuantifier); } /** * Changes the length of the last element of this chain to an unbounded pattern. * @return a new chain * @since 1.1.1 */ @Override public RelationshipChain unbounded() { Relationship lastElement = this.relationships.getLast(); return this.replaceLast(lastElement.unbounded()); } /** * Changes the length of the last element of this chain to a new minimum length. * @param minimum the new minimum * @return a new chain */ @Override public RelationshipChain min(Integer minimum) { Relationship lastElement = this.relationships.getLast(); return this.replaceLast(lastElement.min(minimum)); } /** * Changes the length of the last element of this chain to a new maximum length. * @param maximum the new maximum * @return a new chain */ @Override public RelationshipChain max(Integer maximum) { Relationship lastElement = this.relationships.getLast(); return this.replaceLast(lastElement.max(maximum)); } /** * Changes the length of the last element of this chain. * @param minimum the new minimum * @param maximum the new maximum * @return a new chain */ @Override public RelationshipChain length(Integer minimum, Integer maximum) { Relationship lastElement = this.relationships.getLast(); return this.replaceLast(lastElement.length(minimum, maximum)); } /** * Adds properties to the last element of this chain. * @param newProperties the new properties (can be {@literal null} to remove exiting * properties). * @return a new chain */ public RelationshipChain properties(MapExpression newProperties) { Relationship lastElement = this.relationships.getLast(); return this.replaceLast(lastElement.withProperties(newProperties)); } /** * Adds properties to the last element of this chain. * @param keysAndValues a list of key and values. Must be an even number, with * alternating {@link String} and {@link Expression}. * @return a new chain */ public RelationshipChain properties(Object... keysAndValues) { Relationship lastElement = this.relationships.getLast(); return this.replaceLast(lastElement.withProperties(keysAndValues)); } @Override public Condition asCondition() { return RelationshipPatternCondition.of(this); } @Override public void accept(Visitor visitor) { visitor.enter(this); Node lastNode = null; for (Relationship relationship : this.relationships) { visitor.enter(relationship); relationship.getLeft().accept(visitor); relationship.getDetails().accept(visitor); Visitable.visitIfNotNull(relationship.getQuantifier(), visitor); visitor.leave(relationship); lastNode = relationship.getRight(); } Visitable.visitIfNotNull(lastNode, visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/RelationshipPattern.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import static org.apiguardian.api.API.Status.STABLE; /** * A shared, public interface for {@link Relationship relationships} and * {@link RelationshipChain chains of relationships}. This interface reassembles the * RelationshipPattern. *

* This interface can be used synonymous with the concept of a Path * Pattern. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface RelationshipPattern extends PatternElement, ExposesRelationships { /** * Turns the pattern into a named chain of relationships. * @param name the name to be used. * @return a named relationship that can be chained with more relationship * definitions. */ @CheckReturnValue ExposesRelationships named(String name); /** * Turns the pattern into a named chain of relationships. * @param name the name to be used. * @return a named relationship that can be chained with more relationship * definitions. */ @CheckReturnValue ExposesRelationships named(SymbolicName name); /** * Transform this pattern into a condition. All names of the patterns must be known * upfront in the final statement, as PatternExpressions are not allowed to introduce * new variables. * @return a condition based on this pattern. * @since 2021.0.0 */ Condition asCondition(); /** * Quantifies the relationship. * @param quantifier the quantifier to use * @return a quantified relationship * @since 2023.9.0 */ default PatternElement quantifyRelationship(QuantifiedPathPattern.Quantifier quantifier) { throw new UnsupportedOperationException(); } /** * Quantifies the pattern. * @param quantifier the quantifier to use * @return a quantified path pattern * @since 2023.9.0 */ default PatternElement quantify(QuantifiedPathPattern.Quantifier quantifier) { throw new UnsupportedOperationException(); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Remove.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.STABLE; /** * See Remove. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class Remove extends AbstractClause implements UpdatingClause { private final ExpressionList setItems; Remove(ExpressionList setItems) { this.setItems = setItems; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.setItems.accept(visitor); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/RendererBridge.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Dialect; import org.neo4j.cypherdsl.core.renderer.GeneralizedRenderer; import org.neo4j.cypherdsl.core.renderer.Renderer; /** * A bridge to the renderer as a single entry point from core to the renderer * infrastructure. * * @author Michael J. Simons * @since 2023.1.0 */ final class RendererBridge { private static final Configuration CONFIGURATION = Configuration.newConfig() .withDialect(Dialect.NEO4J_5_DEFAULT_CYPHER) .alwaysEscapeNames(false) .build(); private RendererBridge() { } static String render(Visitable visitable) { String name; Class clazz = visitable.getClass(); if (clazz.isAnonymousClass()) { name = clazz.getName(); } else { name = clazz.getSimpleName(); } return "%s{cypher=%s}".formatted(name, Renderer.getRenderer(CONFIGURATION, GeneralizedRenderer.class).render(visitable)); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ResultStatement.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * A statement that returns items from the graph. The shape of those items can be pretty * much anything: A list of records containing only properties, or nodes with properties * mixed with relationships and so on. The only guarantee given is that the query will * return some data when executed. * * @author Michael J. Simons * @since 2021.2.1 */ @API(status = STABLE, since = "2021.2.1") public interface ResultStatement extends Statement { } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Return.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.DefaultStatementBuilder.OrderBuilder; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.internal.Distinct; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * See Return. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class Return implements Clause { private final Distinct distinct; private final ReturnBody body; private final boolean raw; private Return(boolean raw, boolean distinct, ExpressionList returnItems, Order order, Skip skip, Limit limit) { this.distinct = (!raw && distinct) ? Distinct.INSTANCE : null; this.body = new ReturnBody(returnItems, order, skip, limit); this.raw = raw; } static Return create(boolean raw, boolean distinct, List returnList, OrderBuilder orderBuilder) { if (returnList.isEmpty()) { return null; } if (raw) { String message = "A raw return must consist of exactly one raw expression."; Assertions.isTrue(returnList.size() == 1, message); Expression firstExpression = returnList.get(0); Assertions.isTrue(firstExpression instanceof RawLiteral || firstExpression instanceof AliasedExpression ae && ae.getDelegate() instanceof RawLiteral, message); } ExpressionList returnItems = new ExpressionList(returnList); return new Return(raw, distinct, returnItems, orderBuilder.buildOrder().orElse(null), orderBuilder.getSkip(), orderBuilder.getLimit()); } @Override public void accept(Visitor visitor) { visitor.enter(this); Visitable.visitIfNotNull(this.distinct, visitor); this.body.accept(visitor); visitor.leave(this); } /** * {@return true if this clause has been created with proper, literal Cypher in place} */ @API(status = INTERNAL) public boolean isRaw() { return this.raw; } Distinct getDistinct() { return this.distinct; } ReturnBody getBody() { return this.body; } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ReturnBody.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * The container or "body" for return items, order and optional skip and things. See * ReturnBody * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class ReturnBody implements Visitable { private final ExpressionList returnItems; private final Order order; private final Skip skip; private final Limit limit; ReturnBody(ExpressionList returnItems, Order order, Skip skip, Limit limit) { this.returnItems = returnItems; this.order = order; this.skip = skip; this.limit = limit; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.returnItems.accept(visitor); Visitable.visitIfNotNull(this.order, visitor); Visitable.visitIfNotNull(this.skip, visitor); Visitable.visitIfNotNull(this.limit, visitor); visitor.leave(this); } @API(status = INTERNAL) List getReturnItems() { return this.returnItems.getChildren(); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Set.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.STABLE; /** * See Set. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class Set extends AbstractClause implements UpdatingClause { private final ExpressionList setItems; Set(ExpressionList setItems) { this.setItems = setItems; } /** * Creates a {@literal SET} clause based on the given updates. No runtime checks are * whether those expressions are actually updates. Please be mindful here. * @param update the update to apply * @param more additional updates * @return a {@link Set} clause * @since 2023.4.0 */ static Set set(Expression update, Expression... more) { if (more == null || more.length == 0) { return new Set(new ExpressionList(List.of(update))); } List finalExpressionList = new ArrayList<>(); finalExpressionList.add(update); Collections.addAll(finalExpressionList, more); return new Set(new ExpressionList(finalExpressionList)); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.setItems.accept(visitor); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/SinglePartQuery.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Statement.SingleQuery; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.INTERNAL; /** * See SinglePartQuery. * * @author Michael J. Simons * @since 1.0 */ @API(status = INTERNAL, since = "1.0") class SinglePartQuery extends AbstractStatement implements SingleQuery { private final List precedingClauses; private SinglePartQuery(List precedingClauses) { this.precedingClauses = new ArrayList<>(precedingClauses); } static SinglePartQuery create(List precedingClauses, Clause returnOrFinish) { if (precedingClauses.isEmpty() || precedingClauses.get(precedingClauses.size() - 1) instanceof Match) { Assertions.notNull(returnOrFinish, "A returning or finishing clause is required."); } if (returnOrFinish == null) { if (precedingClauses.get(precedingClauses.size() - 1) instanceof ResultStatement) { return new SinglePartQueryAsResultStatementWrapper(precedingClauses); } return new SinglePartQuery(precedingClauses); } else { return new SinglePartQueryWithFinishingClause(precedingClauses, returnOrFinish); } } @Override public void accept(Visitor visitor) { visitor.enter(this); this.precedingClauses.forEach(c -> c.accept(visitor)); visitor.leave(this); } static final class SinglePartQueryAsResultStatementWrapper extends SinglePartQuery implements ResultStatement { private SinglePartQueryAsResultStatementWrapper(List precedingClauses) { super(precedingClauses); } } static final class SinglePartQueryWithFinishingClause extends SinglePartQuery implements ResultStatement { private final Clause finishingClause; private SinglePartQueryWithFinishingClause(List precedingClauses, Clause finishingClause) { super(precedingClauses); this.finishingClause = finishingClause; } @Override public void accept(Visitor visitor) { visitor.enter(this); super.precedingClauses.forEach(c -> c.accept(visitor)); this.finishingClause.accept(visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Skip.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * Representation of the {@code SKIP} clause. * * @author Gerrit Meier * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class Skip implements Visitable { private final Expression expression; private Skip(Expression expression) { this.expression = expression; } static Skip create(Expression expression) { Assertions.notNull(expression, "Cannot skip a value of null."); return new Skip(expression); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.expression.accept(visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/SortItem.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Optional; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * A sort item can be used in an {@code ORDER BY} clause and changes the order of the * items being returned from a query. * * @author Gerrit Meier * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class SortItem implements Visitable { private final Expression expression; private final Direction direction; private SortItem(Expression expression, Direction direction) { this.expression = expression; this.direction = direction; } static SortItem create(Expression expression, Direction direction) { Assertions.notNull(expression, "Expression to sort must not be null."); return new SortItem(expression, Optional.ofNullable(direction).orElse(SortItem.Direction.UNDEFINED)); } /** * Creates a new sort item from {@literal this} instance, setting the sort direction * to ascending. * @return a new sort item. */ public SortItem ascending() { return new SortItem(this.expression, Direction.ASC); } /** * Creates a new sort item from {@literal this} instance, setting the sort direction * to descending. * @return a new sort item. */ public SortItem descending() { return new SortItem(this.expression, Direction.DESC); } @Override public void accept(Visitor visitor) { visitor.enter(this); Expressions.nameOrExpression(this.expression).accept(visitor); if (this.direction != Direction.UNDEFINED) { this.direction.accept(visitor); } visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } /** * Sort direction. * * @since 1.0 */ @API(status = STABLE) public enum Direction implements Visitable { /** Undefined direction. */ UNDEFINED(""), /** Ascending order. */ ASC("ASC"), /** Descending order. */ DESC("DESC"); private final String symbol; Direction(String symbol) { this.symbol = symbol; } /** * {@return the database internal symbol for a direction} */ public String getSymbol() { return this.symbol; } @Override public String toString() { return RendererBridge.render(this); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Statement.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.StatementBuilder.OngoingStandaloneCallWithoutArguments; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.internal.LoadCSV; import org.neo4j.cypherdsl.core.internal.ProcedureName; import org.neo4j.cypherdsl.core.internal.UsingPeriodicCommit; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * Shall be the common interfaces for queries that we support. *

* For reference see: Cypher. * We have skipped the intermediate "Query" structure so a statement in the context of * this generator is either a {@link RegularQuery} or a {@code StandaloneCall}. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface Statement extends Visitable { /** * {@return a new statement builder} */ static StatementBuilder builder() { return new DefaultStatementBuilder(); } /** * Creates a statement based on a list of {@link Clause clauses}. It is your task to * provide a sanity check of the clauses. The builder will use the clauses "as is", * neither change their order nor their type. * @param clauses a list of clauses, must not be null * @return a statement * @since 2021.3.0 */ static Statement of(List clauses) { Assertions.notNull(clauses, "Clauses must not be null."); return new ClausesBasedStatement(clauses, null); } /** * Creates a statement based on a list of {@link Clause clauses} and prepends it with * {@literal USING PERIODIC COMMIT}. It is your task to provide a sanity check of the * clauses. The builder will use the clauses "as is", neither change their order nor * their type. *

* The {@literal USING PERIODIC HINT} is only valid right before the * {@literal LOAD CSV clause}. * @param batchSize the batch size to pass to {@literal PERIODIC COMMIT}. * @param clauses a list of clauses, must not be null * @return a statement * @since 2021.3.0 */ @API(status = STABLE, since = "2021.3.0") static Statement usingPeriodic(Integer batchSize, List clauses) { Assertions.notNull(clauses, "Clauses must not be null."); Assertions.isTrue(!clauses.isEmpty(), "Clauses must not be empty."); Assertions.isInstanceOf(LoadCSV.class, clauses.get(0), "First clause must be a LOAD CSV clause."); return new ClausesBasedStatement(clauses, new UsingPeriodicCommit(batchSize)); } /** * Returns an entry point into a statement that starts with a call to stored * * procedure. * @param namespaceAndProcedure the fully qualified name of a stored procedure. Each * part can be given as a separate String, but a fully qualified String is ok as well. * @return an entry point into a statement that starts with a call to stored procedure */ static OngoingStandaloneCallWithoutArguments call(String... namespaceAndProcedure) { return new DefaultStatementBuilder.StandaloneCallBuilder(ProcedureName.from(namespaceAndProcedure)); } /** * Analyzes the statement and provides access to the resolved properties, their * (potential) owners and the context in which they have been resolved. Identifiable * expressions may be retrieved via * {@link StatementCatalog#getIdentifiableExpressions()}. * @return an immutable object representing properties resolved in a statement * together with their context and owner * @since 2023.1.0 */ StatementCatalog getCatalog(); /** * This method uses the default renderer to create a String representation of this * statement. The generated Cypher will use escaped literals and correct placeholders * like {@code $param} for parameters. The placeholders for parameters can be * retrieved via {@link StatementCatalog#getParameterNames}. Bounded values for * parameters can be retrieved via {@link StatementCatalog#getParameters()}. *

* This method is thread safe. * @return a valid Cypher statement * @since 2021.0.0 */ String getCypher(); /** * {@return the context of this statement, allowing access to parameter names etc} */ @API(status = INTERNAL, since = "2021.0.0") StatementContext getContext(); /** * Some constants may be rendered as parameters. * @return true if literal parameters hav */ boolean isRenderConstantsAsParameters(); /** * Use this method to configure whether some constant values should be rendered as * parameters or as literals before the first call to * {@link StatementCatalog#getParameters()} or {@link Statement#getCypher()}. *

* Renderers are free to chose to ignore this. * @param renderConstantsAsParameters set to true to render constants as parameters * (when using {@link #getCypher()}. */ void setRenderConstantsAsParameters(boolean renderConstantsAsParameters); /** * {@return true if this statement can be assured to return something} */ default boolean doesReturnOrYield() { return this instanceof ResultStatement || this instanceof UnionQueryImpl; } /** * Represents {@code RegularQuery}. * * @since 1.0 */ interface RegularQuery extends Statement { } /** * Represents a {@code SingleQuery}. * * @since 1.0 */ interface SingleQuery extends RegularQuery { } /** * Represents a {@code UnionQuery}. * * @since 2023.0.0 */ sealed interface UnionQuery extends RegularQuery permits UnionQueryImpl { } /** * Represents a {@code USE} statement, utilizing a composite graph call. A statement * utilizing composite databases might use an {@code EXPLAIN} clause but cannot be * profiled (as of Neo4j 5.3). * * @since 2023.0.0 */ sealed interface UseStatement extends Statement permits DecoratedQuery { /** * {@return a statement decorated with EXPLAIN} */ default Statement explain() { return DecoratedQuery.explain(this); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/StatementBuilder.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Collection; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.annotations.CheckReturnValue; import org.neo4j.cypherdsl.core.internal.RelationshipPatternCondition; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.STABLE; /** * The statement builder is the union of several other builders that all expose various * clauses. * * @author Michael J. Simons * @author Gerrit Meier * @author Andreas Berger * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface StatementBuilder extends ExposesMatch, ExposesCreate, ExposesMerge, ExposesUnwind, ExposesReturning, ExposesFinish, ExposesSubqueryCall, ExposesWith { /** * An ongoing update statement that can be used to chain more update statements or add * a with or return clause. * * @since 1.0 */ interface OngoingUpdate extends BuildableStatement, ExposesCreate, ExposesMerge, ExposesDelete, ExposesReturning, ExposesFinish, ExposesWith, ExposesSet, ExposesForeach { } /** * An ongoing update statement that can be used to chain more updating statements, * define actions on a merge or add a with or return clause. * * @since 2021.0.0 */ interface OngoingMerge extends OngoingUpdate, ExposesMergeAction, ExposesSetAndRemove { } /** * A shared marker interface for things that can be turned into a subquery to be used * inside the WHERE clause. * * @since 2020.1.2 */ interface ExposesExistentialSubqueryCall { /** * This can be used against a 4.x database to turn this ongoing match statement * into a condition to be used in an existential subquery. * @return an existential subquery. * @neo4j.version 4.0.0 */ @Neo4jVersion(minimum = "4.0.0") Condition asCondition(); } /** * A match that exposes {@code returning} and {@code where} methods to add required * information. While the where clause is optional, a returning clause needs to be * specified before the statement can be built. * * @since 1.0 */ interface OngoingReadingWithoutWhere extends OngoingReading, ExposesHints, ExposesWhere, ExposesMatch, ExposesExistentialSubqueryCall { } /** * A match that has a non-empty {@code where}-part. THe returning clause is still * open. * * @since 1.0 */ interface OngoingReadingWithWhere extends OngoingReading, ExposesMatch, ExposesLogicalOperators, ExposesExistentialSubqueryCall { } /** * A match that exposes {@code returning} and for which it is not decided whether the * optional where part has been used or note. * * @since 1.0 */ interface OngoingReading extends ExposesReturning, ExposesFinish, ExposesWith, ExposesUpdatingClause, ExposesUnwind, ExposesCreate, ExposesMatch, ExposesCall, ExposesSubqueryCall { } /** * Builder part for unwinding. * * @since 1.0 */ interface OngoingUnwind { /** * Adds an {@code AS} part that allows to define an alias for the iterable being * unwound. * @param variable the alias name * @return a normal, ongoing read. */ @CheckReturnValue OngoingReading as(String variable); /** * Reuse an existing symbolic name. * @param variable a symbolic name * @return a normal, ongoing read. * @since 2021.0.2 */ @CheckReturnValue default OngoingReading as(SymbolicName variable) { return as(variable.getValue()); } } /** * A match that knows what to return and which is ready to be build. * * @since 1.0 */ interface OngoingReadingAndReturn extends TerminalExposesOrderBy, TerminalExposesSkip, TerminalExposesLimit, BuildableStatement { /** * Returns the set of identifiable expressions in the {@literal RETURN} clause, * the final statement might have a different set. * @return the set of identifiable expressions in the {@literal RETURN} clause * @since 2021.3.2 */ Collection getIdentifiableExpressions(); } /** * A match that knows what to pipe to the next part of a multipart query. * * @since 1.0 */ interface OrderableOngoingReadingAndWithWithoutWhere extends OrderableOngoingReadingAndWith { /** * Adds a where clause to this match. * @param condition the new condition, must not be {@literal null} * @return a match restricted by a where clause with no return items yet. */ @CheckReturnValue OrderableOngoingReadingAndWithWithWhere where(Condition condition); /** * Adds a where clause based on a path pattern to this match. See Using * path patterns in WHERE. * @param pathPattern the path pattern to add to the where clause. This path * pattern must not be {@literal null} and must not introduce new variables not * available in the match. * @return a match restricted by a where clause with no return items yet. * @since 1.0.1 */ @CheckReturnValue default OrderableOngoingReadingAndWithWithWhere where(RelationshipPattern pathPattern) { Assertions.notNull(pathPattern, "The path pattern must not be null."); return this.where(RelationshipPatternCondition.of(pathPattern)); } } /** * Represents a reading statement ending in a combination of {@code WITH} and * {@code WHERE} clauses. * * @since 1.0 * @see OrderableOngoingReadingAndWith * @see ExposesLogicalOperators */ interface OrderableOngoingReadingAndWithWithWhere extends OrderableOngoingReadingAndWith, ExposesLogicalOperators { } /** * Represents a reading statement ending in a with clause, potentially already having * an order and not exposing order methods. * * @since 1.0 */ interface OngoingReadingAndWith extends OngoingReading, ExposesMatch, ExposesLoadCSV { } /** * Represents a reading statement ending in a {@code WITH} clause. * * @since 1.0 * @see OngoingReadingAndWith */ interface OrderableOngoingReadingAndWith extends ExposesOrderBy, ExposesSkip, ExposesLimit, OngoingReadingAndWith { /** * Returns the set of identifiable expressions in the {@literal WITH} clause. The * final statement might have a different set. * @return the set of identifiable expressions in the {@literal WITH} clause * @since 2021.3.2 */ Collection getIdentifiableExpressions(); } /** * Combines the capabilities of skip, limit and adds additional expressions to the * order-by items. * * @since 1.0 */ interface OngoingMatchAndReturnWithOrder extends TerminalExposesSkip, TerminalExposesLimit, BuildableStatement { /** * Adds another expression to the list of order items. * @param expression the expression that is added with an {@literal AND} * @return a new order specifying step. */ @CheckReturnValue TerminalOngoingOrderDefinition and(Expression expression); } /** * An intermediate step while defining the order of a result set. This definitional * will eventually return a buildable statement and thus is terminal. * * @since 1.0 */ interface TerminalOngoingOrderDefinition extends TerminalExposesSkip, TerminalExposesLimit, BuildableStatement { /** * Specifies descending order and jumps back to defining the match and return * statement. * @return the ongoing definition of a match */ @CheckReturnValue OngoingMatchAndReturnWithOrder descending(); /** * Specifies ascending order and jumps back to defining the match and return * statement. * @return the ongoing definition of a match */ @CheckReturnValue OngoingMatchAndReturnWithOrder ascending(); } /** * Combines the capabilities of skip, limit and adds additional expressions to the * order-by items. * * @since 1.0 */ interface OngoingReadingAndWithWithWhereAndOrder extends ExposesSkip, ExposesLimit, OngoingReadingAndWith { /** * Adds another expression to the list of order items. * @param expression the expression that is added with an {@literal AND} * @return a new order specifying step. */ @CheckReturnValue OngoingOrderDefinition and(Expression expression); } /** * An intermediate step while defining the order of a with clause. * * @since 1.0 */ interface OngoingOrderDefinition extends ExposesSkip, ExposesLimit { /** * Specifies descending order and jumps back to defining the match and return * statement. * @return the ongoing definition of a match */ @CheckReturnValue OngoingReadingAndWithWithWhereAndOrder descending(); /** * Specifies ascending order and jumps back to defining the match and return * statement. * @return the ongoing definition of a match */ @CheckReturnValue OngoingReadingAndWithWithWhereAndOrder ascending(); } /** * A statement that has all information required to be build and exposes a build * method. * * @param the type of the statement that is returned * @since 1.0 */ interface BuildableStatement { /** * {@return the statement ready to be used, i.e. in a renderer} */ T build(); /** * Returns a statement decorated with a {@code EXPLAIN} clause. * @return a decorated statement * @since 2020.1.2 */ default Statement explain() { return DecoratedQuery.explain(build()); } /** * Returns a statement decorated with a {@code PROFILE} clause. * @return a decorated statement * @since 2020.1.2 */ default Statement profile() { return DecoratedQuery.profile(build()); } } /** * A step that exposes several methods to specify ordering. This is a terminal * operation just before a statement is buildable. * * @since 1.0 */ interface TerminalExposesOrderBy { /** * Order the result set by one or more {@link SortItem sort items}. Those can be * retrieved for all expression with {@link Cypher#sort(Expression)} or directly * from properties. * @param sortItem one or more sort items * @return a build step that still offers methods for defining skip and limit */ @CheckReturnValue OngoingMatchAndReturnWithOrder orderBy(SortItem... sortItem); /** * Order the result set by one or more {@link SortItem sort items}. Those can be * retrieved for all expression with {@link Cypher#sort(Expression)} or directly * from properties. * @param sortItem one or more sort items * @return a build step that still offers methods for defining skip and limit * @since 2021.2.2 */ @CheckReturnValue OngoingMatchAndReturnWithOrder orderBy(Collection sortItem); /** * Order the result set by an expression. * @param expression the expression to order by * @return a step that allows for adding more expression or fine-tuning the sort * direction of the last expression */ @CheckReturnValue TerminalOngoingOrderDefinition orderBy(Expression expression); } /** * A step that exposes the {@link #skip(Number)} method. * * @since 1.0 */ interface TerminalExposesSkip { /** * Adds a skip clause, skipping the given number of records. * @param number how many records to skip. If this is null, then no records are * skipped. * @return a step that only allows the limit of records to be specified. */ @CheckReturnValue TerminalExposesLimit skip(Number number); /** * Adds a skip clause. * @param expression the expression to skip by * @return a step that only allows the limit of records to be specified. * @since 2021.0.0 */ @CheckReturnValue TerminalExposesLimit skip(Expression expression); } /** * A step that exposes the {@link #limit(Number)} method. * * @since 1.0 */ interface TerminalExposesLimit extends BuildableStatement { /** * Limits the number of returned records. * @param number how many records to return. If this is null, all the records are * returned. * @return a buildable match statement. */ @CheckReturnValue BuildableStatement limit(Number number); /** * Limits the number of returned records. * @param expression how many records to return. If this is null, all the records * are returned. * @return a buildable match statement. * @since 2021.0.0 */ @CheckReturnValue BuildableStatement limit(Expression expression); } /** * Terminal operation that only allows access to {@link BuildableStatement}. A marker * interface to clarify the intention instead of just exposing the * {@code BuildableStatement}. * * @since 2024.3.0 */ interface Terminal extends BuildableStatement { } /** * See {@link TerminalExposesOrderBy}, but on a with clause. * * @since 1.0 */ interface ExposesOrderBy { /** * Order the result set by one or more {@link SortItem sort items}. Those can be * retrieved for all expression with {@link Cypher#sort(Expression)} or directly * from properties. * @param sortItem one or more sort items * @return a build step that still offers methods for defining skip and limit */ @CheckReturnValue OrderableOngoingReadingAndWithWithWhere orderBy(SortItem... sortItem); /** * Order the result set by one or more {@link SortItem sort items}. Those can be * retrieved for all expression with {@link Cypher#sort(Expression)} or directly * from properties. * @param sortItem one or more sort items * @return a build step that still offers methods for defining skip and limit * @since 2021.2.2 */ @CheckReturnValue OrderableOngoingReadingAndWithWithWhere orderBy(Collection sortItem); /** * Order the result set by an expression. * @param expression the expression to order by * @return a step that allows for adding more expression or fine-tuning the sort * direction of the last expression */ @CheckReturnValue OngoingOrderDefinition orderBy(Expression expression); } /** * The union type of an ongoing reading with a WITH and a SKIP clause. * * @since 2021.0.0 */ interface OngoingReadingAndWithWithSkip extends OngoingReadingAndWith, ExposesLimit { } /** * A step that exposes the {@link #skip(Number)} method. * * @since 1.0 */ interface ExposesSkip { /** * Adds a skip clause, skipping the given number of records. * @param number how many records to skip. If this is null, then no records are * skipped. * @return a step that only allows the limit of records to be specified. */ @CheckReturnValue OngoingReadingAndWithWithSkip skip(Number number); /** * Adds a skip clause. * @param expression how many records to skip. If this is null, then no records * are skipped. * @return a step that only allows the limit of records to be specified. * @since 2021.0.0 */ @CheckReturnValue OngoingReadingAndWithWithSkip skip(Expression expression); } /** * A step that exposes the {@link #limit(Number)} method. * * @since 1.0 */ interface ExposesLimit { /** * Limits the number of returned records. * @param number how many records to return. If this is null, all the records are * returned. * @return a buildable match statement. */ @CheckReturnValue OngoingReadingAndWith limit(Number number); /** * Limits the number of returned records. * @param expression how many records to return. If this is null, all the records * are returned. * @return a buildable match statement. * @since 2021.0.0 */ @CheckReturnValue OngoingReadingAndWith limit(Expression expression); } /** * Steps for building a {@link Foreach} clause. * * @since 2023.4.0 */ interface ExposesForeach { /** * Starts defining a {@link Foreach} clause. * @param variable the variable to use in the iterator * @return a step for selecting the source of iteration * @since 2023.4.0 */ ForeachSourceStep foreach(SymbolicName variable); } /** * Initial step of defining a {@link Foreach FOREACH-clause}. * * @since 2023.4.0 */ sealed interface ForeachSourceStep permits DefaultStatementBuilder.ForeachBuilder { /** * Defines the source to be iterated by {@code FOREACH}. Must evaluate to * something iterable, for example something like {@link Cypher#nodes(NamedPath)} * for * @param list the expression to iterate on * @return the next step. */ ForeachUpdateStep in(Expression list); } /** * Second step of defining a {@link Foreach FOREACH-clause} in which the updating * clause is defined. * * @since 2023.4.0 */ sealed interface ForeachUpdateStep permits DefaultStatementBuilder.ForeachBuilder { /** * Defines the updating clause that to will be applied to every item in the source * expression. * @param updatingClauses the updating clauses to apply * @return the final {@link Foreach} clause */ OngoingUpdate apply(UpdatingClause... updatingClauses); } /** * A step providing all the supported updating clauses (DELETE, SET). * * @since 1.0 */ interface ExposesUpdatingClause extends ExposesDelete, ExposesMerge, ExposesSetAndRemove, ExposesForeach { } /** * A step that exposes only the {@code DELETE} clause. * * @since 1.0 */ interface ExposesDelete { /** * Renders a {@code DELETE} clause targeting the given variables. NO checks are * done whether they have been matched previously. * @param variables variables indicating the things to delete. * @return a match with a {@literal DELETE} clause that can be build now */ @CheckReturnValue default OngoingUpdate delete(String... variables) { return delete(Expressions.createSymbolicNames(variables)); } /** * Renders a {@code DELETE} clause targeting the given variables. NO checks are * done whether they have been matched previously. * @param variables variables indicating the things to delete. * @return a match with a {@literal DELETE} clause that can be build now */ @CheckReturnValue default OngoingUpdate delete(Named... variables) { return delete(Expressions.createSymbolicNames(variables)); } /** * Creates a delete step with one or more expressions to be deleted. * @param expressions the expressions to be deleted. * @return a match with a {@literal DELETE} clause that can be build now */ @CheckReturnValue OngoingUpdate delete(Expression... expressions); /** * Creates a delete step with one or more expressions to be deleted. * @param expressions the expressions to be deleted. * @return a match with a {@literal DELETE} clause that can be build now * @since 2021.2.2 */ @CheckReturnValue OngoingUpdate delete(Collection expressions); /** * Renders a {@code DETACH DELETE} clause targeting the given variables. NO checks * are done whether they have been matched previously. * @param variables variables indicating the things to delete. * @return a match with a {@literal DETACH DELETE} clause that can be build now */ @CheckReturnValue default OngoingUpdate detachDelete(String... variables) { return detachDelete(Expressions.createSymbolicNames(variables)); } /** * Renders a {@code DETACH DELETE} clause targeting the given variables. NO checks * are done whether they have been matched previously. * @param variables variables indicating the things to delete. * @return a match with a {@literal DETACH DELETE} clause that can be build now */ @CheckReturnValue default OngoingUpdate detachDelete(Named... variables) { return detachDelete(Expressions.createSymbolicNames(variables)); } /** * Starts building a delete step that will use {@code DETACH} to remove * relationships. * @param expressions the expressions to be deleted. * @return a match with {@literal DETACH DELETE} clause that can be build now */ @CheckReturnValue OngoingUpdate detachDelete(Expression... expressions); /** * Starts building a delete step that will use {@code DETACH} to remove * relationships. * @param expressions the expressions to be deleted. * @return a match with {@literal DETACH DELETE} clause that can be build now * @since 2021.2.2 */ @CheckReturnValue OngoingUpdate detachDelete(Collection expressions); } /** * Set part of a statement. * * @since 1.0 */ interface ExposesSet { /** * Adds a {@code SET} clause to the statement. The list of expressions must be * even, each pair will be turned into SET operation. * @param expressions the list of expressions to use in a set clause. * @return an ongoing match and update */ @CheckReturnValue BuildableMatchAndUpdate set(Expression... expressions); /** * Adds a {@code SET} clause to the statement. The list of expressions must be * even, each pair will be turned into SET operation. * @param expressions the list of expressions to use in a set clause. * @return an ongoing match and update * @since 2021.2.2 */ @CheckReturnValue BuildableMatchAndUpdate set(Collection expressions); /** * Adds a {@code SET} clause to the statement, modifying the given named thing * with an expression. * @param variable the named thing to modify * @param expression the modifying expression * @return an ongoing match and update */ @CheckReturnValue default BuildableMatchAndUpdate set(Named variable, Expression expression) { return set(variable.getRequiredSymbolicName(), expression); } /** * Creates a {@code +=} operation. The left hand side must resolve to a container * (either a node or a relationship) of properties and the right hand side must be * a map of new or updated properties * @param target the target container that should be modified * @param properties the new properties * @return an ongoing match and update * @since 2020.1.5 */ @CheckReturnValue BuildableMatchAndUpdate mutate(Expression target, Expression properties); /** * Creates a {@code +=} operation. The left hand side must resolve to a container * (either a node or a relationship) of properties and the right hand side must be * a map of new or updated properties * @param variable the named thing to modify * @param properties the new properties * @return an ongoing match and update * @since 2020.1.5 */ @CheckReturnValue default BuildableMatchAndUpdate mutate(Named variable, Expression properties) { return mutate(variable.getRequiredSymbolicName(), properties); } } /** * Exposes node mutations. * * @param the type of the next step * @since 2023.5.0 */ interface ExposesSetLabel { /** * Creates {@code SET} clause for setting the given labels to a node. * @param node the node whose labels are to be changed * @param labels the labels to be set * @return a match with a SET clause that can be build now */ @CheckReturnValue R set(Node node, String... labels); /** * Creates {@code SET} clause for setting the given labels to a node. * @param node the node whose labels are to be changed * @param labels the labels to be set * @return a match with a SET clause that can be build now * @since 2021.2.2 */ @CheckReturnValue R set(Node node, Collection labels); /** * Creates {@code SET} clause for setting the given labels to a node. * @param node the node whose labels are to be changed * @param labels the labels to be set * @return a match with a SET clause that can be build now * @since 2025.1.0 */ @CheckReturnValue R set(Node node, Labels labels); } /** * A step that exposes the set clause. * * @since 1.0 */ interface ExposesSetAndRemove extends ExposesSet, ExposesSetLabel { /** * Creates {@code SET} clause for removing the given labels from a node. * @param node the node whose labels are to be changed * @param labels the labels to be removed * @return a match with a REMOVE clause that can be build now */ @CheckReturnValue BuildableMatchAndUpdate remove(Node node, String... labels); /** * Creates {@code SET} clause for removing the given labels from a node. * @param node the node whose labels are to be changed * @param labels the labels to be removed * @return a match with a REMOVE clause that can be build now * @since 2021.2.2 */ @CheckReturnValue BuildableMatchAndUpdate remove(Node node, Collection labels); /** * Creates {@code SET} clause for removing the given labels from a node. * @param node the node whose labels are to be changed * @param labels the labels to be removed * @return a match with a REMOVE clause that can be build now * @since 2025.1.0 */ @CheckReturnValue BuildableMatchAndUpdate remove(Node node, Labels labels); /** * Creates {@code SET} clause for removing the enumerated properties. * @param properties the properties to be removed * @return a match with a REMOVE clause that can be build now */ @CheckReturnValue BuildableMatchAndUpdate remove(Property... properties); /** * Creates {@code SET} clause for removing the enumerated properties. * @param properties the properties to be removed * @return a match with a REMOVE clause that can be build now * @since 2021.2.2 */ @CheckReturnValue BuildableMatchAndUpdate remove(Collection properties); } /** * After a MATCH..UPDATE chain has been established, a RETURN can be added, a pipeline * with WITH can be started or more mutating steps can be added. * * @since 1.0 */ interface OngoingMatchAndUpdate extends ExposesReturning, ExposesFinish, ExposesWith, ExposesUpdatingClause, ExposesCreate { } /** * A buildable ongoing MATCH and UPDATE. * * @since 2021.0.0 */ interface BuildableMatchAndUpdate extends OngoingMatchAndUpdate, BuildableStatement { } /** * Provides a way to specify an action that happens after a {@code MERGE} clause. * * @since 2020.1.2 */ interface ExposesMergeAction { /** * This allows to specify the action that should happen when the merge clause lead * to the creation of a new pattern. * @return an ongoing definition of a merge action. */ @CheckReturnValue OngoingMergeAction onCreate(); /** * This allows to specify the action that should happen when the pattern of the * merge clause already existed and matched. * @return an ongoing definition of a merge action. */ @CheckReturnValue OngoingMergeAction onMatch(); } /** * An interface combining a buildable MATCH and UPDATE with the possibility to add * actions after a MERGE clause. * * @since 2021.0.0 */ interface BuildableOngoingMergeAction extends BuildableMatchAndUpdate, ExposesMergeAction { } /** * A variant of {@link ExposesSet} that allows for further chaining of actions. * * @since 2020.1.2 */ interface OngoingMergeAction extends ExposesSetLabel { /** * Adds a {@code SET} clause to the statement. The list of expressions must be * even, each pair will be turned into SET operation. * @param expressions the list of expressions to use in a set clause. * @return an ongoing match and update */ @CheckReturnValue BuildableOngoingMergeAction set(Expression... expressions); /** * Adds a {@code SET} clause to the statement. The list of expressions must be * even, each pair will be turned into SET operation. * @param expressions the list of expressions to use in a set clause. * @return an ongoing match and update * @since 2021.2.2 */ @CheckReturnValue BuildableOngoingMergeAction set(Collection expressions); /** * Adds a {@code SET} clause to the statement, modifying the given named thing * with an expression. * @param variable the named thing to modify * @param expression the modifying expression * @return an ongoing match and update */ @CheckReturnValue default BuildableOngoingMergeAction set(Named variable, Expression expression) { return set(variable.getRequiredSymbolicName(), expression); } /** * Creates a {@code +=} operation. The left hand side must resolve to a container * (either a node or a relationship) of properties and the right hand side must be * a map of new or updated properties * @param target the target container that should be modified * @param properties the new properties * @return an ongoing match and update * @since 2020.1.5 */ @CheckReturnValue BuildableOngoingMergeAction mutate(Expression target, Expression properties); /** * Creates a {@code +=} operation. The left hand side must resolve to a container * (either a node or a relationship) of properties and the right hand side must be * a map of new or updated properties * @param variable the named thing to modify * @param properties the new properties * @return an ongoing match and update * @since 2020.1.5 */ @CheckReturnValue default BuildableOngoingMergeAction mutate(Named variable, Expression properties) { return mutate(variable.getRequiredSymbolicName(), properties); } } /** * A trait for an ongoing standalone call to expose all of its results via an * asterisk. * * @since 2022.8.3 */ interface ExposesYieldStar { /** * Mostly a helper method to indicate the overload as * {@link org.neo4j.cypherdsl.core.ExposesCall.ExposesYield} uses vargs for all * overloads, and that would not work nicely without arguments on this one here. * * Allows to use a {@literal *} in this standalone call. * @param asterisk the actual * ;) * @return the ongoing standalone call to be configured. * @since 2022.8.0 */ OngoingStandaloneCallWithReturnFields yield(Asterisk asterisk); /** * Convenience method to yield all items of this standalone call. * @return the ongoing standalone call to be configured. * @since 2022.8.0 */ default OngoingStandaloneCallWithReturnFields yieldStar() { return this.yield(Cypher.asterisk()); } } /** * The union of a buildable statement and call exposing new arguments and yields. */ interface OngoingStandaloneCallWithoutArguments extends StatementBuilder.BuildableStatement, ExposesCall.ExposesWithArgs, ExposesCall.ExposesYield, ExposesCall.AsFunction, ExposesYieldStar { /** * Turn this call into a void call to continue with querying. * @return the call, continue with a normal query from here. * @since 2022.4.0 */ VoidCall withoutResults(); } /** * The union of a buildable statement and call exposing yields. */ interface OngoingStandaloneCallWithArguments extends StatementBuilder.BuildableStatement, ExposesCall.ExposesYield, ExposesCall.AsFunction, ExposesYieldStar { /** * Turn this call into a void call to continue with querying. * @return the call, continue with a normal query from here. * @since 2022.4.0 */ VoidCall withoutResults(); } /** * A buildable statement exposing where and return clauses. */ sealed interface OngoingStandaloneCallWithReturnFields extends StatementBuilder.BuildableStatement, ExposesMatch, ExposesWhere, ExposesReturning, ExposesFinish, ExposesWith, ExposesSubqueryCall, ExposesAndThen permits DefaultStatementBuilder.YieldingStandaloneCallBuilder { } /** * The union of an in-query call exposing new arguments and yields. */ interface OngoingInQueryCallWithoutArguments extends ExposesCall.ExposesWithArgs, ExposesCall.ExposesYield { /** * Turn this call into a void call to continue with querying. * @return the call, continue with a normal query from here. * @since 2022.4.0 */ VoidCall withoutResults(); } /** * The union of an in-query call exposing yields. */ interface OngoingInQueryCallWithArguments extends ExposesCall.ExposesYield { /** * Turn this call into a void call to continue with querying. * @return the call, continue with a normal query from here. * @since 2022.4.0 */ VoidCall withoutResults(); } /** * The result of a call to a stored procedure not having any results. It is possible * to continue with "normal" querying after the execution of such a procedure. * * @since 2022.4.0 */ interface VoidCall extends OngoingReading { } /** * An in-query call exposing where and return clauses. */ interface OngoingInQueryCallWithReturnFields extends ExposesMatch, ExposesWhere, ExposesReturning, ExposesFinish, ExposesWith, ExposesSubqueryCall, ExposesForeach { } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/StatementCatalog.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; import org.neo4j.cypherdsl.core.renderer.Configuration; /** * The statement catalog gives an overview about relevant items in a statement. These * items include tokens (labels and relationship types), the resolved properties for given * sets of these tokens (those sets reassembling concrete entities, think Nodes with one * or more labels or relationships with a concrete type) as well as conditions in which * the properties have been used. This structural analysis can be used to predict how * graph elements are accessed, or they can be used to make sure certain conditions are * contained within a statement. *

* Finally, the list of identifiable elements at the end of a statement (aka after a * {@code RETURN} clause) is contained in the catalog. *

* In addition, this interface provides the namespace for all elements that are * represented as non-AST elements as part of a catalog, such as the {@link Property * properties} and {@link PropertyFilter property conditions}. *

* Any instance of a {@link StatementCatalog catalog} and its contents can be safely * assumed to be immutable. * * @author Michael J. Simons * @since 2023.1.0 */ public sealed interface StatementCatalog permits StatementCatalogBuildingVisitor.DefaultStatementCatalog { /** * Convenience method to create node label tokens. * @param label the label to be used * @return a new label token */ static Token label(String label) { return Token.label(label); } /** * Convenience method to create relationship type tokens. * @param type the type to be used * @return a new relationship type token */ static Token type(String type) { return Token.type(type); } /** * Convenience method to create a new property without a specific owner. * @param name the name of the property * @return a new property */ static Property property(String name) { return new Property(name); } /** * Convenience method to create a new property with a defined owner. * @param owner the set of tokens defining the owner * @param name the name of the property * @return a new property */ static Property property(Set owner, String name) { return new Property(owner, name); } /** * Returns a collection of all tokens used in the analyzed statement. * @return a collection of all tokens used */ Collection getAllTokens(); /** * Returns a collection of all node labels used in the analyzed statement. * @return a collection of all labels used */ default Collection getNodeLabels() { return getAllTokens().stream() .filter(token -> token.type() == Token.Type.NODE_LABEL) .collect(Collectors.toUnmodifiableSet()); } /** * Returns a collection of all relationship types used in the analyzed statement. * @return a collection of all types used */ default Collection getRelationshipTypes() { return getAllTokens().stream() .filter(token -> token.type() == Token.Type.RELATIONSHIP_TYPE) .collect(Collectors.toUnmodifiableSet()); } /** * This method can be used with any token returned from {@link #getNodeLabels()} to * retrieve relationships that have a node with the given token as start node. * Alternative, use {@link StatementCatalog#label(String)} to build labels to query * this method. * @param label the label to retrieve outgoing relations for * @return a set of tokens describing relations * @since 2023.3.0 */ Collection getOutgoingRelations(Token label); /** * This method can be used with any token returned from * {@link #getRelationshipTypes()} to retrieve target nodes of that relationship. * Alternative, use {@link StatementCatalog#type(String)} to build types to query this * method. * @param type the type of relationship to retrieve target nodes * @return a set of tokens describing labels * @since 2023.3.0 */ Collection getTargetNodes(Token type); /** * This method can be used with any token returned from {@link #getNodeLabels()} to * retrieve relationships that have a node with the given token as end node. * Alternative, use {@link StatementCatalog#label(String)} to build labels to query * this method. * @param label the label to retrieve incoming relations for * @return a set of tokens describing relations * @since 2023.3.0 */ Collection getIncomingRelations(Token label); /** * This method can be used with any token returned from * {@link #getRelationshipTypes()} to retrieve source nodes of that relationship. * Alternative, use {@link StatementCatalog#type(String)} to build types to query this * method. * @param type the type of relationship to retrieve source nodes * @return a set of tokens describing labels * @since 2023.3.0 */ Collection getSourceNodes(Token type); /** * This method can be used with any token returned from {@link #getNodeLabels()} to * retrieve relationships that are connected to nodes with the given token. * Alternative, use {@link StatementCatalog#label(String)} to build labels to query * this method. * @param label the label to retrieve relations for * @return a set of tokens describing relations * @since 2023.3.0 */ Collection getUndirectedRelations(Token label); /** * Returns a collection of all properties resolved in the analyzed statement. * @return a collection of all properties resolved */ Collection getProperties(); /** * Returns a collection all filters resolved in the analyzed statement. * @return a map of all filters. */ default Collection getAllFilters() { Set result = new HashSet<>(this.getAllLabelFilters()); this.getAllPropertyFilters().forEach((p, f) -> result.addAll(f)); return result; } /** * Returns a collection of all filters that checked for the existence of labels. * @return a collection of all label filters */ Collection getAllLabelFilters(); /** * Returns a map that contains all properties that have been used in a comparing * condition as keys and a set of all comparisons they appeared in. * @return a map of all properties used in comparisons */ Map> getAllPropertyFilters(); /** * Returns a collection of all filters applied on a specific property. * @param property the property for which filter should be retrieved * @return a collection of all conditions involving properties resolved in the * statement */ default Collection getFilters(Property property) { return getAllPropertyFilters().get(property); } /** * Returns a collection of all expressions that are identifiable expression in a * non-void (or non-unit) Cypher statement. These expressions might refer to * properties, but can be of course function calls, existential sub-queries and the * like. * @return a collection of identifiable expressions. */ Collection getIdentifiableExpressions(); /** * After a statement has been build, all parameters that have been added to the * statement can be retrieved through this method. The result will only contain * parameters with a defined value. If you are interested in all parameter names, use * {@link #getParameterNames()}. *

* The map can be used for example as an argument with various methods on the Neo4j * Java Driver that allow the execution of parameterized queries. * @return a map of all parameters with a bound value. * @since 2023.2.0 */ Map getParameters(); /** * After the statement has been build, this method returns a list of all parameter * names used, regardless whether a value was bound to the parameter o not. * @return a set of parameter names being used. * @since 2023.2.0 */ Collection getParameterNames(); /** * A statement can be configured to use generated names (see * {@link Configuration#isUseGeneratedNames()}). This method returns the used * remapping table. * @return a map of renamed parameters when * {@link Configuration#isUseGeneratedNames()} would be set to {@literal true} */ Map getRenamedParameters(); /** * Returns a collection of all literals used in a statement. * @return a collection of all literals used in a statement * @since 2023.4.0 */ @SuppressWarnings("squid:S1452") // Generic items, this is exactly what we want here Collection> getLiterals(); /** * Enum for the clause in which a comparison was made. */ enum Clause { /** * The comparison was used in a {@code MATCH} clause. */ MATCH, /** * The comparison was used in a {@code CREATE} clause. */ CREATE, /** * The comparison was used in a {@code MERGE} clause. */ MERGE, /** * The comparison was used in a {@code DELETE} clause. */ DELETE, /** * The comparison was used in a {@code WITH} clause. The {@code WITH} clause is * different to the {@code WHERE} clause as here "WHERE simply filters the * results." (quoting from where). */ WITH, /** * Used in case the analysis could not determine a clause. */ UNKNOWN } /** * This interface represents all kinds of filters in a query such as filters * on labels and filters * on properties. */ sealed interface Filter permits LabelFilter, PropertyFilter { } /** * A token can either describe a node label or a relationship type. * * @param type the type of this token * @param value the concrete value */ record Token(Type type, String value) implements Comparable { /** * Turns a specific {@link NodeLabel label} into a more abstract token. * @param label a label, must not be {@literal null}. * @return a token */ public static Token label(NodeLabel label) { return new Token(Token.Type.NODE_LABEL, Objects.requireNonNull(label, "Label must not be null.").getValue()); } /** * Turns a specific node label into a more abstract token. * @param label a label, must not be {@literal null}. * @return a token */ public static Token label(String label) { return new Token(Token.Type.NODE_LABEL, Objects.requireNonNull(label, "Label must not be null.")); } /** * Turns a specific relationship type into a more abstract token. * @param type a string representing a type, must not be {@literal null}. * @return a token */ public static Token type(String type) { return new Token(Token.Type.RELATIONSHIP_TYPE, Objects.requireNonNull(type, "Type must not be null.")); } @Override public int compareTo(Token o) { int result = this.type().compareTo(o.type()); if (result == 0) { result = this.value().compareTo(o.value()); } return result; } /** * The specific token type. */ public enum Type { /** * Represents a node label. */ NODE_LABEL, /** * Represents a relationship type. */ RELATIONSHIP_TYPE } } /** * A property that has been resolved. In case this property has been resolved for an * entity, the entity itself will be defined by its set of tokens. Tokens are * guaranteed to be sorted and will be of the same type. * * @param name the name of the resolved property * @param owningToken zero or many owning tokens for a property */ record Property(Set owningToken, String name) { /** * Creates a new property without an owner. * @param name the name of the resolved property */ public Property(String name) { this(Set.of(), name); } /** * Creates a new property with a single owning token. * @param owningToken the owning token * @param name the name of the resolved property */ public Property(Token owningToken, String name) { this(Set.of(owningToken), name); } /** * The constructor enforces the use of unmodifiable sets and unique types accross * tokens. * @param name the name of the resolved property * @param owningToken zero or many owning tokens for a property */ public Property { if (owningToken.stream().map(Token::type).distinct().count() > 1) { throw new IllegalArgumentException("Owning tokens are of multiple types"); } owningToken = Collections.unmodifiableSet(new TreeSet<>(owningToken)); } /** * {@return an optional, owning type} */ public Optional owningType() { return this.owningToken.stream().map(Token::type).distinct().findFirst(); } } /** * This type represents a filter on nodes requiring one or more labels. * * @param symbolicName the symbolic name used when creating the filter * @param value the set of tokens that made up this filter */ record LabelFilter(String symbolicName, Set value) implements Filter { /** * Makes sure the values are stored in an unmutable fashion. * @param symbolicName the symbolic name used when creating the filter * @param value the set of tokens that made up this filter */ public LabelFilter { value = Set.copyOf(value); } } /** * This type encapsulates a comparison in which a property of a node or relationship * was used. The property may appear on the left hand side or right hand side or even * on both side of the comparison (think being used in a function on both sides with * different arguments). The {@code clause} attribute will specify the context in * which the comparison was made. *

* The expressions used in the comparison are provided as Cypher-DSL AST expressions. * They can be freely visited or rendered into Cypher via the * {@link org.neo4j.cypherdsl.core.renderer.GeneralizedRenderer} like this: * *

{@code
	 *     var cypher = Renderer.getRenderer(Configuration.prettyPrinting(), GeneralizedRenderer.class)
	 *          .render(comparison.left());
	 * }
* * @param clause the clause in which the comparison was used * @param left the left hand side of the comparison * @param operator the operator used * @param right the right hand side of the comparison * @param parameterNames the parameter names used in this comparison * @param parameters parameters with defined values used in this comparison */ record PropertyFilter(Clause clause, Expression left, Operator operator, Expression right, Set parameterNames, Map parameters) implements Filter { /** * The constructor enforces the use of unmodifiable sets. * @param clause the clause in which the comparison was used * @param left the left hand side of the comparison * @param operator the operator used * @param right the right hand side of the comparison * @param parameterNames the parameter names used in this comparison * @param parameters parameters with defined values used in this comparison */ public PropertyFilter { parameterNames = Set.copyOf(parameterNames); parameters = Collections.unmodifiableMap(new HashMap<>(parameters)); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/StatementCatalogBuildingVisitor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayDeque; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.stream.Collectors; import org.neo4j.cypherdsl.build.annotations.RegisterForReflection; import org.neo4j.cypherdsl.core.ParameterCollectingVisitor.ParameterInformation; import org.neo4j.cypherdsl.core.StatementCatalog.Clause; import org.neo4j.cypherdsl.core.StatementCatalog.PropertyFilter; import org.neo4j.cypherdsl.core.StatementCatalog.Token; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.internal.Namespace; import org.neo4j.cypherdsl.core.internal.ReflectiveVisitor; import org.neo4j.cypherdsl.core.internal.ScopingStrategy; /** * This visitor creates a {@link StatementCatalog statement catalog}. It is not thread * safe and must not be used multiple times. Please create a new instance on each * invocation. * * @author Michael J. Simons * @since 2023.1.0 */ @RegisterForReflection @SuppressWarnings({ "unused", "squid:S1172" }) class StatementCatalogBuildingVisitor extends ReflectiveVisitor { /** * Constant class name for skipping compounds, not inclined to make this type public. */ private static final String TYPE_OF_COMPOUND_CONDITION = "org.neo4j.cypherdsl.core.CompoundCondition"; /** * Current clause the visitor is in. */ private final AtomicReference currentClause = new AtomicReference<>(Clause.UNKNOWN); /** * The current pattern element visited. */ private final Deque currentPatternElement = new ArrayDeque<>(); private final Set tokens = new LinkedHashSet<>(); private final Set properties = new LinkedHashSet<>(); private final Set labelFilters = new LinkedHashSet<>(); private final Map> propertyFilters = new LinkedHashMap<>(); /** * Scoped lookup tables from symbolic name to pattern elements (nodes or * relationships). */ private final Deque> patternLookup = new ArrayDeque<>(); /** * A stack of conditions (keeping track of the latest entered). */ private final Deque currentConditions = new ArrayDeque<>(); private final AtomicReference> currentHasLabelCondition = new AtomicReference<>(); /** * Required for resolving parameter names, must be from the same statement that is * analyzed. */ private final StatementContext statementContext; private final boolean renderConstantsAsParameters; /** * Delegating the hard work to the shared scope strategy in most cases. */ private final ScopingStrategy scopingStrategy; private final ParameterCollectingVisitor allParameters; private final Map> currentUndirectedRelations = new HashMap<>(); private final Map> currentIncomingRelations = new HashMap<>(); private final Map> currentOutgoingRelations = new HashMap<>(); private final Map relationships = new HashMap<>(); private final Set> literals = new LinkedHashSet<>(); StatementCatalogBuildingVisitor(StatementContext statementContext, boolean renderConstantsAsParameters) { this.statementContext = statementContext; this.renderConstantsAsParameters = renderConstantsAsParameters; this.scopingStrategy = ScopingStrategy.create( List.of((cause, imports) -> this.patternLookup.push(createNewScope(imports))), List.of((cause, exports) -> importIntoCurrentScope(exports))); this.patternLookup.push(new HashMap<>()); this.allParameters = new ParameterCollectingVisitor(statementContext, renderConstantsAsParameters); } private static void copyIdentifiableElements(Collection elements, Map source, Map target) { for (IdentifiableElement e : elements) { if (e instanceof SymbolicName s && source.containsKey(s)) { target.put(s, source.get(s)); } else if (e instanceof Named n && e instanceof PatternElement p) { target.put(n.getRequiredSymbolicName(), p); } } } private static Set getAllLabels(Node node) { Set result = new TreeSet<>(); if (node.getLabels().isEmpty()) { node.accept(segment -> { if (segment instanceof Labels l) { l.getStaticValues().stream().map(Token::label).forEach(result::add); } }); } else { node.getLabels().stream().map(NodeLabel::getValue).map(Token::label).forEach(result::add); } return result; } private Map createNewScope(Collection imports) { addRelationsInCurrentScope(); Map currentScope = this.patternLookup.isEmpty() ? Collections.emptyMap() : this.patternLookup.peek(); Map newScope = new HashMap<>(); copyIdentifiableElements(imports, currentScope, newScope); return newScope; } private void importIntoCurrentScope(Collection exports) { Map previousScope = this.patternLookup.pop(); Map currentScope = this.patternLookup.isEmpty() ? new HashMap<>() : this.patternLookup.peek(); copyIdentifiableElements(exports, previousScope, currentScope); } StatementCatalog getResult() { addRelationsInCurrentScope(); var parameterInformation = this.allParameters.getResult(); return new DefaultStatementCatalog(this.tokens, this.labelFilters, this.properties, this.propertyFilters, this.scopingStrategy.getIdentifiables(), parameterInformation, this.relationships, this.literals); } void addRelationsInCurrentScope() { finish(this.currentOutgoingRelations, Relationships::outgoing); finish(this.currentIncomingRelations, Relationships::incoming); finish(this.currentUndirectedRelations, Relationships::undirected); } /** * Finishes up the relationship's storage (retrieval of actual labels from the nodes). * @param nodesToRelations the map to process * @param targetProvider the target where to store the tokens */ private void finish(Map> nodesToRelations, Function> targetProvider) { nodesToRelations.forEach((k, v) -> { var labels = getAllLabels((Node) k.getSymbolicName().map(this::lookup).orElse(k)); labels.forEach(t -> { var rels = this.relationships.computeIfAbsent(t, unused -> new Relationships()); targetProvider.apply(rels).addAll(v); }); }); nodesToRelations.clear(); } @Override protected boolean preEnter(Visitable visitable) { this.scopingStrategy.doEnter(visitable); return true; } @Override protected void postLeave(Visitable visitable) { this.scopingStrategy.doLeave(visitable); } void enter(Match match) { this.currentClause.compareAndSet(Clause.UNKNOWN, Clause.MATCH); } void leave(Match match) { this.currentClause.compareAndSet(Clause.MATCH, Clause.UNKNOWN); } void enter(Create create) { this.currentClause.compareAndSet(Clause.UNKNOWN, Clause.CREATE); } void leave(Create create) { this.currentClause.compareAndSet(Clause.CREATE, Clause.UNKNOWN); } void enter(Merge merge) { this.currentClause.compareAndSet(Clause.UNKNOWN, Clause.MERGE); } void leave(Merge merge) { this.currentClause.compareAndSet(Clause.MERGE, Clause.UNKNOWN); } void enter(Delete delete) { this.currentClause.compareAndSet(Clause.UNKNOWN, Clause.DELETE); } void leave(Delete delete) { this.currentClause.compareAndSet(Clause.DELETE, Clause.UNKNOWN); } void enter(With with) { this.currentClause.compareAndSet(Clause.UNKNOWN, Clause.WITH); } void leave(With with) { this.currentClause.compareAndSet(Clause.WITH, Clause.UNKNOWN); } void enter(Node node) { node.getSymbolicName().ifPresent(s -> store(s, node)); this.currentPatternElement.push(node); } void enter(KeyValueMapEntry mapEntry) { var owner = this.currentPatternElement.peek(); if (owner == null) { return; } StatementCatalog.Property property; if (owner instanceof Node node) { property = new StatementCatalog.Property(getAllLabels(node), mapEntry.getKey()); } else if (owner instanceof Relationship relationship) { property = new StatementCatalog.Property( relationship.getDetails().getTypes().stream().map(Token::type).collect(Collectors.toSet()), mapEntry.getKey()); } else { property = null; } if (property == null) { return; } this.properties.add(property); Expression left; if (((PropertyContainer) owner).getSymbolicName().isPresent()) { left = ((PropertyContainer) owner).property(mapEntry.getKey()); } else { left = PropertyLookup.forName(mapEntry.getKey()); } var parameterInformation = extractParameters(mapEntry.getValue()); this.propertyFilters.computeIfAbsent(property, ignored -> new HashSet<>()) .add(new PropertyFilter(this.currentClause.get(), left, Operator.EQUALITY, mapEntry.getValue(), parameterInformation.names, parameterInformation.values)); } void leave(Node node) { this.currentPatternElement.removeFirstOccurrence(node); } void enter(Relationship relationship) { relationship.getSymbolicName().ifPresent(s -> store(s, relationship)); this.currentPatternElement.push(relationship); var types = relationship.getDetails().getTypes().stream().map(Token::type).toList(); this.tokens.addAll(types); storeRelations(relationship.getLeft(), relationship.getRight(), types, relationship.getDetails().getDirection()); } /** * Stores the source and target of this relationships in incoming/outgoing and * undirected lists for processing after leaving the scope / statement (when all * labels are known). * @param left left hand side of the relation * @param right right hand side of the relation * @param types types of the relation * @param direction direction of the relation */ private void storeRelations(Node left, Node right, List types, Relationship.Direction direction) { final Function> targetSupplier = unused -> new HashSet<>(); switch (direction) { case UNI -> { this.currentUndirectedRelations.computeIfAbsent(left, targetSupplier).addAll(types); this.currentUndirectedRelations.computeIfAbsent(right, targetSupplier).addAll(types); } case LTR -> { this.currentOutgoingRelations.computeIfAbsent(left, targetSupplier).addAll(types); this.currentIncomingRelations.computeIfAbsent(right, targetSupplier).addAll(types); } case RTL -> { this.currentIncomingRelations.computeIfAbsent(left, targetSupplier).addAll(types); this.currentOutgoingRelations.computeIfAbsent(right, targetSupplier).addAll(types); } } } void leave(Relationship relationship) { this.currentPatternElement.removeFirstOccurrence(relationship); } void enter(org.neo4j.cypherdsl.core.Property property) { if (property.getNames().size() != 1) { return; } var lookup = property.getNames().get(0); if (lookup.isDynamicLookup()) { return; } if (!(property.getContainerReference() instanceof SymbolicName s)) { return; } var propertyName = new AtomicReference(); lookup.accept(segment -> { if (segment instanceof SymbolicName name) { propertyName.compareAndSet(null, name.getValue()); } }); StatementCatalog.Property newProperty; var patternElement = lookup(s); if (patternElement instanceof Node node) { newProperty = new StatementCatalog.Property(getAllLabels(node), propertyName.get()); } else if (patternElement instanceof Relationship relationship) { newProperty = new StatementCatalog.Property( relationship.getDetails().getTypes().stream().map(Token::type).collect(Collectors.toSet()), propertyName.get()); } else { return; } this.properties.add(newProperty); if (inCurrentCondition(property)) { this.propertyFilters.computeIfAbsent(newProperty, ignored -> new HashSet<>()) .add(extractPropertyCondition(newProperty, this.currentConditions.peek())); } } void enter(Parameter parameter) { this.allParameters.enter(parameter); } private boolean inCurrentCondition(org.neo4j.cypherdsl.core.Property property) { var currentCondition = this.currentConditions.peek(); if (currentCondition == null) { return false; } var result = new AtomicBoolean(); currentCondition.accept(segment -> { if (segment == property) { result.compareAndSet(false, true); } }); return result.get(); } private PropertyFilter extractPropertyCondition(StatementCatalog.Property property, org.neo4j.cypherdsl.core.Condition condition) { var left = new AtomicReference(); var op = new AtomicReference(); var right = new AtomicReference(); condition.accept(new Visitor() { int cnt; @Override public void enter(Visitable segment) { if (++this.cnt != 2) { return; } if (segment instanceof Operator operator) { op.compareAndSet(null, operator); } else if (segment instanceof Expression expression && !left.compareAndSet(null, expression)) { right.compareAndSet(null, expression); } } @Override public void leave(Visitable segment) { --this.cnt; } }); var parameterInformation = extractParameters(left.get(), right.get()); return new PropertyFilter(this.currentClause.get(), left.get(), op.get(), right.get(), parameterInformation.names, parameterInformation.values); } void enter(NodeLabel label) { this.tokens.add(new Token(Token.Type.NODE_LABEL, label.getValue())); var currentCondition = this.currentConditions.peek(); if (currentCondition instanceof HasLabelCondition hasLabelCondition) { this.currentHasLabelCondition.get().add(Token.label(label)); } } void enter(Labels labels) { labels.getStaticValues().forEach(label -> this.tokens.add(StatementCatalog.Token.label(label))); this.extractParametersFromLabels(labels, null); } @SuppressWarnings("squid:S3776") void extractParametersFromLabels(Labels l, Labels.Type parent) { if (l == null) { return; } var current = l.getType(); extractParametersFromLabels(l.getLhs(), current); if (l.getValue() != null) { l.getValue().forEach(v -> v.accept(this)); } extractParametersFromLabels(l.getRhs(), current); } PatternElement lookup(SymbolicName s) { if (this.patternLookup.isEmpty()) { throw new IllegalStateException("Invalid scope"); } return this.patternLookup.peek().get(s); } void enter(org.neo4j.cypherdsl.core.Condition condition) { if (TYPE_OF_COMPOUND_CONDITION.equals(condition.getClass().getName())) { return; } this.currentConditions.push(condition); if (condition instanceof HasLabelCondition) { this.currentHasLabelCondition.compareAndSet(null, new TreeSet<>()); } } void enter(Literal literal) { if (literal instanceof Asterisk || literal instanceof PeriodLiteral || literal instanceof RawLiteral.RawElement || literal == LiteralBase.BLANK || literal == ListOperator.DOTS || literal instanceof Namespace) { return; } this.literals.add(literal); } void leave(org.neo4j.cypherdsl.core.Condition condition) { if (TYPE_OF_COMPOUND_CONDITION.equals(condition.getClass().getName())) { return; } this.currentConditions.pop(); var setOfRequiredTokens = this.currentHasLabelCondition.getAndSet(null); if (condition instanceof HasLabelCondition hasLabelCondition && setOfRequiredTokens != null) { AtomicReference symbolicName = new AtomicReference<>(); hasLabelCondition.accept(segment -> { if (segment instanceof SymbolicName s) { symbolicName.compareAndSet(null, s.getValue()); } }); this.labelFilters.add(new StatementCatalog.LabelFilter(symbolicName.get(), setOfRequiredTokens)); } } void store(SymbolicName s, PatternElement patternElement) { if (this.patternLookup.isEmpty()) { throw new IllegalStateException("Invalid scope"); } var currentScope = this.patternLookup.peek(); // Don't overwrite in same scope or when imported, // size == 1 catering for with clauses on top level if (currentScope.containsKey(s) && (this.scopingStrategy.getCurrentImports().contains(s) || this.patternLookup.size() == 1)) { return; } currentScope.put(s, patternElement); } private ParameterInformation extractParameters(Expression... expressions) { var parameterCollectingVisitor = new ParameterCollectingVisitor(this.statementContext, this.renderConstantsAsParameters); for (Expression expression : expressions) { if (expression == null) { continue; } expression.accept(parameterCollectingVisitor); } return parameterCollectingVisitor.getResult(); } /** * A holder for the relationship types connected to node labels. * * @param outgoing outgoing types * @param incoming incoming types * @param undirected undirected connections */ record Relationships(Set outgoing, Set incoming, Set undirected) { Relationships() { this(new HashSet<>(), new HashSet<>(), new HashSet<>()); } static Relationships empty() { return new Relationships(Set.of(), Set.of(), Set.of()); } Relationships copy() { return new Relationships(Set.copyOf(this.outgoing), Set.copyOf(this.incoming), Set.copyOf(this.undirected)); } } static final class DefaultStatementCatalog implements StatementCatalog { private final Set tokens; private final Set properties; private final Collection labelFilters; private final Map> propertyFilters; private final Set identifiableExpressions; private final ParameterInformation parameterInformation; private final Map relationships; private final Set> literals; @SuppressWarnings("squid:S107") // Totally fine with that number of args. DefaultStatementCatalog(Set tokens, Set labelFilters, Set properties, Map> propertyFilters, Collection identifiableExpressions, ParameterInformation parameterInformation, Map relationships, Set> literals) { this.tokens = Collections.unmodifiableSet(tokens); this.labelFilters = Collections.unmodifiableSet(labelFilters); this.properties = Collections.unmodifiableSet(properties); this.propertyFilters = propertyFilters.entrySet() .stream() .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, e -> Collections.unmodifiableSet(e.getValue()))); this.identifiableExpressions = (identifiableExpressions instanceof Set s) ? Collections.unmodifiableSet(s) : Set.copyOf(identifiableExpressions); this.parameterInformation = parameterInformation; this.relationships = relationships.entrySet() .stream() .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, e -> e.getValue().copy())); this.literals = Collections.unmodifiableSet(literals); } @Override public Set getAllTokens() { return this.tokens; } @Override public Set getProperties() { return this.properties; } @Override public Collection getAllLabelFilters() { return this.labelFilters; } @Override public Map> getAllPropertyFilters() { return this.propertyFilters; } @Override public Set getIdentifiableExpressions() { return this.identifiableExpressions; } @Override public Map getParameters() { return this.parameterInformation.values; } @Override public Collection getParameterNames() { return this.parameterInformation.names; } @Override public Map getRenamedParameters() { return this.parameterInformation.renames; } @Override public Collection getOutgoingRelations(Token label) { return extractRelations(label, Relationships::outgoing); } private Collection extractRelations(Token label, Function> tokenProvider) { if (label.type() != Token.Type.NODE_LABEL) { throw new IllegalArgumentException(label + " must be a node label, not a relationship type"); } return tokenProvider.apply(this.relationships.getOrDefault(label, Relationships.empty())); } @Override public Collection getTargetNodes(Token type) { if (type.type() != Token.Type.RELATIONSHIP_TYPE) { throw new IllegalArgumentException(type + " must be a relationship type, not a node label"); } return this.relationships.entrySet() .stream() .filter(e -> e.getValue().incoming().contains(type)) .map(Map.Entry::getKey) .collect(Collectors.toSet()); } @Override public Collection getIncomingRelations(Token label) { return extractRelations(label, Relationships::incoming); } @Override public Collection getSourceNodes(Token type) { if (type.type() != Token.Type.RELATIONSHIP_TYPE) { throw new IllegalArgumentException(type + " must be a relationship type, not a node label"); } return this.relationships.entrySet() .stream() .filter(e -> e.getValue().outgoing().contains(type)) .map(Map.Entry::getKey) .collect(Collectors.toSet()); } @Override public Collection getUndirectedRelations(Token label) { return extractRelations(label, Relationships::undirected); } @Override public Set> getLiterals() { return this.literals; } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/StatementContext.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.internal.DefaultStatementContext; import static org.apiguardian.api.API.Status.INTERNAL; /** * Context for while rendering a statement. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = INTERNAL, since = "2021.1.0") public sealed interface StatementContext permits DefaultStatementContext { /** * Gets or creates the name of a parameter. * @param parameter the parameter whose name should be retrieved * @return the name of the parameter or a generated name */ String getParameterName(Parameter parameter); /** * Resolves a {@link SymbolicName symbolic name} into a string: A symbolic name can be * a placeholder without an actual value. In such cases a value is randomly generated * and will stay constant for that name as long as the statement exists. In case the * {@code symbolicName} has a constant value it will be returned, * @param symbolicName the symbolic name to resolve * @return a value for the given name * @since 2023.0.3 */ String resolve(SymbolicName symbolicName); /** * Checks whether a given {@link SymbolicName symbolic name} has been resolved in this * {@link StatementContext context}. * @param symbolicName the symbolic name to check * @return true if the given name has already been resolved in this context * @since 2023.0.3 */ boolean isResolved(SymbolicName symbolicName); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/StringLiteral.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Locale; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * The string representation of a string literal will be a quoted Cypher string in single * tickmarks with escaped reserved characters. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class StringLiteral extends LiteralBase { private static final Pattern RESERVED_CHARS = Pattern.compile("([" + Pattern.quote("\\'\"") + "])"); private static final String QUOTED_LITERAL_FORMAT = "'%s'"; StringLiteral(CharSequence content) { super(content); } /** * Escapes a string so that it can be used as a string literal in both single * tickmarks ({@literal '}) and quotes ({@literal "}). * @param unescapedString the string to escape * @return an empty optional when the unescaped string is {@literal null}, an escaped * string otherwise */ static Optional escapeString(CharSequence unescapedString) { if (unescapedString == null) { return Optional.empty(); } final StringBuilder buffer = new StringBuilder(); Matcher matcher = RESERVED_CHARS.matcher(unescapedString); while (matcher.find()) { matcher.appendReplacement(buffer, "\\\\\\" + matcher.group(1)); } matcher.appendTail(buffer); return Optional.of(buffer.toString()); } @Override public String asString() { final Optional escapedContent = escapeString(getContent()); return String.format(Locale.ENGLISH, QUOTED_LITERAL_FORMAT, escapedContent.orElse("")); } @Override public CharSequence getContent() { return this.content; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Subquery.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * Represents a "callable" sub-query. A sub-query can contain statement that returns * something (standard statements, union statements and calls to stored procedures, * yielding their elements). Sub-queries can be nested. * * @author Michael J. Simons * @neo4j.version 4.0.0 * @since 2020.1.2 */ @API(status = STABLE, since = "2020.1.2") @Neo4jVersion(minimum = "4.0.0") public final class Subquery extends AbstractClause implements Clause { private final ImportingWith importingWith; private final Statement statement; private final RawLiteral rawStatement; private Subquery(ImportingWith importingWith, Statement statement) { this.importingWith = importingWith; this.statement = statement; this.rawStatement = null; } private Subquery(RawLiteral rawStatement) { this.rawStatement = rawStatement; this.importingWith = null; this.statement = null; } static Subquery raw(String format, Object... mixedArgs) { return new Subquery(RawLiteral.create(format, mixedArgs)); } /** * The {@code statement} can either be a unit sub-query, used to modify the graph. * Those won't impact the amount of rows returned by the enclosing query. In case it's * a statement that returns or yields values it must ensure that it does not return * variables with the same names as variables in the enclosing query. * @param statement the statement to wrap into a sub-query. * @param imports the variables imported into the subquery * @return a sub-query. */ static Subquery call(Statement statement, IdentifiableElement... imports) { return new Subquery(ImportingWith.of(imports), statement); } @Override public void accept(Visitor visitor) { visitor.enter(this); if (this.rawStatement != null) { this.rawStatement.accept(visitor); } else { this.importingWith.accept(visitor); this.statement.accept(visitor); } visitor.leave(this); } @API(status = INTERNAL) InTransactions inTransactionsOf(Integer rows) { return new InTransactions(this, rows); } /** * {@return true if this sub-query yields any items} */ @API(status = INTERNAL) public boolean doesReturnOrYield() { return this.statement != null && this.statement.doesReturnOrYield(); } /** * {@return the importing with clause if any} */ @API(status = INTERNAL) public With importingWith() { var imports = (this.importingWith != null) ? this.importingWith.imports() : null; if (imports == null && this.statement instanceof ClausesBasedStatement cbs) { return cbs.getClauses() .stream() .findFirst() .filter(With.class::isInstance) .map(With.class::cast) .orElse(null); } return imports; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/SubqueryExpression.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; /** * A sub-query expression can either be an {@link ExistentialSubquery EXISTS-tential} or a * {@link CountExpression COUNT} expression as of Neo4j 5. * * @author Michael J. Simons * @since 2023.0.0 * */ public sealed interface SubqueryExpression extends Expression permits ExistentialSubquery, CountExpression, CollectExpression { } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/SubqueryExpressionBuilder.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * Something that can build counting sub-queries. Might be used in the future for * existential sub-queries, too. * * @author Michael J. Simons * @since 2023.9.0 */ @API(status = STABLE, since = "2023.9.0") public interface SubqueryExpressionBuilder { /** * Creates a {@literal COUNT} sub-query expressions from at least one pattern. * @param requiredPattern one pattern is required * @param patternElement optional pattern * @return the immutable {@link CountExpression} * @since 2023.9.0 */ CountExpression count(PatternElement requiredPattern, PatternElement... patternElement); /** * Creates a {@literal COUNT} with an inner {@literal UNION} sub-query. * @param union the union that will be the source of the {@literal COUNT} sub-query * @return the immutable {@link CountExpression} * @since 2023.9.0 */ CountExpression count(Statement.UnionQuery union); /** * Creates a {@literal COLLECT} subquery from a statement, including its filters and * conditions. The statement must return exactly one column. It must however not * contain any updates. While it would render syntactically correct Cypher, Neo4j does * not support updates inside counting sub-queries. *

* To avoid confusion, shadowing of imported variables is not allowed. An outside * scope variable is shadowed when a newly introduced variable within the inner scope * is defined with the same variable. * @param statement the statement to be passed to {@code COLLECT{}} * @return a collecting sub-query. * @since 2023.9.0 */ CollectExpression collect(Statement statement); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/SymbolicName.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import org.apiguardian.api.API; import org.apiguardian.api.API.Status; import org.neo4j.cypherdsl.core.utils.Assertions; import org.neo4j.cypherdsl.core.utils.LRUCache; import static org.apiguardian.api.API.Status.INTERNAL; /** * A symbolic name to identify nodes, relationships and aliased items. *

* See SchemaName * SymbolicName *

* While OpenCypher extends the UNICODE * IDENTIFIER AND PATTERN SYNTAX with some characters, this DSL uses the same * identifier Java itself uses for simplicity and until otherwise needed. * * @author Michael J. Simons * @author Andreas Berger * @since 1.0 */ @API(status = Status.EXPERIMENTAL, since = "1.0") public final class SymbolicName implements Expression, IdentifiableElement { private static final Map CACHE = Collections.synchronizedMap(new LRUCache<>(32)); private final String value; private SymbolicName(String value) { this.value = value; } static SymbolicName of(String name) { Assertions.hasText(name, "Name must not be empty."); return CACHE.computeIfAbsent(name, SymbolicName::new); } static SymbolicName unsafe(String name) { Assertions.hasText(name, "Name must not be empty."); return new SymbolicName(name); } static SymbolicName unresolved() { return new SymbolicName(null); } /** * {@return the value of this symbolic name} */ @API(status = INTERNAL) public String getValue() { return this.value; } /** * Creates a new symbolic name by concatenating {@code otherValue} to this names * value. Returns {@literal this} if {@code otherValue} is empty. * @param otherValue the value to concat. * @return a new symbolic name */ public SymbolicName concat(String otherValue) { Assertions.notNull(otherValue, "Value to concat must not be null."); if (otherValue.isEmpty()) { return this; } return SymbolicName.of(this.value + otherValue); } /** * A list will never be a valid entry for a map projection, so this convenient method * prevents trying to create one from a list of objects. It will delegate to * {@link #project(Object...)} with the content of the list. * @param entries a list of entries for the projection * @return a map projection. * @since 2021.0.0 */ public MapProjection project(List entries) { return project(entries.toArray()); } /** * Creates a map projection based on this node. The node needs a symbolic name for * this to work. *

* Entries of type {@code String} in {@code entries} followed by an {@link Expression} * will be treated as map keys pointing to the expression in the projection, * {@code String} entries alone will be treated as property lookups on the node. * @param entries a list of entries for the projection * @return a map projection. * @since 2021.0.0 */ public MapProjection project(Object... entries) { return MapProjection.create(this, entries); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } // Unresolved values are only equal to themselves if (this.value == null) { return false; } SymbolicName that = (SymbolicName) o; return this.value.equals(that.value); } @Override public int hashCode() { return (this.value != null) ? Objects.hash(this.value) : super.hashCode(); } @Override public String toString() { return (this.value != null) ? RendererBridge.render(this) : "Unresolved SymbolicName"; } @Override public Expression asExpression() { return this; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/TemporalLiteral.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetTime; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAccessor; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * A literal representing a temporal value to be formatted in a way that Neo4j's Cypher * understands it. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = STABLE, since = "2021.1.0") public final class TemporalLiteral extends LiteralBase { private final String value; TemporalLiteral(TemporalAccessor content) { super(content); String method; DateTimeFormatter formatter; if (content instanceof LocalDate) { method = "date"; formatter = DateTimeFormatter.ISO_LOCAL_DATE; } else if (content instanceof LocalDateTime) { method = "localdatetime"; formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; } else if (content instanceof ZonedDateTime) { method = "datetime"; formatter = DateTimeFormatter.ISO_ZONED_DATE_TIME; } else if (content instanceof LocalTime) { method = "localtime"; formatter = DateTimeFormatter.ISO_LOCAL_TIME; } else if (content instanceof OffsetTime) { method = "time"; formatter = DateTimeFormatter.ISO_OFFSET_TIME; } else { throw new UnsupportedLiteralException(content); } this.value = String.format("%s('%s')", method, formatter.format(content)); } @Override public String asString() { return this.value; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/TreeNode.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Deque; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Queue; import java.util.function.Consumer; import java.util.function.Function; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; /** * A mutable tree structure providing * Breadth-first search * (aka in-level order traversal) and pre-ordered * depth-first search. This * class is thread-safe. * * @param the type of each node value * @author Michael J. Simons * @since 2023.2.0 */ @API(status = API.Status.EXPERIMENTAL, since = "2023.2.0") public final class TreeNode { private final TreeNode parent; private final int level; private final List> children; private final E value; private TreeNode(TreeNode parent, int level, E value) { this.parent = parent; this.level = level; this.value = value; this.children = new ArrayList<>(); } /** * Creates a tree from a {@link Statement}. This allows to visit all elements of * statement without implementing custom {@link Visitor visitors.} The root of the * returned tree will always be the statement. * @param statement the statement that should be represented as a tree * @return a tree with the statement as root */ public static TreeNode from(Statement statement) { var visitor = new TreeBuildingVisitor(); statement.accept(visitor); return visitor.root; } /** * Creates a new tree, starting at the root. * @param value the actual value * @param the type of the value * @return the new node */ static TreeNode root(E value) { return new TreeNode<>(null, 0, value); } /** * Appends a value to this node and thus creating a new child node. This node will be * modified and keeps track of the child node. * @param childValue the value of the new child node * @return the new child (this node will be the parent of the new node) */ TreeNode append(E childValue) { var newChild = new TreeNode<>(this, this.level + 1, childValue); this.children.add(newChild); return newChild; } /** * {@return true if this is the root node} */ public boolean isRoot() { return this.parent == null; } /** * Returns the level or the height in this tree in which {@code 0} is the level of the * root node. * @return the level or the height in this tree */ public int getLevel() { return this.level; } /** * {@return the parent of this node or null if this is a root node} */ public TreeNode getParent() { return this.parent; } /** * {@return an immutable collection of this nodes children} */ public Collection> getChildren() { return List.copyOf(this.children); } /** * {@return the value of this node} */ public E getValue() { return this.value; } /** * {@return a breadth-first iterator of this node and it's children} */ public Iterator> breadthFirst() { return new BreadthFirstIterator<>(this); } /** * {@return a depth-first, pre-ordered iterator of this node and it's children} */ public Iterator> preOrder() { return new PreOrderIterator<>(this); } /** * Creates an ASCII representation of this node and its children. * @param target the target to which to print this tree to * @param toString how to format nodes if this type */ public void printTo(Consumer target, Function, String> toString) { this.printTo0(target, toString, this, "", true); } private void printTo0(Consumer target, Function, String> toString, TreeNode node, String prefix, boolean isTail) { var localValue = toString.apply(node); var connector = isTail ? "└── " : "├── "; if (this == node) { connector = ""; } target.accept(prefix + connector + localValue + "\n"); var newPrefix = prefix + (isTail ? " ".repeat(connector.length()) : "│ "); for (int i = 0; i < node.children.size(); ++i) { var child = node.children.get(i); printTo0(target, toString, child, newPrefix, i + 1 == node.getChildren().size()); } } private static final class TreeBuildingVisitor implements Visitor { final Deque> nodes = new ArrayDeque<>(); TreeNode root; @Override public void enter(Visitable segment) { var currentParent = this.nodes.peek(); if (currentParent == null) { currentParent = TreeNode.root(segment); } else { currentParent = currentParent.append(segment); } this.nodes.push(currentParent); } @Override public void leave(Visitable segment) { this.root = this.nodes.pop(); } } private static final class BreadthFirstIterator implements Iterator> { private final Queue> queue; BreadthFirstIterator(TreeNode root) { this.queue = new ArrayDeque<>(); this.queue.add(root); } @Override public boolean hasNext() { return !this.queue.isEmpty(); } @Override public TreeNode next() { if (this.queue.isEmpty()) { throw new NoSuchElementException(); } var n = this.queue.remove(); this.queue.addAll(n.children); return n; } } private static final class PreOrderIterator implements Iterator> { private final Deque>> stack; PreOrderIterator(TreeNode root) { this.stack = new ArrayDeque<>(); this.stack.push(List.of(root).iterator()); } @Override public boolean hasNext() { return !this.stack.isEmpty() && this.stack.peek().hasNext(); } @Override public TreeNode next() { if (this.stack.isEmpty()) { throw new NoSuchElementException(); } var nodesUpNext = this.stack.peek(); var currentNode = nodesUpNext.next(); if (!nodesUpNext.hasNext()) { this.stack.pop(); } if (!currentNode.children.isEmpty()) { this.stack.push(currentNode.children.iterator()); } return currentNode; } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/UnionPart.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * Represents a part of a union. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class UnionPart implements Visitable { private final boolean all; private final Statement query; UnionPart(boolean all, Statement query) { this.all = all; this.query = query; } /** * {@return true, if an ALL keyword should be rendered} */ @API(status = INTERNAL) public boolean isAll() { return this.all; } Statement getQuery() { return this.query; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.query.accept(visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/UnionQueryImpl.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.utils.Assertions; import static org.apiguardian.api.API.Status.INTERNAL; /** * Implementation of a {@code UNION} query. * * @author Michael J. Simons * @since 1.0 */ @API(status = INTERNAL, since = "1.0") final class UnionQueryImpl extends AbstractStatement implements Statement.UnionQuery { private final boolean all; private final Statement firstQuery; private final List additionalQueries; private UnionQueryImpl(boolean all, Statement firstQuery, List additionalQueries) { this.all = all; this.firstQuery = firstQuery; this.additionalQueries = additionalQueries; } @SuppressWarnings("squid:S6416") // This is about the assertion, Sonar suddenly things // this is an issue: Idk. We want the exception. static UnionQueryImpl create(boolean unionAll, List queries) { Assertions.isTrue(queries != null && queries.size() >= 2, "At least two queries are needed."); @SuppressWarnings("squid:S2259") // Really, we asserted it List unionParts = queries.stream().skip(1).map(q -> new UnionPart(unionAll, q)).toList(); return new UnionQueryImpl(unionAll, queries.get(0), unionParts); } /** * Creates a new union query by appending more parts. * @param newAdditionalQueries more additional queries * @return a new union query */ UnionQueryImpl addAdditionalQueries(List newAdditionalQueries) { List queries = new ArrayList<>(); queries.add(this.firstQuery); queries.addAll(this.additionalQueries.stream().map(UnionPart::getQuery).toList()); queries.addAll(newAdditionalQueries); return create(this.isAll(), queries); } boolean isAll() { return this.all; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.firstQuery.accept(visitor); this.additionalQueries.forEach(q -> q.accept(visitor)); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Unwind.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.STABLE; /** * See Unwind. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class Unwind extends AbstractClause implements ReadingClause { private final Expression expressionToUnwind; Unwind(Expression expressionToUnwind, String variable) { this.expressionToUnwind = ((expressionToUnwind instanceof Aliased aliased) ? aliased.asName() : expressionToUnwind) .as(variable); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.expressionToUnwind.accept(visitor); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/UpdatingClause.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; /** * Representation of any writing aka updating clause. * * @author Michael J. Simons * @since 1.0 */ public interface UpdatingClause extends Clause { } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Use.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; /** * The {@literal USE} clause can be prepended to statements or be used in a * {@literal CALL} subquery. It is meant to select composite databases or constituents * thereof. * * @author Michael J. Simons * @since 2023.0.0 */ public sealed interface Use extends Clause permits UseClauseImpl { /** * {@return true if this instance requires dynamic graph lookups} */ boolean dynamic(); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/UseClauseImpl.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.internal.SchemaNamesBridge; /** * The factory methods are on the concrete class, as they should not be exposed to the * outside via the interface. * * @param target the target of the {@code USE} clause * @param dynamic {@code true} if dynamic graph lookups are to be used * @author Michael J. Simons * @since 2023.0.0 */ record UseClauseImpl(Expression target, boolean dynamic) implements Use { static Use of(String target) { var components = target.split("\\."); Expression targetExpression; if (components.length == 1) { targetExpression = Cypher.raw(SchemaNamesBridge.sanitize(components[0], false).orElseThrow()); } else { targetExpression = Cypher.raw(SchemaNamesBridge.sanitize(components[0], false) .flatMap(composite -> SchemaNamesBridge.sanitize(components[1], false).map(v -> composite + "." + v)) .orElseThrow()); } return new UseClauseImpl(targetExpression, false); } static Use of(Expression target) { return new UseClauseImpl(target, !(target instanceof FunctionInvocation fi) || !"graph.byName".equals(fi.getFunctionName())); } @Override public void accept(Visitor visitor) { visitor.enter(this); this.target.accept(visitor); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/Where.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.STABLE; /** * Roughly corresponding to Where. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class Where implements Visitable { private final Condition condition; Where(Condition condition) { this.condition = condition; } /** * Creates a new {@literal WHERE}. * @param optionalWhere an optional expression that must be usable * {@link Expression#asCondition() "as condition"}. * @return a {@literal WHERE} expression or null when {@code optionalWhere} has been * {@literal NULL} * @since 2022.0.0 */ public static Where from(Expression optionalWhere) { return (optionalWhere != null) ? new Where(optionalWhere.asCondition()) : null; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.condition.accept(visitor); visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/With.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.EnterResult; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.internal.Distinct; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; /** * See With. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class With implements Visitable, Clause { private final Distinct distinct; private final ReturnBody body; private final Where where; With(Return returnClause, Where where) { this.distinct = returnClause.getDistinct(); this.body = returnClause.getBody(); this.where = where; } With(boolean distinct, ExpressionList returnItems, Order order, Skip skip, Limit limit, Where where) { this.distinct = distinct ? Distinct.INSTANCE : null; this.body = new ReturnBody(returnItems, order, skip, limit); this.where = where; } @Override public void accept(Visitor visitor) { if (visitor.enterWithResult(this) == EnterResult.CONTINUE) { Visitable.visitIfNotNull(this.distinct, visitor); this.body.accept(visitor); Visitable.visitIfNotNull(this.where, visitor); } visitor.leave(this); } @Override public String toString() { return RendererBridge.render(this); } @API(status = INTERNAL) public List getItems() { return this.body.getReturnItems(); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/annotations/CheckReturnValue.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.annotations; 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; import org.apiguardian.api.API; /** * Simple version of the JSR 305 annotation that allows detecting accidentally omitted * calls to * {@link org.neo4j.cypherdsl.core.Cypher#match(org.neo4j.cypherdsl.core.PatternElement...)} * ()} and the likes in IntelliJ. *

* This annotation is {@link org.apiguardian.api.API.Status#INTERNAL}. Clients should not * rely on its presence. * * @author Lukas Eder * @author Michael J. Simons * @see https://github.com/jOOQ/jOOQ/issues/11718 * @see https://youtrack.jetbrains.com/issue/IDEA-265263 */ @Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.CLASS) @API(status = API.Status.INTERNAL, since = "2021.1.1") public @interface CheckReturnValue { } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/annotations/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * Annotations for describing contracts. */ package org.neo4j.cypherdsl.core.annotations; ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ast/EnterResult.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.ast; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * Result of entering a {@link Visitable}. Visitables are not required to check the result * and can enter their child elements nevertheless. * * @author Michael J. Simons * @since 2022.3.0 */ @API(status = STABLE, since = "2022.3.0") public enum EnterResult { /** * Continue with all child elements. */ CONTINUE, /** * Skip child elements. */ SKIP_CHILDREN } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ast/ProvidesAffixes.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.ast; import java.util.Optional; /** * Additional pre- and suffixes for elements. * * @author Michael J. Simons */ public interface ProvidesAffixes { /** * {@return a prefix for this visitable} */ default Optional getPrefix() { return Optional.empty(); } /** * {@return a suffix for this visitable} */ default Optional getSuffix() { return Optional.empty(); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ast/TypedSubtree.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.ast; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.INTERNAL; /** * This class helps to group items of the same type on the same level of the tree into a * list structure that can be recognized by visitors. * * @param the children's type * @author Michael J. Simons * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public abstract class TypedSubtree implements Visitable { /** * The content of this typed subtree. */ protected final List children; /** * Creates a new typed subtree with the given content. * @param children the content of this subtree. */ @SafeVarargs @SuppressWarnings("varargs") protected TypedSubtree(T... children) { this.children = Arrays.asList(children); } /** * Creates a new typed subtree with the given content. * @param children the content of this subtree. */ protected TypedSubtree(Collection children) { this.children = new ArrayList<>(children); } @Override public final void accept(Visitor visitor) { visitor.enter(this); this.children.forEach(child -> prepareVisit(child).accept(visitor)); visitor.leave(this); } /** * A hook for interfere with the visitation of child elements. * @param child the current child element * @return the visitable that has been prepared */ protected Visitable prepareVisit(T child) { return child; } @API(status = INTERNAL) protected List getChildren() { return this.children; } @API(status = INTERNAL) public String separator() { return ", "; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ast/Visitable.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.ast; /** * Interface for implementations that accepts {@link Visitor visitors}. * * @author Michael Simons * @since 1.0 * @see Visitor */ public interface Visitable { /** * A helper method that presents the {@code visitor} to the {@code visitable} if the * visitable is not null. Not meant to be overridden. * @param visitable the visitable to visit if not null * @param visitor the visitor to use */ static void visitIfNotNull(Visitable visitable, Visitor visitor) { if (visitable != null) { visitable.accept(visitor); } } /** * Accept a {@link Visitor} visiting this {@link Visitable} and its nested * {@link Visitable}s if applicable. * @param visitor the visitor to notify, must not be {@literal null}. */ default void accept(Visitor visitor) { visitor.enter(this); visitor.leave(this); } /** * Most {@link Visitable visitables} will render themselves into a Cypher fragment * preceded with the actual classname. The representation however is not cached - in * contrast to the ones for full statements. Using {@code toString} is recommended for * debugging purposes mainly, and not for production use. *

* The concrete classname has been prepended to help debugging and actually to * discourage using fragments to build queries without explicitly rendering them, * either as statement or going through the renderer on purpose. * @return a string representation of this visitable formatted as * {@literal Classname{cypher=value}} */ String toString(); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ast/Visitor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.ast; /** * Visitors are used to traverse the complete Cypher-DSL AST. * * @author Michael J. Simons * @since 1.0 */ @FunctionalInterface public interface Visitor { /** * Enter a {@link Visitable}. Not all visitables will obey to the result * @param segment the segment to visit. */ void enter(Visitable segment); /** * A method that is used to pass control to some extent from the visitor to the * {@link Visitable}. Not all visitables react to this, and we don't give any * guarantees about which will. This method has been mainly introduced in parallel to * {@link #enter(Visitable)} so that existing external implementations of * {@link Visitor visitors} won't break. * @param segment the segment to visit. * @return a result indicating whether visitation of child elements should continue or * not. * @since 2022.3.0 */ default EnterResult enterWithResult(Visitable segment) { enter(segment); return EnterResult.CONTINUE; } /** * Leave a {@link Visitable}. * @param segment the visited segment. */ default void leave(Visitable segment) { } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ast/VisitorWithResult.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.ast; /** * Sometimes it will be necessary - for example in dialects - to change the flow of * elements visited. Some {@link Visitable vistables} will react on * {@link Visitor#enterWithResult(Visitable)} and change course (not all, and we don't * give any guarantees on any behaviour). This class has been introduced for visitors * providing such a behaviour so that an implementation doesn't need to deal with an empty * {@link Visitor#enter(Visitable)} method. * * @author Michael J. Simons * @since 2022.3.0 */ @SuppressWarnings("missing-explicit-ctor") public abstract class VisitorWithResult implements Visitor { @Override public final void enter(Visitable segment) { enterWithResult(segment); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/ast/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * Support for describing an abstract syntax tree. */ package org.neo4j.cypherdsl.core.ast; ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/CaseElse.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; /** * Represents a finalizing `else` expression. * * @author Gerrit Meier * @author Michael J. Simons */ @API(status = INTERNAL, since = "1.0") public final class CaseElse implements Visitable { private final Expression elseExpression; /** * Starts creating the {@literal ELSE} part. * @param elseExpression finishing else part */ public CaseElse(Expression elseExpression) { this.elseExpression = elseExpression; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.elseExpression.accept(visitor); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/CaseWhenThen.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; /** * Represents a pair of `when-then` expressions. * * @author Gerrit Meier * @author Michael J. Simons */ @API(status = INTERNAL, since = "1.0") public final class CaseWhenThen implements Visitable { private final Expression whenExpression; private final Expression thenExpression; /** * Creates w new instance. * @param whenExpression intermediate when part * @param thenExpression the then part to happen after matching when above */ public CaseWhenThen(Expression whenExpression, Expression thenExpression) { this.whenExpression = whenExpression; this.thenExpression = thenExpression; } @Override public void accept(Visitor visitor) { visitor.enter(this); this.whenExpression.accept(visitor); visitor.leave(this); this.thenExpression.accept(visitor); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/ConstantParameterHolder.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Cypher; import static org.apiguardian.api.API.Status.INTERNAL; /** * An internal holder for a constant value that might be rendered as a parameter. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = INTERNAL, since = "2021.1.0") public final class ConstantParameterHolder { private final Object value; private final String literalValue; /** * New instance for a reference to a constant parameter. An additional literal for the * parameter will be generated. * @param value the value of the constant parameter */ public ConstantParameterHolder(Object value) { this.value = value; this.literalValue = Cypher.literalOf(value).asString(); } /** * {@return the original value} */ public Object getValue() { return this.value; } /** * {@return the value, but as a Cypher literal} */ public String asString() { return this.literalValue; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/DefaultStatementContext.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Parameter; import org.neo4j.cypherdsl.core.StatementContext; import org.neo4j.cypherdsl.core.SymbolicName; import org.neo4j.cypherdsl.core.utils.Strings; import static org.apiguardian.api.API.Status.INTERNAL; /** * The default implementation of the {@link StatementContext}. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = INTERNAL, since = "2021.1.0") public final class DefaultStatementContext implements StatementContext { private final AtomicInteger parameterCount = new AtomicInteger(); private final Map, String> parameterNames = new ConcurrentHashMap<>(); /** * Keeps track of unresolved symbolic names. */ private final Map resolvedSymbolicNames = new ConcurrentHashMap<>(); @Override public String getParameterName(Parameter parameter) { return this.parameterNames.computeIfAbsent(parameter, p -> p.isAnon() ? String.format("pcdsl%02d", this.parameterCount.incrementAndGet()) : p.getName()); } @Override public String resolve(SymbolicName symbolicName) { return this.resolvedSymbolicNames.computeIfAbsent(symbolicName, k -> { String value = k.getValue(); if (Strings.hasText(value)) { return SchemaNamesBridge.sanitize(value, false).orElse(value); } return String.format("%s%03d", Strings.randomIdentifier(8), this.resolvedSymbolicNames.size()); }); } @Override public boolean isResolved(SymbolicName symbolicName) { return this.resolvedSymbolicNames.containsKey(symbolicName); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/Distinct.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import static org.apiguardian.api.API.Status.INTERNAL; /** * AST representation of the {@literal DISTINCT} keyword. * * @author Michael J. Simons * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public enum Distinct implements Visitable { /** * The single instance of the {@code DISTINCT} keyword. */ INSTANCE } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/FixedNamesStrategy.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import org.neo4j.cypherdsl.core.AliasedExpression; import org.neo4j.cypherdsl.core.Parameter; import org.neo4j.cypherdsl.core.StatementContext; import org.neo4j.cypherdsl.core.SymbolicName; /** * A strategy that will always use fixed names for aliased expressions. * * @author Michael J. Simons * @since 2023.2.0 */ final class FixedNamesStrategy implements NameResolvingStrategy { private final StatementContext context; FixedNamesStrategy(StatementContext context) { this.context = context; } @Override public String resolve(SymbolicName symbolicName, boolean inEntity, boolean inPropertyLookup) { return this.context.resolve(symbolicName); } @Override public String resolve(AliasedExpression aliasedExpression, boolean isNew, boolean inLastReturn) { return aliasedExpression.getAlias(); } @Override public boolean isResolved(SymbolicName symbolicName) { return this.context.isResolved(symbolicName); } @Override public String resolve(Parameter parameter) { return this.context.getParameterName(parameter); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/GeneratedNamesStrategy.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import java.util.ArrayDeque; import java.util.Collection; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import org.neo4j.cypherdsl.core.AliasedExpression; import org.neo4j.cypherdsl.core.IdentifiableElement; import org.neo4j.cypherdsl.core.Parameter; import org.neo4j.cypherdsl.core.StatementContext; import org.neo4j.cypherdsl.core.SymbolicName; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.renderer.Configuration.GeneratedNames; /** * A strategy that generates unique names per scope for named items in a statement. * * @author Michael J. Simons * @since 2023.2.0 */ final class GeneratedNamesStrategy implements NameResolvingStrategy { /** * Unscoped counter for parameters. */ private final AtomicInteger parameterCount = new AtomicInteger(0); /** * Stack of counters per scope for the variable names. */ private final Deque scopedVariableCount = new ArrayDeque<>(); /** * Scope for the already generated names. */ private final Deque> scopedNameLookup = new ArrayDeque<>(); /** * Used names that are either brought into a child scope or exported into the * containing scope. */ private final Deque> scopedNamesUsed = new ArrayDeque<>(); private final StatementContext statementContext; private final Set config; GeneratedNamesStrategy(StatementContext statementContext, Set config) { this.statementContext = statementContext; this.config = config; this.enterScope(null, List.of()); } /** * {@return the lookup table for names in the current scope} */ Map nameLookup() { return Objects.requireNonNull(this.scopedNameLookup.peek()); } @Override public void enterScope(Visitable cause, Collection imports) { var newNameLookup = new HashMap(); var newUsedNames = new HashSet(); var outerNameLookup = this.scopedNameLookup.peek(); if (outerNameLookup != null) { for (IdentifiableElement anImport : imports) { var theKey = Key.of(anImport); if (outerNameLookup.containsKey(theKey)) { newNameLookup.put(theKey, outerNameLookup.get(theKey)); } } newUsedNames.addAll(outerNameLookup.values()); } this.scopedVariableCount.push(new AtomicInteger(0)); this.scopedNameLookup.push(newNameLookup); this.scopedNamesUsed.push(newUsedNames); } @Override public void leaveScope(Visitable cause, Collection exports) { this.scopedVariableCount.pop(); var innerNameLookup = this.scopedNameLookup.pop(); this.scopedNamesUsed.pop(); var outerNameLookup = Objects.requireNonNull(this.scopedNameLookup.peek()); var previouslyUsedNames = Objects.requireNonNull(this.scopedNamesUsed.peek()); for (IdentifiableElement anExport : exports) { var theKey = Key.of(anExport); if (innerNameLookup.containsKey(theKey)) { outerNameLookup.put(theKey, innerNameLookup.get(theKey)); } else if (anExport instanceof AliasedExpression name) { outerNameLookup.put(theKey, name.getAlias()); } else if (anExport instanceof SymbolicName name) { outerNameLookup.put(theKey, name.getValue()); } } previouslyUsedNames.addAll(innerNameLookup.values()); } @Override public String resolve(SymbolicName symbolicName, boolean inEntity, boolean inPropertyLookup) { if (inPropertyLookup) { return this.statementContext.resolve(symbolicName); } // Maybe it has been used as an alias, so we can't skip early var theKey = Key.of(symbolicName); var nameLookup = nameLookup(); if (!this.config.contains(GeneratedNames.ENTITY_NAMES) || (!inEntity && !this.config.contains(GeneratedNames.ALL_ALIASES) && !this.config.contains(GeneratedNames.INTERNAL_ALIASES_ONLY))) { // Not using nameLookup.getOrDefault() to not resolve the name early if (nameLookup.containsKey(theKey)) { return nameLookup.get(theKey); } return this.statementContext.resolve(symbolicName); } return nameLookup.computeIfAbsent(theKey, key -> newName()); } private String newName() { String name; var namesUsed = Objects.requireNonNull(this.scopedNamesUsed.peek()); var variableCount = Objects.requireNonNull(this.scopedVariableCount.peek()); do { name = String.format("v%d", variableCount.getAndIncrement()); } while (namesUsed.contains(name)); return name; } @Override public String resolve(AliasedExpression aliasedExpression, boolean isNew, boolean inLastReturn) { if (!(this.config.contains(GeneratedNames.ALL_ALIASES) || (this.config.contains(GeneratedNames.INTERNAL_ALIASES_ONLY) && !inLastReturn))) { return aliasedExpression.getAlias(); } var nameLookup = nameLookup(); if (this.config.contains(GeneratedNames.REUSE_ALIASES)) { return nameLookup.computeIfAbsent(Key.of(aliasedExpression), key -> newName()); } else { var result = newName(); nameLookup().put(Key.of(aliasedExpression), result); return result; } } @Override public String resolve(Parameter parameter) { if (!this.config.contains(GeneratedNames.PARAMETER_NAMES)) { return this.statementContext.getParameterName(parameter); } return nameLookup().computeIfAbsent(Key.of(parameter), key -> { var p = (Parameter) key.value(); return !p.isAnon() ? String.format("p%d", this.parameterCount.getAndIncrement()) : this.statementContext.getParameterName(p); }); } @Override public boolean isResolved(SymbolicName symbolicName) { return this.statementContext.isResolved(symbolicName); } record Key(Object value) { static Key of(Object o) { if (o instanceof AliasedExpression aliasedExpression) { return new Key(aliasedExpression.asName()); } return new Key(o); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/HandlerException.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; /** * An exception that is thrown when one of the handlers fails to execute. * * @author Michael J. Simons */ class HandlerException extends RuntimeException { private static final long serialVersionUID = 1L; HandlerException(Throwable cause) { super(cause); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/LoadCSV.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import java.net.URI; import java.util.Objects; import java.util.Optional; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Clause; import static org.apiguardian.api.API.Status.INTERNAL; /** * A representation of the {@code LOAD CSV} clause, including it's periodic commit and * field terminator configuration. Not meant to be used outside the Cypher-DSL directly. * Will be changed without further notice. * * @author Michael J. Simons * @since 2021.2.1 */ @API(status = INTERNAL, since = "2021.2.1") public final class LoadCSV implements Clause { private final URI uri; private final boolean withHeaders; private final String alias; private final String fieldTerminator; /** * Constructs a new {@link LoadCSV} clause. * @param uri required uri * @param withHeaders with or without headers * @param alias the alias per row */ public LoadCSV(URI uri, boolean withHeaders, String alias) { this(uri, withHeaders, alias, null); } private LoadCSV(URI uri, boolean withHeaders, String alias, String fieldTerminator) { this.uri = uri; this.withHeaders = withHeaders; this.alias = alias; this.fieldTerminator = fieldTerminator; } /** * {@return the uri of the csv file} */ public URI getUri() { return this.uri; } /** * {@return true if headers are to be evaluated} */ public boolean isWithHeaders() { return this.withHeaders; } /** * {@return the field terminator to use} */ public String getFieldTerminator() { return this.fieldTerminator; } /** * {@return the alias for one row in the csv file} */ public String getAlias() { return this.alias; } /** * Creates a new {@link LoadCSV LOAD CSV clause} with the given field terminator. * @param newFieldTerminator the new field terminator * @return a new instance or this instance if the terminator hasn't changed */ public LoadCSV withFieldTerminator(final String newFieldTerminator) { String value = Optional.ofNullable(newFieldTerminator).map(String::trim).filter(v -> !v.isEmpty()).orElse(null); if (Objects.equals(this.fieldTerminator, value)) { return this; } return new LoadCSV(this.uri, this.withHeaders, this.alias, value); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/NameResolvingStrategy.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import java.util.Collection; import java.util.EnumSet; import java.util.Set; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.AliasedExpression; import org.neo4j.cypherdsl.core.IdentifiableElement; import org.neo4j.cypherdsl.core.Parameter; import org.neo4j.cypherdsl.core.StatementContext; import org.neo4j.cypherdsl.core.SymbolicName; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.renderer.Configuration.GeneratedNames; import static org.apiguardian.api.API.Status.INTERNAL; /** * This class acts as facade towards the {@link StatementContext statement context} and * can generate variable and parameter names throughout the lifetime of this visitor. * * @author Michael J. Simons */ @API(status = INTERNAL, since = "2023.2.0") public sealed interface NameResolvingStrategy permits FixedNamesStrategy, GeneratedNamesStrategy { /** * Creates a strategy for using generated names in the given context. * @param context a statement context * @param config for which generated names should be used * @return a new strategy */ static NameResolvingStrategy useGeneratedNames(StatementContext context, Set config) { return new GeneratedNamesStrategy(context, config); } /** * Creates a strategy that uses generated parameter names. * @param context a statement context * @return a new strategy */ static NameResolvingStrategy useGeneratedParameterNames(StatementContext context) { return new GeneratedNamesStrategy(context, EnumSet.of(GeneratedNames.PARAMETER_NAMES)); } /** * Creates a strategy that uses the given names. * @param context a statement context * @return a new strategy */ static NameResolvingStrategy useGivenNames(StatementContext context) { return new FixedNamesStrategy(context); } /** * Resolves a symbolic name. * @param symbolicName the name to resolve * @param inEntity {@literal true} if this happens inside an entity * @param inPropertyLookup {@literal true} if this happens for a property lookup * @return a value */ String resolve(SymbolicName symbolicName, boolean inEntity, boolean inPropertyLookup); /** * Resolves an aliased expression. * @param isNew true if it's a newly created {@link AliasedExpression} * @param aliasedExpression the aliased expression to resolve * @param inLastReturn true if the name is resolved as part of the ultimate * {@code RETURN} clause of a statement * @return a value */ String resolve(AliasedExpression aliasedExpression, boolean isNew, boolean inLastReturn); /** * Returns {@code true} if the {@code symbolicName} has been resolved. * @param symbolicName the name that might be already resolved * @return {@code true} if the {@code symbolicName} has been resolved */ boolean isResolved(SymbolicName symbolicName); /** * Resolves a parameter name. * @param parameter the name to resolv * @return a value */ String resolve(Parameter parameter); /** * A callback used together with a {@link ScopingStrategy} to deal with imports into a * local scope. * @param cause the clause that caused the creation of a new scope * @param imports the imports */ default void enterScope(Visitable cause, Collection imports) { } /** * A callback used together with a {@link ScopingStrategy} to deal with exports when * leaving a local scope. * @param cause the clause being left * @param exports the exports */ default void leaveScope(Visitable cause, Collection exports) { } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/Namespace.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Literal; import static org.apiguardian.api.API.Status.INTERNAL; /** * Representation of a namespace (i.e. for procedures) element. * * @author Michael J. Simons * @since 2020.0.1 */ @API(status = INTERNAL, since = "2020.0.1") public final class Namespace implements Literal { private final String[] content; Namespace(String[] value) { this.content = value; } @Override public String asString() { return String.join(".", this.content); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/ProcedureName.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import java.util.Arrays; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; /** * Represents a structured Neo4j procedure name. * * @author Michael J. Simons * @since 2020.0.1 */ @API(status = INTERNAL, since = "2020.0.1") public final class ProcedureName implements Visitable { private final Namespace optionalNamespace; private final String value; private ProcedureName(String value) { this(null, value); } private ProcedureName(Namespace namespace, String value) { this.optionalNamespace = namespace; this.value = value; } /** * Creates a new {@link ProcedureName} from an array of names: The last element will * be the final procedure name, the head items will be concatenated into a proper * namespace. * @param namespaceAndProcedure list of names * @return a new procedure */ public static ProcedureName from(String... namespaceAndProcedure) { if (namespaceAndProcedure.length == 1) { return new ProcedureName(namespaceAndProcedure[0]); } else { Namespace namespace = new Namespace(Arrays.copyOf(namespaceAndProcedure, namespaceAndProcedure.length - 1)); return new ProcedureName(namespace, namespaceAndProcedure[namespaceAndProcedure.length - 1]); } } /** * Creates a new {@link ProcedureName} from a given namespace and name. * @param namespace optional (nested) namespace * @param procedure the actual name of the procedure * @return a new procedure */ public static ProcedureName from(List namespace, String procedure) { if (namespace.isEmpty()) { return new ProcedureName(procedure); } else { return new ProcedureName(new Namespace(namespace.toArray(new String[0])), procedure); } } /** * {@return the fully qualified, Cypher name of this procedure} */ public String getQualifiedName() { String namespace = ""; if (this.optionalNamespace != null) { namespace = this.optionalNamespace.asString() + "."; } return namespace + this.value; } @Override public void accept(Visitor visitor) { visitor.enter(this); Visitable.visitIfNotNull(this.optionalNamespace, visitor); visitor.leave(this); } /** * Use {@link #getQualifiedName()} to retrieve the full name, including the namespace. * @return the actual name of the procedure, without any namespace. */ public String getValue() { return this.value; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/ReflectiveVisitor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Deque; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.EnterResult; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.ast.VisitorWithResult; import org.neo4j.cypherdsl.core.renderer.SchemaEnforcementFailedException; import static org.apiguardian.api.API.Status.INTERNAL; /** * This is a convenience class implementing a {@link Visitor} and it takes care of * choosing the right methods to dispatch the {@link Visitor#enter(Visitable)} and * {@link Visitor#leave(Visitable)} calls to. *

* Classes extending this visitor need to provide corresponding {@code enter} and * {@code leave} methods taking exactly one argument of the type of {@link Visitable} they * are interested it. *

* The type must be an exact match, this support class doesn't try to find a close match * up in the class hierarchy if it doesn't find an exact match. * * @author Michael J. Simons * @author Gerrit Meier * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public abstract class ReflectiveVisitor extends VisitorWithResult { /** * A shared cache of unbound methods for entering and leaving phases. The key is the * concrete class of the visitor as well as the hierarchy of the visitable. */ private static final Map> VISITING_METHODS_CACHE = new ConcurrentHashMap<>(); /** * If theres any special delegate for a dialect or similar for a given visitable, it * will be tracked here. */ protected final Map visitablesAndDelegates = new HashMap<>(); /** * Keeps track of the ASTs current level. */ protected Deque currentVisitedElements = new LinkedList<>(); @SuppressWarnings("PMD.EmptyCatchBlock") private static Optional findHandleFor(TargetAndPhase targetAndPhase) { Class visitorClass = targetAndPhase.visitorClass; do { // Loop over the hierarchy of visitors so that we catch overloaded and // inherited methods. // the loop goes from concrete to abstract, so that the most concrete // visitor wins for (Class clazz : targetAndPhase.classHierarchyOfVisitable) { try { Method method = getMethodInPhaseWithActualVisitor(targetAndPhase, visitorClass, clazz); return Optional.of(method); } catch (NoSuchMethodException ex) { // We don't do anything if the method doesn't exists // Try the next parameter type in the hierarchy or the next visitor } } visitorClass = visitorClass.getSuperclass(); } while (visitorClass != null && visitorClass != ReflectiveVisitor.class); return Optional.empty(); } @SuppressWarnings("squid:S3011") // Very much the point of the whole thing private static Method getMethodInPhaseWithActualVisitor(TargetAndPhase targetAndPhase, Class visitorClass, Class clazz) throws NoSuchMethodException { Method method = visitorClass.getDeclaredMethod(targetAndPhase.phase.methodName, clazz); method.setAccessible(true); return method; } /** * This is a hook that is called with the uncasted, raw visitable just before entering * a visitable. *

* The hook is called regardless wither a matching {@code enter} is found or not. * @param visitable the visitable that is passed on to a matching enter after this * call. * @return true, when visiting of elements should be stopped until this element is * left again. */ protected abstract boolean preEnter(Visitable visitable); /** * Returns an indicator whether the visitable can be skipped or must be visited. * @param visitable the visitable prior to entering it * @return an indicator whether the visitable can be skipped or must be visited */ protected PreEnterResult getPreEnterResult(Visitable visitable) { return preEnter(visitable) ? PreEnterResult.doEnter() : PreEnterResult.skip(); } /** * This is a hook that is called with the uncasted, raw visitable just after leaving * the visitable. *

* The hook is called regardless wither a matching {@code leave} is found or not. * @param visitable the visitable that is passed on to a matching leave after this * call. */ protected abstract void postLeave(Visitable visitable); @Override public final EnterResult enterWithResult(Visitable visitable) { PreEnterResult preEnterResult = getPreEnterResult(visitable); if (preEnterResult != PreEnterResult.skip()) { this.currentVisitedElements.push(visitable); if (preEnterResult.delegate != null) { this.visitablesAndDelegates.put(visitable, preEnterResult.delegate); return preEnterResult.delegate.enterWithResult(visitable); } else { executeConcreteMethodIn(new TargetAndPhase(this, visitable.getClass(), Phase.ENTER), visitable); } } return EnterResult.CONTINUE; } @Override public final void leave(Visitable visitable) { if (visitable != null && this.currentVisitedElements.peek() == visitable) { if (this.visitablesAndDelegates.containsKey(visitable)) { Visitor delegate = this.visitablesAndDelegates.remove(visitable); delegate.leave(visitable); } else { executeConcreteMethodIn(new TargetAndPhase(this, visitable.getClass(), Phase.LEAVE), visitable); } postLeave(visitable); this.currentVisitedElements.pop(); } } private void executeConcreteMethodIn(TargetAndPhase targetAndPhase, Visitable onVisitable) { Optional optionalMethod = VISITING_METHODS_CACHE.computeIfAbsent(targetAndPhase, ReflectiveVisitor::findHandleFor); optionalMethod.ifPresent(handle -> { try { handle.invoke(this, onVisitable); } catch (Throwable throwable) { if (throwable instanceof InvocationTargetException ite && ite.getCause() instanceof SchemaEnforcementFailedException sefe) { throw sefe; } throw new HandlerException(throwable); } }); } /** * Private enum to specify a visiting phase. */ private enum Phase { ENTER("enter"), LEAVE("leave"); final String methodName; Phase(String methodName) { this.methodName = methodName; } } /** * This class is an indicator of what should happen after a new visitable has been * identified. */ public static final class PreEnterResult { private static final PreEnterResult DO_ENTER = new PreEnterResult(null); private static final PreEnterResult SKIP = new PreEnterResult(null); private final Visitor delegate; private PreEnterResult(Visitor delegate) { this.delegate = delegate; } /** * Do enter and treat as usual. * @return the result */ public static PreEnterResult doEnter() { return DO_ENTER; } /** * Skip the element completely. * @return the result */ public static PreEnterResult skip() { return SKIP; } /** * Enter to visit but delegate the visitation. * @param handler the delegate * @return the result */ public static PreEnterResult delegateTo(Visitor handler) { return new PreEnterResult(handler); } } private static class TargetAndPhase { /** * The most concrete visitor class. It may be that the handle that is eventually * found will not be called with that class, but with a parent. The attribute here * is just a starting point and later on, a cache key. */ private final Class visitorClass; private final Set> classHierarchyOfVisitable; private final Phase phase; TargetAndPhase(T visitor, Class concreteVisitableClass, Phase phase) { this.visitorClass = visitor.getClass(); this.phase = phase; this.classHierarchyOfVisitable = new LinkedHashSet<>(); Class classOfVisitable = concreteVisitableClass; do { this.classHierarchyOfVisitable.add(classOfVisitable); // Add all interfaces apart visitable too. Arrays.stream(classOfVisitable.getInterfaces()) .filter(c -> c != Visitable.class) .forEach(this.classHierarchyOfVisitable::add); classOfVisitable = classOfVisitable.getSuperclass(); } while (classOfVisitable != null && classOfVisitable != Object.class); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof TargetAndPhase)) { return false; } TargetAndPhase that = (TargetAndPhase) o; return this.visitorClass.equals(that.visitorClass) && this.classHierarchyOfVisitable.equals(that.classHierarchyOfVisitable) && this.phase == that.phase; } @Override public int hashCode() { return Objects.hash(this.visitorClass, this.classHierarchyOfVisitable, this.phase); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/RelationshipLength.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import static org.apiguardian.api.API.Status.INTERNAL; /** * Expresses the length of a relationship. * * @author Michael J. Simons * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public final class RelationshipLength implements Visitable { private final Integer minimum; private final Integer maximum; private final boolean unbounded; private RelationshipLength(Integer minimum, Integer maximum) { this.minimum = minimum; this.maximum = maximum; this.unbounded = minimum == null && maximum == null; } /** * Creates an unbounded {@link RelationshipLength}. * @return the new length definition */ public static RelationshipLength unbounded() { return new RelationshipLength(null, null); } /** * Creates a {@link RelationshipLength} with given minimum and maximum values. * @param minimum minimum length * @param maximum maximum length * @return the new length definition */ public static RelationshipLength of(Integer minimum, Integer maximum) { return new RelationshipLength(minimum, maximum); } /** * {@return minimum number of hops to match} */ @API(status = INTERNAL) public Integer getMinimum() { return this.minimum; } /** * {@return maximum number of hops to match} */ @API(status = INTERNAL) public Integer getMaximum() { return this.maximum; } /** * {@return true if neither minimum nor maximum number of hops are set} */ @API(status = INTERNAL) public boolean isUnbounded() { return this.unbounded; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/RelationshipPatternCondition.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Condition; import org.neo4j.cypherdsl.core.Operator; import org.neo4j.cypherdsl.core.RelationshipPattern; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; /** * Internal wrapper for marking a path pattern as a condition. * * @author Michael J. Simons * @since 1.0.1 */ @API(status = INTERNAL, since = "1.0") public final class RelationshipPatternCondition implements Condition { private final boolean not; private final RelationshipPattern pathPattern; private RelationshipPatternCondition(boolean not, RelationshipPattern pathPattern) { this.not = not; this.pathPattern = pathPattern; } /** * Creates a new {@link Condition} matching the given pattern. * @param pathPattern the pattern to be matched * @return a new condition */ public static RelationshipPatternCondition of(RelationshipPattern pathPattern) { return new RelationshipPatternCondition(false, pathPattern); } /** * Creates a new {@link Condition} that evaluates to {@literal true} when the pattern * does not match. * @param pathPattern the pattern to be matched * @return a new condition */ public static RelationshipPatternCondition not(RelationshipPattern pathPattern) { return new RelationshipPatternCondition(true, pathPattern); } @Override public Condition not() { return new RelationshipPatternCondition(!this.not, this.pathPattern); } @Override public void accept(Visitor visitor) { visitor.enter(this); if (this.not) { Operator.NOT.accept(visitor); } this.pathPattern.accept(visitor); visitor.leave(this); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/RelationshipTypes.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import java.util.Arrays; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import static org.apiguardian.api.API.Status.INTERNAL; /** * See RelationshipTypes. * * @author Michael J. Simons * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public final class RelationshipTypes implements Visitable { private final List values; private RelationshipTypes(List values) { this.values = values; } /** * Creates a new holder for relationship types from a set of raw strings. * @param types the types to be included in this value holder * @return a new value holder */ public static RelationshipTypes of(String... types) { List listOfTypes = Arrays.stream(types).filter(type -> !(type == null || type.isEmpty())).toList(); return new RelationshipTypes(listOfTypes); } /** * Returns the list of types. The types are not escaped and must be escaped prior to * * rendering. * @return the list of types */ public List getValues() { return this.values; } @Override public String toString() { return "RelationshipTypes{values=" + this.values + '}'; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/SchemaNamesBridge.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import java.util.Optional; import org.neo4j.cypherdsl.support.schema_name.SchemaNames; /** * This is a bridge to {@link SchemaNames}. Having this indirection avoids changing of the * bytecode of classes using the sanitizer: When the byte code changes, JaCoCo won't be * able to analyze it. Of course, one solution would be shading the sanitizer after the * integration tests, but the integration tests are meant to test the modularized Jar file * proper. * * @author Michael J. Simons * @since 2023.0.0 */ public final class SchemaNamesBridge { private SchemaNamesBridge() { } public static Optional sanitize(String value, boolean enforceQuotes) { return SchemaNames.sanitize(value, enforceQuotes); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/ScopingStrategy.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Predicate; import java.util.stream.Collectors; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Aliased; import org.neo4j.cypherdsl.core.AliasedExpression; import org.neo4j.cypherdsl.core.Asterisk; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.ExistentialSubquery; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.Foreach; import org.neo4j.cypherdsl.core.FunctionInvocation; import org.neo4j.cypherdsl.core.IdentifiableElement; import org.neo4j.cypherdsl.core.MapProjection; import org.neo4j.cypherdsl.core.Named; import org.neo4j.cypherdsl.core.Order; import org.neo4j.cypherdsl.core.PatternComprehension; import org.neo4j.cypherdsl.core.PatternElement; import org.neo4j.cypherdsl.core.ProcedureCall; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.Return; import org.neo4j.cypherdsl.core.Statement; import org.neo4j.cypherdsl.core.Subquery; import org.neo4j.cypherdsl.core.SubqueryExpression; import org.neo4j.cypherdsl.core.SymbolicName; import org.neo4j.cypherdsl.core.UnionPart; import org.neo4j.cypherdsl.core.With; import org.neo4j.cypherdsl.core.ast.TypedSubtree; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; /** * A strategy to keep track of {@link Named named variables} inside a scope. * * @author Michael J. Simons * @since 2021.3.2 */ @API(status = INTERNAL, since = "2021.3.2") public final class ScopingStrategy { /** * Keeps track of named objects that have been already visited. */ private final Deque> dequeOfVisitedNamed = new ArrayDeque<>(); /** * Some expressions have implicit scopes, we might not clear that after returning from * inner statements or returns. */ private final Deque> implicitScope = new ArrayDeque<>(); private final Deque> currentImports = new ArrayDeque<>(); private final Deque> definedInSubquery = new ArrayDeque<>(); private final List>> onScopeEntered = new ArrayList<>(); private final List>> onScopeLeft = new ArrayList<>(); /** * A flag if we can skip aliasing. This is currently the case in exactly one scenario: * A aliased expression passed to a map project. In that case, the alias is already * defined by the key to use in the projected map, and we cannot define him in `AS * xxx` fragment. */ private final Deque skipAliasing = new ArrayDeque<>(); private Set afterStatement = Collections.emptySet(); private Visitable previous; private boolean inOrder = false; private boolean inProperty = false; private boolean inSubquery = false; private boolean inListFunctionPredicate = false; private ScopingStrategy() { this.dequeOfVisitedNamed.push(new LinkedHashSet<>()); } /** * {@return an empty scoping strategy, for internal use only} */ public static ScopingStrategy create() { return new ScopingStrategy(); } /** * Returns an empty scoping strategy, for internal use only. * @param onScopeEntered event handlers to be called after entering local or implicit * scope * @param onScopeLeft event handlers to b called after leaving local or implicit scope * @return an empty scoping strategy, for internal use only */ public static ScopingStrategy create(List>> onScopeEntered, List>> onScopeLeft) { var strategy = create(); strategy.onScopeEntered.addAll(onScopeEntered); strategy.onScopeLeft.addAll(onScopeLeft); return strategy; } /** * Anything that might import variables from the outside, without using an explicit * {@code WITH} clause. * @param visitable the element to be checked whether it implicitly imports named * elements. * @return {@literal true} if named elements are imported */ private static boolean hasImplicitScope(Visitable visitable) { return visitable instanceof SubqueryExpression || visitable instanceof Statement.UnionQuery; } private static Predicate byHasAName() { Predicate hasAName = Named.class::isInstance; hasAName = hasAName.or(AliasedExpression.class::isInstance); hasAName = hasAName.or(SymbolicName.class::isInstance); return hasAName; } /** * Extracts an identifier from an {@link IdentifiableElement identifiable element}. * @param i the identifiable element * @return the identifier */ private static String extractIdentifier(IdentifiableElement i) { String value; if (i instanceof Named named) { value = named.getSymbolicName().map(SymbolicName::getValue).orElse(null); } else if (i instanceof Aliased aliased) { value = aliased.getAlias(); } else if (i instanceof SymbolicName symbolicName) { value = symbolicName.getValue(); } else { value = null; } return value; } private static boolean hasLocalScope(Visitable visitable) { return visitable instanceof PatternComprehension || visitable instanceof Subquery || visitable instanceof SubqueryExpression || visitable instanceof Foreach; } /** * Called when visiting a {@link Visitable}. * @param visitable element to be checked for new scope */ public void doEnter(Visitable visitable) { // We don't want the identifiable in an order clause to be retained if (visitable instanceof Order) { this.inOrder = true; } if (visitable instanceof Property) { this.inProperty = true; } if (visitable instanceof Subquery subquery) { var with = subquery.importingWith(); this.inSubquery = true; this.definedInSubquery.push(new LinkedHashSet<>()); Set imports = new LinkedHashSet<>(); this.currentImports.push(imports); if (with != null) { with.getItems() .stream() .filter(IdentifiableElement.class::isInstance) .map(IdentifiableElement.class::cast) .map(ScopingStrategy::extractIdentifier) .filter(Objects::nonNull) .forEach(imports::add); } } if (isListFunctionPredicate(visitable)) { this.inListFunctionPredicate = true; } if (visitable instanceof MapProjection) { this.skipAliasing.push(true); } else if (visitable instanceof SubqueryExpression) { this.skipAliasing.push(false); } boolean notify = false; Set scopeSeed = this.dequeOfVisitedNamed.isEmpty() ? Collections.emptySet() : this.dequeOfVisitedNamed.peek(); if (hasLocalScope(visitable)) { notify = true; this.dequeOfVisitedNamed.push(new LinkedHashSet<>(scopeSeed)); } if (hasImplicitScope(visitable)) { notify = true; this.implicitScope.push(new LinkedHashSet<>(scopeSeed)); } if (notify) { scopeSeed.addAll(this.afterStatement); var importsAndScope = new LinkedHashSet(); var current = this.currentImports.peek(); if (current != null) { current.stream().map(Cypher::name).forEach(importsAndScope::add); } importsAndScope.addAll(scopeSeed); this.onScopeEntered.forEach(c -> c.accept(visitable, importsAndScope)); } } public boolean isSkipAliasing() { return Optional.ofNullable(this.skipAliasing.peek()).orElse(false); } /** * Returns {@code true} if the named item has been visited in the current scope * before. * @param namedItem an item that might have been visited in the current scope * @return {@code true} if the named item has been visited in the current scope before */ public boolean hasVisitedBefore(Named namedItem) { if (!hasScope()) { return false; } Set scope = this.dequeOfVisitedNamed.peek(); return hasVisitedInScope(scope, namedItem); } private boolean isListFunctionPredicate(Visitable visitable) { return visitable instanceof FunctionInvocation fi && Set.of("all", "any", "none", "single").contains(fi.getFunctionName().toLowerCase(Locale.ROOT)); } /** * Called when leaving a {@link Visitable}. * @param visitable element to be checked for a scope to be closed */ public void doLeave(Visitable visitable) { if (!hasScope()) { return; } if (visitable instanceof IdentifiableElement identifiableElement && !this.inOrder && (!this.inProperty || visitable instanceof Property)) { if (identifiableElement instanceof SymbolicName && this.inListFunctionPredicate) { this.inListFunctionPredicate = false; } else { this.dequeOfVisitedNamed.peek().add(identifiableElement); if (this.inSubquery) { var identifier = extractIdentifier(identifiableElement); if (identifier != null) { this.definedInSubquery.peek().add(identifier); } } } } if (isListFunctionPredicate(visitable)) { this.inListFunctionPredicate = false; } boolean notify = false; if (visitable instanceof Statement) { leaveStatement(visitable); } else if (hasLocalScope(visitable)) { notify = true; Set lastVisitedNames = this.dequeOfVisitedNamed.pop(); if (visitable instanceof ExistentialSubquery) { this.afterStatement.retainAll(lastVisitedNames); } } else { clearPreviouslyVisitedNamed(visitable); } if (visitable instanceof Order) { this.inOrder = false; } if (visitable instanceof Property) { this.inProperty = false; } if (visitable instanceof Subquery) { this.inSubquery = false; this.currentImports.pop(); this.definedInSubquery.pop(); } if (visitable instanceof MapProjection || visitable instanceof SubqueryExpression) { this.skipAliasing.pop(); } if (hasImplicitScope(visitable)) { notify = true; this.implicitScope.pop(); } this.previous = visitable; if (notify) { Set retainedElements = new HashSet<>(this.afterStatement); this.onScopeLeft.forEach(c -> c.accept(visitable, retainedElements)); } } private void leaveStatement(Visitable visitable) { Set lastScope = this.dequeOfVisitedNamed.peek(); // We keep properties only around when they have been actually returned if (this.previous instanceof UnionPart && this.afterStatement != null) { lastScope.stream().filter(i -> !(i instanceof Property)).forEach(this.afterStatement::add); } else if (!(this.previous instanceof Return || this.previous instanceof YieldItems)) { this.afterStatement = lastScope.stream() .filter(i -> !(i instanceof Property)) .collect(Collectors.toCollection(LinkedHashSet::new)); } else { this.afterStatement = new LinkedHashSet<>(lastScope); } // A procedure call doesn't change scope. if (visitable instanceof ProcedureCall) { return; } lastScope.retainAll(Optional.ofNullable(this.implicitScope.peek()).orElseGet(Set::of)); } private boolean hasScope() { return !this.dequeOfVisitedNamed.isEmpty(); } private boolean hasVisitedInScope(Collection visited, Named needle) { return visited.contains(needle) || needle.getSymbolicName().isPresent() && visited.stream() .filter(byHasAName()) .map(ScopingStrategy::extractIdentifier) .filter(Objects::nonNull) .anyMatch(identifiedBy(needle)); } private Predicate identifiedBy(Named needle) { return i -> { boolean result = i.equals(needle.getRequiredSymbolicName().getValue()); if (result && this.inSubquery) { var imported = Optional.ofNullable(this.currentImports.peek()).orElseGet(Set::of).contains(i); return imported || this.definedInSubquery.peek().contains(i); } return result; }; } private void clearPreviouslyVisitedNamed(Visitable visitable) { if (visitable instanceof With with) { clearPreviouslyVisitedAfterWith(with); } else if (visitable instanceof Return || visitable instanceof YieldItems) { clearPreviouslyVisitedAfterReturnish(visitable); } } private void clearPreviouslyVisitedAfterWith(With with) { // We need to clear the named cache after defining a with. // Everything not taken into the next step has to go. Set retain = new HashSet<>(); Set visitedNamed = this.dequeOfVisitedNamed.peek(); if (visitedNamed == null) { return; } with.accept(segment -> { if (segment instanceof SymbolicName symbolicName) { visitedNamed.stream().filter(element -> { if (element instanceof Named named) { return named.getSymbolicName().filter(s -> s.equals(segment)).isPresent(); } else if (element instanceof Aliased aliased) { return aliased.getAlias().equals((symbolicName).getValue()); } else { return element.equals(segment); } }).forEach(retain::add); } else if (segment instanceof Asterisk) { retain.addAll(visitedNamed); } }); retain.addAll(Optional.ofNullable(this.implicitScope.peek()).orElseGet(Set::of)); visitedNamed.retainAll(retain); } private void clearPreviouslyVisitedAfterReturnish(Visitable returnish) { // Everything not returned has to go. Set retain = new HashSet<>(); Set visitedNamed = this.dequeOfVisitedNamed.peek(); returnish.accept(new Visitor() { int level = 0; Visitable entranceLevel1; @Override public void enter(Visitable segment) { if (this.entranceLevel1 == null && segment instanceof TypedSubtree) { this.entranceLevel1 = segment; return; } if (this.entranceLevel1 != null) { ++this.level; } // Only collect things exactly one level into the list of returned items if (this.level == 1 && segment instanceof IdentifiableElement identifiableElement) { retain.add(identifiableElement); } } @Override public void leave(Visitable segment) { if (this.entranceLevel1 != null) { this.level = Math.max(0, this.level - 1); if (segment == this.entranceLevel1) { this.entranceLevel1 = null; } } } }); retain.addAll(Optional.ofNullable(this.implicitScope.peek()).orElseGet(Set::of)); if (visitedNamed != null) { visitedNamed.retainAll(retain); } } /** * {@return an unmodifiable collections with identifiable in the current scope} */ public Collection getIdentifiables() { if (!hasScope()) { return Collections.emptySet(); } Predicate allNamedElementsHaveResolvedNames = e -> !(e instanceof Named named) || named.getSymbolicName().filter(s -> s.getValue() != null).isPresent(); Set items = Optional.ofNullable(this.dequeOfVisitedNamed.peek()) .filter(scope -> !scope.isEmpty()) .orElse(this.afterStatement); return items.stream() .filter(allNamedElementsHaveResolvedNames) .map(IdentifiableElement::asExpression) .collect(Collectors.collectingAndThen(Collectors.toCollection(LinkedHashSet::new), Collections::unmodifiableSet)); } public PatternElement lookup(Named node) { if (!hasScope() || node.getSymbolicName().isEmpty()) { return null; } var scope = this.dequeOfVisitedNamed.peek(); var identifiedBy = identifiedBy(node); return scope.stream().filter(byHasAName()).filter(PatternElement.class::isInstance).filter(i -> { var identifier = extractIdentifier(i); return identifier != null && identifiedBy.test(identifier); }).map(PatternElement.class::cast).findFirst().orElse(null); } /** * {@return the set of current imports} */ public Set getCurrentImports() { return Optional.ofNullable(this.currentImports.peek()) .stream() .flatMap(v -> v.stream().map(Cypher::name)) .collect(Collectors.toSet()); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/UsingPeriodicCommit.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Neo4jVersion; import org.neo4j.cypherdsl.core.ast.Visitable; import static org.apiguardian.api.API.Status.INTERNAL; /** * A visitable representing a {@code USING PERIODIC COMMIT} clause. Not meant to be used * outside the Cypher-DSL directly. Will be changed without further notice. * * @author Michael J. Simons * @param rate the rate to be applied * @since 2021.2.1 */ @API(status = INTERNAL, since = "2021.2.1") @Neo4jVersion(minimum = "3.5", last = "4.4") public record UsingPeriodicCommit(Integer rate) implements Visitable { } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/YieldItems.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.ast.TypedSubtree; import static org.apiguardian.api.API.Status.INTERNAL; /** * Items yielded by a stand alone or in query call. * * @author Michael J. Simons * @since 2020.0.1 */ @API(status = INTERNAL, since = "2020.0.1") public final class YieldItems extends TypedSubtree { private YieldItems(Expression... children) { super(children); } /** * Creates a new {@literal YIELD} expressions. * @param c the elements to yield * @return the new expression */ public static YieldItems yieldAllOf(Expression... c) { if (c == null || c.length == 0) { throw new IllegalArgumentException("Cannot yield an empty list of items."); } return new YieldItems(c); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/internal/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * This is basically the kitchen sink for all classes that must be public but are not * actually part of the public API. We will change them at will and don't give any * guarantees about them. All classes inside that package are marked as * {@link org.apiguardian.api.API.Status#INTERNAL}. The {@code internal} package won't be * exported when the Cypher-DSL is run on the module path. * * @author Michael J. Simons */ package org.neo4j.cypherdsl.core.internal; ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * Contains an internal DSL for creating Cypher. This API is in an experimental status at * the moment and might change without further notice. *

* While we have extensive tests in place to check that queries SDN needs are correctly * generated, the AST itself is not validated for type errors. That is, one could misuse * the DSL here to create an AST that renders to a syntactically correct Cypher statement, * that will explode during runtime. For example using wrongly typed expression (an * expression referencing a list while a map is needed or something like that). *

* With this in mind, please use this DSL consciously if you find it useful. It won't go * away anytime soon, but might change in ways that break your code without further * notice. * * @author Michael J. Simons */ package org.neo4j.cypherdsl.core; ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/querydsl/CypherContext.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.querydsl; import java.util.ArrayList; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import com.querydsl.core.types.Operator; import com.querydsl.core.types.Template; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.Literal.UnsupportedLiteralException; import org.neo4j.cypherdsl.core.Parameter; import org.neo4j.cypherdsl.core.internal.ConstantParameterHolder; import static org.apiguardian.api.API.Status.INTERNAL; /** * Context of a Cypher-DSL statement, holding all known expressions and parameters. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = INTERNAL, since = "2021.1.0") public final class CypherContext { @SuppressWarnings("FieldCanBeLocal") private static final String TO_STRING_VALUE_OF_UNSUPPORTED = "'" + CypherTemplates.UNSUPPORTED_MARKER + "'"; private final List expressions = new ArrayList<>(); private final Map> parameters = new IdentityHashMap<>(); public void add(Expression expression) { this.expressions.add(expression); } public Expression[] getExpressions() { return this.expressions.toArray(new Expression[0]); } Template getTemplate(Operator op) { Template template = CypherTemplates.DEFAULT.getTemplate(op); if (template != null) { for (Template.Element element : template.getElements()) { if (TO_STRING_VALUE_OF_UNSUPPORTED.equals(element.toString())) { throw new UnsupportedOperatorException(op); } } } return template; } int getPrecedence(Operator op) { return CypherTemplates.DEFAULT.getPrecedence(op); } Parameter getOrCreateParameterFor(Object object) { return this.parameters.computeIfAbsent(object, o -> { Object value; try { value = new ConstantParameterHolder(o); } catch (UnsupportedLiteralException ex) { value = o; } return Cypher.anonParameter(value); }); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/querydsl/CypherTemplates.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.querydsl; import com.querydsl.core.types.Ops; import com.querydsl.core.types.PathType; import com.querydsl.core.types.Templates; /** * Static holder for Cypher templates for the QueryDSL adapter. * * @author Michael J. Simons */ final class CypherTemplates extends Templates { static final String UNSUPPORTED_MARKER = "__UNSUPPORTED__"; static final Templates DEFAULT = new CypherTemplates(); private CypherTemplates() { super('\\'); final String sizeOfOneElement = "size({0})"; // boolean add(Ops.AND, "{0} AND {1}", Precedence.AND); add(Ops.NOT, "NOT {0}", Precedence.NOT_HIGH); add(Ops.OR, "{0} OR {1}", Precedence.OR); add(Ops.XNOR, "NOT ({0} XOR {1})", Precedence.XNOR); add(Ops.XOR, "{0} XOR {1}", Precedence.XOR); // collection add(Ops.COL_IS_EMPTY, "size({0}) = 0"); add(Ops.COL_SIZE, sizeOfOneElement); // array add(Ops.ARRAY_SIZE, sizeOfOneElement); // map add(Ops.MAP_SIZE, "size(keys({0}))"); add(Ops.MAP_IS_EMPTY, "size(keys({0})) = 0"); add(Ops.CONTAINS_KEY, "any(v in keys({0}) where v = {1})"); add(Ops.CONTAINS_VALUE, "any(v in [k IN KEYS({0}) | {0}[k]] where v = {1})"); // comparison add(Ops.BETWEEN, "{0} >= {1} AND {0} <= {2}", Precedence.COMPARISON); add(Ops.GOE, "{0} >= {1}", Precedence.COMPARISON); add(Ops.GT, "{0} > {1}", Precedence.COMPARISON); add(Ops.LOE, "{0} <= {1}", Precedence.COMPARISON); add(Ops.LT, "{0} < {1}", Precedence.COMPARISON); // numeric add(Ops.NEGATE, "-{0}", Precedence.NEGATE); add(Ops.ADD, "{0} + {1}", Precedence.ARITH_LOW); add(Ops.DIV, "{0} / {1}", Precedence.ARITH_HIGH); add(Ops.MOD, "{0} % {1}", Precedence.ARITH_HIGH); add(Ops.MULT, "{0} * {1}", Precedence.ARITH_HIGH); add(Ops.SUB, "{0} - {1}", Precedence.ARITH_LOW); // various add(Ops.EQ, "{0} = {1}", Precedence.EQUALITY); add(Ops.EQ_IGNORE_CASE, "{0l} = {1l}", Precedence.EQUALITY); add(Ops.INSTANCE_OF, UNSUPPORTED_MARKER, Precedence.COMPARISON); add(Ops.NE, "{0} != {1}", Precedence.EQUALITY); add(Ops.IN, "any(v in {0} where v = {1})", Precedence.COMPARISON); add(Ops.NOT_IN, "none(v in {0} where v = {1})", Precedence.COMPARISON); add(Ops.IS_NULL, "{0} is null", Precedence.COMPARISON); add(Ops.IS_NOT_NULL, "{0} is not null", Precedence.COMPARISON); add(Ops.ALIAS, "{0} as {1}", 0); add(Ops.NUMCAST, UNSUPPORTED_MARKER); add(Ops.STRING_CAST, UNSUPPORTED_MARKER); // string add(Ops.CONCAT, "{0} + {1}", Precedence.ARITH_LOW); add(Ops.LOWER, "toLower({0})"); add(Ops.SUBSTR_1ARG, "substring({0}, {1})"); add(Ops.SUBSTR_2ARGS, "substring({0}, {1}, {2})"); add(Ops.TRIM, "trim({0})"); add(Ops.UPPER, "toUpper({0})"); add(Ops.MATCHES, "{0} =~ {1}"); add(Ops.MATCHES_IC, "{0} =~ ('(?i)' + {1})"); add(Ops.STARTS_WITH, "{0} STARTS WITH {1}"); add(Ops.STARTS_WITH_IC, "{0l} STARTS WITH {1l}"); add(Ops.ENDS_WITH, "{0} ENDS WITH {1}"); add(Ops.ENDS_WITH_IC, "{0l} ENDS WITH {1l}"); add(Ops.STRING_CONTAINS, "{0} CONTAINS {1}"); add(Ops.STRING_CONTAINS_IC, "{0l} CONTAINS {1l}"); add(Ops.CHAR_AT, "substring({0}, {1}, 1)"); add(Ops.STRING_LENGTH, sizeOfOneElement); add(Ops.INDEX_OF, UNSUPPORTED_MARKER); add(Ops.INDEX_OF_2ARGS, UNSUPPORTED_MARKER); add(Ops.STRING_IS_EMPTY, "size({0}) = 0 "); add(Ops.LIKE, "{0} =~ '.*' + {1} + '.*'", Precedence.COMPARISON); add(Ops.LIKE_IC, "{0} =~ '(?i).*' + {1} + '.*'", Precedence.COMPARISON); add(Ops.LIKE_ESCAPE, UNSUPPORTED_MARKER, Precedence.COMPARISON); add(Ops.LIKE_ESCAPE_IC, UNSUPPORTED_MARKER, Precedence.COMPARISON); add(Ops.StringOps.LEFT, "left({0}, {1})"); add(Ops.StringOps.RIGHT, "right({0}, {1})"); add(Ops.StringOps.LTRIM, "ltrim({0})"); add(Ops.StringOps.RTRIM, "rtrim({0})"); add(Ops.StringOps.LOCATE, UNSUPPORTED_MARKER); add(Ops.StringOps.LOCATE2, UNSUPPORTED_MARKER); add(Ops.StringOps.LPAD, UNSUPPORTED_MARKER); add(Ops.StringOps.RPAD, UNSUPPORTED_MARKER); add(Ops.StringOps.LPAD2, UNSUPPORTED_MARKER); add(Ops.StringOps.RPAD2, UNSUPPORTED_MARKER); // date time add(Ops.DateTimeOps.SYSDATE, "datetime()"); add(Ops.DateTimeOps.CURRENT_DATE, "date()"); add(Ops.DateTimeOps.CURRENT_TIME, "time()"); add(Ops.DateTimeOps.CURRENT_TIMESTAMP, "datetime().epochmillis"); add(Ops.DateTimeOps.DATE, "date({0})"); add(Ops.DateTimeOps.MILLISECOND, "{0}.millisecond"); add(Ops.DateTimeOps.SECOND, "{0}.second"); add(Ops.DateTimeOps.MINUTE, "{0}.minute"); add(Ops.DateTimeOps.HOUR, "{0}.hour"); add(Ops.DateTimeOps.WEEK, "{0}.week"); add(Ops.DateTimeOps.MONTH, "{0}.month"); add(Ops.DateTimeOps.YEAR, "{0}.year"); add(Ops.DateTimeOps.YEAR_MONTH, UNSUPPORTED_MARKER); add(Ops.DateTimeOps.YEAR_WEEK, "{0}.weekYear"); add(Ops.DateTimeOps.DAY_OF_WEEK, "{0}.dayOfWeek"); add(Ops.DateTimeOps.DAY_OF_MONTH, UNSUPPORTED_MARKER); add(Ops.DateTimeOps.DAY_OF_YEAR, UNSUPPORTED_MARKER); add(Ops.DateTimeOps.ADD_YEARS, "{0} + duration({years: {1}})"); add(Ops.DateTimeOps.ADD_MONTHS, "{0} + duration({months: {1}})"); add(Ops.DateTimeOps.ADD_WEEKS, "{0} + duration({weeks: {1}})"); add(Ops.DateTimeOps.ADD_DAYS, "{0} + duration({days: {1}})"); add(Ops.DateTimeOps.ADD_HOURS, "{0} + duration({hours: {1}})"); add(Ops.DateTimeOps.ADD_MINUTES, "{0} + duration({minutes: {1}})"); add(Ops.DateTimeOps.ADD_SECONDS, "{0} + duration({seconds: {1}})"); add(Ops.DateTimeOps.DIFF_YEARS, "duration.between({0}, {1}).years"); add(Ops.DateTimeOps.DIFF_MONTHS, "duration.between({0}, {1}).months"); add(Ops.DateTimeOps.DIFF_WEEKS, "duration.between({0}, {1}).weeks"); add(Ops.DateTimeOps.DIFF_DAYS, "duration.between({0}, {1}).days"); add(Ops.DateTimeOps.DIFF_HOURS, "duration.between({0}, {1}).hours"); add(Ops.DateTimeOps.DIFF_MINUTES, "duration.between({0}, {1}).minutes"); add(Ops.DateTimeOps.DIFF_SECONDS, "duration.between({0}, {1}).seconds"); add(Ops.DateTimeOps.TRUNC_YEAR, "date.truncate('year', {0})"); add(Ops.DateTimeOps.TRUNC_MONTH, "date.truncate('month', {0})"); add(Ops.DateTimeOps.TRUNC_WEEK, "date.truncate('week', {0})"); add(Ops.DateTimeOps.TRUNC_DAY, "date.truncate('day', {0})"); add(Ops.DateTimeOps.TRUNC_HOUR, "datetime.truncate('hour', {0})"); add(Ops.DateTimeOps.TRUNC_MINUTE, "datetime.truncate('minute', {0})"); add(Ops.DateTimeOps.TRUNC_SECOND, "datetime.truncate('second', {0})"); // math add(Ops.MathOps.ABS, "abs({0})"); add(Ops.MathOps.ACOS, "acos({0})"); add(Ops.MathOps.ASIN, "asin({0})"); add(Ops.MathOps.ATAN, "atan({0})"); add(Ops.MathOps.CEIL, "ceil({0})"); add(Ops.MathOps.COS, "cos({0})"); add(Ops.MathOps.COSH, UNSUPPORTED_MARKER); add(Ops.MathOps.COT, "cot({0})"); add(Ops.MathOps.COTH, UNSUPPORTED_MARKER); add(Ops.MathOps.DEG, "degrees({0})"); add(Ops.MathOps.TAN, "tan({0})"); add(Ops.MathOps.TANH, UNSUPPORTED_MARKER); add(Ops.MathOps.SQRT, "sqrt({0})"); add(Ops.MathOps.SIGN, "sign({0})"); add(Ops.MathOps.SIN, "sin({0})"); add(Ops.MathOps.SINH, UNSUPPORTED_MARKER); add(Ops.MathOps.ROUND, "round({0})"); add(Ops.MathOps.ROUND2, "round({0}, {1})"); add(Ops.MathOps.RAD, "radians({0})"); add(Ops.MathOps.RANDOM, UNSUPPORTED_MARKER); add(Ops.MathOps.RANDOM2, UNSUPPORTED_MARKER); add(Ops.MathOps.POWER, UNSUPPORTED_MARKER); add(Ops.MathOps.MIN, "CASE WHEN {0} < {1} THEN {0} ELSE {1} END"); add(Ops.MathOps.MAX, "CASE WHEN {0} > {1} THEN {0} ELSE {1} END"); add(Ops.MathOps.LOG, UNSUPPORTED_MARKER); add(Ops.MathOps.LN, UNSUPPORTED_MARKER); add(Ops.MathOps.FLOOR, "floor({0})"); add(Ops.MathOps.EXP, "exp({0})"); // path types add(PathType.PROPERTY, "{0}.{1s}"); add(PathType.VARIABLE, "{0s}"); add(PathType.DELEGATE, "{0}"); add(Ops.ORDINAL, UNSUPPORTED_MARKER); for (PathType type : new PathType[] { PathType.LISTVALUE, PathType.MAPVALUE, PathType.ARRAYVALUE }) { add(type, "{0}[{1}]"); } for (PathType type : new PathType[] { PathType.LISTVALUE_CONSTANT, PathType.MAPVALUE_CONSTANT, PathType.ARRAYVALUE_CONSTANT }) { add(type, "{0}[{1s}]"); } // case add(Ops.CASE, "CASE {0} END", Precedence.CASE); add(Ops.CASE_WHEN, "WHEN {0} THEN {1} {2}", Precedence.CASE); add(Ops.CASE_ELSE, "ELSE {0}", Precedence.CASE); // case for add(Ops.CASE_EQ, "CASE {0} {1} END", Precedence.CASE); add(Ops.CASE_EQ_WHEN, "WHEN {1} THEN {2} {3}", Precedence.CASE); add(Ops.CASE_EQ_ELSE, "ELSE {0}", Precedence.CASE); // coalesce add(Ops.COALESCE, "coalesce({0})"); add(Ops.NULLIF, UNSUPPORTED_MARKER); // subquery add(Ops.EXISTS, "exists({0})", 0); // numeric aggregates add(Ops.AggOps.BOOLEAN_ALL, "all({0})"); add(Ops.AggOps.BOOLEAN_ANY, "any({0})"); add(Ops.AggOps.AVG_AGG, "avg({0})"); add(Ops.AggOps.MAX_AGG, "max({0})"); add(Ops.AggOps.MIN_AGG, "min({0})"); add(Ops.AggOps.SUM_AGG, "sum({0})"); add(Ops.AggOps.COUNT_AGG, "count({0})"); add(Ops.AggOps.COUNT_DISTINCT_AGG, "count(distinct {0})"); add(Ops.AggOps.COUNT_DISTINCT_ALL_AGG, "count(distinct *)"); add(Ops.AggOps.COUNT_ALL_AGG, "count(*)"); // quantified expressions add(Ops.QuantOps.AVG_IN_COL, "avg({0})"); add(Ops.QuantOps.MAX_IN_COL, "max({0})"); add(Ops.QuantOps.MIN_IN_COL, "min({0})"); add(Ops.QuantOps.ANY, "any {0}"); add(Ops.QuantOps.ALL, "all {0}"); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/querydsl/ToCypherFormatStringVisitor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.querydsl; import java.util.Arrays; import java.util.List; import com.querydsl.core.types.Constant; import com.querydsl.core.types.Expression; import com.querydsl.core.types.FactoryExpression; import com.querydsl.core.types.Operation; import com.querydsl.core.types.ParamExpression; import com.querydsl.core.types.Path; import com.querydsl.core.types.SubQueryExpression; import com.querydsl.core.types.Template; import com.querydsl.core.types.TemplateExpression; import com.querydsl.core.types.Visitor; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Cypher; import static org.apiguardian.api.API.Status.INTERNAL; /** * This is basically a copy of Query-DSL's * {@link com.querydsl.core.types.ToStringVisitor}. The main purpose of the string * generated here is to be used with our {@link Cypher#raw(String, Object...)} feature, * that allows to insert arbitrary query fragments into the AST. It is easier to render * the Query-DSL fragments than recreating our AST from Query-DSL. *

* The main difference in the original {@code ToStringVisitor} is to be found in * {@link #visit(ParamExpression, CypherContext)} and * {@link #visit(Constant, CypherContext)}. Both methods will use the {@literal $E} * notation to indicate an expression for the {@code RawLiteral} and add the expression * (either a literal or parameter) to the {@link CypherContext}. After all rendering has * been done by Query-DSL, the adapter will take the renderer string as a format string * and pass it on to {@link Cypher#raw(String, Object...)} along with all expressions * collected along the way. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = INTERNAL, since = "2021.1.0") public final class ToCypherFormatStringVisitor implements Visitor { /** * Global instance of this visitor. */ public static final ToCypherFormatStringVisitor INSTANCE = new ToCypherFormatStringVisitor(); private ToCypherFormatStringVisitor() { } @Override public String visit(FactoryExpression e, CypherContext context) { final StringBuilder builder = new StringBuilder(); builder.append("new ").append(e.getType().getSimpleName()).append("("); boolean first = true; for (Expression arg : e.getArgs()) { if (!first) { builder.append(", "); } builder.append(arg.accept(this, context)); first = false; } builder.append(")"); return builder.toString(); } @Override public String visit(Operation o, CypherContext context) { final Template template = context.getTemplate(o.getOperator()); if (template != null) { final int precedence = context.getPrecedence(o.getOperator()); final StringBuilder builder = new StringBuilder(); for (Template.Element element : template.getElements()) { final Object rv = element.convert(o.getArgs()); if (rv instanceof Expression) { if (precedence > -1 && rv instanceof Operation && precedence < context.getPrecedence(((Operation) rv).getOperator())) { builder.append("(").append(((Expression) rv).accept(this, context)).append(")"); continue; } builder.append(((Expression) rv).accept(this, context)); } else { builder.append(rv.toString()); } } return builder.toString(); } else { throw new IllegalArgumentException( "unknown operation with operator " + o.getOperator().name() + " and args " + o.getArgs()); } } @Override public String visit(ParamExpression param, CypherContext context) { context.add(Cypher.parameter(param.getName())); return "$E"; } @Override public String visit(Path p, CypherContext context) { final Path parent = p.getMetadata().getParent(); final Object elem = p.getMetadata().getElement(); if (parent != null) { Template pattern = context.getTemplate(p.getMetadata().getPathType()); if (pattern != null) { final List args = Arrays.asList(parent, elem); final StringBuilder builder = new StringBuilder(); for (Template.Element element : pattern.getElements()) { Object rv = element.convert(args); if (rv instanceof Expression) { builder.append(((Expression) rv).accept(this, context)); } else { builder.append(rv.toString()); } } return builder.toString(); } else { throw new IllegalArgumentException("No pattern for " + p.getMetadata().getPathType()); } } else { return elem.toString(); } } @Override public String visit(SubQueryExpression expr, CypherContext context) { return expr.getMetadata().toString(); } @Override public String visit(TemplateExpression expr, CypherContext context) { final StringBuilder builder = new StringBuilder(); for (Template.Element element : expr.getTemplate().getElements()) { Object rv = element.convert(expr.getArgs()); if (rv instanceof Expression) { builder.append(((Expression) rv).accept(this, context)); } else { builder.append(rv.toString()); } } return builder.toString(); } @Override public String visit(Constant expr, CypherContext context) { Object constantValue = expr.getConstant(); if (constantValue == null) { context.add(Cypher.literalOf(null)); } else if (constantValue instanceof Boolean) { context.add(Cypher.literalOf(constantValue)); } else { context.add(context.getOrCreateParameterFor(constantValue)); } return "$E"; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/querydsl/UnsupportedOperatorException.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.querydsl; import java.io.Serial; import com.querydsl.core.types.Operator; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * Thrown when a QueryDSL operator cannot be used with the Cypher-DSL predicate converter. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = STABLE, since = "2021.1.0") public final class UnsupportedOperatorException extends IllegalArgumentException { @Serial private static final long serialVersionUID = 2025849674095086421L; private final Operator unsupportedOperator; public UnsupportedOperatorException(Operator unsupportedOperator) { super("The Cypher-DSL cannot use the Query-DSL operator " + unsupportedOperator); this.unsupportedOperator = unsupportedOperator; } public Operator getUnsupportedOperator() { return this.unsupportedOperator; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/querydsl/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * Shims for turning Query-DSL predicates into conditions. */ package org.neo4j.cypherdsl.core.querydsl; ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/renderer/ConfigurableRenderer.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.BiFunction; import org.neo4j.cypherdsl.core.Statement; import org.neo4j.cypherdsl.core.StatementContext; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.internal.DefaultStatementContext; import org.neo4j.cypherdsl.core.utils.LRUCache; /** * The default renderer for any Cypher-DSL statement. * * @author Michael J. Simons * @author Gerrit Meier * @since 1.0 */ final class ConfigurableRenderer implements GeneralizedRenderer, Renderer { private static final Map CONFIGURATIONS = new ConcurrentHashMap<>(8); private static final int STATEMENT_CACHE_SIZE = 128; private final LRUCache renderedStatementCache = new LRUCache<>(STATEMENT_CACHE_SIZE); private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock read = this.lock.readLock(); private final Lock write = this.lock.writeLock(); private final Configuration configuration; ConfigurableRenderer(Configuration configuration) { this.configuration = configuration; } /** * Creates a new instance of the configurable renderer or uses an existing one * matching the given configuration. * @param configuration the configuration for the render * @return a new renderer */ static ConfigurableRenderer create(Configuration configuration) { return CONFIGURATIONS.computeIfAbsent(configuration, ConfigurableRenderer::new); } @Override public String render(Statement statement) { return render((Visitable) statement); } @Override // This is about not using map.computeIfAbsent. This is done very much on purpose to // keep this // class thread safe. The LRUCache is basically LinkedHashMap and the method wouldn't // be threadsafe. @SuppressWarnings("squid:S3824") public String render(Visitable visitable) { record RenderingConfig(StatementContext ctx, boolean renderConstantsAsParameters) { } BiFunction renderOp = (cfg, v) -> { var renderingVisitor = createVisitor(cfg.ctx, cfg.renderConstantsAsParameters); v.accept(renderingVisitor); var result = renderingVisitor.getRenderedContent().trim(); return this.configuration.getDialect().getPrefix().map(pv -> pv + result).orElse(result); }; if (visitable instanceof Statement statement) { String renderedContent; int key = Objects.hash(statement, statement.isRenderConstantsAsParameters(), this.configuration.getDialect()); try { this.read.lock(); renderedContent = this.renderedStatementCache.get(key); } finally { this.read.unlock(); } if (renderedContent == null) { try { this.write.lock(); renderedContent = renderOp.apply( new RenderingConfig(statement.getContext(), statement.isRenderConstantsAsParameters()), statement); this.renderedStatementCache.put(key, renderedContent); } catch (SchemaEnforcementFailedException ex) { renderedContent = ""; } finally { this.write.unlock(); } } return renderedContent; } else { return renderOp.apply(new RenderingConfig(new DefaultStatementContext(), false), visitable); } } private RenderingVisitor createVisitor(StatementContext statementContext, boolean renderConstantsAsParameters) { if (!this.configuration.isPrettyPrint()) { return new DefaultVisitor(statementContext, renderConstantsAsParameters, this.configuration); } else { return new PrettyPrintingVisitor(statementContext, renderConstantsAsParameters, this.configuration); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/renderer/Configuration.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; /** * This class provides some configuration settings for the Cypher-DSL, mainly around * rendering of a statement. Instances of {@link Configuration} are threadsafe and can be * reused. Please use the {@link Configuration.Builder associated builder} via * {@link Configuration#newConfig()} to create new variants. * * @author Michael J. Simons * @since 2021.0.1 */ @API(status = STABLE, since = "2021.0.1") public final class Configuration { private static final Configuration DEFAULT_CONFIG = newConfig().build(); private static final Configuration PRETTY_PRINTING = newConfig().withPrettyPrint(true) .alwaysEscapeNames(false) .build(); /** * Set to {@literal true} to enable pretty printing. */ private final boolean prettyPrint; /** * Configure your favorite indentation style. */ private final IndentStyle indentStyle; /** * The indentation sizes. Only applicable when using {@link IndentStyle#SPACE}. * Defaults to {@literal 2}. */ private final int indentSize; /** * A flag if all names (node labels and relationship types) should be escaped all the * time. Defaults to {@literal true}. When pretty printing, this defaults to * {@literal false}. */ private final boolean alwaysEscapeNames; /** * Configure if and which names (identifiers of entities, parameter names and aliases) * will be replaced with generated names during rendering. The names will stay * constant for the lifetime of a statement. It defaults to the empty set. This * setting can be useful if you want to normalize your statements: Imagine two * statements coming from two different sources who may have used different variable * or parameter names. Using generated names will make those statements produce the * same rendering, given they have the same semantics. * */ private final Set generatedNames; /** * The dialect to use when rendering a statement. The default dialect works well with * Neo4j 4.4 and prior. */ private final Dialect dialect; /** * A flag of the renderer should be instructed to enforce a schema. */ private final boolean enforceSchema; /** * The map of known relationship definitions. The key is the relationship type. */ private final Map> relationshipDefinitions; /** * Flag that can be used to disable dynamic labels, defaults to false. */ private final boolean disableDynamicLabels; private Configuration(Builder builder) { this.prettyPrint = builder.prettyPrint; this.alwaysEscapeNames = builder.alwaysEscapeNames; this.indentStyle = builder.indentStyle; this.indentSize = builder.indentSize; this.dialect = (builder.dialect != null) ? builder.dialect : Dialect.NEO4J_5_DEFAULT_CYPHER; this.generatedNames = builder.generatedNames; this.enforceSchema = builder.enforceSchema; this.disableDynamicLabels = builder.disableDynamicLabels; Map> mutableRelationshipDefinitions = new HashMap<>(); builder.relationshipDefinitions.forEach((k, v) -> mutableRelationshipDefinitions.put(k, List.copyOf(v))); this.relationshipDefinitions = Map.copyOf(mutableRelationshipDefinitions); } /** * Creates a new relationship definition from a string in the form * {@code (sourceLabel, TYPE, targetLabel)}. * @param definition the literal definition of the relationship * @return a new relationship definition */ public static RelationshipDefinition relationshipDefinition(String definition) { return RelationshipDefinition.of(definition); } /** * Cypher is not pretty printed by default. No indentation settings apply. * @return the default config */ public static Configuration defaultConfig() { return DEFAULT_CONFIG; } /** * Pretty printing with default indentation settings. * @return a configuration enabling pretty printing. */ public static Configuration prettyPrinting() { return PRETTY_PRINTING; } /** * {@return a new builder} for creating a new configuration from scratch */ public static Builder newConfig() { return Builder.newConfig(); } /** * {@return true if this configuration uses pretty printing} */ public boolean isPrettyPrint() { return this.prettyPrint; } /** * {@return the indentation style} whether to use tabs or spaces to indent things */ public IndentStyle getIndentStyle() { return this.indentStyle; } /** * {@return width of one indentation} */ public int getIndentSize() { return this.indentSize; } /** * {@return true when names should be always escaped} */ public boolean isAlwaysEscapeNames() { return this.alwaysEscapeNames; } /** * Returns the set of object types for which generated names should be used. * @return the set of object types for which generated names should be used * @since 2023.2.0 */ public Set getGeneratedNames() { return this.generatedNames; } /** * Returns {@literal true} when symbolic and parameter names should be replaced with * * generated names. * @return {@literal true} when symbolic and parameter names should be replaced with * generated names * @since 2023.2.0 */ public boolean isUseGeneratedNames() { return !this.generatedNames.isEmpty(); } /** * {@return the target dialect} */ public Dialect getDialect() { return this.dialect; } /** * Returns {@literal true} if a schema should be enforced. * @return {@literal true} if a schema should be enforced * @since 2023.7.0 */ @API(status = EXPERIMENTAL, since = "2023.7.0") public boolean isEnforceSchema() { return this.enforceSchema; } /** * Returns a map of predefined relationships. * @return a map of predefined relationships * @since 2023.7.0 */ @API(status = EXPERIMENTAL, since = "2023.7.0") public Map> getRelationshipDefinitions() { return this.relationshipDefinitions; } /** * Returns {@literal true} if dynamic labels are disabled. * @return {@literal true} if dynamic labels are disabled * @since 2025.0.3 */ public boolean isDisableDynamicLabels() { return this.disableDynamicLabels; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Configuration that = (Configuration) o; return this.prettyPrint == that.prettyPrint && this.indentSize == that.indentSize && this.indentStyle == that.indentStyle && this.alwaysEscapeNames == that.alwaysEscapeNames && this.dialect == that.dialect && this.generatedNames.equals(that.generatedNames) && this.enforceSchema == that.enforceSchema && this.relationshipDefinitions.equals(that.relationshipDefinitions) && this.disableDynamicLabels == that.disableDynamicLabels; } @Override public int hashCode() { return Objects.hash(this.prettyPrint, this.indentStyle, this.indentSize, this.alwaysEscapeNames, this.dialect, this.generatedNames, this.enforceSchema, this.relationshipDefinitions, this.disableDynamicLabels); } @Override public String toString() { return "Configuration{" + "prettyPrint=" + this.prettyPrint + ", indentStyle=" + this.indentStyle + ", indentSize=" + this.indentSize + '}'; } /** * Enum for the available indent styles. */ public enum IndentStyle { /** Use tabs for indentation. */ TAB, /** Use a configurable number of spaces for indentation. */ SPACE } /** * Enum for configuring name rewriting. */ public enum GeneratedNames { /** * Entity names are rewritten. */ ENTITY_NAMES, /** * Parameter names are rewritten. */ PARAMETER_NAMES, /** * All aliases are rewritten. */ ALL_ALIASES, /** * All aliases except the ones used in the last {@literal RETURN} clause. */ INTERNAL_ALIASES_ONLY, /** * This is an additional flag that is on by default for aliases, but can be * removed from the config, so that every alias name triggers a new generated * name. Otherwise, any overwriting of an alias will reuse the name that was * previously generated for that alias. */ REUSE_ALIASES } /** * Simple definition of a known / schema relationship. * * @param sourceLabel the source label * @param type the type of the relationship * @param targetLabel the target label */ public record RelationshipDefinition(String sourceLabel, String type, String targetLabel) { /** * Will make sure all parameters are trimmed. * @param sourceLabel the source label * @param type the type of the relationship * @param targetLabel the target label */ public RelationshipDefinition { sourceLabel = sourceLabel.trim(); type = type.trim(); targetLabel = targetLabel.trim(); } private static RelationshipDefinition of(String definition) { var tuple = Objects.requireNonNull(definition).replace("(", "").replace(")", "").split(","); if (tuple.length != 3) { throw new IllegalArgumentException("Invalid relationship definition " + definition); } return new RelationshipDefinition(tuple[0], tuple[1], tuple[2]); } boolean selfReferential() { return this.sourceLabel.equals(this.targetLabel); } } /** * Use this builder to create new {@link Configuration} instances. */ @SuppressWarnings("HiddenField") public static final class Builder { private boolean prettyPrint = false; private IndentStyle indentStyle = IndentStyle.SPACE; private int indentSize = 2; private boolean alwaysEscapeNames = true; private Dialect dialect = Dialect.NEO4J_5_DEFAULT_CYPHER; private Set generatedNames = EnumSet.noneOf(GeneratedNames.class); private boolean enforceSchema = false; private boolean disableDynamicLabels = false; private Map> relationshipDefinitions = new HashMap<>(); private Builder() { } static Builder newConfig() { return new Builder(); } /** * Enables or disables pretty printing. Enabling pretty printing will disable * unnecessary escaping of labels and types. * @param prettyPrint use {@literal true} for enabling pretty printing * @return this builder */ public Builder withPrettyPrint(boolean prettyPrint) { this.prettyPrint = prettyPrint; if (this.prettyPrint) { return this.alwaysEscapeNames(false); } return this; } /** * Configures the indentation style. * @param indentStyle the new indentation style * @return this builder */ public Builder withIndentStyle(IndentStyle indentStyle) { if (indentStyle == null) { throw new IllegalArgumentException("Indent style is required."); } this.indentStyle = indentStyle; return this; } /** * Configures the indentation size (width). * @param indentSize the new indentation size * @return this builder */ public Builder withIndentSize(int indentSize) { this.indentSize = indentSize; return this; } /** * Configure whether names should be always escaped. * @param alwaysEscapeNames use {@literal true} to always escape names * @return this builder */ public Builder alwaysEscapeNames(boolean alwaysEscapeNames) { this.alwaysEscapeNames = alwaysEscapeNames; return this; } /** * Configure whether variable names should be always generated. * @param useGeneratedNames set to {@literal true} to use generated symbolic * names, parameter names and aliases * @return this builder */ public Builder withGeneratedNames(boolean useGeneratedNames) { if (useGeneratedNames) { this.generatedNames = EnumSet.allOf(GeneratedNames.class); } else { this.generatedNames = EnumSet.noneOf(GeneratedNames.class); } return this; } /** * Configure for which type of object generated names should be used. * @param useGeneratedNames the set of objects for which generated names should be * used * @return this builder */ public Builder withGeneratedNames(Set useGeneratedNames) { this.generatedNames = Objects.requireNonNullElseGet(useGeneratedNames, () -> EnumSet.noneOf(GeneratedNames.class)); return this; } /** * Use a configuration with a dialect fitting your target database if the default * dialect for {@link Dialect#NEO4J_4 Neo4j 4.x and earlier} leads to incompatible * results with your version of Neo4j. * @param dialect the new dialect * @return this builder. You can both use the original or this instance. * @since 2022.3.0 */ public Builder withDialect(Dialect dialect) { this.dialect = dialect; return this; } /** * Adds a new relationship definition to the current schema. * @param relationshipDefinition a new relationship definition * @return this builder * @since 2023.7.0 */ @API(status = EXPERIMENTAL, since = "2023.7.0") public Builder withRelationshipDefinition(RelationshipDefinition relationshipDefinition) { if (relationshipDefinition == null) { return this; } var relationships = this.relationshipDefinitions.computeIfAbsent(relationshipDefinition.type, k -> new ArrayList<>()); relationships.add(relationshipDefinition); return this; } /** * Configure whether to enforce a schema or not. * @param enforceSchema set to {@literal true} to enforce the schema defined by * known {@link #withRelationshipDefinition(RelationshipDefinition) relationship * definitions}. * @return this builder * @since 2023.7.0 */ @API(status = EXPERIMENTAL, since = "2023.7.0") public Builder withEnforceSchema(boolean enforceSchema) { this.enforceSchema = enforceSchema; return this; } /** * Use this setting to disable dynamic labels. * @param disableDynamicLabels the new value * @return this builder * @since 2025.0.3 */ public Builder disableDynamicLabels(boolean disableDynamicLabels) { this.disableDynamicLabels = disableDynamicLabels; return this; } /** * {@return a new immutable configuration} */ public Configuration build() { return new Configuration(this); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/renderer/DefaultVisitor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayDeque; import java.util.Deque; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import java.util.stream.Collectors; import org.neo4j.cypherdsl.build.annotations.RegisterForReflection; import org.neo4j.cypherdsl.core.AliasedExpression; import org.neo4j.cypherdsl.core.Case; import org.neo4j.cypherdsl.core.CollectExpression; import org.neo4j.cypherdsl.core.Condition; import org.neo4j.cypherdsl.core.CountExpression; import org.neo4j.cypherdsl.core.Create; import org.neo4j.cypherdsl.core.Delete; import org.neo4j.cypherdsl.core.ExistentialSubquery; import org.neo4j.cypherdsl.core.Foreach; import org.neo4j.cypherdsl.core.FunctionInvocation; import org.neo4j.cypherdsl.core.Hint; import org.neo4j.cypherdsl.core.InTransactions; import org.neo4j.cypherdsl.core.KeyValueMapEntry; import org.neo4j.cypherdsl.core.Labels; import org.neo4j.cypherdsl.core.Limit; import org.neo4j.cypherdsl.core.ListComprehension; import org.neo4j.cypherdsl.core.ListExpression; import org.neo4j.cypherdsl.core.Literal; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Match; import org.neo4j.cypherdsl.core.Merge; import org.neo4j.cypherdsl.core.MergeAction; import org.neo4j.cypherdsl.core.NestedExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Operation; import org.neo4j.cypherdsl.core.Operator; import org.neo4j.cypherdsl.core.Order; import org.neo4j.cypherdsl.core.Parameter; import org.neo4j.cypherdsl.core.PatternComprehension; import org.neo4j.cypherdsl.core.PatternExpression; import org.neo4j.cypherdsl.core.PatternSelector; import org.neo4j.cypherdsl.core.ProcedureCall; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.PropertyLookup; import org.neo4j.cypherdsl.core.QuantifiedPathPattern; import org.neo4j.cypherdsl.core.Relationship; import org.neo4j.cypherdsl.core.Remove; import org.neo4j.cypherdsl.core.Return; import org.neo4j.cypherdsl.core.Set; import org.neo4j.cypherdsl.core.Skip; import org.neo4j.cypherdsl.core.SortItem; import org.neo4j.cypherdsl.core.StatementContext; import org.neo4j.cypherdsl.core.Subquery; import org.neo4j.cypherdsl.core.SubqueryExpression; import org.neo4j.cypherdsl.core.SymbolicName; import org.neo4j.cypherdsl.core.UnionPart; import org.neo4j.cypherdsl.core.Unwind; import org.neo4j.cypherdsl.core.Use; import org.neo4j.cypherdsl.core.Where; import org.neo4j.cypherdsl.core.With; import org.neo4j.cypherdsl.core.ast.ProvidesAffixes; import org.neo4j.cypherdsl.core.ast.TypedSubtree; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.internal.CaseElse; import org.neo4j.cypherdsl.core.internal.CaseWhenThen; import org.neo4j.cypherdsl.core.internal.ConstantParameterHolder; import org.neo4j.cypherdsl.core.internal.Distinct; import org.neo4j.cypherdsl.core.internal.LoadCSV; import org.neo4j.cypherdsl.core.internal.NameResolvingStrategy; import org.neo4j.cypherdsl.core.internal.Namespace; import org.neo4j.cypherdsl.core.internal.ProcedureName; import org.neo4j.cypherdsl.core.internal.ReflectiveVisitor; import org.neo4j.cypherdsl.core.internal.RelationshipLength; import org.neo4j.cypherdsl.core.internal.RelationshipPatternCondition; import org.neo4j.cypherdsl.core.internal.RelationshipTypes; import org.neo4j.cypherdsl.core.internal.SchemaNamesBridge; import org.neo4j.cypherdsl.core.internal.ScopingStrategy; import org.neo4j.cypherdsl.core.internal.UsingPeriodicCommit; import org.neo4j.cypherdsl.core.internal.YieldItems; /** * This is a simple (some would call it naive) implementation of a visitor to the Cypher * AST created by the Cypher builder based on the {@link ReflectiveVisitor reflective * visitor}. *

* It takes care of separating elements of subtrees containing the element type with a * separator and provides pairs of {@code enter} / {@code leave} for the structuring * elements of the Cypher AST as needed. *

* This rendering visitor is not meant to be used outside framework code, and we don't * give any guarantees on the format being output apart from that it works within the * constraints of SDN-RX respectively SDN 6 and later. * * @author Michael J. Simons * @author Gerrit Meier * @since 1.0 */ @SuppressWarnings({ "unused", "squid:S1172" }) @RegisterForReflection class DefaultVisitor extends ReflectiveVisitor implements RenderingVisitor { private static final EnumSet SKIP_SPACES = EnumSet.of(Operator.EXPONENTIATION, Operator.UNARY_MINUS, Operator.UNARY_PLUS); /** * Target of all rendering. */ protected final StringBuilder builder = new StringBuilder(); /** * A set of aliased expressions that already have been seen and for which an alias * must be used on each following appearance. */ protected final java.util.Set visitableToAliased = new HashSet<>(); /** * This keeps track on which level of the tree a separator is needed. */ private final Map separatorOnLevel = new ConcurrentHashMap<>(); /** * Keeps track of scoped, named variables. */ private final ScopingStrategy scopingStrategy; /** * Keeps track if currently in an aliased expression so that the content can be * skipped when already visited. */ private final Deque currentAliasedElements = new ArrayDeque<>(); /** * A cache of delegates, avoiding unnecessary object creation. */ private final Map, Visitor> delegateCache = new ConcurrentHashMap<>(); private final NameResolvingStrategy nameResolvingStrategy; private final boolean enforceSchema; private final Map> relationshipDefinitions; private final Deque inPatternExpression = new ArrayDeque<>(); /** * Rendering parameters is not a config property due to some needs in Spring Data * Neo4j: This needs to be configured per statement, not per config there. */ private final boolean renderConstantsAsParameters; protected final boolean alwaysEscapeNames; private final Dialect dialect; private final boolean disableDynamicLabels; boolean inReturn; boolean inSubquery; private final Deque inLabelExpression = new ArrayDeque<>(); /** * The current level in the tree of cypher elements. */ private int currentLevel = 0; /** * Will be set to true when entering an already visited node. */ private boolean skipNodeContent = false; /** * Will be set to true when entering an already visited relationship. */ private boolean skipRelationshipContent = false; /** * Will be true when inside a {@link RelationshipPatternCondition}. */ private boolean inRelationshipCondition = false; private boolean inEntity; private boolean inPropertyLookup; private Relationship.Direction directionOverride; DefaultVisitor(StatementContext statementContext) { this(statementContext, false); } DefaultVisitor(StatementContext statementContext, boolean renderConstantsAsParameters) { this(statementContext, renderConstantsAsParameters, Configuration.newConfig().alwaysEscapeNames(true).build()); } DefaultVisitor(StatementContext statementContext, boolean renderConstantsAsParameters, Configuration configuration) { this.nameResolvingStrategy = configuration.isUseGeneratedNames() ? NameResolvingStrategy.useGeneratedNames(statementContext, configuration.getGeneratedNames()) : NameResolvingStrategy.useGivenNames(statementContext); this.scopingStrategy = ScopingStrategy.create(List.of(this.nameResolvingStrategy::enterScope), List.of(this.nameResolvingStrategy::leaveScope)); this.renderConstantsAsParameters = renderConstantsAsParameters; this.alwaysEscapeNames = configuration.isAlwaysEscapeNames(); this.dialect = configuration.getDialect(); this.enforceSchema = configuration.isEnforceSchema(); this.disableDynamicLabels = configuration.isDisableDynamicLabels(); this.relationshipDefinitions = configuration.getRelationshipDefinitions(); } boolean hasIdentifiables() { return !this.scopingStrategy.getIdentifiables().isEmpty(); } private void enableSeparator(int level, boolean on, Supplier supplier) { if (on) { this.separatorOnLevel.put(level, new SeparatorAndSupplier(new AtomicReference<>(""), (supplier != null) ? supplier : () -> "")); } else { this.separatorOnLevel.remove(level); } } private Optional separatorOnCurrentLevel() { return Optional.ofNullable(this.separatorOnLevel.get(this.currentLevel)); } @Override protected boolean preEnter(Visitable visitable) { Visitable lastAliased = this.currentAliasedElements.peek(); if (this.skipNodeContent || this.visitableToAliased.contains(lastAliased)) { return false; } if (visitable instanceof AliasedExpression aliasedExpression) { this.currentAliasedElements.push(aliasedExpression); } int nextLevel = ++this.currentLevel + 1; if (visitable instanceof TypedSubtree ts) { enableSeparator(nextLevel, true, ts::separator); } separatorOnCurrentLevel().ifPresent(ref -> this.builder.append(ref.seperator().getAndSet(""))); if (visitable instanceof ProvidesAffixes providesAffixes) { providesAffixes.getPrefix().ifPresent(this::doWithPrefix); } boolean doEnter = !this.skipNodeContent; if (doEnter) { this.scopingStrategy.doEnter(visitable); } return doEnter; } @Override protected final PreEnterResult getPreEnterResult(Visitable visitable) { boolean doEnter = preEnter(visitable); if (!doEnter) { return PreEnterResult.skip(); } Class handlerType; if (visitable instanceof Labels && this.disableDynamicLabels) { handlerType = Neo4j5Pre26LabelsVisitor.class; } else { handlerType = this.dialect.getHandler(visitable); } if (handlerType != null) { Visitor handler = this.delegateCache.computeIfAbsent(handlerType, this::newHandler); return PreEnterResult.delegateTo(handler); } return PreEnterResult.doEnter(); } private Visitor newHandler(Class handlerType) { try { Constructor ctor = handlerType.getDeclaredConstructor(DefaultVisitor.class); return ctor.newInstance(this); } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) { throw new IllegalArgumentException(this.dialect.name() + " has defined an illegal handler not providing a constructor accepting a delegate."); } } @Override protected void postLeave(Visitable visitable) { this.scopingStrategy.doLeave(visitable); separatorOnCurrentLevel().ifPresent(ref -> ref.seperator().set(ref.supplier().get())); if (visitable instanceof ProvidesAffixes providesAffixes) { providesAffixes.getSuffix().ifPresent(this::doWithSuffix); } if (visitable instanceof TypedSubtree) { enableSeparator(this.currentLevel + 1, false, null); } if (this.currentAliasedElements.peek() == visitable) { this.currentAliasedElements.pop(); } if (visitable instanceof AliasedExpression aliasedExpression) { this.visitableToAliased.add(aliasedExpression); } --this.currentLevel; } protected void doWithPrefix(String prefix) { this.builder.append(prefix); } protected void doWithSuffix(String suffix) { this.builder.append(suffix); } void enter(Match match) { if (match.isOptional()) { this.builder.append("OPTIONAL "); } this.builder.append("MATCH "); } void leave(Match match) { this.builder.append(" "); } void enter(Where where) { this.builder.append(" WHERE "); } void enter(Create create) { this.builder.append("CREATE "); } void leave(Create create) { this.builder.append(" "); } void enter(Merge merge) { this.builder.append("MERGE "); } void leave(Merge merge) { if (!merge.hasEvents()) { // The last SET will include this this.builder.append(" "); } } void enter(MergeAction onCreateOrMatchEvent) { switch (onCreateOrMatchEvent.getType()) { case ON_CREATE -> this.builder.append("ON CREATE"); case ON_MATCH -> this.builder.append("ON MATCH"); } this.builder.append(" "); } void enter(Condition condition) { this.inRelationshipCondition = condition instanceof RelationshipPatternCondition; } void leave(Condition condition) { this.inRelationshipCondition = false; } void enter(Distinct distinct) { this.builder.append("DISTINCT "); } void enter(Return returning) { this.inReturn = true; addBlankIfNecessary(); if (!returning.isRaw()) { this.builder.append("RETURN "); } } void leave(Return returning) { this.inReturn = false; } void enter(With with) { this.builder.append("WITH "); } void leave(With with) { this.builder.append(" "); } void enter(Delete delete) { if (delete.isDetach()) { this.builder.append("DETACH "); } this.builder.append("DELETE "); } void leave(Delete match) { this.builder.append(" "); } boolean inLastReturn() { return this.inReturn && !this.inSubquery; } void enter(AliasedExpression aliased) { if (this.visitableToAliased.contains(aliased)) { this.builder.append(escapeIfNecessary(this.nameResolvingStrategy.resolve(aliased, false, inLastReturn()))); } } void leave(AliasedExpression aliased) { if (!(this.visitableToAliased.contains(aliased) || this.scopingStrategy.isSkipAliasing())) { this.builder.append(" AS ") .append(escapeIfNecessary(this.nameResolvingStrategy.resolve(aliased, true, inLastReturn()))); } } void enter(NestedExpression nested) { this.builder.append("("); } void leave(NestedExpression nested) { this.builder.append(")"); } void enter(Order order) { addBlankIfNecessary(); this.builder.append("ORDER BY "); } private void addBlankIfNecessary() { var lastString = this.builder.substring(Math.max(0, this.builder.length() - 1)); if (!lastString.isBlank() && !lastString.equals("{")) { this.builder.append(" "); } } void enter(Skip skip) { this.builder.append(" SKIP "); } void enter(Limit limit) { this.builder.append(" LIMIT "); } void enter(SortItem.Direction direction) { this.builder.append(" ").append(direction.getSymbol()); } void enter(PropertyLookup propertyLookup) { this.inPropertyLookup = true; if (propertyLookup.isDynamicLookup()) { this.builder.append("["); } else { this.builder.append("."); } } void leave(PropertyLookup propertyLookup) { this.inPropertyLookup = false; if (propertyLookup.isDynamicLookup()) { this.builder.append("]"); } } void enter(FunctionInvocation functionInvocation) { String functionName = functionInvocation.getFunctionName(); if ("elementId".equals(functionName)) { functionName = "toString(id"; } this.builder.append(functionName).append("("); } void leave(FunctionInvocation functionInvocation) { String functionName = functionInvocation.getFunctionName(); if ("elementId".equals(functionName)) { this.builder.append(")"); } this.builder.append(")"); } void enter(Operation operation) { if (operation.needsGrouping()) { this.builder.append("("); } } void enter(Operator operator) { Operator.Type type = operator.getType(); if (type == Operator.Type.LABEL) { return; } boolean skipSpaces = SKIP_SPACES.contains(operator); if (type != Operator.Type.PREFIX && !skipSpaces) { this.builder.append(" "); } this.builder.append(operator.getRepresentation()); if (type != Operator.Type.POSTFIX && !skipSpaces) { this.builder.append(" "); } } void leave(Operation operation) { if (operation.needsGrouping()) { this.builder.append(")"); } } void enter(Literal expression) { this.builder.append(expression.asString()); } void enter(Node node) { this.builder.append("("); // This is only relevant for nodes in relationships. // Otherwise, all the labels would be rendered again. this.skipNodeContent = this.scopingStrategy.hasVisitedBefore(node); if (this.skipNodeContent) { this.builder.append(this.nameResolvingStrategy .resolve(node.getSymbolicName().orElseGet(node::getRequiredSymbolicName), true, false)); } this.inEntity = true; } void leave(Node node) { this.builder.append(")"); this.skipNodeContent = false; this.inEntity = false; } void enter(NodeLabel nodeLabel) { escapeName(nodeLabel.getValue()).ifPresent(label -> { var inLabelExpression = this.inLabelExpression.peek(); if (inLabelExpression == null || !inLabelExpression) { this.builder.append(Symbols.NODE_LABEL_START); } this.builder.append(label); }); } void enter(Labels labels) { this.inLabelExpression.push(true); if (!labels.isEmpty()) { this.builder.append(":"); } renderLabelExpression(labels, null); } @SuppressWarnings("squid:S3776") void renderLabelExpression(Labels l, Labels.Type parent) { if (l == null) { return; } if (l.isNegated()) { this.builder.append("!"); } var current = l.getType(); boolean close = false; if (current != Labels.Type.LEAF) { close = (parent != null || l.isNegated()) && l.getType() != parent; if (close && !l.isNegated() && (current == Labels.Type.CONJUNCTION || parent == Labels.Type.DISJUNCTION)) { close = false; } } if (close) { this.builder.append("("); } renderLabelExpression(l.getLhs(), current); if (current == Labels.Type.LEAF) { l.getValue().forEach(v -> v.accept(this)); } else if (EnumSet.of(Labels.Type.COLON_CONJUNCTION, Labels.Type.COLON_DISJUNCTION).contains(current) && !l.getValue().isEmpty()) { l.getValue().get(0).accept(this); for (var value : l.getValue().subList(1, l.getValue().size())) { this.builder.append(current.getValue()); value.accept(this); } } else if (!l.isEmpty()) { this.builder.append(current.getValue()); } renderLabelExpression(l.getRhs(), current); if (close) { this.builder.append(")"); } } void enter(Labels.Value value) { var modifier = switch (value.modifier()) { case ALL -> "$("; case ANY -> "$any("; case STATIC -> ""; }; this.builder.append(modifier); } void leave(Labels.Value value) { if (value.modifier() != Labels.Modifier.STATIC) { this.builder.append(")"); } } void leave(Labels labels) { this.inLabelExpression.pop(); } void enter(Properties properties) { this.builder.append(" "); } void enter(SymbolicName symbolicName) { if (!this.inRelationshipCondition || this.nameResolvingStrategy.isResolved(symbolicName)) { if (Boolean.TRUE.equals(this.inPatternExpression.peek()) && !this.scopingStrategy.hasVisitedBefore(() -> Optional.of(symbolicName))) { return; } this.builder.append(this.nameResolvingStrategy.resolve(symbolicName, this.inEntity, this.inPropertyLookup)); } } void enter(PatternExpression p) { this.inPatternExpression.push(true); } void leave(PatternExpression p) { this.inPatternExpression.pop(); } void enter(Relationship relationship) { this.skipRelationshipContent = this.scopingStrategy.hasVisitedBefore(relationship); if (this.enforceSchema && relationship.getDetails().getDirection() != Relationship.Direction.UNI) { this.directionOverride = computeDirectionOverride(relationship); } } /** * Helper to retrieve a nodes label. * @param node the node * @return a set of labels */ private java.util.Set getLabels(Node node) { var nl = node.getLabels(); if (nl.isEmpty()) { var patternElement = this.scopingStrategy.lookup(node); if (patternElement instanceof Node boundNode) { nl = boundNode.getLabels(); } } return nl.stream().map(NodeLabel::getValue).collect(Collectors.toSet()); } /** * Computes an overwrite for enforcing a schema. * @param relationship the relationship to potentially override * @return a new direction */ Relationship.Direction computeDirectionOverride(Relationship relationship) { var sourceLabels = getLabels(relationship.getLeft()); var targetLabels = getLabels(relationship.getRight()); var details = relationship.getDetails(); // Bail out on equal labels if (sourceLabels.equals(targetLabels)) { return details.getDirection(); } for (String type : details.getTypes()) { outer: if (this.relationshipDefinitions.containsKey(type)) { var knownRelationships = this.relationshipDefinitions.get(type).stream().toList(); for (var knownRelationship : knownRelationships) { if (knownRelationship.selfReferential() && (sourceLabels.isEmpty() || targetLabels.isEmpty())) { break outer; } if (sourceLabels.contains(knownRelationship.targetLabel()) && (targetLabels.isEmpty() || targetLabels.contains(knownRelationship.sourceLabel())) || targetLabels.contains(knownRelationship.sourceLabel()) && (sourceLabels.isEmpty() || sourceLabels.contains(knownRelationship.sourceLabel()))) { return Relationship.Direction.RTL; } else if (sourceLabels.contains(knownRelationship.sourceLabel()) && (targetLabels.isEmpty() || targetLabels.contains(knownRelationship.targetLabel())) || targetLabels.contains(knownRelationship.targetLabel()) && (sourceLabels.isEmpty() || sourceLabels.contains(knownRelationship.sourceLabel()))) { return Relationship.Direction.LTR; } } } if (!sourceLabels.isEmpty() && !targetLabels.isEmpty()) { throw new SchemaEnforcementFailedException(); } } if (details.getTypes().isEmpty()) { var knownRelationships = this.relationshipDefinitions.values().stream().flatMap(List::stream).toList(); for (var knownRelationship : knownRelationships) { if (sourceLabels.contains(knownRelationship.targetLabel()) && targetLabels.contains(knownRelationship.sourceLabel())) { return Relationship.Direction.RTL; } else if (sourceLabels.contains(knownRelationship.sourceLabel()) && targetLabels.contains(knownRelationship.targetLabel())) { return Relationship.Direction.LTR; } } } return details.getDirection(); } void enter(Relationship.Details details) { Relationship.Direction direction = Optional.ofNullable(this.directionOverride).orElseGet(details::getDirection); this.builder.append(direction.getSymbolLeft()); if (details.hasContent()) { this.builder.append("["); } this.inEntity = true; } void enter(RelationshipTypes types) { if (this.skipRelationshipContent) { return; } this.builder.append(types.getValues() .stream() .map(this::escapeName) .map(Optional::orElseThrow) .collect(Collectors.joining(Symbols.REL_TYP_SEPARATOR, Symbols.REL_TYPE_START, ""))); } void enter(RelationshipLength length) { if (this.skipRelationshipContent) { return; } Integer minimum = length.getMinimum(); Integer maximum = length.getMaximum(); if (length.isUnbounded()) { this.builder.append("*"); return; } if (minimum == null && maximum == null) { return; } this.builder.append("*"); if (minimum != null) { this.builder.append(minimum); } this.builder.append(".."); if (maximum != null) { this.builder.append(maximum); } } void leave(Relationship.Details details) { Relationship.Direction direction = Optional.ofNullable(this.directionOverride).orElseGet(details::getDirection); if (details.hasContent()) { this.builder.append("]"); } this.builder.append(direction.getSymbolRight()); this.inEntity = false; } void leave(Relationship relationship) { this.skipRelationshipContent = false; this.directionOverride = null; } void enter(Parameter parameter) { Object value = parameter.getValue(); if (value instanceof ConstantParameterHolder constantParameterHolder && !this.renderConstantsAsParameters) { this.builder.append(constantParameterHolder.asString()); } else { this.builder.append("$").append(this.nameResolvingStrategy.resolve(parameter)); } } void enter(MapExpression map) { this.builder.append("{"); } void enter(KeyValueMapEntry map) { this.builder.append(escapeIfNecessary(map.getKey())).append(": "); } void leave(MapExpression map) { this.builder.append("}"); } void enter(ListExpression list) { this.builder.append("["); } void leave(ListExpression list) { this.builder.append("]"); } void enter(Unwind unwind) { this.builder.append("UNWIND "); } void leave(Unwind unwind) { this.builder.append(" "); } void enter(UnionPart unionPart) { this.builder.append(" UNION "); if (unionPart.isAll()) { this.builder.append("ALL "); } } void enter(Set set) { this.builder.append("SET "); } void leave(Set set) { this.builder.append(" "); } void enter(Remove remove) { this.builder.append("REMOVE "); } void leave(Remove remove) { this.builder.append(" "); } void enter(PatternComprehension patternComprehension) { this.builder.append("["); } void leave(PatternComprehension patternComprehension) { this.builder.append("]"); } void enter(ListComprehension listComprehension) { this.builder.append("["); } void leave(ListComprehension listComprehension) { this.builder.append("]"); } void enter(Case genericCase) { this.builder.append("CASE"); } void enter(Case.SimpleCase simpleCase) { this.builder.append("CASE "); } void enter(CaseWhenThen caseWhenExpression) { this.builder.append(" WHEN "); } void leave(CaseWhenThen caseWhenExpression) { this.builder.append(" THEN "); } void enter(CaseElse caseElseExpression) { this.builder.append(" ELSE "); } void leave(Case caseExpression) { this.builder.append(" END"); } void enter(ProcedureCall procedureCall) { this.builder.append("CALL "); } void leave(Namespace namespace) { this.builder.append("."); } void leave(ProcedureName procedureName) { this.builder.append(procedureName.getValue()); } void enter(YieldItems yieldItems) { this.builder.append(" YIELD "); } void leave(ProcedureCall procedureCall) { this.builder.append(" "); } void enter(Enum anEnum) { this.builder.append(anEnum.name().replace("_", " ")).append(" "); } void enter(Subquery subquery) { this.inSubquery = true; this.builder.append("CALL {"); } void leave(Subquery subquery) { this.inSubquery = false; int l = this.builder.length() - 1; if (this.builder.charAt(l) == ' ' && !subquery.doesReturnOrYield()) { this.builder.replace(l, this.builder.length(), "} "); } else { this.builder.append("} "); } } void leave(InTransactions inTransactions) { int l = this.builder.length() - 1; if (this.builder.charAt(l) != ' ') { this.builder.append(" "); } this.builder.append("IN TRANSACTIONS "); if (inTransactions.getRows() != null) { this.builder.append("OF ").append(inTransactions.getRows()).append(" ROWS "); } } void enter(Foreach foreach) { this.builder.append("FOREACH ("); } void leave(Foreach foreach) { this.builder.setCharAt(this.builder.length() - 1, ')'); // replace trailing space // with ')' this.builder.append(" "); } void enter(SubqueryExpression subquery) { if (subquery instanceof CountExpression) { this.builder.append("COUNT"); } else if (subquery instanceof ExistentialSubquery) { this.builder.append("EXISTS"); } else if (subquery instanceof CollectExpression) { this.builder.append("COLLECT"); } this.builder.append(" { "); } void leave(SubqueryExpression subquery) { // Trimming the inner match without having to do this in the match (looking up if // inside subquery). if (this.builder.charAt(this.builder.length() - 1) == ' ') { this.builder.replace(this.builder.length() - 1, this.builder.length(), " }"); } else { this.builder.append(" }"); } } void enter(Hint hint) { this.builder.append(" USING "); } void enter(LoadCSV loadCSV) { this.builder.append("LOAD CSV"); if (loadCSV.isWithHeaders()) { this.builder.append(" WITH HEADERS"); } this.builder.append(" FROM '").append(loadCSV.getUri().toString()).append("' AS ").append(loadCSV.getAlias()); if (loadCSV.getFieldTerminator() != null) { this.builder.append(" FIELDTERMINATOR '").append(loadCSV.getFieldTerminator()).append("'"); } this.builder.append(" "); } void enter(UsingPeriodicCommit usingPeriodicCommit) { this.builder.append("USING PERIODIC COMMIT "); if (usingPeriodicCommit.rate() != null) { this.builder.append(usingPeriodicCommit.rate()).append(" "); } } void enter(Use use) { this.builder.append("USE "); if (use.dynamic()) { this.builder.append("graph.byName("); } } void leave(Use use) { if (use.dynamic()) { this.builder.append(")"); } this.builder.append(" "); } void enter(PatternSelector shortest) { if (shortest instanceof PatternSelector.ShortestK shortestK) { this.builder.append("SHORTEST ").append(shortestK.getK()).append(" "); } else if (shortest instanceof PatternSelector.ShortestKGroups shortestK) { this.builder.append("SHORTEST ").append(shortestK.getK()).append(" GROUPS "); } else if (shortest instanceof PatternSelector.AllShortest) { this.builder.append("ALL SHORTEST "); } else if (shortest instanceof PatternSelector.Any) { this.builder.append("ANY "); } } void enter(QuantifiedPathPattern.TargetPattern qpp) { this.builder.append("("); } void leave(QuantifiedPathPattern.TargetPattern qpp) { this.builder.append(")"); } void enter(QuantifiedPathPattern.Quantifier quantifier) { this.builder.append(quantifier.toString()); } @Override public String getRenderedContent() { return this.builder.toString(); } /** * Escapes a symbolic name. Such a symbolic name is either used for a nodes label, the * type of a relationship or a variable. * @param unescapedName the name to escape. * @return an empty optional when the unescaped name is {@literal null}, otherwise the * correctly escaped name, safe to be used in statements. */ protected final Optional escapeName(String unescapedName) { return SchemaNamesBridge.sanitize(unescapedName, this.alwaysEscapeNames); } protected final String escapeIfNecessary(String potentiallyNonIdentifier) { return SchemaNamesBridge.sanitize(potentiallyNonIdentifier, false).orElse(null); } record SeparatorAndSupplier(AtomicReference seperator, Supplier supplier) { } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/renderer/Dialect.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; import java.util.Optional; import java.util.function.Function; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Comparison; import org.neo4j.cypherdsl.core.FunctionInvocation; import org.neo4j.cypherdsl.core.Labels; import org.neo4j.cypherdsl.core.PatternSelector; import org.neo4j.cypherdsl.core.Subquery; import org.neo4j.cypherdsl.core.With; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.STABLE; /** * The dialect to be used when rendering a statement into Cypher. * * @author Michael J. Simons * @since 2022.3.0 */ @API(status = STABLE, since = "2022.3.0") public enum Dialect { /** * Neo4j 4.4 and earlier. */ NEO4J_4 { private final DefaultNeo4j4HandlerSupplier handlerSupplier = new DefaultNeo4j4HandlerSupplier(); @Override Class getHandler(Visitable visitable) { return this.handlerSupplier.apply(visitable).orElseGet(() -> super.getHandler(visitable)); } }, /** * Neo4j 5. */ NEO4J_5 { private final DefaultNeo4j5HandlerSupplier handlerSupplier = new DefaultNeo4j5HandlerSupplier(); @Override Class getHandler(Visitable visitable) { return this.handlerSupplier.apply(visitable).orElseGet(() -> super.getHandler(visitable)); } }, /** * Enhanced Neo4j 5 dialect that renders importing with statements for call subqueries * as variable scoped subqueries, suitable for Neo4j >= 5.23. * * @since 2024.1.0 */ NEO4J_5_23 { private final DefaultNeo4j5HandlerSupplier handlerSupplier = new DefaultNeo4j5HandlerSupplierWithNewImportScopeSubquerySupport(); @Override Class getHandler(Visitable visitable) { return this.handlerSupplier.apply(visitable).orElseGet(() -> super.getHandler(visitable)); } }, /** * A Neo4j 5.26 and higher dialect that always renders the {@code CYPHER 5} prefix. * @since 2024.5.0 * @deprecated Use {@link #NEO4J_5_CYPHER_5} if necessary or * {@link #NEO4J_5_DEFAULT_CYPHER} in all other cases. */ @Deprecated(forRemoval = true) NEO4J_5_26 { private final DefaultNeo4j5HandlerSupplier handlerSupplier = new DefaultNeo4j5HandlerSupplierWithDynamicLabelsSupport(); @Override Class getHandler(Visitable visitable) { return this.handlerSupplier.apply(visitable).orElseGet(() -> super.getHandler(visitable)); } @Override Optional getPrefix() { return Optional.of("CYPHER 5 "); } }, /** * Essentially the same as {@link #NEO4J_5_23} using the databases default Cypher * version. This is the default dialect for Cypher-DSL 2025.0.0 and onwards. * * @since 2025.0.0 */ NEO4J_5_DEFAULT_CYPHER { @Override Class getHandler(Visitable visitable) { return NEO4J_5_26.getHandler(visitable); } }, /** * Essentially the same as {@link #NEO4J_5_23} but also enabling the {@code CYPHER 5} * prefix on generated statements. This dialect works on Neo4j 5.26 and higher. * Genrally, {@link #NEO4J_5_DEFAULT_CYPHER} is preferrable. * * @since 2025.0.0 */ NEO4J_5_CYPHER_5 { @Override Class getHandler(Visitable visitable) { return NEO4J_5_26.getHandler(visitable); } @Override Optional getPrefix() { return Optional.of("CYPHER 5 "); } }, /** * Essentially the same as {@link #NEO4J_5_23} but also enabling the {@code CYPHER 25} * prefix on generated statements. This dialect works on Neo4j 5.26 and higher. * Genrally, {@link #NEO4J_5_23} is preferable. * * @since 2025.0.0 */ NEO4J_5_CYPHER_25 { @Override Class getHandler(Visitable visitable) { return NEO4J_5_26.getHandler(visitable); } @Override Optional getPrefix() { return Optional.of("CYPHER 25 "); } }, /** * A placeholder for Neo4j 2025 calver. Same behaviour as * {@link #NEO4J_5_DEFAULT_CYPHER} in this version of Cypher-DSL. * * @since 2025.0.0 */ NEO4J_2025 { @Override Class getHandler(Visitable visitable) { return NEO4J_5_23.getHandler(visitable); } }; Class getHandler(Visitable visitable) { return null; } Optional getPrefix() { return Optional.empty(); } private static final class DefaultNeo4j4HandlerSupplier implements Function>> { @Override public Optional> apply(Visitable visitable) { if (visitable instanceof PatternSelector) { return Optional.of(PatternSelectorVisitorPreNeo4j521.class); } else if (visitable instanceof Labels) { return Optional.of(Neo4j5Pre26LabelsVisitor.class); } return Optional.empty(); } } private static class DefaultNeo4j5HandlerSupplier implements Function>> { @Override public Optional> apply(Visitable visitable) { if (visitable instanceof FunctionInvocation) { return Optional.of(Neo4j5FunctionInvocationVisitor.class); } else if (visitable instanceof Comparison) { return Optional.of(Neo4j5ComparisonVisitor.class); } else if (visitable instanceof PatternSelector) { return Optional.of(PatternSelectorVisitorPreNeo4j521.class); } else if (visitable instanceof Labels) { return Optional.of(Neo4j5Pre26LabelsVisitor.class); } return Optional.empty(); } } private static class DefaultNeo4j5HandlerSupplierWithNewImportScopeSubquerySupport extends DefaultNeo4j5HandlerSupplier { @Override public Optional> apply(Visitable visitable) { if (visitable instanceof PatternSelector) { return Optional.empty(); } return super.apply(visitable).or(() -> { if (visitable instanceof Subquery || visitable instanceof With) { return Optional.of(Neo4j523SubqueryVisitor.class); } return Optional.empty(); }); } } private static final class DefaultNeo4j5HandlerSupplierWithDynamicLabelsSupport extends DefaultNeo4j5HandlerSupplierWithNewImportScopeSubquerySupport { @Override public Optional> apply(Visitable visitable) { if (visitable instanceof Labels) { return Optional.empty(); } return super.apply(visitable); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/renderer/GeneralizedRenderer.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.ast.Visitable; import static org.apiguardian.api.API.Status.STABLE; /** * This is a more general renderer than {@link Renderer}. The generalized renderer will * render any {@link Visitable}. This renderer will not cache rendered results for * visitables. * * @author Michael J. Simons * @since 2023.1.0 */ @API(status = STABLE, since = "2023.1.0") public sealed interface GeneralizedRenderer extends Renderer permits ConfigurableRenderer { /** * Renders any {@link Visitable}. * @param visitable the visitable to render * @return a Cypher fragment (in case of arbitrary visitables), a full statement in * case a {@link org.neo4j.cypherdsl.core.Statement} was passed to the renderer. */ String render(Visitable visitable); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/renderer/Neo4j523SubqueryVisitor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; import java.util.ArrayDeque; import java.util.Deque; import java.util.Objects; import org.neo4j.cypherdsl.build.annotations.RegisterForReflection; import org.neo4j.cypherdsl.core.ReturnBody; import org.neo4j.cypherdsl.core.Subquery; import org.neo4j.cypherdsl.core.With; import org.neo4j.cypherdsl.core.ast.EnterResult; import org.neo4j.cypherdsl.core.ast.TypedSubtree; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.ast.VisitorWithResult; /** * Rendering variable * scope call clauses. * * @author Michael J. Simons * @since 2024.1.0 */ @RegisterForReflection(allDeclaredConstructors = true) final class Neo4j523SubqueryVisitor extends VisitorWithResult { private static final GeneralizedRenderer RETURN_BODY_RENDERER = Renderer .getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_5_23).build(), GeneralizedRenderer.class); private final DefaultVisitor delegate; private With importing; Neo4j523SubqueryVisitor(DefaultVisitor delegate) { this.delegate = delegate; } @Override public EnterResult enterWithResult(Visitable segment) { if (segment instanceof Subquery subquery) { this.importing = subquery.importingWith(); this.delegate.enter(subquery); this.delegate.builder.replace(this.delegate.builder.length() - 1, this.delegate.builder.length(), "("); if (this.importing != null) { this.importing.accept(innerSegment -> { if (innerSegment instanceof ReturnBody returnBody) { returnBody.accept(this.delegate); } }); } else if (this.delegate.hasIdentifiables()) { this.delegate.builder.append("*"); } this.delegate.builder.append(") {"); } else if (segment instanceof With possibleImporting) { if (!Objects.equals(this.importing, possibleImporting)) { this.delegate.enter(possibleImporting); } else { possibleImporting.accept(new Visitor() { private final Deque entered = new ArrayDeque<>(); @Override public void enter(Visitable segment) { this.entered.push(Neo4j523SubqueryVisitor.this.delegate.preEnter(segment)); } @Override public void leave(Visitable segment) { if (this.entered.pop()) { Neo4j523SubqueryVisitor.this.delegate.postLeave(segment); var currentLength = Neo4j523SubqueryVisitor.this.delegate.builder.length(); if (segment instanceof TypedSubtree t && Neo4j523SubqueryVisitor.this.delegate.builder .subSequence(currentLength - t.separator().length(), currentLength) .equals(t.separator())) { Neo4j523SubqueryVisitor.this.delegate.builder .setLength(currentLength - t.separator().length()); } } } }); return EnterResult.SKIP_CHILDREN; } } return EnterResult.CONTINUE; } @Override public void leave(Visitable segment) { if (segment instanceof Subquery subquery) { this.delegate.leave(subquery); } else if (segment instanceof With possibleImporting) { if (Objects.equals(this.importing, possibleImporting)) { this.importing = null; } else { this.delegate.leave(possibleImporting); } } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/renderer/Neo4j5ComparisonVisitor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.neo4j.cypherdsl.build.annotations.RegisterForReflection; import org.neo4j.cypherdsl.core.Comparison; import org.neo4j.cypherdsl.core.Operator; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.ast.EnterResult; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.VisitorWithResult; import org.neo4j.cypherdsl.core.renderer.Neo4j5FunctionInvocationVisitor.SingleArgExtractor; /** * Takes care of *

    *
  • Inverting NOT EXISTS (n.prop) into n.prop IS NULL
  • *
* . * * @author Michael J. Simons */ @RegisterForReflection(allDeclaredConstructors = true) final class Neo4j5ComparisonVisitor extends VisitorWithResult { private final DefaultVisitor delegate; Neo4j5ComparisonVisitor(DefaultVisitor delegate) { this.delegate = delegate; } @Override public EnterResult enterWithResult(Visitable segment) { Comparison comparison = (Comparison) segment; AtomicReference capture = new AtomicReference<>(); AtomicInteger level = new AtomicInteger(0); AtomicReference nPropExists = new AtomicReference<>(); comparison.accept(new VisitorWithResult() { @Override public EnterResult enterWithResult(Visitable visitable) { boolean isOneLevelBelow = level.getAndIncrement() == 1; if (isOneLevelBelow) { if (visitable instanceof Operator operator) { capture.compareAndSet(null, operator); } else if (Neo4j5FunctionInvocationVisitor.isNPropExists(visitable)) { nPropExists.compareAndSet(null, visitable); } } return EnterResult.CONTINUE; } @Override public void leave(Visitable segment) { level.decrementAndGet(); } }); if (capture.get() == Operator.NOT && nPropExists.get() != null) { SingleArgExtractor singleArgExtractor = new SingleArgExtractor<>(Property.class); nPropExists.get().accept(singleArgExtractor); singleArgExtractor.singleArg.accept(this.delegate); this.delegate.builder.append(" IS NULL"); return EnterResult.SKIP_CHILDREN; } return EnterResult.CONTINUE; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/renderer/Neo4j5FunctionInvocationVisitor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; import java.util.concurrent.atomic.AtomicReference; import org.neo4j.cypherdsl.build.annotations.RegisterForReflection; import org.neo4j.cypherdsl.core.FunctionInvocation; import org.neo4j.cypherdsl.core.NestedExpression; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.ast.EnterResult; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.ast.VisitorWithResult; /** * Some logic to rewrite a couple of functions for Neo4j 5. * * @author Michael J. Simons * @since 2022.3.0 */ @RegisterForReflection(allDeclaredConstructors = true) final class Neo4j5FunctionInvocationVisitor implements Visitor { private final DefaultVisitor delegate; Neo4j5FunctionInvocationVisitor(DefaultVisitor delegate) { this.delegate = delegate; } static boolean isNPropExists(Visitable visitable) { if (visitable instanceof NestedExpression) { AtomicReference capture = new AtomicReference<>(); visitable.accept(new VisitorWithResult() { @Override public EnterResult enterWithResult(Visitable segment) { if (segment instanceof NestedExpression) { // The same object return EnterResult.CONTINUE; } return capture.compareAndSet(null, segment) ? EnterResult.SKIP_CHILDREN : EnterResult.CONTINUE; } }); visitable = capture.get(); } if (!(visitable instanceof FunctionInvocation functionInvocation)) { return false; } if ("exists".equals(functionInvocation.getFunctionName())) { SingleArgExtractor singleArgExtractor = new SingleArgExtractor<>(Property.class); functionInvocation.accept(singleArgExtractor); return singleArgExtractor.singleArg != null; } return false; } @Override public void enter(Visitable visitable) { FunctionInvocation functionInvocation = (FunctionInvocation) visitable; if ("distance".equals(functionInvocation.getFunctionName())) { this.delegate.builder.append("point.distance("); } else if ("elementId".equals(functionInvocation.getFunctionName())) { this.delegate.builder.append("elementId("); } else if (!isNPropExists(visitable)) { this.delegate.enter(functionInvocation); } } @Override public void leave(Visitable visitable) { FunctionInvocation functionInvocation = (FunctionInvocation) visitable; if (isNPropExists(visitable)) { this.delegate.builder.append(" IS NOT NULL"); } else if ("elementId".equals(functionInvocation.getFunctionName())) { this.delegate.builder.append(")"); } else { this.delegate.leave(functionInvocation); } } static class SingleArgExtractor extends VisitorWithResult { final Class expectedType; boolean insideArguments; int level = 0; T singleArg; SingleArgExtractor(Class expectedType) { this.expectedType = expectedType; } @SuppressWarnings("unchecked") @Override public EnterResult enterWithResult(Visitable segment) { if (segment instanceof NestedExpression) { return EnterResult.CONTINUE; } if (++this.level == 2) { this.insideArguments = true; } if (this.insideArguments && this.level == 3 && this.expectedType.isInstance(segment)) { this.singleArg = (this.singleArg != null) ? null : (T) segment; } return EnterResult.CONTINUE; } @Override public void leave(Visitable segment) { this.insideArguments = this.level-- != 2; } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/renderer/Neo4j5Pre26LabelsVisitor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; import java.util.EnumSet; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import org.neo4j.cypherdsl.build.annotations.RegisterForReflection; import org.neo4j.cypherdsl.core.Labels; import org.neo4j.cypherdsl.core.ListExpression; import org.neo4j.cypherdsl.core.ListLiteral; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Parameter; import org.neo4j.cypherdsl.core.StringLiteral; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.internal.SchemaNamesBridge; /** * Express colon conjunctions containing string resolvable expressions as static labels * prior to Neo4j 5.26. * * @author Michael J. Simons * @since 2025.0.2 */ @RegisterForReflection(allDeclaredConstructors = true) final class Neo4j5Pre26LabelsVisitor implements Visitor { private final DefaultVisitor delegate; private final AtomicBoolean didDelegate = new AtomicBoolean(false); Neo4j5Pre26LabelsVisitor(DefaultVisitor delegate) { this.delegate = delegate; } void render(Object visitable) { if (visitable instanceof Parameter parameter) { render(parameter.getValue()); } else if (visitable instanceof ListExpression listExpression) { listExpression.accept(segment -> { if (segment instanceof ListExpression) { return; } render(segment); }); } else if (visitable instanceof ListLiteral listLiteral) { for (var literal : listLiteral.getContent()) { render(literal); } } else if (visitable instanceof StringLiteral stringLiteral) { renderAsLabel(stringLiteral.getContent().toString()); } else if (visitable instanceof List list) { for (var item : list) { if (item instanceof String string) { renderAsLabel(string); } } } else if (visitable instanceof String string) { renderAsLabel(string); } else if (visitable instanceof NodeLabel label) { renderAsLabel(label.getValue()); } else if (!"org.neo4j.cypherdsl.core.ExpressionList".equals(visitable.getClass().getName())) { throw new IllegalArgumentException("Cannot render the given Labels in a Cypher pre 5.26 compatible way"); } } private void renderAsLabel(String string) { this.delegate.builder.append(":") .append(SchemaNamesBridge.sanitize(string, this.delegate.alwaysEscapeNames).orElseThrow()); } @Override public void enter(Visitable segment) { if (!(segment instanceof Labels labels)) { return; } if (labels.getLhs() == null && labels.getRhs() == null && EnumSet.of(Labels.Type.LEAF, Labels.Type.COLON_CONJUNCTION).contains(labels.getType())) { for (var value : labels.getValue()) { render(value.visitable()); } } else { this.didDelegate.compareAndSet(false, true); this.delegate.enter(labels); } } @Override public void leave(Visitable segment) { if (this.didDelegate.compareAndSet(true, false)) { // Cast needs to be in here to make the visitor pick up the correct target this.delegate.leave((Labels) segment); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/renderer/PatternSelectorVisitorPreNeo4j521.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; import java.util.ArrayDeque; import java.util.Deque; import org.neo4j.cypherdsl.core.PatternSelector; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; final class PatternSelectorVisitorPreNeo4j521 implements Visitor { private final DefaultVisitor delegate; private final Deque handled = new ArrayDeque<>(); PatternSelectorVisitorPreNeo4j521(DefaultVisitor delegate) { this.delegate = delegate; } @Override public void enter(Visitable segment) { if (segment instanceof PatternSelector.ShortestK shortestK && shortestK.getK() == 1) { this.delegate.builder.append("shortestPath("); this.handled.push(true); } else if (segment instanceof PatternSelector.AllShortest) { this.delegate.builder.append("allShortestPaths("); this.handled.push(true); } else { this.delegate.enter(((PatternSelector) segment)); this.handled.push(false); } } @Override public void leave(Visitable segment) { if (this.handled.pop()) { this.delegate.builder.append(")"); } else { this.delegate.leave(segment); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/renderer/PrettyPrintingVisitor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; import java.util.function.BiConsumer; import org.neo4j.cypherdsl.build.annotations.RegisterForReflection; import org.neo4j.cypherdsl.core.Condition; import org.neo4j.cypherdsl.core.Create; import org.neo4j.cypherdsl.core.KeyValueMapEntry; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Match; import org.neo4j.cypherdsl.core.Merge; import org.neo4j.cypherdsl.core.MergeAction; import org.neo4j.cypherdsl.core.Operator; import org.neo4j.cypherdsl.core.Parameter; import org.neo4j.cypherdsl.core.PropertyLookup; import org.neo4j.cypherdsl.core.Return; import org.neo4j.cypherdsl.core.Set; import org.neo4j.cypherdsl.core.StatementContext; import org.neo4j.cypherdsl.core.Subquery; import org.neo4j.cypherdsl.core.SubqueryExpression; import org.neo4j.cypherdsl.core.Unwind; import org.neo4j.cypherdsl.core.Use; import org.neo4j.cypherdsl.core.Where; import org.neo4j.cypherdsl.core.With; import org.neo4j.cypherdsl.core.ast.ProvidesAffixes; import org.neo4j.cypherdsl.core.internal.ConstantParameterHolder; import org.neo4j.cypherdsl.core.renderer.Configuration.IndentStyle; /** * This visitor extends the default visitor and tries to pretty print a Cypher statement, * i.e. using some indentation and linebreaks. * * @author Andreas Berger * @author Michael J. Simons * @author Christophe Willemsen */ @SuppressWarnings("unused") @RegisterForReflection class PrettyPrintingVisitor extends DefaultVisitor { private final BiConsumer indentionProvider; /** * In contrast to the current level in the {@link DefaultVisitor} that contains the * level of elements in the tree. */ private int indentationLevel; private boolean passedFirstReadingOrUpdatingClause; PrettyPrintingVisitor(StatementContext statementContext, Configuration configuration) { this(statementContext, false, configuration); } PrettyPrintingVisitor(StatementContext statementContext, boolean renderConstantsAsParameters, Configuration configuration) { super(statementContext, renderConstantsAsParameters, configuration); IndentStyle indentStyle = configuration.getIndentStyle(); int indentSize = configuration.getIndentSize(); if (indentStyle == IndentStyle.TAB) { this.indentionProvider = (builder, width) -> builder.append("\t".repeat(Math.max(0, width))); } else { this.indentionProvider = (builder, width) -> builder.append(" ".repeat(Math.max(0, width * indentSize))); } } private void indent(int width) { this.indentionProvider.accept(this.builder, width); } @Override void enter(Where where) { if (this.currentVisitedElements.stream().noneMatch(Return.class::isInstance)) { this.builder.append("\n"); indent(this.indentationLevel); this.builder.append("WHERE "); } else { super.enter(where); } } @Override void enter(Return returning) { trimNewline(); indent(this.indentationLevel); super.enter(returning); } @Override void enter(Unwind unwind) { trimNewline(); indent(this.indentationLevel); super.enter(unwind); } @Override void enter(With with) { if (this.passedFirstReadingOrUpdatingClause) { trimNewline(); indent(this.indentationLevel); } this.passedFirstReadingOrUpdatingClause = true; super.enter(with); } @Override void enter(Set set) { if (this.currentVisitedElements.stream().noneMatch(MergeAction.class::isInstance)) { trimNewline(); indent(this.indentationLevel); } super.enter(set); } @Override void enter(Match match) { if (this.passedFirstReadingOrUpdatingClause) { trimNewline(); indent(this.indentationLevel); } this.passedFirstReadingOrUpdatingClause = true; super.enter(match); } @Override void enter(Create create) { if (this.passedFirstReadingOrUpdatingClause) { trimNewline(); indent(this.indentationLevel); } this.passedFirstReadingOrUpdatingClause = true; super.enter(create); } @Override void enter(PropertyLookup propertyLookup) { if (this.currentVisitedElements.stream().skip(1).limit(1).anyMatch(MapExpression.class::isInstance)) { trimNewline(); indent(this.indentationLevel); } super.enter(propertyLookup); } @Override void enter(KeyValueMapEntry map) { if (this.indentationLevel > 0) { trimNewline(); indent(this.indentationLevel); } super.enter(map); } @Override void enter(Condition condition) { if (condition instanceof ProvidesAffixes) { this.indentationLevel++; } super.enter(condition); } @Override void leave(Condition condition) { if (condition instanceof ProvidesAffixes) { this.indentationLevel--; } super.leave(condition); } @Override void enter(Operator operator) { Operator.Type type = operator.getType(); if (type == Operator.Type.LABEL) { return; } if (type != Operator.Type.PREFIX && operator != Operator.EXPONENTIATION) { this.builder.append(" "); } if (operator == Operator.OR || operator == Operator.AND || operator == Operator.XOR) { trimNewline(); indent(this.indentationLevel); } this.builder.append(operator.getRepresentation()); if (type != Operator.Type.POSTFIX && operator != Operator.EXPONENTIATION) { this.builder.append(" "); } } @Override void enter(MapExpression map) { this.indentationLevel++; int cnt = 0; for (int i = this.builder.length() - 1; i >= 0; --i) { if (Character.isWhitespace(this.builder.charAt(i))) { ++cnt; } else { break; } } this.builder.setLength(this.builder.length() - cnt); this.builder.append(" "); super.enter(map); } @Override void leave(MapExpression map) { this.indentationLevel--; trimNewline(); indent(this.indentationLevel); super.leave(map); } @Override void enter(MergeAction onCreateOrMatchEvent) { trimNewline(); indent(1); super.enter(onCreateOrMatchEvent); } @Override void enter(Merge merge) { if (this.passedFirstReadingOrUpdatingClause) { trimNewline(); indent(this.indentationLevel); } this.passedFirstReadingOrUpdatingClause = true; super.enter(merge); } @Override void enter(SubqueryExpression subquery) { super.enter(subquery); this.indentationLevel++; } @Override void leave(SubqueryExpression subquery) { this.indentationLevel--; trimNewline(); indent(this.indentationLevel); this.builder.append("}"); } @Override void enter(Subquery subquery) { this.passedFirstReadingOrUpdatingClause = true; trimNewline(); indent(this.indentationLevel); this.indentationLevel++; super.enter(subquery); } @Override void enter(Use use) { trimNewline(); indent(this.indentationLevel); super.enter(use); } @Override void leave(Subquery subquery) { this.indentationLevel--; trimNewline(); indent(this.indentationLevel); this.inSubquery = false; this.builder.append("} "); } private void trimNewline() { for (int i = this.builder.length() - 1; i >= 0; i--) { if (this.builder.charAt(i) == ' ') { this.builder.deleteCharAt(i); } else { break; } } this.builder.append("\n"); } @Override void enter(Parameter parameter) { Object value = parameter.getValue(); if (value instanceof ConstantParameterHolder constantParameterHolder) { this.builder.append(constantParameterHolder.asString()); } else { super.enter(parameter); } } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/renderer/Renderer.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Statement; import static org.apiguardian.api.API.Status.STABLE; /** * Instances of this class are supposed to be thread-safe. Please use * {@link Renderer#getDefaultRenderer()} to get hold of an implementation. * * @author Michael J. Simons * @since 1.0 */ @API(status = STABLE, since = "1.0") public sealed interface Renderer permits ConfigurableRenderer, GeneralizedRenderer { /** * Provides the default renderer. This method may or may not provide shared instances * of the renderer. * @return the default renderer. */ static Renderer getDefaultRenderer() { return getRenderer(Configuration.defaultConfig()); } /** * Creates a new renderer for the given configuration. * @param configuration the configuration for this renderer * @return a new renderer (might be a shared instance). */ static Renderer getRenderer(Configuration configuration) { return getRenderer(configuration, Renderer.class); } /** * Creates a new renderer for the given configuration. * @param the specific type of the renderer * @param configuration the configuration for this renderer * @param type reification of the renderers type * @return a new renderer (might be a shared instance). * @since 2023.1.0 */ static T getRenderer(Configuration configuration, Class type) { return type.cast(ConfigurableRenderer.create(configuration)); } /** * Renders a statement. * @param statement the statement to render * @return the rendered Cypher statement. */ String render(Statement statement); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/renderer/RenderingVisitor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; import org.neo4j.cypherdsl.core.ast.Visitor; /** * Internal interface for various implementations of {@link Visitor visitors} that provide * a rendered view of the visitable they have been used with. * * @author Michael J. Simons */ interface RenderingVisitor extends Visitor { /** * {@return renderer content after this visitor has been accepted by a Visitable} */ String getRenderedContent(); } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/renderer/SchemaEnforcementFailedException.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; import java.io.Serial; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.INTERNAL; /** * Thrown when schema validation fails. * * @author Michael J. Simons * @since 2023.7.0 */ @API(status = INTERNAL, since = "2023.7.0") public final class SchemaEnforcementFailedException extends RuntimeException { @Serial private static final long serialVersionUID = 5473466301617398078L; /** * Creates a new instance, not to be used by client code. */ public SchemaEnforcementFailedException() { // Make the Java module system happy. } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/renderer/Symbols.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; /** * Symbols used during Cypher rendering. * * @author Michael J. Simons * @since 1.0 */ final class Symbols { static final String NODE_LABEL_START = ":"; static final String REL_TYPE_START = ":"; static final String REL_TYP_SEPARATOR = "|"; private Symbols() { } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/renderer/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * A renderer for {@link org.neo4j.cypherdsl.core.Statement statements} in the simplest * form possible. */ package org.neo4j.cypherdsl.core.renderer; ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/utils/Assertions.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.utils; import java.util.Arrays; import java.util.Objects; import org.apiguardian.api.API; /** * Assertions used throughout the Cypher-DSL. Mostly copied over from * {@literal org.springframework.util.Assert}. Thanks to the original authors: Keith * Donald, Juergen Hoeller, Sam Brannen, Colin Sampaleanu and Rob Harrop. Not supported * for external use in any way. * * @author Michael J. Simons * @since 2020.0.0 */ @API(status = API.Status.INTERNAL, since = "2020.0.0") public final class Assertions { private Assertions() { } /** * Assert that the given String contains valid text content; that is, it must not be * {@code null} and must contain at least one non-whitespace character. *
Assert.hasText(name, "'name' must not be empty");
* @param text the String to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the text does not contain valid text content */ public static void hasText(String text, String message) { if (!Strings.hasText(text)) { throw new IllegalArgumentException(message); } } /** * Assert a boolean expression, throwing an {@code IllegalArgumentException} if the * expression evaluates to {@code false}.
Assert.isTrue(i > 0, "The value must be greater than zero");
* @param expression a boolean expression * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if {@code expression} is {@code false} */ public static void isTrue(boolean expression, String message) { if (!expression) { throw new IllegalArgumentException(message); } } /** * Assert that an object is not {@code null}. *
Assert.notNull(clazz, "The class must not be null");
* @param object the object to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the object is {@code null} */ public static void notNull(Object object, String message) { if (object == null) { throw new IllegalArgumentException(message); } } /** * Assert that the provided object is an instance of the provided class. *
Assert.instanceOf(Foo.class, foo, "Foo expected");
* @param type the type to check against * @param obj the object to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the object is not an instance of type */ public static void isInstanceOf(Class type, Object obj, String message) { notNull(type, "Type to check against must not be null"); if (!type.isInstance(obj)) { throw new IllegalArgumentException(message); } } /** * Assert that an array contains elements; that is, it must not be {@code null} and * must contain at least one element. *
Assert.notEmpty(array, "The array must contain elements");
* @param array the array to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the object array is {@code null} or contains no * elements */ public static void notEmpty(Object[] array, String message) { if (isEmpty(array)) { throw new IllegalArgumentException(message); } } private static boolean isEmpty(Object[] array) { return array == null || array.length == 0 || Arrays.stream(array).allMatch(Objects::isNull); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/utils/LRUCache.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.utils; import java.io.Serial; import java.util.LinkedHashMap; import java.util.Map; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.INTERNAL; /** * A non thread safe least recently used (LRU) cache. It must be used with a locking * implementation around it. * * @param type of the keys * @param type of the values * @author Michael J. Simons * @since 2021.0.2 */ @API(status = INTERNAL, since = "2021.0.2") public final class LRUCache extends LinkedHashMap { @Serial private static final long serialVersionUID = -6819899594092598277L; /** * Cache size. When current size reaches that values, the eldest entries will be * removed. */ private final int cacheSize; /** * Creates a new LRU cache with the given cache size. * @param cacheSize the number of entries to keep around. */ public LRUCache(int cacheSize) { super(cacheSize / 4, 0.75f, true); this.cacheSize = cacheSize; } @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() >= this.cacheSize; } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/utils/Strings.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.utils; import java.util.concurrent.ThreadLocalRandom; import org.apiguardian.api.API; /** * The usual, static class with helper methods centered around missing functionality in * {@link String}. Not supported for external use in any way. * * @author Michael J. Simons * @since 2020.1.0 */ @API(status = API.Status.INTERNAL, since = "2020.1.0") public final class Strings { private Strings() { } /** * Returns true, if the string is neither null nor empty nor blank. * @param str a string to be checked for text. * @return true, if the string is neither null nor empty nor blank */ public static boolean hasText(String str) { return str != null && !str.isBlank(); } /** * Returns a random identifier that is a valid identifier. * @param length the length of the identifier to generate. * @return a random identifier that is a valid identifier */ public static String randomIdentifier(int length) { int leftLimit = 65; // letter 'A' int rightLimit = 122; // letter 'z' // I really need only random string values, this is fine @SuppressWarnings("squid:S2245") ThreadLocalRandom random = ThreadLocalRandom.current(); return random.ints(leftLimit, rightLimit + 1) .filter(Character::isLetter) .limit(length) .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) .toString(); } } ================================================ FILE: neo4j-cypher-dsl/src/main/java/org/neo4j/cypherdsl/core/utils/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * Set of {@code *Utils} classes for internal usage. */ package org.neo4j.cypherdsl.core.utils; ================================================ FILE: neo4j-cypher-dsl/src/main/resources/META-INF/native-image/org.neo4j/neo4j-cypher-dsl/native-image.properties ================================================ # # Copyright (c) 2019-2026 "Neo4j," # Neo4j Sweden AB [https://neo4j.com] # # This file is part of Neo4j. # # 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. # Args = -H:ReflectionConfigurationResources=${.}/reflection-config.json \ -H:ResourceConfigurationResources=${.}/resources-config.json ================================================ FILE: neo4j-cypher-dsl/src/main/resources/META-INF/native-image/org.neo4j/neo4j-cypher-dsl/resources-config.json ================================================ { "bundles": [ { "name": "org.neo4j.cypherdsl.core.messages" } ] } ================================================ FILE: neo4j-cypher-dsl/src/main/resources/org/neo4j/cypherdsl/core/messages.properties ================================================ # # Copyright (c) 2019-2026 "Neo4j," # Neo4j Sweden AB [https://neo4j.com] # # This file is part of Neo4j. # # 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. # assertions.expression-required = The expression is required. assertions.expressions-required = Expressions to return are required. assertions.at-least-one-expression-required = At least one expressions to return is required. assertions.node-required = The node is required. assertions.relationship-required = The relationship is required. assertions.variable-required = The variable is required. assertions.components-required = The components are required. assertions.temporal-value-required = The temporalValue is required. assertions.year-required = The year is required. assertions.month-required = The month is required. assertions.day-required = The day is required. assertions.tz-required = The timezone is required. assertions.range-target-required = The range's target expression must not be null. assertions.range-index-required = The index of the range must not be null. assertions.range-start-required = The start of the range must not be null. assertions.range-end-required = The end of the range must not be null. assertions.expression-for-function-required = The expression for {0}() is required. assertions.pattern-for-function-required = The pattern for {0}() is required. assertions.at-least-one-arg-required = {0}() requires at least one argument. assertions.correct-usage-of-distinct = The distinct operator can only be applied within aggregate functions. assertions.requires-name-for-mutation = A property container must be named to be mutated. assertions.named-path-required = The path needs to be named! ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/BooleanLiteralTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class BooleanLiteralTests { @ParameterizedTest @CsvSource(nullValues = "n/a", value = { "n/a,false", "false,false", "true,true" }) void valueOfShouldWork(Boolean input, boolean expected) { Literal literal = BooleanLiteral.of(input); assertThat(literal).isInstanceOf(BooleanLiteral.class); BooleanLiteral booleanLiteral = (BooleanLiteral) literal; if (expected) { assertThat(booleanLiteral).isEqualTo(BooleanLiteral.TRUE); } else { assertThat(booleanLiteral).isEqualTo(BooleanLiteral.FALSE); } } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/BuiltInFunctionsTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Locale; import org.junit.jupiter.api.Nested; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons * */ class BuiltInFunctionsTests { @Nested class MathematicalFunctionsTest { /** * Sanity check that we didn't miss up the names. For the time being, the * constants are equal to the actual implementation names. */ @ParameterizedTest @EnumSource(BuiltInFunctions.MathematicalFunctions.class) void functionNameSanityCheck(BuiltInFunctions.MathematicalFunctions function) { assertThat(function.name().toLowerCase(Locale.ROOT)).isEqualTo(function.getImplementationName()); } } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/ClausesTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Collections; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * @author Michael J. Simons */ class ClausesTests { @Test void updatingClausesShouldBeCheckInForeach() { Clause anonymousClause = new Clause() { @Override public void accept(Visitor visitor) { Clause.super.accept(visitor); } }; assertThatIllegalArgumentException() .isThrownBy(() -> Clauses.forEach(SymbolicName.of("x"), Cypher.literalNull(), Collections.singletonList(anonymousClause))) .withMessage( "Only updating clauses SET, REMOVE, CREATE, MERGE, DELETE, and FOREACH are allowed as clauses applied inside FOREACH."); } @Test void foreachShouldWork() { Clause foreach = Clauses.forEach(SymbolicName.of("x"), Cypher.literalNull(), Collections.singletonList(new Set(new ExpressionList()))); assertThat(foreach).isNotNull(); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/ComparisonTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * @author Michael J. Simons * @author Gerrit Meier */ class ComparisonTests { @Test void preconditionsShouldBeAsserted() { Expression expression = Cypher.literalOf("Arbitrary expression"); assertThatIllegalArgumentException().isThrownBy(() -> Comparison.create(null, Operator.EQUALITY, expression)) .withMessage("Left expression must not be null."); assertThatIllegalArgumentException().isThrownBy(() -> Comparison.create(expression, Operator.EQUALITY, null)) .withMessage("Right expression must not be null."); assertThatIllegalArgumentException().isThrownBy(() -> Comparison.create(expression, null, expression)) .withMessage("Operator must not be empty."); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/Cypher5IT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Dialect; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; /** * Tests around GH-1153 */ class Cypher5IT { private final Renderer renderer = Renderer .getRenderer(Configuration.newConfig().alwaysEscapeNames(false).withDialect(Dialect.NEO4J_5_CYPHER_5).build()); @Test void cypher5PrefixOnSimpleStatements() { var n = Cypher.node("Movie").named("n"); var stmt = Cypher.match(n).returning(n).build(); assertThat(this.renderer.render(stmt)).isEqualTo("CYPHER 5 MATCH (n:Movie) RETURN n"); } @Test void unionShouldNotHaveDoublePrefixes() { Statement statement1 = Cypher.match(CypherIT.BIKE_NODE) .where(CypherIT.BIKE_NODE.property("a").isEqualTo(Cypher.literalOf("A"))) .returning(CypherIT.BIKE_NODE) .build(); Statement statement2 = Cypher.match(CypherIT.BIKE_NODE) .where(CypherIT.BIKE_NODE.property("b").isEqualTo(Cypher.literalOf("B"))) .returning(CypherIT.BIKE_NODE) .build(); Statement statement3 = Cypher.match(CypherIT.BIKE_NODE) .where(CypherIT.BIKE_NODE.property("c").isEqualTo(Cypher.literalOf("C"))) .returning(CypherIT.BIKE_NODE) .build(); Statement statement; statement = Cypher.union(statement1, statement2, statement3); assertThat(this.renderer.render(statement)).isEqualTo( "CYPHER 5 MATCH (b:Bike) WHERE b.a = 'A' RETURN b UNION MATCH (b:Bike) WHERE b.b = 'B' RETURN b UNION MATCH (b:Bike) WHERE b.c = 'C' RETURN b"); } @Test void useShouldWork() { var statement = Cypher.match(Cypher.anyNode("n")).returning("n").build(); var cypher = this.renderer.render(Cypher.use("neo4j", statement)); assertThat(cypher).isEqualTo("CYPHER 5 USE neo4j MATCH (n) RETURN n"); } @Test void explainShouldWork() { var statement = Cypher.match(Cypher.anyNode("n")).returning("n").build(); var cypher = this.renderer.render(DecoratedQuery.explain(statement)); assertThat(cypher).isEqualTo("CYPHER 5 EXPLAIN MATCH (n) RETURN n"); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/CypherIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Dialect; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatNoException; /** * @author Michael J. Simons * @author Gerrit Meier * @author Andreas Berger */ class CypherIT { public static final Configuration NEO5J_CONFIG = Configuration.newConfig().withDialect(Dialect.NEO4J_5).build(); static final Node BIKE_NODE = Cypher.node("Bike").named("b"); static final Node USER_NODE = Cypher.node("User").named("u"); private static final Renderer cypherRenderer = Renderer.getDefaultRenderer(); @Test void statementShouldBeRenderable() { Statement statement = Cypher.returning(Cypher.literalTrue().as("t")).build(); String cypher = statement.getCypher(); assertThat(cypher).isEqualTo("RETURN true AS t"); assertThat(statement.getCypher()).isSameAs(cypher); } @Test void parameterLiterals() { Statement statement = Cypher .returning(Cypher.literalOf(Cypher.parameter("asd")).as("t"), Cypher.literalOf(Map.of("another", Cypher.parameter("parameter"))).as("t2")) .build(); String cypher = statement.getCypher(); assertThat(cypher).isEqualTo("RETURN $asd AS t, {another: $parameter} AS t2"); assertThat(statement.getCypher()).isSameAs(cypher); } @Test void shouldGenerateStatementsOfCorrectType() { Statement statement = Cypher.returning(Cypher.literalTrue().as("t")).build(); assertThat(statement).isInstanceOf(ResultStatement.class).isInstanceOf(Statement.SingleQuery.class); statement = Cypher.match(BIKE_NODE, USER_NODE, Cypher.node("U").named("o")) .set(BIKE_NODE.property("x").to(Cypher.literalTrue())) .build(); assertThat(statement).isNotInstanceOf(ResultStatement.class).isInstanceOf(Statement.SingleQuery.class); statement = Cypher.match(BIKE_NODE, USER_NODE, Cypher.node("U").named("o")) .set(BIKE_NODE.property("x").to(Cypher.literalTrue())) .with(BIKE_NODE) .returning(BIKE_NODE) .build(); assertThat(statement).isInstanceOf(ResultStatement.class).isInstanceOf(MultiPartQuery.class); } @Nested class PathMatchingAndAssignment { private final Renderer renderer = Renderer .getRenderer(Configuration.newConfig().alwaysEscapeNames(false).build()); private final PatternElement stationsPattern = Cypher.node("Station") .named("wos") .relationshipBetween(Cypher.node("Station").named("bmv"), "LINK") .quantifyRelationship(QuantifiedPathPattern.plus()); private final AliasedExpression stops = Cypher.listWith(Cypher.name("n")) .in(Cypher.nodes(Cypher.path("p").get())) .returning(Cypher.property("n", "name")) .as("stops"); @Test void plain() { var pattern = Cypher.node("Station").relationshipTo(Cypher.node("Station").named("b"), "LINK").named("l"); var statement = Cypher.match(Cypher.path("p").definedBy(pattern)).returning(Cypher.name("p")).build(); assertThat(statement.getCypher()).isEqualTo("MATCH p = (:`Station`)-[l:`LINK`]->(b:`Station`) RETURN p"); } @Test void deprecatedShortest() { var pattern = Cypher.node("Station").relationshipTo(Cypher.node("Station").named("b"), "LINK").named("l"); var statement = Cypher.match(Cypher.anyShortest().named("p").definedBy(pattern)) .returning(Cypher.name("p")) .build(); assertThat(statement.getCypher()) .isEqualTo("MATCH p = ANY (:`Station`)-[l:`LINK`]->(b:`Station`) RETURN p"); } @ParameterizedTest @ValueSource(ints = { -1, 0, 1, 5 }) void shortestK(int k) { if (k <= 0) { assertThatIllegalArgumentException().isThrownBy(() -> Cypher.shortestK(k)) .withMessage("The path count needs to be greater than 0."); } else { var statement = Cypher.match(Cypher.shortestK(k).named("p").definedBy(this.stationsPattern)) .where(Cypher.property("wos", "name").isEqualTo(Cypher.literalOf("Worcester Shrub Hill"))) .and(Cypher.property("bmv", "name").isEqualTo(Cypher.literalOf("Bromsgrove"))) .returning(Cypher.length(Cypher.path("p").get()).as("result")) .build(); assertThat(this.renderer.render(statement)).isEqualTo( "MATCH p = SHORTEST %d (wos:Station)-[:LINK]-+(bmv:Station) WHERE (wos.name = 'Worcester Shrub Hill' AND bmv.name = 'Bromsgrove') RETURN length(p) AS result", k); } } @ParameterizedTest @ValueSource(ints = { -1, 0, 1, 2 }) void shortestKGroups(int k) { if (k <= 0) { assertThatIllegalArgumentException().isThrownBy(() -> Cypher.shortestK(k)) .withMessage("The path count needs to be greater than 0."); } else { var statement = Cypher.match(Cypher.shortestKGroups(k).named("p").definedBy(this.stationsPattern)) .where(Cypher.property("wos", "name").isEqualTo(Cypher.literalOf("Worcester Shrub Hill"))) .and(Cypher.property("bmv", "name").isEqualTo(Cypher.literalOf("Bromsgrove"))) .returning(this.stops, Cypher.length(Cypher.path("p").get()).as("pathLength")) .build(); assertThat(this.renderer.render(statement)).isEqualTo( "MATCH p = SHORTEST %d GROUPS (wos:Station)-[:LINK]-+(bmv:Station) WHERE (wos.name = 'Worcester Shrub Hill' AND bmv.name = 'Bromsgrove') RETURN [n IN nodes(p) | n.name] AS stops, length(p) AS pathLength", k); } } @Test void allShortest() { var statement = Cypher.match(Cypher.allShortest().named("p").definedBy(this.stationsPattern)) .where(Cypher.property("wos", "name").isEqualTo(Cypher.literalOf("Worcester Shrub Hill"))) .and(Cypher.property("bmv", "name").isEqualTo(Cypher.literalOf("Bromsgrove"))) .returning(this.stops) .build(); assertThat(this.renderer.render(statement)).isEqualTo( "MATCH p = ALL SHORTEST (wos:Station)-[:LINK]-+(bmv:Station) WHERE (wos.name = 'Worcester Shrub Hill' AND bmv.name = 'Bromsgrove') RETURN [n IN nodes(p) | n.name] AS stops"); } @Test void anyShortest() { var pattern = Cypher.node("Station") .withProperties("name", Cypher.literalOf("Pershore")) .relationshipBetween( Cypher.node("Station").named("b").withProperties("name", Cypher.literalOf("Bromsgrove")), "LINK") .named("l") .quantifyRelationship(QuantifiedPathPattern.plus()) .where(Cypher.property("l", "distance").lt(Cypher.literalOf(10))); var distances = Cypher.listWith(Cypher.name("r")) .in(Cypher.relationships(Cypher.path("path").get())) .returning(Cypher.property("r", "distance")) .as("distances"); var statement = Cypher.match(Cypher.anyShortest().named("path").definedBy(pattern)) .returning(distances) .build(); assertThat(this.renderer.render(statement)).isEqualTo( "MATCH path = ANY (:Station {name: 'Pershore'})-[l:LINK WHERE l.distance < 10]-+(b:Station {name: 'Bromsgrove'}) RETURN [r IN relationships(path) | r.distance] AS distances"); } } @Nested class SingleQuerySinglePart { @Nested class ReadingAndReturn { @Test void unrelatedNodes() { Statement statement = Cypher.match(BIKE_NODE, USER_NODE, Cypher.node("U").named("o")) .returning(BIKE_NODE, USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (b:`Bike`), (u:`User`), (o:`U`) RETURN b, u"); } @Test void asteriskShouldWork() { Statement statement = Cypher.match(BIKE_NODE, USER_NODE, Cypher.node("U").named("o")) .returning(Cypher.asterisk()) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (b:`Bike`), (u:`User`), (o:`U`) RETURN *"); } @Test void aliasedExpressionsInReturn() { Node unnamedNode = Cypher.node("ANode"); Node namedNode = Cypher.node("AnotherNode").named("o"); Statement statement = Cypher.match(unnamedNode, namedNode) .returning(namedNode.as("theOtherNode")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (:`ANode`), (o:`AnotherNode`) RETURN o AS theOtherNode"); } @Test void simpleRelationship() { Statement statement = Cypher.match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS")) .returning(BIKE_NODE, USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`)-[:`OWNS`]->(b:`Bike`) RETURN b, u"); } @Test void simpleRelationshipChainNamed() { for (RelationshipChain chain : new RelationshipChain[] { USER_NODE.relationshipTo(BIKE_NODE, "OWNS") .relationshipTo(Cypher.node("Brand"), "MADE_BY") .named(SymbolicName.of("m")), USER_NODE.relationshipTo(BIKE_NODE, "OWNS") .relationshipTo(Cypher.node("Brand"), "MADE_BY") .named("m") }) { Statement statement = Cypher.match(chain).returning(BIKE_NODE, USER_NODE).build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`)-[:`OWNS`]->(b:`Bike`)-[m:`MADE_BY`]->(:`Brand`) RETURN b, u"); } } @Test // GH-169 void multipleRelationshipTypes() { Statement statement = Cypher.match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS", "RIDES")) .returning(BIKE_NODE, USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`)-[:`OWNS`|`RIDES`]->(b:`Bike`) RETURN b, u"); } @Test // GH-170 void relationshipWithProperties() { Statement statement = Cypher .match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS") .withProperties(Cypher.mapOf("boughtOn", Cypher.literalOf("2019-04-16")))) .returning(BIKE_NODE, USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`)-[:`OWNS` {boughtOn: '2019-04-16'}]->(b:`Bike`) RETURN b, u"); } @Test // GH-168 void relationshipWithMinimumLength() { Statement statement = Cypher.match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").min(3)) .returning(BIKE_NODE, USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`)-[:`OWNS`*3..]->(b:`Bike`) RETURN b, u"); } @Test // GH-168 void relationshipWithMaximumLength() { Statement statement = Cypher.match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").max(5)) .returning(BIKE_NODE, USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`)-[:`OWNS`*..5]->(b:`Bike`) RETURN b, u"); } @Test // GH-168 void unboundedRelationship() { Statement statement = Cypher.match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").unbounded()) .returning(BIKE_NODE, USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`)-[:`OWNS`*]->(b:`Bike`) RETURN b, u"); } @Test // GH-168 void relationshipWithLength() { Statement statement = Cypher.match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").length(3, 5)) .returning(BIKE_NODE, USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`)-[:`OWNS`*3..5]->(b:`Bike`) RETURN b, u"); } @Test // GH-168 void relationshipWithLengthAndProperties() { Statement statement = Cypher .match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS") .length(3, 5) .withProperties(Cypher.mapOf("boughtOn", Cypher.literalOf("2019-04-16")))) .returning(BIKE_NODE, USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`)-[:`OWNS`*3..5 {boughtOn: '2019-04-16'}]->(b:`Bike`) RETURN b, u"); } @Test void simpleRelationshipWithReturn() { Relationship owns = USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o"); Statement statement = Cypher.match(owns).returning(BIKE_NODE, USER_NODE, owns).build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`)-[o:`OWNS`]->(b:`Bike`) RETURN b, u, o"); } @Test void chainedRelations() { Node tripNode = Cypher.node("Trip").named("t"); Statement statement = Cypher .match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS") .named("r1") .relationshipTo(tripNode, "USED_ON") .named("r2")) .where(USER_NODE.property("name").matches(".*aName")) .returning(BIKE_NODE, USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`)-[r1:`OWNS`]->(b:`Bike`)-[r2:`USED_ON`]->(t:`Trip`) WHERE u.name =~ '.*aName' RETURN b, u"); statement = Cypher .match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").relationshipTo(tripNode, "USED_ON").named("r2")) .where(USER_NODE.property("name").matches(".*aName")) .returning(BIKE_NODE, USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`)-[:`OWNS`]->(b:`Bike`)-[r2:`USED_ON`]->(t:`Trip`) WHERE u.name =~ '.*aName' RETURN b, u"); statement = Cypher .match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS") .relationshipTo(tripNode, "USED_ON") .named("r2") .relationshipFrom(USER_NODE, "WAS_ON") .named("x") .relationshipBetween(Cypher.node("SOMETHING")) .named("y")) .where(USER_NODE.property("name").matches(".*aName")) .returning(BIKE_NODE, USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`)-[:`OWNS`]->(b:`Bike`)-[r2:`USED_ON`]->(t:`Trip`)<-[x:`WAS_ON`]-(u)-[y]-(:`SOMETHING`) WHERE u.name =~ '.*aName' RETURN b, u"); } @Test // GH-177 void chainedRelationshipsWithPropertiesAndLength() { Node tripNode = Cypher.node("Trip").named("t"); Statement statement = Cypher .match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS") .relationshipTo(tripNode, "USED_ON") .named("r2") .min(1) .properties(Cypher.mapOf("when", Cypher.literalOf("2019-04-16"))) .relationshipFrom(USER_NODE, "WAS_ON") .named("x") .max(2) .properties("whatever", Cypher.literalOf("2020-04-16")) .relationshipBetween(Cypher.node("SOMETHING")) .named("y") .length(2, 3) .properties(Cypher.mapOf("idk", Cypher.literalOf("2021-04-16")))) .where(USER_NODE.property("name").matches(".*aName")) .returning(BIKE_NODE, USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`)-[:`OWNS`]->(b:`Bike`)-[r2:`USED_ON`*1.. {when: '2019-04-16'}]->(t:`Trip`)<-[x:`WAS_ON`*..2 {whatever: '2020-04-16'}]-(u)-[y*2..3 {idk: '2021-04-16'}]-(:`SOMETHING`) WHERE u.name =~ '.*aName' RETURN b, u"); } @Test // GH-182 void sizeOfRelationship() { Statement statement = Cypher.match(Cypher.anyNode("a")) .where(Cypher.property("a", "name").isEqualTo(Cypher.literalOf("Alice"))) .returning(Cypher.size(Cypher.anyNode("a").relationshipTo(Cypher.anyNode())).as("fof")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (a) WHERE a.name = 'Alice' RETURN size((a)-->()) AS fof"); } @Test // GH-182 void sizeOfRelationshipChain() { Statement statement = Cypher.match(Cypher.anyNode("a")) .where(Cypher.property("a", "name").isEqualTo(Cypher.literalOf("Alice"))) .returning(Cypher .size(Cypher.anyNode("a").relationshipTo(Cypher.anyNode()).relationshipTo(Cypher.anyNode())) .as("fof")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (a) WHERE a.name = 'Alice' RETURN size((a)-->()-->()) AS fof"); } @Test void sortOrderDefault() { Statement statement = Cypher.match(USER_NODE) .returning(USER_NODE) .orderBy(Cypher.sort(USER_NODE.property("name"))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) RETURN u ORDER BY u.name"); } @Test // GH-189 void sortOrderDefaultBasedOnSortItemCollection() { Statement statement = Cypher.match(USER_NODE) .returning(USER_NODE) .orderBy(Collections.singleton(Cypher.sort(USER_NODE.property("name")))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) RETURN u ORDER BY u.name"); } @Test void sortOrderAscending() { Statement statement = Cypher.match(USER_NODE) .returning(USER_NODE) .orderBy(Cypher.sort(USER_NODE.property("name")).ascending()) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) RETURN u ORDER BY u.name ASC"); } @Test void sortOrderDescending() { Statement statement = Cypher.match(USER_NODE) .returning(USER_NODE) .orderBy(Cypher.sort(USER_NODE.property("name")).descending()) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) RETURN u ORDER BY u.name DESC"); } @Test void sortOrderConcatenation() { Statement statement = Cypher.match(USER_NODE) .returning(USER_NODE) .orderBy(Cypher.sort(USER_NODE.property("name")).descending(), Cypher.sort(USER_NODE.property("age")).ascending()) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) RETURN u ORDER BY u.name DESC, u.age ASC"); } @Test void sortOrderDefaultExpression() { Statement statement = Cypher.match(USER_NODE) .returning(USER_NODE) .orderBy(USER_NODE.property("name")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) RETURN u ORDER BY u.name"); } @Test void sortOrderAscendingExpression() { Statement statement = Cypher.match(USER_NODE) .returning(USER_NODE) .orderBy(USER_NODE.property("name").ascending()) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) RETURN u ORDER BY u.name ASC"); } @Test void sortOrderDescendingExpression() { Statement statement = Cypher.match(USER_NODE) .returning(USER_NODE) .orderBy(USER_NODE.property("name").descending()) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) RETURN u ORDER BY u.name DESC"); } @Test void sortOrderConcatenationExpression() { Statement statement = Cypher.match(USER_NODE) .returning(USER_NODE) .orderBy(USER_NODE.property("name")) .descending() .and(USER_NODE.property("age")) .ascending() .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) RETURN u ORDER BY u.name DESC, u.age ASC"); } @Test void skip() { Statement statement = Cypher.match(USER_NODE).returning(USER_NODE).skip(1).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) RETURN u SKIP 1"); } @Test void nullSkip() { Statement statement = Cypher.match(USER_NODE).returning(USER_NODE).skip((Number) null).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) RETURN u"); } @Test void limit() { Statement statement = Cypher.match(USER_NODE).returning(USER_NODE).limit(1).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) RETURN u LIMIT 1"); } @Test // GH-129 void limitWithParams() { Statement statement = Cypher.match(USER_NODE) .returning(USER_NODE) .limit(Cypher.parameter("param")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) RETURN u LIMIT $param"); } @Test void nullLimit() { Statement statement = Cypher.match(USER_NODE).returning(USER_NODE).limit((Number) null).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) RETURN u"); } @Test void skipAndLimit() { Statement statement = Cypher.match(USER_NODE).returning(USER_NODE).skip(1).limit(1).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) RETURN u SKIP 1 LIMIT 1"); } @Test // GH-129 void skipAndLimitWithParams() { Statement statement = Cypher.match(USER_NODE) .returning(USER_NODE) .skip(Cypher.parameter("skip")) .limit(Cypher.parameter("limit")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) RETURN u SKIP $skip LIMIT $limit"); } @Test void nullSkipAndLimit() { Statement statement = Cypher.match(USER_NODE) .returning(USER_NODE) .skip((Number) null) .limit((Number) null) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) RETURN u"); } @Test void distinct() { String expected = "MATCH (u:`User`) RETURN DISTINCT u SKIP 1 LIMIT 1"; Statement statement = Cypher.match(USER_NODE).returningDistinct(USER_NODE).skip(1).limit(1).build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); statement = Cypher.match(USER_NODE).returningDistinct("u").skip(1).limit(1).build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } } } @Nested class Finish { @Test void finishAfterMatch() { String expected = "MATCH (u:`User`) FINISH"; Statement statement = Cypher.match(USER_NODE).finish().build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } @Test void finishAfterMatchWithWhere() { String expected = "MATCH (u:`User`) WHERE u:`User` FINISH"; Statement statement = Cypher.match(USER_NODE).where(USER_NODE.hasLabels("User")).finish().build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } @Test void finishAfterSet() { String expected = "MATCH (u:`User`) SET u.name = 'hans' FINISH"; Statement statement = Cypher.match(USER_NODE) .set(USER_NODE.property("name").to(Cypher.literalOf("hans"))) .finish() .build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } @Test void finishAfterDelete() { String expected = "MATCH (u:`User`) DELETE u FINISH"; Statement statement = Cypher.match(USER_NODE).delete(USER_NODE).finish().build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } @Test void finishAfterCreate() { String expected = "CREATE (u:`User`) FINISH"; Statement statement = Cypher.create(USER_NODE).finish().build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } @Test void finishAfterMerge() { String expected = "MERGE (u:`User`)-[:`KNOWS`]->(u) FINISH"; Statement statement = Cypher.merge(USER_NODE.relationshipTo(USER_NODE, "KNOWS")).finish().build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } } @Nested class ExplainedAndProfiledQueries { @Test // GH-98 void shouldRenderExplain() { Statement statement = Cypher.match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS")) .where(USER_NODE.property("a").isNull()) .with(BIKE_NODE, USER_NODE) .returning(BIKE_NODE) .explain(); assertThat(cypherRenderer.render(statement)) .isEqualTo("EXPLAIN MATCH (u:`User`)-[:`OWNS`]->(b:`Bike`) WHERE u.a IS NULL WITH b, u RETURN b"); } @Test // GH-99 void shouldRenderProfile() { Node tripNode = Cypher.node("Trip").named("tt"); Statement statement = Cypher.match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS")) .where(USER_NODE.property("a").isNull()) .with(BIKE_NODE, USER_NODE) .match(tripNode) .where(tripNode.property("name").isEqualTo(Cypher.literalOf("Festive500"))) .with(BIKE_NODE, USER_NODE, tripNode) .returning(BIKE_NODE, USER_NODE, tripNode) .profile(); assertThat(cypherRenderer.render(statement)).isEqualTo( "PROFILE MATCH (u:`User`)-[:`OWNS`]->(b:`Bike`) WHERE u.a IS NULL WITH b, u MATCH (tt:`Trip`) WHERE tt.name = 'Festive500' WITH b, u, tt RETURN b, u, tt"); } } @Nested class SingleQueryMultiPart { @Test void simpleWith() { Statement statement = Cypher.match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS")) .where(USER_NODE.property("a").isNull()) .with(BIKE_NODE, USER_NODE) .returning(BIKE_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`)-[:`OWNS`]->(b:`Bike`) WHERE u.a IS NULL WITH b, u RETURN b"); } @Test void shouldRenderLeadingWith() { Statement statement = Cypher.with(Cypher.parameter("listOfPropertyMaps").as("p")) .unwind("p") .as("item") .returning("item") .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("WITH $listOfPropertyMaps AS p UNWIND p AS item RETURN item"); } @Test // GH-189 void shouldRenderLeadingWithBasedOnExpression() { Statement statement = Cypher.with(Collections.singleton(Cypher.parameter("listOfPropertyMaps").as("p"))) .unwind("p") .as("item") .returning("item") .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("WITH $listOfPropertyMaps AS p UNWIND p AS item RETURN item"); } @Test // GH-129 void withWithSkip() { Node m = Cypher.node("Movie").named("m"); Statement statement = Cypher.match(m).with(m).skip(1).returning(Cypher.count(m)).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (m:`Movie`) WITH m SKIP 1 RETURN count(m)"); statement = Cypher.match(m).with(m).skip(Cypher.parameter("skip")).returning(Cypher.count(m)).build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (m:`Movie`) WITH m SKIP $skip RETURN count(m)"); } @Test // GH-129 void withWithLimit() { Node m = Cypher.node("Movie").named("m"); Statement statement = Cypher.match(m).with(m).limit(23).returning(Cypher.count(m)).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (m:`Movie`) WITH m LIMIT 23 RETURN count(m)"); statement = Cypher.match(m).with(m).limit(Cypher.parameter("limit")).returning(Cypher.count(m)).build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (m:`Movie`) WITH m LIMIT $limit RETURN count(m)"); } @Test // GH-129 void withWithSkipAndLimit() { Node m = Cypher.node("Movie").named("m"); Statement statement = Cypher.match(m).with(m).skip(1).limit(23).returning(Cypher.count(m)).build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (m:`Movie`) WITH m SKIP 1 LIMIT 23 RETURN count(m)"); statement = Cypher.match(m) .with(m) .skip(Cypher.parameter("skip")) .limit(Cypher.parameter("limit")) .returning(Cypher.count(m)) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (m:`Movie`) WITH m SKIP $skip LIMIT $limit RETURN count(m)"); } @Test // GH-129 void withWithAndNullsForSkipAndLimit() { Node m = Cypher.node("Movie").named("m"); Statement statement = Cypher.match(m) .with(m) .skip((Number) null) .limit((Number) null) .returning(Cypher.count(m)) .build(); String expected = "MATCH (m:`Movie`) WITH m RETURN count(m)"; assertThat(cypherRenderer.render(statement)).isEqualTo(expected); statement = Cypher.match(m) .with(m) .skip((Expression) null) .limit((Expression) null) .returning(Cypher.count(m)) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } @Test void simpleWithChained() { Node tripNode = Cypher.node("Trip").named("t"); Statement statement = Cypher.match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS")) .where(USER_NODE.property("a").isNull()) .with(BIKE_NODE, USER_NODE) .match(tripNode) .where(tripNode.property("name").isEqualTo(Cypher.literalOf("Festive500"))) .with(BIKE_NODE, USER_NODE, tripNode) .returning(BIKE_NODE, USER_NODE, tripNode) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`)-[:`OWNS`]->(b:`Bike`) WHERE u.a IS NULL WITH b, u MATCH (t:`Trip`) WHERE t.name = 'Festive500' WITH b, u, t RETURN b, u, t"); } @Test void deletingSimpleWith() { Statement statement = Cypher.match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS")) .where(USER_NODE.property("a").isNull()) .delete(USER_NODE) .with(BIKE_NODE, USER_NODE) .returning(BIKE_NODE, USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`)-[:`OWNS`]->(b:`Bike`) WHERE u.a IS NULL DELETE u WITH b, u RETURN b, u"); } @Test void deletingSimpleWithReverse() { Statement statement = Cypher.match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS")) .where(USER_NODE.property("a").isNull()) .with(BIKE_NODE, USER_NODE) .delete(USER_NODE) .returning(BIKE_NODE, USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`)-[:`OWNS`]->(b:`Bike`) WHERE u.a IS NULL WITH b, u DELETE u RETURN b, u"); } @Test void mixedClausesWithWith() { Node tripNode = Cypher.node("Trip").named("t"); Statement statement = Cypher.match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS")) .match(tripNode) .delete(tripNode) .with(BIKE_NODE, tripNode) .match(USER_NODE) .with(BIKE_NODE, USER_NODE) .returning(BIKE_NODE, USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`)-[:`OWNS`]->(b:`Bike`) MATCH (t:`Trip`) DELETE t WITH b, t MATCH (u:`User`) WITH b, u RETURN b, u"); } } @Nested class MultipleMatches { @Test void simple() { Statement statement = Cypher.match(BIKE_NODE) .match(USER_NODE, Cypher.node("U").named("o")) .returning(BIKE_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (b:`Bike`) MATCH (u:`User`), (o:`U`) RETURN b"); } @Test // GH-189 void simpleWithPatternCollection() { Statement statement = Cypher.match(Collections.singleton(BIKE_NODE)) .match(USER_NODE, Cypher.node("U").named("o")) .returning(BIKE_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (b:`Bike`) MATCH (u:`User`), (o:`U`) RETURN b"); } @Test // GH-189 void simpleWithPatternCollectionInExposesMatch() { PatternElement[] patternElements = { USER_NODE, Cypher.node("U").named("o") }; Statement statement = Cypher.match(Collections.singleton(BIKE_NODE)) .match(Arrays.asList(patternElements)) .returning(BIKE_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (b:`Bike`) MATCH (u:`User`), (o:`U`) RETURN b"); } @Test void simpleWhere() { Statement statement = Cypher.match(BIKE_NODE) .match(USER_NODE, Cypher.node("U").named("o")) .where(USER_NODE.property("a").isNull()) .returning(BIKE_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (b:`Bike`) MATCH (u:`User`), (o:`U`) WHERE u.a IS NULL RETURN b"); } @Test void multiWhere() { Statement statement = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("a").isNotNull()) .match(USER_NODE, Cypher.node("U").named("o")) .where(USER_NODE.property("a").isNull()) .returning(BIKE_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (b:`Bike`) WHERE b.a IS NOT NULL MATCH (u:`User`), (o:`U`) WHERE u.a IS NULL RETURN b"); } @Test @SuppressWarnings("deprecation") void multiWhereMultiConditions() { Statement statement = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("a").isNotNull()) .and(BIKE_NODE.property("b").isNull()) .match(USER_NODE, Cypher.node("U").named("o")) .where(USER_NODE.property("a").isNull().or(USER_NODE.internalId().isEqualTo(Cypher.literalOf(4711)))) .returning(BIKE_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (b:`Bike`) WHERE (b.a IS NOT NULL AND b.b IS NULL) MATCH (u:`User`), (o:`U`) WHERE (u.a IS NULL OR id(u) = 4711) RETURN b"); } @Test void optional() { Statement statement = Cypher.optionalMatch(BIKE_NODE) .match(USER_NODE, Cypher.node("U").named("o")) .where(USER_NODE.property("a").isNull()) .returning(BIKE_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("OPTIONAL MATCH (b:`Bike`) MATCH (u:`User`), (o:`U`) WHERE u.a IS NULL RETURN b"); } @Test void optionalWithFlag() { Statement statement = Cypher.match(true, BIKE_NODE) .match(USER_NODE, Cypher.node("U").named("o")) .where(USER_NODE.property("a").isNull()) .returning(BIKE_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("OPTIONAL MATCH (b:`Bike`) MATCH (u:`User`), (o:`U`) WHERE u.a IS NULL RETURN b"); } @Test // GH-189 void optionalWithFlagAndPatternCollection() { Statement statement = Cypher.match(true, Collections.singleton(BIKE_NODE)) .match(USER_NODE, Cypher.node("U").named("o")) .where(USER_NODE.property("a").isNull()) .returning(BIKE_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("OPTIONAL MATCH (b:`Bike`) MATCH (u:`User`), (o:`U`) WHERE u.a IS NULL RETURN b"); } @Test // GH-189 void optionalWithPatternCollection() { Statement statement = Cypher.optionalMatch(Collections.singleton(BIKE_NODE)) .match(USER_NODE, Cypher.node("U").named("o")) .where(USER_NODE.property("a").isNull()) .returning(BIKE_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("OPTIONAL MATCH (b:`Bike`) MATCH (u:`User`), (o:`U`) WHERE u.a IS NULL RETURN b"); } @Test @SuppressWarnings({ "ResultOfMethodCallIgnored" }) // That is the purpose of this // test void usingSameWithStepWithoutReassign() { StatementBuilder.OrderableOngoingReadingAndWith firstStep = Cypher.match(BIKE_NODE).with(BIKE_NODE); firstStep.optionalMatch(USER_NODE); firstStep.optionalMatch(Cypher.node("Trip")); Statement statement = firstStep.returning(Cypher.asterisk()).build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (b:`Bike`) WITH b OPTIONAL MATCH (u:`User`) OPTIONAL MATCH (:`Trip`) RETURN *"); } @Test @SuppressWarnings({ "ResultOfMethodCallIgnored" }) // That is the purpose of this // test void usingSameWithStepWithoutReassignThenUpdate() { StatementBuilder.OrderableOngoingReadingAndWith firstStep = Cypher.match(BIKE_NODE).with(BIKE_NODE); firstStep.optionalMatch(USER_NODE); firstStep.optionalMatch(Cypher.node("Trip")); firstStep.delete("u"); Statement statement = firstStep.returning(Cypher.asterisk()).build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (b:`Bike`) WITH b OPTIONAL MATCH (u:`User`) OPTIONAL MATCH (:`Trip`) DELETE u RETURN *"); } @Test void usingSameWithStepWithReassign() { ExposesMatch firstStep = Cypher.match(BIKE_NODE).with(BIKE_NODE); firstStep = firstStep.optionalMatch(USER_NODE); firstStep = firstStep.optionalMatch(Cypher.node("Trip")); Statement statement = ((ExposesReturning) firstStep).returning(Cypher.asterisk()).build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (b:`Bike`) WITH b OPTIONAL MATCH (u:`User`) OPTIONAL MATCH (:`Trip`) RETURN *"); } @Test void queryPartsShouldBeExtractableInQueries() { // THose can be a couple of queries ending in a WITH statement so the // pipeline they present in the full query is also present in Java. Function step1Supplier = previous -> previous .match(Cypher.node("S1").named("n")) .where(Cypher.property("n", "a").isEqualTo(Cypher.literalOf("A"))) .with("n"); Function step2Supplier = previous -> previous .match(Cypher.anyNode("n").relationshipTo(Cypher.node("S2").named("m"), "SOMEHOW_RELATED")) .with("n", "m"); Statement statement = step1Supplier.andThen(step2Supplier) .apply(Statement.builder()) .returning("n", "m") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n:`S1`) WHERE n.a = 'A' WITH n MATCH (n)-[:`SOMEHOW_RELATED`]->(m:`S2`) WITH n, m RETURN n, m"); } @Test void optionalNext() { Statement statement = Cypher.match(BIKE_NODE) .optionalMatch(USER_NODE, Cypher.node("U").named("o")) .where(USER_NODE.property("a").isNull()) .returning(BIKE_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (b:`Bike`) OPTIONAL MATCH (u:`User`), (o:`U`) WHERE u.a IS NULL RETURN b"); } @Test void optionalNextBasedOnPatternElementCollection() { PatternElement[] patternElements = { USER_NODE, Cypher.node("U").named("o") }; Statement statement = Cypher.match(BIKE_NODE) .optionalMatch(Arrays.asList(patternElements)) .where(USER_NODE.property("a").isNull()) .returning(BIKE_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (b:`Bike`) OPTIONAL MATCH (u:`User`), (o:`U`) WHERE u.a IS NULL RETURN b"); } @Test void optionalMatchThenDelete() { Statement statement = Cypher.match(BIKE_NODE) .optionalMatch(USER_NODE, Cypher.node("U").named("o")) .delete(USER_NODE, BIKE_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (b:`Bike`) OPTIONAL MATCH (u:`User`), (o:`U`) DELETE u, b"); } } @Nested @SuppressWarnings("deprecation") class FunctionRendering { @Test void inWhereClause() { Statement statement = Cypher.match(USER_NODE) .where(USER_NODE.internalId().isEqualTo(Cypher.literalOf(1L))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) WHERE id(u) = 1 RETURN u"); } @Test void inReturnClause() { Statement statement = Cypher.match(USER_NODE).returning(Cypher.count(USER_NODE)).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) RETURN count(u)"); } @Test // GH-195 void inReturnClauseBasedOnExpressionCollection() { Statement statement = Cypher.match(USER_NODE) .returning(Collections.singleton(Cypher.count(USER_NODE))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) RETURN count(u)"); } @Test // GH-195 void inDistinctReturnClauseBasedOnExpressionCollection() { Statement statement = Cypher.match(USER_NODE) .returningDistinct(Collections.singleton(Cypher.count(USER_NODE))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) RETURN DISTINCT count(u)"); } @Test void inReturnClauseWithDistinct() { Statement statement = Cypher.match(USER_NODE).returning(Cypher.countDistinct(USER_NODE)).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) RETURN count(DISTINCT u)"); } @Test void aliasedInReturnClause() { Statement statement = Cypher.match(USER_NODE).returning(Cypher.count(USER_NODE).as("cnt")).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) RETURN count(u) AS cnt"); } @Test void shouldSupportMoreThanOneArgument() { Statement statement = Cypher.match(USER_NODE) .returning(Cypher.coalesce(USER_NODE.property("a"), USER_NODE.property("b"), Cypher.literalOf("¯\\_(ツ)_/¯"))) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) RETURN coalesce(u.a, u.b, '¯\\\\_(ツ)_/¯')"); } @Test void literalsShouldDealWithNull() { Statement statement = Cypher.match(USER_NODE) .returning(Cypher.coalesce(Cypher.literalOf(null), USER_NODE.property("field")).as("p")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) RETURN coalesce(NULL, u.field) AS p"); } @Test // GH-257 void functionsBasedOnRelationships() { String expected = "MATCH p = ANY (bacon:`Person` {name: 'Kevin Bacon'})-[*]-(meg:`Person` {name: 'Meg Ryan'}) RETURN p"; Relationship relationship = Cypher.node("Person") .named("bacon") .withProperties("name", Cypher.literalOf("Kevin Bacon")) .relationshipBetween( Cypher.node("Person").named("meg").withProperties("name", Cypher.literalOf("Meg Ryan"))) .unbounded(); Statement statement = Cypher.match(Cypher.anyShortest().named("p").definedBy(relationship)) .returning("p") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); SymbolicName p = Cypher.name("p"); statement = Cypher.match(Cypher.anyShortest().named(p).definedBy(relationship)).returning(p).build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } } @Nested class ComparisonRendering { @Test void equalsWithStringLiteral() { Statement statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name").isEqualTo(Cypher.literalOf("Test"))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) WHERE u.name = 'Test' RETURN u"); } @Test void equalsWithNumberLiteral() { Statement statement = Cypher.match(USER_NODE) .where(USER_NODE.property("age").isEqualTo(Cypher.literalOf(21))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) WHERE u.age = 21 RETURN u"); } } @Nested class Conditions { @Test void conditionsChainingAnd() { Statement statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name") .isEqualTo(Cypher.literalOf("Test")) .and(USER_NODE.property("age").isEqualTo(Cypher.literalOf(21)))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WHERE (u.name = 'Test' AND u.age = 21) RETURN u"); } @Test void conditionsChainingOr() { Statement statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name") .isEqualTo(Cypher.literalOf("Test")) .or(USER_NODE.property("age").isEqualTo(Cypher.literalOf(21)))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WHERE (u.name = 'Test' OR u.age = 21) RETURN u"); } @Test void includesAllShouldWork() { Node node = Cypher.anyNode().named("n"); Statement statement = Cypher.match(node) .where(Cypher.includesAll(node.property("l"), Cypher.literalOf(new String[] { "A", "B" }))) .returning(node) .build(); Statement statement2 = Cypher.match(node) .where(node.property("l").includesAll(Cypher.literalOf(new String[] { "A", "B" }))) .returning(node) .build(); assertThat(statement.getCypher()).isEqualTo("MATCH (n) WHERE all(x IN ['A', 'B'] WHERE x IN n.l) RETURN n"); assertThat(statement2.getCypher()).isEqualTo(statement.getCypher()); } @Test void includesAnyShouldWork() { Node node = Cypher.anyNode().named("n"); Statement statement = Cypher.match(node) .where(Cypher.includesAny(node.property("l"), Cypher.literalOf(new String[] { "A", "B" }))) .returning(node) .build(); Statement statement2 = Cypher.match(node) .where(node.property("l").includesAny(Cypher.literalOf(new String[] { "A", "B" }))) .returning(node) .build(); assertThat(statement.getCypher()).isEqualTo("MATCH (n) WHERE any(x IN ['A', 'B'] WHERE x IN n.l) RETURN n"); assertThat(statement2.getCypher()).isEqualTo(statement.getCypher()); } @Test void nestedConditions() { Statement statement; Condition isTrue = Cypher.isTrue(); Condition isFalse = Cypher.isFalse(); statement = Cypher.match(USER_NODE).where(isTrue.or(isFalse).and(isTrue)).returning(USER_NODE).build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WHERE ((true OR false) AND true) RETURN u"); statement = Cypher.match(USER_NODE) .where(isTrue.or(isFalse).and(isTrue)) .or(isFalse) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WHERE (((true OR false) AND true) OR false) RETURN u"); statement = Cypher.match(USER_NODE) .where(isTrue.or(isFalse).and(isTrue)) .or(isFalse) .and(isFalse) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WHERE ((((true OR false) AND true) OR false) AND false) RETURN u"); statement = Cypher.match(USER_NODE) .where(isTrue.or(isFalse).and(isTrue)) .or(isFalse.and(isTrue)) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WHERE (((true OR false) AND true) OR (false AND true)) RETURN u"); statement = Cypher.match(USER_NODE) .where(isTrue.or(isFalse).and(isTrue)) .or(isFalse.and(isTrue)) .and(isTrue) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`) WHERE ((((true OR false) AND true) OR (false AND true)) AND true) RETURN u"); statement = Cypher.match(USER_NODE) .where(isTrue.or(isFalse).and(isTrue)) .or(isFalse.or(isTrue)) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WHERE (((true OR false) AND true) OR false OR true) RETURN u"); statement = Cypher.match(USER_NODE) .where(isTrue.or(isFalse).and(isTrue).or(isFalse.or(isTrue))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WHERE (((true OR false) AND true) OR false OR true) RETURN u"); statement = Cypher.match(USER_NODE) .where(isTrue.or(isTrue).or(isTrue)) .or(isFalse.or(isFalse).or(isFalse)) .or(isTrue) .or(isTrue) .or(isTrue) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`) WHERE (true OR true OR true OR false OR false OR false OR true OR true OR true) RETURN u"); } @Test void conditionsChainingXor() { Statement statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name") .isEqualTo(Cypher.literalOf("Test")) .xor(USER_NODE.property("age").isEqualTo(Cypher.literalOf(21)))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WHERE (u.name = 'Test' XOR u.age = 21) RETURN u"); } @Test // GH-110 void multipleEmptyConditionsMustCollapse() { var no = Cypher.noCondition(); String expected = "MATCH (u:`User`) RETURN u"; Statement statement; statement = Cypher.match(USER_NODE).where(no.or(no)).and(no.and(no).or(no)).returning(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); statement = Cypher.match(USER_NODE).where(no.or(no)).returning(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); statement = Cypher.match(USER_NODE).where(no.and(no).or(no)).returning(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } @Test // GH-110 void multipleEmptyConditionsMustCollapse2() { var no = Cypher.noCondition(); Supplier t = () -> USER_NODE.property("a").isEqualTo(Cypher.literalTrue()); String expected = "MATCH (u:`User`) WHERE u.a = true RETURN u"; Statement statement; statement = Cypher.match(USER_NODE) .where(no.and(t.get()).or(no)) .and(no.and(no).or(no)) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); statement = Cypher.match(USER_NODE).where(no.or(no).or(t.get())).returning(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); statement = Cypher.match(USER_NODE).where(no.and(t.get()).or(no)).returning(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); statement = Cypher.match(USER_NODE).where(no.or(no)).and(t.get()).returning(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } @Test // GH-110 void multipleEmptyConditionsMustCollapse3() { var no = Cypher.noCondition(); Supplier t = () -> USER_NODE.property("a").isEqualTo(Cypher.literalTrue()); Supplier f = () -> USER_NODE.property("b").isEqualTo(Cypher.literalFalse()); String expected = "MATCH (u:`User`) WHERE (u.a = true AND u.b = false) RETURN u"; Statement statement; statement = Cypher.match(USER_NODE) .where(no.and(t.get()).or(no)) .and(no.and(f.get()).or(no)) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); statement = Cypher.match(USER_NODE).where(no.or(no).or(t.get()).and(f.get())).returning(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); statement = Cypher.match(USER_NODE) .where(no.and(t.get()).or(no)) .and(f.get().or(no)) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } @Test void chainingOnWhere() { Statement statement; Literal test = Cypher.literalOf("Test"); Literal foobar = Cypher.literalOf("foobar"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name").isEqualTo(test)) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) WHERE u.name = 'Test' RETURN u"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name").isEqualTo(test)) .and(USER_NODE.property("name").isEqualTo(test)) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WHERE (u.name = 'Test' AND u.name = 'Test') RETURN u"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name").isEqualTo(test)) .and(USER_NODE.property("name").isEqualTo(test)) .and(USER_NODE.property("name").isEqualTo(test)) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WHERE (u.name = 'Test' AND u.name = 'Test' AND u.name = 'Test') RETURN u"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name").isEqualTo(test)) .or(USER_NODE.property("name").isEqualTo(test)) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WHERE (u.name = 'Test' OR u.name = 'Test') RETURN u"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name").isEqualTo(test)) .or(USER_NODE.property("name").isEqualTo(test)) .or(USER_NODE.property("name").isEqualTo(test)) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WHERE (u.name = 'Test' OR u.name = 'Test' OR u.name = 'Test') RETURN u"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name").isEqualTo(test)) .and(USER_NODE.property("name").isEqualTo(test)) .or(USER_NODE.property("name").isEqualTo(foobar)) .and(USER_NODE.property("name").isEqualTo(test)) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`) WHERE (((u.name = 'Test' AND u.name = 'Test') OR u.name = 'foobar') AND u.name = 'Test') RETURN u"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name").isEqualTo(test)) .or(USER_NODE.property("name").isEqualTo(foobar)) .and(USER_NODE.property("name").isEqualTo(test)) .and(USER_NODE.property("name").isEqualTo(test)) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`) WHERE ((u.name = 'Test' OR u.name = 'foobar') AND u.name = 'Test' AND u.name = 'Test') RETURN u"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name").isEqualTo(test)) .or(USER_NODE.property("name").isEqualTo(foobar)) .and(USER_NODE.property("name").isEqualTo(test)) .or(USER_NODE.property("name").isEqualTo(foobar)) .and(USER_NODE.property("name").isEqualTo(test)) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`) WHERE ((((u.name = 'Test' OR u.name = 'foobar') AND u.name = 'Test') OR u.name = 'foobar') AND u.name = 'Test') RETURN u"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name").isNotNull()) .and(USER_NODE.property("name").isEqualTo(test)) .or(USER_NODE.property("age").isEqualTo(Cypher.literalOf(21))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WHERE ((u.name IS NOT NULL AND u.name = 'Test') OR u.age = 21) RETURN u"); } @Test void chainingOnConditions() { Statement statement; Literal test = Cypher.literalOf("Test"); Literal foobar = Cypher.literalOf("foobar"); Literal bazbar = Cypher.literalOf("bazbar"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name") .isEqualTo(test) .or(USER_NODE.property("name").isEqualTo(foobar)) .or(USER_NODE.property("name").isEqualTo(foobar))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`) WHERE (u.name = 'Test' OR u.name = 'foobar' OR u.name = 'foobar') RETURN u"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name") .isEqualTo(test) .and(USER_NODE.property("name").isEqualTo(bazbar)) .or(USER_NODE.property("name").isEqualTo(foobar)) .or(USER_NODE.property("name").isEqualTo(foobar))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`) WHERE ((u.name = 'Test' AND u.name = 'bazbar') OR u.name = 'foobar' OR u.name = 'foobar') RETURN u"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name").isEqualTo(test)) .and(USER_NODE.property("name").isEqualTo(bazbar).and(USER_NODE.property("name").isEqualTo(foobar))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`) WHERE (u.name = 'Test' AND u.name = 'bazbar' AND u.name = 'foobar') RETURN u"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name") .isEqualTo(test) .and(USER_NODE.property("name").isEqualTo(bazbar)) .or(USER_NODE.property("name").isEqualTo(foobar)) .or(USER_NODE.property("name").isEqualTo(foobar))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`) WHERE ((u.name = 'Test' AND u.name = 'bazbar') OR u.name = 'foobar' OR u.name = 'foobar') RETURN u"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name") .isEqualTo(test) .and(USER_NODE.property("name").isEqualTo(bazbar)) .or(USER_NODE.property("name").isEqualTo(foobar)) .or(USER_NODE.property("name").isEqualTo(foobar)) .and(USER_NODE.property("name").isEqualTo(bazbar))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`) WHERE (((u.name = 'Test' AND u.name = 'bazbar') OR u.name = 'foobar' OR u.name = 'foobar') AND u.name = 'bazbar') RETURN u"); } @Test void chainingCombined() { Statement statement; Literal test = Cypher.literalOf("Test"); Literal foobar = Cypher.literalOf("foobar"); Literal bazbar = Cypher.literalOf("bazbar"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name") .isEqualTo(test) .and(USER_NODE.property("name").isEqualTo(bazbar)) .or(USER_NODE.property("name").isEqualTo(foobar)) .or(USER_NODE.property("name").isEqualTo(foobar))) .and(USER_NODE.property("name") .isEqualTo(bazbar) .and(USER_NODE.property("name").isEqualTo(foobar)) .or(USER_NODE.property("name").isEqualTo(test)) .not()) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`) WHERE (((u.name = 'Test' AND u.name = 'bazbar') OR u.name = 'foobar' OR u.name = 'foobar') AND NOT (((u.name = 'bazbar' AND u.name = 'foobar') OR u.name = 'Test'))) RETURN u"); } @Test void negatedConditions() { Statement statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name").isNotNull().not()) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) WHERE u.name IS NULL RETURN u"); } @Test void noConditionShouldNotBeRendered() { Statement statement; statement = Cypher.match(USER_NODE).where(Cypher.noCondition()).returning(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) RETURN u"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name").isEqualTo(Cypher.literalOf("test"))) .and(Cypher.noCondition()) .or(Cypher.noCondition()) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) WHERE u.name = 'test' RETURN u"); } @Test // GH-137 void groupingBug() { Node node = Cypher.node("Person").named("person"); Statement statement; statement = Cypher.match(node) .where(Cypher.literalOf("A").isTrue().or(Cypher.literalOf("B").isTrue())) .and(Cypher.literalOf("C").isTrue()) .returning(node) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (person:`Person`) WHERE (('A' = true OR 'B' = true) AND 'C' = true) RETURN person"); statement = Cypher.match(node) .where(Cypher.literalOf("A").isTrue().or(Cypher.literalOf("B").isTrue())) .and(Cypher.literalOf("C").isTrue().or(Cypher.literalOf("D").isTrue())) .returning(node) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (person:`Person`) WHERE (('A' = true OR 'B' = true) AND ('C' = true OR 'D' = true)) RETURN person"); } @Test // GH-244 void inPatternComprehensions() { Statement statement; Node a = Cypher.node("Person").withProperties("name", Cypher.literalOf("Keanu Reeves")).named("a"); Node b = Cypher.anyNode("b"); statement = Cypher.match(a) .returning(Cypher.listBasedOn(a.relationshipBetween(b)) .where(b.hasLabels("Movie").and(b.property("released").isNotNull())) .returning(b.property("released")) .as("years")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (a:`Person` {name: 'Keanu Reeves'}) RETURN [(a)--(b) WHERE (b:`Movie` AND b.released IS NOT NULL) | b.released] AS years"); statement = Cypher.match(a) .returning(Cypher.listBasedOn(a.relationshipBetween(b)) .where(b.hasLabels("Movie") .and(b.property("released").isNotNull()) .or(b.property("title").isEqualTo(Cypher.literalOf("The Matrix"))) .or(b.property("title").isEqualTo(Cypher.literalOf("The Matrix 2")))) .returning(b.property("released")) .as("years")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (a:`Person` {name: 'Keanu Reeves'}) RETURN [(a)--(b) WHERE ((b:`Movie` AND b.released IS NOT NULL) OR b.title = 'The Matrix' OR b.title = 'The Matrix 2') | b.released] AS years"); statement = Cypher.match(a) .returning(Cypher.listBasedOn(a.relationshipBetween(b)) .where(b.hasLabels("Movie")) .and(b.property("released").isNotNull()) .or(b.property("title").isEqualTo(Cypher.literalOf("The Matrix"))) .or(b.property("title").isEqualTo(Cypher.literalOf("The Matrix 2"))) .returning(b.property("released")) .as("years")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (a:`Person` {name: 'Keanu Reeves'}) RETURN [(a)--(b) WHERE ((b:`Movie` AND b.released IS NOT NULL) OR b.title = 'The Matrix' OR b.title = 'The Matrix 2') | b.released] AS years"); statement = Cypher.match(a) .returning(Cypher.listBasedOn(a.relationshipBetween(b)) .where(b.hasLabels("Movie")) .returning(b.property("released")) .as("years")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (a:`Person` {name: 'Keanu Reeves'}) RETURN [(a)--(b) WHERE b:`Movie` | b.released] AS years"); } @Nested // GH-206, 3.6.5. Using path patterns in WHERE class PathPatternConditions { @Test void doc3651And() { Node timothy = Cypher.node("Person") .named("timothy") .withProperties("name", Cypher.literalOf("Timothy")); Node other = Cypher.node("Person").named("other"); Statement statement; String expected = "MATCH (timothy:`Person` {name: 'Timothy'}), (other:`Person`) WHERE (other.name IN ['Andy', 'Peter'] AND (timothy)<--(other)) RETURN other.name, other.age"; statement = Cypher.match(timothy, other) .where(other.property("name") .in(Cypher.listOf(Cypher.literalOf("Andy"), Cypher.literalOf("Peter")))) .and(timothy.relationshipFrom(other)) .returning(other.property("name"), other.property("age")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); statement = Cypher.match(timothy, other) .where(other.property("name") .in(Cypher.listOf(Cypher.literalOf("Andy"), Cypher.literalOf("Peter"))) .and(timothy.relationshipFrom(other))) .returning(other.property("name"), other.property("age")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } @Test void doc3651Or() { Node timothy = Cypher.node("Person") .named("timothy") .withProperties("name", Cypher.literalOf("Timothy")); Node other = Cypher.node("Person").named("other"); Statement statement; String expected = "MATCH (timothy:`Person` {name: 'Timothy'}), (other:`Person`) WHERE (other.name IN ['Andy', 'Peter'] OR (timothy)<--(other)) RETURN other.name, other.age"; statement = Cypher.match(timothy, other) .where(other.property("name") .in(Cypher.listOf(Cypher.literalOf("Andy"), Cypher.literalOf("Peter")))) .or(timothy.relationshipFrom(other)) .returning(other.property("name"), other.property("age")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); statement = Cypher.match(timothy, other) .where(other.property("name") .in(Cypher.listOf(Cypher.literalOf("Andy"), Cypher.literalOf("Peter"))) .or(timothy.relationshipFrom(other))) .returning(other.property("name"), other.property("age")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } @Test void doc3651XOr() { Node timothy = Cypher.node("Person") .named("timothy") .withProperties("name", Cypher.literalOf("Timothy")); Node other = Cypher.node("Person").named("other"); Statement statement; String expected = "MATCH (timothy:`Person` {name: 'Timothy'}), (other:`Person`) WHERE (other.name IN ['Andy', 'Peter'] XOR (timothy)<--(other)) RETURN other.name, other.age"; statement = Cypher.match(timothy, other) .where(other.property("name") .in(Cypher.listOf(Cypher.literalOf("Andy"), Cypher.literalOf("Peter"))) .xor(timothy.relationshipFrom(other))) .returning(other.property("name"), other.property("age")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } @Test void doc3652() { Node person = Cypher.node("Person").named("person"); Node peter = Cypher.node("Person").named("peter").withProperties("name", Cypher.literalOf("Peter")); Statement statement; statement = Cypher.match(person, peter) .where(Cypher.not(person.relationshipTo(peter))) .returning(person.property("name"), person.property("age")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (person:`Person`), (peter:`Person` {name: 'Peter'}) WHERE NOT (person)-->(peter) RETURN person.name, person.age"); } @Test void doc3653() { Node person = Cypher.node("Person").named("n"); Statement statement; statement = Cypher.match(person) .where(person.relationshipBetween( Cypher.anyNode().withProperties("name", Cypher.literalOf("Timothy")), "KNOWS")) .returning(person.property("name"), person.property("age")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (n:`Person`) WHERE (n)-[:`KNOWS`]-( {name: 'Timothy'}) RETURN n.name, n.age"); } @Test void gh113() { Node foo = Cypher.node("Foo").named("foo"); Node bar = Cypher.node("Bar").named("bar"); Relationship fooBar = foo.relationshipTo(bar, "FOOBAR").named("rel"); PatternComprehension pc = Cypher.listBasedOn(fooBar) .where(bar.relationshipTo(Cypher.node("ZZZ"), "HAS")) .returning(fooBar, bar); Statement statement = Cypher.match(foo).returning(foo.getRequiredSymbolicName(), pc).build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (foo:`Foo`) RETURN foo, [(foo)-[rel:`FOOBAR`]->(bar:`Bar`) WHERE (bar)-[:`HAS`]->(:`ZZZ`) | [rel, bar]]"); } @Test void doc3654() { Node person = Cypher.node("Person").named("n"); Statement statement; Relationship pathPattern = person.relationshipTo(Cypher.anyNode()).named("r"); statement = Cypher.match(pathPattern) .where(person.property("name").isEqualTo(Cypher.literalOf("Andy"))) .and(Cypher.type(pathPattern).matches("K.*")) .returning(Cypher.type(pathPattern), pathPattern.property("since")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n:`Person`)-[r]->() WHERE (n.name = 'Andy' AND type(r) =~ 'K.*') RETURN type(r), r.since"); } @Test void afterWith() { Node timothy = Cypher.node("Person") .named("timothy") .withProperties("name", Cypher.literalOf("Timothy")); Node other = Cypher.node("Person").named("other"); Statement statement; String expected = "MATCH (timothy:`Person` {name: 'Timothy'}), (other:`Person`) WITH timothy, other WHERE (other.name IN ['Andy', 'Peter'] AND (timothy)<--(other)) RETURN other.name, other.age"; statement = Cypher.match(timothy, other) .with(timothy, other) .where(other.property("name") .in(Cypher.listOf(Cypher.literalOf("Andy"), Cypher.literalOf("Peter")))) .and(timothy.relationshipFrom(other)) .returning(other.property("name"), other.property("age")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); statement = Cypher.match(timothy, other) .with(timothy, other) .where(other.property("name") .in(Cypher.listOf(Cypher.literalOf("Andy"), Cypher.literalOf("Peter"))) .and(timothy.relationshipFrom(other))) .returning(other.property("name"), other.property("age")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } } } @Nested class RemoveClause { @Test void shouldRenderRemoveOnNodes() { Statement statement; statement = Cypher.match(USER_NODE).remove(USER_NODE, "A", "B").returning(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) REMOVE u:`A`:`B` RETURN u"); statement = Cypher.match(USER_NODE) .with(USER_NODE) .set(USER_NODE, "A", "B") .remove(USER_NODE, "C", "D") .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WITH u SET u:`A`:`B` REMOVE u:`C`:`D` RETURN u"); } @Test void shouldRenderRemoveOfProperties() { Statement statement; statement = Cypher.match(USER_NODE) .remove(USER_NODE.property("a"), USER_NODE.property("b")) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) REMOVE u.a, u.b RETURN u"); statement = Cypher.match(USER_NODE) .with(USER_NODE) .remove(USER_NODE.property("a")) .remove(USER_NODE.property("b")) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WITH u REMOVE u.a REMOVE u.b RETURN u"); } } @Nested class MutatingSetClause { @Test void simpleWithParam() { Statement statement = Cypher.match(USER_NODE) .mutate(USER_NODE, Cypher.parameter("newMapsOfHell")) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) SET u += $newMapsOfHell RETURN u"); } @Test void afterMerge() { Statement statement = Cypher.merge(USER_NODE) .onMatch() .mutate(USER_NODE, Cypher.parameter("newMapsOfHell")) .onCreate() .mutate(USER_NODE, Cypher.mapOf("a", Cypher.literalOf("B"))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MERGE (u:`User`) ON MATCH SET u += $newMapsOfHell ON CREATE SET u += {a: 'B'} RETURN u"); } @Test void simpleWithMap() { Statement statement = Cypher.match(USER_NODE) .mutate(USER_NODE, Cypher.mapOf("a", Cypher.literalOf("B"))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) SET u += {a: 'B'} RETURN u"); } @Test void chainedWithParam() { Statement statement = Cypher.match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("r")) .mutate(USER_NODE, Cypher.mapOf("a", Cypher.literalOf("B"))) .mutate(BIKE_NODE, Cypher.parameter("newMapsOfHell")) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`)-[r:`OWNS`]->(b:`Bike`) SET u += {a: 'B'} SET b += $newMapsOfHell RETURN u"); } @Test void chainedWithMap() { Statement statement = Cypher.match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("r")) .mutate(USER_NODE, Cypher.mapOf("a", Cypher.literalOf("B"))) .mutate(BIKE_NODE, Cypher.mapOf("c", Cypher.literalOf("D"))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`)-[r:`OWNS`]->(b:`Bike`) SET u += {a: 'B'} SET b += {c: 'D'} RETURN u"); } @Test void multipleLevelsOfChaining() { Statement statement = Cypher.match(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("r")) .mutate(USER_NODE, Cypher.mapOf("a", Cypher.literalOf("B"))) .mutate(BIKE_NODE, Cypher.mapOf("c", Cypher.literalOf("D"))) .mutate(Cypher.name("r"), Cypher.mapOf("e", Cypher.literalOf("F"))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`)-[r:`OWNS`]->(b:`Bike`) SET u += {a: 'B'} SET b += {c: 'D'} SET r += {e: 'F'} RETURN u"); } @Test void foldingMultipleMutatesIntoOne() { Relationship ownsRelationship = USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("r"); Statement statement = Cypher.match(ownsRelationship) .set(USER_NODE.mutate(Cypher.mapOf("a", Cypher.literalOf("B"))), BIKE_NODE.mutate(Cypher.mapOf("c", Cypher.literalOf("D"))), ownsRelationship.mutate(Cypher.mapOf("e", Cypher.literalOf("F")))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`)-[r:`OWNS`]->(b:`Bike`) SET u += {a: 'B'}, b += {c: 'D'}, r += {e: 'F'} RETURN u"); } @Test void afterMergeFolding() { Relationship ownsRelationship = USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("r"); Statement statement = Cypher.merge(ownsRelationship) .onMatch() .set(USER_NODE.mutate(Cypher.mapOf("a", Cypher.literalOf("B"))), BIKE_NODE.mutate(Cypher.mapOf("c", Cypher.literalOf("D"))), ownsRelationship.mutate(Cypher.mapOf("e", Cypher.literalOf("F")))) .onCreate() .mutate(USER_NODE, Cypher.mapOf("a", Cypher.literalOf("B"))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MERGE (u:`User`)-[r:`OWNS`]->(b:`Bike`) ON MATCH SET u += {a: 'B'}, b += {c: 'D'}, r += {e: 'F'} ON CREATE SET u += {a: 'B'} RETURN u"); } @Test void mergeMutate() { Statement statement = Cypher.merge(USER_NODE) .mutate(USER_NODE, Cypher.mapOf("e", Cypher.literalOf("F"))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (u:`User`) SET u += {e: 'F'} RETURN u"); } @Test void createMutate() { Statement statement = Cypher.create(USER_NODE) .mutate(USER_NODE, Cypher.mapOf("e", Cypher.literalOf("F"))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("CREATE (u:`User`) SET u += {e: 'F'} RETURN u"); } @Test void mergeMutateWithMapProjection() { Statement statement; SymbolicName map = Cypher.name("map"); statement = Cypher .with(Cypher.mapOf("p", Cypher.literalOf("Hello, Welt"), "i", Cypher.literalOf(1)).as(map)) .merge(USER_NODE) .mutate(USER_NODE, map.project(Cypher.asterisk())) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("WITH {p: 'Hello, Welt', i: 1} AS map MERGE (u:`User`) SET u += map{.*}"); } @Test void mergeMutateWithSymbolicName() { Statement statement; SymbolicName map = Cypher.name("map"); statement = Cypher .with(Cypher.mapOf("p", Cypher.literalOf("Hello, Welt"), "i", Cypher.literalOf(1)).as(map)) .merge(USER_NODE) .mutate(USER_NODE, map) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("WITH {p: 'Hello, Welt', i: 1} AS map MERGE (u:`User`) SET u += map"); } @Test void mergeMutateWithFunctionCall() { Statement statement; statement = Cypher.merge(USER_NODE).mutate(USER_NODE, Cypher.call("returns.map").asFunction()).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (u:`User`) SET u += returns.map()"); } } @Nested class SetClause { @Test void shouldRenderSetAfterCreate() { Statement statement; statement = Cypher.create(USER_NODE) .set(USER_NODE.property("p").to(Cypher.literalOf("Hallo, Welt"))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("CREATE (u:`User`) SET u.p = 'Hallo, Welt'"); } @Test void shouldRenderSetAfterMerge() { Statement statement; statement = Cypher.merge(USER_NODE) .set(USER_NODE.property("p").to(Cypher.literalOf("Hallo, Welt"))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (u:`User`) SET u.p = 'Hallo, Welt'"); } @Test void shouldRenderSetAfterCreateAndWith() { Statement statement; statement = Cypher.create(USER_NODE) .with(USER_NODE) .set(USER_NODE.property("p").to(Cypher.literalOf("Hallo, Welt"))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("CREATE (u:`User`) WITH u SET u.p = 'Hallo, Welt'"); } @Test void shouldRenderSetAfterMergeAndWith() { Statement statement; statement = Cypher.merge(USER_NODE) .with(USER_NODE) .set(USER_NODE.property("p").to(Cypher.literalOf("Hallo, Welt"))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (u:`User`) WITH u SET u.p = 'Hallo, Welt'"); } @Test void shouldRenderSet() { Statement statement; statement = Cypher.match(USER_NODE) .set(USER_NODE.property("p").to(Cypher.literalOf("Hallo, Welt"))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) SET u.p = 'Hallo, Welt'"); statement = Cypher.match(USER_NODE) .set(USER_NODE.property("p").to(Cypher.literalOf("Hallo, Welt"))) .set(USER_NODE.property("a").to(Cypher.literalOf("Selber hallo."))) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) SET u.p = 'Hallo, Welt' SET u.a = 'Selber hallo.'"); statement = Cypher.match(USER_NODE) .set(USER_NODE.property("p").to(Cypher.literalOf("Hallo")), USER_NODE.property("g").to(Cypher.literalOf("Welt"))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) SET u.p = 'Hallo', u.g = 'Welt'"); } @Test void shouldRenderSetOnNodes() { Statement statement; statement = Cypher.match(USER_NODE).set(USER_NODE, "A", "B").returning(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) SET u:`A`:`B` RETURN u"); statement = Cypher.match(USER_NODE) .with(USER_NODE) .set(USER_NODE, "A", "B") .set(USER_NODE, "C", "D") .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WITH u SET u:`A`:`B` SET u:`C`:`D` RETURN u"); } @Test void shouldRenderSetFromAListOfExpression() { Statement statement; statement = Cypher.match(USER_NODE).set(USER_NODE.property("p"), Cypher.literalOf("Hallo, Welt")).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) SET u.p = 'Hallo, Welt'"); statement = Cypher.match(USER_NODE) .set(USER_NODE.property("p"), Cypher.literalOf("Hallo"), USER_NODE.property("g"), Cypher.literalOf("Welt")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) SET u.p = 'Hallo', u.g = 'Welt'"); statement = Cypher.match(USER_NODE) .set(USER_NODE.property("p"), Cypher.literalOf("Hallo, Welt")) .set(USER_NODE.property("p"), Cypher.literalOf("Hallo"), USER_NODE.property("g"), Cypher.literalOf("Welt")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) SET u.p = 'Hallo, Welt' SET u.p = 'Hallo', u.g = 'Welt'"); // noinspection ResultOfMethodCallIgnored assertThatIllegalArgumentException().isThrownBy(() -> Cypher.match(USER_NODE).set(USER_NODE.property("g"))) .withMessage("The list of expression to set must be even."); } @Test void shouldRenderMixedSet() { Statement statement; statement = Cypher.match(USER_NODE) .set(USER_NODE.property("p1"), Cypher.literalOf("Two expressions")) .set(USER_NODE.property("p2").to(Cypher.literalOf("A set expression"))) .set(USER_NODE.property("p3").to(Cypher.literalOf("One of two set expression")), USER_NODE.property("p4").to(Cypher.literalOf("Two of two set expression"))) .set(USER_NODE.property("p5"), Cypher.literalOf("Pair one of 2 expressions"), USER_NODE.property("p6"), Cypher.literalOf("Pair two of 4 expressions")) .returning(Cypher.asterisk()) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`) SET u.p1 = 'Two expressions' SET u.p2 = 'A set expression' SET u.p3 = 'One of two set expression', u.p4 = 'Two of two set expression' SET u.p5 = 'Pair one of 2 expressions', u.p6 = 'Pair two of 4 expressions' RETURN *"); } } @Nested class MergeClause { @Test void shouldRenderMergeWithoutReturn() { Statement statement; statement = Cypher.merge(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (u:`User`)"); statement = Cypher.merge(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o")).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (u:`User`)-[o:`OWNS`]->(b:`Bike`)"); } @Test // GH-189 void shouldRenderMergeBasedOnPatternExpressionCollectionWithoutReturn() { Statement statement; statement = Cypher.merge(Collections.singleton(USER_NODE)).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (u:`User`)"); statement = Cypher.merge(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o")).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (u:`User`)-[o:`OWNS`]->(b:`Bike`)"); } @Test void shouldRenderMultipleMergesWithoutReturn() { Statement statement; statement = Cypher.merge(USER_NODE).merge(BIKE_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (u:`User`) MERGE (b:`Bike`)"); statement = Cypher.merge(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o")) .merge(Cypher.node("Other")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MERGE (u:`User`)-[o:`OWNS`]->(b:`Bike`) MERGE (:`Other`)"); } @Test void shouldRenderMergeReturn() { Statement statement; statement = Cypher.merge(USER_NODE).returning(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (u:`User`) RETURN u"); Relationship r = USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o"); statement = Cypher.merge(r).returning(USER_NODE, r).build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MERGE (u:`User`)-[o:`OWNS`]->(b:`Bike`) RETURN u, o"); statement = Cypher.merge(USER_NODE) .returning(USER_NODE) .orderBy(USER_NODE.property("name")) .skip(23) .limit(42) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MERGE (u:`User`) RETURN u ORDER BY u.name SKIP 23 LIMIT 42"); } @Test void shouldRenderMultipleMergesReturn() { Statement statement; statement = Cypher.merge(USER_NODE).merge(BIKE_NODE).returning(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (u:`User`) MERGE (b:`Bike`) RETURN u"); Relationship r = USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o"); statement = Cypher.merge(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o")) .merge(Cypher.node("Other")) .returning(USER_NODE, r) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MERGE (u:`User`)-[o:`OWNS`]->(b:`Bike`) MERGE (:`Other`) RETURN u, o"); } @Test void shouldRenderMergeWithWith() { Statement statement; statement = Cypher.merge(USER_NODE).with(USER_NODE).returning(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (u:`User`) WITH u RETURN u"); statement = Cypher.merge(USER_NODE) .with(USER_NODE) .set(USER_NODE.property("x").to(Cypher.literalOf("y"))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (u:`User`) WITH u SET u.x = 'y'"); } @Test void matchShouldExposeMerge() { Statement statement; statement = Cypher.match(USER_NODE).merge(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o")).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) MERGE (u)-[o:`OWNS`]->(b:`Bike`)"); } @Test void withShouldExposeMerge() { Statement statement; statement = Cypher.match(USER_NODE) .withDistinct(USER_NODE) .merge(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WITH DISTINCT u MERGE (u)-[o:`OWNS`]->(b:`Bike`)"); } @Test void mixedCreateAndMerge() { Statement statement; Node tripNode = Cypher.node("Trip").named("t"); statement = Cypher.create(USER_NODE) .merge(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o")) .withDistinct(BIKE_NODE) .merge(tripNode.relationshipFrom(BIKE_NODE, "USED_ON")) .returning(Cypher.asterisk()) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "CREATE (u:`User`) MERGE (u)-[o:`OWNS`]->(b:`Bike`) WITH DISTINCT b MERGE (t:`Trip`)<-[:`USED_ON`]-(b) RETURN *"); } @Test // GH-104 void singleCreateAction() { Literal halloWeltString = Cypher.literalOf("Hallo, Welt"); for (Statement statement : new Statement[] { Cypher.merge(USER_NODE).onCreate().set(USER_NODE.property("p").to(halloWeltString)).build(), Cypher.merge(USER_NODE).onCreate().set(USER_NODE.property("p"), halloWeltString).build() }) { assertThat(cypherRenderer.render(statement)) .isEqualTo("MERGE (u:`User`) ON CREATE SET u.p = 'Hallo, Welt'"); } } @Test // GH-104 void singleMatchAction() { Literal halloWeltString = Cypher.literalOf("Hallo, Welt"); for (Statement statement : new Statement[] { Cypher.merge(USER_NODE).onMatch().set(USER_NODE.property("p").to(halloWeltString)).build(), Cypher.merge(USER_NODE).onMatch().set(USER_NODE.property("p"), halloWeltString).build(), }) { assertThat(cypherRenderer.render(statement)) .isEqualTo("MERGE (u:`User`) ON MATCH SET u.p = 'Hallo, Welt'"); } } @Test void stuffThanSingleMatchAction() { for (Statement statement : new Statement[] { Cypher.create(BIKE_NODE) .set(BIKE_NODE.property("nice").to(Cypher.literalTrue())) .merge(USER_NODE) .onMatch() .set(USER_NODE.property("happy").to(Cypher.literalTrue())) .create(USER_NODE.relationshipTo(BIKE_NODE, "OWNS")) .build(), }) { assertThat(cypherRenderer.render(statement)).isEqualTo( "CREATE (b:`Bike`) SET b.nice = true MERGE (u:`User`) ON MATCH SET u.happy = true CREATE (u)-[:`OWNS`]->(b)"); } } @Test void singleActionMultipleProperties() { for (Statement statement : new Statement[] { Cypher.merge(USER_NODE) .onMatch() .set(USER_NODE.property("p1").to(Cypher.literalOf("v1")), USER_NODE.property("p2").to(Cypher.literalOf("v2"))) .build(), Cypher.merge(USER_NODE) .onCreate() .set(USER_NODE.property("p1").to(Cypher.literalOf("v1")), USER_NODE.property("p2").to(Cypher.literalOf("v2"))) .build(), }) { assertThat(cypherRenderer.render(statement)) .matches("\\QMERGE (u:`User`) ON \\E(CREATE|MATCH)\\Q SET u.p1 = 'v1', u.p2 = 'v2'\\E"); } } @Test void multipleActionMultipleProperties() { Statement statement = Cypher.merge(USER_NODE) .onMatch() .set(USER_NODE.property("p1").to(Cypher.literalOf("v1")), USER_NODE.property("p2").to(Cypher.literalOf("v2"))) .onCreate() .set(USER_NODE.property("p3").to(Cypher.literalOf("v3")), USER_NODE.property("p4").to(Cypher.literalOf("v4"))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MERGE (u:`User`) ON MATCH SET u.p1 = 'v1', u.p2 = 'v2' ON CREATE SET u.p3 = 'v3', u.p4 = 'v4'"); } @Test // GH-104 void singleCreateThanMatchAction() { Literal helloWorldString = Cypher.literalOf("Hello, World"); Literal halloWeltString = Cypher.literalOf("Hallo, Welt"); Statement statement = Cypher.merge(USER_NODE) .onCreate() .set(USER_NODE.property("p").to(helloWorldString)) .onMatch() .set(USER_NODE.property("p").to(halloWeltString)) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MERGE (u:`User`) ON CREATE SET u.p = 'Hello, World' ON MATCH SET u.p = 'Hallo, Welt'"); } @Test // GH-104 void singleMatchThanCreateAction() { Literal helloWorldString = Cypher.literalOf("Hello, World"); Literal halloWeltString = Cypher.literalOf("Hallo, Welt"); Statement statement = Cypher.merge(USER_NODE) .onMatch() .set(USER_NODE.property("p").to(halloWeltString)) .onCreate() .set(USER_NODE.property("p").to(helloWorldString)) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MERGE (u:`User`) ON MATCH SET u.p = 'Hallo, Welt' ON CREATE SET u.p = 'Hello, World'"); } @Test // GH-104 void multipleMatchesAndCreates() { Statement statement = Cypher.merge(USER_NODE) .onMatch() .set(USER_NODE.property("p1").to(Cypher.literalOf("v1"))) .onCreate() .set(USER_NODE.property("p2").to(Cypher.literalOf("v2"))) .onCreate() .set(USER_NODE.property("p3").to(Cypher.literalOf("v3"))) .onMatch() .set(USER_NODE.property("p4").to(Cypher.literalOf("v4"))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MERGE (u:`User`) ON MATCH SET u.p1 = 'v1' ON CREATE SET u.p2 = 'v2' ON CREATE SET u.p3 = 'v3' ON MATCH SET u.p4 = 'v4'"); } @Test // GH-104 void actionThanSet() { Statement statement = Cypher.merge(USER_NODE) .onMatch() .set(USER_NODE.property("p1").to(Cypher.literalOf("v1"))) .set(USER_NODE.property("p2").to(Cypher.literalOf("v2"))) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MERGE (u:`User`) ON MATCH SET u.p1 = 'v1' SET u.p2 = 'v2' RETURN u"); } @Test // GH-104 void actionThanContinue() { Statement statement = Cypher.merge(USER_NODE) .onMatch() .set(USER_NODE.property("p1").to(Cypher.literalOf("v1"))) .with(USER_NODE) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MERGE (u:`User`) ON MATCH SET u.p1 = 'v1' WITH u RETURN u"); } @Test // GH-750 void mergeShouldExposeRemoveLabels() { var n = Cypher.node("LABEL1").named("n"); var statement = Cypher.merge(n).remove(n, "OLD_LABEL").set(n, "NEW_LABEL").build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MERGE (n:`LABEL1`) REMOVE n:`OLD_LABEL` SET n:`NEW_LABEL`"); } @Test // GH-750 void mergeShouldExposeRemoveProperties() { var n = Cypher.node("LABEL1").named("n"); var statement = Cypher.merge(n).remove(n.property("x")).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (n:`LABEL1`) REMOVE n.x"); } @Test // GH-750 void mergeShouldExposeSetLabels() { var n = Cypher.node("LABEL1").named("n"); var statement = Cypher.merge(n).set(n, "NEW_LABEL").build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (n:`LABEL1`) SET n:`NEW_LABEL`"); } @Test // GH-750 void mergeShouldExposeSetProperties() { var n = Cypher.node("LABEL1").named("n"); var statement = Cypher.merge(n).set(n.property("x"), Cypher.literalOf("y")).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (n:`LABEL1`) SET n.x = 'y'"); } @Test // GH-750 void mergeShouldExposeSetLabelsAfterAction() { var n = Cypher.node("LABEL1").named("n"); var statement = Cypher.merge(n).onMatch().set(n, "NEW_LABEL").build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (n:`LABEL1`) ON MATCH SET n:`NEW_LABEL`"); } @Test // GH-750 void mergeShouldExposeSetMultipleLabelsAfterAction() { var n = Cypher.node("LABEL1").named("n"); var statement = Cypher.merge(n).onMatch().set(n, List.of("A", "B")).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (n:`LABEL1`) ON MATCH SET n:`A`:`B`"); } @Test // GH-750 void mergeShouldExposeSetPropertiesAfterAction() { var n = Cypher.node("LABEL1").named("n"); var statement = Cypher.merge(n).onMatch().set(n.property("x"), Cypher.literalOf("y")).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (n:`LABEL1`) ON MATCH SET n.x = 'y'"); } } @Nested class CreateClause { @Test void shouldRenderCreateWithoutReturn() { Statement statement; statement = Cypher.create(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("CREATE (u:`User`)"); statement = Cypher.create(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o")).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("CREATE (u:`User`)-[o:`OWNS`]->(b:`Bike`)"); } @Test // GH-189 void shouldRenderCreateBasedOnPatternElementCollectionWithoutReturn() { Statement statement; statement = Cypher.create(Collections.singleton(USER_NODE)).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("CREATE (u:`User`)"); statement = Cypher.create(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o")).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("CREATE (u:`User`)-[o:`OWNS`]->(b:`Bike`)"); } @Test void shouldRenderMultipleCreatesWithoutReturn() { Statement statement; statement = Cypher.create(USER_NODE).create(BIKE_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("CREATE (u:`User`) CREATE (b:`Bike`)"); statement = Cypher.create(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o")) .create(Cypher.node("Other")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("CREATE (u:`User`)-[o:`OWNS`]->(b:`Bike`) CREATE (:`Other`)"); } @Test void shouldRenderMultipleCreatesBasedOnPatternElementCollectionWithoutReturn() { Statement statement; statement = Cypher.create(USER_NODE).create(Collections.singleton(BIKE_NODE)).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("CREATE (u:`User`) CREATE (b:`Bike`)"); statement = Cypher.create(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o")) .create(Collections.singleton(Cypher.node("Other"))) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("CREATE (u:`User`)-[o:`OWNS`]->(b:`Bike`) CREATE (:`Other`)"); } @Test void shouldRenderCreateReturn() { Statement statement; statement = Cypher.create(USER_NODE).returning(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("CREATE (u:`User`) RETURN u"); Relationship r = USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o"); statement = Cypher.create(r).returning(USER_NODE, r).build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("CREATE (u:`User`)-[o:`OWNS`]->(b:`Bike`) RETURN u, o"); statement = Cypher.create(USER_NODE) .returning(USER_NODE) .orderBy(USER_NODE.property("name")) .skip(23) .limit(42) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("CREATE (u:`User`) RETURN u ORDER BY u.name SKIP 23 LIMIT 42"); } @Test void shouldRenderMultipleCreatesReturn() { Statement statement; statement = Cypher.create(USER_NODE).create(BIKE_NODE).returning(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("CREATE (u:`User`) CREATE (b:`Bike`) RETURN u"); Relationship r = USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o"); statement = Cypher.create(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o")) .create(Cypher.node("Other")) .returning(USER_NODE, r) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("CREATE (u:`User`)-[o:`OWNS`]->(b:`Bike`) CREATE (:`Other`) RETURN u, o"); } @Test void shouldRenderCreateWithWith() { Statement statement; statement = Cypher.create(USER_NODE).with(USER_NODE).returning(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("CREATE (u:`User`) WITH u RETURN u"); statement = Cypher.create(USER_NODE) .with(USER_NODE) .set(USER_NODE.property("x").to(Cypher.literalOf("y"))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("CREATE (u:`User`) WITH u SET u.x = 'y'"); } @Test void matchShouldExposeCreate() { Statement statement; statement = Cypher.match(USER_NODE).create(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o")).build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) CREATE (u)-[o:`OWNS`]->(b:`Bike`)"); } @Test void withShouldExposeCreate() { Statement statement; statement = Cypher.match(USER_NODE) .withDistinct(USER_NODE) .create(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WITH DISTINCT u CREATE (u)-[o:`OWNS`]->(b:`Bike`)"); statement = Cypher.match(USER_NODE) .withDistinct(USER_NODE.getRequiredSymbolicName()) .create(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WITH DISTINCT u CREATE (u)-[o:`OWNS`]->(b:`Bike`)"); } @Test void withWithStringVarsShouldWork() { Statement statement; statement = Cypher.match(USER_NODE) .withDistinct("u") .create(USER_NODE.relationshipTo(BIKE_NODE, "OWNS").named("o")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WITH DISTINCT u CREATE (u)-[o:`OWNS`]->(b:`Bike`)"); } } @Nested class DeleteClause { @Test void shouldRenderDeleteWithoutReturn() { Statement statement; statement = Cypher.match(USER_NODE).detachDelete(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) DETACH DELETE u"); statement = Cypher.match(USER_NODE).detachDelete("u").build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) DETACH DELETE u"); statement = Cypher.match(USER_NODE).with(USER_NODE).detachDelete(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) WITH u DETACH DELETE u"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("a").isNotNull()) .and(USER_NODE.property("b").isNull()) .delete(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WHERE (u.a IS NOT NULL AND u.b IS NULL) DELETE u"); statement = Cypher.match(USER_NODE, BIKE_NODE).delete(USER_NODE, BIKE_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`), (b:`Bike`) DELETE u, b"); } @Test void shouldRenderDeleteBasedOnExpressionCollectionWithoutReturn() { Statement statement; statement = Cypher.match(USER_NODE) .detachDelete(Collections.singleton(USER_NODE.getRequiredSymbolicName())) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) DETACH DELETE u"); statement = Cypher.match(USER_NODE) .with(USER_NODE) .detachDelete(Collections.singleton(USER_NODE.getRequiredSymbolicName())) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) WITH u DETACH DELETE u"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("a").isNotNull()) .and(USER_NODE.property("b").isNull()) .delete(Collections.singleton(USER_NODE.getRequiredSymbolicName())) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WHERE (u.a IS NOT NULL AND u.b IS NULL) DELETE u"); Expression[] namedOnes = { USER_NODE.getRequiredSymbolicName(), BIKE_NODE.getRequiredSymbolicName() }; statement = Cypher.match(USER_NODE, BIKE_NODE).delete(Arrays.asList(namedOnes)).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`), (b:`Bike`) DELETE u, b"); } @Test void shouldRenderDeleteWithReturn() { Statement statement; statement = Cypher.match(USER_NODE).detachDelete(USER_NODE).returning(USER_NODE).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (u:`User`) DETACH DELETE u RETURN u"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("a").isNotNull()) .and(USER_NODE.property("b").isNull()) .detachDelete(USER_NODE) .returning(USER_NODE) .orderBy(USER_NODE.property("a").ascending()) .skip(2) .limit(1) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`) WHERE (u.a IS NOT NULL AND u.b IS NULL) DETACH DELETE u RETURN u ORDER BY u.a ASC SKIP 2 LIMIT 1"); statement = Cypher.match(USER_NODE) .where(USER_NODE.property("a").isNotNull()) .and(USER_NODE.property("b").isNull()) .detachDelete(USER_NODE) .returningDistinct(USER_NODE) .orderBy(USER_NODE.property("a").ascending()) .skip(2) .limit(1) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`) WHERE (u.a IS NOT NULL AND u.b IS NULL) DETACH DELETE u RETURN DISTINCT u ORDER BY u.a ASC SKIP 2 LIMIT 1"); } @Test @SuppressWarnings("deprecation") void shouldRenderNodeDelete() { Node n = Cypher.anyNode("n"); Relationship r = n.relationshipBetween(Cypher.anyNode()).named("r0"); Statement statement = Cypher.match(n) .where(n.internalId().isEqualTo(Cypher.literalOf(4711))) .optionalMatch(r) .delete(r, n) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (n) WHERE id(n) = 4711 OPTIONAL MATCH (n)-[r0]-() DELETE r0, n"); } @Test @SuppressWarnings("deprecation") void shouldRenderChainedDeletes() { Node n = Cypher.anyNode("n"); Relationship r = n.relationshipBetween(Cypher.anyNode()).named("r0"); Statement statement = Cypher.match(n) .where(n.internalId().isEqualTo(Cypher.literalOf(4711))) .optionalMatch(r) .delete(r, n) .delete(BIKE_NODE) .detachDelete(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n) WHERE id(n) = 4711 OPTIONAL MATCH (n)-[r0]-() DELETE r0, n DELETE b DETACH DELETE u"); } } @Nested class ForeachClause { @Test void shouldRenderForeach() { Node n = Cypher.anyNode("n"); Clause clause = Clauses.forEach(Cypher.name("a"), Cypher.listOf(), List.of(Clauses.merge(List.of(n), List.of(MergeAction.of(MergeAction.Type.ON_CREATE, (Set) Clauses.set(List.of(Cypher.name("n").property("prop").to(Cypher.literalOf(1))))))))); assertThat(cypherRenderer.render(Statement.of(List.of(clause)))) .isEqualTo("FOREACH (a IN [] | MERGE (n) ON CREATE SET n.prop = 1)"); } @Test void shouldRenderNestedForeach() { Node n = Cypher.anyNode("n"); Clause clause = Clauses.forEach(Cypher.name("a"), Cypher.listOf(), List.of(Clauses .forEach(Cypher.name("a"), Cypher.listOf(), List.of(Clauses.merge(List.of(n), List.of(MergeAction.of( MergeAction.Type.ON_CREATE, (Set) Clauses.set(List.of(Cypher.name("n").property("prop").to(Cypher.literalOf(1))))))))))); assertThat(cypherRenderer.render(Statement.of(List.of(clause)))) .isEqualTo("FOREACH (a IN [] | FOREACH (a IN [] | MERGE (n) ON CREATE SET n.prop = 1))"); } @Test void fakeIfAsDocumentedInKbShouldWork() { Node node = Cypher.node("Node").named("node").withProperties("id", Cypher.literalOf(12345)); Property needsUpdate = node.property("needsUpdate"); Statement stmt = Cypher.match(node) .foreach(Cypher.name("i")) .in(Cypher.caseExpression() .when(needsUpdate) .then(Cypher.listOf(Cypher.literalOf("1"))) .elseDefault(Cypher.listOf())) .apply((UpdatingClause) Clauses .set(List.of(Cypher.set(node.property("newProperty"), Cypher.literalOf(5678)))), (UpdatingClause) Clauses.remove(List.of(needsUpdate)), (UpdatingClause) Clauses.set(List.of(Cypher.setLabel(node, "Updated")))) .build(); assertThat(stmt.getCypher()).isEqualTo( "MATCH (node:`Node` {id: 12345}) FOREACH (i IN CASE WHEN node.needsUpdate THEN ['1'] ELSE [] END | SET node.newProperty = 5678 REMOVE node.needsUpdate SET node:`Updated`)"); } @Test void foreachCanBeChained() { SymbolicName event = Cypher.name("event"); SymbolicName create = Cypher.name("create"); SymbolicName delete = Cypher.name("delete"); ListExpression t = Cypher.listOf(Cypher.literalOf("1")); ListExpression f = Cypher.listOf(); Node user = Cypher.node("User") .withProperties("id", Cypher.property(Cypher.property("event", "keys"), "id")) .named("n"); Clause merge = Clauses.merge(List.of(user), List.of()); var stmt = Cypher.unwind(Cypher.parameter("messages")) .as(event) .with(Cypher.caseExpression() .when(Cypher.valueAt(event, 0).eq(Cypher.literalOf("C"))) .then(t) .elseDefault(f) .as(create), Cypher.caseExpression() .when(Cypher.valueAt(event, 0).eq(Cypher.literalOf("d"))) .then(t) .elseDefault(f) .as(delete), Cypher.valueAt(event, 1).as(event)) .foreach(Cypher.name("i")) .in(create) .apply((UpdatingClause) merge, (UpdatingClause) Clauses .set(List.of(Cypher.set(user.asExpression(), Cypher.property("event", "properties")))), (UpdatingClause) Clauses .set(List.of(Cypher.mutate(user.asExpression(), Cypher.property("event", "keys"))))) .foreach(Cypher.name("i")) .in(delete) .apply((UpdatingClause) merge, (UpdatingClause) Clauses.delete(true, List.of(user.asExpression()))) .build(); assertThat(stmt.getCypher()).isEqualTo( "UNWIND $messages AS event WITH CASE WHEN event[0] = 'C' THEN ['1'] ELSE [] END AS create, CASE WHEN event[0] = 'd' THEN ['1'] ELSE [] END AS delete, event[1] AS event FOREACH (i IN create | MERGE (n:`User` {id: event.keys.id}) SET n = event.properties SET n += event.keys) FOREACH (i IN delete | MERGE (n:`User` {id: event.keys.id}) DETACH DELETE n)"); } } @Nested class Expressions { @Test void shouldRenderParameters() { Statement statement; statement = Cypher.match(USER_NODE) .where(USER_NODE.property("a").isEqualTo(Cypher.parameter("aParameter"))) .detachDelete(USER_NODE) .returning(USER_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (u:`User`) WHERE u.a = $aParameter DETACH DELETE u RETURN u"); } } @Nested @TestInstance(TestInstance.Lifecycle.PER_CLASS) class OperationsAndComparisons { @SuppressWarnings("unused") // This the source of test parameters private Stream operatorsToTest() { return Stream.of(Arguments.of("Unary minus", Cypher.minus(Cypher.literalOf(1)), "RETURN -1"), Arguments.of("Unary plus", Cypher.plus(Cypher.literalOf(1)), "RETURN +1")); } @ParameterizedTest(name = "{0}") @MethodSource("operatorsToTest") void unaryOperatorsShouldWork(@SuppressWarnings("unused") String name, Expression operator, String expected) { assertThat(cypherRenderer.render(Cypher.returning(operator).build())).isEqualTo(expected); } @Test @SuppressWarnings("ConstantConditions") // This is what the test is about void preconditionsShouldBeAssertedOnUnary() { assertThatIllegalArgumentException().isThrownBy(() -> Operation.create(Operator.OR, Cypher.literalTrue())) .withMessage("Operator must be unary."); // Likely to be fail in IDEA when the JetBrains annotations are present and // asserted by the ides runner assertThatIllegalArgumentException().isThrownBy(() -> Operation.create(null, Cypher.literalTrue())) .withMessage("Operator must not be null."); assertThatIllegalArgumentException().isThrownBy(() -> Operation.create(Operator.UNARY_MINUS, null)) .withMessage("The expression must not be null."); } @Test void shouldRenderOperations() { Statement statement; statement = Cypher.match(Cypher.anyNode("n")) .returning(Cypher.literalOf(1).add(Cypher.literalOf(2))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (n) RETURN (1 + 2)"); } @Test void shouldRenderComparision() { Statement statement; statement = Cypher.match(Cypher.anyNode("n")) .returning(Cypher.literalOf(1).gt(Cypher.literalOf(2))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (n) RETURN 1 > 2"); statement = Cypher.match(Cypher.anyNode("n")) .returning(Cypher.literalOf(1).gt(Cypher.literalOf(2)).isTrue()) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (n) RETURN (1 > 2) = true"); statement = Cypher.match(Cypher.anyNode("n")) .returning(Cypher.literalOf(1).gt(Cypher.literalOf(2)).isTrue().isFalse()) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (n) RETURN ((1 > 2) = true) = false"); } } @Nested class ExpressionsRendering { @Test void shouldRenderMap() { Statement statement; statement = Cypher.match(Cypher.anyNode("n")) .returning(Cypher.point(Cypher.mapOf("latitude", Cypher.parameter("latitude"), "longitude", Cypher.parameter("longitude"), "crs", Cypher.literalOf("WGS-84")))) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (n) RETURN point({latitude: $latitude, longitude: $longitude, crs: 'WGS-84'})"); } @Test void shouldRenderPointFunction() { Statement statement; Node n = Cypher.anyNode("n"); statement = Cypher.match(n) .where(Cypher.distance(n.property("location"), Cypher.point(Cypher.parameter("point.point"))) .gt(Cypher.parameter("point.distance"))) .returning(n) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n) WHERE point.distance(n.location, point($point.point)) > $point.distance RETURN n"); } } @Nested class PropertyRendering { @Test // GH-157 void usingExistingJavaMaps() { Map newProperties = new LinkedHashMap<>(); newProperties.put("prop1", 23); newProperties.put("theTruth", 42); newProperties.put("somethingElse", "foobar"); newProperties.put("aParam", Cypher.parameter("x").withValue("y")); Node node = Cypher.node("ANode").named("n").withProperties(newProperties); assertThat(Cypher.match(node).returning(node).build().getCypher()) .isEqualTo("MATCH (n:`ANode` {prop1: 23, theTruth: 42, somethingElse: 'foobar', aParam: $x}) RETURN n"); } @Test // GH-114 void manuallyNested() { Node node = Cypher.node("Person").named("p"); Property locationProperty = node.property("location"); Statement statement = Cypher.match(node) .where(Cypher.property(locationProperty, "x").gt(Cypher.literalOf(6))) .returning(node) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (p:`Person`) WHERE p.location.x > 6 RETURN p"); } @Test // GH-114 void chained() { Node node = Cypher.node("Person").named("p"); Statement statement = Cypher.match(node) .where(node.property("location", "x").gt(Cypher.literalOf(6))) .returning(node) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (p:`Person`) WHERE p.location.x > 6 RETURN p"); } @Test // GH-114 void chainedAndFancy() { Node node = Cypher.node("Person").named("p"); Statement statement = Cypher .create(node.withProperties(Cypher.mapOf("home.location", Cypher .point(Cypher.mapOf("latitude", Cypher.literalOf(50.751), "longitude", Cypher.literalOf(6.179)))))) .returning(node) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "CREATE (p:`Person` {`home.location`: point({latitude: 50.751, longitude: 6.179})}) RETURN p"); statement = Cypher.match(node) .where(node.property("home.location", "y").gt(Cypher.literalOf(50))) .returning(node) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (p:`Person`) WHERE p.`home.location`.y > 50 RETURN p"); } @Test // GH-123 void explicitlyDefined() { Node node = Cypher.node("Person").named("p"); Property ly1 = Cypher.property(node.getRequiredSymbolicName(), "home.location", "y"); Property ly2 = Cypher.property("p", "home.location", "y"); Statement statement = Cypher.match(node).where(ly1.gt(Cypher.literalOf(50))).returning(ly2).build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (p:`Person`) WHERE p.`home.location`.y > 50 RETURN p.`home.location`.y"); } @Test void chainedInProjection() { Node node = Cypher.node("Person").named("p"); // noinspection ResultOfMethodCallIgnored assertThatIllegalArgumentException() .isThrownBy(() -> Cypher.match(node) .returning(node.project("__internalNeo4jId__", Cypher.elementId(node), "name") .and(node.property("home.location", "y"))) .build()) .withMessage("Cannot project nested properties!"); } @Test void shouldRenderNodeProperties() { for (Node nodeWithProperties : new Node[] { Cypher.node("Test", Cypher.mapOf("a", Cypher.literalOf("b"))), Cypher.node("Test").withProperties(Cypher.mapOf("a", Cypher.literalOf("b"))), Cypher.node("Test").withProperties("a", Cypher.literalOf("b")) }) { Statement statement; statement = Cypher.match(nodeWithProperties).returning(Cypher.asterisk()).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (:`Test` {a: 'b'}) RETURN *"); statement = Cypher.merge(nodeWithProperties).returning(Cypher.asterisk()).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (:`Test` {a: 'b'}) RETURN *"); } } @Test void nestedProperties() { Node nodeWithProperties = Cypher.node("Test") .withProperties("outer", Cypher.mapOf("a", Cypher.literalOf("b"))); Statement statement; statement = Cypher.match(nodeWithProperties).returning(Cypher.asterisk()).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (:`Test` {outer: {a: 'b'}}) RETURN *"); } @Test void shouldNotRenderPropertiesInReturn() { Node nodeWithProperties = BIKE_NODE.withProperties("a", Cypher.literalOf("b")); Statement statement; statement = Cypher.match(nodeWithProperties, nodeWithProperties.relationshipFrom(USER_NODE, "OWNS")) .returning(nodeWithProperties) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (b:`Bike` {a: 'b'}), (b)<-[:`OWNS`]-(u:`User`) RETURN b"); } @Test // GH-127 void dynamicPropertyLookupsOnNodes() { Node m = Cypher.node("Movie").named("m"); Statement statement; statement = Cypher.match(m).returning(m.property(Cypher.literalOf("title"))).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (m:`Movie`) RETURN m['title']"); statement = Cypher.match(m).returning(m.property(Cypher.parameter("prop"))).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (m:`Movie`) RETURN m[$prop]"); } @Test // GH-127 void dynamicPropertyLookupsOnRelationships() { Node m = Cypher.node("Movie").named("m"); Relationship r = m.relationshipFrom(Cypher.anyNode(), "ACTED_IN").named("r"); Statement statement; statement = Cypher.match(r).returning(r.property(Cypher.literalOf("roles"))).build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (m:`Movie`)<-[r:`ACTED_IN`]-() RETURN r['roles']"); statement = Cypher.match(r).returning(r.property(Cypher.parameter("prop"))).build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (m:`Movie`)<-[r:`ACTED_IN`]-() RETURN r[$prop]"); } @Test // GH-127 void arbitraryDynamicLookups() { Node m = Cypher.node("Movie").named("m"); Statement statement; statement = Cypher.match(m).returning(Cypher.property("m", Cypher.literalOf("title"))).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (m:`Movie`) RETURN m['title']"); statement = Cypher.match(m) .returning(Cypher.property(SymbolicName.of("m"), Cypher.literalOf("title"))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (m:`Movie`) RETURN m['title']"); statement = Cypher.with(Cypher.mapOf("a", Cypher.literalOf("X"), "b", Cypher.literalOf("Y")).as("m")) .returning(Cypher.property("m", Cypher.literalOf("a")), Cypher.property("m", "a")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("WITH {a: 'X', b: 'Y'} AS m RETURN m['a'], m.a"); } @Test void indexedProperties() { SymbolicName alias = SymbolicName.of("line"); Statement statement = Cypher.returning(Cypher.valueAt(alias, 1)).build(); assertThat(statement.getCypher()).isEqualTo("RETURN line[1]"); } } @Nested class Returning { @Test void shouldRenderLeadingReturning() { String cypher = Cypher.returning(Cypher.literalOf(1)).build().getCypher(); assertThat(cypher).isEqualTo("RETURN 1"); } @Test // GH-189 void shouldRenderLeadingReturningBasedOnExpressionCollection() { String cypher = Cypher.returning(Collections.singletonList(Cypher.literalOf(1))).build().getCypher(); assertThat(cypher).isEqualTo("RETURN 1"); } } @Nested class UnwindRendering { @Test @SuppressWarnings("deprecation") void unwindWithoutWith() { final Node rootNode = Cypher.anyNode("n"); final SymbolicName label = Cypher.name("label"); final Statement statement = Cypher.match(rootNode) .where(rootNode.internalId().isEqualTo(Cypher.literalOf(1))) .unwind(rootNode.labels()) .as("label") .with(label) .where(label.in(Cypher.parameter("fixedLabels")).not()) .returning(Cypher.collect(label).as("labels")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n) WHERE id(n) = 1 UNWIND labels(n) AS label WITH label WHERE NOT (label IN $fixedLabels) RETURN collect(label) AS labels"); } @Test void shouldRenderLeadingUnwind() { Statement statement = Cypher.unwind(Cypher.literalOf(1), Cypher.literalTrue(), Cypher.literalFalse()) .as("n") .returning(Cypher.name("n")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("UNWIND [1, true, false] AS n RETURN n"); } @Test // GH-189 void shouldRenderLeadingUnwindBasedOnExpressionCollection() { Expression[] expressions = { Cypher.literalOf(1), Cypher.literalTrue(), Cypher.literalFalse() }; Statement statement = Cypher.unwind(Arrays.asList(expressions)).as("n").returning(Cypher.name("n")).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("UNWIND [1, true, false] AS n RETURN n"); } @Test void shouldRenderLeadingUnwindWithUpdate() { Statement statement = Cypher.unwind(Cypher.literalOf(1), Cypher.literalTrue(), Cypher.literalFalse()) .as("n") .merge(BIKE_NODE.withProperties("b", Cypher.name("n"))) .returning(BIKE_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("UNWIND [1, true, false] AS n MERGE (b:`Bike` {b: n}) RETURN b"); } @Test void shouldRenderLeadingUnwindWithCreate() { Statement statement = Cypher.unwind(Cypher.literalOf(1), Cypher.literalTrue(), Cypher.literalFalse()) .as("n") .create(BIKE_NODE.withProperties("b", Cypher.name("n"))) .returning(BIKE_NODE) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("UNWIND [1, true, false] AS n CREATE (b:`Bike` {b: n}) RETURN b"); } @Test void shouldRenderUnwind() { AliasedExpression collected = Cypher.collect(BIKE_NODE).as("collected"); Statement statement = Cypher.match(BIKE_NODE) .with(collected) .unwind(collected) .as("x") .with("x") .delete(Cypher.name("x")) .returning("x") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (b:`Bike`) WITH collect(b) AS collected UNWIND collected AS x WITH x DELETE x RETURN x"); } } @Nested class Unions { @Test void shouldRenderUnions() { Statement statement1 = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("a").isEqualTo(Cypher.literalOf("A"))) .returning(BIKE_NODE) .build(); Statement statement2 = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("b").isEqualTo(Cypher.literalOf("B"))) .returning(BIKE_NODE) .build(); Statement statement3 = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("c").isEqualTo(Cypher.literalOf("C"))) .returning(BIKE_NODE) .build(); Statement statement; statement = Cypher.union(statement1, statement2, statement3); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (b:`Bike`) WHERE b.a = 'A' RETURN b UNION MATCH (b:`Bike`) WHERE b.b = 'B' RETURN b UNION MATCH (b:`Bike`) WHERE b.c = 'C' RETURN b"); } @Test // GH-189 void shouldRenderUnionsBasedOnStatementCollections() { Statement statement1 = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("a").isEqualTo(Cypher.literalOf("A"))) .returning(BIKE_NODE) .build(); Statement statement2 = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("b").isEqualTo(Cypher.literalOf("B"))) .returning(BIKE_NODE) .build(); Statement statement3 = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("c").isEqualTo(Cypher.literalOf("C"))) .returning(BIKE_NODE) .build(); Statement statement; Statement[] statements = { statement1, statement2, statement3 }; statement = Cypher.union(Arrays.asList(statements)); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (b:`Bike`) WHERE b.a = 'A' RETURN b UNION MATCH (b:`Bike`) WHERE b.b = 'B' RETURN b UNION MATCH (b:`Bike`) WHERE b.c = 'C' RETURN b"); } @Test void shouldRenderAllUnions() { Statement statement1 = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("a").isEqualTo(Cypher.literalOf("A"))) .returning(BIKE_NODE) .build(); Statement statement2 = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("b").isEqualTo(Cypher.literalOf("B"))) .returning(BIKE_NODE) .build(); Statement statement; statement = Cypher.unionAll(statement1, statement2); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (b:`Bike`) WHERE b.a = 'A' RETURN b UNION ALL MATCH (b:`Bike`) WHERE b.b = 'B' RETURN b"); } @Test // GH-189 void shouldRenderAllUnionsBasedOnStatementCollections() { Statement statement1 = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("a").isEqualTo(Cypher.literalOf("A"))) .returning(BIKE_NODE) .build(); Statement statement2 = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("b").isEqualTo(Cypher.literalOf("B"))) .returning(BIKE_NODE) .build(); Statement statement; Statement[] statements = { statement1, statement2 }; statement = Cypher.unionAll(Arrays.asList(statements)); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (b:`Bike`) WHERE b.a = 'A' RETURN b UNION ALL MATCH (b:`Bike`) WHERE b.b = 'B' RETURN b"); } @Test void shouldAppendToExistingUnions() { Statement statement1 = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("a").isEqualTo(Cypher.literalOf("A"))) .returning(BIKE_NODE) .build(); Statement statement2 = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("b").isEqualTo(Cypher.literalOf("B"))) .returning(BIKE_NODE) .build(); Statement statement; statement = Cypher.unionAll(statement1, statement2); Statement statement3 = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("c").isEqualTo(Cypher.literalOf("C"))) .returning(BIKE_NODE) .build(); Statement statement4 = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("d").isEqualTo(Cypher.literalOf("D"))) .returning(BIKE_NODE) .build(); statement = Cypher.unionAll(statement, statement3, statement4); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (b:`Bike`) WHERE b.a = 'A' RETURN b UNION ALL MATCH (b:`Bike`) WHERE b.b = 'B' RETURN b UNION ALL MATCH (b:`Bike`) WHERE b.c = 'C' RETURN b UNION ALL MATCH (b:`Bike`) WHERE b.d = 'D' RETURN b"); } @Test void shouldNotMix() { Statement statement1 = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("a").isEqualTo(Cypher.literalOf("A"))) .returning(BIKE_NODE) .build(); Statement statement2 = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("b").isEqualTo(Cypher.literalOf("B"))) .returning(BIKE_NODE) .build(); Statement statement; statement = Cypher.unionAll(statement1, statement2); Statement statement3 = Cypher.match(BIKE_NODE) .where(BIKE_NODE.property("c").isEqualTo(Cypher.literalOf("C"))) .returning(BIKE_NODE) .build(); // noinspection ResultOfMethodCallIgnored assertThatIllegalArgumentException().isThrownBy(() -> Cypher.union(statement, statement3)) .withMessage("Cannot mix union and union all!"); } } @Nested class MapProjections { @Test void asterisk() { Statement statement; Node n = Cypher.anyNode("n"); statement = Cypher.match(n).returning(n.project(Cypher.asterisk())).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (n) RETURN n{.*}"); } @SuppressWarnings("ResultOfMethodCallIgnored") @Test void invalid() { String expectedMessage = "FunctionInvocation{cypher=elementId(n)} of type class org.neo4j.cypherdsl.core.FunctionInvocation cannot be used with an implicit name as map entry."; Node n = Cypher.anyNode("n"); assertThatIllegalArgumentException().isThrownBy(() -> n.project(Cypher.elementId(n))) .withMessage(expectedMessage); assertThatIllegalArgumentException() .isThrownBy(() -> n.project("a", Cypher.mapOf("a", Cypher.literalOf("b")), Cypher.elementId(n))) .withMessage(expectedMessage); } @Nested class OnNodes { @Test void simple() { Statement statement; Node n = Cypher.anyNode("n"); statement = Cypher.match(n) .returning(n.project("__internalNeo4jId__", Cypher.elementId(n), "name")) .build(); var renderer = Renderer.getRenderer(NEO5J_CONFIG); assertThat(renderer.render(statement)) .isEqualTo("MATCH (n) RETURN n{__internalNeo4jId__: elementId(n), .name}"); statement = Cypher.match(n) .returning(n.project("name", "__internalNeo4jId__", Cypher.elementId(n))) .build(); assertThat(renderer.render(statement)) .isEqualTo("MATCH (n) RETURN n{.name, __internalNeo4jId__: elementId(n)}"); } @Test void doc21221() { String expected = "MATCH (actor:`Person` {name: 'Tom Hanks'})-[:`ACTED_IN`]->(movie:`Movie`) RETURN actor{.name, .realName, movies: collect(movie{.title, .released})}"; Statement statement; Node actor = Cypher.node("Person").named("actor"); Node movie = Cypher.node("Movie").named("movie"); statement = Cypher .match(actor.withProperties("name", Cypher.literalOf("Tom Hanks")) .relationshipTo(movie, "ACTED_IN")) .returning(actor.project("name", "realName", "movies", Cypher.collect(movie.project("title", "released")))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); statement = Cypher .match(actor.withProperties("name", Cypher.literalOf("Tom Hanks")) .relationshipTo(movie, "ACTED_IN")) .returning(actor.project("name", "realName", "movies", Cypher.collect(movie.project(movie.property("title"), movie.property("released"))))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); statement = Cypher .match(actor.withProperties("name", Cypher.literalOf("Tom Hanks")) .relationshipTo(movie, "ACTED_IN")) .returning(actor.project("name", "realName", "movies", Cypher.collect(movie.project("title", "year", movie.property("released"))))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (actor:`Person` {name: 'Tom Hanks'})-[:`ACTED_IN`]->(movie:`Movie`) RETURN actor{.name, .realName, movies: collect(movie{.title, year: movie.released})}"); } @Test void nested() { Statement statement; Node n = Cypher.node("Person").named("p"); Node m = Cypher.node("Movie").named("m"); statement = Cypher.match(n.relationshipTo(m, "ACTED_IN")) .returning(n.project("__internalNeo4jId__", Cypher.elementId(n), "name", "nested", m.project("title", "__internalNeo4jId__", Cypher.elementId(m)))) .build(); assertThat(Renderer.getRenderer(NEO5J_CONFIG).render(statement)).isEqualTo( "MATCH (p:`Person`)-[:`ACTED_IN`]->(m:`Movie`) RETURN p{__internalNeo4jId__: elementId(p), .name, nested: m{.title, __internalNeo4jId__: elementId(m)}}"); } @Test void requiredSymbolicNameShouldBeGenerated() { Node person = Cypher.node("Person"); Statement statement = Cypher.match(person).returning(person.project("something")).build(); assertThat(cypherRenderer.render(statement)) .matches("MATCH \\([a-zA-Z].*\\d{3}:`Person`\\) RETURN [a-zA-Z].*\\d{3}\\{\\.something}"); } @Test void generatedSymbolicNameMustPreventNodeContentDoubleRenderingAswWell() { Node person = Cypher.node("Person"); assertThat(person.getRequiredSymbolicName()).isNotNull(); Node movie = Cypher.node("Movie"); assertThat(movie.getRequiredSymbolicName()).isNotNull(); Relationship directed = person.relationshipTo(movie, "DIRECTED"); Relationship actedIn = person.relationshipTo(movie, "ACTED_IN"); Statement statement = Cypher.match(directed).match(actedIn).returning(directed, actedIn).build(); assertThat(cypherRenderer.render(statement)).matches( "MATCH \\(\\w+:`Person`\\)-\\[\\w+:`DIRECTED`]->\\(\\w+:`Movie`\\) MATCH \\(\\w+\\)-\\[\\w+:`ACTED_IN`]->\\(\\w+\\) RETURN \\w+, \\w+"); } @Test void addedProjections() { Statement statement; Node p = Cypher.node("Person").named("p"); Node m = Cypher.node("Movie").named("m"); Relationship rel = p.relationshipTo(m, "ACTED_IN").named("r"); statement = Cypher.match(rel) .returning(p.project("__internalNeo4jId__", Cypher.elementId(p), "name") .and(rel) .and(m) .and(p.property("foo")) .and("a", p.property("x"))) .build(); assertThat(Renderer.getRenderer(NEO5J_CONFIG).render(statement)).isEqualTo( "MATCH (p:`Person`)-[r:`ACTED_IN`]->(m:`Movie`) RETURN p{__internalNeo4jId__: elementId(p), .name, r, m, .foo, a: p.x}"); } } @Nested class OnRelationShips { @Test void simple() { Statement statement; Node n = Cypher.node("Person").named("p"); Node m = Cypher.node("Movie").named("m"); Relationship rel = n.relationshipTo(m, "ACTED_IN").named("r"); statement = Cypher.match(rel) .returning(rel.project("__internalNeo4jId__", Cypher.elementId(rel), "roles")) .build(); assertThat(Renderer.getRenderer(NEO5J_CONFIG).render(statement)).isEqualTo( "MATCH (p:`Person`)-[r:`ACTED_IN`]->(m:`Movie`) RETURN r{__internalNeo4jId__: elementId(r), .roles}"); } @Test void nested() { Statement statement; Node n = Cypher.node("Person").named("p"); Node m = Cypher.node("Movie").named("m"); Relationship rel = n.relationshipTo(m, "ACTED_IN").named("r"); statement = Cypher.match(rel) .returning(m.project("title", "roles", rel.project("__internalNeo4jId__", Cypher.elementId(rel), "roles"))) .build(); assertThat(Renderer.getRenderer(NEO5J_CONFIG).render(statement)).isEqualTo( "MATCH (p:`Person`)-[r:`ACTED_IN`]->(m:`Movie`) RETURN m{.title, roles: r{__internalNeo4jId__: elementId(r), .roles}}"); } @Test void requiredSymbolicNameShouldBeGenerated() { Node n = Cypher.node("Person"); Node m = Cypher.node("Movie"); Relationship rel = n.relationshipTo(m, "ACTED_IN"); Statement statement = Cypher.match(rel).returning(rel.project("something")).build(); assertThat(cypherRenderer.render(statement)).matches( "MATCH \\(:`Person`\\)-\\[[a-zA-Z]*\\d{3}:`ACTED_IN`]->\\(:`Movie`\\) RETURN [a-zA-Z]*\\d{3}\\{\\.something}"); } } } @Nested class WithAndOrder { @Test void orderOnWithShouldWork() { SortItem byTitle = Cypher.sort(Cypher.property("m", "title")); SortItem byName = Cypher.sort(Cypher.property("p", "name")); Supplier baseStatementSupplier = () -> Cypher .match(Cypher.node("Movie") .named("m") .relationshipFrom(Cypher.node("Person").named("p"), "ACTED_IN") .named("r")) .with(Cypher.name("m"), Cypher.name("p")); for (StatementBuilder.OrderableOngoingReadingAndWithWithWhere orderedStatement : new StatementBuilder.OrderableOngoingReadingAndWithWithWhere[] { baseStatementSupplier.get().orderBy(byTitle, byName), baseStatementSupplier.get().orderBy(Arrays.asList(byTitle, byName)) }) { Statement statement = orderedStatement .returning(Cypher.property("m", "title").as("movie"), Cypher.collect(Cypher.property("p", "name")).as("actors")) .build(); String expected = "MATCH (m:`Movie`)<-[r:`ACTED_IN`]-(p:`Person`) WITH m, p ORDER BY m.title, p.name RETURN m.title AS movie, collect(p.name) AS actors"; assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } } @Test void concatenatedOrdering() { Statement statement; statement = Cypher .match(Cypher.node("Movie") .named("m") .relationshipFrom(Cypher.node("Person").named("p"), "ACTED_IN") .named("r")) .with(Cypher.name("m"), Cypher.name("p")) .orderBy(Cypher.property("m", "title")) .ascending() .and(Cypher.property("p", "name")) .ascending() .returning(Cypher.property("m", "title").as("movie"), Cypher.collect(Cypher.property("p", "name")).as("actors")) .build(); String expected = "MATCH (m:`Movie`)<-[r:`ACTED_IN`]-(p:`Person`) WITH m, p ORDER BY m.title ASC, p.name ASC RETURN m.title AS movie, collect(p.name) AS actors"; assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } } @Nested class ListComprehensions { @Test void simple() { SymbolicName name = Cypher.name("a"); Statement statement = Cypher .returning(Cypher.listWith(name) .in(Cypher.listOf(Cypher.literalOf(1), Cypher.literalOf(2), Cypher.literalOf(3), Cypher.literalOf(4))) .returning()) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN [a IN [1, 2, 3, 4]]"); } @Test void withReturning() { SymbolicName name = Cypher.name("a"); Statement statement = Cypher .returning(Cypher.listWith(name) .in(Cypher.listOf(Cypher.literalOf(1), Cypher.literalOf(2), Cypher.literalOf(3), Cypher.literalOf(4))) .returning(name.remainder(Cypher.literalOf(2)))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN [a IN [1, 2, 3, 4] | (a % 2)]"); } @Test void withWhere() { SymbolicName name = Cypher.name("a"); Statement statement = Cypher .returning(Cypher.listWith(name) .in(Cypher.listOf(Cypher.literalOf(1), Cypher.literalOf(2), Cypher.literalOf(3), Cypher.literalOf(4))) .where(name.gt(Cypher.literalOf(2))) .returning()) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN [a IN [1, 2, 3, 4] WHERE a > 2]"); } @Test void withWhereAndReturning() { SymbolicName name = Cypher.name("a"); Statement statement = Cypher .returning(Cypher.listWith(name) .in(Cypher.listOf(Cypher.literalOf(1), Cypher.literalOf(2), Cypher.literalOf(3), Cypher.literalOf(4))) .where(name.gt(Cypher.literalOf(2))) .returning(name.remainder(Cypher.literalOf(2)))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN [a IN [1, 2, 3, 4] WHERE a > 2 | (a % 2)]"); } @Test // GH-189 void simpleWithCollection() { SymbolicName name = Cypher.name("a"); Expression[] expressions = { Cypher.literalOf(1), Cypher.literalOf(2), Cypher.literalOf(3), Cypher.literalOf(4) }; Statement statement = Cypher .returning(Cypher.listWith(name).in(Cypher.listOf(Arrays.asList(expressions))).returning()) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN [a IN [1, 2, 3, 4]]"); } @Test void docsExample() { SymbolicName name = Cypher.name("x"); Statement statement; statement = Cypher .returning(Cypher.listWith(name) .in(Cypher.range(Cypher.literalOf(0), Cypher.literalOf(10))) .where(name.remainder(Cypher.literalOf(2)).isEqualTo(Cypher.literalOf(0))) .returning(name.pow(Cypher.literalOf(3))) .as("result")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("RETURN [x IN range(0, 10) WHERE (x % 2) = 0 | x^3] AS result"); statement = Cypher .returning(Cypher.listWith(name) .in(Cypher.range(Cypher.literalOf(0), Cypher.literalOf(10))) .where(name.remainder(Cypher.literalOf(2)).isEqualTo(Cypher.literalOf(0))) .returning() .as("result")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("RETURN [x IN range(0, 10) WHERE (x % 2) = 0] AS result"); statement = Cypher .returning(Cypher.listWith(name) .in(Cypher.range(Cypher.literalOf(0), Cypher.literalOf(10))) .returning(name.pow(Cypher.literalOf(3))) .as("result")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN [x IN range(0, 10) | x^3] AS result"); } } @Nested class PatternComprehensions { @Test void simple() { Statement statement; Node a = Cypher.node("Person").withProperties("name", Cypher.literalOf("Keanu Reeves")).named("a"); Node b = Cypher.anyNode("b"); statement = Cypher.match(a) .returning(Cypher.listBasedOn(a.relationshipBetween(b)).returning(b.property("released")).as("years")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (a:`Person` {name: 'Keanu Reeves'}) RETURN [(a)--(b) | b.released] AS years"); } @Test void simpleWithWhere() { Statement statement; Node a = Cypher.node("Person").withProperties("name", Cypher.literalOf("Keanu Reeves")).named("a"); Node b = Cypher.anyNode("b"); statement = Cypher.match(a) .returning(Cypher.listBasedOn(a.relationshipBetween(b)) .where(b.hasLabels("Movie")) .returning(b.property("released")) .as("years")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (a:`Person` {name: 'Keanu Reeves'}) RETURN [(a)--(b) WHERE b:`Movie` | b.released] AS years"); } @Test void nested() { Statement statement; Node n = Cypher.node("Person").named("n"); Node o1 = Cypher.node("Organisation").named("o1"); Node l1 = Cypher.node("Location").named("l1"); Node p2 = Cypher.node("Person").named("p2"); Relationship r_f1 = n.relationshipTo(o1, "FOUNDED").named("r_f1"); Relationship r_e1 = n.relationshipTo(o1, "EMPLOYED_BY").named("r_e1"); Relationship r_l1 = n.relationshipTo(l1, "LIVES_AT").named("r_l1"); Relationship r_l2 = l1.relationshipFrom(p2, "LIVES_AT").named("r_l2"); statement = Cypher.match(n) .returning(n.getRequiredSymbolicName(), Cypher.listOf(Cypher.listBasedOn(r_f1).returning(r_f1, o1), Cypher.listBasedOn(r_e1).returning(r_e1, o1), Cypher.listBasedOn(r_l1) .returning(r_l1.getRequiredSymbolicName(), l1.getRequiredSymbolicName(), // The building of the statement works with // and without the outer list, // I'm not sure if it would be necessary for // the result, but as I took the query from // Neo4j-OGM, I'd like to keep it Cypher.listOf(Cypher.listBasedOn(r_l2).returning(r_l2, p2))))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n:`Person`) RETURN n, [[(n)-[r_f1:`FOUNDED`]->(o1:`Organisation`) | [r_f1, o1]], [(n)-[r_e1:`EMPLOYED_BY`]->(o1:`Organisation`) | [r_e1, o1]], [(n)-[r_l1:`LIVES_AT`]->(l1:`Location`) | [r_l1, l1, [[(l1)<-[r_l2:`LIVES_AT`]-(p2:`Person`) | [r_l2, p2]]]]]]"); } } @Nested class MultipleLabels { @Test void matchWithMultipleLabels() { Node node = Cypher.node("a", "b", "c").named("n"); Statement statement = Cypher.match(node).returning(node).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (n:`a`:`b`:`c`) RETURN n"); } @Test void createWithMultipleLabels() { Node node = Cypher.node("a", "b", "c").named("n"); Statement statement = Cypher.create(node).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("CREATE (n:`a`:`b`:`c`)"); } } @Nested class Case { @Test void simpleCase() { Node node = Cypher.node("a").named("n"); Statement statement = Cypher.match(node) .where(Cypher.caseExpression(node.property("value")) .when(Cypher.literalOf("blubb")) .then(Cypher.literalTrue()) .asCondition()) .returning(node) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (n:`a`) WHERE CASE n.value WHEN 'blubb' THEN true END RETURN n"); } @Test void simpleCaseWithElse() { Node node = Cypher.node("a").named("n"); Statement statement = Cypher.match(node) .where(Cypher.caseExpression(node.property("value")) .when(Cypher.literalOf("blubb")) .then(Cypher.literalTrue()) .elseDefault(Cypher.literalFalse()) .asCondition()) .returning(node) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (n:`a`) WHERE CASE n.value WHEN 'blubb' THEN true ELSE false END RETURN n"); } @Test void simpleCaseWithMultipleWhenThen() { Node node = Cypher.node("a").named("n"); Statement statement = Cypher.match(node) .where(Cypher.caseExpression(node.property("value")) .when(Cypher.literalOf("blubb")) .then(Cypher.literalTrue()) .when(Cypher.literalOf("bla")) .then(Cypher.literalFalse()) .asCondition()) .returning(node) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n:`a`) WHERE CASE n.value WHEN 'blubb' THEN true WHEN 'bla' THEN false END RETURN n"); } @Test void simpleCaseWithMultipleWhenThenAndElse() { Node node = Cypher.node("a").named("n"); Statement statement = Cypher.match(node) .where(Cypher.caseExpression(node.property("value")) .when(Cypher.literalOf("blubb")) .then(Cypher.literalTrue()) .when(Cypher.literalOf("bla")) .then(Cypher.literalFalse()) .elseDefault(Cypher.literalOf(1)) .asCondition()) .returning(node) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n:`a`) WHERE CASE n.value WHEN 'blubb' THEN true WHEN 'bla' THEN false ELSE 1 END RETURN n"); } @Test void genericCase() { Node node = Cypher.node("a").named("n"); Statement statement = Cypher.match(node) .where(Cypher.caseExpression() .when(node.property("value").isEqualTo(Cypher.literalOf("blubb"))) .then(Cypher.literalTrue()) .asCondition()) .returning(node) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (n:`a`) WHERE CASE WHEN n.value = 'blubb' THEN true END RETURN n"); } @Test void genericCaseWithElse() { Node node = Cypher.node("a").named("n"); Statement statement = Cypher.match(node) .where(Cypher.caseExpression() .when(node.property("value").isEqualTo(Cypher.literalOf("blubb"))) .then(Cypher.literalTrue()) .elseDefault(Cypher.literalFalse()) .asCondition()) .returning(node) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (n:`a`) WHERE CASE WHEN n.value = 'blubb' THEN true ELSE false END RETURN n"); } @Test void genericCaseWithMultipleWhenThen() { Node node = Cypher.node("a").named("n"); Statement statement = Cypher.match(node) .where(Cypher.caseExpression() .when(node.property("value").isEqualTo(Cypher.literalOf("blubb"))) .then(Cypher.literalTrue()) .when(node.property("value").isEqualTo(Cypher.literalOf("bla"))) .then(Cypher.literalFalse()) .asCondition()) .returning(node) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n:`a`) WHERE CASE WHEN n.value = 'blubb' THEN true WHEN n.value = 'bla' THEN false END RETURN n"); } @Test void genericCaseWithMultipleWhenThenAndElse() { Node node = Cypher.node("a").named("n"); Statement statement = Cypher.match(node) .where(Cypher.caseExpression() .when(node.property("value").isEqualTo(Cypher.literalOf("blubb"))) .then(Cypher.literalTrue()) .when(node.property("value").isEqualTo(Cypher.literalOf("bla"))) .then(Cypher.literalFalse()) .elseDefault(Cypher.literalOf(1)) .asCondition()) .returning(node) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n:`a`) WHERE CASE WHEN n.value = 'blubb' THEN true WHEN n.value = 'bla' THEN false ELSE 1 END RETURN n"); } // from // https://neo4j.com/docs/cypher-manual/current/syntax/expressions/#syntax-simple-case @Test void canGetAliasedInReturn() { Node node = Cypher.anyNode("n"); Statement statement = Cypher.match(node) .returning(Cypher.caseExpression(node.property("eyes")) .when(Cypher.literalOf("blue")) .then(Cypher.literalOf(1)) .when(Cypher.literalOf("brown")) .then(Cypher.literalOf(2)) .elseDefault(Cypher.literalOf(3)) .as("result")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (n) RETURN CASE n.eyes WHEN 'blue' THEN 1 WHEN 'brown' THEN 2 ELSE 3 END AS result"); } } @Nested class NamedPaths { @Test void selectorShouldBeSensible() { var patternElement = Cypher.node("M").relationshipFrom(Cypher.node("N")).named("r"); var namedPath = NamedPath.select(PatternSelector.any(), patternElement); assertThat(RendererBridge.render(namedPath)) .matches("NamedPath\\{cypher=.+ = ANY \\(:M\\)<-\\[r]-\\(:N\\)}"); } @Test void doc3148() { // See docs NamedPath p = Cypher.path("p") .definedBy(Cypher.anyNode("michael") .withProperties("name", Cypher.literalOf("Michael Douglas")) .relationshipTo(Cypher.anyNode())); Statement statement = Cypher.match(p).returning(p).build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH p = (michael {name: 'Michael Douglas'})-->() RETURN p"); } @Test void shouldWorkInListComprehensions() { NamedPath p = Cypher.path("p") .definedBy(Cypher.anyNode("n").relationshipTo(Cypher.anyNode(), "LIKES", "OWNS").unbounded()); Statement statement = Cypher.returning(Cypher.listBasedOn(p).returning(p)).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN [p = (n)-[:`LIKES`|`OWNS`*]->() | p]"); } @Test // GH-200 void shouldWorkWithRelationshipPatterns() { RelationshipPattern relationshipPattern = Cypher.anyNode("n").relationshipTo(Cypher.anyNode("m")); NamedPath p = Cypher.path("p").definedBy(relationshipPattern); Statement statement = Cypher.match(p).returning(p).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH p = (n)-->(m) RETURN p"); } @Test // GH-200 void shouldWorkWithNodes() { NamedPath p = Cypher.path("p").definedBy(Cypher.anyNode("n")); Statement statement = Cypher.match(p).returning(p).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH p = (n) RETURN p"); } @Test // GH-200 void shouldDirectlyUseProvidedNamedPaths() { NamedPath p = Cypher.path("p").definedBy(Cypher.anyNode("n")); NamedPath x = Cypher.path("x").definedBy(p); Statement statement = Cypher.match(x).returning(p).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH p = (n) RETURN p"); } } @Nested class Predicatez { @Test void allShouldWork() { NamedPath p = Cypher.path("p") .definedBy(Cypher.anyNode("a").relationshipTo(Cypher.anyNode("b")).min(1).max(3)); Statement statement = Cypher.match(p) .where(Cypher.property("a", "name").isEqualTo(Cypher.literalOf("Alice"))) .and(Cypher.property("b", "name").isEqualTo(Cypher.literalOf("Daniel"))) .and(Cypher.all("x").in(Cypher.nodes(p)).where(Cypher.property("x", "age").gt(Cypher.literalOf(30)))) .returning(p) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH p = (a)-[*1..3]->(b) WHERE (a.name = 'Alice' AND b.name = 'Daniel' AND all(x IN nodes(p) WHERE x.age > 30)) RETURN p"); } @Test void anyShouldWork() { Node a = Cypher.anyNode("a"); Statement statement = Cypher.match(a) .where(Cypher.property("a", "name").isEqualTo(Cypher.literalOf("Eskil"))) .and(Cypher.any("x").in(a.property("array")).where(Cypher.name("x").isEqualTo(Cypher.literalOf("one")))) .returning(a.property("name"), a.property("array")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (a) WHERE (a.name = 'Eskil' AND any(x IN a.array WHERE x = 'one')) RETURN a.name, a.array"); } @Test void noneShouldWork() { NamedPath p = Cypher.path("p") .definedBy(Cypher.anyNode("a").relationshipTo(Cypher.anyNode("b")).min(1).max(3)); Statement statement = Cypher.match(p) .where(Cypher.property("a", "name").isEqualTo(Cypher.literalOf("Alice"))) .and(Cypher.none("x") .in(Cypher.nodes(p)) .where(Cypher.property("x", "age").isEqualTo(Cypher.literalOf(25)))) .returning(p) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH p = (a)-[*1..3]->(b) WHERE (a.name = 'Alice' AND none(x IN nodes(p) WHERE x.age = 25)) RETURN p"); } @Test void singleShouldWork() { NamedPath p = Cypher.path("p").definedBy(Cypher.anyNode("n").relationshipTo(Cypher.anyNode("b"))); Statement statement = Cypher.match(p) .where(Cypher.property("n", "name").isEqualTo(Cypher.literalOf("Alice"))) .and(Cypher.single("var") .in(Cypher.nodes(p)) .where(Cypher.property("var", "eyes").isEqualTo(Cypher.literalOf("blue")))) .returning(p) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH p = (n)-->(b) WHERE (n.name = 'Alice' AND single(var IN nodes(p) WHERE var.eyes = 'blue')) RETURN p"); } } @Nested class ListOperator { @Test void valueAtShouldWork() { Statement statement = Cypher.returning(Cypher.valueAt(Cypher.range(0, 10), 3)).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN range(0, 10)[3]"); } @Test void subListUntilShouldWork() { Statement statement = Cypher.returning(Cypher.subListUntil(Cypher.range(0, 10), 3)).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN range(0, 10)[..3]"); } @Test void subListFromShouldWork() { Statement statement = Cypher.returning(Cypher.subListFrom(Cypher.range(0, 10), -3)).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN range(0, 10)[-3..]"); } @Test void subListShouldWork() { Statement statement = Cypher.returning(Cypher.subList(Cypher.range(0, 10), 2, 4)).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN range(0, 10)[2..4]"); } @Test void subListUntilExpressionShouldWork() { Statement statement = Cypher.returning(Cypher.subListUntil(Cypher.range(0, 10), Cypher.parameter("end"))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN range(0, 10)[..$end]"); } @Test void subListFromExpressionShouldWork() { Statement statement = Cypher.returning(Cypher.subListFrom(Cypher.range(0, 10), Cypher.parameter("start"))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN range(0, 10)[$start..]"); } @Test void subListExpressionShouldWork() { Statement statement = Cypher .returning(Cypher.subList(Cypher.range(0, 10), Cypher.parameter("start"), Cypher.parameter("end"))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN range(0, 10)[$start..$end]"); } @Test void shouldWorkWithMapProjections() { Node person = Cypher.node("Person").named("person"); Node location = Cypher.node("Location").named("personLivesIn"); Statement statement = Cypher.match(person) .returning(person.project("livesIn", Cypher.valueAt(Cypher.listBasedOn(person.relationshipTo(location, "LIVES_IN")) .returning(location.project("name")), 0))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (person:`Person`) RETURN person{livesIn: [(person)-[:`LIVES_IN`]->(personLivesIn:`Location`) | personLivesIn{.name}][0]}"); } @Test void shouldSupportExpressions() { Node person = Cypher.node("Person").named("person"); Node location = Cypher.node("Location").named("personLivesIn"); Statement statement = Cypher.match(person) .returning(person.project("livesIn", Cypher.subList( Cypher.listBasedOn(person.relationshipTo(location, "LIVES_IN")) .returning(location.project("name")), Cypher.parameter("personLivedInOffset"), Cypher.parameter("personLivedInOffset").add(Cypher.parameter("personLivedInFirst"))))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (person:`Person`) RETURN person{livesIn: [(person)-[:`LIVES_IN`]->(personLivesIn:`Location`) | personLivesIn{.name}][$personLivedInOffset..($personLivedInOffset + $personLivedInFirst)]}"); } @Test void propertiesShouldBeAccessibleOnResolvedNodes() { var nodes = Cypher.name("nodes"); var cypher = Cypher.match(Cypher.node("Foo").named("n")) .with(Cypher.collect(Cypher.name("n")).as("nodes")) .returning(Cypher.property(Cypher.valueAt(nodes, 0), "foo")) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (n:`Foo`) WITH collect(n) AS nodes RETURN nodes[0].foo"); } } @Nested class DoubleRendering { @Test void fragmentOfStatementShouldBeReusable() { Node personNode = Cypher.node("Person").named("p"); Property ageProperty = personNode.property("age"); StatementBuilder.OngoingReadingAndReturn returning = Cypher.match(personNode).returning("p"); Statement s1 = returning.orderBy(ageProperty.ascending()).limit(1).build(); Statement s2 = returning.orderBy(ageProperty.descending()).limit(1).build(); assertThat(cypherRenderer.render(s1)).isEqualTo("MATCH (p:`Person`) RETURN p ORDER BY p.age ASC LIMIT 1"); assertThat(cypherRenderer.render(s2)).isEqualTo("MATCH (p:`Person`) RETURN p ORDER BY p.age DESC LIMIT 1"); } @Test void aliasedFunctionsShouldNotBeRenderedTwiceInProjection() { Node o = Cypher.node("Order").named("o"); Node li = Cypher.node("LineItem").named("li"); Relationship hasLineItems = o.relationshipTo(li).named("h"); var netAmount = Cypher.sum(li.property("price").multiply(li.property("quantity"))).as("netAmount"); var totalAmount = netAmount.multiply(Cypher.literalOf(1).add(Cypher.parameter("taxRate"))) .as("totalAmount"); Statement statement = Cypher.match(hasLineItems) .where(o.property("id").isEqualTo(Cypher.parameter("id"))) .with(o.getRequiredSymbolicName(), netAmount, totalAmount) .returning(o.project(o.property("x"), netAmount, totalAmount, netAmount.multiply(Cypher.parameter("taxRate")).as("taxAmount"))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (o:`Order`)-[h]->(li:`LineItem`) WHERE o.id = $id WITH o, sum((li.price * li.quantity)) AS netAmount, (netAmount * (1 + $taxRate)) AS totalAmount RETURN o{.x, netAmount: netAmount, totalAmount: totalAmount, taxAmount: (netAmount * $taxRate)}"); } @Test void aliasedFunctionsShouldNotBeRenderedTwiceInReturn() { Node o = Cypher.node("Order").named("o"); Node li = Cypher.node("LineItem").named("li"); Relationship hasLineItems = o.relationshipTo(li).named("h"); var netAmount = Cypher.sum(li.property("price").multiply(li.property("quantity"))).as("netAmount"); var totalAmount = netAmount.multiply(Cypher.literalOf(1).add(Cypher.parameter("taxRate"))) .as("totalAmount"); Statement statement = Cypher.match(hasLineItems) .where(o.property("id").isEqualTo(Cypher.parameter("id"))) .with(o.getRequiredSymbolicName(), netAmount, totalAmount) .returning(netAmount, totalAmount) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (o:`Order`)-[h]->(li:`LineItem`) WHERE o.id = $id WITH o, sum((li.price * li.quantity)) AS netAmount, (netAmount * (1 + $taxRate)) AS totalAmount RETURN netAmount, totalAmount"); } } @Nested class Foreach { @Test void basic() { var start = Cypher.anyNode("start"); var finish = Cypher.anyNode("finish"); var p = Cypher.path("p").definedBy(start.relationshipTo(finish).unbounded()); var n = Cypher.name("n"); var stmnt = Cypher.match(p) .where(start.property("name") .eq(Cypher.literalOf("A")) .and(finish.property("name").eq(Cypher.literalOf("D")))) .foreach(n) .in(Cypher.nodes(p)) .apply(Set.set(n.property("marked").to(Cypher.literalTrue()))) .build(); assertThat(stmnt.getCypher()) .isEqualTo("MATCH p = (start)-[*]->(finish) " + "WHERE (start.name = 'A' AND finish.name = 'D') " + "FOREACH (n IN nodes(p) | SET n.marked = true)"); } @Test void mixedWithOtherClauses() { var start = Cypher.anyNode("start"); var finish = Cypher.anyNode("finish"); var p = Cypher.path("p").definedBy(start.relationshipTo(finish).unbounded()); var n = Cypher.name("n"); var stmnt = Cypher.match(p) .where(start.property("name") .eq(Cypher.literalOf("A")) .and(finish.property("name").eq(Cypher.literalOf("D")))) .set(start.property("x").to(Cypher.literalTrue())) .foreach(n) .in(Cypher.nodes(p)) .apply(Set.set(n.property("marked").to(Cypher.literalTrue()), n.property("foo").to(Cypher.literalOf("bar")))) .delete(finish) .build(); assertThat(stmnt.getCypher()).isEqualTo("MATCH p = (start)-[*]->(finish) " + "WHERE (start.name = 'A' AND finish.name = 'D') " + "SET start.x = true " + "FOREACH (n IN nodes(p) | SET n.marked = true, n.foo = 'bar') " + "DELETE finish"); } @Test void withWith() { var start = Cypher.anyNode("start"); var n = Cypher.name("n"); var stmnt = Cypher.match(start) .with(start) .foreach(n) .in(Cypher.listOf(start.asExpression())) .apply(Delete.delete(n)) .build(); // Probably the worst way to delete a graph I ever wrote down assertThat(stmnt.getCypher()).isEqualTo("MATCH (start) WITH start FOREACH (n IN [start] | DELETE n)"); } @Test void inComplexStatement() { var configNode = Cypher.node("confignode").withProperties("oid", Cypher.parameter("oid")).named("p"); var nodes = Cypher.name("nodes"); var node = Cypher.name("node"); var relationships = Cypher.name("relationships"); var stmnt = Cypher.match(configNode) .call("apoc.path.subgraphAll") .withArgs(configNode.getRequiredSymbolicName(), Cypher.mapOf("relationshipFilter", Cypher.literalOf("BELONGS_TO_ARRAY|IN|IN_ARRAY"), "minLevel", Cypher.literalOf(1), "maxLevel", Cypher.literalOf(3))) .yield(nodes, relationships) .foreach(node) .in(nodes) .apply(Delete.detachDelete(node)) .returning(nodes) .build(); var prettyPrintingRenderer = Renderer.getRenderer(Configuration.prettyPrinting()); assertThat(prettyPrintingRenderer.render(stmnt)).isEqualTo(""" MATCH (p:confignode { oid: $oid }) CALL apoc.path.subgraphAll(p, { relationshipFilter: 'BELONGS_TO_ARRAY|IN|IN_ARRAY', minLevel: 1, maxLevel: 3 }) YIELD nodes, relationships FOREACH (node IN nodes | DETACH DELETE node) RETURN nodes"""); } } @Nested class PrettyPrinting { private final Statement statement; PrettyPrinting() { Node otherNode = Cypher.anyNode("other"); this.statement = Cypher.match(USER_NODE) .where(USER_NODE.property("name").isEqualTo(Cypher.literalOf("Max"))) .and(USER_NODE.property("lastName").isEqualTo(Cypher.literalOf("Mustermann"))) .and(Cypher.match(USER_NODE.relationshipTo(BIKE_NODE, "LIKES")) .where(BIKE_NODE.relationshipTo(Cypher.anyNode(), "LINK")) .or(Cypher.match(BIKE_NODE.relationshipTo(Cypher.anyNode(), "LINK")).asCondition()) .asCondition()) .set(USER_NODE.property("lastName").to(Cypher.parameter("newName"))) .with(USER_NODE) .match(BIKE_NODE) .create(USER_NODE.relationshipTo(BIKE_NODE, "LIKES")) .with(USER_NODE) .call(Cypher.with(USER_NODE) .match(USER_NODE.relationshipTo(Cypher.anyNode("x"), "SOMETHING")) .call(Cypher.with(Cypher.anyNode("x")) .match(Cypher.anyNode("x").relationshipTo(Cypher.anyNode("y"), "DEEPER")) .returning(Cypher.anyNode("y").project("bar").as("bar")) .build()) .returning(Cypher.anyNode("x").project("foo", "bar", Cypher.name("bar")).as("anyThing")) .build()) .with(USER_NODE) .call(Cypher.use("movies.actors", Cypher.match(Cypher.node("Person").named("person")).returning("person").build())) .with(USER_NODE) .returning(USER_NODE.project("name", USER_NODE.property("name"), "anyThing", Cypher.name("anyThing"), "nesting1", Cypher.mapOf("name", USER_NODE.property("name"), "nesting2", Cypher.mapOf("name", BIKE_NODE.property("name"), "pattern", Cypher.listBasedOn(USER_NODE.relationshipTo(otherNode, "LIKES")) .where(otherNode.property("foo").isEqualTo(Cypher.parameter("foo"))) .returning(otherNode.project("x", "y")))))) .build(); } @Test void escapingNamesShouldBeOptionalInNonPrettyPrintToo() { Renderer renderer = Renderer.getRenderer(Configuration.newConfig().alwaysEscapeNames(false).build()); assertThat(renderer.render(Cypher.match(Cypher.node("Fine").named("a")).returning("a").build())) .isEqualTo("MATCH (a:Fine) RETURN a"); assertThat(renderer.render(Cypher.match(Cypher.node("Not Fine").named("a")).returning("a").build())) .isEqualTo("MATCH (a:`Not Fine`) RETURN a"); } @Test void configurationOfIndentWithShouldWork() { assertThat(Renderer.getRenderer(Configuration.newConfig().withPrettyPrint(true).withIndentSize(5).build()) .render(Cypher.match(Cypher.node("Node")) .where(Cypher.isTrue()) .and(Cypher.isFalse()) .returning(Cypher.asterisk()) .build())) .isEqualTo(""" MATCH (:Node) WHERE (true AND false) RETURN *"""); } @Test void prettyPrintingWithDefaultSettingShouldWork() { assertThat(Renderer.getRenderer(Configuration.prettyPrinting()).render(this.statement)).isEqualTo(""" MATCH (u:User) WHERE (u.name = 'Max' AND u.lastName = 'Mustermann' AND EXISTS { MATCH (u)-[:LIKES]->(b:Bike) WHERE ((b)-[:LINK]->() OR EXISTS { MATCH (b)-[:LINK]->() }) }) SET u.lastName = $newName WITH u MATCH (b:Bike) CREATE (u)-[:LIKES]->(b) WITH u CALL (u) { MATCH (u)-[:SOMETHING]->(x) CALL (x) { MATCH (x)-[:DEEPER]->(y) RETURN y { .bar } AS bar } RETURN x { .foo, bar: bar } AS anyThing } WITH u CALL (*) { USE movies.actors MATCH (person:Person) RETURN person } WITH u RETURN u { name: u.name, anyThing: anyThing, nesting1: { name: u.name, nesting2: { name: b.name, pattern: [(u)-[:LIKES]->(other) WHERE other.foo = $foo | other { .x, .y }] } } }"""); } @Test void prettyPrintingWithTabsShouldWork() { assertThat(Renderer .getRenderer(Configuration.newConfig() .withPrettyPrint(true) .withIndentStyle(Configuration.IndentStyle.TAB) .build()) .render(this.statement)).isEqualTo(""" MATCH (u:User) WHERE (u.name = 'Max' \tAND u.lastName = 'Mustermann' \tAND EXISTS { \t\tMATCH (u)-[:LIKES]->(b:Bike) \t\tWHERE ((b)-[:LINK]->() \t\t\tOR EXISTS { \t\t\t\tMATCH (b)-[:LINK]->() \t\t\t}) \t}) SET u.lastName = $newName WITH u MATCH (b:Bike) CREATE (u)-[:LIKES]->(b) WITH u CALL (u) { \tMATCH (u)-[:SOMETHING]->(x) \tCALL (x) { \t\tMATCH (x)-[:DEEPER]->(y) \t\tRETURN y { \t\t\t.bar \t\t} AS bar \t} \tRETURN x { \t\t.foo, \t\tbar: bar \t} AS anyThing } WITH u CALL (*) { \tUSE movies.actors \tMATCH (person:Person) \tRETURN person } WITH u RETURN u { \tname: u.name, \tanyThing: anyThing, \tnesting1: { \t\tname: u.name, \t\tnesting2: { \t\t\tname: b.name, \t\t\tpattern: [(u)-[:LIKES]->(other) WHERE other.foo = $foo | other { \t\t\t\t.x, \t\t\t\t.y \t\t\t}] \t\t} \t} }"""); } /** * See * https://neo4j.com/docs/cypher-manual/current/styleguide/#cypher-styleguide-indentation-and-line-breaks */ @Test void onClauses() { Node n = Cypher.anyNode("n"); Node a = Cypher.node("A").named("a"); Node b = Cypher.node("B").named("b"); Statement mergeStatement = Cypher.merge(n) .onCreate() .set(n.property("prop").to(Cypher.literalOf(0))) .merge(a.relationshipBetween(b, "T")) .onCreate() .set(a.property("name").to(Cypher.literalOf("me"))) .onMatch() .set(b.property("name").to(Cypher.literalOf("you"))) .returning(a.property("prop")) .build(); assertThat(Renderer.getRenderer(Configuration.prettyPrinting()).render(mergeStatement)).isEqualTo(""" MERGE (n) ON CREATE SET n.prop = 0 MERGE (a:A)-[:T]-(b:B) ON CREATE SET a.name = 'me' ON MATCH SET b.name = 'you' RETURN a.prop"""); } @Test void singleExistsSubquery() { Node a = Cypher.node("A").named("a"); Node b = Cypher.node("B").named("b"); Statement mergeStatement = Cypher.match(a) .where(Cypher.match(a.relationshipTo(b)).asCondition()) .returning(a) .build(); assertThat(Renderer.getRenderer(Configuration.prettyPrinting()).render(mergeStatement)).isEqualTo(""" MATCH (a:A) WHERE EXISTS { MATCH (a)-->(b:B) } RETURN a"""); } @Test void multipleSubQueries() { Node a = Cypher.node("A").named("a"); Node b = Cypher.node("B").named("b"); SymbolicName c = Cypher.name("c"); Statement mergeStatement = Cypher.call(Cypher.create(a).returning(a).build()) .call(Cypher.create(b).returning(b).build()) .unwind(Cypher.listOf(a.getRequiredSymbolicName(), b.getRequiredSymbolicName())) .as(c) .returning(c.project("name")) .build(); assertThat(Renderer.getRenderer(Configuration.prettyPrinting()).render(mergeStatement)).isEqualTo(""" CALL () { CREATE (a:A) RETURN a } CALL (*) { CREATE (b:B) RETURN b } UNWIND [a, b] AS c RETURN c { .name }"""); } @Test void prettyPrintingQueryStartingWithSubquery() { String rawQuery = "MATCH (node) RETURN node"; SymbolicName node = Cypher.name("node"); ResultStatement resultStatement = Cypher.call(Cypher.returningRaw(Cypher.raw(rawQuery).as(node)).build()) .returning(node) .build(); assertThat(Renderer.getRenderer(Configuration.prettyPrinting()).render(resultStatement)).isEqualTo(""" CALL () { MATCH (node) RETURN node AS node } RETURN node"""); } } // GH-858 @Nested class ConditionalPatterns { @Test void nodesWithWhere() { var node = Cypher.node("Movie").withProperties(Map.of("title", "The Matrix")).named("n"); var cypher = Cypher.match(node.where(node.property("released").eq(Cypher.literalOf(1999)))) .returning(node) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (n:`Movie` {title: 'The Matrix'} WHERE n.released = 1999) RETURN n"); } @Test void relationshipsWithWhere() { var node = Cypher.node("Movie").withProperties(Map.of("title", "The Matrix")).named("n"); var actor = Cypher.node("Actor"); var cypher = Cypher .match(node.relationshipFrom(actor, "ACTED_IN") .named("r") .where(Cypher.property("r", "role").eq(Cypher.literalOf("Neo4j")))) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo( "MATCH (n:`Movie` {title: 'The Matrix'})<-[r:`ACTED_IN` WHERE r.role = 'Neo4j']-(:`Actor`) RETURN *"); } } @Nested class QuantifiedPathPatterns { RelationshipPattern relationshipPattern = Cypher.node("A") .named("a") .relationshipTo(Cypher.node("B").named("b"), "X"); @Test void quantifyShouldWork() { var cypher = Cypher.match(this.relationshipPattern.quantify(QuantifiedPathPattern.interval(1, 3))) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH ((a:`A`)-[:`X`]->(b:`B`)){1,3} RETURN *"); } @Test void nestingInBooleanExpressionIsAllowed() { var pattern = Cypher.anyNode("n") .relationshipTo(Cypher.node("X")) .quantifyRelationship(QuantifiedPathPattern.plus()); var innerRel = Cypher.node("A") .named("n") .relationshipTo(Cypher.anyNode().withProperties(Map.of("p", 30)), "R") .quantify(QuantifiedPathPattern.interval(2, 3)) .where(Cypher.exists(pattern)); var cypher = Cypher.match(innerRel).returning(Cypher.asterisk()).build().getCypher(); assertThat(cypher) .isEqualTo("MATCH ((n:`A`)-[:`R`]->( {p: 30}) WHERE EXISTS { (n)-->+(:`X`) }){2,3} RETURN *"); } @Test void betweenMAndNIterations() { var cypher = Cypher .match(this.relationshipPattern.quantifyRelationship(QuantifiedPathPattern.interval(1, 3))) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (a:`A`)-[:`X`]->{1,3}(b:`B`) RETURN *"); } @Test void oneOrMoreIterations() { var cypher = Cypher .match(this.relationshipPattern.quantifyRelationship(QuantifiedPathPattern.interval(1, null))) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (a:`A`)-[:`X`]->{1,}(b:`B`) RETURN *"); } @Test void zeroOrMoreIterations() { var cypher = Cypher .match(this.relationshipPattern.quantifyRelationship(QuantifiedPathPattern.interval(0, null))) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (a:`A`)-[:`X`]->{0,}(b:`B`) RETURN *"); } @Test void nIterations() { var cypher = Cypher .match(this.relationshipPattern.quantifyRelationship(QuantifiedPathPattern.interval(1, 1))) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (a:`A`)-[:`X`]->{1,1}(b:`B`) RETURN *"); } @Test void zeroOrMore() { var cypher = Cypher .match(this.relationshipPattern.quantifyRelationship(QuantifiedPathPattern.interval(null, null))) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (a:`A`)-[:`X`]->{0,}(b:`B`) RETURN *"); } @Test void between0AndNIterations() { var cypher = Cypher .match(this.relationshipPattern.quantifyRelationship(QuantifiedPathPattern.interval(null, 3))) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (a:`A`)-[:`X`]->{0,3}(b:`B`) RETURN *"); } @Test void invalidLower() { assertThatIllegalArgumentException().isThrownBy(() -> QuantifiedPathPattern.interval(-1, null)) .withMessage("Lower bound must be greater than or equal to zero"); } @Test void invalidLower1() { assertThatIllegalArgumentException().isThrownBy(() -> QuantifiedPathPattern.interval(null, 0)) .withMessage("Upper bound must be greater than zero"); } @Test void invalidLower2() { assertThatIllegalArgumentException().isThrownBy(() -> QuantifiedPathPattern.interval(2, 1)) .withMessage("Upper bound must be greater than or equal to 2"); assertThatNoException().isThrownBy(() -> QuantifiedPathPattern.interval(2, 2)); } } @Nested class Call { @Test void simpleCallRawCypher() { var cypher = Cypher.callRawCypher("MATCH (n:Test) WHERE n.id = $id RETURN id(n) as a, n.id as b") .build() .getCypher(); assertThat(cypher).isEqualTo("CALL () {MATCH (n:Test) WHERE n.id = $id RETURN id(n) as a, n.id as b}"); } @Test void rawCypherWithWhereAndReturn() { var cypher = Cypher.callRawCypher("MATCH (n:Test) RETURN id(n) as a, n.id as b, n.timestamp as timestamp") .with(Cypher.asterisk()) .where(Cypher.gt(Cypher.raw("timestamp"), Cypher.parameter("from")) .and(Cypher.lte(Cypher.raw("timestamp"), Cypher.parameter("to")))) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo( "CALL () {MATCH (n:Test) RETURN id(n) as a, n.id as b, n.timestamp as timestamp} WITH * WHERE (timestamp > $from AND timestamp <= $to) RETURN *"); } } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/CypherTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.fail; /** * @author Michael J. Simons */ class CypherTests { @Test void sortDirectionShouldBeSpecified() { SortItem sortItem = Cypher.sort(Cypher.literalFalse(), SortItem.Direction.ASC); sortItem.accept(segment -> { if (segment instanceof SortItem.Direction direction) { assertThat(direction).extracting(SortItem.Direction::getSymbol).isEqualTo("ASC"); } }); } @Test @SuppressWarnings("ResultOfMethodCallIgnored") void shouldNotCreateIllegalLiterals() { assertThatIllegalArgumentException().isThrownBy(() -> Cypher.literalOf(new CypherTests())) .withMessageStartingWith("Unsupported literal type: "); } @Test void nullLiteralShouldBeSameInstance() { assertThat(Cypher.literalNull()).isSameAs(NullLiteral.INSTANCE); } @Test void shouldCreateListLiterals() { List> params = new ArrayList<>(); params.add(Cypher.literalFalse()); params.add(Cypher.literalTrue()); Literal listLiteral = Cypher.literalOf(params); assertThat(listLiteral).isInstanceOf(ListLiteral.class).returns("[false, true]", Literal::asString); } @Test void shouldCreateMapLiterals() { Map> params = new LinkedHashMap<>(); Map mapValue = new LinkedHashMap<>(); mapValue.put("m1", 1); mapValue.put("m2", "m2"); params.put('k', Cypher.literalFalse()); // Character key also valid params.put("k2", Cypher.literalTrue()); params.put("k3", Cypher.literalOf(Arrays.asList("a", "b", "c"))); params.put("k4", Cypher.literalOf(mapValue)); Literal mapLiteral = Cypher.literalOf(params); assertThat(mapLiteral).isInstanceOf(MapLiteral.class) .returns("{k: false, k2: true, k3: ['a', 'b', 'c'], k4: {m1: 1, m2: 'm2'}}", Literal::asString); } @Test void shouldQuoteStrings() { assertThat(Cypher.quote("foo")).isEqualTo("'foo'"); assertThat(Cypher.quote("fo`o")).isEqualTo("'fo`o'"); } @Test void shouldCreatePropertyPointingToSymbolicName() { Property property = Cypher.property("a", "b"); AtomicInteger counter = new AtomicInteger(0); property.accept(segment -> { int cnt = counter.incrementAndGet(); switch (cnt) { case 1 -> assertThat(segment).isInstanceOf(Property.class); case 2 -> assertThat(segment).isInstanceOf(SymbolicName.class).extracting("value").isEqualTo("a"); case 3 -> assertThat(segment).isInstanceOf(PropertyLookup.class); case 4 -> assertThat(segment).isInstanceOf(SymbolicName.class).extracting("value").isEqualTo("b"); default -> fail("Unexpected segment: " + segment.getClass()); } }); } @Test // GH-189 void shouldCreatePropertyWithNameFromCollectionPointingToSymbolicName() { Property property = Cypher.property("a", Collections.singleton("b")); AtomicInteger counter = new AtomicInteger(0); property.accept(segment -> { int cnt = counter.incrementAndGet(); switch (cnt) { case 1 -> assertThat(segment).isInstanceOf(Property.class); case 2 -> assertThat(segment).isInstanceOf(SymbolicName.class).extracting("value").isEqualTo("a"); case 3 -> assertThat(segment).isInstanceOf(PropertyLookup.class); case 4 -> assertThat(segment).isInstanceOf(SymbolicName.class).extracting("value").isEqualTo("b"); default -> fail("Unexpected segment: " + segment.getClass()); } }); } @Test // GH-189 void shouldCreateAdditionalLabelsFromCollection() { Node node = Cypher.node("Primary", MapExpression.create(false), Collections.singleton("Secondary")); assertThat(node.getLabels()).extracting("value").containsExactly("Primary", "Secondary"); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/DefaultStatementBuilderTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Collection; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * @author Michael J. Simons */ class DefaultStatementBuilderTests { @Test void matchPreconditionsShouldBeAsserted() { DefaultStatementBuilder builder = new DefaultStatementBuilder(); assertThatIllegalArgumentException().isThrownBy(() -> builder.match((PatternElement[]) null)) .withMessage("Patterns to match are required."); assertThatIllegalArgumentException().isThrownBy(() -> builder.match()) .withMessage("At least one pattern to match is required."); } @Nested class MessageBundles { @Test void shouldUseMessageBundleOnNullExpressions() { DefaultStatementBuilder builder = new DefaultStatementBuilder(); assertThatIllegalArgumentException().isThrownBy(() -> builder.returning((Collection) null)) .withMessage("Expressions to return are required."); assertThatIllegalArgumentException().isThrownBy(() -> builder.with((Collection) null)) .withMessage("Expressions to return are required."); StatementBuilder.BuildableMatchAndUpdate update = builder.set(Cypher.node("N").named("n"), "x"); assertThatIllegalArgumentException().isThrownBy(() -> update.returning((Collection) null)) .withMessage("Expressions to return are required."); } @Test void shouldUseMessageBundleOnEmptyExpressions() { DefaultStatementBuilder builder = new DefaultStatementBuilder(); assertThatIllegalArgumentException().isThrownBy(() -> builder.returning((Expression) null)) .withMessage("At least one expressions to return is required."); assertThatIllegalArgumentException().isThrownBy(() -> builder.with((IdentifiableElement) null)) .withMessage("At least one expressions to return is required."); StatementBuilder.BuildableMatchAndUpdate update = builder.set(Cypher.node("N").named("n"), "x"); assertThatIllegalArgumentException().isThrownBy(() -> update.returning((Expression) null)) .withMessage("At least one expressions to return is required."); } } @Nested class ReturningPreconditionsShouldBeAsserted { @Test void forExpressions() { DefaultStatementBuilder builder = new DefaultStatementBuilder(); assertThatIllegalArgumentException().isThrownBy(() -> builder.returning((Expression[]) null)) .withMessage("Expressions to return are required."); assertThatIllegalArgumentException().isThrownBy(() -> builder.returning(new Expression[0])) .withMessage("At least one expressions to return is required."); } } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/DeleteTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class DeleteTests { @Test void buildingDeleteClauseWithMultipleElements() { var delete = Delete.delete(Cypher.name("n"), Cypher.name("b")); assertThat(delete).hasToString("Delete{cypher=DELETE n, b}"); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/DialectIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Dialect; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * @author Michael J. Simons */ // tag::dialect-example[] class DialectIT { // end::dialect-example[] static Stream nPropExists() { return Stream.of(Arguments.of(Dialect.NEO4J_4, false, "MATCH (n:`Movie`) WHERE exists(n.title) RETURN n"), Arguments.of(Dialect.NEO4J_4, true, "MATCH (n:`Movie`) WHERE NOT (exists(n.title)) RETURN n"), Arguments.of(Dialect.NEO4J_5, false, "MATCH (n:`Movie`) WHERE n.title IS NOT NULL RETURN n"), Arguments.of(Dialect.NEO4J_5, true, "MATCH (n:`Movie`) WHERE n.title IS NULL RETURN n")); } static Stream distanceFunction() { return Stream.of(Arguments.of(Dialect.NEO4J_4, "MATCH (n) RETURN distance(n.a, n.b)"), Arguments.of(Dialect.NEO4J_5, "MATCH (n) RETURN point.distance(n.a, n.b)")); } static Stream elementId() { return Stream.of(Arguments.of(Dialect.NEO4J_4, "MATCH (n) RETURN toString(id(n))"), Arguments.of(Dialect.NEO4J_5, "MATCH (n) RETURN elementId(n)")); } @ParameterizedTest @MethodSource void nPropExists(Dialect dialect, boolean negate, String expected) { Node n = Cypher.node("Movie").named("n"); Renderer renderer = Renderer.getRenderer(Configuration.newConfig().withDialect(dialect).build()); Condition condition = Cypher.exists(n.property("title")); if (negate) { condition = condition.not(); } String cypher = renderer.render(Cypher.match(n).where(condition).returning(n).build()); assertThat(cypher).isEqualTo(expected); } @ParameterizedTest @MethodSource void distanceFunction(Dialect dialect, String expected) { Node n = Cypher.anyNode("n"); Renderer renderer = Renderer.getRenderer(Configuration.newConfig().withDialect(dialect).build()); String cypher = renderer .render(Cypher.match(n).returning(Cypher.distance(n.property("a"), n.property("b"))).build()); assertThat(cypher).isEqualTo(expected); } @ParameterizedTest @MethodSource void elementId(Dialect dialect, String expected) { Node n = Cypher.anyNode("n"); Renderer renderer = Renderer.getRenderer(Configuration.newConfig().withDialect(dialect).build()); String cypher = renderer.render(Cypher.match(n).returning(n.elementId()).build()); assertThat(cypher).isEqualTo(expected); } @ParameterizedTest @CsvSource(textBlock = """ NEO4J_4,MATCH p = shortestPath((wos:`Station`)-[:`LINK`]->(bmv:`Station`)) RETURN p NEO4J_5,MATCH p = shortestPath((wos:`Station`)-[:`LINK`]->(bmv:`Station`)) RETURN p NEO4J_5_23,MATCH p = SHORTEST 1 (wos:`Station`)-[:`LINK`]->(bmv:`Station`) RETURN p NEO4J_5_26,CYPHER 5 MATCH p = SHORTEST 1 (wos:`Station`)-[:`LINK`]->(bmv:`Station`) RETURN p NEO4J_5_DEFAULT_CYPHER,MATCH p = SHORTEST 1 (wos:`Station`)-[:`LINK`]->(bmv:`Station`) RETURN p NEO4J_5_CYPHER_5,CYPHER 5 MATCH p = SHORTEST 1 (wos:`Station`)-[:`LINK`]->(bmv:`Station`) RETURN p NEO4J_5_CYPHER_25,CYPHER 25 MATCH p = SHORTEST 1 (wos:`Station`)-[:`LINK`]->(bmv:`Station`) RETURN p """) void shortestPath(Dialect dialect, String expected) { var stmt = Cypher.match(Cypher.shortestK(1) .named("p") .definedBy(Cypher.node("Station").named("wos").relationshipTo(Cypher.node("Station").named("bmv"), "LINK"))) .returning(Cypher.name("p")) .build(); var renderer = Renderer.getRenderer(Configuration.newConfig().withDialect(dialect).build()); assertThat(renderer.render(stmt)).isEqualTo(expected); } @ParameterizedTest @CsvSource(textBlock = """ NEO4J_4,MATCH p = allShortestPaths((wos:`Station`)-[:`LINK`]->(bmv:`Station`)) RETURN p NEO4J_5,MATCH p = allShortestPaths((wos:`Station`)-[:`LINK`]->(bmv:`Station`)) RETURN p NEO4J_5_23,MATCH p = ALL SHORTEST (wos:`Station`)-[:`LINK`]->(bmv:`Station`) RETURN p NEO4J_5_26,CYPHER 5 MATCH p = ALL SHORTEST (wos:`Station`)-[:`LINK`]->(bmv:`Station`) RETURN p NEO4J_5_DEFAULT_CYPHER,MATCH p = ALL SHORTEST (wos:`Station`)-[:`LINK`]->(bmv:`Station`) RETURN p NEO4J_5_CYPHER_5,CYPHER 5 MATCH p = ALL SHORTEST (wos:`Station`)-[:`LINK`]->(bmv:`Station`) RETURN p NEO4J_5_CYPHER_25,CYPHER 25 MATCH p = ALL SHORTEST (wos:`Station`)-[:`LINK`]->(bmv:`Station`) RETURN p """) void allShortestPath(Dialect dialect, String expected) { var stmt = Cypher.match(Cypher.allShortest() .named("p") .definedBy(Cypher.node("Station").named("wos").relationshipTo(Cypher.node("Station").named("bmv"), "LINK"))) .returning(Cypher.name("p")) .build(); var renderer = Renderer.getRenderer(Configuration.newConfig().withDialect(dialect).build()); assertThat(renderer.render(stmt)).isEqualTo(expected); } @ParameterizedTest @CsvSource(quoteCharacter = '@', textBlock = """ false, NEO4J_4, MATCH (m:`Movie`) SET m:`a`:`b`:`c`:`d`:`e`:`f`:`g`:`h`:`i`:`j`:`k`:`l`:`m` RETURN * false, NEO4J_5, MATCH (m:`Movie`) SET m:`a`:`b`:`c`:`d`:`e`:`f`:`g`:`h`:`i`:`j`:`k`:`l`:`m` RETURN * false, NEO4J_5_23, MATCH (m:`Movie`) SET m:`a`:`b`:`c`:`d`:`e`:`f`:`g`:`h`:`i`:`j`:`k`:`l`:`m` RETURN * false, NEO4J_5_26, @CYPHER 5 MATCH (m:`Movie`) SET m:$($x):$($y):$($z):$(['f', 'g']):$('h'):$(['i', 'j']):$($foo):$($bar) RETURN *@ false, NEO4J_5_CYPHER_5, @CYPHER 5 MATCH (m:`Movie`) SET m:$($x):$($y):$($z):$(['f', 'g']):$('h'):$(['i', 'j']):$($foo):$($bar) RETURN *@ false, NEO4J_5_DEFAULT_CYPHER, @MATCH (m:`Movie`) SET m:$($x):$($y):$($z):$(['f', 'g']):$('h'):$(['i', 'j']):$($foo):$($bar) RETURN *@ false, NEO4J_5_CYPHER_25, @CYPHER 25 MATCH (m:`Movie`) SET m:$($x):$($y):$($z):$(['f', 'g']):$('h'):$(['i', 'j']):$($foo):$($bar) RETURN *@ true, NEO4J_4, MATCH (m:`Movie`) SET m:`a`:`b`:`c`:`d`:`e`:`f`:`g`:`h`:`i`:`j`:`k`:`l`:`m` RETURN * true, NEO4J_5, MATCH (m:`Movie`) SET m:`a`:`b`:`c`:`d`:`e`:`f`:`g`:`h`:`i`:`j`:`k`:`l`:`m` RETURN * true, NEO4J_5_23, MATCH (m:`Movie`) SET m:`a`:`b`:`c`:`d`:`e`:`f`:`g`:`h`:`i`:`j`:`k`:`l`:`m` RETURN * true, NEO4J_5_26, CYPHER 5 MATCH (m:`Movie`) SET m:`a`:`b`:`c`:`d`:`e`:`f`:`g`:`h`:`i`:`j`:`k`:`l`:`m` RETURN * true, NEO4J_5_CYPHER_5, CYPHER 5 MATCH (m:`Movie`) SET m:`a`:`b`:`c`:`d`:`e`:`f`:`g`:`h`:`i`:`j`:`k`:`l`:`m` RETURN * true, NEO4J_5_DEFAULT_CYPHER, MATCH (m:`Movie`) SET m:`a`:`b`:`c`:`d`:`e`:`f`:`g`:`h`:`i`:`j`:`k`:`l`:`m` RETURN * true, NEO4J_5_CYPHER_25, CYPHER 25 MATCH (m:`Movie`) SET m:`a`:`b`:`c`:`d`:`e`:`f`:`g`:`h`:`i`:`j`:`k`:`l`:`m` RETURN * """) void labelRenderingShouldWork(boolean disableDynamicLabels, Dialect dialect, String expected) { var m = Cypher.node(Cypher.exactlyLabel("Movie")).named("m"); var parameterWithListOfStrings = Cypher.parameter("x", Cypher.listOf(Cypher.literalOf("a"), Cypher.literalOf("b"))); var parameterWithASingleString = Cypher.parameter("y", Cypher.literalOf("c")); var parameterWithListLiteral = Cypher.parameter("z", Cypher.literalOf(List.of(Cypher.literalOf("d"), Cypher.literalOf("e")))); var parameterWithAListOfJavaStrings = Cypher.parameter("foo", List.of("k", "l")); var stmt = Cypher.match(m) .set(m, Labels.all(parameterWithListOfStrings) .conjunctionWith(Labels.all(parameterWithASingleString)) .conjunctionWith(Labels.all(parameterWithListLiteral)) .conjunctionWith(Labels.all(Cypher.listOf(Cypher.literalOf("f"), Cypher.literalOf("g")))) .conjunctionWith(Labels.all(Cypher.literalOf("h"))) .conjunctionWith(Labels.all(Cypher.literalOf(List.of(Cypher.literalOf("i"), Cypher.literalOf("j"))))) .conjunctionWith(Labels.all(parameterWithAListOfJavaStrings)) .conjunctionWith(Labels.all(Cypher.parameter("bar", "m")))) .returning(Cypher.asterisk()) .build(); var renderer = Renderer.getRenderer( Configuration.newConfig().disableDynamicLabels(disableDynamicLabels).withDialect(dialect).build()); assertThat(renderer.render(stmt)).isEqualTo(expected); } @Test void expressionsMightNotBeRenderedInLabels() { var m = Cypher.node("Movie").named("m"); var stmt = Cypher.match(m).set(m, Labels.all(m.property("x"))).returning(Cypher.asterisk()).build(); var renderer = Renderer.getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_4).build()); assertThatIllegalArgumentException().isThrownBy(() -> renderer.render(stmt)) .withMessage("Cannot render the given Labels in a Cypher pre 5.26 compatible way"); } @Test // GH-539 // tag::dialect-example[] void shouldRenderElementId() { var screen = Cypher.node("ScreenStateNode").named("screen"); var id = Cypher.literalOf("4:d32903f5-48ef-40fb-9ce5-9a3039852c46:2"); var statement = Cypher.match(screen) .where(Cypher.elementId(screen).eq(id)) .returning(Cypher.elementId(screen)) .build(); // Config and renderer is thread safe, you can store it somewhere global var rendererConfig = Configuration.newConfig().withDialect(Dialect.NEO4J_5).build(); var renderer = Renderer.getRenderer(rendererConfig); var cypher = renderer.render(statement); assertThat(cypher).isEqualTo("MATCH (screen:`ScreenStateNode`) " + "WHERE elementId(screen) = '4:d32903f5-48ef-40fb-9ce5-9a3039852c46:2' " + "RETURN elementId(screen)"); } } // end::dialect-example[] ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/DurationLiteralTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.time.Duration; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class DurationLiteralTests { static Stream asStringShouldWork() { return Stream.of(Arguments.of(Duration.ofDays(1), "duration('PT24H')"), Arguments.of(Duration.ofHours(1), "duration('PT1H')"), Arguments.of(Duration.ofMinutes(61), "duration('PT1H1M')"), Arguments.of(Duration.ofSeconds(61), "duration('PT1M1S')"), Arguments.of(Duration.ofSeconds(61).plusMillis(1123), "duration('PT1M2.123S')"), Arguments.of(Duration.ofSeconds(61).plusNanos(1123), "duration('PT1M1.000001123S')"), Arguments.of(Duration.ofHours(23).plusMinutes(61).plusSeconds(120), "duration('PT24H3M')"), Arguments.of(Duration.ofDays(364).plusHours(47).plusMinutes(59).plusSeconds(61).plusMillis(1001), "duration('PT8784H2.001S')")); } @ParameterizedTest @MethodSource void asStringShouldWork(Duration value, String expected) { var literal = DurationLiteral.of(value); assertThat(literal.asString()).isEqualTo(expected); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/ExpressionTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; import org.assertj.core.api.Assertions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.neo4j.cypherdsl.core.ast.Visitable; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class ExpressionTests { @SuppressWarnings("unused") private static Stream mathematicalOperators() { return Stream.of(Arguments.of(Cypher.literalOf(1).add(Cypher.literalOf(2)), Operator.ADDITION), Arguments.of(Cypher.literalOf(1).subtract(Cypher.literalOf(2)), Operator.SUBTRACTION), Arguments.of(Cypher.literalOf(1).multiply(Cypher.literalOf(2)), Operator.MULTIPLICATION), Arguments.of(Cypher.literalOf(1).divide(Cypher.literalOf(2)), Operator.DIVISION), Arguments.of(Cypher.literalOf(1).remainder(Cypher.literalOf(2)), Operator.MODULO_DIVISION), Arguments.of(Cypher.literalOf(1).pow(Cypher.literalOf(2)), Operator.EXPONENTIATION)); } @SuppressWarnings("unused") private static Stream stringOperators() { return Stream.of(Arguments.of(Cypher.literalOf("a").concat(Cypher.literalOf("b")), Operator.CONCAT), Arguments.of(Cypher.literalOf("a").matches(Cypher.literalOf("b")), Operator.MATCHES)); } @ParameterizedTest @MethodSource("mathematicalOperators") void correctMathematicalOperatorsShouldBeUsed(Operation operation, Operator expectedOperator) { AtomicInteger counter = new AtomicInteger(0); operation.accept(segment -> { int i = counter.getAndIncrement(); switch (i) { case 0 -> assertThat(segment).isInstanceOf(Operation.class); case 1 -> assertThat(segment).isInstanceOfSatisfying(NumberLiteral.class, v -> assertThat(v.getContent()).isEqualTo(1)); case 2 -> assertThat(segment).isInstanceOfSatisfying(Operator.class, Assertions::assertThat) .isEqualTo(expectedOperator); case 3 -> assertThat(segment).isInstanceOfSatisfying(NumberLiteral.class, v -> assertThat(v.getContent()).isEqualTo(2)); default -> throw new IllegalArgumentException("Too many segments to visit."); } }); assertThat(counter.get()).isEqualTo(4); } @ParameterizedTest @MethodSource("stringOperators") void correctStringOperatorsShouldBeUsed(Visitable visitable, Operator expectedOperator) { AtomicInteger counter = new AtomicInteger(0); visitable.accept(segment -> { int i = counter.getAndIncrement(); switch (i) { case 0: break; case 1: assertThat(segment).isInstanceOfSatisfying(StringLiteral.class, v -> assertThat(v.getContent()).isEqualTo("a")); break; case 2: assertThat(segment).isInstanceOfSatisfying(Operator.class, Assertions::assertThat) .isEqualTo(expectedOperator); break; case 3: assertThat(segment).isInstanceOfSatisfying(StringLiteral.class, v -> assertThat(v.getContent()).isEqualTo("b")); break; default: throw new IllegalArgumentException("Too many segments to visit."); } }); assertThat(counter.get()).isEqualTo(4); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/ExpressionsIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; class ExpressionsIT { @Test void simpleCountSubquery() { var person = Cypher.node("Person").named("person"); var cypher = Cypher.match(person) .where(Cypher.count(person.relationshipTo(Cypher.node("Dog"), "HAS_DOG")).gt(Cypher.literalOf(1))) .returning(person.property("name").as("name")) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (person:`Person`) " + "WHERE COUNT { (person)-[:`HAS_DOG`]->(:`Dog`) } > 1 " + "RETURN person.name AS name"); } @Test void countSubqueryWithWhereClause() { var person = Cypher.node("Person").named("person"); var dog = Cypher.node("Dog").named("dog"); var cypher = Cypher.match(person) .where(Cypher.count(person.relationshipTo(dog, "HAS_DOG")) .where(person.property("name").eq(dog.property("name"))) .gt(Cypher.literalOf(1))) .returning(person.property("name").as("name")) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (person:`Person`) " + "WHERE COUNT { (person)-[:`HAS_DOG`]->(dog:`Dog`) WHERE person.name = dog.name } > 1 " + "RETURN person.name AS name"); } @Test void countSubqueryWithUnion() { var person = Cypher.node("Person").named("person"); var dog = Cypher.node("Dog").named("dog"); var cat = Cypher.node("Cat").named("cat"); var inner = Cypher.union( Cypher.match(person.relationshipTo(dog, "HAS_DOG")) .returning(dog.property("name").as("petName")) .build(), Cypher.match(person.relationshipTo(cat, "HAS_CAT")) .returning(cat.property("name").as("petName")) .build()); var cypher = Cypher.match(person) .returning(person.property("name").as("name"), Cypher.count(inner).as("numPets")) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (person:`Person`) " + "RETURN " + "person.name AS name, " + "COUNT { " + "MATCH (person)-[:`HAS_DOG`]->(dog:`Dog`) " + "RETURN dog.name AS petName " + "UNION " + "MATCH (person)-[:`HAS_CAT`]->(cat:`Cat`) " + "RETURN cat.name AS petName " + "} AS numPets"); } @Test void countSubqueryWithWith() { var person = Cypher.node("Person").named("person"); var dog = Cypher.node("Dog").named("d"); var dogName = Cypher.literalOf("Ozzy").as("dogName"); var cypher = Cypher.match(person) .where(Cypher.subqueryWith(dogName) .count(person.relationshipTo(dog, "HAS_DOG")) .where(dog.property("name").eq(dogName)) .eq(Cypher.literalOf(1))) .returning(person.property("name").as("name")) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (person:`Person`) " + "WHERE COUNT { " + "WITH 'Ozzy' AS dogName " + "MATCH (person)-[:`HAS_DOG`]->(d:`Dog`) " + "WHERE d.name = dogName " + "} = 1 " + "RETURN person.name AS name"); } @Test void countSubqueryInReturn() { var person = Cypher.node("Person").named("person"); var dog = Cypher.node("Dog"); var cypher = Cypher.match(person) .returning(person.property("name"), Cypher.count(person.relationshipTo(dog, "HAS_DOG")).as("howManyDogs")) .build() .getCypher(); assertThat(cypher).isEqualTo( "MATCH (person:`Person`) RETURN person.name, COUNT { (person)-[:`HAS_DOG`]->(:`Dog`) } AS howManyDogs"); } @Test void countSubqueryInSet() { var person = Cypher.node("Person").named("person"); var dog = Cypher.node("Dog"); var howManyDogs = person.property("howManyDogs"); var cypher = Cypher.match(person) .where(person.property("name").eq(Cypher.literalOf("Andy"))) .set(howManyDogs.to(Cypher.count(person.relationshipTo(dog, "HAS_DOG")))) .returning(howManyDogs.as("howManyDogs")) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (person:`Person`) WHERE person.name = 'Andy' " + "SET person.howManyDogs = COUNT { (person)-[:`HAS_DOG`]->(:`Dog`) } " + "RETURN person.howManyDogs AS howManyDogs"); } @Test void countSubqueryInCase() { var person = Cypher.node("Person").named("person"); var dog = Cypher.node("Dog"); var dogCount = Cypher.count(person.relationshipTo(dog, "HAS_DOG")); var personName = person.property("name"); var cypher = Cypher.match(person) .returning(Cypher.caseExpression() .when(dogCount.gt(Cypher.literalOf(1))) .then(Cypher.literalOf("Doglover ").concat(personName)) .elseDefault(personName) .as("result")) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (person:`Person`) " + "RETURN " + "CASE " + "WHEN COUNT { (person)-[:`HAS_DOG`]->(:`Dog`) } > 1 THEN ('Doglover ' + person.name) " + "ELSE person.name " + "END AS result"); } @Test // GH-578 void fullStatementsAsCountSubQuery() { Node p = Cypher.node("Person").named("person"); Statement inner = Cypher.match(p.relationshipTo(Cypher.node("Dog"), "HAS_DOG")) .returning(p.property("name")) .build(); Statement outer = Cypher.match(p) .where(Cypher.count(inner).eq(Cypher.literalOf(1))) .returning(p.property("name").as("name")) .build(); String cypher = outer.getCypher(); assertThat(cypher).isEqualTo( "MATCH (person:`Person`) WHERE COUNT { MATCH (person)-[:`HAS_DOG`]->(:`Dog`) RETURN person.name } = 1 RETURN person.name AS name"); } @Test // GH-578 void fullStatementsAsCountSubQueryWithImports() { Node p = Cypher.node("Person").named("person"); Node d = Cypher.node("Dog").named("d"); SymbolicName dogName = Cypher.name("dogName"); Statement inner = Cypher.match(p.relationshipTo(d, "HAS_DOG")) .where(d.property("name").eq(dogName)) .returning(p.property("name")) .build(); Statement outer = Cypher.match(p) .where(Cypher.count(inner, Cypher.literalOf("Ozzy").as(dogName)).eq(Cypher.literalOf(1))) .returning(p.property("name").as("name")) .build(); String cypher = outer.getCypher(); assertThat(cypher).isEqualTo( "MATCH (person:`Person`) WHERE COUNT { WITH 'Ozzy' AS dogName MATCH (person)-[:`HAS_DOG`]->(d:`Dog`) WHERE d.name = dogName RETURN person.name } = 1 RETURN person.name AS name"); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/ForeignAdapterFactoryTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIf; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * @author Michael J. Simons */ @DisabledIf("requiredClassesAreMissing") class ForeignAdapterFactoryTests { static boolean requiredClassesAreMissing() { try { Class.forName("com.querydsl.core.types.Expression"); } catch (ClassNotFoundException ex) { return true; } return false; } @Test void shouldNotAdaptNull() { assertThatIllegalArgumentException().isThrownBy(() -> Cypher.adapt(null)) .withMessage("Cannot adapt literal NULL expressions."); } @Test void shouldFailOnUnadaptableThings() { assertThatIllegalArgumentException().isThrownBy(() -> Cypher.adapt(23)) .withMessageMatching("Cannot adapt expressions of .+ to Cypher-DSL expressions\\."); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/FunctionInvocationTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.ast.TypedSubtree; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * @author Michael J. Simons */ class FunctionInvocationTests { @Test void argumentsShouldBeAsserted() { assertThatIllegalArgumentException() .isThrownBy(() -> FunctionInvocation.create(BuiltInFunctions.Aggregates.AVG, (Expression) null)) .withMessage("The expression for avg() is required."); assertThatIllegalArgumentException() .isThrownBy(() -> FunctionInvocation.createDistinct(BuiltInFunctions.Aggregates.AVG, (Expression) null)) .withMessage("The expression for avg() is required."); assertThatIllegalArgumentException() .isThrownBy(() -> FunctionInvocation.createDistinct(BuiltInFunctions.Scalars.HEAD, (Expression) null)) .withMessage("The distinct operator can only be applied within aggregate functions."); assertThatIllegalArgumentException() .isThrownBy(() -> FunctionInvocation.create(BuiltInFunctions.Aggregates.AVG, (PatternElement) null)) .withMessage("The pattern for avg() is required."); assertThatIllegalArgumentException() .isThrownBy(() -> FunctionInvocation.create(BuiltInFunctions.Aggregates.AVG, (TypedSubtree) null)) .withMessage("avg() requires at least one argument."); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/FunctionsIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Locale; import java.util.TimeZone; import java.util.stream.Stream; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.MethodSource; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Dialect; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * @author Michael J. Simons * @author Gerrit Meier */ class FunctionsIT { private static final Renderer cypherRenderer = Renderer .getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_4).build()); private static Stream neo5jSpecificFunctions() { Node n = Cypher.node("Node").named("n"); Node m = Cypher.node("Node2").named("m"); Relationship r = n.relationshipTo(m).named("r"); return Stream.of(Arguments.of(Cypher.elementId(n), "RETURN elementId(n)"), Arguments.of(Cypher.elementId(r), "RETURN elementId(r)")); } private static Stream functionsToTest() { Node n = Cypher.node("Node").named("n"); Node m = Cypher.node("Node2").named("m"); Relationship r = n.relationshipTo(m).named("r"); Expression e1 = Cypher.name("e1"); Expression e2 = Cypher.name("e2"); FunctionInvocation p1 = Cypher .point(Cypher.mapOf("latitude", Cypher.literalOf(1), "longitude", Cypher.literalOf(2))); FunctionInvocation p2 = Cypher .point(Cypher.mapOf("latitude", Cypher.literalOf(3), "longitude", Cypher.literalOf(4))); // NOTE: Not all of those return valid Cypher statements. They are used only for // integration testing the function calls so far. @SuppressWarnings("deprecation") var idOfRel = Functions.id(r); return Stream.of(Arguments.of(Cypher.left(null, null), "RETURN left(NULL, NULL)"), Arguments.of(Cypher.left(Cypher.literalOf("hello"), Cypher.literalOf(3)), "RETURN left('hello', 3)"), Arguments.of(Cypher.ltrim(Cypher.literalOf(" hello")), "RETURN ltrim(' hello')"), Arguments.of(Cypher.replace(Cypher.literalOf("hello"), Cypher.literalOf("l"), Cypher.literalOf("w")), "RETURN replace('hello', 'l', 'w')"), Arguments.of(Cypher.reverse(Cypher.literalOf("hello")), "RETURN reverse('hello')"), Arguments.of(Cypher.right(null, null), "RETURN right(NULL, NULL)"), Arguments.of(Cypher.right(Cypher.literalOf("hello"), Cypher.literalOf(3)), "RETURN right('hello', 3)"), Arguments.of(Cypher.rtrim(Cypher.literalOf(" hello ")), "RETURN rtrim(' hello ')"), Arguments.of(Cypher.substring(Cypher.literalOf("hello"), Cypher.literalOf(1), Cypher.literalOf(3)), "RETURN substring('hello', 1, 3)"), Arguments.of(Cypher.substring(Cypher.literalOf("hello"), Cypher.literalOf(2), null), "RETURN substring('hello', 2)"), Arguments.of(Cypher.toStringOrNull(Cypher.literalOf("hello")), "RETURN toStringOrNull('hello')"), Arguments.of(idOfRel, "RETURN id(r)"), Arguments.of(Cypher.elementId(n), "RETURN toString(id(n))"), Arguments.of(Cypher.elementId(r), "RETURN toString(id(r))"), Arguments.of(Cypher.keys(n), "RETURN keys(n)"), Arguments.of(Cypher.keys(r), "RETURN keys(r)"), Arguments.of(Cypher.keys(e1), "RETURN keys(e1)"), Arguments.of(Cypher.labels(n), "RETURN labels(n)"), Arguments.of(Cypher.type(r), "RETURN type(r)"), Arguments.of(Cypher.count(n), "RETURN count(n)"), Arguments.of(Cypher.countDistinct(n), "RETURN count(DISTINCT n)"), Arguments.of(Cypher.count(e1), "RETURN count(e1)"), Arguments.of(Cypher.countDistinct(e1), "RETURN count(DISTINCT e1)"), Arguments.of(Cypher.coalesce(e1, e2), "RETURN coalesce(e1, e2)"), Arguments.of(Cypher.toLower(e1), "RETURN toLower(e1)"), Arguments.of(Cypher.toUpper(e1), "RETURN toUpper(e1)"), Arguments.of(Cypher.trim(e1), "RETURN trim(e1)"), Arguments.of(Cypher.split(e1, Cypher.literalOf(",")), "RETURN split(e1, ',')"), Arguments.of(Cypher.size(e1), "RETURN size(e1)"), Arguments.of(Cypher.size(r), "RETURN size((:`Node`)-[]->(:`Node2`))"), Arguments.of(Cypher.exists(e1), "RETURN exists(e1)"), Arguments.of(Cypher.distance(p1, p2), "RETURN distance(point({latitude: 1, longitude: 2}), point({latitude: 3, longitude: 4}))"), Arguments.of(Cypher.avg(e1), "RETURN avg(e1)"), Arguments.of(Cypher.avgDistinct(e1), "RETURN avg(DISTINCT e1)"), Arguments.of(Cypher.collect(e1), "RETURN collect(e1)"), Arguments.of(Cypher.collectDistinct(e1), "RETURN collect(DISTINCT e1)"), Arguments.of(Cypher.collect(n), "RETURN collect(n)"), Arguments.of(Cypher.collectDistinct(n), "RETURN collect(DISTINCT n)"), Arguments.of(Cypher.max(e1), "RETURN max(e1)"), Arguments.of(Cypher.maxDistinct(e1), "RETURN max(DISTINCT e1)"), Arguments.of(Cypher.min(e1), "RETURN min(e1)"), Arguments.of(Cypher.minDistinct(e1), "RETURN min(DISTINCT e1)"), Arguments.of(Cypher.percentileCont(e1, 0.4), "RETURN percentileCont(e1, 0.4)"), Arguments.of(Cypher.percentileContDistinct(e1, 0.4), "RETURN percentileCont(DISTINCT e1, 0.4)"), Arguments.of(Cypher.percentileDisc(e1, 0.4), "RETURN percentileDisc(e1, 0.4)"), Arguments.of(Cypher.percentileDiscDistinct(e1, 0.4), "RETURN percentileDisc(DISTINCT e1, 0.4)"), Arguments.of(Cypher.stDev(e1), "RETURN stDev(e1)"), Arguments.of(Cypher.stDevDistinct(e1), "RETURN stDev(DISTINCT e1)"), Arguments.of(Cypher.stDevP(e1), "RETURN stDevP(e1)"), Arguments.of(Cypher.stDevPDistinct(e1), "RETURN stDevP(DISTINCT e1)"), Arguments.of(Cypher.sum(e1), "RETURN sum(e1)"), Arguments.of(Cypher.sumDistinct(e1), "RETURN sum(DISTINCT e1)"), Arguments.of(Cypher.range(Cypher.literalOf(1), Cypher.literalOf(3)), "RETURN range(1, 3)"), Arguments.of(Cypher.range(Cypher.literalOf(1), Cypher.literalOf(3), Cypher.literalOf(2)), "RETURN range(1, 3, 2)"), Arguments.of(Cypher.head(e1), "RETURN head(e1)"), Arguments.of(Cypher.last(e1), "RETURN last(e1)"), Arguments.of(Cypher.nodes(Cypher.path("p").definedBy(r)), "RETURN nodes(p)"), Arguments.of(Cypher.length(Cypher.path("p").definedBy(r)), "RETURN length(p)"), Arguments.of(Cypher.properties(n), "RETURN properties(n)"), Arguments.of(Cypher.properties(r), "RETURN properties(r)"), Arguments.of(Cypher.properties(Cypher.mapOf("a", Cypher.literalOf("b"))), "RETURN properties({a: 'b'})"), Arguments.of(Cypher.relationships(Cypher.path("p").definedBy(r)), "RETURN relationships(p)"), Arguments.of(Cypher.startNode(r), "RETURN startNode(r)"), Arguments.of(Cypher.endNode(r), "RETURN endNode(r)"), Arguments.of(Cypher.date(), "RETURN date()"), Arguments.of(Cypher.calendarDate(2020, 9, 21), "RETURN date({year: 2020, month: 9, day: 21})"), Arguments.of(Cypher.weekDate(1984, 10, 3), "RETURN date({year: 1984, week: 10, dayOfWeek: 3})"), Arguments.of(Cypher.weekDate(1984, 10, null), "RETURN date({year: 1984, week: 10})"), Arguments.of(Cypher.weekDate(1984, null, null), "RETURN date({year: 1984})"), Arguments.of(Cypher.quarterDate(1984, 10, 45), "RETURN date({year: 1984, quarter: 10, dayOfQuarter: 45})"), Arguments.of(Cypher.quarterDate(1984, 10, null), "RETURN date({year: 1984, quarter: 10})"), Arguments.of(Cypher.quarterDate(1984, null, null), "RETURN date({year: 1984})"), Arguments.of(Cypher.ordinalDate(1984, 202), "RETURN date({year: 1984, ordinalDay: 202})"), Arguments.of(Cypher.ordinalDate(1984, null), "RETURN date({year: 1984})"), Arguments.of(Cypher.date(Cypher.mapOf("year", Cypher.literalOf(2020))), "RETURN date({year: 2020})"), Arguments.of(Cypher.date("2020-09-15"), "RETURN date('2020-09-15')"), Arguments.of(Cypher.date(Cypher.parameter("$myDateParameter")), "RETURN date($myDateParameter)"), Arguments.of(Cypher.datetime(), "RETURN datetime()"), Arguments.of( Cypher.datetime(Cypher.mapOf("year", Cypher.literalOf(1984), "month", Cypher.literalOf(10), "day", Cypher.literalOf(11), "hour", Cypher .literalOf(12), "minute", Cypher.literalOf(31), "timezone", Cypher.literalOf("Europe/Stockholm"))), "RETURN datetime({year: 1984, month: 10, day: 11, hour: 12, minute: 31, timezone: 'Europe/Stockholm'})"), Arguments.of(Cypher.datetime("2015-07-21T21:40:32.142+0100"), "RETURN datetime('2015-07-21T21:40:32.142+0100')"), Arguments.of(Cypher.datetime(Cypher.parameter("$myDateParameter")), "RETURN datetime($myDateParameter)"), Arguments.of(Cypher.datetime(TimeZone.getTimeZone("America/Los_Angeles")), "RETURN datetime({timezone: 'America/Los_Angeles'})"), Arguments.of(Cypher.localdatetime(), "RETURN localdatetime()"), Arguments.of( Cypher.localdatetime(Cypher.mapOf("year", Cypher.literalOf(1984), "month", Cypher.literalOf(10), "day", Cypher.literalOf(11), "hour", Cypher.literalOf(12), "minute", Cypher.literalOf(31), "second", Cypher.literalOf(14), "millisecond", Cypher .literalOf(123), "microsecond", Cypher.literalOf(456), "nanosecond", Cypher.literalOf(789))), "RETURN localdatetime({year: 1984, month: 10, day: 11, hour: 12, minute: 31, second: 14, millisecond: 123, microsecond: 456, nanosecond: 789})"), Arguments.of(Cypher.localdatetime("2015-07-21T21:40:32.142"), "RETURN localdatetime('2015-07-21T21:40:32.142')"), Arguments.of(Cypher.localdatetime(Cypher.parameter("$myDateParameter")), "RETURN localdatetime($myDateParameter)"), Arguments.of(Cypher.localdatetime(TimeZone.getTimeZone("America/Los_Angeles")), "RETURN localdatetime({timezone: 'America/Los_Angeles'})"), Arguments.of(Cypher.localtime(), "RETURN localtime()"), Arguments.of( Cypher.localtime(Cypher.mapOf("hour", Cypher.literalOf(12), "minute", Cypher.literalOf(31), "second", Cypher.literalOf(14), "millisecond", Cypher .literalOf(123), "microsecond", Cypher.literalOf(456), "nanosecond", Cypher.literalOf(789))), "RETURN localtime({hour: 12, minute: 31, second: 14, millisecond: 123, microsecond: 456, nanosecond: 789})"), Arguments.of(Cypher.localtime("21:40:32.142"), "RETURN localtime('21:40:32.142')"), Arguments.of(Cypher.localtime(Cypher.parameter("$myDateParameter")), "RETURN localtime($myDateParameter)"), Arguments.of(Cypher.localtime(TimeZone.getTimeZone("America/Los_Angeles")), "RETURN localtime({timezone: 'America/Los_Angeles'})"), Arguments.of(Cypher.time(), "RETURN time()"), Arguments.of( Cypher.time(Cypher.mapOf("hour", Cypher.literalOf(12), "minute", Cypher.literalOf(31), "second", Cypher.literalOf(14), "millisecond", Cypher.literalOf(123), "microsecond", Cypher.literalOf(456), "nanosecond", Cypher.literalOf(789))), "RETURN time({hour: 12, minute: 31, second: 14, millisecond: 123, microsecond: 456, nanosecond: 789})"), Arguments.of(Cypher.time("21:40:32.142"), "RETURN time('21:40:32.142')"), Arguments.of(Cypher.time(Cypher.parameter("$myDateParameter")), "RETURN time($myDateParameter)"), Arguments.of(Cypher.time(TimeZone.getTimeZone("America/Los_Angeles")), "RETURN time({timezone: 'America/Los_Angeles'})"), Arguments.of( Cypher.duration(Cypher.mapOf("days", Cypher.literalOf(14), "hours", Cypher.literalOf(16), "minutes", Cypher.literalOf(12))), "RETURN duration({days: 14, hours: 16, minutes: 12})"), Arguments.of(Cypher.duration("P14DT16H12M"), "RETURN duration('P14DT16H12M')"), Arguments.of(Cypher.duration(Cypher.parameter("$myDateParameter")), "RETURN duration($myDateParameter)"), Arguments.of(Cypher.toInteger(Cypher.literalOf("23")), "RETURN toInteger('23')"), Arguments.of(Cypher.toString(Cypher.literalOf(23)), "RETURN toString(23)"), Arguments.of(Cypher.toFloat(Cypher.literalOf("23.42")), "RETURN toFloat('23.42')"), Arguments.of(Cypher.toBoolean(Cypher.literalOf("false")), "RETURN toBoolean('false')"), Arguments.of(Cypher.randomUUID(), "RETURN randomUUID()"), Arguments.of(Cypher.cartesian(2.3, 4.5), "RETURN point({x: 2.3, y: 4.5})"), Arguments.of(Cypher.coordinate(56.7, 12.78), "RETURN point({longitude: 56.7, latitude: 12.78})")); } @Test void propertiesInvocationShouldBeReusable() { Node source = Cypher.node("Movie", Cypher.mapOf("title", Cypher.literalOf("The Matrix"))).named("src"); FunctionInvocation p = Cypher.properties(source); Node copy = Cypher.node("MovieCopy").named("copy"); String query = cypherRenderer.render(Cypher.match(source).create(copy).set(copy, p).returning(copy).build()); assertThat(query).isEqualTo( "MATCH (src:`Movie` {title: 'The Matrix'}) CREATE (copy:`MovieCopy`) SET copy = properties(src) RETURN copy"); } @ParameterizedTest @EnumSource(BuiltInFunctions.MathematicalFunctions.class) void mathFunctionsShouldBeRenderedAsExpected(BuiltInFunctions.MathematicalFunctions function) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { if (function.getMinArgs() != function.getMaxArgs()) { Class[] argTypes = new Class[2]; argTypes[0] = Expression.class; argTypes[1] = Expression[].class; Method m = Functions.class.getDeclaredMethod(function.name().toLowerCase(Locale.ROOT), argTypes); m.setAccessible(true); Expression arg1 = Cypher.literalOf(1); for (int n = 0; n <= function.getMaxArgs() - function.getMinArgs(); ++n) { Expression[] vargs = new Expression[n]; StringBuilder expected = new StringBuilder("RETURN " + function.getImplementationName() + "(1"); for (int i = 0; i < n; ++i) { vargs[i] = Cypher.literalOf(i); expected.append(", "); expected.append(i); } FunctionInvocation f = (FunctionInvocation) m.invoke(null, arg1, vargs); expected.append(");"); assertThat(cypherRenderer.render(Cypher.returning(f).build()) + ";").isEqualTo(expected.toString()); } } else { StringBuilder expected = new StringBuilder("RETURN " + function.getImplementationName() + "("); int n = function.getMinArgs(); Class[] argTypes = new Class[n]; Expression[] args = new Expression[n]; for (int i = 0; i < n; ++i) { argTypes[i] = Expression.class; args[i] = Cypher.literalOf(i); if (i > 0) { expected.append(", "); } expected.append(i); } expected.append(");"); Method m = Functions.class.getDeclaredMethod(function.name().toLowerCase(Locale.ROOT), argTypes); m.setAccessible(true); FunctionInvocation f = (FunctionInvocation) m.invoke(null, (Object[]) args); assertThat(cypherRenderer.render(Cypher.returning(f).build()) + ";").isEqualTo(expected.toString()); } } @ParameterizedTest(name = "{0}") @MethodSource("functionsToTest") void functionShouldBeRenderedAsExpected(FunctionInvocation functionInvocation, String expected) { assertThat(cypherRenderer.render(Cypher.returning(functionInvocation).build())).isEqualTo(expected); } @ParameterizedTest(name = "{0}") @MethodSource("neo5jSpecificFunctions") void neo5jSpecificFunctionsShouldWork(FunctionInvocation functionInvocation, String expected) { Renderer renderer = Renderer.getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_5).build()); assertThat(renderer.render(Cypher.returning(functionInvocation).build())).isEqualTo(expected); } @Test void assortedErrors() { var literalExpression = Cypher.literalOf("something"); assertThatIllegalArgumentException().isThrownBy(() -> Cypher.left(literalExpression, null)) .withMessage("length might not be null when the expression is not null"); assertThatIllegalArgumentException().isThrownBy(() -> Cypher.right(literalExpression, null)) .withMessage("length might not be null when the expression is not null"); assertThatIllegalArgumentException().isThrownBy(() -> Cypher.substring(literalExpression, null, null)) .withMessage("start is required"); } @Test // GH-777 void isEmptyShouldWork() { var p = Cypher.node("Person").named("p"); var stmt = Cypher.match(p) .where(Cypher.not(Cypher.isEmpty(p.property("nationality")))) .returning(p.property("name"), p.property("nationality")) .build() .getCypher(); assertThat(stmt) .isEqualTo("MATCH (p:`Person`) " + "WHERE NOT (isEmpty(p.nationality)) " + "RETURN p.name, p.nationality"); } @Nested class Reduction { @Test void reductionOfPaths() { Relationship r = Cypher.node("Node").named("n").relationshipTo(Cypher.node("Node2").named("m")).named("r"); NamedPath namedPath = Cypher.path("patternPath").definedBy(r); SymbolicName x = Cypher.name("x"); ListExpression emptyList = Cypher.listOf(); FunctionInvocation listToReduce = Cypher.relationships(namedPath); SymbolicName n = Cypher.name("n"); Statement statement = Cypher .returning( Cypher.reduce(n).in(listToReduce).map(x.add(n)).accumulateOn(x).withInitialValueOf(emptyList)) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("RETURN reduce(x = [], n IN relationships(patternPath) | (x + n))"); } @Test void manual() { Node a = Cypher.anyNode().named("a"); Node b = Cypher.anyNode().named("b"); Node c = Cypher.anyNode().named("c"); NamedPath p = Cypher.path("p").definedBy(a.relationshipTo(b).relationshipTo(c)); SymbolicName n = Cypher.name("n"); SymbolicName totalAge = Cypher.name("totalAge"); Statement statement = Cypher.match(p) .where(a.property("name").isEqualTo(Cypher.literalOf("Alice"))) .and(b.property("name").isEqualTo(Cypher.literalOf("Bob"))) .and(c.property("name").isEqualTo(Cypher.literalOf("Daniel"))) .returning(Cypher.reduce(n) .in(Cypher.nodes(p)) .map(totalAge.add(Cypher.property(n, "age"))) .accumulateOn(totalAge) .withInitialValueOf(Cypher.literalOf(0)) .as("reduction")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH p = (a)-->(b)-->(c) " + "WHERE (a.name = 'Alice' AND b.name = 'Bob' AND c.name = 'Daniel') " + "RETURN reduce(totalAge = 0, n IN nodes(p) | (totalAge + n.age)) AS reduction"); } @Test void ofListComprehension() { SymbolicName n = Cypher.name("n"); SymbolicName totalAge = Cypher.name("totalAge"); SymbolicName x = Cypher.name("x"); Statement statement = Cypher .returning(Cypher.reduce(n) .in(Cypher.listWith(x) .in(Cypher.range(0, 10)) .where(x.remainder(Cypher.literalOf(2)).isEqualTo(Cypher.literalOf(0))) .returning(x.pow(Cypher.literalOf(3)))) .map(totalAge.add(n)) .accumulateOn(totalAge) .withInitialValueOf(Cypher.literalOf(0.0)) .as("result")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "RETURN reduce(totalAge = 0.0, n IN [x IN range(0, 10) WHERE (x % 2) = 0 | x^3] | (totalAge + n)) AS result"); } } @Nested // GH-728 class Graphs { @Test void namesShouldWork() { assertThat(Cypher.returning(Cypher.graphNames().as("name")).build().getCypher()) .isEqualTo("RETURN graph.names() AS name"); } @Test void propertiesByNameShouldWork() { var name = Cypher.name("name"); var stmnt = Cypher.unwind(Cypher.graphNames()) .as(name) .returning(name, Cypher.graphPropertiesByName(name).as("props")) .build(); assertThat(stmnt.getCypher()) .isEqualTo("UNWIND graph.names() AS name RETURN name, graph.propertiesByName(name) AS props"); } @Test void byNameShouldWork() { var name = Cypher.name("graphName"); var stmnt = Cypher.unwind(Cypher.graphNames()) .as(name) .call(Cypher.use(Cypher.graphByName(name), Cypher.match(Cypher.anyNode("n")).returning(Cypher.name("n")).build())) .returning(Cypher.name("n")) .build(); assertThat(stmnt.getCypher()).isEqualTo( "UNWIND graph.names() AS graphName CALL (*) {USE graph.byName(graphName) MATCH (n) RETURN n} RETURN n"); } } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/FunctionsTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.lang.reflect.Method; import java.util.Locale; import java.util.Map; import java.util.stream.Stream; import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.platform.commons.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.Mockito.mock; /** * @author Michael J. Simons */ class FunctionsTests { private static final String FUNCTION_NAME_FIELD = "functionName"; @SuppressWarnings("deprecation") private static Stream functionsToTest() { return ReflectionUtils .findMethods(Functions.class, method -> method.getParameterCount() == 1 && (method.getParameterTypes()[0].isAssignableFrom(Expression.class) || method.getParameterTypes()[0].isAssignableFrom(Node.class) || method.getParameterTypes()[0].isAssignableFrom(Relationship.class) || method.getParameterTypes()[0].isAssignableFrom(MapExpression.class)) && (!java.util.Set.of("properties", "expressionOrNullLit").contains(method.getName()) || !method.getParameterTypes()[0].isAssignableFrom(MapExpression.class)), ReflectionUtils.HierarchyTraversalMode.TOP_DOWN) .stream() .map(method -> Arguments.of(Named.of(method.getName(), method))); } @ParameterizedTest(name = "{index}: {0}") @MethodSource("functionsToTest") void preconditionsShouldBeAsserted(Method method) { assertThatIllegalArgumentException().isThrownBy(() -> TestUtils.invokeMethod(method, null, (Expression) null)) .withMessageMatching( "The (expression|node|relationship|temporalAmount|temporalValue|variable|pattern|components) (?:for .* )?(?:is|are) required."); } @ParameterizedTest @MethodSource("functionsToTest") void functionInvocationsShouldBeCreated(Method method) { var expectedValue = method.getName().replace("Distinct", ""); if (expectedValue.startsWith("graph")) { expectedValue = "graph." + expectedValue.substring(5, 6).toLowerCase(Locale.ROOT) + expectedValue.substring(6); } Class parameterType = method.getParameterTypes()[0]; Object parameter; if (parameterType == SymbolicName.class) { parameter = SymbolicName.of("undef"); } else if (parameterType == MapExpression.class) { parameter = MapExpression.create(Map.of()); } else if (parameterType == org.neo4j.cypherdsl.core.Named.class || parameterType == Node.class) { parameter = Cypher.node("Undef"); } else if (parameterType == Relationship.class || parameterType == RelationshipPattern.class) { parameter = Cypher.node("Undef").relationshipTo(Cypher.anyNode()).named("r"); } else if (parameterType == Expression.class) { parameter = SymbolicName.of("Undef"); } else { throw new IllegalArgumentException("Cannot test a function with parameter type " + parameterType); } FunctionInvocation invocation = (FunctionInvocation) TestUtils.invokeMethod(method, null, parameter); assertThat(invocation).hasFieldOrPropertyWithValue(FUNCTION_NAME_FIELD, expectedValue); } @Nested class coalesce { @Test void preconditionsShouldBeAsserted() { String message = "The expression for coalesce() is required."; assertThatIllegalArgumentException().isThrownBy(() -> Cypher.coalesce((Expression[]) null)) .withMessage(message); assertThatIllegalArgumentException().isThrownBy(() -> Cypher.coalesce(new Expression[0])) .withMessage(message); assertThatIllegalArgumentException().isThrownBy(() -> Cypher.coalesce(new Expression[] { null })) .withMessage(message); } @Test void shouldCreateCorrectInvocation() { FunctionInvocation invocation = Cypher.coalesce(mock(Expression.class)); assertThat(invocation).hasFieldOrPropertyWithValue(FUNCTION_NAME_FIELD, "coalesce"); } } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/HintsIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * @author Michael J. Simons * */ class HintsIT { private static final Renderer cypherRenderer = Renderer.getDefaultRenderer(); private Node liskov = Cypher.node("Scientist").named("liskov").withProperties("name", Cypher.literalOf("Liskov")); private Node wing = Cypher.node("Scientist").named("wing"); private Node conway = Cypher.node("Scientist").named("conway").withProperties("name", Cypher.literalOf("Conway")); private Node cs = Cypher.node("Science").named("cs").withProperties("name", Cypher.literalOf("Computer Science")); @Nested class IndexHints { @Test void usingIndexShouldWork() { Statement statement = Cypher .match(HintsIT.this.liskov.relationshipTo(HintsIT.this.wing, "KNOWS") .relationshipTo(HintsIT.this.cs, "RESEARCHED") .relationshipFrom(HintsIT.this.conway, "RESEARCHED")) .usingIndex(HintsIT.this.liskov.property("name")) .returning(HintsIT.this.liskov.property("born").as("column")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (liskov:`Scientist` {name: 'Liskov'})-[:`KNOWS`]->(wing:`Scientist`)-[:`RESEARCHED`]->(cs:`Science` {name: 'Computer Science'})<-[:`RESEARCHED`]-(conway:`Scientist` {name: 'Conway'}) " + "USING INDEX liskov:`Scientist`(name) " + "RETURN liskov.born AS column"); } @Test void usingIndexSeekShouldWork() { Statement statement = Cypher .match(HintsIT.this.liskov.relationshipTo(HintsIT.this.wing, "KNOWS") .relationshipTo(HintsIT.this.cs, "RESEARCHED") .relationshipFrom(HintsIT.this.conway, "RESEARCHED")) .usingIndexSeek(HintsIT.this.liskov.property("name")) .returning(HintsIT.this.liskov.property("born").as("column")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (liskov:`Scientist` {name: 'Liskov'})-[:`KNOWS`]->(wing:`Scientist`)-[:`RESEARCHED`]->(cs:`Science` {name: 'Computer Science'})<-[:`RESEARCHED`]-(conway:`Scientist` {name: 'Conway'}) " + "USING INDEX SEEK liskov:`Scientist`(name) " + "RETURN liskov.born AS column"); } @Test void usingCompositeIndexesShouldWork() { Node m = Cypher.node("Method") .named("m") .withProperties("name", Cypher.literalOf("Foo"), "type", Cypher.literalOf("Bar")); Statement statement = Cypher.match(m) .usingIndex(m.property("name"), m.property("type")) .returning(m) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (m:`Method` {name: 'Foo', type: 'Bar'}) " + "USING INDEX m:`Method`(name, type) " + "RETURN m"); } @Test void propertiesOfDifferentNodesCannotBeUsedOnTheSameHInt() { Node m = Cypher.node("Method") .named("m") .withProperties("name", Cypher.literalOf("Foo"), "type", Cypher.literalOf("Bar")); assertThatIllegalStateException() .isThrownBy(() -> Cypher.match(m) .usingIndex(m.property("name"), Cypher.node("Method").named("x").property("type")) .returning(m) .build()) .withMessage( "If you want to use more than one index on different nodes you must use multiple `USING INDEX` statements."); } @Test void nestedPropertiesAreNotAllowed() { Node m = Cypher.node("Method") .named("m") .withProperties("name", Cypher.literalOf("Foo"), "type", Cypher.literalOf("Bar")); assertThatIllegalArgumentException() .isThrownBy(() -> Cypher.match(m).usingIndex(m.property("a", "name")).returning(m).build()) .withMessage("One single property is required. Nested properties are not supported."); } @Test void multipleLabelsAreNotAllowed() { Node m = Cypher.node("Method", "Foo") .named("m") .withProperties("name", Cypher.literalOf("Foo"), "type", Cypher.literalOf("Bar")); assertThatIllegalArgumentException() .isThrownBy(() -> Cypher.match(m).usingIndex(m.property("name")).returning(m).build()) .withMessage("Exactly one label is required to define the index."); } @Test void propertiesMustReferenceANode() { Node m = Cypher.node("Method") .named("m") .withProperties("name", Cypher.literalOf("Foo"), "type", Cypher.literalOf("Bar")); assertThatIllegalArgumentException() .isThrownBy(() -> Cypher.match(m).usingIndex(Cypher.property("m", "name")).returning(m).build()) .withMessage("Cannot use a property without a reference to a container inside an index hint."); } @Neo4jVersion(minimum = "4.3") @Test void indexesOnRelationshipsShouldWork() { Node m = Cypher.node("Method") .named("m") .withProperties("name", Cypher.literalOf("Foo"), "type", Cypher.literalOf("Bar")); Relationship r = m.relationshipTo(Cypher.node("Method"), "CALLS") .named("r") .withProperties("whatever", Cypher.literalTrue()); Statement statement = Cypher.match(r).usingIndex(r.property("whatever")).returning(m).build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (m:`Method` {name: 'Foo', type: 'Bar'})-[r:`CALLS` {whatever: true}]->(:`Method`) USING INDEX r:`CALLS`(whatever) RETURN m"); } @Test void multipleIndizesShouldWork() { Statement statement = Cypher .match(HintsIT.this.liskov.relationshipTo(HintsIT.this.wing, "KNOWS") .relationshipTo(HintsIT.this.cs, "RESEARCHED") .relationshipFrom(HintsIT.this.conway, "RESEARCHED")) .usingIndex(HintsIT.this.liskov.property("name")) .usingIndex(HintsIT.this.conway.property("name")) .returning(HintsIT.this.liskov.property("born").as("column")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (liskov:`Scientist` {name: 'Liskov'})-[:`KNOWS`]->(wing:`Scientist`)-[:`RESEARCHED`]->(cs:`Science` {name: 'Computer Science'})<-[:`RESEARCHED`]-(conway:`Scientist` {name: 'Conway'}) " + "USING INDEX liskov:`Scientist`(name) " + "USING INDEX conway:`Scientist`(name) " + "RETURN liskov.born AS column"); } } @Nested class ScanHints { @Test void usingScanHintsShouldWork() { Node s = Cypher.node("Scientist").named("s"); Statement statement = Cypher.match(s) .usingScan(s) .where(s.property("born").lt(Cypher.literalOf(1939))) .returning(s.property("born").as("column")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (s:`Scientist`) " + "USING SCAN s:`Scientist` " + "WHERE s.born < 1939 " + "RETURN s.born AS column"); } @Test void scansCanNotBeUsedWithMultipleLabels() { Assertions.assertThatIllegalArgumentException() .isThrownBy(() -> Cypher.match(Cypher.node("Scientist").named("s")) .usingScan(Cypher.node("Scientist", "Other").named("s")) .returning(SymbolicName.of("s")) .build()) .withMessage("Exactly one label is required for a SCAN hint."); } @Test void scansCanNotBeUsedWithoutLabels() { Assertions.assertThatIllegalArgumentException() .isThrownBy(() -> Cypher.match(Cypher.node("Scientist").named("s")) .usingScan(Cypher.anyNode()) .returning(SymbolicName.of("s")) .build()) .withMessage("Exactly one label is required for a SCAN hint."); } @Test void scansNeedANode() { Assertions.assertThatIllegalArgumentException() .isThrownBy(() -> Cypher.match(Cypher.node("Scientist").named("s")) .usingScan(null) .returning(SymbolicName.of("s")) .build()) .withMessage("Cannot apply a SCAN hint without a node."); } } @Nested class JoinHints { @Test void singleJoinHintShouldWork() { Statement statement = Cypher .match(HintsIT.this.liskov.relationshipTo(HintsIT.this.wing, "KNOWS") .relationshipTo(HintsIT.this.cs, "RESEARCHED") .relationshipFrom(HintsIT.this.conway, "RESEARCHED")) .usingIndex(HintsIT.this.liskov.property("name")) .usingIndex(HintsIT.this.conway.property("name")) .usingJoinOn(HintsIT.this.wing) .returning(HintsIT.this.liskov.property("born").as("column")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (liskov:`Scientist` {name: 'Liskov'})-[:`KNOWS`]->(wing:`Scientist`)-[:`RESEARCHED`]->(cs:`Science` {name: 'Computer Science'})<-[:`RESEARCHED`]-(conway:`Scientist` {name: 'Conway'}) " + "USING INDEX liskov:`Scientist`(name) " + "USING INDEX conway:`Scientist`(name) " + "USING JOIN ON wing " + "RETURN liskov.born AS column"); } @Test void multipleJoinHintShouldWork() { Statement statement = Cypher .match(HintsIT.this.liskov .relationshipTo( Cypher.node("Scientist").named("wing").withProperties("name", Cypher.literalOf("Wing")), "KNOWS") .relationshipTo(HintsIT.this.cs, "RESEARCHED") .relationshipFrom(HintsIT.this.liskov, "RESEARCHED")) .usingIndex(HintsIT.this.liskov.property("name")) .usingJoinOn(HintsIT.this.liskov, HintsIT.this.cs) .returning(HintsIT.this.wing.property("born").as("column")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (liskov:`Scientist` {name: 'Liskov'})-[:`KNOWS`]->(wing:`Scientist` {name: 'Wing'})-[:`RESEARCHED`]->(cs:`Science` {name: 'Computer Science'})<-[:`RESEARCHED`]-(liskov) " + "USING INDEX liskov:`Scientist`(name) " + "USING JOIN ON liskov, cs " + "RETURN wing.born AS column"); } @Test void joinWithoutOtherIndexesShouldWork() { Statement statement = Cypher .match(HintsIT.this.liskov.relationshipTo(HintsIT.this.wing, "KNOWS") .relationshipTo(HintsIT.this.cs, "RESEARCHED") .relationshipFrom(HintsIT.this.conway, "RESEARCHED")) .usingJoinOn(HintsIT.this.wing) .returning(HintsIT.this.liskov.property("born").as("column")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (liskov:`Scientist` {name: 'Liskov'})-[:`KNOWS`]->(wing:`Scientist`)-[:`RESEARCHED`]->(cs:`Science` {name: 'Computer Science'})<-[:`RESEARCHED`]-(conway:`Scientist` {name: 'Conway'}) " + "USING JOIN ON wing " + "RETURN liskov.born AS column"); } @Test void joinRequiresNodeName() { Assertions.assertThatIllegalArgumentException() .isThrownBy(() -> Cypher .match(HintsIT.this.liskov.relationshipTo(HintsIT.this.wing, "KNOWS") .relationshipTo(HintsIT.this.cs, "RESEARCHED") .relationshipFrom(HintsIT.this.conway, "RESEARCHED")) .usingJoinOn(new Node[0]) .returning(HintsIT.this.liskov.property("born").as("column")) .build()) .withMessage("At least one node is required to define a JOIN hint."); } @Test void joinRequiresSymbolicName() { Assertions.assertThatIllegalArgumentException() .isThrownBy(() -> Cypher .match(HintsIT.this.liskov.relationshipTo(HintsIT.this.wing, "KNOWS") .relationshipTo(HintsIT.this.cs, "RESEARCHED") .relationshipFrom(HintsIT.this.conway, "RESEARCHED")) .usingJoinOn(new SymbolicName[0]) .returning(HintsIT.this.liskov.property("born").as("column")) .build()) .withMessage("At least one name is required to define a JOIN hint."); } } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/IdentifiableExpressionsIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Collection; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; class IdentifiableExpressionsIT { @Test void simpleWith() { Node b = Cypher.anyNode("b"); Collection variables = Cypher.match(Cypher.node("Label").named("a").relationshipTo(b)) .with(Cypher.name("a"), b) .getIdentifiableExpressions(); assertThat(variables.stream().map(Cypher::format)).containsExactlyInAnyOrder("a", "b"); } @Test void multipleHops() { Node b = Cypher.anyNode("b"); SymbolicName a = Cypher.name("a"); Collection variables = Cypher.match(Cypher.node("Label").named("a").relationshipTo(b)) .with(a, b) .optionalMatch(b.relationshipTo(Cypher.anyNode("c"))) .with(a, Cypher.name("c")) .getIdentifiableExpressions(); assertThat(variables.stream().map(Cypher::format)).containsExactlyInAnyOrder("a", "c"); } @Test void complexWith() { Node b = Cypher.anyNode("b"); Node a = Cypher.node("Label").named("a"); Collection variables = Cypher.match(a.relationshipTo(b)) .with(a, b) .optionalMatch(b.relationshipTo(Cypher.anyNode("c"))) .with(a, Cypher.name("c"), Cypher.caseExpression() .when(Cypher.name("c").isNull().or(Cypher.hasLabelsOrType(Cypher.name("c"), "Label"))) .then(Cypher.literalTrue()) .elseDefault(Cypher.literalFalse()) .as("isNullOrLabel")) .getIdentifiableExpressions(); assertThat(variables.stream().map(Cypher::format)).containsExactlyInAnyOrder("a", "c", "isNullOrLabel"); } @Test @SuppressWarnings("deprecation") void complexWithAsStatement() { Node b = Cypher.anyNode("b"); Node a = Cypher.node("Label").named("a"); Collection variables = Cypher.match(a.relationshipTo(b)) .with(a, b) .optionalMatch(b.relationshipTo(Cypher.anyNode("c"))) .returning(a.getRequiredSymbolicName(), Cypher.name("c"), Cypher.caseExpression() .when(Cypher.name("c").isNull().or(Cypher.hasLabelsOrType(Cypher.name("c"), "Label"))) .then(Cypher.literalTrue()) .elseDefault(Cypher.literalFalse()) .as("isNullOrLabel")) .build() .getCatalog() .getIdentifiableExpressions(); assertThat(variables.stream().map(Cypher::format)).containsExactlyInAnyOrder("a", "c", "isNullOrLabel"); } @Test void noneExpressionsInReturn() { Node b = Cypher.anyNode("b"); Node a = Cypher.node("Label").named("a"); Collection variables = Cypher.match(a.relationshipTo(b)) .returning(a) .build() .getCatalog() .getIdentifiableExpressions(); assertThat(variables.stream().map(Cypher::format)).containsExactlyInAnyOrder("a"); } @Test void nonIdentifiableItems() { Node b = Cypher.anyNode("b"); Node a = Cypher.node("Label").named("a"); Collection variables = Cypher.match(a.relationshipTo(b)) .with(b.as("007")) .getIdentifiableExpressions(); assertThat(variables.stream().map(Cypher::format)).containsExactlyInAnyOrder("007"); } @Test void onReturn() { Node b = Cypher.anyNode("b"); Collection variables = Cypher.returning(Cypher.literalOf(1), b.as("007"), b.property("f")) .getIdentifiableExpressions(); assertThat(variables.stream().map(Cypher::format)).containsExactlyInAnyOrder("007", "b.f"); } @Test void returnedProperties() { Node b = Cypher.anyNode("b"); Collection variables = Cypher.match(b) .returning(b.property("x"), b.as("007")) .getIdentifiableExpressions(); assertThat(variables.stream().map(Cypher::format)).containsExactlyInAnyOrder("b.x", "007"); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/InternalNodeImplTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * This tests focus on the internal node implementation. * * @author Michael J. Simons */ class InternalNodeImplTests { @Test void preconditionsShouldBeAsserted() { String expectedMessage = "A primary label is required."; assertThatIllegalArgumentException().isThrownBy(() -> new InternalNodeImpl("")).withMessage(expectedMessage); assertThatIllegalArgumentException().isThrownBy(() -> new InternalNodeImpl(" \t")).withMessage(expectedMessage); assertThatIllegalArgumentException().isThrownBy(() -> Cypher.node("")).withMessage(expectedMessage); assertThatIllegalArgumentException().isThrownBy(() -> Cypher.node(" \t")).withMessage(expectedMessage); } @Test void shouldNotAddEmptyAdditionalLabels() { assertThatIllegalArgumentException().isThrownBy(() -> new InternalNodeImpl("primary", " ", "\t ")) .withMessage("An empty label is not allowed."); assertThatIllegalArgumentException().isThrownBy(() -> Cypher.node("primary", " ", "\t ")) .withMessage("An empty label is not allowed."); } @Test void shouldCreateNodes() { Node node = new InternalNodeImpl("primary", "secondary"); List labels = new ArrayList<>(); node.accept(new Visitor() { @Override public void enter(Visitable segment) { if (segment instanceof NodeLabel) { labels.add(((NodeLabel) segment).getValue()); } } }); assertThat(labels).contains("primary", "secondary"); } @Nested class MutationsAndSetShouldRequireName { @Test void mutationWithParameter() { Node node = Cypher.anyNode(); Parameter parameter = Cypher.parameter("x"); assertThatIllegalStateException().isThrownBy(() -> node.mutate(parameter)) .withMessage("A property container must be named to be mutated."); } @Test void mutationWithMap() { Node node = Cypher.anyNode(); MapExpression mapExpression = Cypher.mapOf("a", Cypher.literalTrue()); assertThatIllegalStateException().isThrownBy(() -> node.mutate(mapExpression)) .withMessage("A property container must be named to be mutated."); } @Test void setWithParameter() { Node node = Cypher.anyNode(); Parameter parameter = Cypher.parameter("x"); assertThatIllegalStateException().isThrownBy(() -> node.set(parameter)) .withMessage("A property container must be named to be mutated."); } @Test void setWithMap() { Node node = Cypher.anyNode(); MapExpression mapExpression = Cypher.mapOf("a", Cypher.literalTrue()); assertThatIllegalStateException().isThrownBy(() -> node.set(mapExpression)) .withMessage("A property container must be named to be mutated."); } } @Nested @TestInstance(Lifecycle.PER_CLASS) class PropertiesShouldBeHandled { private Stream createNodesWithProperties() { return Stream.of( Arguments.of(new InternalNodeImpl("N").named("n").withProperties("p", Cypher.literalTrue())), Arguments.of(new InternalNodeImpl("N").named("n") .withProperties(MapExpression.create(false, "p", Cypher.literalTrue())))); } @ParameterizedTest @MethodSource("createNodesWithProperties") void shouldAddProperties(Node node) { AtomicBoolean failTest = new AtomicBoolean(true); node.accept(new Visitor() { Class expectedTypeOfNextSegment = null; @Override public void enter(Visitable segment) { if (segment instanceof SymbolicName) { assertThat(((SymbolicName) segment).getValue()).isEqualTo("n"); } else if (segment instanceof NodeLabel) { assertThat(((NodeLabel) segment).getValue()).isEqualTo("N"); } else if (segment instanceof KeyValueMapEntry) { assertThat(((KeyValueMapEntry) segment).getKey()).isEqualTo("p"); this.expectedTypeOfNextSegment = BooleanLiteral.class; } else if (this.expectedTypeOfNextSegment != null) { assertThat(segment).isInstanceOf(this.expectedTypeOfNextSegment); failTest.getAndSet(false); } } }); assertThat(failTest).isFalse(); } @Test void shouldCreateProperty() { Node node = new InternalNodeImpl("N").named("n"); Property property = node.property("p"); java.util.Set expected = new HashSet<>(); expected.addAll(property.getNames()); expected.add(node.getRequiredSymbolicName()); expected.add(property); property.accept(expected::remove); assertThat(expected).isEmpty(); } } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/InternalPropertyImplTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class InternalPropertyImplTests { @Test void simplePropertyGetNameShouldWork() { assertThat(InternalPropertyImpl.create(SymbolicName.unresolved(), "simple").getName()).isEqualTo("simple"); } @Test void multiplePropertyGetNameShouldWork() { assertThat(InternalPropertyImpl.create(SymbolicName.unresolved(), "foo", "bar").getName()).isEqualTo("foo.bar"); } @Test void externalReferenceHasPriority() { assertThat(InternalPropertyImpl.create(SymbolicName.unresolved(), "foo", "bar") .referencedAs("something") .getName()).isEqualTo("something"); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/InternalRelationshipImplTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.HashSet; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * @author Michael J. Simons */ class InternalRelationshipImplTests { @Test void preconditionsShouldBeAsserted() { assertThatIllegalArgumentException() .isThrownBy(() -> new InternalRelationshipImpl(null, null, null, null, Cypher.node("a"), new String[0])) .withMessage("Left node is required."); assertThatIllegalArgumentException() .isThrownBy(() -> new InternalRelationshipImpl(null, Cypher.node("a"), null, null, null, new String[0])) .withMessage("Right node is required."); } @Nested @TestInstance(Lifecycle.PER_CLASS) class PropertiesShouldBeHandled { private Stream createNodesWithProperties() { return Stream.of( Arguments.of(Cypher.node("N") .named("n") .relationshipTo(Cypher.anyNode()) .withProperties("p", Cypher.literalTrue())), Arguments.of(Cypher.node("N") .named("n") .relationshipTo(Cypher.anyNode()) .withProperties(MapExpression.create(false, "p", Cypher.literalTrue())))); } @ParameterizedTest @MethodSource("createNodesWithProperties") void shouldAddProperties(Relationship relationship) { AtomicBoolean failTest = new AtomicBoolean(true); relationship.accept(new Visitor() { Class expectedTypeOfNextSegment = null; @Override public void enter(Visitable segment) { if (segment instanceof SymbolicName) { assertThat(((SymbolicName) segment).getValue()).isEqualTo("n"); } else if (segment instanceof NodeLabel) { assertThat(((NodeLabel) segment).getValue()).isEqualTo("N"); } else if (segment instanceof KeyValueMapEntry) { assertThat(((KeyValueMapEntry) segment).getKey()).isEqualTo("p"); this.expectedTypeOfNextSegment = BooleanLiteral.class; } else if (this.expectedTypeOfNextSegment != null) { assertThat(segment).isInstanceOf(this.expectedTypeOfNextSegment); failTest.getAndSet(false); } } @Override public void leave(Visitable segment) { if (this.expectedTypeOfNextSegment == BooleanLiteral.class) { failTest.getAndSet(true); this.expectedTypeOfNextSegment = Node.class; } } }); assertThat(failTest).isFalse(); } @Test void shouldCreateProperty() { Relationship relationship = Cypher.node("N").named("n").relationshipTo(Cypher.anyNode()).named("r"); Property property = relationship.property("p"); java.util.Set expected = new HashSet<>(); expected.addAll(property.getNames()); expected.add(relationship.getRequiredSymbolicName()); expected.add(property); property.accept(expected::remove); assertThat(expected).isEmpty(); } } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/IssueRelatedIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.time.ZonedDateTime; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.stream.Stream; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.MethodSource; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Dialect; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * @author Michael J. Simons */ class IssueRelatedIT { private static final Renderer cypherRenderer = Renderer.getDefaultRenderer(); private final Node person = Cypher.node("Person").named("person"); static Stream relpatternChainingArgs() { Stream.Builder arguments = Stream.builder(); arguments.add(Arguments.of(true, 1, false, "MATCH (s)-[:`PART_OF`*0..1]->(:`Resume`)-[:`IS_PARALLEL`*0..1]->(:`Resume`)-[l:`LEADS_TO`]->(e) RETURN s, l, e")); arguments.add(Arguments.of(true, 2, false, "MATCH (s)-[:`PART_OF`*0..1]->(:`Resume`)-[:`IS_PARALLEL`*0..1]->(:`Resume`)-[l:`LEADS_TO`*2..2]->(e) RETURN s, l, e")); arguments.add(Arguments.of(true, 1, true, "MATCH (s)-[:`PART_OF`*0..1]->(:`Resume`)-[:`IS_PARALLEL`*0..1]->(:`Resume`)<-[l:`LEADS_TO`]-(e) RETURN s, l, e")); arguments.add(Arguments.of(true, 2, true, "MATCH (s)-[:`PART_OF`*0..1]->(:`Resume`)-[:`IS_PARALLEL`*0..1]->(:`Resume`)<-[l:`LEADS_TO`*2..2]-(e) RETURN s, l, e")); arguments.add(Arguments.of(false, 1, false, "MATCH (s)-[l:`LEADS_TO`]->(e) RETURN s, l, e")); arguments.add(Arguments.of(false, 2, false, "MATCH (s)-[l:`LEADS_TO`*2..2]->(e) RETURN s, l, e")); arguments.add(Arguments.of(false, 1, true, "MATCH (s)<-[l:`LEADS_TO`]-(e) RETURN s, l, e")); arguments.add(Arguments.of(false, 2, true, "MATCH (s)<-[l:`LEADS_TO`*2..2]-(e) RETURN s, l, e")); return arguments.build(); } static Statement createSomewhatComplexStatement() { Node this0 = Cypher.node("Movie").named("this0"); Condition validationCondition = Cypher.any("r") .in(Cypher.listOf(Cypher.literalOf("admin"))) .where(Cypher.any("rr") .in(Cypher.parameter("$auth.roles")) .where(SymbolicName.of("r").eq(SymbolicName.of("rr")))); Node g = Cypher.node("Genre") .named("this0_genres_connectOrCreate0") .withProperties(Cypher.mapOf("name", Cypher.parameter("this0_genres_connectOrCreate0_node_name"))); Statement innerSubquery = Cypher.call("apoc.util.validate") .withArgs(validationCondition.not(), Cypher.literalOf("@neo4j/graphql/FORBIDDEN"), Cypher.listOf(Cypher.literalOf(0))) .withoutResults() .merge(g) .onCreate() .set(g.property("name").to(Cypher.parameter("this0_genres_connectOrCreate0_on_create_name"))) .merge(this0.relationshipTo(g, "IN_GENRE").named("this0_relationship_this0_genres_connectOrCreate0")) .returning(Cypher.count(Cypher.asterisk())) .build(); Statement subquery = Cypher.create(this0) .set(this0.property("title").to(Cypher.parameter("this0_title"))) .with(this0) .call(innerSubquery, this0) .returning(this0) .build(); return Cypher.call(subquery) .returning(Cypher.listOf(Cypher.createProjection(this0.getRequiredSymbolicName(), "title")).as("data")) .build(); } static MapExpression toMap(ZonedDateTime value) { return Cypher.mapOf("year", Cypher.literalOf(value.getYear()), "month", Cypher.literalOf(value.getMonthValue()), "day", Cypher.literalOf(value.getDayOfMonth()), "hour", Cypher.literalOf(value.getHour()), "minute", Cypher.literalOf(value.getMinute()), "second", Cypher.literalOf(value.getSecond()), "nanosecond", Cypher.literalOf(value.getNano()), "timezone", Cypher.literalOf(value.getZone().toString())); } static Stream conditionExpressionShouldWorkInReturn() { return Stream.of( Arguments.of(Cypher.returning(Cypher.literalOf(1), Cypher.literalTrue().asCondition()).build(), "RETURN 1, true"), Arguments.of(Cypher .returning(Cypher.literalOf(1), Cypher.literalTrue().asCondition().and(Cypher.literalFalse().asCondition())) .build(), "RETURN 1, (true AND false)"), Arguments.of(Cypher .returning(Cypher.literalOf(1), Cypher.literalTrue().asCondition().or(Cypher.literalFalse().asCondition())) .build(), "RETURN 1, (true OR false)")); } static Stream reusingAliases() { return Stream.of( Arguments.of(EnumSet.complementOf(EnumSet.of(Configuration.GeneratedNames.REUSE_ALIASES)), "MATCH (v0:`Movie`) RETURN v0{.a, .b} AS v1"), Arguments.of(EnumSet.allOf(Configuration.GeneratedNames.class), "MATCH (v0:`Movie`) RETURN v0{.a, .b} AS v0")); } @Test void gh266SizeShouldBeSupported() { Node node = Cypher.node("Node").named("node"); String cypher = Cypher.match(node).returning(node.property("thing").size()).build().getCypher(); assertThat(cypher).isEqualTo("MATCH (node:`Node`) RETURN size(node.thing)"); } @Test void gh266HasSizeUtilityShouldWork() { Node node = Cypher.node("Node").named("node"); String cypher = Cypher.match(node) .where(node.property("thing").hasSize(Cypher.literalOf(0))) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (node:`Node`) WHERE size(node.thing) = 0 RETURN *"); } @Test void gh115() { Node nodes = Cypher.node("Node").named("node").withProperties("id", Cypher.literalOf("node_42")); StatementBuilder.OngoingReadingWithoutWhere matchNodes = Cypher.match(nodes); NamedPath p = Cypher.path(Cypher.name("path")).get(); Statement statement = matchNodes.call("apoc.path.spanningTree") .withArgs(nodes.getRequiredSymbolicName(), Cypher.mapOf("relationshipFilter", Cypher.literalOf(""), "labelFilter", Cypher.literalOf(""))) .yield(p) .returningDistinct(nodes.getRequiredSymbolicName(), Cypher.collect(Cypher.relationships(p)).as("rels"), Cypher.collect(Cypher.nodes(p)).as("nodes")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (node:`Node` {id: 'node_42'}) " + "CALL apoc.path.spanningTree(node, {relationshipFilter: '', labelFilter: ''}) YIELD path " + "RETURN DISTINCT node, collect(relationships(path)) AS rels, collect(nodes(path)) AS nodes"); } @Test void gh70() { Node strawberry = Cypher.node("Fruit", Cypher.mapOf("kind", Cypher.literalOf("strawberry"))); Statement statement = Cypher.match(strawberry) .set(strawberry.property("color").to(Cypher.literalOf("red"))) .build(); assertThat(cypherRenderer.render(statement)) .matches("MATCH \\([a-zA-Z]*\\d{3}:`Fruit` \\{kind: 'strawberry'}\\) SET [a-zA-Z]*\\d{3}\\.color = 'red'"); } @Test void gh167() { final Node app = Cypher.node("Location").named("app").withProperties("uuid", Cypher.parameter("app_uuid")); final Node locStart = Cypher.node("Location").named("loc_start"); final Node resume = Cypher.node("Resume").named("r"); final Node offer = Cypher.node("Offer").named("o"); final Node startN = Cypher.node("ResumeNode").named("start_n"); final Relationship aFl = app.relationshipFrom(locStart, "PART_OF").length(0, 3); final Relationship lFr = locStart.relationshipFrom(resume, "IN", "IN_ANALYTICS"); @SuppressWarnings("deprecation") Statement statement = Cypher.match(aFl, lFr) .withDistinct(resume, locStart, app) .match(resume.relationshipTo(offer.withProperties("is_valid", Cypher.literalTrue()), "IN_COHORT_OF") .relationshipTo(Cypher.anyNode("app"), "IN")) .withDistinct(resume, locStart, app, offer) .match(offer.relationshipTo(startN, "FOR")) .where(Functions.id(startN).in(Cypher.parameter("start_ids"))) .returningDistinct(resume, locStart, app, offer, startN) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (app:`Location` {uuid: $app_uuid})<-[:`PART_OF`*0..3]-(loc_start:`Location`), (loc_start)<-[:`IN`|`IN_ANALYTICS`]-(r:`Resume`) WITH DISTINCT r, loc_start, app MATCH (r)-[:`IN_COHORT_OF`]->(o:`Offer` {is_valid: true})-[:`IN`]->(app) WITH DISTINCT r, loc_start, app, o MATCH (o)-[:`FOR`]->(start_n:`ResumeNode`) WHERE id(start_n) IN $start_ids RETURN DISTINCT r, loc_start, app, o, start_n"); } @Test void gh174() { final Node r = Cypher.node("Resume").named("r"); final Node o = Cypher.node("Offer").named("o"); Statement s = Cypher.match(r.relationshipTo(o, "FOR")) .where(r.hasLabels("LastResume").not()) .and(Cypher.coalesce(o.property("valid_only"), Cypher.literalFalse()) .isEqualTo(Cypher.literalFalse()) .and(r.hasLabels("InvalidStatus").not()) .or(o.property("valid_only").isTrue().and(r.hasLabels("InvalidStatus")))) .returningDistinct(r, o) .build(); assertThat(cypherRenderer.render(s)).isEqualTo( "MATCH (r:`Resume`)-[:`FOR`]->(o:`Offer`) WHERE (NOT (r:`LastResume`) AND ((coalesce(o.valid_only, false) = false AND NOT (r:`InvalidStatus`)) OR (o.valid_only = true AND r:`InvalidStatus`))) RETURN DISTINCT r, o"); } @Test void gh184() { final Node r = Cypher.node("Resume").named("r"); final Node u = Cypher.node("UserSearchable").named("u"); final Node o = Cypher.node("Offer").named("o"); Statement s = Cypher.match(r.relationshipFrom(u, "HAS")) .where(r.hasLabels("LastResume").not()) .and(Cypher.coalesce(o.property("valid_only"), Cypher.literalFalse()) .isEqualTo(Cypher.literalFalse()) .and(r.hasLabels("InvalidStatus").not()) .or(o.property("valid_only").isTrue().and(r.hasLabels("ValidStatus")))) .and(r.property("is_internship") .isTrue() .and(Cypher.size(r.relationshipTo(Cypher.anyNode(), "PART_OF")).isEmpty()) .not()) .and(r.property("is_sandwich_training") .isTrue() .and(Cypher.size(r.relationshipTo(Cypher.anyNode(), "PART_OF")).isEmpty()) .not()) .returningDistinct(r, o) .build(); assertThat(cypherRenderer.render(s)).isEqualTo("MATCH (r:`Resume`)<-[:`HAS`]-(u:`UserSearchable`) " + "WHERE (NOT (r:`LastResume`) " + "AND ((coalesce(o.valid_only, false) = false " + "AND NOT (r:`InvalidStatus`)) " + "OR (o.valid_only = true " + "AND r:`ValidStatus`)) " + "AND NOT (" + "(r.is_internship = true AND size(size((r)-[:`PART_OF`]->())) = 0)" + ") " + "AND NOT (" + "(r.is_sandwich_training = true AND size(size((r)-[:`PART_OF`]->())) = 0)" + ")" + ") RETURN DISTINCT r, o"); } @Test void gh185() { final Node r = Cypher.node("Resume").named("r"); final Node u = Cypher.node("UserSearchable").named("u"); Statement s = Cypher.match(r.relationshipFrom(u, "HAS")) .where(Cypher.not(Cypher.exists(r.relationshipTo(u, "EXCLUDES")))) .returningDistinct(r) .build(); assertThat(cypherRenderer.render(s)).isEqualTo( "MATCH (r:`Resume`)<-[:`HAS`]-(u:`UserSearchable`) WHERE NOT (exists((r)-[:`EXCLUDES`]->(u))) RETURN DISTINCT r"); } @Test void gh187() { final Node r = Cypher.node("Resume").named("r"); final Node u = Cypher.node("User").named("u"); Statement s = Cypher.match(r.relationshipFrom(u, "HAS")) .with(Cypher.head(Cypher.collect(r.getRequiredSymbolicName())).as("r")) .returning(r) .build(); assertThat(cypherRenderer.render(s)) .isEqualTo("MATCH (r:`Resume`)<-[:`HAS`]-(u:`User`) WITH head(collect(r)) AS r RETURN r"); } @Test void gh188() { final Node r = Cypher.node("Resume").named("r"); final Node u = Cypher.node("User").named("u"); Statement s = Cypher.match(r.relationshipFrom(u, "HAS")) .returning(Cypher.countDistinct(r.getRequiredSymbolicName()).as("r")) .build(); assertThat(cypherRenderer.render(s)) .isEqualTo("MATCH (r:`Resume`)<-[:`HAS`]-(u:`User`) RETURN count(DISTINCT r) AS r"); } @Test void gh197() { // avg Statement s = Cypher.match(this.person).returning(Cypher.avg(this.person.property("age"))).build(); assertThat(cypherRenderer.render(s)).isEqualTo("MATCH (person:`Person`) RETURN avg(person.age)"); // max/min final ListExpression list = Cypher.listOf(Cypher.literalOf(1), Cypher.literalOf("a"), Cypher.literalOf(null), Cypher.literalOf(0.2), Cypher.literalOf("b"), Cypher.literalOf("1"), Cypher.literalOf("99")); s = Cypher.unwind(list).as("val").returning(Cypher.max(Cypher.name("val"))).build(); assertThat(cypherRenderer.render(s)) .isEqualTo("UNWIND [1, 'a', NULL, 0.2, 'b', '1', '99'] AS val RETURN max(val)"); s = Cypher.unwind(list).as("val").returning(Cypher.min(Cypher.name("val"))).build(); assertThat(cypherRenderer.render(s)) .isEqualTo("UNWIND [1, 'a', NULL, 0.2, 'b', '1', '99'] AS val RETURN min(val)"); // percentileCont/percentileDisc s = Cypher.match(this.person).returning(Cypher.percentileCont(this.person.property("age"), 0.4)).build(); assertThat(cypherRenderer.render(s)) .isEqualTo("MATCH (person:`Person`) RETURN percentileCont(person.age, 0.4)"); s = Cypher.match(this.person).returning(Cypher.percentileDisc(this.person.property("age"), 0.5)).build(); assertThat(cypherRenderer.render(s)) .isEqualTo("MATCH (person:`Person`) RETURN percentileDisc(person.age, 0.5)"); // stDev/stDevP s = Cypher.match(this.person) .where(this.person.property("name") .in(Cypher.listOf(Cypher.literalOf("A"), Cypher.literalOf("B"), Cypher.literalOf("C")))) .returning(Cypher.stDev(this.person.property("age"))) .build(); assertThat(cypherRenderer.render(s)) .isEqualTo("MATCH (person:`Person`) WHERE person.name IN ['A', 'B', 'C'] RETURN stDev(person.age)"); s = Cypher.match(this.person) .where(this.person.property("name") .in(Cypher.listOf(Cypher.literalOf("A"), Cypher.literalOf("B"), Cypher.literalOf("C")))) .returning(Cypher.stDevP(this.person.property("age"))) .build(); assertThat(cypherRenderer.render(s)) .isEqualTo("MATCH (person:`Person`) WHERE person.name IN ['A', 'B', 'C'] RETURN stDevP(person.age)"); // sum s = Cypher.match(this.person) .with(Cypher.listOf(Cypher.mapOf("type", this.person.getRequiredSymbolicName(), "nb", Cypher.sum(this.person.getRequiredSymbolicName()))) .as("counts")) .returning(Cypher.sum(this.person.property("age"))) .build(); assertThat(cypherRenderer.render(s)).isEqualTo( "MATCH (person:`Person`) WITH [{type: person, nb: sum(person)}] AS counts RETURN sum(person.age)"); } @Test void gh200() { final Node r = Cypher.node("Resume").named("r"); Statement s = Cypher.match(r) .with(r.getRequiredSymbolicName()) .returningDistinct(r.getRequiredSymbolicName()) .build(); assertThat(cypherRenderer.render(s)).isEqualTo("MATCH (r:`Resume`) WITH r RETURN DISTINCT r"); } @Test void gh204() { final Node a = Cypher.node("A").named("a"); final Node b = Cypher.node("B").named("b"); final Node c = Cypher.node("C").named("c"); Statement s = Cypher.match(a.relationshipTo(b).relationshipTo(c).max(2)).returning(a).build(); assertThat(cypherRenderer.render(s)).isEqualTo("MATCH (a:`A`)-->(b:`B`)-[*..2]->(c:`C`) RETURN a"); } @Test void gh245() { String expected = "MATCH (person:`Person`) RETURN person{alias: person.name}"; Statement statement; statement = Cypher.match(this.person) .returning(this.person.project("alias", this.person.property("name"))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); statement = Cypher.match(this.person) .returning(this.person.project(this.person.property("name").as("alias"))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } @Test void gh44() { Node n = Cypher.anyNode("n"); Statement statement = Cypher.match(n).returning(Cypher.collectDistinct(n).as("distinctNodes")).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (n) RETURN collect(DISTINCT n) AS distinctNodes"); } @Test void gh84() { Node parent = Cypher.node("Parent").named("parent"); Node child = Cypher.node("Child").named("child"); Statement statement = Cypher.call("apoc.create.relationship") .withArgs(parent.getRequiredSymbolicName(), Cypher.literalOf("ChildEdge"), Cypher.mapOf("score", Cypher.literalOf(0.33), "weight", Cypher.literalOf(1.7)), child.getRequiredSymbolicName()) .yield("rel") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "CALL apoc.create.relationship(parent, 'ChildEdge', {score: 0.33, weight: 1.7}, child) YIELD rel"); } @Test // GH-106 void aliasesShouldBeEscapedIfNecessary() { AliasedExpression alias = Cypher.name("n").as("das ist ein Alias"); Statement statement = Cypher.match(Cypher.anyNode().named("n")).with(alias).returning(alias).build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (n) WITH n AS `das ist ein Alias` RETURN `das ist ein Alias`"); } @Test // GH-106 void projectedPropertiesShouldBeEscapedIfNecessary() { Node node = Cypher.anyNode().named("n"); Statement statement = Cypher.match(node).returning(node.project("property 1", "property 2")).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (n) RETURN n{.`property 1`, .`property 2`}"); } @Test // GH-106 void mapKeysShouldBeEscapedIfNecessary() { Statement statement = Cypher .returning(Cypher.mapOf("key 1", Cypher.literalTrue(), "key 2", Cypher.literalFalse())) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN {`key 1`: true, `key 2`: false}"); } @Test // GH-121 void aliasOnWrongPosition() { SymbolicName u = Cypher.name("u"); SymbolicName rn = Cypher.name("rn"); SymbolicName nn = Cypher.name("nn"); Node rnNode = Cypher.node("SomeLabel").named(rn); var rnAliasedAsNN = rnNode.as("nn"); Statement statement = Cypher.match(Cypher.node("User").named(u), rnNode, Cypher.node("SomeLabel").named(nn)) .withDistinct(u, rn, rnAliasedAsNN) .returning(u, rn, rnAliasedAsNN) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (u:`User`), (rn:`SomeLabel`), (nn:`SomeLabel`) WITH DISTINCT u, rn, rn AS nn RETURN u, rn, nn"); } @Test // GH-123 void propertiesOfFunctions() { Statement statement = Cypher.returning(Cypher.property(Cypher.datetime(), "epochSeconds")).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN datetime().epochSeconds"); } @Test // GH-123 void propertiesOfFunctionsInsideQuery() { var collectedThings = Cypher.collect(Cypher.name("n")).as("collectedThings"); Statement statement = Cypher.match(Cypher.anyNode().named("n")) .with(collectedThings) .returning(Cypher.property(Cypher.last(collectedThings), "name")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (n) WITH collect(n) AS collectedThings RETURN last(collectedThings).name"); } @Test void gh127() { SymbolicName key = Cypher.name("key"); String dynamicPrefix = "properties."; Statement statement = Cypher.match(this.person) .returning(this.person.project("json", Cypher.call("apoc.map.fromPairs") .withArgs(Cypher.listWith(key) .in(Cypher.call("keys").withArgs(this.person.getRequiredSymbolicName()).asFunction()) .where(key.startsWith(Cypher.literalOf(dynamicPrefix))) .returning(Cypher.call("substring") .withArgs(key, Cypher.literalOf(dynamicPrefix.length())) .asFunction(), this.person.property(key))) .asFunction())) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (person:`Person`) " + "RETURN person{" + "json: apoc.map.fromPairs([key IN keys(person) WHERE key STARTS WITH 'properties.' | [substring(key, 11), " + "person[key]]])" + "}"); } @Test // GH-133 void allowSymbolicNamesAsCondition() { Node company = Cypher.node("Company").named("company"); SymbolicName cond = Cypher.name("cond"); StatementBuilder.OngoingReadingAndReturn cypher = Cypher.match(company) .where(Cypher.any(cond) .in(Cypher.listBasedOn(company.relationshipTo(this.person, "WORKS_AT")) .returning(this.person.property("name").isEqualTo(Cypher.parameter("name")))) .where(cond.asCondition())) .returning(company); assertThat(cypherRenderer.render(cypher.build())).isEqualTo( "MATCH (company:`Company`) WHERE any(cond IN [(company)-[:`WORKS_AT`]->(person:`Person`) | person.name = $name] WHERE cond) RETURN company"); } @Test // GH-131 void projectSymbolicNames() { Node user = Cypher.node("User").named("user"); Node userKnows = Cypher.node("User").named("userKnows"); SymbolicName sortedElement = Cypher.name("sortedElement"); PatternComprehension innerPatternComprehension = Cypher.listBasedOn(user.relationshipTo(userKnows, "KNOWS")) .returning(userKnows.project("born", userKnows.property("born"))); Statement statement = Cypher.match(user) .returning(user.project("knows", Cypher.listWith(sortedElement) .in(innerPatternComprehension) .returning(sortedElement.project("born", Cypher.mapOf("formatted", Cypher.call("toString").withArgs(Cypher.property(sortedElement, "born")).asFunction()))))) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (user:`User`) RETURN user{knows: [sortedElement IN [(user)-[:`KNOWS`]->(userKnows:`User`) | userKnows{born: userKnows.born}] | sortedElement{born: {formatted: toString(sortedElement.born)}}]}"); } @Test // GH-128 void relationshipPatternsAsCondition() { Statement statement = Cypher.match(this.person) .where(this.person.relationshipTo(Cypher.anyNode(), "A") .asCondition() .or(this.person.relationshipTo(Cypher.anyNode(), "B"))) .and(this.person.relationshipTo(Cypher.anyNode(), "C") .asCondition() .or(this.person.relationshipTo(Cypher.anyNode(), "D") .asCondition() .and(this.person.relationshipTo(Cypher.anyNode(), "E"))) .or(this.person.relationshipTo(Cypher.anyNode(), "F"))) .returning(this.person) .build(); String expected = ("" + "MATCH (person:`Person`) WHERE (" + " (" + " (person)-[:`A`]->() OR (person)-[:`B`]->()" + " ) AND (" + " (" + " (person)-[:`C`]->() OR (" + " (person)-[:`D`]->() AND (person)-[:`E`]->()" + " )" + " ) OR (person)-[:`F`]->())" + ") RETURN person") .replaceAll("\\s{2,}", ""); assertThat(cypherRenderer.render(statement)).isEqualTo(expected); } @Test // GH-142 void pointShouldAcceptExpressionToo() { Parameter location = Cypher.parameter("location"); Property distance = Cypher.property(location, "distance"); Expression point = Cypher.point(Cypher.property(location, "point")); Statement statement = Cypher.match(this.person) .where(Cypher.distance(this.person.property("location"), point).isEqualTo(distance)) .returning(this.person) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (person:`Person`) WHERE point.distance(person.location, point($location.point)) = $location.distance RETURN person"); } @Test // GH-141 void propertiesShouldBeExtractableFromExpressions() { Parameter location = Cypher.parameter("location"); Expression point = Cypher.call("point").withArgs(location.property("point")).asFunction(); Property distance = Cypher.property(location, "distance"); Statement statement = Cypher.match(this.person) .where(Cypher.distance(this.person.property("location"), point).isEqualTo(distance)) .returning(this.person) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (person:`Person`) WHERE point.distance(person.location, point($location.point)) = $location.distance RETURN person"); } @Test void removeAllPropertiesShouldWork() { Node n = Cypher.node("DeleteMe").named("n"); String cypher = Cypher.match(n) .set(n, Cypher.mapOf()) .set(n.property("newProperty").to(Cypher.literalOf("aValue"))) .returning(n) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (n:`DeleteMe`) SET n = {} SET n.newProperty = 'aValue' RETURN n"); } @Test // GH-168 void containersMustBeMutatableByProperties() { Node nodeA = Cypher.node("Target").named("t"); Node nodeB = Cypher.node("Source").named("s"); String cypher = Cypher.match(nodeA, nodeB).mutate(nodeA, nodeB.property("whatever")).build().getCypher(); assertThat(cypher).isEqualTo("MATCH (t:`Target`), (s:`Source`) SET t += s.whatever"); } @ParameterizedTest // GH-152 @MethodSource("relpatternChainingArgs") void relpatternChaining(boolean multihops, int length, boolean backward, String expected) { Node start = Cypher.anyNode("s"); Node end = Cypher.anyNode("e"); PatternElement result; if (multihops) { RelationshipChain leadsTo; leadsTo = start.relationshipTo(Cypher.node("Resume"), "PART_OF") .length(0, 1) .relationshipTo(Cypher.node("Resume"), "IS_PARALLEL") .length(0, 1); if (backward) { leadsTo = leadsTo.relationshipFrom(end, "LEADS_TO").named("l"); } else { leadsTo = leadsTo.relationshipTo(end, "LEADS_TO").named("l"); } if (length > 1) { leadsTo = leadsTo.length(length, length); } result = leadsTo; } else { Relationship leadsTo; if (backward) { leadsTo = start.relationshipFrom(end, "LEADS_TO").named("l"); } else { leadsTo = start.relationshipTo(end, "LEADS_TO").named("l"); } if (length > 1) { leadsTo = leadsTo.length(length, length); } result = leadsTo; } String cypher = Cypher.match(result).returning("s", "l", "e").build().getCypher(); assertThat(cypher).isEqualTo(expected); } @Test // GH-187 void returningRawShouldWork() { String userProvidedCypher = "MATCH (this)-[:LINK]-(o:Other) RETURN o"; Node node = Cypher.node("Node").named("node"); SymbolicName result = Cypher.name("result"); Statement statement = Cypher.match(node) .call(Cypher.with(node) // https://neo4j.com/docs/cypher-manual/current/clauses/call-subquery/#subquery-correlated-importing // > Aliasing or expressions are not supported in importing WITH clauses - // e.g. WITH a AS b or WITH a+1 AS b. .with(node.as("this")) .returningRaw(Cypher.raw(userProvidedCypher).as(result)) .build()) .returning(result.project("foo", "bar")) .build(); String cypher = Renderer.getRenderer(Configuration.prettyPrinting()).render(statement); assertThat(cypher).isEqualTo(""" MATCH (node:Node) CALL (node) { WITH node AS this MATCH (this)-[:LINK]-(o:Other) RETURN o AS result } RETURN result { .foo, .bar }"""); } @Test void veryRawCallShouldWork() { SymbolicName msg = Cypher.name("message"); String statement = Cypher.unwind(Cypher.parameter("events")) .as(msg) .with(msg.property("value").as("event"), msg.property("header").as("header"), msg.property("key").as("key"), msg.property("value").as("value")) .callRawCypher("WITH key, value CREATE (e:Event {key: key, value: value})") .build() .getCypher(); assertThat(statement).isEqualTo( "UNWIND $events AS message WITH message.value AS event, message.header AS header, message.key AS key, message.value AS value CALL (*) {WITH key, value CREATE (e:Event {key: key, value: value})}"); } @Test // GH-190 void mixedWithShouldMakeSense() { Node node = Cypher.node("Node").named("node"); Expression someExpression = Cypher.literalFalse(); Property ll = node.property("ll"); Property l = node.property("l"); AliasedExpression aCase = Cypher.caseExpression().when(ll.isNull()).then(l).elseDefault(ll).as("l"); String cypher = Cypher.match(node) .with(node, someExpression.as("f"), aCase) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo( "MATCH (node:`Node`) WITH node, false AS f, CASE WHEN node.ll IS NULL THEN node.l ELSE node.ll END AS l RETURN *"); } @Test // GH-190 void withAliasOnTopLevel() { String cypher = Cypher.with(Cypher.literalFalse().as("f")).returning(Cypher.asterisk()).build().getCypher(); assertThat(cypher).isEqualTo("WITH false AS f RETURN *"); } @Test // GH-190 void mixedWithDistinctShouldMakeSense() { Node node = Cypher.node("Node").named("node"); Expression someExpression = Cypher.literalFalse(); Property ll = node.property("ll"); Property l = node.property("l"); AliasedExpression aCase = Cypher.caseExpression().when(ll.isNull()).then(l).elseDefault(ll).as("l"); String cypher = Cypher.match(node) .withDistinct(node, someExpression.as("f"), aCase) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo( "MATCH (node:`Node`) WITH DISTINCT node, false AS f, CASE WHEN node.ll IS NULL THEN node.l ELSE node.ll END AS l RETURN *"); } @Test // GH-190 void heterogenAliasWithDistinctShouldMakeSense() { Node node = Cypher.node("Node").named("node"); Expression someExpression = Cypher.literalFalse(); String cypher = Cypher.match(node) .withDistinct(someExpression.as("f"), Cypher.date().as("aDate")) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (node:`Node`) WITH DISTINCT false AS f, date() AS aDate RETURN *"); } @Test // GH-190 void symbolicNameAndAlias() { Node node = Cypher.node("Node").named("node"); Expression someExpression = Cypher.literalFalse(); String cypher = Cypher.match(node) .with(Cypher.name("n"), someExpression.as("f"), Cypher.date().as("aDate")) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (node:`Node`) WITH n, false AS f, date() AS aDate RETURN *"); } @Test // GH-192 void patternExpressionsAreNotAllowedToIntroduceNewVariables() { Node bike = Cypher.node("Bike").named("b"); Node owner = Cypher.node("Person").named("o"); Relationship owns = owner.relationshipTo(bike, "OWNS").named("r"); Property p = owns.property("x"); Statement statement = Cypher.match(bike) .where(owns.asCondition()) .with(bike) .match(owns) .returning(bike.property("f"), p) .build(); assertThat(statement.getCypher()).isEqualTo( "MATCH (b:`Bike`) WHERE (:`Person`)-[:`OWNS`]->(b) WITH b MATCH (o:`Person`)-[r:`OWNS`]->(b) RETURN b.f, r.x"); statement = Cypher.match(owns).where(owns.asCondition()).returning(owns).build(); assertThat(statement.getCypher()) .isEqualTo("MATCH (o:`Person`)-[r:`OWNS`]->(b:`Bike`) WHERE (o)-[r]->(b) RETURN r"); } @Test // GH-193 void matchAfterYieldShouldWorkStandalone() { Node g = Cypher.node("Group").named("g"); Node a = Cypher.node("Asset").named("a"); Node d = Cypher.node("Device").named("d"); SymbolicName node = Cypher.name("node"); String cypher = Cypher.call("db.index.fulltext.queryNodes") .withArgs(Cypher.literalOf("livesearch"), Cypher.literalOf("*a*")) .yield(node) .match(g.relationshipTo(a, "GROUPS") .relationshipFrom(Cypher.node("Deploy"), "ON") .relationshipFrom(d, "SCHEDULED")) .where(a.property("asset_id").isEqualTo(Cypher.property(node, "asset_id"))) .withDistinct(Cypher.collect(d.project(d.property("sigfox_id"), a)).as("assetdata")) .returning("assetdata") .build() .getCypher(); assertThat(cypher).isEqualTo("CALL db.index.fulltext.queryNodes('livesearch', '*a*') " + "YIELD node " + "MATCH (g:`Group`)-[:`GROUPS`]->(a:`Asset`)<-[:`ON`]-(:`Deploy`)<-[:`SCHEDULED`]-(d:`Device`) " + "WHERE a.asset_id = node.asset_id " + "WITH DISTINCT collect(d{.sigfox_id, a}) AS assetdata RETURN assetdata"); } @Test // GH-193 void matchAfterYieldShouldWorkInQuery() { Node g = Cypher.node("Group").named("g"); Node a = Cypher.node("Asset").named("a"); Node d = Cypher.node("Device").named("d"); SymbolicName node = Cypher.name("node"); SymbolicName nameOfIndex = Cypher.name("nameOfIndex"); String cypher = Cypher.with(Cypher.parameter("p").as(nameOfIndex)) .call("db.index.fulltext.queryNodes") .withArgs(nameOfIndex, Cypher.literalOf("*a*")) .yield(node) .match(g.relationshipTo(a, "GROUPS") .relationshipFrom(Cypher.node("Deploy"), "ON") .relationshipFrom(d, "SCHEDULED")) .where(a.property("asset_id").isEqualTo(Cypher.property(node, "asset_id"))) .withDistinct(Cypher.collect(d.project(d.property("sigfox_id"), a)).as("assetdata")) .returning("assetdata") .build() .getCypher(); assertThat(cypher) .isEqualTo("WITH $p AS nameOfIndex CALL db.index.fulltext.queryNodes(nameOfIndex, '*a*') " + "YIELD node " + "MATCH (g:`Group`)-[:`GROUPS`]->(a:`Asset`)<-[:`ON`]-(:`Deploy`)<-[:`SCHEDULED`]-(d:`Device`) " + "WHERE a.asset_id = node.asset_id " + "WITH DISTINCT collect(d{.sigfox_id, a}) AS assetdata RETURN assetdata"); } @Test // GH-189 void propertyForExpressionWithCollectionOfNames() { Node node = Cypher.node("Node").named("n"); String cypher = Cypher.match(node) .returning(Cypher.property(node.getRequiredSymbolicName(), Collections.singleton("name"))) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (n:`Node`) RETURN n.name"); } @Test // GH-189 void exposesWithBasedOnExpressionCollectionOnNext() { Node node = Cypher.node("Node").named("n"); String cypher = Cypher.match(node) .with(Collections.singleton(node.getRequiredSymbolicName())) // DefaultStatementBuilder .with(Collections.singleton(node.getRequiredSymbolicName())) // DefaultStatementWithWithBuilder .call("my.procedure") .yield("x") .with(Collections.singleton(node.getRequiredSymbolicName())) // InQueryCallBuilder .returning(node) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (n:`Node`) WITH n WITH n CALL my.procedure() YIELD x WITH n RETURN n"); } @Test // GH-189 void exposesWithDistinctBasedOnExpressionCollectionOnNext() { Node node = Cypher.node("Node").named("n"); String cypher = Cypher.match(node) .withDistinct(Collections.singleton(node.getRequiredSymbolicName())) // DefaultStatementBuilder .withDistinct(Collections.singleton(node.getRequiredSymbolicName())) // DefaultStatementWithWithBuilder .call("my.procedure") .yield("x") .withDistinct(Collections.singleton(node.getRequiredSymbolicName())) // InQueryCallBuilder .returning(node) .build() .getCypher(); assertThat(cypher).isEqualTo( "MATCH (n:`Node`) WITH DISTINCT n WITH DISTINCT n CALL my.procedure() YIELD x WITH DISTINCT n RETURN n"); } @Test // GH-197 void symbolicNamesInNotConditionsMustNotBeResolvedWhenConditionIsARelationshipPatternV1() { Node node = Cypher.node("Division").named("node"); Statement q = Cypher.match(node) .withDistinct(node) .where(Cypher.not(Cypher.anyNode(node.getRequiredSymbolicName()) .relationshipTo(Cypher.node("Department"), "IN") .relationshipTo(Cypher.node("Department"), "INSIDE") .properties("rel_property", Cypher.literalTrue()) .relationshipTo(Cypher.node("Employee"), "EMPLOYS"))) .returning(Cypher.asterisk()) .build(); assertThat(Renderer.getRenderer(Configuration.newConfig().build()).render(q)).isEqualTo( "MATCH (node:`Division`) WITH DISTINCT node WHERE NOT (node)-[:`IN`]->(:`Department`)-[:`INSIDE` {rel_property: true}]->(:`Department`)-[:`EMPLOYS`]->(:`Employee`) RETURN *"); assertThat(Renderer.getRenderer(Configuration.newConfig().alwaysEscapeNames(false).build()).render(q)) .isEqualTo( "MATCH (node:Division) WITH DISTINCT node WHERE NOT (node)-[:IN]->(:Department)-[:INSIDE {rel_property: true}]->(:Department)-[:EMPLOYS]->(:Employee) RETURN *"); assertThat(Renderer.getRenderer(Configuration.prettyPrinting()).render(q)).isEqualTo(""" MATCH (node:Division) WITH DISTINCT node WHERE NOT (node)-[:IN]->(:Department)-[:INSIDE { rel_property: true }]->(:Department)-[:EMPLOYS]->(:Employee) RETURN *"""); } @Test // GH-197 void symbolicNamesInNotConditionsMustNotBeResolvedWhenConditionIsARelationshipPatternV2() { Node node = Cypher.node("Division").named("node"); Statement q = Cypher.match(node) .withDistinct(node) .where(Cypher.not(node.relationshipTo(Cypher.node("Department"), "IN") .relationshipTo(Cypher.node("Department"), "INSIDE") .properties("rel_property", Cypher.literalTrue()) .relationshipTo(Cypher.node("Employee"), "EMPLOYS"))) .returning(Cypher.asterisk()) .build(); assertThat(Renderer.getRenderer(Configuration.newConfig().build()).render(q)).isEqualTo( "MATCH (node:`Division`) WITH DISTINCT node WHERE NOT (node)-[:`IN`]->(:`Department`)-[:`INSIDE` {rel_property: true}]->(:`Department`)-[:`EMPLOYS`]->(:`Employee`) RETURN *"); assertThat(Renderer.getRenderer(Configuration.newConfig().alwaysEscapeNames(false).build()).render(q)) .isEqualTo( "MATCH (node:Division) WITH DISTINCT node WHERE NOT (node)-[:IN]->(:Department)-[:INSIDE {rel_property: true}]->(:Department)-[:EMPLOYS]->(:Employee) RETURN *"); assertThat(Renderer.getRenderer(Configuration.prettyPrinting()).render(q)).isEqualTo(""" MATCH (node:Division) WITH DISTINCT node WHERE NOT (node)-[:IN]->(:Department)-[:INSIDE { rel_property: true }]->(:Department)-[:EMPLOYS]->(:Employee) RETURN *"""); } @Test // GH-197 void symbolicNamesMustBeClearedAfterUnion() { Node actor = Cypher.node("Actor").named("n"); Node movie = Cypher.node("Movie").named("n"); Statement stmt = Cypher.unionAll(Cypher.match(actor).returning(actor.property("name").as("name")).build(), Cypher.match(movie).returning(movie.property("title").as("name")).build()); assertThat(stmt.getCypher()) .isEqualTo("MATCH (n:`Actor`) RETURN n.name AS name UNION ALL MATCH (n:`Movie`) RETURN n.title AS name"); } @Test // GH-203 void patternComprehensionMustBeScoped() { Node testNode = Cypher.node("Department").named("d"); Node user = Cypher.node("User").named("u"); Statement query = Cypher.match(testNode) .returning(testNode.project(Cypher.asterisk(), "firstname", Cypher.listBasedOn(testNode.relationshipTo(user)).returning(user.property("firstname")), "lastname", Cypher.listBasedOn(testNode.relationshipTo(user)).returning(user.property("lastname")))) .build(); assertThat(query.getCypher()).isEqualTo( "MATCH (d:`Department`) RETURN d{.*, firstname: [(d)-->(u:`User`) | u.firstname], lastname: [(d)-->(u:`User`) | u.lastname]}"); } @Test // GH-319 void communitySite20220304() { SymbolicName nodes = Cypher.name("nodes"); SymbolicName relations = Cypher.name("relations"); SymbolicName second_nodes = Cypher.name("second_nodes"); SymbolicName second_relations = Cypher.name("second_relations"); NamedPath first_path = Cypher.path("p") .definedBy(Cypher.node("lookingType").relationshipFrom(Cypher.anyNode(), "specifiedRelation")); NamedPath second_path = Cypher.path("second_p") .definedBy(Cypher.anyNode("n") .relationshipTo(Cypher.anyNode().named(second_nodes), "otherRelation") .named(second_relations)); Statement inner = Cypher.unwind(nodes) .as("n") .with("n") .match(second_path) .returning(second_nodes, second_relations) .build(); Statement completeStatement = Cypher.match(first_path) .with(Cypher.nodes(first_path).as(nodes), Cypher.relationships(first_path).as(relations)) .call(inner, nodes) .returning(nodes, relations, Cypher.collect(second_nodes), Cypher.collect(second_relations)) .build(); Renderer renderer = Renderer.getRenderer(Configuration.prettyPrinting()); String cypher = renderer.render(completeStatement); String expected = """ MATCH p = (:lookingType)<-[:specifiedRelation]-() WITH nodes(p) AS nodes, relationships(p) AS relations CALL (nodes) { UNWIND nodes AS n WITH n MATCH second_p = (n)-[second_relations:otherRelation]->(second_nodes) RETURN second_nodes, second_relations } RETURN nodes, relations, collect(second_nodes), collect(second_relations)"""; assertThat(cypher).isEqualTo(expected); } @SuppressWarnings("removal") @ParameterizedTest // GH-319 @EnumSource(Dialect.class) void subqueryWithRename(Dialect dialect) { SymbolicName nodes = Cypher.name("nodes"); SymbolicName relations = Cypher.name("relations"); NamedPath first_path = Cypher.path("p") .definedBy(Cypher.node("Target").relationshipFrom(Cypher.anyNode(), "REL")); Statement completeStatement = Cypher.match(first_path) .with(Cypher.nodes(first_path).as(nodes), Cypher.relationships(first_path).as(relations)) .call(Cypher.returning(Cypher.name("x")).build(), nodes.as("x")) .returning(Cypher.asterisk()) .build(); Renderer renderer = Renderer .getRenderer(Configuration.newConfig().withDialect(dialect).withPrettyPrint(true).build()); String cypher = renderer.render(completeStatement); String expected = """ MATCH p = (:Target)<-[:REL]-() WITH nodes(p) AS nodes, relationships(p) AS relations CALL { WITH nodes WITH nodes AS x RETURN x } RETURN *"""; if (EnumSet.complementOf(EnumSet.of(Dialect.NEO4J_4, Dialect.NEO4J_5)).contains(dialect)) { expected = expected.replace("CALL {", "CALL (nodes) {").replace(" WITH nodes\n", ""); } if (EnumSet.of(Dialect.NEO4J_5_26, Dialect.NEO4J_5_CYPHER_5).contains(dialect)) { expected = "CYPHER 5 " + expected; } else if (EnumSet.of(Dialect.NEO4J_5_CYPHER_25).contains(dialect)) { expected = "CYPHER 25 " + expected; } assertThat(cypher).isEqualTo(expected); } @Test // GH-319 void subqueryWithoutImport() { SymbolicName nodes = Cypher.name("nodes"); SymbolicName relations = Cypher.name("relations"); NamedPath first_path = Cypher.path("p") .definedBy(Cypher.node("Target").relationshipFrom(Cypher.anyNode(), "REL")); Statement completeStatement = Cypher.match(first_path) .with(Cypher.nodes(first_path).as(nodes), Cypher.relationships(first_path).as(relations)) .call(Cypher.returning(Cypher.literalOf(1)).build()) .returning(Cypher.literalTrue()) .build(); String cypher; cypher = Renderer.getRenderer(Configuration.prettyPrinting()).render(completeStatement); assertThat(cypher).isEqualTo(""" MATCH p = (:Target)<-[:REL]-() WITH nodes(p) AS nodes, relationships(p) AS relations CALL (*) { RETURN 1 } RETURN true"""); cypher = Renderer.getRenderer(Configuration.prettyPrinting()).render(completeStatement); assertThat(cypher).isEqualTo(""" MATCH p = (:Target)<-[:REL]-() WITH nodes(p) AS nodes, relationships(p) AS relations CALL (*) { RETURN 1 } RETURN true"""); cypher = Renderer .getRenderer(Configuration.newConfig().withPrettyPrint(true).withDialect(Dialect.NEO4J_4).build()) .render(completeStatement); assertThat(cypher).isEqualTo(""" MATCH p = (:Target)<-[:REL]-() WITH nodes(p) AS nodes, relationships(p) AS relations CALL { RETURN 1 } RETURN true"""); } @Test // GH-349 void allowProcedureCallWithoutResult() { Node n = Cypher.anyNode("n"); ResultStatement statement = Cypher.match(n) .call("apoc.util.validate") .withArgs(Cypher.exists(n.property("foo")).not(), Cypher.literalOf("Error"), Cypher.listOf()) .withoutResults() .returning(n) .build(); assertThat(statement.getCypher()) .isEqualTo("MATCH (n) CALL apoc.util.validate(n.foo IS NULL, 'Error', []) RETURN n"); } @Test // GH-349 void allowProcedureCallWithoutResultAndArguments() { Node n = Cypher.anyNode("n"); ResultStatement statement = Cypher.match(n).call("apoc.util.validate").withoutResults().returning(n).build(); assertThat(statement.getCypher()).isEqualTo("MATCH (n) CALL apoc.util.validate() RETURN n"); } @Test // GH-349 void wildValidate() { Statement statement = createSomewhatComplexStatement(); Renderer renderer = Renderer.getRenderer(Configuration.newConfig().alwaysEscapeNames(false).build()); assertThat(renderer.render(statement)).isEqualTo( "CALL () {CREATE (this0:Movie) SET this0.title = $this0_title WITH this0 CALL (this0) {CALL apoc.util.validate(NOT (any(r IN ['admin'] WHERE any(rr IN $auth.roles WHERE r = rr))), '@neo4j/graphql/FORBIDDEN', [0]) MERGE (this0_genres_connectOrCreate0:Genre {name: $this0_genres_connectOrCreate0_node_name}) ON CREATE SET this0_genres_connectOrCreate0.name = $this0_genres_connectOrCreate0_on_create_name MERGE (this0)-[this0_relationship_this0_genres_connectOrCreate0:IN_GENRE]->(this0_genres_connectOrCreate0) RETURN count(*)} RETURN this0} RETURN [this0{.title}] AS data"); } @Test // GH-1193 void addLabels() { Node user = Cypher.node("User").named("n"); Statement statement = Cypher.match(user).set(Cypher.setLabel(user, "Cool", "Person")).returning(user).build(); assertThat(statement.getCypher()).isEqualTo("MATCH (n:`User`) SET n:`Cool`:`Person` RETURN n"); } @Test // GH-1186 void testMultiPatternElementExist() { final Node user = Cypher.node("user").named("T1"); final Node dept = Cypher.node("dept").named("T2"); final Node parentDept = Cypher.node("dept").named("T3"); final Statement statement = Cypher.match(user) .where(Cypher .exists(List.of(user.relationshipTo(dept, "dept_id"), dept.relationshipTo(parentDept, "parent_id")))) .returning(user) .build(); assertThat(statement.getCypher()).isEqualTo( "MATCH (T1:`user`) WHERE EXISTS { (T1)-[:`dept_id`]->(T2:`dept`), (T2)-[:`parent_id`]->(T3:`dept`) } RETURN T1"); } @Test // GH-362 void nodeNeedsToBeRenderedTwiceWithPatternsInCondition() { Node n = Cypher.node("Node").named("n"); Node p = Cypher.anyNode("p"); SymbolicName x = Cypher.name("x"); Condition cond1 = Cypher.none(p.getRequiredSymbolicName()) .in(x) .where(p.relationshipTo(Cypher.anyNode(), "Y") .asCondition() .or(p.relationshipTo(Cypher.anyNode(), "Z").asCondition())); Condition cond2 = Cypher.any(p.getRequiredSymbolicName()) .in(x) .where(p.property("bar").eq(Cypher.literalTrue())); Statement s = Cypher.match(n) .with(Cypher.collect(n).as(x)) .where(cond1.and(cond2)) .returning(Cypher.count(n)) .build(); assertThat(s.getCypher()).isEqualTo( "MATCH (n:`Node`) WITH collect(n) AS x WHERE (none(p IN x WHERE ((p)-[:`Y`]->() OR (p)-[:`Z`]->())) AND any(p IN x WHERE p.bar = true)) RETURN count(n)"); } @Test // GH-350 void compoundConditionShouldBeImmutableAsStatedInTheDocs() { Node n = Cypher.anyNode("n"); Condition a = org.neo4j.cypherdsl.core.Cypher.noCondition(); a = a.and(n.property("b").eq(Cypher.literalOf(1))); Condition temp = a; a = a.and(n.property("c").eq(Cypher.literalOf(2))); ResultStatement statement = Cypher.match(n).where(temp).returning(n).build(); assertThat(statement.getCypher()).isEqualTo("MATCH (n) WHERE n.b = 1 RETURN n"); } @Test // GH-389 void shouldRenderCorrectDateTimeCall() { Expression datetime = Cypher.datetime(toMap(ZonedDateTime.parse("2022-06-19T15:47:38.590917308Z[UTC]"))); assertThat(Cypher.returning(datetime).build().getCypher()).isEqualTo( "RETURN datetime({year: 2022, month: 6, day: 19, hour: 15, minute: 47, second: 38, nanosecond: 590917308, timezone: 'UTC'})"); } @Test // GH-388 void shouldRenderSetOpOnNodeWithMap() { Node node = Cypher.node("CordraObject") .named("existingNode") .withProperties("_id", Cypher.literalOf("test/55de0539eb1e14f26a04")); Statement statement = Cypher.merge(node) .set(node, Cypher.mapOf("_id", Cypher.literalOf("test/55de0539eb1e14f26a04"), "_type", Cypher.literalOf("Movie"), "title", Cypher.literalOf("Top Gun"), "released", Cypher.literalOf(1986))) .returning(node) .build(); assertThat(Renderer.getRenderer(Configuration.prettyPrinting()).render(statement)).isEqualTo(""" MERGE (existingNode:CordraObject { _id: 'test/55de0539eb1e14f26a04' }) SET existingNode = { _id: 'test/55de0539eb1e14f26a04', _type: 'Movie', title: 'Top Gun', released: 1986 } RETURN existingNode"""); } @Test // GH-388 void shouldProvideSetOperations() { Node node = Cypher.node("CordraObject") .named("existingNode") .withProperties("_id", Cypher.literalOf("test/55de0539eb1e14f26a04")); Operation thisChangesEverything = node .set(Cypher.mapOf("_id", Cypher.literalOf("test/55de0539eb1e14f26a04"), "_type", Cypher.literalOf("Movie"), "title", Cypher.literalOf("Top Gun"), "released", Cypher.literalOf(1986))); Statement statement = Cypher.merge(node).set(thisChangesEverything).returning(node).build(); assertThat(Renderer.getRenderer(Configuration.prettyPrinting()).render(statement)).isEqualTo(""" MERGE (existingNode:CordraObject { _id: 'test/55de0539eb1e14f26a04' }) SET existingNode = { _id: 'test/55de0539eb1e14f26a04', _type: 'Movie', title: 'Top Gun', released: 1986 } RETURN existingNode"""); } @Test // GH-388 void shouldProvideSetOperationsForParameter() { Node node = Cypher.node("CordraObject") .named("existingNode") .withProperties("_id", Cypher.literalOf("test/55de0539eb1e14f26a04")); Operation thisChangesEverything = node.set(Cypher.parameter("aNewMap")); Statement statement = Cypher.merge(node).set(thisChangesEverything).returning(node).build(); assertThat(Renderer.getRenderer(Configuration.prettyPrinting()).render(statement)).isEqualTo(""" MERGE (existingNode:CordraObject { _id: 'test/55de0539eb1e14f26a04' }) SET existingNode = $aNewMap RETURN existingNode"""); } @Test // GH-419 void aliasedElementsShouldBeCarriedForwardWithWithToo() { Node objectInstanceNode = Cypher.node("ObjectInstance").named("oi"); Node attributeTypeNode = Cypher.node("AttributeType").named("att"); Node attributeNode = Cypher.node("Attribute").named("at"); SymbolicName attributeTypeAndValue = Cypher.name("attributeTypeAndValue"); SymbolicName collection = Cypher.name("collection"); SymbolicName oi = objectInstanceNode.getRequiredSymbolicName(); Statement statement = Cypher.match(objectInstanceNode) .with(Cypher.mapOf(oi.getValue(), oi).as(collection)) .unwind(Cypher.parameter("attributes")) .as(attributeTypeAndValue) .with(attributeTypeAndValue, collection) .match(attributeTypeNode.withProperties("name", attributeTypeAndValue.property("name")) .relationshipFrom(attributeNode, "OF_TYPE")) .with(attributeNode, collection.property("oi").as(oi)) .match(objectInstanceNode.relationshipTo(attributeNode, "IS_IDENTIFIED_BY")) .returning(attributeNode) .build(); String cypher = Renderer .getRenderer(Configuration.newConfig().withPrettyPrint(true).alwaysEscapeNames(true).build()) .render(statement); assertThat(cypher).isEqualTo(""" MATCH (oi:`ObjectInstance`) WITH { oi: oi } AS collection UNWIND $attributes AS attributeTypeAndValue WITH attributeTypeAndValue, collection MATCH (att:`AttributeType` { name: attributeTypeAndValue.name })<-[:`OF_TYPE`]-(at:`Attribute`) WITH at, collection.oi AS oi MATCH (oi)-[:`IS_IDENTIFIED_BY`]->(at) RETURN at"""); } @Test void bbBoxManual() { Node location = Cypher.node("Location").named("loc"); Expression sw = Cypher .point(Cypher.mapOf("longitude", Cypher.literalOf(2.592773), "latitude", Cypher.literalOf(46.346928))); Expression ne = Cypher .point(Cypher.mapOf("longitude", Cypher.literalOf(18.654785), "latitude", Cypher.literalOf(55.714735))); Expression withinBBox = Cypher.call("point.withinBBox") .withArgs(location.property("coordinates"), sw, ne) .asFunction(); Condition conditions = Cypher.noCondition(); conditions = conditions.and(withinBBox.asCondition()); String stmt = Cypher.match(location).where(conditions).returning(location).build().getCypher(); assertThat(stmt).isEqualTo( "MATCH (loc:`Location`) WHERE point.withinBBox(loc.coordinates, point({longitude: 2.592773, latitude: 46.346928}), point({longitude: 18.654785, latitude: 55.714735})) RETURN loc"); } @Test void bbBox() { Node location = Cypher.node("Location").named("loc"); Expression sw = Cypher.coordinate(2.592773, 46.346928); Expression ne = Cypher.coordinate(18.654785, 55.714735); Expression withinBBox = Cypher.withinBBox(location.property("coordinates"), sw, ne); Condition conditions = Cypher.noCondition(); conditions = conditions.and(withinBBox.asCondition()); String stmt = Cypher.match(location).where(conditions).returning(location).build().getCypher(); assertThat(stmt).isEqualTo( "MATCH (loc:`Location`) WHERE point.withinBBox(loc.coordinates, point({longitude: 2.592773, latitude: 46.346928}), point({longitude: 18.654785, latitude: 55.714735})) RETURN loc"); } @Test // GH-490 void shouldAllowAsteriskInYield() { Statement yield = Cypher.call("db.info").yield(Cypher.asterisk()).build(); assertThat(Renderer.getDefaultRenderer().render(yield)).isEqualTo("CALL db.info() YIELD *"); } @Test // GH-544 void shouldAllowAsteriskInYieldAndArgs() { Statement yield = Cypher.call("dbms.listConfig") .withArgs(Cypher.literalOf("port")) .yield(Cypher.asterisk()) .build(); assertThat(Renderer.getDefaultRenderer().render(yield)).isEqualTo("CALL dbms.listConfig('port') YIELD *"); } @Test // GH-505 void testLabelRemoval() { Node node = Cypher.node("Wine").named("n"); Operation removeOp = Cypher.removeLabel(node, "Drink"); List propertyExpressions = Collections.singletonList(removeOp); @SuppressWarnings("deprecation") StatementBuilder.OngoingReadingWithWhere ongoingReadingWithWhere = Cypher.match(node) .where(Functions.id(node).isEqualTo(Cypher.literalOf(1))); String expectedMessage = "REMOVE operations are not supported in a SET clause"; assertThatIllegalArgumentException().isThrownBy(() -> ongoingReadingWithWhere.set(propertyExpressions)) .withMessage(expectedMessage); assertThatIllegalArgumentException().isThrownBy(() -> ongoingReadingWithWhere.set(removeOp)) .withMessage(expectedMessage); assertThatIllegalArgumentException().isThrownBy(() -> Cypher.match(node).set(removeOp)) .withMessage(expectedMessage); @SuppressWarnings("deprecation") String correctQuery = ongoingReadingWithWhere.remove(node, "Drink") .returning(Functions.id(node).as("id")) .build() .getCypher(); assertThat(correctQuery).isEqualTo("MATCH (n:`Wine`) WHERE id(n) = 1 REMOVE n:`Drink` RETURN id(n) AS id"); } @Test // GH-524 void unwindWithoutWith() { SymbolicName id = Cypher.name("id"); Node n = Cypher.node("Person").named("n"); Renderer renderer = Renderer.getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_5).build()); Statement statement = Cypher.unwind(Cypher.parameter("ids")) .as(id) .match(n) .where(n.elementId().isEqualTo(id)) .returning(n) .build(); assertThat(renderer.render(statement)) .isEqualTo("UNWIND $ids AS id MATCH (n:`Person`) WHERE elementId(n) = id RETURN n"); } @Test // GH-547 void mixedBagOfWith() { var cypher = Cypher.match(this.person) .with(this.person, Cypher.count(this.person.relationshipTo(Cypher.anyNode(), "ACTED_IN")).as("actedInDegree")) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo( "MATCH (person:`Person`) WITH person, COUNT { (person)-[:`ACTED_IN`]->() } AS actedInDegree RETURN *"); } @Test // GH-553 void allowCovariantForMakingDynamicCypherCreationEasier() { var patterns = List.of(Cypher.node("A").named("a"), Cypher.node("B").relationshipTo(Cypher.node("C"), "IS_RELATED").named("r")); var cypher = Cypher.match(patterns.stream().toList()).returning(Cypher.asterisk()).build().getCypher(); assertThat(cypher).isEqualTo("MATCH (a:`A`), (:`B`)-[r:`IS_RELATED`]->(:`C`) RETURN *"); } @Test // GH-585 void rerenderShouldYieldSameResultOnSameRenderer() throws NoSuchFieldException, IllegalAccessException { var anonymous = Cypher.node("Person"); var statement = Cypher.match(anonymous).delete(anonymous).build(); var s1 = statement.getCypher(); var s2 = statement.getCypher(); var defaultRenderer = Renderer.getDefaultRenderer(); var s3 = defaultRenderer.render(statement); assertThat(s1).isEqualTo(s2); assertThat(s2).isEqualTo(s3); // Nuke the cache var renderedStatementCache = defaultRenderer.getClass().getDeclaredField("renderedStatementCache"); renderedStatementCache.setAccessible(true); ((Map) renderedStatementCache.get(defaultRenderer)).clear(); var s4 = defaultRenderer.render(statement); assertThat(s3).isEqualTo(s4); } @Test // GH-589 void unionMustNotDestroyScope() { var actor = Cypher.node("Actor").named("a"); var movie = Cypher.node("Movie").named("m"); var serie = Cypher.node("Serie").named("s"); var x = Cypher.name("x"); Statement statement = Cypher.match(actor) .call(Cypher.union( Cypher.with(actor) .match(actor.relationshipTo(movie).named("ACTED_IN")) .returning(movie.as(x.getValue())) .build(), Cypher.with(actor) .match(actor.relationshipTo(serie).named("ACTED_IN")) .returning(serie.as(x.getValue())) .build())) .returning(x) .build(); var expected = """ MATCH (a:Actor) CALL (*) { WITH a MATCH (a)-[ACTED_IN]->(m:Movie) RETURN m AS x UNION WITH a MATCH (a)-[ACTED_IN]->(s:Serie) RETURN s AS x } RETURN x"""; var cypher = Renderer.getRenderer(Configuration.prettyPrinting()).render(statement); assertThat(cypher).isEqualTo(expected); } @Test // GH-595 void callWithMustImportIntoScope() { SymbolicName var = Cypher.name("var"); Node movie = Cypher.node("Movie").named("m"); Node actor = Cypher.node("Actor").named("a"); SymbolicName actors = Cypher.name("actors"); Statement statement = Cypher.unwind(Cypher.parameter("x")) .as(var) .call(Cypher.with(var).create(movie).returning(movie).build()) .call(Cypher.with(movie) .match(movie.relationshipFrom(actor, "ACTED_IN")) .returning(Cypher.collect(actor).as(actors)) .build()) .returning(movie.project(movie.property("title"), "actors", actors)) .build(); var cypher = Renderer .getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_4).withPrettyPrint(true).build()) .render(statement); assertThat(cypher).isEqualTo(""" UNWIND $x AS var CALL { WITH var CREATE (m:Movie) RETURN m } CALL { WITH m MATCH (m)<-[:ACTED_IN]-(a:Actor) RETURN collect(a) AS actors } RETURN m { .title, actors: actors }"""); cypher = Renderer.getRenderer(Configuration.newConfig().withPrettyPrint(true).build()).render(statement); assertThat(cypher).isEqualTo(""" UNWIND $x AS var CALL (var) { CREATE (m:Movie) RETURN m } CALL (m) { MATCH (m)<-[:ACTED_IN]-(a:Actor) RETURN collect(a) AS actors } RETURN m { .title, actors: actors }"""); } @Test // GH-595 void callWithMustImportIntoScopeProper() { SymbolicName var = Cypher.name("var"); Node movie = Cypher.node("Movie").named("m"); Node actor = Cypher.node("Actor").named("a"); SymbolicName actors = Cypher.name("actors"); Statement statement = Cypher.unwind(Cypher.parameter("x")) .as(var) .call(Cypher.create(movie).returning(movie).build(), var) .call(Cypher.match(movie.relationshipFrom(actor, "ACTED_IN")) .returning(Cypher.collect(actor).as(actors)) .build(), movie) .returning(movie.project(movie.property("title"), "actors", actors)) .build(); var cypher = Renderer .getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_4).withPrettyPrint(true).build()) .render(statement); assertThat(cypher).isEqualTo(""" UNWIND $x AS var CALL { WITH var CREATE (m:Movie) RETURN m } CALL { WITH m MATCH (m)<-[:ACTED_IN]-(a:Actor) RETURN collect(a) AS actors } RETURN m { .title, actors: actors }"""); cypher = Renderer.getRenderer(Configuration.newConfig().withPrettyPrint(true).build()).render(statement); assertThat(cypher).isEqualTo(""" UNWIND $x AS var CALL (var) { CREATE (m:Movie) RETURN m } CALL (m) { MATCH (m)<-[:ACTED_IN]-(a:Actor) RETURN collect(a) AS actors } RETURN m { .title, actors: actors }"""); } @ParameterizedTest(name = "{1}") // GH-605 @MethodSource void conditionExpressionShouldWorkInReturn(Statement statement, String expected) { String cypher = statement.getCypher(); assertThat(cypher).isEqualTo(expected); } @Test void manipulatingListExpressionsShouldBePossible() { var n = Cypher.node("Person").named("n"); var x = Cypher.name("x"); var rolesToAdd = Cypher.parameter("rolesToAdd"); var rolesToRemove = Cypher.parameter("rolesToRemove"); var cypher = Cypher.match(n) .returning(Cypher.listWith(x) .in(n.property("roles")) .where(x.in(rolesToAdd).and(Cypher.not(x.in(rolesToRemove)))) .returning() .add(rolesToAdd)) .build() .getCypher(); assertThat(cypher).isEqualTo( "MATCH (n:`Person`) RETURN ([x IN n.roles WHERE (x IN $rolesToAdd AND NOT (x IN $rolesToRemove))] + $rolesToAdd)"); } @Test // GH-630 void typesOfShouldWork() { var p = Cypher.path("p").definedBy(Cypher.node("Movie").relationshipFrom(Cypher.anyNode()).named("r")); var x = Cypher.name("x"); var relationTypeCast = Cypher.listWith(x).in(Cypher.relationships(p)).returning(Cypher.type(x)); assertThat(Cypher.match(p).returning(relationTypeCast).build().getCypher()) .isEqualTo("MATCH p = (:`Movie`)<-[r]-() RETURN [x IN relationships(p) | type(x)]"); // Current workaround var relationTypeCast2 = Cypher.listWith(x) .in(Cypher.relationships(p)) .returning(Cypher.call("type").withArgs(x).asFunction()); assertThat(Cypher.match(p).returning(relationTypeCast2).build().getCypher()) .isEqualTo("MATCH p = (:`Movie`)<-[r]-() RETURN [x IN relationships(p) | type(x)]"); } @Test // GH-630 void labelsOfShouldWork() { var p = Cypher.path("p").definedBy(Cypher.node("Movie").relationshipFrom(Cypher.anyNode()).named("r")); var nodes = Cypher.name("nodes"); var node = Cypher.name("node"); var labels = Cypher.name("labels"); var label = Cypher.name("label"); // The worst query ever to retrieve those labels. Don't do this at home. assertThat(Cypher.match(p) .with(Cypher.nodes(p).as(nodes)) .unwind(nodes) .as(node) .with(Cypher.labels(node).as(labels)) .unwind(labels) .as(label) .returningDistinct(label) .build() .getCypher()).isEqualTo( "MATCH p = (:`Movie`)<-[r]-() WITH nodes(p) AS nodes UNWIND nodes AS node WITH labels(node) AS labels UNWIND labels AS label RETURN DISTINCT label"); } @Test // GH-634 void withAllShouldWork() { var this0 = Cypher.node("User").named("this"); var x = Cypher.name("x"); var stmt = Cypher.match(this0) .call(Cypher.with(Cypher.asterisk()) .with(this0.as("x")) .returning(Cypher.count(Cypher.asterisk()).as(x)) .build()) .returning(x, Cypher.count(Cypher.asterisk())) .build(); assertThat(stmt.getCypher()) .isEqualTo("MATCH (this:`User`) CALL (*) {WITH this AS x RETURN count(*) AS x} RETURN x, count(*)"); assertThat(Renderer.getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_4).build()).render(stmt)) .isEqualTo("MATCH (this:`User`) CALL {WITH * WITH this AS x RETURN count(*) AS x} RETURN x, count(*)"); } @Test void withAllProperShouldWork() { var this0 = Cypher.node("User").named("this"); var x = Cypher.name("x"); var stmt = Cypher.match(this0) .call(Cypher.with(this0.as("x")).returning(Cypher.count(Cypher.asterisk()).as(x)).build(), Cypher.asterisk()) .returning(x, Cypher.count(Cypher.asterisk())) .build(); assertThat(stmt.getCypher()) .isEqualTo("MATCH (this:`User`) CALL (*) {WITH this AS x RETURN count(*) AS x} RETURN x, count(*)"); assertThat(Renderer.getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_4).build()).render(stmt)) .isEqualTo("MATCH (this:`User`) CALL {WITH * WITH this AS x RETURN count(*) AS x} RETURN x, count(*)"); } @Test // GH-634 void withAllAndThenSome() { var stmt = Cypher.match(Cypher.anyNode("n")) .with(Cypher.asterisk(), Cypher.count(Cypher.asterisk()).as("count")) .returning(Cypher.asterisk()) .build(); var expected = "MATCH (n) WITH *, count(*) AS count RETURN *"; assertThat(stmt.getCypher()).isEqualTo(expected); } @Test // GH-642 void mapLiteralShouldRenderProper() { var l = Cypher.literalOf(new TreeMap<>(Map.of("a", 1, "b", "c", "whatever", Cypher.literalOf(1)))); assertThat(l).hasToString("MapLiteral{cypher={a: 1, b: 'c', whatever: 1}}"); } @Test // GH-694 @SuppressWarnings("deprecation") void fullTextCallShouldWork() { var city = Cypher.anyNode("city"); var node = Cypher.name("node"); var score = Cypher.name("score"); var statement = Cypher .match(Cypher.path("path").definedBy(Cypher.node("Person").named("p").relationshipTo(city, "TRAVELED_TO"))) .where(Cypher.exists(Cypher.call("db.index.fulltext.queryNodes") .withArgs(Cypher.literalOf("City"), Cypher.literalOf("ham*")) .yield(node, score) .with(Cypher.asterisk()) .where(Functions.id(Cypher.anyNode(node)).eq(Functions.id(city))) .returning(node, score) .build())) .returning(Cypher.name("path")) .build(); assertThat(statement.getCypher()).isEqualTo(""" MATCH path = (p:`Person`)-[:`TRAVELED_TO`]->(city) WHERE EXISTS { CALL db.index.fulltext.queryNodes('City', 'ham*') YIELD node, score WITH * WHERE id(node) = id(city) RETURN node, score } RETURN path""".replace("\n", " ")); } @Test // GH-712 void relationshipChainsMustActivelyEnterRelationshipsDuringVisit() { var tom = Cypher.node("Person").named("person0").withProperties("name", Cypher.literalOf("Tom Hanks")); var movie = Cypher.node("Movie").named("movie0"); var coActors = Cypher.node("Person").named("person1"); var path0 = Cypher.path("path0"); var path1 = Cypher.path("path1"); var statement = Cypher .match(path0.definedBy( tom.relationshipTo(movie, "ACTED_IN").named("acted_in0").relationshipFrom(coActors, "ACTED_IN"))) .match(path1.definedBy( tom.relationshipTo(movie, "ACTED_IN").named("acted_in0").relationshipFrom(coActors, "ACTED_IN"))) .returning("path0", "path1") .build(); var cypher = Renderer.getRenderer(Configuration.prettyPrinting()).render(statement); assertThat(cypher).isEqualTo(""" MATCH path0 = (person0:Person { name: 'Tom Hanks' })-[acted_in0:ACTED_IN]->(movie0:Movie)<-[:ACTED_IN]-(person1:Person) MATCH path1 = (person0)-[acted_in0]->(movie0)<-[:ACTED_IN]-(person1) RETURN path0, path1"""); } @Test // GH-749 void nonBuilderRelationshipMethodShouldWork() { var n1 = Cypher.node("N1"); var n2 = Cypher.node("N2"); assertThat(Cypher.match(n1.relationshipWith(n2, Relationship.Direction.LTR, "X").named("x")) .returning(Cypher.asterisk()) .build() .getCypher()).isEqualTo("MATCH (:`N1`)-[x:`X`]->(:`N2`) RETURN *"); assertThat(Cypher.match(n1.relationshipWith(n2, Relationship.Direction.RTL, "X").named("x")) .returning(Cypher.asterisk()) .build() .getCypher()).isEqualTo("MATCH (:`N1`)<-[x:`X`]-(:`N2`) RETURN *"); assertThat(Cypher.match(n1.relationshipWith(n2, Relationship.Direction.UNI, "X").named("x")) .returning(Cypher.asterisk()) .build() .getCypher()).isEqualTo("MATCH (:`N1`)-[x:`X`]-(:`N2`) RETURN *"); } @Test // GH-826 void identifiablesCreatedInSubqueriesMustBeRecognizedAsSeenToo() { var n1 = Cypher.node("Foo").named("n1"); var n2 = Cypher.node("Bar").named("n2"); var resultStatement = Cypher.create(n1) .with(n1) .call(Cypher.with(n1) .merge(n2.withProperties("foo", Cypher.literalOf("x"))) .create(n1.relationshipTo(n2, "NESTED")) .returning(Cypher.count(n2).as("foo_2")) .build()) .returning(Cypher.literalTrue()) .build(); assertThat(resultStatement.getCypher()).isEqualTo( "CREATE (n1:`Foo`) WITH n1 CALL (n1) {MERGE (n2:`Bar` {foo: 'x'}) CREATE (n1)-[:`NESTED`]->(n2) RETURN count(n2) AS foo_2} RETURN true"); } @Test // GH-832 void sequentialExistingSubqueriesShouldNotHaveScopingIssues() { var n1 = Cypher.node("Foo").named("n1"); var n2 = Cypher.node("Bar").named("n2"); var resultStatement = Cypher.match(n1) .where(Cypher.match(n1.relationshipTo(n2)) .where(n2.property("bar").isFalse()) .asCondition() .or(Cypher.match(n1.relationshipTo(n2)).where(n2.property("foo").isTrue()).asCondition())) .returning(Cypher.literalTrue()) .build(); assertThat(resultStatement.getCypher()).isEqualTo( "MATCH (n1:`Foo`) " + "WHERE (EXISTS {" + " MATCH (n1)-->(n2:`Bar`)" + " WHERE n2.bar = false " + "} " + "OR EXISTS {" + " MATCH (n1)-->(n2:`Bar`)" + " WHERE n2.foo = true " + "}) " + "RETURN true"); } @Test // GH-838 void aliasMustBeIncludedInSubqueries1() { var n1 = Cypher.node("Foo").named("n1"); var point = Cypher.name("point"); var resultStatement = Cypher.match(n1) .returning(n1.project("points", Cypher.collect(Cypher.unwind(n1.property("points")) .as(point) .returning(Cypher.listOf(point.property("x"), point.property("y"))) .build()))) .build(); assertThat(resultStatement.getCypher()).isEqualTo("MATCH (n1:`Foo`) " + "RETURN n1{" + "points: COLLECT {" + " UNWIND n1.points AS point" + " RETURN [point.x, point.y]" + " }" + "}"); } @Test // GH-838 void aliasMustBeIncludedInSubqueries2() { var n1 = Cypher.node("Foo").named("n1"); var point = Cypher.name("point"); var points = Cypher.name("points"); var resultStatement = Cypher.match(n1) .returning(n1.project("points", org.neo4j.cypherdsl.core.Cypher.collect(Cypher.with(n1.property("points").as(points)) .unwind(points) .as(point) .returning(Cypher.listOf(point.property("x"), point.property("y"))) .build()))) .build(); assertThat(resultStatement.getCypher()).isEqualTo("MATCH (n1:`Foo`) " + "RETURN n1{" + "points: COLLECT {" + " WITH n1.points AS points" + " UNWIND points AS point" + " RETURN [point.x, point.y]" + " }" + "}"); } @Test // GH-838 void aliasMustBeIncludedInSubqueries3() { var n1 = Cypher.node("Foo").named("n1"); var n2 = Cypher.node("Bar").named("n2"); var point = Cypher.name("point"); var resultStatement = Cypher.match(n1) .returning(n1.project("points", Cypher.collect(Cypher.unwind(n1.property("points")) .as(point) .match(n2) .where(n2.property("loc").eq(point)) .returning(n2.project(n2.property("y").as("x"), Cypher.collect( Cypher.match(Cypher.node("FooBar").named("fb")).returning(Cypher.name("fb").as("foo")).build()) .as("y"))) .build()))) .build(); assertThat(resultStatement.getCypher()).isEqualTo("MATCH (n1:`Foo`) " + "RETURN n1{" + "points: COLLECT {" + " UNWIND n1.points AS point" + " MATCH (n2:`Bar`)" + " WHERE n2.loc = point" + " RETURN n2{x: n2.y, y: COLLECT { MATCH (fb:`FooBar`) RETURN fb AS foo }}" + " }" + "}"); } @Test // GH-533 void additionalUnitSubqueries() { var createThis1 = Cypher.name("create_this1"); var createVar0 = Cypher.name("create_var0"); var createVar2 = Cypher.name("create_var2"); var createThis5 = Cypher.name("create_this5"); var createVar3 = Cypher.name("create_var3"); var stmt = Cypher.unwind(Cypher.parameter("create_param0")) .as(createVar0) .call(Cypher.with(createVar0) .create(Cypher.node("Movie").named(createThis1)) .set(createThis1.property("id").to(createVar0.property("id"))) .with(createThis1, createVar0) .call(Cypher.with(createThis1, createVar0) .unwind(createVar0.property("actors").property("create")) .as(createVar2) .with(createVar2.property("node").as(createVar3), createVar2.property("edge").as("create_var4"), createThis1) .create(Cypher.node("Actor").named(createThis5)) .set(createThis5.property("name").to(createVar3.property("name"))) .merge(Cypher.anyNode(createThis1) .relationshipFrom(Cypher.anyNode(createThis5), "ACTED_IN") .named("create_this6")) .build()) .returning(createThis1) .build()) .returning(Cypher.collect(createThis1.project("id")).as("data")) .build(); String cypher = Renderer .getRenderer(Configuration.newConfig().withPrettyPrint(true).withDialect(Dialect.NEO4J_4).build()) .render(stmt); assertThat(cypher).isEqualTo(""" UNWIND $create_param0 AS create_var0 CALL { WITH create_var0 CREATE (create_this1:Movie) SET create_this1.id = create_var0.id WITH create_this1, create_var0 CALL { WITH create_this1, create_var0 UNWIND create_var0.actors.create AS create_var2 WITH create_var2.node AS create_var3, create_var2.edge AS create_var4, create_this1 CREATE (create_this5:Actor) SET create_this5.name = create_var3.name MERGE (create_this1)<-[create_this6:ACTED_IN]-(create_this5) } RETURN create_this1 } RETURN collect(create_this1 { .id }) AS data"""); cypher = Renderer.getRenderer(Configuration.prettyPrinting()).render(stmt); assertThat(cypher).isEqualTo(""" UNWIND $create_param0 AS create_var0 CALL (create_var0) { CREATE (create_this1:Movie) SET create_this1.id = create_var0.id WITH create_this1, create_var0 CALL (create_this1, create_var0) { UNWIND create_var0.actors.create AS create_var2 WITH create_var2.node AS create_var3, create_var2.edge AS create_var4, create_this1 CREATE (create_this5:Actor) SET create_this5.name = create_var3.name MERGE (create_this1)<-[create_this6:ACTED_IN]-(create_this5) } RETURN create_this1 } RETURN collect(create_this1 { .id }) AS data"""); } @Test // GH-533 void additionalUnitSubqueriesExplicitImports() { var createThis1 = Cypher.name("create_this1"); var createVar0 = Cypher.name("create_var0"); var createVar2 = Cypher.name("create_var2"); var createThis5 = Cypher.name("create_this5"); var createVar3 = Cypher.name("create_var3"); var stmt = Cypher.unwind(Cypher.parameter("create_param0")) .as(createVar0) .call(Cypher.with(createVar0) .create(Cypher.node("Movie").named(createThis1)) .set(createThis1.property("id").to(createVar0.property("id"))) .with(createThis1, createVar0) .call(Cypher.with(createThis1, createVar0) .unwind(createVar0.property("actors").property("create")) .as(createVar2) .with(createVar2.property("node").as(createVar3), createVar2.property("edge").as("create_var4"), createThis1) .create(Cypher.node("Actor").named(createThis5)) .set(createThis5.property("name").to(createVar3.property("name"))) .merge(Cypher.anyNode(createThis1) .relationshipFrom(Cypher.anyNode(createThis5), "ACTED_IN") .named("create_this6")) .build(), createThis1, createVar0) .returning(createThis1) .build(), createVar0) .returning(Cypher.collect(createThis1.project("id")).as("data")) .build(); String cypher = Renderer .getRenderer(Configuration.newConfig().withPrettyPrint(true).withDialect(Dialect.NEO4J_4).build()) .render(stmt); assertThat(cypher).isEqualTo(""" UNWIND $create_param0 AS create_var0 CALL { WITH create_var0 WITH create_var0 CREATE (create_this1:Movie) SET create_this1.id = create_var0.id WITH create_this1, create_var0 CALL { WITH create_this1, create_var0 WITH create_this1, create_var0 UNWIND create_var0.actors.create AS create_var2 WITH create_var2.node AS create_var3, create_var2.edge AS create_var4, create_this1 CREATE (create_this5:Actor) SET create_this5.name = create_var3.name MERGE (create_this1)<-[create_this6:ACTED_IN]-(create_this5) } RETURN create_this1 } RETURN collect(create_this1 { .id }) AS data"""); cypher = Renderer.getRenderer(Configuration.prettyPrinting()).render(stmt); assertThat(cypher).isEqualTo(""" UNWIND $create_param0 AS create_var0 CALL (create_var0) { WITH create_var0 CREATE (create_this1:Movie) SET create_this1.id = create_var0.id WITH create_this1, create_var0 CALL (create_this1, create_var0) { WITH create_this1, create_var0 UNWIND create_var0.actors.create AS create_var2 WITH create_var2.node AS create_var3, create_var2.edge AS create_var4, create_this1 CREATE (create_this5:Actor) SET create_this5.name = create_var3.name MERGE (create_this1)<-[create_this6:ACTED_IN]-(create_this5) } RETURN create_this1 } RETURN collect(create_this1 { .id }) AS data"""); } @Test // GH-903 void variablesOfListPredicatesMustNotBeScoped() { var expected = """ MATCH (this:`Movie`) WHERE single(this0 IN [(this)-[this1:`IN_GENRE`]->(this0:`Genre`) WHERE this0.name = $param0 | 1] WHERE true) RETURN this{.actorCount} AS this"""; var movie = Cypher.node("Movie").named("this"); var genre = Cypher.node("Genre").named("this0"); var rel = movie.relationshipTo(genre, "IN_GENRE").named("this1"); var statement = Cypher.match(movie) .where(Cypher.single("this0") .in(Cypher.listBasedOn(rel) .where(genre.property("name").eq(Cypher.parameter("param0"))) .returning(Cypher.literalOf(1))) .where(Cypher.isTrue())) .returning(movie.project("actorCount").as("this")) .build(); assertThat(statement.getCypher()).isEqualTo(expected.replace("\n", " ")); } @ParameterizedTest // GH-999 @EnumSource(value = Dialect.class, names = { "NEO4J_4", "NEO4J_5_DEFAULT_CYPHER" }) void subQueryFromParserScope(Dialect dialect) { var cfg = Configuration.newConfig().withPrettyPrint(true).withGeneratedNames(true).withDialect(dialect).build(); var renderer = Renderer.getRenderer(cfg); var named = Cypher.node("Movie").named("m"); var stmt = Cypher.call(Cypher.create(named).returning("m").build()) .call(Cypher .returning(named.project(Cypher.property(named.getRequiredSymbolicName(), "title")).as("movies")) .build(), named) .returning("movies") .build(); var normalized = renderer.render(stmt); var expected = (dialect == Dialect.NEO4J_4) ? """ CALL { CREATE (v0:Movie) RETURN v0 } CALL { WITH v0 RETURN v0 { .title } AS v1 } RETURN v1""" : """ CALL () { CREATE (v0:Movie) RETURN v0 } CALL (v0) { RETURN v0 { .title } AS v1 } RETURN v1"""; assertThat(normalized).isEqualTo(expected); } @Test // GH-1014 void patternExpressionMustNotIntroduceNames() { var userIdParam = Cypher.parameter("userId"); var book = Cypher.node("Book").named("b"); var userActivity = Cypher.node("UserSuggestionActivity").named("ua").withProperties("userId", userIdParam); // ua has not been used before var rel = book.relationshipBetween(userActivity); var cypher = Cypher.match(book) .where(Cypher.not(Cypher.exists(rel))) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo( "MATCH (b:`Book`) WHERE NOT (exists((b)--(:`UserSuggestionActivity` {userId: $userId}))) RETURN *"); // b has not been used before cypher = Cypher.match(userActivity) .where(Cypher.not(Cypher.exists(rel))) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo( "MATCH (ua:`UserSuggestionActivity` {userId: $userId}) WHERE NOT (exists((:`Book`)--(ua))) RETURN *"); // Both have been used before, example does not make much sense, but still cypher = Cypher.match(book.relationshipFrom(userActivity, "WROTE")) .where(Cypher.not(Cypher.exists(rel))) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo( "MATCH (b:`Book`)<-[:`WROTE`]-(ua:`UserSuggestionActivity` {userId: $userId}) WHERE NOT (exists((b)--(ua))) RETURN *"); // All expressions cypher = Cypher.match(book).returning(Cypher.size(rel)).build().getCypher(); assertThat(cypher) .isEqualTo("MATCH (b:`Book`) RETURN size((b)--(:`UserSuggestionActivity` {userId: $userId}))"); } @Test void generatedNamesWithDeeplyNestedSubquerie() { var m = Cypher.node("Movie").named("this"); var a = Cypher.node("Actor").named("this1"); var edge = Cypher.name("edge"); var this1 = Cypher.name("this1"); var edges = Cypher.name("edges"); var stmt = Cypher.match(m) .call(Cypher.match(m.relationshipFrom(a, "ACTED_IN").named("this0")) .with(Cypher.collect(Cypher.mapOf("node", this1)).as(edges)) .with(edges, Cypher.size(edges).as("totalCount")) .call(Cypher.unwind(edges) .as(edge) .with(edge.property("node").as(this1)) .call(Cypher .match(Cypher.anyNode(this1) .relationshipTo(Cypher.node("Movie").named("this3"), "ACTED_IN") .named("this2")) .returning(Cypher.asterisk()) .build(), this1) .build(), edges) .build(), m) .returning(Cypher.asterisk()) .build(); assertThat(Renderer .getRenderer(Configuration.newConfig() .withPrettyPrint(true) .withGeneratedNames(true) .withDialect(Dialect.NEO4J_4) .build()) .render(stmt)).isEqualTo(""" MATCH (v0:Movie) CALL { WITH v0 MATCH (v0)<-[v1:ACTED_IN]-(v2:Actor) WITH collect( { node: v2 }) AS v3 WITH v3, size(v3) AS v4 CALL { WITH v3 UNWIND v3 AS v5 WITH v5.node AS v6 CALL { WITH v6 MATCH (v6)-[v0:ACTED_IN]->(v1:Movie) RETURN * } } } RETURN *"""); assertThat( Renderer.getRenderer(Configuration.newConfig().withPrettyPrint(true).withGeneratedNames(true).build()) .render(stmt)) .isEqualTo(""" MATCH (v0:Movie) CALL (v0) { MATCH (v0)<-[v1:ACTED_IN]-(v2:Actor) WITH collect( { node: v2 }) AS v3 WITH v3, size(v3) AS v4 CALL (v3) { UNWIND v3 AS v5 WITH v5.node AS v6 CALL (v6) { MATCH (v6)-[v0:ACTED_IN]->(v1:Movie) RETURN * } } } RETURN *"""); } @ParameterizedTest // GH-1084 @MethodSource void reusingAliases(EnumSet config, String expected) { var renderer = Renderer.getRenderer(Configuration.newConfig().withGeneratedNames(config).build()); var movie = Cypher.node("Movie").named("movie"); var cypher = renderer.render(Cypher.match(movie).returning(movie.project("a", "b").as("movie")).build()); assertThat(cypher).isEqualTo(expected); } @Test // GH-1113 void listComprehensionsBasedOnRelationshipsMustWork() { Node anotherNode = Cypher.node("AnotherNode").named("n"); Node movie = Cypher.node("Movie").named("m"); var refersTo = movie.relationshipFrom(anotherNode, "HAS_RELATION"); var stmt = Cypher.match(movie) .where(movie.property("name").isEqualTo(Cypher.literalOf("star"))) .returningDistinct(Cypher.listBasedOn(refersTo).returning(Cypher.name("n"))) .build(); assertThat(stmt.getCypher()).isEqualTo( "MATCH (m:`Movie`) WHERE m.name = 'star' RETURN DISTINCT [(m)<-[:`HAS_RELATION`]-(n:`AnotherNode`) | n]"); } @Test void callRawCypherFollowedByWhereMustWork() { var cypher = Cypher.callRawCypher("MATCH (n:Movie) RETURN n") .with(Cypher.asterisk()) .where(Cypher.property("n", "title").eq(Cypher.literalOf("The Matrix"))) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher).isEqualTo("CALL () {MATCH (n:Movie) RETURN n} WITH * WHERE n.title = 'The Matrix' RETURN *"); } @Test void callCypherFollowedByWhereMustWork() { var cypher = Cypher.call(Cypher.match(Cypher.node("Movie").named("n")).returning(Cypher.name("n")).build()) .with(Cypher.asterisk()) .where(Cypher.property("n", "title").eq(Cypher.literalOf("The Matrix"))) .returning(Cypher.asterisk()) .build() .getCypher(); assertThat(cypher) .isEqualTo("CALL () {MATCH (n:`Movie`) RETURN n} WITH * WHERE n.title = 'The Matrix' RETURN *"); } @Test void callRawCypherFollowedByWhereWithoutWithMustThrowGoodException() { var buildableSubquery = Cypher.callRawCypher("MATCH (n:Movie) RETURN n"); var trueCondition = Cypher.isTrue(); assertThatIllegalArgumentException().isThrownBy(() -> buildableSubquery.where(trueCondition)) .withMessage("A CALL{} clause requires to WITH before you can add further conditions"); } @Test // GH-1235 void caseParsingAndRenderingShouldWork() { var node = Cypher.node("Node").named("n"); var query = Cypher.match(node) .returning(Cypher.caseExpression(node.property("prop")) .when(Cypher.literalOf("A")) .then(Cypher.literalOf(1)) .elseDefault(Cypher.literalOf(2))) .build(); assertThat(query.getCypher()).isEqualTo("MATCH (n:`Node`) RETURN CASE n.prop WHEN 'A' THEN 1 ELSE 2 END"); } @Test // GH-1313 void asConditionShouldWorkWithMultipleMatchesToo() { var me = Cypher.node("User").named("me").withProperties(Cypher.mapOf("id", Cypher.parameter("userId"))); var c = Cypher.node("Company").named("c"); var myCompany = Cypher.node("Company").named("myCompany"); var isAdmin = Cypher .match(me.relationshipTo( Cypher.node("Role").withProperties(Cypher.mapOf("name", Cypher.literalOf("Admin"))), "HAS_ROLE")) .asCondition() .as("isAdmin"); var stmt = Cypher.match(me) .with(me, isAdmin) .match(c) .where(isAdmin.asCondition() .or(Cypher.match(me.relationshipTo(myCompany, "WORKdS_FOR")) .match(myCompany.relationshipTo(c, "PARENT_OF")) .asCondition() )) .returning(c) .build(); var expected = """ MATCH (me:User { id: $userId }) WITH me, EXISTS { MATCH (me)-[:HAS_ROLE]->(:Role { name: 'Admin' }) } AS isAdmin MATCH (c:Company) WHERE (isAdmin OR EXISTS { MATCH (me)-[:WORKdS_FOR]->(myCompany:Company) MATCH (myCompany)-[:PARENT_OF]->(c) }) RETURN c"""; assertThat(Renderer.getRenderer(Configuration.prettyPrinting()).render(stmt)).isEqualTo(expected); } @ParameterizedTest // GH-1404 @EnumSource(Dialect.class) void labelsExpressionMustBeLeftFromAllLabelHandlers(Dialect dialect) { var stmt = Cypher .match(Cypher.node(Labels.exactly("Purchase_orders").or(Labels.exactly("Another"))) .named("po") .relationshipFrom(Cypher.node("Material").named("m"), "IS_PURCHASED")) .returning(Cypher.name("m")) .build(); var renderer = Renderer.getRenderer(Configuration.newConfig().withDialect(dialect).build()); assertThat(renderer.render(stmt)) .endsWith("MATCH (po:`Purchase_orders`|`Another`)<-[:`IS_PURCHASED`]-(m:`Material`) RETURN m"); } @Nested class Chaining { @Test void afterYieldingCalls() { StatementBuilder.OngoingStandaloneCallWithReturnFields statementPart1 = Cypher .call("db.index.vector.queryNodes") .withArgs(Cypher.parameter("indexName"), Cypher.parameter("numberOfNearestNeighbours"), Cypher.parameter("embeddingValue")) .yield("node", "score"); // statement 2 var node = Cypher.anyNode().named("node"); var relatedNode = Cypher.anyNode().named("relatedNode"); var relationship = node.relationshipTo(relatedNode, "CONNECTED_TO"); var statementPart2 = Cypher.match(relationship).returning(relatedNode.property("value").as("value")); var newStatement = statementPart1.andThen(statementPart2.build()).build(); assertThat(newStatement.getCypher()).isEqualToIgnoringWhitespace(""" CALL db.index.vector.queryNodes($indexName, $numberOfNearestNeighbours, $embeddingValue) YIELD node, score MATCH (node)-[:`CONNECTED_TO`]->(relatedNode) RETURN relatedNode.value AS value"""); } } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/LabelsTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Map; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; class LabelsTests { static final Labels SIMPLE_OR = Labels.exactly("Person").or(Labels.exactly("Organization")); static final Labels OR_AND_NOT = SIMPLE_OR.and(Labels.exactly("Sanctioned").negate()); @Test // GH-1077 void labelExpressionsShouldWork1() { var node = Cypher.node(SIMPLE_OR).named("n"); var cypher = Cypher.match(node).returning(node).build().getCypher(); assertThat(cypher).isEqualTo("MATCH (n:`Person`|`Organization`) RETURN n"); } @Test // GH-1077 void labelExpressionsShouldWork2() { var node = Cypher.node(OR_AND_NOT).named("n"); var cypher = Cypher.match(node).returning(node).build().getCypher(); assertThat(cypher).isEqualTo("MATCH (n:(`Person`|`Organization`)&!`Sanctioned`) RETURN n"); } @Test void shouldMatchLabelsDynamically() { var labels = Cypher.listOf(Cypher.literalOf("Person"), Cypher.literalOf("Director")).as("labels"); var directors = Cypher.node(Cypher.allLabels(labels)).named("directors"); var stmt = Cypher.with(labels).match(directors).returning(directors).build(); assertThat(stmt.getCypher()) .isEqualTo("WITH ['Person', 'Director'] AS labels MATCH (directors:$(labels)) RETURN directors"); } @Test void shouldMatchNodesDynamicallyUsingAny() { var labels = Cypher.listOf(Cypher.literalOf("Movie"), Cypher.literalOf("Actor")); var n = Cypher.node(Cypher.anyLabel(labels)).named("n"); var stmt = Cypher.match(n).returning(n.as("nodes")).build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (n:$any(['Movie', 'Actor'])) RETURN n AS nodes"); } @Test void combinationWithLabelExpressionsShouldWork() { var labels = Cypher.anyLabel(Cypher.listOf(Cypher.literalOf("Movie"), Cypher.literalOf("Actor"))) .and(Cypher.exactlyLabel("Foo")) .or(Cypher.exactlyLabel("Bar").and(Cypher.allLabels(Cypher.parameter("IWantToDie")))); var n = Cypher.node(labels).named("n"); var stmt = Cypher.match(n).returning(n.as("nodes")).build(); assertThat(stmt.getCypher()) .isEqualTo("MATCH (n:$any(['Movie', 'Actor'])&`Foo`|`Bar`&$($IWantToDie)) RETURN n AS nodes"); } @Test void dynamicConjunctionsShouldWork() { var labels = Cypher.allLabels(Cypher.parameter("a")).or(Cypher.anyLabel(Cypher.parameter("b"))); var n = Cypher.node(labels).named("n"); var stmt = Cypher.match(n).returning(n.as("nodes")).build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (n:$($a)|$any($b)) RETURN n AS nodes"); } @Test void dynamicSetWrongType1() { var labels = Cypher.allLabels(Cypher.parameter("a")).or(Cypher.anyLabel(Cypher.parameter("b"))); var n = Cypher.node("Whatever").named("n"); var match = Cypher.match(n); assertThatIllegalArgumentException().isThrownBy(() -> match.set(n, labels)) .withMessage( "Only a single dynamic label expression or a set of static labels might be used in an updating clause"); } @Test void dynamicRemoveWrongType1() { var labels = Cypher.allLabels(Cypher.parameter("a")).or(Cypher.anyLabel(Cypher.parameter("b"))); var n = Cypher.node("Whatever").named("n"); var match = Cypher.match(n); assertThatIllegalArgumentException().isThrownBy(() -> match.remove(n, labels)) .withMessage( "Only a single dynamic label expression or a set of static labels might be used in an updating clause"); } @Test void dynamicSetWrongSelector() { var labels = Cypher.anyLabel(Cypher.parameter("a")); var n = Cypher.node("Whatever").named("n"); var match = Cypher.match(n); assertThatIllegalArgumentException().isThrownBy(() -> match.set(n, labels)) .withMessage( "Only a single dynamic label expression or a set of static labels might be used in an updating clause"); } @Test void dynamicRemoveWrongSelector() { var labels = Cypher.anyLabel(Cypher.parameter("a")); var n = Cypher.node("Whatever").named("n"); var match = Cypher.match(n); assertThatIllegalArgumentException().isThrownBy(() -> match.remove(n, labels)) .withMessage( "Only a single dynamic label expression or a set of static labels might be used in an updating clause"); } @Test void dynamicSetAll() { var labels = Cypher.allLabels(Cypher.parameter("a")); var n = Cypher.node("Whatever").named("n"); var stmt = Cypher.match(n).set(n, labels).build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (n:`Whatever`) SET n:$($a)"); } @Test void dynamicRemoveAll() { var labels = Cypher.allLabels(Cypher.parameter("a")); var n = Cypher.node("Whatever").named("n"); var stmt = Cypher.match(n).remove(n, labels).build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (n:`Whatever`) REMOVE n:$($a)"); } @Test void dynamicSetAllColon() { var labels = Cypher.allLabels(Cypher.parameter("a")).conjunctionWith(Cypher.allLabels(Cypher.parameter("b"))); var n = Cypher.node("Whatever").named("n"); var stmt = Cypher.match(n).set(n, labels).build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (n:`Whatever`) SET n:$($a):$($b)"); } @Test void dynamicRemoveAllColon() { var labels = Cypher.allLabels(Cypher.parameter("a")).conjunctionWith(Cypher.allLabels(Cypher.parameter("b"))); var n = Cypher.node("Whatever").named("n"); var stmt = Cypher.match(n).remove(n, labels).build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (n:`Whatever`) REMOVE n:$($a):$($b)"); } @Test void dynamicSetAllColonMixed() { var labels = Cypher.allLabels(Cypher.parameter("a")) .conjunctionWith(Cypher.allLabels(Cypher.parameter("b"))) .conjunctionWith(Labels.exactly("OhBuggerOff")); var n = Cypher.node("Whatever").named("n"); var stmt = Cypher.match(n).set(n, labels).build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (n:`Whatever`) SET n:$($a):$($b):`OhBuggerOff`"); } @Test void dynamicRemoveAllColonMixed() { var labels = Cypher.allLabels(Cypher.parameter("a")) .conjunctionWith(Cypher.allLabels(Cypher.parameter("b"))) .conjunctionWith(Labels.exactly("OhBuggerOff")); var n = Cypher.node("Whatever").named("n"); var stmt = Cypher.match(n).remove(n, labels).build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (n:`Whatever`) REMOVE n:$($a):$($b):`OhBuggerOff`"); } @Test void labelsAsParametersMustBeInTheCatalog() { var labels = Cypher.allLabels(Cypher.parameter("a", "X")) .conjunctionWith(Cypher.allLabels(Cypher.parameter("b", "Y"))); var n = Cypher.node("Whatever").named("n"); var stmt = Cypher.match(n).remove(n, labels).build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (n:`Whatever`) REMOVE n:$($a):$($b)"); assertThat(stmt.getCatalog().getParameterNames()).containsExactly("a", "b"); assertThat(stmt.getCatalog().getParameters()).containsAllEntriesOf(Map.of("a", "X", "b", "Y")); } @Nested class AsConditions { @Test // GH-1077 void labelExpressionsShouldWork1() { var node = Cypher.anyNode("n"); var cypher = Cypher.match(node).where(node.hasLabels(SIMPLE_OR)).returning(node).build().getCypher(); assertThat(cypher).isEqualTo("MATCH (n) WHERE n:`Person`|`Organization` RETURN n"); } @Test // GH-1077 void labelExpressionsShouldWork2() { var node = Cypher.anyNode("n"); var cypher = Cypher.match(node).where(node.hasLabels(OR_AND_NOT)).returning(node).build().getCypher(); assertThat(cypher).isEqualTo("MATCH (n) WHERE n:(`Person`|`Organization`)&!`Sanctioned` RETURN n"); } @Test // GH-1141 void labelExpressionsInPredicates() { var movieOrFilm = Labels.exactly("Movie").or(Labels.exactly("Film")); String statement; Node a = Cypher.node("Person").withProperties("name", Cypher.literalOf("Keanu Reeves")).named("a"); Node b = Cypher.anyNode("b"); statement = Cypher.match(a) .returning(Cypher.listBasedOn(a.relationshipBetween(b)) .where(b.hasLabels(movieOrFilm).and(b.property("released").isNotNull())) .returning(b.property("released")) .as("years")) .build() .getCypher(); assertThat(statement).isEqualTo( "MATCH (a:`Person` {name: 'Keanu Reeves'}) RETURN [(a)--(b) WHERE (b:`Movie`|`Film` AND b.released IS NOT NULL) | b.released] AS years"); statement = Cypher.match(a) .returning(Cypher.listBasedOn(a.relationshipBetween(b)) .where(b.hasLabels(movieOrFilm) .and(b.property("released").isNotNull()) .or(b.property("title").isEqualTo(Cypher.literalOf("The Matrix"))) .or(b.property("title").isEqualTo(Cypher.literalOf("The Matrix 2")))) .returning(b.property("released")) .as("years")) .build() .getCypher(); assertThat(statement).isEqualTo( "MATCH (a:`Person` {name: 'Keanu Reeves'}) RETURN [(a)--(b) WHERE ((b:`Movie`|`Film` AND b.released IS NOT NULL) OR b.title = 'The Matrix' OR b.title = 'The Matrix 2') | b.released] AS years"); statement = Cypher.match(a) .returning(Cypher.listBasedOn(a.relationshipBetween(b)) .where(b.hasLabels(movieOrFilm)) .and(b.property("released").isNotNull()) .or(b.property("title").isEqualTo(Cypher.literalOf("The Matrix"))) .or(b.property("title").isEqualTo(Cypher.literalOf("The Matrix 2"))) .returning(b.property("released")) .as("years")) .build() .getCypher(); assertThat(statement).isEqualTo( "MATCH (a:`Person` {name: 'Keanu Reeves'}) RETURN [(a)--(b) WHERE ((b:`Movie`|`Film` AND b.released IS NOT NULL) OR b.title = 'The Matrix' OR b.title = 'The Matrix 2') | b.released] AS years"); statement = Cypher.match(a) .returning(Cypher.listBasedOn(a.relationshipBetween(b)) .where(b.hasLabels(movieOrFilm)) .returning(b.property("released")) .as("years")) .build() .getCypher(); assertThat(statement).isEqualTo( "MATCH (a:`Person` {name: 'Keanu Reeves'}) RETURN [(a)--(b) WHERE b:`Movie`|`Film` | b.released] AS years"); } } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/LoadCSVIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.net.URI; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class LoadCSVIT { @Test void loadAndCreateShouldWork() { SymbolicName l = SymbolicName.of("line"); Statement statement = Cypher.loadCSV(URI.create("file:///artists.csv")) .as(l) .create(Cypher.node("Artist") .withProperties("name", Cypher.valueAt(l, 1), "year", Cypher.toInteger(Cypher.valueAt(l, 2)))) .build(); assertThat(statement.getCypher()).isEqualTo("LOAD CSV FROM 'file:///artists.csv' AS line " + "CREATE (:`Artist` {name: line[1], year: toInteger(line[2])})"); } @Test void loadAndCreateWithHeadersShouldWork() { SymbolicName l = SymbolicName.of("line"); Statement statement = Cypher.loadCSV(URI.create("file:///artists.csv"), true) .as(l) .create(Cypher.node("Artist") .withProperties("name", Cypher.valueAt(l, 1), "year", Cypher.toInteger(Cypher.valueAt(l, 2)))) .build(); assertThat(statement.getCypher()).isEqualTo("LOAD CSV WITH HEADERS FROM 'file:///artists.csv' AS line " + "CREATE (:`Artist` {name: line[1], year: toInteger(line[2])})"); } @Test void loadAndCreateWithFieldTerminatorShouldWork() { SymbolicName l = SymbolicName.of("line"); Statement statement = Cypher.loadCSV(URI.create("file:///artists.csv")) .as(l) .withFieldTerminator(";") .create(Cypher.node("Artist") .withProperties("name", Cypher.valueAt(l, 1), "year", Cypher.toInteger(Cypher.valueAt(l, 2)))) .build(); assertThat(statement.getCypher()).isEqualTo("LOAD CSV FROM 'file:///artists.csv' AS line FIELDTERMINATOR ';' " + "CREATE (:`Artist` {name: line[1], year: toInteger(line[2])})"); } @Test void usingPeriodCommitShouldWork() { SymbolicName l = SymbolicName.of("line"); Statement statement = Cypher.usingPeriodicCommit() .loadCSV(URI.create("file:///artists.csv")) .as(l) .create(Cypher.node("Artist") .withProperties("name", Cypher.valueAt(l, 1), "year", Cypher.toInteger(Cypher.valueAt(l, 2)))) .build(); assertThat(statement.getCypher()).isEqualTo("USING PERIODIC COMMIT LOAD CSV FROM 'file:///artists.csv' AS line " + "CREATE (:`Artist` {name: line[1], year: toInteger(line[2])})"); } @Test void usingPeriodCommitWithRateShouldWork() { SymbolicName l = SymbolicName.of("line"); Statement statement = Cypher.usingPeriodicCommit(500) .loadCSV(URI.create("file:///artists.csv")) .as(l) .create(Cypher.node("Artist") .withProperties("name", Cypher.valueAt(l, 1), "year", Cypher.toInteger(Cypher.valueAt(l, 2)))) .build(); assertThat(statement.getCypher()) .isEqualTo("USING PERIODIC COMMIT 500 LOAD CSV FROM 'file:///artists.csv' AS line " + "CREATE (:`Artist` {name: line[1], year: toInteger(line[2])})"); } @Test void usingLinenumberShouldWork() { SymbolicName l = SymbolicName.of("line"); Statement statement = Cypher.loadCSV(URI.create("file:///artists.csv")) .as(l) .returning(Cypher.linenumber().as("number"), l) .build(); assertThat(statement.getCypher()) .isEqualTo("LOAD CSV FROM 'file:///artists.csv' AS line RETURN linenumber() AS number, line"); } @Test void usingFileShouldWork() { SymbolicName l = SymbolicName.of("line"); Statement statement = Cypher.loadCSV(URI.create("file:///artists.csv")) .as(l) .returning(Cypher.file().as("path")) .build(); assertThat(statement.getCypher()) .isEqualTo("LOAD CSV FROM 'file:///artists.csv' AS line RETURN file() AS path"); } @Test void allOptionsCombinedShouldWork() { SymbolicName l = SymbolicName.of("line"); Statement statement = Cypher.usingPeriodicCommit(42) .loadCSV(URI.create("file:///artists.csv"), true) .as(l) .withFieldTerminator(";") .create(Cypher.node("Artist") .withProperties("name", Cypher.valueAt(l, 1), "year", Cypher.toInteger(Cypher.valueAt(l, 2)), "source", Cypher.file().concat(Cypher.literalOf("@")).concat(Cypher.linenumber()))) .build(); assertThat(statement.getCypher()).isEqualTo( "USING PERIODIC COMMIT 42 LOAD CSV WITH HEADERS FROM 'file:///artists.csv' AS line FIELDTERMINATOR ';' " + "CREATE (:`Artist` {name: line[1], year: toInteger(line[2]), source: ((file() + '@') + linenumber())})"); } @Test void devGuideExample1() { SymbolicName row = SymbolicName.of("row"); Property id = row.property("Id"); Statement statement = Cypher.loadCSV(URI.create("file:///companies.csv"), true) .as(row) .with(row) .where(id.isNotNull()) .merge(Cypher.node("Company").named("c").withProperties("companyId", id)) .build(); assertThat(statement.getCypher()).isEqualTo("LOAD CSV WITH HEADERS FROM 'file:///companies.csv' AS row " + "WITH row WHERE row.Id IS NOT NULL " + "MERGE (c:`Company` {companyId: row.Id})"); } @Test void devGuideExample2() { SymbolicName row = SymbolicName.of("row"); Property id = row.property("Id"); Statement statement = Cypher.loadCSV(URI.create("file:///companies.csv"), true) .as(row) .merge(Cypher.node("Company") .named("c") .withProperties("companyId", id, "hqLocation", Cypher.coalesce(row.property("Location"), Cypher.literalOf("Unknown")))) .build(); assertThat(statement.getCypher()).isEqualTo("LOAD CSV WITH HEADERS FROM 'file:///companies.csv' AS row " + "MERGE (c:`Company` {companyId: row.Id, hqLocation: coalesce(row.Location, 'Unknown')})"); } @Test void devGuideExample3() { SymbolicName row = SymbolicName.of("row"); Property id = row.property("Id"); Property email = row.property("Email"); Node node = Cypher.node("Company").named("c").withProperties("companyId", id); Statement statement = Cypher.loadCSV(URI.create("file:///companies.csv"), true) .as(row) .merge(node) .set(node.property("emailAddress") .to(Cypher.caseExpression(Cypher.trim(email)) .when(Cypher.literalOf("")) .then(NullLiteral.INSTANCE) .elseDefault(email))) .build(); assertThat(statement.getCypher()).isEqualTo("LOAD CSV WITH HEADERS FROM 'file:///companies.csv' AS row " + "MERGE (c:`Company` {companyId: row.Id}) " + "SET c.emailAddress = CASE trim(row.Email) WHEN '' THEN NULL ELSE row.Email END"); } @Test void devGuideExample4() { SymbolicName row = SymbolicName.of("row"); Property id = row.property("Id"); Property email = row.property("Email"); SymbolicName skill = Cypher.name("skill"); Node e = Cypher.node("Employee").named("e").withProperties("employeeId", id, "email", email); Node s = Cypher.node("Skill").named("s").withProperties("name", skill); Statement statement = Cypher.loadCSV(URI.create("file:///employees.csv"), true) .as(row) .merge(e) .with(e, row) .unwind(Cypher.split(row.property("Skills"), Cypher.literalOf(":"))) .as(skill) .merge(s) .merge(e.relationshipTo(s, "HAS_EXPERIENCE").named("r")) .build(); assertThat(statement.getCypher()).isEqualTo("LOAD CSV WITH HEADERS FROM 'file:///employees.csv' AS row " + "MERGE (e:`Employee` {employeeId: row.Id, email: row.Email}) " + "WITH e, row " + "UNWIND split(row.Skills, ':') AS skill " + "MERGE (s:`Skill` {name: skill}) " + "MERGE (e)-[r:`HAS_EXPERIENCE`]->(s)"); } @Test void inQuery() { SymbolicName row = SymbolicName.of("row"); Node userNode = Cypher.node("User").named("u").withProperties("name", Cypher.literalOf("Michael")); Statement statement = Cypher.match(userNode) .with(userNode) .orderBy(userNode.property("name")) .ascending() .loadCSV(URI.create("file:///bikes.csv")) .as(row) .merge(userNode.relationshipTo(Cypher.node("Bike").withProperties("name", Cypher.valueAt(row, 0)), "OWNS")) .build(); assertThat(statement.getCypher()).isEqualTo( "MATCH (u:`User` {name: 'Michael'}) WITH u ORDER BY u.name ASC LOAD CSV FROM 'file:///bikes.csv' AS row MERGE (u)-[:`OWNS`]->(:`Bike` {name: row[0]})"); } @Test void finish() { SymbolicName row = SymbolicName.of("row"); Node userNode = Cypher.node("User").named("u").withProperties("name", Cypher.literalOf("Michael")); Statement statement = Cypher.match(userNode) .with(userNode) .orderBy(userNode.property("name")) .ascending() .loadCSV(URI.create("file:///bikes.csv")) .as(row) .finish() .build(); assertThat(statement.getCypher()).isEqualTo( "MATCH (u:`User` {name: 'Michael'}) WITH u ORDER BY u.name ASC LOAD CSV FROM 'file:///bikes.csv' AS row FINISH"); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/PackageAndAPIStructureTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.ImportOption; import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.conditions.ArchConditions; import org.apiguardian.api.API; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.neo4j.cypherdsl.core.internal.SchemaNamesBridge; import org.neo4j.cypherdsl.support.schema_name.SchemaNames; import static com.tngtech.archunit.base.DescribedPredicate.not; import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.targetOwner; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableFrom; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableTo; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage; import static com.tngtech.archunit.core.domain.properties.HasModifiers.Predicates.modifier; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; /** * @author Michael J. Simons */ @TestInstance(TestInstance.Lifecycle.PER_CLASS) class PackageAndAPIStructureTests { private JavaClasses coreClasses; // tag::arch-rules.naming:TypeNameMustBeginWithGroupId[] @BeforeAll void importCorePackage() { this.coreClasses = new ClassFileImporter().withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) .importPackages("org.neo4j.cypherdsl.core.."); } // end::arch-rules.naming:TypeNameMustBeginWithGroupId[] @DisplayName("API Guardian annotations must not be used on fields") // tag::arch-rules.api:api-guardian-usage[] @Test void apiGuardian() { ArchRule rule = fields().should().notBeAnnotatedWith(API.class); rule.check(this.coreClasses); } // end::arch-rules.api:api-guardian-usage[] @DisplayName("Non abstract, public classes that are only part of internal API must be final and not reside in core") // tag::arch-rules.api:internal[] @Test void internalPublicClassesMustBeFinal() { ArchRule rule = classes().that() .areAnnotatedWith(API.class) .and() .arePublic() .and() .areTopLevelClasses() .and(not(modifier(JavaModifier.ABSTRACT))) .and(new DescribedPredicate<>("Is internal API") { @Override public boolean test(JavaClass input) { API.Status status = input.getAnnotationOfType(API.class).status(); return "INTERNAL".equals(status.name()); } }) .should() .haveModifier(JavaModifier.FINAL) .andShould(ArchConditions.not(ArchConditions.resideInAPackage("..core"))); rule.check(this.coreClasses); } // end::arch-rules.api:internal[] @DisplayName("The Cypher-DSL core package must not depend on the rendering infrastructure") // tag::arch-rules.structure:core-must-not-depend-on-renderer[] @Test void coreMostNotDependOnRendering() { ArchRule rule = noClasses().that() .resideInAPackage("..core") .and(not(assignableFrom(AbstractStatement.class).or(assignableFrom(RendererBridge.class)))) .should() .dependOnClassesThat(resideInAPackage("..renderer..")); rule.check(this.coreClasses); } // end::arch-rules.structure:core-must-not-depend-on-renderer[] @DisplayName("Supporting packages must not depend on anything from the outside") // tag::arch-rules.structure:supporting-packages-are-dependency-free[] @ParameterizedTest @ValueSource(strings = { "..core.ast", "..core.utils" }) void independentSupportPackages(String supportPackage) { ArchRule rule = noClasses().that() .resideInAPackage(supportPackage) .should() .dependOnClassesThat(resideInAPackage("..core..").and(not(resideInAPackage(supportPackage)))); rule.check(this.coreClasses); } // end::arch-rules.structure:supporting-packages-are-dependency-free[] @Test void allCallsToSchemaNamesMustUseTheBridge() { ArchRule rule = noClasses().that() .areNotAssignableFrom(SchemaNamesBridge.class) .should() .callCodeUnitWhere(targetOwner(assignableTo(SchemaNames.class))); rule.check(this.coreClasses); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/ParameterIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Map; import java.util.Set; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * @author Andreas Berger * @author Michael J. Simons */ class ParameterIT { private static final Node userNode = Cypher.node("User").named("u"); private static Stream conflictingParameters() { return Stream.of("x", null) .flatMap(v -> Stream.of( Arguments.of(Cypher.match(userNode) .set(userNode.property("name").to(Cypher.parameter("param").withValue(v)), userNode.property("firstName").to(Cypher.parameter("param"))) .returning(userNode) .build()), Arguments.of(Cypher.match(userNode) .set(userNode.property("firstName").to(Cypher.parameter("param")), userNode.property("name").to(Cypher.parameter("param").withValue(v))) .returning(userNode) .build()))); } @Test void shouldCollectParameters() { Statement statement = Cypher.match(userNode) .where(userNode.property("name").isEqualTo(Cypher.parameter("name", "Neo"))) .returning(userNode) .limit(Cypher.parameter("param").withValue(5)) .build(); assertThat(statement.getCatalog().getParameters()).containsEntry("param", 5).containsEntry("name", "Neo"); assertThat(statement.getCatalog().getParameterNames()).containsExactlyInAnyOrder("param", "name"); } @Test void shouldCollectParametersOnlyOnce() { Statement statement = Cypher.match(userNode) .where(userNode.property("cnt").isEqualTo(Cypher.parameter("param", 5))) .and(Cypher.anonParameter("y").isTrue()) .or(Cypher.anonParameter("y").isFalse()) .and(Cypher.anonParameter("z").isTrue()) .or(Cypher.anonParameter("z").isFalse()) .returning(userNode) .limit(Cypher.parameter("param").withValue(5)) .build(); assertThat(statement.getCatalog().getParameters()).containsOnly(Map.entry("param", 5), Map.entry("pcdsl01", "y"), Map.entry("pcdsl02", "z")); } @Test void equalsShouldWork() { var p1 = Cypher.anonParameter("y"); var p2 = Cypher.anonParameter("y"); var p3 = Cypher.anonParameter("z"); var p4 = Cypher.parameter("pcdsl01", "z"); assertThat(p1).isEqualTo(p1); assertThat(p1).isEqualTo(p2); assertThat(p1).isNotEqualTo(p3); assertThat(p1).isNotEqualTo(p4); } @Test void shouldDealWithNullValues() { Statement statement = Cypher.match(userNode) .set(userNode.property("name").to(Cypher.parameter("param").withValue(null))) .returning(userNode) .build(); assertThat(Renderer.getDefaultRenderer().render(statement)) .isEqualTo("MATCH (u:`User`) SET u.name = $param RETURN u"); assertThat(statement.getCatalog().getParameters()).containsEntry("param", null); } @ParameterizedTest @MethodSource("conflictingParameters") void shouldFailWithNoValueVsNull(Statement statement) { assertThatExceptionOfType(ConflictingParametersException.class).isThrownBy(statement::getCatalog); } @Test void shouldNotFailWithSameNameAndMultipleNulLValues() { Statement statement = Cypher.match(userNode) .set(userNode.property("name").to(Cypher.parameter("param").withValue(null)), userNode.property("firstName").to(Cypher.parameter("param").withValue(null))) .returning(userNode) .build(); assertThat(Renderer.getDefaultRenderer().render(statement)) .isEqualTo("MATCH (u:`User`) SET u.name = $param, u.firstName = $param RETURN u"); assertThat(statement.getCatalog().getParameters()).containsEntry("param", null); } @Test void shouldNotFailWithSameNameAndNoValue() { Statement statement = Cypher.match(userNode) .set(userNode.property("name").to(Cypher.parameter("param")), userNode.property("firstName").to(Cypher.parameter("param"))) .returning(userNode) .build(); assertThat(Renderer.getDefaultRenderer().render(statement)) .isEqualTo("MATCH (u:`User`) SET u.name = $param, u.firstName = $param RETURN u"); assertThat(statement.getCatalog().getParameters()).isEmpty(); assertThat(statement.getCatalog().getParameterNames()).containsExactlyInAnyOrder("param"); } @Test void shouldFailOnDifferentBoundValues() { Statement statement = Cypher.match(userNode) .returning(userNode) .skip(Cypher.parameter("param").withValue(1)) .limit(Cypher.parameter("param").withValue(5)) .build(); assertThatExceptionOfType(ConflictingParametersException.class).isThrownBy(statement::getCatalog) .satisfies(e -> { Map> erroneousParameters = e.getErroneousParameters(); assertThat(erroneousParameters).containsKey("param"); Set values = erroneousParameters.get("param"); assertThat(values).containsExactlyInAnyOrder(1, 5); }); } @SuppressWarnings("deprecation") @Test void shouldFailOnDifferentBoundValuesWhenSameValueIsUsedTwice() { Statement statement = Cypher.match(userNode) .where(userNode.internalId().isEqualTo(Cypher.parameter("param").withValue(5))) .returning(userNode) .skip(Cypher.parameter("param").withValue(1)) .limit(Cypher.parameter("param").withValue(1)) .build(); assertThatExceptionOfType(ConflictingParametersException.class).isThrownBy(statement::getCatalog) .satisfies(e -> { Map> erroneousParameters = e.getErroneousParameters(); assertThat(erroneousParameters).containsKey("param"); Set values = erroneousParameters.get("param"); assertThat(values).containsExactlyInAnyOrder(1, 5); }); } @Test void shouldWorkWithUnions() { final Node bikeNode = Cypher.node("Bike").named("b"); Statement statement1 = Cypher.match(bikeNode) .where(bikeNode.property("a").isEqualTo(Cypher.parameter("p1").withValue("A"))) .returning(bikeNode) .build(); assertThat(statement1.getCatalog().getParameters()).containsEntry("p1", "A"); Statement statement2 = Cypher.match(bikeNode) .where(bikeNode.property("b").isEqualTo(Cypher.parameter("p2").withValue("B"))) .returning(bikeNode) .build(); assertThat(statement2.getCatalog().getParameters()).containsEntry("p2", "B"); Statement statement3 = Cypher.match(bikeNode) .where(bikeNode.property("c").isEqualTo(Cypher.parameter("p3").withValue("C"))) .returning(bikeNode) .build(); assertThat(statement3.getCatalog().getParameters()).containsEntry("p3", "C"); Statement statement = Cypher.union(statement1, statement2, statement3); assertThat(Renderer.getDefaultRenderer().render(statement)).isEqualTo( "MATCH (b:`Bike`) WHERE b.a = $p1 RETURN b UNION MATCH (b:`Bike`) WHERE b.b = $p2 RETURN b UNION MATCH (b:`Bike`) WHERE b.c = $p3 RETURN b"); Map expectedParams = Map.of("p1", "A", "p2", "B", "p3", "C"); assertThat(statement.getCatalog().getParameters()).containsAllEntriesOf(expectedParams); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/ParameterLiteralTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; class ParameterLiteralTests { @Test void anonParametersAreUnsupported() { var anonParameter = Cypher.anonParameter("whatever"); Assertions.assertThatIllegalArgumentException() .isThrownBy(() -> ParameterLiteral.of(anonParameter)) .withMessage("Anonymous parameters cannot be used as parameter literals"); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/PredicatesTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.lang.reflect.Method; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.Mockito.mock; /** * @author Michael J. Simons */ class PredicatesTests { private static final String FUNCTION_NAME_FIELD = "functionName"; private static Stream predicatesToTest() { return Stream.of(Arguments.of("exists", Property.class), Arguments.of("exists", RelationshipPattern.class)); } @ParameterizedTest @MethodSource("predicatesToTest") void preconditionsShouldBeAsserted(String predicateName, Class argumentType) { @SuppressWarnings("deprecation") Method method = TestUtils.findMethod(Predicates.class, predicateName, argumentType); assertThatIllegalArgumentException().isThrownBy(() -> TestUtils.invokeMethod(method, null, (Expression) null)) .withMessageEndingWith("is required."); } @ParameterizedTest @MethodSource("predicatesToTest") void functionInvocationsShouldBeCreated(String functionName, Class argumentType) { @SuppressWarnings("deprecation") Method method = TestUtils.findMethod(Predicates.class, functionName, argumentType); BooleanFunctionCondition invocation = (BooleanFunctionCondition) TestUtils.invokeMethod(method, null, mock(argumentType)); assertThat(invocation).extracting("delegate").hasFieldOrPropertyWithValue(FUNCTION_NAME_FIELD, functionName); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/ProcedureCallsIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.Arrays; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * @author Michael J. Simons */ class ProcedureCallsIT { private static final Renderer cypherRenderer = Renderer.getDefaultRenderer(); @Test void simple() { String expected = "CALL db.labels()"; Statement call = Cypher.call("db", "labels").build(); assertThat(cypherRenderer.render(call)).isEqualTo(expected); call = Cypher.call("db.labels").build(); assertThat(cypherRenderer.render(call)).isEqualTo(expected); } @Test void simpleBasedOnStringCollection() { String expected = "CALL db.labels()"; String[] prodecureCallPart = { "db", "labels" }; Statement call = Cypher.call(Arrays.asList(prodecureCallPart)).build(); assertThat(cypherRenderer.render(call)).isEqualTo(expected); assertThat(call.getCatalog().getIdentifiableExpressions()).isEmpty(); } @Test void shouldGenerateStatementsOfCorrectType() { Statement call = Cypher.call("db", "labels").build(); assertThat(call).isInstanceOf(ProcedureCall.class) .isInstanceOf(Statement.class) .isNotInstanceOf(ResultStatement.class); call = Cypher.call("db", "labels").yield("label").build(); assertThat(call).isInstanceOf(ProcedureCall.class) .isInstanceOf(Statement.class) .isInstanceOf(ResultStatement.class); call = Cypher.call("db", "labels").yield("label").returning("label").build(); assertThat(call).isInstanceOf(Statement.SingleQuery.class) .isInstanceOf(Statement.class) .isInstanceOf(ResultStatement.class); } @Test void withArgs() { Statement call = Cypher.call("dbms.security.createUser") .withArgs(Cypher.literalOf("johnsmith"), Cypher.literalOf("h6u4%kr"), BooleanLiteral.FALSE) .build(); assertThat(cypherRenderer.render(call)) .isEqualTo("CALL dbms.security.createUser('johnsmith', 'h6u4%kr', false)"); } @Test void yieldItems() { String expected = "CALL dbms.procedures() YIELD name, signature"; Statement call = Cypher.call("dbms.procedures").yield("name", "signature").build(); assertThat(cypherRenderer.render(call)).isEqualTo(expected); call = Cypher.call("dbms.procedures").yield(Cypher.name("name"), Cypher.name("signature")).build(); assertThat(cypherRenderer.render(call)).isEqualTo(expected); assertThat(call.getCatalog().getIdentifiableExpressions()).containsExactlyInAnyOrder(Cypher.name("name"), Cypher.name("signature")); } @Test void yieldItemsRenamed() { Statement call = Cypher.call("db.propertyKeys").yield(Cypher.name("propertyKey").as("prop")).build(); assertThat(cypherRenderer.render(call)).isEqualTo("CALL db.propertyKeys() YIELD propertyKey AS prop"); } @Test void withArgsAndYield() { Statement call = Cypher.call("dbms.listConfig").withArgs(Cypher.literalOf("browser")).yield("name").build(); assertThat(cypherRenderer.render(call)).isEqualTo("CALL dbms.listConfig('browser') YIELD name"); } @Test void where() { SymbolicName name = Cypher.name("name"); Statement call = Cypher.call("dbms.listConfig") .withArgs(Cypher.literalOf("browser")) .yield(name) .where(name.matches("browser\\.allow.*")) .returning(Cypher.asterisk()) .build(); assertThat(cypherRenderer.render(call)) .isEqualTo("CALL dbms.listConfig('browser') YIELD name WHERE name =~ 'browser\\\\.allow.*' RETURN *"); } @Test void returning() { SymbolicName label = Cypher.name("label"); Statement call = Cypher.call("db.labels").yield(label).returning(Cypher.count(label).as("numLabels")).build(); assertThat(cypherRenderer.render(call)) .isEqualTo("CALL db.labels() YIELD label RETURN count(label) AS numLabels"); } @Test void withThanReturning() { SymbolicName label = Cypher.name("label"); Statement call = Cypher.call("db.labels") .yield(label) .with(label) .returning(Cypher.count(label).as("numLabels")) .build(); assertThat(cypherRenderer.render(call)) .isEqualTo("CALL db.labels() YIELD label WITH label RETURN count(label) AS numLabels"); assertThat(call.getCatalog().getIdentifiableExpressions()).hasSize(1).first().satisfies(i -> { assertThat(i).isInstanceOf(AliasedExpression.class); assertThat(((AliasedExpression) i).getAlias()).isEqualTo("numLabels"); }); } @Test void withThanReturningInQuery() { SymbolicName label = Cypher.name("label"); Statement call = Cypher.match(Cypher.anyNode().named("n")) .with("n") .call("db.labels") .yield(label) .with(label) .returning(Cypher.count(label).as("numLabels")) .build(); assertThat(cypherRenderer.render(call)) .isEqualTo("MATCH (n) WITH n CALL db.labels() YIELD label WITH label RETURN count(label) AS numLabels"); } @Test // GH-101 void shouldBeUsableAsExpression() { Node p = Cypher.node("Person").named("p"); Statement stmt = Cypher .merge(p.withProperties(Cypher.mapOf("id", Cypher.call("apoc.create.uuid").asFunction()))) .set(p.property("firstName").to(Cypher.literalOf("Michael")), p.property("surname").to(Cypher.literalOf("Hunger"))) .returning(p) .build(); assertThat(cypherRenderer.render(stmt)).isEqualTo( "MERGE (p:`Person` {id: apoc.create.uuid()}) SET p.firstName = 'Michael', p.surname = 'Hunger' RETURN p"); } @Test void dynamicDistinct() { assertThat(cypherRenderer .render(Cypher.returning(Cypher.call("aVg").withArgs(Cypher.literalOf(1)).asFunction(true)).build())) .isEqualTo("RETURN aVg(DISTINCT 1)"); } @Test void dynamicDistinctUnsupported() { assertThatIllegalArgumentException() .isThrownBy(() -> Cypher.call("foobar").withArgs(Cypher.literalOf(1)).asFunction(true)) .withMessage("The distinct operator can only be applied within aggregate functions."); } @Test // GH-101 void shouldBeUsableWithParametersAsExpression() { Node p = Cypher.node("Person").named("p"); Statement stmt = Cypher .merge(p.withProperties(Cypher.mapOf("id", Cypher.call("apoc.create.uuid").asFunction()))) .set(p.property("surname").to(Cypher.literalOf("Simons"))) .with(p) .call("apoc.create.setProperty") .withArgs(p.getRequiredSymbolicName(), Cypher.call("apoc.text.camelCase").withArgs(Cypher.literalOf("first name")).asFunction(), Cypher.literalOf("Michael")) .yield("node") .returning("node") .build(); assertThat(cypherRenderer.render(stmt)).isEqualTo( "MERGE (p:`Person` {id: apoc.create.uuid()}) SET p.surname = 'Simons' WITH p CALL apoc.create.setProperty(p, apoc.text.camelCase('first name'), 'Michael') YIELD node RETURN node"); } @Nested class MultipartQueries { @Test void unrelated() { SymbolicName name = Cypher.name("name"); Statement call = Cypher.call("dbms.listConfig") .withArgs(Cypher.literalOf("browser")) .yield(name) .where(name.matches("browser\\.allow.*")) .match(Cypher.anyNode("n")) .with(name) .returning(Cypher.asterisk()) .build(); assertThat(cypherRenderer.render(call)).isEqualTo( "CALL dbms.listConfig('browser') YIELD name WHERE name =~ 'browser\\\\.allow.*' MATCH (n) WITH name RETURN *"); } @Test void related() { SymbolicName name = Cypher.name("name"); SymbolicName description = Cypher.name("description"); Statement call = Cypher.call("dbms.listConfig") .withArgs(Cypher.literalOf("browser")) .yield(name, description) .where(name.matches("browser\\.allow.*")) .with(Cypher.asterisk()) .create(Cypher.node("Config").withProperties("name", name, "description", description).named("n")) .returning(Cypher.name("n")) .build(); assertThat(cypherRenderer.render(call)).isEqualTo( "CALL dbms.listConfig('browser') YIELD name, description WHERE name =~ 'browser\\\\.allow.*' WITH * CREATE (n:`Config` {name: name, description: description}) RETURN n"); } @Test void relatedInner() { SymbolicName name = Cypher.name("name"); AliasedExpression parameters = Cypher .listOf(Cypher.literalOf("browser"), Cypher.literalOf("causal_clustering")) .as("parameters"); Statement call = Cypher.with(parameters) .unwind(parameters) .as("p") .call("dbms.listConfig") .withArgs(Cypher.name("p")) .yield(name) .where(name.matches(".*allow.*")) .returning(name) .build(); assertThat(cypherRenderer.render(call)).isEqualTo( "WITH ['browser', 'causal_clustering'] AS parameters UNWIND parameters AS p CALL dbms.listConfig(p) YIELD name WHERE name =~ '.*allow.*' RETURN name"); } } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/RawLiteralTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Generates a raw cypher literal. The factory method is able to replace {@code $E} * placeholders with expressions passed to it. To use a {@literal $E} escape it as * {$literal \$E}. * * @author Michael J. Simons * */ class RawLiteralTests { @Test void shouldWorkWithoutPlaceHolder() { String cypher = Cypher.returning(Cypher.raw("1 * 2").as("result")).build().getCypher(); assertThat(cypher).isEqualTo("RETURN 1 * 2 AS result"); } @Test void shouldUnescapeEscapedPlaceholders() { String cypher = Cypher.returning(Cypher.raw("\\$E * \\$E").as("result")).build().getCypher(); assertThat(cypher).isEqualTo("RETURN $E * $E AS result"); } @Test void noArguments() { assertThatIllegalArgumentException().isThrownBy(() -> Cypher.raw("$E + 23 * $E")) .withMessageStartingWith("Too few arguments for the raw literal format `"); } @Test void tooFewArguments() { assertThatIllegalArgumentException().isThrownBy(() -> Cypher.raw("$E + 23 * $E", Cypher.literalOf(1))) .withMessageStartingWith("Too few arguments for the raw literal format `"); } @Test void tooManyArguments() { assertThatIllegalArgumentException() .isThrownBy(() -> Cypher.raw("$E + 23 * $E", Cypher.literalOf(1), Cypher.literalOf(2), Cypher.literalOf(3))) .withMessageStartingWith("Too many arguments for the raw literal format `"); } @Test void mixedArguments() { String cypher = Cypher.returning(Cypher.raw("($E + $E) + \\$E", Cypher.parameter("summand1"), 2).as("result")) .build() .getCypher(); assertThat(cypher).isEqualTo("RETURN ($summand1 + 2) + $E AS result"); } @Test void mixedArguments2() { String cypher = Cypher .returning( Cypher.raw("($E + $E) + \\$E", Cypher.parameter("summand1"), 2, Cypher.parameter("E")).as("result")) .build() .getCypher(); assertThat(cypher).isEqualTo("RETURN ($summand1 + 2) + $E AS result"); } @Test void mixedArguments3() { assertThatIllegalArgumentException() .isThrownBy(() -> Cypher.raw("($E + $E) + \\$E", Cypher.parameter("summand1"), 2, Cypher.parameter("F"))) .withMessageStartingWith("Too many arguments for the raw literal format `"); } @Test void stringLiteral() { String cypher = Cypher.returning(Cypher.raw("size($E)", "test").as("result")).build().getCypher(); assertThat(cypher).isEqualTo("RETURN size('test') AS result"); } @Test // GH-187 void withParamAsExpression() { String userProvidedCypher = "MATCH (this)-[:LINK]-(o:Other) WHERE o.name = $E RETURN o"; String cypher = Cypher.match(Cypher.anyNode().named("this")) .with("this") .returningRaw(Cypher.raw(userProvidedCypher, Cypher.parameter("name").withValue("fooo")).as("result")) .build() .getCypher(); assertThat(cypher) .isEqualTo("MATCH (this) WITH this MATCH (this)-[:LINK]-(o:Other) WHERE o.name = $name RETURN o AS result"); } @Test void withParam() { String userProvidedCypher = "MATCH (this)-[:LINK]-(o:Other) WHERE 1 = $E OR (o.name = $name AND $E IS NOT NULL) RETURN o"; for (Object[] args : new Object[][] { { Cypher.literalOf(1), Cypher.literalOf("whatever"), Cypher.parameter("name").withValue("fooo") }, { Cypher.literalOf(1), Cypher.parameter("name").withValue("fooo"), Cypher.literalOf("whatever") }, { Cypher.literalOf(1), Cypher.literalOf("whatever") }, { Cypher.parameter("name").withValue("fooo"), Cypher.literalOf(1), Cypher.literalOf("whatever") } }) { String cypher = Cypher.match(Cypher.anyNode().named("this")) .with("this") .returningRaw(Cypher.raw(userProvidedCypher, args).as("result")) .build() .getCypher(); assertThat(cypher).isEqualTo( "MATCH (this) WITH this MATCH (this)-[:LINK]-(o:Other) WHERE 1 = 1 OR (o.name = $name AND 'whatever' IS NOT NULL) RETURN o AS result"); } } @Test void withParamSimple() { String userProvidedCypher = "MATCH (this)-[:LINK]-(o:Other) WHERE o.name = $name RETURN o"; String cypher = Cypher.match(Cypher.anyNode().named("this")) .with("this") .returningRaw(Cypher.raw(userProvidedCypher, Cypher.parameter("name").withValue("fooo")).as("result")) .build() .getCypher(); assertThat(cypher) .isEqualTo("MATCH (this) WITH this MATCH (this)-[:LINK]-(o:Other) WHERE o.name = $name RETURN o AS result"); } @Test void withParamSimpleNoArgs() { String userProvidedCypher = "MATCH (this)-[:LINK]-(o:Other) WHERE o.name = $name RETURN o"; String cypher = Cypher.match(Cypher.anyNode().named("this")) .with("this") .returningRaw(Cypher.raw(userProvidedCypher).as("result")) .build() .getCypher(); assertThat(cypher) .isEqualTo("MATCH (this) WITH this MATCH (this)-[:LINK]-(o:Other) WHERE o.name = $name RETURN o AS result"); } @Test void shouldUnescapeEscapedPlaceholdersAndUseThem() { String cypher = Cypher.returning(Cypher.raw("\\$E * \\$E", Parameter.create("E")).as("result")) .build() .getCypher(); assertThat(cypher).isEqualTo("RETURN $E * $E AS result"); cypher = Cypher.returning(Cypher.raw("\\$E * \\$E", Parameter.create("E")).as("result")).build().getCypher(); assertThat(cypher).isEqualTo("RETURN $E * $E AS result"); } @Test void shouldUnescapeEscapedPlaceholdersAndUseThemAndBlanksDontMatter() { String cypher = Cypher.returning(Cypher.raw("\\$E * \\$E\n", Parameter.create("E")).as("result")) .build() .getCypher(); assertThat(cypher).isEqualTo("RETURN $E * $E\n AS result"); } @Test void onlyExpressionArguments() { String cypher = Cypher.returning(Cypher.raw("$E + $E", Cypher.literalOf(1), Cypher.literalOf(2)).as("result")) .build() .getCypher(); assertThat(cypher).isEqualTo("RETURN 1 + 2 AS result"); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/RelationshipChainTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ @TestInstance(TestInstance.Lifecycle.PER_CLASS) class RelationshipChainTests { private final Node s = Cypher.node("Start").named("s"); private final Node e = Cypher.node("End").named("e"); private final RelationshipChain chain1 = this.s.relationshipTo(Cypher.anyNode()).relationshipTo(this.e); @Test void namedShouldReturnNew() { RelationshipChain chain2 = this.chain1.named("x"); assertThat(chain2).isNotSameAs(this.chain1); String cypher1 = Cypher.match(this.chain1).returning(Cypher.asterisk()).build().getCypher(); assertThat(cypher1).isEqualTo("MATCH (s:`Start`)-->()-->(e:`End`) RETURN *"); String cypher2 = Cypher.match(chain2).returning(Cypher.asterisk()).build().getCypher(); assertThat(cypher2).isEqualTo("MATCH (s:`Start`)-->()-[x]->(e:`End`) RETURN *"); } @Test void unboundedShouldReturnNew() { RelationshipChain chain2 = this.chain1.unbounded(); assertThat(chain2).isNotSameAs(this.chain1); String cypher1 = Cypher.match(this.chain1).returning(Cypher.asterisk()).build().getCypher(); assertThat(cypher1).isEqualTo("MATCH (s:`Start`)-->()-->(e:`End`) RETURN *"); String cypher2 = Cypher.match(chain2).returning(Cypher.asterisk()).build().getCypher(); assertThat(cypher2).isEqualTo("MATCH (s:`Start`)-->()-[*]->(e:`End`) RETURN *"); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/RelationshipTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class RelationshipTests { @Test void relationshipWithoutTypesMustNotThrowNPEs() { Relationship relationship = Cypher.anyNode().relationshipTo(Cypher.anyNode()); assertThat(relationship.getDetails().getTypes()).isEmpty(); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/StatementCatalogBuildingVisitorTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.net.URI; import java.util.Set; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Dialect; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * @author Michael J. Simons */ class StatementCatalogBuildingVisitorTests { @Test void simpleShowCase() { // tag::catalog-example[] var p = Cypher.node("Person").named("p"); var m = Cypher.node("Movie").named("m"); var a = m.withProperties("title", Cypher.literalOf("The Matrix")).relationshipFrom(p, "ACTED_IN").named("a"); var statement = Cypher.match(a) .where(p.property("born").gte(Cypher.parameter("born", 1979))) .returning(p) .build(); var catalog = statement.getCatalog(); assertThat(catalog.getNodeLabels()).extracting(StatementCatalog.Token::value) .containsExactlyInAnyOrder("Person", "Movie"); assertThat(catalog.getProperties()).containsExactlyInAnyOrder( StatementCatalog.property(Set.of(StatementCatalog.label("Movie")), "title"), StatementCatalog.property(Set.of(StatementCatalog.label("Person")), "born")); // end::catalog-example[] var cypher = statement.getCypher(); assertThat(cypher).isEqualTo( "MATCH (m:`Movie` {title: 'The Matrix'})<-[a:`ACTED_IN`]-(p:`Person`) WHERE p.born >= $born RETURN p"); } @Test void labelExpressionsShouldWork() { var p = Cypher.node(Cypher.exactlyLabel("Person").or(Cypher.exactlyLabel("Actor"))); var statement = Cypher.match(p) .where(p.property("born").gte(Cypher.parameter("born", 1979))) .returning(p) .build(); var catalog = statement.getCatalog(); assertThat(catalog.getNodeLabels()).extracting(StatementCatalog.Token::value) .containsExactlyInAnyOrder("Actor", "Person"); assertThat(catalog.getProperties()).containsExactlyInAnyOrder(StatementCatalog .property(Set.of(StatementCatalog.label("Actor"), StatementCatalog.label("Person")), "born")); } @Test void labelFiltersShouldWork() { var n = Cypher.node("Person").withProperties("name", Cypher.literalOf("John Doe")).named("n"); var m = Cypher.node("Person").named("m"); var statement = Cypher.match(n.relationshipTo(m, "IS_FRIEND_WITH")) .where(n.hasLabels("Active")) .and(m.hasLabels("Kind", "Positive").and(m.property("name").eq(Cypher.literalOf("Jane")))) .returning(Cypher.asterisk()) .build(); var cypher = statement.getCypher(); assertThat(cypher).isEqualTo( "MATCH (n:`Person` {name: 'John Doe'})-[:`IS_FRIEND_WITH`]->(m:`Person`) WHERE (n:`Active` AND m:`Kind`:`Positive` AND m.name = 'Jane') RETURN *"); var catalog = statement.getCatalog(); var expectedLabelFilters = Set.of( new StatementCatalog.LabelFilter("n", Set.of(StatementCatalog.label("Active"))), new StatementCatalog.LabelFilter("m", Set.of(StatementCatalog.label("Kind"), StatementCatalog.label("Positive")))); assertThat(catalog.getAllFilters()).hasSize(4) .filteredOn(StatementCatalog.LabelFilter.class::isInstance) .map(StatementCatalog.LabelFilter.class::cast) .containsExactlyInAnyOrderElementsOf(expectedLabelFilters); assertThat(catalog.getAllLabelFilters()).containsExactlyInAnyOrderElementsOf(expectedLabelFilters); } @Test // GH-674 void shouldThrowWhenAskingWithTypeForTypes() { var n = Cypher.node("Person").withProperties("name", Cypher.literalOf("John Doe")).named("n"); var m = Cypher.node("Person").named("m"); var catalog = Cypher.match(n.relationshipTo(m, "IS_FRIEND_WITH")) .where(n.hasLabels("Active")) .and(m.hasLabels("Kind", "Positive").and(m.property("name").eq(Cypher.literalOf("Jane")))) .returning(Cypher.asterisk()) .build() .getCatalog(); var aType = StatementCatalog.Token.type("whatever"); var expectedMessage = "Token[type=RELATIONSHIP_TYPE, value=whatever] must be a node label, not a relationship type"; assertThatIllegalArgumentException().isThrownBy(() -> catalog.getIncomingRelations(aType)) .withMessage(expectedMessage); assertThatIllegalArgumentException().isThrownBy(() -> catalog.getOutgoingRelations(aType)) .withMessage(expectedMessage); assertThatIllegalArgumentException().isThrownBy(() -> catalog.getUndirectedRelations(aType)) .withMessage(expectedMessage); } @Test // GH-674 void shouldThrowWhenAskingWithLabelForLabels() { var n = Cypher.node("Person").withProperties("name", Cypher.literalOf("John Doe")).named("n"); var m = Cypher.node("Person").named("m"); var catalog = Cypher.match(n.relationshipTo(m, "IS_FRIEND_WITH")) .where(n.hasLabels("Active")) .and(m.hasLabels("Kind", "Positive").and(m.property("name").eq(Cypher.literalOf("Jane")))) .returning(Cypher.asterisk()) .build() .getCatalog(); var aLabel = StatementCatalog.Token.label("whatever"); var expectedMessage = "Token[type=NODE_LABEL, value=whatever] must be a relationship type, not a node label"; assertThatIllegalArgumentException().isThrownBy(() -> catalog.getTargetNodes(aLabel)) .withMessage(expectedMessage); assertThatIllegalArgumentException().isThrownBy(() -> catalog.getSourceNodes(aLabel)) .withMessage(expectedMessage); } @Test // GH-738 void literalRetrievalShouldWork() { var literals = Cypher.match(Cypher.anyNode("n")) .returning(Cypher.asterisk()) .build() .getCatalog() .getLiterals(); assertThat(literals).isEmpty(); literals = Cypher.match(Cypher.anyNode("n").withProperties(Cypher.mapOf("a", Cypher.literalOf("A")))) .where(Cypher.name("n").property("prop").eq(Cypher.literalOf(42))) .and(Cypher.name("n").property("b").isFalse()) .returning(Cypher.asterisk()) .build() .getCatalog() .getLiterals(); assertThat(literals).map(Literal::asString).containsExactlyInAnyOrder("'A'", "42", "false"); var x = Cypher.name("x"); var stmt = Cypher.usingPeriodicCommit() .loadCSV(URI.create("https://test.com/test.csv")) .as("x") .with("x") .merge(Cypher.anyNode("n").withProperties("x", x)) .onCreate() .set(x.property("y").to(Cypher.literalNull()), x.property("a").to(Cypher.literalOf("Hallo"))) .onCreate() .set(x.property("b").to(Cypher.subList(Cypher.listOf(Cypher.literalTrue()), 1, 2))) .returning(Cypher.raw("x")) .build(); assertThat(stmt.getCypher()).isEqualTo( "USING PERIODIC COMMIT LOAD CSV FROM 'https://test.com/test.csv' AS x WITH x MERGE (n {x: x}) ON CREATE SET x.y = NULL, x.a = 'Hallo' ON CREATE SET x.b = [true][1..2] RETURN x"); assertThat(stmt.getCatalog().getLiterals()).map(Literal::asString) .containsExactlyInAnyOrder("1", "2", "NULL", "true", "'Hallo'"); } @Test // GH-738 void procedureNamesMustNotAppearAsLiterals() { var stmt = Cypher.call("dbms.routing.getRoutingTable") .withArgs(Cypher.parameter("routingContext"), Cypher.parameter("databaseName")) .yieldStar() .build(); assertThat(stmt.getCatalog().getLiterals()).isEmpty(); } @Test // GH-674 void retrievalOfRelationshipsShouldWork() { var n = Cypher.node("Person").withProperties("name", Cypher.literalOf("John Doe")).named("n"); var m = Cypher.node("Person").named("m"); var catalog = Cypher.match(n.relationshipTo(m, "IS_FRIEND_WITH")) .where(n.hasLabels("Active")) .and(m.hasLabels("Kind", "Positive").and(m.property("name").eq(Cypher.literalOf("Jane")))) .returning(Cypher.asterisk()) .build() .getCatalog(); var isFriendWith = StatementCatalog.Token.type("IS_FRIEND_WITH"); assertThat(catalog.getTargetNodes(isFriendWith)).isNotEmpty(); assertThat(catalog.getSourceNodes(isFriendWith)).isNotEmpty(); var person = StatementCatalog.Token.label("Person"); assertThat(catalog.getIncomingRelations(person)).containsExactlyInAnyOrder(isFriendWith); assertThat(catalog.getOutgoingRelations(person)).containsExactlyInAnyOrder(isFriendWith); } @RepeatedTest(20) void random_order_test() { var rendererConfig = Configuration.newConfig().withDialect(Dialect.NEO4J_5).withPrettyPrint(true).build(); var renderer = Renderer.getRenderer(rendererConfig); var person = Cypher.node("Person").named("n"); var movie = Cypher.node("Movie").named("m"); var rel = person.relationshipTo(movie).named("r"); var innerStatement = Cypher.match(rel).returning(person, rel, movie).build(); var graph_name = Cypher.name("__graph__name__"); var statement = Cypher.unwind(Cypher.graphNames()) .as(graph_name) .call(Cypher.use(Cypher.graphByName(graph_name), innerStatement)) .returning(innerStatement.getCatalog().getIdentifiableExpressions()) .build(); assertThat(renderer.render(statement)).isEqualTo(""" UNWIND graph.names() AS __graph__name__ CALL { USE graph.byName(__graph__name__) MATCH (n:Person)-[r]->(m:Movie) RETURN n, r, m } RETURN n, r, m"""); } @Test // GH-785 void nullParametersMustBeAllowed() { var statement = Cypher.match(Cypher.anyNode("n")) .where(Cypher.property("n", "whatever").isEqualTo(Cypher.parameter("foo", null))) .returning(Cypher.asterisk()) .build(); assertThat(statement.getCypher()).isEqualTo("MATCH (n) WHERE n.whatever = $foo RETURN *"); assertThat(statement.getCatalog().getParameters()).containsEntry("foo", null); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/StringLiteralTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class StringLiteralTests { @Test void shouldEscapeContent() { StringLiteral literal = new StringLiteral("A\\B\\\\Ca'bc123\\"); assertThat(literal.asString()).isEqualTo("'A\\\\B\\\\\\\\Ca\\'bc123\\\\'"); } @Test void shouldEscapeNull() { StringLiteral literal = new StringLiteral(null); assertThat(literal.asString()).isEqualTo("''"); } @Test void shouldCorrectlyEscapeEmptyStrings() { for (String[] strings : new String[][] { { "", "" }, { " \t ", " \t " }, { "Nothing to escape", "Nothing to escape" }, { "' \" '", "\\' \\\" \\'" } }) { String string = strings[0]; String expectedEscapedString = strings[1]; assertThat(StringLiteral.escapeString(string)).hasValue(expectedEscapedString); } } @Test void shouldNotTryToEscapeNullStrings() { assertThat(StringLiteral.escapeString(null)).isEmpty(); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/SubqueriesGQLIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.net.URI; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * @author Michael J. Simons */ class SubqueriesGQLIT { private static final Renderer cypherRenderer = Renderer.getDefaultRenderer(); @Test // GH-533 void unitSubqueries() { var p = Cypher.node("Person").named("p"); var outer = Cypher.match(p); var inner = Cypher.with(p) .unwind(Cypher.range(1, 5)) .as("i") .create(Cypher.node("Person").withProperties("name", p.property("name"))) .build(); var statement = outer.call(inner).returning(Cypher.count(Cypher.asterisk())).build(); assertThat(statement.getCypher()).isEqualTo( "MATCH (p:`Person`) CALL (p) {UNWIND range(1, 5) AS i CREATE (:`Person` {name: p.name})} RETURN count(*)"); } @Nested class Scope { @Test void nodePatternInCallMustBeFullAndNotKnown() { Statement parsed = Cypher.match(Cypher.node("Person").named("n")) .call(Cypher .match(Cypher.node("Movie").named("n").withProperties("title", Cypher.literalOf("The Matrix"))) .where(Cypher.anyNode("n").property("released").gte(Cypher.literalOf(1980))) .returning(Cypher.anyNode("n").as("m")) .build()) .returning(Cypher.anyNode("n").property("name")) .build(); String cypher = Renderer.getRenderer(Configuration.newConfig().alwaysEscapeNames(false).build()) .render(parsed); assertThat(cypher).isEqualTo( "MATCH (n:Person) CALL (*) {MATCH (n:Movie {title: 'The Matrix'}) WHERE n.released >= 1980 RETURN n AS m} RETURN n.name"); } } @Nested class ResultReturningSubqueries { @Test void importingVariablesShouldWork() { Statement statement = Cypher.unwind(Cypher.literalOf(0), Cypher.literalOf(1), Cypher.literalOf(2)) .as("x") .call(Cypher.with(Cypher.name("x")) .returning(Cypher.name("x").multiply(Cypher.literalOf(10)).as("y")) .build()) .returning("x", "y") .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("UNWIND [0, 1, 2] AS x CALL (x) {RETURN (x * 10) AS y} RETURN x, y"); assertThat(statement.getCatalog().getIdentifiableExpressions()) .containsExactlyInAnyOrder(SymbolicName.of("x"), SymbolicName.of("y")); } @Test void postUnionProcessingShouldWork() { Property ageProperty = Cypher.property("p", "age"); Property nameProperty = Cypher.property("p", "name"); // This must be 2 different person nodes and statement, otherwise the union // will reuse it. Statement s1 = Cypher.match(Cypher.node("Person").named("p")) .returning("p") .orderBy(ageProperty.ascending()) .limit(1) .build(); Statement s2 = Cypher.match(Cypher.node("Person").named("p")) .returning("p") .orderBy(ageProperty.descending()) .limit(1) .build(); Statement statement = Cypher.call(Cypher.union(s1, s2)) .returning(nameProperty, ageProperty) .orderBy(nameProperty) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "CALL () {MATCH (p:`Person`) RETURN p ORDER BY p.age ASC LIMIT 1 UNION MATCH (p:`Person`) RETURN p ORDER BY p.age DESC LIMIT 1} RETURN p.name, p.age ORDER BY p.name"); } @Test void aggregationAndSideEffectsShouldWork() { Node person = Cypher.node("Person").named("p"); Node clone = Cypher.node("Clone").named("c"); Statement statement = Cypher.match(person) .call(Cypher.unwind(Cypher.range(1, 5)) .as("i") .create(clone) .returning(Cypher.count(clone).as("numberOfClones")) .build()) .returning(person.property("name"), Cypher.name("numberOfClones")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (p:`Person`) CALL (*) {UNWIND range(1, 5) AS i CREATE (c:`Clone`) RETURN count(c) AS numberOfClones} RETURN p.name, numberOfClones"); } @Test void aggregationOnImportedVariablesShouldWork() { Node person = Cypher.node("Person").named("p"); Node other = Cypher.node("Person").named("other"); Statement statement = Cypher.match(person) .call(Cypher.with(person) .match(other) .where(other.property("age").lt(person.property("age"))) .returning(Cypher.count(other).as("youngerPersonsCount")) .build()) .returning(person.property("name"), Cypher.name("youngerPersonsCount")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (p:`Person`) CALL (p) {MATCH (other:`Person`) WHERE other.age < p.age RETURN count(other) AS youngerPersonsCount} RETURN p.name, youngerPersonsCount"); } @Test void nestedAfterProcedureCall() { // With with Statement statement = Cypher.call("dbms.components") .yield("name") .with("name") .call(Cypher.with("name") .match(Cypher.anyNode().named("n")) .where(Cypher.property("n", "name").isEqualTo(Cypher.name("name"))) .returning("n") .build()) .returning("n") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "CALL dbms.components() YIELD name WITH name CALL (name) {MATCH (n) WHERE n.name = name RETURN n} RETURN n"); assertThat(statement.getCatalog().getIdentifiableExpressions()) .containsExactlyInAnyOrder(SymbolicName.of("n")); // Without with statement = Cypher.call("dbms.components") .yield("name") .call(Cypher.with("name") .match(Cypher.anyNode().named("n")) .where(Cypher.property("n", "name").isEqualTo(Cypher.name("name"))) .returning("n") .build()) .returning("n") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "CALL dbms.components() YIELD name CALL (name) {MATCH (n) WHERE n.name = name RETURN n} RETURN n"); // After inQueryCall with with SymbolicName label = Cypher.name("label"); statement = Cypher.match(Cypher.anyNode().named("n")) .with("n") .call("db.labels") .yield(label) .with(label) .call(Cypher.with(label) .match(Cypher.anyNode().named("n")) .where(Cypher.property("n", "name").isEqualTo(label)) .returning("n") .build()) .returning("n") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n) WITH n CALL db.labels() YIELD label WITH label CALL (label) {MATCH (n) WHERE n.name = label RETURN n} RETURN n"); assertThat(statement.getCatalog().getIdentifiableExpressions()) .containsExactlyInAnyOrder(SymbolicName.of("n")); // After inQueryCall without with statement = Cypher.match(Cypher.anyNode().named("n")) .with("n") .call("db.labels") .yield(label) .call(Cypher.with(label) .match(Cypher.anyNode().named("n2")) .where(Cypher.property("n2", "name").isEqualTo(label)) .returning("n2") .build()) .returning("n2") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n) WITH n CALL db.labels() YIELD label CALL (label) {MATCH (n2) WHERE n2.name = label RETURN n2} RETURN n2"); } @Test void afterRegularWith() { Statement statement = Cypher.match(Cypher.node("Person").named("p")) .with("p") .call(Cypher.with("p") .match(Cypher.anyNode().named("n")) .where(Cypher.property("n", "name").isEqualTo(Cypher.property("p", "name"))) .returning("n") .build()) .returning("n") .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (p:`Person`) WITH p CALL (p) {MATCH (n) WHERE n.name = p.name RETURN n} RETURN n"); assertThat(statement.getCatalog().getIdentifiableExpressions()) .containsExactlyInAnyOrder(SymbolicName.of("n")); } @Test void afterRegularWithManualImport() { Statement statement = Cypher.match(Cypher.node("Person").named("p")) .with("p") .call(Cypher.match(Cypher.anyNode().named("n")) .where(Cypher.property("n", "name").isEqualTo(Cypher.property("p", "name"))) .returning("n") .build(), "p") .returning("n") .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (p:`Person`) WITH p CALL (p) {MATCH (n) WHERE n.name = p.name RETURN n} RETURN n"); assertThat(statement.getCatalog().getIdentifiableExpressions()) .containsExactlyInAnyOrder(SymbolicName.of("n")); } @Test void callsCallingCalls() { Statement someStatement = Cypher.match(Cypher.anyNode().named("n")).returning("n").build(); Statement statement = someStatement; for (int i = 0; i < 5; ++i) { statement = Cypher.call(statement).returning("n").build(); } assertThat(cypherRenderer.render(statement)).isEqualTo( "CALL () {CALL () {CALL () {CALL () {CALL () {MATCH (n) RETURN n} RETURN n} RETURN n} RETURN n} RETURN n} RETURN n"); } } @Nested class ExistentialSubqueries { @Test // GH-578 void fullStatementsAsExistentialSubQuery() { Node p = Cypher.node("Person").named("person"); Statement inner = Cypher.match(p.relationshipTo(Cypher.node("Dog"), "HAS_DOG")) .returning(p.property("name")) .build(); Statement outer = Cypher.match(p) .where(Cypher.exists(inner)) .returning(p.property("name").as("name")) .build(); String cypher = outer.getCypher(); assertThat(cypher).isEqualTo( "MATCH (person:`Person`) WHERE EXISTS { MATCH (person)-[:`HAS_DOG`]->(:`Dog`) RETURN person.name } RETURN person.name AS name"); } @Test // GH-578 void fullStatementsAsExistentialSubQueryWithImports() { Node p = Cypher.node("Person").named("person"); Node d = Cypher.node("Dog").named("d"); SymbolicName dogName = Cypher.name("dogName"); Statement inner = Cypher.match(p.relationshipTo(d, "HAS_DOG")) .where(d.property("name").eq(dogName)) .returning(p.property("name")) .build(); Statement outer = Cypher.match(p) .where(Cypher.exists(inner, Cypher.literalOf("Ozzy").as(dogName))) .returning(p.property("name").as("name")) .build(); String cypher = outer.getCypher(); assertThat(cypher).isEqualTo( "MATCH (person:`Person`) WHERE EXISTS { WITH 'Ozzy' AS dogName MATCH (person)-[:`HAS_DOG`]->(d:`Dog`) WHERE d.name = dogName RETURN person.name } RETURN person.name AS name"); } @Test void simple() { Node p = Cypher.node("Person").named("p"); Node friend = Cypher.node("Person").named("friend"); Relationship r = p.relationshipTo(friend, "IS_FRIENDS_WITH").named("r"); Statement statement = Cypher.match(r) .where(Cypher .match(p.relationshipTo(Cypher.node("Company").withProperties("name", Cypher.literalOf("Neo4j")), "WORKS_FOR")) .asCondition()) .returning(p, r, friend) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (p:`Person`)-[r:`IS_FRIENDS_WITH`]->(friend:`Person`) WHERE EXISTS { MATCH (p)-[:`WORKS_FOR`]->(:`Company` {name: 'Neo4j'}) } RETURN p, r, friend"); } @Test void withBooleanOpsAndWhere() { Node p = Cypher.node("Person").named("person"); Node company = Cypher.anyNode().named("company"); Node t = Cypher.node("Technology").named("t"); Statement statement = Cypher.match(p.relationshipTo(company, "WORKS_FOR")) .where(company.property("name").startsWith(Cypher.literalOf("Company"))) .and(Cypher.match(p.relationshipTo(t, "LIKES")) .where(Cypher.size(t.relationshipFrom(Cypher.anyNode(), "LIKES")).gte(Cypher.literalOf(3))) .asCondition()) .returning(p.property("name").as("person"), company.property("name").as("company")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (person:`Person`)-[:`WORKS_FOR`]->(company) WHERE (company.name STARTS WITH 'Company' AND EXISTS { MATCH (person)-[:`LIKES`]->(t:`Technology`) WHERE size((t)<-[:`LIKES`]-()) >= 3 }) RETURN person.name AS person, company.name AS company"); statement = Cypher.match(p.relationshipTo(company, "WORKS_FOR")) .where(Cypher.match(p.relationshipTo(t, "LIKES")) .where(Cypher.size(t.relationshipFrom(Cypher.anyNode(), "LIKES")).gte(Cypher.literalOf(3))) .asCondition()) .and(company.property("name").startsWith(Cypher.literalOf("Company"))) .returning(p.property("name").as("person"), company.property("name").as("company")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (person:`Person`)-[:`WORKS_FOR`]->(company) WHERE (EXISTS { MATCH (person)-[:`LIKES`]->(t:`Technology`) WHERE size((t)<-[:`LIKES`]-()) >= 3 } AND company.name STARTS WITH 'Company') RETURN person.name AS person, company.name AS company"); statement = Cypher.match(p.relationshipTo(company, "WORKS_FOR")) .where(Cypher.match(p.relationshipTo(t, "LIKES")) .where(Cypher.size(t.relationshipFrom(Cypher.anyNode(), "LIKES")).gte(Cypher.literalOf(3))) .asCondition() .and(company.property("name").startsWith(Cypher.literalOf("Company")))) .returning(p.property("name").as("person"), company.property("name").as("company")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (person:`Person`)-[:`WORKS_FOR`]->(company) WHERE (EXISTS { MATCH (person)-[:`LIKES`]->(t:`Technology`) WHERE size((t)<-[:`LIKES`]-()) >= 3 } AND company.name STARTS WITH 'Company') RETURN person.name AS person, company.name AS company"); } } @Nested class InTransactions { @Test void docs44_7() { SymbolicName line = Cypher.name("line"); Statement statement = Cypher.loadCSV(URI.create("file:///friends.csv")) .as("line") .callInTransactions(Cypher.with("line") .create(Cypher.node("Person") .withProperties("name", Cypher.valueAt(line, 1), "age", Cypher.toInteger(Cypher.valueAt(line, 2)))) .build()) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "LOAD CSV FROM 'file:///friends.csv' AS line CALL (line) {CREATE (:`Person` {name: line[1], age: toInteger(line[2])})} IN TRANSACTIONS"); } @Test void docs44_7_1a() { SymbolicName line = Cypher.name("line"); Statement statement = Cypher.loadCSV(URI.create("file:///friends.csv")) .as("line") .callInTransactions(Cypher.with("line") .create(Cypher.node("Person") .withProperties("name", Cypher.valueAt(line, 1), "age", Cypher.toInteger(Cypher.valueAt(line, 2)))) .build(), 2) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "LOAD CSV FROM 'file:///friends.csv' AS line CALL (line) {CREATE (:`Person` {name: line[1], age: toInteger(line[2])})} IN TRANSACTIONS OF 2 ROWS"); } @Test void docs44_7_1b() { Statement statement = Cypher.match(Cypher.anyNode("n")) .callInTransactions(Cypher.with("n").detachDelete("n").build(), 2) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (n) CALL (n) {DETACH DELETE n} IN TRANSACTIONS OF 2 ROWS"); } @ParameterizedTest @ValueSource(ints = { -1, 23 }) void afterRegularWith(int numRows) { ResultStatement subquery = Cypher .create(Cypher.anyNode("p").relationshipTo(Cypher.node("User").named("u"), "IS")) .returning(Cypher.name("u")) .build(); StatementBuilder.OrderableOngoingReadingAndWithWithoutWhere with = Cypher .match(Cypher.node("Person").named("p")) .with("p"); Statement statement = ((numRows < 0) ? with.callInTransactions(subquery, "p") : with.callInTransactions(subquery, numRows, "p")) .returning("u") .build(); String expected = "MATCH (p:`Person`) WITH p CALL (p) {CREATE (p)-[:`IS`]->(u:`User`) RETURN u} IN TRANSACTIONS"; if (numRows > 0) { expected += " OF " + numRows + " ROWS"; } expected += " RETURN u"; assertThat(cypherRenderer.render(statement)).isEqualTo(expected); assertThat(statement.getCatalog().getIdentifiableExpressions()) .containsExactlyInAnyOrder(SymbolicName.of("u")); } @ParameterizedTest @ValueSource(ints = { -1, 23 }) void afterRegularWithSymNameImport(int numRows) { SymbolicName p = Cypher.name("p"); ResultStatement subquery = Cypher .create(Cypher.anyNode(p).relationshipTo(Cypher.node("User").named("u"), "IS")) .returning(Cypher.name("u")) .build(); StatementBuilder.OrderableOngoingReadingAndWithWithoutWhere with = Cypher .match(Cypher.node("Person").named(p)) .with(p); Statement statement = ((numRows < 0) ? with.callInTransactions(subquery, p) : with.callInTransactions(subquery, numRows, p)) .returning("u") .build(); String expected = "MATCH (p:`Person`) WITH p CALL (p) {CREATE (p)-[:`IS`]->(u:`User`) RETURN u} IN TRANSACTIONS"; if (numRows > 0) { expected += " OF " + numRows + " ROWS"; } expected += " RETURN u"; assertThat(cypherRenderer.render(statement)).isEqualTo(expected); assertThat(statement.getCatalog().getIdentifiableExpressions()) .containsExactlyInAnyOrder(SymbolicName.of("u")); } @Test void nestedAfterProcedureCall() { // With with Statement statement = Cypher.call("dbms.components") .yield("name") .with("name") .callInTransactions(Cypher.with("name") .match(Cypher.anyNode().named("n")) .where(Cypher.property("n", "name").isEqualTo(Cypher.name("name"))) .returning("n") .build()) .returning("n") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "CALL dbms.components() YIELD name WITH name CALL (name) {MATCH (n) WHERE n.name = name RETURN n} IN TRANSACTIONS RETURN n"); assertThat(statement.getCatalog().getIdentifiableExpressions()) .containsExactlyInAnyOrder(SymbolicName.of("n")); // Without with statement = Cypher.call("dbms.components") .yield("name") .callInTransactions(Cypher.with("name") .match(Cypher.anyNode().named("n")) .where(Cypher.property("n", "name").isEqualTo(Cypher.name("name"))) .returning("n") .build()) .returning("n") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "CALL dbms.components() YIELD name CALL (name) {MATCH (n) WHERE n.name = name RETURN n} IN TRANSACTIONS RETURN n"); // After inQueryCall with with SymbolicName label = Cypher.name("label"); statement = Cypher.match(Cypher.anyNode().named("n")) .with("n") .call("db.labels") .yield(label) .with(label) .callInTransactions(Cypher.with(label) .match(Cypher.anyNode().named("n")) .where(Cypher.property("n", "name").isEqualTo(label)) .returning("n") .build()) .returning("n") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n) WITH n CALL db.labels() YIELD label WITH label CALL (label) {MATCH (n) WHERE n.name = label RETURN n} IN TRANSACTIONS RETURN n"); assertThat(statement.getCatalog().getIdentifiableExpressions()) .containsExactlyInAnyOrder(SymbolicName.of("n")); // After inQueryCall without with statement = Cypher.match(Cypher.anyNode().named("n")) .with("n") .call("db.labels") .yield(label) .callInTransactions(Cypher.with(label) .match(Cypher.anyNode().named("n2")) .where(Cypher.property("n2", "name").isEqualTo(label)) .returning("n2") .build()) .returning("n2") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n) WITH n CALL db.labels() YIELD label CALL (label) {MATCH (n2) WHERE n2.name = label RETURN n2} IN TRANSACTIONS RETURN n2"); } } @Nested class CollectSubqueries { private final Node person = Cypher.node("Person").named("person"); private final Property personName = this.person.property("name"); private final Node dog = Cypher.node("Dog").named("dog"); private final Property dogName = this.dog.property("name"); private final Relationship hasDog = this.person.relationshipTo(this.dog, "HAS_DOG"); private final Node cat = Cypher.node("Cat").named("cat"); @Test void shouldCheckForReturnStatement() { var nonReturnStatement = Cypher.create(Cypher.node("Foo")).build(); assertThatIllegalArgumentException().isThrownBy(() -> Cypher.collect(nonReturnStatement)) .withMessage( "The final RETURN clause in a subquery used with COLLECT is mandatory and the RETURN clause must return exactly one column."); } @Test void simple() { var inner = Cypher.match(this.person.relationshipTo(this.dog, "HAS_DOG")).returning(this.dogName).build(); var stmt = Cypher.match(this.person) .where(Cypher.literalOf("Ozzy").in(Cypher.collect(inner))) .returning(this.person.property("name").as("name")) .build(); assertThat(stmt.getCypher()).isEqualTo( "MATCH (person:`Person`) WHERE 'Ozzy' IN COLLECT { MATCH (person)-[:`HAS_DOG`]->(dog:`Dog`) RETURN dog.name } RETURN person.name AS name"); } @Test void withWhereClause() { var r = this.hasDog.named("r"); var inner = Cypher.match(r) .where(r.property("since").gt(Cypher.literalOf(2017))) .returning(this.dogName) .build(); var stmt = Cypher.match(this.person) .returning(this.person.property("name").as("name"), Cypher.collect(inner).as("youngDogs")) .build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (person:`Person`) " + "RETURN person.name AS name, COLLECT {" + " MATCH (person)-[r:`HAS_DOG`]->(dog:`Dog`)" + " WHERE r.since > 2017" + " RETURN dog.name " + "} AS youngDogs"); } @Test void withAUnion() { var hasCat = this.person.relationshipTo(this.cat, "HAS_CAT"); var inner = Cypher.union(Cypher.match(this.hasDog).returning(this.dogName.as("petName")).build(), Cypher.match(hasCat).returning(this.cat.property("name").as("petName")).build()); var stmt = Cypher.match(this.person) .returning(this.person.property("name").as("name"), Cypher.collect(inner).as("petNames")) .build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (person:`Person`) " + "RETURN person.name AS name, " + "COLLECT {" + " MATCH (person)-[:`HAS_DOG`]->(dog:`Dog`)" + " RETURN dog.name AS petName" + " UNION" + " MATCH (person)-[:`HAS_CAT`]->(cat:`Cat`)" + " RETURN cat.name AS petName " + "} AS petNames"); } @Test void withWith() { var r = this.hasDog.named("r"); var yearOfTheDog = Cypher.literalOf(2018).as("yearOfTheDog"); var inner = Cypher.match(r).where(r.property("since").eq(yearOfTheDog)).returning(this.dogName).build(); var stmt = Cypher.match(this.person) .returning(this.person.property("name").as("name"), Cypher.subqueryWith(yearOfTheDog).collect(inner).as("dogsOfTheYear")) .build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (person:`Person`) " + "RETURN person.name AS name, COLLECT {" + " WITH 2018 AS yearOfTheDog" + " MATCH (person)-[r:`HAS_DOG`]->(dog:`Dog`)" + " WHERE r.since = yearOfTheDog" + " RETURN dog.name " + "} AS dogsOfTheYear"); } @Test void inReturn() { var toy = Cypher.node("Toy").named("t"); var inner = Cypher.match(this.hasDog) .match(this.dog.relationshipTo(toy, "HAS_TOY")) .returning(toy.property("name")) .build(); var stmt = Cypher.match(this.person) .returning(this.person.property("name"), Cypher.collect(inner).as("toyNames")) .build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (person:`Person`) " + "RETURN person.name, " + "COLLECT {" + " MATCH (person)-[:`HAS_DOG`]->(dog:`Dog`)" + " MATCH (dog)-[:`HAS_TOY`]->(t:`Toy`)" + " RETURN t.name " + "} AS toyNames"); } @Test void collectInSet() { var inner = Cypher.match(this.hasDog).returning(this.dogName).build(); var stmt = Cypher.match(this.person) .where(this.person.property("name").eq(Cypher.literalOf("Peter"))) .set(this.person.property("dogNames").to(Cypher.collect(inner))) .returning(this.person.property("dogNames").as("dogNames")) .build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (person:`Person`) WHERE person.name = 'Peter' " + "SET person.dogNames = COLLECT { MATCH (person)-[:`HAS_DOG`]->(dog:`Dog`) RETURN dog.name } " + "RETURN person.dogNames AS dogNames"); } @Test void inCase() { var inner = Cypher.match(this.hasDog).returning(this.dogName).build(); var stmt = Cypher.match(this.person) .returning(Cypher.caseExpression() .when(Cypher.collect(inner).eq(Cypher.listOf())) .then(Cypher.literalOf("No Dogs ").concat(this.personName)) .elseDefault(this.personName) .as("result")) .build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (person:`Person`) " + "RETURN " + "CASE " + "WHEN COLLECT { MATCH (person)-[:`HAS_DOG`]->(dog:`Dog`) RETURN dog.name } = [] THEN ('No Dogs ' + person.name) " + "ELSE person.name " + "END AS result"); } @Test void asGroupingKey() { var inner = Cypher.match(this.hasDog).returning(this.dogName).build(); var stmt = Cypher.match(this.person) .returning(Cypher.collect(inner).as("dogNames"), Cypher.avg(this.person.property("age")).as("averageAge")) .orderBy(Cypher.name("dogNames")) .build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (person:`Person`) " + "RETURN COLLECT { MATCH (person)-[:`HAS_DOG`]->(dog:`Dog`) RETURN dog.name } AS dogNames," + " avg(person.age) AS averageAge " + "ORDER BY dogNames"); } } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/SubqueriesIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.net.URI; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Dialect; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * @author Michael J. Simons */ class SubqueriesIT { private static final Renderer cypherRenderer = Renderer .getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_4).build()); @Test // GH-533 void unitSubqueries() { var p = Cypher.node("Person").named("p"); var outer = Cypher.match(p); var inner = Cypher.with(p) .unwind(Cypher.range(1, 5)) .as("i") .create(Cypher.node("Person").withProperties("name", p.property("name"))) .build(); var statement = outer.call(inner).returning(Cypher.count(Cypher.asterisk())).build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (p:`Person`) CALL {WITH p UNWIND range(1, 5) AS i CREATE (:`Person` {name: p.name})} RETURN count(*)"); } @Nested class Scope { @Test void nodePatternInCallMustBeFullAndNotKnown() { Statement parsed = Cypher.match(Cypher.node("Person").named("n")) .call(Cypher .match(Cypher.node("Movie").named("n").withProperties("title", Cypher.literalOf("The Matrix"))) .where(Cypher.anyNode("n").property("released").gte(Cypher.literalOf(1980))) .returning(Cypher.anyNode("n").as("m")) .build()) .returning(Cypher.anyNode("n").property("name")) .build(); String cypher = Renderer.getRenderer(Configuration.newConfig().alwaysEscapeNames(false).build()) .render(parsed); assertThat(cypher).isEqualTo( "MATCH (n:Person) CALL (*) {MATCH (n:Movie {title: 'The Matrix'}) WHERE n.released >= 1980 RETURN n AS m} RETURN n.name"); } } @Nested class DialectSupport { @ParameterizedTest @CsvSource(delimiterString = "|", textBlock = """ NEO4J_5|UNWIND [0, 1, 2] AS x CALL {RETURN 'hello' AS innerReturn} RETURN innerReturn NEO4J_5_23|UNWIND [0, 1, 2] AS x CALL (*) {RETURN 'hello' AS innerReturn} RETURN innerReturn """) void starterExample(Dialect dialect, String expected) { var stmt = Cypher.unwind(Cypher.listOf(Cypher.literalOf(0), Cypher.literalOf(1), Cypher.literalOf(2))) .as("x") .call(Cypher.returning(Cypher.literalOf("hello").as("innerReturn")).build()) .returning("innerReturn") .build(); var renderer = Renderer.getRenderer(Configuration.newConfig().withDialect(dialect).build()); assertThat(renderer.render(stmt)).isEqualTo(expected); } @ParameterizedTest @CsvSource(delimiterString = "|", textBlock = """ NEO4J_5|MATCH (p:Player), (t:Team) CALL {WITH p WITH p, rand() AS random SET p.rating = random RETURN p.name AS playerName, p.rating AS rating} RETURN playerName, rating, t AS team ORDER BY rating LIMIT 1 NEO4J_5_23|MATCH (p:Player), (t:Team) CALL (p) {WITH p, rand() AS random SET p.rating = random RETURN p.name AS playerName, p.rating AS rating} RETURN playerName, rating, t AS team ORDER BY rating LIMIT 1 """) void someImports(Dialect dialect, String expected) { var p = Cypher.node("Player").named("p"); var t = Cypher.node("Team").named("t"); var rating = Cypher.name("rating"); var playerName = Cypher.name("playerName"); var stmt = Cypher.match(p, t) .call(Cypher.with(p, Cypher.rand().as("random")) .set(p.property("rating").to(Cypher.name("random"))) .returning(p.property("name").as(playerName), p.property("rating").as(rating)) .build(), p) .returning(playerName, rating, t.as("team")) .orderBy(rating) .limit(1) .build(); var renderer = Renderer .getRenderer(Configuration.newConfig().alwaysEscapeNames(false).withDialect(dialect).build()); assertThat(renderer.render(stmt)).isEqualTo(expected); } @ParameterizedTest @CsvSource(delimiterString = "|", textBlock = """ NEO4J_5|MATCH (p:Player), (t:Team) CALL {WITH * RETURN p AS player, t AS team} RETURN player, team NEO4J_5_23|MATCH (p:Player), (t:Team) CALL (*) {RETURN p AS player, t AS team} RETURN player, team """) void allImports(Dialect dialect, String expected) { var p = Cypher.node("Player").named("p"); var t = Cypher.node("Team").named("t"); var player = Cypher.name("player"); var stmt = Cypher.match(p, t) .call(Cypher.returning(p.as("player"), t.as("team")).build(), Cypher.asterisk()) .returning(player, Cypher.name("team")) .build(); var renderer = Renderer .getRenderer(Configuration.newConfig().alwaysEscapeNames(false).withDialect(dialect).build()); assertThat(renderer.render(stmt)).isEqualTo(expected); } @ParameterizedTest @CsvSource(delimiterString = "|", textBlock = """ NEO4J_5|MATCH (t:Team) CALL {MATCH (p:Player) RETURN count(p) AS totalPlayers} RETURN count(t) AS totalTeams, totalPlayers NEO4J_5_23|MATCH (t:Team) CALL (*) {MATCH (p:Player) RETURN count(p) AS totalPlayers} RETURN count(t) AS totalTeams, totalPlayers """) void noImports(Dialect dialect, String expected) { var p = Cypher.node("Player").named("p"); var t = Cypher.node("Team").named("t"); var stmt = Cypher.match(t) .call(Cypher.match(p).returning(Cypher.count(p).as("totalPlayers")).build()) .returning(Cypher.count(t).as("totalTeams"), Cypher.name("totalPlayers")) .build(); var renderer = Renderer .getRenderer(Configuration.newConfig().alwaysEscapeNames(false).withDialect(dialect).build()); assertThat(renderer.render(stmt)).isEqualTo(expected); } } @Nested class ResultReturningSubqueries { @Test void importingVariablesShouldWork() { Statement statement = Cypher.unwind(Cypher.literalOf(0), Cypher.literalOf(1), Cypher.literalOf(2)) .as("x") .call(Cypher.with(Cypher.name("x")) .returning(Cypher.name("x").multiply(Cypher.literalOf(10)).as("y")) .build()) .returning("x", "y") .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("UNWIND [0, 1, 2] AS x CALL {WITH x RETURN (x * 10) AS y} RETURN x, y"); assertThat(statement.getCatalog().getIdentifiableExpressions()) .containsExactlyInAnyOrder(SymbolicName.of("x"), SymbolicName.of("y")); } @Test void postUnionProcessingShouldWork() { Property ageProperty = Cypher.property("p", "age"); Property nameProperty = Cypher.property("p", "name"); // This must be 2 different person nodes and statement, otherwise the union // will reuse it. Statement s1 = Cypher.match(Cypher.node("Person").named("p")) .returning("p") .orderBy(ageProperty.ascending()) .limit(1) .build(); Statement s2 = Cypher.match(Cypher.node("Person").named("p")) .returning("p") .orderBy(ageProperty.descending()) .limit(1) .build(); Statement statement = Cypher.call(Cypher.union(s1, s2)) .returning(nameProperty, ageProperty) .orderBy(nameProperty) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "CALL {MATCH (p:`Person`) RETURN p ORDER BY p.age ASC LIMIT 1 UNION MATCH (p:`Person`) RETURN p ORDER BY p.age DESC LIMIT 1} RETURN p.name, p.age ORDER BY p.name"); } @Test void aggregationAndSideEffectsShouldWork() { Node person = Cypher.node("Person").named("p"); Node clone = Cypher.node("Clone").named("c"); Statement statement = Cypher.match(person) .call(Cypher.unwind(Cypher.range(1, 5)) .as("i") .create(clone) .returning(Cypher.count(clone).as("numberOfClones")) .build()) .returning(person.property("name"), Cypher.name("numberOfClones")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (p:`Person`) CALL {UNWIND range(1, 5) AS i CREATE (c:`Clone`) RETURN count(c) AS numberOfClones} RETURN p.name, numberOfClones"); } @Test void aggregationOnImportedVariablesShouldWork() { Node person = Cypher.node("Person").named("p"); Node other = Cypher.node("Person").named("other"); Statement statement = Cypher.match(person) .call(Cypher.with(person) .match(other) .where(other.property("age").lt(person.property("age"))) .returning(Cypher.count(other).as("youngerPersonsCount")) .build()) .returning(person.property("name"), Cypher.name("youngerPersonsCount")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (p:`Person`) CALL {WITH p MATCH (other:`Person`) WHERE other.age < p.age RETURN count(other) AS youngerPersonsCount} RETURN p.name, youngerPersonsCount"); } @Test void nestedAfterProcedureCall() { // With with Statement statement = Cypher.call("dbms.components") .yield("name") .with("name") .call(Cypher.with("name") .match(Cypher.anyNode().named("n")) .where(Cypher.property("n", "name").isEqualTo(Cypher.name("name"))) .returning("n") .build()) .returning("n") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "CALL dbms.components() YIELD name WITH name CALL {WITH name MATCH (n) WHERE n.name = name RETURN n} RETURN n"); assertThat(statement.getCatalog().getIdentifiableExpressions()) .containsExactlyInAnyOrder(SymbolicName.of("n")); // Without with statement = Cypher.call("dbms.components") .yield("name") .call(Cypher.with("name") .match(Cypher.anyNode().named("n")) .where(Cypher.property("n", "name").isEqualTo(Cypher.name("name"))) .returning("n") .build()) .returning("n") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "CALL dbms.components() YIELD name CALL {WITH name MATCH (n) WHERE n.name = name RETURN n} RETURN n"); // After inQueryCall with with SymbolicName label = Cypher.name("label"); statement = Cypher.match(Cypher.anyNode().named("n")) .with("n") .call("db.labels") .yield(label) .with(label) .call(Cypher.with(label) .match(Cypher.anyNode().named("n")) .where(Cypher.property("n", "name").isEqualTo(label)) .returning("n") .build()) .returning("n") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n) WITH n CALL db.labels() YIELD label WITH label CALL {WITH label MATCH (n) WHERE n.name = label RETURN n} RETURN n"); assertThat(statement.getCatalog().getIdentifiableExpressions()) .containsExactlyInAnyOrder(SymbolicName.of("n")); // After inQueryCall without with statement = Cypher.match(Cypher.anyNode().named("n")) .with("n") .call("db.labels") .yield(label) .call(Cypher.with(label) .match(Cypher.anyNode().named("n2")) .where(Cypher.property("n2", "name").isEqualTo(label)) .returning("n2") .build()) .returning("n2") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n) WITH n CALL db.labels() YIELD label CALL {WITH label MATCH (n2) WHERE n2.name = label RETURN n2} RETURN n2"); } @Test void afterRegularWith() { Statement statement = Cypher.match(Cypher.node("Person").named("p")) .with("p") .call(Cypher.with("p") .match(Cypher.anyNode().named("n")) .where(Cypher.property("n", "name").isEqualTo(Cypher.property("p", "name"))) .returning("n") .build()) .returning("n") .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (p:`Person`) WITH p CALL {WITH p MATCH (n) WHERE n.name = p.name RETURN n} RETURN n"); assertThat(statement.getCatalog().getIdentifiableExpressions()) .containsExactlyInAnyOrder(SymbolicName.of("n")); } @Test void afterRegularWithManualImport() { Statement statement = Cypher.match(Cypher.node("Person").named("p")) .with("p") .call(Cypher.match(Cypher.anyNode().named("n")) .where(Cypher.property("n", "name").isEqualTo(Cypher.property("p", "name"))) .returning("n") .build(), "p") .returning("n") .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (p:`Person`) WITH p CALL {WITH p MATCH (n) WHERE n.name = p.name RETURN n} RETURN n"); assertThat(statement.getCatalog().getIdentifiableExpressions()) .containsExactlyInAnyOrder(SymbolicName.of("n")); } @Test void callsCallingCalls() { Statement someStatement = Cypher.match(Cypher.anyNode().named("n")).returning("n").build(); Statement statement = someStatement; for (int i = 0; i < 5; ++i) { statement = Cypher.call(statement).returning("n").build(); } assertThat(cypherRenderer.render(statement)).isEqualTo( "CALL {CALL {CALL {CALL {CALL {MATCH (n) RETURN n} RETURN n} RETURN n} RETURN n} RETURN n} RETURN n"); } } @Nested class ExistentialSubqueries { @Test // GH-578 void fullStatementsAsExistentialSubQuery() { Node p = Cypher.node("Person").named("person"); Statement inner = Cypher.match(p.relationshipTo(Cypher.node("Dog"), "HAS_DOG")) .returning(p.property("name")) .build(); Statement outer = Cypher.match(p) .where(Cypher.exists(inner)) .returning(p.property("name").as("name")) .build(); String cypher = outer.getCypher(); assertThat(cypher).isEqualTo( "MATCH (person:`Person`) WHERE EXISTS { MATCH (person)-[:`HAS_DOG`]->(:`Dog`) RETURN person.name } RETURN person.name AS name"); } @Test // GH-578 void fullStatementsAsExistentialSubQueryWithImports() { Node p = Cypher.node("Person").named("person"); Node d = Cypher.node("Dog").named("d"); SymbolicName dogName = Cypher.name("dogName"); Statement inner = Cypher.match(p.relationshipTo(d, "HAS_DOG")) .where(d.property("name").eq(dogName)) .returning(p.property("name")) .build(); Statement outer = Cypher.match(p) .where(Cypher.exists(inner, Cypher.literalOf("Ozzy").as(dogName))) .returning(p.property("name").as("name")) .build(); String cypher = outer.getCypher(); assertThat(cypher).isEqualTo( "MATCH (person:`Person`) WHERE EXISTS { WITH 'Ozzy' AS dogName MATCH (person)-[:`HAS_DOG`]->(d:`Dog`) WHERE d.name = dogName RETURN person.name } RETURN person.name AS name"); } @Test void simple() { Node p = Cypher.node("Person").named("p"); Node friend = Cypher.node("Person").named("friend"); Relationship r = p.relationshipTo(friend, "IS_FRIENDS_WITH").named("r"); Statement statement = Cypher.match(r) .where(Cypher .match(p.relationshipTo(Cypher.node("Company").withProperties("name", Cypher.literalOf("Neo4j")), "WORKS_FOR")) .asCondition()) .returning(p, r, friend) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (p:`Person`)-[r:`IS_FRIENDS_WITH`]->(friend:`Person`) WHERE EXISTS { MATCH (p)-[:`WORKS_FOR`]->(:`Company` {name: 'Neo4j'}) } RETURN p, r, friend"); } @Test void withBooleanOpsAndWhere() { Node p = Cypher.node("Person").named("person"); Node company = Cypher.anyNode().named("company"); Node t = Cypher.node("Technology").named("t"); Statement statement = Cypher.match(p.relationshipTo(company, "WORKS_FOR")) .where(company.property("name").startsWith(Cypher.literalOf("Company"))) .and(Cypher.match(p.relationshipTo(t, "LIKES")) .where(Cypher.size(t.relationshipFrom(Cypher.anyNode(), "LIKES")).gte(Cypher.literalOf(3))) .asCondition()) .returning(p.property("name").as("person"), company.property("name").as("company")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (person:`Person`)-[:`WORKS_FOR`]->(company) WHERE (company.name STARTS WITH 'Company' AND EXISTS { MATCH (person)-[:`LIKES`]->(t:`Technology`) WHERE size((t)<-[:`LIKES`]-()) >= 3 }) RETURN person.name AS person, company.name AS company"); statement = Cypher.match(p.relationshipTo(company, "WORKS_FOR")) .where(Cypher.match(p.relationshipTo(t, "LIKES")) .where(Cypher.size(t.relationshipFrom(Cypher.anyNode(), "LIKES")).gte(Cypher.literalOf(3))) .asCondition()) .and(company.property("name").startsWith(Cypher.literalOf("Company"))) .returning(p.property("name").as("person"), company.property("name").as("company")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (person:`Person`)-[:`WORKS_FOR`]->(company) WHERE (EXISTS { MATCH (person)-[:`LIKES`]->(t:`Technology`) WHERE size((t)<-[:`LIKES`]-()) >= 3 } AND company.name STARTS WITH 'Company') RETURN person.name AS person, company.name AS company"); statement = Cypher.match(p.relationshipTo(company, "WORKS_FOR")) .where(Cypher.match(p.relationshipTo(t, "LIKES")) .where(Cypher.size(t.relationshipFrom(Cypher.anyNode(), "LIKES")).gte(Cypher.literalOf(3))) .asCondition() .and(company.property("name").startsWith(Cypher.literalOf("Company")))) .returning(p.property("name").as("person"), company.property("name").as("company")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (person:`Person`)-[:`WORKS_FOR`]->(company) WHERE (EXISTS { MATCH (person)-[:`LIKES`]->(t:`Technology`) WHERE size((t)<-[:`LIKES`]-()) >= 3 } AND company.name STARTS WITH 'Company') RETURN person.name AS person, company.name AS company"); } } @Nested class InTransactions { @Test void docs44_7() { SymbolicName line = Cypher.name("line"); Statement statement = Cypher.loadCSV(URI.create("file:///friends.csv")) .as("line") .callInTransactions(Cypher.with("line") .create(Cypher.node("Person") .withProperties("name", Cypher.valueAt(line, 1), "age", Cypher.toInteger(Cypher.valueAt(line, 2)))) .build()) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "LOAD CSV FROM 'file:///friends.csv' AS line CALL {WITH line CREATE (:`Person` {name: line[1], age: toInteger(line[2])})} IN TRANSACTIONS"); } @Test void docs44_7_1a() { SymbolicName line = Cypher.name("line"); Statement statement = Cypher.loadCSV(URI.create("file:///friends.csv")) .as("line") .callInTransactions(Cypher.with("line") .create(Cypher.node("Person") .withProperties("name", Cypher.valueAt(line, 1), "age", Cypher.toInteger(Cypher.valueAt(line, 2)))) .build(), 2) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "LOAD CSV FROM 'file:///friends.csv' AS line CALL {WITH line CREATE (:`Person` {name: line[1], age: toInteger(line[2])})} IN TRANSACTIONS OF 2 ROWS"); } @Test void docs44_7_1b() { Statement statement = Cypher.match(Cypher.anyNode("n")) .callInTransactions(Cypher.with("n").detachDelete("n").build(), 2) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (n) CALL {WITH n DETACH DELETE n} IN TRANSACTIONS OF 2 ROWS"); } @ParameterizedTest @ValueSource(ints = { -1, 23 }) void afterRegularWith(int numRows) { ResultStatement subquery = Cypher .create(Cypher.anyNode("p").relationshipTo(Cypher.node("User").named("u"), "IS")) .returning(Cypher.name("u")) .build(); StatementBuilder.OrderableOngoingReadingAndWithWithoutWhere with = Cypher .match(Cypher.node("Person").named("p")) .with("p"); Statement statement = ((numRows < 0) ? with.callInTransactions(subquery, "p") : with.callInTransactions(subquery, numRows, "p")) .returning("u") .build(); String expected = "MATCH (p:`Person`) WITH p CALL {WITH p CREATE (p)-[:`IS`]->(u:`User`) RETURN u} IN TRANSACTIONS"; if (numRows > 0) { expected += " OF " + numRows + " ROWS"; } expected += " RETURN u"; assertThat(cypherRenderer.render(statement)).isEqualTo(expected); assertThat(statement.getCatalog().getIdentifiableExpressions()) .containsExactlyInAnyOrder(SymbolicName.of("u")); } @ParameterizedTest @ValueSource(ints = { -1, 23 }) void afterRegularWithSymNameImport(int numRows) { SymbolicName p = Cypher.name("p"); ResultStatement subquery = Cypher .create(Cypher.anyNode(p).relationshipTo(Cypher.node("User").named("u"), "IS")) .returning(Cypher.name("u")) .build(); StatementBuilder.OrderableOngoingReadingAndWithWithoutWhere with = Cypher .match(Cypher.node("Person").named(p)) .with(p); Statement statement = ((numRows < 0) ? with.callInTransactions(subquery, p) : with.callInTransactions(subquery, numRows, p)) .returning("u") .build(); String expected = "MATCH (p:`Person`) WITH p CALL {WITH p CREATE (p)-[:`IS`]->(u:`User`) RETURN u} IN TRANSACTIONS"; if (numRows > 0) { expected += " OF " + numRows + " ROWS"; } expected += " RETURN u"; assertThat(cypherRenderer.render(statement)).isEqualTo(expected); assertThat(statement.getCatalog().getIdentifiableExpressions()) .containsExactlyInAnyOrder(SymbolicName.of("u")); } @Test void nestedAfterProcedureCall() { // With with Statement statement = Cypher.call("dbms.components") .yield("name") .with("name") .callInTransactions(Cypher.with("name") .match(Cypher.anyNode().named("n")) .where(Cypher.property("n", "name").isEqualTo(Cypher.name("name"))) .returning("n") .build()) .returning("n") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "CALL dbms.components() YIELD name WITH name CALL {WITH name MATCH (n) WHERE n.name = name RETURN n} IN TRANSACTIONS RETURN n"); assertThat(statement.getCatalog().getIdentifiableExpressions()) .containsExactlyInAnyOrder(SymbolicName.of("n")); // Without with statement = Cypher.call("dbms.components") .yield("name") .callInTransactions(Cypher.with("name") .match(Cypher.anyNode().named("n")) .where(Cypher.property("n", "name").isEqualTo(Cypher.name("name"))) .returning("n") .build()) .returning("n") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "CALL dbms.components() YIELD name CALL {WITH name MATCH (n) WHERE n.name = name RETURN n} IN TRANSACTIONS RETURN n"); // After inQueryCall with with SymbolicName label = Cypher.name("label"); statement = Cypher.match(Cypher.anyNode().named("n")) .with("n") .call("db.labels") .yield(label) .with(label) .callInTransactions(Cypher.with(label) .match(Cypher.anyNode().named("n")) .where(Cypher.property("n", "name").isEqualTo(label)) .returning("n") .build()) .returning("n") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n) WITH n CALL db.labels() YIELD label WITH label CALL {WITH label MATCH (n) WHERE n.name = label RETURN n} IN TRANSACTIONS RETURN n"); assertThat(statement.getCatalog().getIdentifiableExpressions()) .containsExactlyInAnyOrder(SymbolicName.of("n")); // After inQueryCall without with statement = Cypher.match(Cypher.anyNode().named("n")) .with("n") .call("db.labels") .yield(label) .callInTransactions(Cypher.with(label) .match(Cypher.anyNode().named("n2")) .where(Cypher.property("n2", "name").isEqualTo(label)) .returning("n2") .build()) .returning("n2") .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n) WITH n CALL db.labels() YIELD label CALL {WITH label MATCH (n2) WHERE n2.name = label RETURN n2} IN TRANSACTIONS RETURN n2"); } } @Nested class CollectSubqueries { private final Node person = Cypher.node("Person").named("person"); private final Property personName = this.person.property("name"); private final Node dog = Cypher.node("Dog").named("dog"); private final Property dogName = this.dog.property("name"); private final Relationship hasDog = this.person.relationshipTo(this.dog, "HAS_DOG"); private final Node cat = Cypher.node("Cat").named("cat"); @Test void shouldCheckForReturnStatement() { var nonReturnStatement = Cypher.create(Cypher.node("Foo")).build(); assertThatIllegalArgumentException().isThrownBy(() -> Cypher.collect(nonReturnStatement)) .withMessage( "The final RETURN clause in a subquery used with COLLECT is mandatory and the RETURN clause must return exactly one column."); } @Test void simple() { var inner = Cypher.match(this.person.relationshipTo(this.dog, "HAS_DOG")).returning(this.dogName).build(); var stmt = Cypher.match(this.person) .where(Cypher.literalOf("Ozzy").in(Cypher.collect(inner))) .returning(this.person.property("name").as("name")) .build(); assertThat(stmt.getCypher()).isEqualTo( "MATCH (person:`Person`) WHERE 'Ozzy' IN COLLECT { MATCH (person)-[:`HAS_DOG`]->(dog:`Dog`) RETURN dog.name } RETURN person.name AS name"); } @Test void withWhereClause() { var r = this.hasDog.named("r"); var inner = Cypher.match(r) .where(r.property("since").gt(Cypher.literalOf(2017))) .returning(this.dogName) .build(); var stmt = Cypher.match(this.person) .returning(this.person.property("name").as("name"), Cypher.collect(inner).as("youngDogs")) .build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (person:`Person`) " + "RETURN person.name AS name, COLLECT {" + " MATCH (person)-[r:`HAS_DOG`]->(dog:`Dog`)" + " WHERE r.since > 2017" + " RETURN dog.name " + "} AS youngDogs"); } @Test void withAUnion() { var hasCat = this.person.relationshipTo(this.cat, "HAS_CAT"); var inner = Cypher.union(Cypher.match(this.hasDog).returning(this.dogName.as("petName")).build(), Cypher.match(hasCat).returning(this.cat.property("name").as("petName")).build()); var stmt = Cypher.match(this.person) .returning(this.person.property("name").as("name"), Cypher.collect(inner).as("petNames")) .build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (person:`Person`) " + "RETURN person.name AS name, " + "COLLECT {" + " MATCH (person)-[:`HAS_DOG`]->(dog:`Dog`)" + " RETURN dog.name AS petName" + " UNION" + " MATCH (person)-[:`HAS_CAT`]->(cat:`Cat`)" + " RETURN cat.name AS petName " + "} AS petNames"); } @Test void withWith() { var r = this.hasDog.named("r"); var yearOfTheDog = Cypher.literalOf(2018).as("yearOfTheDog"); var inner = Cypher.match(r).where(r.property("since").eq(yearOfTheDog)).returning(this.dogName).build(); var stmt = Cypher.match(this.person) .returning(this.person.property("name").as("name"), Cypher.subqueryWith(yearOfTheDog).collect(inner).as("dogsOfTheYear")) .build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (person:`Person`) " + "RETURN person.name AS name, COLLECT {" + " WITH 2018 AS yearOfTheDog" + " MATCH (person)-[r:`HAS_DOG`]->(dog:`Dog`)" + " WHERE r.since = yearOfTheDog" + " RETURN dog.name " + "} AS dogsOfTheYear"); } @Test void inReturn() { var toy = Cypher.node("Toy").named("t"); var inner = Cypher.match(this.hasDog) .match(this.dog.relationshipTo(toy, "HAS_TOY")) .returning(toy.property("name")) .build(); var stmt = Cypher.match(this.person) .returning(this.person.property("name"), Cypher.collect(inner).as("toyNames")) .build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (person:`Person`) " + "RETURN person.name, " + "COLLECT {" + " MATCH (person)-[:`HAS_DOG`]->(dog:`Dog`)" + " MATCH (dog)-[:`HAS_TOY`]->(t:`Toy`)" + " RETURN t.name " + "} AS toyNames"); } @Test void collectInSet() { var inner = Cypher.match(this.hasDog).returning(this.dogName).build(); var stmt = Cypher.match(this.person) .where(this.person.property("name").eq(Cypher.literalOf("Peter"))) .set(this.person.property("dogNames").to(Cypher.collect(inner))) .returning(this.person.property("dogNames").as("dogNames")) .build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (person:`Person`) WHERE person.name = 'Peter' " + "SET person.dogNames = COLLECT { MATCH (person)-[:`HAS_DOG`]->(dog:`Dog`) RETURN dog.name } " + "RETURN person.dogNames AS dogNames"); } @Test void inCase() { var inner = Cypher.match(this.hasDog).returning(this.dogName).build(); var stmt = Cypher.match(this.person) .returning(Cypher.caseExpression() .when(Cypher.collect(inner).eq(Cypher.listOf())) .then(Cypher.literalOf("No Dogs ").concat(this.personName)) .elseDefault(this.personName) .as("result")) .build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (person:`Person`) " + "RETURN " + "CASE " + "WHEN COLLECT { MATCH (person)-[:`HAS_DOG`]->(dog:`Dog`) RETURN dog.name } = [] THEN ('No Dogs ' + person.name) " + "ELSE person.name " + "END AS result"); } @Test void asGroupingKey() { var inner = Cypher.match(this.hasDog).returning(this.dogName).build(); var stmt = Cypher.match(this.person) .returning(Cypher.collect(inner).as("dogNames"), Cypher.avg(this.person.property("age")).as("averageAge")) .orderBy(Cypher.name("dogNames")) .build(); assertThat(stmt.getCypher()).isEqualTo("MATCH (person:`Person`) " + "RETURN COLLECT { MATCH (person)-[:`HAS_DOG`]->(dog:`Dog`) RETURN dog.name } AS dogNames," + " avg(person.age) AS averageAge " + "ORDER BY dogNames"); } } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/SymbolicNameTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons * */ public class SymbolicNameTests { @Nested class ResolvedSymbolicNames { @Test void equalsShouldWorkSameValue() { SymbolicName name1 = SymbolicName.of("a"); SymbolicName name2 = name1; SymbolicName name3 = SymbolicName.of("a"); assertThat(name1).isEqualTo(name2).isEqualTo(name3); } @Test void equalsShouldWorkDifferentValue() { SymbolicName name1 = SymbolicName.of("a"); SymbolicName name2 = SymbolicName.of("b"); assertThat(name1).isNotEqualTo(name2); } @Test void shouldNotEqualUnresolved() { SymbolicName name1 = SymbolicName.of("a"); assertThat(name1).isNotEqualTo(SymbolicName.unresolved()); } @Test void sameResolvedNamesShouldHaveSameHashCodes() { SymbolicName name1 = SymbolicName.of("a"); SymbolicName name2 = SymbolicName.of("a"); assertThat(name1).hasSameHashCodeAs(name2); } @Test void differentResolvedNamesShouldHaveDifferentHashCodes() { SymbolicName name1 = SymbolicName.of("a"); SymbolicName name2 = SymbolicName.of("b"); assertThat(name1.hashCode()).isNotEqualTo(name2.hashCode()); } @Test void toStringShouldWork() { SymbolicName name1 = SymbolicName.of("a"); assertThat(name1).hasToString("SymbolicName{cypher=a}"); } } @Nested class UnresolvedSymbolicNames { @Test void equalsShouldWorkSameValue() { SymbolicName name1 = SymbolicName.unresolved(); SymbolicName name2 = name1; SymbolicName name3 = SymbolicName.unresolved(); assertThat(name1).isEqualTo(name2).isNotEqualTo(name3); assertThat(name2).isNotEqualTo(name3); } @Test void shouldNotEqualResolved() { SymbolicName name1 = SymbolicName.unresolved(); assertThat(name1).isNotEqualTo(SymbolicName.of("a")); } @Test void differentUnresolvedNamesShouldHaveDifferentHashCodes() { SymbolicName name1 = SymbolicName.unresolved(); SymbolicName name2 = SymbolicName.unresolved(); assertThat(name1.hashCode()).isNotEqualTo(name2.hashCode()); } @Test void toStringShouldWork() { SymbolicName name1 = SymbolicName.unresolved(); assertThat(name1).hasToString("Unresolved SymbolicName"); } } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/TemporalLiteralTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalField; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * @author Michael J. Simons * */ class TemporalLiteralTests { @Test void localDateShouldWork() { TemporalLiteral literal = new TemporalLiteral(LocalDate.of(2021, 3, 10)); assertThat(literal.asString()).isEqualTo("date('2021-03-10')"); } @Test void localDateTimeShouldWork() { TemporalLiteral literal = new TemporalLiteral(LocalDateTime.of(2021, 3, 10, 15, 13, 0)); assertThat(literal.asString()).isEqualTo("localdatetime('2021-03-10T15:13:00')"); } @Test void zonedDateTimeShouldWork() { TemporalLiteral literal = new TemporalLiteral( ZonedDateTime.of(LocalDate.of(2021, 3, 10), LocalTime.of(15, 41, 0), ZoneId.of("Europe/Berlin"))); assertThat(literal.asString()).isEqualTo("datetime('2021-03-10T15:41:00+01:00[Europe/Berlin]')"); } @Test void localTimeShouldWork() { TemporalLiteral literal = new TemporalLiteral(LocalTime.of(15, 42)); assertThat(literal.asString()).isEqualTo("localtime('15:42:00')"); } @Test void offsetTimeShouldWork() { TemporalLiteral literal = new TemporalLiteral(OffsetTime.of(LocalTime.of(15, 15, 0), ZoneOffset.ofHours(2))); assertThat(literal.asString()).isEqualTo("time('15:15:00+02:00')"); } @Test void shouldThrowAnExceptionOnUnsupportedTemporalAccessor() { assertThatIllegalArgumentException().isThrownBy(() -> new TemporalLiteral(new TemporalAccessor() { @Override public boolean isSupported(TemporalField field) { return false; } @Override public long getLong(TemporalField field) { return 0; } })); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/TestUtils.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * @author Michael J. Simons * */ final class TestUtils { private TestUtils() { } static Method findMethod(Class clazz, String name, Class... paramTypes) { try { return clazz.getDeclaredMethod(name, paramTypes); } catch (NoSuchMethodException ex) { throw new RuntimeException(ex); } } static Object invokeMethod(Method method, Object target, Object... args) { try { return method.invoke(target, args); } catch (InvocationTargetException ex) { Throwable targetException = ex.getTargetException(); if (targetException instanceof RuntimeException) { throw ((RuntimeException) targetException); } else { throw new RuntimeException(targetException); } } catch (Exception ex) { throw new RuntimeException(ex); } } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/ToStringSmokeTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.neo4j.cypherdsl.core.ast.Visitable; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; /** * @author Michael J. Simons */ class ToStringSmokeTests { static Stream toStringShouldWork() { return Stream.of(Arguments.of(Cypher.node("Person").named("n"), "(n:Person)"), Arguments.of(Cypher.node("Person") .named("p") .relationshipTo(Cypher.node("Movie").named("m"), "PLAYED_IN") .named("r"), "(p:Person)-[r:PLAYED_IN]->(m:Movie)"), Arguments.of( Cypher.node("Person") .named("p") .relationshipTo(Cypher.node("Movie").named("m"), "PLAYED_IN") .named("r") .relationshipFrom(Cypher.node("Person").named("d"), "DIRECTED"), "(p:Person)-[r:PLAYED_IN]->(m:Movie)<-[:DIRECTED]-(d:Person)"), Arguments.of(Cypher.elementId(Cypher.anyNode("n")), "elementId(n)"), Arguments.of(Cypher.call("db.labels").asFunction(), "db.labels()"), Arguments.of(Cypher.literalOf("aString"), "'aString'"), Arguments.of(Cypher.literalOf(1), "1"), Arguments.of(Cypher.parameter("p"), "$p"), Arguments.of(Cypher.anyNode("n").property("prop"), "n.prop")); } @ParameterizedTest @MethodSource void toStringShouldWork(Visitable visitable, String expected) { assertThat(visitable).hasToString(visitable.getClass().getSimpleName() + "{cypher=" + expected + "}"); } @Test void mostConstructsShouldNotHaveDefaultToString() { var statement = IssueRelatedIT.createSomewhatComplexStatement(); statement.accept(segment -> { if (segment.getClass().getPackage().getName().equals("org.neo4j.cypherdsl.core.internal") || segment instanceof Statement) { return; } try { boolean hasOverwrittenToString = (segment.getClass() .getMethod("toString") .getDeclaringClass() != Object.class); assertThat(hasOverwrittenToString).isTrue(); assertThatNoException().isThrownBy(segment::toString); } catch (NoSuchMethodException ex) { throw new RuntimeException(ex); } }); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/TreeNodeTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.NoSuchElementException; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * @author Michael J. Simons */ class TreeNodeTests { private static final TreeNode ROOT; static { ROOT = TreeNode.root(10); var n2 = ROOT.append(2); ROOT.append(34); var n56 = ROOT.append(56); var n100 = ROOT.append(100); n2.append(77); n2.append(88); var n1 = n56.append(1); n1.append(23); n100.append(7); n100.append(8); n100.append(9); } @Test void breadthFirstSearchShouldWork() { var it = ROOT.breadthFirst(); assertThat(it).toIterable() .extracting(TreeNode::getValue) .containsExactly(10, 2, 34, 56, 100, 77, 88, 1, 7, 8, 9, 23); assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(it::next); } @Test void preOrderShouldWork() { var it = ROOT.preOrder(); assertThat(it).toIterable() .extracting(TreeNode::getValue) .containsExactly(10, 2, 77, 88, 34, 56, 1, 23, 100, 7, 8, 9); assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(it::next); } @Test void rootsBloodyRoots() { assertThat(ROOT.isRoot()).isTrue(); } @Test void printShouldWork() { var buffer = new StringBuilder(); ROOT.printTo(buffer::append, node -> node.getValue().toString()); assertThat(buffer).hasToString(""" 10 ├── 2 │ ├── 77 │ └── 88 ├── 34 ├── 56 │ └── 1 │ └── 23 └── 100 ├── 7 ├── 8 └── 9 """); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/UseIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; import org.neo4j.cypherdsl.core.StatementBuilder.OngoingReadingAndReturn; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Dialect; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * @author Michael J. Simons */ class UseIT { private final Renderer renderer = Renderer .getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_5).build()); private final OngoingReadingAndReturn ongoingInnerStatementDefinition = Cypher .call(Cypher.match(Cypher.node("Movie").named("movie")).returning("movie").build()) .returning("movie"); static Stream graphByNameShouldWork() { return Stream.of( Arguments.of(Cypher.literalOf("myComposite.myConstituent"), "USE graph.byName('myComposite.myConstituent') MATCH (n) RETURN n"), Arguments.of(Cypher.parameter("graphName"), "USE graph.byName($graphName) MATCH (n) RETURN n")); } @ParameterizedTest @CsvSource(textBlock = """ myDatabase, USE myDatabase MATCH (n) RETURN n myComposite.myConstituent, USE myComposite.myConstituent MATCH (n) RETURN n """) void simpleUseShouldWork(String target, String expected) { var statement = Cypher.match(Cypher.anyNode("n")).returning("n").build(); var cypher = Cypher.use(target, statement).getCypher(); assertThat(cypher).isEqualTo(expected); } @ParameterizedTest @MethodSource void graphByNameShouldWork(Expression target, String expected) { var statement = Cypher.match(Cypher.anyNode("n")).returning("n").build(); var cypher = ""; if (target instanceof StringLiteral literal) { cypher = Cypher.use(literal, statement).getCypher(); } else if (target instanceof Parameter parameter) { cypher = Cypher.use(parameter, statement).getCypher(); } else { Assertions.fail("Unexpected expression type in test."); } assertThat(cypher).isEqualTo(expected); } @Test void usageInSubqueryShouldWork() { var expected = """ UNWIND ['cineasts.latest', 'cineasts.upcoming'] AS graphName CALL (*) { USE graph.byName(graphName) MATCH (movie:`Movie`) RETURN movie } RETURN movie.title AS title """.lines() .map(String::trim) .collect(Collectors.joining(" ")) .replace("CALL (*) { ", "CALL (*) {") .replace(" }", "}"); var innerStatement = Cypher.match(Cypher.node("Movie").named("movie")).returning("movie").build(); var cypher = Cypher.unwind(Cypher.literalOf(List.of("cineasts.latest", "cineasts.upcoming"))) .as("graphName") .call(Cypher.use((Expression) Cypher.name("graphName"), innerStatement)) .returning(Cypher.name("movie").property("title").as("title")) .build() .getCypher(); assertThat(cypher).isEqualTo(expected); } @Test void useBeforeCall() { var innerStatement = this.ongoingInnerStatementDefinition.build(); var cypher = Cypher.use("cineasts.latest", innerStatement).getCypher(); assertThat(cypher).isEqualTo("USE cineasts.latest CALL () {MATCH (movie:`Movie`) RETURN movie} RETURN movie"); } @Test void nestedUseShouldThrow() { var innerStatement = Cypher.use("x", this.ongoingInnerStatementDefinition.build()); assertThatIllegalArgumentException().isThrownBy(() -> Cypher.use("y", innerStatement)) .withMessage("Nested USE clauses are not supported"); } @Test void useBeforeCallWithCallInTx() { var title = Cypher.name("title"); var movie = Cypher.node("Movie", Cypher.mapOf("title", title)).named("m"); var innerStatement = Cypher.unwind(Cypher.parameter("newMovies")) .as(title) .call(Cypher.with(title).merge(movie).returning(movie.elementId().as("id")).build()) .returning("id") .build(); var cypher = this.renderer.render(Cypher.use("cineasts.latest", innerStatement)); assertThat(cypher).isEqualTo( "USE cineasts.latest UNWIND $newMovies AS title CALL {WITH title MERGE (m:`Movie` {title: title}) RETURN elementId(m) AS id} RETURN id"); } @Test void addExplain() { var innerStatement = this.ongoingInnerStatementDefinition.build(); var cypher = Cypher.use("cineasts.latest", innerStatement).explain().getCypher(); assertThat(cypher) .isEqualTo("EXPLAIN USE cineasts.latest CALL () {MATCH (movie:`Movie`) RETURN movie} RETURN movie"); } @Test void innerProfileShouldThrow() { var innerStatement = this.ongoingInnerStatementDefinition.profile(); assertThatIllegalArgumentException().isThrownBy(() -> Cypher.use("x", innerStatement)) .withMessage("PROFILE'd statements are not supported inside USE clauses"); } @Test void innerExplainShouldThrow() { var innerStatement = this.ongoingInnerStatementDefinition.explain(); assertThatIllegalArgumentException().isThrownBy(() -> Cypher.use("x", innerStatement)) .withMessage("EXPLAIN'ed statements are not supported inside USE clauses"); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/internal/LoadCSVTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import java.net.URI; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class LoadCSVTests { @Test void witherShouldWorkWithNullsOnBothEnds() { LoadCSV a = new LoadCSV(URI.create("file:///f.csv"), true, "a"); LoadCSV b = a.withFieldTerminator(null); assertThat(a).isSameAs(b); } @Test void witherShouldWorkWithEmptyValues() { LoadCSV a = new LoadCSV(URI.create("file:///f.csv"), true, "a"); LoadCSV b = a.withFieldTerminator(" \t"); assertThat(a).isSameAs(b); assertThat(a.getFieldTerminator()).isNull(); } @Test void witherShouldWorkWithNonEmptyValues() { LoadCSV a = new LoadCSV(URI.create("file:///f.csv"), true, "a"); LoadCSV b = a.withFieldTerminator(";"); assertThat(a).isNotSameAs(b); assertThat(a.getFieldTerminator()).isNull(); assertThat(b.getFieldTerminator()).isEqualTo(";"); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/internal/ReflectiveVisitorTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.internal; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.ast.Visitable; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * @author Michael J. Simons */ class ReflectiveVisitorTests { @Test void visitorShouldThrowDedicatedException() { SomeException cause = new SomeException(); ReflectiveVisitor visitor = new ReflectiveVisitor() { @Override protected boolean preEnter(Visitable visitable) { return true; } @Override protected void postLeave(Visitable visitable) { } @SuppressWarnings("unused") void enter(ThrowingVisitable throwingVisitable) { throw cause; } }; Visitable v = new ThrowingVisitable(); assertThatExceptionOfType(HandlerException.class).isThrownBy(() -> visitor.enter(v)) .withRootCauseInstanceOf(SomeException.class); } static class ThrowingVisitable implements Visitable { } static class SomeException extends RuntimeException { private static final long serialVersionUID = -4170504879699181855L; } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/querydsl/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.querydsl; import com.querydsl.core.annotations.QueryEntity; /** * @author Rickard Öberg */ @QueryEntity public class Person { String firstName; String lastName; int age; public String getFirstName() { return this.firstName; } public int getAge() { return this.age; } public String getLastName() { return this.lastName; } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/querydsl/Place.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.querydsl; import com.querydsl.core.annotations.QueryEntity; /** * @author Rickard Öberg */ @QueryEntity public class Place { String name; public String getName() { return this.name; } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/querydsl/QueryDSLAdapterTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.querydsl; import java.time.LocalDate; import java.time.OffsetTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.stream.Stream; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.Ops; import com.querydsl.core.types.Path; import com.querydsl.core.types.Predicate; import com.querydsl.core.types.dsl.Expressions; import com.querydsl.core.types.dsl.Param; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.neo4j.cypherdsl.core.Condition; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.Literal.UnsupportedLiteralException; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Statement; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Renderer; import static com.querydsl.core.alias.Alias.$; import static com.querydsl.core.alias.Alias.alias; import static com.querydsl.core.types.dsl.Expressions.asNumber; import static com.querydsl.core.types.dsl.Expressions.asString; import static com.querydsl.core.types.dsl.Expressions.booleanOperation; import static com.querydsl.core.types.dsl.Expressions.constant; import static com.querydsl.core.types.dsl.Expressions.dateOperation; import static com.querydsl.core.types.dsl.Expressions.numberOperation; import static com.querydsl.core.types.dsl.Expressions.predicate; import static com.querydsl.core.types.dsl.Expressions.stringOperation; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * This is an integration test looking at two things: *
    *
  1. The templates we defined for QueryDSL: They must resolve to semantically correct * Cypher.
  2. *
  3. Whether the {@link ToCypherFormatStringVisitor} actually creates syntactically * fragments.
  4. *
* As we deal with a lot of operators from QueryDSL, it's more economical to test these * two things together. An approach testing the templates in isolation would require * rendering them ("converting" to speak in terms of QueryDSL) as well, pretty much * repeating what we are doing in the {@link ToCypherFormatStringVisitor} again. * * @author Michael J. Simons * */ class QueryDSLAdapterTests { static Map expected(Object... args) { Map result = new HashMap<>(); for (int i = 0; i < args.length; i++) { result.put(String.format("pcdsl%02d", i + 1), args[i]); } return result; } static Stream supportedOpsArgs() { Stream.Builder r = Stream.builder(); //@formatter:off r.add(Arguments.arguments("true = true AND false = true", Expressions.TRUE.isTrue().and(Expressions.FALSE.isTrue()), Collections.emptyMap())); r.add(Arguments.arguments("NOT true", Expressions.TRUE.not(), Collections.emptyMap())); r.add(Arguments.arguments("true = true OR false = true", Expressions.TRUE.isTrue().or(Expressions.FALSE.isTrue()), Collections.emptyMap())); r.add(Arguments.arguments("NOT (true XOR false)", booleanOperation(Ops.XNOR, Expressions.TRUE, Expressions.FALSE), Collections.emptyMap())); r.add(Arguments.arguments("true XOR false", booleanOperation(Ops.XOR, Expressions.TRUE, Expressions.FALSE), Collections.emptyMap())); r.add(Arguments.arguments("size($pcdsl01) = 0", booleanOperation(Ops.COL_IS_EMPTY, constant(Arrays.asList("x"))), expected(Collections.singletonList("x")))); r.add(Arguments.arguments("size($pcdsl01) > $pcdsl02", numberOperation(Integer.class, Ops.COL_SIZE, constant(Arrays.asList("x"))).gt(1), expected(Collections.singletonList("x"), 1))); r.add(Arguments.arguments("size($pcdsl01) > $pcdsl02", numberOperation(Integer.class, Ops.COL_SIZE, constant(new String[]{"x"})).gt(1), expected(new String[]{"x"}, 1))); Map aMap = new HashMap<>(); aMap.put("1", "a"); aMap.put("2", "b"); r.add(Arguments.arguments("size(keys($pcdsl01)) > $pcdsl02", numberOperation(Integer.class, Ops.MAP_SIZE, constant(aMap)).gt(1), expected(aMap, 1))); r.add(Arguments.arguments("size(keys($pcdsl01)) = 0", booleanOperation(Ops.MAP_IS_EMPTY, constant(aMap)), expected(aMap))); r.add(Arguments.arguments("any(v in keys($pcdsl01) where v = $pcdsl02)", booleanOperation(Ops.CONTAINS_KEY, constant(aMap), asString("1")), expected(aMap, "1"))); r.add(Arguments.arguments("any(v in [k IN KEYS($pcdsl01) | $pcdsl01[k]] where v = $pcdsl02)", booleanOperation(Ops.CONTAINS_VALUE, constant(aMap), asString("b")), expected(aMap, "b"))); r.add(Arguments.arguments("size($pcdsl01 + $pcdsl02) = $pcdsl03", stringOperation(Ops.CONCAT, asString("a"), asString("b")).length().eq(2), expected("a", "b", 2))); r.add(Arguments.arguments("toLower($pcdsl01) = $pcdsl02", stringOperation(Ops.LOWER, asString("A")).eq(asString("a")), expected("A", "a"))); r.add(Arguments.arguments("substring($pcdsl01, 1) = $pcdsl02", stringOperation(Ops.SUBSTR_1ARG, asString("1234"), Expressions.ONE).eq(asString("234")), expected("1234", "234"))); r.add(Arguments.arguments("substring($pcdsl01, 1, 2) = $pcdsl02", stringOperation(Ops.SUBSTR_2ARGS, asString("1234"), Expressions.ONE, Expressions.TWO).eq(asString("23")), expected("1234", "23"))); r.add(Arguments.arguments("trim($pcdsl01) = $pcdsl02", stringOperation(Ops.TRIM, asString(" A ")).eq(asString("A")), expected(" A ", "A"))); r.add(Arguments.arguments("toUpper($pcdsl01) = $pcdsl02", stringOperation(Ops.UPPER, asString("a")).eq(asString("A")), expected("a", "A"))); r.add(Arguments.arguments("$pcdsl01 =~ $pcdsl02", booleanOperation(Ops.MATCHES, asString("a"), asString("A")), expected("a", "A"))); r.add(Arguments.arguments("$pcdsl01 =~ ('(?i)' + $pcdsl02)", booleanOperation(Ops.MATCHES_IC, asString("a"), asString("A")), expected("a", "A"))); r.add(Arguments.arguments("$pcdsl01 =~ ('(?i)' + $pcdsl02)", booleanOperation(Ops.MATCHES_IC, asString("a"), asString("A")), expected("a", "A"))); r.add(Arguments.arguments("$pcdsl01 STARTS WITH $pcdsl02", booleanOperation(Ops.STARTS_WITH, asString("ABC"), asString("a")), expected("ABC", "a"))); r.add(Arguments.arguments("toLower($pcdsl01) STARTS WITH toLower($pcdsl02)", booleanOperation(Ops.STARTS_WITH_IC, asString("ABC"), asString("a")), expected("ABC", "a"))); r.add(Arguments.arguments("$pcdsl01 ENDS WITH $pcdsl02", booleanOperation(Ops.ENDS_WITH, asString("ABC"), asString("c")), expected("ABC", "c"))); r.add(Arguments.arguments("toLower($pcdsl01) ENDS WITH toLower($pcdsl02)", booleanOperation(Ops.ENDS_WITH_IC, asString("ABC"), asString("c")), expected("ABC", "c"))); r.add(Arguments.arguments("$pcdsl01 CONTAINS $pcdsl02", booleanOperation(Ops.STRING_CONTAINS, asString("ABC"), asString("c")), expected("ABC", "c"))); r.add(Arguments.arguments("toLower($pcdsl01) CONTAINS toLower($pcdsl02)", booleanOperation(Ops.STRING_CONTAINS_IC, asString("ABC"), asString("c")), expected("ABC", "c"))); r.add(Arguments.arguments("substring($pcdsl01, 2, 1) = $pcdsl02", Expressions.operation(Character.class, Ops.CHAR_AT, asString("1234"), Expressions.TWO).eq('3'), expected("1234", '3'))); r.add(Arguments.arguments("size($pcdsl01) = $pcdsl02", asString("ABC").length().eq(3), expected("ABC", 3))); r.add(Arguments.arguments("$pcdsl01 =~ '.*' + $pcdsl02 + '.*'", booleanOperation(Ops.LIKE, asString("ABC"), asString("a")), expected("ABC", "a"))); r.add(Arguments.arguments("$pcdsl01 =~ '(?i).*' + $pcdsl02 + '.*'", booleanOperation(Ops.LIKE_IC, asString("ABC"), asString("a")), expected("ABC", "a"))); r.add(Arguments.arguments("size(left($pcdsl01, $pcdsl02)) = $pcdsl02", stringOperation(Ops.StringOps.LEFT, asString("ABCD"), asNumber(3)).length().eq(3), expected("ABCD", 3))); r.add(Arguments.arguments("size(right($pcdsl01, $pcdsl02)) = $pcdsl02", stringOperation(Ops.StringOps.RIGHT, asString("ABCD"), asNumber(3)).length().eq(3), expected("ABCD", 3))); r.add(Arguments.arguments("size(ltrim($pcdsl01)) = $pcdsl02", stringOperation(Ops.StringOps.LTRIM, asString(" ABCD")).length().eq(4), expected(" ABCD", 4))); r.add(Arguments.arguments("size(rtrim($pcdsl01)) = $pcdsl02", stringOperation(Ops.StringOps.RTRIM, asString("ABCD ")).length().eq(4), expected("ABCD ", 4))); ZonedDateTime t = ZonedDateTime.of(2021, 3, 10, 15, 50, 0, 0, ZoneId.of("Europe/Berlin")); r.add(Arguments.arguments("datetime() >= $pcdsl01", dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE).goe(t), expected(t))); LocalDate l = LocalDate.of(2021, 3, 10); r.add(Arguments.arguments("date() >= $pcdsl01", dateOperation(LocalDate.class, Ops.DateTimeOps.CURRENT_DATE).goe(l), expected(l))); OffsetTime ot = OffsetTime.of(15, 53, 0, 0, ZoneOffset.UTC); r.add(Arguments.arguments("time() >= $pcdsl01", dateOperation(OffsetTime.class, Ops.DateTimeOps.CURRENT_TIME).goe(ot), expected(ot))); r.add(Arguments.arguments("datetime().epochmillis >= $pcdsl01", dateOperation(Long.class, Ops.DateTimeOps.CURRENT_TIMESTAMP).goe(23L), expected(23L))); r.add(Arguments.arguments("date($pcdsl01) >= $pcdsl02", dateOperation(LocalDate.class, Ops.DateTimeOps.DATE, Expressions.asString("2021-03-10")).goe(l), expected("2021-03-10", l))); r.add(Arguments.arguments("datetime().millisecond >= $pcdsl01", dateOperation(Integer.class, Ops.DateTimeOps.MILLISECOND, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).goe(23), expected(23))); r.add(Arguments.arguments("datetime().second >= $pcdsl01", dateOperation(Integer.class, Ops.DateTimeOps.SECOND, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).goe(23), expected(23))); r.add(Arguments.arguments("datetime().minute >= $pcdsl01", dateOperation(Integer.class, Ops.DateTimeOps.MINUTE, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).goe(23), expected(23))); r.add(Arguments.arguments("datetime().hour >= $pcdsl01", dateOperation(Integer.class, Ops.DateTimeOps.HOUR, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).goe(23), expected(23))); r.add(Arguments.arguments("datetime().week >= $pcdsl01", dateOperation(Integer.class, Ops.DateTimeOps.WEEK, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).goe(23), expected(23))); r.add(Arguments.arguments("datetime().month >= $pcdsl01", dateOperation(Integer.class, Ops.DateTimeOps.MONTH, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).goe(23), expected(23))); r.add(Arguments.arguments("datetime().year >= $pcdsl01", dateOperation(Integer.class, Ops.DateTimeOps.YEAR, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).goe(23), expected(23))); r.add(Arguments.arguments("datetime().weekYear >= $pcdsl01", dateOperation(Integer.class, Ops.DateTimeOps.YEAR_WEEK, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).goe(23), expected(23))); r.add(Arguments.arguments("datetime().dayOfWeek >= $pcdsl01", dateOperation(Integer.class, Ops.DateTimeOps.DAY_OF_WEEK, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).goe(23), expected(23))); t = ZonedDateTime.of(2021, 3, 10, 16, 48, 0, 0, ZoneId.of("Europe/Berlin")); r.add(Arguments.arguments("datetime() + duration({years: $pcdsl01}) >= $pcdsl02", dateOperation(ZonedDateTime.class, Ops.DateTimeOps.ADD_YEARS, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE), asNumber(23)).goe(t), expected(23, t))); r.add(Arguments.arguments("datetime() + duration({months: $pcdsl01}) >= $pcdsl02", dateOperation(ZonedDateTime.class, Ops.DateTimeOps.ADD_MONTHS, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE), asNumber(23)).goe(t), expected(23, t))); r.add(Arguments.arguments("datetime() + duration({weeks: $pcdsl01}) >= $pcdsl02", dateOperation(ZonedDateTime.class, Ops.DateTimeOps.ADD_WEEKS, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE), asNumber(23)).goe(t), expected(23, t))); r.add(Arguments.arguments("datetime() + duration({days: $pcdsl01}) >= $pcdsl02", dateOperation(ZonedDateTime.class, Ops.DateTimeOps.ADD_DAYS, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE), asNumber(23)).goe(t), expected(23, t))); r.add(Arguments.arguments("datetime() + duration({hours: $pcdsl01}) >= $pcdsl02", dateOperation(ZonedDateTime.class, Ops.DateTimeOps.ADD_HOURS, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE), asNumber(23)).goe(t), expected(23, t))); r.add(Arguments.arguments("datetime() + duration({minutes: $pcdsl01}) >= $pcdsl02", dateOperation(ZonedDateTime.class, Ops.DateTimeOps.ADD_MINUTES, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE), asNumber(23)).goe(t), expected(23, t))); r.add(Arguments.arguments("datetime() + duration({seconds: $pcdsl01}) >= $pcdsl02", dateOperation(ZonedDateTime.class, Ops.DateTimeOps.ADD_SECONDS, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE), asNumber(23)).goe(t), expected(23, t))); r.add(Arguments.arguments("duration.between(datetime(), datetime()).years = $pcdsl01", dateOperation(Integer.class, Ops.DateTimeOps.DIFF_YEARS, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE), dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).eq(0), expected(0))); r.add(Arguments.arguments("duration.between(datetime(), datetime()).months = $pcdsl01", dateOperation(Integer.class, Ops.DateTimeOps.DIFF_MONTHS, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE), dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).eq(0), expected(0))); r.add(Arguments.arguments("duration.between(datetime(), datetime()).weeks = $pcdsl01", dateOperation(Integer.class, Ops.DateTimeOps.DIFF_WEEKS, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE), dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).eq(0), expected(0))); r.add(Arguments.arguments("duration.between(datetime(), datetime()).days = $pcdsl01", dateOperation(Integer.class, Ops.DateTimeOps.DIFF_DAYS, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE), dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).eq(0), expected(0))); r.add(Arguments.arguments("duration.between(datetime(), datetime()).hours = $pcdsl01", dateOperation(Integer.class, Ops.DateTimeOps.DIFF_HOURS, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE), dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).eq(0), expected(0))); r.add(Arguments.arguments("duration.between(datetime(), datetime()).minutes = $pcdsl01", dateOperation(Integer.class, Ops.DateTimeOps.DIFF_MINUTES, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE), dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).eq(0), expected(0))); r.add(Arguments.arguments("duration.between(datetime(), datetime()).seconds = $pcdsl01", dateOperation(Integer.class, Ops.DateTimeOps.DIFF_SECONDS, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE), dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).eq(0), expected(0))); t = ZonedDateTime.of(2021, 3, 10, 17, 02, 0, 0, ZoneId.of("Europe/Berlin")); r.add(Arguments.arguments("date.truncate('year', datetime()) >= $pcdsl01", dateOperation(ZonedDateTime.class, Ops.DateTimeOps.TRUNC_YEAR, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).goe(t), expected(t))); r.add(Arguments.arguments("date.truncate('month', datetime()) >= $pcdsl01", dateOperation(ZonedDateTime.class, Ops.DateTimeOps.TRUNC_MONTH, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).goe(t), expected(t))); r.add(Arguments.arguments("date.truncate('week', datetime()) >= $pcdsl01", dateOperation(ZonedDateTime.class, Ops.DateTimeOps.TRUNC_WEEK, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).goe(t), expected(t))); r.add(Arguments.arguments("date.truncate('day', datetime()) >= $pcdsl01", dateOperation(ZonedDateTime.class, Ops.DateTimeOps.TRUNC_DAY, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).goe(t), expected(t))); r.add(Arguments.arguments("datetime.truncate('hour', datetime()) >= $pcdsl01", dateOperation(ZonedDateTime.class, Ops.DateTimeOps.TRUNC_HOUR, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).goe(t), expected(t))); r.add(Arguments.arguments("datetime.truncate('minute', datetime()) >= $pcdsl01", dateOperation(ZonedDateTime.class, Ops.DateTimeOps.TRUNC_MINUTE, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).goe(t), expected(t))); r.add(Arguments.arguments("datetime.truncate('second', datetime()) >= $pcdsl01", dateOperation(ZonedDateTime.class, Ops.DateTimeOps.TRUNC_SECOND, dateOperation(ZonedDateTime.class, Ops.DateTimeOps.SYSDATE)).goe(t), expected(t))); r.add(Arguments.arguments("abs($pcdsl01) > $pcdsl02", numberOperation(Double.class, Ops.MathOps.ABS, asNumber(1)).gt(1), expected(1, 1.0))); r.add(Arguments.arguments("acos($pcdsl01) > $pcdsl02", numberOperation(Double.class, Ops.MathOps.ACOS, asNumber(1)).gt(1), expected(1, 1.0))); r.add(Arguments.arguments("asin($pcdsl01) > $pcdsl02", numberOperation(Double.class, Ops.MathOps.ASIN, asNumber(1)).gt(1), expected(1, 1.0))); r.add(Arguments.arguments("atan($pcdsl01) > $pcdsl02", numberOperation(Double.class, Ops.MathOps.ATAN, asNumber(1)).gt(1), expected(1, 1.0))); r.add(Arguments.arguments("ceil($pcdsl01) > $pcdsl02", numberOperation(Double.class, Ops.MathOps.CEIL, asNumber(1)).gt(1), expected(1, 1.0))); r.add(Arguments.arguments("cos($pcdsl01) > $pcdsl02", numberOperation(Double.class, Ops.MathOps.COS, asNumber(1)).gt(1), expected(1, 1.0))); r.add(Arguments.arguments("cot($pcdsl01) > $pcdsl02", numberOperation(Double.class, Ops.MathOps.COT, asNumber(1)).gt(1), expected(1, 1.0))); r.add(Arguments.arguments("degrees($pcdsl01) > $pcdsl02", numberOperation(Double.class, Ops.MathOps.DEG, asNumber(1)).gt(1), expected(1, 1.0))); r.add(Arguments.arguments("tan($pcdsl01) > $pcdsl02", numberOperation(Double.class, Ops.MathOps.TAN, asNumber(1)).gt(1), expected(1, 1.0))); r.add(Arguments.arguments("sqrt($pcdsl01) > $pcdsl02", numberOperation(Double.class, Ops.MathOps.SQRT, asNumber(4)).gt(1), expected(4, 1.0))); r.add(Arguments.arguments("sign($pcdsl01) > $pcdsl02", numberOperation(Double.class, Ops.MathOps.SIGN, asNumber(1)).gt(1), expected(1, 1.0))); r.add(Arguments.arguments("sin($pcdsl01) > $pcdsl02", numberOperation(Double.class, Ops.MathOps.SIN, asNumber(1)).gt(1), expected(1, 1.0))); r.add(Arguments.arguments("round($pcdsl01) > $pcdsl02", numberOperation(Double.class, Ops.MathOps.ROUND, asNumber(1)).gt(1), expected(1, 1.0))); r.add(Arguments.arguments("round($pcdsl01, $pcdsl01) > $pcdsl02", numberOperation(Double.class, Ops.MathOps.ROUND2, asNumber(1), asNumber(1)).gt(1), expected(1, 1.0))); r.add(Arguments.arguments("radians($pcdsl01) > $pcdsl02", numberOperation(Double.class, Ops.MathOps.RAD, asNumber(1)).gt(1), expected(1, 1.0))); r.add(Arguments.arguments("CASE WHEN $pcdsl01 < $pcdsl02 THEN $pcdsl01 ELSE $pcdsl02 END > $pcdsl03", numberOperation(Double.class, Ops.MathOps.MIN, asNumber(1), asNumber(2)).gt(1), expected(1, 2, 1.0))); r.add(Arguments.arguments("CASE WHEN $pcdsl01 > $pcdsl02 THEN $pcdsl01 ELSE $pcdsl02 END > $pcdsl03", numberOperation(Double.class, Ops.MathOps.MAX, asNumber(1), asNumber(2)).gt(1), expected(1, 2, 1.0))); r.add(Arguments.arguments("floor($pcdsl01) > $pcdsl02", numberOperation(Double.class, Ops.MathOps.FLOOR, asNumber(1.1)).gt(1), expected(1.1, 1.0))); r.add(Arguments.arguments("exp($pcdsl01) > $pcdsl02", numberOperation(Double.class, Ops.MathOps.EXP, asNumber(1)).gt(1), expected(1, 1.0))); Predicate p = Expressions.cases().when(Expressions.TRUE).then(Expressions.asNumber(1)) .when(Expressions.FALSE).then(Expressions.asNumber(2)) .otherwise(Expressions.asNumber(3)).gt(3); r.add(Arguments.arguments("(CASE WHEN true THEN $pcdsl01 WHEN false THEN $pcdsl02 ELSE $pcdsl03 END) > $pcdsl03", p, expected(1, 2, 3))); //@formatter:on return r.build(); } static Stream unsupportedOpsShouldBeRecognizedBeforeHandArgs() { Stream.Builder r = Stream.builder(); r.add(Arguments.arguments(numberOperation(Integer.class, Ops.INDEX_OF, asString("ABC"), asString("B")).eq(1))); r.add(Arguments.arguments( numberOperation(Integer.class, Ops.INDEX_OF_2ARGS, asString("ABC"), asString("B"), asNumber(3)).eq(1))); return r.build(); } @MethodSource("supportedOpsArgs") @ParameterizedTest(name = "{index} {0}") void supportedOpsWithParameters(String expectedFragment, Predicate predicate, Map expectedParameters) { Statement statement = Cypher.with(Cypher.literalOf(1).as("e")) .where(Cypher.adapt(predicate).asCondition()) .returning("e") .build(); statement.setRenderConstantsAsParameters(true); assertThat(statement.getCatalog().getParameters()).containsExactlyEntriesOf(expectedParameters); assertThat(statement.getCypher()).isEqualTo("WITH 1 AS e WHERE " + expectedFragment + " RETURN e"); } @MethodSource("supportedOpsArgs") @ParameterizedTest(name = "{index} {0}") void supportedOpsWithLiterals(String expectedFragment, Predicate predicate, Map expectedParameters) { Statement statement = Cypher.with(Cypher.literalOf(1).as("e")) .where(Cypher.adapt(predicate).asCondition()) .returning("e") .build(); String expectedString = "WITH 1 AS e WHERE " + expectedFragment + " RETURN e"; Map finalExpectedParameters = new HashMap<>(); for (Map.Entry entry : expectedParameters.entrySet()) { String k = entry.getKey(); Object v = entry.getValue(); try { String replacement = Cypher.literalOf(v).asString(); expectedString = expectedString.replaceAll(java.util.regex.Pattern.quote("$" + k), replacement); } catch (UnsupportedLiteralException ex) { finalExpectedParameters.put(k, v); } } assertThat(statement.getCatalog().getParameters()).containsExactlyEntriesOf(finalExpectedParameters); assertThat(statement.getCypher()).isEqualTo(expectedString); } @MethodSource("unsupportedOpsShouldBeRecognizedBeforeHandArgs") @ParameterizedTest void unsupportedOpsShouldBeRecognizedBeforeHand(Predicate predicate) { Assertions.assertThatIllegalArgumentException().isThrownBy(() -> Cypher.adapt(predicate).asCondition()); } @Test void queryingByPathBasedOnClassShouldWork() { Path person = Expressions.path(Person.class, "n"); Path personFirstName = Expressions.path(String.class, person, "firstName"); Path personAge = Expressions.path(Integer.class, person, "age"); BooleanBuilder expr = new BooleanBuilder(predicate(Ops.EQ, personFirstName, constant("P"))) .and(predicate(Ops.GT, personAge, constant(25))); Statement statement = Cypher.match(Cypher.adapt(person).asNode()) .where(Cypher.adapt(expr).asCondition()) .returning(Cypher.adapt(person).asName()) .build(); assertThat(statement.getCatalog().getParameters()).isEmpty(); assertThat(statement.getCypher()) .isEqualTo("MATCH (n:`Person`) WHERE n.firstName = 'P' AND n.age > 25 RETURN n"); } @Test void queryingByProxyShouldWork() { Person person = alias(Person.class, "n"); Predicate p = $(person.getFirstName()).eq("P").and($(person.getAge()).gt(25)); Statement statement = Cypher.match(Cypher.node("Person").named("n")) .where(Cypher.adapt(p).asCondition()) .returning(Cypher.name(person.toString())) .build(); assertThat(statement.getCatalog().getParameters()).isEmpty(); assertThat(statement.getCypher()) .isEqualTo("MATCH (n:`Person`) WHERE n.firstName = 'P' AND n.age > 25 RETURN n"); } @Test void queryingByQClassShouldWork() { QPerson person = QPerson.person; Predicate p = person.firstName.eq("P").and(person.age.gt(25)); Statement statement = Cypher.match(Cypher.adapt(person).asNode()) .where(Cypher.adapt(p).asCondition()) .returning(Cypher.adapt(person).asName()) .build(); assertThat(statement.getCatalog().getParameters()).isEmpty(); assertThat(statement.getCypher()) .isEqualTo("MATCH (person:`Person`) WHERE person.firstName = 'P' AND person.age > 25 RETURN person"); } @Test void pathsShouldBeTurnedIntoExpressions() { QPerson person = QPerson.person; Statement statement = Cypher.match(Cypher.adapt(person).asNode()) .where(Cypher.adapt(person.firstName.eq("Rickard")).asCondition()) .returning(Cypher.adapt(person.firstName).asExpression()) .orderBy(Cypher.adapt(person.firstName).asExpression().descending()) .build(); assertThat(statement.getCatalog().getParameters()).isEmpty(); assertThat(statement.getCypher()).isEqualTo( "MATCH (person:`Person`) WHERE person.firstName = 'Rickard' RETURN person.firstName ORDER BY person.firstName DESC"); } @Test void qClassToNodeShouldWork() { // tag::query-dsl-simple[] QPerson n = new QPerson("n"); // <.> Statement statement = Cypher.match(Cypher.adapt(n).asNode()) // <.> .where(Cypher.adapt(n.firstName.eq("P").and(n.age.gt(25))).asCondition()) // <.> .returning(Cypher.adapt(n).asName()) // <.> .build(); assertThat(statement.getCatalog().getParameters()).isEmpty(); assertThat(statement.getCypher()) .isEqualTo("MATCH (n:`Person`) WHERE n.firstName = 'P' AND n.age > 25 RETURN n"); // end::query-dsl-simple[] } @Test void qClassToNodeShouldWorkNoConstants() { // tag::query-dsl-simple-avoid-constants[] QPerson n = new QPerson("n"); Statement statement = Cypher.match(Cypher.adapt(n).asNode()) .where(Cypher.adapt(n.firstName.eq("P").and(n.age.gt(25))).asCondition()) .returning(Cypher.adapt(n).asName()) .build(); statement.setRenderConstantsAsParameters(true); // <.> assertThat(statement.getCatalog().getParameters()).containsEntry("pcdsl01", "P"); // <.> assertThat(statement.getCatalog().getParameters()).containsEntry("pcdsl02", 25); assertThat(statement.getCypher()) .isEqualTo("MATCH (n:`Person`) WHERE n.firstName = $pcdsl01 AND n.age > $pcdsl02 RETURN n"); // <.> // end::query-dsl-simple-avoid-constants[] } @Test void changingModeShouldWork() { QPerson n = new QPerson("n"); Statement statement = Cypher.match(Cypher.adapt(n).asNode()) .where(Cypher.adapt(n.firstName.eq("P").and(n.age.gt(25))).asCondition()) .returning(Cypher.adapt(n).asName()) .build(); statement.setRenderConstantsAsParameters(true); // <.> assertThat(statement.getCatalog().getParameters()).containsEntry("pcdsl01", "P"); // <.> assertThat(statement.getCatalog().getParameters()).containsEntry("pcdsl02", 25); assertThat(statement.getCypher()) .isEqualTo("MATCH (n:`Person`) WHERE n.firstName = $pcdsl01 AND n.age > $pcdsl02 RETURN n"); // <.> statement.setRenderConstantsAsParameters(false); assertThat(statement.getCatalog().getParameters()).isEmpty(); assertThat(statement.getCypher()) .isEqualTo("MATCH (n:`Person`) WHERE n.firstName = 'P' AND n.age > 25 RETURN n"); } @Test void prettyPrinterShouldAlwaysUseConstants() { QPerson n = new QPerson("n"); Statement statement = Cypher.match(Cypher.adapt(n).asNode()) .where(Cypher.adapt(n.firstName.eq("P").and(n.age.gt(25))).asCondition()) .returning(Cypher.adapt(n).asName()) .build(); statement.setRenderConstantsAsParameters(true); assertThat(statement.getCatalog().getParameters()).containsEntry("pcdsl01", "P"); assertThat(statement.getCatalog().getParameters()).containsEntry("pcdsl02", 25); assertThat(Renderer.getRenderer(Configuration.prettyPrinting()).render(statement)).isEqualTo(""" MATCH (n:Person) WHERE n.firstName = 'P' AND n.age > 25 RETURN n"""); } @Test void parametersShouldBeRendered() { // tag::query-dsl-parameters[] QPerson n = new QPerson("n"); Statement statement = Cypher.match(Cypher.adapt(n).asNode()) .where(Cypher.adapt( n.firstName.eq(new Param<>(String.class, "name")).and(n.age.gt(new Param<>(Integer.class, "age"))) // <.> ).asCondition()) .returning(Cypher.adapt(n).asName()) .build(); assertThat(statement.getCatalog().getParameterNames()).hasSize(2); // <.> assertThat(statement.getCypher()) .isEqualTo("MATCH (n:`Person`) WHERE n.firstName = $name AND n.age > $age RETURN n"); // end::query-dsl-parameters[] } @Test void regexShouldBeSupported() { QPerson n = new QPerson("n"); Statement statement = Cypher.match(Cypher.adapt(n).asNode()) .where(Cypher.adapt(n.firstName.matches("(?i).*rick.*")).asCondition()) .returning(Cypher.adapt(n).asName()) .build(); assertThat(statement.getCatalog().getParameterNames()).isEmpty(); assertThat(statement.getCypher()).isEqualTo("MATCH (n:`Person`) WHERE n.firstName =~ '(?i).*rick.*' RETURN n"); } @Test void existShouldWork() { QPerson n = new QPerson("n"); Statement statement = Cypher.match(Cypher.adapt(n).asNode()) .where(Cypher.adapt(Expressions.predicate(Ops.EXISTS, n.firstName)).asCondition()) .returning(Cypher.adapt(n).asName()) .build(); assertThat(statement.getCatalog().getParameterNames()).isEmpty(); assertThat(statement.getCypher()).isEqualTo("MATCH (n:`Person`) WHERE exists(n.firstName) RETURN n"); } @Test void shouldNotAdaptArbitraryThingsAsCondition() { assertThatIllegalArgumentException().isThrownBy(() -> Cypher.adapt(Expressions.TWO).asCondition()) .withMessage("Only Query-DSL predicates can be turned into Cypher-DSL's predicates."); } @Test void shouldNotAdaptArbitraryThingsAsNodes() { assertThatIllegalArgumentException().isThrownBy(() -> Cypher.adapt(Expressions.TWO).asNode()) .withMessage("Only Query-DSL paths can be turned into nodes."); } @Test void shouldNotAdaptArbitraryThingsAsNames() { assertThatIllegalArgumentException().isThrownBy(() -> Cypher.adapt(Expressions.TWO).asName()) .withMessage("Only Query-DSL paths can be turned into names."); } @Test void emptyBooleanBuilder() { Condition condition = Cypher.adapt(new BooleanBuilder()).asCondition(); Statement statement = Cypher.match(Cypher.anyNode().named("n")) .where(condition) .returning(Cypher.name("n")) .build(); assertThat(statement.getCatalog().getParameterNames()).isEmpty(); assertThat(statement.getCypher()).isEqualTo("MATCH (n) RETURN n"); } @Test void booleanBuilder() { Condition condition = Cypher.adapt(new BooleanBuilder().and(Expressions.FALSE).and(Expressions.TRUE)) .asCondition(); Statement statement = Cypher.match(Cypher.anyNode().named("n")) .where(condition) .returning(Cypher.name("n")) .build(); assertThat(statement.getCatalog().getParameterNames()).isEmpty(); assertThat(statement.getCypher()).isEqualTo("MATCH (n) WHERE false AND true RETURN n"); } @Test void communityExample() { QPerson user = QPerson.person; Predicate predicate = user.firstName.equalsIgnoreCase("dave") .and(user.lastName.startsWithIgnoreCase("mathews")); Node p = Cypher.node("Entity").named("p"); String cypher = Cypher.match(p).where(Cypher.adapt(predicate).asCondition()).returning(p).build().getCypher(); assertThat(cypher).isEqualTo( "MATCH (p:`Entity`) WHERE toLower(person.firstName) = 'dave' AND toLower(person.lastName) STARTS WITH 'mathews' RETURN p"); } @Test void communityExampleWithWorkaround() { QPerson user = QPerson.person; Predicate predicate = user.firstName.toLowerCase() .eq("dave".toLowerCase()) .and(user.lastName.startsWithIgnoreCase("mathews")); Node p = Cypher.node("Entity").named("p"); String cypher = Cypher.match(p).where(Cypher.adapt(predicate).asCondition()).returning(p).build().getCypher(); assertThat(cypher).isEqualTo( "MATCH (p:`Entity`) WHERE toLower(person.firstName) = 'dave' AND toLower(person.lastName) STARTS WITH 'mathews' RETURN p"); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/querydsl/Stuff.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.querydsl; import com.querydsl.core.annotations.QueryEntity; /** * @author Rickard Öberg */ @QueryEntity public class Stuff { String name; public String getName() { return this.name; } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/querydsl/UnsupportedOperatorExceptionTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.querydsl; import com.querydsl.core.types.Ops; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; /** * @author Michael J. Simons */ class UnsupportedOperatorExceptionTests { @Test void unsupportedOperatorShouldBeAccessible() { Assertions.assertThat(new UnsupportedOperatorException(Ops.MathOps.ABS).getUnsupportedOperator()) .isEqualTo(Ops.MathOps.ABS); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/renderer/ConfigurableRendererTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; import java.util.EnumSet; import java.util.Map; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.Cypher; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class ConfigurableRendererTests { @Test void cacheShouldWork() { var statement = Cypher.match(Cypher.node("Movie").named("n")).returning(Cypher.name("n").property("f")).build(); var cypher1 = Renderer.getDefaultRenderer().render(statement); var cypher2 = Renderer.getDefaultRenderer().render(statement); assertThat(cypher1).isSameAs(cypher2); var statement2 = Cypher.match(Cypher.node("Movie").named("n")) .returning(Cypher.name("n").property("f")) .build(); var cypher3 = Renderer.getDefaultRenderer().render(statement2); assertThat(cypher2).isNotSameAs(cypher3); } @Test // GH-596 void shouldGenerateConstantNamesPerSchema() { var actor = Cypher.node("Actor").named("a"); var cool = Cypher.parameter("cool"); var statement = Cypher.with(Cypher.literalOf("The Matrix").as(Cypher.name("title"))) .match(Cypher.node("Movie") .named("n") .withProperties("foo", Cypher.literalOf("bazbar")) .relationshipFrom(actor, "ACTED_IN") .named("r")) .where(Cypher.name("n").property("title").eq(Cypher.name("title"))) .and(Cypher.name("a").property("name").eq(Cypher.literalOf("Keanu Reeves"))) .and(actor.property("born").isEqualTo(Cypher.parameter("born"))) .and(actor.property("cool").isEqualTo(cool)) .and(actor.property("cool2").isEqualTo(cool)) .and(actor.property("whatever").isEqualTo(Cypher.parameter("born"))) .and(Cypher.name("r").property("x").isEqualTo(Cypher.anonParameter("foo"))) .returning(Cypher.name("n").property("f")) .build(); var cypher1 = Renderer.getRenderer(Configuration.newConfig().withGeneratedNames(true).build()) .render(statement); var expectedGeneratedNames = "WITH 'The Matrix' AS v0 " + "MATCH (v1:`Movie` {foo: 'bazbar'})<-[v2:`ACTED_IN`]-(v3:`Actor`) " + "WHERE (v1.title = v0 AND v3.name = 'Keanu Reeves' AND v3.born = $p0 AND " + "v3.cool = $p1 AND v3.cool2 = $p1 AND v3.whatever = $p0 AND v2.x = $pcdsl01) RETURN v1.f"; var expectedDefault = "WITH 'The Matrix' AS title " + "MATCH (n:`Movie` {foo: 'bazbar'})<-[r:`ACTED_IN`]-(a:`Actor`) " + "WHERE (n.title = title AND a.name = 'Keanu Reeves' AND a.born = $born AND " + "a.cool = $cool AND a.cool2 = $cool AND a.whatever = $born AND r.x = $pcdsl01) RETURN n.f"; assertThat(cypher1).isEqualTo(expectedGeneratedNames); assertThat(statement.getCatalog().getRenamedParameters()).containsExactly(Map.entry("born", "p0"), Map.entry("cool", "p1")); // Compare a second rendering var cypher2 = Renderer.getRenderer(Configuration.newConfig().withGeneratedNames(true).build()) .render(statement); assertThat(cypher2).isEqualTo(expectedGeneratedNames); assertThat(cypher1).isSameAs(cypher2); assertThat(statement.getCypher()).isEqualTo(expectedDefault); } @Test // GH-645 void shouldExportSymbolicNames() { var stmnt = Cypher.match(Cypher.node("N").named("n")) .call(Cypher.match(Cypher.anyNode("m")).returning(Cypher.name("m")).build()) .returning("n", "m") .build(); assertThat(Renderer .getRenderer(Configuration.newConfig() .withGeneratedNames(EnumSet.complementOf(EnumSet.of(Configuration.GeneratedNames.ENTITY_NAMES))) .build()) .render(stmnt)).isEqualTo("MATCH (n:`N`) CALL (*) {MATCH (m) RETURN m} RETURN n, m"); } @Test // GH-645 void shouldExportNamedAsSymbolicNames() { var m = Cypher.anyNode("m"); var stmnt = Cypher.match(Cypher.node("N").named("n")) .call(Cypher.match(m).returning(m).build()) .returning("n", "m") .build(); assertThat(Renderer .getRenderer(Configuration.newConfig() .withGeneratedNames(EnumSet.complementOf(EnumSet.of(Configuration.GeneratedNames.ENTITY_NAMES))) .build()) .render(stmnt)).isEqualTo("MATCH (n:`N`) CALL (*) {MATCH (m) RETURN m} RETURN n, m"); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/renderer/ConfigurationTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons * */ class ConfigurationTests { @Test void dialectShouldBeIncludedInEquals() { Configuration cfg0 = Configuration.newConfig().build(); Configuration cfg1 = Configuration.newConfig().withDialect(Dialect.NEO4J_5_DEFAULT_CYPHER).build(); Configuration cfg2 = Configuration.newConfig().withDialect(Dialect.NEO4J_4).build(); assertThat(cfg0).isEqualTo(cfg1).isNotEqualTo(cfg2); } @Test void dialectShouldBeIncludedInHash() { Configuration cfg0 = Configuration.newConfig().build(); Configuration cfg1 = Configuration.newConfig().withDialect(Dialect.NEO4J_5_DEFAULT_CYPHER).build(); Configuration cfg2 = Configuration.newConfig().withDialect(Dialect.NEO4J_4).build(); assertThat(cfg0).hasSameHashCodeAs(cfg1).doesNotHaveSameHashCodeAs(cfg2); } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/renderer/DefaultVisitorTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.EnumSource; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class DefaultVisitorTests { private final DefaultVisitor visitor = new DefaultVisitor(null); @ParameterizedTest @CsvSource({ "ALabel, `ALabel`", "A Label, `A Label`", "A `Label, `A ``Label`", "`A `Label, ```A ``Label`", "Spring Data Neo4j⚡️RX, `Spring Data Neo4j⚡️RX`" }) void shouldCorrectlyEscapeNames(String name, String expectedEscapedName) { assertThat(this.visitor.escapeName(name)).hasValue(expectedEscapedName); } @Test void shouldNotTryToEscapeNullNames() { assertThat(this.visitor.escapeName(null)).isEmpty(); } @EnumSource(TestEnum.class) @ParameterizedTest void underscoresInEnumsShouldBeRemoved(TestEnum testEnum) { DefaultVisitor visitorUnderTest = new DefaultVisitor(null); visitorUnderTest.enter(testEnum); assertThat(visitorUnderTest.builder).hasToString(testEnum.expected); } @ParameterizedTest @CsvSource({ "ABC, ABC", "A C, `A C`", "A` C, `A`` C`", "A`` C, `A`` C`", "ALabel, ALabel", "A Label, `A Label`", "A `Label, `A ``Label`", "`A `Label, ```A ``Label`", "Spring Data Neo4j⚡️RX, `Spring Data Neo4j⚡️RX`", "Foo \u0060, `Foo ```", // This is the backtick itself in the string "Foo \\u0060, `Foo ```", // This is the backtick unicode escaped so that // without further processing `foo \u0060` would // end up at Cypher, "`, ````", "\u0060, ````", "```, ``````", "\u0060\u0060\u0060, ``````", "Hello`, `Hello```", "Hi````there, `Hi````there`", "Hi`````there, `Hi``````there`", "`a`b`c`, ```a``b``c```", "\u0060a`b`c\u0060d\u0060, ```a``b``c``d```" }) void shouldEscapeIfNecessary(String name, String expectedEscapedName) { assertThat(this.visitor.escapeIfNecessary(name)).isEqualTo(expectedEscapedName); } @Test void shouldNotUnnecessaryEscape() { assertThat(this.visitor.escapeIfNecessary(" ")).isEqualTo("` `"); assertThat(this.visitor.escapeIfNecessary(null)).isNull(); assertThat(this.visitor.escapeIfNecessary("a")).isEqualTo("a"); } enum TestEnum { NO("NO"), ONE_UNDERSCORE("ONE UNDERSCORE"), THOSE_ARE_MORE_UNDERSCORES("THOSE ARE MORE UNDERSCORES"); String expected; TestEnum(String expected) { this.expected = expected + " "; } } } ================================================ FILE: neo4j-cypher-dsl/src/test/java/org/neo4j/cypherdsl/core/renderer/PrettyPrintingVisitorTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.core.renderer; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import static org.assertj.core.api.Assertions.assertThat; /** * @author Andreas Berger * @author Michael J. Simons */ class PrettyPrintingVisitorTests { private final PrettyPrintingVisitor prettyPrintingVisitor = new PrettyPrintingVisitor(null, Configuration.newConfig() .alwaysEscapeNames(false) .withIndentStyle(Configuration.IndentStyle.TAB) .withIndentSize(4) .build()); @ParameterizedTest @CsvSource({ "ALabel, ALabel", "A Label, `A Label`", "A `Label, `A ``Label`", "`A `Label, ```A ``Label`", "Spring Data Neo4j⚡️RX, `Spring Data Neo4j⚡️RX`" }) void shouldCorrectlyEscapeNames(String name, String expectedEscapedName) { assertThat(this.prettyPrintingVisitor.escapeName(name)).hasValue(expectedEscapedName); } @Test void shouldNotTryToEscapeNullNames() { assertThat(this.prettyPrintingVisitor.escapeName(null)).isEmpty(); } } ================================================ FILE: neo4j-cypher-dsl/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker ================================================ mock-maker-inline ================================================ FILE: neo4j-cypher-dsl-bom/pom.xml ================================================ 4.0.0 org.neo4j neo4j-cypher-dsl-parent ${revision}${sha1}${changelist} neo4j-cypher-dsl-bom pom Neo4j Cypher DSL (BOM) The BOM for the Neo4j Cypher DSL. org.neo4j neo4j-cypher-dsl ${project.version} org.neo4j neo4j-cypher-dsl-codegen-core ${project.version} org.neo4j neo4j-cypher-dsl-codegen-ogm ${project.version} org.neo4j neo4j-cypher-dsl-codegen-sdn6 ${project.version} org.neo4j neo4j-cypher-dsl-parser ${project.version} org.neo4j neo4j-cypher-dsl-schema-name-support ${project.version} org.codehaus.mojo flatten-maven-plugin true bom remove ================================================ FILE: neo4j-cypher-dsl-build/annotations/pom.xml ================================================ 4.0.0 org.neo4j neo4j-cypher-dsl-build ${revision}${sha1}${changelist} neo4j-cypher-dsl-build-annotations Neo4j Cypher DSL (Build Utils: Annotations) Annotations for minimum GraalVM support. org.apache.maven.plugins maven-install-plugin true org.apache.maven.plugins maven-deploy-plugin true org.apache.maven.plugins maven-javadoc-plugin true org.apache.maven.plugins maven-compiler-plugin -proc:none ================================================ FILE: neo4j-cypher-dsl-build/annotations/src/main/java/module-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * For internal use only: Marks classes for inclusion in reflection-config.json for GraalVM. * * @author Michael J. Simons * @since 2023.0.0 */ module org.neo4j.cypherdsl.build.annotations { exports org.neo4j.cypherdsl.build.annotations; } ================================================ FILE: neo4j-cypher-dsl-build/annotations/src/main/java/org/neo4j/cypherdsl/build/annotations/RegisterForReflection.java ================================================ /* * Copyright (c) 2019-2025 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.build.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Used to mark classes to be included in `reflection-config.json` so that it doesn't need * updates when new reflective visitors and the like are added. *

* A thin documentation about the file format can be found here. * For now, only {@code allDeclaredMethods} is supported. *

* In the future, support can be added for *

    *
  • allPublicConstructors
  • *
  • allPublicMethods
  • *
  • allPublicFields
  • *
  • allDeclaredConstructors
  • *
  • allDeclaredFields
  • *
* * @author Michael J. Simons * @since 2022.2.2 */ @Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) public @interface RegisterForReflection { /** * {@return true if all declared methods should be marked for inclusion} */ boolean allDeclaredMethods() default true; /** * {@return true if all declared constructors should be marked for inclusion} */ boolean allDeclaredConstructors() default false; } ================================================ FILE: neo4j-cypher-dsl-build/annotations/src/main/java/org/neo4j/cypherdsl/build/annotations/package-info.java ================================================ /* * Copyright (c) 2019-2025 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * Utilities to make our lives a bit easier with GraalVM native image. Designed and * catered for our own needs, not meant to be consumed by other libraries. */ package org.neo4j.cypherdsl.build.annotations; ================================================ FILE: neo4j-cypher-dsl-build/pom.xml ================================================ 4.0.0 org.neo4j neo4j-cypher-dsl-parent ${revision}${sha1}${changelist} neo4j-cypher-dsl-build pom Neo4j Cypher DSL (Build Utils) Annotations and processors for minimum GraalVM support. annotations processor org.apache.maven.plugins maven-install-plugin true org.apache.maven.plugins maven-deploy-plugin true org.apache.maven.plugins maven-javadoc-plugin true ================================================ FILE: neo4j-cypher-dsl-build/processor/pom.xml ================================================ 4.0.0 org.neo4j neo4j-cypher-dsl-build ${revision}${sha1}${changelist} neo4j-cypher-dsl-build-proc Neo4j Cypher DSL (Build Utils: Processor) Annotations and processors for minimum GraalVM support. org.neo4j.cypherdsl.build.proc com.fasterxml.jackson.core jackson-databind org.neo4j neo4j-cypher-dsl-build-annotations ${project.version} com.google.testing.compile compile-testing test org.assertj assertj-core test org.junit.jupiter junit-jupiter test org.apache.maven.plugins maven-install-plugin true org.apache.maven.plugins maven-deploy-plugin true org.apache.maven.plugins maven-javadoc-plugin true org.apache.maven.plugins maven-compiler-plugin -proc:none org.apache.maven.plugins maven-jar-plugin ${java-module-name} ================================================ FILE: neo4j-cypher-dsl-build/processor/src/main/java/org/neo4j/cypherdsl/build/processor/Entry.java ================================================ /* * Copyright (c) 2019-2025 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.build.processor; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonInclude; /** * An entry that should find its way into GraalVM reflection-config.json. * * @author Michael J. Simons */ @JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NON_PRIVATE, isGetterVisibility = JsonAutoDetect.Visibility.NON_PRIVATE) final class Entry { private final String name; private boolean allDeclaredMethods; private boolean allDeclaredConstructors; Entry(String name) { this.name = name; } String getName() { return this.name; } @JsonInclude(JsonInclude.Include.NON_DEFAULT) boolean isAllDeclaredMethods() { return this.allDeclaredMethods; } void setAllDeclaredMethods(boolean allDeclaredMethods) { this.allDeclaredMethods = allDeclaredMethods; } @JsonInclude(JsonInclude.Include.NON_DEFAULT) boolean isAllDeclaredConstructors() { return this.allDeclaredConstructors; } void setAllDeclaredConstructors(boolean allDeclaredConstructors) { this.allDeclaredConstructors = allDeclaredConstructors; } } ================================================ FILE: neo4j-cypher-dsl-build/processor/src/main/java/org/neo4j/cypherdsl/build/processor/RegisterForReflectionProcessor.java ================================================ /* * Copyright (c) 2019-2025 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.build.processor; import java.io.IOException; import java.io.OutputStream; import java.util.Collection; import java.util.Comparator; import java.util.Set; import java.util.TreeSet; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedOptions; import javax.lang.model.SourceVersion; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import javax.tools.FileObject; import javax.tools.StandardLocation; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import org.neo4j.cypherdsl.build.annotations.RegisterForReflection; /** * Processor reacting on {@code @RegisterForReflection}. * * @author Michael J. Simons * @since 2022.2.2 */ @SupportedAnnotationTypes("org.neo4j.cypherdsl.build.annotations.RegisterForReflection") @SupportedOptions(RegisterForReflectionProcessor.NATIVE_IMAGE_SUBDIR_OPTION) public final class RegisterForReflectionProcessor extends AbstractProcessor { static final String NATIVE_IMAGE_SUBDIR_OPTION = "org.neo4j.cypherdsl.build.native_config_dir"; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); private final Collection entries = new TreeSet<>(Comparator.comparing(Entry::getName)); static boolean registersElements(RegisterForReflection registerForReflection) { return registerForReflection.allDeclaredMethods() || registerForReflection.allDeclaredConstructors(); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { if (roundEnv.processingOver() && !this.entries.isEmpty()) { try { String subDir = this.processingEnv.getOptions().getOrDefault(NATIVE_IMAGE_SUBDIR_OPTION, ""); if (!(subDir.isEmpty() || subDir.endsWith("/"))) { subDir += "/"; } String reflectionConfigPath = String.format("META-INF/native-image/%sreflection-config.json", subDir); FileObject fileObject = this.processingEnv.getFiler() .createResource(StandardLocation.CLASS_OUTPUT, "", reflectionConfigPath); try (OutputStream oos = fileObject.openOutputStream()) { OBJECT_MAPPER.writeValue(oos, this.entries); } } catch (IOException ex) { this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, ex.getMessage()); } } else if (!annotations.isEmpty()) { roundEnv.getElementsAnnotatedWith(RegisterForReflection.class) .stream() .filter(e -> e.getKind().isClass() && registersElements(e.getAnnotation(RegisterForReflection.class))) .map(TypeElement.class::cast) .map(e -> { RegisterForReflection registerForReflection = e.getAnnotation(RegisterForReflection.class); Entry entry = new Entry(e.getQualifiedName().toString()); entry.setAllDeclaredMethods(registerForReflection.allDeclaredMethods()); entry.setAllDeclaredConstructors(registerForReflection.allDeclaredConstructors()); return entry; }) .forEach(this.entries::add); } return true; } } ================================================ FILE: neo4j-cypher-dsl-build/processor/src/main/java/org/neo4j/cypherdsl/build/processor/package-info.java ================================================ /* * Copyright (c) 2019-2025 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * Contains the processors for the built-time annotations. */ package org.neo4j.cypherdsl.build.processor; ================================================ FILE: neo4j-cypher-dsl-build/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor ================================================ org.neo4j.cypherdsl.build.processor.RegisterForReflectionProcessor ================================================ FILE: neo4j-cypher-dsl-build/processor/src/test/java/org/neo4j/cypherdsl/build/processor/RegisterForReflectionProcessorTests.java ================================================ /* * Copyright (c) 2019-2025 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.build.processor; import java.io.IOException; import java.io.UncheckedIOException; import java.util.Objects; import java.util.stream.IntStream; import javax.tools.JavaFileObject; import javax.tools.StandardLocation; import com.google.testing.compile.Compilation; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class RegisterForReflectionProcessorTests { static Compiler getCompiler(Object... options) { Object[] defaultOptions = new Object[] { "--release", "17" }; Object[] finalOptions = new Object[options.length + defaultOptions.length]; System.arraycopy(defaultOptions, 0, finalOptions, 0, defaultOptions.length); System.arraycopy(options, 0, finalOptions, defaultOptions.length, options.length); return Compiler.javac().withOptions(finalOptions); } @Test void shouldOnlyGenerateReflectionConfigWithContentWhenClassesArePresent() { JavaFileObject[] resources = IntStream.range(0, 4).mapToObj(this::getTestClassN).toArray(JavaFileObject[]::new); Compilation compilation = getCompiler("-Aorg.neo4j.cypherdsl.build.native_config_dir=foo/bar") .withProcessors(new RegisterForReflectionProcessor()) .compile(resources); assertThat(compilation.status()).isEqualTo(Compilation.Status.SUCCESS); assertThat(compilation.warnings()).isEmpty(); assertThat(compilation.generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/native-image/foo/bar/reflection-config.json")) .map(f -> { try { return f.getCharContent(true); } catch (IOException ex) { throw new UncheckedIOException(ex); } }) .hasValue(""" [ { "name" : "org.neo4j.cypherdsl.build.test.Class0", "allDeclaredMethods" : true }, { "name" : "org.neo4j.cypherdsl.build.test.Class3", "allDeclaredConstructors" : true } ]"""); } @Test void shouldHandleEmptyDir() { Compilation compilation = getCompiler().withProcessors(new RegisterForReflectionProcessor()) .compile(getTestClassN(0)); assertThat(compilation.status()).isEqualTo(Compilation.Status.SUCCESS); assertThat(compilation.warnings()).isEmpty(); assertThat(compilation.generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/native-image/reflection-config.json")) .isPresent(); } @Test void shouldHandleSlash() { Compilation compilation = getCompiler("-Aorg.neo4j.cypherdsl.build.native_config_dir=foo/") .withProcessors(new RegisterForReflectionProcessor()) .compile(getTestClassN(0)); assertThat(compilation.status()).isEqualTo(Compilation.Status.SUCCESS); assertThat(compilation.warnings()).isEmpty(); assertThat(compilation.generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/native-image/foo/reflection-config.json")) .isPresent(); } @Test void shouldNotGenerateEmpty() { JavaFileObject[] resources = IntStream.range(1, 3).mapToObj(this::getTestClassN).toArray(JavaFileObject[]::new); Compilation compilation = getCompiler("-Aorg.neo4j.cypherdsl.build.native_config_dir=foo/bar") .withProcessors(new RegisterForReflectionProcessor()) .compile(resources); assertThat(compilation.status()).isEqualTo(Compilation.Status.SUCCESS); assertThat(compilation.warnings()).isEmpty(); assertThat(compilation.generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/native-image/foo/bar/reflection-config.json")) .isEmpty(); } private JavaFileObject getTestClassN(int i) { return JavaFileObjects.forResource( Objects.requireNonNull(this.getClass().getResource(String.format("/test_classes/Class%d.java", i)))); } } ================================================ FILE: neo4j-cypher-dsl-build/processor/src/test/resources/test_classes/Class0.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.build.test; import org.neo4j.cypherdsl.build.annotations.RegisterForReflection; @RegisterForReflection public class Class0 { } ================================================ FILE: neo4j-cypher-dsl-build/processor/src/test/resources/test_classes/Class1.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.build.test; import org.neo4j.cypherdsl.build.annotations.RegisterForReflection; @RegisterForReflection(allDeclaredMethods = false) public class Class1 { } ================================================ FILE: neo4j-cypher-dsl-build/processor/src/test/resources/test_classes/Class2.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.build.test; public class Class2 { } ================================================ FILE: neo4j-cypher-dsl-build/processor/src/test/resources/test_classes/Class3.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.build.test; import org.neo4j.cypherdsl.build.annotations.RegisterForReflection; @RegisterForReflection(allDeclaredMethods = false, allDeclaredConstructors = true) public class Class3 { } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/pom.xml ================================================ 4.0.0 org.neo4j neo4j-cypher-dsl-codegen ${revision}${sha1}${changelist} neo4j-cypher-dsl-codegen-core Code Generator (Core) Core module for the Cypher DSL code generation. 0.1 0.1 ${basedir}/../../${aggregate.report.dir} com.squareup javapoet org.neo4j neo4j-cypher-dsl com.google.testing.compile compile-testing test org.assertj assertj-core test org.junit.jupiter junit-jupiter test ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/module-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * @author Michael J. Simons * @since 2023.0.0 */ @SuppressWarnings({"requires-automatic"}) // javapoet module org.neo4j.cypherdsl.codegen.core { requires java.compiler; requires com.squareup.javapoet; requires transitive org.apiguardian.api; requires transitive org.neo4j.cypherdsl.core; exports org.neo4j.cypherdsl.codegen.core; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/org/neo4j/cypherdsl/codegen/core/AbstractClassNameGenerator.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.INTERNAL; /** * Shared type name generator for the default naming strategies for generated node and * relationship implementations. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = INTERNAL, since = "2021.1.0") abstract class AbstractClassNameGenerator { /** * Strips away invalid identifiers, returns an initial upper case name. * @param suggestedName some arbitrary suggested name * @return a String builder for further processing. */ final StringBuilder generateTypeName(String suggestedName) { StringBuilder sb = new StringBuilder(); int codePoint; int i = 0; while (i < suggestedName.length()) { codePoint = suggestedName.codePointAt(i); if (Identifiers.isValidAt(i, codePoint)) { if (sb.isEmpty()) { codePoint = Character.toUpperCase(codePoint); } sb.append(Character.toChars(codePoint)); } i += Character.charCount(codePoint); } return sb; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/org/neo4j/cypherdsl/codegen/core/AbstractMappingAnnotationProcessor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import java.io.IOException; import java.io.Writer; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.NestingKind; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleElementVisitor8; import javax.lang.model.util.Types; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; /** * Base class to build generators based on Neo4j supported object mapping frameworks (we * ship with support for SDN6+ and Neo4j-OGM). * * @author Michael J. Simons * @since 2025.0.0 */ public abstract class AbstractMappingAnnotationProcessor extends AbstractProcessor { protected Messager messager; protected Types typeUtils; protected Filer filer; protected Configuration configuration; /** * Default constructor is required by the annotation processor system of Java. */ public AbstractMappingAnnotationProcessor() { } /** * Reads all supported from the processing environment and creates a suitable * {@link Configuration}. * @param processingEnv the processing environment * @return a working configuration */ protected static Configuration createConfiguration(ProcessingEnvironment processingEnv) { Configuration.Builder builder = Configuration.newConfig(); Map options = processingEnv.getOptions(); if (options.containsKey(Configuration.PROPERTY_PREFIX)) { builder.withPrefix(options.get(Configuration.PROPERTY_PREFIX)); } if (options.containsKey(Configuration.PROPERTY_SUFFIX)) { builder.withSuffix(options.get(Configuration.PROPERTY_SUFFIX)); } if (options.containsKey(Configuration.PROPERTY_INDENT_STYLE)) { builder .withIndentStyle(Configuration.IndentStyle.valueOf(options.get(Configuration.PROPERTY_INDENT_STYLE))); } if (options.containsKey(Configuration.PROPERTY_INDENT_SIZE)) { builder.withIndentSize(Integer.parseInt(options.get(Configuration.PROPERTY_INDENT_SIZE))); } return builder.withTimestamp(options.getOrDefault(Configuration.PROPERTY_TIMESTAMP, null)) .withAddAtGenerated( Boolean.parseBoolean(options.getOrDefault(Configuration.PROPERTY_ADD_AT_GENERATED, "true"))) .withTarget(processingEnv.getSourceVersion().equals(SourceVersion.RELEASE_8) ? Configuration.JavaVersion.RELEASE_8 : Configuration.JavaVersion.RELEASE_11) .withExcludes(options.get(Configuration.PROPERTY_EXCLUDES)) .build(); } /** * Checks if the definitions contain either all the same or no properties at all. * @param definitions a list of all relationship definitions for one owning node * @return true if all definitions have no or the same set of properties */ protected static boolean sameOrNoProperties( List> definitions) { boolean same = true; Set properties = null; for (Map.Entry definition : definitions) { Set newProperties = definition.getValue().getProperties(); if (properties == null) { properties = newProperties; } else if (properties.size() != newProperties.size() || !properties.containsAll(newProperties)) { same = false; break; } } return same; } /** * Returns all types annotated with {@code annotation} found in the environment of * this round. * @param annotation the annotation to search for * @param from the environment to extract from * @return a set of type elements that match the given annotation */ protected Set getTypesAnnotatedWith(TypeElement annotation, RoundEnvironment from) { return from.getElementsAnnotatedWith(annotation) .stream() .filter(e -> e.getKind().isClass()) .map(TypeElement.class::cast) .filter(t -> !this.configuration.exclude(t.getQualifiedName().toString())) .collect(Collectors.toSet()); } @Override public final synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.typeUtils = processingEnv.getTypeUtils(); this.filer = processingEnv.getFiler(); this.messager = processingEnv.getMessager(); this.configuration = createConfiguration(processingEnv); initFrameworkSpecific(processingEnv); } /** * Do your framework specific initialisation here, such as figuring out your actual * annotations. * @param processingEnv the current processing env */ protected abstract void initFrameworkSpecific(ProcessingEnvironment processingEnv); @Override public final SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } /** * Creates {@link NodeModelBuilder node builder} for all classes annotated with * {@code @Node}. The node builders won't have any properties associated with them. * This is done in the next step. *

* The reason for not populating the properties in one go in this step is simple: When * one annotated node class refers to another, we can be sure that this property is * not a simple property but a relationship property, without going through other * expensive checks. But to be able to do this, we must know the annotated classes * upfront. * @param classesAnnotatedWithNode the set of annotated classes * @return a map from type to a node builder for that type */ protected final Map populateListOfNodes(Set classesAnnotatedWithNode) { Map result = new HashMap<>(); classesAnnotatedWithNode.forEach(annotatedClass -> { String qualifiedName = annotatedClass.getQualifiedName().toString(); String suggestedTypeName = annotatedClass.getSimpleName().toString(); String packageName = null; List subpackages = new LinkedList<>(); Element enclosingElement = annotatedClass.getEnclosingElement(); while (!(enclosingElement == null || enclosingElement.getKind().equals(ElementKind.PACKAGE))) { subpackages.add(0, enclosingElement.getSimpleName().toString().toLowerCase(Locale.ROOT)); enclosingElement = enclosingElement.getEnclosingElement(); } if (enclosingElement == null) { int lastDot = qualifiedName.lastIndexOf('.'); if (lastDot > 0) { packageName = qualifiedName.substring(0, lastDot); } } else if (enclosingElement.getKind().equals(ElementKind.PACKAGE)) { String q = ((PackageElement) enclosingElement).getQualifiedName().toString(); packageName = q + ((q.isEmpty() || subpackages.isEmpty()) ? "" : ".") + String.join(".", subpackages); } NodeModelBuilder builder = NodeModelBuilder.create(this.configuration, packageName, suggestedTypeName) .addLabels(getLabel(annotatedClass)); result.put(annotatedClass, builder); }); return Collections.unmodifiableMap(result); } /** * Finds labels on the annotated element. If there's no label specified on the node * annotation, the simple class name is used. Otherwise, all labels in declaration * order (primary, value, labels) are used. * @param annotatedClass the annotated class * @return a collection of labels */ protected abstract Collection getLabel(TypeElement annotatedClass); protected final void writeSourceFiles(Collection> builders) throws IOException { for (ModelBuilder nodeModelBuilder : builders) { JavaFileObject jfo = this.filer.createSourceFile(nodeModelBuilder.getCanonicalClassName()); try (Writer writer = jfo.openWriter()) { nodeModelBuilder.writeTo(writer); } } } protected abstract PropertiesAndRelationshipGrouping newPropertiesAndRelationshipGrouping(); /** * Turns an element into a property description. * @param e the element to transform * @return a new property description */ protected abstract PropertyDefinition asPropertyDefinition(Element e); /** * This populates the properties of all node builders and returns all identifiable * relationships. Why is this done as a side effect? The tests for relationships are * rather expensive and may need to instantiate classes. When this is unavoidable, it * makes sense to use that information right away and not do it a second time later * one- * @param nodeBuilders the map of all node builders for all annotated classes * @return a map from a node builder to a list of fields describing relationships */ protected final Map> populateNodePropertiesAndCollectRelationshipFields( Map nodeBuilders) { Map> relationshipFields = new HashMap<>(); nodeBuilders.forEach((type, nodeImplBuilder) -> { var groupPropertiesAndRelationships = newPropertiesAndRelationshipGrouping(); type.getEnclosedElements().forEach(groupPropertiesAndRelationships::apply); Map> fields = groupPropertiesAndRelationships.getResult(); nodeImplBuilder.addProperties(fields.get(FieldType.P).stream().map(this::asPropertyDefinition).toList()); relationshipFields.put(nodeImplBuilder, fields.get(FieldType.R)); }); return Collections.unmodifiableMap(relationshipFields); } protected abstract RelationshipPropertyDefinition asRelationshipDefinition(NodeModelBuilder owner, Element e, Map>> relationshipProperties, Map nodeBuilders); /** * This takes the list of all know fields pointing to relationships and computes a map * with relationship types and their definitions. A specific relationship type may * have been used between several other nodes. * @param allRelationshipFields all known relationship files * @param relationshipProperties a map of properties discovered for relationships * @param nodeBuilders the current list of node builders to work with * @return a map from a relationship type to a list of definitions. The definition * including the node builders themselves. The entry in the list is (start, * definition) */ protected final Map>> computeRelationshipDefinitions( Map> allRelationshipFields, Map>> relationshipProperties, Map nodeBuilders) { record F(String type, NodeModelBuilder definingType, RelationshipPropertyDefinition definition, boolean outgoing) { } Map>> definitions = new HashMap<>(); Map> hlp = allRelationshipFields.entrySet().stream().flatMap((e) -> { var start = e.getKey(); var l = e.getValue(); return l.stream().map(f -> { RelationshipPropertyDefinition propertyDefinition = asRelationshipDefinition(start, f, relationshipProperties, nodeBuilders); if (propertyDefinition == null) { return null; } return new F(propertyDefinition.getType(), start, propertyDefinition, propertyDefinition.getStart() == start); }); }).filter(Objects::nonNull).collect(Collectors.groupingBy(F::type)); // This finds bidirectional definitions per type. // it will filter out incoming (that is, include only the outgoing definition in // the metamodel) // hlp.forEach((type, definitionsPerType) -> { definitionsPerType.forEach(x -> { if (definitionsPerType.size() == 1 || x.outgoing() || definitionsPerType.stream() .noneMatch(x2 -> x2.outgoing && x2.definition().getStart() == x.definition().getStart() && x2.definition().getEnd() == x.definition().getEnd())) { definitions.computeIfAbsent(x.definition().getType(), k -> new ArrayList<>()) .add(new AbstractMap.SimpleEntry<>(x.definingType(), x.definition())); } else { this.messager.printMessage(Diagnostic.Kind.WARNING, "Bidirectional mappings are not supported for the static meta model (Relationship: %s)" .formatted(type)); } }); }); return Collections.unmodifiableMap(definitions); } /** * Creates and populates the list of relationships from their definitions. It also * registered the freshly generated relationship builders with the previously created * node builders. * @param relationshipDefinitions definitions by type and owner * @return map of builder per type. Can be multiple builders in case of different * relationship property classes with different properties. */ protected final Map> populateListOfRelationships( Map>> relationshipDefinitions) { Map> result = new HashMap<>(); for (var entry : relationshipDefinitions.entrySet()) { String type = entry.getKey(); List> allDefinitions = entry.getValue(); allDefinitions.stream() .collect(Collectors.groupingBy(pd -> pd.getValue().getStart().getPackageName())) .forEach((packageName, definitions) -> { RelationshipModelBuilder relationshipBuilder = null; // Simple case: All unique types if (definitions.size() == 1) { NodeModelBuilder owner = definitions.get(0).getKey(); RelationshipPropertyDefinition definition = definitions.get(0).getValue(); relationshipBuilder = RelationshipModelBuilder.create(this.configuration, owner.getPackageName(), type); relationshipBuilder.setStartNode(definition.getStart()); relationshipBuilder.setEndNode(definition.getEnd()); relationshipBuilder.addProperties(definition.getProperties()); } else { Set owners = definitions.stream() .map(Map.Entry::getKey) .collect(Collectors.toSet()); // Exactly one owner, but variable targets if (owners.size() == 1) { NodeModelBuilder owner = owners.stream().findFirst().get(); Map> ownerAtStartOrEnd = definitions.stream() .map(Map.Entry::getValue) .collect(Collectors.partitioningBy(p -> p.getStart() == owner)); if (sameOrNoProperties(definitions)) { relationshipBuilder = RelationshipModelBuilder.create(this.configuration, owner.getPackageName(), type); relationshipBuilder.addProperties(definitions.get(0).getValue().getProperties()); if (ownerAtStartOrEnd.get(true).isEmpty()) { relationshipBuilder.setEndNode(owner); relationshipBuilder.setStartNode( uniqueNodeBuilder(definitions, RelationshipPropertyDefinition::getStart)); } else if (ownerAtStartOrEnd.get(false).isEmpty()) { relationshipBuilder.setStartNode(owner); relationshipBuilder.setEndNode( uniqueNodeBuilder(definitions, RelationshipPropertyDefinition::getEnd)); } } else { List newBuilders = new ArrayList<>(); for (Map.Entry definition : definitions) { RelationshipPropertyDefinition propertyDefinition = definition.getValue(); RelationshipModelBuilder newBuilder = RelationshipModelBuilder.create( this.configuration, owner.getPackageName(), type, type + "_" + propertyDefinition.getEnd() .getPlainClassName() .toUpperCase(Locale.ROOT)); newBuilder.addProperties(propertyDefinition.getProperties()); if (ownerAtStartOrEnd.get(true).isEmpty()) { newBuilder.setStartNode(propertyDefinition.getStart()); newBuilder.setEndNode(owner); } else if (ownerAtStartOrEnd.get(false).isEmpty()) { newBuilder.setStartNode(owner); newBuilder.setEndNode(propertyDefinition.getEnd()); } definition.getKey() .addRelationshipDefinition(propertyDefinition.withBuilder(newBuilder)); newBuilders.add(newBuilder); } result.put(type, Collections.unmodifiableList(newBuilders)); } } else if (owners.size() > 1) { var intermediateBuilders = new HashMap(); for (var owner : owners) { for (Map.Entry definition : definitions) { var start = definition.getValue().getStart(); var end = definition.getValue().getEnd(); if (intermediateBuilders.containsKey(type)) { var intermediateBuilder = intermediateBuilders.get(type); if (intermediateBuilder.start != start) { intermediateBuilder.start = null; } if (intermediateBuilder.end != end) { intermediateBuilder.end = null; } } else { var localRelationshipBuilder = RelationshipModelBuilder .create(this.configuration, owner.getPackageName(), type); intermediateBuilders.put(type, new LocalRelBuilderState(localRelationshipBuilder, start, end)); } } } // Go over all half finished builders and close them intermediateBuilders.forEach((t, intermediateBuilder) -> { var finalBuilder = intermediateBuilder.value; finalBuilder.setStartNode(intermediateBuilder.start); finalBuilder.setEndNode(intermediateBuilder.end); // now that we have seen all definitions per type, we can // assign them proper for (Map.Entry definition : definitions) { if (definition.getValue().getType().equals(t)) { var finishedDefinition = definition.getValue().withBuilder(finalBuilder); definition.getKey().addRelationshipDefinition(finishedDefinition); } } var builders = result.computeIfAbsent(type, ignored -> new ArrayList<>()); builders.add(finalBuilder); }); } } // A single builder created for all definitions // Multiple builders have been taken care of independent. if (relationshipBuilder != null) { for (Map.Entry definition : definitions) { RelationshipPropertyDefinition finishedDefinition = definition.getValue() .withBuilder(relationshipBuilder); definition.getKey().addRelationshipDefinition(finishedDefinition); } var builders = result.computeIfAbsent(type, ignored -> new ArrayList<>()); builders.add(relationshipBuilder); } }); } return Collections.unmodifiableMap(result); } private static NodeModelBuilder uniqueNodeBuilder( List> definitions, Function mapper) { var hlp = definitions.stream().map(Map.Entry::getValue).map(mapper).distinct().toList(); if (hlp.size() == 1) { return hlp.get(0); } return null; } protected final boolean describesEnum(TypeMirror typeMirror) { List superTypes = this.typeUtils.directSupertypes(typeMirror); if (!(superTypes.size() == 1 && superTypes.get(0).getKind().equals(TypeKind.DECLARED))) { return false; } TypeMirror tm = superTypes.get(0); String name = ((DeclaredType) tm).asElement() .accept(new TypeElementVisitor<>(newTypeElementNameFunction()), null); return Enum.class.getName().equals(name); } protected final Function newTypeElementNameFunction() { return new TypeElementNameFunction(); } protected enum FieldType { P, R } protected interface PropertiesAndRelationshipGrouping { void apply(Element element); Map> getResult(); } /** * Recursively extracts a loadable and instantiable name from a canonical class name * by checking whether the current element is a nesting type. If so, the enclosing * element will be visited and the elements simple name will be appended with a * {@literal $}. */ private static final class TypeElementNameFunction implements Function { @Override public String apply(TypeElement typeElement) { NestingKind nestingKind = typeElement.getNestingKind(); if (nestingKind.isNested()) { return typeElement.getEnclosingElement().accept(new TypeElementVisitor<>(this), null) + "$" + typeElement.getSimpleName(); } return typeElement.getQualifiedName().toString(); } } /** * Utility class that can be used to extract a {@link TypeElement} from a type or * anything else that one can retrieve from the actual element. * * @param the type of the returned value */ protected static class TypeElementVisitor extends SimpleElementVisitor8 { private final Function delegate; public TypeElementVisitor(Function delegate) { this.delegate = delegate; } @Override public E visitType(TypeElement e, Void unused) { return this.delegate.apply(e); } } private static class LocalRelBuilderState { RelationshipModelBuilder value; NodeModelBuilder start; NodeModelBuilder end; LocalRelBuilderState(RelationshipModelBuilder value, NodeModelBuilder start, NodeModelBuilder end) { this.value = value; this.end = end; this.start = start; } } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/org/neo4j/cypherdsl/codegen/core/AbstractModelBuilder.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Path; import java.time.Clock; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Stream; import javax.lang.model.element.Modifier; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.TypeSpec; import org.apiguardian.api.API; import org.neo4j.cypherdsl.codegen.core.Configuration.JavaVersion; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; import static org.apiguardian.api.API.Status.INTERNAL; /** * Base class with some shared state and information for builders of {@link NodeBase} and * {@link RelationshipBase}. * * @param concrete type of this builder * @author Michael J. Simons * @since 2021.1.0 */ @API(status = INTERNAL, since = "2021.1.0") abstract class AbstractModelBuilder> implements ModelBuilder { protected static final ClassName TYPE_NAME_NODE = ClassName.get(Node.class); protected static final ClassName TYPE_NAME_NODE_BASE = ClassName.get(NodeBase.class); protected static final ClassName TYPE_NAME_SYMBOLIC_NAME = ClassName.get(SymbolicName.class); protected static final ClassName TYPE_NAME_LIST = ClassName.get(List.class); protected static final ClassName TYPE_NAME_MAP_EXPRESSION = ClassName.get(MapExpression.class); /** * Generator for field names. */ protected final FieldNameGenerator fieldNameGenerator; /** * The fully qualified name of the generated class. */ protected final ClassName className; /** * Simple, undecorated class name. */ protected final String plainClassName; /** * A set of fields to generate. */ protected final Set properties = new LinkedHashSet<>(); private final JavaVersion target; private final String indent; /** * Will be initialized with Double-checked locking into an unmodifiable * {@link JavaFile}, no need to worry. */ @SuppressWarnings("squid:S3077") private volatile JavaFile javaFile; private Clock clock = Clock.systemDefaultZone(); private boolean addAtGenerated = true; AbstractModelBuilder(FieldNameGenerator fieldNameGenerator, ClassName className, String plainClassName, JavaVersion target, String indent) { this.fieldNameGenerator = fieldNameGenerator; this.className = className; this.plainClassName = plainClassName; this.target = target; this.indent = indent; } /** * Internal utility method to extract the actual class name while trying to avoid best * guesses. * @param optionalSource the source from which to extract the class name to be * generated * @return a class name */ static ClassName extractClassName(ModelBuilder optionalSource) { if (optionalSource == null) { return null; } else if (optionalSource instanceof NodeImplBuilder builder) { return builder.getClassName(); } else if (optionalSource instanceof RelationshipImplBuilder builder) { return builder.getClassName(); } else { return ClassName.bestGuess(optionalSource.getCanonicalClassName()); } } /** * Central method to trigger building of the {@link #javaFile} if that has not been * done yet. {@link #buildJavaFile()} is supposed to fail when it realizes the build * has already been done. * @return a JavaPoet {@link JavaFile} that can be written to file or string. */ protected abstract JavaFile buildJavaFile(); @Override public final T addProperty(String newProperty) { return addProperty(PropertyDefinition.create(newProperty, newProperty)); } @Override public final T addProperty(PropertyDefinition newProperty) { return addProperties(Collections.singleton(newProperty)); } @Override @SuppressWarnings("unchecked") public final T addProperties(Collection newProperties) { return (T) callOnlyWithoutJavaFilePresent(() -> { this.properties.addAll(newProperties); return this; }); } @Override public final String getPackageName() { return this.className.packageName(); } @Override public final String getCanonicalClassName() { return this.className.canonicalName(); } @Override public final String getPlainClassName() { return this.plainClassName; } @Override public final void writeTo(Path path) { try { getJavaFile().writeTo(path); } catch (IOException ex) { throw new UncheckedIOException(ex); } } @Override public final void writeTo(Appendable appendable) { try { getJavaFile().writeTo(appendable); } catch (IOException ex) { throw new UncheckedIOException(ex); } } @Override public final String writeToString() { try { StringBuilder out = new StringBuilder(); getJavaFile().writeTo(out); return out.toString(); } catch (IOException ex) { throw new UncheckedIOException(ex); } } @Override public final String getFieldName() { return this.fieldNameGenerator.generate(getPlainClassName()); } @SuppressWarnings("unchecked") final T apply(Configuration configuration) { configuration.getClock().ifPresent(c -> this.clock = c); this.addAtGenerated = configuration.isAddAtGenerated(); return (T) this; } /** * Makes sure that no Java file has been created before modifying the builder. * @param the type of the value of the objects supplied * @param c a supplier of arbitrary code * @return the value created for a property unless a source file was already present */ final V callOnlyWithoutJavaFilePresent(Supplier c) { synchronized (this) { if (this.javaFile == null) { return c.get(); } } throw new IllegalStateException("Class has already been generated, cannot add properties."); } /** * Adds information about the generated class. * @param builder the builder that should be decorated * @return a type builder. */ final TypeSpec.Builder addGenerated(TypeSpec.Builder builder) { ClassName nameOfAtGenerated; if (this.target == JavaVersion.RELEASE_8) { nameOfAtGenerated = ClassName.get("javax.annotation", "Generated"); } else if (this.target == JavaVersion.RELEASE_11 && this.addAtGenerated) { nameOfAtGenerated = ClassName.get("javax.annotation.processing", "Generated"); } else { nameOfAtGenerated = null; } String comment = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration."; if (nameOfAtGenerated == null) { builder.addJavadoc(CodeBlock.of(comment)); } else { AnnotationSpec spec = AnnotationSpec.builder(nameOfAtGenerated) .addMember("value", "$S", getClass().getName()) .addMember("date", "$S", ZonedDateTime.now(this.clock).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)) .addMember("comments", "$S", comment) .build(); builder.addAnnotation(spec); } return builder; } /** * Turns the property definitions into field spec. * @return a stream of field specs */ final Stream generateFieldSpecsFromProperties() { return this.properties.stream().map(p -> { String fieldName; CodeBlock initializer; if (p.getNameInDomain() == null) { fieldName = p.getNameInGraph(); initializer = CodeBlock.of("this.property($S)", p.getNameInGraph()); } else { fieldName = p.getNameInDomain(); initializer = CodeBlock.of("this.property($S).referencedAs($S)", p.getNameInGraph(), p.getNameInDomain()); } return FieldSpec .builder(Property.class, this.fieldNameGenerator.generate(fieldName), Modifier.PUBLIC, Modifier.FINAL) .initializer(initializer) .build(); }); } /** * Prepares a file builder for the given type. * @param typeSpec the type that should be written * @return a builder with some shared settings like indent etc. applied. */ final JavaFile.Builder prepareFileBuilder(TypeSpec typeSpec) { return JavaFile.builder(getPackageName(), typeSpec).skipJavaLangImports(true).indent(this.indent); } /** * {@return the internal class name used by JavaPoet} */ final ClassName getClassName() { return this.className; } private JavaFile getJavaFile() { JavaFile result = this.javaFile; if (result == null) { synchronized (this) { result = this.javaFile; if (result == null) { this.javaFile = buildJavaFile(); result = this.javaFile; } } } return result; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/org/neo4j/cypherdsl/codegen/core/ClassNameGenerator.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.RelationshipBase; import static org.apiguardian.api.API.Status.INTERNAL; /** * Strategy interface for generating class names for {@link NodeBase} or * {@link RelationshipBase}. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = INTERNAL, since = "2021.1.0") @FunctionalInterface public interface ClassNameGenerator { /** * Generates a valid Java class based on a suggestion. Most of the time the suggestion * will either be a simple string, maybe an existing class name or the concatenated * labels of a node as returned by {@code db.schema.nodeTypeProperties}. *

* Any implementation must remove invalid identifier parts. * @param suggestedName basically an arbitrary string suggesting a type name. * @return a pair of names: A valid identifier for a type and matching field name. */ String generate(String suggestedName); } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/org/neo4j/cypherdsl/codegen/core/Configuration.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import java.nio.file.Path; import java.time.Clock; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import java.util.function.UnaryOperator; import java.util.stream.Collectors; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.EXPERIMENTAL; /** * Main configuration objects for all aspects of code generation. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = EXPERIMENTAL, since = "2021.1.0") public final class Configuration { /** * Lookup key to be used in an annotations processors environment options for prefixes * to be used. */ public static final String PROPERTY_PREFIX = "org.neo4j.cypherdsl.codegen.prefix"; /** * Lookup key to be used in an annotations processors environment options for suffixes * to be used. */ public static final String PROPERTY_SUFFIX = "org.neo4j.cypherdsl.codegen.suffix"; /** * Lookup key to be used in an annotations processors environment options for the * indent style to be used. */ public static final String PROPERTY_INDENT_STYLE = "org.neo4j.cypherdsl.codegen.indent_style"; /** * Lookup key to be used in an annotations processors environment options for the * indent size to be used. */ public static final String PROPERTY_INDENT_SIZE = "org.neo4j.cypherdsl.codegen.indent_size"; /** * Lookup key to be used in an annotations processors environment options for the * timestamp to be used. */ public static final String PROPERTY_TIMESTAMP = "org.neo4j.cypherdsl.codegen.timestamp"; /** * Lookup key to be used in an annotations processors environment options for the flag * whether generated code should be marked. */ public static final String PROPERTY_ADD_AT_GENERATED = "org.neo4j.cypherdsl.codegen.add_at_generated"; /** * Lookup key to be used in an annotations processors environment options for a comma * separated list of types that should be excluded from being processed. */ public static final String PROPERTY_EXCLUDES = "org.neo4j.cypherdsl.codegen.excludes"; private static final Configuration DEFAULT_CONFIG = newConfig().build(); /** * Defines decoration for generated type names, applies to both nodes and * relationships. */ private final UnaryOperator typeNameDecorator; /** * Defines how classes representing nodes should be named. */ private final ClassNameGenerator nodeNameGenerator; /** * Defines how classes representing relationships should be named. */ private final ClassNameGenerator relationshipNameGenerator; private final FieldNameGenerator fieldNameGenerator; /** * On which Java version should the generated classes be compilable? Defaults to Java * Release 8. */ private final JavaVersion target; /** * The fully qualified name of the Java package into which the classes should be * generated. */ private final String defaultPackage; /** * The path into which the Java classes should be generated. A package structure * matching {@link #defaultPackage} will be created. */ private final Optional path; /** * The indention string used in the generated source files. */ private final String indent; /** * Optional clock to use while generation things. */ private final Optional clock; /** * Flag if the {@code @Generated}-annotation should be added. On JDK9+ on the module * path it would require jdk.compiler. If you don't want it, disable it with this * flag. */ private final boolean addAtGenerated; /** * Set of qualified names to exclude from processing. */ private final Set excludes; private Configuration(UnaryOperator typeNameDecorator, ClassNameGenerator nodeNameGenerator, ClassNameGenerator relationshipNameGenerator, FieldNameGenerator fieldNameGenerator, JavaVersion target, String defaultPackage, Path path, String indent, Clock clock, boolean addAtGenerated, Set excludes) { this.typeNameDecorator = typeNameDecorator; this.nodeNameGenerator = nodeNameGenerator; this.relationshipNameGenerator = relationshipNameGenerator; this.fieldNameGenerator = fieldNameGenerator; this.target = target; this.defaultPackage = defaultPackage; this.path = Optional.ofNullable(path); this.indent = indent; this.clock = Optional.ofNullable(clock); this.addAtGenerated = addAtGenerated; this.excludes = excludes; } /** * {@return an instance of the default configuration} */ public static Configuration defaultConfig() { return DEFAULT_CONFIG; } /** * Starts building new configuration. * @return a new builder */ public static Builder newConfig() { return Builder.newConfig(); } /** * Starts building a new configuration for the given path. * @param path the path into which code should be generated * @return a new builder */ public static Builder newConfig(final Path path) { return Builder.newConfig(path); } /** * {@return the generator for node names} */ public ClassNameGenerator getNodeNameGenerator() { return this.nodeNameGenerator; } /** * {@return the generator for class names} */ public ClassNameGenerator getRelationshipNameGenerator() { return this.relationshipNameGenerator; } /** * {@return the generator for constant field names} */ public FieldNameGenerator getConstantFieldNameGenerator() { return this.fieldNameGenerator; } /** * {@return the target java version of the generated code} */ public JavaVersion getTarget() { return this.target; } /** * {@return the default package name to use} */ public String getDefaultPackage() { return this.defaultPackage; } /** * {@return the path to generate code into} */ public Optional getPath() { return this.path; } /** * {@return the decorator that applies pre- and suffixes to typenames} */ public UnaryOperator getTypeNameDecorator() { return this.typeNameDecorator; } /** * {@return the string to be used as indent} */ public String getIndent() { return this.indent; } /** * {@return an optional clock, different from the system.} */ public Optional getClock() { return this.clock; } /** * {@return literal true if generated code is marked as such} */ public boolean isAddAtGenerated() { return this.addAtGenerated; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Configuration that = (Configuration) o; return this.typeNameDecorator.equals(that.typeNameDecorator) && this.nodeNameGenerator.equals(that.nodeNameGenerator) && this.relationshipNameGenerator.equals(that.relationshipNameGenerator) && this.fieldNameGenerator.equals(that.fieldNameGenerator) && this.target == that.target && this.defaultPackage.equals(that.defaultPackage) && this.path.equals(that.path) && this.indent.equals(that.indent) && this.clock.equals(that.clock) && this.addAtGenerated == that.addAtGenerated; } @Override public int hashCode() { return Objects.hash(this.typeNameDecorator, this.nodeNameGenerator, this.relationshipNameGenerator, this.fieldNameGenerator, this.target, this.defaultPackage, this.path, this.indent, this.clock, this.addAtGenerated); } /** * Returns {@literal true} if the type with the given name should be excluded. * @param qualifiedName the type name to be checked * @return {@literal true} if the type with the given name should be excluded */ boolean exclude(String qualifiedName) { return this.excludes.contains(qualifiedName); } /** * Enum for the available indent styles. */ public enum IndentStyle { /** Use tabs for indentation. */ TAB, /** Use a configurable number of spaces for indentation. */ SPACE } /** * The target Java baseline. */ public enum JavaVersion { /** * Generated code should compile on Java 8. */ RELEASE_8, /** * Generated code should compile on Java 11 or higher. */ RELEASE_11 } /** * Use this builder to create new * {@link org.neo4j.cypherdsl.core.renderer.Configuration} instances. */ @SuppressWarnings("HiddenField") public static final class Builder { private ClassNameGenerator nodeNameGenerator = new NodeNameGenerator(); private ClassNameGenerator relationshipNameGenerator = new RelationshipNameGenerator(); private FieldNameGenerator fieldNameGenerator = FieldNameGenerator.Default.INSTANCE; private JavaVersion target = JavaVersion.RELEASE_11; private String defaultPackage = ""; private Path path; private String prefix; private String suffix = "_"; private IndentStyle indentStyle = IndentStyle.TAB; private int indentSize = 2; private String timestamp; private boolean addAtGenerated = false; private Set excludes = Set.of(); private Builder() { } static Builder newConfig() { return new Builder(); } static Builder newConfig(final Path path) { return new Builder().withPath(path); } /** * Changes the node name generator. * @param nodeNameGenerator a new generator * @return this builder */ public Builder withNodeNameGenerator(ClassNameGenerator nodeNameGenerator) { if (nodeNameGenerator == null) { throw new IllegalArgumentException("A class name generator for nodes is required."); } this.nodeNameGenerator = nodeNameGenerator; return this; } /** * Changes the relationship name generator. * @param relationshipNameGenerator a new generator * @return this builder */ public Builder withRelationshipNameGenerator(ClassNameGenerator relationshipNameGenerator) { if (relationshipNameGenerator == null) { throw new IllegalArgumentException("A class name generator for relationships is required."); } this.relationshipNameGenerator = relationshipNameGenerator; return this; } /** * Changes the field name generator. * @param fieldNameGenerator a new generator * @return this builder * @since 2023.8.0 */ public Builder withFieldNameGenerator(FieldNameGenerator fieldNameGenerator) { if (fieldNameGenerator == null) { throw new IllegalArgumentException("A field name generator is required."); } this.fieldNameGenerator = fieldNameGenerator; return this; } /** * Configures the targeted Java version. * @param target the new target version * @return this builder */ public Builder withTarget(JavaVersion target) { if (target == null) { throw new IllegalArgumentException("A java version is required."); } this.target = target; return this; } /** * Configures the default target package. * @param defaultPackage the target package * @return this builder */ public Builder withDefaultPackage(String defaultPackage) { if (defaultPackage == null) { throw new IllegalArgumentException("A default package is required."); } this.defaultPackage = defaultPackage; return this; } /** * Configures a static timestamp for the builder to be used. * @param timestamp timestamp to write into the generated classes. Uses the * current time when no timestamp is given. Expected format is * {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME}. * @return this builder */ public Builder withTimestamp(String timestamp) { this.timestamp = timestamp; return this; } /** * A path is not always necessary, for example in an annotation processor. * @param path a path into which the files should be written * @return this builder */ public Builder withPath(Path path) { this.path = path; return this; } /** * Configure a prefix for the generated classes. Will only be used when no other * naming generator is configured. * @param prefix prepended to the names of generated classes. * @return this builder */ public Builder withPrefix(String prefix) { this.prefix = prefix; return this; } /** * Configure a suffix for the generated classes. Will only be used when no other * naming generator is configured. * @param suffix appended to the names of generated classes. * @return this builder */ public Builder withSuffix(String suffix) { this.suffix = suffix; return this; } /** * Should generated sources be marked as such. * @param addAtGenerated set to {@literal true} to mark generated sources as * generated * @return this builder */ public Builder withAddAtGenerated(boolean addAtGenerated) { this.addAtGenerated = addAtGenerated; return this; } /** * Configures the indentation style, aka Tabs vs. Spaces, I'll be watching. * @param indentStyle the style to use * @return this builder */ public Builder withIndentStyle(IndentStyle indentStyle) { if (indentStyle == null) { throw new IllegalArgumentException("Indent style is required."); } this.indentStyle = indentStyle; return this; } /** * Configures the indent size. * @param indentSize the number of indents to use * @return this builder */ public Builder withIndentSize(int indentSize) { this.indentSize = indentSize; return this; } /** * Configures the types to exclude. * @param excludes if {@literal null} or empty, no types will be excluded * @return this builder */ public Builder withExcludes(String excludes) { if (excludes == null || excludes.isBlank()) { this.excludes = Set.of(); } else { this.excludes = Arrays.stream(excludes.split(",")) .map(String::trim) .filter(Predicate.not(String::isBlank)) .collect(Collectors.toUnmodifiableSet()); } return this; } /** * {@return a new, immutable configuration} */ public Configuration build() { UnaryOperator typeNameDecorator; if (this.prefix == null && this.suffix == null) { typeNameDecorator = UnaryOperator.identity(); } else { typeNameDecorator = s -> ((this.prefix != null) ? this.prefix.trim() : "") + s + ((this.suffix != null) ? this.suffix.trim() : ""); } String indent; if (this.indentStyle == IndentStyle.TAB) { indent = "\t"; } else { indent = " ".repeat(Math.max(0, this.indentSize)); } Clock clock = null; if (this.timestamp != null && !this.timestamp.isEmpty()) { ZonedDateTime z = ZonedDateTime.from(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(this.timestamp)); clock = Clock.fixed(z.toInstant(), z.getZone()); } return new Configuration(typeNameDecorator, this.nodeNameGenerator, this.relationshipNameGenerator, this.fieldNameGenerator, this.target, this.defaultPackage, this.path, indent, clock, this.addAtGenerated, this.excludes); } } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/org/neo4j/cypherdsl/codegen/core/FieldNameGenerator.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; /** * A generator for constant field names. * * @author Michael J. Simons * @since 2021.1.0 */ @FunctionalInterface public interface FieldNameGenerator { /** * Generates a new name for constant field. * @param name the name of a node or relationship * @return a valid constant field name */ String generate(String name); /** * Single instance of the default {@link FieldNameGenerator}. */ enum Default implements FieldNameGenerator { /** * Singleton holder. */ INSTANCE; @Override public String generate(String name) { StringBuilder sb = new StringBuilder(); int codePoint; int previousIndex = 0; boolean prevWasLower = false; boolean nextIsLower = false; int i = 0; while (i < name.length()) { codePoint = name.codePointAt(i); if (Identifiers.isValidAt(i, codePoint)) { if (nextIsLower || Character.isLowerCase(codePoint)) { prevWasLower = true; nextIsLower = false; if (!(sb.isEmpty() || Character.isLetter(name.codePointAt(previousIndex)))) { sb.append("_"); } codePoint = Character.toUpperCase(codePoint); } else if (!sb.isEmpty() && (prevWasLower || i + 1 < name.length() && Character.isLowerCase(name.codePointAt(i + 1)))) { sb.append("_"); nextIsLower = true; } sb.append(Character.toChars(codePoint)); } previousIndex = i; i += Character.charCount(codePoint); } return sb.toString(); } } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/org/neo4j/cypherdsl/codegen/core/Identifiers.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; /** * Utilities for dealing with identifiers. * * @author Michael J. Simons */ final class Identifiers { private Identifiers() { } /** * A convenience method to decide whether a given {@code codePoint} is a valid Java * identifier at the given position {@code p}. * @param p position on which the {@code codePoint} is supposed to be used as * identifier * @param codePoint a codepoint * @return true if the codePoint could be used as part of an identifier at the given * position */ static boolean isValidAt(int p, int codePoint) { return p == 0 && Character.isJavaIdentifierStart(codePoint) || p > 0 && Character.isJavaIdentifierPart(codePoint); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/org/neo4j/cypherdsl/codegen/core/ModelBuilder.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import java.io.IOException; import java.nio.file.Path; import java.util.Collection; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.EXPERIMENTAL; /** * Shared interface for model builder. This interface and the concrete classes * implementing it is considered to be public API. * * @param a type representing the concrete model builder itself * @author Michael J. Simons * @since 2021.1.0 */ @API(status = EXPERIMENTAL, since = "2021.1.0") public interface ModelBuilder> { /** * Adds a single new property to this model. * @param newProperty the new property * @return this builder * @throws IllegalStateException when this builder has already been used to create * Java class. */ SELF addProperty(String newProperty); /** * Adds a single new property to this model. * @param newProperty the new property * @return this builder * @throws IllegalStateException when this builder has already been used to create * Java class. */ SELF addProperty(PropertyDefinition newProperty); /** * Adds a collection of properties to this model. * @param newProperties a new collection of properties * @return this builder * @throws IllegalStateException when this builder has already been used to create * Java class. */ SELF addProperties(Collection newProperties); /** * {@return the qualified package name or an empty string for the default package} */ String getPackageName(); /** * {@return the canonical class name} */ String getCanonicalClassName(); /** * {@return the simple class name without any pre- or suffix} */ String getPlainClassName(); /** * Triggers the creation of the final model and writes it as a Java class file to the * given path. It is safe to call this method several times or any of the other * {@code writeTo} methods, but changing the builder after this method has been called * will lead to an {@link IllegalStateException}. * @param path the path to write this model to. * @throws java.io.UncheckedIOException in case IO fails, any {@link IOException} is * wrapped and rethrown. */ void writeTo(Path path); /** * Triggers the creation of the final model and returns a Java class definition as * string. It is safe to call this method several times or any of the other * {@code writeTo} methods, but changing the builder after this method has been called * will lead to an {@link IllegalStateException}. * @return the generated class as a string * @throws java.io.UncheckedIOException in case IO fails, any {@link IOException} is * wrapped and rethrown. */ String writeToString(); /** * Triggers the creation of the final model and appends it to the given * {@link Appendable} as Java class. It is safe to call this method several times or * any of the other {@code writeTo} methods, but changing the builder after this * method has been called will lead to an {@link IllegalStateException}. *

* An appendable that is also closable will still be open after passing it to this * method. * @param appendable to where the generated source should be appended to * @throws java.io.UncheckedIOException in case IO fails, any {@link IOException} is * wrapped and rethrown. */ void writeTo(Appendable appendable); /** * Returns a field name suitable for storing an instance of the class generated by * * this builder. * @return a field name suitable for storing an instance of the class generated by * this builder */ String getFieldName(); } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/org/neo4j/cypherdsl/codegen/core/NodeImplBuilder.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.function.Function; import java.util.stream.Stream; import javax.lang.model.element.Modifier; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; import com.squareup.javapoet.WildcardTypeName; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; /** * This is a builder. It builds classes extending {@link NodeBase}. The workflow is as * follows: Create an instance via {@link #create(Configuration, String, String)}, * defining the target package (fully qualified name) as well as the type. Unless * {@link #writeTo(java.nio.file.Path)} or {@link #writeToString()} is called, additional * labels and properties can be added with {@link #addLabels(Collection)} and * {@link #addProperty(String)}. A call to any of the {@code writeToXXX} methods will * trigger the generation of source code and after that, this instance becomes effectively * immutable. * * @author Michael J. Simons * @author Andreas Berger * @since 2021.1.0 */ final class NodeImplBuilder extends AbstractModelBuilder implements NodeModelBuilder { private static final ClassName TYPE_NAME_NODE_LABEL = ClassName.get(NodeLabel.class); private final Set labels = new LinkedHashSet<>(); private final Set relationshipDefinitions = new LinkedHashSet<>(); private final Set relationshipMethodDefinitions = new LinkedHashSet<>(); private NodeModelBuilder baseModel; private TypeVariableName self; private boolean extensible; private NodeImplBuilder(Configuration configuration, ClassName className, String fieldName) { super(configuration.getConstantFieldNameGenerator(), className, fieldName, configuration.getTarget(), configuration.getIndent()); this.self = TypeVariableName.get("SELF", className); } static NodeModelBuilder create(Configuration configuration, String packageName, String suggestedTypeName) { String className = configuration.getNodeNameGenerator().generate(suggestedTypeName); String usedPackageName = (packageName != null) ? packageName : configuration.getDefaultPackage(); NodeImplBuilder builder = new NodeImplBuilder(configuration, ClassName.get(usedPackageName, configuration.getTypeNameDecorator().apply(className)), className); return builder.apply(configuration); } private static boolean isSelfReferential(RelationshipPropertyDefinition rpd) { return rpd.getStart() == rpd.getEnd(); } private static boolean isNotSelfReferential(RelationshipPropertyDefinition rpd) { return !isSelfReferential(rpd); } static String capitalize(String str) { if (str == null || str.isBlank()) { return str; } else { char baseChar = str.charAt(0); char updatedChar = Character.toUpperCase(baseChar); if (baseChar == updatedChar) { return str; } else { char[] chars = str.toCharArray(); chars[0] = updatedChar; return new String(chars); } } } @Override public NodeModelBuilder addLabel(String newLabel) { return addLabels((newLabel != null) ? Collections.singleton(newLabel) : Collections.emptyList()); } @Override public NodeModelBuilder addLabels(Collection newLabels) { return callOnlyWithoutJavaFilePresent(() -> { if (newLabels != null) { this.labels.addAll(newLabels); } return this; }); } @Override public NodeModelBuilder addRelationshipDefinition(RelationshipPropertyDefinition definition) { return callOnlyWithoutJavaFilePresent(() -> { if (definition != null) { this.relationshipDefinitions.add(definition); } return this; }); } @Override public NodeModelBuilder addRelationshipFactory(RelationshipFactoryDefinition definition) { return callOnlyWithoutJavaFilePresent(() -> { if (definition != null) { this.relationshipMethodDefinitions.add(definition); } return this; }); } @Override public NodeModelBuilder setBaseNodeModel(NodeModelBuilder baseNodeModel) { return callOnlyWithoutJavaFilePresent(() -> { this.baseModel = baseNodeModel; return this; }); } @Override public NodeModelBuilder setExtensible(boolean isExtensible) { return callOnlyWithoutJavaFilePresent(() -> { synchronized (NodeImplBuilder.this) { this.extensible = isExtensible; if (this.extensible) { this.self = TypeVariableName.get("SELF", ParameterizedTypeName.get(this.className, WildcardTypeName.subtypeOf(Object.class))); } else { this.self = TypeVariableName.get("SELF", this.className); } return this; } }); } @Override public boolean isExtensible() { return this.extensible; } private MethodSpec buildDefaultConstructor(FieldSpec primaryLabelField) { var superCallBuilder = CodeBlock.builder().add("super("); superCallBuilder.add(CodeBlock.of("$N", primaryLabelField)); if (this.labels.size() > 1) { superCallBuilder.add(", "); superCallBuilder .add(CodeBlock.join(this.labels.stream().skip(1).map(l -> CodeBlock.of("$S", l)).toList(), ", ")); } superCallBuilder.add(")"); return MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) .addStatement(superCallBuilder.build()) .build(); } private MethodSpec buildConstructorForInheritance(FieldSpec primaryLabelField) { var primaryLabelParam = ParameterSpec.builder(String.class, "primaryLabel").build(); var additionalLabels = ParameterSpec.builder(String[].class, "additionalLabels").build(); CodeBlock.Builder superCallBuilder = CodeBlock.builder().add("super("); superCallBuilder.add(CodeBlock.of("$N", primaryLabelParam)); superCallBuilder.add(", "); superCallBuilder.add(CodeBlock.of("$T.concat($T.stream($N), $T.of($N", Stream.class, Arrays.class, additionalLabels, Stream.class, primaryLabelField)); if (this.labels.size() > 1) { superCallBuilder.add(", "); superCallBuilder .add(CodeBlock.join(this.labels.stream().skip(1).map(l -> CodeBlock.of("$S", l)).toList(), ", ")); } superCallBuilder.add(")).toArray($T[]::new))", String.class); return MethodSpec.constructorBuilder() .addModifiers(Modifier.PROTECTED) .addParameter(primaryLabelParam) .addParameter(additionalLabels) .varargs() .addStatement(superCallBuilder.build()) .build(); } private MethodSpec buildCreateMethod() { ParameterSpec symbolicName = ParameterSpec.builder(TYPE_NAME_SYMBOLIC_NAME, "symbolicName").build(); ParameterSpec labelsParameter = ParameterSpec .builder(ParameterizedTypeName.get(TYPE_NAME_LIST, TYPE_NAME_NODE_LABEL), "labels") .build(); ParameterSpec properties = ParameterSpec.builder(ClassName.get(Properties.class), "properties").build(); MethodSpec.Builder builder = MethodSpec.methodBuilder("create") .addModifiers(Modifier.PROTECTED) .addParameter(symbolicName) .addParameter(labelsParameter) .addParameter(properties); if (this.baseModel != null) { builder.addAnnotation(Override.class); } if (this.extensible) { builder.addAnnotation( AnnotationSpec.builder(SuppressWarnings.class).addMember("value", "\"unchecked\"").build()); builder .addStatement("return ($T) new $T<>($N, $N, $N)", this.self, this.className, symbolicName, labelsParameter, properties) .returns(this.self); } else { builder.addStatement("return new $T($N, $N, $N)", this.className, symbolicName, labelsParameter, properties) .returns(this.className); } return builder.build(); } private MethodSpec buildCopyConstructor() { ParameterSpec symbolicName = ParameterSpec.builder(TYPE_NAME_SYMBOLIC_NAME, "symbolicName").build(); ParameterSpec labelsParameter = ParameterSpec .builder(ParameterizedTypeName.get(TYPE_NAME_LIST, TYPE_NAME_NODE_LABEL), "labels") .build(); ParameterSpec properties = ParameterSpec.builder(ClassName.get(Properties.class), "properties").build(); return MethodSpec.constructorBuilder() .addModifiers(this.extensible ? Modifier.PROTECTED : Modifier.PRIVATE) .addParameter(symbolicName) .addParameter(labelsParameter) .addParameter(properties) .addStatement("super($N, $N, $N)", symbolicName, labelsParameter, properties) .build(); } private MethodSpec buildNamedMethod() { ParameterSpec newSymbolicName = ParameterSpec.builder(TYPE_NAME_SYMBOLIC_NAME, "newSymbolicName").build(); MethodSpec.Builder builder = MethodSpec.methodBuilder("named") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) .addParameter(newSymbolicName); if (this.extensible) { builder.returns(this.self).addStatement("return create($N, getLabels(), getProperties())", newSymbolicName); } else { builder.returns(this.className) .addStatement("return new $T($N, getLabels(), getProperties())", this.className, newSymbolicName); } return builder.build(); } private MethodSpec buildWithPropertiesMethod() { ParameterSpec newProperties = ParameterSpec.builder(TYPE_NAME_MAP_EXPRESSION, "newProperties").build(); MethodSpec.Builder builder = MethodSpec.methodBuilder("withProperties") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) .addParameter(newProperties); if (this.extensible) { builder.returns(this.self) .addStatement("return create(getSymbolicName().orElse(null), getLabels(), Properties.create($N))", newProperties); } else { builder.returns(this.className) .addStatement("return new $T(getSymbolicName().orElse(null), getLabels(), Properties.create($N))", this.className, newProperties); } return builder.build(); } private List buildFields(FieldSpec primaryLabelField) { FieldSpec defaultInstance; if (this.extensible) { var type = ParameterizedTypeName.get(this.className, ParameterizedTypeName.get(this.className, WildcardTypeName.subtypeOf(Object.class))); defaultInstance = FieldSpec.builder(type, getFieldName(), Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) .initializer("new $T<>()", this.className) .build(); } else { defaultInstance = FieldSpec .builder(this.className, getFieldName(), Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) .initializer("new $T()", this.className) .build(); } Stream properties = generateFieldSpecsFromProperties(); Stream relationships = this.relationshipDefinitions.stream() .filter(NodeImplBuilder::isNotSelfReferential) .map(p -> { String fieldName = this.fieldNameGenerator .generate((p.getNameInDomain() != null) ? p.getNameInDomain() : p.getType()); ClassName startClass = extractClassName(p.getStart()); ClassName endClass = extractClassName(p.getEnd()); var relationshipClassName = getRelationTypeWithParameters(p.getRelationshipBuilder(), startClass, endClass); FieldSpec.Builder builder = FieldSpec.builder(relationshipClassName, fieldName, Modifier.PUBLIC, Modifier.FINAL); if (this == p.getStart()) { builder.initializer("new $T(this, $T.$N)", relationshipClassName, endClass, p.getEnd().getFieldName()); } else { builder.initializer("new $T($T.$N, this)", relationshipClassName, startClass, p.getStart().getFieldName()); } return builder.build(); }); return Stream.concat(Stream.of(primaryLabelField, defaultInstance), Stream.concat(properties, relationships)) .toList(); } @Override protected JavaFile buildJavaFile() { if (this.labels.isEmpty()) { throw new IllegalStateException("Cannot build NodeImpl without labels!"); } var baseNode = (this.baseModel != null) ? extractClassName(this.baseModel) : TYPE_NAME_NODE_BASE; if (this.baseModel != null && (!(this.baseModel instanceof NodeImplBuilder nmb) || !nmb.extensible)) { throw new IllegalStateException( "Cannot extend non-extensible node " + this.baseModel.getCanonicalClassName()); } var primaryLabelField = FieldSpec .builder(String.class, this.fieldNameGenerator.generate("$PRIMARY_LABEL"), Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC) .initializer("$S", this.labels.stream().findFirst().orElseThrow()) .build(); TypeSpec.Builder builder = addGenerated(TypeSpec.classBuilder(this.className)); builder.addModifiers(Modifier.PUBLIC); if (this.extensible) { builder.addTypeVariable(this.self).superclass(ParameterizedTypeName.get(baseNode, this.self)); } else { builder.addModifiers(Modifier.FINAL).superclass(ParameterizedTypeName.get(baseNode, this.className)); } builder.addFields(buildFields(primaryLabelField)).addMethod(buildDefaultConstructor(primaryLabelField)); if (this.extensible) { builder.addMethod(buildConstructorForInheritance(primaryLabelField)); } if (this.extensible || baseNode != TYPE_NAME_NODE_BASE) { builder.addMethod(buildCreateMethod()); } builder.addMethod(buildCopyConstructor()); if (baseNode == TYPE_NAME_NODE_BASE) { builder.addMethod(buildNamedMethod()).addMethod(buildWithPropertiesMethod()); } this.relationshipDefinitions.stream() .filter(NodeImplBuilder::isSelfReferential) .map(buildSelfReferentialAccessor()) .forEach(builder::addMethod); this.relationshipMethodDefinitions.stream().map(buildRelationshipFactoryMethod()).forEach(builder::addMethod); TypeSpec newType = builder.build(); return prepareFileBuilder(newType).build(); } private TypeName getRelationTypeWithParameters(RelationshipModelBuilder relationshipBuilder, TypeName startClass, ClassName endClass) { TypeName relationshipClassName; if (relationshipBuilder instanceof RelationshipImplBuilder impl) { relationshipClassName = impl.getTypeName(startClass, endClass); } else { relationshipClassName = extractClassName(relationshipBuilder); } return relationshipClassName; } private Function buildSelfReferentialAccessor() { return p -> { ClassName relationshipClassName = extractClassName(p.getRelationshipBuilder()); ParameterSpec end = ParameterSpec.builder(this.className, p.getNameInDomain()).build(); return MethodSpec.methodBuilder("with" + capitalize(p.getNameInDomain())) .addModifiers(Modifier.PUBLIC) .returns(relationshipClassName) .addParameter(end) .addStatement("return new $T(this, $N)", relationshipClassName, end) .build(); }; } private Function buildRelationshipFactoryMethod() { return p -> { ClassName startClass = extractClassName(p.getStart()); ClassName endClass = extractClassName(p.getEnd()); TypeName relationshipClassName = getRelationTypeWithParameters(p.getRelationshipBuilder(), startClass, endClass); var b = MethodSpec.methodBuilder(p.getNameInDomain()) .addModifiers(Modifier.PUBLIC) .returns(relationshipClassName); if (this == p.getStart()) { ParameterSpec param = ParameterSpec.builder(endClass, "end").build(); b.addParameter(param).addStatement("return new $T(this, $N)", relationshipClassName, param); } else { ParameterSpec param = ParameterSpec.builder(startClass, "start").build(); b.addParameter(param).addStatement("return new $T($N, this)", relationshipClassName, param); } return b.build(); }; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/org/neo4j/cypherdsl/codegen/core/NodeModelBuilder.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import java.util.Collection; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.EXPERIMENTAL; /** * Helper class to create static code describing labels and properties of a * {@code org.neo4j.cypherdsl.core.Node} object. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = EXPERIMENTAL, since = "2021.1.0") public interface NodeModelBuilder extends ModelBuilder { /** * Get a new instance of a node model builder. The instance can be modified as long as * no {@code writeToXXX} method has been called. * @param configuration the generators configuration * @param packageName a package name, which can be null. In that case, the default * package name from the configuration is taken * @param suggestedTypeName a suggested type name * @return a new instance without any labels or properties registered. */ static NodeModelBuilder create(Configuration configuration, String packageName, String suggestedTypeName) { return NodeImplBuilder.create(configuration, packageName, suggestedTypeName); } /** * Adds a new label to this builder. * @param newLabel the new label * @return this builder * @throws IllegalStateException when this builder has already been used to create * Java class. */ NodeModelBuilder addLabel(String newLabel); /** * Adds several new labels to this builder. * @param newLabels the list of new labels * @return this builder * @throws IllegalStateException when this builder has already been used to create * Java class. */ NodeModelBuilder addLabels(Collection newLabels); /** * Adds a relationship definition to this builder. * @param definition the definition of a relationship * @return this builder * @throws IllegalStateException when this builder has already been used to create * Java class. */ NodeModelBuilder addRelationshipDefinition(RelationshipPropertyDefinition definition); /** * Adds a factory method for a relationship to this builder. * @param definition the definition of the relationship * @return this builder * @throws IllegalStateException when this builder has already been used to create * Java class. * @since 2023.9.0 */ NodeModelBuilder addRelationshipFactory(RelationshipFactoryDefinition definition); /** * Adds a model this model should extend. * @param baseModel the model to extend * @return this builder * @throws IllegalStateException when this builder has already been used to create * Java class. * @since 2023.9.2 */ NodeModelBuilder setBaseNodeModel(NodeModelBuilder baseModel); /** * Returns {@literal true} when this builder produces extensible modules. * @return {@literal true} when this builder produces extensible modules */ boolean isExtensible(); /** * If true, this model will be extensible. * @param extensible true if this model should be extensible * @return this builder * @throws IllegalStateException when this builder has already been used to create * Java class. * @since 2023.9.2 */ NodeModelBuilder setExtensible(boolean extensible); } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/org/neo4j/cypherdsl/codegen/core/NodeNameGenerator.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.INTERNAL; /** * Default generator for creating names of node classes. Just creates a valid java * identifier from a suggested name. No further optimization is done. Suggested name can * be a list of labels etc. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = INTERNAL, since = "2021.1.0") final class NodeNameGenerator extends AbstractClassNameGenerator implements ClassNameGenerator { @Override public String generate(String suggestedName) { return generateTypeName(suggestedName).toString(); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/org/neo4j/cypherdsl/codegen/core/PropertyDefinition.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import java.util.Objects; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.EXPERIMENTAL; /** * Represents a property name: With a name in the graph and an optional name in the domain * model. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = EXPERIMENTAL, since = "2021.1.0") public final class PropertyDefinition { /** * Name of the property inside the graph. */ private final String nameInGraph; /** * Optional name of the property inside the domain. */ private final String nameInDomain; private PropertyDefinition(String nameInGraph, String nameInDomain) { this.nameInGraph = nameInGraph; this.nameInDomain = nameInDomain; } /** * Creates a new property definition. * @param nameInGraph required name in graph. * @param optionalNameInDomain optional in domain * @return a working definition */ public static PropertyDefinition create(String nameInGraph, String optionalNameInDomain) { if (nameInGraph == null || nameInGraph.isBlank()) { throw new IllegalArgumentException("The name of the property in the graph must not be null or empty."); } return new PropertyDefinition(nameInGraph, optionalNameInDomain); } /** * {@return the name as defined in the future graph} */ public String getNameInGraph() { return this.nameInGraph; } /** * {@return the name as defined in the domain object (most likely the field name)} */ public String getNameInDomain() { return this.nameInDomain; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } PropertyDefinition that = (PropertyDefinition) o; return this.nameInGraph.equals(that.nameInGraph) && Objects.equals(this.nameInDomain, that.nameInDomain); } @Override public int hashCode() { return Objects.hash(this.nameInGraph, this.nameInDomain); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/org/neo4j/cypherdsl/codegen/core/RelationshipFactoryDefinition.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.EXPERIMENTAL; /** * This represents a method in the static metamodel that creates a relationship. * * @author Andreas Berger * @since 2023.9.0 */ @API(status = EXPERIMENTAL, since = "2023.9.0") public final class RelationshipFactoryDefinition { /** * Optional name in the domain, for example a field name. */ private final String nameInDomain; /** * start of the relationship. */ private final NodeModelBuilder start; /** * end of the relationship. */ private final NodeModelBuilder end; /** * The actual builder that defines this relationship. */ private final RelationshipModelBuilder relationshipBuilder; private RelationshipFactoryDefinition(String nameInDomain, RelationshipModelBuilder relationshipBuilder, NodeModelBuilder start, NodeModelBuilder end) { this.nameInDomain = nameInDomain; this.start = start; this.end = end; this.relationshipBuilder = relationshipBuilder; } /** * Creates a new definition. * @param nameInDomain the name of the relationship in the domain class * @param start builder for the start node in the domain * @param end builder for the end node in the domain * @return a valid definition */ public static RelationshipFactoryDefinition create(String nameInDomain, NodeModelBuilder start, NodeModelBuilder end) { return new RelationshipFactoryDefinition(nameInDomain, null, start, end); } /** * Creates a new relationship definition with a new builder for it. * @param newBuilder the new builder to use * @return a new instance, {@literal this} won't change */ public RelationshipFactoryDefinition withBuilder(RelationshipModelBuilder newBuilder) { return new RelationshipFactoryDefinition(this.nameInDomain, newBuilder, this.start, this.end); } /** * Returns the name in the domain model (most likely the field name). * @return the name in the domain model */ public String getNameInDomain() { return this.nameInDomain; } /** * {@return a builder for the start node} */ public NodeModelBuilder getStart() { return this.start; } /** * {@return a builder for the end node} */ public NodeModelBuilder getEnd() { return this.end; } RelationshipModelBuilder getRelationshipBuilder() { return this.relationshipBuilder; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/org/neo4j/cypherdsl/codegen/core/RelationshipImplBuilder.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collection; import java.util.Deque; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.lang.model.element.Modifier; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; import com.squareup.javapoet.WildcardTypeName; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import static org.apiguardian.api.API.Status.INTERNAL; /** * This is a builder. It builds classes extending {@link RelationshipImplBuilder}. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = INTERNAL, since = "2021.1.0") final class RelationshipImplBuilder extends AbstractModelBuilder implements RelationshipModelBuilder { private static final ClassName TYPE_NAME_RELATIONSHIP_BASE = ClassName.get(RelationshipBase.class); private static final TypeVariableName S = TypeVariableName.get("S", ParameterizedTypeName.get(TYPE_NAME_NODE_BASE, WildcardTypeName.subtypeOf(Object.class))); private static final TypeVariableName E = TypeVariableName.get("E", ParameterizedTypeName.get(TYPE_NAME_NODE_BASE, WildcardTypeName.subtypeOf(Object.class))); /** * The required relationship type. */ private final FieldSpec relationshipTypeField; /** * The possible start- and end-node connections. */ private final Deque edges = new ArrayDeque<>(); private RelationshipImplBuilder(Configuration configuration, String relationshipType, ClassName className, String fieldName) { super(configuration.getConstantFieldNameGenerator(), className, fieldName, configuration.getTarget(), configuration.getIndent()); if (relationshipType == null) { throw new IllegalStateException("Cannot build a RelationshipImpl without a single relationship type!"); } this.relationshipTypeField = FieldSpec .builder(String.class, configuration.getConstantFieldNameGenerator().generate("$TYPE"), Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC) .initializer("$S", relationshipType) .build(); } static RelationshipModelBuilder create(Configuration configuration, String packageName, String relationshipType, String alternateClassNameSuggestion) { String className = configuration.getRelationshipNameGenerator() .generate((alternateClassNameSuggestion != null) ? alternateClassNameSuggestion : relationshipType); String usedPackageName = (packageName != null) ? packageName : configuration.getDefaultPackage(); RelationshipImplBuilder builder = new RelationshipImplBuilder(configuration, relationshipType, ClassName.get(usedPackageName, configuration.getTypeNameDecorator().apply(className)), className); return builder.apply(configuration); } static Edge pickDefault(Collection relations) { var startNodes = relations.stream().map(edge -> edge.start).collect(Collectors.toSet()); var endNodes = relations.stream().map(edge -> edge.end).collect(Collectors.toSet()); var expectedStartNode = (startNodes.size() == 1) ? startNodes.iterator().next() : S; var expectedEndNode = (endNodes.size() == 1) ? endNodes.iterator().next() : E; return new Edge(expectedStartNode, expectedEndNode); } @Override public RelationshipModelBuilder setStartNode(NodeModelBuilder newStartNode) { if (newStartNode == null) { return this; } return callOnlyWithoutJavaFilePresent(() -> { var lastRelation = getOrCreateLastDefinedRelationType(); lastRelation.start = extractClassName(newStartNode); return this; }); } @Override public RelationshipModelBuilder setEndNode(NodeModelBuilder newEndNode) { if (newEndNode == null) { return this; } return callOnlyWithoutJavaFilePresent(() -> { var lastRelation = getOrCreateLastDefinedRelationType(); lastRelation.end = extractClassName(newEndNode); return this; }); } private Edge getOrCreateLastDefinedRelationType() { var lastRelation = this.edges.peek(); if (lastRelation == null) { lastRelation = new Edge(S, E); this.edges.push(lastRelation); } return lastRelation; } private MethodSpec buildConstructor(Edge relation, Edge expectedTypes) { ParameterSpec startNodeParam = ParameterSpec.builder(relation.start, "start").build(); ParameterSpec endNodeParam = ParameterSpec.builder(relation.end, "end").build(); MethodSpec.Builder builder = MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) .addParameter(startNodeParam) .addParameter(endNodeParam); if (relation.start != expectedTypes.start) { if (relation.end != expectedTypes.end) { builder.addStatement("super(($T) $N, $N, ($T) $N)", expectedTypes.start, startNodeParam, this.relationshipTypeField, expectedTypes.end, endNodeParam); } else { builder.addStatement("super(($T) $N, $N, $N)", expectedTypes.start, startNodeParam, this.relationshipTypeField, endNodeParam); } } else if (relation.end != expectedTypes.end) { builder.addStatement("super($N, $N, ($T) $N)", startNodeParam, this.relationshipTypeField, expectedTypes.end, endNodeParam); } else { builder.addStatement("super($N, $N, $N)", startNodeParam, this.relationshipTypeField, endNodeParam); } return builder.build(); } @Override public RelationshipModelBuilder addRelationship(NodeModelBuilder newStartNode, NodeModelBuilder newEndNode) { return callOnlyWithoutJavaFilePresent(() -> { TypeName startNode = S; if (newStartNode != null) { startNode = newStartNode.isExtensible() ? ParameterizedTypeName.get(extractClassName(newStartNode), WildcardTypeName.subtypeOf(Object.class)) : extractClassName(newStartNode); } TypeName endNode = E; if (newEndNode != null) { endNode = newEndNode.isExtensible() ? ParameterizedTypeName.get(extractClassName(newEndNode), WildcardTypeName.subtypeOf(Object.class)) : extractClassName(newEndNode); } this.edges.add(new Edge(startNode, endNode)); return this; }); } private MethodSpec buildCopyConstructor() { ParameterSpec symbolicName = ParameterSpec.builder(TYPE_NAME_SYMBOLIC_NAME, "symbolicName").build(); ParameterSpec start = ParameterSpec.builder(TYPE_NAME_NODE, "start").build(); ParameterSpec properties = ParameterSpec.builder(ClassName.get(Properties.class), "properties").build(); ParameterSpec end = ParameterSpec.builder(TYPE_NAME_NODE, "end").build(); return MethodSpec.constructorBuilder() .addModifiers(Modifier.PRIVATE) .addParameters(Arrays.asList(symbolicName, start, properties, end)) .addStatement("super($N, $N, $N, $N, $N)", symbolicName, start, this.relationshipTypeField, properties, end) .build(); } private MethodSpec buildNamedMethod(TypeName typeName) { ParameterSpec newSymbolicName = ParameterSpec.builder(TYPE_NAME_SYMBOLIC_NAME, "newSymbolicName").build(); return MethodSpec.methodBuilder("named") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) .returns(typeName) .addParameter(newSymbolicName) .addStatement("return new $T$L($N, getLeft(), getDetails().getProperties(), getRight())", super.className, (typeName == super.className) ? "" : "<>", newSymbolicName) .build(); } private MethodSpec buildWithPropertiesMethod(TypeName typeName) { ParameterSpec newProperties = ParameterSpec.builder(TYPE_NAME_MAP_EXPRESSION, "newProperties").build(); return MethodSpec.methodBuilder("withProperties") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) .returns(typeName) .addParameter(newProperties) .addStatement( "return new $T$L(getSymbolicName().orElse(null), getLeft(), Properties.create($N), getRight())", super.className, (typeName == super.className) ? "" : "<>", newProperties) .build(); } private List buildFields() { return Stream.concat(Stream.of(this.relationshipTypeField), generateFieldSpecsFromProperties()).toList(); } @Override protected JavaFile buildJavaFile() { var expectedTypes = pickDefault(this.edges); var builder = TypeSpec.classBuilder(super.className); var typeName = getTypeName(expectedTypes); if (typeName instanceof ParameterizedTypeName pt) { for (TypeName t : pt.typeArguments) { if (t instanceof TypeVariableName tn) { builder.addTypeVariable(tn); } } } addGenerated(builder) .superclass(ParameterizedTypeName.get(TYPE_NAME_RELATIONSHIP_BASE, expectedTypes.start, expectedTypes.end, typeName)) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addFields(buildFields()); (this.edges.isEmpty() ? Stream.of(new Edge(S, E)) : this.edges.stream()) .map(relation -> buildConstructor(relation, expectedTypes)) .forEach(builder::addMethod); var newType = builder.addMethod(buildCopyConstructor()) .addMethod(buildNamedMethod(typeName)) .addMethod(buildWithPropertiesMethod(typeName)) .build(); return prepareFileBuilder(newType).build(); } private TypeName getTypeName(Edge expectedTypes) { return getTypeName(expectedTypes, null, null); } TypeName getTypeName(TypeName concreteStartType, TypeName concreteEndType) { return getTypeName(pickDefault(this.edges), concreteStartType, concreteEndType); } private TypeName getTypeName(Edge expectedEdge, TypeName concreteStartType, TypeName concreteEndType) { var concreteOrDefaultStart = (concreteStartType != null) ? concreteStartType : S; var concreteOrDefaultEnd = (concreteEndType != null) ? concreteEndType : E; if (expectedEdge.start == S && expectedEdge.end == E) { return ParameterizedTypeName.get(super.className, concreteOrDefaultStart, concreteOrDefaultEnd); } else if (expectedEdge.start == S) { return ParameterizedTypeName.get(super.className, concreteOrDefaultStart); } else if (expectedEdge.end == E) { return ParameterizedTypeName.get(super.className, concreteOrDefaultEnd); } else { return super.className; } } private static final class Edge { TypeName start; TypeName end; private Edge(TypeName start, TypeName end) { this.start = start; this.end = end; } } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/org/neo4j/cypherdsl/codegen/core/RelationshipModelBuilder.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.RelationshipBase; import static org.apiguardian.api.API.Status.EXPERIMENTAL; /** * A variant of a {@link ModelBuilder} responsible for creating classes extending from * {@link RelationshipBase} that will in the end represent a static model of relationships * and their properties. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = EXPERIMENTAL, since = "2021.1.0") public interface RelationshipModelBuilder extends ModelBuilder { /** * Start building a new {@link RelationshipModelBuilder}. * @param configuration configuration of the generator * @param packageName the package name into the model should be generated * @param relationshipType the type of the relationship * @return the new builder */ static RelationshipModelBuilder create(Configuration configuration, String packageName, String relationshipType) { return create(configuration, packageName, relationshipType, null); } /** * Start building a new {@link RelationshipModelBuilder}, including alternate name * suggestions. * @param configuration configuration of the generator * @param packageName the package name into the model should be generated * @param relationshipType the type of the relationship * @param alternateClassNameSuggestion an alternative suggestion for the class name * @return the new builder */ static RelationshipModelBuilder create(Configuration configuration, String packageName, String relationshipType, String alternateClassNameSuggestion) { return RelationshipImplBuilder.create(configuration, packageName, relationshipType, alternateClassNameSuggestion); } /** * Registers the start node of a relationship with this builder. Will add a new * relationship to this builder if none is present, will modify the last one if this * method or {@link #setEndNode(NodeModelBuilder)} or * {@link #addRelationship(NodeModelBuilder, NodeModelBuilder)} has already been used. *

* A wild card will be emitted for the generated class instead of a concrete class so * that this relationship model suites several different relationships with the same * type. * @param startNode the new start node, may be null * @return this builder * @throws IllegalStateException when this builder has already been used to create * Java class. */ RelationshipModelBuilder setStartNode(NodeModelBuilder startNode); /** * Registers the end node of the relationship with this builder. Will add a new * relationship to this builder if none is present, will modify the last one if this * method or {@link #setStartNode(NodeModelBuilder)} or * {@link #addRelationship(NodeModelBuilder, NodeModelBuilder)} has already been used. *

* A wild card will be emitted for the generated class instead of a concrete class so * that this relationship model suites several different relationships with the same * type. * @param endNode the new start node, may be null * @return this builder * @throws IllegalStateException when this builder has already been used to create * Java class. */ RelationshipModelBuilder setEndNode(NodeModelBuilder endNode); /** * Registers a relationship between start and end node with this builder. * @param startNode the new start node, may be null * @param endNode the new start node, may be null * @return this builder * @throws IllegalStateException when this builder has already been used to create * Java class. * @since 2023.9.0 */ RelationshipModelBuilder addRelationship(NodeModelBuilder startNode, NodeModelBuilder endNode); } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/org/neo4j/cypherdsl/codegen/core/RelationshipNameGenerator.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.INTERNAL; /** * Default generator for creating names of relationship classes. It uses the given type as * class name, generates a valid type from it and then tries to derive a camel cased name. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = INTERNAL, since = "2021.1.0") final class RelationshipNameGenerator extends AbstractClassNameGenerator implements ClassNameGenerator { @Override public String generate(String suggestedName) { final StringBuilder sb = generateTypeName(suggestedName); int i = 0; int last = 0; while (i <= sb.length()) { if (i == sb.length() || sb.charAt(i) == '_') { char copy = sb.charAt(last); sb.setCharAt(last, Character.toUpperCase(sb.charAt(last))); while (++last < i) { boolean flip = last + 1 == i && Character.isUpperCase(copy) || last + 1 < i && Character.isUpperCase(sb.charAt(last + 1)); copy = sb.charAt(last); if (flip) { sb.setCharAt(last, Character.toLowerCase(sb.charAt(last))); } } last = i; } if (i != sb.length() && sb.charAt(i) == '_') { sb.deleteCharAt(i); } ++i; } return sb.toString(); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/org/neo4j/cypherdsl/codegen/core/RelationshipPropertyDefinition.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.EXPERIMENTAL; /** * This represents a property in the static metamodel for a relationship. Such a property * can refer to several other {@link PropertyDefinition property definitions} on its own * when we detect properties stored on the relationship. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = EXPERIMENTAL, since = "2021.1.0") public final class RelationshipPropertyDefinition { /** * A set of properties to generate. */ private final Set properties; /** * Relationship type as stored in the database. */ private final String type; private final String optionalPropertyHolder; /** * Optional name in the domain, for example a field name. */ private final String nameInDomain; /** * Start of the relationship. Generated, static relationships are always left to right * (start to end). */ private final NodeModelBuilder start; /** * end of the relationship. Generated, static relationships are always left to right * (start to end). */ private final NodeModelBuilder end; /** * The actual builder that defines this relationship. */ private RelationshipModelBuilder relationshipBuilder; private RelationshipPropertyDefinition(String type, String optionalPropertyHolder, String nameInDomain, NodeModelBuilder start, RelationshipModelBuilder relationshipBuilder, NodeModelBuilder end, Set properties) { this.type = type; this.optionalPropertyHolder = optionalPropertyHolder; this.nameInDomain = nameInDomain; this.start = start; this.end = end; this.relationshipBuilder = relationshipBuilder; this.properties = properties; } /** * Creates a new definition. * @param type the type of the relationship as stored in the database * @param optionalPropertyHolder the class name of the holder of the relationships * properties * @param nameInDomain the name of the relationship in the domain class * @param start builder for the start node in the domain * @param end builder for the end node in the domain * @param optionalProperties a collection of properties, maybe null or empty * @return a valid definition */ public static RelationshipPropertyDefinition create(String type, String optionalPropertyHolder, String nameInDomain, NodeModelBuilder start, NodeModelBuilder end, Collection optionalProperties) { return new RelationshipPropertyDefinition(type, optionalPropertyHolder, nameInDomain, start, null, end, (optionalProperties != null) ? new HashSet<>(optionalProperties) : Collections.emptySet()); } /** * Creates a new relationship definition with a new builder for it. * @param newBuilder the new builder to use * @return a new instance, {@literal this} won't change */ public RelationshipPropertyDefinition withBuilder(RelationshipModelBuilder newBuilder) { return new RelationshipPropertyDefinition(this.type, this.optionalPropertyHolder, this.nameInDomain, this.start, newBuilder, this.end, this.properties); } /** * {@return the type of the relationship} */ public String getType() { return this.type; } /** * Returns the name in the domain model (most likely the field name). * @return the name in the domain model */ public String getNameInDomain() { return this.nameInDomain; } /** * {@return a builder for the start node} */ public NodeModelBuilder getStart() { return this.start; } /** * {@return a builder for the end node} */ public NodeModelBuilder getEnd() { return this.end; } RelationshipModelBuilder getRelationshipBuilder() { return this.relationshipBuilder; } /** * {@return a set of properties on this relationship} */ public Set getProperties() { return Collections.unmodifiableSet(this.properties); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/main/java/org/neo4j/cypherdsl/codegen/core/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * Basic infrastructure for creating static metamodels based on the Cypher-DSL. */ package org.neo4j.cypherdsl.codegen.core; ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/test/java/org/neo4j/cypherdsl/codegen/core/ConfigurationTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import java.util.function.UnaryOperator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class ConfigurationTests { @Test // GH-829 void fieldNameGeneratorShouldBeConfigurable() { Configuration configuration = Configuration.newConfig().withFieldNameGenerator(name -> "Wurst" + name).build(); assertThat(configuration.getConstantFieldNameGenerator().generate("salat")).isEqualTo("Wurstsalat"); } @Nested class TypeNameDecoratorTest { @Test void typeNameDecoratorShouldDefaultToIdentity() { Configuration configuration = Configuration.newConfig().withSuffix(null).build(); assertThat(configuration.getTypeNameDecorator()).isSameAs(UnaryOperator.identity()); } @Test void prefixShouldBeApplied() { Configuration configuration = Configuration.newConfig().withPrefix("p").build(); assertThat(configuration.getTypeNameDecorator().apply("v")).isEqualTo("pv_"); } @Test void suffixShouldBeNullable() { Configuration configuration = Configuration.newConfig().withPrefix("p").withSuffix(null).build(); assertThat(configuration.getTypeNameDecorator().apply("v")).isEqualTo("pv"); } @Test void suffixShouldBeApplied() { Configuration configuration = Configuration.newConfig().withSuffix("s").build(); assertThat(configuration.getTypeNameDecorator().apply("v")).isEqualTo("vs"); } } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/test/java/org/neo4j/cypherdsl/codegen/core/ConstantFieldNamingStrategyTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class ConstantFieldNamingStrategyTests { @ParameterizedTest @CsvSource({ "aName, A_NAME", "AName, A_NAME", "BAZ_BAR, BAZ_BAR", "aName, A_NAME", "ANumberedNam3, A_NUMBERED_NAM_3", "Foo3Bar, FOO_3_BAR", "Foo3BaR, FOO_3_BA_R", "foo3BaR, FOO_3_BA_R", "🖖someThing, SOME_THING", "$someThing, $_SOME_THING", "$$some33Thing, $_$_SOME_3_3_THING", "🧐someThing✋x, SOME_THING_X", "🧐someThing✋✋X, SOME_THING_X" }) void toConstantFieldNameShouldWork(String name, String expectedEscapedName) { assertThat(FieldNameGenerator.Default.INSTANCE.generate(name)).isEqualTo(expectedEscapedName); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/test/java/org/neo4j/cypherdsl/codegen/core/ModelBuilderTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import java.io.File; import java.net.URISyntaxException; import java.util.List; import java.util.Locale; import com.google.testing.compile.CompilationSubject; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * @author Andreas Berger */ class ModelBuilderTests { @Test void testGeneratingRelationWithMultipleStartAndEndNodes() { var configuration = Configuration.newConfig().build(); var a = NodeModelBuilder.create(configuration, null, "A").addLabel("A"); var b = NodeModelBuilder.create(configuration, null, "B").addLabel("B").addLabel("X"); var c = NodeModelBuilder.create(configuration, null, "C").addLabel("C"); var rel = RelationshipModelBuilder.create(configuration, null, "BELONGS_TO") .addRelationship(a, b) .addRelationship(a, c) .addRelationship(c, b); a.addRelationshipDefinition( RelationshipPropertyDefinition.create("BELONGS_TO", null, "belongsTo", a, b, null).withBuilder(rel)) .addRelationshipFactory(RelationshipFactoryDefinition.create("belongsTo", a, b).withBuilder(rel)) .addRelationshipFactory(RelationshipFactoryDefinition.create("belongsTo", a, c).withBuilder(rel)); b.addRelationshipDefinition( RelationshipPropertyDefinition.create("BELONGS_TO", null, "belongsTo", a, b, null).withBuilder(rel)) .addRelationshipFactory(RelationshipFactoryDefinition.create("belongsTo", a, b).withBuilder(rel)); assertThat(a.writeToString()).isEqualTo(""" import java.util.List; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; /** * This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration. */ public final class A_ extends NodeBase { public static final String $PRIMARY_LABEL = "A"; public static final A_ A = new A_(); public final BelongsTo_ BELONGS_TO = new BelongsTo_(this, B_.B); public A_() { super($PRIMARY_LABEL); } private A_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public A_ named(SymbolicName newSymbolicName) { return new A_(newSymbolicName, getLabels(), getProperties()); } @Override public A_ withProperties(MapExpression newProperties) { return new A_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } public BelongsTo_ belongsTo(B_ end) { return new BelongsTo_(this, end); } public BelongsTo_ belongsTo(C_ end) { return new BelongsTo_(this, end); } } """); assertThat(b.writeToString()).isEqualTo(""" import java.util.List; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; /** * This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration. */ public final class B_ extends NodeBase { public static final String $PRIMARY_LABEL = "B"; public static final B_ B = new B_(); public final BelongsTo_ BELONGS_TO = new BelongsTo_(A_.A, this); public B_() { super($PRIMARY_LABEL, "X"); } private B_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public B_ named(SymbolicName newSymbolicName) { return new B_(newSymbolicName, getLabels(), getProperties()); } @Override public B_ withProperties(MapExpression newProperties) { return new B_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } public BelongsTo_ belongsTo(A_ start) { return new BelongsTo_(start, this); } } """); assertThat(rel.writeToString()).isEqualTo( """ import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; /** * This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration. */ public final class BelongsTo_, E extends NodeBase> extends RelationshipBase> { public static final String $TYPE = "BELONGS_TO"; public BelongsTo_(A_ start, B_ end) { super((S) start, $TYPE, (E) end); } public BelongsTo_(A_ start, C_ end) { super((S) start, $TYPE, (E) end); } public BelongsTo_(C_ start, B_ end) { super((S) start, $TYPE, (E) end); } private BelongsTo_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public BelongsTo_ named(SymbolicName newSymbolicName) { return new BelongsTo_<>(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public BelongsTo_ withProperties(MapExpression newProperties) { return new BelongsTo_<>(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } """); } @Test void testConstructorsOfRelationsWithoutGeneric() { var configuration = Configuration.newConfig().build(); var a = NodeModelBuilder.create(configuration, null, "A").addLabel("A"); var b = NodeModelBuilder.create(configuration, null, "B").addLabel("B"); assertThat(RelationshipModelBuilder.create(configuration, null, "BELONGS_TO") .addRelationship(a, b) .writeToString()).isEqualTo( """ import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; /** * This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration. */ public final class BelongsTo_ extends RelationshipBase { public static final String $TYPE = "BELONGS_TO"; public BelongsTo_(A_ start, B_ end) { super(start, $TYPE, end); } private BelongsTo_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public BelongsTo_ named(SymbolicName newSymbolicName) { return new BelongsTo_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public BelongsTo_ withProperties(MapExpression newProperties) { return new BelongsTo_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } """); } @Test void testConstructorsOfRelationsWithEndNodeGeneric() { var configuration = Configuration.newConfig().build(); var a = NodeModelBuilder.create(configuration, null, "A").addLabel("A"); var b = NodeModelBuilder.create(configuration, null, "B").addLabel("B"); var c = NodeModelBuilder.create(configuration, null, "C").addLabel("C"); assertThat(RelationshipModelBuilder.create(configuration, null, "BELONGS_TO") .addRelationship(a, b) .addRelationship(a, c) .writeToString()).isEqualTo( """ import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; /** * This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration. */ public final class BelongsTo_> extends RelationshipBase> { public static final String $TYPE = "BELONGS_TO"; public BelongsTo_(A_ start, B_ end) { super(start, $TYPE, (E) end); } public BelongsTo_(A_ start, C_ end) { super(start, $TYPE, (E) end); } private BelongsTo_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public BelongsTo_ named(SymbolicName newSymbolicName) { return new BelongsTo_<>(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public BelongsTo_ withProperties(MapExpression newProperties) { return new BelongsTo_<>(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } """); } @Test void testConstructorsOfRelationsWithStartNodeGeneric() { var configuration = Configuration.newConfig().build(); var a = NodeModelBuilder.create(configuration, null, "A").addLabel("A"); var b = NodeModelBuilder.create(configuration, null, "B").addLabel("B"); var c = NodeModelBuilder.create(configuration, null, "C").addLabel("C"); assertThat(RelationshipModelBuilder.create(configuration, null, "BELONGS_TO") .addRelationship(a, b) .addRelationship(c, b) .writeToString()).isEqualTo( """ import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; /** * This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration. */ public final class BelongsTo_> extends RelationshipBase> { public static final String $TYPE = "BELONGS_TO"; public BelongsTo_(A_ start, B_ end) { super((S) start, $TYPE, end); } public BelongsTo_(C_ start, B_ end) { super((S) start, $TYPE, end); } private BelongsTo_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public BelongsTo_ named(SymbolicName newSymbolicName) { return new BelongsTo_<>(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public BelongsTo_ withProperties(MapExpression newProperties) { return new BelongsTo_<>(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } """); } @Test void testGeneratingWithInheritance() { var configuration = Configuration.newConfig().build(); var a = NodeModelBuilder.create(configuration, null, "A") .addLabel("A") .addLabel("B") .addProperty("foo") .setExtensible(true); var c = NodeModelBuilder.create(configuration, null, "C") .addLabel("C") .addLabel("D") .setBaseNodeModel(a) .addProperty("bar"); assertThat(a.writeToString()).isEqualTo( """ import java.util.Arrays; import java.util.List; import java.util.stream.Stream; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; /** * This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration. */ public class A_> extends NodeBase { public static final String $PRIMARY_LABEL = "A"; public static final A_> A = new A_<>(); public final Property FOO = this.property("foo").referencedAs("foo"); public A_() { super($PRIMARY_LABEL, "B"); } protected A_(String primaryLabel, String... additionalLabels) { super(primaryLabel, Stream.concat(Arrays.stream(additionalLabels), Stream.of($PRIMARY_LABEL, "B")).toArray(String[]::new)); } protected A_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @SuppressWarnings("unchecked") protected SELF create(SymbolicName symbolicName, List labels, Properties properties) { return (SELF) new A_<>(symbolicName, labels, properties); } @Override public SELF named(SymbolicName newSymbolicName) { return create(newSymbolicName, getLabels(), getProperties()); } @Override public SELF withProperties(MapExpression newProperties) { return create(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } """); assertThat(c.writeToString()).isEqualTo(""" import java.util.List; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; /** * This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration. */ public final class C_ extends A_ { public static final String $PRIMARY_LABEL = "C"; public static final C_ C = new C_(); public final Property BAR = this.property("bar").referencedAs("bar"); public C_() { super($PRIMARY_LABEL, "D"); } private C_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override protected C_ create(SymbolicName symbolicName, List labels, Properties properties) { return new C_(symbolicName, labels, properties); } } """); } @Test void testThrowingErrorWhenExtendingNonExtensibleNode() { var configuration = Configuration.newConfig().build(); var a = NodeModelBuilder.create(configuration, null, "A").addLabel("A"); var c = NodeModelBuilder.create(configuration, null, "C").addLabel("C").setBaseNodeModel(a); // test that exception is thrown assertThatThrownBy(c::writeToString).isInstanceOf(IllegalStateException.class) .hasMessage("Cannot extend non-extensible node A_"); } @Test // GH-970 void modelBuilderMustProduceValidGenerics() throws URISyntaxException { var configuration = Configuration.newConfig().withDefaultPackage("org.example").build(); var foo = NodeModelBuilder.create(configuration, null, "Foo") .setExtensible(true) .addLabel("Foo") .addProperty("prop1"); var bar = NodeModelBuilder.create(configuration, null, "Bar").addLabel("Bar").addProperty("prop2"); var related = RelationshipModelBuilder.create(configuration, null, "Related").addRelationship(foo, bar); var cp = new File(new File(this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI()) + "/../../../../neo4j-cypher-dsl/target/classes"); var compilation = Compiler.javac() .withClasspath(List.of(cp)) .compile(JavaFileObjects.forSourceLines("org.example.Foo_", foo.writeToString()), JavaFileObjects.forSourceLines("org.example.Bar_", bar.writeToString()), JavaFileObjects.forSourceLines("org.example.Related_", related.writeToString())); CompilationSubject.assertThat(compilation).succeeded(); var warningsOtherThanApiGuardian = compilation.warnings() .stream() .filter(d -> !d.getMessage(Locale.ROOT).startsWith("unknown enum constant org.apiguardian.api.API.")) .toList(); assertThat(warningsOtherThanApiGuardian).isEmpty(); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/test/java/org/neo4j/cypherdsl/codegen/core/NodeImplBuilderTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons * */ class NodeImplBuilderTests { @ParameterizedTest @CsvSource(nullValues = "n/a", value = { "n/a,n/a", "foo, Foo", "fOo, FOo", "Foo, n/a" }) void capitalizeShouldWork(String in, String expected) { String result = NodeImplBuilder.capitalize(in); if (in == null) { assertThat(result).isNull(); } else if (expected == null) { assertThat(result).isSameAs(in); } else { assertThat(result).isEqualTo(expected); } } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/test/java/org/neo4j/cypherdsl/codegen/core/NodeNameGeneratorTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class NodeNameGeneratorTests { private final NodeNameGenerator defaultStrategy = new NodeNameGenerator(); static Stream suggestedAndEscapedNames() { Stream.Builder arguments = Stream.builder(); for (String pair : new String[] { "BAZ_BAR, BAZ_BAR", "aName, AName", "ANumberedNam3, ANumberedNam3", "Foo3Bar, Foo3Bar", "Foo3BaR, Foo3BaR", "foo3BaR, Foo3BaR", "🖖someThing, SomeThing", "$someThing, $someThing", "$$some33Thing, $$some33Thing", "🧐someThing✋x, SomeThingx", "🧐someThing✋✋X, SomeThingX" }) { String[] suggestedAndExpectedName = pair.split(","); arguments.add(Arguments.of(suggestedAndExpectedName[0].trim(), suggestedAndExpectedName[1].trim())); } return arguments.build(); } @ParameterizedTest @MethodSource("suggestedAndEscapedNames") void createTypeNameShouldWork(String name, String expectedEscapedName) { assertThat(this.defaultStrategy.generate(name)).isEqualTo(expectedEscapedName); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-core/src/test/java/org/neo4j/cypherdsl/codegen/core/RelationshipNameGeneratorTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.core; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class RelationshipNameGeneratorTests { private final RelationshipNameGenerator defaultStrategy = new RelationshipNameGenerator(); static Stream suggestedAndEscapedNames() { Stream.Builder arguments = Stream.builder(); for (String pair : new String[] { "BOR_BOR_B, BorBorB", "BAZ_BOR, BazBor", "aName, AName", "ANumberedNam3, ANumberedNam3", "Foo3Bar, Foo3Bar", "Foo3BaR, Foo3BaR", "foo3BaR, Foo3BaR", "🖖someThing, SomeThing", "$someThing, $someThing", "$$some33Thing, $$some33Thing", "🧐someThing✋x, SomeThingx", "🧐someThing✋✋X, SomeThingX" }) { String[] suggestedAndExpectedName = pair.split(","); arguments.add(Arguments.of(suggestedAndExpectedName[0].trim(), suggestedAndExpectedName[1].trim())); } return arguments.build(); } @ParameterizedTest @MethodSource("suggestedAndEscapedNames") void createTypeNameShouldWork(String name, String expectedEscapedName) { assertThat(this.defaultStrategy.generate(name)).isEqualTo(expectedEscapedName); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/pom.xml ================================================ 4.0.0 org.neo4j neo4j-cypher-dsl-codegen ${revision}${sha1}${changelist} neo4j-cypher-dsl-codegen-ogm Code Generator (Neo4j-OGM) Annotation processor reading Neo4j-OGM annotations and creating a static model of them. org.neo4j.cypherdsl.codegen.ogm ${basedir}/../../${aggregate.report.dir} org.neo4j neo4j-cypher-dsl-codegen-core org.neo4j neo4j-ogm-core com.google.testing.compile compile-testing test org.assertj assertj-core test org.junit.jupiter junit-jupiter test org.junit.vintage junit-vintage-engine test src/test/java org/neo4j/cypherdsl/codegen/ogm/models/**/*.java src/test/resources org.apache.maven.plugins maven-surefire-plugin @{argLine} --add-opens jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED org.apache.maven.plugins maven-compiler-plugin -proc:none org.apache.maven.plugins maven-jar-plugin ${java-module-name} ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/main/java/org/neo4j/cypherdsl/codegen/ogm/OGMAnnotationProcessor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm; import java.io.IOException; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedOptions; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.RecordComponentElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementKindVisitor8; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleTypeVisitor8; import javax.lang.model.util.TypeKindVisitor8; import javax.tools.Diagnostic; import org.apiguardian.api.API; import org.neo4j.cypherdsl.codegen.core.AbstractMappingAnnotationProcessor; import org.neo4j.cypherdsl.codegen.core.Configuration; import org.neo4j.cypherdsl.codegen.core.ModelBuilder; import org.neo4j.cypherdsl.codegen.core.NodeModelBuilder; import org.neo4j.cypherdsl.codegen.core.PropertyDefinition; import org.neo4j.cypherdsl.codegen.core.RelationshipModelBuilder; import org.neo4j.cypherdsl.codegen.core.RelationshipPropertyDefinition; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Property; import org.neo4j.ogm.annotation.Relationship; import org.neo4j.ogm.annotation.RelationshipEntity; import static org.apiguardian.api.API.Status.EXPERIMENTAL; /** * This processor works on Neo4j-OGM annotations and creates a static metamodel based on * CypherDSL for those classes. * * @author Shinigami92 (Christopher Quadflieg) * @author Michael J. Simons * @since 2025.0.0 */ @API(status = EXPERIMENTAL, since = "2025.0.0") @SupportedAnnotationTypes({ OGMAnnotationProcessor.NODE_ENTITY_ANNOTATION, OGMAnnotationProcessor.RELATIONSHIP_ENTITY_ANNOTATION }) @SupportedOptions({ Configuration.PROPERTY_PREFIX, Configuration.PROPERTY_SUFFIX, Configuration.PROPERTY_INDENT_STYLE, Configuration.PROPERTY_INDENT_SIZE, Configuration.PROPERTY_TIMESTAMP, Configuration.PROPERTY_ADD_AT_GENERATED, Configuration.PROPERTY_EXCLUDES }) public final class OGMAnnotationProcessor extends AbstractMappingAnnotationProcessor { static final String NODE_ENTITY_ANNOTATION = "org.neo4j.ogm.annotation.NodeEntity"; static final String RELATIONSHIP_ENTITY_ANNOTATION = "org.neo4j.ogm.annotation.RelationshipEntity"; static final Set VALID_GENERATED_ID_TYPES = Set.of(Long.class.getName(), long.class.getName()); private final List convertAnnotationTypes = new ArrayList<>(); private TypeElement propertyAnnotationType; private TypeElement nodeEntityAnnotationType; private TypeElement relationshipEntityAnnotationType; private TypeElement relationshipAnnotationType; private TypeElement startNodeAnnotationType; private TypeElement endNodeAnnotationType; private TypeElement ogmIdAnnotationType; private TypeElement generatedValueAnnotationType; private static boolean declaredTypeContains(DeclaredType dt, Element annotatedEntity) { // Single field if (dt.equals(annotatedEntity.asType())) { return true; } else { // Treating anything that has a generic type argument for a collection shaped // thing // Most other mappings with OGM won't work anyhow, so this is good enough for // now for (var typeArgument : dt.getTypeArguments()) { if (typeArgument.equals(annotatedEntity.asType())) { return true; } } } return false; } @Override public void initFrameworkSpecific(ProcessingEnvironment processingEnv) { Elements elementUtils = processingEnv.getElementUtils(); this.nodeEntityAnnotationType = elementUtils.getTypeElement(NODE_ENTITY_ANNOTATION); this.relationshipEntityAnnotationType = elementUtils.getTypeElement(RELATIONSHIP_ENTITY_ANNOTATION); this.relationshipAnnotationType = elementUtils.getTypeElement("org.neo4j.ogm.annotation.Relationship"); this.startNodeAnnotationType = elementUtils.getTypeElement("org.neo4j.ogm.annotation.StartNode"); this.endNodeAnnotationType = elementUtils.getTypeElement("org.neo4j.ogm.annotation.EndNode"); for (var name : List.of("Convert", "DateLong", "DateString", "EnumString", "NumberString")) { this.convertAnnotationTypes .add(elementUtils.getTypeElement("org.neo4j.ogm.annotation.typeconversion.%s".formatted(name))); } this.propertyAnnotationType = elementUtils.getTypeElement("org.neo4j.ogm.annotation.Property"); this.ogmIdAnnotationType = elementUtils.getTypeElement("org.neo4j.ogm.annotation.Id"); this.generatedValueAnnotationType = elementUtils.getTypeElement("org.neo4j.ogm.annotation.GeneratedValue"); } @Override protected Collection getLabel(TypeElement annotatedClass) { NodeEntity nodeAnnotation = annotatedClass.getAnnotation(NodeEntity.class); Set labels = new LinkedHashSet<>(); Consumer addLabel = label -> { if (!label.isEmpty()) { labels.add(label); } }; addLabel.accept(nodeAnnotation.label()); addLabel.accept(nodeAnnotation.value()); if (labels.isEmpty()) { addLabel.accept(annotatedClass.getSimpleName().toString()); } return Collections.unmodifiableCollection(labels); } @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { if (annotations.isEmpty()) { return false; } Map nodeBuilders = populateListOfNodes( getTypesAnnotatedWith(this.nodeEntityAnnotationType, roundEnv)); Map> relationshipFields = populateNodePropertiesAndCollectRelationshipFields( nodeBuilders); Map>> relationshipProperties = collectRelationshipProperties( relationshipFields, roundEnv); Map> relationshipBuilders = populateListOfRelationships( computeRelationshipDefinitions(relationshipFields, relationshipProperties, nodeBuilders)); List> allBuilders = new ArrayList<>(nodeBuilders.values()); relationshipBuilders.values().forEach(allBuilders::addAll); try { writeSourceFiles(allBuilders); } catch (IOException ex) { this.messager.printMessage(Diagnostic.Kind.ERROR, "Could not write source files: " + ex.getMessage()); } return true; } private String getRelationshipEntityType(Element annotatedEntity) { var relationshipEntity = annotatedEntity.getAnnotation(RelationshipEntity.class); if (relationshipEntity == null) { return null; } String typeValue = relationshipEntity.type(); String valueValue = relationshipEntity.value(); String relationshipType = null; if (!typeValue.isEmpty() && !valueValue.isEmpty()) { if (!typeValue.equals(valueValue)) { this.messager.printMessage(Diagnostic.Kind.ERROR, "Different @AliasFor mirror values for annotation [org.neo4j.ogm.annotation.RelationshipEntity]!", annotatedEntity); } relationshipType = typeValue; } else if (!typeValue.isEmpty()) { relationshipType = typeValue; } else if (!valueValue.isEmpty()) { relationshipType = valueValue; } return relationshipType; } // Tries to figure out the direction of this relationship, so that we can decide on // how to treat start/end nodes private Optional findDirection(Element annotatedEntity, Element startElement, Set allRelationshipFields) { return allRelationshipFields.stream().filter(element -> { // The enclosing field must be of the same type as the startElement if (!element.getEnclosingElement().asType().equals(startElement.asType())) { return false; } // Get the type if (element.asType() instanceof DeclaredType dt) { return declaredTypeContains(dt, annotatedEntity); } return false; }).map(element -> { var relationship = element.getAnnotation(Relationship.class); return (relationship != null) ? relationship.direction() : Relationship.Direction.OUTGOING; }).findFirst(); } private Map>> collectRelationshipProperties( Map> relationshipFields, RoundEnvironment roundEnvironment) { var allRelationshipFields = relationshipFields.values() .stream() .flatMap(List::stream) .collect(Collectors.toSet()); Map>> result = new HashMap<>(); Set annotatedEntities = getTypesAnnotatedWith(this.relationshipEntityAnnotationType, roundEnvironment); annotatedEntities.forEach(annotatedEntity -> { List properties = new ArrayList<>(); Element startElement = null; Element endElement = null; for (Element enclosedElement : annotatedEntity.getEnclosedElements()) { if (!enclosedElement.getKind().isField()) { continue; } Set declaredAnnotations = enclosedElement.getAnnotationMirrors() .stream() .map(AnnotationMirror::getAnnotationType) .map(DeclaredType::asElement) .collect(Collectors.toSet()); if (declaredAnnotations.contains(this.startNodeAnnotationType)) { startElement = enclosedElement; } else if (declaredAnnotations.contains(this.endNodeAnnotationType)) { endElement = enclosedElement; } else { properties.add(asPropertyDefinition(enclosedElement)); } } TypeElement actualTargetType = null; Element relationshipDefiningElement = null; if (startElement != null && endElement != null) { var finalEndElement = endElement; var direction = findDirection(annotatedEntity, startElement, allRelationshipFields) .or(() -> findDirection(annotatedEntity, finalEndElement, allRelationshipFields)) .orElse(Relationship.Direction.OUTGOING); relationshipDefiningElement = (direction != Relationship.Direction.OUTGOING) ? startElement : endElement; } else if (startElement != null) { relationshipDefiningElement = startElement; } else if (endElement != null) { relationshipDefiningElement = endElement; } if (relationshipDefiningElement != null) { actualTargetType = this.typeUtils.asElement(relationshipDefiningElement.asType()) .accept(new TypeElementVisitor<>(Function.identity()), null); if (actualTargetType == null) { this.messager.printMessage(Diagnostic.Kind.WARNING, "Cannot resolve generic type, not generating a property for relationships referring to " + annotatedEntity.getQualifiedName(), relationshipDefiningElement); } } if (actualTargetType != null) { result.put(annotatedEntity, new AbstractMap.SimpleEntry<>(actualTargetType, Collections.unmodifiableList(properties))); } }); return Collections.unmodifiableMap(result); } @Override protected PropertyDefinition asPropertyDefinition(Element e) { Optional optionalPropertyAnnotation = Optional.ofNullable(e.getAnnotation(Property.class)); PropertyDefinition propertyDefinition; String fieldName = e.getSimpleName().toString(); if (optionalPropertyAnnotation.isPresent()) { Property propertyAnnotation = optionalPropertyAnnotation.get(); String nameValue = propertyAnnotation.name(); String valueValue = propertyAnnotation.value(); if (!nameValue.isEmpty() && !valueValue.isEmpty()) { if (!nameValue.equals(valueValue)) { this.messager.printMessage(Diagnostic.Kind.ERROR, "Different @AliasFor mirror values for annotation [org.neo4j.ogm.annotation.Property]!", e); } propertyDefinition = PropertyDefinition.create(nameValue, fieldName); } else if (!nameValue.isEmpty()) { propertyDefinition = PropertyDefinition.create(nameValue, fieldName); } else if (!valueValue.isEmpty()) { propertyDefinition = PropertyDefinition.create(valueValue, fieldName); } else { propertyDefinition = PropertyDefinition.create(fieldName, null); } } else { propertyDefinition = PropertyDefinition.create(fieldName, null); } return propertyDefinition; } @Override protected RelationshipPropertyDefinition asRelationshipDefinition(NodeModelBuilder owner, Element e, Map>> relationshipProperties, Map nodeBuilders) { Optional optionalRelationshipAnnotation = Optional .ofNullable(e.getAnnotation(Relationship.class)); String fieldName = e.getSimpleName().toString(); // Default Relationship#direction is outgoing. boolean isIncoming = false; String relationshipType; if (optionalRelationshipAnnotation.isPresent()) { Relationship relationshipAnnotation = optionalRelationshipAnnotation.get(); String typeValue = relationshipAnnotation.type(); String valueValue = relationshipAnnotation.value(); isIncoming = relationshipAnnotation.direction() == Relationship.Direction.INCOMING; if (!typeValue.isEmpty() && !valueValue.isEmpty()) { if (!typeValue.equals(valueValue)) { this.messager.printMessage(Diagnostic.Kind.ERROR, "Different @AliasFor mirror values for annotation [org.neo4j.ogm.annotation.Relationship]!", e); } relationshipType = typeValue; } else if (!typeValue.isEmpty()) { relationshipType = typeValue; } else if (!valueValue.isEmpty()) { relationshipType = valueValue; } else { relationshipType = fieldName; } } else { relationshipType = fieldName; } DeclaredType declaredType = e.asType().accept(new SimpleTypeVisitor8() { @Override public DeclaredType visitDeclared(DeclaredType t, Void unused) { return t; } }, null); if (declaredType == null) { return null; } TypeMirror relatedType = null; if (declaredType.getTypeArguments().size() == 1) { relatedType = declaredType.getTypeArguments().get(0); } else if (declaredType.getTypeArguments().isEmpty()) { relatedType = declaredType; } Element key = this.typeUtils.asElement(relatedType); NodeModelBuilder end = (key != null) ? nodeBuilders.get(key) : null; List properties = null; String optionalPropertyHolder = null; if (key == null) { return null; } else if (end == null) { Map.Entry> typeAndProperties = relationshipProperties.get(key); if (typeAndProperties != null) { optionalPropertyHolder = key.toString(); end = nodeBuilders.get(typeAndProperties.getKey()); properties = typeAndProperties.getValue(); } } if (end == null) { return null; } else if (isIncoming) { return RelationshipPropertyDefinition.create(relationshipType, optionalPropertyHolder, fieldName, end, owner, properties); } else { return RelationshipPropertyDefinition.create(relationshipType, optionalPropertyHolder, fieldName, owner, end, properties); } } @Override protected PropertiesAndRelationshipGrouping newPropertiesAndRelationshipGrouping() { return new GroupPropertiesAndRelationships(); } /** * Pre-groups fields into properties and relationships to avoid running the * association check multiple times. */ // Silence Sonar complaining about the class hierarchy, which is given through // the ElementKindVisitor8, which we need but cannot change @SuppressWarnings("squid:S110") class GroupPropertiesAndRelationships extends ElementKindVisitor8>, Void> implements PropertiesAndRelationshipGrouping { private final Map> result; GroupPropertiesAndRelationships() { final Map> hlp = new EnumMap<>(FieldType.class); hlp.put(FieldType.R, new ArrayList<>()); hlp.put(FieldType.P, new ArrayList<>()); this.result = Collections.unmodifiableMap(hlp); } @Override public void apply(Element element) { element.accept(this, null); } @Override public Map> getResult() { return this.result; } @Override public Map> visitTypeAsRecord(TypeElement e, Void unused) { // We must overwrite this or visitUnknown() in case we encounter a record return this.result; } @Override public Map> visitRecordComponent(RecordComponentElement e, Void unused) { return visitFieldOrRecordComponent(e); } @Override public Map> visitVariableAsField(VariableElement e, Void unused) { return visitFieldOrRecordComponent(e); } private Map> visitFieldOrRecordComponent(Element e) { Set declaredAnnotations = e.getAnnotationMirrors() .stream() .map(AnnotationMirror::getAnnotationType) .map(DeclaredType::asElement) .collect(Collectors.toSet()); // Skip internal ids if (isInternalId(e, declaredAnnotations)) { return this.result; } this.result.get(isAssociation(declaredAnnotations, e) ? FieldType.R : FieldType.P).add(e); return this.result; } private boolean isInternalId(Element e, Set declaredAnnotations) { boolean idAnnotationPresent = declaredAnnotations.contains(OGMAnnotationProcessor.this.ogmIdAnnotationType); if (!idAnnotationPresent) { return false; } return e.getAnnotationMirrors() .stream() .filter(m -> m.getAnnotationType() .asElement() .equals(OGMAnnotationProcessor.this.generatedValueAnnotationType)) .findFirst() .map(generatedValue -> isUsingInternalIdGenerator(e, generatedValue)) .orElse(false); } private boolean isUsingInternalIdGenerator(Element e, AnnotationMirror generatedValue) { Map values = generatedValue.getElementValues() .entrySet() .stream() .collect(Collectors.toMap(entry -> entry.getKey().getSimpleName().toString(), Map.Entry::getValue)); DeclaredType generatorClassValue = values.containsKey("strategy") ? (DeclaredType) values.get("strategy").getValue() : null; String name = null; if (generatorClassValue != null) { name = generatorClassValue.toString(); } // The defaults will not be materialized return (name == null || "org.neo4j.ogm.id.InternalIdStrategy".equals(name)) && VALID_GENERATED_ID_TYPES.contains(e.asType().toString()); } /** * Returns true if this field is an association. * @param declaredAnnotations the declared annotations on this field * @param field a variable element describing a field. No further checks done if * this is true or not * @return true if this field is an association */ private boolean isAssociation(Set declaredAnnotations, Element field) { TypeMirror typeMirrorOfField = field.asType(); if (declaredAnnotations.contains(OGMAnnotationProcessor.this.relationshipAnnotationType)) { return true; } if (declaredAnnotations.contains(OGMAnnotationProcessor.this.propertyAnnotationType)) { return false; } // They will be converted anyway if (describesEnum(typeMirrorOfField)) { return false; } // Strings, primitives and their boxed variants are never associations if (typeMirrorOfField.getKind().isPrimitive()) { return false; } else { var type = typeMirrorOfField.accept(new TypeKindVisitor8<>() { @Override public String visitDeclared(DeclaredType t, Object o) { return t.asElement().accept(new TypeElementVisitor<>(newTypeElementNameFunction()), null); } }, null); if ("java.lang.String".equals(type)) { return false; } try { OGMAnnotationProcessor.this.typeUtils.unboxedType(typeMirrorOfField); return false; } catch (IllegalArgumentException ex) { // Exception driven development for the win, yeah } } // Stuff that is explicitly converted can't be an association either for (var converterAnnotation : OGMAnnotationProcessor.this.convertAnnotationTypes) { if (declaredAnnotations.contains(converterAnnotation)) { return false; } } // Unless it is a start / end node, it is by a big chance an implicit // relationship and hence, an association boolean isStartNode = declaredAnnotations.contains(OGMAnnotationProcessor.this.startNodeAnnotationType); boolean isEndNode = declaredAnnotations.contains(OGMAnnotationProcessor.this.endNodeAnnotationType); return !(isStartNode || isEndNode); } } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/main/java/org/neo4j/cypherdsl/codegen/ogm/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * Contains the Neo4j-OGM annotation processor that creates a static metamodel for OGM * annotated classes. */ package org.neo4j.cypherdsl.codegen.ogm; ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/main/resources/META-INF/services/javax.annotation.processing.Processor ================================================ org.neo4j.cypherdsl.codegen.ogm.OGMAnnotationProcessor ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/OGMAnnotationProcessorTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm; import java.util.Arrays; import javax.tools.JavaFileObject; import javax.tools.ToolProvider; import com.google.testing.compile.Compilation; import com.google.testing.compile.CompilationSubject; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; import io.github.classgraph.ClassGraph; import io.github.classgraph.ScanResult; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.converter.ArgumentConversionException; import org.junit.jupiter.params.converter.ConvertWith; import org.junit.jupiter.params.converter.SimpleArgumentConverter; import org.junit.jupiter.params.provider.CsvSource; /** * @author Michael J. Simons * @author Shinigami92 (Christopher Quadflieg) */ class OGMAnnotationProcessorTests { static Compiler getCompiler(String release, Object... options) { String ts = "-Aorg.neo4j.cypherdsl.codegen.timestamp=2025-09-21T21:21:00+01:00"; Object[] defaultOptions; if (ToolProvider.getSystemJavaCompiler().isSupportedOption("--release") >= 0) { // release 8 deprecated since Java 21, surprised with -options defaultOptions = new Object[] { "-Xlint:-options", ts, "--release", release }; } else if ("8".equals(release)) { defaultOptions = new Object[] { ts, "-source", "8", "-target", "8" }; } else { throw new IllegalArgumentException("Release %s not supported for testing".formatted(release)); } Object[] finalOptions = new Object[options.length + defaultOptions.length]; System.arraycopy(defaultOptions, 0, finalOptions, 0, defaultOptions.length); System.arraycopy(options, 0, finalOptions, defaultOptions.length, options.length); return Compiler.javac().withOptions(finalOptions); } JavaFileObject[] getJavaResources(String base) { try (ScanResult scanResult = new ClassGraph().acceptPaths(base).scan()) { return scanResult.getResourcesWithExtension("java") .stream() .map(resource -> JavaFileObjects.forResource(resource.getURL())) .toArray(JavaFileObject[]::new); } } @CsvSource({ "8, ids, 'InternalGeneratedId, ExternalGeneratedId, ExternalGeneratedIdImplicit, InternalGeneratedPrimitiveLongId',", "8, simple, 'Person, Movie, ActedIn, Follows, Directed, Produced',", "8, labels, 'LabelOnNode1, LabelOnNode2', nodeswithdifferentlabelannotations", "8, same_properties_for_rel_type, 'Person, Movie, Play, ActedIn', ", "8, different_properties_for_rel_type, 'Person, Movie, Play, ActedInPlay, ActedInMovie', ", "8, same_rel_different_target, 'Person, Movie, Book, Wrote', ", "8, same_rel_different_source, 'Person, Movie, Book, Wrote', ", "8, same_rel_mixed, 'Person, Movie, Book, Wrote', ", "8, same_rel_mixed_different_directions, 'Person, Movie, Book, Wrote', ", "8, abstract_rels, 'Person, Movie, Directed',", "8, primitives, 'Connector', ", "8, enums_and_inner_classes, 'ConnectorTransport', ", "8, related_classes_not_on_cp_like_in_reallife, 'Movie, Person', ", "8, self_referential, 'Example', ", "17, records, 'NodeWithRecordProperties, RecordAsRelationship, RecordTarget', " }) @ParameterizedTest void validSourceFiles(String release, String scenario, @ConvertWith(StringArrayConverter.class) String[] expected, String subpackage) { Compilation compilation = getCompiler(release).withProcessors(new OGMAnnotationProcessor()) .compile(getJavaResources("/org/neo4j/cypherdsl/codegen/ogm/models/" + scenario)); CompilationSubject.assertThat(compilation).succeeded(); if ("abstract_rels".equals(scenario)) { CompilationSubject.assertThat(compilation) .hadWarningContaining( "Cannot resolve generic type, not generating a property for relationships referring to org.neo4j.cypherdsl.codegen.ogm.models.abstract_rels.Actor"); } else { CompilationSubject.assertThat(compilation).hadWarningCount(0); } for (String expectedSourceFile : expected) { String finalName = scenario + "." + ((subpackage != null) ? subpackage + "." : "") + expectedSourceFile + "_"; CompilationSubject.assertThat(compilation) .generatedSourceFile("org.neo4j.cypherdsl.codegen.ogm.models." + finalName) .hasSourceEquivalentTo(JavaFileObjects.forResource(finalName.replaceAll("\\.", "/") + ".java")); } } static class StringArrayConverter extends SimpleArgumentConverter { @Override protected Object convert(Object source, Class targetType) throws ArgumentConversionException { if (source instanceof String && String[].class.isAssignableFrom(targetType)) { return Arrays.stream(((String) source).split("\\s*,\\s*")).map(String::trim).toArray(String[]::new); } else { throw new IllegalArgumentException( "Conversion from " + source.getClass() + " to " + targetType + " not supported."); } } } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/abstract_rels/Actor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.abstract_rels; import java.util.ArrayList; import java.util.List; import org.neo4j.ogm.annotation.RelationshipEntity; import org.neo4j.ogm.annotation.StartNode; /** * Example type. * * @param a type * @author Michael J. Simons */ @RelationshipEntity public class Actor { @StartNode private final T person; private List roles = new ArrayList<>(); public Actor(T person) { this.person = person; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/abstract_rels/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.abstract_rels; import java.util.ArrayList; import java.util.List; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Property; import org.neo4j.ogm.annotation.Relationship; /** * @author Michael J. Simons */ @NodeEntity public class Movie { @Id private final String title; @Property("tagline") private final String description; @Relationship(type = "ACTED_IN", direction = Relationship.Direction.INCOMING) private List> actors = new ArrayList<>(); @Relationship(type = "DIRECTED", direction = Relationship.Direction.INCOMING) private List directors = new ArrayList<>(); public Movie(String title, String description) { this.title = title; this.description = description; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/abstract_rels/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.abstract_rels; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * @author Michael J. Simons */ @NodeEntity public class Person { @Id private String name; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/different_properties_for_rel_type/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.different_properties_for_rel_type; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * @author Michael J. Simons */ @NodeEntity public class Movie { @Id @GeneratedValue private Long id; private String title; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/different_properties_for_rel_type/MovieAppearance.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.different_properties_for_rel_type; import java.util.ArrayList; import java.util.List; import org.neo4j.ogm.annotation.EndNode; import org.neo4j.ogm.annotation.RelationshipEntity; import org.neo4j.ogm.annotation.StartNode; /** * @author Michael J. Simons */ @RelationshipEntity public class MovieAppearance { @StartNode private Person person; @EndNode private Movie movie; private List roles = new ArrayList<>(); private String stuntDouble; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/different_properties_for_rel_type/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.different_properties_for_rel_type; import java.util.ArrayList; import java.util.List; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Relationship; /** * @author Michael J. Simons */ @NodeEntity public class Person { @Id @GeneratedValue private Long id; private String name; private Integer born; @Relationship(type = "ACTED_IN") private MovieAppearance movies; @Relationship(type = "ACTED_IN") private List plays = new ArrayList<>(); } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/different_properties_for_rel_type/Play.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.different_properties_for_rel_type; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * @author Michael J. Simons */ @NodeEntity public class Play { @Id @GeneratedValue private Long id; private String title; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/different_properties_for_rel_type/TheaterAppearance.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.different_properties_for_rel_type; import java.util.ArrayList; import java.util.List; import org.neo4j.ogm.annotation.EndNode; import org.neo4j.ogm.annotation.RelationshipEntity; import org.neo4j.ogm.annotation.StartNode; /** * @author Michael J. Simons */ @RelationshipEntity public class TheaterAppearance { @StartNode private Person person; @EndNode private Play play; private List roles = new ArrayList<>(); } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/different_properties_for_rel_type/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * The same relationship type ACTED_IN is used with different properties. Multiple types * needs to be generated. */ package org.neo4j.cypherdsl.codegen.ogm.models.different_properties_for_rel_type; ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/enums_and_inner_classes/AnotherConverter.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.enums_and_inner_classes; import org.neo4j.ogm.typeconversion.AttributeConverter; /** * @author Michael J. Simons */ public abstract class AnotherConverter implements AttributeConverter { } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/enums_and_inner_classes/ConnectorTransport.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.enums_and_inner_classes; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.typeconversion.Convert; /** * @author Michael J. Simons */ @NodeEntity(label = "Transport") public class ConnectorTransport { @Id private final ConnectorTransportType value; private OtherEnum otherEnum; @SuppressWarnings("checkstyle:AnnotationUseStyle") @Convert(value = AnotherConverter.class) private InnerClass.InnerInnerClass innerInnerClass; @SuppressWarnings("checkstyle:AnnotationUseStyle") @Convert(value = InnerInnerClassConverter.class) private InnerClass.InnerInnerClass innerInnerClassButWithoutConverter; public ConnectorTransport(ConnectorTransportType value) { this.value = value; } /** * Some enum */ public enum ConnectorTransportType { HTTP, BOLT } /** * An inner class */ public static class InnerClass { /** * With a nested inner class (used to check whether the recursive algorithm stops * at some point) */ public static class InnerInnerClass { } } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/enums_and_inner_classes/InnerInnerClassConverter.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.enums_and_inner_classes; import org.neo4j.ogm.typeconversion.AttributeConverter; /** * @author Michael J. Simons */ public abstract class InnerInnerClassConverter implements AttributeConverter { } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/enums_and_inner_classes/OtherEnum.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.enums_and_inner_classes; /** * @author Michael J. Simons */ public enum OtherEnum { A, B } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/ids/ExternalGeneratedId.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.ids; import java.util.UUID; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.typeconversion.Convert; import org.neo4j.ogm.id.UuidStrategy; import org.neo4j.ogm.typeconversion.UuidStringConverter; /** * @author Michael J. Simons */ @NodeEntity public class ExternalGeneratedId { @Id @GeneratedValue(strategy = UuidStrategy.class) @Convert(UuidStringConverter.class) private UUID id; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/ids/ExternalGeneratedIdImplicit.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.ids; import java.util.UUID; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.typeconversion.Convert; import org.neo4j.ogm.typeconversion.UuidStringConverter; /** * @author Michael J. Simons */ @NodeEntity public class ExternalGeneratedIdImplicit { @Id @GeneratedValue @Convert(UuidStringConverter.class) private UUID id; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/ids/InternalGeneratedId.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.ids; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * @author Michael J. Simons */ @NodeEntity public class InternalGeneratedId { @Id @GeneratedValue private Long id; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/ids/InternalGeneratedPrimitiveLongId.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.ids; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * @author Michael J. Simons */ @NodeEntity public class InternalGeneratedPrimitiveLongId { @Id @GeneratedValue private long id; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/labels/NodesWithDifferentLabelAnnotations.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.labels; import org.neo4j.ogm.annotation.NodeEntity; /** * Tests various ways to define labels. The generated classes will end up in a subpackage * as they are static, inner classes. * * @author Michael J. Simons */ public class NodesWithDifferentLabelAnnotations { @NodeEntity("ALabel") static class LabelOnNode1 { } @SuppressWarnings("checkstyle:AnnotationUseStyle") @NodeEntity(value = "ALabel") static class LabelOnNode2 { } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/primitives/Connector.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.primitives; import java.util.UUID; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Relationship; import org.neo4j.ogm.annotation.typeconversion.Convert; import org.neo4j.ogm.typeconversion.UuidStringConverter; /** * @author Michael J. Simons */ @NodeEntity("Connector") public class Connector { @Id @GeneratedValue @Convert(UuidStringConverter.class) private UUID id; private final String uri; private final boolean official; private int anInt; private float aFloat; private double aDouble; @Relationship private char aChar; public Connector(String uri, boolean official) { this.uri = uri; this.official = official; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/records/NodeWithRecordProperties.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.records; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Property; import org.neo4j.ogm.annotation.Relationship; import org.neo4j.ogm.annotation.typeconversion.Convert; import org.neo4j.ogm.typeconversion.AttributeConverter; /** * Random test class. */ @NodeEntity public class NodeWithRecordProperties { @Id private String id; @Convert(RecordConverter.class) private RecordAsProperty recordAsPropertyWithConversion; @Property private RecordAsProperty yoloingNoConversion; // Inner types for relationships are not supported @Relationship private RecordTarget recordAsRelationship; record RecordAsProperty(String value) { } abstract static class RecordConverter implements AttributeConverter { } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/records/RecordTarget.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.records; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * Random test class. * * @param value a value */ @NodeEntity public record RecordTarget(@Id String value) { } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_properties_for_rel_type/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.same_properties_for_rel_type; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * @author Michael J. Simons */ @NodeEntity public class Movie { @Id @GeneratedValue private Long id; private String title; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_properties_for_rel_type/MovieAppearance.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.same_properties_for_rel_type; import java.util.ArrayList; import java.util.List; import org.neo4j.ogm.annotation.EndNode; import org.neo4j.ogm.annotation.RelationshipEntity; /** * @author Michael J. Simons */ @RelationshipEntity public class MovieAppearance { @EndNode private Movie movie; private List roles = new ArrayList<>(); } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_properties_for_rel_type/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.same_properties_for_rel_type; import java.util.ArrayList; import java.util.List; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Relationship; /** * @author Michael J. Simons */ @NodeEntity public class Person { @Id @GeneratedValue private Long id; private String name; private Integer born; @Relationship(type = "ACTED_IN") private List movies = new ArrayList<>(); @Relationship(type = "ACTED_IN") private List plays = new ArrayList<>(); } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_properties_for_rel_type/Play.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.same_properties_for_rel_type; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * @author Michael J. Simons */ @NodeEntity public class Play { @Id @GeneratedValue private Long id; private String title; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_properties_for_rel_type/TheaterAppearance.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.same_properties_for_rel_type; import java.util.ArrayList; import java.util.List; import org.neo4j.ogm.annotation.EndNode; import org.neo4j.ogm.annotation.RelationshipEntity; /** * @author Michael J. Simons */ @RelationshipEntity public class TheaterAppearance { @EndNode private Play play; private List roles = new ArrayList<>(); } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_properties_for_rel_type/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * The same relationship type ACTED_IN is used with same properties. Only one type needs * to be generated. */ package org.neo4j.cypherdsl.codegen.ogm.models.same_properties_for_rel_type; ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_rel_different_source/Book.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.same_rel_different_source; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Relationship; /** * @author Michael J. Simons */ @NodeEntity public class Book { @Id private final String title; @Relationship(value = "WROTE", direction = Relationship.Direction.INCOMING) private Person writtenBy; Book(String title) { this.title = title; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_rel_different_source/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.same_rel_different_source; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Relationship; /** * @author Michael J. Simons */ @NodeEntity public class Movie { @Id private final String title; @Relationship(value = "WROTE", direction = Relationship.Direction.INCOMING) private Person writtenBy; Movie(String title) { this.title = title; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_rel_different_source/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.same_rel_different_source; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * @author Michael J. Simons */ @NodeEntity public class Person { @Id private String name; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_rel_different_target/Book.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.same_rel_different_target; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * @author Michael J. Simons */ @NodeEntity public class Book { @Id private final String title; Book(String title) { this.title = title; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_rel_different_target/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.same_rel_different_target; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * @author Michael J. Simons */ @NodeEntity public class Movie { @Id private final String title; Movie(String title) { this.title = title; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_rel_different_target/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.same_rel_different_target; import java.util.List; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Relationship; /** * @author Michael J. Simons */ @NodeEntity public class Person { @Id private String name; @Relationship("WROTE") private List writtenMovies; @Relationship("WROTE") private List writtenBooks; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_rel_different_target/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * Here the WROTE relationship targets different entities. It should be generated with a * wild card. */ package org.neo4j.cypherdsl.codegen.ogm.models.same_rel_different_target; ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_rel_mixed/Book.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.same_rel_mixed; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Relationship; /** * @author Michael J. Simons */ @NodeEntity public class Book { @Id private final String title; @Relationship(value = "WROTE", direction = Relationship.Direction.INCOMING) private Person writtenBy; Book(String title) { this.title = title; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_rel_mixed/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.same_rel_mixed; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * @author Michael J. Simons */ @NodeEntity public class Movie { @Id private final String title; Movie(String title) { this.title = title; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_rel_mixed/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.same_rel_mixed; import java.util.List; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Relationship; /** * @author Michael J. Simons */ @NodeEntity public class Person { @Id private String name; @Relationship("WROTE") private List writtenMovies; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_rel_mixed_different_directions/Book.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.same_rel_mixed_different_directions; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Relationship; /** * @author Michael J. Simons */ @NodeEntity public class Book { @Id private final String title; // Makes not much sense, but one never knows @Relationship(value = "WROTE", direction = Relationship.Direction.OUTGOING) private Person writtenBy; Book(String title) { this.title = title; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_rel_mixed_different_directions/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.same_rel_mixed_different_directions; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Relationship; /** * @author Michael J. Simons */ @NodeEntity public class Movie { @Id private final String title; @Relationship(value = "WROTE", direction = Relationship.Direction.INCOMING) private Person writtenBy; Movie(String title) { this.title = title; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/same_rel_mixed_different_directions/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.same_rel_mixed_different_directions; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * @author Michael J. Simons */ @NodeEntity public class Person { @Id private String name; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/self_referential/Example.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.self_referential; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Relationship; /** * @author Michael J. Simons */ @NodeEntity public class Example { @Id private final long id; @Relationship(type = "BELONGS_TO") private Example parent; public Example(long id) { this.id = id; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/simple/Actor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.simple; import java.util.ArrayList; import java.util.List; import org.neo4j.ogm.annotation.EndNode; import org.neo4j.ogm.annotation.RelationshipEntity; import org.neo4j.ogm.annotation.StartNode; /** * @author Michael J. Simons */ @RelationshipEntity(type = "ACTED_IN") public class Actor { @StartNode private final Person person; @EndNode Movie movie; private List roles = new ArrayList<>(); public Actor(Person person) { this.person = person; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/simple/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.simple; import java.util.ArrayList; import java.util.List; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Property; import org.neo4j.ogm.annotation.Relationship; /** * @author Michael J. Simons */ @NodeEntity public class Movie { @Id private final String title; @Property("tagline") private final String description; @Relationship(type = "ACTED_IN", direction = Relationship.Direction.INCOMING) private List actors = new ArrayList<>(); @Relationship(type = "DIRECTED", direction = Relationship.Direction.INCOMING) private List directors = new ArrayList<>(); // This will be ignored, not annotated and the other type is not a node private SomeType someProp; private Integer released; public Movie(String title, String description) { this.title = title; this.description = description; } static class SomeType { } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/simple/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.simple; import java.util.List; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * @author Michael J. Simons */ @NodeEntity public class Person { @Id private String name; private Integer born; private Person follows; private List produced; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/java/org/neo4j/cypherdsl/codegen/ogm/models/simple/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * This is basically the "happy path" as demonstrated in several examples. */ package org.neo4j.cypherdsl.codegen.ogm.models.simple; ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/abstract_rels/Directed_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.abstract_rels; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Directed_ extends RelationshipBase { public static final String $TYPE = "DIRECTED"; public Directed_(Person_ start, Movie_ end) { super(start, $TYPE, end); } private Directed_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Directed_ named(SymbolicName newSymbolicName) { return new Directed_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Directed_ withProperties(MapExpression newProperties) { return new Directed_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/abstract_rels/Movie_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.abstract_rels; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Movie_ extends NodeBase { public static final String $PRIMARY_LABEL = "Movie"; public static final Movie_ MOVIE = new Movie_(); public final Property TITLE = this.property("title"); public final Property DESCRIPTION = this.property("tagline").referencedAs("description"); public final Directed_ DIRECTORS = new Directed_(Person_.PERSON, this); public Movie_() { super($PRIMARY_LABEL); } private Movie_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Movie_ named(SymbolicName newSymbolicName) { return new Movie_(newSymbolicName, getLabels(), getProperties()); } @Override public Movie_ withProperties(MapExpression newProperties) { return new Movie_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/abstract_rels/Person_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.abstract_rels; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Person_ extends NodeBase { public static final String $PRIMARY_LABEL = "Person"; public static final Person_ PERSON = new Person_(); public final Property NAME = this.property("name"); public Person_() { super($PRIMARY_LABEL); } private Person_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person_ named(SymbolicName newSymbolicName) { return new Person_(newSymbolicName, getLabels(), getProperties()); } @Override public Person_ withProperties(MapExpression newProperties) { return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/different_properties_for_rel_type/ActedInMovie_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.different_properties_for_rel_type; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class ActedInMovie_ extends RelationshipBase { public static final String $TYPE = "ACTED_IN"; public final Property ROLES = this.property("roles"); public final Property STUNT_DOUBLE = this.property("stuntDouble"); public ActedInMovie_(Person_ start, Movie_ end) { super(start, $TYPE, end); } private ActedInMovie_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public ActedInMovie_ named(SymbolicName newSymbolicName) { return new ActedInMovie_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public ActedInMovie_ withProperties(MapExpression newProperties) { return new ActedInMovie_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/different_properties_for_rel_type/ActedInPlay_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.different_properties_for_rel_type; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class ActedInPlay_ extends RelationshipBase { public static final String $TYPE = "ACTED_IN"; public final Property ROLES = this.property("roles"); public ActedInPlay_(Person_ start, Play_ end) { super(start, $TYPE, end); } private ActedInPlay_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public ActedInPlay_ named(SymbolicName newSymbolicName) { return new ActedInPlay_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public ActedInPlay_ withProperties(MapExpression newProperties) { return new ActedInPlay_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/different_properties_for_rel_type/Movie_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.different_properties_for_rel_type; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Movie_ extends NodeBase { public static final String $PRIMARY_LABEL = "Movie"; public static final Movie_ MOVIE = new Movie_(); public final Property TITLE = this.property("title"); public Movie_() { super($PRIMARY_LABEL); } private Movie_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Movie_ named(SymbolicName newSymbolicName) { return new Movie_(newSymbolicName, getLabels(), getProperties()); } @Override public Movie_ withProperties(MapExpression newProperties) { return new Movie_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/different_properties_for_rel_type/Person_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.different_properties_for_rel_type; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Person_ extends NodeBase { public static final String $PRIMARY_LABEL = "Person"; public static final Person_ PERSON = new Person_(); public final Property NAME = this.property("name"); public final Property BORN = this.property("born"); public final ActedInMovie_ MOVIES = new ActedInMovie_(this, Movie_.MOVIE); public final ActedInPlay_ PLAYS = new ActedInPlay_(this, Play_.PLAY); public Person_() { super($PRIMARY_LABEL); } private Person_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person_ named(SymbolicName newSymbolicName) { return new Person_(newSymbolicName, getLabels(), getProperties()); } @Override public Person_ withProperties(MapExpression newProperties) { return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/different_properties_for_rel_type/Play_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.different_properties_for_rel_type; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Play_ extends NodeBase { public static final String $PRIMARY_LABEL = "Play"; public static final Play_ PLAY = new Play_(); public final Property TITLE = this.property("title"); public Play_() { super($PRIMARY_LABEL); } private Play_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Play_ named(SymbolicName newSymbolicName) { return new Play_(newSymbolicName, getLabels(), getProperties()); } @Override public Play_ withProperties(MapExpression newProperties) { return new Play_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/enums_and_inner_classes/ConnectorTransport_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.enums_and_inner_classes; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class ConnectorTransport_ extends NodeBase { public static final String $PRIMARY_LABEL = "Transport"; public static final ConnectorTransport_ CONNECTOR_TRANSPORT = new ConnectorTransport_(); public final Property VALUE = this.property("value"); public final Property OTHER_ENUM = this.property("otherEnum"); public final Property INNER_INNER_CLASS = this.property("innerInnerClass"); public final Property INNER_INNER_CLASS_BUT_WITHOUT_CONVERTER = this.property("innerInnerClassButWithoutConverter"); public ConnectorTransport_() { super($PRIMARY_LABEL); } private ConnectorTransport_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public ConnectorTransport_ named(SymbolicName newSymbolicName) { return new ConnectorTransport_(newSymbolicName, getLabels(), getProperties()); } @Override public ConnectorTransport_ withProperties(MapExpression newProperties) { return new ConnectorTransport_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/ids/ExternalGeneratedIdImplicit_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.ids; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class ExternalGeneratedIdImplicit_ extends NodeBase { public static final String $PRIMARY_LABEL = "ExternalGeneratedIdImplicit"; public static final ExternalGeneratedIdImplicit_ EXTERNAL_GENERATED_ID_IMPLICIT = new ExternalGeneratedIdImplicit_(); public final Property ID = this.property("id"); public ExternalGeneratedIdImplicit_() { super($PRIMARY_LABEL); } private ExternalGeneratedIdImplicit_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public ExternalGeneratedIdImplicit_ named(SymbolicName newSymbolicName) { return new ExternalGeneratedIdImplicit_(newSymbolicName, getLabels(), getProperties()); } @Override public ExternalGeneratedIdImplicit_ withProperties(MapExpression newProperties) { return new ExternalGeneratedIdImplicit_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/ids/ExternalGeneratedId_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.ids; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class ExternalGeneratedId_ extends NodeBase { public static final String $PRIMARY_LABEL = "ExternalGeneratedId"; public static final ExternalGeneratedId_ EXTERNAL_GENERATED_ID = new ExternalGeneratedId_(); public final Property ID = this.property("id"); public ExternalGeneratedId_() { super($PRIMARY_LABEL); } private ExternalGeneratedId_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public ExternalGeneratedId_ named(SymbolicName newSymbolicName) { return new ExternalGeneratedId_(newSymbolicName, getLabels(), getProperties()); } @Override public ExternalGeneratedId_ withProperties(MapExpression newProperties) { return new ExternalGeneratedId_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/ids/InternalGeneratedId_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.ids; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class InternalGeneratedId_ extends NodeBase { public static final String $PRIMARY_LABEL = "InternalGeneratedId"; public static final InternalGeneratedId_ INTERNAL_GENERATED_ID = new InternalGeneratedId_(); public InternalGeneratedId_() { super($PRIMARY_LABEL); } private InternalGeneratedId_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public InternalGeneratedId_ named(SymbolicName newSymbolicName) { return new InternalGeneratedId_(newSymbolicName, getLabels(), getProperties()); } @Override public InternalGeneratedId_ withProperties(MapExpression newProperties) { return new InternalGeneratedId_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/ids/InternalGeneratedPrimitiveLongId_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.ids; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class InternalGeneratedPrimitiveLongId_ extends NodeBase { public static final String $PRIMARY_LABEL = "InternalGeneratedPrimitiveLongId"; public static final InternalGeneratedPrimitiveLongId_ INTERNAL_GENERATED_PRIMITIVE_LONG_ID = new InternalGeneratedPrimitiveLongId_(); public InternalGeneratedPrimitiveLongId_() { super($PRIMARY_LABEL); } private InternalGeneratedPrimitiveLongId_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public InternalGeneratedPrimitiveLongId_ named(SymbolicName newSymbolicName) { return new InternalGeneratedPrimitiveLongId_(newSymbolicName, getLabels(), getProperties()); } @Override public InternalGeneratedPrimitiveLongId_ withProperties(MapExpression newProperties) { return new InternalGeneratedPrimitiveLongId_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/labels/nodeswithdifferentlabelannotations/LabelOnNode1_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.labels.nodeswithdifferentlabelannotations; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class LabelOnNode1_ extends NodeBase { public static final String $PRIMARY_LABEL = "ALabel"; public static final LabelOnNode1_ LABEL_ON_NODE_1 = new LabelOnNode1_(); public LabelOnNode1_() { super($PRIMARY_LABEL); } private LabelOnNode1_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public LabelOnNode1_ named(SymbolicName newSymbolicName) { return new LabelOnNode1_(newSymbolicName, getLabels(), getProperties()); } @Override public LabelOnNode1_ withProperties(MapExpression newProperties) { return new LabelOnNode1_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/labels/nodeswithdifferentlabelannotations/LabelOnNode2_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.labels.nodeswithdifferentlabelannotations; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class LabelOnNode2_ extends NodeBase { public static final String $PRIMARY_LABEL = "ALabel"; public static final LabelOnNode2_ LABEL_ON_NODE_2 = new LabelOnNode2_(); public LabelOnNode2_() { super($PRIMARY_LABEL); } private LabelOnNode2_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public LabelOnNode2_ named(SymbolicName newSymbolicName) { return new LabelOnNode2_(newSymbolicName, getLabels(), getProperties()); } @Override public LabelOnNode2_ withProperties(MapExpression newProperties) { return new LabelOnNode2_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/org/neo4j/cypherdsl/codegen/ogm/models/related_classes_not_on_cp_like_in_reallife/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.related_classes_not_on_cp_like_in_reallife; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Property; import org.neo4j.ogm.annotation.Relationship; /** * @author Michael J. Simons */ @NodeEntity public final class Movie { @Id private final String title; @Property("tagline") private final String description; @Relationship(value = "DIRECTED", direction = Relationship.Direction.INCOMING) private final List directors; private Type type; private Integer released; public Movie(String title, String description) { this.title = title; this.description = description; this.directors = new ArrayList<>(); } public Movie(String title, String description, List directors) { this.title = title; this.description = description; this.directors = directors == null ? Collections.emptyList() : new ArrayList<>(directors); } public String getTitle() { return title; } public String getDescription() { return description; } public List getDirectors() { return Collections.unmodifiableList(this.directors); } public Integer getReleased() { return released; } public void setReleased(Integer released) { this.released = released; } public Type getType() { return type; } public void setType(Type type) { this.type = type; } public Movie addDirectors(Collection newDirectors) { this.directors.addAll(newDirectors); return this; } public enum Type { AKTION_FILM, KOMÖDIE, SCHNULZE } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/org/neo4j/cypherdsl/codegen/ogm/models/related_classes_not_on_cp_like_in_reallife/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.ogm.models.related_classes_not_on_cp_like_in_reallife; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * @author Michael J. Simons */ @NodeEntity public final class Person { @Id @GeneratedValue private final Long id; private final String name; private Integer born; private Person(Long id, String name, Integer born) { this.id = id; this.born = born; this.name = name; } public Person(String name, Integer born) { this(null, name, born); } public Long getId() { return id; } public String getName() { return name; } public Integer getBorn() { return born; } public void setBorn(Integer born) { this.born = born; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/primitives/Connector_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.primitives; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Connector_ extends NodeBase { public static final String $PRIMARY_LABEL = "Connector"; public static final Connector_ CONNECTOR = new Connector_(); public final Property ID = this.property("id"); public final Property URI = this.property("uri"); public final Property OFFICIAL = this.property("official"); public final Property AN_INT = this.property("anInt"); public final Property A_FLOAT = this.property("aFloat"); public final Property A_DOUBLE = this.property("aDouble"); public Connector_() { super($PRIMARY_LABEL); } private Connector_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Connector_ named(SymbolicName newSymbolicName) { return new Connector_(newSymbolicName, getLabels(), getProperties()); } @Override public Connector_ withProperties(MapExpression newProperties) { return new Connector_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/records/NodeWithRecordProperties_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.records; import java.util.List; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class NodeWithRecordProperties_ extends NodeBase { public static final String $PRIMARY_LABEL = "NodeWithRecordProperties"; public static final NodeWithRecordProperties_ NODE_WITH_RECORD_PROPERTIES = new NodeWithRecordProperties_(); public final Property ID = this.property("id"); public final Property RECORD_AS_PROPERTY_WITH_CONVERSION = this.property("recordAsPropertyWithConversion"); public final Property YOLOING_NO_CONVERSION = this.property("yoloingNoConversion"); public final RecordAsRelationship_ RECORD_AS_RELATIONSHIP = new RecordAsRelationship_(this, RecordTarget_.RECORD_TARGET); public NodeWithRecordProperties_() { super($PRIMARY_LABEL); } private NodeWithRecordProperties_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public NodeWithRecordProperties_ named(SymbolicName newSymbolicName) { return new NodeWithRecordProperties_(newSymbolicName, getLabels(), getProperties()); } @Override public NodeWithRecordProperties_ withProperties(MapExpression newProperties) { return new NodeWithRecordProperties_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/records/RecordAsRelationship_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.records; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class RecordAsRelationship_ extends RelationshipBase { public static final String $TYPE = "recordAsRelationship"; public RecordAsRelationship_(NodeWithRecordProperties_ start, RecordTarget_ end) { super(start, $TYPE, end); } private RecordAsRelationship_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public RecordAsRelationship_ named(SymbolicName newSymbolicName) { return new RecordAsRelationship_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public RecordAsRelationship_ withProperties(MapExpression newProperties) { return new RecordAsRelationship_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/records/RecordTarget_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.records; import java.util.List; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class RecordTarget_ extends NodeBase { public static final String $PRIMARY_LABEL = "RecordTarget"; public static final RecordTarget_ RECORD_TARGET = new RecordTarget_(); public final Property VALUE = this.property("value"); public RecordTarget_() { super($PRIMARY_LABEL); } private RecordTarget_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public RecordTarget_ named(SymbolicName newSymbolicName) { return new RecordTarget_(newSymbolicName, getLabels(), getProperties()); } @Override public RecordTarget_ withProperties(MapExpression newProperties) { return new RecordTarget_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/related_classes_not_on_cp_like_in_reallife/Movie_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.related_classes_not_on_cp_like_in_reallife; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Movie_ extends NodeBase { public static final String $PRIMARY_LABEL = "Movie"; public static final Movie_ MOVIE = new Movie_(); public final Property TITLE = this.property("title"); public final Property DESCRIPTION = this.property("tagline").referencedAs("description"); public final Property TYPE = this.property("type"); public final Property RELEASED = this.property("released"); public final Directed_ DIRECTORS = new Directed_(Person_.PERSON, this); public Movie_() { super($PRIMARY_LABEL); } private Movie_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Movie_ named(SymbolicName newSymbolicName) { return new Movie_(newSymbolicName, getLabels(), getProperties()); } @Override public Movie_ withProperties(MapExpression newProperties) { return new Movie_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/related_classes_not_on_cp_like_in_reallife/Person_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.related_classes_not_on_cp_like_in_reallife; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Person_ extends NodeBase { public static final String $PRIMARY_LABEL = "Person"; public static final Person_ PERSON = new Person_(); public final Property NAME = this.property("name"); public final Property BORN = this.property("born"); public Person_() { super($PRIMARY_LABEL); } private Person_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person_ named(SymbolicName newSymbolicName) { return new Person_(newSymbolicName, getLabels(), getProperties()); } @Override public Person_ withProperties(MapExpression newProperties) { return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_properties_for_rel_type/ActedIn_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_properties_for_rel_type; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class ActedIn_> extends RelationshipBase> { public static final String $TYPE = "ACTED_IN"; public final Property ROLES = this.property("roles"); public ActedIn_(Person_ start, E end) { super(start, $TYPE, end); } private ActedIn_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public ActedIn_ named(SymbolicName newSymbolicName) { return new ActedIn_<>(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public ActedIn_ withProperties(MapExpression newProperties) { return new ActedIn_<>(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_properties_for_rel_type/Movie_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_properties_for_rel_type; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Movie_ extends NodeBase { public static final String $PRIMARY_LABEL = "Movie"; public static final Movie_ MOVIE = new Movie_(); public final Property TITLE = this.property("title"); public Movie_() { super($PRIMARY_LABEL); } private Movie_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Movie_ named(SymbolicName newSymbolicName) { return new Movie_(newSymbolicName, getLabels(), getProperties()); } @Override public Movie_ withProperties(MapExpression newProperties) { return new Movie_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_properties_for_rel_type/Person_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_properties_for_rel_type; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Person_ extends NodeBase { public static final String $PRIMARY_LABEL = "Person"; public static final Person_ PERSON = new Person_(); public final Property NAME = this.property("name"); public final Property BORN = this.property("born"); public final ActedIn_ MOVIES = new ActedIn_(this, Movie_.MOVIE); public final ActedIn_ PLAYS = new ActedIn_(this, Play_.PLAY); public Person_() { super($PRIMARY_LABEL); } private Person_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person_ named(SymbolicName newSymbolicName) { return new Person_(newSymbolicName, getLabels(), getProperties()); } @Override public Person_ withProperties(MapExpression newProperties) { return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_properties_for_rel_type/Play_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_properties_for_rel_type; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Play_ extends NodeBase { public static final String $PRIMARY_LABEL = "Play"; public static final Play_ PLAY = new Play_(); public final Property TITLE = this.property("title"); public Play_() { super($PRIMARY_LABEL); } private Play_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Play_ named(SymbolicName newSymbolicName) { return new Play_(newSymbolicName, getLabels(), getProperties()); } @Override public Play_ withProperties(MapExpression newProperties) { return new Play_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_rel_different_source/Book_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_rel_different_source; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Book_ extends NodeBase { public static final String $PRIMARY_LABEL = "Book"; public static final Book_ BOOK = new Book_(); public final Property TITLE = this.property("title"); public final Wrote_ WRITTEN_BY = new Wrote_(Person_.PERSON, this); public Book_() { super($PRIMARY_LABEL); } private Book_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Book_ named(SymbolicName newSymbolicName) { return new Book_(newSymbolicName, getLabels(), getProperties()); } @Override public Book_ withProperties(MapExpression newProperties) { return new Book_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_rel_different_source/Movie_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_rel_different_source; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Movie_ extends NodeBase { public static final String $PRIMARY_LABEL = "Movie"; public static final Movie_ MOVIE = new Movie_(); public final Property TITLE = this.property("title"); public final Wrote_ WRITTEN_BY = new Wrote_(Person_.PERSON, this); public Movie_() { super($PRIMARY_LABEL); } private Movie_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Movie_ named(SymbolicName newSymbolicName) { return new Movie_(newSymbolicName, getLabels(), getProperties()); } @Override public Movie_ withProperties(MapExpression newProperties) { return new Movie_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_rel_different_source/Person_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_rel_different_source; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Person_ extends NodeBase { public static final String $PRIMARY_LABEL = "Person"; public static final Person_ PERSON = new Person_(); public final Property NAME = this.property("name"); public Person_() { super($PRIMARY_LABEL); } private Person_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person_ named(SymbolicName newSymbolicName) { return new Person_(newSymbolicName, getLabels(), getProperties()); } @Override public Person_ withProperties(MapExpression newProperties) { return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_rel_different_source/Wrote_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_rel_different_source; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Wrote_> extends RelationshipBase> { public static final String $TYPE = "WROTE"; public Wrote_(Person_ start, E end) { super(start, $TYPE, end); } private Wrote_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Wrote_ named(SymbolicName newSymbolicName) { return new Wrote_<>(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Wrote_ withProperties(MapExpression newProperties) { return new Wrote_<>(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_rel_different_target/Book_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_rel_different_target; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Book_ extends NodeBase { public static final String $PRIMARY_LABEL = "Book"; public static final Book_ BOOK = new Book_(); public final Property TITLE = this.property("title"); public Book_() { super($PRIMARY_LABEL); } private Book_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Book_ named(SymbolicName newSymbolicName) { return new Book_(newSymbolicName, getLabels(), getProperties()); } @Override public Book_ withProperties(MapExpression newProperties) { return new Book_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_rel_different_target/Movie_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_rel_different_target; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Movie_ extends NodeBase { public static final String $PRIMARY_LABEL = "Movie"; public static final Movie_ MOVIE = new Movie_(); public final Property TITLE = this.property("title"); public Movie_() { super($PRIMARY_LABEL); } private Movie_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Movie_ named(SymbolicName newSymbolicName) { return new Movie_(newSymbolicName, getLabels(), getProperties()); } @Override public Movie_ withProperties(MapExpression newProperties) { return new Movie_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_rel_different_target/Person_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_rel_different_target; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Person_ extends NodeBase { public static final String $PRIMARY_LABEL = "Person"; public static final Person_ PERSON = new Person_(); public final Property NAME = this.property("name"); public final Wrote_ WRITTEN_MOVIES = new Wrote_(this, Movie_.MOVIE); public final Wrote_ WRITTEN_BOOKS = new Wrote_(this, Book_.BOOK); public Person_() { super($PRIMARY_LABEL); } private Person_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person_ named(SymbolicName newSymbolicName) { return new Person_(newSymbolicName, getLabels(), getProperties()); } @Override public Person_ withProperties(MapExpression newProperties) { return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_rel_different_target/Wrote_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_rel_different_target; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Wrote_> extends RelationshipBase> { public static final String $TYPE = "WROTE"; public Wrote_(Person_ start, E end) { super(start, $TYPE, end); } private Wrote_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Wrote_ named(SymbolicName newSymbolicName) { return new Wrote_<>(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Wrote_ withProperties(MapExpression newProperties) { return new Wrote_<>(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_rel_mixed/Book_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_rel_mixed; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Book_ extends NodeBase { public static final String $PRIMARY_LABEL = "Book"; public static final Book_ BOOK = new Book_(); public final Property TITLE = this.property("title"); public final Wrote_ WRITTEN_BY = new Wrote_(Person_.PERSON, this); public Book_() { super($PRIMARY_LABEL); } private Book_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Book_ named(SymbolicName newSymbolicName) { return new Book_(newSymbolicName, getLabels(), getProperties()); } @Override public Book_ withProperties(MapExpression newProperties) { return new Book_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_rel_mixed/Movie_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_rel_mixed; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Movie_ extends NodeBase { public static final String $PRIMARY_LABEL = "Movie"; public static final Movie_ MOVIE = new Movie_(); public final Property TITLE = this.property("title"); public Movie_() { super($PRIMARY_LABEL); } private Movie_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Movie_ named(SymbolicName newSymbolicName) { return new Movie_(newSymbolicName, getLabels(), getProperties()); } @Override public Movie_ withProperties(MapExpression newProperties) { return new Movie_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_rel_mixed/Person_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_rel_mixed; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Person_ extends NodeBase { public static final String $PRIMARY_LABEL = "Person"; public static final Person_ PERSON = new Person_(); public final Property NAME = this.property("name"); public final Wrote_ WRITTEN_MOVIES = new Wrote_(this, Movie_.MOVIE); public Person_() { super($PRIMARY_LABEL); } private Person_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person_ named(SymbolicName newSymbolicName) { return new Person_(newSymbolicName, getLabels(), getProperties()); } @Override public Person_ withProperties(MapExpression newProperties) { return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_rel_mixed/Wrote_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_rel_mixed; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Wrote_> extends RelationshipBase> { public static final String $TYPE = "WROTE"; public Wrote_(Person_ start, E end) { super(start, $TYPE, end); } private Wrote_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Wrote_ named(SymbolicName newSymbolicName) { return new Wrote_<>(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Wrote_ withProperties(MapExpression newProperties) { return new Wrote_<>(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_rel_mixed_different_directions/Book_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_rel_mixed_different_directions; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Book_ extends NodeBase { public static final String $PRIMARY_LABEL = "Book"; public static final Book_ BOOK = new Book_(); public final Property TITLE = this.property("title"); public final Wrote_ WRITTEN_BY = new Wrote_(this, Person_.PERSON); public Book_() { super($PRIMARY_LABEL); } private Book_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Book_ named(SymbolicName newSymbolicName) { return new Book_(newSymbolicName, getLabels(), getProperties()); } @Override public Book_ withProperties(MapExpression newProperties) { return new Book_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_rel_mixed_different_directions/Movie_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_rel_mixed_different_directions; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Movie_ extends NodeBase { public static final String $PRIMARY_LABEL = "Movie"; public static final Movie_ MOVIE = new Movie_(); public final Property TITLE = this.property("title"); public final Wrote_ WRITTEN_BY = new Wrote_(Person_.PERSON, this); public Movie_() { super($PRIMARY_LABEL); } private Movie_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Movie_ named(SymbolicName newSymbolicName) { return new Movie_(newSymbolicName, getLabels(), getProperties()); } @Override public Movie_ withProperties(MapExpression newProperties) { return new Movie_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_rel_mixed_different_directions/Person_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_rel_mixed_different_directions; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Person_ extends NodeBase { public static final String $PRIMARY_LABEL = "Person"; public static final Person_ PERSON = new Person_(); public final Property NAME = this.property("name"); public Person_() { super($PRIMARY_LABEL); } private Person_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person_ named(SymbolicName newSymbolicName) { return new Person_(newSymbolicName, getLabels(), getProperties()); } @Override public Person_ withProperties(MapExpression newProperties) { return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/same_rel_mixed_different_directions/Wrote_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.same_rel_mixed_different_directions; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Wrote_, E extends NodeBase> extends RelationshipBase> { public static final String $TYPE = "WROTE"; public Wrote_(S start, E end) { super(start, $TYPE, end); } private Wrote_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Wrote_ named(SymbolicName newSymbolicName) { return new Wrote_<>(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Wrote_ withProperties(MapExpression newProperties) { return new Wrote_<>(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/self_referential/Example_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.self_referential; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Example_ extends NodeBase { public static final String $PRIMARY_LABEL = "Example"; public static final Example_ EXAMPLE = new Example_(); public final Property ID = this.property("id"); public Example_() { super($PRIMARY_LABEL); } private Example_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Example_ named(SymbolicName newSymbolicName) { return new Example_(newSymbolicName, getLabels(), getProperties()); } @Override public Example_ withProperties(MapExpression newProperties) { return new Example_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } public BelongsTo_ withParent(Example_ parent) { return new BelongsTo_(this, parent); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/simple/ActedIn_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.simple; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class ActedIn_ extends RelationshipBase { public static final String $TYPE = "ACTED_IN"; public final Property ROLES = this.property("roles"); public ActedIn_(Person_ start, Movie_ end) { super(start, $TYPE, end); } private ActedIn_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public ActedIn_ named(SymbolicName newSymbolicName) { return new ActedIn_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public ActedIn_ withProperties(MapExpression newProperties) { return new ActedIn_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/simple/Directed_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.simple; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Directed_ extends RelationshipBase { public static final String $TYPE = "DIRECTED"; public Directed_(Person_ start, Movie_ end) { super(start, $TYPE, end); } private Directed_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Directed_ named(SymbolicName newSymbolicName) { return new Directed_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Directed_ withProperties(MapExpression newProperties) { return new Directed_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/simple/Follows_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.simple; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Follows_ extends RelationshipBase { public static final String $TYPE = "follows"; public Follows_(Person_ start, Person_ end) { super(start, $TYPE, end); } private Follows_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Follows_ named(SymbolicName newSymbolicName) { return new Follows_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Follows_ withProperties(MapExpression newProperties) { return new Follows_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/simple/Movie_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.simple; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Movie_ extends NodeBase { public static final String $PRIMARY_LABEL = "Movie"; public static final Movie_ MOVIE = new Movie_(); public final Property TITLE = this.property("title"); public final Property DESCRIPTION = this.property("tagline").referencedAs("description"); public final Property RELEASED = this.property("released"); public final ActedIn_ ACTORS = new ActedIn_(Person_.PERSON, this); public final Directed_ DIRECTORS = new Directed_(Person_.PERSON, this); public Movie_() { super($PRIMARY_LABEL); } private Movie_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Movie_ named(SymbolicName newSymbolicName) { return new Movie_(newSymbolicName, getLabels(), getProperties()); } @Override public Movie_ withProperties(MapExpression newProperties) { return new Movie_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/simple/Person_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.simple; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Person_ extends NodeBase { public static final String $PRIMARY_LABEL = "Person"; public static final Person_ PERSON = new Person_(); public final Property NAME = this.property("name"); public final Property BORN = this.property("born"); public final Produced_ PRODUCED = new Produced_(this, Movie_.MOVIE); public Person_() { super($PRIMARY_LABEL); } private Person_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person_ named(SymbolicName newSymbolicName) { return new Person_(newSymbolicName, getLabels(), getProperties()); } @Override public Person_ withProperties(MapExpression newProperties) { return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } public Follows_ withFollows(Person_ follows) { return new Follows_(this, follows); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-ogm/src/test/resources/simple/Produced_.java ================================================ package org.neo4j.cypherdsl.codegen.ogm.models.simple; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2025-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Produced_ extends RelationshipBase { public static final String $TYPE = "produced"; public Produced_(Person_ start, Movie_ end) { super(start, $TYPE, end); } private Produced_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Produced_ named(SymbolicName newSymbolicName) { return new Produced_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Produced_ withProperties(MapExpression newProperties) { return new Produced_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/pom.xml ================================================ 4.0.0 org.neo4j neo4j-cypher-dsl-codegen ${revision}${sha1}${changelist} neo4j-cypher-dsl-codegen-sdn6 Code Generator (SDN 6) Annotation processor reading SDN 6 annotations and creating a static model of them. 0.7 0.7 org.neo4j.cypherdsl.codegen.sdn6 ${basedir}/../../${aggregate.report.dir} org.neo4j neo4j-cypher-dsl-codegen-core org.slf4j slf4j-simple org.springframework.data spring-data-neo4j com.google.testing.compile compile-testing test org.assertj assertj-core test org.junit.jupiter junit-jupiter test org.junit.vintage junit-vintage-engine test src/test/java org/neo4j/cypherdsl/codegen/sdn6/models/**/*.java src/test/resources org.apache.maven.plugins maven-surefire-plugin @{argLine} --add-opens jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED org.apache.maven.plugins maven-compiler-plugin -proc:none org.apache.maven.plugins maven-jar-plugin ${java-module-name} ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/main/java/org/neo4j/cypherdsl/codegen/sdn6/SDN6AnnotationProcessor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6; import java.io.IOException; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedOptions; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.RecordComponentElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementKindVisitor8; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleTypeVisitor8; import javax.tools.Diagnostic; import org.apiguardian.api.API; import org.neo4j.cypherdsl.codegen.core.AbstractMappingAnnotationProcessor; import org.neo4j.cypherdsl.codegen.core.Configuration; import org.neo4j.cypherdsl.codegen.core.ModelBuilder; import org.neo4j.cypherdsl.codegen.core.NodeModelBuilder; import org.neo4j.cypherdsl.codegen.core.PropertyDefinition; import org.neo4j.cypherdsl.codegen.core.RelationshipModelBuilder; import org.neo4j.cypherdsl.codegen.core.RelationshipPropertyDefinition; import org.springframework.data.neo4j.core.convert.Neo4jConversions; import org.springframework.data.neo4j.core.convert.Neo4jPersistentPropertyConverter; import org.springframework.data.neo4j.core.convert.Neo4jSimpleTypes; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Property; import org.springframework.data.neo4j.core.schema.Relationship; import static org.apiguardian.api.API.Status.EXPERIMENTAL; /** * Annotation processor supporting the annotations provided by * Spring Data Neo4j * 6+. * * @author Michael J. Simons * @since 2021.1.0 */ @API(status = EXPERIMENTAL, since = "2021.1.0") @SupportedAnnotationTypes({ SDN6AnnotationProcessor.NODE_ANNOTATION, SDN6AnnotationProcessor.RELATIONSHIP_PROPERTIES_ANNOTATION }) @SupportedOptions({ Configuration.PROPERTY_PREFIX, Configuration.PROPERTY_SUFFIX, Configuration.PROPERTY_INDENT_STYLE, Configuration.PROPERTY_INDENT_SIZE, Configuration.PROPERTY_TIMESTAMP, Configuration.PROPERTY_ADD_AT_GENERATED, SDN6AnnotationProcessor.PROPERTY_CUSTOM_CONVERTER_CLASSES, Configuration.PROPERTY_EXCLUDES }) public final class SDN6AnnotationProcessor extends AbstractMappingAnnotationProcessor { static final String NODE_ANNOTATION = "org.springframework.data.neo4j.core.schema.Node"; static final String RELATIONSHIP_PROPERTIES_ANNOTATION = "org.springframework.data.neo4j.core.schema.RelationshipProperties"; static final String PROPERTY_CUSTOM_CONVERTER_CLASSES = "org.neo4j.cypherdsl.codegen.sdn.custom_converter_classes"; static final Set VALID_GENERATED_ID_TYPES = Set.of(Long.class.getName(), long.class.getName()); // Resources // * http://hannesdorfmann.com/annotation-processing/annotationprocessing101/ // * // https://speakerdeck.com/gunnarmorling/das-annotation-processing-api-use-cases-und-best-practices static { disableSpringConverterDebugLog(); } private TypeElement nodeAnnotationType; private TypeElement relationshipAnnotationType; private TypeElement compositePropertyAnnotationType; private TypeElement convertWithAnnotationType; private TypeElement propertyAnnotationType; private TypeElement targetNodeAnnotationType; private TypeElement relationshipPropertiesAnnotationType; private TypeElement sdnIdAnnotationType; private TypeElement sdcIdAnnotationType; private TypeElement generatedValueAnnotationType; private Neo4jConversions conversions; @SuppressWarnings("PMD") private static void disableSpringConverterDebugLog() { try { Class logback = Class.forName("ch.qos.logback.classic.Logger"); // Don't replace the qualified names, Checkstyle will yell at you. logback.getMethod("setLevel") .invoke(org.slf4j.LoggerFactory.getLogger("org.springframework.data.convert.CustomConversions"), org.slf4j.event.Level.DEBUG); } catch (Exception ex) { // There's nothing we can do or should do in case there is no logback. } } @Override protected void initFrameworkSpecific(ProcessingEnvironment processingEnv) { this.conversions = createConversions(processingEnv); Elements elementUtils = processingEnv.getElementUtils(); this.nodeAnnotationType = elementUtils.getTypeElement(NODE_ANNOTATION); this.relationshipPropertiesAnnotationType = elementUtils.getTypeElement(RELATIONSHIP_PROPERTIES_ANNOTATION); this.relationshipAnnotationType = elementUtils .getTypeElement("org.springframework.data.neo4j.core.schema.Relationship"); this.propertyAnnotationType = elementUtils .getTypeElement("org.springframework.data.neo4j.core.schema.Property"); this.compositePropertyAnnotationType = elementUtils .getTypeElement("org.springframework.data.neo4j.core.schema.CompositeProperty"); this.convertWithAnnotationType = elementUtils .getTypeElement("org.springframework.data.neo4j.core.convert.ConvertWith"); this.targetNodeAnnotationType = elementUtils .getTypeElement("org.springframework.data.neo4j.core.schema.TargetNode"); this.sdnIdAnnotationType = elementUtils.getTypeElement("org.springframework.data.neo4j.core.schema.Id"); this.sdcIdAnnotationType = elementUtils.getTypeElement("org.springframework.data.annotation.Id"); this.generatedValueAnnotationType = elementUtils .getTypeElement("org.springframework.data.neo4j.core.schema.GeneratedValue"); } /** * Create an instance of {@link Neo4jConversions} and registers optional additional * converters with it. The converters must have a default-non-args constructor. * @param processingEnv the processing environment * @return a conversions instance */ private Neo4jConversions createConversions(ProcessingEnvironment processingEnv) { Map options = processingEnv.getOptions(); if (!options.containsKey(PROPERTY_CUSTOM_CONVERTER_CLASSES)) { return new Neo4jConversions(); } String classNames = options.get(PROPERTY_CUSTOM_CONVERTER_CLASSES); List converters = new ArrayList<>(); Arrays.stream(classNames.split(",")).map(String::trim).filter(cn -> !cn.isEmpty()).forEach(cn -> { try { Class clazz = Class.forName(cn); if (Neo4jPersistentPropertyConverter.class.isAssignableFrom(clazz)) { this.messager.printMessage(Diagnostic.Kind.MANDATORY_WARNING, "Cannot use dedicated Neo4j persistent property converter of type `" + cn + "` as Spring converter, it will be ignored."); return; } converters.add(clazz.getDeclaredConstructor().newInstance()); } catch (Exception ex) { String message = ex.getMessage(); if (ex instanceof ClassNotFoundException) { message = "Class `" + cn + "` not found"; } this.messager.printMessage(Diagnostic.Kind.MANDATORY_WARNING, "Cannot load converter of type `" + cn + "`, it will be ignored: " + message + "."); } }); return new Neo4jConversions(converters); } @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { if (annotations.isEmpty()) { return false; } Map nodeBuilders = populateListOfNodes( getTypesAnnotatedWith(this.nodeAnnotationType, roundEnv)); Map>> relationshipProperties = collectRelationshipProperties( roundEnv); Map> relationshipFields = populateNodePropertiesAndCollectRelationshipFields( nodeBuilders); Map> relationshipBuilders = populateListOfRelationships( computeRelationshipDefinitions(relationshipFields, relationshipProperties, nodeBuilders)); List> allBuilders = new ArrayList<>(nodeBuilders.values()); relationshipBuilders.values().forEach(allBuilders::addAll); try { writeSourceFiles(allBuilders); } catch (IOException ex) { this.messager.printMessage(Diagnostic.Kind.ERROR, "Could not write source files: " + ex.getMessage()); } return true; } @Override protected Collection getLabel(TypeElement annotatedClass) { Node nodeAnnotation = annotatedClass.getAnnotation(Node.class); Set labels = new LinkedHashSet<>(); Consumer addLabel = label -> { if (!label.isEmpty()) { labels.add(label); } }; addLabel.accept(nodeAnnotation.primaryLabel()); Arrays.stream(nodeAnnotation.value()).forEach(addLabel); Arrays.stream(nodeAnnotation.labels()).forEach(addLabel); if (labels.isEmpty()) { addLabel.accept(annotatedClass.getSimpleName().toString()); } return Collections.unmodifiableCollection(labels); } /** * Collects classes annotated with {@code @RelationshipProperties}. * @param roundEnvironment the current environment * @return a map from the other end of the relationship (target node) to the * properties of the relationship */ private Map>> collectRelationshipProperties( RoundEnvironment roundEnvironment) { Map>> result = new HashMap<>(); Set relationshipProperties = getTypesAnnotatedWith(this.relationshipPropertiesAnnotationType, roundEnvironment); relationshipProperties.forEach(e -> { List properties = new ArrayList<>(); TypeElement actualTargetType = null; for (Element enclosedElement : e.getEnclosedElements()) { if (!enclosedElement.getKind().isField()) { continue; } Set declaredAnnotations = enclosedElement.getAnnotationMirrors() .stream() .map(AnnotationMirror::getAnnotationType) .map(DeclaredType::asElement) .collect(Collectors.toSet()); if (declaredAnnotations.contains(this.targetNodeAnnotationType)) { Element element = this.typeUtils.asElement(enclosedElement.asType()); actualTargetType = element.accept(new TypeElementVisitor<>(Function.identity()), null); if (actualTargetType == null) { this.messager.printMessage(Diagnostic.Kind.WARNING, "Cannot resolve generic type, not generating a property for relationships referring to " + e.getQualifiedName(), element); } } else { properties.add(asPropertyDefinition(enclosedElement)); } } if (actualTargetType != null) { result.put(e, new AbstractMap.SimpleEntry<>(actualTargetType, Collections.unmodifiableList(properties))); } }); return Collections.unmodifiableMap(result); } @Override protected PropertyDefinition asPropertyDefinition(Element e) { Optional optionalPropertyAnnotation = Optional.ofNullable(e.getAnnotation(Property.class)); PropertyDefinition propertyDefinition; String fieldName = e.getSimpleName().toString(); if (optionalPropertyAnnotation.isPresent()) { Property propertyAnnotation = optionalPropertyAnnotation.get(); String nameValue = propertyAnnotation.name(); String valueValue = propertyAnnotation.value(); if (!nameValue.isEmpty() && !valueValue.isEmpty()) { if (!nameValue.equals(valueValue)) { this.messager.printMessage(Diagnostic.Kind.ERROR, "Different @AliasFor mirror values for annotation [org.springframework.data.neo4j.core.schema.Property]!", e); } propertyDefinition = PropertyDefinition.create(nameValue, fieldName); } else if (!nameValue.isEmpty()) { propertyDefinition = PropertyDefinition.create(nameValue, fieldName); } else if (!valueValue.isEmpty()) { propertyDefinition = PropertyDefinition.create(valueValue, fieldName); } else { propertyDefinition = PropertyDefinition.create(fieldName, null); } } else { propertyDefinition = PropertyDefinition.create(fieldName, null); } return propertyDefinition; } @Override protected RelationshipPropertyDefinition asRelationshipDefinition(NodeModelBuilder owner, Element e, Map>> relationshipProperties, Map nodeBuilders) { Optional optionalRelationshipAnnotation = Optional .ofNullable(e.getAnnotation(Relationship.class)); String fieldName = e.getSimpleName().toString(); // Default SDN 6 is outgoing. SDN 6 does not support undirected. boolean isIncoming = false; String relationshipType; if (optionalRelationshipAnnotation.isPresent()) { Relationship relationshipAnnotation = optionalRelationshipAnnotation.get(); String typeValue = relationshipAnnotation.type(); String valueValue = relationshipAnnotation.value(); isIncoming = relationshipAnnotation.direction() == Relationship.Direction.INCOMING; if (!typeValue.isEmpty() && !valueValue.isEmpty()) { if (!typeValue.equals(valueValue)) { this.messager.printMessage(Diagnostic.Kind.ERROR, "Different @AliasFor mirror values for annotation [org.springframework.data.neo4j.core.schema.Relationship]!", e); } relationshipType = typeValue; } else if (!typeValue.isEmpty()) { relationshipType = typeValue; } else if (!valueValue.isEmpty()) { relationshipType = valueValue; } else { relationshipType = fieldName; } } else { relationshipType = fieldName; } DeclaredType declaredType = e.asType().accept(new SimpleTypeVisitor8() { @Override public DeclaredType visitDeclared(DeclaredType t, Void unused) { return t; } }, null); if (declaredType == null) { return null; } TypeMirror relatedType = null; if (declaredType.getTypeArguments().size() == 1) { relatedType = declaredType.getTypeArguments().get(0); } else if (declaredType.getTypeArguments().isEmpty()) { relatedType = declaredType; } Element key = this.typeUtils.asElement(relatedType); NodeModelBuilder end = (key != null) ? nodeBuilders.get(key) : null; List properties = null; String optionalPropertyHolder = null; if (key == null) { return null; } else if (end == null) { Map.Entry> typeAndProperties = relationshipProperties.get(key); if (typeAndProperties != null) { optionalPropertyHolder = key.toString(); end = nodeBuilders.get(typeAndProperties.getKey()); properties = typeAndProperties.getValue(); } } if (end == null) { return null; } else if (isIncoming) { return RelationshipPropertyDefinition.create(relationshipType, optionalPropertyHolder, fieldName, end, owner, properties); } else { return RelationshipPropertyDefinition.create(relationshipType, optionalPropertyHolder, fieldName, owner, end, properties); } } @Override protected PropertiesAndRelationshipGrouping newPropertiesAndRelationshipGrouping() { return new GroupPropertiesAndRelationships(); } /** * Pre-groups fields into properties and relationships to avoid running the * association check multiple times. */ @SuppressWarnings("squid:S110") // Not something we need or can do anything about. class GroupPropertiesAndRelationships extends ElementKindVisitor8>, Void> implements PropertiesAndRelationshipGrouping { private final Map> result; GroupPropertiesAndRelationships() { final Map> hlp = new EnumMap<>(FieldType.class); hlp.put(FieldType.R, new ArrayList<>()); hlp.put(FieldType.P, new ArrayList<>()); this.result = Collections.unmodifiableMap(hlp); } @Override public void apply(Element element) { element.accept(this, null); } @Override public Map> getResult() { return this.result; } @Override public Map> visitTypeAsRecord(TypeElement e, Void unused) { // We must overwrite this or visitUnknown() in case we encounter a record return this.result; } @Override public Map> visitRecordComponent(RecordComponentElement e, Void unused) { return visitFieldOrRecordComponent(e); } @Override public Map> visitVariableAsField(VariableElement e, Void unused) { return visitFieldOrRecordComponent(e); } private Map> visitFieldOrRecordComponent(Element e) { Set declaredAnnotations = e.getAnnotationMirrors() .stream() .map(AnnotationMirror::getAnnotationType) .map(DeclaredType::asElement) .collect(Collectors.toSet()); // Skip internal ids if (isInternalId(e, declaredAnnotations)) { return this.result; } this.result.get(isAssociation(declaredAnnotations, e) ? FieldType.R : FieldType.P).add(e); return this.result; } private boolean isInternalId(Element e, Set declaredAnnotations) { boolean idAnnotationPresent = declaredAnnotations.contains(SDN6AnnotationProcessor.this.sdcIdAnnotationType) || declaredAnnotations.contains(SDN6AnnotationProcessor.this.sdnIdAnnotationType); if (!idAnnotationPresent) { return false; } return e.getAnnotationMirrors() .stream() .filter(m -> m.getAnnotationType() .asElement() .equals(SDN6AnnotationProcessor.this.generatedValueAnnotationType)) .findFirst() .map(generatedValue -> isUsingInternalIdGenerator(e, generatedValue)) .orElse(false); } private boolean isUsingInternalIdGenerator(Element e, AnnotationMirror generatedValue) { Map values = generatedValue.getElementValues() .entrySet() .stream() .collect(Collectors.toMap(entry -> entry.getKey().getSimpleName().toString(), Map.Entry::getValue)); DeclaredType generatorClassValue = values.containsKey("generatorClass") ? (DeclaredType) values.get("generatorClass").getValue() : null; DeclaredType valueValue = values.containsKey("value") ? (DeclaredType) values.get("value").getValue() : null; String name = null; if (generatorClassValue != null && valueValue != null && !generatorClassValue.equals(valueValue)) { SDN6AnnotationProcessor.this.messager.printMessage(Diagnostic.Kind.ERROR, "Different @AliasFor mirror values for annotation [org.springframework.data.neo4j.core.schema.GeneratedValue]!", e); } else if (generatorClassValue != null) { name = generatorClassValue.toString(); } else if (valueValue != null) { name = valueValue.toString(); } // The defaults will not be materialized return (name == null || "org.springframework.data.neo4j.core.schema.GeneratedValue.InternalIdGenerator".equals(name)) && VALID_GENERATED_ID_TYPES.contains(e.asType().toString()); } /** * Reassembles * org.springframework.data.neo4j.core.mapping.DefaultNeo4jPersistentProperty#isAssociation. * @param declaredAnnotations the set of annotations declared on the given field * @param field a variable element describing a field. No further checks done if * this is true or not * @return true if this field is an association */ private boolean isAssociation(Set declaredAnnotations, Element field) { TypeMirror typeMirrorOfField = field.asType(); if (declaredAnnotations.contains(SDN6AnnotationProcessor.this.relationshipAnnotationType)) { return true; } if (declaredAnnotations.contains(SDN6AnnotationProcessor.this.propertyAnnotationType)) { return false; } boolean isTargetNodeOrComposite = declaredAnnotations .contains(SDN6AnnotationProcessor.this.targetNodeAnnotationType) || declaredAnnotations.contains(SDN6AnnotationProcessor.this.compositePropertyAnnotationType); BooleanSupplier simpleTypeOrCustomWriteTarget = () -> { try { String className = typeMirrorOfField.accept(new SimpleTypeVisitor8() { @Override public String visitPrimitive(PrimitiveType t, Void unused) { // While I could use the fact that this is a primitive // directly, I'd rather stick with the // wrapper class so that in turn this can be passed on to the // Spring infrastructure. return SDN6AnnotationProcessor.this.typeUtils.boxedClass(t).getQualifiedName().toString(); } @Override public String visitDeclared(DeclaredType t, Void unused) { return t.asElement().accept(new TypeElementVisitor<>(newTypeElementNameFunction()), null); } }, null); // This is likely to fail for everything but primitives as the // associated thing is currently compiled Class fieldType = Class.forName(className); return Neo4jSimpleTypes.HOLDER.isSimpleType(fieldType) || SDN6AnnotationProcessor.this.conversions.hasCustomWriteTarget(fieldType); } catch (ClassNotFoundException ex) { return false; } }; // They will be converted anyway if (describesEnum(typeMirrorOfField)) { return false; } return !(isTargetNodeOrComposite || declaredAnnotations.contains(SDN6AnnotationProcessor.this.convertWithAnnotationType) || simpleTypeOrCustomWriteTarget.getAsBoolean()); } } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/main/java/org/neo4j/cypherdsl/codegen/sdn6/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * Contains the SDN6+ annotation processor that creates a static metamodel for SDN * annotated classes. */ package org.neo4j.cypherdsl.codegen.sdn6; ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/main/resources/META-INF/services/javax.annotation.processing.Processor ================================================ org.neo4j.cypherdsl.codegen.sdn6.SDN6AnnotationProcessor ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/SDN6AnnotationProcessorTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6; import java.io.IOException; import java.io.UncheckedIOException; import java.util.Arrays; import java.util.HashSet; import javax.tools.JavaFileObject; import javax.tools.ToolProvider; import com.google.testing.compile.Compilation; import com.google.testing.compile.CompilationSubject; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; import org.jspecify.annotations.NonNull; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.converter.ArgumentConversionException; import org.junit.jupiter.params.converter.ConvertWith; import org.junit.jupiter.params.converter.SimpleArgumentConverter; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.core.convert.converter.Converter; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons * */ class SDN6AnnotationProcessorTests { /** * We have Spring on the Classpath anyway... So not reinvent the wheel for finding the * resources. */ private static final PathMatchingResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); static Compiler getCompiler(String release, Object... options) { String ts = "-Aorg.neo4j.cypherdsl.codegen.timestamp=2019-09-21T21:21:00+01:00"; Object[] defaultOptions; if (ToolProvider.getSystemJavaCompiler().isSupportedOption("--release") >= 0) { // release 8 deprecated since Java 21, surprised with -options defaultOptions = new Object[] { "-Xlint:-options", ts, "--release", release }; } else if ("8".equals(release)) { defaultOptions = new Object[] { ts, "-source", "8", "-target", "8" }; } else { throw new IllegalArgumentException("Release %s not supported for testing".formatted(release)); } Object[] finalOptions = new Object[options.length + defaultOptions.length]; System.arraycopy(defaultOptions, 0, finalOptions, 0, defaultOptions.length); System.arraycopy(options, 0, finalOptions, defaultOptions.length, options.length); return Compiler.javac().withOptions(finalOptions); } JavaFileObject[] getJavaResources(String base) { try { Resource[] resources = resourceResolver.getResources(base + "/**/*.java"); JavaFileObject[] result = new JavaFileObject[resources.length]; for (int i = 0; i < resources.length; i++) { result[i] = JavaFileObjects.forResource(resources[i].getURL()); } return result; } catch (IOException ex) { throw new UncheckedIOException(ex); } } @ValueSource(strings = { "foo", "org.neo4j.cypherdsl.codegen.sdn6.SDN6AnnotationProcessorTest$SomeConverter", "org.neo4j.cypherdsl.codegen.sdn6.models.enums_and_inner_classes.InnerInnerClassConverter" }) @ParameterizedTest void shouldNotFailWithInvalidConverters(String converter) { Compilation compilation = getCompiler("8", "-Aorg.neo4j.cypherdsl.codegen.sdn.custom_converter_classes=" + converter) .withProcessors(new SDN6AnnotationProcessor()) .compile(getJavaResources("org/neo4j/cypherdsl/codegen/sdn6/models/simple")); CompilationSubject.assertThat(compilation).succeeded(); String expectedMessage; if (converter.endsWith("InnerInnerClassConverter")) { expectedMessage = "Cannot use dedicated Neo4j persistent property converter of type `" + converter + "` as Spring converter, it will be ignored."; } else { expectedMessage = "Cannot load converter of type `" + converter + "`, it will be ignored: "; } CompilationSubject.assertThat(compilation).hadWarningContaining(expectedMessage); } @Test void shouldRecognizeGlobalConvertersOnInnerClasses() { Compilation compilation = getCompiler("8", "-Aorg.neo4j.cypherdsl.codegen.sdn.custom_converter_classes=org.neo4j.cypherdsl.codegen.sdn6.models.enums_and_inner_classes.SpringBasedConverter") .withProcessors(new SDN6AnnotationProcessor()) .compile(getJavaResources("org/neo4j/cypherdsl/codegen/sdn6/models/enums_and_inner_classes")); CompilationSubject.assertThat(compilation).succeeded(); CompilationSubject.assertThat(compilation) .generatedSourceFile("org.neo4j.cypherdsl.codegen.sdn6.models.enums_and_inner_classes.ConnectorTransport_") .hasSourceEquivalentTo( JavaFileObjects.forResource("enums_and_inner_classes/ConnectorTransportWithGlobalConverter_.java")); } @CsvSource({ "8, ids, 'InternalGeneratedId, InternalGeneratedIdWithSpringId, ExternalGeneratedId, ExternalGeneratedIdImplicit, InternalGeneratedPrimitiveLongId',", "8, simple, 'Person, Movie, ActedIn, Follows, Directed, Produced, Src, Target2, Rel21, Rel22',", "8, labels, 'LabelOnNode1, LabelOnNode2, LabelOnNode3, MultipleLabels1, MultipleLabels2, MultipleLabels3', nodeswithdifferentlabelannotations", "8, same_properties_for_rel_type, 'Person, Movie, Play, ActedIn', ", "8, different_properties_for_rel_type, 'Person, Movie, Play, ActedInPlay, ActedInMovie', ", "8, same_rel_different_target, 'Person, Movie, Book, Wrote', ", "8, same_rel_different_source, 'Person, Movie, Book, Wrote', ", "8, same_rel_mixed, 'Person, Movie, Book, Wrote', ", "8, same_rel_mixed_different_directions, 'Person, Movie, Book, Wrote', ", "8, abstract_rels, 'Person, Movie, Directed',", "8, primitives, 'Connector', ", "8, enums_and_inner_classes, 'ConnectorTransport', ", "8, related_classes_not_on_cp_like_in_reallife, 'Movie, Person, Directed', ", "8, self_referential, 'Example, BelongsTo', ", "17, records, 'NodeWithRecordProperties, RecordAsRelationship, RecordTarget', " }) @ParameterizedTest void validSourceFiles(String release, String scenario, @ConvertWith(StringArrayConverter.class) String[] expected, String subpackage) { var options = """ -Aorg.neo4j.cypherdsl.codegen.excludes= org.neo4j.cypherdsl.codegen.sdn6.models.simple.TotallyIgnored, org.neo4j.cypherdsl.codegen.sdn6.models.simple.Target, org.neo4j.cypherdsl.codegen.sdn6.models.simple.Edge, org.neo4j.cypherdsl.codegen.sdn6.models.simple.Target3, """; Compilation compilation = getCompiler(release, options).withProcessors(new SDN6AnnotationProcessor()) .compile(getJavaResources("org/neo4j/cypherdsl/codegen/sdn6/models/" + scenario)); CompilationSubject.assertThat(compilation).succeeded(); if ("abstract_rels".equals(scenario)) { CompilationSubject.assertThat(compilation) .hadWarningContaining( "Cannot resolve generic type, not generating a property for relationships referring to org.neo4j.cypherdsl.codegen.sdn6.models.abstract_rels.Actor"); } else { CompilationSubject.assertThat(compilation).hadWarningCount(0); } var allExpectedNames = new HashSet(); for (String expectedSourceFile : expected) { var typeName = scenario + "." + ((subpackage != null) ? subpackage + "." : "") + expectedSourceFile + "_"; var resourceName = typeName.replaceAll("\\.", "/") + ".java"; CompilationSubject.assertThat(compilation) .generatedSourceFile("org.neo4j.cypherdsl.codegen.sdn6.models." + typeName) .hasSourceEquivalentTo(JavaFileObjects.forResource(resourceName)); allExpectedNames.add(resourceName); } var generated = compilation.generatedSourceFiles() .stream() .map(JavaFileObject::getName) .map(name -> name.substring(name.lastIndexOf(scenario))) .toList(); assertThat(generated).containsOnly(allExpectedNames.toArray(String[]::new)); } @Test void bidiMappingsForStaticMetaModelAreFiltered() { var resources = "org/neo4j/cypherdsl/codegen/sdn6/models/bidi"; Compilation compilation = getCompiler("17").withProcessors(new SDN6AnnotationProcessor()) .compile(getJavaResources(resources)); CompilationSubject.assertThat(compilation).succeeded(); CompilationSubject.assertThat(compilation).hadWarningCount(1); CompilationSubject.assertThat(compilation) .hadWarningContaining( "Bidirectional mappings are not supported for the static meta model (Relationship: HAS_PATH_POINTS)"); var basePackage = "org.neo4j.cypherdsl.codegen.sdn6.models.bidi."; CompilationSubject.assertThat(compilation) .generatedSourceFile(basePackage + "Element_") .hasSourceEquivalentTo(JavaFileObjects.forResource("bidi/Element_.java")); CompilationSubject.assertThat(compilation) .generatedSourceFile(basePackage + "Point_") .hasSourceEquivalentTo(JavaFileObjects.forResource("bidi/Point_.java")); CompilationSubject.assertThat(compilation) .generatedSourceFile(basePackage + "HasPathPoints_") .hasSourceEquivalentTo(JavaFileObjects.forResource("bidi/HasPathPoints_.java")); } @Test void sameRelDifferentPackageShouldMakeSense() { var resources = "org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_different_package"; Compilation compilation = getCompiler("17").withProcessors(new SDN6AnnotationProcessor()) .compile(getJavaResources(resources)); CompilationSubject.assertThat(compilation).succeeded(); CompilationSubject.assertThat(compilation).hadWarningCount(0); var basePackage = "org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package."; CompilationSubject.assertThat(compilation) .generatedSourceFile(basePackage + "domain.Company_") .hasSourceEquivalentTo(JavaFileObjects.forResource("same_rel_different_package/Company_.java")); CompilationSubject.assertThat(compilation) .generatedSourceFile(basePackage + "domain.DomainEntity_") .hasSourceEquivalentTo(JavaFileObjects.forResource("same_rel_different_package/DomainEntity_.java")); CompilationSubject.assertThat(compilation) .generatedSourceFile(basePackage + "domain.DomainEntity2_") .hasSourceEquivalentTo(JavaFileObjects.forResource("same_rel_different_package/DomainEntity2_.java")); CompilationSubject.assertThat(compilation) .generatedSourceFile(basePackage + "domain.Place_") .hasSourceEquivalentTo(JavaFileObjects.forResource("same_rel_different_package/Place_.java")); CompilationSubject.assertThat(compilation) .generatedSourceFile(basePackage + "domain.In_") .hasSourceEquivalentTo(JavaFileObjects.forResource("same_rel_different_package/In1_.java")); CompilationSubject.assertThat(compilation) .generatedSourceFile(basePackage + "domain.Uses_") .hasSourceEquivalentTo(JavaFileObjects.forResource("same_rel_different_package/Uses1_.java")); CompilationSubject.assertThat(compilation) .generatedSourceFile(basePackage + "domain.Abuses_") .hasSourceEquivalentTo(JavaFileObjects.forResource("same_rel_different_package/Abuses1_.java")); CompilationSubject.assertThat(compilation) .generatedSourceFile(basePackage + "application.CompanyModel_") .hasSourceEquivalentTo(JavaFileObjects.forResource("same_rel_different_package/CompanyModel_.java")); CompilationSubject.assertThat(compilation) .generatedSourceFile(basePackage + "application.PlaceModel_") .hasSourceEquivalentTo(JavaFileObjects.forResource("same_rel_different_package/PlaceModel_.java")); CompilationSubject.assertThat(compilation) .generatedSourceFile(basePackage + "application.In_") .hasSourceEquivalentTo(JavaFileObjects.forResource("same_rel_different_package/In2_.java")); CompilationSubject.assertThat(compilation) .generatedSourceFile(basePackage + "application.Uses_") .hasSourceEquivalentTo(JavaFileObjects.forResource("same_rel_different_package/Uses2_.java")); } /** * A test converter spotting a non-default constructor, so that it cannot be * instantiated */ @SuppressWarnings("unused") static class SomeConverter implements Converter { SomeConverter(@SuppressWarnings("unused") boolean ignoreMe) { } @Override public String convert(@NonNull String source) { return new StringBuilder(source).reverse().toString(); } } static class StringArrayConverter extends SimpleArgumentConverter { @Override protected Object convert(Object source, Class targetType) throws ArgumentConversionException { if (source instanceof String && String[].class.isAssignableFrom(targetType)) { return Arrays.stream(((String) source).split("\\s*,\\s*")).map(String::trim).toArray(String[]::new); } else { throw new IllegalArgumentException( "Conversion from " + source.getClass() + " to " + targetType + " not supported."); } } } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/abstract_rels/Actor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.abstract_rels; import java.util.ArrayList; import java.util.List; import org.springframework.data.neo4j.core.schema.RelationshipProperties; import org.springframework.data.neo4j.core.schema.TargetNode; /** * Example type. * * @param a type * @author Michael J. Simons */ @RelationshipProperties public class Actor { @TargetNode private final T person; private List roles = new ArrayList<>(); public Actor(T person) { this.person = person; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/abstract_rels/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.abstract_rels; import java.util.ArrayList; import java.util.List; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Property; import org.springframework.data.neo4j.core.schema.Relationship; /** * @author Michael J. Simons */ @Node public class Movie { @Id private final String title; @Property("tagline") private final String description; @Relationship(type = "ACTED_IN", direction = Relationship.Direction.INCOMING) private List> actors = new ArrayList<>(); @Relationship(type = "DIRECTED", direction = Relationship.Direction.INCOMING) private List directors = new ArrayList<>(); public Movie(String title, String description) { this.title = title; this.description = description; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/abstract_rels/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.abstract_rels; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class Person { @Id private String name; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/bidi/Element.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.bidi; import java.util.List; import org.springframework.data.annotation.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; @Node public class Element { @Id Long id; @Relationship(type = "HAS_PATH_POINTS", direction = Relationship.Direction.OUTGOING) private List points; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/bidi/Point.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.bidi; import java.util.List; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; @Node public class Point { @Relationship(type = "HAS_PATH_POINTS", direction = Relationship.Direction.INCOMING) private List element; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/different_properties_for_rel_type/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.different_properties_for_rel_type; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class Movie { @Id @GeneratedValue private Long id; private String title; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/different_properties_for_rel_type/MovieAppearance.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.different_properties_for_rel_type; import java.util.ArrayList; import java.util.List; import org.springframework.data.neo4j.core.schema.RelationshipProperties; import org.springframework.data.neo4j.core.schema.TargetNode; /** * @author Michael J. Simons */ @RelationshipProperties public class MovieAppearance { @TargetNode private Movie movie; private List roles = new ArrayList<>(); private String stuntDouble; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/different_properties_for_rel_type/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.different_properties_for_rel_type; import java.util.ArrayList; import java.util.List; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; /** * @author Michael J. Simons */ @Node public class Person { @Id @GeneratedValue private Long id; private String name; private Integer born; @Relationship(type = "ACTED_IN") private List movies = new ArrayList<>(); @Relationship(type = "ACTED_IN") private List plays = new ArrayList<>(); } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/different_properties_for_rel_type/Play.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.different_properties_for_rel_type; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class Play { @Id @GeneratedValue private Long id; private String title; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/different_properties_for_rel_type/TheaterAppearance.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.different_properties_for_rel_type; import java.util.ArrayList; import java.util.List; import org.springframework.data.neo4j.core.schema.RelationshipProperties; import org.springframework.data.neo4j.core.schema.TargetNode; /** * @author Michael J. Simons */ @RelationshipProperties public class TheaterAppearance { @TargetNode private Play play; private List roles = new ArrayList<>(); } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/different_properties_for_rel_type/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * The same relationship type ACTED_IN is used with different properties. Multiple types * needs to be generated. */ package org.neo4j.cypherdsl.codegen.sdn6.models.different_properties_for_rel_type; ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/enums_and_inner_classes/ConnectorTransport.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.enums_and_inner_classes; import org.springframework.data.neo4j.core.convert.ConvertWith; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node(labels = "Transport") public class ConnectorTransport { @Id private final ConnectorTransportType value; private OtherEnum otherEnum; @ConvertWith(converter = InnerInnerClassConverter.class) private InnerClass.InnerInnerClass innerInnerClass; private InnerClass.InnerInnerClass innerInnerClassButWithoutConverter; public ConnectorTransport(ConnectorTransportType value) { this.value = value; } /** * Some enum */ public enum ConnectorTransportType { HTTP, BOLT } /** * An inner class */ public static class InnerClass { /** * With a nested inner class (used to check whether the recursive algorithm stops * at some point) */ public static class InnerInnerClass { } } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/enums_and_inner_classes/InnerInnerClassConverter.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.enums_and_inner_classes; import org.neo4j.cypherdsl.codegen.sdn6.models.enums_and_inner_classes.ConnectorTransport.InnerClass.InnerInnerClass; import org.neo4j.driver.Value; import org.springframework.data.neo4j.core.convert.Neo4jPersistentPropertyConverter; /** * @author Michael J. Simons */ public class InnerInnerClassConverter implements Neo4jPersistentPropertyConverter { @Override public Value write(InnerInnerClass innerInnerClass) { return null; } @Override public InnerInnerClass read(Value value) { return null; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/enums_and_inner_classes/OtherEnum.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.enums_and_inner_classes; /** * @author Michael J. Simons */ public enum OtherEnum { A, B } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/enums_and_inner_classes/SpringBasedConverter.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.enums_and_inner_classes; import org.springframework.core.convert.converter.Converter; /** * Will be registered for some tests with the annotation processor. * * @author Michael J. Simons */ public class SpringBasedConverter implements Converter { @Override public String convert(ConnectorTransport.InnerClass.InnerInnerClass source) { return null; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/ids/ExternalGeneratedId.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.ids; import java.util.UUID; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class ExternalGeneratedId { @Id @GeneratedValue(GeneratedValue.UUIDGenerator.class) private UUID id; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/ids/ExternalGeneratedIdImplicit.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.ids; import java.util.UUID; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class ExternalGeneratedIdImplicit { @Id @GeneratedValue private UUID id; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/ids/InternalGeneratedId.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.ids; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class InternalGeneratedId { @Id @GeneratedValue private Long id; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/ids/InternalGeneratedIdWithSpringId.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.ids; import org.springframework.data.annotation.Id; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class InternalGeneratedIdWithSpringId { @Id @GeneratedValue private Long id; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/ids/InternalGeneratedPrimitiveLongId.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.ids; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class InternalGeneratedPrimitiveLongId { @Id @GeneratedValue private long id; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/labels/NodesWithDifferentLabelAnnotations.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.labels; import org.springframework.data.neo4j.core.schema.Node; /** * Tests various ways to define labels. The generated classes will end up in a subpackage * as they are static, inner classes. * * @author Michael J. Simons */ public class NodesWithDifferentLabelAnnotations { @Node("ALabel") static class LabelOnNode1 { } @SuppressWarnings("checkstyle:AnnotationUseStyle") @Node(value = "ALabel") static class LabelOnNode2 { } @Node(primaryLabel = "ALabel") static class LabelOnNode3 { } @SuppressWarnings("checkstyle:AnnotationUseStyle") @Node(value = { "Label1", "Label2" }) static class MultipleLabels1 { } @Node(primaryLabel = "PL", value = { "Label1", "Label2" }) static class MultipleLabels2 { } @Node(primaryLabel = "PL", value = { "Label1", "Label2" }, labels = "Some more") static class MultipleLabels3 { } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/primitives/Connector.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.primitives; import java.util.UUID; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; /** * @author Florent Biville * @author Michael J. Simons */ @Node(primaryLabel = "Connector") public class Connector { @Id @GeneratedValue(GeneratedValue.UUIDGenerator.class) private UUID id; private final String uri; private final boolean official; private int anInt; private float aFloat; private double aDouble; @Relationship private char aChar; public Connector(String uri, boolean official) { this.uri = uri; this.official = official; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/records/NodeWithRecordProperties.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.records; import org.springframework.data.neo4j.core.convert.ConvertWith; import org.springframework.data.neo4j.core.convert.Neo4jPersistentPropertyConverter; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Property; import org.springframework.data.neo4j.core.schema.Relationship; /** * Random test class. */ @Node public class NodeWithRecordProperties { @Id private String id; @ConvertWith(converter = RecordConverter.class) private RecordAsProperty recordAsPropertyWithConversion; @Property private RecordAsProperty yoloingNoConversion; // Inner types for relationships are not supported @Relationship private RecordTarget recordAsRelationship; record RecordAsProperty(String value) { } abstract static class RecordConverter implements Neo4jPersistentPropertyConverter { } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/records/RecordTarget.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.records; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * Random test class. * * @param value a value */ @Node public record RecordTarget(@Id String value) { } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_properties_for_rel_type/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_properties_for_rel_type; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class Movie { @Id @GeneratedValue private Long id; private String title; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_properties_for_rel_type/MovieAppearance.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_properties_for_rel_type; import java.util.ArrayList; import java.util.List; import org.springframework.data.neo4j.core.schema.RelationshipProperties; import org.springframework.data.neo4j.core.schema.TargetNode; /** * @author Michael J. Simons */ @RelationshipProperties public class MovieAppearance { @TargetNode private Movie movie; private List roles = new ArrayList<>(); } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_properties_for_rel_type/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_properties_for_rel_type; import java.util.ArrayList; import java.util.List; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; /** * @author Michael J. Simons */ @Node public class Person { @Id @GeneratedValue private Long id; private String name; private Integer born; @Relationship(type = "ACTED_IN") private List movies = new ArrayList<>(); @Relationship(type = "ACTED_IN") private List plays = new ArrayList<>(); } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_properties_for_rel_type/Play.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_properties_for_rel_type; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class Play { @Id @GeneratedValue private Long id; private String title; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_properties_for_rel_type/TheaterAppearance.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_properties_for_rel_type; import java.util.ArrayList; import java.util.List; import org.springframework.data.neo4j.core.schema.RelationshipProperties; import org.springframework.data.neo4j.core.schema.TargetNode; /** * @author Michael J. Simons */ @RelationshipProperties public class TheaterAppearance { @TargetNode private Play play; private List roles = new ArrayList<>(); } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_properties_for_rel_type/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * The same relationship type ACTED_IN is used with same properties. Only one type needs * to be generated. */ package org.neo4j.cypherdsl.codegen.sdn6.models.same_properties_for_rel_type; ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_different_package/application/CompanyModel.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.application; import org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.domain.DomainEntity; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; @Node("Company") public class CompanyModel { @Relationship(type = "IN", direction = Relationship.Direction.OUTGOING) private PlaceModel place; @Relationship(type = "USES", direction = Relationship.Direction.OUTGOING) private DomainEntity domainEntity; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_different_package/application/PlaceModel.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.application; import org.springframework.data.neo4j.core.schema.Node; @Node("Place") public class PlaceModel { } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_different_package/domain/Company.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.domain; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; @Node(primaryLabel = "Company", labels = { "CompanyId" }) public class Company { @Relationship(type = "IN", direction = Relationship.Direction.OUTGOING) private Place place; @Relationship(type = "USES", direction = Relationship.Direction.OUTGOING) private DomainEntity domainEntity; @Relationship(type = "USES", direction = Relationship.Direction.OUTGOING) private DomainEntity domainEntity2; @Relationship(type = "ABUSES", direction = Relationship.Direction.OUTGOING) private DomainEntity de1; @Relationship(type = "ABUSES", direction = Relationship.Direction.OUTGOING) private DomainEntity2 de2; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_different_package/domain/DomainEntity.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.domain; import org.springframework.data.neo4j.core.schema.Node; @Node public class DomainEntity { } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_different_package/domain/DomainEntity2.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.domain; import org.springframework.data.neo4j.core.schema.Node; @Node public class DomainEntity2 { } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_different_package/domain/Place.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.domain; import org.springframework.data.neo4j.core.schema.Node; @Node public class Place { } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_different_source/Book.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_source; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; /** * @author Michael J. Simons */ @Node public class Book { @Id private final String title; @Relationship(value = "WROTE", direction = Relationship.Direction.INCOMING) private Person writtenBy; Book(String title) { this.title = title; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_different_source/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_source; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; /** * @author Michael J. Simons */ @Node public class Movie { @Id private final String title; @Relationship(value = "WROTE", direction = Relationship.Direction.INCOMING) private Person writtenBy; Movie(String title) { this.title = title; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_different_source/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_source; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class Person { @Id private String name; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_different_target/Book.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_target; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class Book { @Id private final String title; Book(String title) { this.title = title; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_different_target/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_target; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class Movie { @Id private final String title; Movie(String title) { this.title = title; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_different_target/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_target; import java.util.List; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; /** * @author Michael J. Simons */ @Node public class Person { @Id private String name; @Relationship("WROTE") private List writtenMovies; @Relationship("WROTE") private List writtenBooks; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_different_target/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * Here the WROTE relationship targets different entities. It should be generated with a * wild card. */ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_target; ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_mixed/Book.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_mixed; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; /** * @author Michael J. Simons */ @Node public class Book { @Id private final String title; @Relationship(value = "WROTE", direction = Relationship.Direction.INCOMING) private Person writtenBy; Book(String title) { this.title = title; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_mixed/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_mixed; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class Movie { @Id private final String title; Movie(String title) { this.title = title; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_mixed/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_mixed; import java.util.List; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; /** * @author Michael J. Simons */ @Node public class Person { @Id private String name; @Relationship("WROTE") private List writtenMovies; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_mixed_different_directions/Book.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_mixed_different_directions; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; /** * @author Michael J. Simons */ @Node public class Book { @Id private final String title; // Makes not much sense, but one never knows @Relationship(value = "WROTE", direction = Relationship.Direction.OUTGOING) private Person writtenBy; Book(String title) { this.title = title; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_mixed_different_directions/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_mixed_different_directions; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; /** * @author Michael J. Simons */ @Node public class Movie { @Id private final String title; @Relationship(value = "WROTE", direction = Relationship.Direction.INCOMING) private Person writtenBy; Movie(String title) { this.title = title; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/same_rel_mixed_different_directions/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_mixed_different_directions; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class Person { @Id private String name; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/self_referential/Example.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.self_referential; import org.springframework.data.annotation.PersistenceCreator; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; /** * @author Michael J. Simons */ @Node public class Example { @Id private final long id; @Relationship(type = "BELONGS_TO") private Example parent; @PersistenceCreator public Example(long id) { this.id = id; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/simple/Actor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.simple; import java.util.ArrayList; import java.util.List; import org.springframework.data.neo4j.core.schema.RelationshipProperties; import org.springframework.data.neo4j.core.schema.TargetNode; /** * @author Michael J. Simons */ @RelationshipProperties public class Actor { @TargetNode private final Person person; private List roles = new ArrayList<>(); public Actor(Person person) { this.person = person; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/simple/Bridge.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.simple; import java.util.ArrayList; import java.util.List; import org.springframework.data.neo4j.core.schema.RelationshipProperties; import org.springframework.data.neo4j.core.schema.TargetNode; /** * @author Michael J. Simons */ @RelationshipProperties public class Bridge { @TargetNode private final Target target; private List things = new ArrayList<>(); public Bridge(Target target) { this.target = target; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/simple/Edge.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.simple; import java.util.ArrayList; import java.util.List; import org.springframework.data.neo4j.core.schema.RelationshipProperties; import org.springframework.data.neo4j.core.schema.TargetNode; /** * @author Michael J. Simons */ @RelationshipProperties public class Edge { @TargetNode private final Target2 target; private List things = new ArrayList<>(); public Edge(Target2 target) { this.target = target; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/simple/Kante.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.simple; import java.util.ArrayList; import java.util.List; import org.springframework.data.neo4j.core.schema.RelationshipProperties; import org.springframework.data.neo4j.core.schema.TargetNode; /** * @author Michael J. Simons */ @RelationshipProperties public class Kante { @TargetNode private final Target2 target; private List things = new ArrayList<>(); public Kante(Target2 target) { this.target = target; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/simple/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.simple; import java.util.ArrayList; import java.util.List; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Property; import org.springframework.data.neo4j.core.schema.Relationship; /** * @author Michael J. Simons */ @Node public class Movie { @Id private final String title; @Property("tagline") private final String description; @Relationship(type = "ACTED_IN", direction = Relationship.Direction.INCOMING) private List actors = new ArrayList<>(); @Relationship(type = "DIRECTED", direction = Relationship.Direction.INCOMING) private List directors = new ArrayList<>(); // This will be ignored, not annotated and the other type is not a node private SomeType someProp; private Integer released; public Movie(String title, String description) { this.title = title; this.description = description; } static class SomeType { } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/simple/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.simple; import java.util.List; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class Person { @Id private String name; private Integer born; private Person follows; private List produced; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/simple/Src.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.simple; import java.util.ArrayList; import java.util.List; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Property; import org.springframework.data.neo4j.core.schema.Relationship; /** * @author Michael J. Simons */ @Node public class Src { @Id private final String title; @Property("tagline") private final String description; @Relationship(type = "R1", direction = Relationship.Direction.INCOMING) private List bridges = new ArrayList<>(); @Relationship(type = "R2", direction = Relationship.Direction.INCOMING) private List targets = new ArrayList<>(); @Relationship(type = "R3", direction = Relationship.Direction.INCOMING) private List edges = new ArrayList<>(); // This will be ignored, not annotated and the other type is not a node private SomeType someProp; private Integer released; public Src(String title, String description) { this.title = title; this.description = description; } static class SomeType { } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/simple/Target.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.simple; import java.util.List; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class Target { @Id private String name; private Integer born; private Target rel1; private List rel2; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/simple/Target2.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.simple; import java.util.List; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class Target2 { @Id private String name; private Integer born; private Target2 rel21; private List rel22; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/simple/Target3.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.simple; import java.util.List; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public class Target3 { @Id private String name; private Integer born; private Target3 rel31; private List rel32; } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/simple/TotallyIgnored.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.simple; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; @Node public class TotallyIgnored { @Id private final String title; public TotallyIgnored(String title) { this.title = title; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/java/org/neo4j/cypherdsl/codegen/sdn6/models/simple/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * This is basically the "happy path" as demonstrated in several examples. */ package org.neo4j.cypherdsl.codegen.sdn6.models.simple; ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/abstract_rels/Directed_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.abstract_rels; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Directed_ extends RelationshipBase { public static final String $TYPE = "DIRECTED"; public Directed_(Person_ start, Movie_ end) { super(start, $TYPE, end); } private Directed_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Directed_ named(SymbolicName newSymbolicName) { return new Directed_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Directed_ withProperties(MapExpression newProperties) { return new Directed_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/abstract_rels/Movie_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.abstract_rels; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Movie_ extends NodeBase { public static final String $PRIMARY_LABEL = "Movie"; public static final Movie_ MOVIE = new Movie_(); public final Property TITLE = this.property("title"); public final Property DESCRIPTION = this.property("tagline").referencedAs("description"); public final Directed_ DIRECTORS = new Directed_(Person_.PERSON, this); public Movie_() { super($PRIMARY_LABEL); } private Movie_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Movie_ named(SymbolicName newSymbolicName) { return new Movie_(newSymbolicName, getLabels(), getProperties()); } @Override public Movie_ withProperties(MapExpression newProperties) { return new Movie_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/abstract_rels/Person_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.abstract_rels; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Person_ extends NodeBase { public static final String $PRIMARY_LABEL = "Person"; public static final Person_ PERSON = new Person_(); public final Property NAME = this.property("name"); public Person_() { super($PRIMARY_LABEL); } private Person_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person_ named(SymbolicName newSymbolicName) { return new Person_(newSymbolicName, getLabels(), getProperties()); } @Override public Person_ withProperties(MapExpression newProperties) { return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/bidi/Element_.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.bidi; import java.util.List; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class Element_ extends NodeBase { public static final String $PRIMARY_LABEL = "Element"; public static final Element_ ELEMENT = new Element_(); public final Property ID = this.property("id"); public final HasPathPoints_ POINTS = new HasPathPoints_(this, Point_.POINT); public Element_() { super($PRIMARY_LABEL); } private Element_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Element_ named(SymbolicName newSymbolicName) { return new Element_(newSymbolicName, getLabels(), getProperties()); } @Override public Element_ withProperties(MapExpression newProperties) { return new Element_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/bidi/HasPathPoints_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.bidi; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class HasPathPoints_ extends RelationshipBase { public static final String $TYPE = "HAS_PATH_POINTS"; public HasPathPoints_(Element_ start, Point_ end) { super(start, $TYPE, end); } private HasPathPoints_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public HasPathPoints_ named(SymbolicName newSymbolicName) { return new HasPathPoints_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public HasPathPoints_ withProperties(MapExpression newProperties) { return new HasPathPoints_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/bidi/Point_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.bidi; import java.util.List; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class Point_ extends NodeBase { public static final String $PRIMARY_LABEL = "Point"; public static final Point_ POINT = new Point_(); public Point_() { super($PRIMARY_LABEL); } private Point_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Point_ named(SymbolicName newSymbolicName) { return new Point_(newSymbolicName, getLabels(), getProperties()); } @Override public Point_ withProperties(MapExpression newProperties) { return new Point_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/different_properties_for_rel_type/ActedInMovie_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.different_properties_for_rel_type; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class ActedInMovie_ extends RelationshipBase { public static final String $TYPE = "ACTED_IN"; public final Property ROLES = this.property("roles"); public final Property STUNT_DOUBLE = this.property("stuntDouble"); public ActedInMovie_(Person_ start, Movie_ end) { super(start, $TYPE, end); } private ActedInMovie_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public ActedInMovie_ named(SymbolicName newSymbolicName) { return new ActedInMovie_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public ActedInMovie_ withProperties(MapExpression newProperties) { return new ActedInMovie_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/different_properties_for_rel_type/ActedInPlay_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.different_properties_for_rel_type; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class ActedInPlay_ extends RelationshipBase { public static final String $TYPE = "ACTED_IN"; public final Property ROLES = this.property("roles"); public ActedInPlay_(Person_ start, Play_ end) { super(start, $TYPE, end); } private ActedInPlay_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public ActedInPlay_ named(SymbolicName newSymbolicName) { return new ActedInPlay_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public ActedInPlay_ withProperties(MapExpression newProperties) { return new ActedInPlay_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/different_properties_for_rel_type/Movie_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.different_properties_for_rel_type; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Movie_ extends NodeBase { public static final String $PRIMARY_LABEL = "Movie"; public static final Movie_ MOVIE = new Movie_(); public final Property TITLE = this.property("title"); public Movie_() { super($PRIMARY_LABEL); } private Movie_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Movie_ named(SymbolicName newSymbolicName) { return new Movie_(newSymbolicName, getLabels(), getProperties()); } @Override public Movie_ withProperties(MapExpression newProperties) { return new Movie_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/different_properties_for_rel_type/Person_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.different_properties_for_rel_type; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Person_ extends NodeBase { public static final String $PRIMARY_LABEL = "Person"; public static final Person_ PERSON = new Person_(); public final Property NAME = this.property("name"); public final Property BORN = this.property("born"); public final ActedInMovie_ MOVIES = new ActedInMovie_(this, Movie_.MOVIE); public final ActedInPlay_ PLAYS = new ActedInPlay_(this, Play_.PLAY); public Person_() { super($PRIMARY_LABEL); } private Person_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person_ named(SymbolicName newSymbolicName) { return new Person_(newSymbolicName, getLabels(), getProperties()); } @Override public Person_ withProperties(MapExpression newProperties) { return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/different_properties_for_rel_type/Play_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.different_properties_for_rel_type; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Play_ extends NodeBase { public static final String $PRIMARY_LABEL = "Play"; public static final Play_ PLAY = new Play_(); public final Property TITLE = this.property("title"); public Play_() { super($PRIMARY_LABEL); } private Play_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Play_ named(SymbolicName newSymbolicName) { return new Play_(newSymbolicName, getLabels(), getProperties()); } @Override public Play_ withProperties(MapExpression newProperties) { return new Play_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/enums_and_inner_classes/ConnectorTransportWithGlobalConverter_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.enums_and_inner_classes; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class ConnectorTransport_ extends NodeBase { public static final String $PRIMARY_LABEL = "Transport"; public static final ConnectorTransport_ CONNECTOR_TRANSPORT = new ConnectorTransport_(); public final Property VALUE = this.property("value"); public final Property OTHER_ENUM = this.property("otherEnum"); public final Property INNER_INNER_CLASS = this.property("innerInnerClass"); public final Property INNER_INNER_CLASS_BUT_WITHOUT_CONVERTER = this.property("innerInnerClassButWithoutConverter"); public ConnectorTransport_() { super($PRIMARY_LABEL); } private ConnectorTransport_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public ConnectorTransport_ named(SymbolicName newSymbolicName) { return new ConnectorTransport_(newSymbolicName, getLabels(), getProperties()); } @Override public ConnectorTransport_ withProperties(MapExpression newProperties) { return new ConnectorTransport_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/enums_and_inner_classes/ConnectorTransport_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.enums_and_inner_classes; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class ConnectorTransport_ extends NodeBase { public static final String $PRIMARY_LABEL = "Transport"; public static final ConnectorTransport_ CONNECTOR_TRANSPORT = new ConnectorTransport_(); public final Property VALUE = this.property("value"); public final Property OTHER_ENUM = this.property("otherEnum"); public final Property INNER_INNER_CLASS = this.property("innerInnerClass"); public ConnectorTransport_() { super($PRIMARY_LABEL); } private ConnectorTransport_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public ConnectorTransport_ named(SymbolicName newSymbolicName) { return new ConnectorTransport_(newSymbolicName, getLabels(), getProperties()); } @Override public ConnectorTransport_ withProperties(MapExpression newProperties) { return new ConnectorTransport_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/ids/ExternalGeneratedIdImplicit_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.ids; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class ExternalGeneratedIdImplicit_ extends NodeBase { public static final String $PRIMARY_LABEL = "ExternalGeneratedIdImplicit"; public static final ExternalGeneratedIdImplicit_ EXTERNAL_GENERATED_ID_IMPLICIT = new ExternalGeneratedIdImplicit_(); public final Property ID = this.property("id"); public ExternalGeneratedIdImplicit_() { super($PRIMARY_LABEL); } private ExternalGeneratedIdImplicit_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public ExternalGeneratedIdImplicit_ named(SymbolicName newSymbolicName) { return new ExternalGeneratedIdImplicit_(newSymbolicName, getLabels(), getProperties()); } @Override public ExternalGeneratedIdImplicit_ withProperties(MapExpression newProperties) { return new ExternalGeneratedIdImplicit_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/ids/ExternalGeneratedId_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.ids; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class ExternalGeneratedId_ extends NodeBase { public static final String $PRIMARY_LABEL = "ExternalGeneratedId"; public static final ExternalGeneratedId_ EXTERNAL_GENERATED_ID = new ExternalGeneratedId_(); public final Property ID = this.property("id"); public ExternalGeneratedId_() { super($PRIMARY_LABEL); } private ExternalGeneratedId_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public ExternalGeneratedId_ named(SymbolicName newSymbolicName) { return new ExternalGeneratedId_(newSymbolicName, getLabels(), getProperties()); } @Override public ExternalGeneratedId_ withProperties(MapExpression newProperties) { return new ExternalGeneratedId_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/ids/InternalGeneratedIdWithSpringId_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.ids; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class InternalGeneratedIdWithSpringId_ extends NodeBase { public static final String $PRIMARY_LABEL = "InternalGeneratedIdWithSpringId"; public static final InternalGeneratedIdWithSpringId_ INTERNAL_GENERATED_ID_WITH_SPRING_ID = new InternalGeneratedIdWithSpringId_(); public InternalGeneratedIdWithSpringId_() { super($PRIMARY_LABEL); } private InternalGeneratedIdWithSpringId_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public InternalGeneratedIdWithSpringId_ named(SymbolicName newSymbolicName) { return new InternalGeneratedIdWithSpringId_(newSymbolicName, getLabels(), getProperties()); } @Override public InternalGeneratedIdWithSpringId_ withProperties(MapExpression newProperties) { return new InternalGeneratedIdWithSpringId_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/ids/InternalGeneratedId_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.ids; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class InternalGeneratedId_ extends NodeBase { public static final String $PRIMARY_LABEL = "InternalGeneratedId"; public static final InternalGeneratedId_ INTERNAL_GENERATED_ID = new InternalGeneratedId_(); public InternalGeneratedId_() { super($PRIMARY_LABEL); } private InternalGeneratedId_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public InternalGeneratedId_ named(SymbolicName newSymbolicName) { return new InternalGeneratedId_(newSymbolicName, getLabels(), getProperties()); } @Override public InternalGeneratedId_ withProperties(MapExpression newProperties) { return new InternalGeneratedId_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/ids/InternalGeneratedPrimitiveLongId_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.ids; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class InternalGeneratedPrimitiveLongId_ extends NodeBase { public static final String $PRIMARY_LABEL = "InternalGeneratedPrimitiveLongId"; public static final InternalGeneratedPrimitiveLongId_ INTERNAL_GENERATED_PRIMITIVE_LONG_ID = new InternalGeneratedPrimitiveLongId_(); public InternalGeneratedPrimitiveLongId_() { super($PRIMARY_LABEL); } private InternalGeneratedPrimitiveLongId_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public InternalGeneratedPrimitiveLongId_ named(SymbolicName newSymbolicName) { return new InternalGeneratedPrimitiveLongId_(newSymbolicName, getLabels(), getProperties()); } @Override public InternalGeneratedPrimitiveLongId_ withProperties(MapExpression newProperties) { return new InternalGeneratedPrimitiveLongId_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/labels/nodeswithdifferentlabelannotations/LabelOnNode1_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.labels.nodeswithdifferentlabelannotations; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class LabelOnNode1_ extends NodeBase { public static final String $PRIMARY_LABEL = "ALabel"; public static final LabelOnNode1_ LABEL_ON_NODE_1 = new LabelOnNode1_(); public LabelOnNode1_() { super($PRIMARY_LABEL); } private LabelOnNode1_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public LabelOnNode1_ named(SymbolicName newSymbolicName) { return new LabelOnNode1_(newSymbolicName, getLabels(), getProperties()); } @Override public LabelOnNode1_ withProperties(MapExpression newProperties) { return new LabelOnNode1_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/labels/nodeswithdifferentlabelannotations/LabelOnNode2_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.labels.nodeswithdifferentlabelannotations; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class LabelOnNode2_ extends NodeBase { public static final String $PRIMARY_LABEL = "ALabel"; public static final LabelOnNode2_ LABEL_ON_NODE_2 = new LabelOnNode2_(); public LabelOnNode2_() { super($PRIMARY_LABEL); } private LabelOnNode2_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public LabelOnNode2_ named(SymbolicName newSymbolicName) { return new LabelOnNode2_(newSymbolicName, getLabels(), getProperties()); } @Override public LabelOnNode2_ withProperties(MapExpression newProperties) { return new LabelOnNode2_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/labels/nodeswithdifferentlabelannotations/LabelOnNode3_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.labels.nodeswithdifferentlabelannotations; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class LabelOnNode3_ extends NodeBase { public static final String $PRIMARY_LABEL = "ALabel"; public static final LabelOnNode3_ LABEL_ON_NODE_3 = new LabelOnNode3_(); public LabelOnNode3_() { super($PRIMARY_LABEL); } private LabelOnNode3_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public LabelOnNode3_ named(SymbolicName newSymbolicName) { return new LabelOnNode3_(newSymbolicName, getLabels(), getProperties()); } @Override public LabelOnNode3_ withProperties(MapExpression newProperties) { return new LabelOnNode3_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/labels/nodeswithdifferentlabelannotations/MultipleLabels1_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.labels.nodeswithdifferentlabelannotations; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class MultipleLabels1_ extends NodeBase { public static final String $PRIMARY_LABEL = "Label1"; public static final MultipleLabels1_ MULTIPLE_LABELS_1 = new MultipleLabels1_(); public MultipleLabels1_() { super($PRIMARY_LABEL, "Label2"); } private MultipleLabels1_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public MultipleLabels1_ named(SymbolicName newSymbolicName) { return new MultipleLabels1_(newSymbolicName, getLabels(), getProperties()); } @Override public MultipleLabels1_ withProperties(MapExpression newProperties) { return new MultipleLabels1_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/labels/nodeswithdifferentlabelannotations/MultipleLabels2_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.labels.nodeswithdifferentlabelannotations; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class MultipleLabels2_ extends NodeBase { public static final String $PRIMARY_LABEL = "PL"; public static final MultipleLabels2_ MULTIPLE_LABELS_2 = new MultipleLabels2_(); public MultipleLabels2_() { super($PRIMARY_LABEL, "Label1", "Label2"); } private MultipleLabels2_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public MultipleLabels2_ named(SymbolicName newSymbolicName) { return new MultipleLabels2_(newSymbolicName, getLabels(), getProperties()); } @Override public MultipleLabels2_ withProperties(MapExpression newProperties) { return new MultipleLabels2_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/labels/nodeswithdifferentlabelannotations/MultipleLabels3_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.labels.nodeswithdifferentlabelannotations; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class MultipleLabels3_ extends NodeBase { public static final String $PRIMARY_LABEL = "PL"; public static final MultipleLabels3_ MULTIPLE_LABELS_3 = new MultipleLabels3_(); public MultipleLabels3_() { super($PRIMARY_LABEL, "Label1", "Label2", "Some more"); } private MultipleLabels3_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public MultipleLabels3_ named(SymbolicName newSymbolicName) { return new MultipleLabels3_(newSymbolicName, getLabels(), getProperties()); } @Override public MultipleLabels3_ withProperties(MapExpression newProperties) { return new MultipleLabels3_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/org/neo4j/cypherdsl/codegen/sdn6/models/related_classes_not_on_cp_like_in_reallife/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.related_classes_not_on_cp_like_in_reallife; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.springframework.data.annotation.PersistenceCreator; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Property; import org.springframework.data.neo4j.core.schema.Relationship; import org.springframework.data.neo4j.core.schema.Relationship.Direction; /** * @author Michael J. Simons */ @Node public final class Movie { @Id private final String title; @Property("tagline") private final String description; @Relationship(value = "DIRECTED", direction = Direction.INCOMING) private final List directors; private Type type; private Integer released; public Movie(String title, String description) { this.title = title; this.description = description; this.directors = new ArrayList<>(); } @PersistenceCreator public Movie(String title, String description, List directors) { this.title = title; this.description = description; this.directors = directors == null ? Collections.emptyList() : new ArrayList<>(directors); } public String getTitle() { return title; } public String getDescription() { return description; } public List getDirectors() { return Collections.unmodifiableList(this.directors); } public Integer getReleased() { return released; } public void setReleased(Integer released) { this.released = released; } public Type getType() { return type; } public void setType(Type type) { this.type = type; } public Movie addDirectors(Collection newDirectors) { this.directors.addAll(newDirectors); return this; } public enum Type { AKTION_FILM, KOMÖDIE, SCHNULZE } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/org/neo4j/cypherdsl/codegen/sdn6/models/related_classes_not_on_cp_like_in_reallife/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.codegen.sdn6.models.related_classes_not_on_cp_like_in_reallife; import org.springframework.data.annotation.PersistenceCreator; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ @Node public final class Person { @Id @GeneratedValue private final Long id; private final String name; private Integer born; @PersistenceCreator private Person(Long id, String name, Integer born) { this.id = id; this.born = born; this.name = name; } public Person(String name, Integer born) { this(null, name, born); } public Long getId() { return id; } public String getName() { return name; } public Integer getBorn() { return born; } public void setBorn(Integer born) { this.born = born; } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/primitives/Connector_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.primitives; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Connector_ extends NodeBase { public static final String $PRIMARY_LABEL = "Connector"; public static final Connector_ CONNECTOR = new Connector_(); public final Property ID = this.property("id"); public final Property URI = this.property("uri"); public final Property OFFICIAL = this.property("official"); public final Property AN_INT = this.property("anInt"); public final Property A_FLOAT = this.property("aFloat"); public final Property A_DOUBLE = this.property("aDouble"); public Connector_() { super($PRIMARY_LABEL); } private Connector_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Connector_ named(SymbolicName newSymbolicName) { return new Connector_(newSymbolicName, getLabels(), getProperties()); } @Override public Connector_ withProperties(MapExpression newProperties) { return new Connector_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/records/NodeWithRecordProperties_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.records; import java.util.List; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class NodeWithRecordProperties_ extends NodeBase { public static final String $PRIMARY_LABEL = "NodeWithRecordProperties"; public static final NodeWithRecordProperties_ NODE_WITH_RECORD_PROPERTIES = new NodeWithRecordProperties_(); public final Property ID = this.property("id"); public final Property RECORD_AS_PROPERTY_WITH_CONVERSION = this.property("recordAsPropertyWithConversion"); public final Property YOLOING_NO_CONVERSION = this.property("yoloingNoConversion"); public final RecordAsRelationship_ RECORD_AS_RELATIONSHIP = new RecordAsRelationship_(this, RecordTarget_.RECORD_TARGET); public NodeWithRecordProperties_() { super($PRIMARY_LABEL); } private NodeWithRecordProperties_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public NodeWithRecordProperties_ named(SymbolicName newSymbolicName) { return new NodeWithRecordProperties_(newSymbolicName, getLabels(), getProperties()); } @Override public NodeWithRecordProperties_ withProperties(MapExpression newProperties) { return new NodeWithRecordProperties_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/records/RecordAsRelationship_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.records; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class RecordAsRelationship_ extends RelationshipBase { public static final String $TYPE = "recordAsRelationship"; public RecordAsRelationship_(NodeWithRecordProperties_ start, RecordTarget_ end) { super(start, $TYPE, end); } private RecordAsRelationship_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public RecordAsRelationship_ named(SymbolicName newSymbolicName) { return new RecordAsRelationship_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public RecordAsRelationship_ withProperties(MapExpression newProperties) { return new RecordAsRelationship_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/records/RecordTarget_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.records; import java.util.List; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class RecordTarget_ extends NodeBase { public static final String $PRIMARY_LABEL = "RecordTarget"; public static final RecordTarget_ RECORD_TARGET = new RecordTarget_(); public final Property VALUE = this.property("value"); public RecordTarget_() { super($PRIMARY_LABEL); } private RecordTarget_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public RecordTarget_ named(SymbolicName newSymbolicName) { return new RecordTarget_(newSymbolicName, getLabels(), getProperties()); } @Override public RecordTarget_ withProperties(MapExpression newProperties) { return new RecordTarget_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/related_classes_not_on_cp_like_in_reallife/Directed_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.related_classes_not_on_cp_like_in_reallife; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class Directed_ extends RelationshipBase { public static final String $TYPE = "DIRECTED"; public Directed_(Person_ start, Movie_ end) { super(start, $TYPE, end); } private Directed_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Directed_ named(SymbolicName newSymbolicName) { return new Directed_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Directed_ withProperties(MapExpression newProperties) { return new Directed_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/related_classes_not_on_cp_like_in_reallife/Movie_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.related_classes_not_on_cp_like_in_reallife; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Movie_ extends NodeBase { public static final String $PRIMARY_LABEL = "Movie"; public static final Movie_ MOVIE = new Movie_(); public final Property TITLE = this.property("title"); public final Property DESCRIPTION = this.property("tagline").referencedAs("description"); public final Property TYPE = this.property("type"); public final Property RELEASED = this.property("released"); public final Directed_ DIRECTORS = new Directed_(Person_.PERSON, this); public Movie_() { super($PRIMARY_LABEL); } private Movie_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Movie_ named(SymbolicName newSymbolicName) { return new Movie_(newSymbolicName, getLabels(), getProperties()); } @Override public Movie_ withProperties(MapExpression newProperties) { return new Movie_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/related_classes_not_on_cp_like_in_reallife/Person_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.related_classes_not_on_cp_like_in_reallife; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Person_ extends NodeBase { public static final String $PRIMARY_LABEL = "Person"; public static final Person_ PERSON = new Person_(); public final Property NAME = this.property("name"); public final Property BORN = this.property("born"); public Person_() { super($PRIMARY_LABEL); } private Person_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person_ named(SymbolicName newSymbolicName) { return new Person_(newSymbolicName, getLabels(), getProperties()); } @Override public Person_ withProperties(MapExpression newProperties) { return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_properties_for_rel_type/ActedIn_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_properties_for_rel_type; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class ActedIn_> extends RelationshipBase> { public static final String $TYPE = "ACTED_IN"; public final Property ROLES = this.property("roles"); public ActedIn_(Person_ start, E end) { super(start, $TYPE, end); } private ActedIn_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public ActedIn_ named(SymbolicName newSymbolicName) { return new ActedIn_<>(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public ActedIn_ withProperties(MapExpression newProperties) { return new ActedIn_<>(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_properties_for_rel_type/Movie_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_properties_for_rel_type; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Movie_ extends NodeBase { public static final String $PRIMARY_LABEL = "Movie"; public static final Movie_ MOVIE = new Movie_(); public final Property TITLE = this.property("title"); public Movie_() { super($PRIMARY_LABEL); } private Movie_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Movie_ named(SymbolicName newSymbolicName) { return new Movie_(newSymbolicName, getLabels(), getProperties()); } @Override public Movie_ withProperties(MapExpression newProperties) { return new Movie_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_properties_for_rel_type/Person_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_properties_for_rel_type; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Person_ extends NodeBase { public static final String $PRIMARY_LABEL = "Person"; public static final Person_ PERSON = new Person_(); public final Property NAME = this.property("name"); public final Property BORN = this.property("born"); public final ActedIn_ MOVIES = new ActedIn_(this, Movie_.MOVIE); public final ActedIn_ PLAYS = new ActedIn_(this, Play_.PLAY); public Person_() { super($PRIMARY_LABEL); } private Person_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person_ named(SymbolicName newSymbolicName) { return new Person_(newSymbolicName, getLabels(), getProperties()); } @Override public Person_ withProperties(MapExpression newProperties) { return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_properties_for_rel_type/Play_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_properties_for_rel_type; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Play_ extends NodeBase { public static final String $PRIMARY_LABEL = "Play"; public static final Play_ PLAY = new Play_(); public final Property TITLE = this.property("title"); public Play_() { super($PRIMARY_LABEL); } private Play_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Play_ named(SymbolicName newSymbolicName) { return new Play_(newSymbolicName, getLabels(), getProperties()); } @Override public Play_ withProperties(MapExpression newProperties) { return new Play_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_package/Abuses1_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.domain; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class Abuses_> extends RelationshipBase> { public static final String $TYPE = "ABUSES"; public Abuses_(Company_ start, E end) { super(start, $TYPE, end); } private Abuses_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Abuses_ named(SymbolicName newSymbolicName) { return new Abuses_<>(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Abuses_ withProperties(MapExpression newProperties) { return new Abuses_<>(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_package/CompanyModel_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.application; import java.util.List; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.domain.DomainEntity_; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class CompanyModel_ extends NodeBase { public static final String $PRIMARY_LABEL = "Company"; public static final CompanyModel_ COMPANY_MODEL = new CompanyModel_(); public final In_ PLACE = new In_(this, PlaceModel_.PLACE_MODEL); public final Uses_ DOMAIN_ENTITY = new Uses_(this, DomainEntity_.DOMAIN_ENTITY); public CompanyModel_() { super($PRIMARY_LABEL); } private CompanyModel_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public CompanyModel_ named(SymbolicName newSymbolicName) { return new CompanyModel_(newSymbolicName, getLabels(), getProperties()); } @Override public CompanyModel_ withProperties(MapExpression newProperties) { return new CompanyModel_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_package/Company_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.domain; import java.util.List; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class Company_ extends NodeBase { public static final String $PRIMARY_LABEL = "Company"; public static final Company_ COMPANY = new Company_(); public final In_ PLACE = new In_(this, Place_.PLACE); public final Abuses_ DE_1 = new Abuses_(this, DomainEntity_.DOMAIN_ENTITY); public final Abuses_ DE_2 = new Abuses_(this, DomainEntity2_.DOMAIN_ENTITY_2); public final Uses_ DOMAIN_ENTITY = new Uses_(this, DomainEntity_.DOMAIN_ENTITY); public final Uses_ DOMAIN_ENTITY_2 = new Uses_(this, DomainEntity_.DOMAIN_ENTITY); public Company_() { super($PRIMARY_LABEL, "CompanyId"); } private Company_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Company_ named(SymbolicName newSymbolicName) { return new Company_(newSymbolicName, getLabels(), getProperties()); } @Override public Company_ withProperties(MapExpression newProperties) { return new Company_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_package/DomainEntity2_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.domain; import java.util.List; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class DomainEntity2_ extends NodeBase { public static final String $PRIMARY_LABEL = "DomainEntity2"; public static final DomainEntity2_ DOMAIN_ENTITY_2 = new DomainEntity2_(); public DomainEntity2_() { super($PRIMARY_LABEL); } private DomainEntity2_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public DomainEntity2_ named(SymbolicName newSymbolicName) { return new DomainEntity2_(newSymbolicName, getLabels(), getProperties()); } @Override public DomainEntity2_ withProperties(MapExpression newProperties) { return new DomainEntity2_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_package/DomainEntity_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.domain; import java.util.List; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class DomainEntity_ extends NodeBase { public static final String $PRIMARY_LABEL = "DomainEntity"; public static final DomainEntity_ DOMAIN_ENTITY = new DomainEntity_(); public DomainEntity_() { super($PRIMARY_LABEL); } private DomainEntity_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public DomainEntity_ named(SymbolicName newSymbolicName) { return new DomainEntity_(newSymbolicName, getLabels(), getProperties()); } @Override public DomainEntity_ withProperties(MapExpression newProperties) { return new DomainEntity_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_package/In1_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.domain; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class In_ extends RelationshipBase { public static final String $TYPE = "IN"; public In_(Company_ start, Place_ end) { super(start, $TYPE, end); } private In_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public In_ named(SymbolicName newSymbolicName) { return new In_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public In_ withProperties(MapExpression newProperties) { return new In_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_package/In2_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.application; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class In_ extends RelationshipBase { public static final String $TYPE = "IN"; public In_(CompanyModel_ start, PlaceModel_ end) { super(start, $TYPE, end); } private In_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public In_ named(SymbolicName newSymbolicName) { return new In_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public In_ withProperties(MapExpression newProperties) { return new In_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_package/PlaceModel_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.application; import java.util.List; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class PlaceModel_ extends NodeBase { public static final String $PRIMARY_LABEL = "Place"; public static final PlaceModel_ PLACE_MODEL = new PlaceModel_(); public PlaceModel_() { super($PRIMARY_LABEL); } private PlaceModel_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public PlaceModel_ named(SymbolicName newSymbolicName) { return new PlaceModel_(newSymbolicName, getLabels(), getProperties()); } @Override public PlaceModel_ withProperties(MapExpression newProperties) { return new PlaceModel_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_package/Place_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.domain; import java.util.List; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class Place_ extends NodeBase { public static final String $PRIMARY_LABEL = "Place"; public static final Place_ PLACE = new Place_(); public Place_() { super($PRIMARY_LABEL); } private Place_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Place_ named(SymbolicName newSymbolicName) { return new Place_(newSymbolicName, getLabels(), getProperties()); } @Override public Place_ withProperties(MapExpression newProperties) { return new Place_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_package/Uses1_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.domain; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class Uses_ extends RelationshipBase { public static final String $TYPE = "USES"; public Uses_(Company_ start, DomainEntity_ end) { super(start, $TYPE, end); } private Uses_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Uses_ named(SymbolicName newSymbolicName) { return new Uses_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Uses_ withProperties(MapExpression newProperties) { return new Uses_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_package/Uses2_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.application; import javax.annotation.processing.Generated; import org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_package.domain.DomainEntity_; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class Uses_ extends RelationshipBase { public static final String $TYPE = "USES"; public Uses_(CompanyModel_ start, DomainEntity_ end) { super(start, $TYPE, end); } private Uses_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Uses_ named(SymbolicName newSymbolicName) { return new Uses_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Uses_ withProperties(MapExpression newProperties) { return new Uses_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_source/Book_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_source; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Book_ extends NodeBase { public static final String $PRIMARY_LABEL = "Book"; public static final Book_ BOOK = new Book_(); public final Property TITLE = this.property("title"); public final Wrote_ WRITTEN_BY = new Wrote_(Person_.PERSON, this); public Book_() { super($PRIMARY_LABEL); } private Book_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Book_ named(SymbolicName newSymbolicName) { return new Book_(newSymbolicName, getLabels(), getProperties()); } @Override public Book_ withProperties(MapExpression newProperties) { return new Book_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_source/Movie_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_source; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Movie_ extends NodeBase { public static final String $PRIMARY_LABEL = "Movie"; public static final Movie_ MOVIE = new Movie_(); public final Property TITLE = this.property("title"); public final Wrote_ WRITTEN_BY = new Wrote_(Person_.PERSON, this); public Movie_() { super($PRIMARY_LABEL); } private Movie_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Movie_ named(SymbolicName newSymbolicName) { return new Movie_(newSymbolicName, getLabels(), getProperties()); } @Override public Movie_ withProperties(MapExpression newProperties) { return new Movie_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_source/Person_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_source; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Person_ extends NodeBase { public static final String $PRIMARY_LABEL = "Person"; public static final Person_ PERSON = new Person_(); public final Property NAME = this.property("name"); public Person_() { super($PRIMARY_LABEL); } private Person_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person_ named(SymbolicName newSymbolicName) { return new Person_(newSymbolicName, getLabels(), getProperties()); } @Override public Person_ withProperties(MapExpression newProperties) { return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_source/Wrote_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_source; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Wrote_> extends RelationshipBase> { public static final String $TYPE = "WROTE"; public Wrote_(Person_ start, E end) { super(start, $TYPE, end); } private Wrote_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Wrote_ named(SymbolicName newSymbolicName) { return new Wrote_<>(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Wrote_ withProperties(MapExpression newProperties) { return new Wrote_<>(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_target/Book_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_target; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Book_ extends NodeBase { public static final String $PRIMARY_LABEL = "Book"; public static final Book_ BOOK = new Book_(); public final Property TITLE = this.property("title"); public Book_() { super($PRIMARY_LABEL); } private Book_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Book_ named(SymbolicName newSymbolicName) { return new Book_(newSymbolicName, getLabels(), getProperties()); } @Override public Book_ withProperties(MapExpression newProperties) { return new Book_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_target/Movie_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_target; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Movie_ extends NodeBase { public static final String $PRIMARY_LABEL = "Movie"; public static final Movie_ MOVIE = new Movie_(); public final Property TITLE = this.property("title"); public Movie_() { super($PRIMARY_LABEL); } private Movie_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Movie_ named(SymbolicName newSymbolicName) { return new Movie_(newSymbolicName, getLabels(), getProperties()); } @Override public Movie_ withProperties(MapExpression newProperties) { return new Movie_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_target/Person_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_target; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Person_ extends NodeBase { public static final String $PRIMARY_LABEL = "Person"; public static final Person_ PERSON = new Person_(); public final Property NAME = this.property("name"); public final Wrote_ WRITTEN_MOVIES = new Wrote_(this, Movie_.MOVIE); public final Wrote_ WRITTEN_BOOKS = new Wrote_(this, Book_.BOOK); public Person_() { super($PRIMARY_LABEL); } private Person_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person_ named(SymbolicName newSymbolicName) { return new Person_(newSymbolicName, getLabels(), getProperties()); } @Override public Person_ withProperties(MapExpression newProperties) { return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_different_target/Wrote_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_different_target; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Wrote_> extends RelationshipBase> { public static final String $TYPE = "WROTE"; public Wrote_(Person_ start, E end) { super(start, $TYPE, end); } private Wrote_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Wrote_ named(SymbolicName newSymbolicName) { return new Wrote_<>(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Wrote_ withProperties(MapExpression newProperties) { return new Wrote_<>(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_mixed/Book_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_mixed; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Book_ extends NodeBase { public static final String $PRIMARY_LABEL = "Book"; public static final Book_ BOOK = new Book_(); public final Property TITLE = this.property("title"); public final Wrote_ WRITTEN_BY = new Wrote_(Person_.PERSON, this); public Book_() { super($PRIMARY_LABEL); } private Book_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Book_ named(SymbolicName newSymbolicName) { return new Book_(newSymbolicName, getLabels(), getProperties()); } @Override public Book_ withProperties(MapExpression newProperties) { return new Book_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_mixed/Movie_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_mixed; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Movie_ extends NodeBase { public static final String $PRIMARY_LABEL = "Movie"; public static final Movie_ MOVIE = new Movie_(); public final Property TITLE = this.property("title"); public Movie_() { super($PRIMARY_LABEL); } private Movie_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Movie_ named(SymbolicName newSymbolicName) { return new Movie_(newSymbolicName, getLabels(), getProperties()); } @Override public Movie_ withProperties(MapExpression newProperties) { return new Movie_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_mixed/Person_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_mixed; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Person_ extends NodeBase { public static final String $PRIMARY_LABEL = "Person"; public static final Person_ PERSON = new Person_(); public final Property NAME = this.property("name"); public final Wrote_ WRITTEN_MOVIES = new Wrote_(this, Movie_.MOVIE); public Person_() { super($PRIMARY_LABEL); } private Person_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person_ named(SymbolicName newSymbolicName) { return new Person_(newSymbolicName, getLabels(), getProperties()); } @Override public Person_ withProperties(MapExpression newProperties) { return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_mixed/Wrote_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_mixed; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Wrote_> extends RelationshipBase> { public static final String $TYPE = "WROTE"; public Wrote_(Person_ start, E end) { super(start, $TYPE, end); } private Wrote_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Wrote_ named(SymbolicName newSymbolicName) { return new Wrote_<>(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Wrote_ withProperties(MapExpression newProperties) { return new Wrote_<>(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_mixed_different_directions/Book_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_mixed_different_directions; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Book_ extends NodeBase { public static final String $PRIMARY_LABEL = "Book"; public static final Book_ BOOK = new Book_(); public final Property TITLE = this.property("title"); public final Wrote_ WRITTEN_BY = new Wrote_(this, Person_.PERSON); public Book_() { super($PRIMARY_LABEL); } private Book_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Book_ named(SymbolicName newSymbolicName) { return new Book_(newSymbolicName, getLabels(), getProperties()); } @Override public Book_ withProperties(MapExpression newProperties) { return new Book_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_mixed_different_directions/Movie_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_mixed_different_directions; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Movie_ extends NodeBase { public static final String $PRIMARY_LABEL = "Movie"; public static final Movie_ MOVIE = new Movie_(); public final Property TITLE = this.property("title"); public final Wrote_ WRITTEN_BY = new Wrote_(Person_.PERSON, this); public Movie_() { super($PRIMARY_LABEL); } private Movie_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Movie_ named(SymbolicName newSymbolicName) { return new Movie_(newSymbolicName, getLabels(), getProperties()); } @Override public Movie_ withProperties(MapExpression newProperties) { return new Movie_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_mixed_different_directions/Person_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_mixed_different_directions; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Person_ extends NodeBase { public static final String $PRIMARY_LABEL = "Person"; public static final Person_ PERSON = new Person_(); public final Property NAME = this.property("name"); public Person_() { super($PRIMARY_LABEL); } private Person_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person_ named(SymbolicName newSymbolicName) { return new Person_(newSymbolicName, getLabels(), getProperties()); } @Override public Person_ withProperties(MapExpression newProperties) { return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/same_rel_mixed_different_directions/Wrote_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.same_rel_mixed_different_directions; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Wrote_, E extends NodeBase> extends RelationshipBase> { public static final String $TYPE = "WROTE"; public Wrote_(S start, E end) { super(start, $TYPE, end); } private Wrote_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Wrote_ named(SymbolicName newSymbolicName) { return new Wrote_<>(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Wrote_ withProperties(MapExpression newProperties) { return new Wrote_<>(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/self_referential/BelongsTo_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.self_referential; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class BelongsTo_ extends RelationshipBase { public static final String $TYPE = "BELONGS_TO"; public BelongsTo_(Example_ start, Example_ end) { super(start, $TYPE, end); } private BelongsTo_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public BelongsTo_ named(SymbolicName newSymbolicName) { return new BelongsTo_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public BelongsTo_ withProperties(MapExpression newProperties) { return new BelongsTo_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/self_referential/Example_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.self_referential; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Example_ extends NodeBase { public static final String $PRIMARY_LABEL = "Example"; public static final Example_ EXAMPLE = new Example_(); public final Property ID = this.property("id"); public Example_() { super($PRIMARY_LABEL); } private Example_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Example_ named(SymbolicName newSymbolicName) { return new Example_(newSymbolicName, getLabels(), getProperties()); } @Override public Example_ withProperties(MapExpression newProperties) { return new Example_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } public BelongsTo_ withParent(Example_ parent) { return new BelongsTo_(this, parent); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/simple/ActedIn_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.simple; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class ActedIn_ extends RelationshipBase { public static final String $TYPE = "ACTED_IN"; public final Property ROLES = this.property("roles"); public ActedIn_(Person_ start, Movie_ end) { super(start, $TYPE, end); } private ActedIn_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public ActedIn_ named(SymbolicName newSymbolicName) { return new ActedIn_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public ActedIn_ withProperties(MapExpression newProperties) { return new ActedIn_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/simple/Directed_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.simple; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Directed_ extends RelationshipBase { public static final String $TYPE = "DIRECTED"; public Directed_(Person_ start, Movie_ end) { super(start, $TYPE, end); } private Directed_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Directed_ named(SymbolicName newSymbolicName) { return new Directed_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Directed_ withProperties(MapExpression newProperties) { return new Directed_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/simple/Follows_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.simple; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Follows_ extends RelationshipBase { public static final String $TYPE = "follows"; public Follows_(Person_ start, Person_ end) { super(start, $TYPE, end); } private Follows_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Follows_ named(SymbolicName newSymbolicName) { return new Follows_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Follows_ withProperties(MapExpression newProperties) { return new Follows_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/simple/Movie_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.simple; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Movie_ extends NodeBase { public static final String $PRIMARY_LABEL = "Movie"; public static final Movie_ MOVIE = new Movie_(); public final Property TITLE = this.property("title"); public final Property DESCRIPTION = this.property("tagline").referencedAs("description"); public final Property RELEASED = this.property("released"); public final ActedIn_ ACTORS = new ActedIn_(Person_.PERSON, this); public final Directed_ DIRECTORS = new Directed_(Person_.PERSON, this); public Movie_() { super($PRIMARY_LABEL); } private Movie_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Movie_ named(SymbolicName newSymbolicName) { return new Movie_(newSymbolicName, getLabels(), getProperties()); } @Override public Movie_ withProperties(MapExpression newProperties) { return new Movie_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/simple/Person_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.simple; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Person_ extends NodeBase { public static final String $PRIMARY_LABEL = "Person"; public static final Person_ PERSON = new Person_(); public final Property NAME = this.property("name"); public final Property BORN = this.property("born"); public final Produced_ PRODUCED = new Produced_(this, Movie_.MOVIE); public Person_() { super($PRIMARY_LABEL); } private Person_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person_ named(SymbolicName newSymbolicName) { return new Person_(newSymbolicName, getLabels(), getProperties()); } @Override public Person_ withProperties(MapExpression newProperties) { return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } public Follows_ withFollows(Person_ follows) { return new Follows_(this, follows); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/simple/Produced_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.simple; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated(value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration.") public final class Produced_ extends RelationshipBase { public static final String $TYPE = "produced"; public Produced_(Person_ start, Movie_ end) { super(start, $TYPE, end); } private Produced_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Produced_ named(SymbolicName newSymbolicName) { return new Produced_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Produced_ withProperties(MapExpression newProperties) { return new Produced_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/simple/Rel21_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.simple; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class Rel21_ extends RelationshipBase { public static final String $TYPE = "rel21"; public Rel21_(Target2_ start, Target2_ end) { super(start, $TYPE, end); } private Rel21_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Rel21_ named(SymbolicName newSymbolicName) { return new Rel21_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Rel21_ withProperties(MapExpression newProperties) { return new Rel21_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/simple/Rel22_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.simple; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.RelationshipImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class Rel22_ extends RelationshipBase { public static final String $TYPE = "rel22"; public Rel22_(Target2_ start, Src_ end) { super(start, $TYPE, end); } private Rel22_(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Rel22_ named(SymbolicName newSymbolicName) { return new Rel22_(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Rel22_ withProperties(MapExpression newProperties) { return new Rel22_(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/simple/Src_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.simple; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class Src_ extends NodeBase { public static final String $PRIMARY_LABEL = "Src"; public static final Src_ SRC = new Src_(); public final Property TITLE = this.property("title"); public final Property DESCRIPTION = this.property("tagline").referencedAs("description"); public final Property RELEASED = this.property("released"); public Src_() { super($PRIMARY_LABEL); } private Src_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Src_ named(SymbolicName newSymbolicName) { return new Src_(newSymbolicName, getLabels(), getProperties()); } @Override public Src_ withProperties(MapExpression newProperties) { return new Src_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-codegen/neo4j-cypher-dsl-codegen-sdn6/src/test/resources/simple/Target2_.java ================================================ package org.neo4j.cypherdsl.codegen.sdn6.models.simple; import java.util.List; import javax.annotation.Generated; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; @Generated( value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder", date = "2019-09-21T21:21:00+01:00", comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration." ) public final class Target2_ extends NodeBase { public static final String $PRIMARY_LABEL = "Target2"; public static final Target2_ TARGET_2 = new Target2_(); public final Property NAME = this.property("name"); public final Property BORN = this.property("born"); public final Rel22_ REL_2_2 = new Rel22_(this, Src_.SRC); public Target2_() { super($PRIMARY_LABEL); } private Target2_(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Target2_ named(SymbolicName newSymbolicName) { return new Target2_(newSymbolicName, getLabels(), getProperties()); } @Override public Target2_ withProperties(MapExpression newProperties) { return new Target2_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } public Rel21_ withRel21(Target2_ rel21) { return new Rel21_(this, rel21); } } ================================================ FILE: neo4j-cypher-dsl-codegen/pom.xml ================================================ 4.0.0 org.neo4j neo4j-cypher-dsl-parent ${revision}${sha1}${changelist} neo4j-cypher-dsl-codegen pom Neo4j Cypher DSL (Code Generator) neo4j-cypher-dsl-codegen-core neo4j-cypher-dsl-codegen-ogm neo4j-cypher-dsl-codegen-sdn6 org.neo4j.cypherdsl.codegen org.neo4j neo4j-cypher-dsl-bom ${project.version} pom import ================================================ FILE: neo4j-cypher-dsl-examples/README.adoc ================================================ == Docs This project contains a lot of fragments. Those fragments are not meant to be used together all at once. They are here to be included in our docs. By having them compiled as part of our build, we can make sure we don't have any invalid docs. ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/pom.xml ================================================ 4.0.0 org.neo4j neo4j-cypher-dsl-examples ${revision}${sha1}${changelist} neo4j-cypher-dsl-examples-core Examples (Core) Examples for the core Cypher-DSL used in the documentation. org.neo4j.cypherdsl.examples.core org.neo4j neo4j-cypher-dsl ${revision}${sha1}${changelist} org.assertj assertj-core test org.junit.jupiter junit-jupiter test org.apache.maven.plugins maven-jar-plugin true org.apache.maven.plugins maven-install-plugin true org.apache.maven.plugins maven-deploy-plugin true org.apache.maven.plugins maven-failsafe-plugin integration-test verify ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/ArbitraryProceduresAndFunctionsTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.core; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons * */ class ArbitraryProceduresAndFunctionsTests { private static final Renderer cypherRenderer = Renderer.getDefaultRenderer(); @Test void standaloneCalls() { // tag::standalone-call[] var call = Cypher.call("db", "labels").build(); // <.> assertThat(cypherRenderer.render(call)).isEqualTo("CALL db.labels()"); call = Cypher.call("db.labels").build(); // <.> assertThat(cypherRenderer.render(call)).isEqualTo("CALL db.labels()"); // end::standalone-call[] } @Test void standaloneCallWithArgs() { // tag::standalone-call-with-args[] var call = Cypher.call("dbms.security.createUser") .withArgs(Cypher.literalOf("johnsmith"), Cypher.literalOf("h6u4%kr"), Cypher.literalFalse()) .build(); assertThat(cypherRenderer.render(call)) .isEqualTo("CALL dbms.security.createUser('johnsmith', 'h6u4%kr', false)"); // end::standalone-call-with-args[] } @Test void standaloneCallYielding() { // tag::standalone-call-yielding[] var call = Cypher.call("dbms.procedures").yield("name", "signature").build(); // <.> assertThat(cypherRenderer.render(call)).isEqualTo("CALL dbms.procedures() YIELD name, signature"); call = Cypher.call("dbms.procedures").yield(Cypher.name("name"), Cypher.name("signature")).build(); // <.> assertThat(cypherRenderer.render(call)).isEqualTo("CALL dbms.procedures() YIELD name, signature"); // end::standalone-call-yielding[] } @Test void standaloneCallWhere() { // tag::standalone-call-where[] var name = Cypher.name("name"); var call = Cypher.call("dbms.listConfig") .withArgs(Cypher.literalOf("browser")) .yield(name) .where(name.matches("browser\\.allow.*")) .returning(Cypher.asterisk()) .build(); assertThat(cypherRenderer.render(call)) .isEqualTo("CALL dbms.listConfig('browser') YIELD name WHERE name =~ 'browser\\\\.allow.*' RETURN *"); // end::standalone-call-where[] } @Test void inQueryCalls() { // tag::in-query-calls[] var label = Cypher.name("label"); var statement = Cypher.match(Cypher.anyNode().named("n")) .with("n") .call("db.labels") .yield(label) .with(label) .returning(Cypher.count(label).as("numLabels")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (n) WITH n CALL db.labels() YIELD label WITH label RETURN count(label) AS numLabels"); // end::in-query-calls[] } @Test void asFunction() { // tag::as-function[] var p = Cypher.node("Person").named("p"); var createUuid = Cypher.call("apoc.create.uuid").asFunction(); // <.> var statement = Cypher.merge(p.withProperties(Cypher.mapOf("id", createUuid))) // <.> .set(p.property("firstName").to(Cypher.literalOf("Michael")), p.property("surname").to(Cypher.literalOf("Hunger"))) .returning(p) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MERGE (p:`Person` {id: apoc.create.uuid()}) " + "SET p.firstName = 'Michael', p.surname = 'Hunger' " + "RETURN p"); // end::as-function[] } @Test void asFunctionWithArgs() { // tag::as-function-with-args[] var p = Cypher.node("Person").named("p"); var createUuid = Cypher.call("apoc.create.uuid").asFunction(); // <.> var toCamelCase = Cypher.call("apoc.text.camelCase") .withArgs(Cypher.literalOf("first name")) // <.> .asFunction(); var statement = Cypher.merge(p.withProperties(Cypher.mapOf("id", createUuid))) .set(p.property("surname").to(Cypher.literalOf("Simons"))) .with(p) .call("apoc.create.setProperty") .withArgs(p.getRequiredSymbolicName(), toCamelCase, Cypher.parameter("nameParam") // <.> ) .yield("node") .returning("node") .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MERGE (p:`Person` {id: apoc.create.uuid()}) SET p.surname = 'Simons' " + "WITH p CALL apoc.create.setProperty(p, apoc.text.camelCase('first name'), $nameParam) " + "YIELD node RETURN node"); // end::as-function-with-args[] } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/CypherDSLExamplesTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.core; // tag::cypher-dsl-imports[] import java.util.Collection; import java.util.Locale; import java.util.Map; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.SortItem; import org.neo4j.cypherdsl.core.Statement; import org.neo4j.cypherdsl.core.SymbolicName; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; // end::cypher-dsl-imports[] /** * Examples for the Cypher DSL. * * @author Michael J. Simons * @since 1.0 */ class CypherDSLExamplesTests { private static final Renderer cypherRenderer = Renderer.getDefaultRenderer(); @Test void escapingNames() { // tag::escaping[] var relationship = Cypher.node("Person") .named("a") .relationshipTo(Cypher.node("Movie").named("m"), "ACTED_IN") .named("r"); var statement = Cypher.match(relationship).returning(relationship).build(); var defaultRenderer = Renderer.getDefaultRenderer(); assertThat(defaultRenderer.render(statement)) .isEqualTo("MATCH (a:`Person`)-[r:`ACTED_IN`]->(m:`Movie`) RETURN r"); var escapeOnlyIfNecessary = Configuration.newConfig().alwaysEscapeNames(false).build(); var renderer = Renderer.getRenderer(escapeOnlyIfNecessary); assertThat(renderer.render(statement)).isEqualTo("MATCH (a:Person)-[r:ACTED_IN]->(m:Movie) RETURN r"); renderer = Renderer.getRenderer(Configuration.prettyPrinting()); assertThat(renderer.render(statement)).isEqualTo("MATCH (a:Person)-[r:ACTED_IN]->(m:Movie)\nRETURN r"); // end::escaping[] } @Test void findAllMovies() { // tag::cypher-dsl-e1[] var m = Cypher.node("Movie").named("m"); // <.> var statement = Cypher.match(m) // <.> .returning(m) .build(); // <.> assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (m:`Movie`) RETURN m"); // end::cypher-dsl-e1[] } @Test void playMovieGraphFind() { // tag::cypher-dsl-e2[] var tom = Cypher.anyNode().named("tom").withProperties("name", Cypher.literalOf("Tom Hanks")); var statement = Cypher.match(tom).returning(tom).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (tom {name: 'Tom Hanks'}) RETURN tom"); // end::cypher-dsl-e2[] // tag::cypher-dsl-e3[] var cloudAtlas = Cypher.anyNode().named("cloudAtlas").withProperties("title", Cypher.literalOf("Cloud Atlas")); statement = Cypher.match(cloudAtlas).returning(cloudAtlas).build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (cloudAtlas {title: 'Cloud Atlas'}) RETURN cloudAtlas"); // end::cypher-dsl-e3[] // tag::cypher-dsl-e4[] var people = Cypher.node("Person").named("people"); statement = Cypher.match(people).returning(people.property("name")).limit(10).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("MATCH (people:`Person`) RETURN people.name LIMIT 10"); // end::cypher-dsl-e4[] // tag::cypher-dsl-e5[] var nineties = Cypher.node("Movie").named("nineties"); var released = nineties.property("released"); statement = Cypher.match(nineties) .where(released.gte(Cypher.literalOf(1990)).and(released.lt(Cypher.literalOf(2000)))) .returning(nineties.property("title")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (nineties:`Movie`) WHERE (nineties.released >= 1990 AND nineties.released < 2000) RETURN nineties.title"); // end::cypher-dsl-e5[] } @Test void playMovieGraphQuery() { // tag::cypher-dsl-e6[] var tom = Cypher.node("Person").named("tom").withProperties("name", Cypher.literalOf("Tom Hanks")); var tomHanksMovies = Cypher.anyNode("tomHanksMovies"); var statement = Cypher.match(tom.relationshipTo(tomHanksMovies, "ACTED_IN")) .returning(tom, tomHanksMovies) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (tom:`Person` {name: 'Tom Hanks'})-[:`ACTED_IN`]->(tomHanksMovies) RETURN tom, tomHanksMovies"); // end::cypher-dsl-e6[] // tag::cypher-dsl-e7[] var cloudAtlas = Cypher.anyNode("cloudAtlas").withProperties("title", Cypher.literalOf("Cloud Atlas")); var directors = Cypher.anyNode("directors"); statement = Cypher.match(cloudAtlas.relationshipFrom(directors, "DIRECTED")) .returning(directors.property("name")) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (cloudAtlas {title: 'Cloud Atlas'})<-[:`DIRECTED`]-(directors) RETURN directors.name"); // end::cypher-dsl-e7[] // tag::cypher-dsl-e8[] tom = Cypher.node("Person").named("tom").withProperties("name", Cypher.literalOf("Tom Hanks")); var movie = Cypher.anyNode("m"); var coActors = Cypher.anyNode("coActors"); var people = Cypher.node("Person").named("people"); statement = Cypher.match(tom.relationshipTo(movie, "ACTED_IN").relationshipFrom(coActors, "ACTED_IN")) .returning(coActors.property("name")) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (tom:`Person` {name: 'Tom Hanks'})-[:`ACTED_IN`]->(m)<-[:`ACTED_IN`]-(coActors) RETURN coActors.name"); // end::cypher-dsl-e8[] // tag::cypher-dsl-e9[] cloudAtlas = Cypher.node("Movie").withProperties("title", Cypher.literalOf("Cloud Atlas")); people = Cypher.node("Person").named("people"); var relatedTo = people.relationshipBetween(cloudAtlas).named("relatedTo"); statement = Cypher.match(relatedTo) .returning(people.property("name"), Cypher.type(relatedTo), relatedTo.getRequiredSymbolicName()) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (people:`Person`)-[relatedTo]-(:`Movie` {title: 'Cloud Atlas'}) RETURN people.name, type(relatedTo), relatedTo"); // end::cypher-dsl-e9[] } @Test void playMovieGraphSolve() { // tag::cypher-dsl-bacon[] var bacon = Cypher.node("Person").named("bacon").withProperties("name", Cypher.literalOf("Kevin Bacon")); var hollywood = Cypher.anyNode("hollywood"); var statement = Cypher.match(bacon.relationshipBetween(hollywood).length(1, 4)) .returningDistinct(hollywood) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (bacon:`Person` {name: 'Kevin Bacon'})-[*1..4]-(hollywood) RETURN DISTINCT hollywood"); // end::cypher-dsl-bacon[] } @Test void playMovieGraphRecommend() { // tag::cypher-dsl-r[] var tom = Cypher.node("Person").named("tom").withProperties("name", Cypher.literalOf("Tom Hanks")); var coActors = Cypher.anyNode("coActors"); var cocoActors = Cypher.anyNode("cocoActors"); var strength = Cypher.count(Cypher.asterisk()).as("Strength"); var statement = Cypher .match(tom.relationshipTo(Cypher.anyNode("m"), "ACTED_IN").relationshipFrom(coActors, "ACTED_IN"), coActors.relationshipTo(Cypher.anyNode("m2"), "ACTED_IN").relationshipFrom(cocoActors, "ACTED_IN")) .where(Cypher .not(tom.relationshipTo(Cypher.anyNode(), "ACTED_IN").relationshipFrom(cocoActors, "ACTED_IN"))) .and(tom.isNotEqualTo(cocoActors)) .returning(cocoActors.property("name").as("Recommended"), strength) .orderBy(strength.asName().descending()) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "" + "MATCH " + "(tom:`Person` {name: 'Tom Hanks'})-[:`ACTED_IN`]->(m)<-[:`ACTED_IN`]-(coActors), " + "(coActors)-[:`ACTED_IN`]->(m2)<-[:`ACTED_IN`]-(cocoActors) " + "WHERE (NOT (tom)-[:`ACTED_IN`]->()<-[:`ACTED_IN`]-(cocoActors) AND tom <> cocoActors) " + "RETURN cocoActors.name AS Recommended, count(*) AS Strength ORDER BY Strength DESC"); // end::cypher-dsl-r[] } @Test // GH-82 void storedProceduresCanBeCalled() { Statement call = Cypher.call("dbms.listConfig").withArgs(Cypher.literalOf("browser")).yield("name").build(); assertThat(cypherRenderer.render(call)).isEqualTo("CALL dbms.listConfig('browser') YIELD name"); } @Test void where() { SymbolicName name = Cypher.name("name"); Statement call = Cypher.call("dbms.listConfig") .withArgs(Cypher.literalOf("browser")) .yield(name) .where(name.matches("browser\\.allow.*")) .returning(Cypher.asterisk()) .build(); assertThat(cypherRenderer.render(call)) .isEqualTo("CALL dbms.listConfig('browser') YIELD name WHERE name =~ 'browser\\\\.allow.*' RETURN *"); } @Test void collectingParameters() { // tag::collecting-params[] var person = Cypher.node("Person").named("p"); var statement = Cypher.match(person) .where(person.property("nickname").isEqualTo(Cypher.parameter("nickname"))) .set(person.property("firstName").to(Cypher.parameter("firstName").withValue("Thomas")), person.property("name").to(Cypher.parameter("name", "Anderson"))) .returning(person) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (p:`Person`) WHERE p.nickname = $nickname SET p.firstName = $firstName, p.name = $name RETURN p"); Collection parameterNames = statement.getCatalog().getParameterNames(); assertThat(parameterNames).containsExactlyInAnyOrder("nickname", "firstName", "name"); // <.> Map parameters = statement.getCatalog().getParameters(); assertThat(parameters).hasSize(2); // <.> assertThat(parameters).containsEntry("firstName", "Thomas").containsEntry("name", "Anderson"); // end::collecting-params[] } @Test void prettyPrintExample() { // tag::pretty-printing-examle[] var n = Cypher.anyNode("n"); var a = Cypher.node("A").named("a"); var b = Cypher.node("B").named("b"); var mergeStatement = Cypher.merge(n) .onCreate() .set(n.property("prop").to(Cypher.literalOf(0))) .merge(a.relationshipBetween(b, "T")) .onCreate() .set(a.property("name").to(Cypher.literalOf("me"))) .onMatch() .set(b.property("name").to(Cypher.literalOf("you"))) .returning(a.property("prop")) .build(); var renderer = Renderer.getRenderer(Configuration.prettyPrinting()); // <.> assertThat(renderer.render(mergeStatement)) .isEqualTo("MERGE (n)\n" + " ON CREATE SET n.prop = 0\n" + "MERGE (a:A)-[:T]-(b:B)\n" + " ON CREATE SET a.name = 'me'\n" + " ON MATCH SET b.name = 'you'\n" + "RETURN a.prop"); // <.> // end::pretty-printing-examle[] } @Test void rawCypher() { // tag::raw-cypher[] var key = Cypher.name("key"); var cypher = Cypher.call("apoc.meta.schema") .yield("value") .with("value") .unwind(Cypher.keys(Cypher.name("value"))) .as(key) .returning(key, Cypher.raw("value[$E]", key).as("value") // <.> ) .build() .getCypher(); assertThat(cypher).isEqualTo( "CALL apoc.meta.schema() YIELD value WITH value UNWIND keys(value) AS key RETURN key, value[key] AS value"); // end::raw-cypher[] } @Test void fromTheGeniusBar1() { var orderBy = "some property"; var order = "asc"; var skip = "21"; var limit = "42"; var direction = SortItem.Direction.valueOf(order.toUpperCase(Locale.ROOT)); var m = Cypher.node("Movie").named("m"); var dynamicProperty = m.property(orderBy); var statement = Cypher.match(m) .where(Cypher.exists(dynamicProperty)) .returning(m.project(Cypher.asterisk())) .orderBy(dynamicProperty.sorted(direction)) .skip(Integer.parseInt(skip)) .limit(Integer.parseInt(limit)) .build(); assertThat(statement.getCypher()).isEqualTo( "MATCH (m:`Movie`) WHERE m.`some property` IS NOT NULL RETURN m{.*} ORDER BY m.`some property` ASC SKIP 21 LIMIT 42"); } @Test void fromTheGeniusBar2() { var orderBy = "some property"; var order = "asc"; var skip = "21"; var limit = "42"; var direction = SortItem.Direction.valueOf(order.toUpperCase(Locale.ROOT)); var m = Cypher.node("Movie").named("m"); var dynamicProperty = m.property(Cypher.anonParameter(orderBy)); var statement = Cypher.match(m) .where(Cypher.exists(dynamicProperty)) .returning(m.project(Cypher.asterisk())) .orderBy(dynamicProperty.sorted(direction)) .skip(Cypher.anonParameter(Integer.parseInt(skip))) .limit(Cypher.anonParameter(Integer.parseInt(limit))) .build(); assertThat(statement.getCypher()).isEqualTo( "MATCH (m:`Movie`) WHERE m[$pcdsl01] IS NOT NULL RETURN m{.*} ORDER BY m[$pcdsl01] ASC SKIP $pcdsl02 LIMIT $pcdsl03"); assertThat(statement.getCatalog().getParameters()) .containsAllEntriesOf(Map.of("pcdsl01", "some property", "pcdsl02", 21, "pcdsl03", 42)); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/FunctionsListTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.core; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons * */ class FunctionsListTests { private static final Renderer cypherRenderer = Renderer.getDefaultRenderer(); // tag::functions-list-operator[] @Test void valueAtExample() { // tag::functions-list-range[] var range = Cypher.range(0, 10); // end::functions-list-range[] var statement = Cypher.returning(Cypher.valueAt(range, 3)).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN range(0, 10)[3]"); } @Test void subListUntilExample() { var range = Cypher.range(Cypher.literalOf(0), Cypher.literalOf(10)); var statement = Cypher.returning(Cypher.subListUntil(range, 3)).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN range(0, 10)[..3]"); } @Test void subListFromExample() { // tag::functions-list-range-step[] var range = Cypher.range(0, 10, 1); // end::functions-list-range-step[] var statement = Cypher.returning(Cypher.subListFrom(range, -3)).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN range(0, 10, 1)[-3..]"); } @Test void subListExample() { var range = Cypher.range(Cypher.literalOf(0), Cypher.literalOf(10), Cypher.literalOf(1)); var statement = Cypher.returning(Cypher.subList(range, 2, 4)).build(); assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN range(0, 10, 1)[2..4]"); } // end::functions-list-operator[] } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/IssuesExamplesTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.core; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class IssuesExamplesTests { private static final Renderer cypherRenderer = Renderer.getDefaultRenderer(); @Test void gh48() { var n = Cypher.node("Label").named("n"); var statement = Cypher.match(n) .set(n, Cypher.mapOf("a", Cypher.literalOf("bar"), "b", Cypher.literalOf("baz"))) .returning(n) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (n:`Label`) SET n = {a: 'bar', b: 'baz'} RETURN n"); } @Test void gh51() { var n = Cypher.anyNode("n"); var foobarProp = Cypher.property("n", "foobar"); var statement = Cypher.match(n) .where(foobarProp.contains(Cypher.literalOf("baz"))) .or(foobarProp.startsWith(Cypher.literalOf("a"))) .or(foobarProp.endsWith(Cypher.literalOf("b"))) .returning(n) .build(); assertThat(cypherRenderer.render(statement)).isEqualTo( "MATCH (n) WHERE (n.foobar CONTAINS 'baz' OR n.foobar STARTS WITH 'a' OR n.foobar ENDS WITH 'b') RETURN n"); } @SuppressWarnings("deprecation") @Test void gh59() { var change = Cypher.node("Change").named("change"); var code = Cypher.anyNode().named("code"); var codeRelation = change.relationshipBetween(code, "CODE").named("codeRelation"); var changeDetail = Cypher.anyNode().named("changeDetail"); var changeDetailsRelation = change.relationshipBetween(changeDetail, "CHANGE_DETAILS") .named("changeDetailsRelation"); var idIsEqualTo147 = code.internalId().isEqualTo(Cypher.literalOf(147)); var statement = Cypher.match(codeRelation) .where(idIsEqualTo147) .optionalMatch(changeDetailsRelation) .where(idIsEqualTo147) .returning(change, codeRelation, changeDetailsRelation, changeDetail) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (change:`Change`)-[codeRelation:`CODE`]-(code) " + "WHERE id(code) = 147 " + "OPTIONAL MATCH (change)-[changeDetailsRelation:`CHANGE_DETAILS`]-(changeDetail) " + "WHERE id(code) = 147 RETURN change, codeRelation, changeDetailsRelation, changeDetail"); } @Test void gh60() { var src = Cypher.anyNode().withProperties("id", Cypher.literalOf(10)); var n = Cypher.anyNode().named("n"); var statement = Cypher.match(src.relationshipTo(n, "LINKS_WITH").named("r")) .where(Cypher.literalOf("France").in(Cypher.property("r", "markets"))) .returning(n) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH ( {id: 10})-[r:`LINKS_WITH`]->(n) WHERE 'France' IN r.markets RETURN n"); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/core/PropertiesTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.core; import java.util.Map; import java.util.regex.Pattern; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons * */ class PropertiesTests { private static final Renderer cypherRenderer = Renderer.getDefaultRenderer(); @Test void propertiesOnNodesAndRel() { // tag::properties-on-nodes-and-rel[] var personNode = Cypher.node("Person").named("p"); var movieNode = Cypher.node("Movie"); var ratedRel = personNode.relationshipTo(movieNode, "RATED").named("r"); var statement = Cypher.match(ratedRel) .returning(personNode.property("name"), // <.> ratedRel.property("rating")) // <.> .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (p:`Person`)-[r:`RATED`]->(:`Movie`) RETURN p.name, r.rating"); // end::properties-on-nodes-and-rel[] } @Test void propertiesOnNodesAndRelUnnamned() { // tag::properties-on-nodes-and-rel-unnamed[] var personNode = Cypher.node("Person"); var movieNode = Cypher.node("Movie"); var ratedRel = personNode.relationshipTo(movieNode, "RATED"); var statement = Cypher.match(ratedRel) .returning(personNode.property("name"), // <.> ratedRel.property("rating")) // <.> .build(); assertThat(cypherRenderer.render(statement)).matches(Pattern.quote("MATCH (") + "\\w+" + Pattern.quote(":`Person`)-[") + "\\w+" + Pattern.quote(":`RATED`]->(:`Movie`) RETURN ") + "\\w+" + Pattern.quote(".name, ") + "\\w+" + Pattern.quote(".rating")); // end::properties-on-nodes-and-rel-unnamed[] } @Test void propertiesOfExpressions() { // tag::properties-on-expressions[] var epochSeconds = Cypher.property(Cypher.datetime(), "epochSeconds"); // <.> var statement = Cypher.returning(epochSeconds).build(); Assertions.assertThat(cypherRenderer.render(statement)).isEqualTo("RETURN datetime().epochSeconds"); // end::properties-on-expressions[] } @Test void nestedProperties() { // tag::nested-properties[] var node = Cypher.node("Person").named("p"); var locationPropV1 = Cypher.property(node.getRequiredSymbolicName(), "home.location", "y"); var locationPropV2 = Cypher.property("p", "home.location", "y"); var statement = Cypher.match(node) .where(locationPropV1.gt(Cypher.literalOf(50))) .returning(locationPropV2) .build(); assertThat(cypherRenderer.render(statement)) .isEqualTo("MATCH (p:`Person`) WHERE p.`home.location`.y > 50 RETURN p.`home.location`.y"); // end::nested-properties[] } @Test void usingExistingJavaMaps() { var node = Cypher.node("ANode").named("n").withProperties(Map.of("aProperty", 23)); assertThat(Cypher.match(node).returning(node).build().getCypher()) .isEqualTo("MATCH (n:`ANode` {aProperty: 23}) RETURN n"); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/AbstractNodeDefinition.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.model; import java.util.List; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; /** * An example for a set of shared properties for the nodes in a domain. * * @param the final type of what is described here * @author Michael J. Simons */ public abstract class AbstractNodeDefinition> extends NodeBase { @SuppressWarnings("this-escape") public final Property ID = this.property("id"); @SuppressWarnings("this-escape") public final Property NAME = this.property("name"); protected AbstractNodeDefinition(String... additionalLabel) { super("DefaultNode", additionalLabel); } protected AbstractNodeDefinition(SymbolicName symbolicName, List labels, Properties properties) { // <.> super(symbolicName, labels, properties); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/AbstractRelationshipDefinition.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.model; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; /** * An example for a set of shared properties for the relationships in a domain. * * @param start node * @param end node * @author Michael J. Simons */ public abstract class AbstractRelationshipDefinition, E extends AbstractNodeDefinition> extends RelationshipBase> { public static final String $DEFAULT_TYPE = "COOP_REL"; @SuppressWarnings("this-escape") public final Property CREATED_AT = this.property("createdAt"); protected AbstractRelationshipDefinition(S start, E end, String... additionalTypes) { super(start, $DEFAULT_TYPE, end, additionalTypes); } protected AbstractRelationshipDefinition(SymbolicName symbolicName, Node start, Properties properties, Node end, String... additionalTypes) { super(symbolicName, start, $DEFAULT_TYPE, properties, end, additionalTypes); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/ActedIn.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.model; // tag::simple-model[] import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; // end::simple-model[] /** * @author Michael J. Simons * */ // tag::simple-model[] public final class ActedIn extends RelationshipBase { // <.> public static final String $TYPE = "ACTED_IN"; public final Property ROLE = this.property("role"); // <.> protected ActedIn(Person start, Movie end) { super(start, $TYPE, end); // <.> } private ActedIn(SymbolicName symbolicName, Node start, Properties properties, Node end) { // <.> super(symbolicName, start, $TYPE, properties, end); } @Override public ActedIn named(SymbolicName newSymbolicName) { // <.> return new ActedIn(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public ActedIn withProperties(MapExpression newProperties) { // <.> return new ActedIn(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } // end::simple-model[] ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/BelongsTo.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.model; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; /** * Defines additional types for a relationship model based on a common type. * * @author Michael J. Simons */ public final class BelongsTo extends AbstractRelationshipDefinition { public static final String $TYPE = "BELONGS_TO"; protected BelongsTo(Department start, Division end) { super(start, end, $TYPE); } private BelongsTo(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, properties, end, $TYPE); } @Override public BelongsTo named(SymbolicName newSymbolicName) { return new BelongsTo(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public BelongsTo withProperties(MapExpression newProperties) { return new BelongsTo(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/Department.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.model; import java.util.List; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; /** * Defines additional labels for a node model based on a common type. * * @author Michael J. Simons */ public final class Department extends AbstractNodeDefinition { public static final Department DEPARTMENT = new Department(); public final BelongsTo BELONGS_TO = new BelongsTo(this, Division.DIVISION); public Department() { super("Department"); } private Department(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Department named(SymbolicName newSymbolicName) { return new Department(newSymbolicName, getLabels(), getProperties()); } @Override public Department withProperties(MapExpression newProperties) { return new Department(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/Directed.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.model; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; /** * I modelled the relationships "DIRECTED" and "ACTED_IN" in this test slightly different * to get a feeling how to deal with the same type of relationship between different * nodes. A person might as well be the director of a musical etc. * * @param end node * @author Michael J. Simons */ public final class Directed> extends RelationshipBase> { public static final String $TYPE = "DIRECTED"; protected Directed(Person start, E end) { super(start, $TYPE, end); } private Directed(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public Directed named(SymbolicName newSymbolicName) { return new Directed<>(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public Directed withProperties(MapExpression newProperties) { return new Directed<>(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/Division.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.model; import java.util.List; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.SymbolicName; /** * Defines additional labels for a node model based on a common type. * * @author Michael J. Simons */ public final class Division extends AbstractNodeDefinition { public static final Division DIVISION = new Division(); public Division() { super("Division"); } private Division(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Division named(SymbolicName newSymbolicName) { return new Division(newSymbolicName, getLabels(), getProperties()); } @Override public Division withProperties(MapExpression newProperties) { return new Division(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.model; // tag::simple-model[] import java.util.List; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; // end::simple-model[] /** * @author Michael J. Simons */ // tag::simple-model[] // tag::add-properties[] public final class Movie extends NodeBase { // <.> // end::add-properties[] public static final Movie MOVIE = new Movie(); // <.> // end::simple-model[] public final Property TAGLINE = this.property("tagline"); // tag::add-properties[] public final Property TITLE = this.property("title"); // <.> // end::add-properties[] public final Property RELEASED = this.property("released"); // tag::simple-model[] public Movie() { super("Movie"); // <.> } private Movie(SymbolicName symbolicName, List labels, Properties properties) { // <.> super(symbolicName, labels, properties); } @Override public Movie named(SymbolicName newSymbolicName) { // <.> return new Movie(newSymbolicName, getLabels(), getProperties()); } @Override public Movie withProperties(MapExpression newProperties) { // <.> return new Movie(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } // tag::add-properties[] } // end::simple-model[] // end::add-properties[] ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.model; import java.util.List; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.SymbolicName; /** * @author Michael J. Simons * */ // tag::simple-model[] public final class Person extends NodeBase { public static final Person PERSON = new Person(); public final Directed DIRECTED = new Directed<>(this, Movie.MOVIE); public final ActedIn ACTED_IN = new ActedIn(this, Movie.MOVIE); // <.> public final Property NAME = this.property("name"); public final Property FIRST_NAME = this.property("firstName"); public final Property BORN = this.property("born"); // end::simple-model[] public Person() { super("Person"); } private Person(SymbolicName symbolicName, List labels, Properties properties) { super(symbolicName, labels, properties); } @Override public Person named(SymbolicName newSymbolicName) { return new Person(newSymbolicName, getLabels(), getProperties()); } @Override public Person withProperties(MapExpression newProperties) { return new Person(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties)); } // tag::simple-model[] } // end::simple-model[] ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/StaticModelIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.model; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.Cypher; /** * @author Michael J. Simons */ class StaticModelIT { @Test void simpleMatchShouldWork() { // tag::simple-model[] var cypher = Cypher.match(Movie.MOVIE).returning(Movie.MOVIE).build().getCypher(); // end::simple-model[] Assertions.assertThat(cypher).matches("MATCH \\(\\w+:`Movie`\\) RETURN \\w+"); } @Test void simpleMatchRenamedShouldWork() { // tag::simple-model-renamed[] var movie = Movie.MOVIE.named("m"); var cypher = Cypher.match(movie).returning(movie).build().getCypher(); // end::simple-model-renamed[] Assertions.assertThat(cypher).isEqualTo("MATCH (m:`Movie`) RETURN m"); } @Test void renamingShouldWork() { // tag::add-properties[] var movie = Movie.MOVIE.named("m"); var cypher = Cypher.match(movie) .where(movie.TITLE.isEqualTo(Cypher.literalOf("The Matrix"))) // <.> .returning(movie) .build() .getCypher(); Assertions.assertThat(cypher).isEqualTo("MATCH (m:`Movie`) WHERE m.title = 'The Matrix' RETURN m"); // end::add-properties[] } @Test void propertiesShouldWorkInMatchWithType() { // tag::query-node-by-properties[] var movie = Movie.MOVIE.withProperties(Movie.MOVIE.TITLE, Cypher.literalOf("The Matrix")).named("m1"); var cypher = Cypher.match(movie).returning(movie).build().getCypher(); Assertions.assertThat(cypher).isEqualTo("MATCH (m1:`Movie` {title: 'The Matrix'}) RETURN m1"); // end::query-node-by-properties[] } @Test void relRropertiesShouldWorkInMatchWithType() { var actedIn = Person.PERSON.ACTED_IN.withProperties(Person.PERSON.ACTED_IN.ROLE, Cypher.literalOf("Neo")) .named("n"); var cypher = Cypher.match(actedIn).returning(Movie.MOVIE).build().getCypher(); Assertions.assertThat(cypher) .matches("MATCH \\(\\w+:`Person`\\)-\\[n:`ACTED_IN` \\{role: 'Neo'}]->\\(\\w+:`Movie`\\) RETURN \\w+"); } @Test void relRropertiesShouldWorkInMatchWithTypeCustomName() { // tag::query-rel-by-properties[] var actedIn = Person.PERSON.ACTED_IN.withProperties(Person.PERSON.ACTED_IN.ROLE, Cypher.literalOf("Neo")); var cypher = Cypher.match(actedIn).returning(Movie.MOVIE).build().getCypher(); Assertions.assertThat(cypher) .matches("MATCH \\(\\w+:`Person`\\)-\\[\\w+:`ACTED_IN` \\{role: 'Neo'}]->\\(\\w+:`Movie`\\) RETURN \\w+"); // end::query-rel-by-properties[] } @Test void renamingRelationshipsShouldWork() { var directed = Person.PERSON.DIRECTED.named("d"); var cypher = Cypher.match(directed).returning(Movie.MOVIE).build().getCypher(); Assertions.assertThat(cypher) .matches("MATCH \\(\\w+:`Person`\\)-\\[d:`DIRECTED`]->\\(\\w+:`Movie`\\) RETURN \\w+"); } @Test void matchOnRelationshipsShouldWork() { var cypher = Cypher.match(Person.PERSON.DIRECTED).returning(Movie.MOVIE).build().getCypher(); Assertions.assertThat(cypher) .matches("MATCH \\(\\w+:`Person`\\)-\\[\\w+:`DIRECTED`]->\\(\\w+:`Movie`\\) RETURN \\w+"); } @Test void matchOnRelationshipsShouldWorkInverse() { var cypher = Cypher.match(Person.PERSON.DIRECTED.inverse()).returning(Person.PERSON).build().getCypher(); Assertions.assertThat(cypher) .matches("MATCH \\(\\w+:`Movie`\\)<-\\[:`DIRECTED`]-\\(\\w+:`Person`\\) RETURN \\w+"); } @Test void multipleRelationships() { // tag::multiple-relationships[] var cypher = Cypher.match(Person.PERSON.DIRECTED) .match(Person.PERSON.ACTED_IN) .returning(Person.PERSON.DIRECTED, Person.PERSON.ACTED_IN) .build() .getCypher(); // end::multiple-relationships[] Assertions.assertThat(cypher) .matches( "MATCH \\(\\w+:`Person`\\)-\\[\\w+:`DIRECTED`]->\\(\\w+:`Movie`\\) MATCH \\(\\w+\\)-\\[\\w+:`ACTED_IN`]->\\(\\w+\\) RETURN \\w+, \\w+"); } @Test void chaining() { // tag::chaining-relationships[] var otherPerson = Person.PERSON.named("o"); var cypher = Cypher.match(Person.PERSON.DIRECTED.inverse().relationshipTo(otherPerson, "FOLLOWS") // <.> ).where(otherPerson.NAME.isEqualTo(Cypher.literalOf("Someone"))).returning(Person.PERSON).build().getCypher(); Assertions.assertThat(cypher) .matches( "MATCH \\(\\w+:`Movie`\\)<-\\[:`DIRECTED`]-\\(\\w+:`Person`\\)-\\[:`FOLLOWS`]->\\(o:`Person`\\) WHERE o\\.name = 'Someone' RETURN \\w+"); // end::chaining-relationships[] } @Test void workWithPropertiesShouldBePossible() { // tag::work-with-properties[] var cypher = Cypher.match(Person.PERSON).returning(Person.PERSON.NAME, Person.PERSON.BORN).build().getCypher(); Assertions.assertThat(cypher).matches("MATCH \\(\\w+:`Person`\\) RETURN \\w+\\.name, \\w+\\.born"); // end::work-with-properties[] } @Test void queryingNonStaticInformationAndPathsShouldWork() { var otherPerson = Person.PERSON.named("o"); var cypher = Cypher .match(Person.PERSON.withProperties(Person.PERSON.NAME, Cypher.literalOf("Tom Hanks")) .relationshipTo(otherPerson, "WORKED_WITH")) // <.> .returning(otherPerson.NAME) .build() .getCypher(); Assertions.assertThat(cypher) .matches( "MATCH \\(\\w+:`Person` \\{name: 'Tom Hanks'}\\)-\\[:`WORKED_WITH`]->\\(o:`Person`\\) RETURN o\\.name"); } @Test void workingOnTheDelegateShouldMakeSens() { // tag::deriving-new-properties[] var cypher = Cypher.match(Person.PERSON) .returning(Person.PERSON.NAME.concat(Cypher.literalOf(" whatever"))) .build() .getCypher(); Assertions.assertThat(cypher).matches("MATCH \\(\\w+:`Person`\\) RETURN \\(\\w+\\.name \\+ ' whatever'\\)"); // end::deriving-new-properties[] } @Test void oldQueryDSLExampleRevisited() { var person = new Person().named("person"); Assertions .assertThat(Cypher.match(person) .where(person.FIRST_NAME.eq(Cypher.literalOf("P")).and(person.property("age").gt(Cypher.literalOf(25)))) .returning(person) .build() .getCypher()) .isEqualTo("MATCH (person:`Person`) WHERE (person.firstName = 'P' AND person.age > 25) RETURN person"); } @Test void inheritanceMappingExampleNodes() { var cypher = Cypher.match(Division.DIVISION).returning(Division.DIVISION.NAME).build().getCypher(); Assertions.assertThat(cypher).matches("MATCH \\(\\w+:`DefaultNode`:`Division`\\) RETURN \\w+\\.name"); } @Test void namedInheritedModelShouldWork() { var division = Division.DIVISION.named("d"); var cypher = Cypher.match(division).returning(division.NAME).build().getCypher(); Assertions.assertThat(cypher).matches("MATCH \\(d:`DefaultNode`:`Division`\\) RETURN d\\.name"); } @Test void inheritanceMatchOnRelationshipsShouldWork() { var cypher = Cypher.match(Department.DEPARTMENT.BELONGS_TO) .returning(Division.DIVISION.asExpression(), Department.DEPARTMENT.BELONGS_TO.CREATED_AT) .build() .getCypher(); Assertions.assertThat(cypher) .matches( "MATCH \\(\\w+:`DefaultNode`:`Department`\\)-\\[\\w+:`COOP_REL`\\|`BELONGS_TO`]->\\(\\w+:`DefaultNode`:`Division`\\) RETURN \\w+, \\w+\\.createdAt"); } @Test void inheritanceMatchOnRelationshipsShouldWorkInverse() { var inversRelationship = Department.DEPARTMENT.BELONGS_TO.inverse(); var cypher = Cypher.match(inversRelationship) .returning(Division.DIVISION.asExpression(), Department.DEPARTMENT.BELONGS_TO.CREATED_AT .referencedAs(inversRelationship.getRequiredSymbolicName().getValue())) .build() .getCypher(); Assertions.assertThat(cypher) .matches( "MATCH \\(\\w+:`DefaultNode`:`Division`\\)<-\\[\\w+:`COOP_REL`\\|`BELONGS_TO`]-\\(\\w+:`DefaultNode`:`Department`\\) RETURN \\w+, \\w+\\.createdAt"); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-core/src/test/java/org/neo4j/cypherdsl/examples/model/UnboundRelation.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.model; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeBase; import org.neo4j.cypherdsl.core.Properties; import org.neo4j.cypherdsl.core.RelationshipBase; import org.neo4j.cypherdsl.core.SymbolicName; /** * Basically only the holder of the type. * * @param start node * @param end node * @author Michael J. Simons */ public final class UnboundRelation, E extends NodeBase> extends RelationshipBase> { public static final String $TYPE = "UNBOUND"; protected UnboundRelation(S start, E end) { super(start, $TYPE, end); } private UnboundRelation(SymbolicName symbolicName, Node start, Properties properties, Node end) { super(symbolicName, start, $TYPE, properties, end); } @Override public UnboundRelation named(SymbolicName newSymbolicName) { return new UnboundRelation<>(newSymbolicName, getLeft(), getDetails().getProperties(), getRight()); } @Override public UnboundRelation withProperties(MapExpression newProperties) { return new UnboundRelation<>(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight()); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/pom.xml ================================================ 4.0.0 org.neo4j neo4j-cypher-dsl-examples ${revision}${sha1}${changelist} neo4j-cypher-dsl-examples-ogm-quarkus Examples (OGM Code generator) Example how to use the OGM code generator in a Quarkus application. 0.4 0.3 org.neo4j.cypherdsl.examples.ogm_quarkus quarkus-bom io.quarkus.platform ${basedir}/../../${aggregate.report.dir} ${quarkus.platform.group-id} ${quarkus.platform.artifact-id} ${quarkus.platform.version} pom import org.neo4j neo4j-cypher-dsl-bom ${project.version} pom import io.quarkus quarkus-arc io.quarkus quarkus-resteasy-jsonb org.neo4j neo4j-cypher-dsl org.neo4j neo4j-ogm-quarkus io.quarkus quarkus-junit5 test io.rest-assured rest-assured test org.assertj assertj-core test org.apache.maven.plugins maven-compiler-plugin org.neo4j neo4j-cypher-dsl-codegen-ogm ${project.version} -Xlint:all,-options,-path,-processing,-exports,-classfile -Werror -implicit:class ${quarkus.platform.group-id} quarkus-maven-plugin ${quarkus.platform.version} true build generate-code org.apache.maven.plugins maven-surefire-plugin org.jboss.logmanager.LogManager ${maven.home} org.apache.maven.plugins maven-failsafe-plugin ${project.build.directory}/${project.build.finalName}-runner org.jboss.logmanager.LogManager ${maven.home} integration-test verify ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/main/java/org/neo4j/cypherdsl/examples/ogm/books/Book.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.ogm.books; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * Example type. * * @author Michael J. Simons */ @NodeEntity public class Book { @Id @GeneratedValue String id; private String lang; } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/main/java/org/neo4j/cypherdsl/examples/ogm/books/BookGenre.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.ogm.books; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * Example type. * * @author Michael J. Simons */ @NodeEntity public class BookGenre { @Id @GeneratedValue String id; } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/main/java/org/neo4j/cypherdsl/examples/ogm/books/UserDetails.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.ogm.books; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * Example type. * * @author Michael J. Simons */ @NodeEntity public class UserDetails { @Id @GeneratedValue String id; } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/main/java/org/neo4j/cypherdsl/examples/ogm/books/UserPreferences.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.ogm.books; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * Example type. * * @author Michael J. Simons */ @NodeEntity public class UserPreferences { @Id @GeneratedValue String id; String userId; } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/main/java/org/neo4j/cypherdsl/examples/ogm/books/UserSuggestionActivity.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.ogm.books; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * Example type. * * @author Michael J. Simons */ @NodeEntity public class UserSuggestionActivity { @Id @GeneratedValue String id; String userId; } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/main/java/org/neo4j/cypherdsl/examples/ogm/books/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * This package exists only to reproduce an issue, it is not a complete example. */ package org.neo4j.cypherdsl.examples.ogm.books; ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/main/java/org/neo4j/cypherdsl/examples/ogm/misc/Example.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.ogm.misc; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Relationship; /** * Example type. * * @author Michael J. Simons */ @NodeEntity public class Example { @Id private final long id; @Relationship(type = "BELONGS_TO") private Example parent; public Example(long id) { this.id = id; } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/main/java/org/neo4j/cypherdsl/examples/ogm/misc/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * More OGM examples. */ package org.neo4j.cypherdsl.examples.ogm.misc; ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/main/java/org/neo4j/cypherdsl/examples/ogm/movies/Actor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.ogm.movies; import java.util.List; import jakarta.json.bind.annotation.JsonbProperty; import jakarta.json.bind.annotation.JsonbTransient; import org.neo4j.ogm.annotation.EndNode; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.RelationshipEntity; import org.neo4j.ogm.annotation.StartNode; /** * Example type. * * @author Michael J. Simons */ @RelationshipEntity("ACTED_IN") public final class Actor { @Id @GeneratedValue @JsonbTransient private Long id; private List roles; @StartNode @JsonbTransient private Person person; @EndNode private Movie movie; /** * {@return name of this actor} */ @JsonbProperty public String getName() { return this.person.getName(); } /** * {@return read only view of all the roles this actor played} */ public List getRoles() { return List.copyOf(this.roles); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/main/java/org/neo4j/cypherdsl/examples/ogm/movies/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.ogm.movies; import java.time.LocalDate; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Property; import org.neo4j.ogm.annotation.Relationship; /** * Example type. * * @author Michael J. Simons */ @NodeEntity public final class Movie { @Id private String title; @Property("tagline") private String description; @Relationship(value = "ACTED_IN", direction = Relationship.Direction.INCOMING) private List actors = new ArrayList<>(); @Relationship(value = "DIRECTED", direction = Relationship.Direction.INCOMING) private List directors = new ArrayList<>(); private LocalDate watchedOn; private Integer released; /** * A new movie. * @param title the unmodifiable title */ public Movie(String title) { this.title = title; } /** * Make OGM happy. */ Movie() { } /** * {@return the title} */ public String getTitle() { return this.title; } /** * {@return the description} */ public String getDescription() { return this.description; } /** * {@return read only view of the actors} */ public List getActors() { return List.copyOf(this.actors); } /** * {@return read only view of the directors} */ public List getDirectors() { return List.copyOf(this.directors); } /** * {@return release year} */ public Integer getReleased() { return this.released; } /** * Sets the release year. * @param released new release year */ public void setReleased(Integer released) { this.released = released; } /** * Adds new actors. * @param newActors list of new actors * @return this instance */ public Movie addActors(Collection newActors) { this.actors.addAll(newActors); return this; } /** * Adds new directors. * @param newDirectors list of new actors * @return this instance */ public Movie addDirectors(Collection newDirectors) { this.directors.addAll(newDirectors); return this; } public LocalDate getWatchedOn() { return this.watchedOn; } public void setWatchedOn(LocalDate watchedOn) { this.watchedOn = watchedOn; } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/main/java/org/neo4j/cypherdsl/examples/ogm/movies/MovieRepository.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.ogm.movies; import java.util.Collection; import java.util.Map; import jakarta.enterprise.context.ApplicationScoped; import org.neo4j.ogm.session.SessionFactory; /** * Example type. * * @author Michael J. Simons */ @ApplicationScoped class MovieRepository { private final SessionFactory sessionFactory; MovieRepository(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } Collection findAll() { return this.sessionFactory.openSession().loadAll(Movie.class); } Movie findByTitle(String title) { return this.sessionFactory.openSession() .queryForObject(Movie.class, "MATCH (m:$($label)) WHERE m[$property] = $propertyValue RETURN m", Map .of("label", Movie_.$PRIMARY_LABEL, "property", Movie_.MOVIE.TITLE.getName(), "propertyValue", title)); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/main/java/org/neo4j/cypherdsl/examples/ogm/movies/MovieResource.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.ogm.movies; import java.util.Collection; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; /** * Example resource. * * @author Michael J. Simons */ @RequestScoped @Path("/api/movies") public class MovieResource { private final MovieRepository movieRepository; /** * Creates a new {@link MovieResource}. * @param movieRepository the repository to retrieve movies from */ @Inject public MovieResource(MovieRepository movieRepository) { this.movieRepository = movieRepository; } /** * {@return all movies} */ @GET @Produces(MediaType.APPLICATION_JSON) public Collection getMovies() { return this.movieRepository.findAll(); } @GET @Path("/{title}") @Produces(MediaType.APPLICATION_JSON) public Movie getMovie(@PathParam("title") String title) { return this.movieRepository.findByTitle(title); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/main/java/org/neo4j/cypherdsl/examples/ogm/movies/PeopleRepository.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.ogm.movies; import jakarta.enterprise.context.ApplicationScoped; import org.neo4j.ogm.session.SessionFactory; /** * Example repository. * * @author Michael J. Simons */ @ApplicationScoped class PeopleRepository { private final SessionFactory sessionFactory; PeopleRepository(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } Person save(Person person) { var session = this.sessionFactory.openSession(); session.save(person); return person; } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/main/java/org/neo4j/cypherdsl/examples/ogm/movies/PeopleResource.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.ogm.movies; import jakarta.enterprise.context.RequestScoped; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; /** * Example resource. * * @author Michael J. Simons */ @RequestScoped @Path("/api/people") public class PeopleResource { private final PeopleRepository peopleRepository; /** * Creates a new instance with based on the given repository. * @param peopleRepository the repository to retrieve people from */ public PeopleResource(PeopleRepository peopleRepository) { this.peopleRepository = peopleRepository; } /** * Creates a new person. * @param newPerson the new person * @return response containing the new person */ @POST @Produces(MediaType.APPLICATION_JSON) public Response createNewPerson(Person newPerson) { var savedPerson = this.peopleRepository.save(newPerson); return Response.status(Response.Status.CREATED).entity(savedPerson).build(); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/main/java/org/neo4j/cypherdsl/examples/ogm/movies/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.ogm.movies; import jakarta.json.bind.annotation.JsonbCreator; import jakarta.json.bind.annotation.JsonbProperty; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; /** * Example type. * * @author Michael J. Simons */ @NodeEntity public final class Person { @Id @GeneratedValue private Long id; private String name; private Integer born; /** * A new person with a given name and a year of birth. * @param name given name * @param born and a year in which they have been born */ @JsonbCreator public Person(@JsonbProperty("name") String name, @JsonbProperty("born") Integer born) { this.name = name; this.born = born; } /** * Make OGM happy. */ Person() { } /** * {@return the person id} */ public Long getId() { return this.id; } /** * {@return the name} */ public String getName() { return this.name; } /** * {@return birth year} */ public Integer getBorn() { return this.born; } /** * Sets the year of birth. * @param born a new birth year */ public void setBorn(Integer born) { this.born = born; } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/main/java/org/neo4j/cypherdsl/examples/ogm/movies/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * OGM Movie example. */ package org.neo4j.cypherdsl.examples.ogm.movies; ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/main/resources/application.properties ================================================ # # Copyright (c) 2019-2026 "Neo4j," # Neo4j Sweden AB [https://neo4j.com] # # This file is part of Neo4j. # # 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. # org.neo4j.ogm.base-packages=org.neo4j.cypherdsl.examples.ogm.movies org.neo4j.ogm.use-native-types=true quarkus.neo4j.devservices.image-name=neo4j:2025.06 org.neo4j.migrations.enabled=false org.neo4j.migrations.locations-to-scan=neo4j/migrations,neo4j/example-data ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/test/java/org/neo4j/cypherdsl/examples/ogm/Neo4jOgmResourcesIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.ogm; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.util.List; import java.util.Map; import java.util.Objects; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; import io.restassured.http.ContentType; import jakarta.inject.Inject; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.driver.Driver; import org.neo4j.driver.Values; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ @QuarkusTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class Neo4jOgmResourcesIT { private static final int NUMBER_OF_INITIAL_MOVIES = 38; @Inject Driver driver; @BeforeAll public void createData() throws IOException { var movies = Files.readString( Path.of(Objects.requireNonNull(Neo4jOgmResourcesIT.class.getResource("/movies.cypher")).getPath())); try (var session = this.driver.session(); var tx = session.beginTransaction()) { tx.run("MATCH (n) DETACH DELETE n"); tx.run(movies); tx.commit(); } } /** * Test not Quarkus or Code gen builder specific, we just happen to have both a Neo4j * instance and a driver connected to it. */ @Test // GH-1449 void everlastingDurationFun() { var theDuration = Duration.ofDays(364).plusHours(47).plusMinutes(59).plusSeconds(61).plusMillis(1001); var isoOne = this.driver.executableQuery(Cypher.returning(Cypher.literalOf(theDuration)).build().getCypher()) .execute() .records() .get(0) .get(0) .asIsoDuration(); var isoTwo = this.driver.executableQuery("RETURN $1") .withParameters(Map.of("1", Values.value(theDuration))) .execute() .records() .get(0) .get(0) .asIsoDuration(); assertThat(isoOne).isEqualTo(isoTwo); } @Test public void getMoviesShouldWork() { var response = RestAssured.given().when().get("/api/movies").then().statusCode(200).extract().response(); var json = response.jsonPath(); assertThat(json.>getJsonObject("$").size()).isEqualTo(NUMBER_OF_INITIAL_MOVIES); var allTitles = json.>getJsonObject("title"); assertThat(allTitles.contains("Cloud Atlas")).isTrue(); } @Test public void getMovieWithANativeTypeShouldWork() { var response = RestAssured.given() .when() .get("/api/movies/The Matrix") .then() .statusCode(200) .extract() .response(); var json = response.jsonPath(); assertThat(json.get("watchedOn")).isNotNull(); } @Test public void createPersonShouldWork() { var response = RestAssured.given() .body("{\"name\":\"Lieschen Müller\",\"born\":2020}") .contentType(ContentType.JSON) .when() .post("/api/people") .then() .statusCode(201) .extract() .response(); var json = response.jsonPath(); assertThat(json.getObject("id", Long.class)).isNotNull(); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/test/java/org/neo4j/cypherdsl/examples/ogm/UsageTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.ogm; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.examples.ogm.misc.Example_; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons * */ class UsageTests { @Test // GH-335 void defaultUsageOfSelfReferentialNode() { Example_ node = Example_.EXAMPLE; assertThat(node).isNotNull(); } @Test // GH-335 void selfReferentialNodesShouldLeadToUsableCode() { Example_ node = Example_.EXAMPLE.named("example"); assertThat(node).isNotNull(); } @Test // GH-335 void ltolx() { var left = Example_.EXAMPLE.named("n"); var parent = left.withParent(Example_.EXAMPLE.named("m")); assertThat(parent.getLeft().getRequiredSymbolicName().getValue()).isEqualTo("n"); } @Test // GH-335 void rtorx() { var parent = Example_.EXAMPLE.withParent(Example_.EXAMPLE.named("m")); assertThat(parent.getRight().getRequiredSymbolicName().getValue()).isEqualTo("m"); } @Test // GH-335 void renamingLRShouldWork() { var left = Example_.EXAMPLE.named("n"); var right = Example_.EXAMPLE.named("m"); var rel = left.withParent(right).named("r"); var cypher = Cypher.match(rel) .where(right.ID.isEqualTo(Cypher.literalOf(1L))) .returning(left, right) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (n:`Example`)-[r:`BELONGS_TO`]->(m:`Example`) WHERE m.id = 1 RETURN n, m"); } @Test // GH-335 void renamingShouldWork() { var node = Example_.EXAMPLE.named("n"); var rel = node.withParent(Example_.EXAMPLE).named("r"); var cypher = Cypher.match(rel) .where(node.ID.isEqualTo(Cypher.literalOf(1L))) .returning(node) .build() .getCypher(); assertThat(cypher .matches("MATCH \\(n:`Example`\\)-\\[r:`BELONGS_TO`]->\\(.+:`Example`\\) WHERE n\\.id = 1 RETURN n")) .isTrue(); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/test/java/org/neo4j/cypherdsl/examples/ogm/books/ScopingTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.ogm.books; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.neo4j.cypherdsl.core.Cypher; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class ScopingTests { @ParameterizedTest // GH-1014 @ValueSource(booleans = { true, false }) void patternExpressionMustNotIntroduceNames(boolean withIt) { var userIdParam = Cypher.parameter("userId"); var limitParam = Cypher.parameter("limit"); var langParam = Cypher.parameter("lang"); var book = Book_.BOOK.withProperties(Book_.BOOK.LANG, langParam).named("book"); var userDetails = UserDetails_.USER_DETAILS.withProperties(UserDetails_.USER_DETAILS.ID, userIdParam) .named("user"); var userPreferences = UserPreferences_.USER_PREFERENCES .withProperties(UserPreferences_.USER_PREFERENCES.USER_ID, userIdParam) .named("preferences"); var genre = BookGenre_.BOOK_GENRE.named("genre"); var userActivity = UserSuggestionActivity_.USER_SUGGESTION_ACTIVITY .withProperties(UserSuggestionActivity_.USER_SUGGESTION_ACTIVITY.USER_ID, userIdParam) .named("activity"); var relationBookGenre = book.relationshipTo(genre, "BELONGS_TO").named("rel"); var relationBookAvoidedGenre = book.relationshipTo(BookGenre_.BOOK_GENRE, "BELONGS_TO") .relationshipFrom(userPreferences, "AVOIDED"); var relationUserPreferencesGenreBook = userDetails.relationshipTo(userPreferences, "HAS_PREFERENCES") .relationshipTo(genre, "PREFERRED") .relationshipFrom(book, "BELONGS_TO"); var returningBooks = Cypher.call("distinct").withArgs(Cypher.name("book")).asFunction(); // This is the correct variant, as shown in the latest code update on the issue, // matching the activity first, then // passing it on. if (withIt) { var statement = Cypher.match(relationUserPreferencesGenreBook) .match(userDetails.relationshipTo(userActivity, "HAS_ACTIVITY")) .where(Cypher.not(Cypher.exists(relationBookAvoidedGenre))) .with(book, userActivity) .limit(limitParam) .match(relationBookGenre) .where(Cypher.not(Cypher.exists(book.relationshipBetween(userActivity)))) .returning(returningBooks, Cypher.collect(Cypher.name("rel")), Cypher.collect(genre)) .build(); assertThat(statement.getCypher()).isEqualTo( "MATCH (user:`UserDetails` {id: $userId})-[:`HAS_PREFERENCES`]->(preferences:`UserPreferences` {userId: $userId})-[:`PREFERRED`]->(genre:`BookGenre`)<-[:`BELONGS_TO`]-(book:`Book` {lang: $lang}) MATCH (user)-[:`HAS_ACTIVITY`]->(activity:`UserSuggestionActivity` {userId: $userId}) WHERE NOT (exists((book)-[:`BELONGS_TO`]->(:`BookGenre`)<-[:`AVOIDED`]-(preferences))) WITH book, activity LIMIT $limit MATCH (book)-[rel:`BELONGS_TO`]->(genre:`BookGenre`) WHERE NOT (exists((book)--(activity))) RETURN distinct(book), collect(rel), collect(genre)"); } else { // Statement is identical, except the with clause. activity goes out of scope, // hence in the existential subquery it will be rerendered. // There however it must not include the name a new var statement = Cypher.match(relationUserPreferencesGenreBook) .match(userDetails.relationshipTo(userActivity, "HAS_ACTIVITY")) .where(Cypher.not(Cypher.exists(relationBookAvoidedGenre))) .with(book) .limit(limitParam) .match(relationBookGenre) .where(Cypher.not(Cypher.exists(book.relationshipBetween(userActivity)))) .returning(returningBooks, Cypher.collect(Cypher.name("rel")), Cypher.collect(genre)) .build(); assertThat(statement.getCypher()).isEqualTo( "MATCH (user:`UserDetails` {id: $userId})-[:`HAS_PREFERENCES`]->(preferences:`UserPreferences` {userId: $userId})-[:`PREFERRED`]->(genre:`BookGenre`)<-[:`BELONGS_TO`]-(book:`Book` {lang: $lang}) MATCH (user)-[:`HAS_ACTIVITY`]->(activity:`UserSuggestionActivity` {userId: $userId}) WHERE NOT (exists((book)-[:`BELONGS_TO`]->(:`BookGenre`)<-[:`AVOIDED`]-(preferences))) WITH book LIMIT $limit MATCH (book)-[rel:`BELONGS_TO`]->(genre:`BookGenre`) WHERE NOT (exists((book)--(:`UserSuggestionActivity` {userId: $userId}))) RETURN distinct(book), collect(rel), collect(genre)"); } } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-ogm-quarkus/src/test/resources/movies.cypher ================================================ CREATE (TheMatrix:Movie {title:'The Matrix', released:1999, tagline:'Welcome to the Real World', watchedOn: date()}) CREATE (Keanu:Person {name:'Keanu Reeves', born:1964}) CREATE (Carrie:Person {name:'Carrie-Anne Moss', born:1967}) CREATE (Laurence:Person {name:'Laurence Fishburne', born:1961}) CREATE (Hugo:Person {name:'Hugo Weaving', born:1960}) CREATE (LillyW:Person {name:'Lilly Wachowski', born:1967}) CREATE (LanaW:Person {name:'Lana Wachowski', born:1965}) CREATE (JoelS:Person {name:'Joel Silver', born:1952}) CREATE (Keanu)-[:ACTED_IN {roles:['Neo']}]->(TheMatrix), (Carrie)-[:ACTED_IN {roles:['Trinity']}]->(TheMatrix), (Laurence)-[:ACTED_IN {roles:['Morpheus']}]->(TheMatrix), (Hugo)-[:ACTED_IN {roles:['Agent Smith']}]->(TheMatrix), (LillyW)-[:DIRECTED]->(TheMatrix), (LanaW)-[:DIRECTED]->(TheMatrix), (JoelS)-[:PRODUCED]->(TheMatrix) CREATE (Emil:Person {name:"Emil Eifrem", born:1978}) CREATE (Emil)-[:ACTED_IN {roles:["Emil"]}]->(TheMatrix) CREATE (TheMatrixReloaded:Movie {title:'The Matrix Reloaded', released:2003, tagline:'Free your mind'}) CREATE (Keanu)-[:ACTED_IN {roles:['Neo']}]->(TheMatrixReloaded), (Carrie)-[:ACTED_IN {roles:['Trinity']}]->(TheMatrixReloaded), (Laurence)-[:ACTED_IN {roles:['Morpheus']}]->(TheMatrixReloaded), (Hugo)-[:ACTED_IN {roles:['Agent Smith']}]->(TheMatrixReloaded), (LillyW)-[:DIRECTED]->(TheMatrixReloaded), (LanaW)-[:DIRECTED]->(TheMatrixReloaded), (JoelS)-[:PRODUCED]->(TheMatrixReloaded) CREATE (TheMatrixRevolutions:Movie {title:'The Matrix Revolutions', released:2003, tagline:'Everything that has a beginning has an end'}) CREATE (Keanu)-[:ACTED_IN {roles:['Neo']}]->(TheMatrixRevolutions), (Carrie)-[:ACTED_IN {roles:['Trinity']}]->(TheMatrixRevolutions), (Laurence)-[:ACTED_IN {roles:['Morpheus']}]->(TheMatrixRevolutions), (Hugo)-[:ACTED_IN {roles:['Agent Smith']}]->(TheMatrixRevolutions), (LillyW)-[:DIRECTED]->(TheMatrixRevolutions), (LanaW)-[:DIRECTED]->(TheMatrixRevolutions), (JoelS)-[:PRODUCED]->(TheMatrixRevolutions) CREATE (TheDevilsAdvocate:Movie {title:"The Devil's Advocate", released:1997, tagline:'Evil has its winning ways'}) CREATE (Charlize:Person {name:'Charlize Theron', born:1975}) CREATE (Al:Person {name:'Al Pacino', born:1940}) CREATE (Taylor:Person {name:'Taylor Hackford', born:1944}) CREATE (Keanu)-[:ACTED_IN {roles:['Kevin Lomax']}]->(TheDevilsAdvocate), (Charlize)-[:ACTED_IN {roles:['Mary Ann Lomax']}]->(TheDevilsAdvocate), (Al)-[:ACTED_IN {roles:['John Milton']}]->(TheDevilsAdvocate), (Taylor)-[:DIRECTED]->(TheDevilsAdvocate) CREATE (AFewGoodMen:Movie {title:"A Few Good Men", released:1992, tagline:"In the heart of the nation's capital, in a courthouse of the U.S. government, one man will stop at nothing to keep his honor, and one will stop at nothing to find the truth."}) CREATE (TomC:Person {name:'Tom Cruise', born:1962}) CREATE (JackN:Person {name:'Jack Nicholson', born:1937}) CREATE (DemiM:Person {name:'Demi Moore', born:1962}) CREATE (KevinB:Person {name:'Kevin Bacon', born:1958}) CREATE (KieferS:Person {name:'Kiefer Sutherland', born:1966}) CREATE (NoahW:Person {name:'Noah Wyle', born:1971}) CREATE (CubaG:Person {name:'Cuba Gooding Jr.', born:1968}) CREATE (KevinP:Person {name:'Kevin Pollak', born:1957}) CREATE (JTW:Person {name:'J.T. Walsh', born:1943}) CREATE (JamesM:Person {name:'James Marshall', born:1967}) CREATE (ChristopherG:Person {name:'Christopher Guest', born:1948}) CREATE (RobR:Person {name:'Rob Reiner', born:1947}) CREATE (AaronS:Person {name:'Aaron Sorkin', born:1961}) CREATE (TomC)-[:ACTED_IN {roles:['Lt. Daniel Kaffee']}]->(AFewGoodMen), (JackN)-[:ACTED_IN {roles:['Col. Nathan R. Jessup']}]->(AFewGoodMen), (DemiM)-[:ACTED_IN {roles:['Lt. Cdr. JoAnne Galloway']}]->(AFewGoodMen), (KevinB)-[:ACTED_IN {roles:['Capt. Jack Ross']}]->(AFewGoodMen), (KieferS)-[:ACTED_IN {roles:['Lt. Jonathan Kendrick']}]->(AFewGoodMen), (NoahW)-[:ACTED_IN {roles:['Cpl. Jeffrey Barnes']}]->(AFewGoodMen), (CubaG)-[:ACTED_IN {roles:['Cpl. Carl Hammaker']}]->(AFewGoodMen), (KevinP)-[:ACTED_IN {roles:['Lt. Sam Weinberg']}]->(AFewGoodMen), (JTW)-[:ACTED_IN {roles:['Lt. Col. Matthew Andrew Markinson']}]->(AFewGoodMen), (JamesM)-[:ACTED_IN {roles:['Pfc. Louden Downey']}]->(AFewGoodMen), (ChristopherG)-[:ACTED_IN {roles:['Dr. Stone']}]->(AFewGoodMen), (AaronS)-[:ACTED_IN {roles:['Man in Bar']}]->(AFewGoodMen), (RobR)-[:DIRECTED]->(AFewGoodMen), (AaronS)-[:WROTE]->(AFewGoodMen) CREATE (TopGun:Movie {title:"Top Gun", released:1986, tagline:'I feel the need, the need for speed.'}) CREATE (KellyM:Person {name:'Kelly McGillis', born:1957}) CREATE (ValK:Person {name:'Val Kilmer', born:1959}) CREATE (AnthonyE:Person {name:'Anthony Edwards', born:1962}) CREATE (TomS:Person {name:'Tom Skerritt', born:1933}) CREATE (MegR:Person {name:'Meg Ryan', born:1961}) CREATE (TonyS:Person {name:'Tony Scott', born:1944}) CREATE (JimC:Person {name:'Jim Cash', born:1941}) CREATE (TomC)-[:ACTED_IN {roles:['Maverick']}]->(TopGun), (KellyM)-[:ACTED_IN {roles:['Charlie']}]->(TopGun), (ValK)-[:ACTED_IN {roles:['Iceman']}]->(TopGun), (AnthonyE)-[:ACTED_IN {roles:['Goose']}]->(TopGun), (TomS)-[:ACTED_IN {roles:['Viper']}]->(TopGun), (MegR)-[:ACTED_IN {roles:['Carole']}]->(TopGun), (TonyS)-[:DIRECTED]->(TopGun), (JimC)-[:WROTE]->(TopGun) CREATE (JerryMaguire:Movie {title:'Jerry Maguire', released:2000, tagline:'The rest of his life begins now.'}) CREATE (ReneeZ:Person {name:'Renee Zellweger', born:1969}) CREATE (KellyP:Person {name:'Kelly Preston', born:1962}) CREATE (JerryO:Person {name:"Jerry O'Connell", born:1974}) CREATE (JayM:Person {name:'Jay Mohr', born:1970}) CREATE (BonnieH:Person {name:'Bonnie Hunt', born:1961}) CREATE (ReginaK:Person {name:'Regina King', born:1971}) CREATE (JonathanL:Person {name:'Jonathan Lipnicki', born:1996}) CREATE (CameronC:Person {name:'Cameron Crowe', born:1957}) CREATE (TomC)-[:ACTED_IN {roles:['Jerry Maguire']}]->(JerryMaguire), (CubaG)-[:ACTED_IN {roles:['Rod Tidwell']}]->(JerryMaguire), (ReneeZ)-[:ACTED_IN {roles:['Dorothy Boyd']}]->(JerryMaguire), (KellyP)-[:ACTED_IN {roles:['Avery Bishop']}]->(JerryMaguire), (JerryO)-[:ACTED_IN {roles:['Frank Cushman']}]->(JerryMaguire), (JayM)-[:ACTED_IN {roles:['Bob Sugar']}]->(JerryMaguire), (BonnieH)-[:ACTED_IN {roles:['Laurel Boyd']}]->(JerryMaguire), (ReginaK)-[:ACTED_IN {roles:['Marcee Tidwell']}]->(JerryMaguire), (JonathanL)-[:ACTED_IN {roles:['Ray Boyd']}]->(JerryMaguire), (CameronC)-[:DIRECTED]->(JerryMaguire), (CameronC)-[:PRODUCED]->(JerryMaguire), (CameronC)-[:WROTE]->(JerryMaguire) CREATE (StandByMe:Movie {title:"Stand By Me", released:1986, tagline:"For some, it's the last real taste of innocence, and the first real taste of life. But for everyone, it's the time that memories are made of."}) CREATE (RiverP:Person {name:'River Phoenix', born:1970}) CREATE (CoreyF:Person {name:'Corey Feldman', born:1971}) CREATE (WilW:Person {name:'Wil Wheaton', born:1972}) CREATE (JohnC:Person {name:'John Cusack', born:1966}) CREATE (MarshallB:Person {name:'Marshall Bell', born:1942}) CREATE (WilW)-[:ACTED_IN {roles:['Gordie Lachance']}]->(StandByMe), (RiverP)-[:ACTED_IN {roles:['Chris Chambers']}]->(StandByMe), (JerryO)-[:ACTED_IN {roles:['Vern Tessio']}]->(StandByMe), (CoreyF)-[:ACTED_IN {roles:['Teddy Duchamp']}]->(StandByMe), (JohnC)-[:ACTED_IN {roles:['Denny Lachance']}]->(StandByMe), (KieferS)-[:ACTED_IN {roles:['Ace Merrill']}]->(StandByMe), (MarshallB)-[:ACTED_IN {roles:['Mr. Lachance']}]->(StandByMe), (RobR)-[:DIRECTED]->(StandByMe) CREATE (AsGoodAsItGets:Movie {title:'As Good as It Gets', released:1997, tagline:'A comedy from the heart that goes for the throat.'}) CREATE (HelenH:Person {name:'Helen Hunt', born:1963}) CREATE (GregK:Person {name:'Greg Kinnear', born:1963}) CREATE (JamesB:Person {name:'James L. Brooks', born:1940}) CREATE (JackN)-[:ACTED_IN {roles:['Melvin Udall']}]->(AsGoodAsItGets), (HelenH)-[:ACTED_IN {roles:['Carol Connelly']}]->(AsGoodAsItGets), (GregK)-[:ACTED_IN {roles:['Simon Bishop']}]->(AsGoodAsItGets), (CubaG)-[:ACTED_IN {roles:['Frank Sachs']}]->(AsGoodAsItGets), (JamesB)-[:DIRECTED]->(AsGoodAsItGets) CREATE (WhatDreamsMayCome:Movie {title:'What Dreams May Come', released:1998, tagline:'After life there is more. The end is just the beginning.'}) CREATE (AnnabellaS:Person {name:'Annabella Sciorra', born:1960}) CREATE (MaxS:Person {name:'Max von Sydow', born:1929}) CREATE (WernerH:Person {name:'Werner Herzog', born:1942}) CREATE (Robin:Person {name:'Robin Williams', born:1951}) CREATE (VincentW:Person {name:'Vincent Ward', born:1956}) CREATE (Robin)-[:ACTED_IN {roles:['Chris Nielsen']}]->(WhatDreamsMayCome), (CubaG)-[:ACTED_IN {roles:['Albert Lewis']}]->(WhatDreamsMayCome), (AnnabellaS)-[:ACTED_IN {roles:['Annie Collins-Nielsen']}]->(WhatDreamsMayCome), (MaxS)-[:ACTED_IN {roles:['The Tracker']}]->(WhatDreamsMayCome), (WernerH)-[:ACTED_IN {roles:['The Face']}]->(WhatDreamsMayCome), (VincentW)-[:DIRECTED]->(WhatDreamsMayCome) CREATE (SnowFallingonCedars:Movie {title:'Snow Falling on Cedars', released:1999, tagline:'First loves last. Forever.'}) CREATE (EthanH:Person {name:'Ethan Hawke', born:1970}) CREATE (RickY:Person {name:'Rick Yune', born:1971}) CREATE (JamesC:Person {name:'James Cromwell', born:1940}) CREATE (ScottH:Person {name:'Scott Hicks', born:1953}) CREATE (EthanH)-[:ACTED_IN {roles:['Ishmael Chambers']}]->(SnowFallingonCedars), (RickY)-[:ACTED_IN {roles:['Kazuo Miyamoto']}]->(SnowFallingonCedars), (MaxS)-[:ACTED_IN {roles:['Nels Gudmundsson']}]->(SnowFallingonCedars), (JamesC)-[:ACTED_IN {roles:['Judge Fielding']}]->(SnowFallingonCedars), (ScottH)-[:DIRECTED]->(SnowFallingonCedars) CREATE (YouveGotMail:Movie {title:"You've Got Mail", released:1998, tagline:'At odds in life... in love on-line.'}) CREATE (ParkerP:Person {name:'Parker Posey', born:1968}) CREATE (DaveC:Person {name:'Dave Chappelle', born:1973}) CREATE (SteveZ:Person {name:'Steve Zahn', born:1967}) CREATE (TomH:Person {name:'Tom Hanks', born:1956}) CREATE (NoraE:Person {name:'Nora Ephron', born:1941}) CREATE (TomH)-[:ACTED_IN {roles:['Joe Fox']}]->(YouveGotMail), (MegR)-[:ACTED_IN {roles:['Kathleen Kelly']}]->(YouveGotMail), (GregK)-[:ACTED_IN {roles:['Frank Navasky']}]->(YouveGotMail), (ParkerP)-[:ACTED_IN {roles:['Patricia Eden']}]->(YouveGotMail), (DaveC)-[:ACTED_IN {roles:['Kevin Jackson']}]->(YouveGotMail), (SteveZ)-[:ACTED_IN {roles:['George Pappas']}]->(YouveGotMail), (NoraE)-[:DIRECTED]->(YouveGotMail) CREATE (SleeplessInSeattle:Movie {title:'Sleepless in Seattle', released:1993, tagline:'What if someone you never met, someone you never saw, someone you never knew was the only someone for you?'}) CREATE (RitaW:Person {name:'Rita Wilson', born:1956}) CREATE (BillPull:Person {name:'Bill Pullman', born:1953}) CREATE (VictorG:Person {name:'Victor Garber', born:1949}) CREATE (RosieO:Person {name:"Rosie O'Donnell", born:1962}) CREATE (TomH)-[:ACTED_IN {roles:['Sam Baldwin']}]->(SleeplessInSeattle), (MegR)-[:ACTED_IN {roles:['Annie Reed']}]->(SleeplessInSeattle), (RitaW)-[:ACTED_IN {roles:['Suzy']}]->(SleeplessInSeattle), (BillPull)-[:ACTED_IN {roles:['Walter']}]->(SleeplessInSeattle), (VictorG)-[:ACTED_IN {roles:['Greg']}]->(SleeplessInSeattle), (RosieO)-[:ACTED_IN {roles:['Becky']}]->(SleeplessInSeattle), (NoraE)-[:DIRECTED]->(SleeplessInSeattle) CREATE (JoeVersustheVolcano:Movie {title:'Joe Versus the Volcano', released:1990, tagline:'A story of love, lava and burning desire.'}) CREATE (JohnS:Person {name:'John Patrick Stanley', born:1950}) CREATE (Nathan:Person {name:'Nathan Lane', born:1956}) CREATE (TomH)-[:ACTED_IN {roles:['Joe Banks']}]->(JoeVersustheVolcano), (MegR)-[:ACTED_IN {roles:['DeDe', 'Angelica Graynamore', 'Patricia Graynamore']}]->(JoeVersustheVolcano), (Nathan)-[:ACTED_IN {roles:['Baw']}]->(JoeVersustheVolcano), (JohnS)-[:DIRECTED]->(JoeVersustheVolcano) CREATE (WhenHarryMetSally:Movie {title:'When Harry Met Sally', released:1998, tagline:'Can two friends sleep together and still love each other in the morning?'}) CREATE (BillyC:Person {name:'Billy Crystal', born:1948}) CREATE (CarrieF:Person {name:'Carrie Fisher', born:1956}) CREATE (BrunoK:Person {name:'Bruno Kirby', born:1949}) CREATE (BillyC)-[:ACTED_IN {roles:['Harry Burns']}]->(WhenHarryMetSally), (MegR)-[:ACTED_IN {roles:['Sally Albright']}]->(WhenHarryMetSally), (CarrieF)-[:ACTED_IN {roles:['Marie']}]->(WhenHarryMetSally), (BrunoK)-[:ACTED_IN {roles:['Jess']}]->(WhenHarryMetSally), (RobR)-[:DIRECTED]->(WhenHarryMetSally), (RobR)-[:PRODUCED]->(WhenHarryMetSally), (NoraE)-[:PRODUCED]->(WhenHarryMetSally), (NoraE)-[:WROTE]->(WhenHarryMetSally) CREATE (ThatThingYouDo:Movie {title:'That Thing You Do', released:1996, tagline:'In every life there comes a time when that thing you dream becomes that thing you do'}) CREATE (LivT:Person {name:'Liv Tyler', born:1977}) CREATE (TomH)-[:ACTED_IN {roles:['Mr. White']}]->(ThatThingYouDo), (LivT)-[:ACTED_IN {roles:['Faye Dolan']}]->(ThatThingYouDo), (Charlize)-[:ACTED_IN {roles:['Tina']}]->(ThatThingYouDo), (TomH)-[:DIRECTED]->(ThatThingYouDo) CREATE (TheReplacements:Movie {title:'The Replacements', released:2000, tagline:'Pain heals, Chicks dig scars... Glory lasts forever'}) CREATE (Brooke:Person {name:'Brooke Langton', born:1970}) CREATE (Gene:Person {name:'Gene Hackman', born:1930}) CREATE (Orlando:Person {name:'Orlando Jones', born:1968}) CREATE (Howard:Person {name:'Howard Deutch', born:1950}) CREATE (Keanu)-[:ACTED_IN {roles:['Shane Falco']}]->(TheReplacements), (Brooke)-[:ACTED_IN {roles:['Annabelle Farrell']}]->(TheReplacements), (Gene)-[:ACTED_IN {roles:['Jimmy McGinty']}]->(TheReplacements), (Orlando)-[:ACTED_IN {roles:['Clifford Franklin']}]->(TheReplacements), (Howard)-[:DIRECTED]->(TheReplacements) CREATE (RescueDawn:Movie {title:'RescueDawn', released:2006, tagline:"Based on the extraordinary true story of one man's fight for freedom"}) CREATE (ChristianB:Person {name:'Christian Bale', born:1974}) CREATE (ZachG:Person {name:'Zach Grenier', born:1954}) CREATE (MarshallB)-[:ACTED_IN {roles:['Admiral']}]->(RescueDawn), (ChristianB)-[:ACTED_IN {roles:['Dieter Dengler']}]->(RescueDawn), (ZachG)-[:ACTED_IN {roles:['Squad Leader']}]->(RescueDawn), (SteveZ)-[:ACTED_IN {roles:['Duane']}]->(RescueDawn), (WernerH)-[:DIRECTED]->(RescueDawn) CREATE (TheBirdcage:Movie {title:'The Birdcage', released:1996, tagline:'Come as you are'}) CREATE (MikeN:Person {name:'Mike Nichols', born:1931}) CREATE (Robin)-[:ACTED_IN {roles:['Armand Goldman']}]->(TheBirdcage), (Nathan)-[:ACTED_IN {roles:['Albert Goldman']}]->(TheBirdcage), (Gene)-[:ACTED_IN {roles:['Sen. Kevin Keeley']}]->(TheBirdcage), (MikeN)-[:DIRECTED]->(TheBirdcage) CREATE (Unforgiven:Movie {title:'Unforgiven', released:1992, tagline:"It's a hell of a thing, killing a man"}) CREATE (RichardH:Person {name:'Richard Harris', born:1930}) CREATE (ClintE:Person {name:'Clint Eastwood', born:1930}) CREATE (RichardH)-[:ACTED_IN {roles:['English Bob']}]->(Unforgiven), (ClintE)-[:ACTED_IN {roles:['Bill Munny']}]->(Unforgiven), (Gene)-[:ACTED_IN {roles:['Little Bill Daggett']}]->(Unforgiven), (ClintE)-[:DIRECTED]->(Unforgiven) CREATE (JohnnyMnemonic:Movie {title:'Johnny Mnemonic', released:1995, tagline:'The hottest data on earth. In the coolest head in town'}) CREATE (Takeshi:Person {name:'Takeshi Kitano', born:1947}) CREATE (Dina:Person {name:'Dina Meyer', born:1968}) CREATE (IceT:Person {name:'Ice-T', born:1958}) CREATE (RobertL:Person {name:'Robert Longo', born:1953}) CREATE (Keanu)-[:ACTED_IN {roles:['Johnny Mnemonic']}]->(JohnnyMnemonic), (Takeshi)-[:ACTED_IN {roles:['Takahashi']}]->(JohnnyMnemonic), (Dina)-[:ACTED_IN {roles:['Jane']}]->(JohnnyMnemonic), (IceT)-[:ACTED_IN {roles:['J-Bone']}]->(JohnnyMnemonic), (RobertL)-[:DIRECTED]->(JohnnyMnemonic) CREATE (CloudAtlas:Movie {title:'Cloud Atlas', released:2012, tagline:'Everything is connected'}) CREATE (HalleB:Person {name:'Halle Berry', born:1966}) CREATE (JimB:Person {name:'Jim Broadbent', born:1949}) CREATE (TomT:Person {name:'Tom Tykwer', born:1965}) CREATE (DavidMitchell:Person {name:'David Mitchell', born:1969}) CREATE (StefanArndt:Person {name:'Stefan Arndt', born:1961}) CREATE (TomH)-[:ACTED_IN {roles:['Zachry', 'Dr. Henry Goose', 'Isaac Sachs', 'Dermot Hoggins']}]->(CloudAtlas), (Hugo)-[:ACTED_IN {roles:['Bill Smoke', 'Haskell Moore', 'Tadeusz Kesselring', 'Nurse Noakes', 'Boardman Mephi', 'Old Georgie']}]->(CloudAtlas), (HalleB)-[:ACTED_IN {roles:['Luisa Rey', 'Jocasta Ayrs', 'Ovid', 'Meronym']}]->(CloudAtlas), (JimB)-[:ACTED_IN {roles:['Vyvyan Ayrs', 'Captain Molyneux', 'Timothy Cavendish']}]->(CloudAtlas), (TomT)-[:DIRECTED]->(CloudAtlas), (LillyW)-[:DIRECTED]->(CloudAtlas), (LanaW)-[:DIRECTED]->(CloudAtlas), (DavidMitchell)-[:WROTE]->(CloudAtlas), (StefanArndt)-[:PRODUCED]->(CloudAtlas) CREATE (TheDaVinciCode:Movie {title:'The Da Vinci Code', released:2006, tagline:'Break The Codes'}) CREATE (IanM:Person {name:'Ian McKellen', born:1939}) CREATE (AudreyT:Person {name:'Audrey Tautou', born:1976}) CREATE (PaulB:Person {name:'Paul Bettany', born:1971}) CREATE (RonH:Person {name:'Ron Howard', born:1954}) CREATE (TomH)-[:ACTED_IN {roles:['Dr. Robert Langdon']}]->(TheDaVinciCode), (IanM)-[:ACTED_IN {roles:['Sir Leight Teabing']}]->(TheDaVinciCode), (AudreyT)-[:ACTED_IN {roles:['Sophie Neveu']}]->(TheDaVinciCode), (PaulB)-[:ACTED_IN {roles:['Silas']}]->(TheDaVinciCode), (RonH)-[:DIRECTED]->(TheDaVinciCode) CREATE (VforVendetta:Movie {title:'V for Vendetta', released:2006, tagline:'Freedom! Forever!'}) CREATE (NatalieP:Person {name:'Natalie Portman', born:1981}) CREATE (StephenR:Person {name:'Stephen Rea', born:1946}) CREATE (JohnH:Person {name:'John Hurt', born:1940}) CREATE (BenM:Person {name: 'Ben Miles', born:1967}) CREATE (Hugo)-[:ACTED_IN {roles:['V']}]->(VforVendetta), (NatalieP)-[:ACTED_IN {roles:['Evey Hammond']}]->(VforVendetta), (StephenR)-[:ACTED_IN {roles:['Eric Finch']}]->(VforVendetta), (JohnH)-[:ACTED_IN {roles:['High Chancellor Adam Sutler']}]->(VforVendetta), (BenM)-[:ACTED_IN {roles:['Dascomb']}]->(VforVendetta), (JamesM)-[:DIRECTED]->(VforVendetta), (LillyW)-[:PRODUCED]->(VforVendetta), (LanaW)-[:PRODUCED]->(VforVendetta), (JoelS)-[:PRODUCED]->(VforVendetta), (LillyW)-[:WROTE]->(VforVendetta), (LanaW)-[:WROTE]->(VforVendetta) CREATE (SpeedRacer:Movie {title:'Speed Racer', released:2008, tagline:'Speed has no limits'}) CREATE (EmileH:Person {name:'Emile Hirsch', born:1985}) CREATE (JohnG:Person {name:'John Goodman', born:1960}) CREATE (SusanS:Person {name:'Susan Sarandon', born:1946}) CREATE (MatthewF:Person {name:'Matthew Fox', born:1966}) CREATE (ChristinaR:Person {name:'Christina Ricci', born:1980}) CREATE (Rain:Person {name:'Rain', born:1982}) CREATE (EmileH)-[:ACTED_IN {roles:['Speed Racer']}]->(SpeedRacer), (JohnG)-[:ACTED_IN {roles:['Pops']}]->(SpeedRacer), (SusanS)-[:ACTED_IN {roles:['Mom']}]->(SpeedRacer), (MatthewF)-[:ACTED_IN {roles:['Racer X']}]->(SpeedRacer), (ChristinaR)-[:ACTED_IN {roles:['Trixie']}]->(SpeedRacer), (Rain)-[:ACTED_IN {roles:['Taejo Togokahn']}]->(SpeedRacer), (BenM)-[:ACTED_IN {roles:['Cass Jones']}]->(SpeedRacer), (LillyW)-[:DIRECTED]->(SpeedRacer), (LanaW)-[:DIRECTED]->(SpeedRacer), (LillyW)-[:WROTE]->(SpeedRacer), (LanaW)-[:WROTE]->(SpeedRacer), (JoelS)-[:PRODUCED]->(SpeedRacer) CREATE (NinjaAssassin:Movie {title:'Ninja Assassin', released:2009, tagline:'Prepare to enter a secret world of assassins'}) CREATE (NaomieH:Person {name:'Naomie Harris'}) CREATE (Rain)-[:ACTED_IN {roles:['Raizo']}]->(NinjaAssassin), (NaomieH)-[:ACTED_IN {roles:['Mika Coretti']}]->(NinjaAssassin), (RickY)-[:ACTED_IN {roles:['Takeshi']}]->(NinjaAssassin), (BenM)-[:ACTED_IN {roles:['Ryan Maslow']}]->(NinjaAssassin), (JamesM)-[:DIRECTED]->(NinjaAssassin), (LillyW)-[:PRODUCED]->(NinjaAssassin), (LanaW)-[:PRODUCED]->(NinjaAssassin), (JoelS)-[:PRODUCED]->(NinjaAssassin) CREATE (TheGreenMile:Movie {title:'The Green Mile', released:1999, tagline:"Walk a mile you'll never forget."}) CREATE (MichaelD:Person {name:'Michael Clarke Duncan', born:1957}) CREATE (DavidM:Person {name:'David Morse', born:1953}) CREATE (SamR:Person {name:'Sam Rockwell', born:1968}) CREATE (GaryS:Person {name:'Gary Sinise', born:1955}) CREATE (PatriciaC:Person {name:'Patricia Clarkson', born:1959}) CREATE (FrankD:Person {name:'Frank Darabont', born:1959}) CREATE (TomH)-[:ACTED_IN {roles:['Paul Edgecomb']}]->(TheGreenMile), (MichaelD)-[:ACTED_IN {roles:['John Coffey']}]->(TheGreenMile), (DavidM)-[:ACTED_IN {roles:['Brutus "Brutal" Howell']}]->(TheGreenMile), (BonnieH)-[:ACTED_IN {roles:['Jan Edgecomb']}]->(TheGreenMile), (JamesC)-[:ACTED_IN {roles:['Warden Hal Moores']}]->(TheGreenMile), (SamR)-[:ACTED_IN {roles:['"Wild Bill" Wharton']}]->(TheGreenMile), (GaryS)-[:ACTED_IN {roles:['Burt Hammersmith']}]->(TheGreenMile), (PatriciaC)-[:ACTED_IN {roles:['Melinda Moores']}]->(TheGreenMile), (FrankD)-[:DIRECTED]->(TheGreenMile) CREATE (FrostNixon:Movie {title:'Frost/Nixon', released:2008, tagline:'400 million people were waiting for the truth.'}) CREATE (FrankL:Person {name:'Frank Langella', born:1938}) CREATE (MichaelS:Person {name:'Michael Sheen', born:1969}) CREATE (OliverP:Person {name:'Oliver Platt', born:1960}) CREATE (FrankL)-[:ACTED_IN {roles:['Richard Nixon']}]->(FrostNixon), (MichaelS)-[:ACTED_IN {roles:['David Frost']}]->(FrostNixon), (KevinB)-[:ACTED_IN {roles:['Jack Brennan']}]->(FrostNixon), (OliverP)-[:ACTED_IN {roles:['Bob Zelnick']}]->(FrostNixon), (SamR)-[:ACTED_IN {roles:['James Reston, Jr.']}]->(FrostNixon), (RonH)-[:DIRECTED]->(FrostNixon) CREATE (Hoffa:Movie {title:'Hoffa', released:1992, tagline:"He didn't want law. He wanted justice."}) CREATE (DannyD:Person {name:'Danny DeVito', born:1944}) CREATE (JohnR:Person {name:'John C. Reilly', born:1965}) CREATE (JackN)-[:ACTED_IN {roles:['Hoffa']}]->(Hoffa), (DannyD)-[:ACTED_IN {roles:['Robert "Bobby" Ciaro']}]->(Hoffa), (JTW)-[:ACTED_IN {roles:['Frank Fitzsimmons']}]->(Hoffa), (JohnR)-[:ACTED_IN {roles:['Peter "Pete" Connelly']}]->(Hoffa), (DannyD)-[:DIRECTED]->(Hoffa) CREATE (Apollo13:Movie {title:'Apollo 13', released:1995, tagline:'Houston, we have a problem.'}) CREATE (EdH:Person {name:'Ed Harris', born:1950}) CREATE (BillPax:Person {name:'Bill Paxton', born:1955}) CREATE (TomH)-[:ACTED_IN {roles:['Jim Lovell']}]->(Apollo13), (KevinB)-[:ACTED_IN {roles:['Jack Swigert']}]->(Apollo13), (EdH)-[:ACTED_IN {roles:['Gene Kranz']}]->(Apollo13), (BillPax)-[:ACTED_IN {roles:['Fred Haise']}]->(Apollo13), (GaryS)-[:ACTED_IN {roles:['Ken Mattingly']}]->(Apollo13), (RonH)-[:DIRECTED]->(Apollo13) CREATE (Twister:Movie {title:'Twister', released:1996, tagline:"Don't Breathe. Don't Look Back."}) CREATE (PhilipH:Person {name:'Philip Seymour Hoffman', born:1967}) CREATE (JanB:Person {name:'Jan de Bont', born:1943}) CREATE (BillPax)-[:ACTED_IN {roles:['Bill Harding']}]->(Twister), (HelenH)-[:ACTED_IN {roles:['Dr. Jo Harding']}]->(Twister), (ZachG)-[:ACTED_IN {roles:['Eddie']}]->(Twister), (PhilipH)-[:ACTED_IN {roles:['Dustin "Dusty" Davis']}]->(Twister), (JanB)-[:DIRECTED]->(Twister) CREATE (CastAway:Movie {title:'Cast Away', released:2000, tagline:'At the edge of the world, his journey begins.'}) CREATE (RobertZ:Person {name:'Robert Zemeckis', born:1951}) CREATE (TomH)-[:ACTED_IN {roles:['Chuck Noland']}]->(CastAway), (HelenH)-[:ACTED_IN {roles:['Kelly Frears']}]->(CastAway), (RobertZ)-[:DIRECTED]->(CastAway) CREATE (OneFlewOvertheCuckoosNest:Movie {title:"One Flew Over the Cuckoo's Nest", released:1975, tagline:"If he's crazy, what does that make you?"}) CREATE (MilosF:Person {name:'Milos Forman', born:1932}) CREATE (JackN)-[:ACTED_IN {roles:['Randle McMurphy']}]->(OneFlewOvertheCuckoosNest), (DannyD)-[:ACTED_IN {roles:['Martini']}]->(OneFlewOvertheCuckoosNest), (MilosF)-[:DIRECTED]->(OneFlewOvertheCuckoosNest) CREATE (SomethingsGottaGive:Movie {title:"Something's Gotta Give", released:2003}) CREATE (DianeK:Person {name:'Diane Keaton', born:1946}) CREATE (NancyM:Person {name:'Nancy Meyers', born:1949}) CREATE (JackN)-[:ACTED_IN {roles:['Harry Sanborn']}]->(SomethingsGottaGive), (DianeK)-[:ACTED_IN {roles:['Erica Barry']}]->(SomethingsGottaGive), (Keanu)-[:ACTED_IN {roles:['Julian Mercer']}]->(SomethingsGottaGive), (NancyM)-[:DIRECTED]->(SomethingsGottaGive), (NancyM)-[:PRODUCED]->(SomethingsGottaGive), (NancyM)-[:WROTE]->(SomethingsGottaGive) CREATE (BicentennialMan:Movie {title:'Bicentennial Man', released:1999, tagline:"One robot's 200 year journey to become an ordinary man."}) CREATE (ChrisC:Person {name:'Chris Columbus', born:1958}) CREATE (Robin)-[:ACTED_IN {roles:['Andrew Marin']}]->(BicentennialMan), (OliverP)-[:ACTED_IN {roles:['Rupert Burns']}]->(BicentennialMan), (ChrisC)-[:DIRECTED]->(BicentennialMan) CREATE (CharlieWilsonsWar:Movie {title:"Charlie Wilson's War", released:2007, tagline:"A stiff drink. A little mascara. A lot of nerve. Who said they couldn't bring down the Soviet empire."}) CREATE (JuliaR:Person {name:'Julia Roberts', born:1967}) CREATE (TomH)-[:ACTED_IN {roles:['Rep. Charlie Wilson']}]->(CharlieWilsonsWar), (JuliaR)-[:ACTED_IN {roles:['Joanne Herring']}]->(CharlieWilsonsWar), (PhilipH)-[:ACTED_IN {roles:['Gust Avrakotos']}]->(CharlieWilsonsWar), (MikeN)-[:DIRECTED]->(CharlieWilsonsWar) CREATE (ThePolarExpress:Movie {title:'The Polar Express', released:2004, tagline:'This Holiday Season… Believe'}) CREATE (TomH)-[:ACTED_IN {roles:['Hero Boy', 'Father', 'Conductor', 'Hobo', 'Scrooge', 'Santa Claus']}]->(ThePolarExpress), (RobertZ)-[:DIRECTED]->(ThePolarExpress) CREATE (ALeagueofTheirOwn:Movie {title:'A League of Their Own', released:1992, tagline:'Once in a lifetime you get a chance to do something different.'}) CREATE (Madonna:Person {name:'Madonna', born:1954}) CREATE (GeenaD:Person {name:'Geena Davis', born:1956}) CREATE (LoriP:Person {name:'Lori Petty', born:1963}) CREATE (PennyM:Person {name:'Penny Marshall', born:1943}) CREATE (TomH)-[:ACTED_IN {roles:['Jimmy Dugan']}]->(ALeagueofTheirOwn), (GeenaD)-[:ACTED_IN {roles:['Dottie Hinson']}]->(ALeagueofTheirOwn), (LoriP)-[:ACTED_IN {roles:['Kit Keller']}]->(ALeagueofTheirOwn), (RosieO)-[:ACTED_IN {roles:['Doris Murphy']}]->(ALeagueofTheirOwn), (Madonna)-[:ACTED_IN {roles:['"All the Way" Mae Mordabito']}]->(ALeagueofTheirOwn), (BillPax)-[:ACTED_IN {roles:['Bob Hinson']}]->(ALeagueofTheirOwn), (PennyM)-[:DIRECTED]->(ALeagueofTheirOwn) CREATE (PaulBlythe:Person {name:'Paul Blythe'}) CREATE (AngelaScope:Person {name:'Angela Scope'}) CREATE (JessicaThompson:Person {name:'Jessica Thompson'}) CREATE (JamesThompson:Person {name:'James Thompson'}) CREATE (JamesThompson)-[:FOLLOWS]->(JessicaThompson), (AngelaScope)-[:FOLLOWS]->(JessicaThompson), (PaulBlythe)-[:FOLLOWS]->(AngelaScope) CREATE (JessicaThompson)-[:REVIEWED {summary:'An amazing journey', rating:95}]->(CloudAtlas), (JessicaThompson)-[:REVIEWED {summary:'Silly, but fun', rating:65}]->(TheReplacements), (JamesThompson)-[:REVIEWED {summary:'The coolest football movie ever', rating:100}]->(TheReplacements), (AngelaScope)-[:REVIEWED {summary:'Pretty funny at times', rating:62}]->(TheReplacements), (JessicaThompson)-[:REVIEWED {summary:'Dark, but compelling', rating:85}]->(Unforgiven), (JessicaThompson)-[:REVIEWED {summary:"Slapstick redeemed only by the Robin Williams and Gene Hackman's stellar performances", rating:45}]->(TheBirdcage), (JessicaThompson)-[:REVIEWED {summary:'A solid romp', rating:68}]->(TheDaVinciCode), (JamesThompson)-[:REVIEWED {summary:'Fun, but a little far fetched', rating:65}]->(TheDaVinciCode), (JessicaThompson)-[:REVIEWED {summary:'You had me at Jerry', rating:92}]->(JerryMaguire) WITH TomH as a MATCH (a)-[:ACTED_IN]->(m)<-[:DIRECTED]-(d) RETURN a,m,d LIMIT 10; ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-parser/pom.xml ================================================ 4.0.0 org.neo4j neo4j-cypher-dsl-examples ${revision}${sha1}${changelist} neo4j-cypher-dsl-examples-parser Examples (Parser) Examples for the Cypher-DSL parser used in the documentation. org.neo4j.cypherdsl.examples.parser org.neo4j neo4j-cypher-dsl ${revision}${sha1}${changelist} org.neo4j neo4j-cypher-dsl-parser ${revision}${sha1}${changelist} org.assertj assertj-core test org.junit.jupiter junit-jupiter test org.apache.maven.plugins maven-checkstyle-plugin ../etc/checkstyle/suppressions.xml org.apache.maven.plugins maven-jar-plugin true org.apache.maven.plugins maven-install-plugin true org.apache.maven.plugins maven-deploy-plugin true org.apache.maven.plugins maven-failsafe-plugin integration-test verify org.apache.maven.plugins maven-javadoc-plugin true ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-parser/src/test/java/org/neo4j/cypherdsl/examples/parser/ConditionExtractingMatchFactory.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.parser; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import org.neo4j.cypherdsl.core.Comparison; import org.neo4j.cypherdsl.core.Condition; import org.neo4j.cypherdsl.core.KeyValueMapEntry; import org.neo4j.cypherdsl.core.Match; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.PropertyContainer; import org.neo4j.cypherdsl.core.Relationship; import org.neo4j.cypherdsl.core.SymbolicName; import org.neo4j.cypherdsl.core.ast.EnterResult; import org.neo4j.cypherdsl.core.ast.Visitable; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.ast.VisitorWithResult; import org.neo4j.cypherdsl.parser.MatchDefinition; abstract class ConditionExtractingMatchFactory implements Function { /** * Conditions extracted from matching properties on nodes aka {@code (n:Movie {title: * 'WHATEVER'}) }. */ protected final Map> nodeConditions = new LinkedHashMap<>(); /** * Conditions extracted from matching properties on nodes aka {@code () -[r:ACTED_IN * {as: 'WHATEVER'}] ->() }. */ protected final Map> relationshipConditions = new LinkedHashMap<>(); @Override public Match apply(MatchDefinition matchDefinition) { matchDefinition.patternElements().forEach(patternElement -> { patternElement.accept(outer -> { List extractedConditions = new ArrayList<>(); if (outer instanceof PropertyContainer container) { outer.accept(inner -> { if (inner instanceof KeyValueMapEntry kvm) { extractedConditions.add(container.property(kvm.getKey()).eq(kvm.getValue())); } }); } if (outer instanceof Node node && node.getSymbolicName().isPresent()) { this.nodeConditions.computeIfAbsent(node, key -> new ArrayList<>()).addAll(extractedConditions); } else if (outer instanceof Relationship relationship && relationship.getSymbolicName().isPresent()) { this.relationshipConditions.computeIfAbsent(relationship, key -> new ArrayList<>()) .addAll(extractedConditions); } }); }); Optional.ofNullable(matchDefinition.optionalWhere()).ifPresent(e -> e.accept(extractPropertyComparisons())); return apply0(matchDefinition); } abstract Match apply0(MatchDefinition matchDefinition); private Visitor extractPropertyComparisons() { return segment -> { if (segment instanceof Comparison c) { c.accept(inner -> { if (inner instanceof Property p) { var reference = new AtomicReference(); p.accept(new VisitorWithResult() { @Override public EnterResult enterWithResult(Visitable propertyContent) { if (propertyContent instanceof SymbolicName symbolicName && reference.compareAndSet(null, symbolicName)) { return EnterResult.SKIP_CHILDREN; } return EnterResult.CONTINUE; } }); if (reference.get() != null) { this.nodeConditions.forEach((k, v) -> { if (k.getSymbolicName().filter(reference.get()::equals).isPresent()) { v.add(c); } }); } } }); } }; } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-parser/src/test/java/org/neo4j/cypherdsl/examples/parser/CypherDSLParserExamplesTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.parser; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.UnaryOperator; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.AliasedExpression; import org.neo4j.cypherdsl.core.Clauses; import org.neo4j.cypherdsl.core.Condition; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.KeyValueMapEntry; import org.neo4j.cypherdsl.core.Match; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Operation; import org.neo4j.cypherdsl.core.PatternElement; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.Relationship; import org.neo4j.cypherdsl.core.Return; import org.neo4j.cypherdsl.core.StringLiteral; import org.neo4j.cypherdsl.core.SymbolicName; import org.neo4j.cypherdsl.core.Where; import org.neo4j.cypherdsl.core.ast.Visitor; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.GeneralizedRenderer; import org.neo4j.cypherdsl.core.renderer.Renderer; import org.neo4j.cypherdsl.parser.CypherParser; import org.neo4j.cypherdsl.parser.ExpressionCreatedEventType; import org.neo4j.cypherdsl.parser.MatchDefinition; import org.neo4j.cypherdsl.parser.Options; import org.neo4j.cypherdsl.parser.PatternElementCreatedEventType; import org.neo4j.cypherdsl.parser.ReturnDefinition; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatNoException; /** * @author Michael J. Simons */ class CypherDSLParserExamplesTests { @Test void createASubqueryCallWithUserProvidedCypher() { // tag::example-using-input[] var userProvidedCypher = "MATCH (this)-[:LINK]-(o:Other) RETURN o as result"; // <.> var userStatement = CypherParser.parse(userProvidedCypher); // <.> var node = Cypher.node("Node").named("node"); var result = Cypher.name("result"); var cypher = Cypher // <.> .match(node) .call(// <.> userStatement, node.as("this")) .returning(result.project("foo", "bar")) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (node:`Node`) " + "CALL (node) {" + "WITH node AS this " // <.> + "MATCH (this)-[:`LINK`]-(o:`Other`) RETURN o AS result" // <.> + "} " + "RETURN result{.foo, .bar}"); // end::example-using-input[] } @Test void extractLabels() { var cypher = "OPTIONAL MATCH (m) SET m:Foo REMOVE m:Bar WITH m CREATE (m:Person) SET n.name = 'John' RETURN n, m"; class LabelCollector implements Function { List labelsSeen = new ArrayList<>(); @Override public Operation apply(Expression expression) { Operation op = (Operation) expression; op.accept(segment -> { if (segment instanceof NodeLabel) { this.labelsSeen.add(((NodeLabel) segment).getValue()); } }); return op; } } var labelsAdded = new LabelCollector(); var labelsRemoved = new LabelCollector(); var options = Options.newOptions() .withCallback(ExpressionCreatedEventType.ON_SET_LABELS, Operation.class, labelsAdded) .withCallback(ExpressionCreatedEventType.ON_REMOVE_LABELS, Operation.class, labelsRemoved) .build(); assertThatNoException().isThrownBy(() -> CypherParser.parse(cypher, options)); assertThat(labelsAdded.labelsSeen).containsExactly("Foo"); assertThat(labelsRemoved.labelsSeen).containsExactly("Bar"); } @Test // GH-299 void extractLabelsWithFilters() { var cypher = "OPTIONAL MATCH (m:Bazbar) SET m:Foo REMOVE m:Bar WITH m CREATE (m:Person) SET n.name = 'John' RETURN n, m"; var labelsSeen = new HashSet(); var options = Options.newOptions().withLabelFilter((labelParsedEventType, strings) -> { // The event type could be used to distinguish the call site at which labels // where needed switch (labelParsedEventType) { case ON_REMOVE: break; case ON_SET: break; case ON_NODE_PATTERN: break; } labelsSeen.addAll(strings); return strings; }).build(); assertThatNoException().isThrownBy(() -> CypherParser.parse(cypher, options)); assertThat(labelsSeen).containsExactlyInAnyOrder("Bazbar", "Foo", "Bar", "Person"); } @Test // GH-299 void extractLabelsInMatchOrCreate() { var cypher = "OPTIONAL MATCH (m:Bazbar) SET m:Foo REMOVE m:Bar WITH m CREATE (m:Person) SET n.name = 'John' RETURN n, m"; class LabelCollector implements UnaryOperator { final Set labelsSeen = new HashSet<>(); @Override public PatternElement apply(PatternElement patternElement) { if (patternElement instanceof Node) { ((Node) patternElement).getLabels().forEach(l -> this.labelsSeen.add(l.getValue())); } return patternElement; } } var labelsOnNodesCreated = new LabelCollector(); var labelsOnNodesMatched = new LabelCollector(); var options = Options.newOptions() .withCallback(PatternElementCreatedEventType.ON_CREATE, labelsOnNodesCreated) .withCallback(PatternElementCreatedEventType.ON_MATCH, labelsOnNodesMatched) .build(); assertThatNoException().isThrownBy(() -> CypherParser.parse(cypher, options)); assertThat(labelsOnNodesCreated.labelsSeen).containsExactly("Person"); assertThat(labelsOnNodesMatched.labelsSeen).containsExactly("Bazbar"); } @Test void rewriteQuery() { var cypher = "MATCH (n:Phone)-[:CALLED]->(o:Phone) RETURN *"; class LabelCollector implements UnaryOperator { @Override public PatternElement apply(PatternElement patternElement) { if (patternElement instanceof Relationship) { Relationship relationship = (Relationship) patternElement; if (relationship.getDetails().getTypes().contains("CALLED")) { var call = Cypher.node("Call").named("c"); var left = relationship.getLeft(); var right = relationship.getRight(); return left.relationshipTo(call, "CALL").relationshipFrom(right, "CALL"); } } return patternElement; } } var labelsOnNodesCreated = new LabelCollector(); var labelsOnNodesMatched = new LabelCollector(); var options = Options.newOptions() .withCallback(PatternElementCreatedEventType.ON_CREATE, labelsOnNodesCreated) .withCallback(PatternElementCreatedEventType.ON_MATCH, labelsOnNodesMatched) .build(); var statement = CypherParser.parse(cypher, options); var rewritten = Renderer.getRenderer(Configuration.prettyPrinting()).render(statement); assertThat(rewritten).isEqualTo("MATCH (n:Phone)-[:CALL]->(c:Call)<-[:CALL]-(o:Phone)\n" + "RETURN *"); } @Test // GH-299 void allLabelsSeen() { var cypher = "OPTIONAL MATCH (m:Bazbar) SET m:Foo REMOVE m:Bar WITH m CREATE (m:Person) SET n.name = 'John' RETURN n, m"; Set labelsSeen = new HashSet<>(); class LabelsInsideExpressions implements Function { @Override public Operation apply(Expression expression) { Operation op = (Operation) expression; op.accept(segment -> { if (segment instanceof NodeLabel) { labelsSeen.add(((NodeLabel) segment).getValue()); } }); return op; } } class LabelsInsidePatterns implements UnaryOperator { @Override public PatternElement apply(PatternElement patternElement) { if (patternElement instanceof Node) { ((Node) patternElement).getLabels().forEach(l -> labelsSeen.add(l.getValue())); } return patternElement; } } var labelsInsideExpressions = new LabelsInsideExpressions(); var labelsInsidePatterns = new LabelsInsidePatterns(); var options = Options.newOptions() .withCallback(ExpressionCreatedEventType.ON_SET_LABELS, Operation.class, labelsInsideExpressions) .withCallback(ExpressionCreatedEventType.ON_REMOVE_LABELS, Operation.class, labelsInsideExpressions) .withCallback(PatternElementCreatedEventType.ON_CREATE, labelsInsidePatterns) .withCallback(PatternElementCreatedEventType.ON_MATCH, labelsInsidePatterns) .build(); assertThatNoException().isThrownBy(() -> CypherParser.parse(cypher, options)); assertThat(labelsSeen).containsExactlyInAnyOrder("Foo", "Bar", "Bazbar", "Person"); } @Test // GH-302 void trackChangedPropertiesToNodes() { var nodes = new HashMap(); UnaryOperator nodesCollector = patternElement -> { patternElement.accept(segment -> { if (segment instanceof Node) { Node node = (Node) segment; node.getSymbolicName().map(SymbolicName::getValue).ifPresent(n -> nodes.put(n, node)); } }); return patternElement; }; class PropertyRecord { String reference; String name; } var removedProperties = new HashSet(); Function removedPropertiesCollector = expression -> { var property = (Property) expression; property.accept(segment -> { if (segment instanceof SymbolicName) { var value = ((SymbolicName) segment).getValue(); if (nodes.containsKey(value)) { var propertyRecord = new PropertyRecord(); propertyRecord.reference = ((SymbolicName) segment).getValue(); propertyRecord.name = property.getName(); removedProperties.add(propertyRecord); } } }); return expression; }; var setProperties = new HashSet(); Function setPropertiesCollector = expression -> { var op = (Operation) expression; var property = new AtomicReference(); var name = new AtomicReference(); op.accept(segment -> { if (segment instanceof SymbolicName) { // First symbolic name will be the property reference var value = ((SymbolicName) segment).getValue(); if (!property.compareAndSet(null, value)) { // Second will be the name name.compareAndSet(null, value); } } }); if (nodes.containsKey(property.get())) { var propertyRecord = new PropertyRecord(); propertyRecord.reference = property.get(); propertyRecord.name = name.get(); setProperties.add(propertyRecord); } return op; }; var options = Options.newOptions() .withCallback(PatternElementCreatedEventType.ON_CREATE, nodesCollector) .withCallback(PatternElementCreatedEventType.ON_MATCH, nodesCollector) .withCallback(ExpressionCreatedEventType.ON_REMOVE_PROPERTY, Expression.class, removedPropertiesCollector) .withCallback(ExpressionCreatedEventType.ON_SET_PROPERTY, Operation.class, setPropertiesCollector) .build(); CypherParser.parse("MATCH (c:Person {uuid: $uuid})-[:WORKS_AT]->(p:Company) REMOVE c.name SET c.thing = 1", options); assertThat(removedProperties).hasSize(1).first().satisfies(p -> { assertThat(p.name).isEqualTo("name"); assertThat(p.reference).isEqualTo("c"); }); assertThat(setProperties).hasSize(1).first().satisfies(p -> { assertThat(p.name).isEqualTo("thing"); assertThat(p.reference).isEqualTo("c"); }); assertThat(nodes).containsKey("c").hasEntrySatisfying("c", n -> { var selectingProperty = new AtomicReference(); Visitor propExtractor = segment -> { if (segment instanceof KeyValueMapEntry) { var entry = (KeyValueMapEntry) segment; selectingProperty.compareAndSet(null, entry.getKey()); } }; n.accept(propExtractor); assertThat(selectingProperty).hasValue("uuid"); }); } @Test void ensureAReturnAlias() { // tag::example-required-alias[] var userProvidedCypher = "MATCH (this)-[:LINK]-(o:Other) RETURN o"; Function ensureAlias = r -> { if (!(r instanceof AliasedExpression)) { return r.as("result"); } return (AliasedExpression) r; }; // <.> var options = Options.newOptions() // <.> .withCallback( // <.> ExpressionCreatedEventType.ON_RETURN_ITEM, AliasedExpression.class, ensureAlias) .build(); var userStatement = CypherParser.parse(userProvidedCypher, options); // <.> // end::example-required-alias[] var node = Cypher.node("Node").named("node"); var result = Cypher.name("result"); var cypher = Cypher.match(node) .call(userStatement, node.as("this")) // <.> .returning(result.project("foo", "bar")) .build() .getCypher(); assertThat(cypher).isEqualTo( "MATCH (node:`Node`) CALL (node) {WITH node AS this MATCH (this)-[:`LINK`]-(o:`Other`) RETURN o AS result} RETURN result{.foo, .bar}"); } @Test void preventPropertyDeletion() { // tag::example-preventing-things[] var userProvidedCypher = "MATCH (this)-[:LINK]-(o:Other) REMOVE this.something RETURN o"; UnaryOperator preventPropertyDeletion = r -> { throw new RuntimeException("Not allowed to remove properties!"); // <.> }; var options = Options.newOptions() .withCallback( // <.> ExpressionCreatedEventType.ON_REMOVE_PROPERTY, Expression.class, preventPropertyDeletion) .build(); assertThatExceptionOfType(RuntimeException.class) .isThrownBy(() -> CypherParser.parse(userProvidedCypher, options)); // <.> // end::example-preventing-things[] } @Test void preventDeleteClause() { var userProvidedCypher = "MATCH (this)-[:LINK]-(o:Other) DELETE this, o"; UnaryOperator preventDelete = r -> { throw new RuntimeException("Not allowed to delete things!"); }; var options = Options.newOptions() .withCallback( // <.> ExpressionCreatedEventType.ON_DELETE_ITEM, Expression.class, preventDelete) .build(); assertThatExceptionOfType(RuntimeException.class) .isThrownBy(() -> CypherParser.parse(userProvidedCypher, options)) .havingCause() .withMessage("Not allowed to delete things!"); } @Test void modifyReturnClause() { // tag::example-shape-the-return-clause[] var userProvidedCypher = "MATCH (this)-[:LINK]-(o:Other) RETURN distinct this, o LIMIT 23"; Function returnClauseFactory = d -> { // <.> var finalExpressionsReturned = d.getExpressions() .stream() .filter(e -> e instanceof SymbolicName && "o".equals(((SymbolicName) e).getValue())) .map(e -> e.as("result")) .collect(Collectors.toList()); return Clauses.returning(false, finalExpressionsReturned, List.of(Cypher.name("o").property("x").descending()), d.getOptionalSkip(), d.getOptionalLimit()); }; var options = Options.newOptions() .withReturnClauseFactory(returnClauseFactory) // <.> .build(); var userStatement = CypherParser.parse(userProvidedCypher, options); var cypher = userStatement.getCypher(); assertThat(cypher) // <.> .isEqualTo("MATCH (this)-[:`LINK`]-(o:`Other`) RETURN o AS result ORDER BY o.x DESC LIMIT 23"); // end::example-shape-the-return-clause[] } @Test // GH-574 void collectingPropertyReferencesShouldWork() { var query = "MATCH (m:Movie {title: 'The Matrix'}) <-[r:ACTED_IN] - (p:Person {born: 1964}) WHERE m.releaseYear IS NOT NULL AND p.name = 'Keanu Reeves' RETURN m"; var collectingPropertyReferences = new ConditionExtractingMatchFactory() { @Override Match apply0(MatchDefinition matchDefinition) { var newConditions = Cypher.noCondition(); for (Condition value : this.nodeConditions.values().stream().flatMap(Collection::stream).toList()) { newConditions = newConditions.and(value); } return (Match) Clauses.match(matchDefinition.optional(), List .of(Cypher.node("Movie").named("m").relationshipFrom(Cypher.node("Person").named("p"), "ACTED_IN")), Where.from(newConditions), matchDefinition.optionalHints()); } }; var options = Options.newOptions().withMatchClauseFactory(collectingPropertyReferences).build(); var cypher = CypherParser.parse(query, options).getCypher(); assertThat(cypher).isEqualTo( "MATCH (m:`Movie`)<-[:`ACTED_IN`]-(p:`Person`) WHERE (m.title = 'The Matrix' AND m.releaseYear IS NOT NULL AND p.born = 1964 AND p.name = 'Keanu Reeves') RETURN m"); } @Test // GH-739 void rewritingStringLiterals() { var query = "MATCH (m:Movie {title: 'The Matrix'}) <-[r:ACTED_IN] - (p:Person {born: 1964}) WHERE m.releaseYear IS NOT NULL AND p.name = 'Keanu Reeves' RETURN m"; var options = Options.newOptions() .withCallback(ExpressionCreatedEventType.ON_NEW_LITERAL, Expression.class, l -> { if (l instanceof StringLiteral sl) { return Cypher.anonParameter(sl.getContent()); } return l; }) .build(); var stmt = CypherParser.parse(query, options); assertThat(stmt.getCypher()).isEqualTo( "MATCH (m:`Movie` {title: $pcdsl01})<-[r:`ACTED_IN`]-(p:`Person` {born: 1964}) WHERE (m.releaseYear IS NOT NULL AND p.name = $pcdsl02) RETURN m"); assertThat(stmt.getCatalog().getParameters()).containsValues("The Matrix", "Keanu Reeves"); } @Test // GH-1060 void buildingWhereClauseShouldWork() { var node = Cypher.node("Product").named("Product"); var where = Where.from(node.property("product_id").eq(Cypher.literalOf("BG2"))); assertThat(where).hasToString("Where{cypher=WHERE Product.product_id = 'BG2'}"); var cypher = Renderer.getRenderer(Configuration.defaultConfig(), GeneralizedRenderer.class).render(where); assertThat(cypher).isEqualTo("WHERE Product.product_id = 'BG2'"); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-parser/src/test/java/org/neo4j/cypherdsl/examples/parser/StatementCatalogBuildingVisitorViaParserTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.parser; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.Literal; import org.neo4j.cypherdsl.core.StatementCatalog; import org.neo4j.cypherdsl.parser.CypherParser; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class StatementCatalogBuildingVisitorViaParserTests { @Test void simpleShowCase() { // tag::catalog-example[] var input = """ MATCH (m:`Movie` {title: 'The Matrix'})<-[a:`ACTED_IN`]-(p:`Person`) WHERE p.born >= $born RETURN p """; var statement = CypherParser.parse(input); var catalog = statement.getCatalog(); assertThat(catalog.getNodeLabels()).extracting(StatementCatalog.Token::value) .containsExactlyInAnyOrder("Person", "Movie"); assertThat(catalog.getProperties()).containsExactlyInAnyOrder( StatementCatalog.property(Set.of(StatementCatalog.label("Movie")), "title"), StatementCatalog.property(Set.of(StatementCatalog.label("Person")), "born")); // end::catalog-example[] var cypher = statement.getCypher(); assertThat(cypher).isEqualTo( "MATCH (m:`Movie` {title: 'The Matrix'})<-[a:`ACTED_IN`]-(p:`Person`) WHERE p.born >= $born RETURN p"); } @Test void mapOfLabelsAndProperties() { var input = """ MATCH (m:`Movie` {title: 'The Matrix'})<-[a:`ACTED_IN`]-(p:`Person`|Actor {b:true}) WHERE p.born >= $born AND a.starring = true RETURN p """; var statement = CypherParser.parse(input); var catalog = statement.getCatalog(); assertThat(catalog.getProperties()).anyMatch( p -> p.owningToken().equals(Set.of(StatementCatalog.label("Person"), StatementCatalog.label("Actor")))); Map> labelsAndProperties = catalog.getProperties() .stream() .filter(p -> p.owningToken().stream().allMatch(t -> t.type() == StatementCatalog.Token.Type.NODE_LABEL)) .collect(Collectors.toMap( p -> p.owningToken().stream().map(StatementCatalog.Token::value).collect(Collectors.joining(",")), p -> List.of(p.name()), (l1, l2) -> { var mergedList = new ArrayList<>(l1); mergedList.addAll(l2); Collections.sort(mergedList); return mergedList; })); assertThat(labelsAndProperties).containsExactly(Map.entry("Movie", List.of("title")), Map.entry("Actor,Person", List.of("b", "born"))); } @Test // GH-674 void retrievalOfRelationshipsAndTargetSourcesShouldWork() { var input = """ MATCH (m:`Movie` {title: 'The Matrix'})<-[a:`ACTED_IN`]-(p:`Person`|Actor {b:true}) MATCH () -[:WHATEVER]-> (m) MATCH (x:X) -[:UNDIRECTED]- (y:Y) MATCH (m) -[:UNDIRECTED]- (y) WHERE p.born >= $born AND a.starring = true WITH m MATCH (m) -[:FOO]->(f:FooNode) CALL { MATCH (:LabelA) -[:A_REL]-> (n:X) } RETURN p """; var statement = CypherParser.parse(input); var catalog = statement.getCatalog(); assertThat(catalog.getOutgoingRelations(StatementCatalog.label("Person"))) .containsExactlyInAnyOrder(StatementCatalog.type("ACTED_IN")); assertThat(catalog.getOutgoingRelations(StatementCatalog.label("Movie"))) .containsExactlyInAnyOrder(StatementCatalog.type("FOO")); assertThat(catalog.getOutgoingRelations(StatementCatalog.label("Actor"))) .containsExactlyInAnyOrder(StatementCatalog.type("ACTED_IN")); assertThat(catalog.getOutgoingRelations(StatementCatalog.label("LabelA"))) .containsExactlyInAnyOrder(StatementCatalog.type("A_REL")); assertThat(catalog.getIncomingRelations(StatementCatalog.label("Movie"))) .containsExactlyInAnyOrder(StatementCatalog.type("ACTED_IN"), StatementCatalog.type("WHATEVER")); assertThat(catalog.getIncomingRelations(StatementCatalog.label("FooNode"))) .containsExactlyInAnyOrder(StatementCatalog.type("FOO")); assertThat(catalog.getIncomingRelations(StatementCatalog.label("X"))) .containsExactlyInAnyOrder(StatementCatalog.type("A_REL")); assertThat(catalog.getUndirectedRelations(StatementCatalog.label("X"))) .containsExactlyInAnyOrder(StatementCatalog.type("UNDIRECTED")); assertThat(catalog.getTargetNodes(StatementCatalog.type("ACTED_IN"))) .containsExactlyInAnyOrder(StatementCatalog.label("Movie")); assertThat(catalog.getTargetNodes(StatementCatalog.type("FOO"))) .containsExactlyInAnyOrder(StatementCatalog.label("FooNode")); assertThat(catalog.getTargetNodes(StatementCatalog.type("A_REL"))) .containsExactlyInAnyOrder(StatementCatalog.label("X")); assertThat(catalog.getTargetNodes(StatementCatalog.type("WHATEVER"))) .containsExactlyInAnyOrder(StatementCatalog.label("Movie")); assertThat(catalog.getTargetNodes(StatementCatalog.type("UNDIRECTED"))).isEmpty(); assertThat(catalog.getSourceNodes(StatementCatalog.type("ACTED_IN"))) .containsExactlyInAnyOrder(StatementCatalog.label("Person"), StatementCatalog.label("Actor")); assertThat(catalog.getSourceNodes(StatementCatalog.type("FOO"))) .containsExactlyInAnyOrder(StatementCatalog.label("Movie")); assertThat(catalog.getSourceNodes(StatementCatalog.type("A_REL"))) .containsExactlyInAnyOrder(StatementCatalog.label("LabelA")); assertThat(catalog.getSourceNodes(StatementCatalog.type("WHATEVER"))).isEmpty(); assertThat(catalog.getSourceNodes(StatementCatalog.type("UNDIRECTED"))).isEmpty(); } @Test // GH-738 void literalsOnParsedStatement() { var stmt = CypherParser.parse( "LOAD CSV FROM 'https://test.com/test.csv' AS x WITH x MERGE (n {x: x}) ON CREATE SET x.y = NULL, x.a = 'Hallo' ON CREATE SET x.b = [true][1..2] RETURN x"); assertThat(stmt.getCatalog().getLiterals()).map(Literal::asString) .containsExactlyInAnyOrder("1", "2", "NULL", "true", "'Hallo'"); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/pom.xml ================================================ 4.0.0 org.springframework.boot spring-boot-starter-parent 4.0.6 org.neo4j neo4j-cypher-dsl-examples-sdn6 ${revision}${sha1}${changelist} Examples (SDN 6 Code generator) Example how to use the SDN 6 code generator. 13.4.2 org.neo4j.cypherdsl.examples.sdn6 17 5.0.0 3.6.0 4.0.0 0.0.47 org.neo4j neo4j-cypher-dsl-bom ${project.version} pom import org.neo4j neo4j-cypher-dsl org.neo4j neo4j-cypher-dsl-parser org.springframework.boot spring-boot-resttestclient test org.springframework.boot spring-boot-starter-data-neo4j org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-testcontainers test org.testcontainers testcontainers-junit-jupiter test org.testcontainers testcontainers-neo4j test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin ${maven-compiler-plugin.version} org.neo4j neo4j-cypher-dsl-codegen-sdn6 ${project.version} com.mycila license-maven-plugin ${license-maven-plugin.version} true SCRIPT_STYLE 2026
etc/license.tpl
** **/.flattened-pom.xml **/*.cypher
true One or more dependencies are licensed under a non-approved license. LICENSE_URL APPROVE https://www.apache.org/licenses/LICENSE-2.0 LICENSE_NAME APPROVE The Apache Software License, Version 2.0 ARTIFACT_PATTERN APPROVE org.neo4j:neo4j-cypher-dsl-parser*
validate check validate
com.github.ekryd.sortpom sortpom-maven-plugin ${sortpom-maven-plugin.version} ${project.build.sourceEncoding} true -1 true groupId,artifactId false org.apache.maven.plugins maven-failsafe-plugin org.apache.maven.plugins maven-jar-plugin true org.apache.maven.plugins maven-install-plugin true org.apache.maven.plugins maven-deploy-plugin true
with_checkstyle [21,) io.spring.javaformat spring-javaformat-maven-plugin ${spring-javaformat.version} validate validate true org.apache.maven.plugins maven-checkstyle-plugin ${maven-checkstyle-plugin.version} **/module-info.java true ../../etc/checkstyle/config.xml ../../etc/checkstyle/suppressions.xml ${project.build.sourceEncoding} true true true com.puppycrawl.tools checkstyle ${checkstyle.version} io.spring.javaformat spring-javaformat-checkstyle ${spring-javaformat.version} validate check validate revisionMissing !revision 9999 sha1Missing !sha changelistMissing !changelist -SNAPSHOT fast fast true true true true true true true
================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/Application.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Main entry for a Spring application. * * @author Michael J. Simons */ @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/books/Book.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.books; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * Example type. * * @author Michael J. Simons */ @Node public class Book { @Id @GeneratedValue String id; private String lang; } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/books/BookGenre.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.books; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * Example type. * * @author Michael J. Simons */ @Node public class BookGenre { @Id @GeneratedValue String id; } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/books/UserDetails.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.books; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * Example type. * * @author Michael J. Simons */ @Node public class UserDetails { @Id @GeneratedValue String id; } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/books/UserPreferences.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.books; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * Example type. * * @author Michael J. Simons */ @Node public class UserPreferences { @Id @GeneratedValue String id; String userId; } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/books/UserSuggestionActivity.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.books; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * Example type. * * @author Michael J. Simons */ @Node public class UserSuggestionActivity { @Id @GeneratedValue String id; String userId; } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/books/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * This package exists only to reproduce an issue, it is not a complete example. */ package org.neo4j.cypherdsl.examples.sdn6.books; ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/misc/Example.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.misc; import org.springframework.data.annotation.PersistenceCreator; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Relationship; /** * Example type. * * @author Michael J. Simons */ @Node public class Example { @Id private final long id; @Relationship(type = "BELONGS_TO") private Example parent; @PersistenceCreator public Example(long id) { this.id = id; } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/misc/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * More SDN examples. */ package org.neo4j.cypherdsl.examples.sdn6.misc; ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/Actor.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.movies; import java.util.Collections; import java.util.List; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.RelationshipProperties; import org.springframework.data.neo4j.core.schema.TargetNode; /** * Demo class. * * @author Michael J. Simons */ @RelationshipProperties public final class Actor { @TargetNode @JsonIgnore private final Person person; private final List roles; @Id @GeneratedValue Long id; public Actor(Person person, List roles) { this.person = person; this.roles = roles; } public Person getPerson() { return this.person; } @JsonProperty public String getName() { return this.person.getName(); } public List getRoles() { return Collections.unmodifiableList(this.roles); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/Genre.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.movies; import java.util.UUID; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * Example type. * * @author Michael J. Simons */ @Node public final class Genre { @Id @GeneratedValue private final UUID id; private final String name; public Genre(UUID id, String name) { this.id = id; this.name = name; } public UUID getId() { return this.id; } public String getName() { return this.name; } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/GenreRepository.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.movies; import java.util.UUID; import org.springframework.data.neo4j.repository.Neo4jRepository; import org.springframework.data.neo4j.repository.support.CypherdslStatementExecutor; /** * Example type. * * @author Michael J. Simons */ public interface GenreRepository extends Neo4jRepository, CypherdslStatementExecutor { } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/Movie.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.movies; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.data.annotation.PersistenceCreator; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Property; import org.springframework.data.neo4j.core.schema.Relationship; import org.springframework.data.neo4j.core.schema.Relationship.Direction; /** * Example type. * * @author Michael J. Simons */ @Node public final class Movie { @Id private final String title; @Property("tagline") private final String description; @Relationship(value = "ACTED_IN", direction = Direction.INCOMING) private final List actors; @Relationship(value = "DIRECTED", direction = Direction.INCOMING) private final List directors; private Integer released; public Movie(String title, String description) { this.title = title; this.description = description; this.actors = new ArrayList<>(); this.directors = new ArrayList<>(); } @JsonCreator @PersistenceCreator public Movie(@JsonProperty("title") String title, @JsonProperty("description") String description, @JsonProperty("actors") List actors, @JsonProperty("directors") List directors) { this.title = title; this.description = description; this.actors = (actors != null) ? new ArrayList<>(actors) : List.of(); this.directors = (directors != null) ? new ArrayList<>(directors) : List.of(); } public String getTitle() { return this.title; } public String getDescription() { return this.description; } public List getActors() { return Collections.unmodifiableList(this.actors); } public List getDirectors() { return Collections.unmodifiableList(this.directors); } public Integer getReleased() { return this.released; } public void setReleased(Integer released) { this.released = released; } public Movie addActors(Collection newActors) { this.actors.addAll(newActors); return this; } public Movie addDirectors(Collection newDirectors) { this.directors.addAll(newDirectors); return this; } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/MovieRepository.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.movies; import org.springframework.data.neo4j.repository.Neo4jRepository; import org.springframework.data.neo4j.repository.support.CypherdslStatementExecutor; /** * Example type. * * @author Michael J. Simons */ interface MovieRepository extends Neo4jRepository, CypherdslStatementExecutor { } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/MovieService.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.movies; // tag::as-property[] import java.util.Collection; import java.util.List; import org.neo4j.cypherdsl.core.Cypher; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; // end::as-property[] /** * Example service. * * @author Michael J. Simons */ // tag::as-property[] @Service final class MovieService { private final MovieRepository movieRepository; MovieService(MovieRepository movieRepository) { this.movieRepository = movieRepository; } List findAll() { return this.movieRepository.findAll(Sort.by(Movie_.MOVIE.TITLE.getName()).ascending()); // <.> } // end::as-property[] // tag::more-examples[] Collection findAllRelatedTo(Person person) { var p = Person_.PERSON.named("p"); var a = Movie_.MOVIE.named("a"); var d = Movie_.MOVIE.named("d"); var m = Cypher.name("m"); var statement = Cypher.match(p) .where(p.NAME.isEqualTo(Cypher.anonParameter(person.getName()))) // <.> .with(p) .optionalMatch(new ActedIn_(p, a)) .optionalMatch(new Directed_(p, d)) .with(Cypher.collect(a).add(Cypher.collect(d)).as("movies")) .unwind("movies") .as(m) .returningDistinct(m) .orderBy(Movie_.MOVIE.named(m).TITLE) .ascending() .build(); return this.movieRepository.findAll(statement); } // end::more-examples[] // tag::as-property[] } // end::as-property[] ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/MoviesController.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.movies; import java.util.List; import java.util.stream.Collectors; import org.springframework.data.domain.Example; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * Example controller. * * @author Michael J. Simons */ @RestController @RequestMapping("/api/movies") public final class MoviesController { private final PeopleService peopleService; private final MovieService movieService; public MoviesController(PeopleService peopleService, MovieService movieService) { this.peopleService = peopleService; this.movieService = movieService; } @GetMapping({ "", "/" }) public List get() { return this.movieService.findAll(); } @GetMapping({ "/relatedTo/{name}" }) public List relatedTo(@PathVariable String name) { return this.peopleService.findOne(Example.of(new Person(name, null))) .stream() .flatMap(p -> this.movieService.findAllRelatedTo(p).stream()) .collect(Collectors.toList()); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/NewPersonCmd.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.movies; import java.time.ZonedDateTime; /** * DTO for creating new people. * * @author Michael J. Simons */ public final class NewPersonCmd { private final String name; private final ZonedDateTime dob; public NewPersonCmd(String name, ZonedDateTime dob) { this.name = name; this.dob = dob; } public String getName() { return this.name; } public ZonedDateTime getDob() { return this.dob; } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/PeopleController.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.movies; import java.util.Optional; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; /** * Example controller. * * @author Michael J. Simons */ @RestController @RequestMapping("/api/people") public class PeopleController { private final PeopleService peopleService; public PeopleController(PeopleService peopleService) { this.peopleService = peopleService; } @GetMapping("/details/{name}") public PersonDetails getDetails(@PathVariable String name) { return this.peopleService.findDetails(name) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); } @GetMapping("/findPeopleBornInThe70tiesOr") public Iterable findPeopleBornInThe70tiesOr(@RequestParam(name = "name") Optional optionalName) { return this.peopleService.findPeopleBornInThe70tiesOr(optionalName); } @GetMapping("/v1/findPeopleBornAfterThe70ties") public Iterable findPeopleBornAfterThe70ties( @RequestParam(name = "conditions") String additionalConditions) { return this.peopleService.findPeopleBornAfterThe70tiesAnd(additionalConditions); } @GetMapping("/v2/findPeopleBornAfterThe70ties") public Iterable findPeopleBornAfterThe70tiesV2( @RequestParam(name = "conditions") String additionalConditions) { return this.peopleService.findPeopleBornAfterThe70tiesAndV2(additionalConditions); } @PostMapping("/createNewPerson") public Person createNewPerson(@RequestBody NewPersonCmd newPersonCmd) { return this.peopleService.createNewPerson(newPersonCmd) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/PeopleRepository.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.movies; // tag::additional-fragments[] import org.springframework.data.neo4j.repository.Neo4jRepository; import org.springframework.data.neo4j.repository.support.CypherdslConditionExecutor; import org.springframework.data.neo4j.repository.support.CypherdslStatementExecutor; // end::additional-fragments[] /** * Example repository. * * @author Michael J. Simons */ // tag::additional-fragments[] public interface PeopleRepository extends Neo4jRepository, CypherdslConditionExecutor, // <.> CypherdslStatementExecutor { // <.> } // end::additional-fragments[] ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/PeopleService.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.movies; // tag::using-person-repo[] import java.util.Optional; import java.util.function.Function; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.SymbolicName; import org.neo4j.cypherdsl.parser.CypherParser; import org.neo4j.cypherdsl.parser.ExpressionCreatedEventType; import org.neo4j.cypherdsl.parser.Options; import org.springframework.data.domain.Example; import org.springframework.data.neo4j.core.mapping.Constants; import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; import org.springframework.stereotype.Service; // end::using-person-repo[] /** * Example service. * * @author Michael J. Simons */ // tag::using-person-repo[] @Service final class PeopleService { private final Person_ person; private final SymbolicName personRootName; private final PeopleRepository peopleRepository; PeopleService(PeopleRepository peopleRepository, Neo4jMappingContext mappingContext) { this.peopleRepository = peopleRepository; this.personRootName = Constants.NAME_OF_TYPED_ROOT_NODE .apply(mappingContext.getRequiredPersistentEntity(Person.class)); this.person = Person_.PERSON.named(this.personRootName); } // end::using-person-repo[] Optional findOne(Example example) { return this.peopleRepository.findOne(example); } // tag::using-parser-with-spring[] Iterable findPeopleBornAfterThe70tiesAnd(String additionalConditions) { return this.peopleRepository.findAll(this.person.BORN.gte(Cypher.literalOf(1980)) .and(CypherParser.parseExpression(additionalConditions).asCondition()) // <.> ); } // end::using-parser-with-spring[] // tag::using-parser-with-spring2[] Iterable findPeopleBornAfterThe70tiesAndV2(String additionalConditions) { Function enforceReference = e -> this.personRootName .property(((SymbolicName) e).getValue()); // <.> var parserOptions = Options.newOptions() .withCallback(ExpressionCreatedEventType.ON_NEW_VARIABLE, Expression.class, enforceReference) // <.> .build(); return this.peopleRepository.findAll(this.person.BORN.gte(Cypher.literalOf(1980)) .and(CypherParser.parseExpression(additionalConditions, parserOptions // <.> ).asCondition())); } // end::using-parser-with-spring2[] // tag::using-person-repo[] Iterable findPeopleBornInThe70tiesOr(Optional optionalName) { return this.peopleRepository.findAll(this.person.BORN.gte(Cypher.literalOf(1970)) .and(this.person.BORN.lt(Cypher.literalOf(1980))) // <.> .or(optionalName.map(name -> this.person.NAME.isEqualTo(Cypher.anonParameter(name))) // <.> .orElseGet(Cypher::noCondition)) // <.> ); } Optional findDetails(String name) { var d = Movie_.MOVIE.named("d"); var a = Movie_.MOVIE.named("a"); var m = Movie_.MOVIE.named("movies"); var r = Cypher.anyNode("relatedPerson"); var statement = Cypher.match(Person_.PERSON.withProperties("name", Cypher.anonParameter(name))) .optionalMatch(d.DIRECTORS) .optionalMatch(a.ACTORS) .optionalMatch(Person_.PERSON.relationshipTo(m).relationshipFrom(r, ActedIn_.$TYPE)) .returningDistinct(Person_.PERSON.getRequiredSymbolicName(), Cypher.collectDistinct(d).as("directed"), Cypher.collectDistinct(a).as("actedIn"), Cypher.collectDistinct(r).as("related")) .build(); return this.peopleRepository.findOne(statement, PersonDetails.class); // <.> } // end::using-person-repo[] // tag::using-temporals[] Optional createNewPerson(NewPersonCmd newPersonCmd) { var p = Person_.PERSON.withProperties(Person_.PERSON.NAME, Cypher.anonParameter(newPersonCmd.getName()) // <.> ).named("p"); var statement = Cypher.merge(p) .onCreate() .set(p.BORN, Cypher.parameter("arbitraryName").withValue(newPersonCmd.getDob().getYear()), // <.> p.DOB, Cypher.anonParameter(newPersonCmd.getDob()) // <.> ) .returning(p) .build(); return this.peopleRepository.findOne(statement); } // end::using-temporals[] // tag::using-person-repo[] } // end::using-person-repo[] ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/Person.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.movies; import java.time.ZonedDateTime; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.data.annotation.PersistenceCreator; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; /** * OGM example type. * * @author Michael J. Simons */ @Node public final class Person { @Id @GeneratedValue private final Long id; private final String name; private Integer born; private ZonedDateTime dob; @PersistenceCreator private Person(Long id, String name, Integer born) { this.id = id; this.born = born; this.name = name; } @JsonCreator public Person(@JsonProperty("name") String name, @JsonProperty("born") Integer born) { this(null, name, born); } public Long getId() { return this.id; } public String getName() { return this.name; } public Integer getBorn() { return this.born; } public void setBorn(Integer born) { this.born = born; } public ZonedDateTime getDob() { return this.dob; } public void setDob(ZonedDateTime dob) { this.dob = dob; } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/PersonDetails.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.movies; import java.util.ArrayList; import java.util.Collections; import java.util.List; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; /** * This is a DTO based projection, containing a couple of additional details, like the * list of movies a person acted in, the movies they direct and which other people they * acted with. * * @author Michael J. Simons */ public final class PersonDetails { private final String name; private final Integer born; private final List actedIn; private final List directed; private final List related; @JsonCreator public PersonDetails(@JsonProperty("name") String name, @JsonProperty("born") Integer born, @JsonProperty("actedIn") List actedIn, @JsonProperty("directed") List directed, @JsonProperty("related") List related) { this.name = name; this.born = born; this.actedIn = new ArrayList<>(actedIn); this.directed = new ArrayList<>(directed); this.related = new ArrayList<>(related); } public String getName() { return this.name; } public Integer getBorn() { return this.born; } public List getActedIn() { return Collections.unmodifiableList(this.actedIn); } public List getDirected() { return Collections.unmodifiableList(this.directed); } public List getRelated() { return Collections.unmodifiableList(this.related); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/movies/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * Domain for testing / educational purpose. */ package org.neo4j.cypherdsl.examples.sdn6.movies; ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/java/org/neo4j/cypherdsl/examples/sdn6/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * A demo application for Spring Data Neo4j 6 with Cypher-DSL integration as well as the * static metamodel. */ package org.neo4j.cypherdsl.examples.sdn6; ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/main/resources/application.properties ================================================ # # Copyright (c) 2019-2026 "Neo4j," # Neo4j Sweden AB [https://neo4j.com] # # This file is part of Neo4j. # # 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. # logging.level.org.springframework.data.neo4j.cypher = trace ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/test/java/org/neo4j/cypherdsl/examples/sdn6/ApplicationIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6; import java.io.IOException; import java.io.InputStreamReader; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; import java.util.regex.Pattern; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIf; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.examples.sdn6.movies.GenreRepository; import org.neo4j.cypherdsl.examples.sdn6.movies.Genre_; import org.neo4j.cypherdsl.examples.sdn6.movies.Movie; import org.neo4j.cypherdsl.examples.sdn6.movies.NewPersonCmd; import org.neo4j.cypherdsl.examples.sdn6.movies.Person; import org.neo4j.cypherdsl.examples.sdn6.movies.PersonDetails; import org.neo4j.driver.AuthTokens; import org.neo4j.driver.GraphDatabase; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.neo4j.Neo4jContainer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.resttestclient.TestRestTemplate; import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @EnabledIf("is606OrHigher") @Testcontainers(disabledWithoutDocker = true) @AutoConfigureTestRestTemplate class ApplicationIT { @Container private static final Neo4jContainer neo4j = new Neo4jContainer("neo4j:2025.06").withReuse(true); @SuppressWarnings("unused") // It is used via {@code @EnableIf} static boolean is606OrHigher() { String version = Optional.of(EnableNeo4jRepositories.class) .map(Class::getPackage) .map(Package::getImplementationVersion) .map(String::trim) .filter(v -> !v.isEmpty()) .orElse("0"); var matcher = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)(?:.*)").matcher(version); return matcher.matches() && Integer.parseInt(matcher.group(1)) >= 6 && (Integer.parseInt(matcher.group(2)) == 0 && Integer.parseInt(matcher.group(3)) >= 6 || Integer.parseInt(matcher.group(2)) >= 1); } @BeforeAll static void loadMovies() throws IOException { ResourceLoader r = new DefaultResourceLoader(); try (var in = new InputStreamReader(r.getResource("classpath:movies.cypher").getInputStream()); var driver = GraphDatabase.driver(neo4j.getBoltUrl(), AuthTokens.basic("neo4j", neo4j.getAdminPassword())); var session = driver.session()) { var movies = FileCopyUtils.copyToString(in); session.run(movies); } } @SuppressWarnings("unused") // Used via Spring magic @DynamicPropertySource static void neo4jProperties(DynamicPropertyRegistry registry) { registry.add("spring.neo4j.uri=", neo4j::getBoltUrl); registry.add("spring.neo4j.authentication.username", () -> "neo4j"); registry.add("spring.neo4j.authentication.password", () -> neo4j.getAdminPassword()); } @Test @DisplayName("Retrieving mapped objects sorted by a static field.") void getMoviesShouldWork(@Autowired TestRestTemplate restTemplate) { var exchange = restTemplate.exchange("/api/movies", HttpMethod.GET, null, new ParameterizedTypeReference>() { }); assertThat(exchange.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(exchange.getBody()).hasSize(38).element(0).extracting(Movie::getTitle).isEqualTo("A Few Good Men"); assertThat(exchange.getBody()).last().extracting(Movie::getTitle).isEqualTo("You've Got Mail"); } @Test @DisplayName("Running a complex query via the Neo4j template itself.") void getRelatedToShouldWork(@Autowired TestRestTemplate restTemplate) { var exchange = restTemplate.exchange("/api/movies/relatedTo/{name}", HttpMethod.GET, null, new ParameterizedTypeReference>() { }, "Tom Hanks"); assertThat(exchange.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(exchange.getBody()).extracting(Movie::getTitle) .containsAll(List.of("A League of Their Own", "Apollo 13", "Cast Away", "Charlie Wilson's War", "Cloud Atlas", "Joe Versus the Volcano", "Sleepless in Seattle", "That Thing You Do", "The Da Vinci Code", "The Green Mile", "The Polar Express", "You've Got Mail")); } @Test @DisplayName("Running a complex query for a projection via the Cypher DSL executor") void getDetailsShouldWork(@Autowired TestRestTemplate restTemplate) { var exchange = restTemplate.exchange("/api/people/details/{name}", HttpMethod.GET, null, PersonDetails.class, "Tom Hanks"); assertThat(exchange.getStatusCode()).isEqualTo(HttpStatus.OK); var details = exchange.getBody(); assertThat(details).isNotNull(); assertThat(details.getName()).isEqualTo("Tom Hanks"); assertThat(details.getActedIn()).hasSize(12); assertThat(details.getDirected()).extracting(Movie::getTitle).containsExactly("That Thing You Do"); assertThat(details.getRelated()).hasSize(35); assertThat(details.getBorn()).isEqualTo(1956); } @Test @DisplayName("Using conditions pt1.") void findPeopleBornInThe70tiesOrShouldWork1(@Autowired TestRestTemplate restTemplate) { var exchange = restTemplate.exchange("/api/people/findPeopleBornInThe70tiesOr", HttpMethod.GET, null, new ParameterizedTypeReference>() { }); assertThat(exchange.getStatusCode()).isEqualTo(HttpStatus.OK); var people = exchange.getBody(); assertThat(people).hasSize(17); } @Test @DisplayName("Using conditions pt2.") void findPeopleBornInThe70tiesOrShouldWork2(@Autowired TestRestTemplate restTemplate) { var exchange = restTemplate.exchange("/api/people/findPeopleBornInThe70tiesOr?name={name}", HttpMethod.GET, null, new ParameterizedTypeReference>() { }, "Natalie Portman"); assertThat(exchange.getStatusCode()).isEqualTo(HttpStatus.OK); var people = exchange.getBody(); assertThat(people).hasSize(18); } @Test @DisplayName("Using conditions pt3.") void findPeopleBornAfterThe70tiesShouldWork(@Autowired TestRestTemplate restTemplate) { // tag::exchange1[] var exchange = restTemplate.exchange("/api/people/v1/findPeopleBornAfterThe70ties?conditions={conditions}", HttpMethod.GET, null, new ParameterizedTypeReference>() { }, "person.name contains \"Ricci\" OR person.name ends with 'Hirsch'"); // end::exchange1[] assertThat(exchange.getStatusCode()).isEqualTo(HttpStatus.OK); var people = exchange.getBody(); assertThat(people).hasSize(2); } @Test @DisplayName("Using conditions pt4.") void findPeopleBornAfterThe70tiesV2ShouldWork(@Autowired TestRestTemplate restTemplate) { var exchange = restTemplate.exchange("/api/people/v2/findPeopleBornAfterThe70ties?conditions={conditions}", HttpMethod.GET, null, new ParameterizedTypeReference>() { }, "name contains \"Ricci\" OR name ends with 'Hirsch'"); assertThat(exchange.getStatusCode()).isEqualTo(HttpStatus.OK); var people = exchange.getBody(); assertThat(people).hasSize(2); } @Test @DisplayName("Using parameters") void usingTemporalsAsParameterShouldWork(@Autowired TestRestTemplate restTemplate) { var dob = ZonedDateTime.of(1990, 10, 31, 23, 42, 0, 0, ZoneId.of("Europe/Berlin")); var result = restTemplate.postForObject("/api/people/createNewPerson", new NewPersonCmd("Liv Lisa Fries", dob), Person.class); assertThat(result.getBorn()).isEqualTo(1990); assertThat(result.getDob()).isEqualTo(dob); } @Test // GH-315 void usingCypherDSLExecutor(@Autowired GenreRepository genreRepository) { var genreModel = Genre_.GENRE.withProperties(Genre_.GENRE.NAME, Cypher.literalOf("Comedy")); var byStatment = Cypher.merge(genreModel) .onCreate() .set(genreModel.ID.to(Cypher.randomUUID())) .returning(genreModel) .build(); var newGenre = genreRepository.findOne(byStatment); assertThat(newGenre).hasValueSatisfying(genre -> { assertThat(genre.getId()).isNotNull(); assertThat(genre.getName()).isEqualTo("Comedy"); }); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/test/java/org/neo4j/cypherdsl/examples/sdn6/UsageTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.examples.sdn6.misc.Example_; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons * */ class UsageTests { @Test // GH-335 void defaultUsageOfSelfReferentialNode() { Example_ node = Example_.EXAMPLE; assertThat(node).isNotNull(); } @Test // GH-335 void selfReferentialNodesShouldLeadToUsableCode() { Example_ node = Example_.EXAMPLE.named("example"); assertThat(node).isNotNull(); } @Test // GH-335 void ltolx() { var left = Example_.EXAMPLE.named("n"); var parent = left.withParent(Example_.EXAMPLE.named("m")); assertThat(parent.getLeft().getRequiredSymbolicName().getValue()).isEqualTo("n"); } @Test // GH-335 void rtorx() { var parent = Example_.EXAMPLE.withParent(Example_.EXAMPLE.named("m")); assertThat(parent.getRight().getRequiredSymbolicName().getValue()).isEqualTo("m"); } @Test // GH-335 void renamingLRShouldWork() { var left = Example_.EXAMPLE.named("n"); var right = Example_.EXAMPLE.named("m"); var rel = left.withParent(right).named("r"); var cypher = Cypher.match(rel) .where(right.ID.isEqualTo(Cypher.literalOf(1L))) .returning(left, right) .build() .getCypher(); assertThat(cypher).isEqualTo("MATCH (n:`Example`)-[r:`BELONGS_TO`]->(m:`Example`) WHERE m.id = 1 RETURN n, m"); } @Test // GH-335 void renamingShouldWork() { var node = Example_.EXAMPLE.named("n"); var rel = node.withParent(Example_.EXAMPLE).named("r"); var cypher = Cypher.match(rel) .where(node.ID.isEqualTo(Cypher.literalOf(1L))) .returning(node) .build() .getCypher(); assertThat(cypher) .matches("MATCH \\(n:`Example`\\)-\\[r:`BELONGS_TO`]->\\(.+:`Example`\\) WHERE n\\.id = 1 RETURN n"); } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/test/java/org/neo4j/cypherdsl/examples/sdn6/books/ScopingTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.examples.sdn6.books; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.neo4j.cypherdsl.core.Cypher; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class ScopingTests { @ParameterizedTest // GH-1014 @ValueSource(booleans = { true, false }) void patternExpressionMustNotIntroduceNames(boolean withIt) { var userIdParam = Cypher.parameter("userId"); var limitParam = Cypher.parameter("limit"); var langParam = Cypher.parameter("lang"); var book = Book_.BOOK.withProperties(Book_.BOOK.LANG, langParam).named("book"); var userDetails = UserDetails_.USER_DETAILS.withProperties(UserDetails_.USER_DETAILS.ID, userIdParam) .named("user"); var userPreferences = UserPreferences_.USER_PREFERENCES .withProperties(UserPreferences_.USER_PREFERENCES.USER_ID, userIdParam) .named("preferences"); var genre = BookGenre_.BOOK_GENRE.named("genre"); var userActivity = UserSuggestionActivity_.USER_SUGGESTION_ACTIVITY .withProperties(UserSuggestionActivity_.USER_SUGGESTION_ACTIVITY.USER_ID, userIdParam) .named("activity"); var relationBookGenre = book.relationshipTo(genre, "BELONGS_TO").named("rel"); var relationBookAvoidedGenre = book.relationshipTo(BookGenre_.BOOK_GENRE, "BELONGS_TO") .relationshipFrom(userPreferences, "AVOIDED"); var relationUserPreferencesGenreBook = userDetails.relationshipTo(userPreferences, "HAS_PREFERENCES") .relationshipTo(genre, "PREFERRED") .relationshipFrom(book, "BELONGS_TO"); var returningBooks = Cypher.call("distinct").withArgs(Cypher.name("book")).asFunction(); // This is the correct variant, as shown in the latest code update on the issue, // matching the activity first, then // passing it on. if (withIt) { var statement = Cypher.match(relationUserPreferencesGenreBook) .match(userDetails.relationshipTo(userActivity, "HAS_ACTIVITY")) .where(Cypher.not(Cypher.exists(relationBookAvoidedGenre))) .with(book, userActivity) .limit(limitParam) .match(relationBookGenre) .where(Cypher.not(Cypher.exists(book.relationshipBetween(userActivity)))) .returning(returningBooks, Cypher.collect(Cypher.name("rel")), Cypher.collect(genre)) .build(); assertThat(statement.getCypher()).isEqualTo( "MATCH (user:`UserDetails` {id: $userId})-[:`HAS_PREFERENCES`]->(preferences:`UserPreferences` {userId: $userId})-[:`PREFERRED`]->(genre:`BookGenre`)<-[:`BELONGS_TO`]-(book:`Book` {lang: $lang}) MATCH (user)-[:`HAS_ACTIVITY`]->(activity:`UserSuggestionActivity` {userId: $userId}) WHERE NOT (exists((book)-[:`BELONGS_TO`]->(:`BookGenre`)<-[:`AVOIDED`]-(preferences))) WITH book, activity LIMIT $limit MATCH (book)-[rel:`BELONGS_TO`]->(genre:`BookGenre`) WHERE NOT (exists((book)--(activity))) RETURN distinct(book), collect(rel), collect(genre)"); } else { // Statement is identical, except the with clause. activity goes out of scope, // hence in the existential subquery it will be rerendered. // There however it must not include the name a new var statement = Cypher.match(relationUserPreferencesGenreBook) .match(userDetails.relationshipTo(userActivity, "HAS_ACTIVITY")) .where(Cypher.not(Cypher.exists(relationBookAvoidedGenre))) .with(book) .limit(limitParam) .match(relationBookGenre) .where(Cypher.not(Cypher.exists(book.relationshipBetween(userActivity)))) .returning(returningBooks, Cypher.collect(Cypher.name("rel")), Cypher.collect(genre)) .build(); assertThat(statement.getCypher()).isEqualTo( "MATCH (user:`UserDetails` {id: $userId})-[:`HAS_PREFERENCES`]->(preferences:`UserPreferences` {userId: $userId})-[:`PREFERRED`]->(genre:`BookGenre`)<-[:`BELONGS_TO`]-(book:`Book` {lang: $lang}) MATCH (user)-[:`HAS_ACTIVITY`]->(activity:`UserSuggestionActivity` {userId: $userId}) WHERE NOT (exists((book)-[:`BELONGS_TO`]->(:`BookGenre`)<-[:`AVOIDED`]-(preferences))) WITH book LIMIT $limit MATCH (book)-[rel:`BELONGS_TO`]->(genre:`BookGenre`) WHERE NOT (exists((book)--(:`UserSuggestionActivity` {userId: $userId}))) RETURN distinct(book), collect(rel), collect(genre)"); } } } ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/test/resources/logback-test.xml ================================================ [%t] %d %5p %40.40c:%4L - %m%n ================================================ FILE: neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6/src/test/resources/movies.cypher ================================================ CREATE (TheMatrix:Movie {title:'The Matrix', released:1999, tagline:'Welcome to the Real World'}) CREATE (Keanu:Person {name:'Keanu Reeves', born:1964}) CREATE (Carrie:Person {name:'Carrie-Anne Moss', born:1967}) CREATE (Laurence:Person {name:'Laurence Fishburne', born:1961}) CREATE (Hugo:Person {name:'Hugo Weaving', born:1960}) CREATE (LillyW:Person {name:'Lilly Wachowski', born:1967}) CREATE (LanaW:Person {name:'Lana Wachowski', born:1965}) CREATE (JoelS:Person {name:'Joel Silver', born:1952}) CREATE (Keanu)-[:ACTED_IN {roles:['Neo']}]->(TheMatrix), (Carrie)-[:ACTED_IN {roles:['Trinity']}]->(TheMatrix), (Laurence)-[:ACTED_IN {roles:['Morpheus']}]->(TheMatrix), (Hugo)-[:ACTED_IN {roles:['Agent Smith']}]->(TheMatrix), (LillyW)-[:DIRECTED]->(TheMatrix), (LanaW)-[:DIRECTED]->(TheMatrix), (JoelS)-[:PRODUCED]->(TheMatrix) CREATE (Emil:Person {name:"Emil Eifrem", born:1978}) CREATE (Emil)-[:ACTED_IN {roles:["Emil"]}]->(TheMatrix) CREATE (TheMatrixReloaded:Movie {title:'The Matrix Reloaded', released:2003, tagline:'Free your mind'}) CREATE (Keanu)-[:ACTED_IN {roles:['Neo']}]->(TheMatrixReloaded), (Carrie)-[:ACTED_IN {roles:['Trinity']}]->(TheMatrixReloaded), (Laurence)-[:ACTED_IN {roles:['Morpheus']}]->(TheMatrixReloaded), (Hugo)-[:ACTED_IN {roles:['Agent Smith']}]->(TheMatrixReloaded), (LillyW)-[:DIRECTED]->(TheMatrixReloaded), (LanaW)-[:DIRECTED]->(TheMatrixReloaded), (JoelS)-[:PRODUCED]->(TheMatrixReloaded) CREATE (TheMatrixRevolutions:Movie {title:'The Matrix Revolutions', released:2003, tagline:'Everything that has a beginning has an end'}) CREATE (Keanu)-[:ACTED_IN {roles:['Neo']}]->(TheMatrixRevolutions), (Carrie)-[:ACTED_IN {roles:['Trinity']}]->(TheMatrixRevolutions), (Laurence)-[:ACTED_IN {roles:['Morpheus']}]->(TheMatrixRevolutions), (Hugo)-[:ACTED_IN {roles:['Agent Smith']}]->(TheMatrixRevolutions), (LillyW)-[:DIRECTED]->(TheMatrixRevolutions), (LanaW)-[:DIRECTED]->(TheMatrixRevolutions), (JoelS)-[:PRODUCED]->(TheMatrixRevolutions) CREATE (TheDevilsAdvocate:Movie {title:"The Devil's Advocate", released:1997, tagline:'Evil has its winning ways'}) CREATE (Charlize:Person {name:'Charlize Theron', born:1975}) CREATE (Al:Person {name:'Al Pacino', born:1940}) CREATE (Taylor:Person {name:'Taylor Hackford', born:1944}) CREATE (Keanu)-[:ACTED_IN {roles:['Kevin Lomax']}]->(TheDevilsAdvocate), (Charlize)-[:ACTED_IN {roles:['Mary Ann Lomax']}]->(TheDevilsAdvocate), (Al)-[:ACTED_IN {roles:['John Milton']}]->(TheDevilsAdvocate), (Taylor)-[:DIRECTED]->(TheDevilsAdvocate) CREATE (AFewGoodMen:Movie {title:"A Few Good Men", released:1992, tagline:"In the heart of the nation's capital, in a courthouse of the U.S. government, one man will stop at nothing to keep his honor, and one will stop at nothing to find the truth."}) CREATE (TomC:Person {name:'Tom Cruise', born:1962}) CREATE (JackN:Person {name:'Jack Nicholson', born:1937}) CREATE (DemiM:Person {name:'Demi Moore', born:1962}) CREATE (KevinB:Person {name:'Kevin Bacon', born:1958}) CREATE (KieferS:Person {name:'Kiefer Sutherland', born:1966}) CREATE (NoahW:Person {name:'Noah Wyle', born:1971}) CREATE (CubaG:Person {name:'Cuba Gooding Jr.', born:1968}) CREATE (KevinP:Person {name:'Kevin Pollak', born:1957}) CREATE (JTW:Person {name:'J.T. Walsh', born:1943}) CREATE (JamesM:Person {name:'James Marshall', born:1967}) CREATE (ChristopherG:Person {name:'Christopher Guest', born:1948}) CREATE (RobR:Person {name:'Rob Reiner', born:1947}) CREATE (AaronS:Person {name:'Aaron Sorkin', born:1961}) CREATE (TomC)-[:ACTED_IN {roles:['Lt. Daniel Kaffee']}]->(AFewGoodMen), (JackN)-[:ACTED_IN {roles:['Col. Nathan R. Jessup']}]->(AFewGoodMen), (DemiM)-[:ACTED_IN {roles:['Lt. Cdr. JoAnne Galloway']}]->(AFewGoodMen), (KevinB)-[:ACTED_IN {roles:['Capt. Jack Ross']}]->(AFewGoodMen), (KieferS)-[:ACTED_IN {roles:['Lt. Jonathan Kendrick']}]->(AFewGoodMen), (NoahW)-[:ACTED_IN {roles:['Cpl. Jeffrey Barnes']}]->(AFewGoodMen), (CubaG)-[:ACTED_IN {roles:['Cpl. Carl Hammaker']}]->(AFewGoodMen), (KevinP)-[:ACTED_IN {roles:['Lt. Sam Weinberg']}]->(AFewGoodMen), (JTW)-[:ACTED_IN {roles:['Lt. Col. Matthew Andrew Markinson']}]->(AFewGoodMen), (JamesM)-[:ACTED_IN {roles:['Pfc. Louden Downey']}]->(AFewGoodMen), (ChristopherG)-[:ACTED_IN {roles:['Dr. Stone']}]->(AFewGoodMen), (AaronS)-[:ACTED_IN {roles:['Man in Bar']}]->(AFewGoodMen), (RobR)-[:DIRECTED]->(AFewGoodMen), (AaronS)-[:WROTE]->(AFewGoodMen) CREATE (TopGun:Movie {title:"Top Gun", released:1986, tagline:'I feel the need, the need for speed.'}) CREATE (KellyM:Person {name:'Kelly McGillis', born:1957}) CREATE (ValK:Person {name:'Val Kilmer', born:1959}) CREATE (AnthonyE:Person {name:'Anthony Edwards', born:1962}) CREATE (TomS:Person {name:'Tom Skerritt', born:1933}) CREATE (MegR:Person {name:'Meg Ryan', born:1961}) CREATE (TonyS:Person {name:'Tony Scott', born:1944}) CREATE (JimC:Person {name:'Jim Cash', born:1941}) CREATE (TomC)-[:ACTED_IN {roles:['Maverick']}]->(TopGun), (KellyM)-[:ACTED_IN {roles:['Charlie']}]->(TopGun), (ValK)-[:ACTED_IN {roles:['Iceman']}]->(TopGun), (AnthonyE)-[:ACTED_IN {roles:['Goose']}]->(TopGun), (TomS)-[:ACTED_IN {roles:['Viper']}]->(TopGun), (MegR)-[:ACTED_IN {roles:['Carole']}]->(TopGun), (TonyS)-[:DIRECTED]->(TopGun), (JimC)-[:WROTE]->(TopGun) CREATE (JerryMaguire:Movie {title:'Jerry Maguire', released:2000, tagline:'The rest of his life begins now.'}) CREATE (ReneeZ:Person {name:'Renee Zellweger', born:1969}) CREATE (KellyP:Person {name:'Kelly Preston', born:1962}) CREATE (JerryO:Person {name:"Jerry O'Connell", born:1974}) CREATE (JayM:Person {name:'Jay Mohr', born:1970}) CREATE (BonnieH:Person {name:'Bonnie Hunt', born:1961}) CREATE (ReginaK:Person {name:'Regina King', born:1971}) CREATE (JonathanL:Person {name:'Jonathan Lipnicki', born:1996}) CREATE (CameronC:Person {name:'Cameron Crowe', born:1957}) CREATE (TomC)-[:ACTED_IN {roles:['Jerry Maguire']}]->(JerryMaguire), (CubaG)-[:ACTED_IN {roles:['Rod Tidwell']}]->(JerryMaguire), (ReneeZ)-[:ACTED_IN {roles:['Dorothy Boyd']}]->(JerryMaguire), (KellyP)-[:ACTED_IN {roles:['Avery Bishop']}]->(JerryMaguire), (JerryO)-[:ACTED_IN {roles:['Frank Cushman']}]->(JerryMaguire), (JayM)-[:ACTED_IN {roles:['Bob Sugar']}]->(JerryMaguire), (BonnieH)-[:ACTED_IN {roles:['Laurel Boyd']}]->(JerryMaguire), (ReginaK)-[:ACTED_IN {roles:['Marcee Tidwell']}]->(JerryMaguire), (JonathanL)-[:ACTED_IN {roles:['Ray Boyd']}]->(JerryMaguire), (CameronC)-[:DIRECTED]->(JerryMaguire), (CameronC)-[:PRODUCED]->(JerryMaguire), (CameronC)-[:WROTE]->(JerryMaguire) CREATE (StandByMe:Movie {title:"Stand By Me", released:1986, tagline:"For some, it's the last real taste of innocence, and the first real taste of life. But for everyone, it's the time that memories are made of."}) CREATE (RiverP:Person {name:'River Phoenix', born:1970}) CREATE (CoreyF:Person {name:'Corey Feldman', born:1971}) CREATE (WilW:Person {name:'Wil Wheaton', born:1972}) CREATE (JohnC:Person {name:'John Cusack', born:1966}) CREATE (MarshallB:Person {name:'Marshall Bell', born:1942}) CREATE (WilW)-[:ACTED_IN {roles:['Gordie Lachance']}]->(StandByMe), (RiverP)-[:ACTED_IN {roles:['Chris Chambers']}]->(StandByMe), (JerryO)-[:ACTED_IN {roles:['Vern Tessio']}]->(StandByMe), (CoreyF)-[:ACTED_IN {roles:['Teddy Duchamp']}]->(StandByMe), (JohnC)-[:ACTED_IN {roles:['Denny Lachance']}]->(StandByMe), (KieferS)-[:ACTED_IN {roles:['Ace Merrill']}]->(StandByMe), (MarshallB)-[:ACTED_IN {roles:['Mr. Lachance']}]->(StandByMe), (RobR)-[:DIRECTED]->(StandByMe) CREATE (AsGoodAsItGets:Movie {title:'As Good as It Gets', released:1997, tagline:'A comedy from the heart that goes for the throat.'}) CREATE (HelenH:Person {name:'Helen Hunt', born:1963}) CREATE (GregK:Person {name:'Greg Kinnear', born:1963}) CREATE (JamesB:Person {name:'James L. Brooks', born:1940}) CREATE (JackN)-[:ACTED_IN {roles:['Melvin Udall']}]->(AsGoodAsItGets), (HelenH)-[:ACTED_IN {roles:['Carol Connelly']}]->(AsGoodAsItGets), (GregK)-[:ACTED_IN {roles:['Simon Bishop']}]->(AsGoodAsItGets), (CubaG)-[:ACTED_IN {roles:['Frank Sachs']}]->(AsGoodAsItGets), (JamesB)-[:DIRECTED]->(AsGoodAsItGets) CREATE (WhatDreamsMayCome:Movie {title:'What Dreams May Come', released:1998, tagline:'After life there is more. The end is just the beginning.'}) CREATE (AnnabellaS:Person {name:'Annabella Sciorra', born:1960}) CREATE (MaxS:Person {name:'Max von Sydow', born:1929}) CREATE (WernerH:Person {name:'Werner Herzog', born:1942}) CREATE (Robin:Person {name:'Robin Williams', born:1951}) CREATE (VincentW:Person {name:'Vincent Ward', born:1956}) CREATE (Robin)-[:ACTED_IN {roles:['Chris Nielsen']}]->(WhatDreamsMayCome), (CubaG)-[:ACTED_IN {roles:['Albert Lewis']}]->(WhatDreamsMayCome), (AnnabellaS)-[:ACTED_IN {roles:['Annie Collins-Nielsen']}]->(WhatDreamsMayCome), (MaxS)-[:ACTED_IN {roles:['The Tracker']}]->(WhatDreamsMayCome), (WernerH)-[:ACTED_IN {roles:['The Face']}]->(WhatDreamsMayCome), (VincentW)-[:DIRECTED]->(WhatDreamsMayCome) CREATE (SnowFallingonCedars:Movie {title:'Snow Falling on Cedars', released:1999, tagline:'First loves last. Forever.'}) CREATE (EthanH:Person {name:'Ethan Hawke', born:1970}) CREATE (RickY:Person {name:'Rick Yune', born:1971}) CREATE (JamesC:Person {name:'James Cromwell', born:1940}) CREATE (ScottH:Person {name:'Scott Hicks', born:1953}) CREATE (EthanH)-[:ACTED_IN {roles:['Ishmael Chambers']}]->(SnowFallingonCedars), (RickY)-[:ACTED_IN {roles:['Kazuo Miyamoto']}]->(SnowFallingonCedars), (MaxS)-[:ACTED_IN {roles:['Nels Gudmundsson']}]->(SnowFallingonCedars), (JamesC)-[:ACTED_IN {roles:['Judge Fielding']}]->(SnowFallingonCedars), (ScottH)-[:DIRECTED]->(SnowFallingonCedars) CREATE (YouveGotMail:Movie {title:"You've Got Mail", released:1998, tagline:'At odds in life... in love on-line.'}) CREATE (ParkerP:Person {name:'Parker Posey', born:1968}) CREATE (DaveC:Person {name:'Dave Chappelle', born:1973}) CREATE (SteveZ:Person {name:'Steve Zahn', born:1967}) CREATE (TomH:Person {name:'Tom Hanks', born:1956}) CREATE (NoraE:Person {name:'Nora Ephron', born:1941}) CREATE (TomH)-[:ACTED_IN {roles:['Joe Fox']}]->(YouveGotMail), (MegR)-[:ACTED_IN {roles:['Kathleen Kelly']}]->(YouveGotMail), (GregK)-[:ACTED_IN {roles:['Frank Navasky']}]->(YouveGotMail), (ParkerP)-[:ACTED_IN {roles:['Patricia Eden']}]->(YouveGotMail), (DaveC)-[:ACTED_IN {roles:['Kevin Jackson']}]->(YouveGotMail), (SteveZ)-[:ACTED_IN {roles:['George Pappas']}]->(YouveGotMail), (NoraE)-[:DIRECTED]->(YouveGotMail) CREATE (SleeplessInSeattle:Movie {title:'Sleepless in Seattle', released:1993, tagline:'What if someone you never met, someone you never saw, someone you never knew was the only someone for you?'}) CREATE (RitaW:Person {name:'Rita Wilson', born:1956}) CREATE (BillPull:Person {name:'Bill Pullman', born:1953}) CREATE (VictorG:Person {name:'Victor Garber', born:1949}) CREATE (RosieO:Person {name:"Rosie O'Donnell", born:1962}) CREATE (TomH)-[:ACTED_IN {roles:['Sam Baldwin']}]->(SleeplessInSeattle), (MegR)-[:ACTED_IN {roles:['Annie Reed']}]->(SleeplessInSeattle), (RitaW)-[:ACTED_IN {roles:['Suzy']}]->(SleeplessInSeattle), (BillPull)-[:ACTED_IN {roles:['Walter']}]->(SleeplessInSeattle), (VictorG)-[:ACTED_IN {roles:['Greg']}]->(SleeplessInSeattle), (RosieO)-[:ACTED_IN {roles:['Becky']}]->(SleeplessInSeattle), (NoraE)-[:DIRECTED]->(SleeplessInSeattle) CREATE (JoeVersustheVolcano:Movie {title:'Joe Versus the Volcano', released:1990, tagline:'A story of love, lava and burning desire.'}) CREATE (JohnS:Person {name:'John Patrick Stanley', born:1950}) CREATE (Nathan:Person {name:'Nathan Lane', born:1956}) CREATE (TomH)-[:ACTED_IN {roles:['Joe Banks']}]->(JoeVersustheVolcano), (MegR)-[:ACTED_IN {roles:['DeDe', 'Angelica Graynamore', 'Patricia Graynamore']}]->(JoeVersustheVolcano), (Nathan)-[:ACTED_IN {roles:['Baw']}]->(JoeVersustheVolcano), (JohnS)-[:DIRECTED]->(JoeVersustheVolcano) CREATE (WhenHarryMetSally:Movie {title:'When Harry Met Sally', released:1998, tagline:'Can two friends sleep together and still love each other in the morning?'}) CREATE (BillyC:Person {name:'Billy Crystal', born:1948}) CREATE (CarrieF:Person {name:'Carrie Fisher', born:1956}) CREATE (BrunoK:Person {name:'Bruno Kirby', born:1949}) CREATE (BillyC)-[:ACTED_IN {roles:['Harry Burns']}]->(WhenHarryMetSally), (MegR)-[:ACTED_IN {roles:['Sally Albright']}]->(WhenHarryMetSally), (CarrieF)-[:ACTED_IN {roles:['Marie']}]->(WhenHarryMetSally), (BrunoK)-[:ACTED_IN {roles:['Jess']}]->(WhenHarryMetSally), (RobR)-[:DIRECTED]->(WhenHarryMetSally), (RobR)-[:PRODUCED]->(WhenHarryMetSally), (NoraE)-[:PRODUCED]->(WhenHarryMetSally), (NoraE)-[:WROTE]->(WhenHarryMetSally) CREATE (ThatThingYouDo:Movie {title:'That Thing You Do', released:1996, tagline:'In every life there comes a time when that thing you dream becomes that thing you do'}) CREATE (LivT:Person {name:'Liv Tyler', born:1977}) CREATE (TomH)-[:ACTED_IN {roles:['Mr. White']}]->(ThatThingYouDo), (LivT)-[:ACTED_IN {roles:['Faye Dolan']}]->(ThatThingYouDo), (Charlize)-[:ACTED_IN {roles:['Tina']}]->(ThatThingYouDo), (TomH)-[:DIRECTED]->(ThatThingYouDo) CREATE (TheReplacements:Movie {title:'The Replacements', released:2000, tagline:'Pain heals, Chicks dig scars... Glory lasts forever'}) CREATE (Brooke:Person {name:'Brooke Langton', born:1970}) CREATE (Gene:Person {name:'Gene Hackman', born:1930}) CREATE (Orlando:Person {name:'Orlando Jones', born:1968}) CREATE (Howard:Person {name:'Howard Deutch', born:1950}) CREATE (Keanu)-[:ACTED_IN {roles:['Shane Falco']}]->(TheReplacements), (Brooke)-[:ACTED_IN {roles:['Annabelle Farrell']}]->(TheReplacements), (Gene)-[:ACTED_IN {roles:['Jimmy McGinty']}]->(TheReplacements), (Orlando)-[:ACTED_IN {roles:['Clifford Franklin']}]->(TheReplacements), (Howard)-[:DIRECTED]->(TheReplacements) CREATE (RescueDawn:Movie {title:'RescueDawn', released:2006, tagline:"Based on the extraordinary true story of one man's fight for freedom"}) CREATE (ChristianB:Person {name:'Christian Bale', born:1974}) CREATE (ZachG:Person {name:'Zach Grenier', born:1954}) CREATE (MarshallB)-[:ACTED_IN {roles:['Admiral']}]->(RescueDawn), (ChristianB)-[:ACTED_IN {roles:['Dieter Dengler']}]->(RescueDawn), (ZachG)-[:ACTED_IN {roles:['Squad Leader']}]->(RescueDawn), (SteveZ)-[:ACTED_IN {roles:['Duane']}]->(RescueDawn), (WernerH)-[:DIRECTED]->(RescueDawn) CREATE (TheBirdcage:Movie {title:'The Birdcage', released:1996, tagline:'Come as you are'}) CREATE (MikeN:Person {name:'Mike Nichols', born:1931}) CREATE (Robin)-[:ACTED_IN {roles:['Armand Goldman']}]->(TheBirdcage), (Nathan)-[:ACTED_IN {roles:['Albert Goldman']}]->(TheBirdcage), (Gene)-[:ACTED_IN {roles:['Sen. Kevin Keeley']}]->(TheBirdcage), (MikeN)-[:DIRECTED]->(TheBirdcage) CREATE (Unforgiven:Movie {title:'Unforgiven', released:1992, tagline:"It's a hell of a thing, killing a man"}) CREATE (RichardH:Person {name:'Richard Harris', born:1930}) CREATE (ClintE:Person {name:'Clint Eastwood', born:1930}) CREATE (RichardH)-[:ACTED_IN {roles:['English Bob']}]->(Unforgiven), (ClintE)-[:ACTED_IN {roles:['Bill Munny']}]->(Unforgiven), (Gene)-[:ACTED_IN {roles:['Little Bill Daggett']}]->(Unforgiven), (ClintE)-[:DIRECTED]->(Unforgiven) CREATE (JohnnyMnemonic:Movie {title:'Johnny Mnemonic', released:1995, tagline:'The hottest data on earth. In the coolest head in town'}) CREATE (Takeshi:Person {name:'Takeshi Kitano', born:1947}) CREATE (Dina:Person {name:'Dina Meyer', born:1968}) CREATE (IceT:Person {name:'Ice-T', born:1958}) CREATE (RobertL:Person {name:'Robert Longo', born:1953}) CREATE (Keanu)-[:ACTED_IN {roles:['Johnny Mnemonic']}]->(JohnnyMnemonic), (Takeshi)-[:ACTED_IN {roles:['Takahashi']}]->(JohnnyMnemonic), (Dina)-[:ACTED_IN {roles:['Jane']}]->(JohnnyMnemonic), (IceT)-[:ACTED_IN {roles:['J-Bone']}]->(JohnnyMnemonic), (RobertL)-[:DIRECTED]->(JohnnyMnemonic) CREATE (CloudAtlas:Movie {title:'Cloud Atlas', released:2012, tagline:'Everything is connected'}) CREATE (HalleB:Person {name:'Halle Berry', born:1966}) CREATE (JimB:Person {name:'Jim Broadbent', born:1949}) CREATE (TomT:Person {name:'Tom Tykwer', born:1965}) CREATE (DavidMitchell:Person {name:'David Mitchell', born:1969}) CREATE (StefanArndt:Person {name:'Stefan Arndt', born:1961}) CREATE (TomH)-[:ACTED_IN {roles:['Zachry', 'Dr. Henry Goose', 'Isaac Sachs', 'Dermot Hoggins']}]->(CloudAtlas), (Hugo)-[:ACTED_IN {roles:['Bill Smoke', 'Haskell Moore', 'Tadeusz Kesselring', 'Nurse Noakes', 'Boardman Mephi', 'Old Georgie']}]->(CloudAtlas), (HalleB)-[:ACTED_IN {roles:['Luisa Rey', 'Jocasta Ayrs', 'Ovid', 'Meronym']}]->(CloudAtlas), (JimB)-[:ACTED_IN {roles:['Vyvyan Ayrs', 'Captain Molyneux', 'Timothy Cavendish']}]->(CloudAtlas), (TomT)-[:DIRECTED]->(CloudAtlas), (LillyW)-[:DIRECTED]->(CloudAtlas), (LanaW)-[:DIRECTED]->(CloudAtlas), (DavidMitchell)-[:WROTE]->(CloudAtlas), (StefanArndt)-[:PRODUCED]->(CloudAtlas) CREATE (TheDaVinciCode:Movie {title:'The Da Vinci Code', released:2006, tagline:'Break The Codes'}) CREATE (IanM:Person {name:'Ian McKellen', born:1939}) CREATE (AudreyT:Person {name:'Audrey Tautou', born:1976}) CREATE (PaulB:Person {name:'Paul Bettany', born:1971}) CREATE (RonH:Person {name:'Ron Howard', born:1954}) CREATE (TomH)-[:ACTED_IN {roles:['Dr. Robert Langdon']}]->(TheDaVinciCode), (IanM)-[:ACTED_IN {roles:['Sir Leight Teabing']}]->(TheDaVinciCode), (AudreyT)-[:ACTED_IN {roles:['Sophie Neveu']}]->(TheDaVinciCode), (PaulB)-[:ACTED_IN {roles:['Silas']}]->(TheDaVinciCode), (RonH)-[:DIRECTED]->(TheDaVinciCode) CREATE (VforVendetta:Movie {title:'V for Vendetta', released:2006, tagline:'Freedom! Forever!'}) CREATE (NatalieP:Person {name:'Natalie Portman', born:1981}) CREATE (StephenR:Person {name:'Stephen Rea', born:1946}) CREATE (JohnH:Person {name:'John Hurt', born:1940}) CREATE (BenM:Person {name: 'Ben Miles', born:1967}) CREATE (Hugo)-[:ACTED_IN {roles:['V']}]->(VforVendetta), (NatalieP)-[:ACTED_IN {roles:['Evey Hammond']}]->(VforVendetta), (StephenR)-[:ACTED_IN {roles:['Eric Finch']}]->(VforVendetta), (JohnH)-[:ACTED_IN {roles:['High Chancellor Adam Sutler']}]->(VforVendetta), (BenM)-[:ACTED_IN {roles:['Dascomb']}]->(VforVendetta), (JamesM)-[:DIRECTED]->(VforVendetta), (LillyW)-[:PRODUCED]->(VforVendetta), (LanaW)-[:PRODUCED]->(VforVendetta), (JoelS)-[:PRODUCED]->(VforVendetta), (LillyW)-[:WROTE]->(VforVendetta), (LanaW)-[:WROTE]->(VforVendetta) CREATE (SpeedRacer:Movie {title:'Speed Racer', released:2008, tagline:'Speed has no limits'}) CREATE (EmileH:Person {name:'Emile Hirsch', born:1985}) CREATE (JohnG:Person {name:'John Goodman', born:1960}) CREATE (SusanS:Person {name:'Susan Sarandon', born:1946}) CREATE (MatthewF:Person {name:'Matthew Fox', born:1966}) CREATE (ChristinaR:Person {name:'Christina Ricci', born:1980}) CREATE (Rain:Person {name:'Rain', born:1982}) CREATE (EmileH)-[:ACTED_IN {roles:['Speed Racer']}]->(SpeedRacer), (JohnG)-[:ACTED_IN {roles:['Pops']}]->(SpeedRacer), (SusanS)-[:ACTED_IN {roles:['Mom']}]->(SpeedRacer), (MatthewF)-[:ACTED_IN {roles:['Racer X']}]->(SpeedRacer), (ChristinaR)-[:ACTED_IN {roles:['Trixie']}]->(SpeedRacer), (Rain)-[:ACTED_IN {roles:['Taejo Togokahn']}]->(SpeedRacer), (BenM)-[:ACTED_IN {roles:['Cass Jones']}]->(SpeedRacer), (LillyW)-[:DIRECTED]->(SpeedRacer), (LanaW)-[:DIRECTED]->(SpeedRacer), (LillyW)-[:WROTE]->(SpeedRacer), (LanaW)-[:WROTE]->(SpeedRacer), (JoelS)-[:PRODUCED]->(SpeedRacer) CREATE (NinjaAssassin:Movie {title:'Ninja Assassin', released:2009, tagline:'Prepare to enter a secret world of assassins'}) CREATE (NaomieH:Person {name:'Naomie Harris'}) CREATE (Rain)-[:ACTED_IN {roles:['Raizo']}]->(NinjaAssassin), (NaomieH)-[:ACTED_IN {roles:['Mika Coretti']}]->(NinjaAssassin), (RickY)-[:ACTED_IN {roles:['Takeshi']}]->(NinjaAssassin), (BenM)-[:ACTED_IN {roles:['Ryan Maslow']}]->(NinjaAssassin), (JamesM)-[:DIRECTED]->(NinjaAssassin), (LillyW)-[:PRODUCED]->(NinjaAssassin), (LanaW)-[:PRODUCED]->(NinjaAssassin), (JoelS)-[:PRODUCED]->(NinjaAssassin) CREATE (TheGreenMile:Movie {title:'The Green Mile', released:1999, tagline:"Walk a mile you'll never forget."}) CREATE (MichaelD:Person {name:'Michael Clarke Duncan', born:1957}) CREATE (DavidM:Person {name:'David Morse', born:1953}) CREATE (SamR:Person {name:'Sam Rockwell', born:1968}) CREATE (GaryS:Person {name:'Gary Sinise', born:1955}) CREATE (PatriciaC:Person {name:'Patricia Clarkson', born:1959}) CREATE (FrankD:Person {name:'Frank Darabont', born:1959}) CREATE (TomH)-[:ACTED_IN {roles:['Paul Edgecomb']}]->(TheGreenMile), (MichaelD)-[:ACTED_IN {roles:['John Coffey']}]->(TheGreenMile), (DavidM)-[:ACTED_IN {roles:['Brutus "Brutal" Howell']}]->(TheGreenMile), (BonnieH)-[:ACTED_IN {roles:['Jan Edgecomb']}]->(TheGreenMile), (JamesC)-[:ACTED_IN {roles:['Warden Hal Moores']}]->(TheGreenMile), (SamR)-[:ACTED_IN {roles:['"Wild Bill" Wharton']}]->(TheGreenMile), (GaryS)-[:ACTED_IN {roles:['Burt Hammersmith']}]->(TheGreenMile), (PatriciaC)-[:ACTED_IN {roles:['Melinda Moores']}]->(TheGreenMile), (FrankD)-[:DIRECTED]->(TheGreenMile) CREATE (FrostNixon:Movie {title:'Frost/Nixon', released:2008, tagline:'400 million people were waiting for the truth.'}) CREATE (FrankL:Person {name:'Frank Langella', born:1938}) CREATE (MichaelS:Person {name:'Michael Sheen', born:1969}) CREATE (OliverP:Person {name:'Oliver Platt', born:1960}) CREATE (FrankL)-[:ACTED_IN {roles:['Richard Nixon']}]->(FrostNixon), (MichaelS)-[:ACTED_IN {roles:['David Frost']}]->(FrostNixon), (KevinB)-[:ACTED_IN {roles:['Jack Brennan']}]->(FrostNixon), (OliverP)-[:ACTED_IN {roles:['Bob Zelnick']}]->(FrostNixon), (SamR)-[:ACTED_IN {roles:['James Reston, Jr.']}]->(FrostNixon), (RonH)-[:DIRECTED]->(FrostNixon) CREATE (Hoffa:Movie {title:'Hoffa', released:1992, tagline:"He didn't want law. He wanted justice."}) CREATE (DannyD:Person {name:'Danny DeVito', born:1944}) CREATE (JohnR:Person {name:'John C. Reilly', born:1965}) CREATE (JackN)-[:ACTED_IN {roles:['Hoffa']}]->(Hoffa), (DannyD)-[:ACTED_IN {roles:['Robert "Bobby" Ciaro']}]->(Hoffa), (JTW)-[:ACTED_IN {roles:['Frank Fitzsimmons']}]->(Hoffa), (JohnR)-[:ACTED_IN {roles:['Peter "Pete" Connelly']}]->(Hoffa), (DannyD)-[:DIRECTED]->(Hoffa) CREATE (Apollo13:Movie {title:'Apollo 13', released:1995, tagline:'Houston, we have a problem.'}) CREATE (EdH:Person {name:'Ed Harris', born:1950}) CREATE (BillPax:Person {name:'Bill Paxton', born:1955}) CREATE (TomH)-[:ACTED_IN {roles:['Jim Lovell']}]->(Apollo13), (KevinB)-[:ACTED_IN {roles:['Jack Swigert']}]->(Apollo13), (EdH)-[:ACTED_IN {roles:['Gene Kranz']}]->(Apollo13), (BillPax)-[:ACTED_IN {roles:['Fred Haise']}]->(Apollo13), (GaryS)-[:ACTED_IN {roles:['Ken Mattingly']}]->(Apollo13), (RonH)-[:DIRECTED]->(Apollo13) CREATE (Twister:Movie {title:'Twister', released:1996, tagline:"Don't Breathe. Don't Look Back."}) CREATE (PhilipH:Person {name:'Philip Seymour Hoffman', born:1967}) CREATE (JanB:Person {name:'Jan de Bont', born:1943}) CREATE (BillPax)-[:ACTED_IN {roles:['Bill Harding']}]->(Twister), (HelenH)-[:ACTED_IN {roles:['Dr. Jo Harding']}]->(Twister), (ZachG)-[:ACTED_IN {roles:['Eddie']}]->(Twister), (PhilipH)-[:ACTED_IN {roles:['Dustin "Dusty" Davis']}]->(Twister), (JanB)-[:DIRECTED]->(Twister) CREATE (CastAway:Movie {title:'Cast Away', released:2000, tagline:'At the edge of the world, his journey begins.'}) CREATE (RobertZ:Person {name:'Robert Zemeckis', born:1951}) CREATE (TomH)-[:ACTED_IN {roles:['Chuck Noland']}]->(CastAway), (HelenH)-[:ACTED_IN {roles:['Kelly Frears']}]->(CastAway), (RobertZ)-[:DIRECTED]->(CastAway) CREATE (OneFlewOvertheCuckoosNest:Movie {title:"One Flew Over the Cuckoo's Nest", released:1975, tagline:"If he's crazy, what does that make you?"}) CREATE (MilosF:Person {name:'Milos Forman', born:1932}) CREATE (JackN)-[:ACTED_IN {roles:['Randle McMurphy']}]->(OneFlewOvertheCuckoosNest), (DannyD)-[:ACTED_IN {roles:['Martini']}]->(OneFlewOvertheCuckoosNest), (MilosF)-[:DIRECTED]->(OneFlewOvertheCuckoosNest) CREATE (SomethingsGottaGive:Movie {title:"Something's Gotta Give", released:2003}) CREATE (DianeK:Person {name:'Diane Keaton', born:1946}) CREATE (NancyM:Person {name:'Nancy Meyers', born:1949}) CREATE (JackN)-[:ACTED_IN {roles:['Harry Sanborn']}]->(SomethingsGottaGive), (DianeK)-[:ACTED_IN {roles:['Erica Barry']}]->(SomethingsGottaGive), (Keanu)-[:ACTED_IN {roles:['Julian Mercer']}]->(SomethingsGottaGive), (NancyM)-[:DIRECTED]->(SomethingsGottaGive), (NancyM)-[:PRODUCED]->(SomethingsGottaGive), (NancyM)-[:WROTE]->(SomethingsGottaGive) CREATE (BicentennialMan:Movie {title:'Bicentennial Man', released:1999, tagline:"One robot's 200 year journey to become an ordinary man."}) CREATE (ChrisC:Person {name:'Chris Columbus', born:1958}) CREATE (Robin)-[:ACTED_IN {roles:['Andrew Marin']}]->(BicentennialMan), (OliverP)-[:ACTED_IN {roles:['Rupert Burns']}]->(BicentennialMan), (ChrisC)-[:DIRECTED]->(BicentennialMan) CREATE (CharlieWilsonsWar:Movie {title:"Charlie Wilson's War", released:2007, tagline:"A stiff drink. A little mascara. A lot of nerve. Who said they couldn't bring down the Soviet empire."}) CREATE (JuliaR:Person {name:'Julia Roberts', born:1967}) CREATE (TomH)-[:ACTED_IN {roles:['Rep. Charlie Wilson']}]->(CharlieWilsonsWar), (JuliaR)-[:ACTED_IN {roles:['Joanne Herring']}]->(CharlieWilsonsWar), (PhilipH)-[:ACTED_IN {roles:['Gust Avrakotos']}]->(CharlieWilsonsWar), (MikeN)-[:DIRECTED]->(CharlieWilsonsWar) CREATE (ThePolarExpress:Movie {title:'The Polar Express', released:2004, tagline:'This Holiday Season... Believe'}) CREATE (TomH)-[:ACTED_IN {roles:['Hero Boy', 'Father', 'Conductor', 'Hobo', 'Scrooge', 'Santa Claus']}]->(ThePolarExpress), (RobertZ)-[:DIRECTED]->(ThePolarExpress) CREATE (ALeagueofTheirOwn:Movie {title:'A League of Their Own', released:1992, tagline:'Once in a lifetime you get a chance to do something different.'}) CREATE (Madonna:Person {name:'Madonna', born:1954}) CREATE (GeenaD:Person {name:'Geena Davis', born:1956}) CREATE (LoriP:Person {name:'Lori Petty', born:1963}) CREATE (PennyM:Person {name:'Penny Marshall', born:1943}) CREATE (TomH)-[:ACTED_IN {roles:['Jimmy Dugan']}]->(ALeagueofTheirOwn), (GeenaD)-[:ACTED_IN {roles:['Dottie Hinson']}]->(ALeagueofTheirOwn), (LoriP)-[:ACTED_IN {roles:['Kit Keller']}]->(ALeagueofTheirOwn), (RosieO)-[:ACTED_IN {roles:['Doris Murphy']}]->(ALeagueofTheirOwn), (Madonna)-[:ACTED_IN {roles:['"All the Way" Mae Mordabito']}]->(ALeagueofTheirOwn), (BillPax)-[:ACTED_IN {roles:['Bob Hinson']}]->(ALeagueofTheirOwn), (PennyM)-[:DIRECTED]->(ALeagueofTheirOwn) CREATE (PaulBlythe:Person {name:'Paul Blythe'}) CREATE (AngelaScope:Person {name:'Angela Scope'}) CREATE (JessicaThompson:Person {name:'Jessica Thompson'}) CREATE (JamesThompson:Person {name:'James Thompson'}) CREATE (JamesThompson)-[:FOLLOWS]->(JessicaThompson), (AngelaScope)-[:FOLLOWS]->(JessicaThompson), (PaulBlythe)-[:FOLLOWS]->(AngelaScope) CREATE (JessicaThompson)-[:REVIEWED {summary:'An amazing journey', rating:95}]->(CloudAtlas), (JessicaThompson)-[:REVIEWED {summary:'Silly, but fun', rating:65}]->(TheReplacements), (JamesThompson)-[:REVIEWED {summary:'The coolest football movie ever', rating:100}]->(TheReplacements), (AngelaScope)-[:REVIEWED {summary:'Pretty funny at times', rating:62}]->(TheReplacements), (JessicaThompson)-[:REVIEWED {summary:'Dark, but compelling', rating:85}]->(Unforgiven), (JessicaThompson)-[:REVIEWED {summary:"Slapstick redeemed only by the Robin Williams and Gene Hackman's stellar performances", rating:45}]->(TheBirdcage), (JessicaThompson)-[:REVIEWED {summary:'A solid romp', rating:68}]->(TheDaVinciCode), (JamesThompson)-[:REVIEWED {summary:'Fun, but a little far fetched', rating:65}]->(TheDaVinciCode), (JessicaThompson)-[:REVIEWED {summary:'You had me at Jerry', rating:92}]->(JerryMaguire) WITH TomH as a MATCH (a)-[:ACTED_IN]->(m)<-[:DIRECTED]-(d) RETURN a,m,d LIMIT 10; ================================================ FILE: neo4j-cypher-dsl-examples/pom.xml ================================================ 4.0.0 org.neo4j neo4j-cypher-dsl-parent ${revision}${sha1}${changelist} neo4j-cypher-dsl-examples pom Neo4j Cypher DSL (Examples) neo4j-cypher-dsl-examples-core neo4j-cypher-dsl-examples-parser neo4j-cypher-dsl-examples-ogm-quarkus neo4j-cypher-dsl-examples-sdn6 org.neo4j.cypherdsl.examples org.apache.maven.plugins maven-enforcer-plugin enforce enforce validate ${maven.compiler.release} ${maven.version} org.apache.maven.plugins maven-jar-plugin true org.apache.maven.plugins maven-install-plugin true org.apache.maven.plugins maven-deploy-plugin true ================================================ FILE: neo4j-cypher-dsl-native-tests/README.adoc ================================================ == Native tests. This is a sample project to ensure that the Cypher-DSL works with GraalVM native. ================================================ FILE: neo4j-cypher-dsl-native-tests/pom.xml ================================================ 4.0.0 org.neo4j neo4j-cypher-dsl-parent ${revision}${sha1}${changelist} neo4j-cypher-dsl-native-tests Neo4j Cypher DSL (Native Tests) Ensures Cypher DSL can be compiled and used natively. org.neo4j.cypherdsl.graalvm 1.1.0 org.neo4j neo4j-cypher-dsl-bom ${project.version} pom import com.querydsl querydsl-core org.neo4j neo4j-cypher-dsl-parser org.assertj assertj-core test org.junit.jupiter junit-jupiter test org.jacoco jacoco-maven-plugin true org.graalvm.buildtools native-maven-plugin ${native-maven-plugin.version} compile-no-fork package org.neo4j.cypherdsl.graalvm.Application application --no-fallback -H:+ReportExceptionStackTraces org.apache.maven.plugins maven-jar-plugin true ${java-module-name} org.apache.maven.plugins maven-install-plugin true org.apache.maven.plugins maven-deploy-plugin true org.apache.maven.plugins maven-failsafe-plugin integration-test verify org.apache.maven.plugins maven-javadoc-plugin true ================================================ FILE: neo4j-cypher-dsl-native-tests/src/main/java/org/neo4j/cypherdsl/graalvm/Application.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.graalvm; import java.util.Comparator; import java.util.Set; import java.util.stream.Collectors; import com.querydsl.core.types.dsl.Expressions; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Statement; import org.neo4j.cypherdsl.core.SymbolicName; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Dialect; import org.neo4j.cypherdsl.core.renderer.Renderer; import org.neo4j.cypherdsl.parser.CypherParser; import org.neo4j.cypherdsl.parser.Options; /** * A random application which will be complied into a native binary. The testing will * check the expected output. * * @author Michael J. Simons */ public final class Application { private static final Renderer cypherRenderer = Renderer.getDefaultRenderer(); private Application() { } @SuppressWarnings("checkstyle:regexp") public static void main(String... a) { System.out.println(cypherRenderer.render(findAllMovies())); var statement = generateQueryWithParams(); var catalog = statement.getCatalog(); catalog.getParameters().forEach((k, v) -> System.out.println(k + "=" + v)); catalog.getParameterNames().forEach(System.out::println); System.out.println(cypherRenderer.render(statement)); System.out.println(cypherRenderer.render(generateComplexQuery())); System.out.println(CypherParser.parse("MATCH (p:Parser) RETURN p").getCypher()); try { // noinspection ResultOfMethodCallIgnored Cypher.returning((Expression) null); } catch (Exception ex) { System.out.println(ex.getMessage()); } System.out.println(useParserForRewrite()); System.out.println(generateDialectBasedQuery()); statement = CypherParser.parseStatement( "MATCH (m:Movie {title: 'The Matrix'}) <- [a:ACTED_IN] - (p:Person) WHERE p.born >= 1979 RETURN m, a, p"); catalog = statement.getCatalog(); catalog.getIdentifiableExpressions() .stream() .filter(SymbolicName.class::isInstance) .map(SymbolicName.class::cast) .map(SymbolicName::getValue) .sorted() .forEach(System.out::println); catalog.getAllPropertyFilters() .entrySet() .stream() .sorted(Comparator.comparing(o -> o.getKey().name())) .forEach(e -> System.out.println(e.getKey().name() + ": " + e.getValue().stream().limit(1).map(cc -> cc.right().toString()).collect(Collectors.joining()))); System.out.println( Cypher.returning(Cypher.adapt(Expressions.TRUE.isTrue().and(Expressions.FALSE.isTrue())).asExpression()) .build() .getCypher()); } private static Statement findAllMovies() { var m = Cypher.node("Movie").named("m"); return Cypher.match(m).returning(m).build(); } private static Statement generateQueryWithParams() { var m = Cypher.node("Movie").named("m"); return Cypher.match(m) .where(m.property("title").isEqualTo(Cypher.parameter("title"))) .or(m.property("title").isEqualTo(Cypher.parameter("pTitle", "someTitle"))) .or(m.property("title").isEqualTo(Cypher.anonParameter("someOtherTitle"))) .returning(m) .build(); } private static Statement generateComplexQuery() { var person = Cypher.node("Person").named("person"); var location = Cypher.node("Location").named("personLivesIn"); return Cypher.match(person) .returning(person.project("livesIn", Cypher.subList( Cypher.listBasedOn(person.relationshipTo(location, "LIVES_IN")) .returning(location.project("name")), Cypher.parameter("personLivedInOffset"), Cypher.parameter("personLivedInOffset").add(Cypher.parameter("personLivedInFirst"))))) .build(); } private static String useParserForRewrite() { return CypherParser .parseStatement("MATCH (p:Person) -[:HAT_GESPIELT_IN] -> (n:Movie) RETURN n", Options.newOptions() .withTypeFilter( (e, t) -> ((t.size() == 1) && t.contains("HAT_GESPIELT_IN")) ? Set.of("ACTED_IN") : t) .build()) .getCypher(); } private static String generateDialectBasedQuery() { Node n = Cypher.anyNode("n"); Renderer renderer = Renderer.getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_5).build()); return renderer.render(Cypher.match(n).returning(Cypher.distance(n.property("a"), n.property("b"))).build()); } } ================================================ FILE: neo4j-cypher-dsl-native-tests/src/main/java/org/neo4j/cypherdsl/graalvm/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * GraalVM native image testing. */ package org.neo4j.cypherdsl.graalvm; ================================================ FILE: neo4j-cypher-dsl-native-tests/src/test/java/org/neo4j/cypherdsl/graalvm/NativeApplicationIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.graalvm; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.UncheckedIOException; import java.nio.file.Paths; import java.util.LinkedHashSet; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * This test is run via failsafe after GraalVM native image has build the application. * * @author Michael J. Simons * */ class NativeApplicationIT { @Test void outputOfNativeBinaryShouldMatchExpectations() throws IOException, ExecutionException, InterruptedException { var statements = List.of("MATCH (m:`Movie`) RETURN m", "pTitle=someTitle", "pcdsl01=someOtherTitle", "pTitle", "pcdsl01", "title", "MATCH (m:`Movie`) WHERE (m.title = $title OR m.title = $pTitle OR m.title = $pcdsl01) RETURN m", "MATCH (person:`Person`) RETURN person{livesIn: [(person)-[:`LIVES_IN`]->(personLivesIn:`Location`) | personLivesIn{.name}][$personLivedInOffset..($personLivedInOffset + $personLivedInFirst)]}", "MATCH (p:`Parser`) RETURN p", "At least one expressions to return is required.", "MATCH (p:`Person`)-[:`ACTED_IN`]->(n:`Movie`) RETURN n", "MATCH (n) RETURN point.distance(n.a, n.b)", "a", "m", "p", "born: NumberLiteral{cypher=1979}", "title: StringLiteral{cypher='The Matrix'}", "RETURN true = true AND false = true"); var p = new ProcessBuilder(Paths.get(".", "target", "application").toAbsolutePath().normalize().toString()) .start(); p.onExit().thenAccept(done -> { try (var in = new BufferedReader(new InputStreamReader(done.getInputStream()))) { var generatedStatements = in.lines().collect(Collectors.toCollection(LinkedHashSet::new)); assertThat(generatedStatements).containsExactlyElementsOf(statements); } catch (IOException ex) { throw new UncheckedIOException(ex); } }).get(); } } ================================================ FILE: neo4j-cypher-dsl-parser/NOTICE.txt ================================================ Copyright (c) "Neo4j" Neo4j Sweden AB [http://neo4j.com] Neo4j is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. Full license texts are found in LICENSES.txt. Third-party licenses -------------------- Apache Software License, Version 2.0 Scala Library ================================================ FILE: neo4j-cypher-dsl-parser/README.adoc ================================================ = TCK for Cypher-DSL Parser Most of the examples / test cases here use the default renderer that always escapes all names. This is unrelated to the actual parsing and can be turned of when rendering the Cypher-DSL-AST. == Nodes Nodes can be parsed and used in constructions of queries for example. They are probably one of the most useful elements. Parse them via: [source,java,indent=0,tabsize=4] ---- import org.neo4j.cypherdsl.parser.CypherParser; public class Demo { public static void main(String...a) { var node = CypherParser.parseNode("(m:Movie)"); } } ---- [[nodes-input]] .Input [source,cypher] ---- () (:`A`) (:A) (:A:B) (:A:`B`:C) (m) (m:Movie) (m {a:'b'}) (m {a:'b', c: 'd'}) ---- They will look like this when rendered with the default renderer: [[nodes-output]] .Output [source,cypher] ---- () (:`A`) (:`A`) (:`A`:`B`) (:`A`:`B`:`C`) (m) (m:`Movie`) (m {a: 'b'}) (m {a: 'b', c: 'd'}) ---- == Clauses Clauses can be parsed like this: [source,java,indent=0,tabsize=4] ---- import java.util.List; import org.neo4j.cypherdsl.core.Statement; import org.neo4j.cypherdsl.core.renderer.Renderer; import org.neo4j.cypherdsl.parser.CypherParser; public class Demo { public static void main(String... a) { var clause = CypherParser.parseClause("MATCH (tom:Person {name: \"Tom Hanks\"})-[:ACTED_IN]->(tomHanksMovies)"); var cypher = Renderer.getDefaultRenderer().render(Statement.of(List.of(clause))); System.out.println(cypher); } } ---- These are the supported clauses: [[clauses-input]] .Input [source,cypher] ---- MATCH (tom {name: "Tom Hanks"}) MATCH (tom:Person {name: "Tom Hanks"})-[:ACTED_IN]->(tomHanksMovies) MATCH (n:Movie), (m:Person) DELETE n DETACH DELETE n RETURN n RETURN n ORDER by n.name RETURN n ORDER by n.name desc RETURN n ORDER by n.name SKIP 5 RETURN n ORDER by n.name SKIP 5 LIMIT 10 RETURN n ORDER by n.name, n.firstName SKIP 5 LIMIT 10 RETURN n.name AS name, n.firstName as vorname ORDER by n.name, n.firstName SKIP 5 LIMIT 10 RETURN distinct n RETURN collect(n) CREATE (m:Movie) CREATE (m:Movie {title: "A title"}) CREATE (a:Person) -[:ACTED_IN] -> (m:Movie {title: "A title"}) MERGE (m:Movie) MERGE (m:Movie {title: "A title"}) MERGE (a:Person) -[:ACTED_IN] -> (m:Movie {title: "A title"}) WITH a WITH a WHERE a.name = 'Michael' WITH a ORDER by n.name, n.firstName desc SKIP 5 LIMIT 10 WHERE a.name = 'Michael' ---- [[clauses-output]] .Rendered output [source,cypher] ---- MATCH (tom {name: 'Tom Hanks'}) MATCH (tom:`Person` {name: 'Tom Hanks'})-[:`ACTED_IN`]->(tomHanksMovies) MATCH (n:`Movie`), (m:`Person`) DELETE n DETACH DELETE n RETURN n RETURN n ORDER BY n.name ASC RETURN n ORDER BY n.name DESC RETURN n ORDER BY n.name ASC SKIP 5 RETURN n ORDER BY n.name ASC SKIP 5 LIMIT 10 RETURN n ORDER BY n.name ASC, n.firstName ASC SKIP 5 LIMIT 10 RETURN n.name AS name, n.firstName AS vorname ORDER BY n.name ASC, n.firstName ASC SKIP 5 LIMIT 10 RETURN DISTINCT n RETURN collect(n) CREATE (m:`Movie`) CREATE (m:`Movie` {title: 'A title'}) CREATE (a:`Person`)-[:`ACTED_IN`]->(m:`Movie` {title: 'A title'}) MERGE (m:`Movie`) MERGE (m:`Movie` {title: 'A title'}) MERGE (a:`Person`)-[:`ACTED_IN`]->(m:`Movie` {title: 'A title'}) WITH a WITH a WHERE a.name = 'Michael' WITH a ORDER BY n.name ASC, n.firstName DESC SKIP 5 LIMIT 10 WHERE a.name = 'Michael' ---- == Whole queries Of course, you can parse a query into a Cypher-DSL statement. Such a statement could be combined with another statement into `UNION` query or be used as a subquery. Statements can be parsed using `parse` or `parseStatement`. Like any other `parseXXX` method in the parser, additional `Options` might be supplied as well. [source,java,indent=0,tabsize=4] ---- import java.util.List; import org.neo4j.cypherdsl.core.Statement; import org.neo4j.cypherdsl.parser.CypherParser; public class Demo { public static void main(String... a) { var statement = CypherParser.parse("MATCH (movie:Movie) RETURN movie.title"); var cypher = statement.getCypher(); System.out.println(cypher); } } ---- [[statements-input]] .Input [source,cypher,separated=true] ---- MATCH (n) RETURN n; MATCH (movie:Movie) RETURN movie.title; MATCH (director {name: 'Oliver Stone'})--(movie) RETURN movie.title; MATCH (wallstreet {title: 'Wall Street'})<-[:ACTED_IN|:DIRECTED]-(person) RETURN person.name; MATCH (charlie:Person {name: 'Charlie Sheen'}), (rob:Person {name: 'Rob Reiner'}) CREATE (rob)-[:`TYPE INCLUDING A SPACE`]->(charlie); MATCH (n {name: 'Andy'}) SET n.surname = 'Taylor' RETURN n.name, n.surname; MATCH (n {name: 'Andy'}) SET (CASE WHEN n.age = 36 THEN n END).worksIn = 'Malmo' RETURN n.name, n.worksIn; MATCH (at {name: 'Andy'}), (pn {name: 'Peter'}) SET at = pn RETURN at.name, at.age, at.hungry, pn.name, pn.age; MATCH (n) RETURN CASE WHEN n.eyes = 'blue' THEN 1 WHEN n.age < 40 THEN 2 ELSE 3 END AS result; MATCH (actor:Person {name: 'Charlie Sheen'})-[:ACTED_IN]->(movie:Movie) RETURN actor{.name, .realName, movies: collect(movie{.title, .year})}; MATCH (actor:Person)-[:ACTED_IN]->(movie:Movie) WITH actor, count(movie) AS nbrOfMovies RETURN actor{.name, nbrOfMovies}; MATCH (actor:Person {name: 'Charlie Sheen'}) RETURN actor{.*, .age}; MATCH (p {name: 'Peter'}) SET p = {name: 'Peter Smith', position: 'Entrepreneur'} RETURN p.name, p.age, p.position; MATCH (p {name: 'Peter'}) SET p += {age: 38, hungry: true, position: 'Entrepreneur'} RETURN p.name, p.age, p.hungry, p.position; MATCH (p {name: 'Peter'}) SET p += {} RETURN p.name, p.age; MATCH (n {name: 'Andy'}) SET n.position = 'Developer', n.surname = 'Taylor'; MATCH (n {name: 'Andy'}) SET n.surname = $surname RETURN n.name, n.surname; MATCH (n {name: 'Stefan'}) SET n:German RETURN n.name, labels(n) AS labels; MATCH (n {name: 'George'}) SET n:Swedish:Bossman RETURN n.name, labels(n) AS labels; MATCH (a {name: 'Andy'}) REMOVE a.age RETURN a.name, a.age; MATCH (n {name: 'Peter'}) REMOVE n:German RETURN n.name, labels(n); MATCH (n {name: 'Peter'}) REMOVE n:German:Swedish RETURN n.name, labels(n); MATCH (n:Actor) RETURN n.name AS name UNION ALL MATCH (n:Movie) RETURN n.title AS name; MATCH (n:Actor) RETURN n.name AS name UNION MATCH (n:Movie) RETURN n.title AS name; UNWIND [1, 2, 3, null] AS x RETURN x, 'val' AS y; WITH [1, 1, 2, 2] AS coll UNWIND coll AS x WITH DISTINCT x RETURN collect(x) AS setOfVals; WITH [1, 2] AS a, [3, 4] AS b UNWIND (a + b) AS x RETURN x; WITH [[1, 2], [3, 4], 5] AS nested UNWIND nested AS x UNWIND x AS y RETURN y; UNWIND [] AS empty RETURN empty, 'literal_that_is_not_returned'; MATCH p=(start)-[*]->(finish) WHERE start.name = 'A' AND finish.name = 'D' RETURN p; MATCH (person:Person) WHERE person.firstname STARTS WITH 'And' RETURN person; CALL db.labels(); CALL db.labels; CALL dbms.procedures() YIELD name, signature WHERE name='dbms.listConfig' RETURN signature; CALL `db`.`labels`(); CALL dbms.security.createUser('example_username', 'example_password', false); CALL dbms.security.createUser($username, $password, $requirePasswordChange); CALL db.labels() YIELD *; CALL db.labels() YIELD label RETURN count(label) AS numLabels; CALL db.labels() YIELD label WHERE label CONTAINS 'User' RETURN count(label) AS numLabels; CALL db.propertyKeys() YIELD propertyKey AS prop MATCH (n) WHERE n[prop] IS NOT NULL RETURN prop, count(n) AS numNodes; MERGE (keanu:Person {name: 'Keanu Reeves'}) ON CREATE SET keanu.created = timestamp() RETURN keanu.name, keanu.created; MERGE (person:Person) ON MATCH SET person.found = true RETURN person.name, person.found; MERGE (keanu:Person {name: 'Keanu Reeves'}) ON CREATE SET keanu.created = timestamp() ON MATCH SET keanu.lastSeen = timestamp() RETURN keanu.name, keanu.created, keanu.lastSeen; MERGE (person:Person) ON MATCH SET person.found = true, person.lastAccessed = timestamp() RETURN person.name, person.found, person.lastAccessed; MERGE (person:Person) ON CREATE SET person.created = timestamp() ON MATCH SET person.found = true, person.lastAccessed = timestamp() RETURN person.name, person.found, person.lastAccessed; MATCH (charlie:Person {name: 'Charlie Sheen'}), (wallStreet:Movie {title: 'Wall Street'}) MERGE (charlie)-[r:ACTED_IN]->(wallStreet) RETURN charlie.name, type(r), wallStreet.title; MATCH (oliver:Person {name: 'Oliver Stone'}), (reiner:Person {name: 'Rob Reiner'}) MERGE (oliver)-[:DIRECTED]->(movie:Movie)<-[:ACTED_IN]-(reiner) RETURN movie; MATCH (charlie:Person {name: 'Charlie Sheen'}), (oliver:Person {name: 'Oliver Stone'}) MERGE (charlie)-[r:KNOWS]-(oliver) RETURN r; MATCH (person:Person) MERGE (city:City {name: person.bornIn}) MERGE (person)-[r:BORN_IN]->(city) RETURN person.name, person.bornIn, city; MATCH (person:Person) MERGE (person)-[r:HAS_CHAUFFEUR]->(chauffeur:Chauffeur {name: person.chauffeurName}) RETURN person.name, person.chauffeurName, chauffeur; MATCH p = (a)-->(b)-->(c) WHERE a.name = 'Alice' AND b.name = 'Bob' AND c.name = 'Daniel' RETURN reduce(totalAge = 0, n IN nodes(p) | totalAge + n.age) AS reduction; MATCH (p:Person)-[r:IS_FRIENDS_WITH]->(friend:Person) WHERE exists((p)-[:WORKS_FOR]->(:Company {name: 'Neo4j'})) RETURN p, r, friend; MATCH (p:Person)-[r:IS_FRIENDS_WITH]->(friend:Person) WHERE EXISTS { MATCH (p)-[:WORKS_FOR]->(:Company {name: 'Neo4j'}) } RETURN p, r, friend; MATCH (person:Person)-[:WORKS_FOR]->(company) WHERE company.name STARTS WITH "Company" AND EXISTS { MATCH (person)-[:LIKES]->(t:Technology) WHERE size((t)<-[:LIKES]-()) >= 3 } RETURN person.name as person, company.name AS company; CALL { MATCH (p:Person)-[:LIKES]->(:Technology {type: "Java"}) RETURN p UNION MATCH (p:Person) WHERE size((p)-[:IS_FRIENDS_WITH]->()) > 1 RETURN p } RETURN p.name AS person, p.birthdate AS dob ORDER BY dob DESC; MATCH p=(start)-[*]->(finish) WHERE start.name = 'A' AND finish.name = 'D' FOREACH (n IN nodes(p) | SET n.marked = true); MATCH (a) WHERE a.name = 'Eskil' RETURN a.array, [x IN a.array WHERE size(x)= 3]; MATCH p =(a)-->(b)-->(c) WHERE a.name = 'Alice' AND b.name = 'Bob' AND c.name = 'Daniel' RETURN [n IN nodes(p) | n.age] AS extracted; call apoc.cypher.run("CALL apoc.cypher.run('CALL apoc.cypher.run(\"CALL apoc.cypher.run(\\'CALL apoc.cypher.run(\\\\\"RETURN true\\\\\", {}) YIELD value RETURN value\\', {}) YIELD value RETURN value\", {}) YIELD value RETURN value', {}) YIELD value RETURN value", {}) YIELD value RETURN value; WITH 1 as year WHERE 2010 <= year <= 2020 RETURN *; MATCH (person:Person) WHERE COUNT { (person)-[:HAS_DOG]->(:Dog) } > 1 RETURN person.name AS name; MATCH (person:Person) WHERE COUNT { (person)-[:HAS_DOG]->(d:Dog) WHERE d.name = 'Lassie' } > 1 RETURN person.name AS name; MATCH (person:Person) RETURN person.name, COUNT { (person)-[:HAS_DOG]->(:Dog) } as howManyDogs; ---- [[statements-output]] .Output [source,cypher] ---- MATCH (n) RETURN n MATCH (movie:`Movie`) RETURN movie.title MATCH (director {name: 'Oliver Stone'})--(movie) RETURN movie.title MATCH (wallstreet {title: 'Wall Street'})<-[:`ACTED_IN`|`DIRECTED`]-(person) RETURN person.name MATCH (charlie:`Person` {name: 'Charlie Sheen'}), (rob:`Person` {name: 'Rob Reiner'}) CREATE (rob)-[:`TYPE INCLUDING A SPACE`]->(charlie) MATCH (n {name: 'Andy'}) SET n.surname = 'Taylor' RETURN n.name, n.surname MATCH (n {name: 'Andy'}) SET (CASE WHEN n.age = 36 THEN n END).worksIn = 'Malmo' RETURN n.name, n.worksIn MATCH (at {name: 'Andy'}), (pn {name: 'Peter'}) SET at = pn RETURN at.name, at.age, at.hungry, pn.name, pn.age MATCH (n) RETURN CASE WHEN n.eyes = 'blue' THEN 1 WHEN n.age < 40 THEN 2 ELSE 3 END AS result MATCH (actor:`Person` {name: 'Charlie Sheen'})-[:`ACTED_IN`]->(movie:`Movie`) RETURN actor{.name, .realName, movies: collect(movie{.title, .year})} MATCH (actor:`Person`)-[:`ACTED_IN`]->(movie:`Movie`) WITH actor, count(movie) AS nbrOfMovies RETURN actor{.name, nbrOfMovies} MATCH (actor:`Person` {name: 'Charlie Sheen'}) RETURN actor{.*, .age} MATCH (p {name: 'Peter'}) SET p = {name: 'Peter Smith', position: 'Entrepreneur'} RETURN p.name, p.age, p.position MATCH (p {name: 'Peter'}) SET p += {age: 38, hungry: true, position: 'Entrepreneur'} RETURN p.name, p.age, p.hungry, p.position MATCH (p {name: 'Peter'}) SET p += {} RETURN p.name, p.age MATCH (n {name: 'Andy'}) SET n.position = 'Developer', n.surname = 'Taylor' MATCH (n {name: 'Andy'}) SET n.surname = $surname RETURN n.name, n.surname MATCH (n {name: 'Stefan'}) SET n:`German` RETURN n.name, labels(n) AS labels MATCH (n {name: 'George'}) SET n:`Swedish`:`Bossman` RETURN n.name, labels(n) AS labels MATCH (a {name: 'Andy'}) REMOVE a.age RETURN a.name, a.age MATCH (n {name: 'Peter'}) REMOVE n:`German` RETURN n.name, labels(n) MATCH (n {name: 'Peter'}) REMOVE n:`German`:`Swedish` RETURN n.name, labels(n) MATCH (n:`Actor`) RETURN n.name AS name UNION ALL MATCH (n:`Movie`) RETURN n.title AS name MATCH (n:`Actor`) RETURN n.name AS name UNION MATCH (n:`Movie`) RETURN n.title AS name UNWIND [1, 2, 3, NULL] AS x RETURN x, 'val' AS y WITH [1, 1, 2, 2] AS coll UNWIND coll AS x WITH DISTINCT x RETURN collect(x) AS setOfVals WITH [1, 2] AS a, [3, 4] AS b UNWIND (a + b) AS x RETURN x WITH [[1, 2], [3, 4], 5] AS nested UNWIND nested AS x UNWIND x AS y RETURN y UNWIND [] AS empty RETURN empty, 'literal_that_is_not_returned' MATCH p = (start)-[*]->(finish) WHERE (start.name = 'A' AND finish.name = 'D') RETURN p MATCH (person:`Person`) WHERE person.firstname STARTS WITH 'And' RETURN person CALL db.labels() CALL db.labels() CALL dbms.procedures() YIELD name, signature WHERE name = 'dbms.listConfig' RETURN signature CALL db.labels() CALL dbms.security.createUser('example_username', 'example_password', false) CALL dbms.security.createUser($username, $password, $requirePasswordChange) CALL db.labels() YIELD * CALL db.labels() YIELD label RETURN count(label) AS numLabels CALL db.labels() YIELD label WHERE label CONTAINS 'User' RETURN count(label) AS numLabels CALL db.propertyKeys() YIELD propertyKey AS prop MATCH (n) WHERE n[prop] IS NOT NULL RETURN prop, count(n) AS numNodes MERGE (keanu:`Person` {name: 'Keanu Reeves'}) ON CREATE SET keanu.created = timestamp() RETURN keanu.name, keanu.created MERGE (person:`Person`) ON MATCH SET person.found = true RETURN person.name, person.found MERGE (keanu:`Person` {name: 'Keanu Reeves'}) ON CREATE SET keanu.created = timestamp() ON MATCH SET keanu.lastSeen = timestamp() RETURN keanu.name, keanu.created, keanu.lastSeen MERGE (person:`Person`) ON MATCH SET person.found = true, person.lastAccessed = timestamp() RETURN person.name, person.found, person.lastAccessed MERGE (person:`Person`) ON CREATE SET person.created = timestamp() ON MATCH SET person.found = true, person.lastAccessed = timestamp() RETURN person.name, person.found, person.lastAccessed MATCH (charlie:`Person` {name: 'Charlie Sheen'}), (wallStreet:`Movie` {title: 'Wall Street'}) MERGE (charlie)-[r:`ACTED_IN`]->(wallStreet) RETURN charlie.name, type(r), wallStreet.title MATCH (oliver:`Person` {name: 'Oliver Stone'}), (reiner:`Person` {name: 'Rob Reiner'}) MERGE (oliver)-[:`DIRECTED`]->(movie:`Movie`)<-[:`ACTED_IN`]-(reiner) RETURN movie MATCH (charlie:`Person` {name: 'Charlie Sheen'}), (oliver:`Person` {name: 'Oliver Stone'}) MERGE (charlie)-[r:`KNOWS`]-(oliver) RETURN r MATCH (person:`Person`) MERGE (city:`City` {name: person.bornIn}) MERGE (person)-[r:`BORN_IN`]->(city) RETURN person.name, person.bornIn, city MATCH (person:`Person`) MERGE (person)-[r:`HAS_CHAUFFEUR`]->(chauffeur:`Chauffeur` {name: person.chauffeurName}) RETURN person.name, person.chauffeurName, chauffeur MATCH p = (a)-->(b)-->(c) WHERE (a.name = 'Alice' AND b.name = 'Bob' AND c.name = 'Daniel') RETURN reduce(totalAge = 0, n IN nodes(p) | (totalAge + n.age)) AS reduction MATCH (p:`Person`)-[r:`IS_FRIENDS_WITH`]->(friend:`Person`) WHERE exists((p)-[:`WORKS_FOR`]->(:`Company` {name: 'Neo4j'})) RETURN p, r, friend MATCH (p:`Person`)-[r:`IS_FRIENDS_WITH`]->(friend:`Person`) WHERE EXISTS { MATCH (p)-[:`WORKS_FOR`]->(:`Company` {name: 'Neo4j'}) } RETURN p, r, friend MATCH (person:`Person`)-[:`WORKS_FOR`]->(company) WHERE (company.name STARTS WITH 'Company' AND EXISTS { MATCH (person)-[:`LIKES`]->(t:`Technology`) WHERE size((t)<-[:`LIKES`]-()) >= 3 }) RETURN person.name AS person, company.name AS company CALL {MATCH (p:`Person`)-[:`LIKES`]->(:`Technology` {type: 'Java'}) RETURN p UNION MATCH (p:`Person`) WHERE size((p)-[:`IS_FRIENDS_WITH`]->()) > 1 RETURN p} RETURN p.name AS person, p.birthdate AS dob ORDER BY dob DESC MATCH p = (start)-[*]->(finish) WHERE (start.name = 'A' AND finish.name = 'D') FOREACH (n IN nodes(p) | SET n.marked = true) MATCH (a) WHERE a.name = 'Eskil' RETURN a.array, [x IN a.array WHERE size(x) = 3] MATCH p = (a)-->(b)-->(c) WHERE (a.name = 'Alice' AND b.name = 'Bob' AND c.name = 'Daniel') RETURN [n IN nodes(p) | n.age] AS extracted CALL apoc.cypher.run('CALL apoc.cypher.run(\'CALL apoc.cypher.run(\"CALL apoc.cypher.run(\\\'CALL apoc.cypher.run(\\\\\"RETURN true\\\\\", {}) YIELD value RETURN value\\\', {}) YIELD value RETURN value\", {}) YIELD value RETURN value\', {}) YIELD value RETURN value', {}) YIELD value RETURN value WITH 1 AS year WHERE (2010 <= year AND year <= 2020) RETURN * MATCH (person:`Person`) WHERE COUNT { (person)-[:`HAS_DOG`]->(:`Dog`) } > 1 RETURN person.name AS name MATCH (person:`Person`) WHERE COUNT { (person)-[:`HAS_DOG`]->(d:`Dog`) WHERE d.name = 'Lassie' } > 1 RETURN person.name AS name MATCH (person:`Person`) RETURN person.name, COUNT { (person)-[:`HAS_DOG`]->(:`Dog`) } AS howManyDogs ---- === Hints You can also use hints: [[statements-input-hints]] .Output [source,cypher,separated=true] ---- MATCH (s:Scientist {born: 1850})-[:RESEARCHED]->(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) RETURN *; MATCH (s:Scientist {born: 1850})-[:RESEARCHED]->(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) USING INDEX p:Pioneer(born) RETURN *; MATCH (s:Scientist {born: 1850})-[:RESEARCHED]->(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) USING INDEX i:INVENTED_BY(year) RETURN *; MATCH (s:Scientist {born: 1850})-[:RESEARCHED]->(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) USING INDEX s:Scientist(born) USING INDEX cc:Country(formed) RETURN *; MATCH (s:Scientist {born: 1850})-[:RESEARCHED]->(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) USING SCAN s:Scientist RETURN *; MATCH (s:Scientist {born: 1850})-[:RESEARCHED]->(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) USING SCAN i:INVENTED_BY RETURN *; MATCH (s:Scientist {born: 1850})-[:RESEARCHED]->(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) USING INDEX s:Scientist(born) USING INDEX cc:Country(formed) USING JOIN ON p RETURN *; MATCH (s:Scientist {born: 1850}) OPTIONAL MATCH (s)-[:RESEARCHED]->(sc:Science) USING JOIN ON s RETURN *; ---- [[statements-output-hints]] .Output [source,cypher] ---- MATCH (s:`Scientist` {born: 1850})-[:`RESEARCHED`]->(sc:`Science`)<-[i:`INVENTED_BY` {year: 560}]-(p:`Pioneer` {born: 525})-[:`LIVES_IN`]->(c:`City`)-[:`PART_OF`]->(cc:`Country` {formed: 411}) RETURN * MATCH (s:`Scientist` {born: 1850})-[:`RESEARCHED`]->(sc:`Science`)<-[i:`INVENTED_BY` {year: 560}]-(p:`Pioneer` {born: 525})-[:`LIVES_IN`]->(c:`City`)-[:`PART_OF`]->(cc:`Country` {formed: 411}) USING INDEX p:`Pioneer`(born) RETURN * MATCH (s:`Scientist` {born: 1850})-[:`RESEARCHED`]->(sc:`Science`)<-[i:`INVENTED_BY` {year: 560}]-(p:`Pioneer` {born: 525})-[:`LIVES_IN`]->(c:`City`)-[:`PART_OF`]->(cc:`Country` {formed: 411}) USING INDEX i:`INVENTED_BY`(year) RETURN * MATCH (s:`Scientist` {born: 1850})-[:`RESEARCHED`]->(sc:`Science`)<-[i:`INVENTED_BY` {year: 560}]-(p:`Pioneer` {born: 525})-[:`LIVES_IN`]->(c:`City`)-[:`PART_OF`]->(cc:`Country` {formed: 411}) USING INDEX s:`Scientist`(born) USING INDEX cc:`Country`(formed) RETURN * MATCH (s:`Scientist` {born: 1850})-[:`RESEARCHED`]->(sc:`Science`)<-[i:`INVENTED_BY` {year: 560}]-(p:`Pioneer` {born: 525})-[:`LIVES_IN`]->(c:`City`)-[:`PART_OF`]->(cc:`Country` {formed: 411}) USING SCAN s:`Scientist` RETURN * MATCH (s:`Scientist` {born: 1850})-[:`RESEARCHED`]->(sc:`Science`)<-[i:`INVENTED_BY` {year: 560}]-(p:`Pioneer` {born: 525})-[:`LIVES_IN`]->(c:`City`)-[:`PART_OF`]->(cc:`Country` {formed: 411}) USING SCAN i:`INVENTED_BY` RETURN * MATCH (s:`Scientist` {born: 1850})-[:`RESEARCHED`]->(sc:`Science`)<-[i:`INVENTED_BY` {year: 560}]-(p:`Pioneer` {born: 525})-[:`LIVES_IN`]->(c:`City`)-[:`PART_OF`]->(cc:`Country` {formed: 411}) USING INDEX s:`Scientist`(born) USING INDEX cc:`Country`(formed) USING JOIN ON p RETURN * MATCH (s:`Scientist` {born: 1850}) OPTIONAL MATCH (s)-[:`RESEARCHED`]->(sc:`Science`) USING JOIN ON s RETURN * ---- `USING PERIODIC` is not supported anymore since Neo4j 5 and Cypher-DSL switched to the 5.x parser starting with 2023.0.0. However, you can still use `LOAD CSV`: [[statements-input-hints-periodic]] .Output [source,cypher,alwaysEscape=false,separated=true] ---- LOAD CSV FROM 'file:///artists-with-escaped-char.csv' AS line CREATE (a:Artist {name: line[1], year: toInteger(line[2])}) RETURN a.name AS name, a.year AS year, size(a.name) AS size; LOAD CSV FROM 'file:///artists.csv' AS line RETURN linenumber() AS number, line; LOAD CSV FROM 'file:///artists.csv' AS line RETURN DISTINCT file() AS path; ---- [[statements-output-hints-periodic]] .Output [source,cypher,alwaysEscape=false] ---- LOAD CSV FROM 'file:///artists-with-escaped-char.csv' AS line CREATE (a:Artist {name: line[1], year: toInteger(line[2])}) RETURN a.name AS name, a.year AS year, size(a.name) AS size LOAD CSV FROM 'file:///artists.csv' AS line RETURN linenumber() AS number, line LOAD CSV FROM 'file:///artists.csv' AS line RETURN DISTINCT file() AS path ---- === Subqueries Executing subqueries requires at least Neo4j 4.0, but you can of course parse and modify those queries: [[statements-input-subqueries]] .Input [source,cypher,separated=true] ---- UNWIND [0, 1, 2] AS x CALL { WITH x RETURN x * 10 AS y } RETURN x, y; CALL { MATCH (p:Person) RETURN p ORDER BY p.age ASC LIMIT 1 UNION MATCH (p:Person) RETURN p ORDER BY p.age DESC LIMIT 1 } RETURN p.name, p.age ORDER BY p.name; CALL { MATCH (p:Person)-[:FRIEND_OF]->(other:Person) RETURN p, other UNION MATCH (p:Child)-[:CHILD_OF]->(other:Parent) RETURN p, other } RETURN DISTINCT p.name, count(other); MATCH (p:Person) CALL { UNWIND range(1, 5) AS i CREATE (c:Clone) RETURN count(c) AS numberOfClones } RETURN p.name, numberOfClones; UNWIND [0, 1, 2] AS x CALL { WITH x RETURN max(x) AS xMax } RETURN x, xMax; MATCH (person:`Person`) WHERE 'Ozzy' IN COLLECT { MATCH (person)-[:`HAS_DOG`]->(dog:`Dog`) RETURN dog.name } RETURN person.name AS name; ---- [[statements-output-subqueries]] .Output [source,cypher] ---- UNWIND [0, 1, 2] AS x CALL {WITH x RETURN (x * 10) AS y} RETURN x, y CALL {MATCH (p:`Person`) RETURN p ORDER BY p.age ASC LIMIT 1 UNION MATCH (p:`Person`) RETURN p ORDER BY p.age DESC LIMIT 1} RETURN p.name, p.age ORDER BY p.name ASC CALL {MATCH (p:`Person`)-[:`FRIEND_OF`]->(other:`Person`) RETURN p, other UNION MATCH (p:`Child`)-[:`CHILD_OF`]->(other:`Parent`) RETURN p, other} RETURN DISTINCT p.name, count(other) MATCH (p:`Person`) CALL {UNWIND range(1, 5) AS i CREATE (c:`Clone`) RETURN count(c) AS numberOfClones} RETURN p.name, numberOfClones UNWIND [0, 1, 2] AS x CALL {WITH x RETURN max(x) AS xMax} RETURN x, xMax MATCH (person:`Person`) WHERE 'Ozzy' IN COLLECT { MATCH (person)-[:`HAS_DOG`]->(dog:`Dog`) RETURN dog.name } RETURN person.name AS name ---- === Original test suite Some statements from the original test suite [[statements-input-originally-generated]] .Input [source,cypher] ---- MATCH (app:`Location` {uuid: $app_uuid})<-[:`PART_OF`*0..3]-(loc_start:`Location`), (loc_start)<-[:`IN`|`IN_ANALYTICS`]-(r:`Resume`) WITH DISTINCT r, loc_start, app MATCH (r)-[:`IN_COHORT_OF`]->(o:`Offer` {is_valid: true})-[:`IN`]->(app) WITH DISTINCT r, loc_start, app, o MATCH (o)-[:`FOR`]->(start_n:`ResumeNode`) WHERE id(start_n) IN $start_ids RETURN DISTINCT r, loc_start, app, o, start_n MATCH (b:`Bike`) WHERE (:`Person`)-[:`OWNS`]->(b) WITH b MATCH (o:`Person`)-[r:`OWNS`]->(b) RETURN b.f, r.x MATCH (o:`Person`)-[r:`OWNS`]->(b:`Bike`) WHERE (o)-[r]->(b) RETURN r MATCH (node:`Division`) WITH DISTINCT node WHERE NOT (node)-[:`IN`]->(:`Department`)-[:`INSIDE` {rel_property: true}]->(:`Department`)-[:`EMPLOYS`]->(:`Employee`) RETURN * MATCH (person:`Person`) WHERE (((person)-[:`A`]->() OR (person)-[:`B`]->()) AND (((person)-[:`C`]->() OR ((person)-[:`D`]->() AND (person)-[:`E`]->())) OR (person)-[:`F`]->())) RETURN person MATCH (node:`Node`) WITH DISTINCT node, false AS f, CASE WHEN node.ll IS NULL THEN node.l ELSE node.ll END AS l RETURN * CALL db.index.fulltext.queryNodes('livesearch', '*a*') YIELD node MATCH (g:`Group`)-[:`GROUPS`]->(a:`Asset`)<-[:`ON`]-(:`Deploy`)<-[:`SCHEDULED`]-(d:`Device`) WHERE a.asset_id = node.asset_id WITH DISTINCT collect(d{.sigfox_id, a}) AS assetdata RETURN assetdata CALL db.index.fulltext.queryNodes('livesearch', '*a*') YIELD node AS x MATCH (g:`Group`)-[:`GROUPS`]->(a:`Asset`)<-[:`ON`]-(:`Deploy`)<-[:`SCHEDULED`]-(d:`Device`) WHERE a.asset_id = x.asset_id WITH DISTINCT collect(d{.sigfox_id, a}) AS assetdata RETURN assetdata WITH $p AS nameOfIndex CALL db.index.fulltext.queryNodes(nameOfIndex, '*a*') YIELD node MATCH (g:`Group`)-[:`GROUPS`]->(a:`Asset`)<-[:`ON`]-(:`Deploy`)<-[:`SCHEDULED`]-(d:`Device`) WHERE a.asset_id = node.asset_id WITH DISTINCT collect(d{.sigfox_id, a}) AS assetdata RETURN assetdata MATCH (n:`Node`) WITH n WITH n CALL my.procedure() YIELD x WITH n RETURN n MERGE (p:`Person` {id: apoc.create.uuid()}) SET p.firstName = 'Michael', p.surname = 'Hunger' RETURN p MATCH (n) WITH n CALL db.labels() YIELD label WITH label RETURN count(label) AS numLabels MATCH (n) WITH n CALL foo() YIELD label WITH label RETURN count(label) AS numLabels RETURN [p = (n)-[:`LIKES`|`OWNS`*]->() | p] MATCH p = (michael {name: 'Michael Douglas'})-->() RETURN p MATCH (person:`Person`) RETURN person{livesIn: [(person)-[:`LIVES_IN`]->(personLivesIn:`Location`) | personLivesIn{.name}][0]} MATCH (a:`Person` {name: 'Keanu Reeves'}) RETURN [(a)--(b) | b.released] AS years MATCH (a:`Person` {name: 'Keanu Reeves'}) RETURN [(a)--(b) WHERE b:`Movie` | b.released] AS years MATCH (n:`Person`) RETURN n, [[(n)-[r_f1:`FOUNDED`]->(o1:`Organisation`) | [r_f1, o1]], [(n)-[r_e1:`EMPLOYED_BY`]->(o1) | [r_e1, o1]], [(n)-[r_l1:`LIVES_AT`]->(l1:`Location`) | [r_l1, l1, [[(l1)<-[r_l2:`LIVES_AT`]-(p2:`Person`) | [r_l2, p2]]]]]] ---- [[statements-output-originally-generated]] .Output [source,cypher] ---- MATCH (app:`Location` {uuid: $app_uuid})<-[:`PART_OF`*0..3]-(loc_start:`Location`), (loc_start)<-[:`IN`|`IN_ANALYTICS`]-(r:`Resume`) WITH DISTINCT r, loc_start, app MATCH (r)-[:`IN_COHORT_OF`]->(o:`Offer` {is_valid: true})-[:`IN`]->(app) WITH DISTINCT r, loc_start, app, o MATCH (o)-[:`FOR`]->(start_n:`ResumeNode`) WHERE id(start_n) IN $start_ids RETURN DISTINCT r, loc_start, app, o, start_n MATCH (b:`Bike`) WHERE (:`Person`)-[:`OWNS`]->(b) WITH b MATCH (o:`Person`)-[r:`OWNS`]->(b) RETURN b.f, r.x MATCH (o:`Person`)-[r:`OWNS`]->(b:`Bike`) WHERE (o)-[r]->(b) RETURN r MATCH (node:`Division`) WITH DISTINCT node WHERE NOT (node)-[:`IN`]->(:`Department`)-[:`INSIDE` {rel_property: true}]->(:`Department`)-[:`EMPLOYS`]->(:`Employee`) RETURN * MATCH (person:`Person`) WHERE (((person)-[:`A`]->() OR (person)-[:`B`]->()) AND ((person)-[:`C`]->() OR ((person)-[:`D`]->() AND (person)-[:`E`]->()) OR (person)-[:`F`]->())) RETURN person MATCH (node:`Node`) WITH DISTINCT node, false AS f, CASE WHEN node.ll IS NULL THEN node.l ELSE node.ll END AS l RETURN * CALL db.index.fulltext.queryNodes('livesearch', '*a*') YIELD node MATCH (g:`Group`)-[:`GROUPS`]->(a:`Asset`)<-[:`ON`]-(:`Deploy`)<-[:`SCHEDULED`]-(d:`Device`) WHERE a.asset_id = node.asset_id WITH DISTINCT collect(d{.sigfox_id, a}) AS assetdata RETURN assetdata CALL db.index.fulltext.queryNodes('livesearch', '*a*') YIELD node AS x MATCH (g:`Group`)-[:`GROUPS`]->(a:`Asset`)<-[:`ON`]-(:`Deploy`)<-[:`SCHEDULED`]-(d:`Device`) WHERE a.asset_id = x.asset_id WITH DISTINCT collect(d{.sigfox_id, a}) AS assetdata RETURN assetdata WITH $p AS nameOfIndex CALL db.index.fulltext.queryNodes(nameOfIndex, '*a*') YIELD node MATCH (g:`Group`)-[:`GROUPS`]->(a:`Asset`)<-[:`ON`]-(:`Deploy`)<-[:`SCHEDULED`]-(d:`Device`) WHERE a.asset_id = node.asset_id WITH DISTINCT collect(d{.sigfox_id, a}) AS assetdata RETURN assetdata MATCH (n:`Node`) WITH n WITH n CALL my.procedure() YIELD x WITH n RETURN n MERGE (p:`Person` {id: apoc.create.uuid()}) SET p.firstName = 'Michael', p.surname = 'Hunger' RETURN p MATCH (n) WITH n CALL db.labels() YIELD label WITH label RETURN count(label) AS numLabels MATCH (n) WITH n CALL foo() YIELD label WITH label RETURN count(label) AS numLabels RETURN [p = (n)-[:`LIKES`|`OWNS`*]->() | p] MATCH p = (michael {name: 'Michael Douglas'})-->() RETURN p MATCH (person:`Person`) RETURN person{livesIn: [(person)-[:`LIVES_IN`]->(personLivesIn:`Location`) | personLivesIn{.name}][0]} MATCH (a:`Person` {name: 'Keanu Reeves'}) RETURN [(a)--(b) | b.released] AS years MATCH (a:`Person` {name: 'Keanu Reeves'}) RETURN [(a)--(b) WHERE b:`Movie` | b.released] AS years MATCH (n:`Person`) RETURN n, [[(n)-[r_f1:`FOUNDED`]->(o1:`Organisation`) | [r_f1, o1]], [(n)-[r_e1:`EMPLOYED_BY`]->(o1) | [r_e1, o1]], [(n)-[r_l1:`LIVES_AT`]->(l1:`Location`) | [r_l1, l1, [[(l1)<-[r_l2:`LIVES_AT`]-(p2:`Person`) | [r_l2, p2]]]]]] ---- == Expressions You can parse expressions, too. They can be used to enrich queries in many places, for example as conditions or properties. === Usable datatypes [[expressions-input-datatypes]] .Input [source,cypher] ---- 1 -1 0XF 0xF -0xE 010 -010 1.1 3.14 6.022E23 6.022e+24.0 TRUE true True fAlse FALSE ---- [[expressions-output-datatypes]] .Output [source,cypher] ---- 1 -1 15 15 -14 8 -8 1.1 3.14 6.022E23 6.022E24 true true true false false ---- === Operators and conditions [[expressions-input-0]] .Input [source,cypher] ---- +1 a++b +-1 -1 --1 NOT true 2+2 2-2 2*2 2/2 2%2 2^2 n.f <> 1 n.f != 1 n.f = 1 n.f <= 1 n.f >= 1 n.f < 1 n.f > 1 n.f =~ '.*' n.f ends with "foo" n.f starts with 'foo' n.f contains 'foo' n.f is NULL actor{.name, .realName, movies: collect(movie{.title, .year})} l[1] l[1..2] [x IN range(0,10) WHERE x % 2 = 0 | x^3 ] [x IN range(0,10) WHERE x % 2 = 0 ] [x IN range(0,10) | x^3 ] [(a)-->(b) WHERE b:Movie | b.released] a xor b ---- [[expressions-output-0]] .Rendered output [source,cypher] ---- +1 (a + +b) +-1 -1 --1 NOT (true) (2 + 2) (2 - 2) (2 * 2) (2 / 2) (2 % 2) 2^2 n.f <> 1 n.f <> 1 n.f = 1 n.f <= 1 n.f >= 1 n.f < 1 n.f > 1 n.f =~ '.*' n.f ENDS WITH 'foo' n.f STARTS WITH 'foo' n.f CONTAINS 'foo' n.f IS NULL actor{.name, .realName, movies: collect(movie{.title, .year})} l[1] l[1..2] [x IN range(0, 10) WHERE (x % 2) = 0 | x^3] [x IN range(0, 10) WHERE (x % 2) = 0] [x IN range(0, 10) | x^3] [(a)-->(b) WHERE b:`Movie` | b.released] (a XOR b) ---- === Quantified path patterns Most queries in this test are from Finbar Goods blog post https://medium.com/neo4j/getting-from-denmark-hill-to-gatwick-airport-with-quantified-path-patterns-bed38da27ca1["Getting From Denmark Hill to Gatwick Airport With Quantified Path Patterns]. Finbar provides the UK Railway dataset at https://github.com/dogofbrian/ukrailway, in case you wanna load the data and follow the examples. [[qpp-input-qpp]] .Input [source,cypher,separated=true,alwaysEscape=false] ---- MATCH (:Station)--()<--(m WHERE m.departs > time('12:00'))-->()-[:NEXT]->(n) RETURN *; MATCH ((n:A)-[:R]->(:B) WHERE EXISTS { (n)-->+(:X) }){2,3}; MATCH (n:Station {name: 'London Euston'})<-[:CALLS_AT]-(s1:Stop) -[:NEXT]->(s2:Stop)-[:CALLS_AT]->(:Station {name: 'Coventry'}) <-[:CALLS_AT]-(s3:Stop)-[:NEXT]->(s4:Stop)-[:CALLS_AT]->(n) RETURN s1.departs+'-'+s2.departs AS outbound, s3.departs+'-'+s4.departs AS `return`; MATCH (:Station { name: 'Denmark Hill' })<-[:X]-(d:Stop) ((:Stop)-[:NEXT]->(:Stop)){1,3} (a:Stop)-[:Y]->(:Station { name: 'Clapham Junction' }) RETURN d.departs AS departureTime, a.arrives AS arrivalTime; match (:A) (()-[r:R]->()){2,3} (:B) return *; match (:A) (()-[r:R]->())+ (:B) return *; match (:A) (()-[r:R]->())* (:B) return *; MATCH p = ((person:`Person`)-[:`DIRECTED`]->(movie:`Movie`)) WHERE person.name = 'Walt Disney' RETURN p; MATCH ((:Station {name: 'Denmark Hill'})-[l:LINK]-(s:Station)){1,4} RETURN *; MATCH p = (:Station {name: 'Denmark Hill'}) (()-[link:LINK]-())+ (:Station {name: 'Gatwick Airport'}) RETURN reduce(acc = 0.0, l IN link | round(acc + l.distance, 2)) AS total, [n in nodes(p) | n.name] AS stations ORDER BY total LIMIT 1; MATCH p = (:Station {name: 'Denmark Hill'}) (()-[link:LINK]-()){1,28} (:Station {name: 'Gatwick Airport'}) RETURN size(relationships(p)) AS numStations, count(*) AS numRoutes ORDER BY numStations; MATCH (gtw:Station {name: 'Gatwick Airport'}) MATCH p = (:Station {name: 'Denmark Hill'}) ((l)-[link:LINK]-(r) WHERE point.distance(r.location, gtw.location) - point.distance(l.location, gtw.location) < 1000)+ (gtw) RETURN reduce(acc = 0.0, l IN link | round(acc + l.distance, 2)) AS total, [n in nodes(p) | n.name] AS stations ORDER BY total LIMIT 1; MATCH (dmk:Station {name: 'Denmark Hill'})<-[:CALLS_AT]-(l1a:CallingPoint)-[:NEXT]->+ (l1b)-[:CALLS_AT]->(x:Station)<-[:CALLS_AT]-(l2a:CallingPoint)-[:NEXT]->+ (l2b)-[:CALLS_AT]->(gtw:Station {name: 'Gatwick Airport'}); MATCH (:Station {name: 'Denmark Hill'})<-[:CALLS_AT]-(r:CallingPoint) (()-[:NEXT]->())+ (:CallingPoint)-[:CALLS_AT]->(:Station {name: 'Gatwick Airport'}) RETURN r.routeName AS route; MATCH (dmk:Station {name: 'Denmark Hill'})<-[:CALLS_AT]-(l1:CallingPoint) (()-[:NEXT]->())+ (:CallingPoint)-[:CALLS_AT]->(x:Station)<-[:CALLS_AT]-(l2:CallingPoint) (()-[:NEXT]->())+ (:CallingPoint)-[:CALLS_AT]->(gtw:Station {name: 'Gatwick Airport'}) RETURN l1.routeName AS leg1, x.name AS changeAt, l2.routeName AS leg2; MATCH (dmk:Station {name: 'Denmark Hill'})<-[:CALLS_AT]-(l1a:CallingPoint)-[:NEXT]->+ (l1b)-[:CALLS_AT]->(x:Station)<-[:CALLS_AT]-(l2a:CallingPoint)-[:NEXT]->+ (l2b)-[:CALLS_AT]->(gtw:Station {name: 'Gatwick Airport'}) MATCH (l1a)-[:HAS]->(s1:Stop)-[:NEXT]->+(s2)<-[:HAS]-(l1b) WHERE time('09:30') < s1.departs < time('10:00') MATCH (l2a)-[:HAS]->(s3:Stop)-[:NEXT]->+(s4)<-[:HAS]-(l2b) WHERE s2.arrives < s3.departs < s2.arrives + duration('PT15M') RETURN s1.departs AS leg1Departs, s2.arrives AS leg1Arrives, x.name AS changeAt, s3.departs AS leg2Departs, s4.arrives AS leg2Arrive, duration.between(s1.departs, s4.arrives).minutes AS journeyTime ORDER BY leg2Arrive LIMIT 5; MATCH (dmk:Station {name: 'Denmark Hill'})<-[:CALLS_AT]-(l1a:CallingPoint) (()-[:NEXT]->(n) WHERE NOT EXISTS { (n)-[:CALLS_AT]->(:Station:LondonGroup) })+ (l1b)-[:CALLS_AT]->(x:Station)<-[:CALLS_AT]-(l2a:CallingPoint) (()-[:NEXT]->(m) WHERE NOT EXISTS { (m)-[:CALLS_AT]->(:Station:LondonGroup) })+ (l2b)-[:CALLS_AT]->(gtw:Station {name: 'Gatwick Airport'}) MATCH (l1a)-[:HAS]->(s1:Stop)-[:NEXT]->+(s2)<-[:HAS]-(l1b) WHERE time('09:30') < s1.departs < time('10:00') MATCH (l2a)-[:HAS]->(s3:Stop)-[:NEXT]->+(s4)<-[:HAS]-(l2b) WHERE s2.arrives < s3.departs < s2.arrives + duration('PT15M') RETURN s1.departs AS leg1Departs, s2.arrives AS leg1Arrives, x.name AS changeAt, s3.departs AS leg2Departs, s4.arrives AS leg2Arrive, duration.between(s1.departs, s4.arrives).minutes AS journeyTime ORDER BY leg2Arrive LIMIT 5; ---- [[qpp-output-qpp]] .Input [source,cypher,separated=true,alwaysEscape=false] ---- MATCH (:Station)--()<--(m WHERE m.departs > time('12:00'))-->()-[:NEXT]->(n) RETURN *; MATCH ((n:A)-[:R]->(:B) WHERE EXISTS { (n)-->+(:X) }){2,3}; MATCH (n:Station {name: 'London Euston'})<-[:CALLS_AT]-(s1:Stop)-[:NEXT]->(s2:Stop)-[:CALLS_AT]->(:Station {name: 'Coventry'})<-[:CALLS_AT]-(s3:Stop)-[:NEXT]->(s4:Stop)-[:CALLS_AT]->(n) RETURN ((s1.departs + '-') + s2.departs) AS outbound, ((s3.departs + '-') + s4.departs) AS return; MATCH (:Station {name: 'Denmark Hill'})<-[:X]-(d:Stop) ((:Stop)-[:NEXT]->(:Stop)){1,3} (a:Stop)-[:Y]->(:Station {name: 'Clapham Junction'}) RETURN d.departs AS departureTime, a.arrives AS arrivalTime; MATCH (:A) (()-[r:R]->()){2,3} (:B) RETURN *; MATCH (:A) (()-[r:R]->())+ (:B) RETURN *; MATCH (:A) (()-[r:R]->())* (:B) RETURN *; MATCH p = ((person:Person)-[:DIRECTED]->(movie:Movie)) WHERE person.name = 'Walt Disney' RETURN p; MATCH ((:Station {name: 'Denmark Hill'})-[l:LINK]-(s:Station)){1,4} RETURN *; MATCH p = (:Station {name: 'Denmark Hill'}) (()-[link:LINK]-())+ (:Station {name: 'Gatwick Airport'}) RETURN reduce(acc = 0.0, l IN link | round((acc + l.distance), 2)) AS total, [n IN nodes(p) | n.name] AS stations ORDER BY total ASC LIMIT 1; MATCH p = (:Station {name: 'Denmark Hill'}) (()-[link:LINK]-()){1,28} (:Station {name: 'Gatwick Airport'}) RETURN size(relationships(p)) AS numStations, count(*) AS numRoutes ORDER BY numStations ASC; MATCH (gtw:Station {name: 'Gatwick Airport'}) MATCH p = (:Station {name: 'Denmark Hill'}) ((l)-[link:LINK]-(r) WHERE (point.distance(r.location, gtw.location) - point.distance(l.location, gtw.location)) < 1000)+ (gtw) RETURN reduce(acc = 0.0, l IN link | round((acc + l.distance), 2)) AS total, [n IN nodes(p) | n.name] AS stations ORDER BY total ASC LIMIT 1; MATCH (dmk:Station {name: 'Denmark Hill'})<-[:CALLS_AT]-(l1a:CallingPoint)-[:NEXT]->+(l1b)-[:CALLS_AT]->(x:Station)<-[:CALLS_AT]-(l2a:CallingPoint)-[:NEXT]->+(l2b)-[:CALLS_AT]->(gtw:Station {name: 'Gatwick Airport'}); MATCH (:Station {name: 'Denmark Hill'})<-[:CALLS_AT]-(r:CallingPoint) (()-[:NEXT]->())+ (:CallingPoint)-[:CALLS_AT]->(:Station {name: 'Gatwick Airport'}) RETURN r.routeName AS route; MATCH (dmk:Station {name: 'Denmark Hill'})<-[:CALLS_AT]-(l1:CallingPoint) (()-[:NEXT]->())+ (:CallingPoint)-[:CALLS_AT]->(x:Station)<-[:CALLS_AT]-(l2:CallingPoint) (()-[:NEXT]->())+ (:CallingPoint)-[:CALLS_AT]->(gtw:Station {name: 'Gatwick Airport'}) RETURN l1.routeName AS leg1, x.name AS changeAt, l2.routeName AS leg2; MATCH (dmk:Station {name: 'Denmark Hill'})<-[:CALLS_AT]-(l1a:CallingPoint)-[:NEXT]->+(l1b)-[:CALLS_AT]->(x:Station)<-[:CALLS_AT]-(l2a:CallingPoint)-[:NEXT]->+(l2b)-[:CALLS_AT]->(gtw:Station {name: 'Gatwick Airport'}) MATCH (l1a)-[:HAS]->(s1:Stop)-[:NEXT]->+(s2)<-[:HAS]-(l1b) WHERE (time('09:30') < s1.departs AND s1.departs < time('10:00')) MATCH (l2a)-[:HAS]->(s3:Stop)-[:NEXT]->+(s4)<-[:HAS]-(l2b) WHERE (s2.arrives < s3.departs AND s3.departs < (s2.arrives + duration('PT15M'))) RETURN s1.departs AS leg1Departs, s2.arrives AS leg1Arrives, x.name AS changeAt, s3.departs AS leg2Departs, s4.arrives AS leg2Arrive, duration.between(s1.departs, s4.arrives).minutes AS journeyTime ORDER BY leg2Arrive ASC LIMIT 5; MATCH (dmk:Station {name: 'Denmark Hill'})<-[:CALLS_AT]-(l1a:CallingPoint) (()-[:NEXT]->(n) WHERE NOT (EXISTS { (n)-[:CALLS_AT]->(:Station:LondonGroup) }))+ (l1b)-[:CALLS_AT]->(x:Station)<-[:CALLS_AT]-(l2a:CallingPoint) (()-[:NEXT]->(m) WHERE NOT (EXISTS { (m)-[:CALLS_AT]->(:Station:LondonGroup) }))+ (l2b)-[:CALLS_AT]->(gtw:Station {name: 'Gatwick Airport'}) MATCH (l1a)-[:HAS]->(s1:Stop)-[:NEXT]->+(s2)<-[:HAS]-(l1b) WHERE (time('09:30') < s1.departs AND s1.departs < time('10:00')) MATCH (l2a)-[:HAS]->(s3:Stop)-[:NEXT]->+(s4)<-[:HAS]-(l2b) WHERE (s2.arrives < s3.departs AND s3.departs < (s2.arrives + duration('PT15M'))) RETURN s1.departs AS leg1Departs, s2.arrives AS leg1Arrives, x.name AS changeAt, s3.departs AS leg2Departs, s4.arrives AS leg2Arrive, duration.between(s1.departs, s4.arrives).minutes AS journeyTime ORDER BY leg2Arrive ASC LIMIT 5; ---- === Unsupported operations The following operations are unsupported: [[unsupported-input-f]] .Input [source,cypher,separated=true] ---- GRANT ROLE poolImpersonation TO pool; SHOW TRANSACTION YIELD *; UNWIND range(1, 10) AS i CALL { WITH i UNWIND [1, 2] AS j CREATE (n:N {i: i, j: j}) } IN TRANSACTIONS; CREATE INDEX node_index_name FOR (n:Person) ON (n.surname); CREATE INDEX rel_index_name FOR ()-[r:KNOWS]-() ON (r.since); CREATE INDEX FOR (n:Person) ON n.firstname; CREATE CONSTRAINT FOR (book:Book) REQUIRE book.isbn IS UNIQUE CREATE INDEX node_index_name IF NOT EXISTS FOR (n:Person) ON (n.surname); SHOW INDEXES; CREATE CONSTRAINT constraint_name FOR (book:Book) REQUIRE book.isbn IS UNIQUE; DROP CONSTRAINT constraint_name; SHOW CONSTRAINTS; REVOKE GRANT TRAVERSE ON HOME GRAPH NODES Post FROM regularUsers; SHOW FUNCTIONS; SHOW PROCEDURES; CREATE ROLE myrole; RENAME ROLE mysecondrole TO mythirdrole; SHOW ROLES; GRANT ROLE myrole TO bob; REVOKE ROLE myrole FROM bob; DROP ROLE mythirdrole; SHOW CURRENT USER; SHOW USERS; CREATE USER jake SET PASSWORD 'abc' CHANGE REQUIRED SET STATUS SUSPENDED SET HOME DATABASE anotherDb; RENAME USER jake TO bob; ALTER USER bob SET PASSWORD 'abc123' CHANGE NOT REQUIRED SET STATUS ACTIVE; ALTER CURRENT USER SET PASSWORD FROM 'abc123' TO '123xyz'; ALTER CURRENT USER SET PASSWORD FROM $oldPassword TO $newPassword; ALTER USER bob SET STATUS ACTIVE; SHOW DATABASES; CREATE DATABASE customers; ALTER DATABASE customers SET ACCESS READ ONLY; STOP DATABASE customers; START DATABASE customers; DROP DATABASE customers; CREATE DATABASE slow WAIT 5 SECONDS; CREATE ALIAS `northwind` FOR DATABASE `northwind-graph-2020`; ALTER ALIAS `northwind` SET DATABASE TARGET `northwind-graph-2021`; DROP ALIAS `northwind` FOR DATABASE; CREATE DATABASE d01; SHOW DATABASE d01 YIELD serverID, databaseID, lastCommittedTxn, replicationLag; USE foo; CREATE USER jake SET ENCRYPTED PASSWORD '1,6d57a5e0b3317055454e455f96c98c750c77fb371f3f0634a1b8ff2a55c5b825,190ae47c661e0668a0c8be8a21ff78a4a34cdf918cae3c407e907b73932bd16c' CHANGE NOT REQUIRED SET STATUS ACTIVE IF NOT EXISTS; DROP USER f; GRANT read privilege ON HOME GRAPH TO myrole; DENY read privilege ON HOME GRAPH TO myrole; REVOKE read privilege ON HOME GRAPH TO myrole; REVOKE GRANT read privilege ON HOME GRAPH TO myrole; REVOKE DENY read privilege ON HOME GRAPH TO myrole; GRANT ASSIGN PRIVILEGE ON DBMS TO role; DENY ASSIGN PRIVILEGE ON DBMS TO role; REVOKE ASSIGN PRIVILEGE ON DBMS TO role; GRANT ALL DATABASE PRIVILEGES ON DATABASE neo4j TO databaseAdminUsers; ---- ================================================ FILE: neo4j-cypher-dsl-parser/pom.xml ================================================ 4.0.0 org.neo4j neo4j-cypher-dsl-parent ${revision}${sha1}${changelist} neo4j-cypher-dsl-parser Neo4j Cypher DSL (Parser) Building on top of the Neo4j 4.4 JavaCC parsers it provides a way from Cypher to Cypher-DSL Ast. 0.68 ${basedir}/../${aggregate.report.dir} org.neo4j neo4j-cypher-dsl-bom ${project.version} pom import org.neo4j neo4j-cypher-dsl org.neo4j neo4j-cypher-javacc-parser com.opencsv opencsv ${opencsv.version} test org.asciidoctor asciidoctorj test org.assertj assertj-core test org.junit.jupiter junit-jupiter test org.neo4j.test neo4j-harness ${neo4j.version} test org.slf4j slf4j-simple test org.apache.maven.plugins maven-surefire-plugin false org.apache.maven.plugins maven-failsafe-plugin ${project.build.outputDirectory} org.neo4j:neo4j-cypher-dsl-parser false org.apache.maven.plugins maven-javadoc-plugin attach-javadocs jar org.apache.maven.plugins maven-shade-plugin shade package org.neo4j:cypher-ast-factory org.neo4j:neo4j-cypher-javacc-parser org.neo4j:cypher-parser-common org.neo4j.cypher.internal org.neo4j.cypherdsl.parser.internal true *:* module-info.class library.properties LICENSE.txt rootdoc.txt org.neo4j:cypher-ast-factory META-INF/**/* org.neo4j:neo4j-cypher-javacc-parser META-INF/**/* org.neo4j:cypher-parser-common META-INF/**/* org.moditect moditect-maven-plugin add-module-infos add-module-info package true org.apache.maven.plugins maven-resources-plugin copy-resources copy-resources generate-test-resources ${basedir}/target/test-classes ${project.basedir} README.adoc com.github.siom79.japicmp japicmp-maven-plugin org.apache.maven.plugins maven-checkstyle-plugin checkstyle ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/module-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * @author Michael J. Simons * @since 2023.0.0 */ @SuppressWarnings( {"requires-automatic"}) // Neo4j Cypher Parser module org.neo4j.cypherdsl.parser { requires transitive org.apiguardian.api; requires transitive org.neo4j.cypherdsl.core; requires org.neo4j.cypher.internal.parser; requires org.neo4j.cypher.internal.parser.javacc; requires org.neo4j.cypher.internal.ast.factory; requires org.neo4j.gql.status; exports org.neo4j.cypherdsl.parser; } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/CyperDslParseException.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.io.Serial; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * A runtime exception wrapping checked parsing exception into a sensible exception * hierarchy. * * @author Michael J. Simons * @since 2021.3.0 */ @API(status = STABLE, since = "2021.3.0") public final class CyperDslParseException extends RuntimeException { @Serial private static final long serialVersionUID = -3188559145717360828L; /** * Creates a new Cypher-DSL specific exception with the given cause. * @param cause original cause, being wrapped in a {@link RuntimeException}. */ public CyperDslParseException(Throwable cause) { super(cause); } } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/CypherDslASTExceptionFactory.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypher.internal.parser.common.ast.factory.ASTExceptionFactory; import org.neo4j.gqlstatus.ErrorGqlStatusObject; import static org.apiguardian.api.API.Status.INTERNAL; /** * Implementation of the exception factory for Neo4j 5s JavaCC based parser. * * @author Michael J. Simons * @since 2021.3.0 */ @API(status = INTERNAL, since = "2021.3.0") enum CypherDslASTExceptionFactory implements ASTExceptionFactory { INSTANCE; @Override public Exception syntaxException(ErrorGqlStatusObject errorGqlStatusObject, String s, List list, Exception e, int i, int i1, int i2) { return new RuntimeException(errorGqlStatusObject.getMessage()); } @Override public Exception syntaxException(String got, List expected, Exception source, int offset, int line, int column) { return new RuntimeException(source.getMessage()); } @Override public Exception syntaxException(Exception source, int offset, int line, int column) { return new RuntimeException(source.getMessage()); } @Override public Exception syntaxException(ErrorGqlStatusObject errorGqlStatusObject, Exception e, int i, int i1, int i2) { return new RuntimeException(errorGqlStatusObject.getMessage()); } } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/CypherDslASTFactory.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.function.Function; import java.util.function.UnaryOperator; import java.util.stream.Stream; import org.apiguardian.api.API; import org.neo4j.cypher.internal.ast.factory.ASTFactory; import org.neo4j.cypher.internal.ast.factory.ASTFactory.NULL; import org.neo4j.cypher.internal.parser.common.ast.factory.AccessType; import org.neo4j.cypher.internal.parser.common.ast.factory.ActionType; import org.neo4j.cypher.internal.parser.common.ast.factory.CallInTxsOnErrorBehaviourType; import org.neo4j.cypher.internal.parser.common.ast.factory.ConstraintType; import org.neo4j.cypher.internal.parser.common.ast.factory.CreateIndexTypes; import org.neo4j.cypher.internal.parser.common.ast.factory.HintIndexType; import org.neo4j.cypher.internal.parser.common.ast.factory.ParameterType; import org.neo4j.cypher.internal.parser.common.ast.factory.ParserCypherTypeName; import org.neo4j.cypher.internal.parser.common.ast.factory.ParserNormalForm; import org.neo4j.cypher.internal.parser.common.ast.factory.ParserTrimSpecification; import org.neo4j.cypher.internal.parser.common.ast.factory.ScopeType; import org.neo4j.cypher.internal.parser.common.ast.factory.ShowCommandFilterTypes; import org.neo4j.cypher.internal.parser.common.ast.factory.SimpleEither; import org.neo4j.cypherdsl.core.Case; import org.neo4j.cypherdsl.core.Clause; import org.neo4j.cypherdsl.core.Clauses; import org.neo4j.cypherdsl.core.Comparison; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.ExposesRelationships; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.Finish; import org.neo4j.cypherdsl.core.FunctionInvocation; import org.neo4j.cypherdsl.core.Hint; import org.neo4j.cypherdsl.core.KeyValueMapEntry; import org.neo4j.cypherdsl.core.Labels; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.MapProjection; import org.neo4j.cypherdsl.core.MergeAction; import org.neo4j.cypherdsl.core.NamedPath; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.NodeLabel; import org.neo4j.cypherdsl.core.Operation; import org.neo4j.cypherdsl.core.Operator; import org.neo4j.cypherdsl.core.Parameter; import org.neo4j.cypherdsl.core.PatternComprehension; import org.neo4j.cypherdsl.core.PatternElement; import org.neo4j.cypherdsl.core.PatternSelector; import org.neo4j.cypherdsl.core.Property; import org.neo4j.cypherdsl.core.PropertyLookup; import org.neo4j.cypherdsl.core.QuantifiedPathPattern; import org.neo4j.cypherdsl.core.Relationship; import org.neo4j.cypherdsl.core.RelationshipPattern; import org.neo4j.cypherdsl.core.Return; import org.neo4j.cypherdsl.core.Set; import org.neo4j.cypherdsl.core.SortItem; import org.neo4j.cypherdsl.core.Statement; import org.neo4j.cypherdsl.core.StringLiteral; import org.neo4j.cypherdsl.core.SymbolicName; import org.neo4j.cypherdsl.core.Where; import org.neo4j.cypherdsl.core.ast.TypedSubtree; import org.neo4j.cypherdsl.core.ast.Visitable; import static org.apiguardian.api.API.Status.INTERNAL; /** * An implementation of Neo4j's {@link ASTFactory} that creates Cypher-DSL AST elements * that can be used for creating conditions, patterns to match etc. * * @author Michael J. Simons * @since 2021.3.0 */ @API(status = INTERNAL, since = "2021.3.0") final class CypherDslASTFactory implements ASTFactory, SortItem, PatternElement, NodeAtom, PathAtom, PathLength, Clause, Expression, Expression, Expression, Hint, Expression, Labels, Expression, Parameter, Expression, Property, Expression, Clause, Statement, Statement, Statement, Clause, Where, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, InputPosition, EntityType, QuantifiedPathPattern.Quantifier, PatternAtom, DatabaseName, PatternSelector, NULL, PatternElement> { private static CypherDslASTFactory instanceFromDefaultOptions; private final Options options; private CypherDslASTFactory(Options options) { this.options = options; } static CypherDslASTFactory getInstance(Options options) { CypherDslASTFactory instance; if (options != null && !options.areDefault()) { instance = new CypherDslASTFactory(options); } else { instance = instanceFromDefaultOptions; if (instance == null) { synchronized (CypherDslASTFactory.class) { instance = instanceFromDefaultOptions; if (instance == null) { instanceFromDefaultOptions = new CypherDslASTFactory( Optional.ofNullable(options).orElseGet(Options::defaultOptions)); instance = instanceFromDefaultOptions; } } } } return instance; } static void isInstanceOf(Class type, Object obj, String message) { if (type == null) { throw new IllegalArgumentException("Type to check against must not be null"); } if (!type.isInstance(obj)) { throw new IllegalArgumentException(message); } } private static void notNull(Object object, String message) { if (object == null) { throw new IllegalArgumentException(message); } } private static SymbolicName assertSymbolicName(Expression v) { if (v == null) { return null; } isInstanceOf(SymbolicName.class, v, "An invalid type has been generated where a SymbolicName was required. Generated type was " + v.getClass().getName()); return (SymbolicName) v; } private String[] computeFinalLabelList(LabelParsedEventType event, List> inputLabels) { return (inputLabels != null) ? this.options.getLabelFilter() .apply(event, inputLabels.stream().map(v -> v.string).toList()) .toArray(new String[0]) : new String[0]; } private String[] computeFinalTypeList(TypeParsedEventType event, Labels inputTypes) { if (inputTypes == null) { return new String[0]; } if ((inputTypes.isNegated() && inputTypes.getValue().size() > 1) || inputTypes.getType() == Labels.Type.CONJUNCTION) { throw new UnsupportedOperationException( "Expressions for relationship types are not supported in Cypher-DSL"); } Collection types = inputTypes.getStaticValues(); return this.options.getTypeFilter().apply(event, types).toArray(new String[0]); } private T applyCallbacksFor(ExpressionCreatedEventType type, T newExpression) { return applyCallbacksFor(type, List.of(newExpression)).get(0); } @SuppressWarnings("unchecked") private List applyCallbacksFor(ExpressionCreatedEventType type, List expressions) { var callbacks = this.options.getOnNewExpressionCallbacks().getOrDefault(type, List.of()); if (callbacks.isEmpty()) { return expressions; } var chainedCallbacks = callbacks.stream().reduce(Function.identity(), Function::andThen); return expressions.stream().map(e -> (T) chainedCallbacks.apply(e)).toList(); } @SuppressWarnings("unchecked") private T applyCallbacksFor(InvocationCreatedEventType type, T newExpression) { var callbacks = this.options.getOnNewInvocationCallbacks().getOrDefault(type, List.of()); if (callbacks.isEmpty()) { return newExpression; } Visitable result = newExpression; for (UnaryOperator callback : callbacks) { result = callback.apply(result); } return (T) result; } @Override public Statements statements(List statements) { return new Statements(statements); } @Override public Statement newSingleQuery(InputPosition p, List clauses) { return newSingleQuery(clauses); } @Override public Statement newSingleQuery(List clauses) { return Statement.of(clauses); } @Override public Statement newUnion(InputPosition p, Statement lhs, Statement rhs, boolean all) { if (all) { return Cypher.unionAll(lhs, rhs); } else { return Cypher.union(lhs, rhs); } } @Override public Clause directUseClause(InputPosition p, DatabaseName databaseName) { throw new UnsupportedOperationException(); } @Override public Clause functionUseClause(InputPosition p, Expression function) { throw new UnsupportedOperationException(); } @Override public Finish newFinishClause(InputPosition p) { return Finish.create(); } @Override public List newReturnItems(InputPosition p, boolean returnAll, List returnItems) { var finalReturnItems = returnItems; if (returnAll) { finalReturnItems = Stream.concat(Stream.of(Cypher.asterisk()), finalReturnItems.stream()).toList(); } if (finalReturnItems.isEmpty()) { if (!returnAll) { throw new IllegalArgumentException("Cannot return nothing."); } finalReturnItems = Collections.singletonList(Cypher.asterisk()); } return finalReturnItems; } @Override public Return newReturnClause(InputPosition p, boolean distinct, List returnItems, List sortItems, InputPosition orderPos, Expression skip, InputPosition skipPosition, Expression limit, InputPosition limitPosition) { return this.options.getReturnClauseFactory() .apply(new ReturnDefinition(distinct, returnItems, sortItems, skip, limit)); } @Override public Expression newReturnItem(InputPosition p, Expression e, Expression v) { var s = assertSymbolicName(v); return applyCallbacksFor(ExpressionCreatedEventType.ON_RETURN_ITEM, e.as(s)); } @Override public Expression newReturnItem(InputPosition p, Expression e, int eStartOffset, int eEndOffset) { return applyCallbacksFor(ExpressionCreatedEventType.ON_RETURN_ITEM, e); } @Override public SortItem orderDesc(InputPosition p, Expression e) { return e.descending(); } @Override public SortItem orderAsc(InputPosition p, Expression e) { return e.ascending(); } @Override public Clause withClause(InputPosition p, Return returnClause, Where where) { return Clauses.with(returnClause, where); } @Override public Clause matchClause(InputPosition p, boolean optional, NULL matchMode, List patternElements, InputPosition patternPos, List hints, Where whereIn) { var patternElementCallbacks = this.options.getOnNewPatternElementCallbacks() .getOrDefault(PatternElementCreatedEventType.ON_MATCH, List.of()); List openForTransformation = new ArrayList<>(); for (PatternElement patternElement : patternElements) { if (patternElement instanceof NodeAtom nodeAtom) { openForTransformation.add(nodeAtom.value()); } else { openForTransformation.add(patternElement); } } var transformedPatternElements = transformIfPossible(patternElementCallbacks, openForTransformation); return this.options.getMatchClauseFactory() .apply(new MatchDefinition(optional, transformedPatternElements, whereIn, hints)); } private List transformIfPossible(List> callbacks, List patternElements) { if (callbacks.isEmpty()) { return patternElements; } @SuppressWarnings("squid:S4276") // The function is needed due to the assigment // below var transformer = Function.identity(); for (UnaryOperator callback : callbacks) { transformer = transformer.andThen(callback); } return patternElements.stream().map(transformer).filter(Objects::nonNull).toList(); } @Override public Hint usingIndexHint(InputPosition p, Expression v, String labelOrRelType, List properties, boolean seekOnly, HintIndexType indexType) { // We build nodes here. As of now, the node isn't used anyway, but only the label // will be used down further the AST. // It is easier than introduce a new common abstraction of label and relationship // type (probably // in line with the decision made for the parser) var node = Cypher.node(labelOrRelType).named(assertSymbolicName(v)); return Hint.useIndexFor(seekOnly, properties.stream().map(node::property).toArray(Property[]::new)); } @Override public Hint usingJoin(InputPosition p, List joinVariables) { return Hint.useJoinOn( joinVariables.stream().map(CypherDslASTFactory::assertSymbolicName).toArray(SymbolicName[]::new)); } @Override public Hint usingScan(InputPosition p, Expression v, String label) { var s = assertSymbolicName(v); // Same note applies as with usingIndexHint in regard to relationships return Hint.useScanFor(Cypher.node(label).named(s)); } @Override public Clause createClause(InputPosition p, List patternElements) { var callbacks = this.options.getOnNewPatternElementCallbacks() .getOrDefault(PatternElementCreatedEventType.ON_CREATE, List.of()); return Clauses.create(transformIfPossible(callbacks, patternElements.stream().map(v -> (v instanceof NodeAtom n) ? n.value() : v).toList())); } @Override public Clause insertClause(InputPosition p, List patternElements) { throw new UnsupportedOperationException(); } @Override public Clause setClause(InputPosition p, List setItems) { return Clauses.set(setItems); } @Override public Operation setProperty(Property property, Expression value) { return applyCallbacksFor(ExpressionCreatedEventType.ON_SET_PROPERTY, property.to(value)); } @Override public Expression setDynamicProperty(Expression dynamicProperty, Expression value) { return applyCallbacksFor(ExpressionCreatedEventType.ON_SET_PROPERTY, Cypher.set(dynamicProperty, value)); } @Override public Operation setVariable(Expression v, Expression value) { return applyCallbacksFor(ExpressionCreatedEventType.ON_SET_VARIABLE, Cypher.set(v, value)); } @Override public Operation addAndSetVariable(Expression v, Expression value) { return applyCallbacksFor(ExpressionCreatedEventType.ON_ADD_AND_SET_VARIABLE, Cypher.mutate(v, value)); } @Override public Expression setLabels(Expression v, List> values, List dynamicLabels, boolean containsIs) { var s = assertSymbolicName(v); Operation operation; if (values.isEmpty() && !(dynamicLabels == null || dynamicLabels.isEmpty())) { var labels = Cypher.allLabels(dynamicLabels.get(0)); for (var e : dynamicLabels.subList(1, dynamicLabels.size())) { labels = labels.conjunctionWith(Cypher.allLabels(e)); } operation = Cypher.setLabel(Cypher.anyNode(s), labels); } else { var labels = computeFinalLabelList(LabelParsedEventType.ON_SET, values); operation = Cypher.setLabel(Cypher.anyNode(s), labels); } return applyCallbacksFor(ExpressionCreatedEventType.ON_SET_LABELS, operation); } @Override public Clause removeClause(InputPosition p, List removeItems) { return Clauses.remove(removeItems); } @Override public Expression removeProperty(Property property) { return applyCallbacksFor(ExpressionCreatedEventType.ON_REMOVE_PROPERTY, property); } @Override public Expression removeDynamicProperty(Expression dynamicProperty) { return applyCallbacksFor(ExpressionCreatedEventType.ON_REMOVE_PROPERTY, dynamicProperty); } @Override public Expression removeLabels(Expression v, List> values, List dynamicLabels, boolean containsIs) { var s = assertSymbolicName(v); var labels = computeFinalLabelList(LabelParsedEventType.ON_REMOVE, values); return applyCallbacksFor(ExpressionCreatedEventType.ON_REMOVE_LABELS, Cypher.removeLabel(Cypher.anyNode(s), labels)); } @Override public Clause deleteClause(InputPosition p, boolean detach, List expressions) { return Clauses.delete(detach, applyCallbacksFor(ExpressionCreatedEventType.ON_DELETE_ITEM, expressions)); } @Override public Clause unwindClause(InputPosition p, Expression e, Expression v) { return Clauses.unwind(e, assertSymbolicName(v)); } @Override public Clause mergeClause(InputPosition p, PatternElement patternElementIn, List setClauses, List actionTypes, List positions) { var patternElement = (patternElementIn instanceof NodeAtom n) ? n.value() : patternElementIn; var mergeActions = new ArrayList(); if (setClauses != null && !setClauses.isEmpty() && actionTypes != null && !actionTypes.isEmpty()) { var iteratorClauses = setClauses.iterator(); var iteratorTypes = actionTypes.iterator(); while (iteratorClauses.hasNext() && iteratorTypes.hasNext()) { var type = iteratorTypes.next(); switch (type) { case OnCreate -> mergeActions.add(MergeAction.of(MergeAction.Type.ON_CREATE, (Set) iteratorClauses.next())); case OnMatch -> mergeActions.add(MergeAction.of(MergeAction.Type.ON_MATCH, (Set) iteratorClauses.next())); default -> throw new IllegalArgumentException("Unsupported MergeActionType: " + type); } } } var callbacks = this.options.getOnNewPatternElementCallbacks() .getOrDefault(PatternElementCreatedEventType.ON_MERGE, List.of()); return Clauses.merge(transformIfPossible(callbacks, List.of(patternElement)), mergeActions); } @Override public Clause callClause(InputPosition p, InputPosition namespacePosition, InputPosition procedureNamePosition, InputPosition procedureResultPosition, List namespace, String name, List arguments, boolean yieldAll, List resultItems, Where where, boolean optional) { var intermediateResult = Clauses.callClause(namespace, name, arguments, (!yieldAll || (resultItems != null)) ? resultItems : List.of(Cypher.asterisk()), where); if (optional) { throw new IllegalArgumentException("Cannot render optional call clause"); } return applyCallbacksFor(InvocationCreatedEventType.ON_CALL, intermediateResult); } @Override public Expression callResultItem(InputPosition p, String name, Expression alias) { var finalName = Cypher.name(name); if (alias != null) { return finalName.as(assertSymbolicName(alias)); } return finalName; } @Override public PatternElement patternWithSelector(PatternSelector patternSelector, PatternElement patternPart) { return NamedPath.select(patternSelector, patternPart); } @Override public PatternElement namedPattern(Expression v, PatternElement patternElement) { return Cypher.path(assertSymbolicName(v)).definedBy(patternElement); } @Override public PatternElement shortestPathPattern(InputPosition p, PatternElement patternElement) { isInstanceOf(RelationshipPattern.class, patternElement, "Only relationship patterns are supported for the shortestPath function."); return new ExpressionAsPatternElementWrapper( FunctionInvocation.create(PatternElementFunctions.SHORTEST_PATH, patternElement)); } @Override public PatternElement allShortestPathsPattern(InputPosition p, PatternElement patternElement) { isInstanceOf(RelationshipPattern.class, patternElement, "Only relationship patterns are supported for the allShortestPaths function."); return new ExpressionAsPatternElementWrapper( FunctionInvocation.create(PatternElementFunctions.ALL_SHORTEST_PATHS, patternElement)); } @Override public PatternElement pathPattern(PatternElement patternElement) { return patternElement; } @Override public PatternElement insertPathPattern(List patternAtoms) { throw new UnsupportedOperationException(); } @SuppressWarnings("squid:S3776") // Yep, it's complex @Override public PatternElement patternElement(List atoms) { if (atoms.isEmpty()) { throw new IllegalArgumentException("Cannot create a PatternElement from an empty list of patterns."); } if (atoms.size() == 1 && atoms.get(0) instanceof ParenthesizedPathPatternAtom atom) { return atom.asPatternElement(); } List patternElements = new ArrayList<>(); NodeAtom lastNodeAtom = null; PathAtom lastPathAtom = null; ExposesRelationships relationshipPattern = null; List patternList = null; for (PatternAtom atom : atoms) { if (atom instanceof ParenthesizedPathPatternAtom specificAtom) { if (lastNodeAtom != null) { patternElements.add(lastNodeAtom.value()); } if (relationshipPattern != null) { patternElements.add((PatternElement) relationshipPattern); } if (patternList != null) { patternElements.add(new PatternList(patternList)); } lastNodeAtom = null; lastPathAtom = null; relationshipPattern = null; patternList = null; patternElements.add(specificAtom.asPatternElement()); } else if (atom instanceof NodeAtom nodeAtom) { if (relationshipPattern != null) { relationshipPattern = lastPathAtom.asRelationshipBetween(relationshipPattern, nodeAtom, this.options.isAlwaysCreateRelationshipsLTR()); } else if (lastNodeAtom == null) { lastNodeAtom = nodeAtom; } else { relationshipPattern = lastNodeAtom.value(); lastNodeAtom = null; // Will be added to the pattern elements either on the occurrence of a // parenthesized pattern or // after iterating all atoms. relationshipPattern = lastPathAtom.asRelationshipBetween(relationshipPattern, nodeAtom, this.options.isAlwaysCreateRelationshipsLTR()); if ((lastPathAtom.getDirection() == Relationship.Direction.RTL || patternList != null) && this.options.isAlwaysCreateRelationshipsLTR()) { if (patternList == null) { patternList = new ArrayList<>(); } patternList.add(((PatternElement) relationshipPattern)); relationshipPattern = null; lastNodeAtom = nodeAtom; } } } else if (atom instanceof PathAtom pathAtom) { lastPathAtom = pathAtom; } } if (relationshipPattern == null && patternList != null && patternList.size() == 1 && patternList.get(0) instanceof RelationshipPattern singleListItem) { relationshipPattern = singleListItem; patternList = null; } if (relationshipPattern != null) { patternElements.add((PatternElement) relationshipPattern); } else if (patternList != null) { patternElements.add(new PatternList(patternList)); } else if (lastNodeAtom != null) { patternElements.add(lastNodeAtom.value()); } return (patternElements.size() == 1) ? patternElements.get(0) : new PatternJuxtaposition(patternElements); } @Override public PatternSelector anyPathSelector(String count, InputPosition countPosition, InputPosition position) { if (count != null) { throw new UnsupportedOperationException("ANY with k is not supported"); } return PatternSelector.any(); } @Override public PatternSelector allPathSelector(InputPosition position) { return PatternSelector.allShortest(); } @Override public PatternSelector anyShortestPathSelector(String count, InputPosition countPosition, InputPosition position) { var k = 1; if (count != null) { k = Integer.parseInt(count); } return PatternSelector.shortestK(k); } @Override public PatternSelector allShortestPathSelector(InputPosition position) { return PatternSelector.allShortest(); } @Override public PatternSelector shortestGroupsSelector(String count, InputPosition countPosition, InputPosition position) { var k = 1; if (count != null) { k = Integer.parseInt(count); } return PatternSelector.shortestKGroups(k); } private static Collection flatten(Labels labels) { var values = new LinkedHashSet(); flatten0(labels, values); return values; } private static void flatten0(Labels l, java.util.Set values) { if (l == null) { return; } var current = l.getType(); flatten0(l.getLhs(), values); if (current == Labels.Type.LEAF || (l.getLhs() == null && l.getRhs() == null && EnumSet.of(Labels.Type.COLON_CONJUNCTION, Labels.Type.COLON_DISJUNCTION).contains(l.getType()))) { values.addAll(l.getValue()); } flatten0(l.getRhs(), values); } @Override public NodeAtom nodePattern(InputPosition p, Expression v, Labels labels, Expression properties, Expression predicate) { Node node; if (labels == null) { node = Cypher.anyNode(); } else if (labels.getType() == Labels.Type.COLON_CONJUNCTION || (labels.getType() == Labels.Type.LEAF && labels.getValue() != null)) { var values = flatten(labels); List staticLabels = new ArrayList<>(); int dynamicLabelsCnt = 0; for (var value : values) { if (value.modifier() == Labels.Modifier.STATIC && value.visitable() instanceof NodeLabel nodeLabel) { staticLabels.add(nodeLabel.getValue()); } else { ++dynamicLabelsCnt; } } var newConjunctionRequired = dynamicLabelsCnt > 0; staticLabels = List .copyOf(this.options.getLabelFilter().apply(LabelParsedEventType.ON_NODE_PATTERN, staticLabels)); if (newConjunctionRequired) { List newValues = new ArrayList<>(); for (var value : values) { if (value.modifier() != Labels.Modifier.STATIC || value.visitable() instanceof NodeLabel nodeLabel && staticLabels.contains(nodeLabel.getValue())) { newValues.add(value); } } node = Cypher.node(Labels.colonConjunction(newValues)); } else if (staticLabels.isEmpty()) { node = Cypher.anyNode(); } else { node = Cypher.node(staticLabels.get(0), staticLabels.subList(1, staticLabels.size())); } } else { node = Cypher.node(labels); } if (v != null) { node = node.named(assertSymbolicName(v)); } if (properties != null) { node = node.withProperties((MapExpression) properties); } if (predicate != null) { node = (Node) node.where(predicate); } return new NodeAtom(node); } @Override public PathAtom relationshipPattern(InputPosition p, boolean left, boolean right, Expression v, Labels relTypes, PathLength pathLength, Expression properties, Expression predicate) { return PathAtom.of(assertSymbolicName(v), pathLength, left, right, computeFinalTypeList(TypeParsedEventType.ON_RELATIONSHIP_PATTERN, relTypes), (MapExpression) properties, relTypes != null && relTypes.isNegated(), predicate); } @Override public PathLength pathLength(InputPosition p, InputPosition pMin, InputPosition pMax, String minLength, String maxLength) { return PathLength.of(minLength, maxLength); } @Override public QuantifiedPathPattern.Quantifier intervalPathQuantifier(InputPosition p, InputPosition posLowerBound, InputPosition posUpperBound, String lowerBound, String upperBound) { return QuantifiedPathPattern.interval((lowerBound != null) ? Integer.parseInt(lowerBound) : null, (upperBound != null) ? Integer.parseInt(upperBound) : null); } @Override public QuantifiedPathPattern.Quantifier fixedPathQuantifier(InputPosition p, InputPosition valuePos, String value) { throw new UnsupportedOperationException(); } @Override public QuantifiedPathPattern.Quantifier plusPathQuantifier(InputPosition p) { return QuantifiedPathPattern.plus(); } @Override public QuantifiedPathPattern.Quantifier starPathQuantifier(InputPosition p) { return QuantifiedPathPattern.star(); } @Override public NULL repeatableElements(InputPosition p) { throw new UnsupportedOperationException(); } @Override public NULL differentRelationships(InputPosition p) { throw new UnsupportedOperationException(); } @Override public PatternAtom parenthesizedPathPattern(InputPosition p, PatternElement internalPattern, Expression where, QuantifiedPathPattern.Quantifier pathPatternQuantifier) { return new ParenthesizedPathPatternAtom((RelationshipPattern) internalPattern, pathPatternQuantifier, where); } @Override public PatternAtom quantifiedRelationship(PathAtom rel, QuantifiedPathPattern.Quantifier pathPatternQuantifier) { return rel.withQuantifier(pathPatternQuantifier); } @Override public Clause loadCsvClause(InputPosition p, boolean headers, Expression source, Expression v, String fieldTerminator) { isInstanceOf(StringLiteral.class, source, "Only string literals are supported as source for the LOAD CSV clause."); return Clauses.loadCSV(headers, (StringLiteral) source, assertSymbolicName(v), fieldTerminator); } @Override public Clause foreachClause(InputPosition p, Expression v, Expression list, List objects) { return Clauses.forEach(assertSymbolicName(v), list, objects); } @Override public Clause subqueryClause(InputPosition p, Statement subquery, NULL inTransactions, boolean scopeAll, boolean hasScope, List expressions, boolean optional) { if (optional) { throw new IllegalArgumentException("Cannot render optional subquery clause"); } return Clauses.callClause(subquery); } @Override public NULL subqueryInTransactionsParams(InputPosition p, NULL batchParams, NULL concurrencyParams, NULL errorParams, NULL reportParams) { throw new UnsupportedOperationException(); } @Override public Clause yieldClause(InputPosition p, boolean returnAll, List expressions, InputPosition returnItemsPosition, List orderBy, InputPosition orderPos, Expression skip, InputPosition skipPosition, Expression limit, InputPosition limitPosition, Where where) { throw new UnsupportedOperationException(); } @Override public Clause showIndexClause(InputPosition p, ShowCommandFilterTypes indexType, Where where, Clause yieldClause) { throw new UnsupportedOperationException(); } @Override public Clause showConstraintClause(InputPosition p, ShowCommandFilterTypes constraintType, Where where, Clause yieldClause) { throw new UnsupportedOperationException(); } @Override public Clause showProcedureClause(InputPosition p, boolean currentUser, String user, Where where, Clause yieldClause) { throw new UnsupportedOperationException(); } @Override public Clause showFunctionClause(InputPosition p, ShowCommandFilterTypes functionType, boolean currentUser, String user, Where where, Clause yieldClause) { throw new UnsupportedOperationException(); } @Override public Statement useGraph(Statement command, Clause useGraph) { throw new UnsupportedOperationException(); } @Override public Statement showRoles(InputPosition p, boolean withUsers, boolean showAll, Clause yieldExpr, Return returnWithoutGraph, Where where) { throw new UnsupportedOperationException(); } @Override public Statement grantRoles(InputPosition p, List, Parameter>> roles, List, Parameter>> users) { throw new UnsupportedOperationException(); } @Override public Statement revokeRoles(InputPosition p, List, Parameter>> roles, List, Parameter>> users) { throw new UnsupportedOperationException(); } @Override public Statement createUser(InputPosition p, boolean replace, boolean ifNotExists, SimpleEither, Parameter> username, Boolean suspended, DatabaseName homeDatabase, List nulls, List systemAuthAttributes) { throw new UnsupportedOperationException(); } @Override public Statement dropUser(InputPosition p, boolean ifExists, SimpleEither, Parameter> username) { throw new UnsupportedOperationException(); } @Override public Statement renameUser(InputPosition p, SimpleEither, Parameter> fromUserName, SimpleEither, Parameter> toUserName, boolean ifExists) { throw new UnsupportedOperationException(); } @Override public Statement setOwnPassword(InputPosition p, Expression currentPassword, Expression newPassword) { throw new UnsupportedOperationException(); } @Override public NULL auth(String provider, List nulls, InputPosition p) { throw new UnsupportedOperationException(); } @Override public NULL authId(InputPosition s, Expression id) { throw new UnsupportedOperationException(); } @Override public NULL password(InputPosition p, Expression password, boolean encrypted) { throw new UnsupportedOperationException(); } @Override public NULL passwordChangeRequired(InputPosition p, boolean changeRequired) { throw new UnsupportedOperationException(); } @Override public Statement alterUser(InputPosition p, boolean ifExists, SimpleEither, Parameter> username, Boolean suspended, DatabaseName homeDatabase, boolean removeHome, List nulls, List systemAuthAttributes, boolean removeAllAuth, List removeAuths) { throw new UnsupportedOperationException(); } @Override public Expression passwordExpression(Parameter password) { throw new UnsupportedOperationException(); } @Override public Expression passwordExpression(InputPosition s, InputPosition e, String password) { throw new UnsupportedOperationException(); } @Override public Statement showUsers(InputPosition p, Clause yieldExpr, Return returnWithoutGraph, Where where, boolean withAuth) { throw new UnsupportedOperationException(); } @Override public Statement showCurrentUser(InputPosition p, Clause yieldExpr, Return returnWithoutGraph, Where where) { throw new UnsupportedOperationException(); } @Override public Statement showSupportedPrivileges(InputPosition p, Clause yieldExpr, Return returnWithoutGraph, Where where) { throw new UnsupportedOperationException(); } @Override public Statement showAllPrivileges(InputPosition p, boolean asCommand, boolean asRevoke, Clause yieldExpr, Return returnWithoutGraph, Where where) { throw new UnsupportedOperationException(); } @Override public Statement showRolePrivileges(InputPosition p, List, Parameter>> roles, boolean asCommand, boolean asRevoke, Clause yieldExpr, Return returnWithoutGraph, Where where) { throw new UnsupportedOperationException(); } @Override public Statement showUserPrivileges(InputPosition p, List, Parameter>> users, boolean asCommand, boolean asRevoke, Clause yieldExpr, Return returnWithoutGraph, Where where) { throw new UnsupportedOperationException(); } @Override public Statement grantPrivilege(InputPosition p, List, Parameter>> roles, NULL privilege) { throw new UnsupportedOperationException(); } @Override public Statement denyPrivilege(InputPosition p, List, Parameter>> roles, NULL privilege) { throw new UnsupportedOperationException(); } @Override public Statement revokePrivilege(InputPosition p, List, Parameter>> roles, NULL privilege, boolean revokeGrant, boolean revokeDeny) { throw new UnsupportedOperationException(); } // The database options are quite different options than ours ;) @SuppressWarnings("HiddenField") @Override public Statement createDatabase(InputPosition p, boolean replace, DatabaseName databaseName, boolean ifNotExists, NULL aNull, SimpleEither, Parameter> options, SimpleEither> topologyPrimaries, SimpleEither> topologySecondaries) { throw new UnsupportedOperationException(); } @Override public Statement createCompositeDatabase(InputPosition p, boolean replace, DatabaseName compositeDatabaseName, boolean ifNotExists, SimpleEither, Parameter> databaseOptions, NULL aNull) { throw new UnsupportedOperationException(); } @Override public Statement dropDatabase(InputPosition p, DatabaseName databaseName, boolean ifExists, boolean composite, boolean aliasAction, boolean dumpData, NULL wait) { throw new UnsupportedOperationException(); } @SuppressWarnings("HiddenField") // The database options are quite different options // than ours ;) @Override public Statement alterDatabase(InputPosition p, DatabaseName databaseName, boolean ifExists, AccessType accessType, SimpleEither> topologyPrimaries, SimpleEither> topologySecondaries, Map options, java.util.Set optionsToRemove, NULL aNull) { throw new UnsupportedOperationException(); } @Override public Statement showDatabase(InputPosition p, NULL scope, Clause yieldExpr, Return returnWithoutGraph, Where where) { throw new UnsupportedOperationException(); } @Override public Statement startDatabase(InputPosition p, DatabaseName databaseName, NULL wait) { throw new UnsupportedOperationException(); } @Override public Statement stopDatabase(InputPosition p, DatabaseName databaseName, NULL wait) { throw new UnsupportedOperationException(); } @Override public NULL databaseScope(InputPosition p, DatabaseName databaseName, boolean isDefault, boolean isHome) { throw new UnsupportedOperationException(); } @Override public Statement dropAlias(InputPosition p, DatabaseName aliasName, boolean ifExists) { throw new UnsupportedOperationException(); } @Override public Statement showAliases(InputPosition p, DatabaseName aliasName, Clause yieldExpr, Return returnWithoutGraph, Where where) { throw new UnsupportedOperationException(); } @Override public void addDeprecatedIdentifierUnicodeNotification(InputPosition p, Character character, String identifier) { } @Override public NULL wait(boolean wait, long seconds) { throw new UnsupportedOperationException(); } @Override public DatabaseName databaseName(InputPosition p, List names) { if (names.isEmpty()) { throw new IllegalArgumentException("No database name"); } if (names.size() == 1) { return new DatabaseName(Cypher.literalOf(names.get(0))); } return new DatabaseName(Cypher.literalOf(names)); } @Override public DatabaseName databaseName(Parameter param) { return new DatabaseName(param); } @Override public Statement createLocalDatabaseAlias(InputPosition p, boolean replace, DatabaseName aliasName, DatabaseName targetName, boolean ifNotExists, SimpleEither, Parameter> properties) { throw new UnsupportedOperationException(); } @Override public Statement createRemoteDatabaseAlias(InputPosition p, boolean replace, DatabaseName aliasName, DatabaseName targetName, boolean ifNotExists, SimpleEither> url, SimpleEither, Parameter> username, Expression password, SimpleEither, Parameter> driverSettings, SimpleEither, Parameter> properties) { throw new UnsupportedOperationException(); } @Override public Statement alterLocalDatabaseAlias(InputPosition p, DatabaseName aliasName, DatabaseName targetName, boolean ifExists, SimpleEither, Parameter> properties) { throw new UnsupportedOperationException(); } @Override public Statement alterRemoteDatabaseAlias(InputPosition p, DatabaseName aliasName, DatabaseName targetName, boolean ifExists, SimpleEither> url, SimpleEither, Parameter> username, Expression password, SimpleEither, Parameter> driverSettings, SimpleEither, Parameter> properties) { throw new UnsupportedOperationException(); } @Override public Expression newVariable(InputPosition p, String name) { return applyCallbacksFor(ExpressionCreatedEventType.ON_NEW_VARIABLE, Cypher.name(name)); } @Override public Parameter newParameter(InputPosition p, Expression v, ParameterType type) { return applyCallbacksFor(ExpressionCreatedEventType.ON_NEW_PARAMETER, parameterFromSymbolicName(v)); } @Override public Parameter newParameter(InputPosition p, String v, ParameterType type) { return applyCallbacksFor(ExpressionCreatedEventType.ON_NEW_PARAMETER, parameterFromSymbolicName(Cypher.name(v))); } @Override public Parameter newSensitiveStringParameter(InputPosition p, Expression v) { throw new UnsupportedOperationException("The Cypher-DSL does not support sensitive parameters."); } @Override public Parameter newSensitiveStringParameter(InputPosition p, String v) { throw new UnsupportedOperationException("The Cypher-DSL does not support sensitive parameters."); } Parameter parameterFromSymbolicName(Expression v) { var symbolicName = assertSymbolicName(v); if (symbolicName == null) { return Cypher.anonParameter(Cypher.literalNull()); } var name = symbolicName.getValue(); return this.options.getParameterValues().containsKey(name) ? Cypher.parameter(name, this.options.getParameterValues().get(name)) : Cypher.parameter(name); } @Override public Expression newDouble(InputPosition p, String image) { return applyCallbacksFor(ExpressionCreatedEventType.ON_NEW_LITERAL, Cypher.literalOf(Double.parseDouble(image))); } @Override public Expression newDecimalInteger(InputPosition p, String image, boolean negated) { return applyCallbacksFor(ExpressionCreatedEventType.ON_NEW_LITERAL, Cypher.literalOf(Long.parseUnsignedLong(image) * (negated ? -1 : 1))); } @Override public Expression newHexInteger(InputPosition p, String image, boolean negated) { return applyCallbacksFor(ExpressionCreatedEventType.ON_NEW_LITERAL, Cypher.literalOf(Long.parseUnsignedLong(image.replaceFirst("(?i)0x", ""), 16) * (negated ? -1 : 1))); } @Override public Expression newOctalInteger(InputPosition p, String image, boolean negated) { return applyCallbacksFor(ExpressionCreatedEventType.ON_NEW_LITERAL, Cypher.literalOf(Long.parseUnsignedLong(image.replaceFirst("(?i)0o", ""), 8) * (negated ? -1 : 1))); } @Override public Expression newString(InputPosition start, InputPosition end, String image) { return applyCallbacksFor(ExpressionCreatedEventType.ON_NEW_LITERAL, Cypher.literalOf(image)); } @Override public Expression newTrueLiteral(InputPosition p) { return applyCallbacksFor(ExpressionCreatedEventType.ON_NEW_LITERAL, Cypher.literalTrue()); } @Override public Expression newFalseLiteral(InputPosition p) { return applyCallbacksFor(ExpressionCreatedEventType.ON_NEW_LITERAL, Cypher.literalFalse()); } @Override public Expression newInfinityLiteral(InputPosition p) { return applyCallbacksFor(ExpressionCreatedEventType.ON_NEW_LITERAL, InfinityLiteral.INSTANCE); } @Override public Expression newNaNLiteral(InputPosition p) { return applyCallbacksFor(ExpressionCreatedEventType.ON_NEW_LITERAL, NaNLiteral.INSTANCE); } @Override public Expression newNullLiteral(InputPosition p) { return applyCallbacksFor(ExpressionCreatedEventType.ON_NEW_LITERAL, Cypher.literalNull()); } @Override public Expression listLiteral(InputPosition p, List values) { return Cypher.listOf(values.toArray(new Expression[0])); } @Override public MapExpression mapLiteral(InputPosition p, List> keys, List values) { Object[] keysAndValues = new Object[keys.size() * 2]; int i = 0; Iterator valueIterator = values.iterator(); for (StringPos key : keys) { keysAndValues[i++] = key.string; keysAndValues[i++] = valueIterator.next(); } return this.options.isCreateSortedMaps() ? Cypher.sortedMapOf(keysAndValues) : Cypher.mapOf(keysAndValues); } @Override public Property property(Expression subject, StringPos propertyKeyName) { return subject.property(propertyKeyName.string); } @Override public Expression or(InputPosition p, Expression lhs, Expression rhs) { return lhs.asCondition().or(rhs.asCondition()); } @Override public Expression xor(InputPosition p, Expression lhs, Expression rhs) { return lhs.asCondition().xor(rhs.asCondition()); } @Override public Expression and(InputPosition p, Expression lhs, Expression rhs) { return lhs.asCondition().and(rhs.asCondition()); } @Override public Labels labelConjunction(InputPosition p, Labels lhs, Labels rhs, boolean containsIs) { return lhs.and(rhs); } @Override public Labels labelDisjunction(InputPosition p, Labels lhs, Labels rhs, boolean containsIs) { return lhs.or(rhs); } @Override public Labels labelNegation(InputPosition p, Labels e, boolean containsIs) { return e.negate(); } @Override public Labels labelWildcard(InputPosition p, boolean containsIs) { throw new UnsupportedOperationException(); } @Override public Labels labelLeaf(InputPosition p, String e, EntityType entityType, boolean containsIs) { return Cypher.exactlyLabel(e); } @Override public Labels labelColonConjunction(InputPosition p, Labels lhs, Labels rhs, boolean containsIs) { return lhs.conjunctionWith(rhs); } @Override public Labels labelColonDisjunction(InputPosition p, Labels lhs, Labels rhs, boolean containsIs) { return lhs.disjunctionWith(rhs); } @Override public Expression labelExpressionPredicate(Expression subject, Labels exp) { if (!(subject instanceof SymbolicName symbolicName)) { throw new IllegalArgumentException( "Expected an symbolic name to create a label based expression predicate!"); } return Cypher.hasLabelsOrType(symbolicName, exp); } @Override public Expression ands(List exprs) { return exprs.stream().reduce(Cypher.noCondition(), (l, r) -> l.asCondition().and(r.asCondition())); } @Override public Expression not(InputPosition p, Expression e) { return e.asCondition().not(); } @Override public Expression plus(InputPosition p, Expression lhs, Expression rhs) { return lhs.add(rhs); } @Override public Expression minus(InputPosition p, Expression lhs, Expression rhs) { return lhs.subtract(rhs); } @Override public Expression concatenate(InputPosition p, Expression lhs, Expression rhs) { return lhs.concat(rhs); } @Override public Expression multiply(InputPosition p, Expression lhs, Expression rhs) { return lhs.multiply(rhs); } @Override public Expression divide(InputPosition p, Expression lhs, Expression rhs) { return lhs.divide(rhs); } @Override public Expression modulo(InputPosition p, Expression lhs, Expression rhs) { return lhs.remainder(rhs); } @Override public Expression pow(InputPosition p, Expression lhs, Expression rhs) { return lhs.pow(rhs); } @Override public Expression unaryPlus(Expression e) { return Cypher.plus(e); } @Override public Expression unaryPlus(InputPosition inputPosition, Expression expression) { return Cypher.plus(expression); } @Override public Expression unaryMinus(InputPosition inputPosition, Expression expression) { return Cypher.minus(expression); } @Override public Expression eq(InputPosition p, Expression lhs, Expression rhs) { return lhs.eq(rhs); } @Override public Expression neq(InputPosition p, Expression lhs, Expression rhs) { return lhs.ne(rhs); } @Override public Expression neq2(InputPosition p, Expression lhs, Expression rhs) { return lhs.ne(rhs); } @Override public Expression lte(InputPosition p, Expression lhs, Expression rhs) { return lhs.lte(rhs); } @Override public Expression gte(InputPosition p, Expression lhs, Expression rhs) { return lhs.gte(rhs); } @Override public Expression lt(InputPosition p, Expression lhs, Expression rhs) { return lhs.lt(rhs); } @Override public Expression gt(InputPosition p, Expression lhs, Expression rhs) { return lhs.gt(rhs); } @Override public Expression regeq(InputPosition p, Expression lhs, Expression rhs) { return lhs.matches(rhs); } @Override public Expression startsWith(InputPosition p, Expression lhs, Expression rhs) { return lhs.startsWith(rhs); } @Override public Expression endsWith(InputPosition p, Expression lhs, Expression rhs) { return lhs.endsWith(rhs); } @Override public Expression contains(InputPosition p, Expression lhs, Expression rhs) { return lhs.contains(rhs); } @Override public Expression in(InputPosition p, Expression lhs, Expression rhs) { return lhs.in(rhs); } @Override public Expression isNull(InputPosition p, Expression e) { return e.isNull(); } @Override public Expression isNotNull(InputPosition p, Expression e) { return e.isNotNull(); } @Override public Expression isTyped(InputPosition p, Expression e, ParserCypherTypeName typeName) { throw new UnsupportedOperationException(); } @Override public Expression isNotTyped(InputPosition p, Expression e, ParserCypherTypeName typeName) { throw new UnsupportedOperationException(); } @Override public Expression isNormalized(InputPosition p, Expression e, ParserNormalForm normalForm) { throw new UnsupportedOperationException(); } @Override public Expression isNotNormalized(InputPosition p, Expression e, ParserNormalForm normalForm) { throw new UnsupportedOperationException(); } @Override public Expression listLookup(Expression list, Expression index) { return Cypher.valueAt(list, index); } @Override public Expression listSlice(InputPosition p, Expression list, Expression start, Expression end) { return Cypher.subList(list, start, end); } @Override public Expression newCountStar(InputPosition p) { return Cypher.count(Cypher.asterisk()); } @Override public Expression functionInvocation(InputPosition p, InputPosition functionNamePosition, List namespace, String name, boolean distinct, List arguments, boolean calledFromUseClause) { String[] parts = new String[namespace.size() + 1]; for (int i = 0; i < namespace.size(); i++) { parts[i] = namespace.get(i); } parts[parts.length - 1] = name; var expression = Cypher.call(parts).withArgs(arguments.toArray(Expression[]::new)).asFunction(distinct); return applyCallbacksFor(InvocationCreatedEventType.ON_INVOCATION, expression); } @Override public Expression listComprehension(InputPosition p, Expression v, Expression list, Expression where, Expression projection) { var in = Cypher.listWith(assertSymbolicName(v)).in(list); if (where != null) { var ongoingComprehension = in.where(where.asCondition()); if (projection != null) { return ongoingComprehension.returning(projection); } return ongoingComprehension.returning(); } return in.returning(projection); } @Override public Expression patternComprehension(InputPosition p, InputPosition relationshipPatternPosition, Expression v, PatternElement patternElement, Expression where, Expression projection) { PatternComprehension.OngoingDefinitionWithoutReturn ongoingDefinitionWithPattern; if (patternElement instanceof RelationshipPattern relationshipPattern) { if (v != null) { ongoingDefinitionWithPattern = Cypher .listBasedOn(Cypher.path(assertSymbolicName(v)).definedBy(relationshipPattern)); } else { ongoingDefinitionWithPattern = Cypher.listBasedOn(relationshipPattern); } } else if (patternElement instanceof NamedPath namedPath) { ongoingDefinitionWithPattern = Cypher.listBasedOn(namedPath); } else { throw new IllegalArgumentException( "Cannot build a pattern comprehension around " + patternElement.getClass().getSimpleName()); } if (where != null) { ongoingDefinitionWithPattern = ((PatternComprehension.OngoingDefinitionWithPattern) ongoingDefinitionWithPattern) .where(where.asCondition()); } return ongoingDefinitionWithPattern.returning(projection); } @Override public Expression reduceExpression(InputPosition p, Expression acc, Expression accExpr, Expression v, Expression list, Expression innerExpr) { var variable = assertSymbolicName(v); if (variable == null) { throw new IllegalArgumentException("A variable to be reduced must be present."); } return Cypher.reduce(variable) .in(list) .map(innerExpr) .accumulateOn(assertSymbolicName(acc)) .withInitialValueOf(accExpr); } @Override public Expression allExpression(InputPosition p, Expression v, Expression list, Expression where) { notNull(where, "all(...) requires a WHERE predicate"); return Cypher.all(assertSymbolicName(v)).in(list).where(where.asCondition()); } @Override public Expression anyExpression(InputPosition p, Expression v, Expression list, Expression where) { notNull(where, "any(...) requires a WHERE predicate"); return Cypher.any(assertSymbolicName(v)).in(list).where(where.asCondition()); } @Override public Expression noneExpression(InputPosition p, Expression v, Expression list, Expression where) { notNull(where, "none(...) requires a WHERE predicate"); return Cypher.none(assertSymbolicName(v)).in(list).where(where.asCondition()); } @Override public Expression singleExpression(InputPosition p, Expression v, Expression list, Expression where) { notNull(where, "single(...) requires a WHERE predicate"); return Cypher.single(assertSymbolicName(v)).in(list).where(where.asCondition()); } @Override public Expression normalizeExpression(InputPosition p, Expression i, ParserNormalForm normalForm) { throw new UnsupportedOperationException(); } @Override public Expression trimFunction(InputPosition inputPosition, ParserTrimSpecification parserTrimSpecification, Expression expression, Expression expression1) { var call = switch (parserTrimSpecification) { case BOTH -> Cypher.call("trim"); case LEADING -> Cypher.call("ltrim"); case TRAILING -> Cypher.call("rtrim"); }; return call .withArgs((expression != null) ? new Expression[] { expression1, expression } : new Expression[] { expression1 }) .asFunction(); } @Override public Expression patternExpression(InputPosition p, PatternElement patternElement) { if (patternElement instanceof ExpressionAsPatternElementWrapper wrapper) { return wrapper.getExpression(); } if (patternElement instanceof RelationshipPattern relationshipPattern) { return new PatternElementAsExpressionWrapper(relationshipPattern); } throw new UnsupportedOperationException(); } @Override public Expression existsExpression(InputPosition p, NULL matchMode, List patternElements, Statement q, Where where) { if (q == null) { return Cypher.exists(patternElements, where); } else { return Cypher.exists(q); } } @Override public Expression countExpression(InputPosition p, NULL matchMode, List patternElements, Statement q, Where where) { if (q == null) { return Cypher.count(patternElements, where); } else { return Cypher.count(q); } } @Override public Expression collectExpression(InputPosition inputPosition, Statement statement) { return Cypher.collect(statement); } @Override public Expression mapProjection(InputPosition p, Expression v, List items) { return this.options.isCreateSortedMaps() ? MapProjection.sorted(assertSymbolicName(v), items.toArray(new Object[0])) : MapProjection.create(assertSymbolicName(v), items.toArray(new Object[0])); } @Override public Expression mapProjectionLiteralEntry(StringPos property, Expression value) { return KeyValueMapEntry.create(property.string, value); } @Override public Expression mapProjectionProperty(StringPos property) { return PropertyLookup.forName(property.string); } @Override public Expression mapProjectionVariable(Expression v) { return v; } @Override public Expression mapProjectionAll(InputPosition p) { return Cypher.asterisk(); } @Override public Expression caseExpression(InputPosition p, Expression e, List whens, List thens, Expression elze) { if (whens != null && thens != null && whens.size() != thens.size()) { throw new IllegalArgumentException("Cannot combine lists of whens with a different sized list of thens."); } var aCase = Cypher.caseExpression(e); if (whens != null && thens != null) { var iteratorWhens = whens.iterator(); var iteratorThens = thens.iterator(); while (iteratorWhens.hasNext() && iteratorThens.hasNext()) { var when = iteratorWhens.next(); if (e != null && when instanceof Comparison comparison && comparison.getComparator().equals(Operator.EQUALITY) && e.equals(comparison.getLeft())) { aCase = aCase.when(comparison.getRight()).then(iteratorThens.next()); } else { aCase = aCase.when(when).then(iteratorThens.next()); } } if (elze != null) { return ((Case.CaseEnding) aCase).elseDefault(elze); } return aCase; } return aCase; } @Override public InputPosition inputPosition(int offset, int line, int column) { return new InputPosition(offset, line, column); } @Override public EntityType nodeType() { return EntityType.NODE; } @Override public EntityType relationshipType() { return EntityType.RELATIONSHIP; } @Override public EntityType nodeOrRelationshipType() { return EntityType.LOLWHAT; } @Override public Where whereClause(InputPosition p, Expression optionalWhere) { return Where.from(optionalWhere); } @Override public NULL subqueryInTransactionsBatchParameters(InputPosition p, Expression batchSize) { throw new UnsupportedOperationException(); } @Override public NULL subqueryInTransactionsConcurrencyParameters(InputPosition p, Expression concurrency) { throw new UnsupportedOperationException(); } @Override public NULL subqueryInTransactionsErrorParameters(InputPosition p, CallInTxsOnErrorBehaviourType onErrorBehaviour) { throw new UnsupportedOperationException(); } @Override public NULL subqueryInTransactionsReportParameters(InputPosition p, Expression v) { throw new UnsupportedOperationException(); } @Override public Clause orderBySkipLimitClause(InputPosition t, List order, InputPosition orderPos, Expression skip, InputPosition skipPos, Expression limit, InputPosition limitPos) { return Clauses.orderBy(order, skip, limit); } @Override public Clause showTransactionsClause(InputPosition p, SimpleEither, Expression> ids, Where where, Clause yieldClause) { throw new UnsupportedOperationException(); } @Override public Clause terminateTransactionsClause(InputPosition p, SimpleEither, Expression> ids, Where where, Clause yieldClause) { throw new UnsupportedOperationException(); } @Override public Clause showSettingsClause(InputPosition p, SimpleEither, Expression> names, Where where, Clause yieldClause) { throw new UnsupportedOperationException(); } @Override public Clause turnYieldToWith(Clause yieldClause) { throw new UnsupportedOperationException(); } @Override public Statement createConstraint(InputPosition p, ConstraintType constraintType, boolean replace, boolean ifNotExists, SimpleEither, Parameter> constraintName, Expression expression, StringPos label, List properties, ParserCypherTypeName propertyType, SimpleEither, Parameter> constraintOptions) { throw new UnsupportedOperationException(); } @Override public Statement dropConstraint(InputPosition p, SimpleEither, Parameter> name, boolean ifExists) { throw new UnsupportedOperationException(); } @Override public Statement createLookupIndex(InputPosition p, boolean replace, boolean ifNotExists, boolean isNode, SimpleEither, Parameter> indexName, Expression expression, StringPos functionName, Expression functionParameter, SimpleEither, Parameter> indexOptions) { throw new UnsupportedOperationException(); } @Override public Statement createIndex(InputPosition p, boolean replace, boolean ifNotExists, boolean isNode, SimpleEither, Parameter> indexName, Expression expression, StringPos label, List properties, SimpleEither, Parameter> indexOptions, CreateIndexTypes indexType) { throw new UnsupportedOperationException(); } @Override public Statement createFulltextIndex(InputPosition p, boolean replace, boolean ifNotExists, boolean isNode, SimpleEither, Parameter> indexName, Expression expression, List> labels, List properties, SimpleEither, Parameter> indexOptions) { throw new UnsupportedOperationException(); } @Override public Statement dropIndex(InputPosition p, SimpleEither, Parameter> name, boolean ifExists) { throw new UnsupportedOperationException(); } @Override public Statement createRole(InputPosition p, boolean replace, SimpleEither, Parameter> roleName, SimpleEither, Parameter> fromRole, boolean ifNotExists, boolean immutable) { throw new UnsupportedOperationException(); } @Override public Statement dropRole(InputPosition p, SimpleEither, Parameter> roleName, boolean ifExists) { throw new UnsupportedOperationException(); } @Override public Statement renameRole(InputPosition p, SimpleEither, Parameter> fromRoleName, SimpleEither, Parameter> toRoleName, boolean ifExists) { throw new UnsupportedOperationException(); } @Override public NULL databasePrivilege(InputPosition p, NULL aNull, NULL aNull2, List qualifier, boolean immutable) { throw new UnsupportedOperationException(); } @Override public NULL dbmsPrivilege(InputPosition p, NULL aNull, List qualifier, boolean immutable) { throw new UnsupportedOperationException(); } @Override public NULL loadPrivilege(InputPosition inputPosition, SimpleEither> simpleEither, SimpleEither> simpleEither1, boolean b) { return null; } @Override public NULL graphPrivilege(InputPosition inputPosition, NULL aNull, NULL aNull2, NULL aNull3, List qualifier, boolean immutable) { throw new UnsupportedOperationException(); } @Override public NULL privilegeAction(ActionType action) { throw new UnsupportedOperationException(); } @Override public NULL propertiesResource(InputPosition p, List property) { throw new UnsupportedOperationException(); } @Override public NULL allPropertiesResource(InputPosition p) { throw new UnsupportedOperationException(); } @Override public NULL labelsResource(InputPosition p, List label) { throw new UnsupportedOperationException(); } @Override public NULL allLabelsResource(InputPosition p) { throw new UnsupportedOperationException(); } @Override public NULL databaseResource(InputPosition p) { throw new UnsupportedOperationException(); } @Override public NULL noResource(InputPosition p) { throw new UnsupportedOperationException(); } @Override public NULL labelQualifier(InputPosition p, String label) { throw new UnsupportedOperationException(); } @Override public NULL relationshipQualifier(InputPosition p, String relationshipType) { throw new UnsupportedOperationException(); } @Override public NULL elementQualifier(InputPosition p, String name) { throw new UnsupportedOperationException(); } @Override public NULL allElementsQualifier(InputPosition p) { throw new UnsupportedOperationException(); } @Override public NULL patternQualifier(List list, Expression expression, Expression expression2) { throw new UnsupportedOperationException(); } @Override public NULL allLabelsQualifier(InputPosition p) { throw new UnsupportedOperationException(); } @Override public NULL allRelationshipsQualifier(InputPosition p) { throw new UnsupportedOperationException(); } @Override public List allQualifier() { throw new UnsupportedOperationException(); } @Override public List allDatabasesQualifier() { throw new UnsupportedOperationException(); } @Override public List userQualifier(List, Parameter>> users) { throw new UnsupportedOperationException(); } @Override public List allUsersQualifier() { throw new UnsupportedOperationException(); } @Override public List functionQualifier(InputPosition p, List functions) { throw new UnsupportedOperationException(); } @Override public List procedureQualifier(InputPosition p, List procedures) { throw new UnsupportedOperationException(); } @Override public List settingQualifier(InputPosition inputPosition, List list) { throw new UnsupportedOperationException(); } @Override public NULL graphScope(InputPosition inputPosition, List graphNames, ScopeType scopeType) { throw new UnsupportedOperationException(); } @Override public NULL databasePrivilegeScope(InputPosition inputPosition, List list, ScopeType scopeType) { throw new UnsupportedOperationException(); } @Override public Labels dynamicLabelLeaf(InputPosition p, Expression e, EntityType entityType, boolean all, boolean containsIs) { if (all) { return Cypher.allLabels(e); } return Cypher.anyLabel(e); } @Override public Statement enableServer(InputPosition p, SimpleEither> serverName, SimpleEither, Parameter> serverOptions) { throw new UnsupportedOperationException(); } @Override public Statement alterServer(InputPosition p, SimpleEither> serverName, SimpleEither, Parameter> serverOptions) { throw new UnsupportedOperationException(); } @Override public Statement renameServer(InputPosition p, SimpleEither> serverName, SimpleEither> newName) { throw new UnsupportedOperationException(); } @Override public Statement dropServer(InputPosition p, SimpleEither> serverName) { throw new UnsupportedOperationException(); } @Override public Statement showServers(InputPosition p, Clause yieldExpr, Return returnWithoutGraph, Where where) { throw new UnsupportedOperationException(); } @Override public Statement deallocateServers(InputPosition p, boolean dryRun, List>> serverNames) { throw new UnsupportedOperationException(); } @Override public Statement reallocateDatabases(InputPosition p, boolean dryRun) { throw new UnsupportedOperationException(); } static class PatternJuxtaposition extends TypedSubtree implements PatternElement { PatternJuxtaposition(Collection children) { super(children); } @Override public String separator() { return " "; } } static class PatternList extends TypedSubtree implements PatternElement { PatternList(Collection children) { super(children); } } } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/CypherParser.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypher.internal.parser.javacc.CharStream; import org.neo4j.cypher.internal.parser.javacc.Cypher; import org.neo4j.cypher.internal.parser.javacc.CypherCharStream; import org.neo4j.cypher.internal.parser.javacc.ParseException; import org.neo4j.cypherdsl.core.Clause; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.RelationshipPattern; import org.neo4j.cypherdsl.core.Statement; import static org.apiguardian.api.API.Status.STABLE; /** * Main entrypoint to a Cypher parser that produces Cypher-DSL statements. * * @author Michael J. Simons * @since 2021.3.0 */ @API(status = STABLE, since = "2021.3.0") public final class CypherParser { /** * Not to be instantiated. */ private CypherParser() { } /** * Parses a single node. * @param input a Cypher fragment * @return a node * @see #parseNode(String, Options) */ public static Node parseNode(String input) { return parseNode(input, Options.defaultOptions()); } /** * Parses a Cypher fragment describing a Node-pattern into a {@link Node} instance. * @param input a Cypher fragment * @param options options for the parser * @return a node */ public static Node parseNode(String input, Options options) { return handle(input, () -> new Cypher<>(CypherDslASTFactory.getInstance(options), CypherDslASTExceptionFactory.INSTANCE, getCharStream(input)) .NodePattern()).value(); } /** * Parses a single relationship. * @param input a Cypher fragment * @return a relationship pattern or chain of relationship pattern * @see #parseNode(String, Options) */ public static RelationshipPattern parseRelationship(String input) { return parseRelationship(input, Options.defaultOptions()); } /** * Parses a Cypher fragment describing a relationship into a * {@link RelationshipPattern} instance. * @param input a Cypher fragment * @param options options for the parser * @return a relationship pattern or chain of relationship pattern */ public static RelationshipPattern parseRelationship(String input, Options options) { return handle(input, () -> (RelationshipPattern) new Cypher<>(CypherDslASTFactory.getInstance(options), CypherDslASTExceptionFactory.INSTANCE, getCharStream(input)) .Pattern()); } /** * Parses a full expression. * @param input a Cypher fragment of an expression * @return a valid Cypher-DSL expression instance * @see #parseExpression(String, Options) */ public static Expression parseExpression(String input) { return parseExpression(input, Options.defaultOptions()); } /** * Parses a Cypher expression into an {@link Expression}. * @param input a Cypher fragment of an expression * @param options options for the parser * @return a valid Cypher-DSL expression instance */ public static Expression parseExpression(String input, Options options) { return handle(input, () -> new Cypher<>(CypherDslASTFactory.getInstance(options), CypherDslASTExceptionFactory.INSTANCE, getCharStream(input)) .Expression()); } /** * Parses a single clause. * @param input a Cypher fragment containing a valid clause * @return a {@link Clause} instance * @see #parseClause(String, Options) */ public static Clause parseClause(String input) { return parseClause(input, Options.defaultOptions()); } /** * Parses a fragment into a {@link Clause} that can be put together into a whole * statement via {@link Statement#of(List)}. * @param input a Cypher fragment containing a valid clause * @param options options for the parser * @return a {@link Clause} instance */ public static Clause parseClause(String input, Options options) { return handle(input, () -> new Cypher<>(CypherDslASTFactory.getInstance(options), CypherDslASTExceptionFactory.INSTANCE, getCharStream(input)) .Clause()); } /** * Parses a full statement. * @param input string representing a statement * @return a {@link Statement} statement. * @see #parseStatement(String, Options) */ public static Statement parseStatement(String input) { return parseStatement(input, Options.defaultOptions()); } /** * Parses a whole statement into a renderable Cypher-DSL {@link Statement}. The * statement might be used in a subquery, with a union or maybe just rewritten. * @param input string representing a statement * @param options options for the parser * @return a {@link Statement} statement. */ public static Statement parseStatement(String input, Options options) { return handle(input, () -> new Cypher<>(CypherDslASTFactory.getInstance(options), CypherDslASTExceptionFactory.INSTANCE, getCharStream(input)) .Statement()); } /** * Parses a {@link String} into a {@link Statement}. * @param input string representing a statement * @return a {@link Statement} statement. * @see #parseStatement(String) */ public static Statement parse(String input) { return parse(input, Options.defaultOptions()); } /** * Parses a {@link String} into a {@link Statement}. * @param input string representing a statement * @param options options for the parser * @return a {@link Statement} statement. * @see #parseStatement(String, Options) */ public static Statement parse(String input, Options options) { return parseStatement(input, options); } private static T handle(String input, ThrowingParser parser) { try { return parser.parse(); } catch (ParseException ex) { throw new CyperDslParseException(ex); } catch (IllegalArgumentException ex) { throw ex; } catch (UnsupportedOperationException ex) { throw new UnsupportedCypherException(input, ex); } catch (Exception ex) { throw new RuntimeException("Unexpected exception", ex); } } private static CharStream getCharStream(String input) { return new CypherCharStream(input); } @FunctionalInterface private interface ThrowingParser { T parse() throws Exception; } } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/DatabaseName.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.neo4j.cypherdsl.core.Expression; /** * Value object for expressions representing database names. * * @param value representing a database name. * @author Michael J. Simons */ public record DatabaseName(Expression value) { } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/EntityType.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.neo4j.cypher.internal.ast.factory.ASTFactory; /** * The type of a parsed entity. * * @author Michael J. Simons * @since 2023.0.0 */ enum EntityType { NODE, RELATIONSHIP, /** * Why the hell is there {@link ASTFactory#nodeOrRelationshipType()} ?! */ LOLWHAT } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/ExpressionAsPatternElementWrapper.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.PatternElement; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; /** * Necessary for {@code shortestPath} and friends. * * @author Michael J. Simons * @since 2021.3.0 */ @API(status = INTERNAL, since = "2021.3.0") final class ExpressionAsPatternElementWrapper implements PatternElement { private final Expression expression; ExpressionAsPatternElementWrapper(Expression expression) { this.expression = expression; } Expression getExpression() { return this.expression; } @Override public void accept(Visitor visitor) { this.expression.accept(visitor); } } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/ExpressionCreatedEventType.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.Operation; import static org.apiguardian.api.API.Status.STABLE; /** * The type of the event when a new expression is parsed and instantiated. Callbacks are * tied to this type. * * @author Michael J. Simons * @since 2021.3.0 */ @API(status = STABLE, since = "2021.3.0") public enum ExpressionCreatedEventType { /** * Fired for every new return item. */ ON_RETURN_ITEM(Expression.class), /** * Fired when a property is set. */ ON_SET_PROPERTY(Operation.class), /** * Fired when a label is defined. */ ON_SET_LABELS(Operation.class), /** * Fired when a variable is defined. */ ON_SET_VARIABLE(Operation.class), /** * Fired when a variable is added or redefined (set). */ ON_ADD_AND_SET_VARIABLE(Operation.class), /** * Fired when parsing a property removal expression. */ ON_REMOVE_PROPERTY(Expression.class), /** * Fired when parsing a label removing expression. */ ON_REMOVE_LABELS(Expression.class), /** * Fired when parsing a new variable expression. */ ON_NEW_VARIABLE(Expression.class), /** * Fired when parsing a new parameter expression. */ ON_NEW_PARAMETER(Expression.class), /** * Fired when preparing a {@code DELETE x, y, z} clause. */ ON_DELETE_ITEM(Expression.class), /** * Fired when new literals are created. */ ON_NEW_LITERAL(Expression.class); private final Class typeProduced; ExpressionCreatedEventType(Class typeProduced) { this.typeProduced = typeProduced; } /** * {@return the actual type that will be produced, might be a specialization} */ Class getTypeProduced() { return this.typeProduced; } } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/InfinityLiteral.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.neo4j.cypherdsl.core.Literal; /** * Custom literal for infinity. * * @author Michael J. Simons * @since 2023.0.0 */ enum InfinityLiteral implements Literal { INSTANCE; @Override public String asString() { return "Infinity"; } } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/InputPosition.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.INTERNAL; /** * Needed for implementing the {@link org.neo4j.cypher.internal.ast.factory.ASTFactory}. * * @param offset the offset * @param line the line number * @param column the column number * @author Michael J. Simons * @since 2021.3.0 */ @API(status = INTERNAL, since = "2021.3.0") record InputPosition(int offset, int line, int column) { } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/InvocationCreatedEventType.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.neo4j.cypherdsl.core.Clause; import org.neo4j.cypherdsl.core.Expression; /** * The type of event emitted when creating a procedure call or function invocation. * * @author Michael J. Simons * @since 2022.8.6 */ public enum InvocationCreatedEventType { /** * When parsing a {@code CALL x.y(z)} like statement. */ ON_CALL(Clause.class), /** * When parsing a {@code sin(x)} like statement. */ ON_INVOCATION(Expression.class); private final Class typeProduced; InvocationCreatedEventType(Class typeProduced) { this.typeProduced = typeProduced; } Class getTypeProduced() { return this.typeProduced; } } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/LabelParsedEventType.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * Type of an event when a label is parsed and created. * * @author Michael J. Simons * @since 2021.3.0 */ @API(status = STABLE, since = "2021.3.0") public enum LabelParsedEventType { /** * Parsed when creating a {@link org.neo4j.cypherdsl.core.Node node pattern}. */ ON_NODE_PATTERN, /** * Parsed in the context of setting new labels. */ ON_SET, /** * Parsed in the context of removing labels. */ ON_REMOVE } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/MatchDefinition.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.util.List; import org.neo4j.cypherdsl.core.Hint; import org.neo4j.cypherdsl.core.PatternElement; import org.neo4j.cypherdsl.core.Where; /** * Shape of a match clause. * * @author Michael J. Simons * @param optional flag if this is an optional match * @param patternElements the pattern elements inside the match * @param optionalWhere an optional where clause * @param optionalHints optional hints * @since 2023.0.2 */ public record MatchDefinition(boolean optional, List patternElements, Where optionalWhere, List optionalHints) { } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/NaNLiteral.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.neo4j.cypherdsl.core.Literal; /** * Custom literal for {@link Double#NaN}. * * @author Michael J. Simons * @since 2023.0.0 */ enum NaNLiteral implements Literal { INSTANCE; @Override public String asString() { return "NaN"; } } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/NodeAtom.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.PatternElement; /** * A value object for {@link Node nodes}. * * @author Michael J. Simons * @param value the actual node * @since 2023.0.0 */ record NodeAtom(Node value) implements PatternAtom, PatternElement { } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/Options.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.UnaryOperator; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Clauses; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.Match; import org.neo4j.cypherdsl.core.PatternElement; import org.neo4j.cypherdsl.core.Return; import org.neo4j.cypherdsl.core.Where; import org.neo4j.cypherdsl.core.ast.Visitable; import static org.apiguardian.api.API.Status.STABLE; /** * Provides arguments to the {@link CypherParser cypher parser}. The options itself are * thread safe and can be reused. The listener and modifications you provide are of course * out of our control. * * @author Michael J. Simons * @since 2021.3.0 */ @API(status = STABLE, since = "2021.3.0") public final class Options { private static final Options DEFAULT_OPTIONS = newOptions().build(); private final BiFunction, Collection> labelFilter; private final BiFunction, Collection> typeFilter; private final Map>> onNewExpressionCallbacks; private final Map>> onNewPatternElementCallbacks; private final Map>> onNewInvocationCallbacks; private final Function returnClauseFactory; private final Function matchClauseFactory; private final boolean createSortedMaps; private final Map parameterValues; private boolean alwaysCreateRelationshipsLTR; private Options(Builder builder) { this.labelFilter = builder.labelFilter; this.typeFilter = builder.typeFilter; Map>> tmp = new EnumMap<>( ExpressionCreatedEventType.class); builder.onNewExpressionCallbacks.forEach((k, v) -> tmp.put(k, List.copyOf(v))); this.onNewExpressionCallbacks = Map.copyOf(tmp); Map>> tmp2 = new EnumMap<>( PatternElementCreatedEventType.class); builder.onNewPatternElementCallbacks.forEach((k, v) -> tmp2.put(k, List.copyOf(v))); this.onNewPatternElementCallbacks = Map.copyOf(tmp2); Map>> tmp3 = new EnumMap<>( InvocationCreatedEventType.class); builder.onNewInvocationCallbacks.forEach((k, v) -> tmp3.put(k, List.copyOf(v))); this.onNewInvocationCallbacks = Map.copyOf(tmp3); this.returnClauseFactory = (builder.returnClauseFactory != null) ? builder.returnClauseFactory : returnDefinition -> Clauses.returning(returnDefinition.isDistinct(), returnDefinition.getExpressions(), returnDefinition.getOptionalSortItems(), returnDefinition.getOptionalSkip(), returnDefinition.getOptionalLimit()); this.matchClauseFactory = (builder.matchClauseFactory != null) ? builder.matchClauseFactory : returnDefinition -> (Match) Clauses.match(returnDefinition.optional(), returnDefinition.patternElements(), returnDefinition.optionalWhere(), returnDefinition.optionalHints()); this.createSortedMaps = builder.createSortedMaps; this.alwaysCreateRelationshipsLTR = builder.alwaysCreateRelationshipsLTR; this.parameterValues = builder.parameterValues; } /** * {@return the default options} */ public static Options defaultOptions() { return DEFAULT_OPTIONS; } /** * Use this method to start defining new options for a parser. * @return a builder for new options. */ public static Builder newOptions() { return Builder.newConfig(); } BiFunction, Collection> getLabelFilter() { return this.labelFilter; } BiFunction, Collection> getTypeFilter() { return this.typeFilter; } Map>> getOnNewExpressionCallbacks() { return this.onNewExpressionCallbacks; } Map>> getOnNewPatternElementCallbacks() { return this.onNewPatternElementCallbacks; } Function getReturnClauseFactory() { return this.returnClauseFactory; } Function getMatchClauseFactory() { return this.matchClauseFactory; } boolean isCreateSortedMaps() { return this.createSortedMaps; } boolean isAlwaysCreateRelationshipsLTR() { return this.alwaysCreateRelationshipsLTR; } Map>> getOnNewInvocationCallbacks() { return this.onNewInvocationCallbacks; } /** * {@return true if these are the default options} */ boolean areDefault() { return this == DEFAULT_OPTIONS; } Map getParameterValues() { return this.parameterValues; } /** * Use this builder to create a new set of options.. */ @SuppressWarnings("HiddenField") public static final class Builder { private final Map>> onNewExpressionCallbacks = new EnumMap<>( ExpressionCreatedEventType.class); private final Map>> onNewPatternElementCallbacks = new EnumMap<>( PatternElementCreatedEventType.class); private final Map>> onNewInvocationCallbacks = new EnumMap<>( InvocationCreatedEventType.class); private BiFunction, Collection> labelFilter = (e, l) -> l; private BiFunction, Collection> typeFilter = (e, t) -> t; private Function returnClauseFactory; private Function matchClauseFactory; private Map parameterValues = Map.of(); private boolean createSortedMaps = false; private boolean alwaysCreateRelationshipsLTR = false; private Builder() { } static Builder newConfig() { return new Builder(); } /** * Configure a filter that is applied to labels. * @param labelFilter takes in an event type, a collection of labels and returns a * probably new collection of labels. * @return this builder. */ public Builder withLabelFilter( BiFunction, Collection> labelFilter) { if (labelFilter == null) { throw new IllegalArgumentException("Label filter may not be null."); } this.labelFilter = labelFilter; return this; } /** * Configure a filter that is applied to types. * @param typeFilter takes in an event type and the parsed type. May return the * type itself or something else. * @return this builder. */ public Builder withTypeFilter( BiFunction, Collection> typeFilter) { if (typeFilter == null) { throw new IllegalArgumentException("Type filter may not be null."); } this.typeFilter = typeFilter; return this; } /** * Adds a callback for when the specific {@link ExpressionCreatedEventType * expression is created} event. For one type of event one or more callbacks can * be declared which will be called in order in which they have been declared. *

* Parsing will be aborted when a callback throws a {@link RuntimeException}. * @param expressionCreatedEventType the type of the event * @param resultingType the type of the expression the callback returns. Must * match the one of the event type. * @param callback a callback * @param the type of the expression produced by the callback. Must match the * one of the event type * @return this builder */ public Builder withCallback(ExpressionCreatedEventType expressionCreatedEventType, Class resultingType, Function callback) { if (!expressionCreatedEventType.getTypeProduced().isAssignableFrom(resultingType)) { throw new IllegalArgumentException("The type that is produced by '" + expressionCreatedEventType + "' is not compatible with " + resultingType); } var callbacks = this.onNewExpressionCallbacks.computeIfAbsent(expressionCreatedEventType, k -> new ArrayList<>()); callbacks.add(callback); return this; } /** * Adds a callback for when a {@link PatternElement} is created during one of the * phases described by {@link PatternElementCreatedEventType}. For one type of * event one or more callbacks can be declared which will be called in order in * which they have been declared. Callbacks can just collect or actually visit the * elements created, or they are free to create new ones, effectively rewriting * the query. *

* Parsing will be aborted when a callback throws a {@link RuntimeException}. * @param patternElementCreatedEventType the type of the event * @param callback a callback * @return this builder * @since 2022.2.0 */ public Builder withCallback(PatternElementCreatedEventType patternElementCreatedEventType, UnaryOperator callback) { var callbacks = this.onNewPatternElementCallbacks.computeIfAbsent(patternElementCreatedEventType, k -> new ArrayList<>()); callbacks.add(callback); return this; } /** * Adds a callback for when either a CALL-Procedure clause or a * function-invocation expression is created. For one type of event one or more * callbacks can be declared which will be called in order in which they have been * declared. Callbacks can just collect or actually visit the elements created, or * they are free to create new ones, effectively rewriting the query. *

* Parsing will be aborted when a callback throws a {@link RuntimeException}. * @param invocationCreatedEventType the event type * @param resultingType the reified type being produced * @param callback a callback * @param the type of the result, must match the one of the event * @return this builder * @since 2022.8.6 */ @SuppressWarnings("unchecked") public Builder withCallback(InvocationCreatedEventType invocationCreatedEventType, Class resultingType, UnaryOperator callback) { if (!invocationCreatedEventType.getTypeProduced().isAssignableFrom(resultingType)) { throw new IllegalArgumentException("The type that is produced by '" + invocationCreatedEventType + "' is not compatible with " + resultingType); } var callbacks = this.onNewInvocationCallbacks.computeIfAbsent(invocationCreatedEventType, k -> new ArrayList<>()); callbacks.add((UnaryOperator) callback); return this; } /** * Configures the factory for return clauses. The idea here is that you might * intercept what is being returned or how it is sorted, limited and the like. The * {@link ReturnDefinition definition} passed to the factory contains all * necessary information for delegating to the * {@link org.neo4j.cypherdsl.core.Clauses#returning(boolean, List, List, Expression, Expression)} * factory. * @param returnClauseFactory the factory producing return classes that should be * used. * @return this builder */ public Builder withReturnClauseFactory(Function returnClauseFactory) { this.returnClauseFactory = returnClauseFactory; return this; } /** * Configures the factory for return clauses. The idea here is that you might * intercept what is being matched and or how it is restricted. The * {@link MatchDefinition definition} passed to the factory contains all necessary * information for delegating to the * {@link org.neo4j.cypherdsl.core.Clauses#match(boolean, List, Where, List)} * factory. * @param matchClauseFactory the factory producing return classes that should be * used. * @return this builder * @since 2023.0.2 */ public Builder withMatchClauseFactory(Function matchClauseFactory) { this.matchClauseFactory = matchClauseFactory; return this; } /** * Set {@code createSortedMaps} to {@literal true} to parse existing maps into * alphabetically sorted maps. * @param createSortedMaps a flag whether to create sorted maps or not * @return this builder * @since 2023.2.0 */ public Builder createSortedMaps(boolean createSortedMaps) { this.createSortedMaps = createSortedMaps; return this; } /** * Instructs the parser to turn relationships that are given as * {@code (a:A) <-[:TYPE]- (b:B)} into {@code (b:B) -[:TYPE]-> (a:A)}. Multi-hop * patterns will be split into a relationship enumeration from left to right with * all parts pointing from left to right * @param alwaysCreateRelationshipsLTR a flag whether to only use left-to-right * relationships * @return this builder * @since 2023.9.3 */ public Builder alwaysCreateRelationshipsLTR(boolean alwaysCreateRelationshipsLTR) { this.alwaysCreateRelationshipsLTR = alwaysCreateRelationshipsLTR; return this; } /** * Defines a lookup table for parameters. Everytime a parameter is parsed, we do * check if a value in this table exists. If so, the parameter will be created as * a named parameter carrying that value. *

* Any previous lookup table will be overwritten when using this method multiple * times. * @param newParameterValues a new lookup table. Use an empty map or * {@literal null} to clear any lookups in the config * @return this builder * @since 2023.4.0 */ public Builder withParameterValues(Map newParameterValues) { this.parameterValues = (newParameterValues != null) ? Collections.unmodifiableMap(new HashMap<>(newParameterValues)) : Map.of(); return this; } /** * Returns a new, unmodifiable {@link Options options instance}. * @return a new, unmodifiable {@link Options options instance} */ public Options build() { return new Options(this); } } } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/ParenthesizedPathPatternAtom.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.PatternElement; import org.neo4j.cypherdsl.core.QuantifiedPathPattern; import org.neo4j.cypherdsl.core.RelationshipPattern; /** * Helper to deal with quantified path patterns. * * @param patternElement the pattern element in parentheses * @param quantifier the quantifier * @param predicate any predicate * @author Michael J. Simons */ record ParenthesizedPathPatternAtom(RelationshipPattern patternElement, QuantifiedPathPattern.Quantifier quantifier, Expression predicate) implements PatternAtom { PatternElement asPatternElement() { return this.patternElement.quantify(this.quantifier).where(this.predicate); } } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/PathAtom.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.neo4j.cypherdsl.core.ExposesPatternLengthAccessors; import org.neo4j.cypherdsl.core.ExposesProperties; import org.neo4j.cypherdsl.core.ExposesRelationships; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.MapExpression; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.QuantifiedPathPattern; import org.neo4j.cypherdsl.core.Relationship; import org.neo4j.cypherdsl.core.Relationship.Direction; import org.neo4j.cypherdsl.core.RelationshipChain; import org.neo4j.cypherdsl.core.SymbolicName; /** * A value object for the details of a path. * * @author Michael J. Simons * @since 2021.3.0 */ final class PathAtom implements PatternAtom { private final SymbolicName name; private final PathLength length; private final Direction direction; private final boolean negatedType; private final String[] types; private final MapExpression properties; private final Expression predicate; private final QuantifiedPathPattern.Quantifier quantifier; private PathAtom(SymbolicName name, PathLength length, Direction direction, boolean negatedType, String[] types, MapExpression properties, Expression predicate, QuantifiedPathPattern.Quantifier quantifier) { this.name = name; this.length = length; this.direction = direction; this.negatedType = negatedType; this.types = types; this.properties = properties; this.predicate = predicate; this.quantifier = quantifier; } @SuppressWarnings("squid:S107") // Totally fine with that number of args for this // internal API. static PathAtom of(SymbolicName name, PathLength length, boolean left, boolean right, String[] relTypes, MapExpression properties, boolean negatedType, Expression predicate) { if (left && right) { throw new IllegalArgumentException( "Only left-to-right, right-to-left or unidirectional path elements are supported."); } Direction direction; if (left) { direction = Direction.RTL; } else if (right) { direction = Direction.LTR; } else { direction = Direction.UNI; } return new PathAtom(name, length, direction, negatedType, relTypes, properties, predicate, null); } ExposesRelationships asRelationshipBetween(ExposesRelationships previous, NodeAtom nodeAtom, boolean alwaysLtr) { var node = nodeAtom.value(); ExposesRelationships relationshipPattern = switch (this.getDirection()) { case LTR -> previous.relationshipTo(node, this.getTypes()); case RTL -> alwaysLtr ? node.relationshipTo((Node) previous, this.getTypes()) : previous.relationshipFrom(node, this.getTypes()); case UNI -> previous.relationshipBetween(node, this.getTypes()); }; relationshipPattern = applyOptionalName(relationshipPattern); relationshipPattern = applyOptionalProperties(relationshipPattern); relationshipPattern = applyOptionalPredicate(relationshipPattern); relationshipPattern = applyOptionalLength(relationshipPattern); return applyOptionalQuantifier(relationshipPattern); } private ExposesRelationships applyOptionalLength(ExposesRelationships relationshipPattern) { if (this.length == null) { return relationshipPattern; } if (this.length.isUnbounded()) { return ((ExposesPatternLengthAccessors) relationshipPattern).unbounded(); } return ((ExposesPatternLengthAccessors) relationshipPattern).length(this.length.getMinimum(), this.length.getMaximum()); } private ExposesRelationships applyOptionalProperties(ExposesRelationships relationshipPattern) { if (this.properties == null) { return relationshipPattern; } if (relationshipPattern instanceof ExposesProperties exposesProperties) { return (ExposesRelationships) exposesProperties.withProperties(this.properties); } return ((RelationshipChain) relationshipPattern).properties(this.properties); } private ExposesRelationships applyOptionalName(ExposesRelationships relationshipPattern) { if (this.name == null) { return relationshipPattern; } if (relationshipPattern instanceof Relationship relationship) { return relationship.named(this.name); } return ((RelationshipChain) relationshipPattern).named(this.name); } private ExposesRelationships applyOptionalPredicate(ExposesRelationships relationshipPattern) { if (this.predicate == null) { return relationshipPattern; } if (relationshipPattern instanceof Relationship relationship) { return (ExposesRelationships) relationship.where(this.predicate); } return ((RelationshipChain) relationshipPattern).where(this.predicate); } private ExposesRelationships applyOptionalQuantifier(ExposesRelationships relationshipPattern) { if (this.quantifier == null) { return relationshipPattern; } if (relationshipPattern instanceof Relationship relationship) { return (ExposesRelationships) relationship.quantifyRelationship(this.quantifier); } return ((RelationshipChain) relationshipPattern).quantifyRelationship(this.quantifier); } Direction getDirection() { return this.direction; } String[] getTypes() { return this.types; } PathAtom withQuantifier(QuantifiedPathPattern.Quantifier newQuantifier) { return (newQuantifier != null) ? new PathAtom(this.name, this.length, this.direction, this.negatedType, this.types, this.properties, this.predicate, newQuantifier) : this; } } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/PathLength.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.INTERNAL; /** * Value object for the lenght of a path. * * @author Michael J. Simons * @since 2021.3.0 */ @API(status = INTERNAL, since = "2021.3.0") final class PathLength { private final Integer minimum; private final Integer maximum; private final boolean unbounded; private PathLength(Integer minimum, Integer maximum) { this.minimum = minimum; this.maximum = maximum; this.unbounded = minimum == null && maximum == null; } static PathLength of(String minimum, String maximum) { Integer min = ((minimum == null) || minimum.isBlank()) ? null : Integer.valueOf(minimum.trim()); Integer max = ((maximum == null) || maximum.isBlank()) ? null : Integer.valueOf(maximum.trim()); return new PathLength(min, max); } Integer getMinimum() { return this.minimum; } Integer getMaximum() { return this.maximum; } boolean isUnbounded() { return this.unbounded; } } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/PatternAtom.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; /** * Artificial interface over a {@link org.neo4j.cypherdsl.core.Node} and {@link PathAtom}. * * @author Michael J. Simons * @since 2023.0.0 */ interface PatternAtom { } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/PatternElementAsExpressionWrapper.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Condition; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.RelationshipPattern; import org.neo4j.cypherdsl.core.ast.Visitor; import static org.apiguardian.api.API.Status.INTERNAL; /** * Necessary for using relationship pattern as expressions. * * @author Michael J. Simons * @since 2021.3.0 */ @API(status = INTERNAL, since = "2021.3.0") final class PatternElementAsExpressionWrapper implements Expression { private final RelationshipPattern relationshipPattern; PatternElementAsExpressionWrapper(RelationshipPattern relationshipPattern) { this.relationshipPattern = relationshipPattern; } @Override public void accept(Visitor visitor) { this.relationshipPattern.accept(visitor); } @Override public Condition asCondition() { return Cypher.matching(this.relationshipPattern); } } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/PatternElementCreatedEventType.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * The type of an event fired when a Cypher-DSL AST element is created. * * @author Michael J. Simons * @since 2022.1.1 */ @API(status = STABLE, since = "2022.2.0") public enum PatternElementCreatedEventType { /** * A {@link org.neo4j.cypherdsl.core.PatternElement} is created during the creation of * a {@code MATCH} clause. */ ON_MATCH, /** * A {@link org.neo4j.cypherdsl.core.PatternElement} is created during the creation of * a {@code CREATE} clause. */ ON_CREATE, /** * A {@link org.neo4j.cypherdsl.core.PatternElement} is created during the creation of * a {@code MERGE} clause. */ ON_MERGE, } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/PatternElementFunctions.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.neo4j.cypherdsl.core.FunctionInvocation; /** * A list of additional pattern element functions, to avoid opening up or extending the * Cypher-DSLs build in API. * * @author Michael J. Simons */ enum PatternElementFunctions implements FunctionInvocation.FunctionDefinition { SHORTEST_PATH("shortestPath"), ALL_SHORTEST_PATHS("allShortestPaths"); private final String implementationName; PatternElementFunctions(String implementationName) { this.implementationName = implementationName; } @Override public String getImplementationName() { return this.implementationName; } } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/ReturnDefinition.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.util.List; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.SortItem; import static org.apiguardian.api.API.Status.STABLE; /** * A value object containing the necessary pieces for creating a * {@link org.neo4j.cypherdsl.core.Return RETURN clause}. One possible producer after * fiddling with the elements is * {@link org.neo4j.cypherdsl.core.Clauses#returning(boolean, List, List, Expression, Expression)}. * * @author Michael J. Simons * @since 2021.3.0 */ @API(status = STABLE, since = "2021.3.0") public final class ReturnDefinition { private final boolean distinct; private final List expressions; private final List optionalSortItems; private final Expression optionalSkip; private final Expression optionalLimit; ReturnDefinition(boolean distinct, List expressions, List optionalSortItems, Expression optionalSkip, Expression optionalLimit) { this.distinct = distinct; this.expressions = expressions; this.optionalSortItems = optionalSortItems; this.optionalSkip = optionalSkip; this.optionalLimit = optionalLimit; } /** * Returns true to indicate this return clause should return only * distinct elements. * @return true if the DISTINCT clause is present */ public boolean isDistinct() { return this.distinct; } /** * {@return list of expressions to be returned} */ public List getExpressions() { return this.expressions; } /** * {@return list of expressions to sort the result by} */ public List getOptionalSortItems() { return this.optionalSortItems; } /** * {@return numerical expression how many items to skip} */ public Expression getOptionalSkip() { return this.optionalSkip; } /** * {@return numerical expression how many items to return} */ public Expression getOptionalLimit() { return this.optionalLimit; } } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/Statements.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.util.List; import org.neo4j.cypherdsl.core.Statement; record Statements(List value) { Statements { value = (value != null) ? List.copyOf(value) : List.of(); } } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/TypeParsedEventType.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * Type of an event when a relationship type is parsed. * * @author Michael J. Simons * @since 2021.3.0 */ @API(status = STABLE, since = "2021.3.0") public enum TypeParsedEventType { /** * Parsed when creating a {@link org.neo4j.cypherdsl.core.RelationshipPattern * relationship pattern}. */ ON_RELATIONSHIP_PATTERN } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/UnsupportedCypherException.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.io.Serial; import org.apiguardian.api.API; import static org.apiguardian.api.API.Status.STABLE; /** * Thrown when the parser detects a clause which is not yet supported. * * @author Michael J. Simons * @since 2021.3.0 */ @API(status = STABLE, since = "2021.3.0") public final class UnsupportedCypherException extends UnsupportedOperationException { @Serial private static final long serialVersionUID = 2871262762217922044L; /** * Original input to the parser. */ private final String input; UnsupportedCypherException(String input, UnsupportedOperationException cause) { super(String.format("You used one Cypher construct not yet supported by the Cypher-DSL:%n%n\t%s%n%n" + "Feel free to open an issue so that we might add support for it at https://github.com/neo4j-contrib/cypher-dsl/issues/new", input), cause); this.input = input; } /** * {@return the original Cypher input handled to the parser} */ public String getInput() { return this.input; } } ================================================ FILE: neo4j-cypher-dsl-parser/src/main/java/org/neo4j/cypherdsl/parser/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * Parses any Neo4j 5.x Cypher statement into a Cypher-DSL AST. */ package org.neo4j.cypherdsl.parser; ================================================ FILE: neo4j-cypher-dsl-parser/src/test/java/org/neo4j/cypherdsl/parser/ComparisonIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.util.EnumSet; import java.util.Map; import java.util.Set; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.Statement; import org.neo4j.cypherdsl.core.TreeNode; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Dialect; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; /** * Tests combining parser and builder and demonstrating comparisons */ class ComparisonIT { static Stream generatedNamesShouldBeGood() { return Stream.of(Arguments.of(""" MATCH (charlie:Person {name: 'Charlie Sheen'}), (rob:Person {name: 'Rob Reiner'}) CREATE (rob)-[:`TYPE INCLUDING A SPACE`]->(charlie) """, """ MATCH (v0:Person {name: 'Charlie Sheen'}), (v1:Person {name: 'Rob Reiner'}) CREATE (v1)-[:`TYPE INCLUDING A SPACE`]->(v0) """, false), Arguments.of(""" MATCH (actor:Person {name: 'Charlie Sheen'})-[:ACTED_IN]->(movie:Movie) RETURN actor{.name, .realName, movies: collect(movie{.title, .year})}; """, """ MATCH (v0:Person {name: 'Charlie Sheen'})-[:ACTED_IN]->(v1:Movie) RETURN v0{.name, .realName, movies: collect(v1{.title, .year})}; """, false), Arguments.of(""" match (n:Person) call { match (n:Movie {title: 'The Matrix'}) where n.released >= 1900 return n as m } return n.name """, """ match (v0:Person) call { match (v0:Movie {title: 'The Matrix'}) where v0.released >= 1900 return v0 as v1 } return v0.name """, false), Arguments.of(""" MATCH (n:Person {name: 'Tom Hanks'}) CALL { WITH n MATCH (m:Movie)<-[:ACTED_IN]-(n) WHERE (m.released >= 1900 AND n.born = 1956) RETURN m } RETURN n.name, m.title """, """ MATCH (v0:Person {name: 'Tom Hanks'}) CALL { WITH v0 MATCH (v1:Movie)<-[:ACTED_IN]-(v0) WHERE (v1.released >= 1900 AND v0.born = 1956) RETURN v1 } RETURN v0.name, v1.title """, false), Arguments.of(""" UNWIND $foo AS input CALL { WITH input CREATE (v0:Movie) SET v0.title = input.title RETURN v0 } RETURN count(*) """, """ UNWIND $p0 AS v0 CALL { WITH v0 CREATE (v1:Movie) SET v1.title = v0.title RETURN v1 } RETURN count(*) """, false), Arguments.of(""" MATCH (this:Movie) CALL { WITH this MATCH (this_actorsAggregate_this1:Actor)-[this_actorsAggregate_this0:ACTED_IN]->(this) RETURN { min: min(this_actorsAggregate_this0.screentime), max: max(this_actorsAggregate_this0.screentime), average: avg(this_actorsAggregate_this0.screentime), sum: sum(this_actorsAggregate_this0.screentime) } AS this_actorsAggregate_var2 } RETURN this { actorsAggregate: { edge: { screentime: this_actorsAggregate_var2 } } } AS this """, """ MATCH (v0:Movie) CALL { WITH v0 MATCH (v1:Actor)-[v2:ACTED_IN]->(v0) RETURN { min: min(v2.screentime), max: max(v2.screentime), average: avg(v2.screentime), sum: sum(v2.screentime) } AS v3 } RETURN v0 { actorsAggregate: { edge: { screentime: v3 } } } AS v0 """, false), Arguments.of(""" MATCH (this:Movie) CALL { WITH this MATCH (person:Person)-[edge:ACTED_IN]->(this) WITH * WHERE person.name CONTAINS $param0 RETURN count(person) AS this_actorsAggregate_var0 } CALL { WITH this MATCH (person:Person)-[edge:DIRECTED]->(this) WITH * WHERE person.name CONTAINS $param1 RETURN count(person) AS this_directorsAggregate_var0 } RETURN this { .title, actorsAggregate: { count: this_actorsAggregate_var0 }, directorsAggregate: { count: this_directorsAggregate_var0 } } AS this""", """ MATCH (v0:Movie) CALL { WITH v0 MATCH (v1:Person)-[v2:ACTED_IN]->(v0) WITH * WHERE v1.name CONTAINS $p0 RETURN count(v1) AS v3 } CALL { WITH v0 MATCH (v1:Person)-[v2:DIRECTED]->(v0) WITH * WHERE v1.name CONTAINS $p1 RETURN count(v1) AS v4 } RETURN v0 { .title, actorsAggregate: { count: v3 }, directorsAggregate: { count: v4 } } AS v0 """, false), Arguments.of(""" MATCH (this:Movie) CALL { WITH this MATCH (this_actorsAggregate_this1:Person)-[this_actorsAggregate_this0:ACTED_IN]->(this) WITH * WHERE this_actorsAggregate_this1.name CONTAINS $this_actorsAggregate_param0 RETURN count(this_actorsAggregate_this1) AS this_actorsAggregate_var2 } CALL { WITH this MATCH (this_directorsAggregate_this1:Person)-[this_directorsAggregate_this0:DIRECTED]->(this) WITH * WHERE this_directorsAggregate_this1.name CONTAINS $this_directorsAggregate_param0 RETURN count(this_directorsAggregate_this1) AS this_directorsAggregate_var2 } RETURN this { .title, actorsAggregate: { count: this_actorsAggregate_var2 }, directorsAggregate: { count: this_directorsAggregate_var2 } } AS this""", """ MATCH (v0:Movie) CALL { WITH v0 MATCH (v1:Person)-[v2:ACTED_IN]->(v0) WITH * WHERE v1.name CONTAINS $p0 RETURN count(v1) AS v3 } CALL { WITH v0 MATCH (v1:Person)-[v2:DIRECTED]->(v0) WITH * WHERE v1.name CONTAINS $p1 RETURN count(v1) AS v4 } RETURN v0 { .title, actorsAggregate: { count: v3 }, directorsAggregate: { count: v4 } } AS v0 """, false), Arguments.of(""" MATCH (this:Post) CALL { WITH this MATCH (this1:User)-[this0:LIKES]->(this) RETURN any(var2 IN collect(this0.someBigInt) WHERE var2 = $param0) AS var3 } WITH * WHERE var3 = true RETURN this { .content } AS this """, """ MATCH (v0:Post) CALL { WITH v0 MATCH (v1:User)-[v2:LIKES]->(v0) RETURN any(v3 IN collect(v2.someBigInt) WHERE v3 = $p0) AS v4 } WITH * WHERE v4 = true RETURN v0 { .content } AS v0""", false), Arguments.of("MATCH (n:Movie)<-[:ACTED_IN]-(p:Person)", "MATCH (v0:`Person`)-[:`ACTED_IN`]->(v1:`Movie`)", true), Arguments.of("MATCH (a:A)-[:R]->(b:B)-[:R2]->(c:C) RETURN *", "MATCH (v0:`A`)-[:`R`]->(v1:`B`)-[:`R2`]->(v2:`C`) RETURN *", true), Arguments.of("MATCH (a:A)-[:R]->(b:B)-[:R2]->(c:C), (x) --> (y) RETURN *", "MATCH (v0:`A`)-[:`R`]->(v1:`B`)-[:`R2`]->(v2:`C`), (v3)-->(v4) RETURN *", true), Arguments.of("MATCH (a:A)-[:R]->(b:B), (b)-[:R2]->(c:C) RETURN *", "MATCH (v0:`A`)-[:`R`]->(v1:`B`), (v1)-[:`R2`]->(v2:`C`) RETURN *", true), Arguments.of("MATCH (a:A)<-[:R]-(b:B)-[:R2]->(c:C), (x) --> (y) RETURN *", "MATCH (v0:`B`)-[:`R`]->(v1:`A`), (v0)-[:`R2`]->(v2:`C`), (v3)-->(v4) RETURN *", true), Arguments.of("MATCH (u:U)<-[:R3]-(a:A)<-[:R]-(b:B)-[:R2]->(c:C), (x) --> (y) RETURN *", "MATCH (v0:`A`)-[:`R3`]->(v1:`U`), (v2:`B`)-[:`R`]->(v0), (v2)-[:`R2`]->(v3:`C`), (v4)-->(v5) RETURN *", true), Arguments.of("MATCH (a:A)<-[:FOO]-(b)-[:BAR]->(c) RETURN *", "MATCH (v0)-[:`FOO`]->(v1:`A`), (v0)-[:`BAR`]->(v2) RETURN *", true), Arguments.of("MATCH (a:A)<-[:FOO]-(b:B)<-[:BAR]-(c:C) RETURN *", "MATCH (v0:`B`)-[:`FOO`]->(v1:`A`), (v2:`C`)-[:`BAR`]->(v0) RETURN *", true)); } static Stream generatedNamesShouldBeConfigurable() { return Stream.of(Arguments.of(EnumSet.allOf(Configuration.GeneratedNames.class), """ MATCH (v0:Movie) WHERE v0.released = $p0 CALL (v0) { MATCH (v1:Actor)-[v2:ACTED_IN]->(v0) RETURN { min: min(v2.screentime), max: max(v2.screentime), average: avg(v2.screentime), sum: sum(v2.screentime) } AS v3, any(v4 IN collect(v1.someBigInt) WHERE v4 = $p1) AS v5 } RETURN v0 { actorsAggregate: { edge: { screentime: v3 } } } AS v0 """), Arguments.of(EnumSet.of(Configuration.GeneratedNames.ENTITY_NAMES), """ MATCH (v0:Movie) WHERE v0.released = $releaseYear CALL (v0) { MATCH (v1:Actor)-[v2:ACTED_IN]->(v0) RETURN { min: min(v2.screentime), max: max(v2.screentime), average: avg(v2.screentime), sum: sum(v2.screentime) } AS this_actorsAggregate_var2, any(foo IN collect(v1.someBigInt) WHERE foo = $param0) AS blerg } RETURN v0 { actorsAggregate: { edge: { screentime: this_actorsAggregate_var2 } } } AS this """), Arguments.of(EnumSet.of(Configuration.GeneratedNames.PARAMETER_NAMES), """ MATCH (this:Movie) WHERE this.released = $p0 CALL (this) { MATCH (this_actorsAggregate_this1:Actor)-[this_actorsAggregate_this0:ACTED_IN]->(this) RETURN { min: min(this_actorsAggregate_this0.screentime), max: max(this_actorsAggregate_this0.screentime), average: avg(this_actorsAggregate_this0.screentime), sum: sum(this_actorsAggregate_this0.screentime) } AS this_actorsAggregate_var2, any(foo IN collect(this_actorsAggregate_this1.someBigInt) WHERE foo = $p1) AS blerg } RETURN this { actorsAggregate: { edge: { screentime: this_actorsAggregate_var2 } } } AS this """), Arguments.of(EnumSet.of(Configuration.GeneratedNames.ALL_ALIASES), """ MATCH (this:Movie) WHERE this.released = $releaseYear CALL (this) { MATCH (this_actorsAggregate_this1:Actor)-[this_actorsAggregate_this0:ACTED_IN]->(this) RETURN { min: min(this_actorsAggregate_this0.screentime), max: max(this_actorsAggregate_this0.screentime), average: avg(this_actorsAggregate_this0.screentime), sum: sum(this_actorsAggregate_this0.screentime) } AS v0, any(foo IN collect(this_actorsAggregate_this1.someBigInt) WHERE foo = $param0) AS v1 } RETURN this { actorsAggregate: { edge: { screentime: v0 } } } AS v2 """), Arguments.of(EnumSet.of(Configuration.GeneratedNames.INTERNAL_ALIASES_ONLY), """ MATCH (this:Movie) WHERE this.released = $releaseYear CALL (this) { MATCH (this_actorsAggregate_this1:Actor)-[this_actorsAggregate_this0:ACTED_IN]->(this) RETURN { min: min(this_actorsAggregate_this0.screentime), max: max(this_actorsAggregate_this0.screentime), average: avg(this_actorsAggregate_this0.screentime), sum: sum(this_actorsAggregate_this0.screentime) } AS v0, any(foo IN collect(this_actorsAggregate_this1.someBigInt) WHERE foo = $param0) AS v1 } RETURN this { actorsAggregate: { edge: { screentime: v0 } } } AS this """), Arguments.of(EnumSet.complementOf(EnumSet.of(Configuration.GeneratedNames.ALL_ALIASES)), """ MATCH (v0:Movie) WHERE v0.released = $p0 CALL (v0) { MATCH (v1:Actor)-[v2:ACTED_IN]->(v0) RETURN { min: min(v2.screentime), max: max(v2.screentime), average: avg(v2.screentime), sum: sum(v2.screentime) } AS v3, any(v4 IN collect(v1.someBigInt) WHERE v4 = $p1) AS v5 } RETURN v0 { actorsAggregate: { edge: { screentime: v3 } } } AS this """)); } static boolean areSemanticallyEquivalent(Statement statement1, Statement statement2) { var cfg = Configuration.newConfig().withGeneratedNames(true).build(); var renderer = Renderer.getRenderer(cfg); var cypher1 = renderer.render(statement1); var cypher2 = renderer.render(statement2); return cypher1.equals(cypher2); } static boolean areSemanticallyEquivalent(Statement statement1, Map args1, Statement statement2, Map args2) { if (!areSemanticallyEquivalent(statement1, statement2)) { return false; } var mapping1 = statement1.getCatalog().getRenamedParameters(); var mapping2 = statement2.getCatalog().getRenamedParameters(); for (Map.Entry entry : mapping1.entrySet()) { String key1 = entry.getKey(); String mapped = entry.getValue(); String key2 = mapping2.entrySet() .stream() .filter(e -> e.getValue().equals(mapped)) .map(Map.Entry::getKey) .findFirst() .orElseThrow(); if (!args1.get(key1).equals(args2.get(key2))) { return false; } } return true; } @ParameterizedTest @MethodSource void generatedNamesShouldBeGood(String in, String expected, boolean alwaysCreateRelationshipsLTR) { var stmt = Renderer.getRenderer(Configuration.newConfig().withGeneratedNames(true).build()) .render(CypherParser.parseStatement(in, Options.newOptions().alwaysCreateRelationshipsLTR(alwaysCreateRelationshipsLTR).build())); assertThat(stmt).isEqualTo(CypherParser.parseStatement(expected).getCypher()); } @ParameterizedTest @MethodSource void generatedNamesShouldBeConfigurable(Set config, String expected) { var in = """ MATCH (this:Movie) WHERE this.released = $releaseYear CALL (*) { MATCH (this_actorsAggregate_this1:Actor)-[this_actorsAggregate_this0:ACTED_IN]->(this) RETURN { min: min(this_actorsAggregate_this0.screentime), max: max(this_actorsAggregate_this0.screentime), average: avg(this_actorsAggregate_this0.screentime), sum: sum(this_actorsAggregate_this0.screentime) } AS this_actorsAggregate_var2, any(foo IN collect(this_actorsAggregate_this1.someBigInt) WHERE foo = $param0) AS blerg } RETURN this { actorsAggregate: { edge: { screentime: this_actorsAggregate_var2 } } } AS this """; var stmt = Renderer.getRenderer(Configuration.newConfig().withGeneratedNames(config).build()) .render(CypherParser.parseStatement(in)); assertThat(stmt).isEqualTo(CypherParser.parseStatement(expected).getCypher()); } @Test void shouldDetectEquivalentStatements() { var stmt1 = CypherParser.parse("Match (x:Movie) where x.title = $param1 RETURN x"); var stmt2 = Cypher.match(Cypher.node("Movie").named("y")) .where(Cypher.name("y").property("title").eq(Cypher.parameter("foo"))) .returning(Cypher.name("y")) .build(); assertThat(areSemanticallyEquivalent(stmt1, stmt2)).isTrue(); } @Test void shouldDetectEquivalentStatementsWithAnonParameters() { var stmt1 = Cypher.match(Cypher.node("Movie").named("x")) .where(Cypher.name("x").property("title").eq(Cypher.anonParameter("foo"))) .returning(Cypher.name("x")) .build(); var stmt2 = Cypher.match(Cypher.node("Movie").named("y")) .where(Cypher.name("y").property("title").eq(Cypher.anonParameter("bar"))) .returning(Cypher.name("y")) .build(); assertThat(areSemanticallyEquivalent(stmt1, stmt2)).isTrue(); } @Test void shouldDetectNonEquivalentStatements() { var stmt1 = CypherParser.parse("Match (x:movie) where x.title = $param1 RETURN x"); var stmt2 = Cypher.match(Cypher.node("Movie").named("y")) .where(Cypher.name("y").property("title").eq(Cypher.parameter("foo"))) .returning(Cypher.name("y")) .build(); assertThat(areSemanticallyEquivalent(stmt1, stmt2)).isFalse(); } @Test void shouldDetectEquivalentStatementsWithArgs() { var stmt1 = CypherParser.parse("Match (x:Movie) where x.title = $param1 RETURN x"); var stmt2 = Cypher.match(Cypher.node("Movie").named("y")) .where(Cypher.name("y").property("title").eq(Cypher.parameter("foo"))) .returning(Cypher.name("y")) .build(); assertThat(areSemanticallyEquivalent(stmt1, Map.of("param1", "The Matrix"), stmt2, Map.of("foo", "The Matrix"))) .isTrue(); } @Test void shouldDetectNonEquivalentStatementsWithArgs() { var stmt1 = CypherParser.parse("Match (x:Movie) where x.title = $param1 RETURN x"); var stmt2 = Cypher.match(Cypher.node("Movie").named("y")) .where(Cypher.name("y").property("title").eq(Cypher.parameter("foo"))) .returning(Cypher.name("y")) .build(); assertThat(areSemanticallyEquivalent(stmt1, Map.of("param1", "The Matrix"), stmt2, Map.of("foo", "Matrix Resurrections"))) .isFalse(); } @Test void treeCanBeUsedToAnalyseStatements() { var stmt = CypherParser.parse(""" MATCH (n:Person {name: 'Tom Hanks'}) CALL { WITH n MATCH (m:Movie)<-[:ACTED_IN]-(n) WHERE (m.released >= 1900 AND n.born = 1956) RETURN m } RETURN n.name, m.title """); var target = new StringBuilder(); TreeNode.from(stmt) .printTo(target::append, node -> (node.getValue() instanceof Statement s) ? s.getCypher() : node.getValue().toString()); assertThat(target).hasToString( """ MATCH (n:`Person` {name: 'Tom Hanks'}) CALL (n) {MATCH (m:`Movie`)<-[:`ACTED_IN`]-(n) WHERE (m.released >= 1900 AND n.born = 1956) RETURN m} RETURN n.name, m.title ├── Match{cypher=MATCH (n:Person {name: 'Tom Hanks'})} │ └── Pattern{cypher=(n:Person {name: 'Tom Hanks'})} │ └── InternalNodeImpl{cypher=(n:Person {name: 'Tom Hanks'})} │ ├── SymbolicName{cypher=n} │ ├── NodeLabel{value='Person'} │ └── Properties{cypher={name: 'Tom Hanks'}} │ └── MapExpression{cypher={name: 'Tom Hanks'}} │ └── KeyValueMapEntry{cypher=name: 'Tom Hanks'} │ └── StringLiteral{cypher='Tom Hanks'} ├── Subquery{cypher=CALL (n) {MATCH (m:Movie)<-[:ACTED_IN]-(n) WHERE (m.released >= 1900 AND n.born = 1956) RETURN m}} │ └── WITH n MATCH (m:`Movie`)<-[:`ACTED_IN`]-(n) WHERE (m.released >= 1900 AND n.born = 1956) RETURN m │ ├── With{cypher=WITH n} │ │ └── ReturnBody{cypher=n} │ │ └── ExpressionList{cypher=n} │ │ └── SymbolicName{cypher=n} │ ├── Match{cypher=MATCH (m:Movie)<-[:ACTED_IN]-(n) WHERE (m.released >= 1900 AND n.born = 1956)} │ │ ├── Pattern{cypher=(m:Movie)<-[:ACTED_IN]-(n)} │ │ │ └── InternalRelationshipImpl{cypher=(m:Movie)<-[:ACTED_IN]-(n)} │ │ │ ├── InternalNodeImpl{cypher=(m:Movie)} │ │ │ │ ├── SymbolicName{cypher=m} │ │ │ │ └── NodeLabel{value='Movie'} │ │ │ ├── Details{cypher=<-[:ACTED_IN]-} │ │ │ │ └── RelationshipTypes{values=[ACTED_IN]} │ │ │ └── InternalNodeImpl{cypher=(n)} │ │ │ └── SymbolicName{cypher=n} │ │ └── Where{cypher=WHERE (m.released >= 1900 AND n.born = 1956)} │ │ └── CompoundCondition{cypher=(m.released >= 1900 AND n.born = 1956)} │ │ ├── Comparison{cypher=m.released >= 1900} │ │ │ ├── InternalPropertyImpl{cypher=m.released} │ │ │ │ ├── SymbolicName{cypher=m} │ │ │ │ └── PropertyLookup{cypher=.released} │ │ │ │ └── SymbolicName{cypher=released} │ │ │ ├── Operator{cypher=>=} │ │ │ └── NumberLiteral{cypher=1900} │ │ ├── Operator{cypher=AND} │ │ └── Comparison{cypher=n.born = 1956} │ │ ├── InternalPropertyImpl{cypher=n.born} │ │ │ ├── SymbolicName{cypher=n} │ │ │ └── PropertyLookup{cypher=.born} │ │ │ └── SymbolicName{cypher=born} │ │ ├── Operator{cypher==} │ │ └── NumberLiteral{cypher=1956} │ └── Return{cypher=RETURN m} │ └── ReturnBody{cypher=m} │ └── ExpressionList{cypher=m} │ └── SymbolicName{cypher=m} └── Return{cypher=RETURN n.name, m.title} └── ReturnBody{cypher=n.name, m.title} └── ExpressionList{cypher=n.name, m.title} ├── InternalPropertyImpl{cypher=n.name} │ ├── SymbolicName{cypher=n} │ └── PropertyLookup{cypher=.name} │ └── SymbolicName{cypher=name} └── InternalPropertyImpl{cypher=m.title} ├── SymbolicName{cypher=m} └── PropertyLookup{cypher=.title} └── SymbolicName{cypher=title} """); } @Test // GH-963 void scopingAndNormalizingShouldWork() { var cypher = """ CALL { CREATE (this0:Movie) WITH * CALL { WITH this0 return true } RETURN this0 } RETURN this0 """; var cfg = Configuration.newConfig() .withPrettyPrint(true) .withGeneratedNames(true) .withDialect(Dialect.NEO4J_4) .build(); var renderer = Renderer.getRenderer(cfg); var parseOptions = Options.newOptions().createSortedMaps(true).build(); var normalized = renderer.render(CypherParser.parse(cypher, parseOptions)); assertThat(normalized).isEqualTo(""" CALL { CREATE (v0:Movie) WITH * CALL { WITH v0 RETURN true } RETURN v0 } RETURN v0"""); } @Test // GH-1147 void shouldNotRecognizeLocallyScopedElementsFromExistentialSubqueries() { var cypher = """ MATCH (this:Series) WHERE ((EXISTS { MATCH (this)-[edge:MANUFACTURER]->(this0:Manufacturer) WHERE (this0.name = $param0 AND edge.current = $param1) } OR EXISTS { MATCH (this)-[edge:MANUFACTURER]->(this1:Manufacturer) WHERE (this1.name = $param2 AND edge.current = $param3) }) AND EXISTS { MATCH (this)-[edge:BRAND]->(this2:Brand) WHERE (this2.name = $param4 AND edge.current = $param5) }) RETURN this"""; var renderer = Renderer .getRenderer(Configuration.newConfig().withPrettyPrint(true).withGeneratedNames(true).build()); var normalized = renderer.render(CypherParser.parse(cypher)); assertThat(normalized).isEqualTo(""" MATCH (v0:Series) WHERE ((EXISTS { MATCH (v0)-[v1:MANUFACTURER]->(v2:Manufacturer) WHERE (v2.name = $p0 AND v1.current = $p1) } OR EXISTS { MATCH (v0)-[v1:MANUFACTURER]->(v2:Manufacturer) WHERE (v2.name = $p2 AND v1.current = $p3) }) AND EXISTS { MATCH (v0)-[v1:BRAND]->(v2:Brand) WHERE (v2.name = $p4 AND v1.current = $p5) }) RETURN v0"""); } } ================================================ FILE: neo4j-cypher-dsl-parser/src/test/java/org/neo4j/cypherdsl/parser/CypherChallengeTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.regex.Pattern; import java.util.stream.Stream; import com.opencsv.CSVReaderBuilder; import com.opencsv.exceptions.CsvValidationException; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; /** * See cypher-direction-competition. * * @author Michael J. Simons */ class CypherChallengeTests { static Stream cypherFixRelChallenge() throws IOException, CsvValidationException { List result = new ArrayList<>(); try (var csvReader = new CSVReaderBuilder(new InputStreamReader( Objects.requireNonNull(CypherChallengeTests.class.getResourceAsStream("/cypher-challenge.csv")))) .withSkipLines(1) .build()) { String[] nextRecord; var p = Pattern.compile("\\(.*?\\)"); while ((nextRecord = csvReader.readNext()) != null) { var builder = Configuration.newConfig(); var m = p.matcher(nextRecord[1]); while (m.find()) { builder.withRelationshipDefinition(Configuration.relationshipDefinition(m.group(0))); } result.add(Arguments.of(builder.withEnforceSchema(true).build(), nextRecord[0], nextRecord[2])); } } return result.stream(); } @ParameterizedTest(name = "{1}") @MethodSource void cypherFixRelChallenge(Configuration configuration, String input, String expected) { var statement = CypherParser.parse(input); var renderer = Renderer.getRenderer(configuration); var cypher = renderer.render(statement); var finalExpectation = expected.isBlank() ? "" : CypherParser.parse(expected).getCypher(); assertThat(cypher).isEqualTo(finalExpectation); } } ================================================ FILE: neo4j-cypher-dsl-parser/src/test/java/org/neo4j/cypherdsl/parser/CypherDslASTFactoryTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.lang.reflect.InvocationTargetException; import java.util.List; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.neo4j.cypherdsl.core.Cypher; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * @author Michael J. Simons */ class CypherDslASTFactoryTests { @Test void isInstanceOfCheckShouldWork() { assertThatIllegalArgumentException().isThrownBy(() -> CypherDslASTFactory.isInstanceOf(null, null, null)) .withMessage("Type to check against must not be null"); assertThatIllegalArgumentException() .isThrownBy(() -> CypherDslASTFactory.isInstanceOf(String.class, 42, "Not the answer")) .withMessage("Not the answer"); } @Test void infinityLiteralShouldWork() { var factory = CypherDslASTFactory.getInstance(null); assertThat(factory.newInfinityLiteral(null)).isEqualTo(InfinityLiteral.INSTANCE); } @Test void nanLiteralShouldWork() { var factory = CypherDslASTFactory.getInstance(null); assertThat(factory.newNaNLiteral(null)).isEqualTo(NaNLiteral.INSTANCE); } @Test void parameterLiteralNullExpressionShouldWork() { var factory = CypherDslASTFactory.getInstance(null); var parameter = factory.parameterFromSymbolicName(null); assertThat(parameter).isNotNull(); assertThat(parameter.isAnon()).isTrue(); } @Test void parameterFromExpressionShouldWork() { var factory = CypherDslASTFactory.getInstance(null); var parameter = factory.parameterFromSymbolicName(CypherParser.parseExpression("a")); assertThat(parameter).isNotNull(); assertThat(parameter.isAnon()).isFalse(); } @Test void databaseName() { var factory = CypherDslASTFactory.getInstance(null); var databaseName = factory.databaseName(null, List.of("x")); assertThat(databaseName).isNotNull(); databaseName = factory.databaseName(null, List.of("x", "y")); assertThat(databaseName).isNotNull(); var parameter = Cypher.parameter("foo"); databaseName = factory.databaseName(parameter); assertThat(databaseName).isNotNull(); assertThat(databaseName.value()).isEqualTo(parameter); var empty = List.of(); assertThatIllegalArgumentException().isThrownBy(() -> factory.databaseName(null, empty)) .withMessage("No database name"); } @Nested class HandleNewMethods { @ParameterizedTest @ValueSource(strings = { "showAliases", "createLocalDatabaseAlias", "createRemoteDatabaseAlias", "alterLocalDatabaseAlias", "alterRemoteDatabaseAlias", "fixedPathQuantifier", "useGraph", "setOwnPassword", "showAllPrivileges", "showRolePrivileges", "showUserPrivileges", "createDatabase", "createCompositeDatabase", "dropDatabase", "showDatabase", "startDatabase", "stopDatabase", "createUser", "dropUser", "alterUser", "renameUser", "newSensitiveStringParameter", "passwordExpression", "showUsers", "showRolePrivileges", "labelWildcard", "subqueryInTransactionsBatchParameters", "subqueryInTransactionsErrorParameters", "subqueryInTransactionsReportParameters", "showTransactionsClause", "terminateTransactionsClause", "turnYieldToWith", "alterDatabase", "settingQualifier", "showSettingsClause", "repeatableElements", "differentRelationships", "createConstraint", "dropConstraint", "showSupportedPrivileges", "isTyped", "isNotTyped", "functionUseClause", "isNormalized", "isNotNormalized", "normalizeExpression", "insertClause", "insertPathPattern", "subqueryInTransactionsBatchParameters", "subqueryInTransactionsConcurrencyParameters", "subqueryInTransactionsErrorParameters", "grantRoles", "showRoles", "renameRole", "revokeRoles", "auth", "authId", "password", "passwordChangeRequired", "databasePrivilegeScope" }) void newMethodsShouldNotBeSupportedOOTB(String methodName) { var factory = CypherDslASTFactory.getInstance(null); var methods = factory.getClass().getMethods(); for (var method : methods) { if (method.getName().equals(methodName)) { Object[] args = new Object[method.getParameterCount()]; int i = 0; for (Class type : method.getParameterTypes()) { // Other primitives needs to be dealt with, but right now, not in // the mood todo t his. args[i++] = (type == boolean.class) ? false : null; } assertThatExceptionOfType(InvocationTargetException.class) .isThrownBy(() -> method.invoke(factory, args)) .withRootCauseInstanceOf(UnsupportedOperationException.class); return; } } Assertions.fail("Didn't find method " + methodName + " any more"); } } } ================================================ FILE: neo4j-cypher-dsl-parser/src/test/java/org/neo4j/cypherdsl/parser/CypherParserTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.util.Arrays; import java.util.EnumSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.neo4j.cypherdsl.core.AliasedExpression; import org.neo4j.cypherdsl.core.Clause; import org.neo4j.cypherdsl.core.Clauses; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.FunctionInvocation; import org.neo4j.cypherdsl.core.Literal; import org.neo4j.cypherdsl.core.Match; import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.Parameter; import org.neo4j.cypherdsl.core.RelationshipPattern; import org.neo4j.cypherdsl.core.Statement; import org.neo4j.cypherdsl.core.Where; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatRuntimeException; /** * @author Michael J. Simons */ class CypherParserTests { static Stream pathMatchingAndAssignment() { return Stream.of(Arguments.of(""" MATCH p = SHORTEST 1 (wos:Station)-[:LINK]-+(bmv:Station) WHERE wos.name = "Worcester Shrub Hill" AND bmv.name = "Bromsgrove" RETURN length(p) AS result """, "MATCH p = SHORTEST 1 (wos:`Station`)-[:`LINK`]-+(bmv:`Station`) WHERE (wos.name = 'Worcester Shrub Hill' AND bmv.name = 'Bromsgrove') RETURN length(p) AS result"), Arguments.of(""" MATCH p = SHORTEST 2 (wos:Station)-[:LINK]-+(bmv:Station) WHERE wos.name = "Worcester Shrub Hill" AND bmv.name = "Bromsgrove" RETURN [n in nodes(p) | n.name] AS stops """, "MATCH p = SHORTEST 2 (wos:`Station`)-[:`LINK`]-+(bmv:`Station`) WHERE (wos.name = 'Worcester Shrub Hill' AND bmv.name = 'Bromsgrove') RETURN [n IN nodes(p) | n.name] AS stops"), Arguments.of(""" MATCH p = ALL SHORTEST (wos:Station)-[:LINK]-+(bmv:Station) WHERE wos.name = "Worcester Shrub Hill" AND bmv.name = "Bromsgrove" RETURN [n in nodes(p) | n.name] AS stops """, "MATCH p = ALL SHORTEST (wos:`Station`)-[:`LINK`]-+(bmv:`Station`) WHERE (wos.name = 'Worcester Shrub Hill' AND bmv.name = 'Bromsgrove') RETURN [n IN nodes(p) | n.name] AS stops"), Arguments.of(""" MATCH p = SHORTEST 2 GROUPS (wos:Station)-[:LINK]-+(bmv:Station) WHERE wos.name = "Worcester Shrub Hill" AND bmv.name = "Bromsgrove" RETURN [n in nodes(p) | n.name] AS stops, length(p) AS pathLength """, "MATCH p = SHORTEST 2 GROUPS (wos:`Station`)-[:`LINK`]-+(bmv:`Station`) WHERE (wos.name = 'Worcester Shrub Hill' AND bmv.name = 'Bromsgrove') RETURN [n IN nodes(p) | n.name] AS stops, length(p) AS pathLength"), Arguments.of( """ MATCH path = ANY (:Station {name: 'Pershore'})-[l:LINK WHERE l.distance < 10]-+(b:Station {name: 'Bromsgrove'}) RETURN [r IN relationships(path) | r.distance] AS distances """, "MATCH path = ANY (:`Station` {name: 'Pershore'})-[l:`LINK` WHERE l.distance < 10]-+(b:`Station` {name: 'Bromsgrove'}) RETURN [r IN relationships(path) | r.distance] AS distances")); } static void assertExpression(String expression) { assertExpression(expression, expression); } static void assertExpression(String expression, String expected) { Expression e = CypherParser.parseExpression(expression); assertThat(Cypher.returning(e).build().getCypher()).isEqualTo(String.format("RETURN %s", expected)); } static void assertNode(Node node, String cypherDslRepresentation) { assertThat(Cypher.match(node).returning(Cypher.asterisk()).build().getCypher()) .isEqualTo(String.format("MATCH %s RETURN *", cypherDslRepresentation)); } private static Stream inputAndIdentifiableExpressions() { return Stream.of(Arguments.of(""" CALL { MATCH (p:Person)-[:LIKES]->(:Technology {type: "Java"}) RETURN p UNION MATCH (p:Person) WHERE size((p)-[:IS_FRIENDS_WITH]->()) > 1 RETURN p } RETURN p.name AS person, p.birthdate AS dob ORDER BY dob DESC""", List.of("person", "dob")), Arguments.of(""" MATCH p=(start)-[*]->(finish) WHERE start.name = 'A' AND finish.f = 'D' FOREACH (n IN nodes(p) | SET n.marked = true)""", List.of("finish", "start", "p")), Arguments.of(""" MATCH (a) WHERE a.name = 'Eskil' RETURN a.array, [x IN a.array WHERE size(x) = 3]""", List.of("a.array"))); } @ParameterizedTest @MethodSource void pathMatchingAndAssignment(String in, String expected) { assertThat(CypherParser.parse(in).getCypher()).isEqualTo(expected); } @Test void shouldProvideANiceErrorMessage() { assertThatExceptionOfType(UnsupportedCypherException.class) .isThrownBy(() -> CypherParser.parse("CREATE ROLE myrole")) .withMessage("You used one Cypher construct not yet supported by the Cypher-DSL:\n" + "\n" + "\tCREATE ROLE myrole\n" + "\n" + "Feel free to open an issue so that we might add support for it at https://github.com/neo4j-contrib/cypher-dsl/issues/new"); } @Test void shouldParseCount() { assertExpression("Count(*)", "count(*)"); } @Test void shouldParseIn() { assertExpression("n in [1,2,3]", "n IN [1, 2, 3]"); } @ParameterizedTest @CsvSource(value = { "f()| f()", "foo.bar()| foo.bar()", "foo.bar(e)| foo.bar(e)", "foo.bar(e,f)| foo.bar(e, f)", "count(distinct e,f)| count(DISTINCT e, f)" }, delimiterString = "|") void shouldParseFunctionInvocation(String input, String expected) { assertExpression(input, expected); } @Test void onNewReturnItemCallbacksShouldBeApplied() { var cnt = new AtomicInteger(0); var options = Options.newOptions() .withCallback(ExpressionCreatedEventType.ON_RETURN_ITEM, AliasedExpression.class, e -> { assertThat(cnt.compareAndSet(0, 1)).isTrue(); if (e instanceof AliasedExpression) { return (AliasedExpression) e; } return e.as("foo"); }) .withCallback(ExpressionCreatedEventType.ON_RETURN_ITEM, Expression.class, e -> { assertThat(cnt.compareAndSet(1, 2)).isTrue(); return e; }) .build(); var statement = CypherParser.parseStatement("RETURN 1", options); assertThat(cnt.get()).isEqualTo(2); assertThat(statement.getCypher()).isEqualTo("RETURN 1 AS foo"); } @Test void shouldParseReturnAll() { var statement1 = CypherParser.parseStatement("WITH 1 AS foo WITH *, 2 AS bar RETURN *"); assertThat(statement1.getCypher()).isEqualTo("WITH 1 AS foo WITH *, 2 AS bar RETURN *"); var statement2 = CypherParser.parseStatement("WITH 1 AS foo WITH *, 2 AS bar RETURN foo"); assertThat(statement2.getCypher()).isEqualTo("WITH 1 AS foo WITH *, 2 AS bar RETURN foo"); } @ParameterizedTest @ValueSource(strings = { "CREATE", "MERGE", "MATCH" }) void patternElementCallBacksShouldBeApplied(String clause) { var builder = Options.newOptions(); EnumSet.allOf(PatternElementCreatedEventType.class).forEach(et -> builder.withCallback(et, patternElement -> { if (patternElement instanceof Node) { var existing = ((Node) patternElement).getLabels().get(0).getValue(); return Cypher.node(existing, "FirstLabelAdded"); } return patternElement; }).withCallback(et, patternElement -> { if (patternElement instanceof Node) { var l1 = ((Node) patternElement).getLabels().get(0).getValue(); var l2 = ((Node) patternElement).getLabels().get(1).getValue(); return Cypher.node(l1, l2, "SecondLabelAdded"); } return patternElement; })); var options = builder.build(); var statement = CypherParser.parseStatement(clause + " (n:Movie) RETURN n", options); assertThat(statement.getCypher()) .isEqualTo(clause + " (:`Movie`:`FirstLabelAdded`:`SecondLabelAdded`) RETURN n"); } @Test void shouldNotAllowInvalidCallbacks() { assertThatIllegalArgumentException() .isThrownBy(() -> Options.newOptions() .withCallback(ExpressionCreatedEventType.ON_SET_PROPERTY, Expression.class, e -> e) .build()) .withMessage( "The type that is produced by 'ON_SET_PROPERTY' is not compatible with interface org.neo4j.cypherdsl.core.Expression"); } @ParameterizedTest @MethodSource("inputAndIdentifiableExpressions") void parseAndIdentifyShouldWork(String cypher, List expected) { var identifiables = CypherParser.parse(cypher).getCatalog().getIdentifiableExpressions(); assertThat(identifiables.stream().map(Cypher::format)).containsExactlyInAnyOrderElementsOf(expected); } @Test void transformingWhereShouldWork() { var query = "MATCH (m:Movie {title: 'The Matrix'}) WHERE m.releaseYear IS NOT NULL OR false RETURN m"; var options = Options.newOptions() .withMatchClauseFactory(matchDefinition -> (Match) Clauses.match(matchDefinition.optional(), matchDefinition.patternElements(), Where.from(Cypher.isFalse()), matchDefinition.optionalHints())) .build(); var cypher = CypherParser.parse(query, options).getCypher(); assertThat(cypher).isEqualTo("MATCH (m:`Movie` {title: 'The Matrix'}) WHERE false RETURN m"); } @ParameterizedTest @CsvSource(delimiterString = "@", textBlock = """ WITH ['x'] AS l MATCH (n) SET n:$(l) RETURN n@WITH ['x'] AS l MATCH (n) SET n:$(l) RETURN n WITH ['x'] AS l, ['y'] AS l2 MATCH (n) SET n:$(l):$(l2) RETURN n@WITH ['x'] AS l, ['y'] AS l2 MATCH (n) SET n:$(l):$(l2) RETURN n """) void labelColonConjunctionSet(String in, String expected) { assertThat(CypherParser.parse(in).getCypher()).isEqualTo(expected); } @Test void labelColonConjunction() { assertThat(CypherParser.parse("MATCH (a:A:B:C) RETURN a").getCypher()) .isEqualTo("MATCH (a:`A`:`B`:`C`) RETURN a"); } @Test void labelColonConjunctionMixed() { var options = Options.newOptions() .withLabelFilter( (labelParsedEventType, strings) -> strings.stream().filter(Predicate.not("B"::equals)).toList()) .build(); assertThat(CypherParser.parse("MATCH (a:A:B:$($trolololo):C) RETURN a", options).getCypher()) .isEqualTo("MATCH (a:`A`:$($trolololo):`C`) RETURN a"); } @Test void labelColonConjunction0Element() { var options = Options.newOptions() .withLabelFilter((labelParsedEventType, strings) -> strings.stream().filter(s -> false).toList()) .build(); assertThat(CypherParser.parse("MATCH (a:A:B) RETURN a", options).getCypher()).isEqualTo("MATCH (a) RETURN a"); } @Test void labelColonConjunction1Element() { var options = Options.newOptions() .withLabelFilter( (labelParsedEventType, strings) -> strings.stream().filter(Predicate.not("B"::equals)).toList()) .build(); assertThat(CypherParser.parse("MATCH (a:A:B) RETURN a", options).getCypher()) .isEqualTo("MATCH (a:`A`) RETURN a"); } @Test void labelColonDisjunction() { assertThat(CypherParser .parse("MATCH (wallstreet {title: 'Wall Street'})<-[:ACTED_IN|:DIRECTED]-(person) RETURN person.name") .getCypher()).isEqualTo( "MATCH (wallstreet {title: 'Wall Street'})<-[:`ACTED_IN`|`DIRECTED`]-(person) RETURN person.name"); } @Test void newOrEdLabels() { String statement = "MATCH (:`Human`|`Occupation`)-[:`OCCUPATION`*0..1]-(h:`Human`) RETURN h"; Statement parsed = CypherParser.parseStatement(statement); var result = parsed.getCypher(); assertThat(result).isEqualTo("MATCH (:`Human`|`Occupation`)-[:`OCCUPATION`*0..1]-(h:`Human`) RETURN h"); } @ParameterizedTest @ValueSource(strings = { "MATCH (h:A&B&C) RETURN h", "MATCH (h:!A&!B&!C) RETURN h", "MATCH (h:A|B|C) RETURN h", "MATCH (h:!A|!B|!C) RETURN h", "MATCH (h:A|B&C|D) RETURN h", "MATCH (h:A|!(B&C)|D) RETURN h", "MATCH (h:(A|B)&!(C|D)) RETURN h", "MATCH (h:(A|B)&(C|D)) RETURN h", "MATCH (h:!(A|B)&(C|D)) RETURN h", "MATCH (h:!(!(A|B)&(C|D))) RETURN h", "MATCH (h:A&B|C&D) RETURN h", "MATCH (h:(Human|Occupation|X)&!A|B|C) RETURN h" }) void labelExpressions(String statement) { assertParsedStatement(statement, statement); } @ParameterizedTest @CsvSource(textBlock = """ MATCH (h:(A&B)) RETURN h,MATCH (h:A&B) RETURN h MATCH (h:A|(B&C)|D) RETURN h,MATCH (h:A|B&C|D) RETURN h MATCH (h:(A&B)|C&D) RETURN h,MATCH (h:A&B|C&D) RETURN h MATCH (h:((A&B)|C&D)) RETURN h,MATCH (h:A&B|C&D) RETURN h MATCH (h:!!(A|B)&(C|D)) RETURN h,MATCH (h:(A|B)&(C|D)) RETURN h MATCH (h:!(!(A|B))&(C|D)) RETURN h,MATCH (h:(A|B)&(C|D)) RETURN h """) void optimizedLabelExpressions(String statement, String expected) { assertParsedStatement(statement, expected); } @ParameterizedTest @CsvSource( textBlock = """ MATCH (n:Person WHERE n.name = 'Tom Hanks') RETURN n,MATCH (n:Person WHERE n.name = 'Tom Hanks') RETURN n MATCH (n:Person WHERE n.name = 'Tom Hanks' OR n.name = 'Bud Spencer') RETURN n,MATCH (n:Person WHERE (n.name = 'Tom Hanks' OR n.name = 'Bud Spencer')) RETURN n MATCH (n:Person WHERE n.name = 'Tom Hanks') WHERE n.born <= 2000 RETURN n,MATCH (n:Person WHERE n.name = 'Tom Hanks') WHERE n.born <= 2000 RETURN n MATCH (n:Person WHERE n.name = 'Tom Hanks' OR n.name = 'Bud Spencer') WHERE n.born <= 2000 RETURN n,MATCH (n:Person WHERE (n.name = 'Tom Hanks' OR n.name = 'Bud Spencer')) WHERE n.born <= 2000 RETURN n """) void whereInMatch(String statement, String expected) { assertParsedStatement(statement, expected); } @ParameterizedTest @CsvSource(delimiterString = "@", textBlock = """ WITH ['Person', 'Director'] AS labels MATCH (directors:$(labels)) RETURN directors@WITH ['Person', 'Director'] AS labels MATCH (directors:$(labels)) RETURN directors MATCH (n:$any(['Movie', 'Actor'])) RETURN n AS nodes@MATCH (n:$any(['Movie', 'Actor'])) RETURN n AS nodes MATCH (n:$any(['Movie', 'Actor'])&`Foo`|`Bar`&$($IWantToDie)) RETURN n AS nodes@MATCH (n:$any(['Movie', 'Actor'])&Foo|Bar&$($IWantToDie)) RETURN n AS nodes MATCH (n:$($a)|$any($b)) RETURN n AS nodes@MATCH (n:$($a)|$any($b)) RETURN n AS nodes """) void dynamicLabelExpressions(String statement, String expected) { assertParsedStatement(statement, expected); } private static void assertParsedStatement(String statement, String expected) { Statement parsed = CypherParser.parseStatement(statement); String cypher = Renderer.getRenderer(Configuration.newConfig().alwaysEscapeNames(false).build()).render(parsed); assertThat(cypher).isEqualTo(expected); } @Test void nodePatternInCall() { var statement = """ match (n:Person) call { match (n:Movie {title: 'The Matrix'}) where n.released >= 1900 return n as m } return n.name """; assertParsedStatement(statement, "MATCH (n:Person) CALL (*) {MATCH (n:Movie {title: 'The Matrix'}) WHERE n.released >= 1900 RETURN n AS m} RETURN n.name"); } @Test void shouldBeAbleToParseIntoSortedMaps() { var in = """ MATCH (this:Movie) WHERE this.released = $releaseYear CALL { WITH this MATCH (this_actorsAggregate_this1:Actor)-[this_actorsAggregate_this0:ACTED_IN]->(this) RETURN { min: min(this_actorsAggregate_this0.screentime), max: max(this_actorsAggregate_this0.screentime), average: avg(this_actorsAggregate_this0.screentime), sum: sum(this_actorsAggregate_this0.screentime) } AS this_actorsAggregate_var2 } RETURN this { actorsAggregate: { edge: { screentime: this_actorsAggregate_var2 } } } AS this """; var parsed = CypherParser.parse(in, Options.newOptions().createSortedMaps(true).build()); var cypher = Renderer.getRenderer( Configuration.newConfig().withPrettyPrint(true).withIndentStyle(Configuration.IndentStyle.TAB).build()) .render(parsed); assertThat(cypher).isEqualTo(""" MATCH (this:Movie) WHERE this.released = $releaseYear CALL (this) { MATCH (this_actorsAggregate_this1:Actor)-[this_actorsAggregate_this0:ACTED_IN]->(this) RETURN { average: avg(this_actorsAggregate_this0.screentime), max: max(this_actorsAggregate_this0.screentime), min: min(this_actorsAggregate_this0.screentime), sum: sum(this_actorsAggregate_this0.screentime) } AS this_actorsAggregate_var2 } RETURN this { actorsAggregate: { edge: { screentime: this_actorsAggregate_var2 } } } AS this"""); } @Test // GH-729 void fillingParametersOfParsedCypherManual() { var oidValue = "look ma, no Cypher injection'}) MATCH (n) DETACH DELETE n --"; String userProvidedCypher = "MATCH (p:`confignode` {oid: $oid}) " + "CALL apoc.path.subgraphAll(p, {relationshipFilter: 'BELONGS_TO_ARRAY|IN|IN_ARRAY', minLevel: 1, maxLevel: 3}) " + "YIELD nodes, relationships " + "FOREACH(node in nodes |detach delete node)" + "RETURN nodes"; var options = Options.newOptions() .withCallback(ExpressionCreatedEventType.ON_NEW_PARAMETER, Parameter.class, newExpression -> { var p = (Parameter) newExpression; if ("oid".equals(p.getName())) { return Cypher.parameter(p.getName(), oidValue); } return p; }) .build(); var userStatement = CypherParser.parse(userProvidedCypher, options); var prettyPrintingRenderer = Renderer.getRenderer(Configuration.prettyPrinting()); assertThat(prettyPrintingRenderer.render(userStatement)).isEqualTo(""" MATCH (p:confignode { oid: $oid }) CALL apoc.path.subgraphAll(p, { relationshipFilter: 'BELONGS_TO_ARRAY|IN|IN_ARRAY', minLevel: 1, maxLevel: 3 }) YIELD nodes, relationships FOREACH (node IN nodes | DETACH DELETE node) RETURN nodes"""); assertThat(userStatement.getCatalog().getParameters()).containsEntry("oid", "look ma, no Cypher injection'}) MATCH (n) DETACH DELETE n --"); } @Test // GH-729 void fillingParametersOfParsedCypher() { var oidValue = "look ma, no Cypher injection'}) MATCH (n) DETACH DELETE n --"; String userProvidedCypher = "MATCH (p:`confignode` {oid: $oid}) " + "CALL apoc.path.subgraphAll(p, {relationshipFilter: 'BELONGS_TO_ARRAY|IN|IN_ARRAY', minLevel: 1, maxLevel: 3}) " + "YIELD nodes, relationships " + "FOREACH(node in nodes |detach delete node)" + "RETURN nodes"; var options = Options.newOptions().withParameterValues(Map.of("oid", oidValue)).build(); var userStatement = CypherParser.parse(userProvidedCypher, options); assertThat(userStatement.getCatalog().getParameters()).containsEntry("oid", "look ma, no Cypher injection'}) MATCH (n) DETACH DELETE n --"); } @Test // GH-739 void literalCallbacks() { var literals = new LinkedHashSet>(); var options = Options.newOptions() .withCallback(ExpressionCreatedEventType.ON_NEW_LITERAL, Expression.class, l -> { literals.add((Literal) l); return l; }) .build(); CypherParser.parse( "RETURN NULL, 1.0 as d, 100 as l, 0x13af as h, 0o1372 as o, 'Hallo' as s, true, false, Inf, NaN", options); assertThat(literals.stream().map(Literal::getClass).distinct()).hasSize(6); assertThat(literals).map(Literal::asString) .containsExactly("NULL", "1.0", "100", "5039", "762", "'Hallo'", "true", "false", "Infinity", "NaN"); } @ParameterizedTest @CsvSource(delimiterString = "%%", textBlock = """ MATCH (statement) WHERE EXISTS { MATCH (statement)-[:FOO WHERE true]->(m) WHERE false } RETURN statement %% MATCH (statement) WHERE EXISTS { MATCH (statement)-[:FOO WHERE true]->(m) WHERE false } RETURN statement MATCH (patternWithWhere) WHERE EXISTS { (patternWithWhere)-[:FOO WHERE true]->(m) WHERE false } RETURN statement %% MATCH (patternWithWhere) WHERE EXISTS { (patternWithWhere)-[:FOO WHERE true]->(m) WHERE false } RETURN statement MATCH (person:Person) WHERE COUNT { (person)-[:HAS_DOG]->(:Dog) } > 1 RETURN person.name AS name %% MATCH (person:Person) WHERE COUNT { (person)-[:HAS_DOG]->(:Dog) } > 1 RETURN person.name AS name MATCH (person:Person) WHERE COUNT { MATCH (person)-[:HAS_DOG]->(:Dog) } > 1 RETURN person.name AS name %% MATCH (person:Person) WHERE COUNT { MATCH (person)-[:HAS_DOG]->(:Dog) } > 1 RETURN person.name AS name MATCH (person:Person) WHERE COUNT { MATCH (person)-[d:HAS_DOG WHERE d.since > 10]->(:Dog) } > 1 RETURN person.name AS name %% MATCH (person:Person) WHERE COUNT { MATCH (person)-[d:HAS_DOG WHERE d.since > 10]->(:Dog) } > 1 RETURN person.name AS name MATCH (person:Person) WHERE COUNT { MATCH (person)-[d:HAS_DOG WHERE d.since > 10]->(:Dog) WHERE false} > 1 RETURN person.name AS name %% MATCH (person:Person) WHERE COUNT { MATCH (person)-[d:HAS_DOG WHERE d.since > 10]->(:Dog) WHERE false } > 1 RETURN person.name AS name """) void existAndCountSubqueriesWithPatternOrStatements(String input, String expected) { var renderer = Renderer.getRenderer(Configuration.newConfig().alwaysEscapeNames(false).build()); var cypher = renderer.render(CypherParser.parse(input)); Assertions.assertThat(cypher).isEqualTo(expected.trim()); } @Test // GH-903 void variablesOfListPredicatesMustNotBeScoped() { var cypher = """ MATCH (this:Movie) WHERE single(this0 IN [(this)-[this1:IN_GENRE]->(this0:Genre) WHERE this0.name = $param0 | 1] WHERE true) RETURN this { .actorCount } AS this"""; var cfg = Configuration.newConfig().withPrettyPrint(true).withGeneratedNames(true).build(); var renderer = Renderer.getRenderer(cfg); var parseOptions = Options.newOptions().createSortedMaps(true).build(); var normalized = renderer.render(CypherParser.parse(cypher, parseOptions)); assertThat(normalized).isEqualTo(""" MATCH (v0:Movie) WHERE single(v1 IN [(v0)-[v2:IN_GENRE]->(v3:Genre) WHERE v3.name = $p0 | 1] WHERE true) RETURN v0 { .actorCount } AS v0"""); } @Test // GH-999 void subQueryFromParserScope() { var cypher = """ CALL { CREATE (m:Movie) RETURN m } CALL { WITH m RETURN m { .title } AS movies } RETURN movies"""; var cfg = Configuration.newConfig().withPrettyPrint(true).withGeneratedNames(true).build(); var renderer = Renderer.getRenderer(cfg); var parseOptions = Options.newOptions().createSortedMaps(true).build(); var normalized = renderer.render(CypherParser.parse(cypher, parseOptions)); assertThat(normalized).isEqualTo(""" CALL () { CREATE (v0:Movie) RETURN v0 } CALL (v0) { RETURN v0 { .title } AS v1 } RETURN v1"""); } @Test void newConcatenate() { var cypher = "RETURN 'a' || 'B'"; var cfg = Configuration.newConfig().withPrettyPrint(true).build(); var renderer = Renderer.getRenderer(cfg); var normalized = renderer.render(CypherParser.parse(cypher)); assertThat(normalized).isEqualTo("RETURN ('a' + 'B')"); } @Test // GH-1059 void patternListAsLTRMustBeUnwrappedIntoRelPattern() { var cypher = """ MATCH (movie:Movie) RETURN size((movie)<-[:REVIEWED]-(:Person)) AS movieReviewedByEveryTotal """; var cfg = Configuration.newConfig().withGeneratedNames(true).build(); var renderer = Renderer.getRenderer(cfg); var parseOptions = Options.newOptions().createSortedMaps(true).alwaysCreateRelationshipsLTR(true).build(); var normalized = renderer.render(CypherParser.parse(cypher, parseOptions)); assertThat(normalized).isEqualTo("MATCH (v0:`Movie`) RETURN size((:`Person`)-[:`REVIEWED`]->(v0)) AS v1"); } @ParameterizedTest @CsvSource(delimiterString = "|", textBlock = """ RETURN trim(' asd ') | RETURN trim(' asd ') RETURN trim(BOTH ' ' FROM ' asd ') | RETURN trim(' asd ', ' ') RETURN trim(LEADING ' ' FROM ' asd ') | RETURN ltrim(' asd ', ' ') RETURN trim(TRAILING ' ' FROM ' asd ') | RETURN rtrim(' asd ', ' ') RETURN btrim(' asd ') | RETURN btrim(' asd ') RETURN btrim(' asd ', ' ') | RETURN btrim(' asd ', ' ') RETURN ltrim(' asd ') | RETURN ltrim(' asd ') RETURN ltrim(' asd ', ' ') | RETURN ltrim(' asd ', ' ') RETURN rtrim(' asd ') | RETURN rtrim(' asd ') RETURN rtrim(' asd ', ' ') | RETURN rtrim(' asd ', ' ') """) void trimShouldWork(String in, String out) { var renderer = Renderer.getRenderer(Configuration.defaultConfig()); var normalized = renderer.render(CypherParser.parse(in)); assertThat(normalized).isEqualTo(out); } @Test void optionalCall() { assertThatIllegalArgumentException().isThrownBy(() -> CypherParser.parse("OPTIONAL call db.awaitIndexes()")) .withMessage("Cannot render optional call clause"); } @Test void optionalSubquery() { assertThatIllegalArgumentException() .isThrownBy(() -> CypherParser.parse("MATCH (n) OPTIONAL CALL() {MATCH (m)}")) .withMessage("Cannot render optional subquery clause"); } @Test void unescapedThings() { assertThat(CypherParser.parse("RETURN 1 AS v$_id").getCypher()).isEqualTo("RETURN 1 AS `v$_id`"); } @ParameterizedTest // GH-1341 @CsvSource(delimiterString = "|", textBlock = """ MATCH (c:Content) ORDER BY c.publishedDate RETURN c | MATCH (c:`Content`) ORDER BY c.publishedDate ASC RETURN c MATCH (c:Content) ORDER BY c.publishedDate SKIP 1 RETURN c | MATCH (c:`Content`) ORDER BY c.publishedDate ASC SKIP 1 RETURN c MATCH (c:Content) ORDER BY c.publishedDate LIMIT 1 RETURN c | MATCH (c:`Content`) ORDER BY c.publishedDate ASC LIMIT 1 RETURN c MATCH (c:Content) ORDER BY c.publishedDate SKIP 1 LIMIT 2 RETURN c | MATCH (c:`Content`) ORDER BY c.publishedDate ASC SKIP 1 LIMIT 2 RETURN c MATCH (c:Content) WITH c WHERE c.prop = 3 ORDER BY c.publishedDate RETURN c | MATCH (c:`Content`) WITH c WHERE c.prop = 3 ORDER BY c.publishedDate ASC RETURN c """) void orderByMidwayShouldWork(String in, String expected) { var statement = CypherParser.parse(in); assertThat(statement.getCypher()).isEqualTo(expected); } @Nested class RelationshipPatterns { @ParameterizedTest @CsvSource(nullValues = "N/A", value = { "N/A, N/A", "N/A, 5", "5, N/A", "5, 10", "-,-" }) void simplePatternWithVariousLengths(String minimum, String maximum) { StringBuilder simplePattern = new StringBuilder("(n)-"); if (minimum != null || maximum != null) { simplePattern.append("[*"); if (minimum != null && !"-".equals(minimum)) { simplePattern.append(minimum); } if (!"-".equals(maximum)) { simplePattern.append(".."); if (maximum != null) { simplePattern.append(maximum); } } simplePattern.append("]"); } simplePattern.append("->(m)"); var rel = CypherParser.parseRelationship(simplePattern.toString()); assertThat(Cypher.match(rel).returning(Cypher.asterisk()).build().getCypher()) .isEqualTo(String.format("MATCH %s RETURN *", simplePattern)); } @ParameterizedTest @ValueSource(strings = { "T", "T1|T2", "T1|T2|T3" }) void types(String types) { var rel = CypherParser.parseRelationship(String.format("(n)-[:%s]->(m)", types)); assertThat(Cypher.match(rel).returning(Cypher.asterisk()).build().getCypher()) .isEqualTo(String.format("MATCH (n)-[:%s]->(m) RETURN *", Arrays.stream(types.split("\\|")) .map(v -> String.format("`%s`", v)) .collect(Collectors.joining("|")))); } @ParameterizedTest @CsvSource({ "-,-", "<-,-", "-,->" }) void direction(String left, String right) { StringBuilder simplePattern = new StringBuilder("(n)").append(left).append(right).append("(m)"); var rel = CypherParser.parseRelationship(simplePattern.toString()); assertThat(Cypher.match(rel).returning(Cypher.asterisk()).build().getCypher()) .isEqualTo(String.format("MATCH %s RETURN *", simplePattern)); } @Test void pointyThingAtBothSidesIsNotSupported() { assertThatIllegalArgumentException().isThrownBy(() -> CypherParser.parseRelationship("(n)<-->(m)")) .withMessage("Only left-to-right, right-to-left or unidirectional path elements are supported."); } @Test void chain() { RelationshipPattern rel = CypherParser.parseRelationship("(n)-->()-->(o)"); assertThat(Cypher.match(rel).returning(Cypher.asterisk()).build().getCypher()) .isEqualTo("MATCH (n)-->()-->(o) RETURN *"); } @Test void shortestPath() { Expression ex = CypherParser.parseExpression("shortestPath((n:A)-->(o:B))"); assertThat(Cypher.returning(ex).build().getCypher()).isEqualTo("RETURN shortestPath((n:`A`)-->(o:`B`))"); } @Test void allShortestPaths() { Expression ex = CypherParser.parseExpression("allShortestPaths((n:A)-->(o:B))"); assertThat(Cypher.returning(ex).build().getCypher()) .isEqualTo("RETURN allShortestPaths((n:`A`)-->(o:`B`))"); } @Test void names() { RelationshipPattern rel = CypherParser.parseRelationship("(n)-[r1]->()-[r2]->(o)"); assertThat(Cypher.match(rel).returning(Cypher.asterisk()).build().getCypher()) .isEqualTo("MATCH (n)-[r1]->()-[r2]->(o) RETURN *"); } @Test void properties() { RelationshipPattern rel = CypherParser.parseRelationship("(n)-[{a: 'b', c: 'd'}]->(o)"); assertThat(Cypher.match(rel).returning(Cypher.asterisk()).build().getCypher()) .isEqualTo("MATCH (n)-[ {a: 'b', c: 'd'}]->(o) RETURN *"); } @Test void propertiesOnAChain() { RelationshipPattern rel = CypherParser .parseRelationship("(n)-[r:TYPE{x:'x'}]->(m)-[{a: 'b', c: 'd'}]->(o)"); assertThat(Cypher.match(rel).returning(Cypher.asterisk()).build().getCypher()) .isEqualTo("MATCH (n)-[r:`TYPE` {x: 'x'}]->(m)-[ {a: 'b', c: 'd'}]->(o) RETURN *"); } } @Nested class Literals { @Test void shouldParseListLiteral() { assertExpression("[1,2,a, 'b']", "[1, 2, a, 'b']"); } @Test void shouldParseLookups() { assertExpression("n[23]", "n[23]"); } } @Nested class Parameters { @Test void newParameterShouldWork() { assertExpression("$foo", "$foo"); } @Test void newNumberedParameterShouldWork() { assertExpression("$1", "$1"); } } @Nested class Predicates { @Test void any() { assertThatIllegalArgumentException() .isThrownBy(() -> CypherParser.parseExpression("any(color IN n.liked_colors)")) .withMessage("any(...) requires a WHERE predicate"); assertExpression("any(color IN n.liked_colors WHERE color = 'yellow')"); } @Test void none() { assertThatIllegalArgumentException() .isThrownBy(() -> CypherParser.parseExpression("none(color IN n.liked_colors)")) .withMessage("none(...) requires a WHERE predicate"); assertExpression("none(color IN n.liked_colors WHERE color = 'yellow')"); } @Test void single() { assertThatIllegalArgumentException() .isThrownBy(() -> CypherParser.parseExpression("single(color IN n.liked_colors)")) .withMessage("single(...) requires a WHERE predicate"); assertExpression("single(color IN n.liked_colors WHERE color = 'yellow')"); } @Test void all() { assertThatIllegalArgumentException() .isThrownBy(() -> CypherParser.parseExpression("all(color IN n.liked_colors)")) .withMessage("all(...) requires a WHERE predicate"); assertExpression("all(color IN n.liked_colors WHERE color = 'yellow')"); } } @Nested class InvocationCallbacks { @Test void onNewFunctionCallbacksShouldBeInvoked() { var functions = new AtomicInteger(0); var options = Options.newOptions() // The best: doing nothing .withCallback(InvocationCreatedEventType.ON_INVOCATION, Expression.class, e -> { assertThat(functions.compareAndSet(0, 1)).isTrue(); return e; }) // Example of rewriting a function .withCallback(InvocationCreatedEventType.ON_INVOCATION, FunctionInvocation.class, e -> { assertThat(functions.compareAndSet(1, 2)).isTrue(); if ("asString".equals(e.getFunctionName())) { AtomicReference args = new AtomicReference<>(); e.accept(segment -> { if (!(segment instanceof FunctionInvocation) && segment instanceof Expression expression) { args.compareAndSet(null, expression); } }); return (FunctionInvocation) Cypher.call("toString").withArgs(args.get()).asFunction(); } return e; }) .build(); var statement = CypherParser.parseStatement("RETURN asString(1) AS x", options); assertThat(functions.get()).isEqualTo(2); assertThat(statement.getCypher()).isEqualTo("RETURN toString(1) AS x"); } @Test void onNewProcedureCallbacksShouldBeInvoked() { var procedures = new AtomicInteger(0); var options = Options.newOptions().withCallback(InvocationCreatedEventType.ON_CALL, Clause.class, e -> { assertThat(procedures.compareAndSet(0, 1)).isTrue(); return e; }).withCallback(InvocationCreatedEventType.ON_CALL, Clause.class, e -> { assertThat(procedures.compareAndSet(1, 2)).isTrue(); return e; }).build(); var statement = CypherParser.parseStatement( "call { call dbms.showCurrentUser() yield username return username} return username", options); assertThat(procedures.get()).isEqualTo(2); assertThat(statement.getCypher()) .isEqualTo("CALL () {CALL dbms.showCurrentUser() YIELD username RETURN username} RETURN username"); } @Test void preventingProcedureCalls() { var options = Options.newOptions().withCallback(InvocationCreatedEventType.ON_CALL, Clause.class, e -> { throw new RuntimeException("Procedure calls are not allowed!"); }).build(); assertThatRuntimeException() .isThrownBy(() -> CypherParser.parseStatement( "call { call dbms.showCurrentUser() yield username return username} return username", options)) .withRootCauseInstanceOf(RuntimeException.class) .withStackTraceContaining("Procedure calls are not allowed!"); assertThatNoException().isThrownBy(() -> CypherParser .parseStatement("call {match (n:Movie) return n.title as title} return title", options)); } @Test void preventingFunctionCalls() { var options = Options.newOptions() .withCallback(InvocationCreatedEventType.ON_INVOCATION, FunctionInvocation.class, functionInvocation -> { if ("id".equals(functionInvocation.getFunctionName())) { throw new RuntimeException("id must not be used anymore"); } return functionInvocation; }) .build(); for (String invalidQuery : List.of("MATCH (n) RETURN id(n)", "MATCH (n) WHERE id(n) = $id RETURN n, {__id: id(n), __labels: labels(n)} AS __node__")) { CypherParser.parseExpression(invalidQuery, options); assertThatRuntimeException().isThrownBy(() -> CypherParser.parseStatement(invalidQuery, options)) .withRootCauseInstanceOf(RuntimeException.class) .withStackTraceContaining("id must not be used anymore"); } assertThatNoException() .isThrownBy(() -> CypherParser.parseStatement("MATCH (n) RETURN elementId(n)", options)); } } } ================================================ FILE: neo4j-cypher-dsl-parser/src/test/java/org/neo4j/cypherdsl/parser/ExtractionIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.util.List; import java.util.Set; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.Operator; import org.neo4j.cypherdsl.core.Statement; import org.neo4j.cypherdsl.core.StatementCatalog.Clause; import org.neo4j.cypherdsl.core.StatementCatalog.Property; import org.neo4j.cypherdsl.core.StatementCatalog.Token; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.GeneralizedRenderer; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; /** * The test does really not belong here, as the core is under test, not the parser, but as * Christophe already was so kind providing the test queries… ¯\_(ツ)_/¯ * * @author Michael J. Simons * @author Christophe Willemsen */ class ExtractionIT { private static final Configuration CYPHER_RENDERER_CONFIGURATION = Configuration.newConfig() .alwaysEscapeNames(false) .build(); private static final GeneralizedRenderer RENDERER = Renderer.getRenderer(CYPHER_RENDERER_CONFIGURATION, GeneralizedRenderer.class); private static final Token FOLLOWS = Token.type("FOLLOWS"); private static final Token TRANSACTS_WITH = Token.type("TRANSACTS_WITH"); private static final Token PERSON = Token.label("Person"); private static final Token ACTIVE = Token.label("Active"); private static final Token EVENT = Token.label("Event"); private static final Token CHECKED_IN_EVENT = Token.type("CHECKED_IN_EVENT"); private static final Token OFFICER = Token.label("Officer"); private static final Token MOVIE = Token.label("Movie"); private static final Property PERSON_NAME = new Property(PERSON, "name"); private static final Property DATE = new Property("date"); private static final Property LAST_SEEN_DATE = new Property("lastSeenDate"); private static final Property NAME = new Property("name"); private static final Property FOLLOWS_ID = new Property(FOLLOWS, "id"); private static final Property PERSON_ID = new Property(PERSON, "id"); private static final Property EVENT_POSITION = new Property(EVENT, "position"); private static final Property OFFICER_NAME = new Property(OFFICER, "name"); private static final Property PERSON_BORN = new Property(PERSON, "born"); private static final Property MOVIE_RELEASED = new Property(MOVIE, "released"); private static final Property MOVIE_TITLE = new Property(MOVIE, "title"); static final List TEST_DATA = List.of( new TestData(""" MATCH (n:Person {name: $name}) RETURN n, COUNT { (n)--() } AS degree """, List.of(PERSON), List.of(), List.of(PERSON_NAME), List.of(new TestDataComparison(Clause.MATCH, PERSON_NAME, "n.name", Operator.EQUALITY, "$name", Set.of("name")))), new TestData(""" MATCH (n:Person) WHERE n.name = $name RETURN n, COUNT { (n)--() } AS degree """, List.of(PERSON), List.of(), List.of(PERSON_NAME), List.of(new TestDataComparison(Clause.MATCH, PERSON_NAME, "n.name", Operator.EQUALITY, "$name", Set.of("name")))), new TestData(""" MATCH (n) WHERE id(n) = $id MATCH (n)-[:CHECKED_IN_EVENT]->(e) WHERE e.date CONTAINS "-" WITH n, e, date(e.date) AS date WITH n, e ORDER BY date WITH n, head(collect(e)) AS event RETURN datetime(event.date + 'T23:59:59Z') AS lastSeenDate, $id AS id """, List.of(), List.of(CHECKED_IN_EVENT), List.of(DATE), List.of(new TestDataComparison(Clause.MATCH, DATE, "e.date", Operator.CONTAINS, "'-'", Set.of()))), new TestData(""" MATCH (n) WHERE id(n) = $id WITH n MATCH (n)-[:CHECKED_IN_EVENT]->(e) WHERE e.date CONTAINS "-" WITH n, e, date(e.date) AS date WITH n, e ORDER BY date WITH n, head(collect(e)) AS event RETURN datetime(event.date + 'T23:59:59Z') AS lastSeenDate, $id AS id """, List.of(), List.of(CHECKED_IN_EVENT), List.of(DATE), List.of(new TestDataComparison(Clause.MATCH, DATE, "e.date", Operator.CONTAINS, "'-'", Set.of()))), new TestData(""" MATCH (n) WHERE n.name = $name AND n.lastSeenDate > datetime() - duration({hours: 12}) RETURN n """, List.of(), List.of(), List.of(NAME, LAST_SEEN_DATE), List.of( new TestDataComparison(Clause.MATCH, NAME, "n.name", Operator.EQUALITY, "$name", Set.of("name")), new TestDataComparison(Clause.MATCH, LAST_SEEN_DATE, "n.lastSeenDate", Operator.GREATER_THAN, "(datetime() - duration({hours: 12}))", Set.of()))), new TestData(""" MATCH (n)-[r:FOLLOWS]->(v) WHERE r.id < 25 MATCH (v)-[r2]->(x) WHERE r2.computed = false RETURN count(*) AS matches """, List.of(), List.of(FOLLOWS), List.of(FOLLOWS_ID, new Property("computed")), List.of( new TestDataComparison(Clause.MATCH, FOLLOWS_ID, "r.id", Operator.LESS_THAN, "25", Set.of()), new TestDataComparison(Clause.MATCH, new Property("computed"), "r2.computed", Operator.EQUALITY, "false", Set.of()))), new TestData(""" MATCH (n)-[r:FOLLOWS]->(v) MATCH (v)-[r2:TRANSACTS_WITH]->(x) WHERE r2.computed = false RETURN count(*) AS matches """, List.of(), List.of(FOLLOWS, TRANSACTS_WITH), List.of(new Property(TRANSACTS_WITH, "computed")), List.of(new TestDataComparison(Clause.MATCH, new Property(TRANSACTS_WITH, "computed"), "r2.computed", Operator.EQUALITY, "false", Set.of()))), new TestData(""" MATCH (n:Person {name: "John Doe"}) WHERE n:Active RETURN n """, List.of(PERSON, ACTIVE), List.of(), List.of(PERSON_NAME), List.of(new TestDataComparison(Clause.MATCH, PERSON_NAME, "n.name", Operator.EQUALITY, "'John Doe'", Set.of()))), new TestData(""" MATCH (n:Person) WHERE 12 > n.id RETURN n """, List.of(PERSON), List.of(), List.of(PERSON_ID), List.of(new TestDataComparison(Clause.MATCH, PERSON_ID, "12", Operator.GREATER_THAN, "n.id", Set.of()))), new TestData(""" MATCH (n:Event) WHERE point.distance($point, n.position) < 1000 RETURN n """, List.of(EVENT), List.of(), List.of(EVENT_POSITION), List.of(new TestDataComparison(Clause.MATCH, EVENT_POSITION, "point.distance($point, n.position)", Operator.LESS_THAN, "1000", Set.of("point")))), new TestData(""" MATCH (n:Event) WHERE point.withinBox(n.position, $lowerLeft, $upperRight) RETURN n """, List.of(EVENT), List.of(), List.of(EVENT_POSITION), List.of(new TestDataComparison(Clause.MATCH, EVENT_POSITION, "point.withinBox(n.position, $lowerLeft, $upperRight)", null, null, Set.of("lowerLeft", "upperRight")))), new TestData(""" UNWIND $names AS name MATCH (n:Person {name: name}) RETURN n """, List.of(PERSON), List.of(), List.of(PERSON_NAME), List.of(new TestDataComparison(Clause.MATCH, PERSON_NAME, "n.name", Operator.EQUALITY, "name", Set.of()))), new TestData(""" MATCH (a:Officer),(b:Officer) WHERE a.name CONTAINS 'MR. ISAAC ELBAZ' AND b.name CONTAINS 'Stephanie J. Bridges' MATCH (a)-[r:OFFICER_OF|INTERMEDIARY_OF|REGISTERED_ADDRESS*..10]-(b) WHERE r.isActive = $activeFlag RETURN p LIMIT 50 """, List.of(OFFICER), List.of(Token.type("OFFICER_OF"), Token.type("INTERMEDIARY_OF"), Token.type("REGISTERED_ADDRESS")), List.of(OFFICER_NAME, new Property(Set.of(Token.type("OFFICER_OF"), Token.type("INTERMEDIARY_OF"), Token.type("REGISTERED_ADDRESS")), "isActive")), List.of(new TestDataComparison(Clause.MATCH, OFFICER_NAME, "a.name", Operator.CONTAINS, "'MR. ISAAC ELBAZ'", Set.of()), new TestDataComparison(Clause.MATCH, OFFICER_NAME, "b.name", Operator.CONTAINS, "'Stephanie J. Bridges'", Set.of()), new TestDataComparison(Clause.MATCH, new Property(Set.of(Token.type("OFFICER_OF"), Token.type("INTERMEDIARY_OF"), Token.type("REGISTERED_ADDRESS")), "isActive"), "r.isActive", Operator.EQUALITY, "$activeFlag", Set.of("activeFlag")))), new TestData(" MATCH (p:Person) -[:ACTED_IN {as: 'Neo'}] -> (m:Movie)", List.of(PERSON, MOVIE), List.of(Token.type("ACTED_IN")), List.of(new Property(Token.type("ACTED_IN"), "as")), List.of(new TestDataComparison(Clause.MATCH, new Property(Token.type("ACTED_IN"), "as"), ".as", Operator.EQUALITY, "'Neo'", Set.of()))), new TestData(""" match (n:Person) call { match (n:Movie {title: 'The Matrix'}) where n.released >= 1900 return n as m } return n.name """, List.of(PERSON, MOVIE), List.of(), List.of(PERSON_NAME, MOVIE_TITLE, MOVIE_RELEASED), List.of(new TestDataComparison(Clause.MATCH, MOVIE_TITLE, "n.title", Operator.EQUALITY, "'The Matrix'", Set.of()), new TestDataComparison(Clause.MATCH, MOVIE_RELEASED, "n.released", Operator.GREATER_THAN_OR_EQUAL_TO, "1900", Set.of()))), new TestData(""" MATCH (n:Person {name: 'Tom Hanks'}) CALL { WITH n MATCH (m:Movie)<-[:ACTED_IN]-(n) WHERE (m.released >= 1900 AND n.born = 1956) RETURN m } RETURN n.name, m.title """, List.of(PERSON, MOVIE), List.of(Token.type("ACTED_IN")), List.of(PERSON_NAME, PERSON_BORN, MOVIE_RELEASED, MOVIE_TITLE), List.of(new TestDataComparison(Clause.MATCH, PERSON_NAME, "n.name", Operator.EQUALITY, "'Tom Hanks'", Set.of()), new TestDataComparison(Clause.MATCH, PERSON_BORN, "n.born", Operator.EQUALITY, "1956", Set.of()), new TestDataComparison(Clause.MATCH, MOVIE_RELEASED, "m.released", Operator.GREATER_THAN_OR_EQUAL_TO, "1900", Set.of()))), new TestData(null, Cypher .match(Cypher.node("Person").named("n").withProperties("name", Cypher.literalOf("Tom Hanks"))) .call(Cypher.match(Cypher.node("Movie").named("m").relationshipFrom(Cypher.anyNode("n"), "ACTED_IN")) .where(Cypher.anyNode("m") .property("released") .gte(Cypher.literalOf(1900)) .and(Cypher.anyNode("n").property("born").eq(Cypher.literalOf(1956)))) .returning(Cypher.anyNode("m")) .build(), Cypher.anyNode("n")) .returning(Cypher.anyNode("n").property("name"), Cypher.anyNode("m").property("title")) .build(), List.of(PERSON, MOVIE), List.of(Token.type("ACTED_IN")), List.of(PERSON_NAME, PERSON_BORN, MOVIE_RELEASED, MOVIE_TITLE), List.of(new TestDataComparison(Clause.MATCH, PERSON_NAME, "n.name", Operator.EQUALITY, "'Tom Hanks'", Set.of()), new TestDataComparison(Clause.MATCH, PERSON_BORN, "n.born", Operator.EQUALITY, "1956", Set.of()), new TestDataComparison(Clause.MATCH, MOVIE_RELEASED, "m.released", Operator.GREATER_THAN_OR_EQUAL_TO, "1900", Set.of()))), new TestData(""" MATCH (n:Person WHERE n.name = 'Tom Hanks') CALL { WITH n MATCH (m:Movie)<-[:ACTED_IN]-(n) WHERE (m.released >= 1900 AND n.born = 1956) RETURN m } WITH n, m WHERE m.title = 'Apollo 13' RETURN n, m """, List.of(PERSON, MOVIE), List.of(Token.type("ACTED_IN")), List.of(PERSON_NAME, PERSON_BORN, MOVIE_RELEASED, MOVIE_TITLE), List.of(new TestDataComparison(Clause.MATCH, PERSON_NAME, "n.name", Operator.EQUALITY, "'Tom Hanks'", Set.of()), new TestDataComparison(Clause.MATCH, PERSON_BORN, "n.born", Operator.EQUALITY, "1956", Set.of()), new TestDataComparison(Clause.MATCH, MOVIE_RELEASED, "m.released", Operator.GREATER_THAN_OR_EQUAL_TO, "1900", Set.of()), new TestDataComparison(Clause.WITH, MOVIE_TITLE, "m.title", Operator.EQUALITY, "'Apollo 13'", Set.of()))), new TestData("CREATE (n:Foo {m: 'Bar'})", List.of(Token.label("Foo")), List.of(), List.of(new Property(Token.label("Foo"), "m")), List.of(new TestDataComparison(Clause.CREATE, new Property(Token.label("Foo"), "m"), "n.m", Operator.EQUALITY, "'Bar'", Set.of()))), new TestData("MATCH (n:Foo {m: 'Bar'}) DELETE n", List.of(Token.label("Foo")), List.of(), List.of(new Property(Token.label("Foo"), "m")), List.of(new TestDataComparison(Clause.MATCH, new Property(Token.label("Foo"), "m"), "n.m", Operator.EQUALITY, "'Bar'", Set.of()))), new TestData("MERGE (n:Foo {m: 'Bar'})", List.of(Token.label("Foo")), List.of(), List.of(new Property(Token.label("Foo"), "m")), List.of(new TestDataComparison(Clause.MERGE, new Property(Token.label("Foo"), "m"), "n.m", Operator.EQUALITY, "'Bar'", Set.of()))), new TestData( "MATCH (m:`Movie` {title: 'The Matrix'})<-[a:`ACTED_IN`]-(p:`Person`) WHERE p.born >= $born RETURN p", List.of(MOVIE, PERSON), List.of(Token.type("ACTED_IN")), List.of(MOVIE_TITLE, PERSON_BORN), List.of(new TestDataComparison(Clause.MATCH, MOVIE_TITLE, "m.title", Operator.EQUALITY, "'The Matrix'", Set.of()), new TestDataComparison(Clause.MATCH, PERSON_BORN, "p.born", Operator.GREATER_THAN_OR_EQUAL_TO, "$born", Set.of("born"))))); static Stream extractionShouldWork() { return TEST_DATA.stream().map(Arguments::of); } @SuppressWarnings("removal") @ParameterizedTest @MethodSource void extractionShouldWork(TestData testData) { var statement = (testData.statement() == null) ? CypherParser.parse(testData.query()) : testData.statement(); var catalog = statement.getCatalog(); assertThat(catalog.getNodeLabels()).containsExactlyInAnyOrderElementsOf(testData.expectedLabels()); assertThat(catalog.getRelationshipTypes()).containsExactlyInAnyOrderElementsOf(testData.expectedTypes()); assertThat(catalog.getProperties()).containsExactlyInAnyOrderElementsOf(testData.expectedProperties()); if (testData.expectedComparisons != null) { for (TestDataComparison expectedComparison : testData.expectedComparisons()) { assertThat(catalog.getFilters(expectedComparison.property())) .allMatch(v -> testData.expectedComparisons() .contains(new TestDataComparison(v.clause(), expectedComparison.property, (v.left() == null) ? null : RENDERER.render(v.left()), v.operator(), (v.right() == null) ? null : RENDERER.render(v.right()), v.parameterNames()))); } } } record TestDataComparison(Clause clause, Property property, String left, Operator operator, String right, Set parameterNames) { } record TestData(String query, Statement statement, List expectedLabels, List expectedTypes, List expectedProperties, List expectedComparisons) { TestData(String query, List expectedLabels, List expectedTypes, List expectedProperties) { this(query, null, expectedLabels, expectedTypes, expectedProperties, null); } TestData(String query, List expectedLabels, List expectedTypes, List expectedProperties, List expectedComparisons) { this(query, null, expectedLabels, expectedTypes, expectedProperties, expectedComparisons); } @Override public String toString() { return (this.query == null) ? "(Statement based)" : this.query; } } } ================================================ FILE: neo4j-cypher-dsl-parser/src/test/java/org/neo4j/cypherdsl/parser/IdentifiableExpressionsTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.neo4j.cypherdsl.core.AliasedExpression; import org.neo4j.cypherdsl.core.SymbolicName; import static org.assertj.core.api.Assertions.assertThat; /** * More of a test for the core itself, but it's easier to just parse the already existing * queries here and use that AST than recreate them manually. * * @author Christophe Willemsen * @author Michael J. Simons */ class IdentifiableExpressionsTests { static final List TEST_DATA = List.of(new TestData(""" MATCH (n)-[:PING_EVENT]->(e) WITH n, e WHERE e.date CONTAINS "-" WITH n, e, date(e.date) AS date WITH n, e ORDER BY date WITH n, head(collect(e)) AS event RETURN id(n) AS id, datetime(event.date + 'T23:59:59Z') AS lastSeenDate """, List.of("id", "lastSeenDate")), new TestData(""" MATCH (p:Person) where id(p) = $id with p MATCH (p)-[:KNOWS]-(kp:Person) WITH p, count(distinct kp) AS knows_people_count MATCH (p)-[:KNOWS]-(kpc:Person)-[:PARTY_TO]->(:Crime) WITH p, knows_people_count, count(distinct kpc) as knows_criminals_count WITH p, toInteger((100*knows_criminals_count/toFloat(knows_people_count))) as score CALL apoc.when( EXISTS((p)-[:PARTY_TO]->(:Crime)), 'RETURN 100 as score', 'RETURN score as score', {p:p, score: score}) YIELD value return id(p) as id, value.score as criminalRiskScore """, List.of("id", "criminalRiskScore")), new TestData(""" MATCH (p:Person) where id(p) = $id with p MATCH (p)-[:KNOWS]-(kp:Person) WITH p, count(distinct kp) AS knows_people_count MATCH (p)-[:KNOWS]-(kpc:Person)-[:PARTY_TO]->(:Crime) WITH p, knows_people_count, count(distinct kpc) as knows_criminals_count WITH p, toInteger((100*knows_criminals_count/toFloat(knows_people_count))) as score return id(p) as id, score as criminalRiskScore """, List.of("id", "criminalRiskScore")), new TestData(""" MATCH (p:Person)-[:PARTY_TO]->(c:Crime) WHERE id(p) = $id RETURN $id AS id, True AS criminal """, List.of("id", "criminal")), new TestData(""" match (l:Location)<-[]-(c:Crime)<-[]-(v:Vehicle) where id(v) = $id return id(v) as id, l.geospatial as location """, List.of("id", "location")), new TestData(""" MATCH (n) WHERE id(n) = $id RETURN n.name STARTS WITH 'Ar' AS badLink, $id AS id """, List.of("badLink", "id")), new TestData(""" MATCH (k:Keyword)-[:DESCRIBES]->(a) WHERE id(k) = $id WITH k, collect(DISTINCT a.time) AS timestamps RETURN timestamps, id(k) as id """, List.of("timestamps", "id")), new TestData(""" MATCH (k:Keyword)-[:DESCRIBES]->(a) WHERE id(k) = $id WITH k, size(collect(DISTINCT a.time)) AS numberOfRoles, [1, 2, 3] AS roles RETURN apoc.coll.randomItems(roles, numberOfRoles, true) AS roles, id(k) AS id """, List.of("roles", "id")), new TestData(""" MATCH (n) WHERE id(n) = $id + 10000 RETURN id(n) AS id, 'Reviewed' IN labels(n) AS reviewed """, List.of("id", "reviewed")), new TestData(""" MATCH (n) WHERE id(n) = $id RETURN $id AS id, COUNT { (n)-[:INVESTED]->() } AS totalInvestments """, List.of("id", "totalInvestments")), new TestData(""" MATCH (n:Person)-[:HAS_PHONE]->(p:Phone) where id(p) = $id return id(p) as id, n.full_name as owner """, List.of("id", "owner")), new TestData(""" MATCH (e:Entity)-[:IN_CLUSTER]->(c) WHERE id(c) = $id RETURN $id as id, point({latitude: avg(e.latitude), longitude:avg(e.longitude)}) as coordinates_cluster """, List.of("id", "coordinates_cluster")), new TestData(""" MATCH (o)<-[:ORG_GROUP]-(:Organization)<-[:AWARDED_TO]-(g:Grant) WHERE id(o)= $id WITH distinct g, o RETURN id(o) as id, sum(g.amount) as `grant_amount` """, List.of("id", "grant_amount")), new TestData(""" MATCH (p:Person)-[:LIVES_IN]->(l:Location) WHERE id(p) = $id RETURN $id AS id, p.pleasant_temperature_threshold <= l.ga_day_temp as IsTemperatureOK """, List.of("id", "IsTemperatureOK")), new TestData(""" MATCH (p:Person) WHERE p.email = $node.values.email MATCH (c:Certification) WHERE c.name="Neo4j Certified Professional" RETURN $id as id, exists((p)-[:HAS_CERTIFICATION]->(c)) as neoCertified """, List.of("id", "neoCertified")), new TestData(""" MATCH (n) WHERE id(n) = $id RETURN 'https://www.google.com/search?q=' + replace(n.name,' ','+') AS googleSearchUrl, $id AS id """, List.of("googleSearchUrl", "id"))); static Stream testGetIdentifiableElementsFromQuery() { return TEST_DATA.stream().map(Arguments::of); } @ParameterizedTest @MethodSource void testGetIdentifiableElementsFromQuery(TestData testData) { var statement = CypherParser.parse(testData.query()); var elements = statement.getCatalog().getIdentifiableExpressions().stream().map(e -> { if (e instanceof AliasedExpression) { return ((AliasedExpression) e).getAlias(); } return ((SymbolicName) e).getValue(); }).toList(); assertThat(elements).hasSameElementsAs(testData.expected()); } record TestData(String query, List expected) { } } ================================================ FILE: neo4j-cypher-dsl-parser/src/test/java/org/neo4j/cypherdsl/parser/OptionsTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.util.HashMap; import java.util.Map; import java.util.function.UnaryOperator; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; /** * @author Michael J. Simons */ class OptionsTests { @Test // GH-299 void patternElementCallBacksShouldBeConfigurable() { var builder = Options.newOptions() .withCallback(PatternElementCreatedEventType.ON_CREATE, UnaryOperator.identity()); var options = builder.build(); assertThat(options.getOnNewPatternElementCallbacks()).containsKey(PatternElementCreatedEventType.ON_CREATE); assertThat(options.getOnNewPatternElementCallbacks().get(PatternElementCreatedEventType.ON_CREATE)).hasSize(1); builder.withCallback(PatternElementCreatedEventType.ON_CREATE, UnaryOperator.identity()); options = builder.build(); assertThat(options.getOnNewPatternElementCallbacks()).containsKey(PatternElementCreatedEventType.ON_CREATE); assertThat(options.getOnNewPatternElementCallbacks().get(PatternElementCreatedEventType.ON_CREATE)).hasSize(2); } @Test // GH-785 void optionsMustAllowNullParameterValues() { Map wurst = new HashMap<>(); wurst.put("salat", null); var builder = Options.newOptions(); assertThatNoException().isThrownBy(() -> builder.withParameterValues(wurst)); } @Test // Grrr aaargh sonar, that code was there before, but alas, here's your 75%. void nullParameterShouldResetThings() { Map wurst = new HashMap<>(); wurst.put("salat", null); var builder = Options.newOptions(); builder.withParameterValues(wurst); Options options = builder.build(); assertThat(options.getParameterValues()).containsEntry("salat", null); builder.withParameterValues(null); options = builder.build(); assertThat(options.getParameterValues()).isEmpty(); } } ================================================ FILE: neo4j-cypher-dsl-parser/src/test/java/org/neo4j/cypherdsl/parser/ParserIssuesIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Dialect; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; class ParserIssuesIT { private final Configuration rendererConfig = Configuration.newConfig() .withPrettyPrint(true) .withGeneratedNames(true) .withDialect(Dialect.NEO4J_4) .build(); @Test // GH-1076 void parsingDeeplyNestedSubqueriesThenNormalizeShouldWork() { var cypher = """ MATCH (this:Movie) WHERE this.title = $param0 CALL { WITH this MATCH (this)<-[this0:ACTED_IN]-(this1:Actor) WITH collect( { node: this1, relationship: this0 }) AS edges WITH edges, size(edges) AS totalCount CALL { WITH edges UNWIND edges AS edge WITH edge.node AS this1, edge.relationship AS this0 CALL { WITH this1 MATCH (this1)-[this2:ACTED_IN]->(this3:Movie) WITH collect( { node: this3, relationship: this2 }) AS edges WITH edges, size(edges) AS totalCount RETURN { totalCount: totalCount } AS var9 } RETURN collect( { properties: { screenTime: this0.screenTime, __resolveType: 'ActedIn' }, node: { name: this1.name, moviesConnection: var9, __resolveType: 'Actor' } }) AS var10 } RETURN { edges: var10, totalCount: totalCount } AS var11 } RETURN this { .title, actorsConnection: var11 } AS this """; var renderer = Renderer.getRenderer(this.rendererConfig); var normalized = renderer.render(CypherParser.parse(cypher, Options.defaultOptions())); assertThat(normalized).isEqualTo(""" MATCH (v0:Movie) WHERE v0.title = $p0 CALL { WITH v0 MATCH (v0)<-[v1:ACTED_IN]-(v2:Actor) WITH collect( { node: v2, relationship: v1 }) AS v3 WITH v3, size(v3) AS v4 CALL { WITH v3 UNWIND v3 AS v5 WITH v5.node AS v6, v5.relationship AS v7 CALL { WITH v6 MATCH (v6)-[v0:ACTED_IN]->(v1:Movie) WITH collect( { node: v1, relationship: v0 }) AS v2 WITH v2, size(v2) AS v4 RETURN { totalCount: v4 } AS v8 } RETURN collect( { properties: { screenTime: v7.screenTime, __resolveType: 'ActedIn' }, node: { name: v6.name, moviesConnection: v8, __resolveType: 'Actor' } }) AS v9 } RETURN { edges: v9, totalCount: v4 } AS v10 } RETURN v0 { .title, actorsConnection: v10 } AS v0"""); } @Test // GH-1075 void unionPartsMustBeExportedToScope() { var cypher = """ MATCH (this:Actor) CALL { WITH this CALL { WITH this MATCH (this)-[this0:ACTED_IN]->(this1:Movie) WITH { properties: { screenTime: this0.screenTime }, node: { __resolveType: 'Movie', title: this1.title } } AS edge RETURN edge UNION WITH this MATCH (this)-[this2:ACTED_IN]->(this3:Series) WITH { properties: { screenTime: this2.screenTime }, node: { __resolveType: 'Series', episodes: this3.episodes, title: this3.title } } AS edge RETURN edge } WITH collect(edge) AS edges RETURN { edges: edges } AS var4 } RETURN this { .name, actedInConnection: var4 } AS this """; var renderer = Renderer.getRenderer(this.rendererConfig); var normalized = renderer.render(CypherParser.parse(cypher)); assertThat(normalized).isEqualTo(""" MATCH (v0:Actor) CALL { WITH v0 CALL { WITH v0 MATCH (v0)-[v1:ACTED_IN]->(v2:Movie) WITH { properties: { screenTime: v1.screenTime }, node: { __resolveType: 'Movie', title: v2.title } } AS v3 RETURN v3 UNION WITH v0 MATCH (v0)-[v4:ACTED_IN]->(v5:Series) WITH { properties: { screenTime: v4.screenTime }, node: { __resolveType: 'Series', episodes: v5.episodes, title: v5.title } } AS v3 RETURN v3 } WITH collect(v3) AS v1 RETURN { edges: v1 } AS v2 } RETURN v0 { .name, actedInConnection: v2 } AS v0"""); } @Test void projectionsShouldBeSortedToo() { var cypher = """ MATCH (user:User) return user { .title, b: 'B', .*, a: 'A' } AS user """; var statement = CypherParser.parse(cypher, Options.newOptions().createSortedMaps(true).build()); var renderer = Renderer.getRenderer(this.rendererConfig); var normalized = renderer.render(statement); assertThat(normalized).isEqualTo(""" MATCH (v0:User) RETURN v0 { a: 'A', b: 'B', .*, .title } AS v0"""); } @Test void aliasesShouldBeReusedToo() { var q1 = """ MATCH (this:Film:Multimedia) CALL { WITH this MATCH (this_actors:Actor:Person)-[this0:ACTED_IN]->(this) WITH this_actors { .name } AS this_actors RETURN collect(this_actors) AS this_actors } RETURN this { .title, actors: this_actors } AS this """; var q2 = """ MATCH (this:Film:Multimedia) CALL { WITH this MATCH (actor0:Actor:Person)-[actedIn0:ACTED_IN]->(this) WITH actor0 { .name } AS actors RETURN collect(actors) AS actors } RETURN this { .title, actors: actors } AS this """; var parserConfig = Options.newOptions().createSortedMaps(true).alwaysCreateRelationshipsLTR(true).build(); var renderer = Renderer.getRenderer(this.rendererConfig); var s1 = renderer.render(CypherParser.parse(q1, parserConfig)); var s2 = renderer.render(CypherParser.parse(q2, parserConfig)); var expected1 = """ MATCH (v0:Film:Multimedia) CALL { WITH v0 MATCH (v1:Actor:Person)-[v2:ACTED_IN]->(v0) WITH v1 { .name } AS v1 RETURN collect(v1) AS v1 } RETURN v0 { actors: v1, .title } AS v0"""; var expected2 = """ MATCH (v0:Film:Multimedia) CALL { WITH v0 MATCH (v1:Actor:Person)-[v2:ACTED_IN]->(v0) WITH v1 { .name } AS v3 RETURN collect(v3) AS v3 } RETURN v0 { actors: v3, .title } AS v0"""; assertThat(s1).isEqualTo(expected1); assertThat(s2).isEqualTo(expected2); } @Test // GH-1235 void caseParsingAndRenderingShouldWork() { final var node = Cypher.node("Node").named("n"); final var query = Cypher.match(node) .returning(Cypher.caseExpression(node.property("prop")) .when(Cypher.literalOf("A")) .then(Cypher.literalOf(1)) .elseDefault(Cypher.literalOf(2))) .build(); assertThat(query.getCypher()).isEqualTo("MATCH (n:`Node`) RETURN CASE n.prop WHEN 'A' THEN 1 ELSE 2 END"); var renderedParsedAndRendered = CypherParser.parse(query.getCypher()).getCypher(); assertThat(renderedParsedAndRendered) .isEqualTo("MATCH (n:`Node`) RETURN CASE n.prop WHEN 'A' THEN 1 ELSE 2 END"); renderedParsedAndRendered = CypherParser.parse(renderedParsedAndRendered).getCypher(); assertThat(renderedParsedAndRendered) .isEqualTo("MATCH (n:`Node`) RETURN CASE n.prop WHEN 'A' THEN 1 ELSE 2 END"); } @Test // GH-1235 void genericCase() { var cypher = """ MATCH (n:Person) RETURN CASE WHEN n.eyes = 'blue' THEN 1 WHEN n.age < 40 THEN 2 ELSE 3 END AS result, n.eyes, n.age """; assertThat(CypherParser.parse(cypher).getCypher()).isEqualTo( "MATCH (n:`Person`) RETURN CASE WHEN n.eyes = 'blue' THEN 1 WHEN n.age < 40 THEN 2 ELSE 3 END AS result, n.eyes, n.age"); } @ParameterizedTest // GH-1404 @EnumSource(Dialect.class) void shouldRenderWithoutDroppingColons(Dialect dialect) { String cypher = "MATCH (:PurchaseOrder|TollerOrder)-[:PLACED_IN]->(:Site), (po:Purchase_orders)<-[:IS_PURCHASED]-(m:Material)\nRETURN *"; var statement = CypherParser.parse(cypher); Configuration rendererConfig = Configuration.newConfig() .alwaysEscapeNames(false) .withPrettyPrint(true) .withDialect(dialect) .build(); var renderer = Renderer.getRenderer(rendererConfig); String result = renderer.render(statement); assertThat(result).endsWith(cypher); } } ================================================ FILE: neo4j-cypher-dsl-parser/src/test/java/org/neo4j/cypherdsl/parser/PathLengthTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class PathLengthTests { @ParameterizedTest @CsvSource(nullValues = "N/A", value = { "N/A, N/A", "N/A, 5", "5, N/A", "5, 10" }) void length1(String minimum, String maximimum) { PathLength pathLength = PathLength.of(minimum, maximimum); if (minimum == null && maximimum == null) { assertThat(pathLength.isUnbounded()).isTrue(); } if (minimum != null) { assertThat(pathLength.getMinimum()).isEqualTo(Integer.parseInt(minimum)); } if (maximimum != null) { assertThat(pathLength.getMaximum()).isEqualTo(Integer.parseInt(maximimum)); } } } ================================================ FILE: neo4j-cypher-dsl-parser/src/test/java/org/neo4j/cypherdsl/parser/QPPPrimerIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.stream.Stream; import com.opencsv.CSVReaderBuilder; import com.opencsv.exceptions.CsvValidationException; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Renderer; import org.neo4j.harness.Neo4j; import org.neo4j.harness.Neo4jBuilders; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ @TestInstance(TestInstance.Lifecycle.PER_CLASS) class QPPPrimerIT { private static final Renderer RENDERER = Renderer .getRenderer(Configuration.newConfig().alwaysEscapeNames(false).build()); private final Neo4j neo4j = Neo4jBuilders.newInProcessBuilder() .withDisabledServer() .withFixture( """ CREATE (n1:Station {name: 'Denmark Hill'}), (n5:Station {name: 'Battersea Park'}), (n6:Station {name: 'Wandsworth Road'}), (n15:Station {name: 'Clapham High Street'}), (n16:Station {name: 'Peckham Rye'}), (n17:Station {name: 'Brixton'}), (n14:Station {name: 'London Victoria'}), (n18:Station {name: 'Clapham Junction'}), (p10:Stop {departs: time('22:37'), arrives: time('22:36')}), (p0:Stop {departs: time('22:41'), arrives: time('22:41')}), (p2:Stop {departs: time('22:43'), arrives: time('22:43')}), (p17:Stop {arrives: time('22:50'), departs: time('22:50')}), (p18:Stop {arrives: time('22:46'), departs: time('22:46')}), (p19:Stop {departs: time('22:33'), arrives: time('22:31')}), (p21:Stop {arrives: time('22:55')}), (p20:Stop {departs: time('22:44'), arrives: time('22:43')}), (p22:Stop {arrives: time('22:55')}), (p23:Stop {arrives: time('22:48')}), (n15)-[:LINK {distance: 1.96}]->(n1)-[:LINK {distance: 0.86}]->(n16), (n15)-[:LINK {distance: 0.39}]->(n6)<-[:LINK {distance: 0.7}]-(n5)-[:LINK {distance: 1.24}]->(n14), (n5)-[:LINK {distance: 1.45}]->(n18), (n14)<-[:LINK {distance: 3.18}]-(n17)-[:LINK {distance: 1.11}]->(n1), (p2)-[:CALLS_AT]->(n6), (p17)-[:CALLS_AT]->(n5), (p19)-[:CALLS_AT]->(n16), (p22)-[:CALLS_AT]->(n14), (p18)-[:CALLS_AT]->(n18), (p0)-[:CALLS_AT]->(n15), (p23)-[:CALLS_AT]->(n5), (p20)-[:CALLS_AT]->(n1), (p21)-[:CALLS_AT]->(n14), (p10)-[:CALLS_AT]->(n1), (p19)-[:NEXT]->(p10)-[:NEXT]->(p0)-[:NEXT]->(p2)-[:NEXT]->(p23), (p22)<-[:NEXT]-(p17)<-[:NEXT]-(p18), (p21)<-[:NEXT]-(p20) """) .build(); static Stream primerStatementsShouldBeParsedAndProduceCorrectResults() throws CsvValidationException, IOException { List result = new ArrayList<>(); try (var csvReader = new CSVReaderBuilder(new InputStreamReader( Objects.requireNonNull(CypherChallengeTests.class.getResourceAsStream("/qpp-primer.csv")))) .withSkipLines(1) .build()) { String[] nextRecord; while ((nextRecord = csvReader.readNext()) != null) { result.add(Arguments.of(nextRecord[0], nextRecord[1].trim().replaceAll("\\s{2,}", " "), Integer.parseInt(nextRecord[2]), Boolean.parseBoolean(nextRecord[3]))); } } return result.stream(); } @AfterAll void shutdownNeo4j() { this.neo4j.close(); } @ParameterizedTest @MethodSource void primerStatementsShouldBeParsedAndProduceCorrectResults(String in, String expected, int expectedNumResults, boolean isCountQuery) { var cypher = RENDERER.render(CypherParser.parse(in)); assertThat(cypher).isEqualTo(expected); assertThat(RENDERER.render(CypherParser.parse(cypher))).isEqualTo(cypher); try (var tx = this.neo4j.defaultDatabaseService().beginTx(); var result = tx.execute(cypher)) { if (isCountQuery) { var cnt = (long) result.next().get(result.columns().get(0)); assertThat(cnt).isEqualTo(expectedNumResults); } else { assertThat(result.stream().count()).isEqualTo(expectedNumResults); } } } } ================================================ FILE: neo4j-cypher-dsl-parser/src/test/java/org/neo4j/cypherdsl/parser/RewriteTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.util.Collection; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.Parameter; import org.neo4j.cypherdsl.core.SymbolicName; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class RewriteTests { // tag::enforcing-labels-function[] final BiFunction, Collection> makeSureALabelIsPresent = (e, c) -> { var finalLabels = new LinkedHashSet<>(c); switch (e) { // <.> case ON_NODE_PATTERN: finalLabels.add("ForcedLabel"); return finalLabels; case ON_SET: finalLabels.add("Modified"); return finalLabels; case ON_REMOVE: finalLabels.remove("ForcedLabel"); return finalLabels; default: return c; } }; // end::enforcing-labels-function[] @Test @SuppressWarnings("squid:S5976") // About making it a parameterized test. Used in the // docs. void shouldRewriteLabelsOnParseNode() { // tag::enforcing-on-parse[] var options = Options.newOptions().withLabelFilter(this.makeSureALabelIsPresent).build(); var statement = CypherParser.parseStatement("MATCH (n:Movie) RETURN n", options).getCypher(); assertThat(statement).isEqualTo("MATCH (n:`Movie`:`ForcedLabel`) RETURN n"); // end::enforcing-on-parse[] } @Test @SuppressWarnings("squid:S5976") // Used in the docs void shouldRewriteLabelsOnSetLabels() { // tag::enforcing-on-set[] var options = Options.newOptions().withLabelFilter(this.makeSureALabelIsPresent).build(); var statement = CypherParser.parseStatement("MATCH (n:Movie) SET n:`Comedy` RETURN n", options).getCypher(); assertThat(statement).isEqualTo("MATCH (n:`Movie`:`ForcedLabel`) SET n:`Comedy`:`Modified` RETURN n"); // end::enforcing-on-set[] } @Test @SuppressWarnings("squid:S5976") // Used in the docs void shouldRewriteLabelsOnRemoveLabels() { // tag::enforcing-on-remove[] var options = Options.newOptions().withLabelFilter(this.makeSureALabelIsPresent).build(); var statement = CypherParser.parseStatement("MATCH (n:Movie) REMOVE n:`Comedy`:`ForcedLabel` RETURN n", options) .getCypher(); assertThat(statement).isEqualTo("MATCH (n:`Movie`:`ForcedLabel`) REMOVE n:`Comedy` RETURN n"); // end::enforcing-on-remove[] } @Test void shouldRewriteTypes() { var statement = CypherParser .parseStatement("MATCH (p:Person) -[:HAT_GESPIELT_IN] -> (n:Movie) RETURN n", Options.newOptions() .withTypeFilter( (e, t) -> ((t.size() == 1) && t.contains("HAT_GESPIELT_IN")) ? Set.of("ACTED_IN") : t) .build()) .getCypher(); assertThat(statement).isEqualTo("MATCH (p:`Person`)-[:`ACTED_IN`]->(n:`Movie`) RETURN n"); } @Test void shouldRewriteVariables() { var p = Cypher.node("Person").named("p"); var parserOptions = Options.newOptions() .withCallback(ExpressionCreatedEventType.ON_NEW_VARIABLE, Expression.class, e -> p.property(((SymbolicName) e).getValue())) .build(); var statement = Cypher.match(p) .where(CypherParser.parseExpression("name = 'Foobar'", parserOptions).asCondition()) .returning(p) .build() .getCypher(); assertThat(statement).isEqualTo("MATCH (p:`Person`) WHERE p.name = 'Foobar' RETURN p"); } @Test void shouldRewriteParameters() { var counter = new AtomicInteger(1); var parserOptions = Options.newOptions() .withCallback(ExpressionCreatedEventType.ON_NEW_PARAMETER, Expression.class, e -> Cypher.parameter(String.format("%d", counter.getAndIncrement()))) .build(); var statement = CypherParser.parse( "CREATE (p:Person {name: $name, height: $height, birthDate: $birthDate, templateEmail: 'Welcome $name!'})", parserOptions) .getCypher(); assertThat(statement) .isEqualTo("CREATE (p:`Person` {name: $1, height: $2, birthDate: $3, templateEmail: 'Welcome $name!'})"); } @Test void shouldRewriteParameters2() { var mapping = Map.of("param1", "foo", "x", "y"); var parserOptions = Options.newOptions() .withCallback(ExpressionCreatedEventType.ON_NEW_PARAMETER, Expression.class, e -> { if (e instanceof Parameter p) { return Cypher.parameter(mapping.getOrDefault(p.getName(), p.getName()), p.getValue()); } return e; }) .withCallback(ExpressionCreatedEventType.ON_NEW_VARIABLE, Expression.class, e -> { if (e instanceof SymbolicName s) { return Cypher.name(mapping.getOrDefault(s.getValue(), s.getValue())); } return e; }) .build(); var statement = CypherParser.parse("Match (x:Movie) where x.title = $param1 RETURN x", parserOptions) .getCypher(); assertThat(statement).isEqualTo("MATCH (y:`Movie`) WHERE y.title = $foo RETURN y"); } } ================================================ FILE: neo4j-cypher-dsl-parser/src/test/java/org/neo4j/cypherdsl/parser/StatementsTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.util.ArrayList; import org.junit.jupiter.api.Test; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.Statement; import static org.assertj.core.api.Assertions.assertThat; class StatementsTests { @Test void shouldSupportNullIst() { var statements = new Statements(null); assertThat(statements.value()).isEmpty(); } @Test void shouldCopy() { var hlp = new ArrayList(); hlp.add(Cypher.returning(Cypher.literalTrue()).build()); var statements = new Statements(hlp); hlp.clear(); assertThat(statements.value()).hasSize(1); } } ================================================ FILE: neo4j-cypher-dsl-parser/src/test/java/org/neo4j/cypherdsl/parser/TckTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import org.asciidoctor.Asciidoctor; import org.asciidoctor.Options; import org.asciidoctor.ast.Block; import org.asciidoctor.ast.Document; import org.asciidoctor.extension.Treeprocessor; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DynamicContainer; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestInstance; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.Statement; import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Dialect; import org.neo4j.cypherdsl.core.renderer.Renderer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * This tests reads and parses the README.adoc and verifies the defined content. * * @author Michael J. Simons */ @TestInstance(TestInstance.Lifecycle.PER_CLASS) class TckTests { private final Map testData = new HashMap<>(); @BeforeAll void init() throws IOException, URISyntaxException { var asciidoctor = Asciidoctor.Factory.create(); var collector = new TestDataExtractor(); asciidoctor.javaExtensionRegistry().treeprocessor(collector); var content = Files.readString(Paths.get(getClass().getResource("/README.adoc").toURI())); asciidoctor.load(content, Options.builder().build()); this.testData.putAll(collector.content); } @TestFactory Stream tck() { return this.testData.entrySet().stream().map(entry -> { try { Stream entries; var type = entry.getKey().split("-")[0]; var method = this.getClass() .getDeclaredMethod(type + "ShouldWork", Renderer.class, String.class, String.class); var renderer = Renderer.getRenderer(Configuration.newConfig() .withDialect(Dialect.NEO4J_4) .alwaysEscapeNames(entry.getValue().alwaysEscape) .build()); entries = entry.getValue() .asArguments() .map(arguments -> DynamicTest.dynamicTest(arguments.getKey(), () -> { try { method.invoke(this, renderer, arguments.getKey(), arguments.getValue()); } catch (InvocationTargetException ex) { throw ex.getCause(); } })); var name = type; if (!entry.getValue().alwaysEscape) { name += " (Only escaping when necessary)"; } return DynamicContainer.dynamicContainer(name, entries); } catch (NoSuchMethodException ex) { throw new RuntimeException(ex); } }); } @SuppressWarnings("unused") void nodesShouldWork(Renderer renderer, String input, String expected) { var node = CypherParser.parseNode(input); assertThat(renderer.render(Cypher.match(node).returning(Cypher.asterisk()).build())) .isEqualTo(String.format("MATCH %s RETURN *", expected)); } @SuppressWarnings("unused") void expressionsShouldWork(Renderer renderer, String input, String expected) { var e = CypherParser.parseExpression(input); assertThat(renderer.render(Cypher.returning(e).build())).isEqualTo(String.format("RETURN %s", expected)); } @SuppressWarnings("unused") void clausesShouldWork(Renderer renderer, String input, String expected) { var cypher = renderer.render(Statement.of(List.of(CypherParser.parseClause(input)))); Assertions.assertThat(cypher).isEqualTo(expected); } @SuppressWarnings("unused") void statementsShouldWork(Renderer renderer, String input, String expected) { var cypher = renderer.render(CypherParser.parseStatement(input)); assertThat(cypher).isEqualTo(expected); } @SuppressWarnings("unused") void qppShouldWork(Renderer renderer, String input, String expected) { var cypher = renderer.render(CypherParser.parseStatement(input)); assertThat(cypher).isEqualTo(expected.replaceAll("\\s{2,}|\n", " ")); } @SuppressWarnings("unused") void unsupportedShouldWork(Renderer renderer, String input, String expected) { assertThatExceptionOfType(UnsupportedCypherException.class).isThrownBy(() -> CypherParser.parse(input)) .withMessageStartingWith("You used one Cypher construct not yet supported by the Cypher-DSL") .withMessageContaining(input); } private static class TestDataExtractor extends Treeprocessor { private final Map content = new HashMap<>(); TestDataExtractor() { super(new HashMap<>()); // Must be mutable } @Override public Document process(Document document) { document.findBy(Map.of("context", ":listing", "style", "source")) .stream() .map(Block.class::cast) .filter(b -> "cypher".equals(b.getAttribute("language"))) .forEach(block -> { var id = block.getId().split("-"); var type = id[0]; var alwaysEscape = !block.hasAttribute("alwaysEscape") || Boolean.parseBoolean((String) block.getAttribute("alwaysEscape")); type = type + "-" + alwaysEscape; var test = this.content.computeIfAbsent(type, key -> new TestData(alwaysEscape)); var separated = block.getAttribute("separated"); var lines = Boolean.parseBoolean((String) separated) ? Arrays.asList(block.getSource().split(";")) : block.getLines(); switch (id[1]) { case "input" -> test.input.addAll(lines); case "output" -> test.expected.addAll(lines); } }); return document; } } private static class TestData { private final boolean alwaysEscape; private final List input = new ArrayList<>(); private final List expected = new ArrayList<>(); TestData(boolean alwaysEscape) { this.alwaysEscape = alwaysEscape; } Stream> asArguments() { if (this.input.size() == this.expected.size()) { var result = Stream.>builder(); for (int i = 0; i < this.input.size(); i++) { result.add(new AbstractMap.SimpleImmutableEntry<>(this.input.get(i).trim(), this.expected.get(i).trim())); } return result.build(); } else { return this.input.stream().collect(Collectors.toMap(Function.identity(), k -> "")).entrySet().stream(); } } } } ================================================ FILE: neo4j-cypher-dsl-parser/src/test/java/org/neo4j/cypherdsl/parser/UnsupportedCypherExceptionTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.parser; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ class UnsupportedCypherExceptionTests { @Test void causeShouldBePresent() { var cause = new UnsupportedOperationException("booms"); var unsupportedCypherException = new UnsupportedCypherException("This is invalid cypher", cause); assertThat(unsupportedCypherException.getCause()).isEqualTo(cause); } @Test void inputShouldBeRetrievable() { var cause = new UnsupportedOperationException("booms"); var input = "This is invalid cypher"; var unsupportedCypherException = new UnsupportedCypherException(input, cause); assertThat(unsupportedCypherException.getInput()).isEqualTo(input); } @Test void messageShouldBeFormatted() { var cause = new UnsupportedOperationException("booms"); var input = "This is invalid cypher"; var unsupportedCypherException = new UnsupportedCypherException(input, cause); assertThat(unsupportedCypherException.getMessage()).isEqualTo( """ You used one Cypher construct not yet supported by the Cypher-DSL: \tThis is invalid cypher Feel free to open an issue so that we might add support for it at https://github.com/neo4j-contrib/cypher-dsl/issues/new"""); } } ================================================ FILE: neo4j-cypher-dsl-parser/src/test/resources/cypher-challenge.csv ================================================ statement,schema,correct_query "MATCH (p:Person)-[:KNOWS]->(:Person) RETURN p, count(*) AS count","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (p:Person)-[:KNOWS]->(:Person) RETURN p, count(*) AS count" "MATCH (p:Person)<-[:KNOWS]-(:Person) RETURN p, count(*) AS count","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (p:Person)<-[:KNOWS]-(:Person) RETURN p, count(*) AS count" "MATCH (p:Person {id:""Foo""})<-[:WORKS_AT]-(o:Organization) RETURN o.name AS name","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (p:Person {id:""Foo""})-[:WORKS_AT]->(o:Organization) RETURN o.name AS name" "MATCH (o:Organization)-[:WORKS_AT]->(p:Person {id:""Foo""}) RETURN o.name AS name","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (o:Organization)<-[:WORKS_AT]-(p:Person {id:""Foo""}) RETURN o.name AS name" "MATCH (o:Organization {name:""Bar""})-[:WORKS_AT]->(p:Person {id:""Foo""}) RETURN o.name AS name","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (o:Organization {name:""Bar""})<-[:WORKS_AT]-(p:Person {id:""Foo""}) RETURN o.name AS name" "MATCH (o:Organization)-[:WORKS_AT]->(p:Person {id:""Foo""})-[:WORKS_AT]->(o1:Organization) RETURN o.name AS name","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (o:Organization)<-[:WORKS_AT]-(p:Person {id:""Foo""})-[:WORKS_AT]->(o1:Organization) RETURN o.name AS name" "MATCH (o:`Organization` {name:""Foo""})-[:WORKS_AT]->(p:Person {id:""Foo""})-[:WORKS_AT]-(o1:Organization {name:""b""}) WHERE id(o) > id(o1) RETURN o.name AS name","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (o:`Organization` {name:""Foo""})<-[:WORKS_AT]-(p:Person {id:""Foo""})-[:WORKS_AT]-(o1:Organization {name:""b""}) WHERE id(o) > id(o1) RETURN o.name AS name" "MATCH (p:Person) RETURN p, [(p)-[:WORKS_AT]->(o:Organization) | o.name] AS op","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (p:Person) RETURN p, [(p)-[:WORKS_AT]->(o:Organization) | o.name] AS op" "MATCH (p:Person) RETURN p, [(p)<-[:WORKS_AT]-(o:Organization) | o.name] AS op","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (p:Person) RETURN p, [(p)-[:WORKS_AT]->(o:Organization) | o.name] AS op" "MATCH (p:Person {name:""John""}) MATCH (p)-[:WORKS_AT]->(:Organization) RETURN p, count(*)","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (p:Person {name:""John""}) MATCH (p)-[:WORKS_AT]->(:Organization) RETURN p, count(*)" "MATCH (p:Person) MATCH (p)<-[:WORKS_AT]-(:Organization) RETURN p, count(*)","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (p:Person) MATCH (p)-[:WORKS_AT]->(:Organization) RETURN p, count(*)" "MATCH (p:Person), (p)<-[:WORKS_AT]-(:Organization) RETURN p, count(*)","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (p:Person), (p)-[:WORKS_AT]->(:Organization) RETURN p, count(*)" "MATCH (o:Organization)-[:WORKS_AT]->(p:Person {id:""Foo""})-[:WORKS_AT]->(o1:Organization) WHERE id(o) < id(o1) RETURN o.name AS name","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (o:Organization)<-[:WORKS_AT]-(p:Person {id:""Foo""})-[:WORKS_AT]->(o1:Organization) WHERE id(o) < id(o1) RETURN o.name AS name" "MATCH (o:Organization)-[:WORKS_AT]-(p:Person {id:""Foo""})-[:WORKS_AT]-(o1:Organization) WHERE id(o) < id(o1) RETURN o.name AS name","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (o:Organization)-[:WORKS_AT]-(p:Person {id:""Foo""})-[:WORKS_AT]-(o1:Organization) WHERE id(o) < id(o1) RETURN o.name AS name" "MATCH (p:Person)--(:Organization)--(p1:Person) RETURN p1","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (p:Person)--(:Organization)--(p1:Person) RETURN p1" "MATCH (p:Person)<--(:Organization)--(p1:Person) RETURN p1","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (p:Person)-->(:Organization)--(p1:Person) RETURN p1" "MATCH (p:Person)<-[r]-(:Organization)--(p1:Person) RETURN p1, r","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (p:Person)-[r]->(:Organization)--(p1:Person) RETURN p1, r" "MATCH (person:Person) CALL { WITH person MATCH (person)-->(o:Organization) RETURN o LIMIT 3 } RETURN person, o","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (person:Person) CALL { WITH person MATCH (person)-->(o:Organization) RETURN o LIMIT 3 } RETURN person, o" "MATCH (person:Person) CALL { WITH person MATCH (person)<--(o:Organization) RETURN o LIMIT 3 } RETURN person, o","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (person:Person) CALL { WITH person MATCH (person)-->(o:Organization) RETURN o LIMIT 3 } RETURN person, o" "MATCH (person:Person) CALL { WITH person MATCH (person)-[:KNOWS]->(o:Organization) RETURN o LIMIT 3 } RETURN person, o","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)", "MATCH (person:Person) CALL { WITH person MATCH (person)<-[:WORKS_AT|INVESTOR]-(o:Organization) RETURN o LIMIT 3 } RETURN person, o","(Person, KNOWS, Person), (Person, WORKS_AT, Organization), (Person, INVESTOR, Organization)","MATCH (person:Person) CALL { WITH person MATCH (person)-[:WORKS_AT|INVESTOR]->(o:Organization) RETURN o LIMIT 3 } RETURN person, o" "MATCH (p:Person) WHERE EXISTS { (p)<-[:KNOWS]-()} RETURN p","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (p:Person) WHERE EXISTS { (p)<-[:KNOWS]-()} RETURN p" "MATCH (p:Person) WHERE EXISTS { (p)-[:KNOWS]->()} RETURN p","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (p:Person) WHERE EXISTS { (p)-[:KNOWS]->()} RETURN p" "MATCH (p:Person) WHERE EXISTS { (p)<-[:WORKS_AT]-()} RETURN p","(Person, KNOWS, Person), (Person, WORKS_AT, Organization)","MATCH (p:Person) WHERE EXISTS { (p)-[:WORKS_AT]->()} RETURN p" "MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE p.name = 'Tom Hanks' AND m.year = 2013 RETURN m.title","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE p.name = 'Tom Hanks' AND m.year = 2013 RETURN m.title" "MATCH (p:Person)-[:ACTED_IN]-(m:Movie) WHERE p.name = 'Tom Hanks' AND m.year = 2013 RETURN m.title","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:ACTED_IN]-(m:Movie) WHERE p.name = 'Tom Hanks' AND m.year = 2013 RETURN m.title" "MATCH (p:Person)<-[:ACTED_IN]-(m:Movie) WHERE p.name = 'Tom Hanks' AND m.year = 2013 RETURN m.title","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE p.name = 'Tom Hanks' AND m.year = 2013 RETURN m.title" "MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE p.name <> 'Tom Hanks' AND m.title = 'Captain Phillips' RETURN p.name","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE p.name <> 'Tom Hanks' AND m.title = 'Captain Phillips' RETURN p.name" "MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE p.name <> 'Tom Hanks' AND m.title = 'Captain Phillips' AND m.year > 2019 AND m.year < 2030 RETURN p.name","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE p.name <> 'Tom Hanks' AND m.title = 'Captain Phillips' AND m.year > 2019 AND m.year < 2030 RETURN p.name" "MATCH (p:Person)<-[:ACTED_IN]-(m:Movie) WHERE p.name <> 'Tom Hanks' AND m.title = 'Captain Phillips' AND m.year > 2019 AND m.year < 2030 RETURN p.name","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE p.name <> 'Tom Hanks' AND m.title = 'Captain Phillips' AND m.year > 2019 AND m.year < 2030 RETURN p.name" "MATCH (p:Person)<-[:FOLLOWS]-(m:Movie) WHERE p.name <> 'Tom Hanks' AND m.title = 'Captain Phillips' AND m.year > 2019 AND m.year < 2030 RETURN p.name","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)", "MATCH (p:Person)-[:`ACTED_IN`]->(m:Movie)<-[:DIRECTED]-(p) WHERE p.born.year > 1960 RETURN p.name, p.born, labels(p), m.title","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:`ACTED_IN`]->(m:Movie)<-[:DIRECTED]-(p) WHERE p.born.year > 1960 RETURN p.name, p.born, labels(p), m.title" "MATCH (p:Person)-[:ACTED_IN]-(m:Movie)<-[:DIRECTED]-(p) WHERE p.born.year > 1960 RETURN p.name, p.born, labels(p), m.title","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:ACTED_IN]-(m:Movie)<-[:DIRECTED]-(p) WHERE p.born.year > 1960 RETURN p.name, p.born, labels(p), m.title" "MATCH (p:Person)-[:ACTED_IN]-(m:Movie)-[:DIRECTED]->(p) WHERE p.born.year > 1960 RETURN p.name, p.born, labels(p), m.title","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:ACTED_IN]-(m:Movie)<-[:DIRECTED]-(p) WHERE p.born.year > 1960 RETURN p.name, p.born, labels(p), m.title" "MATCH (p:`Person`)<-[r]-(m:Movie) WHERE p.name = 'Tom Hanks' RETURN m.title AS movie, type(r) AS relationshipType","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:`Person`)-[r]->(m:Movie) WHERE p.name = 'Tom Hanks' RETURN m.title AS movie, type(r) AS relationshipType" "MATCH (d:Person)-[:DIRECTED]->(m:Movie)-[:IN_GENRE]->(g:Genre) WHERE m.year = 2000 AND g.name = ""Horror"" RETURN d.name","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (d:Person)-[:DIRECTED]->(m:Movie)-[:IN_GENRE]->(g:Genre) WHERE m.year = 2000 AND g.name = ""Horror"" RETURN d.name" "MATCH (d:Person)-[:DIRECTED]->(m:Movie)<--(g:Genre) WHERE m.year = 2000 AND g.name = ""Horror"" RETURN d.name","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (d:Person)-[:DIRECTED]->(m:Movie)-->(g:Genre) WHERE m.year = 2000 AND g.name = ""Horror"" RETURN d.name" "MATCH (d:Person)<--(m:Movie)<--(g:Genre) WHERE m.year = 2000 AND g.name = ""Horror"" RETURN d.name","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (d:Person)-->(m:Movie)-->(g:Genre) WHERE m.year = 2000 AND g.name = ""Horror"" RETURN d.name" "MATCH (d:Person)-[:DIRECTED]-(m:Movie)<-[:IN_GENRE]-(g:Genre) WHERE m.year = 2000 AND g.name = ""Horror"" RETURN d.name","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (d:Person)-[:DIRECTED]-(m:Movie)-[:IN_GENRE]->(g:Genre) WHERE m.year = 2000 AND g.name = ""Horror"" RETURN d.name" "MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE p.name = 'Tom Hanks' AND exists {(p)-[:DIRECTED]->(m)} RETURN p.name, labels(p), m.title","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE p.name = 'Tom Hanks' AND exists {(p)-[:DIRECTED]->(m)} RETURN p.name, labels(p), m.title" "MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE p.name = 'Tom Hanks' AND exists {(p)<-[:DIRECTED]-(m)} RETURN p.name, labels(p), m.title","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE p.name = 'Tom Hanks' AND exists {(p)-[:DIRECTED]->(m)} RETURN p.name, labels(p), m.title" "MATCH (a:Person)-[:ACTED_IN]->(m:Movie) WHERE m.year > 2000 MATCH (m)<-[:DIRECTED]-(d:Person) RETURN a.name, m.title, d.name","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (a:Person)-[:ACTED_IN]->(m:Movie) WHERE m.year > 2000 MATCH (m)<-[:DIRECTED]-(d:Person) RETURN a.name, m.title, d.name" "MATCH (a:Person)-[:ACTED_IN]-(m:Movie) WHERE m.year > 2000 MATCH (m)-[:DIRECTED]->(d:Person) RETURN a.name, m.title, d.name","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (a:Person)-[:ACTED_IN]-(m:Movie) WHERE m.year > 2000 MATCH (m)<-[:DIRECTED]-(d:Person) RETURN a.name, m.title, d.name" "MATCH (m:Movie) WHERE m.title = ""Kiss Me Deadly"" MATCH (m)-[:IN_GENRE]-(g:Genre)-[:IN_GENRE]->(rec:Movie) MATCH (m)-[:ACTED_IN]->(a:Person)-[:ACTED_IN]-(rec) RETURN rec.title, a.name","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (m:Movie) WHERE m.title = ""Kiss Me Deadly"" MATCH (m)-[:IN_GENRE]-(g:Genre)<-[:IN_GENRE]-(rec:Movie) MATCH (m)<-[:ACTED_IN]-(a:Person)-[:ACTED_IN]-(rec) RETURN rec.title, a.name" "MATCH (p:Person)-[:ACTED_IN]->(m:Movie), (coActors:Person)-[:ACTED_IN]->(m) WHERE p.name = 'Eminem' RETURN m.title AS movie ,collect(coActors.name) AS coActors","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:ACTED_IN]->(m:Movie), (coActors:Person)-[:ACTED_IN]->(m) WHERE p.name = 'Eminem' RETURN m.title AS movie ,collect(coActors.name) AS coActors" "MATCH (p:Person)<-[:ACTED_IN]-(m:Movie), (coActors:Person)-[:ACTED_IN]->(m) WHERE p.name = 'Eminem' RETURN m.title AS movie ,collect(coActors.name) AS coActors","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:ACTED_IN]->(m:Movie), (coActors:Person)-[:ACTED_IN]->(m) WHERE p.name = 'Eminem' RETURN m.title AS movie ,collect(coActors.name) AS coActors" "MATCH p = ((person:Person)<-[]-(movie:Movie)) WHERE person.name = 'Walt Disney' RETURN p","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH p = ((person:Person)-[]->(movie:Movie)) WHERE person.name = 'Walt Disney' RETURN p" "MATCH p = ((person:Person)<-[:DIRECTED]-(movie:Movie)) WHERE person.name = 'Walt Disney' RETURN p","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH p = ((person:Person)-[:DIRECTED]->(movie:Movie)) WHERE person.name = 'Walt Disney' RETURN p" "MATCH p = shortestPath((p1:Person)-[*]-(p2:Person)) WHERE p1.name = ""Eminem"" AND p2.name = ""Charlton Heston"" RETURN p","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH p = shortestPath((p1:Person)-[*]-(p2:Person)) WHERE p1.name = ""Eminem"" AND p2.name = ""Charlton Heston"" RETURN p" "MATCH p = ((person:Person)-[:DIRECTED*]->(:Person)) WHERE person.name = 'Walt Disney' RETURN p","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH p = ((person:Person)-[:DIRECTED*]->(:Person)) WHERE person.name = 'Walt Disney' RETURN p" "MATCH p = ((person:Person)-[:DIRECTED*1..4]->(:Person)) WHERE person.name = 'Walt Disney' RETURN p","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH p = ((person:Person)-[:DIRECTED*1..4]->(:Person)) WHERE person.name = 'Walt Disney' RETURN p" "MATCH (p:Person {name: 'Eminem'})-[:ACTED_IN*2]-(others:Person) RETURN others.name","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person {name: 'Eminem'})-[:ACTED_IN*2]-(others:Person) RETURN others.name" "MATCH (u:User {name: ""Misty Williams""})-[r:RATED]->(:Movie) WITH u, avg(r.rating) AS average MATCH (u)-[r:RATED]->(m:Movie) WHERE r.rating > average RETURN average , m.title AS movie, r.rating as rating ORDER BY rating DESC","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie), (User, RATED, Movie)","MATCH (u:User {name: ""Misty Williams""})-[r:RATED]->(:Movie) WITH u, avg(r.rating) AS average MATCH (u)-[r:RATED]->(m:Movie) WHERE r.rating > average RETURN average , m.title AS movie, r.rating as rating ORDER BY rating DESC" "MATCH (u:User {name: ""Misty Williams""})-[r:RATED]->(:Movie) WITH u, avg(r.rating) AS average MATCH (u)<-[r:RATED]-(m:Movie) WHERE r.rating > average RETURN average , m.title AS movie, r.rating as rating ORDER BY rating DESC","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie), (User, RATED, Movie)","MATCH (u:User {name: ""Misty Williams""})-[r:RATED]->(:Movie) WITH u, avg(r.rating) AS average MATCH (u)-[r:RATED]->(m:Movie) WHERE r.rating > average RETURN average , m.title AS movie, r.rating as rating ORDER BY rating DESC" "MATCH (p:`Person`) WHERE p.born.year = 1980 WITH p LIMIT 3 MATCH (p)<-[:ACTED_IN]-(m:Movie) WITH p, collect(m.title) AS movies RETURN p.name AS actor, movies","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:`Person`) WHERE p.born.year = 1980 WITH p LIMIT 3 MATCH (p)-[:ACTED_IN]->(m:Movie) WITH p, collect(m.title) AS movies RETURN p.name AS actor, movies" "MATCH (p:Person) WHERE p.born.year = 1980 WITH p LIMIT 3 MATCH (p)-[:ACTED_IN]->(m:Movie)<-[:IN_GENRE]-(g) WITH p, collect(DISTINCT g.name) AS genres RETURN p.name AS actor, genres","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person) WHERE p.born.year = 1980 WITH p LIMIT 3 MATCH (p)-[:ACTED_IN]->(m:Movie)-[:IN_GENRE]->(g) WITH p, collect(DISTINCT g.name) AS genres RETURN p.name AS actor, genres" "CALL { MATCH (m:Movie) WHERE m.year = 2000 RETURN m ORDER BY m.imdbRating DESC LIMIT 10 } MATCH (:User)-[r:RATED]->(m) RETURN m.title, avg(r.rating)","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (User, RATED, Movie)","CALL { MATCH (m:Movie) WHERE m.year = 2000 RETURN m ORDER BY m.imdbRating DESC LIMIT 10 } MATCH (:User)-[r:RATED]->(m) RETURN m.title, avg(r.rating)" "CALL { MATCH (m:Movie) WHERE m.year = 2000 RETURN m ORDER BY m.imdbRating DESC LIMIT 10 } MATCH (:User)<-[r:RATED]-(m) RETURN m.title, avg(r.rating)","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (User, RATED, Movie)","CALL { MATCH (m:Movie) WHERE m.year = 2000 RETURN m ORDER BY m.imdbRating DESC LIMIT 10 } MATCH (:User)-[r:RATED]->(m) RETURN m.title, avg(r.rating)" "MATCH (m:Movie) CALL { WITH m MATCH (m)-[r:RATED]->(u) WHERE r.rating = 5 RETURN count(u) AS numReviews } RETURN m.title, numReviews ORDER BY numReviews DESC","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (m:Movie) CALL { WITH m MATCH (m)<-[r:RATED]-(u) WHERE r.rating = 5 RETURN count(u) AS numReviews } RETURN m.title, numReviews ORDER BY numReviews DESC" "MATCH (p:Person) WITH p LIMIT 100 CALL { WITH p OPTIONAL MATCH (p)<-[:ACTED_IN]-(m) RETURN m.title + "": "" + ""Actor"" AS work UNION WITH p OPTIONAL MATCH (p)-[:DIRECTED]->(m:Movie) RETURN m.title+ "": "" + ""Director"" AS work } RETURN p.name, collect(work)","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person) WITH p LIMIT 100 CALL { WITH p OPTIONAL MATCH (p)-[:ACTED_IN]->(m) RETURN m.title + "": "" + ""Actor"" AS work UNION WITH p OPTIONAL MATCH (p)-[:DIRECTED]->(m:Movie) RETURN m.title+ "": "" + ""Director"" AS work } RETURN p.name, collect(work)" "MATCH (p:Person)<-[:ACTED_IN {role:""Neo""}]-(m:Movie) WHERE p.name = $actorName AND m.title = $movieName RETURN p, m","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:ACTED_IN {role:""Neo""}]->(m:Movie) WHERE p.name = $actorName AND m.title = $movieName RETURN p, m" "MATCH (p:Person)<-[:ACTED_IN {role:""Neo""}]-(m) WHERE p.name = $actorName AND m.title = $movieName RETURN p","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:ACTED_IN {role:""Neo""}]->(m) WHERE p.name = $actorName AND m.title = $movieName RETURN p" "MATCH (p:Person)-[:ACTED_IN {role:""Neo""}]->(m:Movie) WHERE p.name = $actorName AND m.title = $movieName RETURN p, m","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:ACTED_IN {role:""Neo""}]->(m:Movie) WHERE p.name = $actorName AND m.title = $movieName RETURN p, m" "MATCH (wallstreet:Movie {title: 'Wall Street'})-[:ACTED_IN {role:""Foo""}]->(actor) RETURN actor.name","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (wallstreet:Movie {title: 'Wall Street'})<-[:ACTED_IN {role:""Foo""}]-(actor) RETURN actor.name" "MATCH (p:Person)<-[:`ACTED_IN` {role:""Neo""}]-(m:Movie) WHERE p.name = $actorName AND m.title = $movieName RETURN p, m","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:`ACTED_IN` {role:""Neo""}]->(m:Movie) WHERE p.name = $actorName AND m.title = $movieName RETURN p, m" "MATCH (p:`Person`)<-[:`ACTED_IN` {role:""Neo""}]-(m:Movie) WHERE p.name = $actorName AND m.title = $movieName RETURN p, m","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:`Person`)-[:`ACTED_IN` {role:""Neo""}]->(m:Movie) WHERE p.name = $actorName AND m.title = $movieName RETURN p, m" "MATCH (p:`Person`)<-[:`ACTED_IN` {role:""Neo""}]-(m) WHERE p.name = $actorName AND m.title = $movieName RETURN p, m","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:`Person`)-[:`ACTED_IN` {role:""Neo""}]->(m) WHERE p.name = $actorName AND m.title = $movieName RETURN p, m" "MATCH (p:Person)<-[:!DIRECTED]-(:Movie) RETURN p, count(*)","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:!DIRECTED]->(:Movie) RETURN p, count(*)" "MATCH (p:Person)<-[:`ACTED_IN`|`DIRECTED`]-(m:Movie) WHERE p.name = $actorName AND m.title = $movieName RETURN p, m","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie)","MATCH (p:Person)-[:`ACTED_IN`|`DIRECTED`]->(m:Movie) WHERE p.name = $actorName AND m.title = $movieName RETURN p, m" "MATCH (a:Person:Actor)-[:ACTED_IN]->(:Movie) RETURN a, count(*)","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie), (Actor, ACTED_IN, Movie)","MATCH (a:Person:Actor)-[:ACTED_IN]->(:Movie) RETURN a, count(*)" "MATCH (a:Person:Actor)<-[:ACTED_IN]-(:Movie) RETURN a, count(*)","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie), (Actor, ACTED_IN, Movie)","MATCH (a:Person:Actor)-[:ACTED_IN]->(:Movie) RETURN a, count(*)" "MATCH (a:Person:Actor)<-[:ACTED_IN]-() RETURN a, count(*)","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie), (Actor, ACTED_IN, Movie)","MATCH (a:Person:Actor)-[:ACTED_IN]->() RETURN a, count(*)" "MATCH (a:Person:Actor) RETURN a, [(a)<-[:`ACTED_IN`]-(m) | m.title] AS movies","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie), (Actor, ACTED_IN, Movie)","MATCH (a:Person:Actor) RETURN a, [(a)-[:`ACTED_IN`]->(m) | m.title] AS movies" "MATCH (a:Person:Actor) RETURN a, [(a)-[:`ACTED_IN`]->(m) | m.title] AS movies","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie), (Actor, ACTED_IN, Movie)","MATCH (a:Person:Actor) RETURN a, [(a)-[:`ACTED_IN`]->(m) | m.title] AS movies" "MATCH ()-[:ACTED_IN]->(a:Person:Actor) RETURN a, count(*)","(Person, FOLLOWS, Person), (Person, ACTED_IN, Movie), (Person, REVIEWED, Movie), (Person, WROTE, Movie), (Person, DIRECTED, Movie), (Movie, IN_GENRE, Genre), (Person, RATED, Movie), (Actor, ACTED_IN, Movie)","MATCH ()<-[:ACTED_IN]-(a:Person:Actor) RETURN a, count(*)" "MATCH (n:Person) <- [:REVIEWED] - (:Movie) RETURN n","(Person, KNOWS, Movie)", ================================================ FILE: neo4j-cypher-dsl-parser/src/test/resources/qpp-primer.csv ================================================ Input,Expected,Num results,Is a counting query? "MATCH () RETURN count(*) AS numNodes","MATCH () RETURN count(*) AS numNodes",18,true "MATCH (:Stop) RETURN count(*) AS numStops","MATCH (:Stop) RETURN count(*) AS numStops",10,true "MATCH (s:Stop)-[:CALLS_AT]->(:Station {name: 'Denmark Hill'}) RETURN s.arrives AS arrivalTime","MATCH (s:Stop)-[:CALLS_AT]->(:Station {name: 'Denmark Hill'}) RETURN s.arrives AS arrivalTime",2,false "MATCH (n:Station {name: 'Denmark Hill'})<-[:CALLS_AT]- (s:Stop WHERE s.departs = time('22:37'))-[:NEXT]-> (:Stop)-[:CALLS_AT]->(d:Station) RETURN d.name AS nextCallingPoint","MATCH (n:Station {name: 'Denmark Hill'})<-[:CALLS_AT]-(s:Stop WHERE s.departs = time('22:37'))-[:NEXT]->(:Stop)-[:CALLS_AT]->(d:Station) RETURN d.name AS nextCallingPoint",1,false "MATCH (:Station {name: 'Peckham Rye'})-[link:LINK]-+ (:Station {name: 'Clapham Junction'}) RETURN reduce(acc = 0.0, l IN link | round(acc + l.distance, 2)) AS totalDistance","MATCH (:Station {name: 'Peckham Rye'})-[link:LINK]-+(:Station {name: 'Clapham Junction'}) RETURN reduce(acc = 0.0, l IN link | round((acc + l.distance), 2)) AS totalDistance",2,false "MATCH (:Station {name: 'Peckham Rye'}) (()-[link:LINK]-(s) WHERE link.distance <= 2)+ (:Station {name: 'London Victoria'}) UNWIND s AS station RETURN station.name AS callingPoint","MATCH (:Station {name: 'Peckham Rye'}) (()-[link:LINK]-(s) WHERE link.distance <= 2)+ (:Station {name: 'London Victoria'}) UNWIND s AS station RETURN station.name AS callingPoint",5,false "MATCH (:Station {name: 'Denmark Hill'})<-[:CALLS_AT]-(s1:Stop)-[:NEXT]->+ (sN:Stop WHERE NOT EXISTS { (sN)-[:NEXT]->(:Stop) })-[:CALLS_AT]-> (d:Station) RETURN s1.departs AS departure, sN.arrives AS arrival, d.name AS finalDestination","MATCH (:Station {name: 'Denmark Hill'})<-[:CALLS_AT]-(s1:Stop)-[:NEXT]->+(sN:Stop WHERE NOT (EXISTS { (sN)-[:NEXT]->(:Stop) }))-[:CALLS_AT]->(d:Station) RETURN s1.departs AS departure, sN.arrives AS arrival, d.name AS finalDestination",2,false "MATCH (:Station {name: 'Peckham Rye'})<-[:CALLS_AT]-(:Stop) (()-[:NEXT]->(s:Stop))+ ()-[:CALLS_AT]->(:Station {name: 'Battersea Park'}) UNWIND s AS stop MATCH (stop)-[:CALLS_AT]->(station:Station) RETURN stop.arrives AS arrival, station.name AS callingPoint","MATCH (:Station {name: 'Peckham Rye'})<-[:CALLS_AT]-(:Stop) (()-[:NEXT]->(s:Stop))+ ()-[:CALLS_AT]->(:Station {name: 'Battersea Park'}) UNWIND s AS stop MATCH (stop)-[:CALLS_AT]->(station:Station) RETURN stop.arrives AS arrival, station.name AS callingPoint",4,false "MATCH (n:Station)-[:LINK]-+(n) RETURN DISTINCT n.name AS station","MATCH (n:Station)-[:LINK]-+(n) RETURN DISTINCT n.name AS station",6,false "MATCH (:Station {name: 'Denmark Hill'})<-[:CALLS_AT]- (s1:Stop)-[:NEXT]->+(s2:Stop)-[:CALLS_AT]-> (c:Station)<-[:CALLS_AT]-(x:Stop), (:Station {name: 'Clapham Junction'})<-[:CALLS_AT]- (t1:Stop)-[:NEXT]->+(x)-[:NEXT]->+(:Stop)-[:CALLS_AT]-> (:Station {name: 'London Victoria'}) WHERE t1.departs = time('22:46') AND s2.arrives < x.departs RETURN s1.departs AS departure, s2.arrives AS changeArrival, c.name AS changeAt","MATCH (:Station {name: 'Denmark Hill'})<-[:CALLS_AT]-(s1:Stop)-[:NEXT]->+(s2:Stop)-[:CALLS_AT]->(c:Station)<-[:CALLS_AT]-(x:Stop), (:Station {name: 'Clapham Junction'})<-[:CALLS_AT]-(t1:Stop)-[:NEXT]->+(x)-[:NEXT]->+(:Stop)-[:CALLS_AT]->(:Station {name: 'London Victoria'}) WHERE (t1.departs = time('22:46') AND s2.arrives < x.departs) RETURN s1.departs AS departure, s2.arrives AS changeArrival, c.name AS changeAt",1,false ================================================ FILE: neo4j-cypher-dsl-schema-name-support/README.adoc ================================================ = Schema Name support This module consists of only one class, `SchemaNames`, that allows sanitization and quotation of possible schema names, making them safe to use in string concatenated queries for dynamic manipulation of types and labels. The project is safe to be shaded, it has no dependencies and does not restrict access to its class apart on the package level. ================================================ FILE: neo4j-cypher-dsl-schema-name-support/pom.xml ================================================ 4.0.0 org.neo4j neo4j-cypher-dsl-parent ${revision}${sha1}${changelist} neo4j-cypher-dsl-schema-name-support Neo4j Cypher DSL (Schema Name Support) Utilities for quoting schema names to be safely used in string concatenations. ${basedir}/../${aggregate.report.dir} org.assertj assertj-core test org.junit.jupiter junit-jupiter test org.mockito mockito-core test org.neo4j.driver neo4j-java-driver test org.slf4j slf4j-simple test org.testcontainers testcontainers-junit-jupiter test org.testcontainers testcontainers-neo4j test ================================================ FILE: neo4j-cypher-dsl-schema-name-support/src/main/java/module-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * This module contains utilities to deal with escaping and sanitizing Cypher identifiers. * * @author Michael J. Simons * @since 2023.0.0 */ module org.neo4j.cypherdsl.support.schema_name { exports org.neo4j.cypherdsl.support.schema_name; } ================================================ FILE: neo4j-cypher-dsl-schema-name-support/src/main/java/org/neo4j/cypherdsl/support/schema_name/SchemaNames.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.support.schema_name; import java.io.Serial; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A SchemaName * can appear according to the OpenCypher-Spec all the following places: * *

    *
  • NodePattern aka Label
  • *
  • RelationshipPattern aka Type
  • *
  • PropertyLookup
  • *
* * Some papers refer to a schema name as symbolic names, too. A symbolic name in the * context of the Cypher-DSL is usually used to describe variables inside a statement, so * we stick with the term schema name here. *

* * A schema name must be escaped in statements with backticks - also known as grave * accents - ({@code `}) when it contains content that is not a valid identifier or if the * name is a reserved word. *

* * Backticks themselves must be escaped with a backtick itself: {@code ``}. We * always treat two consecutive backticks as escaped backticks. An odd number of * backticks will always lead to another backtick being inserted so that the single one * will be escaped proper. As a concrete example this means an input like {@code ```} will * be sanitised and quoted as {@code ``````}. These are one leading and one closing * backtick as this schema name needs to be quoted plus one more to escape the outlier. * When used as a label or type, the resulting label will be {@code ``}. *

* * This utility can be used standalone, the distributed module does not have any * dependencies. Shading is ok as well, there is no encapsulation to break. * * @author Michael J. Simons * @since 2022.8.0 */ public final class SchemaNames { private static final String ESCAPED_UNICODE_BACKTICK = "\\u0060"; private static final Pattern PATTERN_ESCAPED_4DIGIT_UNICODE = Pattern.compile("\\\\u+(\\p{XDigit}{4})"); private static final Pattern PATTERN_LABEL_AND_TYPE_QUOTATION = Pattern.compile("(? SUPPORTED_ESCAPE_CHARS = List.of(new String[] { "\\b", "\b" }, new String[] { "\\f", "\f" }, new String[] { "\\n", "\n" }, new String[] { "\\r", "\r" }, new String[] { "\\t", "\t" }, new String[] { "\\`", "``" }); private static final int CACHE_SIZE = 128; /** * Cypher-DSL has a concrete implementation of such a simple cache, but it should be * in this module itself. */ private static final Map CACHE = Collections .synchronizedMap(new LinkedHashMap<>(CACHE_SIZE / 4, 0.75f, true) { @Serial private static final long serialVersionUID = -8109893585632797360L; @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() >= CACHE_SIZE; } }); private SchemaNames() { } /** * Sanitizes the given input to be used as a valid schema name, adds quotes if * necessary. * @param value the value to sanitize * @return a value that is safe to be used in string concatenation, an empty optional * indicates a value that cannot be safely quoted */ public static Optional sanitize(String value) { return sanitize(value, false); } /** * Sanitizes the given input to be used as a valid schema name. * @param value the value to sanitize * @param enforceQuotes if quotation should be enforced, even when not necessary * @return a value that is safe to be used in string concatenation, an empty optional * indicates a value that cannot be safely quoted */ public static Optional sanitize(String value, boolean enforceQuotes) { return sanitize(value, enforceQuotes, -1, -1); } /** * Sanitizes the given input to be used as a valid schema name. * @param value the value to sanitize * @param enforceQuotes if quotation should be enforced, even when not necessary * @param major the Neo4j major version, use a value < 0 to assume the latest major * version * @param minor the Neo4j minor version, use a value < 0 to assume any minor * version * @return a value that is safe to be used in string concatenation, an empty optional * indicates a value that cannot be safely quoted */ public static Optional sanitize(String value, boolean enforceQuotes, int major, int minor) { if (major >= 0 && (major < 3 || major > 6)) { throw new IllegalArgumentException("Unsupported major version: " + major); } if (value == null || value.isEmpty()) { return Optional.empty(); } CacheKey cacheKey = new CacheKey(value, (major < 0) ? -1 : major, (minor < 0) ? -1 : minor); SchemaName escapedValue = CACHE.computeIfAbsent(cacheKey, SchemaNames::sanitize); if (!(enforceQuotes || escapedValue.needsQuotation)) { return Optional.of(escapedValue.value); } return Optional.of(String.format(Locale.ENGLISH, "`%s`", escapedValue.value)); } private static SchemaName sanitize(CacheKey key) { String workingValue = key.value; // Replace current and future escaped chars for (String[] pair : SUPPORTED_ESCAPE_CHARS) { workingValue = workingValue.replace(pair[0], pair[1]); } workingValue = workingValue.replace(ESCAPED_UNICODE_BACKTICK, "`"); // Replace escaped octal hex // Excluding the support for 6 digit literals, as this contradicts the overall // example in CIP-59r Matcher matcher = PATTERN_ESCAPED_4DIGIT_UNICODE.matcher(workingValue); StringBuilder sb = new StringBuilder(); while (matcher.find()) { String replacement = Character.toString((char) Integer.parseInt(matcher.group(1), 16)); matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement)); } matcher.appendTail(sb); workingValue = sb.toString(); if (substituteRemainingEscapedUnicodeLiteral(key.major, key.minor)) { workingValue = workingValue.replace("\\u", "\\u005C\\u0075"); } matcher = PATTERN_LABEL_AND_TYPE_QUOTATION.matcher(workingValue); workingValue = matcher.replaceAll("`$0"); if (unescapeEscapedBackslashes(key.major)) { workingValue = workingValue.replace("\\\\", "\\"); } return new SchemaName(workingValue, !isIdentifier(workingValue)); } /** * True if the start of an escaped Unicode literal {@code \\u} should be obfuscated by * two escaped literals for {@code \} and {@code u}. * @param major the Neo4j Major version * @param minor the Neo4j minor version * @return {@literal true} if the beginning of an escaped Unicode literal needs * special treatment */ private static boolean substituteRemainingEscapedUnicodeLiteral(int major, int minor) { if (major == -1) { return true; } return major >= 4 && major <= 5 && (minor == -1 || minor >= 2); } /** * A helper method to determine whether a given Neo4j version supports escaped * backslashes. * @param major the Neo4j Major version * @return {@literal true} if escaped backslashes aren't supported and must be * unescaped */ private static boolean unescapeEscapedBackslashes(int major) { return major <= 5; } /** * This is a literal copy of * {@code javax.lang.model.SourceVersion#isIdentifier(CharSequence)} included here to * be not dependent on the compiler module. * @param name a possible Java identifier * @return true, if {@code name} represents an identifier. */ private static boolean isIdentifier(CharSequence name) { String id = name.toString(); int cp = id.codePointAt(0); if (!Character.isJavaIdentifierStart(cp) || '$' == cp) { return false; } for (int i = Character.charCount(cp); i < id.length(); i += Character.charCount(cp)) { cp = id.codePointAt(i); if (!Character.isJavaIdentifierPart(cp) || '$' == cp) { return false; } } return true; } private record CacheKey(String value, int major, int minor) { } private record SchemaName(String value, boolean needsQuotation) { } } ================================================ FILE: neo4j-cypher-dsl-schema-name-support/src/main/java/org/neo4j/cypherdsl/support/schema_name/package-info.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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. */ /** * A single class package providing sanitization of strings to be used as literals in * Cypher statements. */ package org.neo4j.cypherdsl.support.schema_name; ================================================ FILE: neo4j-cypher-dsl-schema-name-support/src/test/java/org/neo4j/cypherdsl/support/schema_name/SchemaNamesIT.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.support.schema_name; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.DynamicContainer; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; import org.neo4j.driver.AuthTokens; import org.neo4j.driver.Config; import org.neo4j.driver.Driver; import org.neo4j.driver.GraphDatabase; import org.neo4j.driver.Result; import org.neo4j.driver.Session; import org.neo4j.driver.summary.ResultSummary; import org.neo4j.driver.types.Node; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.neo4j.Neo4jContainer; import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael J. Simons */ @Testcontainers(disabledWithoutDocker = true) class SchemaNamesIT { private static final Config DRIVER_CONFIG = Config.builder().withMaxConnectionPoolSize(1).build(); private static final Map CACHED_CONNECTIONS = Collections.synchronizedMap(new HashMap<>()); private static final String LATEST_VERSION = "4.4"; private static final List TEST_DATA = Arrays.asList( new TestItem(Category.Q, "Simple label", "ABC", "ABC"), new TestItem(Category.Q, "Simple label with non identifiers", "A Label", "A Label"), new TestItem(Category.Q, "Simple label with backticks", "A` C", "A` C"), new TestItem(Category.Q, "Escaped quotes", "A`` C", "A` C"), new TestItem(Category.Q, "Backticks after blank ", "A `Label", "A `Label"), new TestItem(Category.Q, "Non consecutive backticks", "`A `Label", "`A `Label"), new TestItem(Category.U, "Single unicode, no quoting needed", "ᑖ", "ᑖ"), new TestItem(Category.U, "Single multibyte unicode, quoting needed", "⚡️", "⚡️"), new TestItem(Category.U, "Unicode pair inside label", "Spring Data Neo4j⚡️RX", "Spring Data Neo4j⚡️RX"), new TestItem(Category.Q, "Single backtick", "`", "`"), new TestItem(Category.Q, "Single unicode literal backtick", "\u0060", "`"), new TestItem(Category.Q, "One escaped, one unescaped backtick", "```", "``"), new TestItem(Category.Q, "One escaped, one unescaped unicode literal backtick", "\u0060\u0060\u0060", "``"), new TestItem(Category.U, "One escaped, one unescaped Cypher unicode literal backtick", "\\u0060\\u0060\\u0060", "``"), new TestItem(Category.Q, "Backtick at end", "Hello`", "Hello`"), new TestItem(Category.Q, "Escaped backticks", "Hi````there", "Hi``there"), new TestItem(Category.Q, "Mixed escaped and non escaped backticks", "Hi`````there", "Hi```there"), new TestItem(Category.Q, "Even number of scattered backticks", "`a`b`c`", "`a`b`c`"), new TestItem(Category.Q, "Even number of scattered backticks (unicode literals)", "\u0060a`b`c\u0060d\u0060", "`a`b`c`d`"), new TestItem(Category.Q, "Even number of scattered backticks (Cypher unicode literals)", "\\u0060a`b`c\\u0060d\\u0060", "`a`b`c`d`"), new TestItem(Category.B, "Escaped backslash followed by backtick", "Foo\\\\`bar", "Foo\\`bar"), new TestItem(Category.U, "Escaped (invalid) unicode literal", "admin\\user", "admin\\user"), new TestItem(Category.U, "Escaped (invalid) Cypher unicode literal", "admin\\\\user", "admin\\user"), new TestItem(Category.U, "Cypher unicode literals", "\\u0075\\u1456", "uᑖ"), new TestItem(Category.U, "Unicode literal", "\u1456", "ᑖ"), new TestItem(Category.U, "Non recursive Cypher unicode literals", "something\\u005C\\u00751456", "something\\u1456"), new TestItem(Category.U, "Unicode literals creating a backtick unicode", "\u005C\\u0060", "`"), new TestItem(Category.U, "Unicode literals creating only the literal text of a a unicode literal", "\\u005Cu0060", "\\u0060"), new TestItem(Category.U, "Cypher unicode literals creating unicode backtick literal", "\\u005C\\u0060", "\\`"), new TestItem(Category.B, "Single backslash", "x\\y", "x\\y"), new TestItem(Category.B, "Escaped single backslash", "x\\\\y", "x\\y"), new TestItem(Category.B, "Escaped multiple backslash", "x\\\\\\\\y", "x\\\\y"), new TestItem(Category.E, "Escaped backticks (Future Neo4j)", "x\\`y", "x`y"), new TestItem(Category.E, "Escaped backticks (Future Neo4j)", "x\\```y", "x``y"), new TestItem(Category.E, "Escaped backticks (Future Neo4j)", "x`\\```y", "x```y"), new TestItem(Category.Q, "Unicode literal backtick at end", "Foo \u0060", "Foo `"), new TestItem(Category.Q, "Cypher unicode literal backtick at end", "Foo \\u0060", "Foo `")); private static Driver newDriverInstance(Neo4jContainer server) { return GraphDatabase.driver(server.getBoltUrl(), AuthTokens.basic("neo4j", server.getAdminPassword()), DRIVER_CONFIG); } @AfterAll static void closeConnections() { CACHED_CONNECTIONS.values().forEach(Driver::close); } @TestFactory Stream shouldHaveConsistentResultsOnAllSupportedVersions() { Stream versions; if (Boolean.getBoolean("SCHEMA_NAMES_TEST_ALL_VERSIONS")) { versions = Stream.of("3.5", "4.0", "4.1", "4.2", "4.3", LATEST_VERSION); } else { versions = Stream.of(LATEST_VERSION); } return versions.map(version -> { @SuppressWarnings("resource") Neo4jContainer neo4j = new Neo4jContainer("neo4j:" + version).withReuse(true); neo4j.start(); String[] majorMinor = version.split("\\."); int major; int minor; // Latest supported must work without config if (SchemaNamesIT.LATEST_VERSION.equals(version)) { major = -1; minor = -1; } else { major = Integer.parseInt(majorMinor[0]); minor = Integer.parseInt(majorMinor[1]); } Stream categories = TEST_DATA.stream() .collect(Collectors.groupingBy(TestItem::category)) .entrySet() .stream() .map(entry -> { Stream nested = entry.getValue() .stream() .map(item -> DynamicTest.dynamicTest(item.description() + " (" + item.input + ")", () -> { String schemaName = SchemaNames.sanitize(item.input(), false, major, minor) .orElseThrow(NoSuchElementException::new); Driver driver = CACHED_CONNECTIONS.computeIfAbsent(neo4j, SchemaNamesIT::newDriverInstance); try (Session session = driver.session()) { Result result = session.run(String.format("CREATE (n:%s) RETURN n", schemaName)); Node node = result.single().get(0).asNode(); assertThat(node.labels()).containsExactly(item.expected()); ResultSummary summary = result.consume(); assertThat(summary.counters().nodesCreated()).isOne(); } })); return DynamicContainer.dynamicContainer(entry.getKey().value, nested); }); return DynamicContainer.dynamicContainer(version, categories); }); } enum Category { Q("Quoting"), U("Unicode"), B("Backslashes"), E("Escaping"); private final String value; Category(String value) { this.value = value; } } record TestItem(Category category, String description, String input, String expected) { } } ================================================ FILE: neo4j-cypher-dsl-schema-name-support/src/test/java/org/neo4j/cypherdsl/support/schema_name/SchemaNamesTests.java ================================================ /* * Copyright (c) 2019-2026 "Neo4j," * Neo4j Sweden AB [https://neo4j.com] * * This file is part of Neo4j. * * 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 org.neo4j.cypherdsl.support.schema_name; import java.lang.reflect.Field; import java.util.Map; import java.util.Optional; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatNoException; /** * @author Michael J. Simons */ class SchemaNamesTests { @Test void shouldDealWithNull() { assertThat(SchemaNames.sanitize(null)).isEmpty(); } @Test void shouldDealWithEmpty() { assertThat(SchemaNames.sanitize("")).isEmpty(); } @Test void shouldThrowOnInvalidVersions() { assertThatIllegalArgumentException().isThrownBy(() -> SchemaNames.sanitize("whatever", false, 2, 3)) .withMessage("Unsupported major version: 2"); assertThatIllegalArgumentException().isThrownBy(() -> SchemaNames.sanitize("whatever", false, 7, 3)) .withMessage("Unsupported major version: 7"); } @Test void shouldSupportFutureNeo4j() { assertThat(SchemaNames.sanitize("x\\\\y", false, 6, -1)).hasValue("`x\\\\y`"); } @Test void shouldBeAbleToForceQuotes() { assertThat(SchemaNames.sanitize("a", true, -1, -1)).hasValue("`a`"); } @Test void shouldQuoteEmptyString() { assertThat(SchemaNames.sanitize(" ", false, -1, -1)).hasValue("` `"); } @Test void shouldCacheData() throws Exception { Field cacheField = SchemaNames.class.getDeclaredField("CACHE"); cacheField.setAccessible(true); Map cache = (Map) cacheField.get(null); cache.clear(); assertThatNoException().isThrownBy(() -> SchemaNames.sanitize("a")); assertThatNoException().isThrownBy(() -> SchemaNames.sanitize("b")); assertThatNoException().isThrownBy(() -> SchemaNames.sanitize("a")); assertThatNoException().isThrownBy(() -> SchemaNames.sanitize("a", false, -23, -42)); assertThat(cache).hasSize(2); } @Test void shouldOnlyObfuscateUnicodeWhenRequired() { String plain = "`administrator\\user`"; String obfuscated = "`administrator\\u005C\\u0075ser`"; Optional v = SchemaNames.sanitize("administrator\\user", false, 3, -1); assertThat(v).hasValue(plain); v = SchemaNames.sanitize("administrator\\user", false, 4, 0); assertThat(v).hasValue(plain); v = SchemaNames.sanitize("administrator\\user", false, 4, -1); assertThat(v).hasValue(obfuscated); v = SchemaNames.sanitize("administrator\\user", false, 4, 2); assertThat(v).hasValue(obfuscated); } @Test void shouldHandleEscapedBackslashes() { for (String input : new String[] { "x\\y", "x\\\\y" }) { Optional v = SchemaNames.sanitize(input, false, 4, -1); String expected = "`x\\y`"; assertThat(v).hasValue(expected); v = SchemaNames.sanitize("x\\\\y", false, 4, -1); assertThat(v).hasValue(expected); } Optional v = SchemaNames.sanitize("x\\\\y", false, 6, -1); assertThat(v).hasValue("`x\\\\y`"); } } ================================================ FILE: neo4j-cypher-dsl-test-results/pom.xml ================================================ 4.0.0 org.neo4j neo4j-cypher-dsl-parent ${revision}${sha1}${changelist} neo4j-cypher-dsl-test-results Aggregated test results org.neo4j.cypherdsl.test_results org.neo4j neo4j-cypher-dsl-bom ${project.version} pom import org.neo4j neo4j-cypher-dsl org.neo4j neo4j-cypher-dsl-codegen-core org.neo4j neo4j-cypher-dsl-codegen-ogm com.querydsl querydsl-apt org.neo4j neo4j-cypher-dsl-codegen-sdn6 com.querydsl querydsl-apt org.neo4j neo4j-cypher-dsl-parser ${project.version} org.neo4j neo4j-cypher-dsl-schema-name-support com.mycila license-maven-plugin false org.jacoco jacoco-maven-plugin prepare-agent report-aggregate report-aggregate verify **/jacoco.exec ${project.reporting.outputDirectory}/jacoco-aggregate org.apache.maven.plugins maven-surefire-plugin **/jacoco.exec org.apache.maven.plugins maven-javadoc-plugin true org.apache.maven.plugins maven-install-plugin true org.apache.maven.plugins maven-deploy-plugin true org.apache.maven.plugins maven-jar-plugin true ================================================ FILE: pom.xml ================================================ 4.0.0 org.neo4j neo4j-cypher-dsl-parent ${revision}${sha1}${changelist} pom Neo4j Cypher DSL A DSL for generating Cypher statements for Neo4j https://neo4j.github.io/cypher-dsl 2011 Neo4j, Neo4j Sweden AB https://neo4j.com The Apache Software License, Version 2.0 https://www.apache.org/licenses/LICENSE-2.0.txt repo gmeier Gerrit Meier gerrit.meier at neo4j.com Neo Technology https://neo4j.com Developer +1 mhunger Michael Hunger michael.hunger at neo4j.com Neo Technology https://neo4j.com Developer +1 msimons Michael Simons michael.simons at neo4j.com Neo Technology https://neo4j.com Project Lead +1 fbiville Florent Biville florent.biville at neo4j.com Neo Technology https://neo4j.com developer +1 Andy2003 Andreas Berger andreas at berger-ecommerce.com Developer +1 aince Ali Ince ali.ince at neo4j.com Neo Technology https://neo4j.com developer 0 neo4j-cypher-dsl-bom neo4j-cypher-dsl-schema-name-support neo4j-cypher-dsl-build neo4j-cypher-dsl neo4j-cypher-dsl-codegen neo4j-cypher-dsl-parser neo4j-cypher-dsl-native-tests neo4j-cypher-dsl-examples neo4j-cypher-dsl-test-results scm:git:git@github.com:neo4j/cypher-dsl.git scm:git:git@github.com:neo4j/cypher-dsl.git HEAD https://github.com/neo4j/cypher-dsl neo4j-cypher-dsl-test-results/target/site/jacoco-aggregate/jacoco.xml 1.1.2 1.4.2 3.2.0 3.2.1 3.0.1 9.9.1 3.27.7 1.2.2 3.3.0 -SNAPSHOT 4.1.0 13.4.2 0.23.0 0.75 0.75 2022.7.3 2.23.0 3.6.3 1.7.3 33.6.0-jre 2.21.3 0.8.14 0.25.6 25 1.13.0 1.3.2 2.3.1 2.14.1 1.23.0 3.0.2 6.0.3 5.0.0 3.6.0 3.11.0 3.1.4 3.6.2 3.5.5 3.1.4 3.5.0 3.12.0 3.5.0 1.12.0 3.6.2 3.21.0 3.4.0 3.5.5 true true 17 3.9.12 5.23.0 1.3.0.Final 6.1.0 4.2.5 5.0.5 5.26.25 5.12.0 ${project.build.directory}/docs UTF-8 3.35.1 5.1.0 9999 2.0.17 5.6.0.6792 4.0.0 8.0.5 0.0.47 2.0.5 0 com.fasterxml.jackson jackson-bom ${jackson.version} pom import org.junit junit-bom ${junit-jupiter.version} pom import org.testcontainers testcontainers-bom ${testcontainers.version} pom import com.google.auto auto-common ${auto-common.version} com.google.code.findbugs jsr305 ${jsr305.version} com.querydsl querydsl-core ${querydsl.version} com.squareup javapoet ${javapoet.version} com.tngtech.archunit archunit ${archunit.version} javax.xml.bind jaxb-api ${jaxb.version} joda-time joda-time ${joda-time.version} org.apiguardian apiguardian-api ${apiguardian.version} org.asciidoctor asciidoctorj ${asciidoctorj.version} rubygems test-unit org.neo4j neo4j-cypher-javacc-parser ${neo4j.version} org.scala-lang scala-reflect org.neo4j neo4j-ogm-core ${neo4j-ogm.version} org.neo4j neo4j-ogm-quarkus ${neo4j-ogm-quarkus.version} org.neo4j.driver neo4j-java-driver ${neo4j-java-driver.version} org.ow2.asm asm ${asm.version} org.slf4j slf4j-api ${slf4j.version} org.slf4j slf4j-simple ${slf4j.version} org.springframework.data spring-data-neo4j ${spring-data-neo4j.version} cglib cglib ${cglib.version} test com.google.errorprone error_prone_annotations ${error_prone_annotations.version} test com.google.guava guava ${guava.version} test com.google.testing.compile compile-testing ${compile-testing.version} test junit junit org.assertj assertj-core ${assertj.version} test org.checkerframework checker-qual ${checker-qual.version} test org.mockito mockito-core ${mockito.version} test org.mockito mockito-junit-jupiter ${mockito.version} test org.sonarsource.scanner.maven sonar-maven-plugin ${sonar-maven-plugin.version} org.moditect moditect-maven-plugin ${moditect-maven-plugin.version} org.jacoco jacoco-maven-plugin ${jacoco-maven-plugin.version} **/* pre-unit-test prepare-agent true pre-integration-test prepare-agent-integration true ${project.build.directory}/jacoco.exec report-and-check report check post-integration-test BUNDLE INSTRUCTION COVEREDRATIO ${covered-ratio-instructions} COMPLEXITY COVEREDRATIO ${covered-ratio-complexity} org.apache.maven.plugins maven-shade-plugin ${maven-shade-plugin.version} org.apache.maven.plugins maven-install-plugin ${maven-install-plugin.version} org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} org.apache.maven.plugins maven-compiler-plugin ${maven-compiler-plugin.version} true true true -Xlint:all,-options,-path,-processing,-exports -Werror org.apache.maven.plugins maven-javadoc-plugin ${maven-javadoc-plugin.version} true false true ${maven.compiler.release} neo4j.version a Neo4j version required org.apache.maven.plugins maven-jar-plugin ${maven-jar-plugin.version} io.spring.javaformat spring-javaformat-maven-plugin ${spring-javaformat.version} org.apache.maven.plugins maven-checkstyle-plugin ${maven-checkstyle-plugin.version} **/module-info.java true etc/checkstyle/config.xml etc/checkstyle/suppressions.xml ${project.build.sourceEncoding} true true true com.puppycrawl.tools checkstyle ${checkstyle.version} io.spring.javaformat spring-javaformat-checkstyle ${spring-javaformat.version} org.apache.maven.plugins maven-surefire-plugin ${maven-surefire-plugin.version} org.apache.maven.plugins maven-failsafe-plugin ${maven-failsafe-plugin.version} org.apache.maven.plugins maven-resources-plugin ${maven-resources-plugin.version} org.apache.maven.plugins maven-site-plugin ${maven-site-plugin.version} com.github.ekryd.sortpom sortpom-maven-plugin ${sortpom-maven-plugin.version} ${project.build.sourceEncoding} true -1 true scope,groupId,artifactId groupId,artifactId false false com.github.siom79.japicmp japicmp-maven-plugin ${japicmp-maven-plugin.version} ${project.groupId} ${project.artifactId} ${cypher-dsl.version.old} jar ${project.build.directory}/${project.artifactId}-${project.version}.${project.packaging} true true METHOD_NEW_DEFAULT true true MINOR METHOD_DEFAULT_ADDED_IN_IMPLEMENTED_INTERFACE true true org.neo4j.cypherdsl.core.internal org.neo4j.cypherdsl.core.ast.TypedSubtree org.neo4j.cypherdsl.core.utils.Strings#isValidJavaIdentifierPartAt(int,int) org.neo4j.cypherdsl.core.utils.Strings#isIdentifier(java.lang.CharSequence) org.neo4j.cypherdsl.parser.internal org.neo4j.cypherdsl.parser.NodeAtom org.neo4j.cypherdsl.core.StatementContext org.neo4j.cypherdsl.core.Unwind#getVariable() org.neo4j.cypherdsl.core.ExposesWith#with(org.neo4j.cypherdsl.core.Asterisk) org.neo4j.cypherdsl.core.DefaultStatementBuilder$DefaultStatementWithWithBuilder org.neo4j.cypherdsl.core.StatementBuilder$ExposesSetAndRemove#set(org.neo4j.cypherdsl.core.Node,java.util.Collection) org.neo4j.cypherdsl.core.StatementBuilder$ExposesSetAndRemove#set(org.neo4j.cypherdsl.core.Node,java.lang.String[]) org.neo4j.cypherdsl.core.ExposesSubqueryCall#call(org.neo4j.cypherdsl.core.Statement) org.neo4j.cypherdsl.core.ExposesSubqueryCall#call(org.neo4j.cypherdsl.core.Statement,java.lang.String[]) org.neo4j.cypherdsl.core.ExposesSubqueryCall#call(org.neo4j.cypherdsl.core.Statement,org.neo4j.cypherdsl.core.IdentifiableElement[]) org.neo4j.cypherdsl.core.StatementBuilder$OngoingUpdate org.neo4j.cypherdsl.parser.internal.* cmp verify com.mycila license-maven-plugin ${license-maven-plugin.version} org.jreleaser jreleaser-maven-plugin ${jreleaser-maven-plugin.version} com.mycila license-maven-plugin true SCRIPT_STYLE 2026

etc/license.tpl
** **/*.adoc **/*.csv **/*.cypher **/*.sh **/*.tpl **/*.txt **/*.yaml **/.flattened-pom.xml **/.git-blame-ignore-revs **/org.mockito.plugins.MockMaker bin/runtests.java **/src/test/resources/**/*_.java true One or more dependencies are licensed under a non-approved license. LICENSE_URL APPROVE https://www.apache.org/licenses/LICENSE-2.0 LICENSE_NAME APPROVE Apache License, Version 2 LICENSE_NAME APPROVE Apache License, Version 2.0 LICENSE_NAME APPROVE The Apache Software License, Version 2.0 LICENSE_NAME APPROVE The Apache License, Version 2.0 LICENSE_NAME APPROVE Apache 2.0 LICENSE_NAME APPROVE Apache-2.0 LICENSE_NAME APPROVE MIT License LICENSE_NAME APPROVE MIT validate check validate org.codehaus.mojo exec-maven-plugin ${exec-maven-plugin.version} false prepare-release exec bin/prepare-release.sh ${revision}${sha1}${changelist} ${cypher-dsl.version.next} com.github.ekryd.sortpom sortpom-maven-plugin sort sort verify org.jacoco jacoco-maven-plugin org.apache.maven.plugins maven-enforcer-plugin ${maven-enforcer-plugin.version} enforce enforce validate ${java.version} ${maven.version} org.apache.maven.plugins maven-surefire-plugin org.apache.maven.plugins maven-failsafe-plugin integration-test verify org.apache.maven.plugins maven-jar-plugin true true true org.apache.maven.plugins maven-source-plugin ${maven-source-plugin.version} attach-sources jar-no-fork org.apache.maven.plugins maven-javadoc-plugin attach-javadocs jar org.codehaus.mojo flatten-maven-plugin ${flatten-maven-plugin.version} true resolveCiFriendliesOnly flatten flatten package flatten.clean clean clean org.asciidoctor asciidoctor-maven-plugin ${asciidoctor-maven-plugin.version} false html book ${basedir}/docs index.adoc font left img ${neo4j.version} coderay ${use-latest-version-for-docs} asciidoctor-diagram ${project.build.docs} org.asciidoctor asciidoctorj-diagram ${asciidoctorj-diagram.version} generate-docs process-asciidoc prepare-package org.apache.maven.plugins maven-javadoc-plugin ${maven-javadoc-plugin.version} default javadoc aggregate aggregate org.apache.maven.plugins maven-checkstyle-plugin checkstyle with_checkstyle [21,) io.spring.javaformat spring-javaformat-maven-plugin validate validate true org.apache.maven.plugins maven-checkstyle-plugin validate check validate fast fast true true true true true true true true sonar false https://sonarcloud.io ${project.groupId}:${project.artifactId} michael-simons-github org.neo4j:neo4j-cypher-dsl-parent org.sonarsource.scanner.maven sonar-maven-plugin sonar sonar verify jreleaser jreleaser org.jreleaser jreleaser-maven-plugin neo4j-cypher-dsl neo4j {{projectVersion}} {{projectVersion}} ALWAYS true