├── .github
└── workflows
│ ├── pr.yml
│ └── release.yml
├── .gitignore
├── .scalafmt.conf
├── LICENSE.txt
├── README.md
├── build.sbt
├── doc
└── images
│ ├── gremlin-scala-logo-hd.png
│ ├── gremlin-scala-logo-white.png
│ ├── gremlin-scala-logo.png
│ ├── scaladays-talk-shot.png
│ └── yourkit.png
├── gremlin-scala.bat
├── gremlin-scala.sh
├── gremlin-scala
└── src
│ ├── assembly
│ ├── distribution.xml
│ └── standalone.xml
│ ├── main
│ ├── bin
│ │ ├── gremlin-scala.bat
│ │ └── gremlin-scala.sh
│ └── scala
│ │ └── gremlin
│ │ └── scala
│ │ ├── BranchOption.scala
│ │ ├── By.scala
│ │ ├── DefaultsToAny.scala
│ │ ├── DetachedElements.scala
│ │ ├── GraphHelper.scala
│ │ ├── GremlinScala.scala
│ │ ├── P.scala
│ │ ├── ProjectionBuilder.scala
│ │ ├── ScalaEdge.scala
│ │ ├── ScalaElement.scala
│ │ ├── ScalaGraph.scala
│ │ ├── ScalaVertex.scala
│ │ ├── Schema.scala
│ │ ├── SelectAllStep.scala
│ │ ├── SemiEdge.scala
│ │ ├── StepLabel.scala
│ │ ├── TraversalSource.scala
│ │ ├── UnionTraversals.scala
│ │ ├── dsl
│ │ ├── AnonymousVertex.scala
│ │ ├── Constructor.scala
│ │ ├── Converter.scala
│ │ ├── NodeSteps.scala
│ │ └── Steps.scala
│ │ └── package.scala
│ └── test
│ ├── resources
│ └── dummy
│ └── scala
│ └── gremlin
│ └── scala
│ ├── .gitignore
│ ├── AlgorithmSpec.scala
│ ├── ArrowSyntaxSpec.scala
│ ├── ElementSpec.scala
│ ├── FilterSpec.scala
│ ├── GraphHelperSpec.scala
│ ├── GraphSerialisationSpec.scala
│ ├── LogicalSpec.scala
│ ├── MonadSpec.scala
│ ├── PSpec.scala
│ ├── ProjectSpec.scala
│ ├── SchemaSpec.scala
│ ├── SelectSpec.scala
│ ├── TestBase.scala
│ ├── TraversalSpec.scala
│ ├── TraversalStrategySpec.scala
│ ├── dsl
│ └── DslSpec.scala
│ └── marshallable
│ └── MarshallableSpec.scala
├── macros
└── src
│ └── main
│ └── scala
│ └── gremlin
│ └── scala
│ ├── Annotations.scala
│ └── Marshallable.scala
└── project
├── build.properties
└── plugins.sbt
/.github/workflows/pr.yml:
--------------------------------------------------------------------------------
1 | name: pr
2 | on: pull_request
3 | jobs:
4 | pr:
5 | runs-on: ubuntu-18.04
6 | steps:
7 | - uses: actions/checkout@v2
8 | with:
9 | fetch-depth: 1
10 | - uses: olafurpg/setup-scala@v10
11 | - uses: actions/cache@v2
12 | with:
13 | path: |
14 | ~/.sbt
15 | ~/.coursier
16 | key: ${{ runner.os }}-sbt-${{ hashfiles('**/build.sbt') }}
17 | - run: sbt +test
18 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 | on:
3 | push:
4 | branches: [master, main]
5 | tags: ["*"]
6 | jobs:
7 | release:
8 | concurrency: release
9 | runs-on: ubuntu-18.04
10 | steps:
11 | - uses: actions/checkout@v2
12 | with:
13 | fetch-depth: 0
14 | - uses: olafurpg/setup-scala@v10
15 | - run: echo $PGP_SECRET | base64 --decode | gpg --batch --import
16 | env:
17 | PGP_SECRET: ${{ secrets.PGP_SECRET }}
18 | - uses: actions/cache@v2
19 | with:
20 | path: |
21 | ~/.sbt
22 | ~/.coursier
23 | key: ${{ runner.os }}-sbt-${{ hashfiles('**/build.sbt') }}
24 | - run: sbt +test ciReleaseTagNextVersion ciReleaseSonatype
25 | env:
26 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
27 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | private-key.pem
3 | .idea/
4 | /.bsp
5 | /gnupg-*
6 |
--------------------------------------------------------------------------------
/.scalafmt.conf:
--------------------------------------------------------------------------------
1 | maxColumn = 100
2 | rewrite.rules = [AvoidInfix, SortImports]
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2013-2015 Michael Pollmeier
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://github.com/mpollmeier/gremlin-scala/actions?query=workflow%3Arelease)
4 | [](https://gitter.im/mpollmeier/gremlin-scala?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
5 | [](https://maven-badges.herokuapp.com/maven-central/com.michaelpollmeier/gremlin-scala_2.13)
6 | [](http://www.javadoc.io/doc/com.michaelpollmeier/gremlin-scala_2.13)
7 |
8 | ## Gremlin-Scala for Apache Tinkerpop 3
9 |
10 | A wrapper to use [Apache Tinkerpop3](https://github.com/apache/incubator-tinkerpop) - a JVM graph traversal library - from Scala.
11 |
12 | * Beautiful DSL to create vertices and edges
13 | * Type safe traversals
14 | * Scala friendly function signatures
15 | * Minimal runtime overhead - only allocates additional instances if absolutely necessary
16 | * Nothing is hidden away, you can always easily access the underlying Gremlin-Java objects if needed, e.g. to access graph db specifics things like indexes
17 |
18 | ### TOC
19 |
20 | - [Getting started](#getting-started)
21 | - [Using the sbt console](#using-the-sbt-console)
22 | - [Simple traversals](#simple-traversals)
23 | - [Vertices and edges with type safe properties](#vertices-and-edges-with-type-safe-properties)
24 | - [Compiler helps to eliminate invalid traversals](#compiler-helps-to-eliminate-invalid-traversals)
25 | - [Type safe traversals](#type-safe-traversals)
26 | - [A note on predicates](#a-note-on-predicates)
27 | - [Build a custom DSL on top of Gremlin-Scala](#build-a-custom-dsl-on-top-of-gremlin-scala)
28 | - [Common and useful steps](#common-and-useful-steps)
29 | - [Mapping vertices from/to case classes](#mapping-vertices-fromto-case-classes)
30 | - [More advanced traversals](#more-advanced-traversals)
31 | - [Serialise to and from files](#serialise-to-and-from-files)
32 | - [Help - it's open source!](#help---its-open-source)
33 | - [Why such a long version number?](#why-such-a-long-version-number)
34 | - [Talks](#talks)
35 | - [Further reading](#further-reading)
36 | - [Random things worth knowing](#random-things-worth-knowing)
37 | - [Releases](#releases)
38 | - [Breaking changes](#breaking-changes)
39 |
40 | ### Getting started
41 | The [examples project](https://github.com/mpollmeier/gremlin-scala-examples) comes with working examples for different graph databases. Typically you just need to add a dependency on `"com.michaelpollmeier" %% "gremlin-scala" % "SOME_VERSION"` and one for the graph db of your choice to your `build.sbt` (this readme assumes tinkergraph). The latest version is displayed at the top of this readme in the maven badge.
42 |
43 | ### Using the sbt console
44 | * `sbt gremlin-scala/Test/console`
45 | ```scala
46 | import gremlin.scala._
47 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory
48 | implicit val graph = TinkerFactory.createModern.asScala
49 |
50 | val name = Key[String]("name")
51 |
52 | val g = graph.traversal
53 | g.V.hasLabel("person").value(name).toList
54 | // List(marko, vadas, josh, peter)
55 | ```
56 |
57 | ### Simple traversals
58 |
59 | The below create traversals, which are lazy computations. To run a traversal, you can use e.g. `toSet`, `toList`, `head`, `headOption` etc.
60 |
61 | ```scala
62 | import gremlin.scala._
63 | import org.apache.tinkerpop.gremlin.process.traversal.{Order, P}
64 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory
65 |
66 | implicit val graph = TinkerFactory.createModern.asScala
67 | val g = graph.traversal
68 |
69 | g.V //all vertices
70 | g.E //all edges
71 |
72 | g.V(1).outE("knows") //follow outgoing edges
73 | g.V(1).out("knows") //follow outgoing edges to incoming vertex
74 |
75 | val weight = Key[Double]("weight")
76 | for {
77 | person <- g.V.hasLabel("person")
78 | favorite <- person.outE("likes").order(By(weight, Order.decr)).limit(1).inV
79 | } yield (person, favorite.label)
80 |
81 | // remove all people over 30 from the g - also removes corresponding edges
82 | val Age = Key[Int]("age")
83 | g.V.hasLabel("person").has(Age, P.gte(30)).drop.iterate
84 | ```
85 |
86 | Warning: GremlinScala is _not_ a monad, because the underlying Tinkerpop GraphTraversal is not. I.e. while GremlinScala offers `map`, `flatMap` etc. and you can use it in a for comprehension for syntactic sugar, it does not fulfil all monad laws.
87 |
88 | More working examples in [TraversalSpec](https://github.com/mpollmeier/gremlin-scala/blob/master/gremlin-scala/src/test/scala/gremlin/scala/TraversalSpec.scala).
89 |
90 | ### Vertices and edges with type safe properties
91 |
92 | ```scala
93 | import gremlin.scala._
94 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph
95 | import scala.language.postfixOps
96 | implicit val graph = TinkerGraph.open.asScala
97 |
98 | // Keys for properties which can later be used for type safe traversals
99 | val Founded = Key[String]("founded")
100 | val Distance = Key[Int]("distance")
101 |
102 | // create labelled vertex
103 | val paris = graph + "Paris"
104 |
105 | // create vertex with typed properties
106 | val london = graph + ("London", Founded -> "43 AD")
107 |
108 | // create labelled edges
109 | paris --- "OneWayRoad" --> london
110 | paris <-- "OtherWayAround" --- london
111 | paris <-- "Eurostar" --> london
112 |
113 | // create edge with typed properties
114 | paris --- ("Eurostar", Distance -> 495) --> london
115 |
116 | // type safe access to properties
117 | paris.out("Eurostar").value(Founded).head //43 AD
118 | paris.outE("Eurostar").value(Distance).head //495
119 | london.valueOption(Founded) //Some(43 AD)
120 | london.valueOption(Distance) //None
121 | paris.setProperty(Founded, "300 BC")
122 |
123 | val Name = Key[String]("name")
124 | val Age = Key[Int]("age")
125 |
126 | val v1 = graph + ("person", Name -> "marko", Age -> 29) asScala
127 |
128 | v1.keys // Set(Key("name"), Key("age"))
129 | v1.property(Name) // "marko"
130 | v1.valueMap // Map("name" -> "marko", "age" -> 29)
131 | v1.valueMap("name", "age") // Map("name" -> "marko", "age" -> 29)
132 | ```
133 |
134 | More working examples in [SchemaSpec](https://github.com/mpollmeier/gremlin-scala/blob/master/gremlin-scala/src/test/scala/gremlin/scala/SchemaSpec.scala), [ArrowSyntaxSpec](https://github.com/mpollmeier/gremlin-scala/blob/master/gremlin-scala/src/test/scala/gremlin/scala/ArrowSyntaxSpec.scala) and [ElementSpec](https://github.com/mpollmeier/gremlin-scala/blob/master/gremlin-scala/src/test/scala/gremlin/scala/ElementSpec.scala).
135 |
136 | ### Compiler helps to eliminate invalid traversals
137 | Gremlin-Scala aims to helps you at compile time as much as possible. Take this simple example:
138 |
139 | ```scala
140 | import gremlin.scala._
141 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph
142 | implicit val graph = TinkerGraph.open.asScala
143 | val g = graph.traversal
144 | g.V.outE.inV //compiles
145 | g.V.outE.outE //does _not_ compile
146 | ```
147 |
148 | In Gremlin-Groovy there's nothing stopping you to create the second traversal - it will explode at runtime, as outgoing edges do not have outgoing edges. In Gremlin-Scala this simply doesn't compile.
149 |
150 | ### Type safe traversals
151 | You can label any step using `as(StepLabel)` and the compiler will infer the correct types for you in the select step using an HList (a type safe list, i.e. the compiler knows the types of the elements of the list). In Gremlin-Java and Gremlin-Groovy you get a `Map[String, Any]`, so you have to cast to the type you *think* it will be, which is ugly and error prone. For example:
152 |
153 | ```scala
154 | import gremlin.scala._
155 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory
156 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph
157 | def graph = TinkerFactory.createModern.asScala
158 | val g = graph.traversal
159 |
160 | // select all labelled steps
161 | g.V(1).as("a").outE.as("b").select.toList
162 | // returns a `(Vertex, Edge)` for each path
163 |
164 | // select subset of labelled steps
165 | val a = StepLabel[Vertex]()
166 | val b = StepLabel[Edge]()
167 | val c = StepLabel[Double]()
168 |
169 | val traversal = g.V(1).as(a).outE("created").as(b).value("weight").as(c)
170 |
171 | traversal.select((b, c)).head
172 | // returns a `(Edge, Double)`
173 | ```
174 |
175 | More working examples in [SelectSpec](https://github.com/mpollmeier/gremlin-scala/blob/master/gremlin-scala/src/test/scala/gremlin/scala/SelectSpec.scala). Kudos to [shapeless](https://github.com/milessabin/shapeless/) and Scala's sophisticated type system that made this possible.
176 |
177 | As of 3.3.3.2 there is a typesafe `union` step that supports heterogeneous queries:
178 | ```scala
179 | val traversal =
180 | g.V(1).union(
181 | _.join(_.outE)
182 | .join(_.out)
183 | )
184 | // derived type: GremlinScala[(List[Edge], List[Vertex])]
185 | val (outEdges, outVertices) = traversal.head
186 | ```
187 |
188 | ### A note on predicates
189 | tl;dr: use gremlin.scala.P to create predicates of type P.
190 |
191 | Many steps in take a tinkerpop3 predicate of type `org.apache.tinkerpop.gremlin.process.traversal.P`. Creating Ps that take collection types is dangerous though, because you need to ensure you're creating the correct P. For example `P.within(Set("a", "b"))` would be calling the wrong overload (which checks if the value IS the given set). In that instance you actually wanted to create `P.within(Set("a", "b").asJava: java.util.Collection[String])`. To avoid that confusion, it's best to just `import gremlin.scala._` and create it as `P.within(Set("a", "b"))`.
192 |
193 | ### Build a custom DSL on top of Gremlin-Scala
194 | You can now build your own domain specific language, which is super helpful if you don't want to expose your users to the world of graphs and tinkerpop, but merely build an API for them. All you need to do is setup your ADT as case classes, define your DSL as Steps and create one implicit constructor (the only boilerplate code). The magic in gremlin.scala.dsl._ allows you to even write for comprehensions like this (DSL for tinkerpop testgraph):
195 |
196 | ```scala
197 | case class Person (name: String, age: Integer) extends DomainRoot
198 | case class Software(name: String, lang: String) extends DomainRoot
199 |
200 | val traversal = for {
201 | person <- PersonSteps(graph)
202 | software <- person.created
203 | } yield (person.name, software)
204 |
205 | // note: `traversal` is inferred by the compiler as `gremlin.scala.dsl.Steps[(String, Software)]`
206 |
207 | traversal.toSet // returns:
208 | Set(
209 | ("marko", Software("lop", "java")),
210 | ("josh", Software("lop", "java")),
211 | ("peter", Software("lop", "java")),
212 | ("josh", Software("ripple", "java"))
213 | )
214 |
215 | // DSL also supports typesafe as/select:
216 | PersonSteps(graph)
217 | .as("person")
218 | .created
219 | .as("software")
220 | .select
221 | .toList
222 | // inferred return type is `List[(Person, Software)]`
223 | ```
224 |
225 | See the full setup and more tests in [DslSpec](https://github.com/mpollmeier/gremlin-scala/blob/master/gremlin-scala/src/test/scala/gremlin/scala/dsl/DslSpec.scala).
226 |
227 | ### Common and useful steps
228 |
229 | ```scala
230 | // get a vertex by id
231 | g.V(1).headOption
232 |
233 | // get all vertices
234 | g.V.toList
235 |
236 | // group all vertices by their label
237 | g.V.group(By.label)
238 |
239 | // group vertices by a property
240 | val age = Key[Int]("age")
241 | g.V.has(age).group(By(age))
242 |
243 | // order by property decreasing
244 | val age = Key[Int]("age")
245 | g.V.has(age).order(By(age, Order.decr))
246 | ```
247 |
248 | More working examples in [TraversalSpec](https://github.com/mpollmeier/gremlin-scala/blob/master/gremlin-scala/src/test/scala/gremlin/scala/TraversalSpec.scala).
249 |
250 | ### Mapping vertices from/to case classes
251 | You can save and load case classes as a vertex - implemented with a [blackbox macro](http://docs.scala-lang.org/overviews/macros/blackbox-whitebox.html).
252 |
253 | * You can optionally specify the label of your class using `@label`
254 | * `Option` members will be automatically unwrapped, i.e. a `Some[A]` will be stored as the value of type `A` in the database, or `null` if it's `None`. If we wouldn't unwrap it, the database would have to understand Scala's Option type itself.
255 | * The same goes for value classes, i.e. a `case class ShoeSize(value: Int) extends AnyVal` will be stored as an Integer.
256 | * `List` members will be stored as multi-properties, i.e. Cardinality.list
257 | * Annotating members with `@id` and `@underlying` will instruct the marshaller to set the element id and/or the underlying element in the class. Note: you cannot specify the id when adding a vertex like this. Using `@id` only works when retrieving the vertex back from the graph and it therefor must be an `Option`.
258 | * Your classes must be defined outside the scope where they are being used (e.g. in the code below the class `Example` cannot be inside `object Main`).
259 |
260 | _Warning_: this may not work with your particular remote graph, depending on the implementation/configuration. That's because the graph [may choose to only return *referenced elements* which doesn't contain it's properties](http://tinkerpop.apache.org/docs/3.4.1/reference/#_properties_of_elements).
261 |
262 |
263 | ```scala
264 | // this does _not_ work in a REPL
265 | import gremlin.scala._
266 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph
267 |
268 | @label("my_custom_label")
269 | case class Example(longValue: Long, stringValue: Option[String], @underlying vertex: Option[Vertex] = None)
270 |
271 | object Main extends App {
272 | implicit val graph = TinkerGraph.open.asScala
273 | val example = Example(Long.MaxValue, Some("optional value"))
274 | val v = graph + example
275 | v.toCC[Example] // equal to `example`, but with `vertex` set
276 |
277 | // find all vertices with the label of the case class `Example`
278 | graph.V.hasLabel[Example]
279 |
280 | // modify the vertex like a case class
281 | v.updateAs[Example](_.copy(longValue = 0L))
282 | }
283 | ```
284 |
285 | You can also define your own marshaller, if the macro generated one doesn't quite cut it. For that and more examples check out the [MarshallableSpec](https://github.com/mpollmeier/gremlin-scala/blob/master/gremlin-scala/src/test/scala/gremlin/scala/marshallable/MarshallableSpec.scala).
286 |
287 | ### More advanced traversals
288 | Here are some examples of more complex traversals from the [examples repo](https://github.com/mpollmeier/gremlin-scala-examples/). If you want to run them yourself, check out the tinkergraph examples in there.
289 |
290 | _What's the property distribution of all vertices?_
291 | ```scala
292 | graph.V.groupCount(By(__.properties().count)).head
293 | ```
294 |
295 | _What is `Die Hard's` average rating?_
296 | ```scala
297 | graph.V.has("movie", "name", "Die Hard")
298 | .inE("rated")
299 | .values("stars")
300 | .mean
301 | .head
302 | ```
303 |
304 | _Get the maximum number of movies a single user rated_
305 | ```scala
306 | g.V.hasLabel("person")
307 | .flatMap(_.outE("rated").count)
308 | .max
309 | .head
310 | ```
311 |
312 | _What 80's action movies do 30-something programmers like?_
313 | _Group count the movies by their name and sort the group count map in decreasing order by value._
314 | ```scala
315 | g.V
316 | .`match`(
317 | __.as("a").hasLabel("movie"),
318 | __.as("a").out("hasGenre").has("name", "Action"),
319 | __.as("a").has("year", P.between(1980, 1990)),
320 | __.as("a").inE("rated").as("b"),
321 | __.as("b").has("stars", 5),
322 | __.as("b").outV().as("c"),
323 | __.as("c").out("hasOccupation").has("name", "programmer"),
324 | __.as("c").has("age", P.between(30, 40))
325 | )
326 | .select[Vertex]("a")
327 | .map(_.value[String]("name"))
328 | .groupCount()
329 | .order(Scope.local).by(Order.valueDecr)
330 | .limit(Scope.local, 10)
331 | .head
332 | ```
333 |
334 | _What is the most liked movie in each decade?_
335 | ```scala
336 | g.V()
337 | .hasLabel(Movie)
338 | .where(_.inE(Rated).count().is(P.gt(10)))
339 | .groupBy { movie =>
340 | val year = movie.value2(Year)
341 | val decade = (year / 10)
342 | (decade * 10): Integer
343 | }
344 | .map { moviesByDecade =>
345 | val highestRatedByDecade = moviesByDecade.mapValues { movies =>
346 | movies.toList
347 | .sortBy { _.inE(Rated).value(Stars).mean().head }
348 | .reverse.head //get the movie with the highest mean rating
349 | }
350 | highestRatedByDecade.mapValues(_.value2(Name))
351 | }
352 | .order(Scope.local).by(Order.keyIncr)
353 | .head
354 | ```
355 |
356 | ### Serialise to and from files
357 | Currently graphML, graphson and gryo/kryo are supported file formats, it is very easy to serialise and deserialise into those: see [GraphSerialisationSpec](https://github.com/mpollmeier/gremlin-scala/blob/master/gremlin-scala/src/test/scala/gremlin/scala/GraphSerialisationSpec.scala).
358 | An easy way to visualise your graph is to export it into graphML and import it into [gephi](https://gephi.org/).
359 |
360 | ### Help - it's open source!
361 | If you would like to help, here's a list of things that needs to be addressed:
362 | * add more graph databases and examples into the [examples project](https://github.com/mpollmeier/gremlin-scala-examples)
363 | * port over more TP3 steps - see [TP3 testsuite](https://github.com/apache/incubator-tinkerpop/tree/master/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step) and [Gremlin-Scala StandardTests](https://github.com/mpollmeier/gremlin-scala/blob/master/gremlin-scala/src/test/scala/gremlin/scala/GremlinStandardTestSuite.scala)
364 | * ideas for more type safety in traversals
365 | * fill this readme and provide other documentation, or how-tos, e.g. a blog post or tutorial
366 |
367 | ### Why such a long version number?
368 | The first three digits is the TP3 version number, only the last digit is automatically incremented on every release of gremlin-scala.
369 |
370 | ### Talks
371 |
372 |
373 |
374 |
375 | ### Further reading
376 | For more information about Gremlin see the [Gremlin docs](http://tinkerpop.incubator.apache.org/) and the [Gremlin users mailinglist](https://groups.google.com/forum/#!forum/gremlin-users).
377 | Please note that while Gremlin-Scala is very close to the original Gremlin, there are differences to Gremlin-Groovy - don't be afraid, they hopefully all make sense to a Scala developer ;)
378 |
379 | Random links:
380 | * Social Network using Titan Db: [part 1](https://partialflow.wordpress.com/2017/02/26/social-network-using-titan-db-part-1/) and [part 2](https://partialflow.wordpress.com/2017/03/04/social-network-using-titan-db-part-2/)
381 | * [Shortest path algorithm with Gremlin-Scala 3.0.0 (Michael Pollmeier)](http://www.michaelpollmeier.com/2014/12/27/gremlin-scala-shortest-path)
382 | * [Shortest path algorithm with Gremlin-Scala 2.4.1 (Stefan Bleibinhaus)](http://bleibinha.us/blog/2013/10/scala-and-graph-databases-with-gremlin-scala)
383 |
384 | ### Random things worth knowing
385 | * `org.apache.tinkerpop.gremlin.structure.Transaction` is not thread-safe. It's implemented with Apache's ThreadLocal class, see https://github.com/mpollmeier/gremlin-scala/issues/196#issuecomment-301625679
386 |
387 | ### Releases
388 | ... happen automatically for every commit on `master` from [travis.ci](https://travis-ci.org/mpollmeier/gremlin-scala) thanks to [sbt-ci-release-early](https://github.com/ShiftLeftSecurity/sbt-ci-release-early)
389 |
390 | ### YourKit endorsement
391 | YourKit supports open source projects with innovative and intelligent tools for monitoring and profiling Java and .NET applications.
392 | YourKit is the creator of YourKit Java Profiler,
393 | YourKit .NET Profiler, and YourKit YouMonitor.
394 |
395 |
396 | ### Breaking changes
397 | #### 3.4.7.2
398 | Marshallable now treats Sets as multi-properties, i.e. one property for each element in the set. This is similar to how
399 | List properties are handled and allows for a natural representation of vertex properties whose cardinality is `set`
400 | in case classes. This change breaks compatibility with Marshallable's previous behaviour in which Sets were effectively
401 | treated as `single` cardinality properties, i.e. a single property whose value is the entire set.
402 |
403 | #### 3.4.1.13
404 | The implementation for `@label` with non literal values (e.g. `@label(SomeClass.LABEL)`) was dropped due to it's [bad performance](https://github.com/mpollmeier/gremlin-scala/issues/288). Please use String literals instead, e.g. `@label("MyLabel")`.
405 |
406 | #### 3.3.3.2
407 | We now have a fully typed `union` step which supports heterogeneous queries. The old version is still available as `unionFlat`, since it may still be relevant in some situations where the union traversals are homogeneous.
408 |
409 | #### 3.3.2.0
410 | The `by` modulator is now called `By`. E.g. `order(by(Order.decr))` becomes `order(By(Order.decr))`.
411 | Background: case insensitive platforms like OSX (default) and Windows fail to compile `object by` and `trait By` because they lead to two separate .class files. I decided for this option because it conforms to Scala's naming best practices.
412 | See https://github.com/mpollmeier/gremlin-scala/issues/237#issuecomment-375928284.
413 |
414 | #### 3.3.1.2
415 | To fix problems with remote graphs and the arrow syntax (e.g. `vertex1 --- "label" --> vertex2`) there now needs to be an `implicit ScalaGraph` in scope. Background: the configuration for remote is unfortunately not stored in the Tinkerpop Graph instance, but in the TraversalSource. Since a vertex only holds a reference to the graph instance, this configuration must be passed somehow. `ScalaGraph` does contain the configuration, e.g. for remote connections, so we now pass it implicitly.
416 |
417 | #### 3.3.1.1
418 | The type signature of GremlinScala changed: the former type parameter `Labels` is now a type member, which shortens the type if you don't care about Labels. The Labels were only used in a small percentage of steps, but had to be written out by users all the time even if they didn't care.
419 | Rewrite rules (old -> new), using `Vertex` as an example:
420 | `GremlinScala[Vertex, _]` -> `GremlinScala[Vertex]` (existential type: most common, i.e the user doesn't use or care about the Labels)
421 | `GremlinScala[Vertex, HNil]` -> `GremlinScala.Aux[Vertex, HNil]` (equivalent: `GremlinScala[Vertex] {type Labels = HNil}`)
422 | `GremlinScala[Vertex, Vertex :: HNil]` -> `GremlinScala.Aux[Vertex, Vertex :: HNil]` (equivalent: `GremlinScala[Vertex] {type Labels = Vertex :: HNil}`)
423 | Notice: GremlinScala isn't a case class any more - it shouldn't have been in the first place.
424 |
425 | #### 3.2.4.8
426 | The `filter` step changed it's signature and now takes a traversal: `filter(predicate: GremlinScala[End, _] => GremlinScala[_, _])`. The old `filter(predicate: End => Boolean)` is now called `filterOnEnd`, in case you still need it. This change might affect your for comprehensions.
427 |
428 | The reasoning for the change is that it's discouraged to use lambdas (see http://tinkerpop.apache.org/docs/current/reference/#a-note-on-lambdas). Instead we are now creating anonymous traversals, which can be optimised by the driver, sent over the wire as gremlin binary for remote execution etc.
429 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | val gremlinVersion = "3.5.3"
2 | val scalaTestVersion = "3.2.12"
3 |
4 | ThisBuild / organization := "com.michaelpollmeier"
5 | ThisBuild / scalaVersion := "2.13.8"
6 | ThisBuild / crossScalaVersions := Seq("2.11.12", "2.12.15", "2.13.8")
7 |
8 | ThisBuild / libraryDependencies ++= Seq(
9 | "org.apache.tinkerpop" % "gremlin-core" % gremlinVersion,
10 | "com.chuusai" %% "shapeless" % "2.3.9",
11 | "org.slf4j" % "slf4j-nop" % "1.7.36" % Test,
12 | "org.apache.tinkerpop" % "tinkergraph-gremlin" % gremlinVersion % Test,
13 | "org.apache.tinkerpop" % "gremlin-test" % gremlinVersion % Test,
14 | "org.scalatest" %% "scalatest-shouldmatchers" % scalaTestVersion % Test,
15 | "org.scalatest" %% "scalatest-wordspec" % scalaTestVersion % Test,
16 | "org.scalatest" %% "scalatest-funspec" % scalaTestVersion % Test,
17 | "org.scalamock" %% "scalamock" % "5.2.0" % Test
18 | )
19 | ThisBuild / resolvers += "Apache public".at("https://repository.apache.org/content/groups/public/")
20 | ThisBuild / resolvers += Resolver.mavenLocal
21 | ThisBuild / scalacOptions ++= Seq(
22 | // "-Xlint"
23 | // "-Xfatal-warnings",
24 | // , "-Xlog-implicits"
25 | //"-Ydebug",
26 | "-target:jvm-1.8",
27 | "-language:implicitConversions",
28 | "-language:existentials",
29 | "-feature",
30 | "-deprecation" //hard to handle when supporting multiple scala versions...
31 | )
32 |
33 | ThisBuild / Test / console / initialCommands :=
34 | """|import gremlin.scala._
35 | |import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory
36 | |import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph
37 | |import org.apache.tinkerpop.gremlin.process.traversal.{Order, P, Scope}
38 | |implicit val graph = TinkerFactory.createModern.asScala
39 | |val g = graph.traversal""".stripMargin
40 |
41 | ThisBuild / scmInfo := Some(
42 | ScmInfo(url("https://github.com/mpollmeier/gremlin-scala"),
43 | "scm:git@github.com:mpollmeier/gremlin-scala.git"))
44 | ThisBuild / developers := List(
45 | Developer("mpollmeier",
46 | "Michael Pollmeier",
47 | "michael@michaelpollmeier.com",
48 | url("https://michaelpollmeier.com")))
49 | ThisBuild / homepage := Some(url("https://github.com/mpollmeier/gremlin-scala"))
50 | ThisBuild / licenses += ("Apache-2.0", url("https://www.apache.org/licenses/LICENSE-2.0.html"))
51 | ThisBuild / publishTo := sonatypePublishToBundle.value
52 |
53 | // virtual root project
54 | name := "root"
55 | publish / skip := true
56 |
57 | lazy val macros = project // macros must be in a separate compilation unit
58 | .in(file("macros"))
59 | .settings(libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value)
60 | lazy val `gremlin-scala` = project.in(file("gremlin-scala")).dependsOn(macros)
61 |
--------------------------------------------------------------------------------
/doc/images/gremlin-scala-logo-hd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpollmeier/gremlin-scala/b68ca4e1cc6b050c0e98966af8c08c17ad984156/doc/images/gremlin-scala-logo-hd.png
--------------------------------------------------------------------------------
/doc/images/gremlin-scala-logo-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpollmeier/gremlin-scala/b68ca4e1cc6b050c0e98966af8c08c17ad984156/doc/images/gremlin-scala-logo-white.png
--------------------------------------------------------------------------------
/doc/images/gremlin-scala-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpollmeier/gremlin-scala/b68ca4e1cc6b050c0e98966af8c08c17ad984156/doc/images/gremlin-scala-logo.png
--------------------------------------------------------------------------------
/doc/images/scaladays-talk-shot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpollmeier/gremlin-scala/b68ca4e1cc6b050c0e98966af8c08c17ad984156/doc/images/scaladays-talk-shot.png
--------------------------------------------------------------------------------
/doc/images/yourkit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpollmeier/gremlin-scala/b68ca4e1cc6b050c0e98966af8c08c17ad984156/doc/images/yourkit.png
--------------------------------------------------------------------------------
/gremlin-scala.bat:
--------------------------------------------------------------------------------
1 | :: Windows launcher script for Gremlin
2 | @echo off
3 |
4 | cd %CD%\target\
5 |
6 | set TARGET=
7 |
8 | for /f "tokens=*" %%a in ('dir /b /ad') do (
9 | if exist "%%a\bin\gremlin-scala.bat" set TARGET=%%a
10 | )
11 |
12 | cd %TARGET%\bin\
13 | call gremlin-scala.bat %*
14 |
--------------------------------------------------------------------------------
/gremlin-scala.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | `dirname $0`/target/gremlin-scala-*-standalone/bin/gremlin-scala.sh $@
--------------------------------------------------------------------------------
/gremlin-scala/src/assembly/distribution.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | zip
4 |
5 |
6 |
7 |
8 | pom.xml
9 |
10 |
11 |
12 | 0775
13 |
14 | gremlin-scala.sh
15 | gremlin-scala.bat
16 |
17 |
18 |
19 | src
20 |
21 |
22 | data
23 |
24 |
25 | target/apidocs
26 |
27 |
28 | target/site
29 |
30 |
31 | target/${project.artifactId}-${project.version}-standalone
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/gremlin-scala/src/assembly/standalone.xml:
--------------------------------------------------------------------------------
1 |
2 | standalone
3 |
4 | dir
5 |
6 | false
7 |
8 |
9 |
10 | src/main/bin
11 | /bin
12 | 0755
13 |
14 |
15 | target/*.jar
16 | /lib
17 |
18 |
19 |
20 |
21 |
22 | /lib
23 | false
24 | compile
25 |
26 |
27 | /lib
28 | false
29 | provided
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/bin/gremlin-scala.bat:
--------------------------------------------------------------------------------
1 | :: Windows launcher script for Gremlin Scala
2 |
3 | @echo off
4 |
5 |
6 | ::cd ..\lib
7 |
8 | ::set LIBDIR=%CD%
9 |
10 |
11 | set LIBDIR=..\lib
12 |
13 |
14 | set OLD_CLASSPATH=%CLASSPATH%
15 | set CP=
16 |
17 |
18 | for %%i in (%LIBDIR%\*.jar) do call :concatsep %%i
19 |
20 | :: cd ..\..\..\
21 |
22 |
23 | set JAVA_OPTIONS=-Xms32m -Xmx512m
24 |
25 |
26 | :: Launch the application
27 |
28 | if "%1" == "" goto console
29 |
30 | if "%1" == "-v" goto version
31 |
32 |
33 |
34 | :console
35 |
36 | set CLASSPATH=%CP%;%OLD_CLASSPATH%
37 | java %JAVA_OPTIONS% %JAVA_ARGS% com.tinkerpop.gremlin.scala.console.Console
38 | set CLASSPATH=%OLD_CLASSPATH%
39 |
40 | set CLASSPATH=%OLD_CLASSPATH%
41 | goto :eof
42 |
43 |
44 |
45 | :version
46 |
47 | set CLASSPATH=%CP%;%OLD_CLASSPATH%
48 | java %JAVA_OPTIONS% %JAVA_ARGS% com.tinkerpop.gremlin.Version
49 |
50 | set CLASSPATH=%OLD_CLASSPATH%
51 | goto :eof
52 |
53 |
54 |
55 | :concat
56 |
57 | if %1 == %2 goto skip
58 |
59 | SET strg=%strg% %1
60 |
61 |
62 |
63 | :concatsep
64 |
65 | if "%CP%" == "" (
66 |
67 | set CP=%LIBDIR%\%1
68 |
69 | )else (
70 |
71 | set CP=%CP%;%LIBDIR%\%1
72 |
73 | )
74 |
75 |
76 |
77 | :skip
78 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/bin/gremlin-scala.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | CP=$( echo `dirname $0`/../lib/*.jar . | sed 's/ /:/g')
4 | #echo $CP
5 |
6 | # Find Java
7 | if [ "$JAVA_HOME" = "" ] ; then
8 | JAVA="java -server"
9 | else
10 | JAVA="$JAVA_HOME/bin/java -server"
11 | fi
12 |
13 | # Set Java options
14 | if [ "$JAVA_OPTIONS" = "" ] ; then
15 | JAVA_OPTIONS="-Xms32m -Xmx512m"
16 | fi
17 |
18 | # Launch the application
19 | if [ "$1" = "-v" ]; then
20 | $JAVA $JAVA_OPTIONS -cp $CP:$CLASSPATH com.tinkerpop.gremlin.Version
21 | else
22 | $JAVA $JAVA_OPTIONS -cp $CP:$CLASSPATH com.tinkerpop.gremlin.scala.console.Console
23 | fi
24 |
25 | #Something in JLine 2.9.1 is leaving stty in "no echo" (-echo) mode; try to repair that on exit...
26 | stty echo
27 |
28 | # Return the program's exit code
29 | exit $?
30 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/BranchOption.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent.Pick
4 |
5 | /** Define the traversal to run if the given predicate is true - used in branch step
6 | *
7 | * you might think that traversal should be `GremlinScala[End, _] => GremlinScala[Boolean, _]`,
8 | * but that's not how tp3 works: e.g. `.value(Age).is(30)` returns `30`, not `true`
9 | */
10 | trait BranchOption[End, NewEnd] {
11 | def traversal: GremlinScala[End] => GremlinScala[NewEnd]
12 | def pickToken: Any
13 | }
14 |
15 | case class BranchCase[BranchOn, End, NewEnd](pickToken: BranchOn,
16 | traversal: GremlinScala[End] => GremlinScala[NewEnd])
17 | extends BranchOption[End, NewEnd]
18 |
19 | case class BranchMatchAll[End, NewEnd](traversal: GremlinScala[End] => GremlinScala[NewEnd])
20 | extends BranchOption[End, NewEnd] {
21 | override def pickToken = Pick.any
22 | }
23 |
24 | /* if nothing else matched in branch/choose step */
25 | case class BranchOtherwise[End, NewEnd](traversal: GremlinScala[End] => GremlinScala[NewEnd])
26 | extends BranchOption[End, NewEnd] {
27 | override def pickToken = Pick.none
28 | }
29 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/By.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.tinkerpop.gremlin.process.traversal.Order
4 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal
5 | import org.apache.tinkerpop.gremlin.structure.T
6 | import java.util.function.{Function => JFunction}
7 | import java.util.{Collection => JCollection}
8 |
9 | /**
10 | * By step can be used in combination with all sorts of other steps
11 | * e.g. group, groupCount, order, dedup, sack, ...
12 | * http://tinkerpop.apache.org/docs/current/reference/#by-step
13 | * n.b. `By` can be used in place of `OrderBy`, hence extending OrderBy */
14 | trait By[Modulated] extends OrderBy[Modulated] {
15 |
16 | /** When used as the latter By of group method */
17 | type ValueFold[X]
18 | def apply[End](traversal: GraphTraversal[_, End]): GraphTraversal[_, End]
19 | }
20 | trait OrderBy[Modulated] {
21 | def apply[End](traversal: GraphTraversal[_, End]): GraphTraversal[_, End]
22 | }
23 |
24 | object By {
25 |
26 | /* modulate by property */
27 | def apply[Modulated](key: Key[Modulated]) = new By[Modulated] {
28 | override type ValueFold[A] = JCollection[A]
29 | override def apply[End](traversal: GraphTraversal[_, End]) =
30 | traversal.by(key.name)
31 | }
32 |
33 | /* modulate by property and order */
34 | def apply[Modulated](key: Key[Modulated], order: Order) =
35 | new OrderBy[Modulated] {
36 | override def apply[End](traversal: GraphTraversal[_, End]) =
37 | traversal.by(key.name, order)
38 | }
39 |
40 | /* modulate by label - alias for `apply[String](T.label)` */
41 | def label = new By[Label] {
42 | override type ValueFold[A] = JCollection[A]
43 | override def apply[End](traversal: GraphTraversal[_, End]) =
44 | traversal.by(T.label)
45 | }
46 |
47 | /* modulate by label and order - alias for `apply[String](T.label, Order)` */
48 | def label(order: Order) = new OrderBy[Label] {
49 | override def apply[End](traversal: GraphTraversal[_, End]) =
50 | traversal.by(T.label, order)
51 | }
52 |
53 | /* modulate by T(oken) */
54 | def apply[Modulated](token: T) = new By[Modulated] {
55 | override type ValueFold[A] = JCollection[A]
56 | override def apply[End](traversal: GraphTraversal[_, End]) =
57 | traversal.by(token)
58 | }
59 |
60 | /* modulate by T(oken) and order */
61 | def apply[Modulated](token: T, order: Order) = new OrderBy[Modulated] {
62 | override def apply[End](traversal: GraphTraversal[_, End]) =
63 | traversal.by(token, order)
64 | }
65 |
66 | /* modulate by anonymous traversal, e.g. __.inE.value(Name) */
67 | def apply[Modulated](by: GremlinScala[Modulated]) = new By[Modulated] {
68 | override type ValueFold[A] = A
69 | override def apply[End](traversal: GraphTraversal[_, End]) =
70 | traversal.by(by.traversal)
71 | }
72 |
73 | /* modulate by anonymous traversal and order, e.g. (__.inE.value(Name), Order.decr) */
74 | def apply[Modulated](by: GremlinScala[Modulated], order: Order) =
75 | new OrderBy[Modulated] {
76 | override def apply[End](traversal: GraphTraversal[_, End]) =
77 | traversal.by(by.traversal, order)
78 | }
79 |
80 | /* modulate by function
81 | * n.b. prefer one of the other modulators, see http://tinkerpop.apache.org/docs/current/reference/#a-note-on-lambdas */
82 | def apply[From, Modulated](fun: From => Modulated) = new By[Modulated] {
83 | override type ValueFold[A] = JCollection[A]
84 | override def apply[End](traversal: GraphTraversal[_, End]) =
85 | traversal.by[From](new JFunction[From, AnyRef] {
86 | override def apply(from: From): AnyRef = fun(from).asInstanceOf[AnyRef]
87 | })
88 | }
89 |
90 | /* modulate by function and order
91 | * n.b. prefer one of the other modulators, see http://tinkerpop.apache.org/docs/current/reference/#a-note-on-lambdas */
92 | def apply[From, Modulated](fun: From => Modulated, order: Order) =
93 | new OrderBy[Modulated] {
94 | override def apply[End](traversal: GraphTraversal[_, End]) =
95 | traversal.by[From](
96 | new JFunction[From, AnyRef] {
97 | override def apply(from: From): AnyRef =
98 | fun(from).asInstanceOf[AnyRef]
99 | },
100 | order
101 | )
102 | }
103 |
104 | def apply[Modulated](order: Order) = new OrderBy[Modulated] {
105 | override def apply[End](traversal: GraphTraversal[_, End]) =
106 | traversal.by(order)
107 | }
108 |
109 | /* identity modulator */
110 | def apply[Modulated]() = new By[Modulated] {
111 | override type ValueFold[A] = JCollection[A]
112 | override def apply[End](traversal: GraphTraversal[_, End]) = traversal.by()
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/DefaultsToAny.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | sealed class DefaultsToAny[A]
4 |
5 | object DefaultsToAny {
6 | implicit def overrideDefault[A] = new DefaultsToAny[A]
7 |
8 | implicit def default = new DefaultsToAny[Any]
9 | }
10 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/DetachedElements.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import scala.collection.JavaConverters._
4 |
5 | // useful e.g. for optional step with a default
6 |
7 | object DetachedVertex {
8 | def apply(id: AnyRef = "detached",
9 | label: String = "detached",
10 | properties: Map[String, AnyRef] = Map.empty) =
11 | new org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex(id,
12 | label,
13 | properties.asJava)
14 | }
15 |
16 | object DetachedEdge {
17 | def apply(id: AnyRef = "detached",
18 | label: String = "detached",
19 | properties: Map[String, AnyRef] = Map.empty,
20 | outVId: AnyRef = "",
21 | outVLabel: String = "",
22 | inVId: AnyRef = "",
23 | inVLabel: String = "") =
24 | new org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge(id,
25 | label,
26 | properties.asJava,
27 | outVId,
28 | outVLabel,
29 | inVId,
30 | inVLabel)
31 | }
32 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/GraphHelper.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedFactory
4 | import org.apache.tinkerpop.gremlin.structure.util.Attachable
5 | import scala.collection.JavaConverters._
6 |
7 | object GraphHelper {
8 |
9 | def cloneElements(original: ScalaGraph, clone: ScalaGraph): ScalaGraph = {
10 | cloneElements(original.graph, clone.graph).asScala()
11 | }
12 |
13 | /**
14 | * make a deep clone of the graph elements that preserves ids
15 | */
16 | def cloneElements(original: Graph, clone: Graph): Graph = {
17 | original
18 | .vertices()
19 | .asScala
20 | .foreach(v => DetachedFactory.detach(v, true).attach(Attachable.Method.create(clone)))
21 | original
22 | .edges()
23 | .asScala
24 | .foreach(e => DetachedFactory.detach(e, true).attach(Attachable.Method.create(clone)))
25 | clone
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/P.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.tinkerpop.gremlin.process.traversal.{P => JavaP}
4 | import java.util.{Collection => JCollection}
5 | import scala.collection.JavaConverters._
6 |
7 | /** the scala version of tinkerpop's P, mostly to avoid unnecessarily complicated constructs
8 | * like P.within(vertices.asJava: JCollection[Vertex]) */
9 | object P {
10 | // same as `eq`, in case you're having problems with overloaded definition of `eq`
11 | def is[A](value: A) = JavaP.eq(value)
12 | def eq[A](value: A) = JavaP.eq(value)
13 | def neq[A](value: A) = JavaP.neq(value)
14 | def gt[A](value: A) = JavaP.gt(value)
15 | def gte[A](value: A) = JavaP.gte(value)
16 | def lt[A](value: A) = JavaP.lt(value)
17 | def lte[A](value: A) = JavaP.lte(value)
18 | def between[A](a1: A, a2: A) = JavaP.between(a1, a2)
19 | def inside[A](a1: A, a2: A) = JavaP.inside(a1, a2)
20 | def outside[A](a1: A, a2: A) = JavaP.outside(a1, a2)
21 | def within[A](iterable: Iterable[A]) =
22 | JavaP.within(iterable.asJavaCollection: JCollection[A])
23 | def without[A](iterable: Iterable[A]) =
24 | JavaP.without(iterable.asJavaCollection: JCollection[A])
25 |
26 | def fromPredicate[A](predicate: (A, A) => Boolean, value: A) =
27 | new JavaP(toJavaBiPredicate(predicate), value)
28 | }
29 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/ProjectionBuilder.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import java.util.{Map => JMap, UUID}
4 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal
5 | import shapeless.ops.tuple.{Prepend => TuplePrepend}
6 | import shapeless.syntax.std.tuple._
7 |
8 | class ProjectionBuilder[T <: Product] private[gremlin] (
9 | labels: Seq[String],
10 | addBy: GraphTraversal[_, JMap[String, Any]] => GraphTraversal[_, JMap[String, Any]],
11 | buildResult: JMap[String, Any] => T) {
12 |
13 | def apply[U, TR <: Product](by: By[U])(
14 | implicit prepend: TuplePrepend.Aux[T, Tuple1[U], TR]): ProjectionBuilder[TR] = {
15 | val label = UUID.randomUUID().toString
16 | new ProjectionBuilder[TR](labels :+ label,
17 | addBy.andThen(by.apply),
18 | map => buildResult(map) :+ map.get(label).asInstanceOf[U])
19 | }
20 |
21 | def and[U, TR <: Product](by: By[U])(
22 | implicit prepend: TuplePrepend.Aux[T, Tuple1[U], TR]): ProjectionBuilder[TR] = apply(by)
23 |
24 | private[gremlin] def build(g: GremlinScala[_]): GremlinScala[T] = {
25 | GremlinScala(addBy(g.traversal.project(labels.head, labels.tail: _*))).map(buildResult)
26 | }
27 | }
28 |
29 | object ProjectionBuilder {
30 | def apply() = new ProjectionBuilder[Nil.type](Nil, scala.Predef.identity, _ => Nil)
31 | }
32 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/ScalaEdge.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import scala.collection.JavaConverters._
4 |
5 | case class ScalaEdge(edge: Edge) extends ScalaElement[Edge] {
6 | override def element = edge
7 |
8 | override def setProperty[A](key: Key[A], value: A): Edge = {
9 | element.property(key.name, value)
10 | edge
11 | }
12 |
13 | def setProperties(properties: Map[Key[Any], Any]): Edge = {
14 | properties.foreach { case (k, v) => setProperty(k, v) }
15 | edge
16 | }
17 |
18 | def setProperties[CC <: Product: Marshallable](cc: CC): Edge = {
19 | val fromCC = implicitly[Marshallable[CC]].fromCC(cc)
20 | fromCC.properties.foreach { case (k, v) => element.property(k, v) }
21 | edge
22 | }
23 |
24 | override def removeProperty(key: Key[_]): Edge = {
25 | val p = property(key)
26 | if (p.isPresent) p.remove()
27 | edge
28 | }
29 |
30 | override def removeProperties(keys: Key[_]*): Edge = {
31 | keys.foreach(removeProperty)
32 | edge
33 | }
34 |
35 | def toCC[CC <: Product: Marshallable] =
36 | implicitly[Marshallable[CC]].toCC(edge)
37 |
38 | override def properties[A: DefaultsToAny]: Stream[Property[A]] =
39 | edge.properties[A](keys.map(_.name).toSeq: _*).asScala.toStream
40 |
41 | override def properties[A: DefaultsToAny](wantedKeys: String*): Stream[Property[A]] =
42 | edge.properties[A](wantedKeys: _*).asScala.toStream
43 |
44 | //TODO: wait until this is consistent in T3 between Vertex and Edge
45 | //currently Vertex.outE returns a GraphTraversal, Edge.inV doesnt quite exist
46 | //def inV() = GremlinScala[Vertex, HNil](edge.inV())
47 | }
48 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/ScalaElement.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import scala.collection.JavaConverters._
4 | import shapeless.HNil
5 |
6 | trait ScalaElement[ElementType <: Element] {
7 | def element: ElementType
8 |
9 | def graph: ScalaGraph = element.graph
10 |
11 | /** start a new traversal from this element */
12 | def start(): GremlinScala.Aux[ElementType, HNil] = element.graph().inject(element)
13 |
14 | /** start a new traversal from this element and configure it */
15 | def start(configure: TraversalSource => TraversalSource): GremlinScala.Aux[ElementType, HNil] =
16 | GremlinScala[ElementType, HNil](
17 | configure(TraversalSource(element.graph)).underlying.inject(element)
18 | )
19 |
20 | def id[A: DefaultsToAny]: A = element.id.asInstanceOf[A]
21 |
22 | def label: String = element.label
23 |
24 | def keys: Set[Key[Any]] = element.keys.asScala.toSet.map(Key.apply[Any])
25 |
26 | def setProperty[A](key: Key[A], value: A): ElementType
27 |
28 | def removeProperty(key: Key[_]): ElementType
29 |
30 | def removeProperties(keys: Key[_]*): ElementType
31 |
32 | def property[A](key: Key[A]): Property[A] = element.property[A](key.name)
33 |
34 | def properties[A: DefaultsToAny]: Stream[Property[A]]
35 |
36 | def properties[A: DefaultsToAny](keys: String*): Stream[Property[A]]
37 |
38 | // note: this may throw an IllegalStateException - better use `valueOption` or `Property`
39 | def value[A: DefaultsToAny](key: String): A =
40 | element.value[A](key)
41 |
42 | // typesafe version of `value. have to call it `value2` because of a scala compiler bug :(
43 | // https://issues.scala-lang.org/browse/SI-9523
44 | def value2[A](key: Key[A]): A =
45 | element.value[A](key.name)
46 |
47 | def valueOption[A: DefaultsToAny](key: String): Option[A] =
48 | element.property[A](key).toOption
49 |
50 | def valueOption[A](key: Key[A]): Option[A] =
51 | element.property[A](key.name).toOption
52 |
53 | def valueOption[A](key: Key[A], value: Option[A]): ElementType = value match {
54 | case Some(v) => setProperty(key, v)
55 | case None => removeProperty(key)
56 | }
57 |
58 | // note: this may throw an IllegalStateException - better use `Property`
59 | def values[A: DefaultsToAny](keys: String*): Iterator[A] =
60 | element.values[A](keys: _*).asScala
61 |
62 | def valueMap[A: DefaultsToAny]: Map[String, A] =
63 | valueMap[A](keys.toSeq.map(_.name): _*)
64 |
65 | def valueMap[A: DefaultsToAny](keys: String*): Map[String, A] =
66 | properties[A](keys: _*).map(p => (p.key, p.value)).toMap
67 |
68 | def toCC[CC <: Product: Marshallable]: CC
69 |
70 | def updateWith[CC <: Product: Marshallable](update: CC): ElementType = {
71 | val properties: List[(String, Any)] = implicitly[Marshallable[CC]].fromCC(update).properties
72 | val propMap = properties.toMap
73 | this.valueMap.keySet.diff(propMap.keySet).foreach { key =>
74 | val prop = element.property(key)
75 | if (prop.isPresent) prop.remove()
76 | }
77 | propMap.foreach { case (key, value) => element.property(key, value) }
78 |
79 | element
80 | }
81 |
82 | def updateAs[CC <: Product: Marshallable](f: CC => CC): ElementType =
83 | updateWith(f(toCC[CC]))
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/ScalaGraph.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.commons.configuration2.Configuration
4 | import org.apache.tinkerpop.gremlin.process.computer.GraphComputer
5 | import org.apache.tinkerpop.gremlin.structure.Graph.Variables
6 | import org.apache.tinkerpop.gremlin.structure.{T, Transaction}
7 | import shapeless._
8 |
9 | object ScalaGraph {
10 | def apply(graph: Graph): ScalaGraph =
11 | ScalaGraph(TraversalSource(graph))
12 | }
13 |
14 | case class ScalaGraph(traversalSource: TraversalSource) {
15 | lazy val traversal = traversalSource
16 | lazy val graph = traversalSource.graph
17 |
18 | def configure(conf: TraversalSource => TraversalSource) =
19 | ScalaGraph(conf(TraversalSource(graph)))
20 |
21 | def addVertex(): Vertex =
22 | traversalSource.underlying.addV().next
23 |
24 | def addVertex(label: String): Vertex =
25 | traversalSource.underlying.addV(label).next
26 |
27 | def addVertex(properties: (String, Any)*): Vertex = {
28 | val traversal = traversalSource.underlying.addV()
29 | properties.foreach { case (key, value) => traversal.property(key, value) }
30 | traversal.next
31 | }
32 |
33 | def addVertex(label: String, properties: (String, Any)*): Vertex = {
34 | val traversal = traversalSource.underlying.addV(label)
35 | properties.foreach { case (key, value) => traversal.property(key, value) }
36 | traversal.next
37 | }
38 |
39 | def addVertex(label: String, properties: Map[String, Any]): Vertex =
40 | addVertex(label, properties.toSeq: _*)
41 |
42 | def addVertex(properties: Map[String, Any]): Vertex =
43 | addVertex(properties.toSeq: _*)
44 |
45 | /**
46 | * Save an object's values as a new vertex
47 | * Note: `@id` members cannot be set for all graphs (e.g. remote graphs), so it is ignored here generally
48 | */
49 | def addVertex[CC <: Product: Marshallable](cc: CC): Vertex = {
50 | val fromCC = implicitly[Marshallable[CC]].fromCC(cc)
51 | addVertex(fromCC.label, fromCC.properties: _*)
52 | }
53 |
54 | def +[CC <: Product: Marshallable](cc: CC): Vertex = addVertex(cc)
55 |
56 | def +(label: String): Vertex = addVertex(label)
57 |
58 | def +(label: String, properties: KeyValue[_]*): Vertex =
59 | addVertex(label, properties.map(v => (v.key.name, v.value)).toMap)
60 |
61 | /** start a traversal with `addV` */
62 | def addV(): GremlinScala.Aux[Vertex, HNil] =
63 | traversalSource.addV()
64 |
65 | /** start a traversal with `addV` */
66 | def addV(label: String): GremlinScala.Aux[Vertex, HNil] =
67 | traversalSource.addV(label)
68 |
69 | /** start a traversal with `addV` */
70 | def addE(label: String): GremlinScala.Aux[Edge, HNil] =
71 | traversalSource.addE(label)
72 |
73 | /** start a traversal with given `starts`` */
74 | def inject[S](starts: S*): GremlinScala.Aux[S, HNil] =
75 | traversalSource.inject(starts: _*)
76 |
77 | /** start traversal with all vertices */
78 | def V(): GremlinScala.Aux[Vertex, HNil] =
79 | traversalSource.V()
80 |
81 | /** start traversal with all edges */
82 | def E(): GremlinScala.Aux[Edge, HNil] =
83 | traversalSource.E()
84 |
85 | /** start traversal with some vertices identified by given ids */
86 | def V(vertexIds: Any*): GremlinScala.Aux[Vertex, HNil] =
87 | traversalSource.V(vertexIds: _*)
88 |
89 | /** start traversal with some edges identified by given ids */
90 | def E(edgeIds: Any*): GremlinScala.Aux[Edge, HNil] =
91 | traversalSource.E(edgeIds: _*)
92 |
93 | def tx(): Transaction = graph.tx()
94 |
95 | def variables(): Variables = graph.variables()
96 |
97 | def configuration(): Configuration = graph.configuration()
98 |
99 | def compute[C <: GraphComputer](graphComputerClass: Class[C]): C =
100 | graph.compute(graphComputerClass)
101 |
102 | def compute(): GraphComputer = graph.compute()
103 |
104 | def close(): Unit = graph.close()
105 |
106 | /* TODO: reimplement with createThreadedTx, if the underlying graph supports it */
107 | // def transactional[R](work: Graph => R) = graph.tx.submit(work)
108 | }
109 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/ScalaVertex.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import java.util
4 | import org.apache.tinkerpop.gremlin.structure.VertexProperty.Cardinality
5 | import org.apache.tinkerpop.gremlin.structure.{Direction, T, VertexProperty}
6 | import scala.collection.JavaConverters._
7 |
8 | case class ScalaVertex(vertex: Vertex) extends ScalaElement[Vertex] {
9 | override def element = vertex
10 |
11 | def toCC[CC <: Product: Marshallable] =
12 | implicitly[Marshallable[CC]].toCC(vertex)
13 |
14 | override def setProperty[A](key: Key[A], value: A): Vertex = {
15 | element.property(key.name, value)
16 | vertex
17 | }
18 |
19 | def setProperties(properties: Map[Key[Any], Any]): Vertex = {
20 | properties.foreach { case (k, v) => setProperty(k, v) }
21 | vertex
22 | }
23 |
24 | def setProperties[CC <: Product: Marshallable](cc: CC): Vertex = {
25 | val fromCC = implicitly[Marshallable[CC]].fromCC(cc)
26 | fromCC.properties.foreach { case (k, v) => setProperty(Key[Any](k), v) }
27 | vertex
28 | }
29 |
30 | override def removeProperty(key: Key[_]): Vertex =
31 | removeProperty(key, Cardinality.single)
32 |
33 | def removeProperty(key: Key[_], cardinality: Cardinality): Vertex = {
34 | cardinality match {
35 | case Cardinality.single =>
36 | val p = property(key)
37 | if (p.isPresent) p.remove
38 | case Cardinality.list | Cardinality.set =>
39 | vertex.properties(key.name).asScala.foreach { p: VertexProperty[_] =>
40 | p.remove
41 | }
42 | }
43 | vertex
44 | }
45 |
46 | override def removeProperties(keys: Key[_]*): Vertex = {
47 | keys.foreach(removeProperty)
48 | vertex
49 | }
50 |
51 | def out() = start().out()
52 |
53 | def out(labels: String*) = start().out(labels: _*)
54 |
55 | def outE() = start().outE()
56 |
57 | def outE(labels: String*) = start().outE(labels: _*)
58 |
59 | def in() = start().in()
60 |
61 | def in(labels: String*) = start().in(labels: _*)
62 |
63 | def inE() = start().inE()
64 |
65 | def inE(labels: String*) = start().inE(labels: _*)
66 |
67 | def both() = start().both()
68 |
69 | def both(labels: String*) = start().both(labels: _*)
70 |
71 | def bothE() = start().bothE()
72 |
73 | def bothE(labels: String*) = start().bothE(labels: _*)
74 |
75 | /** `implicit ScalaGraph` required for configuration, e.g. when using remote graph */
76 | def addEdge(label: String, inVertex: Vertex, properties: KeyValue[_]*)(implicit graph: ScalaGraph): Edge =
77 | graph.traversal.V(vertex).addE(label, properties: _*).to(inVertex).head()
78 |
79 | def addEdge[CC <: Product: Marshallable](inVertex: Vertex, cc: CC): Edge = {
80 | val fromCC = implicitly[Marshallable[CC]].fromCC(cc)
81 | val idParam = fromCC.id.toSeq.flatMap(List(T.id, _))
82 | val params = fromCC.properties.flatMap(pair => Seq(pair._1, pair._2.asInstanceOf[AnyRef]))
83 | vertex.addEdge(fromCC.label, inVertex.vertex, idParam ++ params: _*)
84 | }
85 |
86 | /** `implicit ScalaGraph` required for configuration, e.g. when using remote graph */
87 | def <--(se: SemiEdge)(implicit graph: ScalaGraph): Edge =
88 | se.from.asScala().addEdge(se.label, vertex, se.properties: _*)
89 |
90 | /** `implicit ScalaGraph` required for configuration, e.g. when using remote graph */
91 | def <--(de: SemiDoubleEdge)(implicit graph: ScalaGraph): (Edge, Edge) =
92 | addEdge(de.label, de.right, de.properties: _*) -> de.right.asScala()
93 | .addEdge(de.label, vertex, de.properties: _*)
94 |
95 | def ---(label: String): SemiEdge = SemiEdge(vertex, label)
96 |
97 | def ---(label: String, properties: KeyValue[_]*): SemiEdge =
98 | SemiEdge(vertex, label, properties: _*)
99 |
100 | def ---(label: String, properties: Map[String, Any]): SemiEdge =
101 | SemiEdge(vertex, label, properties.map {
102 | case (key, value) => Key[Any](key) -> value
103 | }.toSeq: _*)
104 |
105 | def ---[CC <: Product: Marshallable](cc: CC): SemiEdge = {
106 | val fromCC = implicitly[Marshallable[CC]].fromCC(cc)
107 | SemiEdge(vertex, fromCC.label, fromCC.properties.map { r =>
108 | Key[Any](r._1) -> r._2
109 | }.toSeq: _*)
110 | }
111 |
112 | def vertices(direction: Direction, edgeLabels: String*): util.Iterator[Vertex] =
113 | vertex.vertices(direction, edgeLabels: _*)
114 |
115 | def edges(direction: Direction, edgeLabels: String*): util.Iterator[Edge] =
116 | vertex.edges(direction, edgeLabels: _*)
117 |
118 | def property[A](cardinality: Cardinality,
119 | key: Key[A],
120 | value: A,
121 | keyValues: AnyRef*): VertexProperty[A] =
122 | vertex.property(cardinality, key.name, value, keyValues: _*)
123 |
124 | /** convenience function for `property` which normally requires to pass in key/value pairs as varargs */
125 | def setPropertyList[A <: AnyRef](key: String, values: List[A]): VertexProperty[A] = {
126 | removeProperty(Key[A](key), Cardinality.list)
127 | values
128 | .map { value =>
129 | vertex.property(Cardinality.list, key, value)
130 | }
131 | .lastOption
132 | .getOrElse(VertexProperty.empty[A])
133 | }
134 | /* TODO: looks like there's a bug in tinkerpop - passing multiple values with the same key doesn't result in all those values being set:
135 | values match {
136 | case Nil =>
137 | removeProperty(key)
138 | VertexProperty.empty[A]
139 | case headValue :: tailValues =>
140 | val varargs = tailValues.flatMap(value => List(key.name, value))
141 | vertex.property(Cardinality.list, key.name, headValue, varargs: _*)
142 | }
143 | */
144 |
145 | def setPropertyList[A <: AnyRef](key: Key[A], values: List[A]): VertexProperty[A] =
146 | setPropertyList(key.name, values)
147 |
148 | override def properties[A: DefaultsToAny]: Stream[VertexProperty[A]] =
149 | vertex.properties[A](keys.map(_.name).toSeq: _*).asScala.toStream
150 |
151 | override def properties[A: DefaultsToAny](wantedKeys: String*): Stream[VertexProperty[A]] =
152 | vertex.properties[A](wantedKeys: _*).asScala.toStream
153 | }
154 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/Schema.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | // a type safe key for a property of vertices or edges
4 | case class Key[A](name: String) {
5 | def ->(value: A): KeyValue[A] = KeyValue(this, value)
6 |
7 | def of(value: A): KeyValue[A] = KeyValue(this, value)
8 | }
9 |
10 | case class KeyValue[A](key: Key[A], value: A)
11 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/SelectAllStep.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.tinkerpop.gremlin.process.traversal.{Path, Traversal}
4 | import org.apache.tinkerpop.gremlin.process.traversal.Traverser.Admin
5 | import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent
6 | import org.apache.tinkerpop.gremlin.process.traversal.step.map.ScalarMapStep
7 | import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement
8 |
9 | import scala.collection.JavaConverters._
10 | import shapeless._
11 | import shapeless.ops.hlist._
12 |
13 | class SelectAllStep[S, Labels <: HList, LabelsTuple](traversal: Traversal[_, _])(
14 | implicit tupler: Tupler.Aux[Labels, LabelsTuple])
15 | extends ScalarMapStep[S, LabelsTuple](traversal.asAdmin)
16 | with TraversalParent {
17 |
18 | override def getRequirements = Set(TraverserRequirement.PATH).asJava
19 |
20 | protected def map(traverser: Admin[S]): LabelsTuple = {
21 | val labels: Labels = toHList(toList(traverser.path))
22 | tupler(labels)
23 | }
24 |
25 | def toList(path: Path): List[Any] = {
26 | val labels = path.labels
27 | def hasUserLabel(i: Int) = !labels.get(i).isEmpty
28 |
29 | (0 until path.size).filter(hasUserLabel).map(path.get[Any]).toList
30 | }
31 |
32 | private def toHList[T <: HList](path: List[_]): T =
33 | if (path.isEmpty)
34 | HNil.asInstanceOf[T]
35 | else
36 | (path.head :: toHList[IsHCons[T]#T](path.tail)).asInstanceOf[T]
37 | }
38 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/SemiEdge.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | case class SemiEdge(from: Vertex, label: String, properties: KeyValue[_]*) {
4 |
5 | /** `implicit ScalaGraph` required for configuration, e.g. when using remote graph */
6 | def -->(to: Vertex)(implicit graph: ScalaGraph) =
7 | from.asScala().addEdge(label, to, properties: _*)
8 | }
9 |
10 | case class SemiDoubleEdge(right: Vertex, label: String, properties: KeyValue[_]*)
11 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/StepLabel.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import java.util.UUID.randomUUID
4 | import java.util.{Map => JMap}
5 | import scala.annotation.implicitNotFound
6 | import shapeless._
7 | import shapeless.poly._
8 |
9 | // type safety for labelled steps
10 | case class StepLabel[A](name: String = randomUUID.toString)
11 |
12 | object StepLabel {
13 |
14 | object GetLabelName extends (StepLabel ~>> String) {
15 | def apply[B](label: StepLabel[B]) = label.name
16 | }
17 |
18 | object combineLabelWithValue extends Poly2 {
19 | implicit def atLabel[A, L <: HList] =
20 | at[StepLabel[A], (L, JMap[String, Any])] {
21 | case (label, (acc, values)) =>
22 | (values.get(label.name).asInstanceOf[A] :: acc, values)
23 | }
24 | }
25 |
26 | trait ExtractLabelType[A] {
27 | type Out
28 | }
29 |
30 | object ExtractLabelType extends LowPriorityExtractLabelTypeImplicits {
31 | @implicitNotFound(
32 | "Unable to find implicit for extracting LabelType of StepLabel `${A}`. "
33 | + "We probably need to add an implicit def to `LowPriorityExtractLabelTypeImplicits`")
34 | type Aux[A, Out0] = ExtractLabelType[A] { type Out = Out0 }
35 | }
36 |
37 | trait LowPriorityExtractLabelTypeImplicits {
38 | implicit def forSingle[A] = new ExtractLabelType[StepLabel[A]] {
39 | type Out = A
40 | }
41 |
42 | implicit def forHNil = new ExtractLabelType[HNil] { type Out = HNil }
43 |
44 | implicit def forHList[H, T <: HList, HOut, TOut <: HList](
45 | implicit hExtractLabelType: ExtractLabelType.Aux[H, HOut],
46 | tExtractLabelType: ExtractLabelType.Aux[T, TOut])
47 | : ExtractLabelType.Aux[H :: T, HOut :: TOut] =
48 | new ExtractLabelType[H :: T] { type Out = HOut :: TOut }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/TraversalSource.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import java.util.function.{BinaryOperator, Supplier, UnaryOperator}
4 | import org.apache.commons.configuration2.Configuration
5 | import org.apache.tinkerpop.gremlin.process.remote.RemoteConnection
6 | import org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal
7 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource
8 | import shapeless.HNil
9 |
10 | object TraversalSource {
11 | def apply(graph: Graph): TraversalSource =
12 | TraversalSource(graph.traversal())
13 | }
14 |
15 | case class TraversalSource(underlying: GraphTraversalSource) {
16 | def graph: Graph = underlying.getGraph
17 |
18 | def addV(): GremlinScala.Aux[Vertex, HNil] =
19 | GremlinScala[Vertex, HNil](underlying.addV())
20 |
21 | def addV(label: String): GremlinScala.Aux[Vertex, HNil] =
22 | GremlinScala[Vertex, HNil](underlying.addV(label))
23 |
24 | def addE(label: String): GremlinScala.Aux[Edge, HNil] =
25 | GremlinScala[Edge, HNil](underlying.addE(label))
26 |
27 | def inject[S](starts: S*): GremlinScala.Aux[S, HNil] =
28 | GremlinScala[S, HNil](underlying.inject(starts: _*))
29 |
30 | // start traversal with all vertices
31 | def V(): GremlinScala.Aux[Vertex, HNil] =
32 | GremlinScala[Vertex, HNil](underlying.V())
33 |
34 | // start traversal with all edges
35 | def E(): GremlinScala.Aux[Edge, HNil] =
36 | GremlinScala[Edge, HNil](underlying.E())
37 |
38 | // start traversal with some vertices identified by given ids
39 | def V(vertexIds: Any*): GremlinScala.Aux[Vertex, HNil] =
40 | GremlinScala[Vertex, HNil](underlying.V(vertexIds.asInstanceOf[Seq[AnyRef]]: _*))
41 |
42 | // start traversal with some edges identified by given ids
43 | def E(edgeIds: Any*): GremlinScala.Aux[Edge, HNil] =
44 | GremlinScala[Edge, HNil](underlying.E(edgeIds.asInstanceOf[Seq[AnyRef]]: _*))
45 |
46 | def withSack[A](initialValue: A): TraversalSource =
47 | withSack(() => initialValue)
48 |
49 | def withSack[A](initialValue: () => A) =
50 | TraversalSource(underlying.withSack(initialValue: Supplier[A]))
51 |
52 | def withSack[A](initialValue: A, splitOperator: A => A): TraversalSource =
53 | withSack(() => initialValue, splitOperator)
54 |
55 | def withSack[A](initialValue: () => A, splitOperator: A => A) =
56 | TraversalSource(underlying.withSack(initialValue: Supplier[A], splitOperator: UnaryOperator[A]))
57 |
58 | def withSack[A](initialValue: A, mergeOperator: (A, A) => A): TraversalSource =
59 | withSack(() => initialValue, mergeOperator)
60 |
61 | def withSack[A](initialValue: () => A, mergeOperator: (A, A) => A) =
62 | TraversalSource(
63 | underlying.withSack(initialValue: Supplier[A], mergeOperator: BinaryOperator[A]))
64 |
65 | def withSack[A](initialValue: A,
66 | splitOperator: A => A,
67 | mergeOperator: (A, A) => A): TraversalSource =
68 | withSack(() => initialValue, splitOperator, mergeOperator)
69 |
70 | def withSack[A](initialValue: () => A, splitOperator: A => A, mergeOperator: (A, A) => A) =
71 | TraversalSource(
72 | underlying.withSack(initialValue: Supplier[A],
73 | splitOperator: UnaryOperator[A],
74 | mergeOperator: BinaryOperator[A]))
75 |
76 | def withSideEffect[A](key: String, initialValue: A) =
77 | TraversalSource(underlying.withSideEffect(key, initialValue))
78 |
79 | def withSideEffect[A](key: String, initialValue: () => A) =
80 | TraversalSource(underlying.withSideEffect(key, initialValue: Supplier[A]))
81 |
82 | def withSideEffect[A](key: String, initialValue: A, reducer: (A, A) => A) =
83 | TraversalSource(underlying.withSideEffect(key, initialValue, reducer: BinaryOperator[A]))
84 |
85 | def withSideEffect[A](key: String, initialValue: () => A, reducer: (A, A) => A) =
86 | TraversalSource(
87 | underlying.withSideEffect(key, initialValue: Supplier[A], reducer: BinaryOperator[A]))
88 |
89 | def withRemote(configFile: String): TraversalSource =
90 | TraversalSource(traversal().withRemote(configFile))
91 |
92 | def withRemote(configuration: Configuration): TraversalSource =
93 | TraversalSource(traversal().withRemote(configuration))
94 |
95 | def withRemote(connection: RemoteConnection): TraversalSource =
96 | TraversalSource(traversal().withRemote(connection))
97 | }
98 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/UnionTraversals.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import java.util.{List => JList}
4 | import shapeless.{::, HList, HNil}
5 | import shapeless.ops.hlist.Prepend
6 |
7 | /** helper class to construct a typed union step */
8 | class UnionTraversals[Start, Ends <: HList](
9 | val travsUntyped: Seq[GremlinScala.Aux[Start, HNil] => GremlinScala[_]]) {
10 |
11 | def join[End](trav: GremlinScala.Aux[Start, HNil] => GremlinScala[End])(
12 | implicit p: Prepend[Ends, JList[End] :: HNil]): UnionTraversals[Start, p.Out] =
13 | new UnionTraversals[Start, p.Out](travsUntyped :+ trav)
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/dsl/AnonymousVertex.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala.dsl
2 |
3 | import gremlin.scala._
4 | import java.util.{Iterator => JIterator}
5 | import scala.collection.JavaConverters._
6 |
7 | object AnonymousVertex {
8 | import org.apache.tinkerpop.gremlin.structure._
9 |
10 | def apply[CC <: Product](cc: CC)(implicit marshaller: Marshallable[CC], grph: Graph): Vertex = {
11 | val fromCC = marshaller.fromCC(cc)
12 | new Vertex {
13 | val graph = grph
14 | val id = fromCC.id
15 | val label = fromCC.label
16 | def remove(): Unit = ???
17 | def addEdge(label: String, inVertex: Vertex, keyValues: Object*): Edge =
18 | ???
19 | def edges(direction: Direction, edgeLabels: String*): JIterator[Edge] =
20 | ???
21 | def property[V](cardinality: VertexProperty.Cardinality,
22 | key: String,
23 | value: V,
24 | keyValues: Object*): VertexProperty[V] = ???
25 | def vertices(direction: Direction, edgeLabels: String*): JIterator[Vertex] = ???
26 | def properties[V](keys: String*): JIterator[VertexProperty[V]] = {
27 | val requestedKeys: Set[String] = keys.toSet
28 | val x: Iterable[VertexProperty[V]] = fromCC.properties.collect {
29 | case (ccKey, ccValue) if requestedKeys.contains(ccKey) =>
30 | new VertexProperty[V] {
31 | // Members declared in org.apache.tinkerpop.gremlin.structure.Element
32 | def id(): Object = ???
33 | def property[V](x$1: String, x$2: V): Property[V] = ???
34 | def remove(): Unit = ???
35 |
36 | // Members declared in org.apache.tinkerpop.gremlin.structure.Property
37 | def isPresent(): Boolean = true
38 | def key(): String = ccKey
39 | def value(): V = ccValue.asInstanceOf[V]
40 |
41 | // Members declared in org.apache.tinkerpop.gremlin.structure.VertexProperty
42 | def element(): Vertex = ???
43 | def properties[U](x$1: String*): JIterator[Property[U]] = ???
44 | }
45 | }
46 | x.iterator.asJava
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/dsl/Constructor.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala.dsl
2 |
3 | import gremlin.scala._
4 | import shapeless._
5 | import shapeless.ops.hlist.Tupler
6 | import shapeless.ops.product.ToHList
7 |
8 | trait Constructor[DomainType, Labels <: HList] {
9 | type GraphType
10 | type StepsType
11 | def apply(raw: GremlinScala[GraphType]): StepsType
12 | }
13 |
14 | object Constructor extends LowPriorityConstructorImplicits {
15 | type Aux[DomainType, Labels <: HList, GraphTypeOut, StepsTypeOut] =
16 | Constructor[DomainType, Labels] {
17 | type GraphType = GraphTypeOut
18 | type StepsType = StepsTypeOut
19 | }
20 | }
21 |
22 | trait LowPriorityConstructorImplicits extends LowestPriorityConstructorImplicits {
23 |
24 | implicit def forSimpleType[A, Labels <: HList](implicit converter: Converter.Aux[A, A]) =
25 | new Constructor[A, Labels] {
26 | type GraphType = A
27 | type StepsType = Steps[A, A, Labels]
28 | def apply(raw: GremlinScala[GraphType]) = new Steps[A, A, Labels](raw)
29 | }
30 |
31 | def forDomainNode[DomainType <: DomainRoot,
32 | Labels <: HList,
33 | StepsTypeOut <: NodeSteps[DomainType, Labels]](
34 | constr: GremlinScala[Vertex] => StepsTypeOut) =
35 | new Constructor[DomainType, Labels] {
36 | type GraphType = Vertex
37 | type StepsType = StepsTypeOut
38 |
39 | def apply(raw: GremlinScala[GraphType]): StepsTypeOut = constr(raw)
40 | }
41 |
42 | implicit def forList[A, AGraphType, Labels <: HList, AStepsType](
43 | implicit aConverter: Converter.Aux[A, AGraphType]) =
44 | new Constructor[List[A], Labels] {
45 | type GraphType = List[AGraphType]
46 | type StepsType = Steps[List[A], List[AGraphType], Labels]
47 | def apply(raw: GremlinScala[GraphType]) =
48 | new Steps[List[A], List[AGraphType], Labels](raw)
49 | }
50 |
51 | implicit def forSet[A, AGraphType, Labels <: HList, AStepsType](
52 | implicit aConverter: Converter.Aux[A, AGraphType]) =
53 | new Constructor[Set[A], Labels] {
54 | type GraphType = Set[AGraphType]
55 | type StepsType = Steps[Set[A], Set[AGraphType], Labels]
56 | def apply(raw: GremlinScala[GraphType]) =
57 | new Steps[Set[A], Set[AGraphType], Labels](raw)
58 | }
59 |
60 | implicit val forHNil = new Constructor[HNil, HNil] {
61 | type GraphType = HNil
62 | type StepsType = Steps[HNil, HNil, HNil]
63 | def apply(raw: GremlinScala[HNil]) = new Steps[HNil, HNil, HNil](raw)
64 | }
65 |
66 | implicit def forHList[H,
67 | HGraphType,
68 | Labels <: HList,
69 | HStepsType,
70 | T <: HList,
71 | TGraphType <: HList,
72 | TStepsType](implicit
73 | hConstr: Constructor.Aux[H, Labels, HGraphType, HStepsType],
74 | tConstr: Constructor.Aux[T, Labels, TGraphType, TStepsType],
75 | converter: Converter.Aux[H :: T, HGraphType :: TGraphType]) =
76 | new Constructor[H :: T, Labels] {
77 | type GraphType = HGraphType :: TGraphType
78 | type StepsType = Steps[H :: T, HGraphType :: TGraphType, Labels]
79 | def apply(raw: GremlinScala[GraphType]): StepsType =
80 | new Steps[H :: T, HGraphType :: TGraphType, Labels](raw)
81 | }
82 | }
83 |
84 | trait LowestPriorityConstructorImplicits {
85 | // for all Products, e.g. tuples, case classes etc
86 | implicit def forGeneric[
87 | T,
88 | Repr <: HList,
89 | GraphTypeHList <: HList,
90 | GraphTypeTuple <: Product,
91 | Labels <: HList,
92 | StepsType0 <: StepsRoot,
93 | EndDomainHList <: HList,
94 | EndDomainTuple <: Product
95 | ](implicit
96 | gen: Generic.Aux[T, Repr],
97 | constr: Constructor.Aux[Repr, Labels, GraphTypeHList, StepsType0],
98 | graphTypeTupler: Tupler.Aux[GraphTypeHList, GraphTypeTuple],
99 | eq: StepsType0#EndDomain0 =:= EndDomainHList,
100 | tupler: Tupler.Aux[EndDomainHList, EndDomainTuple],
101 | converter: Converter.Aux[T, GraphTypeTuple]) =
102 | new Constructor[T, Labels] {
103 | type GraphType = GraphTypeTuple
104 | type StepsType = Steps[T, GraphType, Labels]
105 | def apply(raw: GremlinScala[GraphType]): StepsType =
106 | new Steps[T, GraphType, Labels](raw)
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/dsl/Converter.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala.dsl
2 |
3 | import gremlin.scala._
4 | import shapeless._
5 | import shapeless.ops.hlist.Tupler
6 | import shapeless.ops.product.ToHList
7 |
8 | trait Converter[DomainType] {
9 | type GraphType
10 | def toGraph(domainType: DomainType): GraphType
11 | def toDomain(graphType: GraphType): DomainType
12 | }
13 |
14 | object Converter extends LowPriorityConverterImplicits {
15 | type Aux[DomainType, Out0] = Converter[DomainType] { type GraphType = Out0 }
16 | }
17 |
18 | trait LowPriorityConverterImplicits extends LowestPriorityConverterImplicits {
19 | /* need to explicitly create these for the base types, otherwise it there would
20 | * be ambiguous implicits (given Converter.forDomainNode) */
21 | implicit def forUnit = identityConverter[Unit]
22 | implicit val forString = identityConverter[String]
23 | implicit val forInt = identityConverter[Int]
24 | implicit val forLong = identityConverter[Long]
25 | implicit val forDouble = identityConverter[Double]
26 | implicit val forFloat = identityConverter[Float]
27 | implicit val forBoolean = identityConverter[Boolean]
28 | implicit val forInteger = identityConverter[Integer]
29 | implicit val forJLong = identityConverter[java.lang.Long]
30 | implicit val forJDouble = identityConverter[java.lang.Double]
31 | implicit val forJFloat = identityConverter[java.lang.Float]
32 | implicit val forJBoolean = identityConverter[java.lang.Boolean]
33 | def identityConverter[A] = new Converter[A] {
34 | type GraphType = A
35 | def toGraph(value: A) = value
36 | def toDomain(value: A) = value
37 | }
38 |
39 | implicit def forDomainNode[DomainType <: DomainRoot](
40 | implicit marshaller: Marshallable[DomainType],
41 | graph: Graph) = new Converter[DomainType] {
42 | type GraphType = Vertex
43 | def toDomain(v: Vertex): DomainType = marshaller.toCC(v)
44 | def toGraph(dt: DomainType): Vertex = AnonymousVertex(dt)
45 | }
46 |
47 | implicit def forList[A, AGraphType](implicit aConverter: Converter.Aux[A, AGraphType]) =
48 | new Converter[List[A]] {
49 | type GraphType = List[AGraphType]
50 | def toDomain(aGraphs: List[AGraphType]): List[A] =
51 | aGraphs.map(aConverter.toDomain)
52 | def toGraph(as: List[A]): List[AGraphType] = as.map(aConverter.toGraph)
53 | }
54 |
55 | implicit def forSet[A, AGraphType](implicit aConverter: Converter.Aux[A, AGraphType]) =
56 | new Converter[Set[A]] {
57 | type GraphType = Set[AGraphType]
58 | def toDomain(aGraphs: Set[AGraphType]): Set[A] =
59 | aGraphs.map(aConverter.toDomain)
60 | def toGraph(as: Set[A]): Set[AGraphType] = as.map(aConverter.toGraph)
61 | }
62 |
63 | implicit val forHNil = new Converter[HNil] {
64 | type GraphType = HNil
65 | def toGraph(value: HNil) = HNil
66 | def toDomain(value: GraphType) = HNil
67 | }
68 |
69 | implicit def forHList[H, HGraphType, T <: HList, TGraphType <: HList](
70 | implicit
71 | hConverter: Converter.Aux[H, HGraphType],
72 | tConverter: Converter.Aux[T, TGraphType]): Converter.Aux[H :: T, HGraphType :: TGraphType] =
73 | new Converter[H :: T] {
74 | type GraphType = HGraphType :: TGraphType
75 |
76 | def toGraph(values: H :: T): GraphType = values match {
77 | case h :: t => hConverter.toGraph(h) :: tConverter.toGraph(t)
78 | }
79 |
80 | def toDomain(values: GraphType): H :: T = values match {
81 | case h :: t => hConverter.toDomain(h) :: tConverter.toDomain(t)
82 | }
83 | }
84 | }
85 |
86 | trait LowestPriorityConverterImplicits {
87 | // for all Products, e.g. tuples, case classes etc
88 | implicit def forGeneric[T, Repr <: HList, GraphType <: HList, GraphTypeTuple <: Product](
89 | implicit
90 | gen: Generic.Aux[T, Repr],
91 | converter: Converter.Aux[Repr, GraphType],
92 | tupler: Tupler.Aux[GraphType, GraphTypeTuple],
93 | toHList: ToHList.Aux[GraphTypeTuple, GraphType]): Converter.Aux[T, GraphTypeTuple] =
94 | new Converter[T] {
95 | type GraphType = GraphTypeTuple
96 |
97 | def toGraph(value: T): GraphTypeTuple =
98 | tupler(converter.toGraph(gen.to(value)))
99 |
100 | def toDomain(value: GraphTypeTuple): T =
101 | gen.from(converter.toDomain(toHList(value)))
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/dsl/NodeSteps.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala.dsl
2 |
3 | import gremlin.scala._
4 | import java.util.{Map => JMap}
5 | import scala.collection.mutable
6 | import shapeless.HList
7 |
8 | /* Root class for all your vertex based DSL steps
9 | * TODO: add support for using Edge instead of Vertex?
10 | */
11 | class NodeSteps[EndDomain <: DomainRoot, Labels <: HList](override val raw: GremlinScala[Vertex])(
12 | implicit marshaller: Marshallable[EndDomain])
13 | extends Steps[EndDomain, Vertex, Labels](raw)(
14 | Converter.forDomainNode[EndDomain](marshaller, raw.traversal.asAdmin.getGraph.get)) {
15 |
16 | /* follow the incoming edges of the given type as long as possible */
17 | def walkIn(edgeType: String): GremlinScala[Vertex] =
18 | raw
19 | .repeat(_.in(edgeType))
20 | .until(_.in(edgeType).count().is(P.eq(0)))
21 |
22 | /** Aggregate all objects at this point into the given collection, e.g. `mutable.ArrayBuffer.empty[EndDomain]`
23 | * Uses eager evaluation (as opposed to `store`() which lazily fills a collection)
24 | */
25 | def aggregate(into: mutable.Buffer[EndDomain]): NodeSteps[EndDomain, Labels] =
26 | new NodeSteps[EndDomain, Labels](
27 | raw.sideEffect { v: Vertex =>
28 | into += v.toCC[EndDomain]
29 | }
30 | )
31 |
32 | def filterOnEnd(predicate: EndDomain => Boolean): NodeSteps[EndDomain, Labels] =
33 | new NodeSteps[EndDomain, Labels](
34 | raw.filterOnEnd { v: Vertex =>
35 | predicate(v.toCC[EndDomain])
36 | }
37 | )
38 |
39 | /** filter by id */
40 | def id(ids: AnyRef*): NodeSteps[EndDomain, Labels] =
41 | new NodeSteps[EndDomain, Labels](raw.hasId(ids: _*))
42 |
43 | /**
44 | Extend the traversal with a side-effect step, where `fun` is a
45 | function that performs a side effect. The function `fun` can
46 | access the current traversal element via the variable `_`.
47 | */
48 | def sideEffect(fun: EndDomain => Any): NodeSteps[EndDomain, Labels] =
49 | new NodeSteps[EndDomain, Labels](raw.sideEffect { v: Vertex =>
50 | fun(v.toCC[EndDomain])
51 | })
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/dsl/Steps.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala.dsl
2 |
3 | import gremlin.scala._
4 | import gremlin.scala.StepLabel.{combineLabelWithValue, GetLabelName}
5 | import java.util.{Map => JMap}
6 | import java.util.stream.{Stream => JStream}
7 | import scala.collection.mutable
8 | import shapeless.{::, HList, HNil}
9 | import shapeless.ops.hlist.{IsHCons, Mapper, Prepend, RightFolder, ToTraversable, Tupler}
10 | import shapeless.ops.product.ToHList
11 |
12 | /** root type for all domain types */
13 | trait DomainRoot extends Product {
14 | // type Underlying
15 | }
16 |
17 | /** just a helper trait for extracting type members for Steps */
18 | trait StepsRoot {
19 | type EndDomain0
20 | type EndGraph0
21 | def raw: GremlinScala[EndGraph0]
22 | }
23 |
24 | class Steps[EndDomain, EndGraph, Labels <: HList](val raw: GremlinScala[EndGraph])(
25 | implicit val converter: Converter.Aux[EndDomain, EndGraph])
26 | extends StepsRoot {
27 | type EndDomain0 = EndDomain
28 | type EndGraph0 = EndGraph
29 |
30 | /* executes traversal and converts results into cpg domain type */
31 | def toList(): List[EndDomain] = raw.toList().map(converter.toDomain)
32 | def toStream(): JStream[EndDomain] = raw.toStream().map { end: EndGraph => converter.toDomain(end) }
33 | def toSet(): Set[EndDomain] = raw.toSet().map(converter.toDomain)
34 | def iterate(): Unit = raw.iterate()
35 | def exec(): Unit = iterate()
36 |
37 | /**
38 | Execute the traversal and convert it to a mutable buffer
39 | */
40 | def toBuffer(): mutable.Buffer[EndDomain] = toList().toBuffer
41 |
42 | def head(): EndDomain = converter.toDomain(raw.head())
43 | def headOption(): Option[EndDomain] = raw.headOption().map(converter.toDomain)
44 | def isDefined: Boolean = headOption().isDefined
45 |
46 | /**
47 | * shortcut for `toList`
48 | */
49 | def l: List[EndDomain] = toList()
50 |
51 | /**
52 | Alias for `toStream`
53 | */
54 | def s(): JStream[EndDomain] = toStream()
55 |
56 | /**
57 | * print the results to stdout
58 | */
59 | def p(): List[String] = {
60 | l.map {
61 | case vertex: Vertex => {
62 | val label = vertex.label
63 | val id = vertex.id().toString
64 | val keyValPairs = vertex.valueMap.toList
65 | .filter(x => x._2.toString != "")
66 | .sortBy(_._1)
67 | .map(x => x._1 + ": " + x._2)
68 | s"($label,$id): " + keyValPairs.mkString(", ")
69 | }
70 | case elem => elem.toString
71 | }
72 | }
73 |
74 | def count(): Long =
75 | raw.count().head()
76 |
77 | override def clone() = new Steps[EndDomain, EndGraph, Labels](raw.clone())
78 |
79 | def dedup(): Steps[EndDomain, EndGraph, Labels] =
80 | new Steps[EndDomain, EndGraph, Labels](raw.dedup())
81 |
82 | /* access all gremlin-scala methods that don't modify the EndGraph type, e.g. `has` */
83 | /* TODO: track/use NewLabelsGraph as given by `fun` */
84 | def onRaw(
85 | fun: GremlinScala[EndGraph] => GremlinScala[EndGraph]): Steps[EndDomain, EndGraph, Labels] =
86 | new Steps[EndDomain, EndGraph, Labels](fun(raw))
87 |
88 | /* TODO: track/use NewLabelsGraph as given by `fun` */
89 | def map[NewEndDomain, NewEndGraph, NewSteps <: StepsRoot](fun: EndDomain => NewEndDomain)(
90 | implicit
91 | newConverter: Converter.Aux[NewEndDomain, NewEndGraph],
92 | constr: Constructor.Aux[NewEndDomain, Labels, NewEndGraph, NewSteps]): NewSteps =
93 | constr {
94 | raw.map { endGraph: EndGraph =>
95 | newConverter.toGraph(fun(converter.toDomain(endGraph)))
96 | }
97 | }
98 |
99 | /* TODO: track/use NewLabelsGraph as given by `fun` */
100 | def flatMap[NewSteps <: StepsRoot](fun: EndDomain => NewSteps)(
101 | implicit
102 | constr: Constructor.Aux[NewSteps#EndDomain0, Labels, NewSteps#EndGraph0, NewSteps],
103 | newConverter: Converter[NewSteps#EndDomain0]
104 | ): NewSteps =
105 | constr {
106 | raw.flatMap { endGraph: EndGraph =>
107 | val newSteps: NewSteps = fun(converter.toDomain(endGraph))
108 | newSteps.raw.asInstanceOf[GremlinScala[NewSteps#EndGraph0]]
109 | // not sure why I need the cast here - should be safe though
110 | }
111 | }
112 |
113 | def filter(predicate: Steps[EndDomain, EndGraph, Labels] => Steps[_, _, _])
114 | : Steps[EndDomain, EndGraph, Labels] = {
115 | val rawWithFilter: GremlinScala[EndGraph] =
116 | raw.filter { gs =>
117 | predicate(
118 | new Steps[EndDomain, EndGraph, Labels](gs)
119 | ).raw
120 | }
121 | new Steps[EndDomain, EndGraph, Labels](rawWithFilter)
122 | }
123 |
124 | def filterNot(predicate: Steps[EndDomain, EndGraph, Labels] => Steps[_, _, _])
125 | : Steps[EndDomain, EndGraph, Labels] = {
126 | val rawWithFilter: GremlinScala[EndGraph] =
127 | raw.filterNot { gs =>
128 | predicate(
129 | new Steps[EndDomain, EndGraph, Labels](gs)
130 | ).raw
131 | }
132 | new Steps[EndDomain, EndGraph, Labels](rawWithFilter)
133 | }
134 |
135 | // labels the current step and preserves the type - use together with `select` step
136 | def as[NewLabels <: HList](stepLabel: String)(
137 | implicit prependDomain: Prepend.Aux[Labels, EndDomain :: HNil, NewLabels])
138 | : Steps[EndDomain, EndGraph, NewLabels] =
139 | new Steps[EndDomain, EndGraph, NewLabels](
140 | raw.asInstanceOf[GremlinScala.Aux[EndGraph, HNil]].as(stepLabel))
141 |
142 | def as[NewLabels <: HList](stepLabel: StepLabel[EndDomain])(
143 | implicit prependDomain: Prepend.Aux[Labels, EndDomain :: HNil, NewLabels])
144 | : Steps[EndDomain, EndGraph, NewLabels] =
145 | new Steps[EndDomain, EndGraph, NewLabels](
146 | raw.asInstanceOf[GremlinScala.Aux[EndGraph, HNil]].as(stepLabel.name))
147 |
148 | // select all labels
149 | def select[LabelsGraph <: HList, LabelsGraphTuple, LabelsTuple]()(
150 | implicit
151 | conv1: Converter.Aux[Labels, LabelsGraph],
152 | tupler1: Tupler.Aux[LabelsGraph, LabelsGraphTuple],
153 | tupler2: Tupler.Aux[Labels, LabelsTuple],
154 | conv2: Converter.Aux[LabelsTuple, LabelsGraphTuple]
155 | ) = new Steps[LabelsTuple, LabelsGraphTuple, Labels](
156 | raw.asInstanceOf[GremlinScala.Aux[EndGraph, LabelsGraph]].select()
157 | )
158 |
159 | // select one specific label
160 | def select[Label, LabelGraph](label: StepLabel[Label])(
161 | implicit conv1: Converter.Aux[Label, LabelGraph]) =
162 | new Steps[Label, LabelGraph, Labels](raw.select(StepLabel[LabelGraph](label.name)))
163 |
164 | // select multiple specific labels
165 | def select[StepLabelsTuple <: Product,
166 | StepLabels <: HList,
167 | H0,
168 | T0 <: HList,
169 | SelectedTypes <: HList,
170 | SelectedTypesTuple <: Product,
171 | SelectedGraphTypesTuple <: Product,
172 | LabelNames <: HList,
173 | Z](stepLabelsTuple: StepLabelsTuple)(
174 | implicit toHList: ToHList.Aux[StepLabelsTuple, StepLabels],
175 | hasOne: IsHCons.Aux[StepLabels, H0, T0],
176 | hasTwo: IsHCons[T0], // witnesses that labels has > 1 elements
177 | extractLabelType: StepLabel.ExtractLabelType.Aux[StepLabels, SelectedTypes],
178 | tupler: Tupler.Aux[SelectedTypes, SelectedTypesTuple],
179 | conv: Converter.Aux[SelectedTypesTuple, SelectedGraphTypesTuple],
180 | stepLabelToString: Mapper.Aux[GetLabelName.type, StepLabels, LabelNames],
181 | trav: ToTraversable.Aux[LabelNames, List, String],
182 | folder: RightFolder.Aux[StepLabels,
183 | (HNil, JMap[String, Any]),
184 | combineLabelWithValue.type,
185 | (SelectedTypes, Z)]
186 | ): Steps[SelectedTypesTuple, SelectedGraphTypesTuple, Labels] = {
187 | val stepLabels: StepLabels = toHList(stepLabelsTuple)
188 | val labels: List[String] = stepLabels.map(GetLabelName).toList
189 | val label1 = labels.head
190 | val label2 = labels.tail.head
191 | val remainder = labels.tail.tail
192 |
193 | val selectTraversal =
194 | raw.traversal.select[Any](label1, label2, remainder: _*)
195 | val newRaw: GremlinScala[SelectedGraphTypesTuple] =
196 | GremlinScala(selectTraversal).map { selectValues =>
197 | val resultTuple = stepLabels.foldRight((HNil: HNil, selectValues))(combineLabelWithValue)
198 | val values: SelectedTypes = resultTuple._1
199 | tupler(values)
200 | .asInstanceOf[SelectedGraphTypesTuple] //dirty but does the trick
201 | }
202 |
203 | new Steps[SelectedTypesTuple, SelectedGraphTypesTuple, Labels](newRaw)
204 | }
205 |
206 | /**
207 | Repeat the given traversal. This step can be combined with the until and emit steps to
208 | provide a termination and emit criteria.
209 | */
210 | def repeat[NewEndDomain >: EndDomain](
211 | repeatTraversal: Steps[EndDomain, EndGraph, HNil] => Steps[NewEndDomain, EndGraph, _])(
212 | implicit newConverter: Converter.Aux[NewEndDomain, EndGraph])
213 | : Steps[NewEndDomain, EndGraph, Labels] =
214 | new Steps[NewEndDomain, EndGraph, Labels](
215 | raw.repeat { rawTraversal =>
216 | repeatTraversal(
217 | new Steps[EndDomain, EndGraph, HNil](rawTraversal)
218 | ).raw
219 | }
220 | )
221 |
222 | /**
223 | Termination criteria for a repeat step.
224 | If used before the repeat step it as "while" characteristics.
225 | If used after the repeat step it as "do-while" characteristics
226 | */
227 | def until(untilTraversal: Steps[EndDomain, EndGraph, HNil] => Steps[_, _, _])
228 | : Steps[EndDomain, EndGraph, Labels] =
229 | new Steps[EndDomain, EndGraph, Labels](
230 | raw.until { rawTraversal =>
231 | untilTraversal(
232 | new Steps[EndDomain, EndGraph, HNil](rawTraversal)
233 | ).raw
234 | }
235 | )
236 |
237 | /**
238 | * Modifier for repeat steps. Configure the amount of times the repeat traversal is
239 | * executed.
240 | */
241 | def times(maxLoops: Int): Steps[EndDomain, EndGraph, Labels] =
242 | new Steps[EndDomain, EndGraph, Labels](raw.times(maxLoops))
243 |
244 | /**
245 | Emit is used with the repeat step to emit the elements of the repeatTraversal after each
246 | iteration of the repeat loop.
247 | */
248 | def emit(): Steps[EndDomain, EndGraph, Labels] =
249 | new Steps[EndDomain, EndGraph, Labels](raw.emit())
250 |
251 | /**
252 | Emit is used with the repeat step to emit the elements of the repeatTraversal after each
253 | iteration of the repeat loop.
254 | The emitTraversal defines under which condition the elements are emitted.
255 | */
256 | def emit(emitTraversal: Steps[EndDomain, EndGraph, HNil] => Steps[_, _, _])
257 | : Steps[EndDomain, EndGraph, Labels] =
258 | new Steps[EndDomain, EndGraph, Labels](
259 | raw.emit { rawTraversal =>
260 | emitTraversal(
261 | new Steps[EndDomain, EndGraph, HNil](rawTraversal)
262 | ).raw
263 | }
264 | )
265 |
266 | /**
267 | * The or step is a filter with multiple `or` related filter traversals.
268 | */
269 | def or(orTraversals: (Steps[EndDomain, EndGraph, HNil] => Steps[_, _, _])*)
270 | : Steps[EndDomain, EndGraph, Labels] = {
271 | val rawOrTraversals = orTraversals.map {
272 | orTraversal => (rawTraversal: GremlinScala[EndGraph]) =>
273 | orTraversal(
274 | new Steps[EndDomain, EndGraph, HNil](
275 | rawTraversal.asInstanceOf[GremlinScala.Aux[EndGraph, HNil]])
276 | ).raw
277 | }
278 |
279 | new Steps[EndDomain, EndGraph, Labels](
280 | raw.or(rawOrTraversals: _*)
281 | )
282 | }
283 |
284 | /**
285 | * The and step is a filter with multiple `and` related filter traversals.
286 | */
287 | def and(andTraversals: (Steps[EndDomain, EndGraph, HNil] => Steps[_, _, _])*)
288 | : Steps[EndDomain, EndGraph, Labels] = {
289 | val rawAndTraversals = andTraversals.map {
290 | andTraversal => (rawTraversal: GremlinScala[EndGraph]) =>
291 | andTraversal(
292 | new Steps[EndDomain, EndGraph, HNil](
293 | rawTraversal.asInstanceOf[GremlinScala.Aux[EndGraph, HNil]])
294 | ).raw
295 | }
296 |
297 | new Steps[EndDomain, EndGraph, Labels](
298 | raw.and(rawAndTraversals: _*)
299 | )
300 | }
301 |
302 | /**
303 | * Step that orders nodes according to f.
304 | * */
305 | def orderBy[A](fun: EndDomain => A): Steps[EndDomain, EndGraph, Labels] =
306 | new Steps[EndDomain, EndGraph, Labels](raw.order(By { v: EndGraph =>
307 | fun(converter.toDomain(v))
308 | }))
309 |
310 | override def toString = s"${getClass.getSimpleName}($raw)"
311 | }
312 |
--------------------------------------------------------------------------------
/gremlin-scala/src/main/scala/gremlin/scala/package.scala:
--------------------------------------------------------------------------------
1 | package gremlin
2 |
3 | import java.util.function.{
4 | BiConsumer,
5 | BiPredicate,
6 | BiFunction,
7 | BinaryOperator,
8 | Consumer,
9 | Function => JFunction,
10 | Predicate => JPredicate,
11 | Supplier,
12 | UnaryOperator
13 | }
14 | import org.apache.tinkerpop.gremlin.process.traversal
15 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal
16 | import org.apache.tinkerpop.gremlin.structure
17 | import shapeless._
18 | import shapeless.ops.hlist.IsHCons
19 | import shapeless.ops.hlist.{IsHCons, ToTraversable}
20 | import shapeless.ops.product.ToHList
21 | import shapeless.syntax.std.product.productOps
22 | import _root_.scala.language.implicitConversions
23 |
24 | package object scala {
25 | type Vertex = structure.Vertex
26 | type Edge = structure.Edge
27 | type Element = structure.Element
28 | type Graph = structure.Graph
29 | type Property[A] = structure.Property[A]
30 | type Traverser[A] = traversal.Traverser[A]
31 | type Label = String
32 | type P[A] = traversal.P[A]
33 |
34 | implicit class GraphAsScala[G <: Graph](g: G) {
35 | def asScala() = ScalaGraph(g)
36 | }
37 |
38 | implicit class GraphAsJava(g: ScalaGraph) {
39 | def asJava() = g.graph
40 | }
41 |
42 | implicit class EdgeAsScala(e: Edge) {
43 | def asScala() = ScalaEdge(e)
44 | }
45 |
46 | implicit class EdgeAsJava(e: ScalaEdge) {
47 | def asJava() = e.edge
48 | }
49 |
50 | implicit class VertexAsScala(e: Vertex) {
51 | def asScala() = ScalaVertex(e)
52 | }
53 |
54 | implicit class VertexAsJava(v: ScalaVertex) {
55 | def asJava() = v.vertex
56 | }
57 |
58 | implicit class PropertyOps[A](property: Property[A]) {
59 | def toOption: Option[A] =
60 | if (property.isPresent) Some(property.value)
61 | else None
62 | }
63 |
64 | // to create a new anonymous traversal, e.g. `__.outE`
65 | def __[A](): GremlinScala.Aux[A, HNil] =
66 | GremlinScala[A, HNil](org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.start[A]())
67 |
68 | def __[A](starts: A*): GremlinScala.Aux[A, HNil] =
69 | GremlinScala[A, HNil](
70 | org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__
71 | .__[A](starts: _*))
72 |
73 | implicit def asScalaVertex(v: Vertex): ScalaVertex = ScalaVertex(v)
74 |
75 | implicit def asScalaEdge(e: Edge): ScalaEdge = ScalaEdge(e)
76 |
77 | implicit def asScalaGraph(g: Graph): ScalaGraph = ScalaGraph(g)
78 |
79 | implicit def asGremlinScala[A](traversal: GraphTraversal[_, A]): GremlinScala.Aux[A, HNil] =
80 | GremlinScala[A, HNil](traversal)
81 |
82 | implicit def toSupplier[A](f: () => A): Supplier[A] = new Supplier[A] {
83 | override def get(): A = f()
84 | }
85 |
86 | implicit def toConsumer[A](f: A => Unit): Consumer[A] = new Consumer[A] {
87 | override def accept(a: A): Unit = f(a)
88 | }
89 |
90 | implicit def toJavaFunction[A, B](f: A => B): JFunction[A, B] =
91 | new JFunction[A, B] {
92 | override def apply(a: A): B = f(a)
93 | }
94 |
95 | implicit def toJavaUnaryOperator[A](f: A => A): UnaryOperator[A] =
96 | new UnaryOperator[A] {
97 | override def apply(a: A): A = f(a)
98 | }
99 |
100 | implicit def toJavaBinaryOperator[A](f: (A, A) => A): BinaryOperator[A] =
101 | new BinaryOperator[A] {
102 | override def apply(a1: A, a2: A): A = f(a1, a2)
103 | }
104 |
105 | implicit def toJavaBiFunction[A, B, C](f: (A, B) => C): BiFunction[A, B, C] =
106 | new BiFunction[A, B, C] {
107 | override def apply(a: A, b: B): C = f(a, b)
108 | }
109 |
110 | implicit def toJavaBiConsumer[A, B](f: (A, B) => Unit): BiConsumer[A, B] =
111 | new BiConsumer[A, B] {
112 | override def accept(a: A, b: B): Unit = f(a, b)
113 | }
114 |
115 | implicit def toJavaPredicate[A](f: A => Boolean): JPredicate[A] =
116 | new JPredicate[A] {
117 | override def test(a: A): Boolean = f(a)
118 | }
119 |
120 | implicit def toJavaBiPredicate[A, B](predicate: (A, B) => Boolean): BiPredicate[A, B] =
121 | new BiPredicate[A, B] {
122 | def test(a: A, b: B) = predicate(a, b)
123 | }
124 |
125 | implicit def liftTraverser[A, B](fun: A => B): Traverser[A] => B =
126 | (t: Traverser[A]) => fun(t.get)
127 |
128 | // Marshalling implicits
129 | implicit class GremlinScalaVertexFunctions(val gs: GremlinScala[Vertex]) {
130 |
131 | /**
132 | * Load a vertex values into a case class
133 | */
134 | def toCC[CC <: Product: Marshallable] = gs.map(_.toCC[CC])
135 | }
136 |
137 | implicit class GremlinScalaEdgeFunctions(val gs: GremlinScala[Edge]) {
138 |
139 | /**
140 | * Load a edge values into a case class
141 | */
142 | def toCC[CC <: Product: Marshallable] = gs.map(_.toCC[CC])
143 | }
144 |
145 | // Arrow syntax implicits
146 | implicit class SemiEdgeFunctions(label: Label) {
147 | def ---(from: Vertex) = SemiEdge(from, label)
148 |
149 | def -->(right: Vertex) = SemiDoubleEdge(right, label)
150 | }
151 |
152 | implicit class SemiEdgeProductFunctions[
153 | LabelAndValuesAsTuple <: Product,
154 | LabelAndValues <: HList,
155 | Lbl <: String,
156 | KeyValues <: HList
157 | ](labelAndValuesAsTuple: LabelAndValuesAsTuple)(
158 | implicit toHList: ToHList.Aux[LabelAndValuesAsTuple, LabelAndValues],
159 | startsWithLabel: IsHCons.Aux[LabelAndValues, Lbl, KeyValues], // first element has to be a Label
160 | keyValueToList: ToTraversable.Aux[KeyValues, List, KeyValue[_]] // all other elements have to be KeyValue[_]
161 | ) {
162 | lazy val labelAndValues = labelAndValuesAsTuple.toHList
163 | lazy val label: String = labelAndValues.head
164 | lazy val keyValues: KeyValues = labelAndValues.tail
165 | lazy val properties: List[KeyValue[_]] = keyValues.toList
166 |
167 | def ---(from: Vertex) = SemiEdge(from, label, properties: _*)
168 | def -->(right: Vertex) = SemiDoubleEdge(right, label, properties: _*)
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/gremlin-scala/src/test/resources/dummy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpollmeier/gremlin-scala/b68ca4e1cc6b050c0e98966af8c08c17ad984156/gremlin-scala/src/test/resources/dummy
--------------------------------------------------------------------------------
/gremlin-scala/src/test/scala/gremlin/scala/.gitignore:
--------------------------------------------------------------------------------
1 | /Playground.scala
2 |
--------------------------------------------------------------------------------
/gremlin-scala/src/test/scala/gremlin/scala/AlgorithmSpec.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph
4 | import org.scalatest.wordspec.AnyWordSpec
5 | import org.scalatest.matchers.should.Matchers
6 |
7 | /* demo common algorithms */
8 | class AlgorithmSpec extends AnyWordSpec with Matchers {
9 |
10 | "directed acyclic graphs".can {
11 | "be detected" in {
12 | // to start with, there's no cycles
13 | implicit val graph = TinkerGraph.open.asScala()
14 | val vA = graph + "a"
15 | val vB = graph + "b"
16 | val vC = graph + "c"
17 | val vD = graph + "d"
18 | vA --- "next" --> vB
19 | vA --- "next" --> vD
20 | vC --- "next" --> vA
21 | vC --- "next" --> vD
22 |
23 | isCyclic(graph) shouldBe false
24 |
25 | //make it cyclic
26 | vB --- "next" --> vC
27 | isCyclic(graph) shouldBe true
28 | }
29 |
30 | def isCyclic(graph: ScalaGraph): Boolean = {
31 | val paths = graph.V()
32 | .as("a")
33 | .repeat(_.out().simplePath())
34 | .emit()
35 | .where(_.out().as("a"))
36 | .toList()
37 |
38 | paths.nonEmpty
39 | }
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/gremlin-scala/src/test/scala/gremlin/scala/ArrowSyntaxSpec.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph
4 | import org.scalatest.wordspec.AnyWordSpec
5 | import org.scalatest.matchers.should.Matchers
6 |
7 | class ArrowSyntaxSpec extends AnyWordSpec with Matchers {
8 |
9 | "A --> B creates an edge".which {
10 |
11 | "has a label" in new Fixture {
12 | paris --- Eurostar --> london
13 |
14 | paris.out(Eurostar).head() shouldBe london
15 | }
16 |
17 | "has a label and one property" in new Fixture {
18 | paris --- (Eurostar, Name -> "alpha") --> london
19 |
20 | paris.out(Eurostar).head() shouldBe london
21 | paris.outE(Eurostar).value(Name).head() shouldBe "alpha"
22 | }
23 |
24 | "has a label and multiple properties" in new Fixture {
25 | paris --- (Eurostar, Name -> "alpha", Length -> 100) --> london
26 |
27 | paris.out(Eurostar).head() shouldBe london
28 | paris.outE(Eurostar).value(Name).head() shouldBe "alpha"
29 | paris.outE(Eurostar).value(Length).head() shouldBe 100
30 | }
31 |
32 | "has a label and multiple properties as Map " in new Fixture {
33 | paris --- (Eurostar, properties) --> london
34 | paris.out(Eurostar).head() shouldBe london
35 | paris.outE(Eurostar).value(Name).head() shouldBe "alpha"
36 | paris.outE(Eurostar).value(Length).head() shouldBe 100
37 | }
38 | }
39 |
40 | "A <-- B creates an edge".which {
41 | "has a label" in new Fixture {
42 | paris <-- Eurostar --- london
43 | london.out(Eurostar).head() shouldBe paris
44 | }
45 |
46 | "has a label and one property" in new Fixture {
47 | paris <-- (Eurostar, Name -> "alpha") --- london
48 | paris.in(Eurostar).head() shouldBe london
49 | paris.inE(Eurostar).value(Name).head() shouldBe "alpha"
50 | }
51 |
52 | "has a label and multiple properties" in new Fixture {
53 | paris <-- (Eurostar, Name -> "alpha", Length -> 100) --- london
54 |
55 | paris.in(Eurostar).head() shouldBe london
56 | paris.inE(Eurostar).value(Name).head() shouldBe "alpha"
57 | paris.inE(Eurostar).value(Length).head() shouldBe 100
58 | }
59 |
60 | /* "has a label and multiple properties as Map" in new Fixture {
61 | paris <-- (Eurostar, properties) --- london
62 |
63 | paris.in(Eurostar).head shouldBe london
64 | paris.inE(Eurostar).value(Name).head shouldBe "alpha"
65 | paris.inE(Eurostar).value(Length).head shouldBe 100
66 | }*/
67 | }
68 |
69 | "A <--> B create edges".which {
70 | "have labels" in new Fixture {
71 | paris <-- Eurostar --> london
72 |
73 | paris.out(Eurostar).head() shouldBe london
74 | london.out(Eurostar).head() shouldBe paris
75 | }
76 |
77 | "have labels and one property" in new Fixture {
78 | paris <-- (Eurostar, Name -> "alpha") --> london
79 |
80 | paris.out(Eurostar).head() shouldBe london
81 | paris.outE(Eurostar).value(Name).head() shouldBe "alpha"
82 | paris.in(Eurostar).head() shouldBe london
83 | paris.inE(Eurostar).value(Name).head() shouldBe "alpha"
84 | }
85 |
86 | "have labels and multiple properties" in new Fixture {
87 | paris <-- (Eurostar, Name -> "alpha", Length -> 100) --> london
88 |
89 | paris.out(Eurostar).head() shouldBe london
90 | paris.outE(Eurostar).value(Name).head() shouldBe "alpha"
91 | paris.outE(Eurostar).value(Length).head() shouldBe 100
92 | paris.in(Eurostar).head() shouldBe london
93 | paris.inE(Eurostar).value(Name).head() shouldBe "alpha"
94 | paris.inE(Eurostar).value(Length).head() shouldBe 100
95 | }
96 |
97 | /* "have labels and multiple properties as Map" in new Fixture {
98 | paris <-- (Eurostar, properties) --> london
99 |
100 | paris.out(Eurostar).head shouldBe london
101 | paris.outE(Eurostar).value(Name).head shouldBe "alpha"
102 | paris.outE(Eurostar).value(Length).head shouldBe 100
103 | paris.in(Eurostar).head shouldBe london
104 | paris.inE(Eurostar).value(Name).head shouldBe "alpha"
105 | paris.inE(Eurostar).value(Length).head shouldBe 100
106 | }*/
107 | }
108 |
109 | // TODO: case class support
110 | // "adding edge with case class" in {
111 | // val graph = TinkerGraph.open.asScala
112 |
113 | // val paris = graph.addVertex("Paris")
114 | // val london = graph.addVertex("London")
115 |
116 | // val e = paris --- CCWithLabelAndId(
117 | // "some string",
118 | // Int.MaxValue,
119 | // Long.MaxValue,
120 | // Some("option type"),
121 | // Seq("test1", "test2"),
122 | // Map("key1" -> "value1", "key2" -> "value2"),
123 | // NestedClass("nested")
124 | // ) --> london
125 |
126 | // e.inVertex shouldBe london
127 | // e.outVertex shouldBe paris
128 | // }
129 |
130 | // "adding bidirectional edge with case class" in {
131 | // val graph = TinkerGraph.open.asScala
132 |
133 | // val paris = graph.addVertex("Paris")
134 | // val london = graph.addVertex("London")
135 |
136 | // val (e0, e1) = paris <-- CCWithLabel(
137 | // "some string",
138 | // Long.MaxValue,
139 | // Some("option type"),
140 | // Seq("test1", "test2"),
141 | // Map("key1" -> "value1", "key2" -> "value2"),
142 | // NestedClass("nested")
143 | // ) --> london
144 |
145 | // e0.inVertex shouldBe london
146 | // e0.outVertex shouldBe paris
147 |
148 | // e1.inVertex shouldBe paris
149 | // e1.outVertex shouldBe london
150 | // }
151 |
152 | // "adding left edge with case class" in {
153 | // val graph = TinkerGraph.open.asScala
154 |
155 | // val paris = graph.addVertex("Paris")
156 | // val london = graph.addVertex("London")
157 |
158 | // val e = paris <-- CCWithLabelAndId(
159 | // "some string",
160 | // Int.MaxValue,
161 | // Long.MaxValue,
162 | // Some("option type"),
163 | // Seq("test1", "test2"),
164 | // Map("key1" -> "value1", "key2" -> "value2"),
165 | // NestedClass("nested")
166 | // ) --- london
167 |
168 | // e.inVertex shouldBe paris
169 | // e.outVertex shouldBe london
170 | // }
171 |
172 | trait Fixture {
173 | implicit val graph = TinkerGraph.open.asScala()
174 | val paris: Vertex = graph + "Paris"
175 | val london = graph + "London"
176 |
177 | val Eurostar = "eurostar" //edge label
178 |
179 | val Name: Key[String] = Key[String]("name")
180 | val Length: Key[Int] = Key[Int]("length")
181 |
182 | val properties: Map[String, Any] =
183 | List(("name", "alpha"), ("length", 100)).toMap
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/gremlin-scala/src/test/scala/gremlin/scala/ElementSpec.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph
4 | import org.apache.tinkerpop.gremlin.structure.T
5 | import org.apache.tinkerpop.gremlin.structure.VertexProperty.Cardinality
6 | import scala.collection.JavaConverters._
7 | import TestGraph._
8 |
9 | // TODO: rewrite using new type safe steps
10 | class ElementSpec extends TestBase {
11 |
12 | describe("properties") {
13 | it("gets properties") {
14 | v1.keys shouldBe Set(Key("name"), Key("age"))
15 | v1.property(Name).value shouldBe "marko"
16 | v1.property(DoesNotExist).isPresent shouldBe false
17 | v1.valueMap shouldBe Map("name" -> "marko", "age" -> 29)
18 | v1.valueMap("name", "age") shouldBe Map("name" -> "marko", "age" -> 29)
19 | v1.properties("name", "age").length shouldBe 2
20 | v1.properties.length shouldBe 2
21 |
22 | e7.keys shouldBe Set(Key("weight"))
23 | e7.property(Weight).value shouldBe 0.5
24 | e7.property(DoesNotExist).isPresent shouldBe false
25 | e7.valueMap("weight") shouldBe Map("weight" -> 0.5)
26 |
27 | graph.V(1).properties(Age.name, Name.name).hasValue("marko").count().head() shouldBe 1
28 | graph.V(1).properties(Age.name, Name.name).hasValue(29).count().head() shouldBe 1
29 | graph.V(1).properties(Age.name, Name.name).hasValue(29, "marko").count().head() shouldBe 2
30 | graph
31 | .V(1)
32 | .properties(Age.name, Name.name)
33 | .hasValue("marko", 29: Integer)
34 | .count()
35 | .head() shouldBe 2
36 | }
37 |
38 | it("maps properties to scala.Option") {
39 | v1.property(Name).toOption should be(Some("marko"))
40 | e7.property(Weight).toOption shouldBe Some(0.5)
41 | }
42 |
43 | it("sets a property") {
44 | v1.setProperty(TestProperty, "updated")
45 | v1.property(TestProperty).value shouldBe "updated"
46 |
47 | e7.setProperty(TestProperty, "updated")
48 | e7.property(TestProperty).value shouldBe "updated"
49 | }
50 |
51 | /** adapted from http://tinkerpop.apache.org/docs/current/reference/#vertex-properties
52 | * TODO: `properties` should take `Key` as well
53 | */
54 | it("sets a property with multiple values") {
55 | // tp3 standard way
56 | val v = graph.addVertex((Name.name -> "marko"), (Name.name -> "marko a. rodriguez"))
57 | graph.V(v).properties(Name.name).count().head() shouldBe 2
58 |
59 | // add one more
60 | v.property(Cardinality.list, Name.name, "m. a. rodriguez")
61 | graph.V(v).properties(Name.name).count().head() shouldBe 3
62 |
63 | // can filter on property values as well
64 | graph.V(v).properties(Name.name).hasValue("marko").count().head() shouldBe 1
65 |
66 | // gremlin-scala style
67 | v.setPropertyList(TestProperty, List("one", "two", "three"))
68 | graph.V(v).properties(TestProperty.name).count().head() shouldBe 3
69 |
70 | // can override
71 | v.setPropertyList(TestProperty, List("three", "four"))
72 | graph.V(v).properties(TestProperty.name).count().head() shouldBe 2
73 | }
74 |
75 | it("removes a property") {
76 | v1.setProperty(TestProperty, "updated")
77 | v1.removeProperty(TestProperty)
78 | v1.property(TestProperty).isPresent shouldBe false
79 |
80 | e7.setProperty(TestProperty, "updated")
81 | e7.removeProperty(TestProperty)
82 | e7.property(TestProperty).isPresent shouldBe false
83 | }
84 | }
85 |
86 | describe("values") {
87 | it("gets a value") {
88 | v1.value2(Name) shouldBe "marko"
89 | e7.value2(Weight) shouldBe 0.5
90 | }
91 |
92 | it("gets an optional value") {
93 | v1.valueOption(Name) shouldBe Some("marko")
94 | v1.valueOption(DoesNotExist) shouldBe None
95 | }
96 |
97 | it("sets an optional value") {
98 | v1.valueOption(TestProperty, None)
99 | v1.property(TestProperty).isPresent shouldBe false
100 | v1.valueOption(TestProperty, Some("test"))
101 | v1.property(TestProperty).value shouldBe "test"
102 |
103 | e7.valueOption(TestProperty, None)
104 | e7.property(TestProperty).isPresent shouldBe false
105 | e7.valueOption(TestProperty, Some("test"))
106 | e7.property(TestProperty).value shouldBe "test"
107 | }
108 |
109 | it("throws an exception if it doesn't exist") {
110 | intercept[IllegalStateException] { v1.value2(DoesNotExist) }
111 | }
112 | }
113 |
114 | describe("id, equality and hashCode") {
115 | it("has an id") {
116 | v1.id shouldBe 1
117 | e7.id shouldBe 7
118 | }
119 |
120 | it("equals") {
121 | v1 == v(1).asScala() shouldBe true
122 | v1 == v(2).asScala() shouldBe false
123 | }
124 |
125 | it("uses the right hashCodes") {
126 | v1.hashCode shouldBe v(1).asScala().hashCode
127 | v1.hashCode should not be v(2).asScala().hashCode
128 |
129 | Set(v1) contains v(1) shouldBe true
130 | Set(v1) contains v(2) shouldBe false
131 | }
132 | }
133 |
134 | describe("adding and removing elements") {
135 |
136 | it("adds a vertex") {
137 | val graph = TinkerGraph.open.asScala()
138 | val v1 = graph.addVertex()
139 | val v2 = graph.addVertex()
140 | v2.setProperty(TestProperty, "testValue")
141 |
142 | graph.V(v1.id).head() shouldBe v1
143 | graph.V(v2.id).head().property(TestProperty).value shouldBe "testValue"
144 | (graph.V().toList() should have).size(2)
145 | }
146 |
147 | it("adds a vertex with a given label") {
148 | val graph = TinkerGraph.open.asScala()
149 | val label1 = "label1"
150 | val label2 = "label2"
151 | val v1 = graph.addVertex(label1)
152 | val v2 = graph.addVertex(label2, Map(TestProperty.name -> "testValue"))
153 |
154 | graph.V().has(T.label, label1).head() shouldBe v1.vertex
155 | graph.V().has(T.label, label2).head() shouldBe v2.vertex
156 | graph.V().has(T.label, label2).value(TestProperty).head() shouldBe "testValue"
157 | }
158 |
159 | it("adds a vertex with a given label with syntactic sugar") {
160 | val graph = TinkerGraph.open.asScala()
161 | val label1 = "label1"
162 | val label2 = "label2"
163 |
164 | val v1 = graph + label1
165 | val v2 = graph + (label2, TestProperty -> "testValue")
166 |
167 | graph.V().hasLabel(label1).head() shouldBe v1.vertex
168 | graph.V().hasLabel(label2).head() shouldBe v2.vertex
169 | graph.V().hasLabel(label2).value(TestProperty).head() shouldBe "testValue"
170 |
171 | graph.asJava().close()
172 | }
173 |
174 | it("adds an edge") {
175 | val graph = TinkerGraph.open.asScala()
176 | val v1 = graph.addVertex()
177 | val v2 = graph.addVertex()
178 |
179 | val e = v1.addEdge("testLabel", v2)
180 | e.label shouldBe "testLabel"
181 | v1.outE().head() shouldBe e.edge
182 | v1.out("testLabel").head() shouldBe v2.vertex
183 | }
184 |
185 | it("adds an edge with additional properties") {
186 | implicit val graph = TinkerGraph.open.asScala()
187 | val v1 = graph.addVertex()
188 | val v2 = graph.addVertex()
189 |
190 | val e = v1.asScala().addEdge("testLabel", v2, TestProperty -> "testValue")
191 | e.label shouldBe "testLabel"
192 | e.value2(TestProperty) shouldBe "testValue"
193 | e.valueMap(TestProperty.name) shouldBe Map(TestProperty.name -> "testValue")
194 | v1.outE().head() shouldBe e.edge
195 | v1.out("testLabel").head() shouldBe v2.vertex
196 | }
197 |
198 | it("removes elements") {
199 | val graph = TinkerGraph.open.asScala()
200 | val v = graph.addVertex()
201 | v.remove()
202 | graph.V().toList() shouldBe empty
203 | }
204 | }
205 |
206 | def v1 = v(1).asScala()
207 | def e7 = e(7).asScala()
208 | val TestProperty = Key[String]("testProperty")
209 | }
210 |
--------------------------------------------------------------------------------
/gremlin-scala/src/test/scala/gremlin/scala/FilterSpec.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.tinkerpop.gremlin.process.traversal.TextP
4 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory
5 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph
6 | import TestGraph._
7 | import org.scalatest.wordspec.AnyWordSpec
8 | import org.scalatest.matchers.should.Matchers
9 |
10 | class FilterSpec extends AnyWordSpec with Matchers {
11 |
12 | "filter" in new Fixture {
13 | graph
14 | .V()
15 | .filter(_.value(Age).is(P.gt(30)))
16 | .value(Name)
17 | .toSet() should be(Set("josh", "peter"))
18 | }
19 |
20 | "filterNot" in new Fixture {
21 | graph
22 | .V()
23 | .filterNot(_.value(Age).is(P.gt(30)))
24 | .value(Name)
25 | .toSet() should be(Set("lop", "marko", "vadas", "ripple"))
26 | }
27 |
28 | "filter on end type" in new Fixture {
29 | graph
30 | .V()
31 | .filterOnEnd(_.property(Age).orElse(0) > 30)
32 | .value(Name)
33 | .toSet() should be(Set("josh", "peter"))
34 | }
35 |
36 | "filter with TextP - e.g. contains, endsWith etc" in new Fixture {
37 | graph
38 | .V()
39 | .has(Name, TextP.containing("os"))
40 | .value(Name)
41 | .toSet() should be(Set("josh"))
42 | }
43 |
44 | "has" in new Fixture {
45 | graph.V().has(Age, 35).value(Name).toSet() shouldBe Set("peter")
46 | }
47 |
48 | "has - sugar" in new Fixture {
49 | implicit val g = TinkerGraph.open.asScala()
50 | g + ("software", Name -> "blueprints", Created -> 2010)
51 |
52 | g.V()
53 | .has(Name -> "blueprints")
54 | .head() <-- "dependsOn" --- (g + ("software", Name -> "gremlin", Created -> 2009))
55 | g.V()
56 | .has(Name -> "gremlin")
57 | .head() <-- "dependsOn" --- (g + ("software", Name -> "gremlinScala"))
58 | g.V()
59 | .has(Name -> "gremlinScala")
60 | .head() <-- "createdBy" --- (g + ("person", Name -> "mpollmeier"))
61 |
62 | g.V().toList().size shouldBe 4
63 | g.V().hasLabel("software").toList().size shouldBe 3
64 | g.V().hasLabel("person").toList().size shouldBe 1
65 |
66 | g.E().toList().size shouldBe 3
67 | g.E().hasLabel("dependsOn").toList().size shouldBe 2
68 | g.E().hasLabel("createdBy").toList().size shouldBe 1
69 |
70 | g.asJava().close()
71 | }
72 |
73 | "hasNot" in new Fixture {
74 | graph.V().hasNot(Age, 35).value(Name).toSet() shouldBe Set(
75 | "lop",
76 | "marko",
77 | "josh",
78 | "vadas",
79 | "ripple"
80 | )
81 | }
82 |
83 | "coin all" in new Fixture {
84 | graph.V().coin(1.0d).value(Name).toSet() shouldBe Set(
85 | "lop",
86 | "marko",
87 | "josh",
88 | "vadas",
89 | "ripple",
90 | "peter"
91 | )
92 | }
93 |
94 | "coin nothing" in new Fixture {
95 | graph.V().coin(0.0d).value(Name).toSet() shouldBe Set()
96 | }
97 |
98 | "dedup success" in new Fixture {
99 | val a = StepLabel[Edge]()
100 | val b = StepLabel[Vertex]()
101 |
102 | graph
103 | .V()
104 | .outE()
105 | .as(a)
106 | .inV()
107 | .as(b)
108 | .select(a)
109 | .select(b)
110 | .order(By(Name))
111 | .value(Name)
112 | .dedup()
113 | .toList() shouldBe List("josh", "lop", "ripple", "vadas")
114 | }
115 |
116 | "drop success" in new Fixture {
117 | graph.V().outE().drop().toSet() shouldBe Set()
118 | }
119 |
120 | "is usage" in new Fixture {
121 | graph.V().value(Age).is(P.lte(30)).toSet() shouldBe Set(27, 29)
122 | }
123 |
124 | "range success" in new Fixture {
125 | val markoVertexId = 1
126 | (graph
127 | .V(markoVertexId)
128 | .out("knows")
129 | .out("created")
130 | .range(0, 1)
131 | .value(Name)
132 | .toSet() should contain).oneOf("lop", "ripple")
133 | }
134 |
135 | "simple path" in new Fixture {
136 | val markoVertexId = 1
137 | graph
138 | .V(markoVertexId)
139 | .out("created")
140 | .in("created")
141 | .simplePath()
142 | .value(Name)
143 | .toSet() shouldBe Set(
144 | "josh",
145 | "peter"
146 | )
147 | }
148 |
149 | "tail" in new Fixture {
150 | graph.V().value(Name).order().tail(2).toSet() shouldBe Set("ripple", "vadas")
151 | }
152 |
153 | "where" in new Fixture {
154 | val a = StepLabel[Vertex]()
155 | graph
156 | .V()
157 | .as(a)
158 | .out("created")
159 | .where(_.as(a).value(Name).is("josh"))
160 | .in("created")
161 | .value(Name)
162 | .toSet() shouldBe Set("marko", "josh", "peter")
163 | }
164 |
165 | trait Fixture {
166 | val graph = TinkerFactory.createClassic.asScala()
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/gremlin-scala/src/test/scala/gremlin/scala/GraphHelperSpec.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph
4 | import org.scalatest.wordspec.AnyWordSpec
5 | import org.scalatest.matchers.should.Matchers
6 |
7 | class GraphHelperSpec extends AnyWordSpec with Matchers {
8 |
9 | "deep clone graph" in {
10 | val original = TinkerGraph.open.asScala()
11 | val testProperty = Key[String]("testProperty")
12 |
13 | {
14 | implicit val graph: ScalaGraph = original
15 | val marko = original + "marko"
16 | val stephen = original + "stephen"
17 | marko --- "knows" --> stephen
18 | }
19 |
20 | {
21 | implicit val clone: ScalaGraph = GraphHelper.cloneElements(original, TinkerGraph.open.asScala())
22 | val stephen = clone.V().hasLabel("stephen").head()
23 | val michael = clone + "michael"
24 | michael --- "knows" --> stephen
25 | clone.V().property(testProperty, "someValue").iterate()
26 |
27 | // original graph should be unchanged
28 | original.V().count().head() shouldBe 2
29 | original.E().count().head() shouldBe 1
30 | original.V().has(testProperty).count().head() shouldBe 0
31 |
32 | // cloned graph should contain old and new elements and properties
33 | clone.V().count().head() shouldBe 3
34 | clone.E().count().head() shouldBe 2
35 | clone.V().has(testProperty).count().head() shouldBe 3
36 | }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/gremlin-scala/src/test/scala/gremlin/scala/GraphSerialisationSpec.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import java.io.FileOutputStream
4 | import org.apache.tinkerpop.gremlin.structure.io.IoCore
5 | import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONVersion
6 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.{TinkerFactory, TinkerGraph}
7 | import org.scalatest.wordspec.AnyWordSpec
8 | import org.scalatest.matchers.should.Matchers
9 |
10 | class GraphSerialisationSpec extends AnyWordSpec with Matchers {
11 |
12 | "serialising from/to" should {
13 |
14 | "support graphML" in {
15 | val file = "target/tinkerpop-modern.graphml"
16 | graph.io(IoCore.graphml).writeGraph(file)
17 |
18 | val newGraph = TinkerGraph.open
19 | newGraph.io(IoCore.graphml).readGraph(file)
20 | newGraph.V().count().head() shouldBe 6
21 | newGraph.E().count().head() shouldBe 6
22 | }
23 |
24 | "support graphson" in {
25 | val file = "target/tinkerpop-modern.graphson.json"
26 | graph.io(IoCore.graphson).writeGraph(file)
27 |
28 | val newGraph = TinkerGraph.open
29 | newGraph.io(IoCore.graphson).readGraph(file)
30 | newGraph.V().count().head() shouldBe 6
31 | newGraph.E().count().head() shouldBe 6
32 | }
33 |
34 | "support graphson v2" in {
35 | val file = "target/tinkerpop-modern.graphson2.json"
36 | val mapper = graph
37 | .io(IoCore.graphson)
38 | .mapper
39 | .normalize(true)
40 | .version(GraphSONVersion.V2_0)
41 | .create
42 | graph
43 | .io(IoCore.graphson)
44 | .writer
45 | .mapper(mapper)
46 | .create
47 | .writeGraph(new FileOutputStream(file), graph)
48 |
49 | val newGraph = TinkerGraph.open
50 | newGraph.io(IoCore.graphson).readGraph(file)
51 | newGraph.V().count().head() shouldBe 6
52 | newGraph.E().count().head() shouldBe 6
53 | }
54 |
55 | "support gryo/kryo" in {
56 | val file = "target/tinkerpop-modern.gryo"
57 | graph.io(IoCore.gryo).writeGraph(file)
58 |
59 | val newGraph = TinkerGraph.open
60 | newGraph.io(IoCore.gryo).readGraph(file)
61 | newGraph.V().count().head() shouldBe 6
62 | newGraph.E().count().head() shouldBe 6
63 | }
64 | }
65 |
66 | def graph = TinkerFactory.createModern
67 | }
68 |
--------------------------------------------------------------------------------
/gremlin-scala/src/test/scala/gremlin/scala/LogicalSpec.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph
4 | import org.scalatest.wordspec.AnyWordSpec
5 | import org.scalatest.matchers.should.Matchers
6 |
7 | class LogicalSpec extends AnyWordSpec with Matchers {
8 |
9 | "choose" should {
10 | "provide simple version for if/else semantic" in new Fixture {
11 | graph.V()
12 | .choose(
13 | _.value(Age).is(P.gt(30)),
14 | onTrue = _.value(Height),
15 | onFalse = _.value(Shoesize)
16 | )
17 | .toSet() shouldBe Set(190,
18 | 176, // Michael and Steffi are >30 - take their height
19 | 5) // Karlotta is <=30 - take her shoesize
20 | }
21 |
22 | "provide if/elseif/else semantic" in new Fixture {
23 | // note: this is only if/elseif/else semantic because the PickToken is enforced to be unique
24 | graph.V()
25 | .choose(
26 | on = _.value(Age),
27 | BranchCase(34, _.value(Height)),
28 | BranchCase(32, _.value(Shoesize)),
29 | BranchOtherwise(_.value(YearOfBirth))
30 | )
31 | .toSet() shouldBe Set(190, // Michael is 34 - take his height
32 | 41, //Steffi is 32 - take her shoesize
33 | 2015) // Karlotta is case `Otherwise` - take her year of birth
34 | }
35 | }
36 |
37 | "coalesce provides if/elseif/else semantics" in new Fixture {
38 | graph.V()
39 | .value(Age)
40 | .coalesce(
41 | _.is(P.lt(31)).constant("young"),
42 | _.is(P.lt(34)).constant("old"),
43 | _.constant("very old")
44 | )
45 | .toList() shouldBe List("very old", "old", "young")
46 | }
47 |
48 | "branch" should {
49 | "execute all cases that match" in new Fixture {
50 | graph.V()
51 | .branch(
52 | on = _.value(Age),
53 | BranchCase(34, _.value(Height)),
54 | BranchCase(32, _.value(Shoesize)),
55 | BranchCase(1, _.value(YearOfBirth))
56 | )
57 | .toSet() shouldBe Set(190, // Michael is 34 - take his height
58 | 41, //Steffi is 32 - take her shoesize
59 | 2015) // Karlotta is 1 - take her year of birth
60 | }
61 |
62 | "allow to use `matchAll` semantics" in new Fixture {
63 | graph.V()
64 | .branch(
65 | on = _.value(Age),
66 | BranchCase(34, _.value(Height)),
67 | BranchMatchAll(_.value(YearOfBirth))
68 | )
69 | .toSet() shouldBe Set(190,
70 | 1983, // Michael's height (since he is 34) and year of birth
71 | 1984, //Steffi's year of birth
72 | 2015) // Karlotta's year of birth
73 | }
74 | }
75 |
76 | "and step returns results if all conditions are met" in new Fixture {
77 | graph.V()
78 | .and(
79 | _.label().is(Person),
80 | _.out().has(Name -> "Karlotta")
81 | )
82 | .value(Name)
83 | .toSet() shouldBe Set("Michael", "Steffi")
84 | }
85 |
86 | "or step returns results if at least one condition is met" in new Fixture {
87 | graph.V()
88 | .or(
89 | _.label().is("does not exist"),
90 | _.has(Age -> 34)
91 | )
92 | .value(Name)
93 | .toSet() shouldBe Set("Michael")
94 | }
95 |
96 | "exists" should {
97 | "return true if one or more elements found" in new Fixture {
98 | graph.V().exists() shouldBe true
99 | }
100 |
101 | "return false if no elements found" in new Fixture {
102 | graph.V().filter(_.has(Key("nonExistingProperty"))).exists() shouldBe false
103 | }
104 | }
105 |
106 | trait Fixture {
107 | implicit val graph = TinkerGraph.open.asScala()
108 |
109 | val Person = "person"
110 | val Name = Key[String]("name")
111 | val Age = Key[Int]("age")
112 | val Height = Key[Int]("height")
113 | val Shoesize = Key[Int]("shoesize")
114 | val YearOfBirth = Key[Int]("yearOfBirth")
115 | val StreetNumber = Key[Int]("streetNumber")
116 | val parentOf = "parentOf"
117 | val marriedTo = "marriedTo"
118 |
119 | val michael = graph + (Person, Name -> "Michael", Age -> 34, Height -> 190, Shoesize -> 44, YearOfBirth -> 1983, StreetNumber -> 3)
120 | val steffi = graph + (Person, Name -> "Steffi", Age -> 32, Height -> 176, Shoesize -> 41, YearOfBirth -> 1984, StreetNumber -> 3)
121 | val karlotta = graph + (Person, Name -> "Karlotta", Age -> 1, Height -> 90, Shoesize -> 5, YearOfBirth -> 2015, StreetNumber -> 3)
122 | michael <-- marriedTo --> steffi
123 | michael --- parentOf --> karlotta
124 | steffi --- parentOf --> karlotta
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/gremlin-scala/src/test/scala/gremlin/scala/MonadSpec.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph
4 | import org.scalatest.wordspec.AnyWordSpec
5 | import org.scalatest.matchers.should.Matchers
6 |
7 | class MonadSpec extends AnyWordSpec with Matchers {
8 |
9 | "obeys the monad laws" in {
10 | // based on examples in http://devth.com/2015/monad-laws-in-scala/
11 |
12 | val graph = TinkerGraph.open.asScala()
13 |
14 | def f(x: Int): GremlinScala[Int] = if (x < 10) __[Int]() else __(x * 2)
15 | def g(x: Int): GremlinScala[Int] = if (x > 50) __(x + 1) else __[Int]()
16 |
17 | withClue("left identity") {
18 | val a = 30
19 | val lhs = __(a).flatMap(f).head()
20 | val rhs = f(a).head()
21 | lhs shouldBe 60
22 | lhs shouldBe rhs
23 | }
24 |
25 | withClue("right identity") {
26 | def m = __(30)
27 | val lhs = m.flatMap { x: Int =>
28 | __(x)
29 | }.head()
30 | lhs shouldBe 30
31 | val rhs = m.head()
32 | lhs shouldBe rhs
33 | }
34 |
35 | withClue("associativity") {
36 | def m = __(30)
37 | val lhs = m.flatMap(f).flatMap(g).head()
38 | lhs shouldBe 61
39 |
40 | val rhs = m.flatMap(x => f(x).flatMap(g)).head()
41 | rhs shouldBe 61
42 |
43 | lhs shouldBe rhs
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/gremlin-scala/src/test/scala/gremlin/scala/PSpec.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.tinkerpop.gremlin.process.traversal.{P => JavaP}
4 | import org.scalatest.wordspec.AnyWordSpec
5 | import org.scalatest.matchers.should.Matchers
6 |
7 | /* test cases copied from TP3 PTest.java */
8 | class PSpec extends AnyWordSpec with Matchers {
9 |
10 | List(
11 | TestCase(P.eq(0), 0, true),
12 | TestCase(P.eq(0), 1, false),
13 | TestCase(P.neq(0), 0, false),
14 | TestCase(P.neq(0), 1, true),
15 | TestCase(P.gt(0), -1, false),
16 | TestCase(P.gt(0), 0, false),
17 | TestCase(P.gt(0), 1, true),
18 | TestCase(P.lt(0), -1, true),
19 | TestCase(P.lt(0), 0, false),
20 | TestCase(P.lt(0), 1, false),
21 | TestCase(P.gte(0), -1, false),
22 | TestCase(P.gte(0), 0, true),
23 | TestCase(P.gte(0), 1, true),
24 | TestCase(P.lte(0), -1, true),
25 | TestCase(P.lte(0), 0, true),
26 | TestCase(P.lte(0), 1, false),
27 | TestCase(P.between(1, 10), 0, false),
28 | TestCase(P.between(1, 10), 1, true),
29 | TestCase(P.between(1, 10), 9, true),
30 | TestCase(P.between(1, 10), 10, false),
31 | TestCase(P.inside(1, 10), 0, false),
32 | TestCase(P.inside(1, 10), 1, false),
33 | TestCase(P.inside(1, 10), 9, true),
34 | TestCase(P.inside(1, 10), 10, false),
35 | TestCase(P.outside(1, 10), 0, true),
36 | TestCase(P.outside(1, 10), 1, false),
37 | TestCase(P.outside(1, 10), 9, false),
38 | TestCase(P.outside(1, 10), 10, false),
39 | TestCase(P.within(Set(1, 2, 3)), 0, false),
40 | TestCase(P.within(Set(1, 2, 3)), 1, true),
41 | TestCase(P.within(Set(1, 2, 3)), 10, false),
42 | TestCase(P.without(Set(1, 2, 3)), 0, true),
43 | TestCase(P.without(Set(1, 2, 3)), 1, false),
44 | TestCase(P.without(Set(1, 2, 3)), 10, true)
45 | ).map { testCase =>
46 | s"$testCase" in {
47 | testCase.predicate.test(testCase.value) shouldBe testCase.expected
48 | }
49 | }
50 |
51 | List(
52 | TestCase(P.between("m", "n").and(P.neq("marko")), "marko", false),
53 | TestCase(P.between("m", "n").and(P.neq("marko")), "matthias", true),
54 | TestCase(P.between("m", "n").or(P.eq("daniel")), "marko", true),
55 | TestCase(P.between("m", "n").or(P.eq("daniel")), "daniel", true),
56 | TestCase(P.between("m", "n").or(P.eq("daniel")), "stephen", false)
57 | ).map { testCase =>
58 | s"$testCase" in {
59 | testCase.predicate.test(testCase.value) shouldBe testCase.expected
60 | }
61 | }
62 |
63 | case class TestCase[A](predicate: JavaP[A], value: A, expected: Boolean)
64 | }
65 |
--------------------------------------------------------------------------------
/gremlin-scala/src/test/scala/gremlin/scala/ProjectSpec.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory
4 | import org.scalatest.wordspec.AnyWordSpec
5 | import org.scalatest.matchers.should.Matchers
6 | import shapeless.syntax.std.tuple._
7 |
8 | class ProjectSpec extends AnyWordSpec with Matchers {
9 |
10 | "projecting by two traversals" in {
11 | val result: (java.lang.Long, java.lang.Long) =
12 | graph.V()
13 | .has(name.of("marko"))
14 | .project(_(By(__().outE().count())).and(By(__().inE().count())))
15 | .head()
16 |
17 | result shouldBe (3, 0)
18 | }
19 |
20 | "projecting by property and traversal" in {
21 | val result: List[(String, java.lang.Long)] =
22 | graph.V()
23 | .out("created")
24 | .project(_(By(name)).and(By(__().in("created").count())))
25 | .toList()
26 |
27 | result shouldBe List(
28 | ("lop", 3),
29 | ("lop", 3),
30 | ("lop", 3),
31 | ("ripple", 1)
32 | )
33 | }
34 |
35 | def graph: ScalaGraph = TinkerFactory.createModern.asScala()
36 | val name = Key[String]("name")
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/gremlin-scala/src/test/scala/gremlin/scala/SchemaSpec.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph
4 | import org.scalatest.wordspec.AnyWordSpec
5 | import org.scalatest.matchers.should.Matchers
6 | import shapeless.test.illTyped
7 |
8 | class SchemaSpec extends AnyWordSpec with Matchers {
9 |
10 | "a schema with defined Keys".can {
11 | val Software = "software"
12 | val Person = "person"
13 | val Paris = "Paris"
14 | val London = "London"
15 | val EuroStar = "eurostar"
16 | object Name extends Key[String]("name")
17 | object Created extends Key[Int]("created")
18 | object Type extends Key[String]("type")
19 | object Weight extends Key[Int]("weight")
20 |
21 | "create vertices" in {
22 | implicit val graph = TinkerGraph.open.asScala()
23 |
24 | val v0 = graph + (Software, Name -> "blueprints", Created -> 2010)
25 | val v1 = graph + (Software, Created -> 2009, Name -> "gremlin")
26 | val v2 = graph + (Software, Name -> "gremlinScala")
27 | val v3 = graph + (Person, Name -> "mpollmeier")
28 |
29 | graph.V().toList().size shouldBe 4
30 | graph.V().hasLabel(Software).toList().size shouldBe 3
31 | graph.V().hasLabel(Person).toList().size shouldBe 1
32 |
33 | graph.V().has(Name).toList().size shouldBe 4
34 | graph.V().has(Created).toList().size shouldBe 2
35 |
36 | graph.asJava().close()
37 | }
38 |
39 | "read type safe properties" when {
40 | "using `value`" should {
41 | "support vertices" in new Fixture {
42 | val name = paris.value2(Name)
43 | val someString: String = name
44 | illTyped { //to ensure that there is no implicit conversion to make the above work
45 | """
46 | val i: Integer = paris.value2(Name)
47 | """
48 | }
49 | }
50 |
51 | "support edges" in new Fixture {
52 | val distance = rail.value2(Distance)
53 | val someInt: Int = distance
54 | illTyped { //to ensure that there is no implicit conversion to make the above work
55 | """
56 | val i: String = v.value2(Distance)
57 | """
58 | }
59 | }
60 |
61 | "support traversal" in new Fixture {
62 | val name = paris.out(EuroStar).value(Name).head()
63 | val someString: String = name //no implicit conversion, it already is a String
64 |
65 | val distance = paris.outE(EuroStar).value(Distance).head()
66 | val someInt: Int = distance //no implicit conversion, it already is an Int
67 | }
68 | }
69 |
70 | "using `property`" should {
71 | "support vertices" in new Fixture {
72 | val name = paris.property(Name)
73 | val someString: Property[String] = name
74 | illTyped { //to ensure that there is no implicit conversion to make the above work
75 | """
76 | val i: Property[Integer] = paris.property(Name)
77 | """
78 | }
79 | }
80 |
81 | "support edges" in new Fixture {
82 | val distance = rail.property(Distance)
83 | val someInt: Property[Int] = distance //no implicit conversion, it already is an Int
84 | illTyped { //to ensure that there is no implicit conversion to make the above work
85 | """
86 | val i: Property[String] = v.property(Distance)
87 | """
88 | }
89 | }
90 | }
91 |
92 | trait Fixture {
93 | val City = "city"
94 | object Name extends Key[String]("name")
95 | object Population extends Key[Int]("population")
96 | object Distance extends Key[Int]("distance")
97 |
98 | implicit val graph = TinkerGraph.open.asScala()
99 | val paris = graph + (City, Name -> "paris")
100 | val london = graph + (City, Name -> "london")
101 | val rail = paris --- (EuroStar, Distance -> 495) --> london
102 | }
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/gremlin-scala/src/test/scala/gremlin/scala/SelectSpec.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory
4 | import org.scalatest.wordspec.AnyWordSpec
5 | import org.scalatest.matchers.should.Matchers
6 | import shapeless.{::, HNil}
7 | import java.util.{Map => JMap}
8 | import scala.collection.JavaConverters._
9 |
10 | class SelectSpec extends AnyWordSpec with Matchers {
11 | def graph = TinkerFactory.createModern.asScala()
12 |
13 | "selecting all labelled steps" should {
14 | "support single label" in {
15 | val path: List[Tuple1[Edge]] =
16 | graph.V(1).outE().as("a").select().toList()
17 |
18 | path(0)._1 shouldBe graph.E(9).head()
19 | path(1)._1 shouldBe graph.E(7).head()
20 | path(2)._1 shouldBe graph.E(8).head()
21 | }
22 |
23 | "support multiple label" in {
24 | val path: List[(Vertex, Edge)] =
25 | graph.V(1).as("a").outE().as("b").select().toList()
26 |
27 | val v1 = graph.V(1).head()
28 | path(0) shouldBe ((v1, graph.E(9).head()))
29 | path(1) shouldBe ((v1, graph.E(7).head()))
30 | path(2) shouldBe ((v1, graph.E(8).head()))
31 | }
32 |
33 | "works without labelled steps" in {
34 | val path: List[Unit] =
35 | graph.V(1).outE().inV().select().toList()
36 |
37 | path(0) shouldBe (())
38 | path(1) shouldBe (())
39 | path(2) shouldBe (())
40 | }
41 | }
42 |
43 | "selecting one or more specific labels" should {
44 | val a = StepLabel[Vertex]()
45 | val b = StepLabel[Edge]()
46 | val c = StepLabel[Double]()
47 |
48 | val v1 = graph.V(1).head()
49 | val e9 = graph.E(9).head()
50 |
51 | def newTraversal: GremlinScala.Aux[Double, Vertex :: Edge :: Double :: HNil] =
52 | graph.V(1).as(a).outE("created").as(b).value(TestGraph.Weight).as(c)
53 |
54 | "derive types for a simple as/select" in {
55 | val traversal = newTraversal
56 | val result: Vertex =
57 | traversal.select(a).head()
58 |
59 | result shouldBe v1
60 | }
61 |
62 | "derive types for as/select with two labels" in {
63 | val traversal = newTraversal
64 | val result: (Vertex, Edge) =
65 | traversal.select((a, b)).head()
66 |
67 | result shouldBe ((v1, e9))
68 | }
69 |
70 | "derive types for as/select with three labels" in {
71 | val traversal = newTraversal
72 | val result: (Vertex, Edge, Double) =
73 | traversal.select((a, b, c)).head()
74 |
75 | result shouldBe ((v1, e9, 0.4))
76 | }
77 | }
78 |
79 | "resets labels on ReducingBarrier steps" should {
80 | "work for `mean`" in {
81 | graph
82 | .V()
83 | .as("a")
84 | .outE("created")
85 | .value(TestGraph.Weight)
86 | .mean()
87 | .as("b")
88 | .select()
89 | .head()
90 | ._1 shouldBe 0.49999999999999994
91 | }
92 |
93 | "work for `count`" in {
94 | graph
95 | .V()
96 | .as("a")
97 | .out("created")
98 | .count()
99 | .as("b")
100 | .select()
101 | .head()
102 | ._1 shouldBe 4
103 | }
104 |
105 | "work for `max`" in {
106 | graph
107 | .V()
108 | .as("a")
109 | .outE("created")
110 | .value(TestGraph.Weight)
111 | .max()
112 | .as("b")
113 | .select()
114 | .head()
115 | ._1 shouldBe 1.0
116 | }
117 |
118 | "work for `min`" in {
119 | graph
120 | .V()
121 | .as("a")
122 | .outE("created")
123 | .value(TestGraph.Weight)
124 | .min()
125 | .as("b")
126 | .select()
127 | .head()
128 | ._1 shouldBe 0.2
129 | }
130 |
131 | "work for `sum`" in {
132 | val sum = graph
133 | .V()
134 | .as("a")
135 | .outE("created")
136 | .value(TestGraph.Weight)
137 | .sum()
138 | .as("b")
139 | .select()
140 | .head()
141 | ._1
142 |
143 | (sum: Double) shouldBe 2d +- 0.1d
144 | }
145 | }
146 |
147 | "select column" should {
148 | "extract keys from map" in {
149 | val result: collection.Set[String] = graph
150 | .V()
151 | .hasLabel("software")
152 | .group(By(__().value(Key[String]("name"))), By(__().in("created")))
153 | .selectKeys
154 | .head()
155 | .asScala
156 | result shouldBe Set("ripple", "lop")
157 | }
158 |
159 | "extract keys from map entry" in {
160 | val result: List[String] = graph
161 | .V()
162 | .hasLabel("software")
163 | .group(By(__().value(Key[String]("name"))), By(__().in("created")))
164 | .unfold[JMap.Entry[String, Vertex]]()
165 | .selectKeys
166 | .toList()
167 | result shouldBe List("ripple", "lop")
168 | }
169 |
170 | "extract keys from path" in {
171 | val result: Seq[collection.Set[String]] = graph
172 | .V(1)
173 | .as("a")
174 | .outE()
175 | .as("b")
176 | .path()
177 | .selectKeys
178 | .head()
179 | .asScala
180 | .toList
181 | .map(_.asScala)
182 | result shouldBe List(Set("a"), Set("b"))
183 | }
184 |
185 | "extract values from map" in {
186 | val result: Iterable[String] = graph
187 | .V()
188 | .hasLabel("software")
189 | .group(By(Key[String]("lang")), By(Key[String]("name")))
190 | .selectValues
191 | .head()
192 | .asScala
193 | .flatMap(_.asScala)
194 | result shouldBe List("lop", "ripple")
195 | }
196 |
197 | "extract values from map entry" in {
198 | val result: List[String] = graph
199 | .V()
200 | .hasLabel("software")
201 | .group(By(__().value(Key[String]("name"))),
202 | By(__().in("created").value(Key[String]("name"))))
203 | .unfold[JMap.Entry[String, String]]()
204 | .selectValues
205 | .toList()
206 |
207 | result shouldBe List("josh", "marko")
208 | }
209 |
210 | "extract values from path" in {
211 | val result: java.util.List[Any] = graph
212 | .V(1)
213 | .as("a")
214 | .outE()
215 | .as("b")
216 | .path()
217 | .selectValues
218 | .head()
219 | result.get(0).toString shouldBe "v[1]"
220 | result.get(1).toString shouldBe "e[9][1-created->3]"
221 | }
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/gremlin-scala/src/test/scala/gremlin/scala/TestBase.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory
4 | import org.scalatest.funspec.AnyFunSpec
5 | import org.scalatest.matchers.should.Matchers
6 |
7 | trait TestGraph {
8 | implicit val graph = TinkerFactory.createClassic.asScala()
9 | def v(i: Int) = graph.V(i: Integer).head()
10 | def e(i: Int) = graph.E(i: Integer).head()
11 |
12 | def print(gs: GremlinScala[_]) = println(gs.toList())
13 | }
14 |
15 | object TestGraph {
16 | val Name = Key[String]("name")
17 | val Age = Key[Int]("age")
18 | val Created = Key[Int]("created")
19 | val Location = Key[String]("location")
20 | val Weight = Key[Double]("weight")
21 | val DoesNotExist = Key[Any]("doesnt_exist")
22 |
23 | @label("person")
24 | case class Person(name: String, age: Int)
25 |
26 | @label("software")
27 | case class Software(name: String, lang: String)
28 | }
29 |
30 | trait TestBase extends AnyFunSpec with Matchers with TestGraph {
31 | implicit class Properties[A](set: Iterable[Property[A]]) {
32 | def unroll(): Iterable[A] = set.map(_.value)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/gremlin-scala/src/test/scala/gremlin/scala/TraversalStrategySpec.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import java.util
4 | import java.util.concurrent.CompletableFuture
5 |
6 | import org.apache.tinkerpop.gremlin.process.remote.RemoteConnection
7 | import org.apache.tinkerpop.gremlin.process.remote.traversal.{
8 | AbstractRemoteTraversal,
9 | DefaultRemoteTraverser,
10 | RemoteTraversal
11 | }
12 | import org.apache.tinkerpop.gremlin.structure.{Vertex => TVertex}
13 | import org.apache.tinkerpop.gremlin.process.traversal.{Bytecode, Step, TraversalSideEffects}
14 | import org.apache.tinkerpop.gremlin.process.traversal.Traverser.Admin
15 | import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph
16 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory
17 | import org.scalamock.scalatest.MockFactory
18 | import org.scalatest.wordspec.AnyWordSpec
19 | import org.scalatest.matchers.should.Matchers
20 |
21 | import scala.concurrent.Await
22 | import scala.concurrent.duration.{FiniteDuration, MILLISECONDS}
23 | import scala.util.Random
24 |
25 | class TraversalStrategySpec extends AnyWordSpec with Matchers with MockFactory {
26 |
27 | "sack step".can {
28 |
29 | /** http://tinkerpop.apache.org/docs/current/reference/#sack-step */
30 | "carry simple value" when {
31 | "using constant for initial sack" in new Fixture {
32 | graph.configure(_.withSack(1d)).V().sack().toList() shouldBe List(1d, 1d, 1d, 1d, 1d, 1d)
33 | }
34 |
35 | "using function for initial sack" in new Fixture {
36 | graph.configure(_.withSack(() => 1d)).V().sack().toList() shouldBe List(1d, 1d, 1d, 1d, 1d, 1d)
37 |
38 | val randomValues =
39 | graph.configure(_.withSack(() => Random.nextDouble())).V().sack().toList()
40 | randomValues.toSet.size shouldBe 6
41 | }
42 | }
43 |
44 | "transform the sack on the go" in new Fixture {
45 | val result = graph
46 | .configure(_.withSack(1d))
47 | .V()
48 | .repeat {
49 | _.outE().sack { (curr: Double, edge) =>
50 | curr * edge.value2(Weight)
51 | }.inV()
52 | }
53 | .times(2)
54 | .sack()
55 | .toSet()
56 |
57 | result shouldBe Set(1d, 0.4d)
58 | }
59 |
60 | "be modulated with by operator" when {
61 | "modulating by property" in new Fixture {
62 | val result = graph
63 | .configure(_.withSack(1d))
64 | .V(1)
65 | .outE()
66 | .sack(multiply, By(Weight))
67 | .inV()
68 | .sack()
69 | .toSet()
70 |
71 | result shouldBe Set(0.4d, 0.5d, 1d)
72 | }
73 |
74 | "modulating by traversal" in new Fixture {
75 | val result = graph
76 | .configure(_.withSack(1d))
77 | .V(1)
78 | .outE()
79 | .sack(multiply, By(__().value(Weight)))
80 | .inV()
81 | .sack()
82 | .toSet()
83 |
84 | result shouldBe Set(0.4d, 0.5d, 1d)
85 | }
86 | def multiply(a: Double, b: Double): Double = a * b
87 | }
88 |
89 | "use provided split operator when cloning sack" in new Fixture {
90 | var counter = 0
91 | val identityWithCounterIncrease = { value: Double =>
92 | counter += 1
93 | value
94 | }
95 |
96 | graph
97 | .configure(_.withSack(1d, splitOperator = identityWithCounterIncrease))
98 | .V()
99 | .out()
100 | .toList()
101 | counter shouldBe 6
102 | }
103 |
104 | "use provided merge operator when bulking sack" in new Fixture {
105 | val sum = (f1: Double, f2: Double) => f1 + f2
106 | graph
107 | .configure(_.withSack(1d, mergeOperator = sum))
108 | .V(1)
109 | .out("knows")
110 | .in("knows")
111 | .sack()
112 | .toList() shouldBe List(2d, 2d)
113 | // without `sum` would be List(1d, 1d)
114 | }
115 |
116 | "be configured when starting from an element" when {
117 | "on a vertex" in new Fixture {
118 | val v1: Vertex = graph.V(1).head()
119 | v1.start(_.withSack(1d)).outE(Knows).sack().toList() shouldBe List(1d, 1d)
120 | }
121 |
122 | "on an edge" in new Fixture {
123 | val e7: Edge = graph.E(7).head()
124 | e7.start(_.withSack(1d)).outV().outE(Knows).sack().toList() shouldBe List(1d, 1d)
125 | }
126 | }
127 | }
128 |
129 | "withRemote" should {
130 | "use RemoteConnection" in new RemoteGraphFixture {
131 | // Execute a traversal with provided vertices
132 | val result = remoteGraph.V().toList()
133 | result shouldEqual mockVertices
134 | }
135 |
136 | "support promise" in new RemoteGraphFixture {
137 | // Execute a traversal using promise with the provided vertices
138 | val future = remoteGraph.V().promise()
139 | val result = Await.result(future, FiniteDuration(1000, MILLISECONDS))
140 | result shouldEqual mockVertices
141 | }
142 | }
143 |
144 | trait Fixture {
145 | val graph = TinkerFactory.createModern.asScala()
146 | val Name = Key[String]("name")
147 | val Lang = Key[String]("lang")
148 | val Weight = Key[Double]("weight")
149 | val Knows = "knows"
150 | }
151 |
152 | trait RemoteGraphFixture {
153 | val graph = EmptyGraph.instance().asScala()
154 |
155 | // Stub out a remote connection that responds to g.V() with 2 vertices
156 | val connection = stub[RemoteConnection]
157 | val remoteGraph = graph.configure(_.withRemote(connection))
158 |
159 | // effectively a g.V() bytecode
160 | val expectedBytecode: Bytecode = new Bytecode()
161 | expectedBytecode.addStep("V")
162 |
163 | // data to return
164 | val mockVertices = List[TVertex](
165 | DetachedVertex(id = 1: Integer, label = "person"),
166 | DetachedVertex(id = 2: Integer, label = "person")
167 | )
168 |
169 | // Create a future that completes immediately and provides a remote traversal providing vertices
170 | val vertexResult = new CompletableFuture[RemoteTraversal[_ <: Any, TVertex]]
171 | val traversal = new AbstractRemoteTraversal[Int, TVertex] {
172 | val it = mockVertices.iterator
173 |
174 | override def nextTraverser: Admin[TVertex] =
175 | new DefaultRemoteTraverser[Vertex](it.next(), 1)
176 |
177 | override def getSideEffects: TraversalSideEffects =
178 | null // not necessary for this test
179 |
180 | override def next(): TVertex = nextTraverser().get()
181 |
182 | override def hasNext: Boolean = it.hasNext
183 |
184 | override def getSteps: util.List[Step[_, _]] = super.getSteps
185 | }
186 | vertexResult.complete(traversal)
187 |
188 | // when expected byte code provided, return vertex result.
189 | (connection.submitAsync[TVertex] _ when expectedBytecode).returns(vertexResult)
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/gremlin-scala/src/test/scala/gremlin/scala/dsl/DslSpec.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala.dsl
2 |
3 | import gremlin.scala._
4 | import java.util.{Map => JMap}
5 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory
6 | import org.scalatest.wordspec.AnyWordSpec
7 | import org.scalatest.matchers.should.Matchers
8 | import scala.collection.mutable
9 | import scala.collection.JavaConverters._
10 | import shapeless._
11 |
12 | class DslSpec extends AnyWordSpec with Matchers {
13 | import TestDomain._
14 |
15 | "finds all persons" in {
16 | val personSteps = PersonSteps(TinkerFactory.createModern)
17 | personSteps.toSet() shouldBe Set(
18 | Person(Some(1), "marko", 29),
19 | Person(Some(2), "vadas", 27),
20 | Person(Some(4), "josh", 32),
21 | Person(Some(6), "peter", 35)
22 | )
23 | }
24 |
25 | "label with `as` and typesafe `select` of domain types" should {
26 |
27 | "select all labelled steps by default" in {
28 | implicit val graph = TinkerFactory.createModern
29 |
30 | val personAndSoftware: List[(Person, Software)] =
31 | PersonSteps(graph)
32 | .as("person")
33 | .created
34 | .as("software")
35 | .select()
36 | .toList()
37 | (personAndSoftware should have).size(4)
38 |
39 | val softwareByCreator: Map[String, Software] = personAndSoftware.map {
40 | case (person, software) => (person.name, software)
41 | }.toMap
42 | softwareByCreator("marko") shouldBe Software("lop", "java")
43 | }
44 |
45 | "allow to select one labelled step only" in {
46 | implicit val graph = TinkerFactory.createModern
47 | val labelPerson = StepLabel[Person]("p")
48 | val labelSoftware = StepLabel[Software]("s")
49 |
50 | val personAndSoftware: Set[Software] =
51 | PersonSteps(graph)
52 | .as(labelPerson)
53 | .created
54 | .as(labelSoftware)
55 | .select(labelSoftware)
56 | .toSet()
57 | personAndSoftware shouldBe Set(Software("lop", "java"), Software("ripple", "java"))
58 | }
59 |
60 | "allow to select multiple labelled steps" in {
61 | implicit val graph = TinkerFactory.createModern
62 | val labelPerson = StepLabel[Person]("p")
63 | val labelSoftware = StepLabel[Software]("s")
64 |
65 | val personAndSoftware: List[(Software, Person)] =
66 | PersonSteps(graph)
67 | .as(labelPerson)
68 | .created
69 | .as(labelSoftware)
70 | .select((labelSoftware, labelPerson))
71 | .toList()
72 | (personAndSoftware should have).size(4)
73 |
74 | val softwareByCreator: Map[String, Software] = personAndSoftware.map {
75 | case (software, person) => (person.name, software)
76 | }.toMap
77 | softwareByCreator("marko") shouldBe Software("lop", "java")
78 | }
79 | }
80 |
81 | "finds combination of person/software in for comprehension" in {
82 | implicit val graph = TinkerFactory.createModern
83 |
84 | val traversal = for {
85 | person <- PersonSteps(graph)
86 | software <- person.created
87 | } yield (person.name, software)
88 |
89 | val tuples = traversal.toSet() shouldBe Set(
90 | ("marko", Software("lop", "java")),
91 | ("josh", Software("lop", "java")),
92 | ("peter", Software("lop", "java")),
93 | ("josh", Software("ripple", "java"))
94 | )
95 | }
96 |
97 | "filter with traversal on domain type" when {
98 | "domain type is a case class" in {
99 | val ripples = PersonSteps(TinkerFactory.createModern)
100 | .filter(_.created.isRipple)
101 |
102 | ripples.toList() shouldBe List(
103 | Person(Some(4), "josh", 32)
104 | )
105 | }
106 | }
107 |
108 | "filterNot with traversal on domain type" in {
109 | val notRipple = PersonSteps(TinkerFactory.createModern)
110 | .filterNot(_.created.isRipple)
111 |
112 | notRipple.toList().size shouldBe 3
113 | }
114 |
115 | "filter on domain type" in {
116 | val markos: List[Person] =
117 | PersonSteps(TinkerFactory.createModern)
118 | .filterOnEnd(_.name == "marko")
119 | .toList()
120 |
121 | markos.size shouldBe 1
122 | }
123 |
124 | "aggregate intermediary results into a collection" in {
125 | val allPersons = mutable.ArrayBuffer.empty[Person]
126 | val markos: List[Person] =
127 | PersonSteps(TinkerFactory.createModern)
128 | .aggregate(allPersons)
129 | .filterOnEnd(_.name == "marko")
130 | .toList()
131 |
132 | markos.size shouldBe 1
133 | allPersons.size should be > 1
134 | }
135 |
136 | "allow side effects" in {
137 | var i = 0
138 | PersonSteps(TinkerFactory.createModern).sideEffect(_ => i = i + 1).iterate()
139 | i shouldBe 4
140 | }
141 |
142 | "deduplicates" in {
143 | val results: List[Person] =
144 | PersonSteps(TinkerFactory.createModern).created.createdBy.dedup().toList()
145 | results.size shouldBe 3
146 | }
147 |
148 | "allows to use underlying gremlin-scala steps" in {
149 | val steps: PersonSteps[_] =
150 | PersonSteps(TinkerFactory.createModern)
151 | .onRaw(_.hasId(1: Integer))
152 | steps.toList().size shouldBe 1
153 | }
154 |
155 | "traverses from person to software" in {
156 | val personSteps =
157 | PersonSteps(TinkerFactory.createModern)
158 | .onRaw(_.hasId(1: Integer))
159 |
160 | personSteps.created.toSet() shouldBe Set(Software("lop", "java"))
161 | }
162 |
163 | "supports collections in map/flatMap" when {
164 | implicit val graph = TinkerFactory.createModern
165 | def personSteps = PersonSteps(graph)
166 |
167 | "using List" in {
168 | val query = personSteps.map { person =>
169 | (person.name, person.created.toList())
170 | }
171 |
172 | val results: List[(String, List[Software])] = query.toList()
173 | results.size shouldBe 4
174 | }
175 |
176 | "using Set" in {
177 | val query = personSteps.map { person =>
178 | (person.name, person.created.toSet())
179 | }
180 |
181 | val results: List[(String, Set[Software])] = query.toList()
182 | results.size shouldBe 4
183 | }
184 | }
185 |
186 | "allows to be cloned" in {
187 | val graph = TinkerFactory.createModern
188 | def personSteps = PersonSteps(graph)
189 |
190 | val query = personSteps.hasName("marko")
191 | val queryCloned = query.clone()
192 | query.toList().size shouldBe 1
193 | queryCloned.toList().size shouldBe 1
194 | }
195 | }
196 |
197 | object TestDomain {
198 | @label("person") case class Person(@id id: Option[Integer], name: String, age: Integer)
199 | extends DomainRoot
200 | @label("software") case class Software(name: String, lang: String) extends DomainRoot
201 |
202 | object PersonSteps {
203 | def apply(graph: Graph) = new PersonSteps[HNil](graph.V().hasLabel[Person]())
204 | }
205 | class PersonSteps[Labels <: HList](override val raw: GremlinScala[Vertex])
206 | extends NodeSteps[Person, Labels](raw) {
207 |
208 | def created = new SoftwareSteps[Labels](raw.out("created"))
209 |
210 | def name =
211 | new Steps[String, String, Labels](raw.map(_.value[String]("name")))
212 |
213 | def hasName(name: String) =
214 | new PersonSteps[Labels](raw.has(Key("name") -> name))
215 | }
216 |
217 | class SoftwareSteps[Labels <: HList](override val raw: GremlinScala[Vertex])
218 | extends NodeSteps[Software, Labels](raw) {
219 |
220 | def createdBy = new PersonSteps[Labels](raw.in("created"))
221 |
222 | def isRipple = new SoftwareSteps[Labels](raw.has(Key("name") -> "ripple"))
223 | }
224 |
225 | implicit def toPersonSteps[Labels <: HList](
226 | steps: Steps[Person, Vertex, Labels]): PersonSteps[Labels] =
227 | new PersonSteps[Labels](steps.raw)
228 |
229 | implicit def personStepsConstructor[Labels <: HList]
230 | : Constructor.Aux[Person, Labels, Vertex, PersonSteps[Labels]] =
231 | Constructor.forDomainNode[Person, Labels, PersonSteps[Labels]](new PersonSteps[Labels](_))
232 |
233 | implicit def softwareStepsConstructor[Labels <: HList]
234 | : Constructor.Aux[Software, Labels, Vertex, SoftwareSteps[Labels]] =
235 | Constructor.forDomainNode[Software, Labels, SoftwareSteps[Labels]](new SoftwareSteps[Labels](_))
236 |
237 | implicit def liftPerson(person: Person)(implicit graph: Graph): PersonSteps[HNil] =
238 | new PersonSteps[HNil](graph.asScala().V(person.id.get))
239 | }
240 |
--------------------------------------------------------------------------------
/gremlin-scala/src/test/scala/gremlin/scala/marshallable/MarshallableSpec.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala.marshallable
2 | // specifically moved this to a separate package to verify `gremlin.scala.` imports
3 |
4 | import gremlin.scala.{
5 | asScalaEdge,
6 | asScalaVertex,
7 | id,
8 | label,
9 | nullable,
10 | underlying,
11 | Edge,
12 | Element,
13 | GraphAsScala,
14 | Marshallable,
15 | Vertex
16 | }
17 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph
18 | import org.scalatest.wordspec.AnyWordSpec
19 | import org.scalatest.matchers.should.Matchers
20 | import scala.collection.JavaConverters._
21 | import shapeless.test.illTyped
22 |
23 | case class CCSimple(s: String, i: Int)
24 |
25 | case class MyValueClass(value: Int) extends AnyVal
26 | case class CCWithValueClass(s: String, i: MyValueClass)
27 | case class CCWithOption(i: Int, s: Option[String])
28 | case class CCWithOptionValueClass(s: String, i: Option[MyValueClass])
29 | case class CCWithOptionAnyVal(x: Option[Int], y: Option[Long])
30 | case class CCWithList(s: String, ss: List[String], is: List[Int], ds: List[Double])
31 | case class CCWithSet(s: String, ss: Set[String])
32 | case class CCWithNullable(i: Int, @nullable maybeNull: String)
33 |
34 | case class CCWithOptionId(s: String, @id id: Option[Int])
35 | case class CCWithOptionIdNested(s: String, @id id: Option[Int], i: MyValueClass)
36 | case class CCWithNonOptionalIdShouldFail(@id id: Int)
37 |
38 | case class MyIdValueClass(value: Long) extends AnyVal
39 | case class CCWithValueClassId(s: String, @id id: Option[MyIdValueClass])
40 |
41 | case class CCWithUnderlyingVertex(@underlying underlying: Option[Vertex], s: String)
42 | case class CCWithNonOptionalUnderlyingShouldFail(@underlying underlying: Vertex)
43 |
44 | @label("label_a")
45 | case class CCWithLabel(s: String)
46 |
47 | @label("the_label")
48 | case class ComplexCC(
49 | s: String,
50 | l: Long,
51 | o: Option[String],
52 | seq: Seq[String],
53 | map: Map[String, String],
54 | nested: NestedClass
55 | ) { def randomDef = ??? }
56 |
57 | case class NestedClass(s: String)
58 |
59 | class NoneCaseClass(s: String)
60 |
61 | class MarshallableSpec extends AnyWordSpec with Matchers {
62 |
63 | "marshals / unmarshals case classes".which {
64 |
65 | "only have simple members" in new Fixture {
66 | val cc = CCSimple("text", 12)
67 | val v = graph + cc
68 |
69 | val vl = graph.V(v.id).head()
70 | vl.label shouldBe cc.getClass.getSimpleName
71 | vl.valueMap should contain("s" -> cc.s)
72 | vl.valueMap should contain("i" -> cc.i)
73 | }
74 |
75 | "contain options" should {
76 | "map `Some[A]` to `A`" in new Fixture {
77 | val ccWithOptionSome =
78 | CCWithOption(Int.MaxValue, Some("optional value"))
79 | val v = graph + ccWithOptionSome
80 | v.toCC[CCWithOption] shouldBe ccWithOptionSome
81 |
82 | val vl = graph.V(v.id).head()
83 | vl.value[String]("s") shouldBe ccWithOptionSome.s.get
84 | }
85 |
86 | "map `None` to `null`" in new Fixture {
87 | val ccWithOptionNone = CCWithOption(Int.MaxValue, None)
88 | val v = graph + ccWithOptionNone
89 | v.toCC[CCWithOption] shouldBe ccWithOptionNone
90 |
91 | val vl = graph.V(v.id).head()
92 | vl.keys should not contain "s" //None should be mapped to `null`
93 | }
94 |
95 | "handle an optional AnyVal" in new Fixture {
96 | val ccWithOptionAnyVal = CCWithOptionAnyVal(Some(1), None)
97 | val v = graph + ccWithOptionAnyVal
98 | v.toCC[CCWithOptionAnyVal]
99 |
100 | val vl = graph.V(v.id).head()
101 | vl.value[Int]("x") shouldBe ccWithOptionAnyVal.x.get
102 | vl.keys should not contain "y"
103 | }
104 |
105 | // Background: if we marshal Option types, the graph db needs to understand scala.Option,
106 | // which wouldn't make any sense. So we rather translate it to `null` if it's `None`.
107 | // https://github.com/mpollmeier/gremlin-scala/issues/98
108 | }
109 |
110 | "contain value classes" should {
111 | "unwrap a plain value class" in new Fixture {
112 | val cc = CCWithValueClass("some text", MyValueClass(42))
113 | val v = graph + cc
114 |
115 | val vl = graph.V(v.id).head()
116 | vl.label shouldBe cc.getClass.getSimpleName
117 | vl.valueMap should contain("s" -> cc.s)
118 | vl.valueMap should contain("i" -> cc.i.value)
119 | vl.toCC[CCWithValueClass] shouldBe cc
120 | }
121 |
122 | "unwrap an optional value class" in new Fixture {
123 | val cc = CCWithOptionValueClass("some text", Some(MyValueClass(42)))
124 | val v = graph + cc
125 |
126 | val vl = graph.V(v.id).head()
127 | vl.label shouldBe cc.getClass.getSimpleName
128 | vl.valueMap should contain("s" -> cc.s)
129 | vl.valueMap should contain("i" -> cc.i.get.value)
130 | vl.toCC[CCWithOptionValueClass] shouldBe cc
131 | }
132 |
133 | "handle None value class" in new Fixture {
134 | val cc = CCWithOptionValueClass("some text", None)
135 | val v = graph + cc
136 |
137 | val vl = graph.V(v.id).head()
138 | vl.label shouldBe cc.getClass.getSimpleName
139 | vl.valueMap should contain("s" -> cc.s)
140 | vl.valueMap.keySet should not contain ("i")
141 | vl.toCC[CCWithOptionValueClass] shouldBe cc
142 | }
143 |
144 | "handle value class ID" in new Fixture {
145 | val cc = CCWithValueClassId(s = "some text", id = None)
146 | val v = graph + cc
147 | val underlyingId = v.id().asInstanceOf[Long]
148 | v.toCC[CCWithValueClassId].id shouldBe Some(MyIdValueClass(underlyingId))
149 | }
150 | }
151 |
152 | "handle List members" in new Fixture {
153 | val cc = CCWithList(s = "foo", ss = List("one", "two"), is = List(1, 2), ds = Nil)
154 |
155 | val v = graph + cc
156 | v.toCC[CCWithList] shouldBe cc
157 |
158 | val vl = graph.V(v.id).head()
159 | val properties = vl.properties[String]("ss").asScala.toList
160 | properties.size shouldBe 2
161 | properties.map(_.value) shouldBe List("one", "two")
162 | }
163 |
164 | "handle Set members" in new Fixture {
165 | val cc = CCWithSet(s = "foo", ss = Set("bar", "baz"))
166 |
167 | val v = graph + cc
168 | v.toCC[CCWithSet] shouldBe cc
169 |
170 | val vl = graph.V(v.id).head()
171 | val properties = vl.properties[String]("ss").asScala.toList
172 | properties.size shouldBe 2
173 | properties.map(_.value) shouldBe List("bar", "baz")
174 | }
175 |
176 | "allows members to be `null` if annotated with `@nullable`" in new Fixture {
177 | val cc = CCWithNullable(i = 42, maybeNull = null)
178 |
179 | val v = graph + cc
180 | v.toCC[CCWithNullable] shouldBe cc
181 | }
182 |
183 | "define their custom marshaller" in new Fixture {
184 | val ccWithOptionNone = CCWithOption(Int.MaxValue, None)
185 |
186 | implicit val marshaller = new Marshallable[CCWithOption] {
187 | import gremlin.scala.PropertyOps
188 | def fromCC(cc: CCWithOption) =
189 | FromCC(None, "CCWithOption", List("i" -> cc.i, "s" -> cc.s.getOrElse("undefined")))
190 |
191 | def toCC(element: Element): CCWithOption =
192 | CCWithOption(i = element.value[Int]("i"), s = element.property[String]("s").toOption)
193 | }
194 |
195 | val v = graph + ccWithOptionNone
196 | v.toCC[CCWithOption](marshaller) shouldBe CCWithOption(ccWithOptionNone.i, Some("undefined"))
197 | }
198 |
199 | "combination of many things" in new Fixture {
200 | val cc = ComplexCC(
201 | "some string",
202 | Long.MaxValue,
203 | Some("option type"),
204 | Seq("test1", "test2"),
205 | Map("key1" -> "value1", "key2" -> "value2"),
206 | NestedClass("nested")
207 | )
208 |
209 | val v = graph + cc
210 |
211 | val vl = graph.V(v.id).head()
212 | vl.label shouldBe "the_label"
213 | vl.valueMap should contain("s" -> cc.s)
214 | vl.valueMap should contain("l" -> cc.l)
215 | vl.valueMap should contain("o" -> cc.o.get)
216 | vl.valueMap should contain("seq" -> cc.seq)
217 | vl.valueMap should contain("map" -> cc.map)
218 | vl.valueMap should contain("nested" -> cc.nested)
219 | }
220 |
221 | "have an Option @id annotation" in new Fixture {
222 | val cc = CCWithOptionId(s = "text", id = Some(12))
223 | val v = graph + cc
224 |
225 | v.toCC[CCWithOptionId].s shouldBe cc.s
226 |
227 | val vl = graph.V(v.id).head()
228 | vl.label shouldBe cc.getClass.getSimpleName
229 | vl.valueMap should contain("s" -> cc.s)
230 | }
231 |
232 | "fails compilation for non-option @id annotation" in new Fixture {
233 | // id must be assigned by graph (in the context of Marshallable)
234 | illTyped {
235 | """
236 | val cc = CCWithNonOptionalIdShouldFail(12)
237 | graph + cc
238 | """
239 | }
240 | }
241 |
242 | "have @underlying vertex" in new Fixture {
243 | val cc = CCWithUnderlyingVertex(
244 | underlying = None, //not known yet, not part of graph yet
245 | "some string"
246 | )
247 |
248 | val vertex = graph + cc
249 | val ccFromVertex = vertex.toCC[CCWithUnderlyingVertex]
250 | ccFromVertex.s shouldBe cc.s
251 | ccFromVertex.underlying shouldBe Symbol("defined")
252 |
253 | graph.V(ccFromVertex.underlying.get.id).value[String]("s").head() shouldBe cc.s
254 | }
255 |
256 | "fails compilation for non-option @underlying annotation" in new Fixture {
257 | illTyped {
258 | """
259 | val cc = CCWithNonOptionalUnderlyingShouldFail(null)
260 | graph + cc
261 | """
262 | }
263 | }
264 | }
265 |
266 | "find vertices by label" in new Fixture {
267 | val ccSimple = CCSimple("a string", 42)
268 | val ccWithOption = CCWithOption(52, Some("other string"))
269 | val ccWithLabel = CCWithLabel("s")
270 |
271 | graph + ccSimple
272 | graph + ccWithOption
273 | graph + ccWithLabel
274 |
275 | graph.V().count().head() shouldBe 3
276 |
277 | val ccSimpleVertices = graph.V().hasLabel[CCSimple]().toList()
278 | (ccSimpleVertices should have).size(1)
279 | ccSimpleVertices.head.toCC[CCSimple] shouldBe ccSimple
280 |
281 | val ccWithLabelVertices = graph.V().hasLabel[CCWithLabel]().toList()
282 | (ccWithLabelVertices should have).size(1)
283 | ccWithLabelVertices.head.toCC[CCWithLabel] shouldBe ccWithLabel
284 | }
285 |
286 | "add edges using case-class".which {
287 | "have no id-annotation" in new CCEdgeAddFixture {
288 | val ccEdgeWithLabelInitial = CCWithLabel("edge-property")
289 |
290 | ccVertexFrom.addEdge(ccVertexTo, ccEdgeWithLabelInitial).toCC[CCWithLabel]
291 |
292 | val ccEdgesWithLabel = graph.E().hasLabel[CCWithLabel]().toList()
293 | (ccEdgesWithLabel should have).size(1)
294 | ccEdgesWithLabel.head.toCC[CCWithLabel] shouldBe ccEdgeWithLabelInitial
295 | }
296 |
297 | "have id-annotation None" in new CCEdgeAddFixture {
298 | val ccEdgeWithOptionIdNoneInitial = CCWithOptionId("edge-property", None)
299 |
300 | val ccEdgeWithOptionIdNone = ccVertexFrom
301 | .addEdge(ccVertexTo, ccEdgeWithOptionIdNoneInitial)
302 | .toCC[CCWithOptionId]
303 | ccEdgeWithOptionIdNone.id should not be empty
304 |
305 | val ccEdgesWithOptionIdNone = graph.E().hasLabel[CCWithOptionId]().toList()
306 | (ccEdgesWithOptionIdNone should have).size(1)
307 | ccEdgesWithOptionIdNone.head
308 | .toCC[CCWithOptionId] shouldBe ccEdgeWithOptionIdNone
309 | }
310 |
311 | "have id-annotation Some(123)" in new CCEdgeAddFixture {
312 | val ccEdgeWithOptionIdSomeInitial =
313 | CCWithOptionId("edge-property", Some(123))
314 |
315 | val ccEdgeWithOptionIdSome = ccVertexFrom
316 | .addEdge(ccVertexTo, ccEdgeWithOptionIdSomeInitial)
317 | .toCC[CCWithOptionId]
318 | ccEdgeWithOptionIdSome.id shouldBe ccEdgeWithOptionIdSomeInitial.id
319 |
320 | val ccEdgesWithOptionIdSome = graph.E().hasLabel[CCWithOptionId]().toList()
321 | (ccEdgesWithOptionIdSome should have).size(1)
322 | ccEdgesWithOptionIdSome.head
323 | .toCC[CCWithOptionId] shouldBe ccEdgeWithOptionIdSome
324 | }
325 | }
326 |
327 | "edge" should {
328 | "update using a case-class template" in new CCEdgeUpdateFixture {
329 | graph
330 | .E(ccWithIdSet.id.get)
331 | .head()
332 | .updateWith(ccUpdate)
333 | .toCC[CC] shouldBe ccUpdate
334 | graph.E(ccWithIdSet.id.get).head().toCC[CC] shouldBe ccUpdate
335 | }
336 |
337 | "update as a case class" in new CCEdgeUpdateFixture {
338 | graph
339 | .E(ccWithIdSet.id.get)
340 | .head()
341 | .updateAs[CC](_.copy(s = ccUpdate.s, i = ccUpdate.i))
342 | .toCC[CC] shouldBe ccUpdate
343 | graph.E(ccWithIdSet.id.get).head().toCC[CC] shouldBe ccUpdate
344 | }
345 | }
346 |
347 | "vertex" should {
348 | "update using a case-class template" in new CCVertexUpdateFixture {
349 | graph
350 | .V(ccWithIdSet.id.get)
351 | .head()
352 | .updateWith(ccUpdate)
353 | .toCC[CC] shouldBe ccUpdate
354 | graph.V(ccWithIdSet.id.get).head().toCC[CC] shouldBe ccUpdate
355 | }
356 |
357 | "update as a case class" in new CCVertexUpdateFixture {
358 | graph
359 | .V(ccWithIdSet.id.get)
360 | .head()
361 | .updateAs[CC](_.copy(s = ccUpdate.s, i = ccUpdate.i))
362 | .toCC[CC] shouldBe ccUpdate
363 | graph.V(ccWithIdSet.id.get).head().toCC[CC] shouldBe ccUpdate
364 | }
365 | }
366 |
367 | "marshals a the end step of a traversal" in new Fixture {
368 | val cc1 = CCSimple("text one", 1)
369 | val cc2 = CCSimple("text two", 2)
370 | graph + cc1
371 | graph + cc2
372 |
373 | val results: Set[CCSimple] = graph.V().toCC[CCSimple].toSet()
374 | results shouldBe Set(cc1, cc2)
375 | }
376 |
377 | case class EmptyCC()
378 | "handle case class without elements" in new Fixture {
379 | val m = implicitly[Marshallable[EmptyCC]]
380 | }
381 |
382 | trait Fixture {
383 | val graph = TinkerGraph.open.asScala()
384 | }
385 |
386 | trait CCUpdateFixture[E <: Element] extends Fixture {
387 | type CC = CCWithOptionIdNested
388 | val ccInitial = CCWithOptionIdNested("string", None, MyValueClass(42))
389 |
390 | def ccWithIdSet: CC
391 | lazy val ccUpdate = ccWithIdSet.copy(s = "otherString", i = MyValueClass(7))
392 | }
393 |
394 | trait CCVertexUpdateFixture extends CCUpdateFixture[Vertex] {
395 | val ccWithIdSet = (graph + ccInitial).toCC[CC]
396 | }
397 |
398 | trait CCEdgeAddFixture extends Fixture {
399 | val ccVertexFrom = graph + "fromLabel"
400 | val ccVertexTo = graph + "toLabel"
401 | }
402 |
403 | trait CCEdgeUpdateFixture extends CCUpdateFixture[Edge] {
404 | private val testVertex = graph + "Huh"
405 | val ccWithIdSet = testVertex.addEdge(testVertex, ccInitial).toCC[CC]
406 | }
407 |
408 | "can't persist a none product type (none case class or tuple)" in {
409 | illTyped {
410 | """
411 | val graph = TinkerGraph.open.asScala()
412 | graph + new NoneCaseClass("test")
413 | """
414 | }
415 | }
416 | }
417 |
--------------------------------------------------------------------------------
/macros/src/main/scala/gremlin/scala/Annotations.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import scala.annotation.{compileTimeOnly, StaticAnnotation}
4 | import scala.annotation.meta._
5 |
6 | /** the underlying graph element, typically a vertex or edge */
7 | @getter @beanGetter
8 | class underlying extends StaticAnnotation
9 |
10 | /** the id of the underlying graph element */
11 | @getter @beanGetter
12 | class id extends StaticAnnotation
13 |
14 | /** annotate a non-option member with `@nullable` to allow `null` values.
15 | * by default this would throw an error on deserialization
16 | * a.k.a. license to shoot yourself in the foot */
17 | @getter @beanGetter
18 | class nullable extends StaticAnnotation
19 |
20 | class label(val label: String = "") extends StaticAnnotation
21 |
--------------------------------------------------------------------------------
/macros/src/main/scala/gremlin/scala/Marshallable.scala:
--------------------------------------------------------------------------------
1 | package gremlin.scala
2 |
3 | import org.apache.tinkerpop.gremlin.structure.Graph.Hidden
4 | import org.apache.tinkerpop.gremlin.structure.Element
5 | import scala.language.experimental.macros
6 | import scala.reflect.macros.blackbox
7 |
8 | trait Marshallable[CC <: Product] {
9 | type Id = AnyRef
10 | type Label = String
11 | case class FromCC(id: Option[Id], label: Label, properties: List[(String, Any)])
12 |
13 | def fromCC(cc: CC): FromCC
14 | def toCC(element: Element): CC
15 | }
16 |
17 | object Marshallable {
18 | implicit def materializeMappable[CC <: Product]: Marshallable[CC] =
19 | macro materializeMappableImpl[CC]
20 |
21 | def materializeMappableImpl[CC <: Product: c.WeakTypeTag](
22 | c: blackbox.Context): c.Expr[Marshallable[CC]] = {
23 | import c.universe._
24 | val tpe = weakTypeOf[CC]
25 | val companion = tpe.typeSymbol.companion
26 |
27 | val (idParam, fromCCParams, toCCParams) = tpe.decls
28 | .foldLeft[(Tree, Seq[Tree], Seq[Tree])]((q"_root_.scala.None", Seq.empty, Seq.empty)) {
29 | case ((_idParam, _fromCCParams, _toCCParams), field: MethodSymbol)
30 | if field.isCaseAccessor =>
31 | val name = field.name
32 | val decoded = name.decodedName.toString
33 | val returnType = field.returnType
34 |
35 | def handleStandardProperty(nullable: Boolean) = {
36 | // check if the property is a value class and try to extract everything we need to unwrap it
37 | val treesForValueClass = for {
38 | valueName <- valueGetter(returnType)
39 | if returnType <:< typeOf[AnyVal]
40 | wrappedType <- wrappedTypeMaybe(returnType)
41 | } yield { // ValueClass property
42 | val valueClassCompanion = returnType.typeSymbol.companion
43 | (_idParam,
44 | _fromCCParams :+ q"_root_.scala.collection.immutable.List($decoded -> cc.$name.$valueName)",
45 | _toCCParams :+ q"$valueClassCompanion(element.value[$wrappedType]($decoded)).asInstanceOf[$returnType]")
46 | }
47 | treesForValueClass.getOrElse { //normal property
48 | val toCCParams =
49 | if (!nullable) {
50 | q"element.value[$returnType]($decoded)"
51 | } else {
52 | // for people who like to shoot themselves in the foot
53 | q"new _root_.gremlin.scala.PropertyOps(element.property[$returnType]($decoded)).toOption.orNull"
54 | }
55 | (_idParam,
56 | _fromCCParams :+ q"_root_.scala.collection.immutable.List($decoded -> cc.$name)",
57 | _toCCParams :+ toCCParams)
58 | }
59 | }
60 |
61 | def handleOptionProperty = {
62 | // check if the property is an Option[AnyVal] and try to extract everything we need to unwrap it
63 | returnType.typeArgs.headOption match {
64 | case Some(innerAnyValClassType) if innerAnyValClassType <:< typeOf[AnyVal] =>
65 | valueGetter(innerAnyValClassType) match {
66 | case Some(wrappedValueGetter) => //Option[ValueClass]
67 | val valueClassCompanion = innerAnyValClassType.typeSymbol.companion
68 | (_idParam,
69 | _fromCCParams :+
70 | q"""
71 | _root_.scala.collection.immutable.List(
72 | cc.$name.map{ x => $decoded -> x.${wrappedValueGetter.name} }.getOrElse($decoded -> null)
73 | )
74 | """,
75 | _toCCParams :+
76 | q"""
77 | new _root_.gremlin.scala.PropertyOps(element.property($decoded))
78 | .toOption
79 | .map($valueClassCompanion.apply)
80 | .asInstanceOf[$returnType]
81 | """)
82 | case None => // Option[AnyVal]
83 | (_idParam,
84 | _fromCCParams :+
85 | q"""
86 | _root_.scala.collection.immutable.List(
87 | cc.$name.map{ x => $decoded -> x }.getOrElse($decoded -> null)
88 | )
89 | """,
90 | _toCCParams :+
91 | q"""
92 | new _root_.gremlin.scala.PropertyOps(element.property($decoded))
93 | .toOption
94 | .asInstanceOf[$returnType]
95 | """)
96 | }
97 |
98 | case _ => // normal option property
99 | (_idParam,
100 | _fromCCParams :+ q"_root_.scala.collection.immutable.List($decoded -> cc.$name.orNull)",
101 | _toCCParams :+
102 | q"""
103 | new _root_.gremlin.scala.PropertyOps(element.property($decoded))
104 | .toOption
105 | .asInstanceOf[$returnType]
106 | """)
107 | }
108 | }
109 |
110 | def handleListProperty = {
111 | (_idParam,
112 | _fromCCParams :+ q"cc.$name.map { x => $decoded -> x }",
113 | _toCCParams :+
114 | q"""
115 | element.properties($decoded)
116 | .asScala
117 | .toList
118 | .map(_.value)
119 | .asInstanceOf[$returnType]
120 | """)
121 | // element.properties[String]("ss").asScala.toList.map(_.value),
122 | // _toCCParams :+ q"element.value[$returnType]($decoded)")
123 | }
124 |
125 | def handleSetProperty = {
126 | (_idParam,
127 | _fromCCParams :+ q"cc.$name.toList.map { x => $decoded -> x }",
128 | _toCCParams :+
129 | q"""
130 | element.properties($decoded)
131 | .asScala
132 | .map(_.value)
133 | .toSet
134 | .asInstanceOf[$returnType]
135 | """)
136 | }
137 |
138 | def valueGetter(tpe: Type): Option[MethodSymbol] =
139 | tpe.decls.sorted
140 | .filter(_.isMethod)
141 | .map(_.asMethod)
142 | .takeWhile(!_.isConstructor)
143 | .find(_.paramLists == Nil /* nullary */ )
144 |
145 | def valueClassConstructor(tpe: Type): Option[MethodSymbol] =
146 | tpe.companion.decls.find(_.name.toString == "apply") match {
147 | case Some(m: MethodSymbol) => Some(m)
148 | case _ => None
149 | }
150 |
151 | def wrappedTypeMaybe(tpe: Type): Option[Type] =
152 | util
153 | .Try(valueClassConstructor(tpe).get.paramLists.head.head.typeSignature)
154 | .toOption
155 |
156 | def handleId = {
157 | assert(
158 | returnType.typeSymbol == weakTypeOf[Option[_]].typeSymbol,
159 | "@id parameter *must* be of type `Option[A]`. In the context of " +
160 | "Marshallable, we have to let the graph assign an id"
161 | )
162 |
163 | val valueClassParams = for {
164 | valueClass <- returnType.typeArgs.headOption if valueClass <:< typeOf[AnyVal]
165 | wrappedValueGetter <- valueGetter(valueClass)
166 | } yield {
167 | val valueClassCompanion = valueClass.typeSymbol.companion
168 | (q"cc.$name.map(_.${wrappedValueGetter.name}).asInstanceOf[_root_.scala.Option[AnyRef]]",
169 | _fromCCParams,
170 | _toCCParams :+ q"_root_.scala.Option(element.id.asInstanceOf[${wrappedValueGetter.returnType}]).map($valueClassCompanion.apply)")
171 | }
172 |
173 | valueClassParams.getOrElse(
174 | (q"cc.$name.asInstanceOf[_root_.scala.Option[AnyRef]]",
175 | _fromCCParams,
176 | _toCCParams :+ q"_root_.scala.Option(element.id).asInstanceOf[$returnType]"))
177 | }
178 |
179 | def handleUnderlying = {
180 | assert(
181 | returnType.typeSymbol == weakTypeOf[Option[_]].typeSymbol,
182 | "@underlying parameter *must* be of type `Option[A]`, since" +
183 | " it can only be defined after it has been added to the graph"
184 | )
185 | (q"cc.$name.asInstanceOf[_root_.scala.Option[AnyRef]]",
186 | _fromCCParams,
187 | _toCCParams :+ q"_root_.scala.Option(element).asInstanceOf[$returnType]")
188 | }
189 |
190 | // main control flow
191 | if (field.annotations.map(_.tree.tpe) contains weakTypeOf[id]) {
192 | handleId // @id
193 | } else if (field.annotations.map(_.tree.tpe) contains weakTypeOf[underlying]) {
194 | handleUnderlying // @underlying
195 | } else { // normal property member
196 | assert(!Hidden.isHidden(decoded),
197 | s"The parameter name $decoded can't be used in the persistable case class $tpe")
198 | if (returnType.typeSymbol == weakTypeOf[Option[_]].typeSymbol) {
199 | handleOptionProperty
200 | } else if (returnType.typeSymbol == weakTypeOf[List[_]].typeSymbol) {
201 | handleListProperty
202 | } else if (returnType.typeSymbol == weakTypeOf[Set[_]].typeSymbol) {
203 | handleSetProperty
204 | } else {
205 | handleStandardProperty(
206 | nullable = field.annotations.map(_.tree.tpe).contains(weakTypeOf[nullable]))
207 | }
208 | }
209 |
210 | case (params, _) => params
211 | }
212 |
213 | val label = tpe.typeSymbol.asClass.annotations
214 | .find(_.tree.tpe =:= weakTypeOf[label])
215 | .map { annotation =>
216 | val label = annotation.tree.children.tail.head
217 | q"""$label"""
218 | }
219 | .getOrElse(q"cc.getClass.getSimpleName")
220 |
221 | val ret = c.Expr[Marshallable[CC]] {
222 | q"""
223 | new _root_.gremlin.scala.Marshallable[$tpe] {
224 | import _root_.scala.collection.JavaConverters._
225 | def fromCC(cc: $tpe) =
226 | this.FromCC(
227 | $idParam,
228 | $label,
229 | _root_.scala.collection.immutable.List[_root_.scala.collection.immutable.List[(_root_.scala.Predef.String,_root_.scala.Any)]](..$fromCCParams)
230 | .flatten
231 | .filter { kv =>
232 | _root_.scala.Option(kv._2).isDefined
233 | }
234 | )
235 | def toCC(element: _root_.gremlin.scala.Element): $tpe = $companion(..$toCCParams)
236 | }
237 | """
238 | }
239 | // if (tpe.toString.contains("CCWithNullable")) println(ret)
240 | ret
241 | }
242 |
243 | }
244 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.6.2
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("io.shiftleft" % "sbt-ci-release-early" % "2.0.35")
2 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
3 |
--------------------------------------------------------------------------------