From ad5dee4cffa1d4fcf202f4b88949c9dfa3f1b6b0 Mon Sep 17 00:00:00 2001 From: augustearth Date: Mon, 11 Nov 2024 18:56:08 -0800 Subject: [PATCH] For #127 --- app/build.gradle | 5 + app/carnival-core/build.gradle | 3 + .../main/groovy/carnival/core/Carnival.groovy | 108 ++- .../core/CarnivalJanusBerkeley.groovy | 737 ++++++++++++++++++ .../core/graph/DefaultGraphSchema.groovy | 16 + .../carnival/core/graph/EdgeConstraint.groovy | 22 +- .../core/graph/EdgePropertyConstraint.groovy | 41 + .../carnival/core/graph/GraphSchema.groovy | 7 + .../core/graph/GremlinTraitUtilities.groovy | 2 +- .../core/graph/PropertyConstraint.groovy | 73 ++ .../core/graph/VertexConstraint.groovy | 3 + .../graph/VertexPropertyConstraint.groovy | 6 + .../core/CarnivalConstraintSpec.groovy | 106 +++ .../core/CarnivalJanusBerkeleySpec.groovy | 429 ++++++++++ .../carnival/core/CarnivalModelSpec.groovy | 46 +- .../groovy/carnival/core/CarnivalSpec.groovy | 72 +- .../graph/DefaultGraphValidatorSpec.groovy | 16 +- .../core/graph/EdgeConstraintSpec.groovy | 94 +++ .../core/graph/GremlinTraitSpec.groovy | 2 +- .../core/graph/LegacyGraphSpec.groovy | 160 ---- .../src/test/resources/logback-test.xml | 66 ++ .../groovy/carnival/graph/Definition.groovy | 34 +- .../carnival/graph/EdgeDefinition.groovy | 38 +- .../carnival/graph/ElementDefinition.groovy | 56 +- .../carnival/graph/PropertyDefinition.groovy | 76 +- .../carnival/graph/VertexDefinition.groovy | 20 +- .../carnival/graph/DefAnnotationsSpec.groovy | 25 +- .../carnival/graph/DefinitionSpec.groovy | 48 ++ .../graph/DefinitionTransformationTest.groovy | 4 +- .../carnival/graph/EdgeBuilderSpec.groovy | 52 +- .../groovy/carnival/graph/EdgeDefSpec.groovy | 6 +- .../carnival/graph/EdgeDefinitionSpec.groovy | 34 +- .../graph/PropertyDefinitionSpec.groovy | 57 +- .../groovy/carnival/graph/TestModel.groovy | 6 +- .../carnival/graph/VertexDefTraitSpec.groovy | 350 --------- .../graph/VertexDefinitionSpec.groovy | 15 +- .../carnival/graph/VertexModelSpec.groovy | 8 +- app/gradle.properties | 2 +- docs/groovy/graph-model-1.groovy | 34 +- 39 files changed, 2184 insertions(+), 695 deletions(-) create mode 100644 app/carnival-core/src/main/groovy/carnival/core/CarnivalJanusBerkeley.groovy create mode 100644 app/carnival-core/src/main/groovy/carnival/core/graph/EdgePropertyConstraint.groovy create mode 100644 app/carnival-core/src/main/groovy/carnival/core/graph/PropertyConstraint.groovy create mode 100644 app/carnival-core/src/test/groovy/carnival/core/CarnivalConstraintSpec.groovy create mode 100644 app/carnival-core/src/test/groovy/carnival/core/CarnivalJanusBerkeleySpec.groovy create mode 100644 app/carnival-core/src/test/groovy/carnival/core/graph/EdgeConstraintSpec.groovy delete mode 100644 app/carnival-core/src/test/groovy/carnival/core/graph/LegacyGraphSpec.groovy create mode 100644 app/carnival-core/src/test/resources/logback-test.xml create mode 100644 app/carnival-graph/src/test/groovy/carnival/graph/DefinitionSpec.groovy delete mode 100644 app/carnival-graph/src/test/groovy/carnival/graph/VertexDefTraitSpec.groovy diff --git a/app/build.gradle b/app/build.gradle index 4446b8a..8204bb4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -253,6 +253,10 @@ subprojects { events 'standard_out' } + /* + I think all this is a relic of the past when Carnival was an app and + and no longer appropriate. + if (System.getProperty('carnival.home')) { systemProperty('carnival.home', System.getProperty('carnival.home')) systemProperty('logback.configurationFile', System.getProperty('carnival.home') + '/config/logback.xml') @@ -272,6 +276,7 @@ subprojects { ant.echo "WARNING: Logback configuration file not set. Use the -D command line argument like -Dlogback.configurationFile=/path/to/file.xml. Or, set the environment variable CARNIVAL_LOGBACK_CONFIG to /path/to/file.xml. See documentation." } } + */ } } diff --git a/app/carnival-core/build.gradle b/app/carnival-core/build.gradle index 8ec6adb..7b4e93f 100644 --- a/app/carnival-core/build.gradle +++ b/app/carnival-core/build.gradle @@ -86,6 +86,9 @@ dependencies { // reflections for dymamic class instantiation implementation 'org.reflections:reflections:0.10.2' + + implementation('org.janusgraph:janusgraph-core:1.1.0-20240927-111508.73232fe') + implementation('org.janusgraph:janusgraph-berkeleyje:1.1.0-20240927-111508.73232fe') } diff --git a/app/carnival-core/src/main/groovy/carnival/core/Carnival.groovy b/app/carnival-core/src/main/groovy/carnival/core/Carnival.groovy index 6679d96..2c6e019 100644 --- a/app/carnival-core/src/main/groovy/carnival/core/Carnival.groovy +++ b/app/carnival-core/src/main/groovy/carnival/core/Carnival.groovy @@ -33,6 +33,7 @@ import carnival.core.graph.GraphValidator import carnival.core.graph.GraphValidationError import carnival.core.graph.DefaultGraphValidator import carnival.core.graph.EdgeConstraint +import carnival.core.graph.PropertyConstraint import carnival.core.graph.VertexConstraint import carnival.core.util.DuplicateModelException @@ -104,7 +105,7 @@ abstract class Carnival implements GremlinTrait { */ public void initialize(Graph graph, GraphTraversalSource g) { log.info "Carnival initialize graph:$graph g:$g" - [Base.EX, Core.EX, Core.VX].each { + [Base.PX, Base.EX, Core.PX, Core.VX, Core.EX].each { addModel(graph, g, it) } } @@ -253,6 +254,9 @@ abstract class Carnival implements GremlinTrait { * @param defClass The element definition class. */ public void addModel(Class defClass) { + + log.debug "\n\ndefClass:${defClass}\n\n" + assert defClass withGremlin { graph, g -> addModel(graph, g, defClass) @@ -276,8 +280,8 @@ abstract class Carnival implements GremlinTrait { def defInterfaces = defClass.getInterfaces() if (defInterfaces.contains(VertexDefinition)) addVertexModel(graph, g, defClass) else if (defInterfaces.contains(EdgeDefinition)) addEdgeModel(graph, g, defClass) + else if (defInterfaces.contains(PropertyDefinition)) addPropertyModel(graph, g, defClass) else throw new RuntimeException("unrecognized definition class: $defClass") - } @@ -326,6 +330,29 @@ abstract class Carnival implements GremlinTrait { } + /** + * Add the edge models in the provided property definition class to this + * Carnival using the provided graph and graph traversal source. This is an + * internal method and not expected to be called by client code. + * @param graph A gremlin graph. + * @param g A graph traversal source. + * @param defClass A property definition class. + */ + public void addPropertyModel(Graph graph, GraphTraversalSource g, Class defClass) { + assert graph + assert g + assert defClass + + log.debug "\n\naddPropertyModel defClass:${defClass}\n\n" + + Set propertyConstraints = findNewPropertyConstraints(defClass) + propertyConstraints.each { pc -> + addConstraint(pc) + } + } + + + /////////////////////////////////////////////////////////////////////////// // GRAPH CONSTRAINTS - VERTEX /////////////////////////////////////////////////////////////////////////// @@ -493,6 +520,83 @@ abstract class Carnival implements GremlinTrait { it.label == ec.label && it.nameSpace == ec.nameSpace } } + + + + /////////////////////////////////////////////////////////////////////////// + // GRAPH CONSTRAINTS - PROPERTIES + /////////////////////////////////////////////////////////////////////////// + + /** + * Add the provided property constraint to this carnival. + * @param propertyConst The property constraint to add. + */ + public void addConstraint(PropertyConstraint propertyConst) { + log.trace "addConstraint propertyConst: ${propertyConst.label} $propertyConst" + + log.trace "adding property definition to graph schema ${propertyConst.label} ${propertyConst}" + graphSchema.propertyConstraints.add(propertyConst) + } + + + /** + * Find property constraints in an property definition class that are not already + * present in this Carnival. + * @param propertyDefClass The property definition class. + * @return A collection of property constraints. + */ + public Collection findNewPropertyConstraints(Class propertyDefClass) { + assert propertyDefClass + Set> edcs = new HashSet>() + edcs.add(propertyDefClass) + findNewPropertyConstraints(edcs) + } + + + /** + * Find property constraints in a provided set of property definition classes that + * are not already present in this Carnival. + * @param propertyDefClasses A set of property definition classes. + * @return A collection of property constraints. + */ + public Collection findNewPropertyConstraints(Set> propertyDefClasses) { + assert propertyDefClasses + + Set newConstraints = new HashSet() + + propertyDefClasses.each { Class edc -> + log.trace "findNewPropertyConstraints edc: $edc" + + edc.values().each { PropertyDefinition edef -> + log.trace "findNewPropertyConstraints edef: $edef" + + PropertyConstraint ec = PropertyConstraint.create(edef) + def exists = existsInGraphSchema(ec) + if (!exists) { + log.trace "new property constraint ${ec.label} ${ec}" + newConstraints.add(ec) + } + if (exists && !ignoreDuplicateModels) throw new DuplicateModelException( + "property constraint already exists: ${ec}" + ) + } + } + + return newConstraints + } + + + /** + * Return true if the provided property constraint exists in the graph + * schema. + * @param vc The property constraint + * @return True if the property constraint exists + */ + boolean existsInGraphSchema(PropertyConstraint ec) { + graphSchema.propertyConstraints.find { + it.label == ec.label && it.nameSpace == ec.nameSpace + } + } diff --git a/app/carnival-core/src/main/groovy/carnival/core/CarnivalJanusBerkeley.groovy b/app/carnival-core/src/main/groovy/carnival/core/CarnivalJanusBerkeley.groovy new file mode 100644 index 0000000..3625ece --- /dev/null +++ b/app/carnival-core/src/main/groovy/carnival/core/CarnivalJanusBerkeley.groovy @@ -0,0 +1,737 @@ +package carnival.core + + + +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.Files + +import groovy.transform.ToString +import groovy.transform.InheritConstructors +import groovy.util.logging.Slf4j + +import org.apache.commons.io.FileUtils + +import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph +import org.apache.tinkerpop.gremlin.process.traversal.Traversal +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource +import org.apache.tinkerpop.gremlin.process.traversal.P +import org.apache.tinkerpop.gremlin.structure.T +import org.apache.tinkerpop.gremlin.structure.Graph +import org.apache.tinkerpop.gremlin.structure.Transaction +import org.apache.tinkerpop.gremlin.structure.Vertex +import org.apache.tinkerpop.gremlin.structure.Edge +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__ + +import org.janusgraph.core.JanusGraph +import org.janusgraph.core.JanusGraphFactory +import org.janusgraph.core.PropertyKey +import org.janusgraph.core.VertexLabel +import org.janusgraph.core.EdgeLabel +import org.janusgraph.core.Multiplicity +import org.janusgraph.core.Cardinality +import org.janusgraph.core.schema.EdgeLabelMaker +import org.janusgraph.core.schema.JanusGraphManagement +import org.janusgraph.core.schema.JanusGraphManagement.IndexBuilder +import org.janusgraph.core.schema.JanusGraphIndex +import org.janusgraph.core.schema.PropertyKeyMaker +import org.janusgraph.core.schema.VertexLabelMaker +import org.janusgraph.graphdb.database.management.ManagementSystem + + +import carnival.core.graph.DefaultGraphSchema +import carnival.core.graph.DefaultGraphValidator +import carnival.core.graph.GraphSchema +import carnival.core.graph.GraphValidator +import carnival.core.graph.GraphValidationError +import carnival.core.graph.GremlinTraitUtilities +import carnival.core.graph.EdgeConstraint +import carnival.core.graph.PropertyConstraint +import carnival.core.graph.VertexConstraint +import carnival.core.graph.VertexPropertyConstraint +import carnival.graph.Base +import carnival.graph.EdgeDefinition +import carnival.graph.PropertyDefinition +import carnival.graph.ElementDefinition +import carnival.graph.VertexDefinition + + + + + +/** + * A Carnival with an underlying Tinkergraph implementation. + */ +@InheritConstructors +@Slf4j +class CarnivalJanusBerkeley extends Carnival { + + /////////////////////////////////////////////////////////////////////////// + // CLASSES + /////////////////////////////////////////////////////////////////////////// + + /** */ + static class Config { + + /** */ + static class Storage { + /** */ + String directory = 'data/graph' + /** */ + boolean transactions = true + } + Storage storage = new Storage() + + } + + + /////////////////////////////////////////////////////////////////////////// + // STATIC + /////////////////////////////////////////////////////////////////////////// + + /** + * Utility method to run a closure in the context of a fresh transaction + * that will be comitted and closed upon successful termination of the + * closure. + * If there is a prior open transaction, it will be closed, but not + * committed. + * If the closure accepts no arguments, it will be called without + * arguments. + * If the closure accepts one argument, it will be called with the + * transaction object as the argument. + * If the closure accepts any other number of arguments, a runtime + * exception will be thrown. + * Note that if the gremlin graph does not support transactions, then an + * error will be thrown. + * @param graph The gremlin graph. + * @param cl The closure to execute. + */ + static public void withTransaction(Graph graph, Closure cl) { + assert graph != null + assert cl != null + + // open a new transaction + def tx = graph.tx() + if (tx.isOpen()) tx.close() + tx.open() + + // execute the closure + def maxClosureParams = cl.getMaximumNumberOfParameters() + try { + if (maxClosureParams == 0) { + cl() + } else if (maxClosureParams == 1) { + cl(tx) + } else { + throw new RuntimeException("closure must accept zero or one arguments") + } + } catch (Exception e) { + try { + tx.rollback() + } catch (Exception e2) { + log.error("could not rollback", e2) + } + throw e + } finally { + try { + tx.commit() + tx.close() + } catch (Exception e3) { + log.error("could not commit", e3) + } + } + } + + + /** + * Return a compound index name for the provided series of property + * definitions. + */ + static String indexNameOf(PropertyDefinition... pds) { + assert pds + + List pdlist = pds.toList() + + if (pdlist.size() == 0) return null + else if (pdlist.size() == 1) return pdlist.first().label + else return pdlist.collect({ it.label }).join('-') + } + + + /** + * Return a compound index name for a vertex constraint and vertex property + * constraint, ie for a single property of a vertex. + */ + static String indexNameOf(VertexConstraint vc, VertexPropertyConstraint vcp) { + assert vc + assert vcp + + String idxName = vc.label + '-' + vcp.name + return idxName + } + + + /** + * Return a compound index name for a vertex definition and a property + * definition. + */ + static String indexNameOf( + VertexDefinition vd, + PropertyDefinition pd1 + ) { + assert vd + assert pd1 + + String idxName = vd.label + '-' + pd1.label + return idxName + } + + + /** + * Return a compound index name for a vertex definition and two property + * definitions, useful for the combo namespace indexes. + */ + static String indexNameOf( + VertexDefinition vd, + PropertyDefinition pd1, + PropertyDefinition pd2 + ) { + assert vd + assert pd1 + assert pd1 + + String idxName = vd.label + '-' + pd1.label + '-' + pd2.label + return idxName + } + + + /////////////////////////////////////////////////////////////////////////// + // FIELDS + /////////////////////////////////////////////////////////////////////////// + + Config config + + + /////////////////////////////////////////////////////////////////////////// + // FACTORY + /////////////////////////////////////////////////////////////////////////// + + /** + * Create a ready-to-use CarnivalJanusBerkeley object. + * @return A CarnivalJanusBerkeley object. + */ + public static CarnivalJanusBerkeley create(Map args = [:]) { + Config config = new Config() + create(config, args) + } + + + + /** + * Create a ready-to-use CarnivalJanusBerkeley object. + * @return A CarnivalJanusBerkeley object. + */ + public static CarnivalJanusBerkeley create(Config config, Map args = [:]) { + log.info "CarnivalJanusBerkeley create args:$args" + + assert config + assert config.storage.directory + + JanusGraph graph = JanusGraphFactory.build(). + set("storage.backend", "berkeleyje"). + set("storage.directory", config.storage.directory). + set("storage.transactions", config.storage.transactions). + open(); + + def transactionsAreSupported = graph.features().graph().supportsTransactions() + assert transactionsAreSupported + + def graphSchema + if (args.vertexBuilders) graphSchema = new DefaultGraphSchema(args.vertexBuilders) + else graphSchema = new DefaultGraphSchema() + + def graphValidator = new DefaultGraphValidator() + def carnival = new CarnivalJanusBerkeley(graph, graphSchema, graphValidator) + carnival.config = config + + def g = graph.traversal() + try { + carnival.addModelCoreProperties(graph, g) + } finally { + if (g) g.close() + } + + carnival.janusSchemaManagement() + + g = graph.traversal() + try { + carnival.addModelCoreVerticesEdges(graph, g) + } finally { + if (g) g.close() + } + + assert carnival + return carnival + } + + + /** + * Initialize a gremlin graph with the core Carnival graph model. + * @param graph The gremlin graph to initialize + * @param g A graph traversal source to use during initialization. + */ + public void addModelCoreProperties(Graph graph, GraphTraversalSource g) { + log.info "Carnival addModelCoreProperties graph:$graph g:$g" + [Base.PX, Core.PX].each { + addModel(graph, g, it) + } + } + + + public void addModelCoreVerticesEdges(Graph graph, GraphTraversalSource g) { + log.info "Carnival addModelCoreVerticesEdges graph:$graph g:$g" + [Base.EX, Core.VX, Core.EX].each { + addModel(graph, g, it) + } + } + + + /////////////////////////////////////////////////////////////////////////// + // SCHEMA MANAGEMENT + /////////////////////////////////////////////////////////////////////////// + + /** + * Apply the property uniqueness constraints contained in the graph schema + * using the provided graph and graph traversal source. + * @param graph The target gremlin graph + * @param g The graph traversal source to use + */ + public void janusSchemaManagement() { + log.debug "janusSchemaManagement" + + JanusGraphManagement mgmt = graph.openManagement() + List idxNames = new ArrayList() + + try { + // create indexes for all Base.PX properties + Set propertyKeys = mgmt.getRelationTypes(PropertyKey).toSet() + + EnumSet.allOf(Base.PX).each { PropertyDefinition bpx -> + PropertyKey pk = propertyKeys.find { + it.name() == bpx.label + } + assert pk + String idxName = indexNameOf(bpx) + idxNames.add(idxName) + mgmt + .buildIndex(idxName, Vertex.class) + .addKey(pk) + .buildCompositeIndex() + } + + // combo index for isClass and nameSpace + PropertyKey icpk = propertyKeys.find { + it.name() == Base.PX.IS_CLASS.label + } + assert icpk + PropertyKey nspk = propertyKeys.find { + it.name() == Base.PX.NAME_SPACE.label + } + assert nspk + String inIdxName = indexNameOf(Base.PX.IS_CLASS, Base.PX.NAME_SPACE) + idxNames.add(inIdxName) + mgmt + .buildIndex(inIdxName, Vertex.class) + .addKey(icpk) + .addKey(nspk) + .buildCompositeIndex() + } catch (Exception e) { + log.error "error creating janus schema", e + log.warn "rolling back janus management" + mgmt.rollback() + } finally { + mgmt.commit() + } + + // wait for index availability + /*idxNames.each { + ManagementSystem.awaitGraphIndexStatus(graph, it).call() + }*/ + } + + + /** */ + public void janusPropertySchema( + Set propertyConstraints, + Graph graph, + GraphTraversalSource g + ) { + log.debug "janusPropertySchema" + + assert propertyConstraints + assert graph + assert g + + //log.debug "propertyConstraints: ${propertyConstraints}" + + JanusGraphManagement mgmt = graph.openManagement() + + try { + propertyConstraints.each { PropertyConstraint pc -> + log.debug "pc: ${pc}" + + PropertyKeyMaker pkm = mgmt + .makePropertyKey(pc.label) + .dataType(pc.dataType) + + if (pc.cardinality) { + Cardinality pcc = Enum.valueOf( + Cardinality, + pc.cardinality.name() + ) + assert pcc + pkm = pkm.cardinality(pcc) + } + + PropertyKey pk = pkm.make() + } + } catch (Exception e) { + log.error "error creating janus property schema", e + log.warn "rolling back janus management" + mgmt.rollback() + } finally { + mgmt.commit() + } + } + + + /** */ + public void janusEdgeSchema( + Set edgeConstraints, + Graph graph, + GraphTraversalSource g + ) { + log.debug "janusEdgeSchema" + + assert edgeConstraints + assert graph + assert g + + //log.debug "edgeConstraints: ${edgeConstraints}" + + JanusGraphManagement mgmt = graph.openManagement() + + try { + Set vertexLabels = mgmt.getVertexLabels().toSet() + + edgeConstraints.each { EdgeConstraint ec -> + log.debug "ec: ${ec}" + + EdgeLabelMaker elm = mgmt.makeEdgeLabel(ec.label) + if (ec.multiplicity) { + Multiplicity jm = Enum.valueOf( + Multiplicity, + ec.multiplicity.name() + ) + assert jm + elm = elm.multiplicity(jm) + } + EdgeLabel el = elm.make() + + if (ec.domainLabels && ec.rangeLabels) { + ec.domainLabels.each { dl -> + VertexLabel dll = vertexLabels.find { + it.name() == dl + } + + ec.rangeLabels.each { rl -> + VertexLabel rll = vertexLabels.find { + it.name() == rl + } + + if (dll && rll) { + mgmt.addConnection(el, dll, rll) + } + } + } + } + } + } catch (Exception e) { + log.error "error creating janus edge schema", e + log.warn "rolling back janus management" + mgmt.rollback() + } finally { + mgmt.commit() + } + } + + + /** */ + public void janusVertexSchema( + Set vertexConstraints, + Graph graph, + GraphTraversalSource g + ) { + log.debug "janusVertexSchema" + + assert vertexConstraints + assert graph + assert g + + //log.debug "vertexConstraints: ${vertexConstraints}" + + JanusGraphManagement mgmt = graph.openManagement() + List idxNames = new ArrayList() + + try { + Set propertyKeys = mgmt.getRelationTypes(PropertyKey).toSet() + + // find Base.PX.NAME_SPACE property key + PropertyKey nspk = propertyKeys.find { + it.name() == Base.PX.NAME_SPACE.label + } + assert nspk + + // find Base.PX.IS_CLASS property key + PropertyKey icpk = propertyKeys.find { + it.name() == Base.PX.IS_CLASS.label + } + assert icpk + + vertexConstraints.each { VertexConstraint vc -> + log.debug "vc: ${vc}" + + VertexLabelMaker vlm = mgmt.makeVertexLabel(vc.label) + VertexLabel vl = vlm.make() + + // combo isclass and namespace + String icnsIdxName = indexNameOf( + vc.vertexDef, Base.PX.IS_CLASS, Base.PX.NAME_SPACE + ) + log.debug "icnsIdxName: ${icnsIdxName}" + idxNames.add(icnsIdxName) + mgmt + .buildIndex(icnsIdxName, Vertex.class) + .addKey(icpk) + .addKey(nspk) + .indexOnly(vl) + .buildCompositeIndex() + + vc.properties.each { VertexPropertyConstraint vpc -> + PropertyKey pk = propertyKeys.find { + it.name() == vpc.name + } + if (pk) { + mgmt.addProperties(vl, pk) + + // property index + String idxName = indexNameOf(vc, vpc) + log.debug "idxName: ${idxName}" + idxNames.add(idxName) + IndexBuilder ib = mgmt + .buildIndex(idxName, Vertex.class) + .addKey(pk) + .indexOnly(vl) + if (vpc.unique) ib = ib.unique() + JanusGraphIndex jgi = ib.buildCompositeIndex() + + // combo namespace and vpc index + String comboIdxName = indexNameOf( + vc.vertexDef, Base.PX.NAME_SPACE, vpc.propertyDef + ) + log.debug "comboIdxName: ${comboIdxName}" + idxNames.add(comboIdxName) + mgmt + .buildIndex(comboIdxName, Vertex.class) + .addKey(nspk) + .addKey(pk) + .indexOnly(vl) + .buildCompositeIndex() + + } else { + log.warn "could not find Janus property key with name: ${vpc.name}" + } + + } + + } + } catch (Exception e) { + log.error "error creating janus vertex schema", e + log.warn "rolling back janus management" + mgmt.rollback() + } finally { + mgmt.commit() + } + + // wait for index availability + /*idxNames.each { + ManagementSystem.awaitGraphIndexStatus(graph, it).call() + }*/ + } + + + /////////////////////////////////////////////////////////////////////////// + // STORAGE + /////////////////////////////////////////////////////////////////////////// + + /** + * Return the graph directory from the provided configuration object as a + * File object using Paths.get. + * @param @config The source configuration + * @return The graph directory as a File object + */ + public static File graphDir(Config config) { + assert config + def graphPath = Paths.get(config.storage.directory) + File graphDir = graphPath.toFile() + graphDir + } + + + /** + * Clear the graph directory of the provided configuration. + * @param config The configuration from which to get the graph directory + */ + public static void clearGraph(Config config) { + log.info "clearGraph" + assert config + File graphDir = graphDir(config) + if (graphDir.exists()) { + FileUtils.deleteDirectory(graphDir) + graphDir.delete() + } + } + + + + /////////////////////////////////////////////////////////////////////////// + // GRAPH MODEL - ELEMENT DEFINITIONS + /////////////////////////////////////////////////////////////////////////// + + /** + * Add the model defined in the given class to this Carnival. + * @param defClass The element definition class. + */ + @Override + public void addModel(Class defClass) { + assert defClass + withGremlin { graph, g -> + addModel(graph, g, defClass) + } + } + + /** + * Add a model defined in the given class to this Carnival using the + * provided graph and graph traversal source. This is an internal method; + * it is expected that client code will use addModel(Class) to add models. + * @see #addModel(Class) + * @param graph A gremlin graph. + * @param g A grpah traversal source to use. + * @param defClass The element definition class. + */ + @Override + public void addModel(Graph graph, GraphTraversalSource g, Class defClass) { + assert graph + assert g + assert defClass + + def defInterfaces = defClass.getInterfaces() + if (defInterfaces.contains(VertexDefinition)) { + addVertexModel(graph, g, defClass) + } else if (defInterfaces.contains(EdgeDefinition)) { + addEdgeModel(graph, g, defClass) + } else if (defInterfaces.contains(PropertyDefinition)) { + addPropertyModel(graph, g, defClass) + } else { + throw new RuntimeException("unrecognized definition class: $defClass") + } + + } + + + /** + * Add a vertex model defined in the given vertex definition class to this + * Carnival using the provided graph and graph traversal source. This is + * an internal method; it is expected that client code will use + * addModel(Class) to add models. + * @see #addModel(Class) + * @param defClass The vertex definition class. + * @param graph A gremlin graph. + * @param g A graph traversal source to use. + */ + @Override + public void addVertexModel(Graph graph, GraphTraversalSource g, Class defClass) { + assert graph + assert g + assert defClass + + Set vertexConstraints = findNewVertexConstraints(defClass) + vertexConstraints.each { vc -> + addConstraint(vc) + } + janusVertexSchema(vertexConstraints, graph, g) + + GremlinTraitUtilities.withGremlin(graph, g) { + addClassVertices(graph, g, vertexConstraints) + } + } + + + /** + * Add the edge models in the provided edge definition class to this + * Carnival using the provided graph and graph traversal source. This is an + * internal method and not expected to be called by client code. + * @param graph A gremlin graph. + * @param g A graph traversal source. + * @param defClass An edge definition class. + */ + @Override + public void addEdgeModel(Graph graph, GraphTraversalSource g, Class defClass) { + assert graph + assert g + assert defClass + + Set edgeConstraints = findNewEdgeConstraints(defClass) + edgeConstraints.each { ec -> + addConstraint(ec) + } + janusEdgeSchema(edgeConstraints, graph, g) + } + + + /** + * Add the edge models in the provided property definition class to this + * Carnival using the provided graph and graph traversal source. This is an + * internal method and not expected to be called by client code. + * @param graph A gremlin graph. + * @param g A graph traversal source. + * @param defClass A property definition class. + */ + public void addPropertyModel(Graph graph, GraphTraversalSource g, Class defClass) { + assert graph + assert g + assert defClass + + log.debug "\n\naddPropertyModel defClass:${defClass}\n\n" + + Set propertyConstraints = findNewPropertyConstraints(defClass) + propertyConstraints.each { pc -> + addConstraint(pc) + } + janusPropertySchema(propertyConstraints, graph, g) + } + + + + /////////////////////////////////////////////////////////////////////////// + // LIFE-CYCLE + /////////////////////////////////////////////////////////////////////////// + + /** + * Close this Carnival. + */ + public void close() { + graph.close() + } + +} diff --git a/app/carnival-core/src/main/groovy/carnival/core/graph/DefaultGraphSchema.groovy b/app/carnival-core/src/main/groovy/carnival/core/graph/DefaultGraphSchema.groovy index 405e423..d34f4b2 100644 --- a/app/carnival-core/src/main/groovy/carnival/core/graph/DefaultGraphSchema.groovy +++ b/app/carnival-core/src/main/groovy/carnival/core/graph/DefaultGraphSchema.groovy @@ -109,4 +109,20 @@ class DefaultGraphSchema implements GraphSchema { return edgeConstraints } + + /////////////////////////////////////////////////////////////////////////// + // PROPERTY CONSTRAINTS + /////////////////////////////////////////////////////////////////////////// + + /** edge constraints */ + private Set propertyConstraints = new HashSet() + + /** + * Get property constraints + * @return Set of PropertyConstraint + */ + public Set getPropertyConstraints() { + return propertyConstraints + } + } diff --git a/app/carnival-core/src/main/groovy/carnival/core/graph/EdgeConstraint.groovy b/app/carnival-core/src/main/groovy/carnival/core/graph/EdgeConstraint.groovy index 35f2960..6345d9b 100644 --- a/app/carnival-core/src/main/groovy/carnival/core/graph/EdgeConstraint.groovy +++ b/app/carnival-core/src/main/groovy/carnival/core/graph/EdgeConstraint.groovy @@ -19,6 +19,7 @@ import org.apache.tinkerpop.gremlin.structure.Edge import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__ import carnival.graph.EdgeDefinition +import carnival.graph.EdgeDefinition.Multiplicity import carnival.graph.PropertyDefinition import carnival.graph.VertexDefinition import carnival.graph.VertexBuilder @@ -44,12 +45,25 @@ class EdgeConstraint implements ElementConstraint { */ static public EdgeConstraint create(EdgeDefinition edgeDef) { assert edgeDef + + def propDefs = [] + edgeDef.edgeProperties.each { PropertyDefinition pdef -> + propDefs << new EdgePropertyConstraint( + name: pdef.label, + unique: pdef.unique, + required: pdef.required, + index: pdef.index + ) + } + EdgeConstraint rd = new EdgeConstraint( edgeDef:edgeDef, label:edgeDef.label, nameSpace:edgeDef.nameSpace, domainLabels: edgeDef.domainLabels, - rangeLabels: edgeDef.rangeLabels//, + rangeLabels: edgeDef.rangeLabels, + multiplicity: edgeDef.multiplicity, + properties: propDefs ) return rd } @@ -74,6 +88,12 @@ class EdgeConstraint implements ElementConstraint { /** The allowable out-vertex labels; null indicates any allowed */ List rangeLabels + /** The multiplicity of the relationthip */ + Multiplicity multiplicity + + /** List of allowed vertex properties */ + List properties + /////////////////////////////////////////////////////////////////////////// // METHODS diff --git a/app/carnival-core/src/main/groovy/carnival/core/graph/EdgePropertyConstraint.groovy b/app/carnival-core/src/main/groovy/carnival/core/graph/EdgePropertyConstraint.groovy new file mode 100644 index 0000000..be2a1d0 --- /dev/null +++ b/app/carnival-core/src/main/groovy/carnival/core/graph/EdgePropertyConstraint.groovy @@ -0,0 +1,41 @@ +package carnival.core.graph + + + +import groovy.transform.ToString +import groovy.transform.EqualsAndHashCode + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + + +/** + * Defines the rules governing the given property. + *

+ * - Unique asserts that the property values must be unique + * across all vertices with a given label that have that + * property. + *

+ * - Required asserts that the property must be present + * and non-null(?) + *

+ * - Index instructs Carnival to use the underlying graph + * database services to index the given property for + * efficient lookup. + */ +@ToString +class EdgePropertyConstraint { + + /** The name of the property */ + String name + + /** Whether the values of this property must be unique */ + Boolean unique = false + + /** Whether this property is required to exist */ + Boolean required = false + + /** Whether this property should be indexed by the database */ + Boolean index = false +} + diff --git a/app/carnival-core/src/main/groovy/carnival/core/graph/GraphSchema.groovy b/app/carnival-core/src/main/groovy/carnival/core/graph/GraphSchema.groovy index 05ee02f..7edd38f 100644 --- a/app/carnival-core/src/main/groovy/carnival/core/graph/GraphSchema.groovy +++ b/app/carnival-core/src/main/groovy/carnival/core/graph/GraphSchema.groovy @@ -63,6 +63,13 @@ interface GraphSchema { */ Collection getEdgeConstraints() + + /** + * Return the constraints on properties. + * @return The collection of property constraints + */ + Collection getPropertyConstraints() + } diff --git a/app/carnival-core/src/main/groovy/carnival/core/graph/GremlinTraitUtilities.groovy b/app/carnival-core/src/main/groovy/carnival/core/graph/GremlinTraitUtilities.groovy index 2e2e0c9..6139b67 100644 --- a/app/carnival-core/src/main/groovy/carnival/core/graph/GremlinTraitUtilities.groovy +++ b/app/carnival-core/src/main/groovy/carnival/core/graph/GremlinTraitUtilities.groovy @@ -84,7 +84,7 @@ class GremlinTraitUtilities { * @param cl The closure to execute * @return The result of the closure */ - static public Object withGremlin(Map args ,Graph graph, GraphTraversalSource g, Closure cl) { + static public Object withGremlin(Map args, Graph graph, GraphTraversalSource g, Closure cl) { assert args != null assert graph != null assert g != null diff --git a/app/carnival-core/src/main/groovy/carnival/core/graph/PropertyConstraint.groovy b/app/carnival-core/src/main/groovy/carnival/core/graph/PropertyConstraint.groovy new file mode 100644 index 0000000..313f6dd --- /dev/null +++ b/app/carnival-core/src/main/groovy/carnival/core/graph/PropertyConstraint.groovy @@ -0,0 +1,73 @@ +package carnival.core.graph + + + +import groovy.transform.ToString +import groovy.transform.EqualsAndHashCode + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +import org.apache.tinkerpop.gremlin.process.traversal.Traversal +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource +import org.apache.tinkerpop.gremlin.process.traversal.P +import org.apache.tinkerpop.gremlin.structure.T +import org.apache.tinkerpop.gremlin.structure.Graph +import org.apache.tinkerpop.gremlin.structure.Transaction +import org.apache.tinkerpop.gremlin.structure.Vertex +import org.apache.tinkerpop.gremlin.structure.Edge +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__ + +import carnival.graph.PropertyDefinition +import carnival.graph.PropertyDefinition.Cardinality + + + +/** + * Defined constraints on a property. + * + */ +@ToString +class PropertyConstraint { + + /////////////////////////////////////////////////////////////////////////// + // STATIC + /////////////////////////////////////////////////////////////////////////// + + /** + * Create an property constraint from a property definition. + * @param propertyDef The property definition + * @return The property constraint + */ + static public PropertyConstraint create(PropertyDefinition propertyDef) { + assert propertyDef + + PropertyConstraint rd = new PropertyConstraint( + propertyDef:propertyDef, + label:propertyDef.label, + dataType:propertyDef.dataType, + cardinality: propertyDef.cardinality + ) + return rd + } + + + /////////////////////////////////////////////////////////////////////////// + // FIELDS + /////////////////////////////////////////////////////////////////////////// + + /** The property definition that defines the constraint parameters */ + PropertyDefinition propertyDef + + /** The property key to which this constraint applies */ + String label + + /** Data type */ + Class dataType + + /** The cardinality of the property */ + Cardinality cardinality + +} + + diff --git a/app/carnival-core/src/main/groovy/carnival/core/graph/VertexConstraint.groovy b/app/carnival-core/src/main/groovy/carnival/core/graph/VertexConstraint.groovy index 45c6828..f7cc698 100644 --- a/app/carnival-core/src/main/groovy/carnival/core/graph/VertexConstraint.groovy +++ b/app/carnival-core/src/main/groovy/carnival/core/graph/VertexConstraint.groovy @@ -44,9 +44,12 @@ class VertexConstraint implements ElementConstraint { * @return A vertex constraint */ static public VertexConstraint create(VertexDefinition vdef) { + assert vdef + def propDefs = [] vdef.vertexProperties.each { PropertyDefinition pdef -> propDefs << new VertexPropertyConstraint( + propertyDef: pdef, name: pdef.label, unique: pdef.unique, required: pdef.required, diff --git a/app/carnival-core/src/main/groovy/carnival/core/graph/VertexPropertyConstraint.groovy b/app/carnival-core/src/main/groovy/carnival/core/graph/VertexPropertyConstraint.groovy index 26406b2..2df0b42 100644 --- a/app/carnival-core/src/main/groovy/carnival/core/graph/VertexPropertyConstraint.groovy +++ b/app/carnival-core/src/main/groovy/carnival/core/graph/VertexPropertyConstraint.groovy @@ -8,6 +8,9 @@ import groovy.transform.EqualsAndHashCode import org.slf4j.Logger import org.slf4j.LoggerFactory +import carnival.graph.PropertyDefinition + + /** * Defines the rules governing the given property. @@ -26,6 +29,9 @@ import org.slf4j.LoggerFactory @ToString class VertexPropertyConstraint { + /** The source property definition */ + PropertyDefinition propertyDef + /** The name of the property */ String name diff --git a/app/carnival-core/src/test/groovy/carnival/core/CarnivalConstraintSpec.groovy b/app/carnival-core/src/test/groovy/carnival/core/CarnivalConstraintSpec.groovy new file mode 100644 index 0000000..d3fb021 --- /dev/null +++ b/app/carnival-core/src/test/groovy/carnival/core/CarnivalConstraintSpec.groovy @@ -0,0 +1,106 @@ +package carnival.core + + + +import spock.lang.Specification +import spock.lang.Unroll +import spock.lang.Shared + +import org.apache.tinkerpop.gremlin.structure.T +import org.apache.tinkerpop.gremlin.process.traversal.Traversal +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource + +import carnival.graph.* +import carnival.core.graph.* + + + +/** + * + */ +class CarnivalConstraintSpec extends Specification { + + /////////////////////////////////////////////////////////////////////////// + // MODELS + /////////////////////////////////////////////////////////////////////////// + + @PropertyModel + static enum PX { + VOLUME( + dataType: Float.class + ), + NOTE_PAD( + cardinality: PropertyDefinition.Cardinality.LIST + ) + } + + @VertexModel + static enum VX { + SUITCASE( + propertyDefs:[ + PX.VOLUME, + PX.NOTE_PAD + ] + ) + } + + + /////////////////////////////////////////////////////////////////////////// + // FIELDS + /////////////////////////////////////////////////////////////////////////// + + @Shared carnival + + + + /////////////////////////////////////////////////////////////////////////// + // SET UP + /////////////////////////////////////////////////////////////////////////// + + def setup() { + carnival = CarnivalTinker.create() + carnival.addModel(PX) + carnival.addModel(VX) + } + + def setupSpec() { } + + + def cleanupSpec() { } + + + def cleanup() { + if (carnival) carnival.close() + } + + + + /////////////////////////////////////////////////////////////////////////// + // TESTS + /////////////////////////////////////////////////////////////////////////// + + def "property constraints"() { + expect: + carnival.graphSchema + carnival.graphSchema.propertyConstraints + carnival.graphSchema.propertyConstraints.size() == 2 + + PX.VOLUME instanceof PropertyDefinition + PX.VOLUME.dataType == Float.class + + PX.NOTE_PAD instanceof PropertyDefinition + PX.NOTE_PAD.dataType == String.class + PX.NOTE_PAD.cardinality == PropertyDefinition.Cardinality.LIST + } + + + def "vertex constraints"() { + expect: + carnival.graphSchema + carnival.graphSchema.vertexConstraints + carnival.graphSchema.vertexConstraints.size() == 15 + } + + +} + diff --git a/app/carnival-core/src/test/groovy/carnival/core/CarnivalJanusBerkeleySpec.groovy b/app/carnival-core/src/test/groovy/carnival/core/CarnivalJanusBerkeleySpec.groovy new file mode 100644 index 0000000..2d4b745 --- /dev/null +++ b/app/carnival-core/src/test/groovy/carnival/core/CarnivalJanusBerkeleySpec.groovy @@ -0,0 +1,429 @@ +package carnival.core + + + +import spock.lang.Specification +import spock.lang.Unroll +import spock.lang.Shared + +import org.apache.tinkerpop.gremlin.structure.T +import org.apache.tinkerpop.gremlin.process.traversal.Traversal +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource + +import org.janusgraph.core.schema.JanusGraphManagement +import org.janusgraph.core.schema.JanusGraphManagement.IndexBuilder +import org.janusgraph.core.schema.JanusGraphIndex +import org.janusgraph.core.Connection +import org.janusgraph.core.VertexLabel +import org.janusgraph.core.EdgeLabel +import org.janusgraph.core.PropertyKey + +import carnival.graph.* +import carnival.core.graph.* + + + +/** + * gradle test --tests "carnival.core.CarnivalJanusBerkeleySpec" + * + */ +class CarnivalJanusBerkeleySpec extends Specification { + + /////////////////////////////////////////////////////////////////////////// + // DEFS + /////////////////////////////////////////////////////////////////////////// + + @PropertyModel + static enum PX { + VOLUME( + dataType: Float.class + ), + NOTE_PAD( + cardinality: PropertyDefinition.Cardinality.LIST + ), + ID + } + + @VertexModel + static enum VX { + SUITCASE( + vertexProperties:[ + PX.VOLUME.withConstraints(required:true, index:true), + PX.NOTE_PAD, + PX.ID.withConstraints(required:true, unique:true) + ] + ), + PENCIL + } + + @EdgeModel + static enum EX { + CONTAINS( + multiplicity: EdgeDefinition.Multiplicity.ONE2MANY, + domain:[VX.SUITCASE], + range:[VX.PENCIL] + ), + IS_CONTAINED_BY( + multiplicity: EdgeDefinition.Multiplicity.MANY2ONE + ) + } + + + /////////////////////////////////////////////////////////////////////////// + // FIELDS + /////////////////////////////////////////////////////////////////////////// + + @Shared CarnivalJanusBerkeley.Config config + @Shared Carnival carnival + + + /////////////////////////////////////////////////////////////////////////// + // SET UP + /////////////////////////////////////////////////////////////////////////// + + + def setupSpec() { } + + def setup() { + config = new CarnivalJanusBerkeley.Config() + CarnivalJanusBerkeley.clearGraph(config) + carnival = CarnivalJanusBerkeley.create(config) + } + + def cleanup() { + carnival.close() + } + + def cleanupSpec() { } + + + + /////////////////////////////////////////////////////////////////////////// + // TESTS + /////////////////////////////////////////////////////////////////////////// + + def "property uniqueness"(){ + when: + carnival.addModel(PX) + carnival.addModel(VX) + + JanusGraphManagement mgmt = carnival.graph.openManagement() + + String idxName = CarnivalJanusBerkeley.indexNameOf( + VX.SUITCASE, PX.ID + ) + println "idxName: ${idxName}" + JanusGraphIndex idx = mgmt.getGraphIndex(idxName) + + mgmt.rollback() + + then: + idx + idx.isUnique() + } + + + def "vertex isclass namespace compound indices"() { + when: + carnival.addModel(PX) + carnival.addModel(VX) + + JanusGraphManagement mgmt = carnival.graph.openManagement() + + String idxName = CarnivalJanusBerkeley.indexNameOf( + VX.SUITCASE, Base.PX.IS_CLASS, Base.PX.NAME_SPACE + ) + println "idxName: ${idxName}" + JanusGraphIndex idx = mgmt.getGraphIndex(idxName) + + mgmt.rollback() + + then: + idx + } + + + def "vertex namespace compound indices"() { + when: + carnival.addModel(PX) + carnival.addModel(VX) + + JanusGraphManagement mgmt = carnival.graph.openManagement() + + String idxName = CarnivalJanusBerkeley.indexNameOf( + VX.SUITCASE, Base.PX.NAME_SPACE, PX.VOLUME + ) + println "idxName: ${idxName}" + JanusGraphIndex idx = mgmt.getGraphIndex(idxName) + + mgmt.rollback() + + then: + idx + } + + + def "isclass and namespace are indexed"() { + when: + JanusGraphManagement mgmt = carnival.graph.openManagement() + + String idxName = CarnivalJanusBerkeley.indexNameOf( + Base.PX.IS_CLASS, Base.PX.NAME_SPACE + ) + println "idxName: ${idxName}" + JanusGraphIndex idx = mgmt.getGraphIndex(idxName) + + mgmt.rollback() + + then: + idx + } + + + def "namespace is indexed"() { + when: + JanusGraphManagement mgmt = carnival.graph.openManagement() + + String idxName = 'NameSpace0CarnivalGraphBasePx' + JanusGraphIndex idx = mgmt.getGraphIndex(idxName) + + mgmt.rollback() + + then: + idx + } + + + def "vertex property index"() { + when: + carnival.addModel(PX) + carnival.addModel(VX) + carnival.addModel(EX) + + JanusGraphManagement mgmt = carnival.graph.openManagement() + + String idxName = VX.SUITCASE.label + '-' + PX.VOLUME.label + JanusGraphIndex idx = mgmt.getGraphIndex(idxName) + + mgmt.rollback() + + then: + idx + + } + + + def "vertex mapped connections"() { + when: + carnival.addModel(PX) + carnival.addModel(VX) + carnival.addModel(EX) + + JanusGraphManagement mgmt = carnival.graph.openManagement() + Set vertexLabels = mgmt.getVertexLabels().toSet() + + VertexLabel suitcaseLabel = vertexLabels.find { + it.name() == VX.SUITCASE.label + } + Set cs = suitcaseLabel.mappedConnections().toSet() + mgmt.rollback() + + then: + suitcaseLabel + cs + cs.size() == 1 + } + + + def "vertex mapped properties"() { + when: + carnival.addModel(PX) + carnival.addModel(VX) + + JanusGraphManagement mgmt = carnival.graph.openManagement() + Set vertexLabels = mgmt.getVertexLabels().toSet() + + VertexLabel suitcaseLabel = vertexLabels.find { + it.name() == VX.SUITCASE.label + } + Set pks = suitcaseLabel.mappedProperties().toSet() + mgmt.rollback() + + then: + suitcaseLabel + pks + pks.size() == 3 + } + + + def "property cardinality"() { + when: + carnival.addModel(PX) + + JanusGraphManagement mgmt = carnival.graph.openManagement() + Set propertyKeys = mgmt.getRelationTypes(PropertyKey).toSet() + PropertyKey volumeKey = propertyKeys.find { + it.name() == PX.VOLUME.label + } + org.janusgraph.core.Cardinality volumeCardinality = volumeKey.cardinality() + PropertyKey notePadKey = propertyKeys.find { + it.name() == PX.NOTE_PAD.label + } + org.janusgraph.core.Cardinality notePadCardinality = notePadKey.cardinality() + mgmt.rollback() + + then: + volumeKey + volumeCardinality == org.janusgraph.core.Cardinality.SINGLE + notePadCardinality == org.janusgraph.core.Cardinality.LIST + } + + + def "property data type"() { + when: + carnival.addModel(PX) + + JanusGraphManagement mgmt = carnival.graph.openManagement() + Set propertyKeys = mgmt.getRelationTypes(PropertyKey).toSet() + PropertyKey volumeKey = propertyKeys.find { + it.name() == PX.VOLUME.label + } + Class volumeDataType = volumeKey.dataType() + PropertyKey notePadKey = propertyKeys.find { + it.name() == PX.NOTE_PAD.label + } + Class notePadDataType = notePadKey.dataType() + mgmt.rollback() + + then: + volumeKey + volumeDataType == Float.class + notePadDataType == String.class + } + + + def 'edge multiplicity'() { + when: + // addModel() calls openManagement and commit() + carnival.addModel(EX) + + JanusGraphManagement mgmt = carnival.graph.openManagement() + Set edgeLabels = mgmt.getRelationTypes(EdgeLabel).toSet() + EdgeLabel containsLabel = edgeLabels.find { + it.name() == EX.CONTAINS.label + } + org.janusgraph.core.Multiplicity clm = containsLabel.multiplicity() + mgmt.rollback() + + then: + containsLabel + clm == org.janusgraph.core.Multiplicity.ONE2MANY + } + + + def 'edge label'() { + when: + // addModel() calls openManagement and commit() + carnival.addModel(VX) + carnival.addModel(EX) + + JanusGraphManagement mgmt = carnival.graph.openManagement() + Set edgeLabels = mgmt.getRelationTypes(EdgeLabel).toSet() + + println "edgeLabels: ${edgeLabels}" + + edgeLabels.each { EdgeLabel vl -> + println "vl: ${vl.label()} ${vl.name()}" + } + + String lbl = EX.CONTAINS.label.toString() + println "lbl: ${lbl}" + + EdgeLabel containsLabel = edgeLabels.find { + it.name() == lbl + } + mgmt.rollback() + + then: + containsLabel + } + + + def 'add model vertex'() { + when: + // addModel() calls openManagement and commit() + carnival.addModel(VX) + + JanusGraphManagement mgmt = carnival.graph.openManagement() + Set vertexLabels = mgmt.getVertexLabels().toSet() + + println "vertexLabels: ${vertexLabels}" + + vertexLabels.each { VertexLabel vl -> + println "vl: ${vl.label()} ${vl.name()}" + } + + String lbl = VX.SUITCASE.label.toString() + println "lbl: ${lbl}" + + VertexLabel suitcaseLabel = vertexLabels.find { + it.name() == lbl + } + mgmt.rollback() + + then: + suitcaseLabel + } + + + def 'test basic ops'() { + when: + carnival.addModel(VX) + carnival.withGremlin { graph, g -> + carnival.addGraphSchemaVertices(graph, g) + } + + then: + carnival + + when: + def numScs + carnival.withGremlin { graph, g -> + numScs = g.V().isa(VX.SUITCASE).count().next() + } + + then: + numScs == 0 + + when: + def sc1 + carnival.withGremlin { graph, g -> + sc1 = VX.SUITCASE.instance().withProperties( + PX.ID, 'id1', + PX.VOLUME, 1 + ).create(graph) + } + + then: + sc1 + + when: + def sc2 + carnival.withGremlin { graph, g -> + sc2 = g.V().isa(VX.SUITCASE).next() + } + + then: + sc2 + sc2.id() == sc1.id() + sc2 == sc1 + } + + + def 'test create'() { + expect: + carnival + } + +} + diff --git a/app/carnival-core/src/test/groovy/carnival/core/CarnivalModelSpec.groovy b/app/carnival-core/src/test/groovy/carnival/core/CarnivalModelSpec.groovy index 6f015b5..62af111 100644 --- a/app/carnival-core/src/test/groovy/carnival/core/CarnivalModelSpec.groovy +++ b/app/carnival-core/src/test/groovy/carnival/core/CarnivalModelSpec.groovy @@ -33,31 +33,21 @@ class CarnivalModelSpec extends Specification { GLOBAL_THING(global:true) } - @VertexModel - static enum VX2 { - THING - } - - @VertexModel + /*@VertexModel static enum VX3 { GLOBAL_THING(global:true) - } + }*/ - @EdgeModel + /*@EdgeModel static enum EX1 { VERB, GLOBAL_VERB(global:true) - } - - @EdgeModel - static enum EX2 { - VERB - } + }*/ - @EdgeModel + /*@EdgeModel static enum EX3 { GLOBAL_VERB(global:true) - } + }*/ /////////////////////////////////////////////////////////////////////////// @@ -99,7 +89,7 @@ class CarnivalModelSpec extends Specification { // TESTS /////////////////////////////////////////////////////////////////////////// - def "duplicate global edge model ignoreDuplicateModels no exception"() { + /*def "duplicate global edge model ignoreDuplicateModels no exception"() { when: carnival.ignoreDuplicateModels = true carnival.addModel(EX1) @@ -112,10 +102,10 @@ class CarnivalModelSpec extends Specification { then: noExceptionThrown() - } + }*/ - def "duplicate edge model ignoreDuplicateModels no exception"() { + /*def "duplicate edge model ignoreDuplicateModels no exception"() { when: carnival.ignoreDuplicateModels = true carnival.addModel(EX1) @@ -128,10 +118,10 @@ class CarnivalModelSpec extends Specification { then: noExceptionThrown() - } + }*/ - def "add duplicate global edge model throws exception"() { + /*def "add duplicate global edge model throws exception"() { expect: !carnival.ignoreDuplicateModels @@ -146,10 +136,10 @@ class CarnivalModelSpec extends Specification { then: DuplicateModelException e = thrown() - } + }*/ - def "add duplicate edge model throws exception"() { + /*def "add duplicate edge model throws exception"() { expect: !carnival.ignoreDuplicateModels @@ -164,10 +154,10 @@ class CarnivalModelSpec extends Specification { then: DuplicateModelException e = thrown() - } + }*/ - def "duplicate global vertex model ignoreDuplicateModels no exception"() { + /*def "duplicate global vertex model ignoreDuplicateModels no exception"() { when: carnival.ignoreDuplicateModels = true carnival.addModel(VX1) @@ -180,7 +170,7 @@ class CarnivalModelSpec extends Specification { then: noExceptionThrown() - } + }*/ def "duplicate vertex model ignoreDuplicateModels no exception"() { @@ -199,7 +189,7 @@ class CarnivalModelSpec extends Specification { } - def "add duplicate global vertex model throws exception"() { + /*def "add duplicate global vertex model throws exception"() { expect: !carnival.ignoreDuplicateModels @@ -214,7 +204,7 @@ class CarnivalModelSpec extends Specification { then: DuplicateModelException e = thrown() - } + }*/ def "add duplicate vertex model throws exception"() { diff --git a/app/carnival-core/src/test/groovy/carnival/core/CarnivalSpec.groovy b/app/carnival-core/src/test/groovy/carnival/core/CarnivalSpec.groovy index 296fb58..8496e53 100644 --- a/app/carnival-core/src/test/groovy/carnival/core/CarnivalSpec.groovy +++ b/app/carnival-core/src/test/groovy/carnival/core/CarnivalSpec.groovy @@ -26,7 +26,7 @@ class CarnivalSpec extends Specification { /////////////////////////////////////////////////////////////////////////// static enum VX implements VertexDefinition { - CGS_SUITCASE + SUITCASE } /////////////////////////////////////////////////////////////////////////// @@ -40,6 +40,10 @@ class CarnivalSpec extends Specification { Core.VX.IDENTIFIER.instance().withProperty(Core.PX.VALUE, "2"), ] + @Shared String idlbl = 'Identifier0CarnivalCoreCoreVx' + @Shared String idClassLbl = 'IdentifierClass0CarnivalCoreCoreVx' + @Shared String pxValueLbl = 'Value0CarnivalCoreCorePx' + /////////////////////////////////////////////////////////////////////////// // SET UP @@ -100,28 +104,28 @@ class CarnivalSpec extends Specification { //carnival.graphSchema.vertexBuilders?.size() == vertexBuilders?.size() when: - vs = g.V().hasLabel('Identifier').toList() + vs = g.V().hasLabel(idlbl).toList() then: vs vs.size() == 2 when: - vs = g.V().hasLabel('Identifier').has("value", "1").toList() + vs = g.V().hasLabel(idlbl).has(pxValueLbl, "1").toList() then: vs vs.size() == 1 when: - vs = g.V().hasLabel('Identifier').has("value", "2").toList() + vs = g.V().hasLabel(idlbl).has(pxValueLbl, "2").toList() then: vs vs.size() == 1 when: - vs = g.V().hasLabel('Identifier').has("value", "3").toList() + vs = g.V().hasLabel(idlbl).has(pxValueLbl, "3").toList() then: vs.size() == 0 @@ -138,8 +142,10 @@ class CarnivalSpec extends Specification { carnival.checkConstraints().size() == 0 when: - g.V().hasLabel('Identifier').has("value", "1").next().remove() - vs = g.V().hasLabel('Identifier').has("value", "1").toList() + println "--- Core.PX.VALUE: ${Core.PX.VALUE.label}" + + g.V().hasLabel(idlbl).has(pxValueLbl, "1").next().remove() + vs = g.V().hasLabel(idlbl).has(pxValueLbl, "1").toList() then: vs.size() == 0 @@ -148,8 +154,7 @@ class CarnivalSpec extends Specification { when: Core.VX.IDENTIFIER.instance().withProperty(Core.PX.VALUE, "1").vertex(graph, g) - //graph.addVertex(T.label, "Identifier", "value", "1") - vs = g.V().hasLabel('Identifier').has("value", "1").toList() + vs = g.V().hasLabel(idlbl).has(pxValueLbl, "1").toList() then: vs.size() == 1 @@ -157,11 +162,11 @@ class CarnivalSpec extends Specification { when: graph.addVertex( - T.label, "Identifier", + T.label, idlbl, Base.PX.NAME_SPACE.label, Core.VX.IDENTIFIER.nameSpace, - "value", "1" + pxValueLbl, "1" ) - vs = g.V().hasLabel('Identifier').has("value", "1").toList() + vs = g.V().hasLabel(idlbl).has(pxValueLbl, "1").toList() then: vs.size() == 2 @@ -179,8 +184,9 @@ class CarnivalSpec extends Specification { carnival.checkConstraints().size() == 0 when: + println "--- Core.VX.IDENTIFIER_CLASS: ${Core.VX.IDENTIFIER_CLASS.label}" vert = graph.addVertex( - T.label, "IdentifierClass", + T.label, idClassLbl, Base.PX.NAME_SPACE.label, Core.VX.IDENTIFIER_CLASS.nameSpace ) @@ -219,7 +225,7 @@ class CarnivalSpec extends Specification { // "hasScope", false, // "hasCreationFacility", true //) - suitcase = VX.CGS_SUITCASE.instance().vertex(graph, g) + suitcase = VX.SUITCASE.instance().vertex(graph, g) then: carnival.checkConstraints().size() == 0 @@ -264,7 +270,7 @@ class CarnivalSpec extends Specification { Core.PX.HAS_SCOPE, false, Core.PX.HAS_CREATION_FACILITY, true ).vertex(graph, g) - suitcase = VX.CGS_SUITCASE.instance().vertex(graph, g) + suitcase = VX.SUITCASE.instance().vertex(graph, g) then: carnival.checkConstraints().size() == 0 @@ -387,9 +393,13 @@ class CarnivalSpec extends Specification { def facility1 = Core.VX.IDENTIFIER_FACILITY.instance().withProperty(Core.PX.NAME, 'facility1').vertex(graph, g) def facility2 = Core.VX.IDENTIFIER_FACILITY.instance().withProperty(Core.PX.NAME, 'facility2').vertex(graph, g) + + def id1Class1 = Core.VX.IDENTIFIER.instance().withProperty(Core.PX.VALUE, 'id1').vertex(graph, g) Base.EX.IS_INSTANCE_OF.relate(g, id1Class1, idClass1) + println "--- id1Class1: ${id1Class1.label}" + def id2Class1 = Core.VX.IDENTIFIER.instance().withProperty(Core.PX.VALUE, 'id2').vertex(graph, g) Base.EX.IS_INSTANCE_OF.relate(g, id2Class1, idClass1) @@ -432,10 +442,10 @@ class CarnivalSpec extends Specification { // existing nodes when: - preIdCount = g.V().hasLabel("Identifier").toList().size() + preIdCount = g.V().hasLabel(idlbl).toList().size() identifier = new Identifier(identifierClass:idClass1, identifierScope:null, value:"id1" ) idNode = identifier.getOrCreateNode(graph) - postIdCount = g.V().hasLabel("Identifier").toList().size() + postIdCount = g.V().hasLabel(idlbl).toList().size() then: idNode == id1Class1 @@ -443,10 +453,10 @@ class CarnivalSpec extends Specification { when: - preIdCount = g.V().hasLabel("Identifier").toList().size() + preIdCount = g.V().hasLabel(idlbl).toList().size() identifier = new Identifier(identifierClass:idClass1, identifierScope:null, value:"id2" ) idNode = identifier.getOrCreateNode(graph) - postIdCount = g.V().hasLabel("Identifier").toList().size() + postIdCount = g.V().hasLabel(idlbl).toList().size() then: idNode == id2Class1 @@ -454,10 +464,10 @@ class CarnivalSpec extends Specification { when: - preIdCount = g.V().hasLabel("Identifier").toList().size() + preIdCount = g.V().hasLabel(idlbl).toList().size() identifier = new Identifier(identifierClass:idClass2, identifierScope:null, value:"id1" ) idNode = identifier.getOrCreateNode(graph) - postIdCount = g.V().hasLabel("Identifier").toList().size() + postIdCount = g.V().hasLabel(idlbl).toList().size() then: idNode == id1Class2 @@ -465,20 +475,20 @@ class CarnivalSpec extends Specification { when: - preIdCount = g.V().hasLabel("Identifier").toList().size() + preIdCount = g.V().hasLabel(idlbl).toList().size() identifier = new Identifier(identifierClass:scopedIdClass, identifierScope:scope1, value:"scopedId1" ) idNode = identifier.getOrCreateNode(graph) - postIdCount = g.V().hasLabel("Identifier").toList().size() + postIdCount = g.V().hasLabel(idlbl).toList().size() then: preIdCount == postIdCount idNode == scopedId when: - preIdCount = g.V().hasLabel("Identifier").toList().size() + preIdCount = g.V().hasLabel(idlbl).toList().size() identifier = new Identifier(identifierClass:facilityIdClass, identifierFacility:facility1, value:"facilityId1" ) idNode = identifier.getOrCreateNode(graph) - postIdCount = g.V().hasLabel("Identifier").toList().size() + postIdCount = g.V().hasLabel(idlbl).toList().size() then: preIdCount == postIdCount @@ -487,10 +497,10 @@ class CarnivalSpec extends Specification { // new nodes when: - preIdCount = g.V().hasLabel("Identifier").toList().size() + preIdCount = g.V().hasLabel(idlbl).toList().size() identifier = new Identifier(identifierClass:idClass2, identifierScope:null, value:"id2" ) idNode = identifier.getOrCreateNode(graph) - postIdCount = g.V().hasLabel("Identifier").toList().size() + postIdCount = g.V().hasLabel(idlbl).toList().size() then: !(idNode in allIdVerts) @@ -498,20 +508,20 @@ class CarnivalSpec extends Specification { when: - preIdCount = g.V().hasLabel("Identifier").toList().size() + preIdCount = g.V().hasLabel(idlbl).toList().size() identifier = new Identifier(identifierClass:scopedId, identifierScope:scope2, value:"scopedId1" ) idNode = identifier.getOrCreateNode(graph) - postIdCount = g.V().hasLabel("Identifier").toList().size() + postIdCount = g.V().hasLabel(idlbl).toList().size() then: !(idNode in allIdVerts) preIdCount == postIdCount - 1 when: - preIdCount = g.V().hasLabel("Identifier").toList().size() + preIdCount = g.V().hasLabel(idlbl).toList().size() identifier = new Identifier(identifierClass:facilityId, identifierFacility:scope2, value:"facilityId1" ) idNode = identifier.getOrCreateNode(graph) - //postIdCount = g.V().hasLabel("Identifier").toList().size() + //postIdCount = g.V().hasLabel(idlbl).toList().size() then: //!(idNode in allIdVerts) diff --git a/app/carnival-core/src/test/groovy/carnival/core/graph/DefaultGraphValidatorSpec.groovy b/app/carnival-core/src/test/groovy/carnival/core/graph/DefaultGraphValidatorSpec.groovy index 972afec..e61839b 100644 --- a/app/carnival-core/src/test/groovy/carnival/core/graph/DefaultGraphValidatorSpec.groovy +++ b/app/carnival-core/src/test/groovy/carnival/core/graph/DefaultGraphValidatorSpec.groovy @@ -205,7 +205,7 @@ class DefaultGraphValidatorSpec extends Specification { } - def "global edge def constraints"() { + /*def "global edge def constraints"() { def thing, anotherThing, suitcase expect: @@ -228,7 +228,7 @@ class DefaultGraphValidatorSpec extends Specification { then: graphValidator.checkModel(g, graphSchema).size() == 0 graphValidator.checkConstraints(g, graphSchema).size() == 1 - } + }*/ def "unmodelled edge labels fail unless there is global def"() { @@ -253,7 +253,7 @@ class DefaultGraphValidatorSpec extends Specification { } - def "unmodelled vertex labels fail unless there is global def"() { + /*def "unmodelled vertex labels fail unless there is global def"() { def thing expect: @@ -270,10 +270,10 @@ class DefaultGraphValidatorSpec extends Specification { then: graphValidator.checkModel(g, graphSchema).size() == 1 - } + }*/ - def "checkModel global vertex def addVertex"() { + /*def "checkModel global vertex def addVertex"() { def thing expect: @@ -293,11 +293,11 @@ class DefaultGraphValidatorSpec extends Specification { then: graphValidator.checkModel(g, graphSchema).size() == 0 graphValidator.checkConstraints(g, graphSchema).size() == 0 - } + }*/ - def "checkModel global vertex def enum"() { + /*def "checkModel global vertex def enum"() { def thing expect: @@ -317,7 +317,7 @@ class DefaultGraphValidatorSpec extends Specification { then: graphValidator.checkModel(g, graphSchema).size() == 0 graphValidator.checkConstraints(g, graphSchema).size() == 0 - } + }*/ def "test checkModel for unmodeled vertex and edge labels"() { diff --git a/app/carnival-core/src/test/groovy/carnival/core/graph/EdgeConstraintSpec.groovy b/app/carnival-core/src/test/groovy/carnival/core/graph/EdgeConstraintSpec.groovy new file mode 100644 index 0000000..ea9e672 --- /dev/null +++ b/app/carnival-core/src/test/groovy/carnival/core/graph/EdgeConstraintSpec.groovy @@ -0,0 +1,94 @@ +package carnival.core.graph + + + +import spock.lang.Specification +import spock.lang.Unroll +import spock.lang.Shared + +import org.apache.tinkerpop.gremlin.structure.T +import org.apache.tinkerpop.gremlin.process.traversal.Traversal +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource + +import carnival.graph.* +import carnival.core.CarnivalTinker + + + +/** + * + * + */ +class EdgeConstraintSpec extends Specification { + + /////////////////////////////////////////////////////////////////////////// + // FIELDS + /////////////////////////////////////////////////////////////////////////// + + @Shared carnival + + + /////////////////////////////////////////////////////////////////////////// + // SET UP + /////////////////////////////////////////////////////////////////////////// + + def setupSpec() { } + + def setup() { + carnival = CarnivalTinker.create() + } + + def cleanup() { + if (carnival) carnival.graph.close() + } + + def cleanupSpec() { } + + + /////////////////////////////////////////////////////////////////////////// + // MODEL + /////////////////////////////////////////////////////////////////////////// + + @PropertyModel + static enum PX { + PROP_A, PROP_B + } + + @EdgeModel + static enum EX { + REL_A, + REL_B( + propertyDefs:[ + PX.PROP_A.withConstraints(index:true), + PX.PROP_B + ] + ) + } + + + + /////////////////////////////////////////////////////////////////////////// + // TESTS + /////////////////////////////////////////////////////////////////////////// + + def "create with property def"() { + when: + EdgeConstraint ec1 = EdgeConstraint.create(EX.REL_B) + + then: + ec1.label == EX.REL_B.label + ec1.properties + + when: + EdgePropertyConstraint epc = ec1.properties.find({ + it.name == PX.PROP_A.label + }) + + then: + epc + } + + + +} + diff --git a/app/carnival-core/src/test/groovy/carnival/core/graph/GremlinTraitSpec.groovy b/app/carnival-core/src/test/groovy/carnival/core/graph/GremlinTraitSpec.groovy index dadfbc5..2e97df9 100644 --- a/app/carnival-core/src/test/groovy/carnival/core/graph/GremlinTraitSpec.groovy +++ b/app/carnival-core/src/test/groovy/carnival/core/graph/GremlinTraitSpec.groovy @@ -10,7 +10,7 @@ import org.apache.tinkerpop.gremlin.structure.T import org.apache.tinkerpop.gremlin.structure.Graph import org.apache.tinkerpop.gremlin.process.traversal.Traversal import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource -import static org.apache.tinkerpop.gremlin.neo4j.process.traversal.LabelP.of +//import static org.apache.tinkerpop.gremlin.neo4j.process.traversal.LabelP.of import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph diff --git a/app/carnival-core/src/test/groovy/carnival/core/graph/LegacyGraphSpec.groovy b/app/carnival-core/src/test/groovy/carnival/core/graph/LegacyGraphSpec.groovy deleted file mode 100644 index a402824..0000000 --- a/app/carnival-core/src/test/groovy/carnival/core/graph/LegacyGraphSpec.groovy +++ /dev/null @@ -1,160 +0,0 @@ -package carnival.core.graph - - - -import spock.lang.Specification -import spock.lang.Unroll -import spock.lang.Shared - -import org.apache.tinkerpop.gremlin.structure.T -import org.apache.tinkerpop.gremlin.process.traversal.Traversal -import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource - -import carnival.graph.* -import carnival.core.CarnivalTinker -import carnival.core.Core - - - -/** - * gradle test --tests "carnival.core.CarnivalSpec" - * - */ -class LegacyGraphSpec extends Specification { - - /////////////////////////////////////////////////////////////////////////// - // DEFS - /////////////////////////////////////////////////////////////////////////// - - static enum VX implements VertexDefinition { - CGS_SUITCASE - } - - /////////////////////////////////////////////////////////////////////////// - // FIELDS - /////////////////////////////////////////////////////////////////////////// - - @Shared carnival - - - /////////////////////////////////////////////////////////////////////////// - // SET UP - /////////////////////////////////////////////////////////////////////////// - - - def setupSpec() { } - - def setup() { - carnival = CarnivalTinker.create() - carnival.graphValidator = new LegacyValidator() - } - - def cleanup() { - if (carnival) carnival.close() - } - - def cleanupSpec() { } - - - - /////////////////////////////////////////////////////////////////////////// - // TESTS - /////////////////////////////////////////////////////////////////////////// - - - def "test checkConstraints for identifier uniqueness"() { - given: - def graph = carnival.graph - def g = graph.traversal() - - def idClass1 = graph.addVertex(T.label, 'IdentifierClass', 'name', 'idClass1', 'hasCreationFacility', false, 'hasScope', false) - def idClass2 = graph.addVertex(T.label, 'IdentifierClass', 'name', 'idClass2', 'hasCreationFacility', false, 'hasScope', false) - def scopedIdClass = graph.addVertex(T.label, 'IdentifierClass', 'name', 'testIdClass', 'hasCreationFacility', false, 'hasScope', true) - def facilityIdClass = graph.addVertex(T.label, 'IdentifierClass', 'name', 'testFacilityClass', 'hasCreationFacility', true, 'hasScope', false) - - def scope1 = graph.addVertex(T.label, 'IdentifierScope', "name", "scope1") - def scope2 = graph.addVertex(T.label, 'IdentifierScope', "name", "scope2") - - def facility1 = graph.addVertex(T.label, 'IdentifierFacility', "name", "facility1") - def facility2 = graph.addVertex(T.label, 'IdentifierFacility', "name", "facility2") - - def id1Class1 = graph.addVertex(T.label, 'Identifier', 'value', 'id1') - id1Class1.addEdge('is_instance_of', idClass1) - - def id2Class1 = graph.addVertex(T.label, 'Identifier', 'value', 'id2') - id2Class1.addEdge('is_instance_of', idClass1) - - def id1Class2 = graph.addVertex(T.label, 'Identifier', 'value', 'id1') - id1Class2.addEdge('is_instance_of', idClass2) - - // scope - def scopedId1Scope1 = graph.addVertex(T.label, 'Identifier', 'value', 'scopedId1') - scopedId1Scope1.addEdge('is_instance_of', scopedIdClass) - scopedId1Scope1.addEdge('is_scoped_by', scope1) - - def scopedId1Scope2 = graph.addVertex(T.label, 'Identifier', 'value', 'scopedId1') - scopedId1Scope2.addEdge('is_instance_of', scopedIdClass) - scopedId1Scope2.addEdge('is_scoped_by', scope2) - - - // facility - def facilityId1Facility1 = graph.addVertex(T.label, 'Identifier', 'value', 'facilityId1') - facilityId1Facility1.addEdge('is_instance_of', facilityIdClass) - facilityId1Facility1.addEdge('was_created_by', facility1) - - def facilityId1Facility2 = graph.addVertex(T.label, 'Identifier', 'value', 'facilityId1') - facilityId1Facility2.addEdge('is_instance_of', facilityIdClass) - facilityId1Facility2.addEdge('was_created_by', facility2) - - - - expect: - carnival.checkConstraints().size() == 0 - - // duplicate id val of the same class - when: - def id1Class1Dupe = graph.addVertex(T.label, 'Identifier', 'value', 'id1') - id1Class1Dupe.addEdge('is_instance_of', idClass1) - - then: - carnival.checkConstraints().size() == 1 - - // reset - when: - id1Class1Dupe.remove() - - then: - carnival.checkConstraints().size() == 0 - - - //duplicate id val of the same class, same scope - when: - def scopedId1Scope1Dupe = graph.addVertex(T.label, 'Identifier', 'value', 'scopedId1') - scopedId1Scope1Dupe.addEdge('is_instance_of', scopedIdClass) - scopedId1Scope1Dupe.addEdge('is_scoped_by', scope1) - - - then: - carnival.checkConstraints().size() == 1 - - // reset - when: - scopedId1Scope1Dupe.remove() - - then: - carnival.checkConstraints().size() == 0 - - - //duplicate id val of the same class, same facility - when: - def facilityId1Facility1Dupe = graph.addVertex(T.label, 'Identifier', 'value', 'facilityId1') - facilityId1Facility1Dupe.addEdge('is_instance_of', facilityIdClass) - facilityId1Facility1Dupe.addEdge('was_created_by', facility1) - - then: - carnival.checkConstraints().size() == 1 - - } - -} - diff --git a/app/carnival-core/src/test/resources/logback-test.xml b/app/carnival-core/src/test/resources/logback-test.xml new file mode 100644 index 0000000..b968e20 --- /dev/null +++ b/app/carnival-core/src/test/resources/logback-test.xml @@ -0,0 +1,66 @@ + + + + + + + + %cyan(%d{HH:mm:ss.SSS}) %green([%thread]) %highlight(%-5level) %magenta(%logger{36}) %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/carnival-graph/src/main/groovy/carnival/graph/Definition.groovy b/app/carnival-graph/src/main/groovy/carnival/graph/Definition.groovy index 19e7e4d..9b26a0b 100644 --- a/app/carnival-graph/src/main/groovy/carnival/graph/Definition.groovy +++ b/app/carnival-graph/src/main/groovy/carnival/graph/Definition.groovy @@ -23,6 +23,33 @@ import carnival.graph.Base @Slf4j class Definition { + /** */ + static public String splitCapitalize(String source, String separator) { + assert source != null + + String[] chunks = source.split(separator) + + StringBuffer str = new StringBuffer() + chunks.each { str.append(it.toLowerCase().capitalize()) } + str.toString() + } + + + /** */ + static private parseLabel(String source) { + assert source != null + + String label + def result = (source =~ /^([a-zA-Z0-9]+)0[A-Z]/).findAll() + //log.debug "result: ${result}" + if (result) { + label = result.first().last() + } + + return label + } + + /** * Look up the element definition of the provided element. If the element * is a vertex, a VertexDefinition will be returned; if the element is an @@ -34,12 +61,13 @@ class Definition { assert v != null assert (v instanceof Edge || v instanceof Vertex) - String label = v.label() - //log.trace "label: ${label}" + String label = parseLabel(v.label()) + //log.debug "label: '${label}'" + if (label == null) return null def defName if (v instanceof Vertex) defName = StringUtils.toScreamingSnakeCase(label) - else if (v instanceof Edge) defName = label.toUpperCase() + else if (v instanceof Edge) defName = StringUtils.toScreamingSnakeCase(label) //label.toUpperCase() //log.trace "defName: $defName" def defClassName diff --git a/app/carnival-graph/src/main/groovy/carnival/graph/EdgeDefinition.groovy b/app/carnival-graph/src/main/groovy/carnival/graph/EdgeDefinition.groovy index f5055f4..2c6e0fd 100644 --- a/app/carnival-graph/src/main/groovy/carnival/graph/EdgeDefinition.groovy +++ b/app/carnival-graph/src/main/groovy/carnival/graph/EdgeDefinition.groovy @@ -28,6 +28,14 @@ import org.apache.tinkerpop.gremlin.structure.Edge @Slf4j trait EdgeDefinition extends ElementDefinition { + ////////////////////////////////////////////////////////////////////////// + // STATIC + /////////////////////////////////////////////////////////////////////////// + + static enum Multiplicity { + MULTI, SIMPLE, MANY2ONE, ONE2MANY, ONE2ONE + } + /////////////////////////////////////////////////////////////////////////// // FIELDS @@ -39,18 +47,27 @@ trait EdgeDefinition extends ElementDefinition { /** The set of permitted vertex definitions for to vertices */ List range = new ArrayList() + /** */ + Multiplicity multiplicity = Multiplicity.MULTI + /////////////////////////////////////////////////////////////////////////// // GETTERS / SETTERS /////////////////////////////////////////////////////////////////////////// - /** Getter wrapper for propertyDefs */ - List getEdgeProperties() { propertyDefs } + /** + * Getter wrapper for propertyDefs + * @return A set of property definitions + */ + Set getEdgeProperties() { this.propertyDefs } - /** Setter wrapper for propertyDefs */ - void setEdgeProperties(ArrayList propertyDefs) { + /** + * Setter wrapper for propertyDefs + * @param A set of proeprty definitions + */ + void setEdgeProperties(Set propertyDefs) { assert propertyDefs != null - propertyDefs = propertyDefs + this.propertyDefs = propertyDefs } @@ -62,9 +79,9 @@ trait EdgeDefinition extends ElementDefinition { * Return the label to use for instantiated edges. * @return The label as a string */ - public String getLabel() { + /*public String getLabel() { name().toLowerCase() - } + }*/ /////////////////////////////////////////////////////////////////////////// @@ -150,6 +167,13 @@ trait EdgeDefinition extends ElementDefinition { */ public void assertRange(VertexDefinition toDef) { assert toDef != null + + /*println "--- toDef: ${toDef} ${toDef.label}" + println "--- range: ${this.range}" + range.each { + println "${it}: ${it.label}" + }*/ + if (this.range.size() > 0) { if (this.range.contains(toDef)) return if (toDef.isGlobal() && this.range.find({ it.label == toDef.label })) return diff --git a/app/carnival-graph/src/main/groovy/carnival/graph/ElementDefinition.groovy b/app/carnival-graph/src/main/groovy/carnival/graph/ElementDefinition.groovy index 98e5cd4..b6fb1d3 100644 --- a/app/carnival-graph/src/main/groovy/carnival/graph/ElementDefinition.groovy +++ b/app/carnival-graph/src/main/groovy/carnival/graph/ElementDefinition.groovy @@ -28,6 +28,14 @@ import carnival.graph.Base @Slf4j trait ElementDefinition extends WithPropertyDefsTrait { + /////////////////////////////////////////////////////////////////////////// + // STATIC + /////////////////////////////////////////////////////////////////////////// + + /** The default separator for components of a name */ + public static final String NAME_SEPARATOR = '_' + + /////////////////////////////////////////////////////////////////////////// // GLOBAL /////////////////////////////////////////////////////////////////////////// @@ -65,12 +73,56 @@ trait ElementDefinition extends WithPropertyDefsTrait { /////////////////////////////////////////////////////////////////////////// /** - * If false, verticies created by this definition can contain properties - * that were not defined by this VertexDefinition. + * If false, elements created by this definition can contain properties + * that were not defined by this ElementDefinition. */ Boolean propertiesMustBeDefined = true + /////////////////////////////////////////////////////////////////////////// + // LABEL + /////////////////////////////////////////////////////////////////////////// + + /** + * Return the string label to use for instantiated vertices. + * @return The string label + */ + public String getLabel() { + + /*println "--- this: ${this}" + println "--- this.class: ${this.class}" + println "--- this instanceof Enum: ${(this instanceof Enum)}"*/ + + def thisClass + if (this instanceof Enum) { + thisClass = this.declaringClass + } else if (this instanceof DynamicVertexDef) { + thisClass = this.metaClass.theClass + } else { + thisClass = this.class + } + assert thisClass + + String classQual = String.valueOf(thisClass) + //println "--1 classQual :${classQual}" + classQual = classQual.minus('class ') + //println "--2 classQual :${classQual}" + classQual = classQual.replace('$', '.') + //println "--3 classQual :${classQual}" + classQual = Definition.splitCapitalize(classQual, '\\.') + //println "--4 classQual :${classQual}" + + String n = name() + n = Definition.splitCapitalize(n, NAME_SEPARATOR) + + StringBuffer str = new StringBuffer(n) + str.append('0') + str.append(classQual) + + return str.toString() + } + + /////////////////////////////////////////////////////////////////////////// // NAME SPACE /////////////////////////////////////////////////////////////////////////// diff --git a/app/carnival-graph/src/main/groovy/carnival/graph/PropertyDefinition.groovy b/app/carnival-graph/src/main/groovy/carnival/graph/PropertyDefinition.groovy index e099c43..8c9ba97 100644 --- a/app/carnival-graph/src/main/groovy/carnival/graph/PropertyDefinition.groovy +++ b/app/carnival-graph/src/main/groovy/carnival/graph/PropertyDefinition.groovy @@ -3,6 +3,8 @@ package carnival.graph import groovy.util.logging.Slf4j +import org.codehaus.groovy.ast.ClassNode +import org.codehaus.groovy.transform.trait.Traits import org.apache.tinkerpop.gremlin.process.traversal.Traversal import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource @@ -25,14 +27,31 @@ import org.apache.tinkerpop.gremlin.structure.Property @Slf4j trait PropertyDefinition { + /////////////////////////////////////////////////////////////////////////// + // STATIC + /////////////////////////////////////////////////////////////////////////// + + /** The default separator for components of a name */ + public static final String NAME_SEPARATOR = '_' + + /** Cardinality of the property */ + static enum Cardinality { + SINGLE, LIST, SET + } + /////////////////////////////////////////////////////////////////////////// // FIELDS /////////////////////////////////////////////////////////////////////////// + /** + * The data type of the property. + */ + Class dataType = String.class + /** * Set by #withDefault() - */ + */ Object defaultValue = null /** @@ -53,6 +72,12 @@ trait PropertyDefinition { */ Boolean index = false + /** + * The cardinality of the property. + * + */ + Cardinality cardinality = Cardinality.SINGLE + /////////////////////////////////////////////////////////////////////////// // FACTORY METHODS @@ -183,11 +208,14 @@ trait PropertyDefinition { if (allBaseDefs.contains(this)) return boolean isDefined = false - + ElementDefinition edt = Definition.lookup(el) + if (edt != null && !(edt instanceof DynamicVertexDef)) { if (edt.propertiesMustBeDefined) { - isDefined = edt.propertyDefs.find({it.label == getLabel()}) + isDefined = edt.propertyDefs.find({ + it.label == getLabel() + }) } } @@ -264,17 +292,49 @@ trait PropertyDefinition { } - /** - * Returns the property label to use for this property definition. - * @return The property label as a string - */ - public String getLabel() { + + /*public String getLabel() { def chunks = name().split('_') if (chunks.size() == 1) return chunks[0].toLowerCase() def str = chunks[0].toLowerCase() chunks.drop(1).each { str += it.toLowerCase().capitalize() } return str + }*/ + + /** + * Returns the property label to use for this property definition. + * @return The property label as a string + */ + public String getLabel() { + + def thisClass + if (this instanceof Enum) { + thisClass = this.declaringClass + } else if (this instanceof PropertyDefinitionHolder) { + def t0 = this + while (t0 instanceof PropertyDefinitionHolder) { + t0 = t0.source + } + thisClass = t0.class + } else { + thisClass = this.class + } + assert thisClass + + String classQual = String.valueOf(thisClass) + classQual = classQual.minus('class ') + classQual = classQual.replace('$', '.') + classQual = Definition.splitCapitalize(classQual, '\\.') + + String n = name() + n = Definition.splitCapitalize(n, NAME_SEPARATOR) + + StringBuffer str = new StringBuffer(n) + str.append('0') + str.append(classQual) + + return str.toString() } diff --git a/app/carnival-graph/src/main/groovy/carnival/graph/VertexDefinition.groovy b/app/carnival-graph/src/main/groovy/carnival/graph/VertexDefinition.groovy index 0c00ba4..aabf6ce 100644 --- a/app/carnival-graph/src/main/groovy/carnival/graph/VertexDefinition.groovy +++ b/app/carnival-graph/src/main/groovy/carnival/graph/VertexDefinition.groovy @@ -40,7 +40,7 @@ trait VertexDefinition extends ElementDefinition { public static final String CLASS_SUFFIX = '_class' /** The default separator for components of a name */ - public static final String NAME_SEPARATOR = '_' + //public static final String NAME_SEPARATOR = '_' @@ -137,13 +137,27 @@ trait VertexDefinition extends ElementDefinition { * Return the string label to use for instantiated vertices. * @return The string label */ - public String getLabel() { + /*public String getLabel() { + + def thisClass = this.class + if (!thisClass && (this instanceof Enum)) { + thisClass = this.declaringClass + } + + def classQual = String.valueOf(thisClass) + classQual = classQual.minus('class ') + classQual = classQual.replace('$', '_') + def n = name() def chunks = n.split(NAME_SEPARATOR) def str = "" chunks.each { str += it.toLowerCase().capitalize() } + + str += '-' + str += classQual + return str - } + }*/ /** diff --git a/app/carnival-graph/src/test/groovy/carnival/graph/DefAnnotationsSpec.groovy b/app/carnival-graph/src/test/groovy/carnival/graph/DefAnnotationsSpec.groovy index b7044ef..28901ae 100644 --- a/app/carnival-graph/src/test/groovy/carnival/graph/DefAnnotationsSpec.groovy +++ b/app/carnival-graph/src/test/groovy/carnival/graph/DefAnnotationsSpec.groovy @@ -13,6 +13,9 @@ import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSo import org.apache.tinkerpop.gremlin.structure.Vertex import org.apache.tinkerpop.gremlin.structure.Edge +import carnival.graph.EdgeDefinition.Multiplicity + + /** @@ -25,7 +28,7 @@ class DefAnnotationsSpec extends Specification { static enum VX { THING, - THING_1( + THING_A( vertexProperties:[ PX.PROP_A.withConstraints(required:true), PX.PROP_B @@ -38,11 +41,18 @@ class DefAnnotationsSpec extends Specification { static enum EX { IS_NOT( domain:[VX.THING], - range:[VX.THING_1] + range:[VX.THING_A] + ), + CONTAINS( + multiplicity: Multiplicity.ONE2MANY + ), + IS_CONTAINED_BY( + multiplicity: Multiplicity.MANY2ONE ) } + @PropertyModel static enum PX { PROP_A, @@ -94,9 +104,16 @@ class DefAnnotationsSpec extends Specification { // TESTS /////////////////////////////////////////////////////////////////////////// + def "edge multiplicity"() { + expect: + EX.CONTAINS.multiplicity == Multiplicity.ONE2MANY + EX.IS_NOT.multiplicity == Multiplicity.MULTI + } + + def "edge domain"() { when: - Vertex v1 = VX.THING_1.instance().withProperty( + Vertex v1 = VX.THING_A.instance().withProperty( PX.PROP_A, 'a' ).create(graph) @@ -109,7 +126,7 @@ class DefAnnotationsSpec extends Specification { def "vertex props"() { when: Vertex v1 = VX.THING.instance().create(graph) - Vertex v2 = VX.THING_1.instance().withProperty( + Vertex v2 = VX.THING_A.instance().withProperty( PX.PROP_A, 'a' ).create(graph) Edge e1 = EX.IS_NOT.instance().from(v1).to(v2).create() diff --git a/app/carnival-graph/src/test/groovy/carnival/graph/DefinitionSpec.groovy b/app/carnival-graph/src/test/groovy/carnival/graph/DefinitionSpec.groovy new file mode 100644 index 0000000..6b44c17 --- /dev/null +++ b/app/carnival-graph/src/test/groovy/carnival/graph/DefinitionSpec.groovy @@ -0,0 +1,48 @@ +package carnival.graph + + + +import spock.lang.Specification +import spock.lang.Unroll +import spock.lang.Shared + +import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph +import org.apache.tinkerpop.gremlin.structure.T +import org.apache.tinkerpop.gremlin.process.traversal.Traversal +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource +import org.apache.tinkerpop.gremlin.structure.Vertex +import org.apache.tinkerpop.gremlin.structure.Edge + +import carnival.graph.EdgeDefinition.Multiplicity + + + + +/** + * + */ +class DefinitionSpec extends Specification { + + + + /////////////////////////////////////////////////////////////////////////// + // TESTS + /////////////////////////////////////////////////////////////////////////// + + def "parseLabel"() { + when: + String res = Definition.parseLabel(source) + + then: + res == expected + + where: + expected | source + 'Thing' | 'Thing0CarnivalGraphVertexdefinitionspecVx' + 'SomeThing' | 'SomeThing0CarnivalGraphVertexdefinitionspecVx' + 'Thing1' | 'Thing10CarnivalGraphVertexdefinitionspecVx' + 'Some0Thing' | 'Some0Thing0CarnivalGraphVertexdefinitionspecVx' + } + +} + diff --git a/app/carnival-graph/src/test/groovy/carnival/graph/DefinitionTransformationTest.groovy b/app/carnival-graph/src/test/groovy/carnival/graph/DefinitionTransformationTest.groovy index 42c803e..3ca2b73 100644 --- a/app/carnival-graph/src/test/groovy/carnival/graph/DefinitionTransformationTest.groovy +++ b/app/carnival-graph/src/test/groovy/carnival/graph/DefinitionTransformationTest.groovy @@ -59,9 +59,9 @@ class ModelTransformationSpec extends Specification { def "vertexdef global"() { expect: TestModel.VX.THING.global == false - TestModel.VX.THING_1.global == false + TestModel.VX.THING_ONE.global == false TestModel.VXG.THING.global == true - TestModel.VXG.THING_1.global == true + TestModel.VXG.THING_ONE.global == true } diff --git a/app/carnival-graph/src/test/groovy/carnival/graph/EdgeBuilderSpec.groovy b/app/carnival-graph/src/test/groovy/carnival/graph/EdgeBuilderSpec.groovy index a9f54a9..505152b 100644 --- a/app/carnival-graph/src/test/groovy/carnival/graph/EdgeBuilderSpec.groovy +++ b/app/carnival-graph/src/test/groovy/carnival/graph/EdgeBuilderSpec.groovy @@ -22,8 +22,8 @@ class EdgeBuilderSpec extends Specification { @VertexModel static enum VX { - EBS_THING_1, - EBS_THING_2 + EBS_THING_ONE, + EBS_THING_TWO } @PropertyModel @@ -34,11 +34,11 @@ class EdgeBuilderSpec extends Specification { @EdgeModel static enum EX { - EBS_REL_1( - domain:[VX.EBS_THING_1], - range:[VX.EBS_THING_2] + EBS_REL_ONE( + domain:[VX.EBS_THING_ONE], + range:[VX.EBS_THING_TWO] ), - EBS_REL_2( + EBS_REL_TWO( propertyDefs:[ PX.EBS_PROP_A.withConstraints(required:true), PX.EBS_PROP_B.defaultValue(1).withConstraints(required:true) @@ -85,11 +85,11 @@ class EdgeBuilderSpec extends Specification { def "properties simple"() { given: Exception e - def v1 = VX.EBS_THING_1.createVertex(graph) - def v2 = VX.EBS_THING_2.createVertex(graph) + def v1 = VX.EBS_THING_ONE.createVertex(graph) + def v2 = VX.EBS_THING_TWO.createVertex(graph) when: - def e1 = EX.EBS_REL_2.instance() + def e1 = EX.EBS_REL_TWO.instance() .from(v1) .to(v2) .create() @@ -99,7 +99,7 @@ class EdgeBuilderSpec extends Specification { e instanceof RequiredPropertyException when: - def e2 = EX.EBS_REL_2.instance() + def e2 = EX.EBS_REL_TWO.instance() .withProperty(PX.EBS_PROP_A, 'a') .from(v1) .to(v2) @@ -116,18 +116,18 @@ class EdgeBuilderSpec extends Specification { given: Exception e def eb - def v1 = VX.EBS_THING_1.createVertex(graph) - def v2 = VX.EBS_THING_2.createVertex(graph) + def v1 = VX.EBS_THING_ONE.createVertex(graph) + def v2 = VX.EBS_THING_TWO.createVertex(graph) when: - eb = EX.EBS_REL_1.instance().from(v2) + eb = EX.EBS_REL_ONE.instance().from(v2) then: e = thrown() e instanceof EdgeDomainException when: - eb = EX.EBS_REL_1.instance().from(v1) + eb = EX.EBS_REL_ONE.instance().from(v1) then: noExceptionThrown() @@ -138,18 +138,18 @@ class EdgeBuilderSpec extends Specification { given: Exception e def eb - def v1 = VX.EBS_THING_1.createVertex(graph) - def v2 = VX.EBS_THING_2.createVertex(graph) + def v1 = VX.EBS_THING_ONE.createVertex(graph) + def v2 = VX.EBS_THING_TWO.createVertex(graph) when: - eb = EX.EBS_REL_1.instance().to(v1) + eb = EX.EBS_REL_ONE.instance().to(v1) then: e = thrown() e instanceof EdgeRangeException when: - eb = EX.EBS_REL_1.instance().to(v2) + eb = EX.EBS_REL_ONE.instance().to(v2) then: noExceptionThrown() @@ -159,12 +159,12 @@ class EdgeBuilderSpec extends Specification { def "create simple"() { given: Exception e - def v1 = VX.EBS_THING_1.createVertex(graph) - def v2 = VX.EBS_THING_2.createVertex(graph) + def v1 = VX.EBS_THING_ONE.createVertex(graph) + def v2 = VX.EBS_THING_TWO.createVertex(graph) when: - def eb1 = EX.EBS_REL_1.instance().from(v1).to(v2).create() - def eb2 = EX.EBS_REL_1.instance().from(v1).to(v2).create() + def eb1 = EX.EBS_REL_ONE.instance().from(v1).to(v2).create() + def eb2 = EX.EBS_REL_ONE.instance().from(v1).to(v2).create() then: noExceptionThrown() @@ -178,12 +178,12 @@ class EdgeBuilderSpec extends Specification { def "edge simple"() { given: Exception e - def v1 = VX.EBS_THING_1.createVertex(graph) - def v2 = VX.EBS_THING_2.createVertex(graph) + def v1 = VX.EBS_THING_ONE.createVertex(graph) + def v2 = VX.EBS_THING_TWO.createVertex(graph) when: - def eb1 = EX.EBS_REL_1.instance().from(v1).to(v2).ensure(g) - def eb2 = EX.EBS_REL_1.instance().from(v1).to(v2).ensure(g) + def eb1 = EX.EBS_REL_ONE.instance().from(v1).to(v2).ensure(g) + def eb2 = EX.EBS_REL_ONE.instance().from(v1).to(v2).ensure(g) then: noExceptionThrown() diff --git a/app/carnival-graph/src/test/groovy/carnival/graph/EdgeDefSpec.groovy b/app/carnival-graph/src/test/groovy/carnival/graph/EdgeDefSpec.groovy index bd39e66..d9a244b 100644 --- a/app/carnival-graph/src/test/groovy/carnival/graph/EdgeDefSpec.groovy +++ b/app/carnival-graph/src/test/groovy/carnival/graph/EdgeDefSpec.groovy @@ -22,14 +22,14 @@ class EdgeDefSpec extends Specification { @VertexModel static enum VX { THING, - THING_1 + THING_ONE } @EdgeModel static enum EX { IS_NOT( domain:[VX.THING], - range:[VX.THING_1] + range:[VX.THING_ONE] ) } @@ -74,7 +74,7 @@ class EdgeDefSpec extends Specification { def "lookup"() { when: Vertex v1 = VX.THING.instance().create(graph) - Vertex v2 = VX.THING_1.instance().create(graph) + Vertex v2 = VX.THING_ONE.instance().create(graph) Edge e = EX.IS_NOT.instance().from(v1).to(v2).create() EdgeDefinition edt = Definition.lookup(e) diff --git a/app/carnival-graph/src/test/groovy/carnival/graph/EdgeDefinitionSpec.groovy b/app/carnival-graph/src/test/groovy/carnival/graph/EdgeDefinitionSpec.groovy index f33dec5..bc04594 100644 --- a/app/carnival-graph/src/test/groovy/carnival/graph/EdgeDefinitionSpec.groovy +++ b/app/carnival-graph/src/test/groovy/carnival/graph/EdgeDefinitionSpec.groovy @@ -12,6 +12,9 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traversal import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource import org.apache.tinkerpop.gremlin.structure.Property +import carnival.graph.EdgeDefinition.Multiplicity + + /** @@ -31,13 +34,13 @@ class EdgeDefinitionSpec extends Specification { ANOTHER_THING } - static enum VX2 implements VertexDefinition { + /*static enum VX2 implements VertexDefinition { THING (global:true), ANOTHER_THING (global:true) private VX2() {} private VX2(Map m) { m.each { k,v -> this."$k" = v } } - } + }*/ static enum EX1 implements EdgeDefinition { RELATION( @@ -81,6 +84,13 @@ class EdgeDefinitionSpec extends Specification { private EX3(Map m) {m.each { k,v -> this."$k" = v } } } + static enum EX4 implements EdgeDefinition { + RELATION( + multiplicity: Multiplicity.SIMPLE, + domain: [DYNAMIC_THING] + ) + } + static enum PX implements PropertyDefinition { PROP_A, PROP_B, @@ -181,7 +191,7 @@ class EdgeDefinitionSpec extends Specification { } - def "vertex vertex enforce range with global"() { + /*def "vertex vertex enforce range with global"() { given: def v1 def v2 @@ -204,10 +214,10 @@ class EdgeDefinitionSpec extends Specification { then: t = thrown() - } + }*/ - def "vertex vertex enforce domain with global"() { + /*def "vertex vertex enforce domain with global"() { given: def v1 def v2 @@ -230,7 +240,7 @@ class EdgeDefinitionSpec extends Specification { then: t = thrown() - } + }*/ def "vertex vertex enforce domain dynamic"() { @@ -245,8 +255,8 @@ class EdgeDefinitionSpec extends Specification { v2 = VX.ANOTHER_THING.instance().vertex(graph, g) e = EX3.RELATION.setRelationship(g, v1, v2) - println "v1: ${v1} ${v1.label()} ${v1.value('nameSpace')}" - println "v2: ${v2} ${v2.label()} ${v2.value('nameSpace')}" + //println "v1: ${v1} ${v1.label()} ${v1.value('nameSpace')}" + //println "v2: ${v2} ${v2.label()} ${v2.value('nameSpace')}" then: noExceptionThrown() @@ -271,8 +281,8 @@ class EdgeDefinitionSpec extends Specification { v2 = VX.ANOTHER_THING.instance().vertex(graph, g) e = EX2.RELATION.setRelationship(g, v1, v2) - println "v1: ${v1} ${v1.label()} ${v1.value('nameSpace')}" - println "v2: ${v2} ${v2.label()} ${v2.value('nameSpace')}" + //println "v1: ${v1} ${v1.label()} ${v1.value('nameSpace')}" + //println "v2: ${v2} ${v2.label()} ${v2.value('nameSpace')}" then: noExceptionThrown() @@ -297,8 +307,8 @@ class EdgeDefinitionSpec extends Specification { v2 = VX.ANOTHER_THING.instance().vertex(graph, g) e = EX2.RELATION.setRelationship(g, v1, v2) - println "v1: ${v1} ${v1.label()} ${v1.value('nameSpace')}" - println "v2: ${v2} ${v2.label()} ${v2.value('nameSpace')}" + //println "v1: ${v1} ${v1.label()} ${v1.value('nameSpace')}" + //println "v2: ${v2} ${v2.label()} ${v2.value('nameSpace')}" then: noExceptionThrown() diff --git a/app/carnival-graph/src/test/groovy/carnival/graph/PropertyDefinitionSpec.groovy b/app/carnival-graph/src/test/groovy/carnival/graph/PropertyDefinitionSpec.groovy index 7fa19db..6f747d2 100644 --- a/app/carnival-graph/src/test/groovy/carnival/graph/PropertyDefinitionSpec.groovy +++ b/app/carnival-graph/src/test/groovy/carnival/graph/PropertyDefinitionSpec.groovy @@ -10,6 +10,8 @@ import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph import org.apache.tinkerpop.gremlin.structure.T import org.apache.tinkerpop.gremlin.process.traversal.Traversal import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource +import org.apache.tinkerpop.gremlin.structure.Vertex +import org.apache.tinkerpop.gremlin.structure.VertexProperty @@ -22,20 +24,23 @@ class PropertyDefinitionSpec extends Specification { @PropertyModel static enum PX { PROP_A, - PROP_B + PROP_B, + PROP_C( + cardinality: PropertyDefinition.Cardinality.LIST + ) } @VertexModel static enum VX { - THING_1, + THING_ONE, - THING_2( + THING_TWO( vertexProperties:[ PX.PROP_A ] ), - THING_3( + THING_THREE( vertexProperties:[ PX.PROP_A, PX.PROP_B @@ -79,9 +84,37 @@ class PropertyDefinitionSpec extends Specification { // TESTS /////////////////////////////////////////////////////////////////////////// + def "cardinality"() { + expect: + PX.PROP_A.cardinality == PropertyDefinition.Cardinality.SINGLE + PX.PROP_B.cardinality == PropertyDefinition.Cardinality.SINGLE + PX.PROP_C.cardinality == PropertyDefinition.Cardinality.LIST + } + + + def "label format"() { + expect: + PX.PROP_A.label == 'PropA0CarnivalGraphPropertydefinitionspecPx' + + when: + Vertex v1 = VX.THING_TWO.instance().withProperties( + PX.PROP_A, 'prop-a-val-1', + ).create(graph) + + List props = v1.properties().toList() + //println "props: ${props}" + VertexProperty theProp = props.find({ + it.label() == 'PropA0CarnivalGraphPropertydefinitionspecPx' + }) + + then: + theProp + } + + def "base properties are defined"() { when: - def v1 = VX.THING_2.instance().create(graph) + def v1 = VX.THING_TWO.instance().create(graph) pm.valueOf(v1) then: @@ -94,7 +127,7 @@ class PropertyDefinitionSpec extends Specification { def "valueOf throws an exception for undefined property"() { when: - def v1 = VX.THING_2.instance().create(graph) + def v1 = VX.THING_TWO.instance().create(graph) PX.PROP_B.valueOf(v1) then: @@ -104,7 +137,7 @@ class PropertyDefinitionSpec extends Specification { def "valueOf returns null if property not present"() { when: - def v1 = VX.THING_2.instance().create(graph) + def v1 = VX.THING_TWO.instance().create(graph) then: !PX.PROP_A.of(v1).isPresent() @@ -114,7 +147,7 @@ class PropertyDefinitionSpec extends Specification { def "valueOf returns property value if present"() { when: - def v1 = VX.THING_2.instance().withProperty(PX.PROP_A, 'a').create(graph) + def v1 = VX.THING_TWO.instance().withProperty(PX.PROP_A, 'a').create(graph) then: PX.PROP_A.valueOf(v1) == 'a' @@ -123,7 +156,7 @@ class PropertyDefinitionSpec extends Specification { def "set closure result"() { when: - def v1 = VX.THING_2.instance().create(graph) + def v1 = VX.THING_TWO.instance().create(graph) then: !PX.PROP_A.of(v1).isPresent() @@ -153,7 +186,7 @@ class PropertyDefinitionSpec extends Specification { def "setIf closure result"() { when: - def v1 = VX.THING_2.instance().create(graph) + def v1 = VX.THING_TWO.instance().create(graph) then: !PX.PROP_A.of(v1).isPresent() @@ -181,7 +214,7 @@ class PropertyDefinitionSpec extends Specification { Exception e when: - def v1 = VX.THING_1.instance().create(graph) + def v1 = VX.THING_ONE.instance().create(graph) PX.PROP_A.set(v1, 'a') then: @@ -189,7 +222,7 @@ class PropertyDefinitionSpec extends Specification { e instanceof IllegalArgumentException when: - def v2 = VX.THING_2.instance().create(graph) + def v2 = VX.THING_TWO.instance().create(graph) PX.PROP_A.set(v2, 'a') then: diff --git a/app/carnival-graph/src/test/groovy/carnival/graph/TestModel.groovy b/app/carnival-graph/src/test/groovy/carnival/graph/TestModel.groovy index 6a72884..c01a6b5 100644 --- a/app/carnival-graph/src/test/groovy/carnival/graph/TestModel.groovy +++ b/app/carnival-graph/src/test/groovy/carnival/graph/TestModel.groovy @@ -13,7 +13,7 @@ class TestModel { @VertexModel(global="true") static enum VXG { THING, - THING_1( + THING_ONE( vertexProperties:[ PX.PROP_A.withConstraints(required:true), PX.PROP_B @@ -26,7 +26,7 @@ class TestModel { static enum VX { THING, - THING_1( + THING_ONE( vertexProperties:[ PX.PROP_A.withConstraints(required:true), PX.PROP_B @@ -39,7 +39,7 @@ class TestModel { static enum EX { IS_NOT( domain:[VX.THING], - range:[VX.THING_1] + range:[VX.THING_ONE] ) } diff --git a/app/carnival-graph/src/test/groovy/carnival/graph/VertexDefTraitSpec.groovy b/app/carnival-graph/src/test/groovy/carnival/graph/VertexDefTraitSpec.groovy deleted file mode 100644 index f5d9e5c..0000000 --- a/app/carnival-graph/src/test/groovy/carnival/graph/VertexDefTraitSpec.groovy +++ /dev/null @@ -1,350 +0,0 @@ -package carnival.graph - - - -import spock.lang.Specification -import spock.lang.Unroll -import spock.lang.Shared - -import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph -import org.apache.tinkerpop.gremlin.structure.T -import org.apache.tinkerpop.gremlin.process.traversal.Traversal -import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource -import org.apache.tinkerpop.gremlin.structure.VertexProperty - - - -/** - * gradle test --tests "carnival.graph.VertexDefinitionSpec" - * - */ -class VertexDefinitionSpec extends Specification { - - static enum VX implements VertexDefinition { - THING, - - THING_1( - vertexProperties:[ - PX.PROP_A.withConstraints(required:true) - ] - ), - - THING_2( - vertexProperties:[ - PX.PROP_A - ] - ), - - THING_3( - vertexProperties:[ - PX.PROP_A.defaultValue(1).withConstraints(required:true) - ] - ), - - THING_4( - vertexProperties:[ - PX.PROP_A.withConstraints(required:true), - PX.PROP_B - ] - ), - - THING_5(propertiesMustBeDefined:false), - - A_CLASS, - B_CLASS ( - superClass: VX.A_CLASS - ), - B ( - instanceOf: VX.B_CLASS - ), - - CLASS_OF_SOMETHING ( - isClass: true - ), - - NOT_A_CLASS ( - isClass: false - ) - - private VX() {} - private VX(Map m) {m.each { k,v -> this."$k" = v }} - } - - - static enum PX implements PropertyDefinition { - PROP_A, - PROP_B - } - - - - /////////////////////////////////////////////////////////////////////////// - // FIELDS - /////////////////////////////////////////////////////////////////////////// - - @Shared graph - @Shared g - - - - /////////////////////////////////////////////////////////////////////////// - // SET UP - /////////////////////////////////////////////////////////////////////////// - - - def setupSpec() { - } - - def setup() { - graph = TinkerGraph.open() - g = graph.traversal() - } - - def cleanup() { - if (g) g.close() - if (graph) graph.close() - } - - def cleanupSpec() { - } - - - - /////////////////////////////////////////////////////////////////////////// - // TESTS - /////////////////////////////////////////////////////////////////////////// - - def "instanceOf edge is automatically created"() { - setup: - // these have to be explicitly set as we are not using Carnival, just - // creating a TinkerGraph and testing VertexDefinition in isolation - if (!VX.A_CLASS.vertex) VX.A_CLASS.vertex = VX.A_CLASS.instance().create(graph) - if (!VX.B_CLASS.vertex) VX.B_CLASS.vertex = VX.B_CLASS.instance().create(graph) - - when: - def b = VX.B.instance().create(graph) - - then: - b != null - g.V(b) - .out(Base.EX.IS_INSTANCE_OF) - .is(VX.B_CLASS.vertex) - .tryNext().isPresent() - } - - - class Something implements VertexDefinition { - String name - String name() { - this.name - } - } - - def "cannot set superclass unless is a class"() { - when: - def s = new Something(name:"a") - - then: - !s.isClass() - - when: - s.superClass = VX.THING - - then: - Exception e = thrown() - } - - - def "cannot set instanceof of class"() { - when: - def s = new Something(name:"a_class") - - then: - s.isClass() - - when: - s.instanceOf = VX.THING - - then: - Exception e = thrown() - - } - - - def "explicit isClass"() { - expect: - VX.CLASS_OF_SOMETHING.isClass() - !VX.NOT_A_CLASS.isClass() - VX.A_CLASS.isClass - VX.A_CLASS.isClass() - } - - - def "can add undefined props on switch"() { - when: - def v1 = VX.THING_5.instance().withProperty(PX.PROP_A, 'a').create(graph) - - then: - noExceptionThrown() - } - - - def "properties must be defined by default"() { - when: - def v1 = VX.THING.instance().withProperty(PX.PROP_A, 'a').create(graph) - - then: - Exception e = thrown() - e instanceof IllegalArgumentException - //e.printStackTrace() - } - - - def "two defined properties"() { - when: - def v1 = VX.THING_4.instance().withProperties( - PX.PROP_A, 'a', - PX.PROP_B, 'b' - ).create(graph) - v1.property('someOtherProp', 'qq') - def dps1 = VX.THING_4.definedPropertiesOf(v1) - println "dps1: $dps1" - - then: - dps1 != null - dps1.size() == 2 - dps1.find { it.label == PX.PROP_A.label } - dps1.find { it.label == PX.PROP_B.label } - - when: - def dp1 = dps1.find { it.label == PX.PROP_A.label } - - then: - dp1.label() == PX.PROP_A.label - dp1.value() == 'a' - - when: - def dp2 = dps1.find { it.label == PX.PROP_B.label } - - then: - dp2.label() == PX.PROP_B.label - dp2.value() == 'b' - } - - - def "one defined property"() { - when: - def v1 = VX.THING_4.instance().withProperty(PX.PROP_A, 'a').create(graph) - v1.property('someOtherProp', 'qq') - def dps1 = VX.THING_4.definedPropertiesOf(v1) - println "dps1: $dps1" - - then: - dps1 != null - dps1.size() == 1 - - when: - def dp1 = dps1.first() - - then: - dp1 instanceof VertexProperty - dp1.label() == PX.PROP_A.label - dp1.value() == 'a' - } - - - def "controlled instance vertex enum"() { - given: - def v - - when: - v = VX.THING.instance().vertex(graph, g) - - then: - v - !v.property(Base.PX.IS_CLASS.label).isPresent() - v.property(Base.PX.NAME_SPACE.label).isPresent() - v.value(Base.PX.NAME_SPACE.label) == 'carnival.graph.VertexDefinitionSpec$VX' - } - - - def "controlled instance vertex object"() { - given: - def v - def vDef - - when: - vDef = new DynamicVertexDef('THING_2') - - then: - vDef instanceof DynamicVertexDef - vDef.getNameSpace() == 'carnival.graph.DynamicVertexDef' - - when: - vDef.nameSpace = 'some.custom.NameSpace' - - then: - vDef.nameSpace == 'some.custom.NameSpace' - - when: - v = vDef.instance().vertex(graph, g) - - then: - v - !v.property(Base.PX.IS_CLASS.label).isPresent() - v.property(Base.PX.NAME_SPACE.label).isPresent() - v.value(Base.PX.NAME_SPACE.label) == 'some.custom.NameSpace' - } - - - def "createVertex enum"() { - given: - def v - - expect: - VX.THING.getNameSpace() == 'carnival.graph.VertexDefinitionSpec$VX' - - when: - v = VX.THING.instance().vertex(graph, g) - - then: - v - !v.property(Base.PX.IS_CLASS.label).isPresent() - v.property(Base.PX.NAME_SPACE.label).isPresent() - v.value(Base.PX.NAME_SPACE.label) == 'carnival.graph.VertexDefinitionSpec$VX' - } - - - - def "createVertex object"() { - given: - def v - def vDef - - when: - vDef = new DynamicVertexDef('THING_2') - - then: - vDef instanceof DynamicVertexDef - vDef.getNameSpace() == 'carnival.graph.DynamicVertexDef' - - when: - vDef.nameSpace = 'some.custom.NameSpace' - - then: - vDef.nameSpace == 'some.custom.NameSpace' - - when: - v = vDef.instance().vertex(graph, g) - - then: - v - !v.property(Base.PX.IS_CLASS.label).isPresent() - v.property(Base.PX.NAME_SPACE.label).isPresent() - v.value(Base.PX.NAME_SPACE.label) == 'some.custom.NameSpace' - } - - - -} - diff --git a/app/carnival-graph/src/test/groovy/carnival/graph/VertexDefinitionSpec.groovy b/app/carnival-graph/src/test/groovy/carnival/graph/VertexDefinitionSpec.groovy index ff87fd3..34e9314 100644 --- a/app/carnival-graph/src/test/groovy/carnival/graph/VertexDefinitionSpec.groovy +++ b/app/carnival-graph/src/test/groovy/carnival/graph/VertexDefinitionSpec.groovy @@ -25,7 +25,7 @@ class VertexDefinitionSpec extends Specification { static enum VX implements VertexDefinition { THING, - THING_1( + THING_ONE( vertexProperties:[ PX.PROP_A.withConstraints(required:true) ] @@ -116,6 +116,19 @@ class VertexDefinitionSpec extends Specification { // TESTS /////////////////////////////////////////////////////////////////////////// + def "label format"() { + expect: + VX.THING.label == 'Thing0CarnivalGraphVertexdefinitionspecVx' + + when: + def v1 = VX.THING.instance().create(graph) + def lbl = v1.label + + then: + lbl == 'Thing0CarnivalGraphVertexdefinitionspecVx' + } + + def "instanceOf edge is automatically created"() { setup: // these have to be explicitly set as we are not using Carnival, just diff --git a/app/carnival-graph/src/test/groovy/carnival/graph/VertexModelSpec.groovy b/app/carnival-graph/src/test/groovy/carnival/graph/VertexModelSpec.groovy index db0eda7..31b4423 100644 --- a/app/carnival-graph/src/test/groovy/carnival/graph/VertexModelSpec.groovy +++ b/app/carnival-graph/src/test/groovy/carnival/graph/VertexModelSpec.groovy @@ -23,7 +23,7 @@ class VertexModelSpec extends Specification { @VertexModel static enum VX { - THING_1(PX) + THING_ONE(PX) /*VX(Class aClass) { println "aClass: ${aClass}" @@ -57,7 +57,7 @@ class VertexModelSpec extends Specification { static enum EX { IS_NOT( domain:[VX.THING], - range:[VX.THING_1] + range:[VX.THING_ONE] ) }*/ @@ -111,8 +111,8 @@ class VertexModelSpec extends Specification { def "vertex props"() { expect: - VX.THING_1.propertyDefs != null - VX.THING_1.propertyDefs.size() == 3 + VX.THING_ONE.propertyDefs != null + VX.THING_ONE.propertyDefs.size() == 3 } } diff --git a/app/gradle.properties b/app/gradle.properties index a3aa6d9..fa8c104 100644 --- a/app/gradle.properties +++ b/app/gradle.properties @@ -11,7 +11,7 @@ gremlinVersion=3.7.2 javaVersion=21 # carnival carnivalGroup=io.github.carnival-data -carnivalVersion=5.0.1-SNAPSHOT +carnivalVersion=5.0.2-SNAPSHOT #When using Docker, these values are set for docker-compose in ../.env #If not using Docker, set these values in another gradle.properties in your home directory diff --git a/docs/groovy/graph-model-1.groovy b/docs/groovy/graph-model-1.groovy index 93bbc03..1ecea90 100644 --- a/docs/groovy/graph-model-1.groovy +++ b/docs/groovy/graph-model-1.groovy @@ -2,9 +2,9 @@ // DEPENDENCIES /////////////////////////////////////////////////////////////////////////////// -//@Grab('io.github.carnival-data:carnival-core:3.0.2-SNAPSHOT') -@Grab('io.github.carnival-data:carnival-core:3.0.1') -@Grab('org.apache.tinkerpop:gremlin-core:3.4.10') +@Grab('io.github.carnival-data:carnival-core:5.0.2-SNAPSHOT') +//@Grab('io.github.carnival-data:carnival-core:3.0.1') +@Grab('org.apache.tinkerpop:gremlin-core:3.7.2') /////////////////////////////////////////////////////////////////////////////// @@ -37,20 +37,19 @@ model: ${cg.checkModel()} """ //System.console().readLine 'Return to continue' -graph.addVertex('Thing') -println """\ -We have added a vertex with the unmodeled label 'Thing' -model: ${cg.checkModel()} -""" -@VertexModel(global="true") +// +// Vertex model definition +// +@VertexModel enum VX1 { THING } + Vertex thing1V = VX1.THING.instance().create(graph) println """\ -We have created a model for 'Thing', but not yet incorporated it into the -graph model. So, we will still see a model error. +We have added a vertex with the type VX.THING, but not yet incorporated the +model into the Carnival, so there will be model errors. model: ${cg.checkModel()} """ @@ -66,23 +65,14 @@ enum VX2 { THING, ANOTHER_THING } +cg.addModel(VX2) Vertex thing2V = VX2.THING.instance().create(graph) VX2.ANOTHER_THING.instance().create(graph) println """\ -We have created a new model, which includes 'Thing', but is not global. We can -have both global and non-global models. However, global models supercede non- -global models. Here we see that the VX2.THING that we create in the graph does -not cause a model error, due to the global model. However, VX2.ANOTHER_THING -does cause an error. +There are no model errors for these new modeled vertices. model: ${cg.checkModel()} """ -cg.addModel(VX2) -println """\ -Now that we have added the VX2 model to our graph model, there are no model -errors. -model: ${cg.checkModel()} -""" @EdgeModel enum EX1 {