From bef05d6c0066006a190b75d213bc251c907d9b95 Mon Sep 17 00:00:00 2001 From: Stefan Plantikow Date: Wed, 18 Feb 2015 23:04:28 +0100 Subject: [PATCH] Add DbStructureLogicalPlanningConfiguration --- .../internal/compiler/v2_2/NameId.scala | 7 + .../compiler/v2_2/spi/GraphStatistics.scala | 45 ++- .../planner/DbStructureGraphStatistics.scala | 46 +++ ...tructureLogicalPlanningConfiguration.scala | 75 ++++ .../LogicalPlanningConfiguration.scala | 82 +--- .../planner/LogicalPlanningTestSupport2.scala | 11 +- .../RealLogicalPlanningConfiguration.scala | 52 +++ .../StubbedLogicalPlanningConfiguration.scala | 76 ++++ .../compatibility/CompatibilityFor2_2.scala | 2 +- .../TransactionBoundGraphStatistics.scala | 64 ++- .../v2_2/TransactionBoundPlanContext.scala | 2 +- .../dbstructure/DbStructureCollector.java | 370 ++++++++++++++++++ .../util/dbstructure/DbStructureLookup.java | 39 ++ .../util/dbstructure/DbStructureTool.java | 2 +- ...eGuide.java => GraphDbStructureGuide.java} | 13 +- .../dbstructure/DbStructureCollectorTest.java | 74 ++++ ...st.java => GraphDbStructureGuideTest.java} | 4 +- 17 files changed, 824 insertions(+), 140 deletions(-) create mode 100644 community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/DbStructureGraphStatistics.scala create mode 100644 community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/DbStructureLogicalPlanningConfiguration.scala create mode 100644 community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/RealLogicalPlanningConfiguration.scala create mode 100644 community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/StubbedLogicalPlanningConfiguration.scala create mode 100644 community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureCollector.java create mode 100644 community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureLookup.java rename community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/{DbStructureGuide.java => GraphDbStructureGuide.java} (97%) create mode 100644 community/kernel/src/test/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureCollectorTest.java rename community/kernel/src/test/java/org/neo4j/kernel/impl/util/dbstructure/{DbStructureGuideTest.java => GraphDbStructureGuideTest.java} (98%) diff --git a/community/cypher/cypher-compiler-2.2/src/main/scala/org/neo4j/cypher/internal/compiler/v2_2/NameId.scala b/community/cypher/cypher-compiler-2.2/src/main/scala/org/neo4j/cypher/internal/compiler/v2_2/NameId.scala index 23c05a8c8ba16..c03f6ce7a9a93 100644 --- a/community/cypher/cypher-compiler-2.2/src/main/scala/org/neo4j/cypher/internal/compiler/v2_2/NameId.scala +++ b/community/cypher/cypher-compiler-2.2/src/main/scala/org/neo4j/cypher/internal/compiler/v2_2/NameId.scala @@ -26,3 +26,10 @@ sealed trait NameId { final case class LabelId(id: Int) extends NameId final case class RelTypeId(id: Int) extends NameId final case class PropertyKeyId(id: Int) extends NameId + +object NameId { + val WILDCARD: Int = -1 + + implicit def toKernelEncode(nameId: NameId): Int = nameId.id + implicit def toKernelEncode(nameId: Option[NameId]): Int = nameId.map(toKernelEncode).getOrElse(WILDCARD) +} diff --git a/community/cypher/cypher-compiler-2.2/src/main/scala/org/neo4j/cypher/internal/compiler/v2_2/spi/GraphStatistics.scala b/community/cypher/cypher-compiler-2.2/src/main/scala/org/neo4j/cypher/internal/compiler/v2_2/spi/GraphStatistics.scala index 3247f126ecf82..2ba4bf3b12913 100644 --- a/community/cypher/cypher-compiler-2.2/src/main/scala/org/neo4j/cypher/internal/compiler/v2_2/spi/GraphStatistics.scala +++ b/community/cypher/cypher-compiler-2.2/src/main/scala/org/neo4j/cypher/internal/compiler/v2_2/spi/GraphStatistics.scala @@ -22,6 +22,14 @@ package org.neo4j.cypher.internal.compiler.v2_2.spi import org.neo4j.cypher.internal.compiler.v2_2.planner.logical.{Cardinality, Selectivity} import org.neo4j.cypher.internal.compiler.v2_2.{LabelId, PropertyKeyId, RelTypeId} +object GraphStatistics { + val DEFAULT_RANGE_SELECTIVITY = Selectivity(0.3) + val DEFAULT_PREDICATE_SELECTIVITY = Selectivity(0.75) + val DEFAULT_EQUALITY_SELECTIVITY = Selectivity(0.1) + val DEFAULT_NUMBER_OF_ID_LOOKUPS = Cardinality(25) + val DEFAULT_REL_UNIQUENESS_SELECTIVITY = Selectivity(1.0 - 1 / 100 /*rel-cardinality*/) +} + trait GraphStatistics { def nodesWithLabelCardinality(labelId: Option[LabelId]): Cardinality @@ -35,10 +43,35 @@ trait GraphStatistics { def indexSelectivity(label: LabelId, property: PropertyKeyId): Option[Selectivity] } -object GraphStatistics { - val DEFAULT_RANGE_SELECTIVITY = Selectivity(0.3) - val DEFAULT_PREDICATE_SELECTIVITY = Selectivity(0.75) - val DEFAULT_EQUALITY_SELECTIVITY = Selectivity(0.1) - val DEFAULT_NUMBER_OF_ID_LOOKUPS = Cardinality(25) - val DEFAULT_REL_UNIQUENESS_SELECTIVITY = Selectivity(1.0 - 1 / 100 /*rel-cardinality*/) +class DelegatingGraphStatistics(delegate: GraphStatistics) extends GraphStatistics { + override def nodesWithLabelCardinality(labelId: Option[LabelId]): Cardinality = + delegate.nodesWithLabelCardinality(labelId) + + override def cardinalityByLabelsAndRelationshipType(fromLabel: Option[LabelId], relTypeId: Option[RelTypeId], toLabel: Option[LabelId]): Cardinality = + delegate.cardinalityByLabelsAndRelationshipType(fromLabel, relTypeId, toLabel) + + /* + Probability of any node with the given label, to have a property with a given value + + indexSelectivity(:X, prop) = s => |MATCH (a:X)| * s = |MATCH (a:X) WHERE x.prop = *| + */ + override def indexSelectivity(label: LabelId, property: PropertyKeyId): Option[Selectivity] = + delegate.indexSelectivity(label, property) +} + +class StatisticsCompletingGraphStatistics(delegate: GraphStatistics) + extends DelegatingGraphStatistics(delegate) { + + override def cardinalityByLabelsAndRelationshipType(fromLabel: Option[LabelId], relTypeId: Option[RelTypeId], toLabel: Option[LabelId]): Cardinality = + (fromLabel, toLabel) match { + case (Some(_), Some(_)) => + // TODO: read real counts from readOperations when they are gonna be properly computed and updated + Cardinality.min( + super.cardinalityByLabelsAndRelationshipType(fromLabel, relTypeId, None), + super.cardinalityByLabelsAndRelationshipType(None, relTypeId, toLabel) + ) + case _ => + super.cardinalityByLabelsAndRelationshipType(fromLabel, relTypeId, toLabel) + } } + diff --git a/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/DbStructureGraphStatistics.scala b/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/DbStructureGraphStatistics.scala new file mode 100644 index 0000000000000..b4dc38762f366 --- /dev/null +++ b/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/DbStructureGraphStatistics.scala @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2002-2015 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.cypher.internal.compiler.v2_2.planner + +import org.neo4j.cypher.internal.compiler.v2_2.{NameId, PropertyKeyId, RelTypeId, LabelId} +import org.neo4j.cypher.internal.compiler.v2_2.planner.logical.{Selectivity, Cardinality} +import org.neo4j.cypher.internal.compiler.v2_2.spi.GraphStatistics +import org.neo4j.kernel.impl.util.dbstructure.DbStructureLookup + +class DbStructureGraphStatistics(lookup: DbStructureLookup) extends GraphStatistics { + + import NameId._ + + override def nodesWithLabelCardinality( label: Option[LabelId] ): Cardinality = + Cardinality(lookup.nodesWithLabelCardinality(label)) + + override def cardinalityByLabelsAndRelationshipType( fromLabel: Option[LabelId], relTypeId: Option[RelTypeId], toLabel: Option[LabelId] ): Cardinality = + Cardinality(lookup.cardinalityByLabelsAndRelationshipType(fromLabel, relTypeId, toLabel)) + + /* + Probability of any node with the given label, to have a property with a given value + + indexSelectivity(:X, prop) = s => |MATCH (a:X)| * s = |MATCH (a:X) WHERE x.prop = *| + */ + override def indexSelectivity( label: LabelId, property: PropertyKeyId ): Option[Selectivity] = { + val result = lookup.indexSelectivity( label.id, property.id ) + if (result.isNaN) None else Some(Selectivity(result)) + } +} diff --git a/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/DbStructureLogicalPlanningConfiguration.scala b/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/DbStructureLogicalPlanningConfiguration.scala new file mode 100644 index 0000000000000..f0e3dd6003af5 --- /dev/null +++ b/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/DbStructureLogicalPlanningConfiguration.scala @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2002-2015 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.cypher.internal.compiler.v2_2.planner + +import java.util + +import org.neo4j.cypher.internal.compiler.v2_2.spi.{GraphStatistics, StatisticsCompletingGraphStatistics} +import org.neo4j.cypher.internal.compiler.v2_2.{LabelId, PropertyKeyId, RelTypeId} +import org.neo4j.helpers.Pair +import org.neo4j.helpers.collection.Visitable +import org.neo4j.kernel.impl.util.dbstructure.{DbStructureCollector, DbStructureLookup, DbStructureVisitor} + +import scala.collection.JavaConverters._ +import scala.collection.mutable + +object DbStructureLogicalPlanningConfiguration { + + def apply(visitable: Visitable[DbStructureVisitor]): LogicalPlanningConfiguration = { + val collector = new DbStructureCollector + visitable.accept(collector) + val lookup = collector.lookup() + apply(lookup, new DbStructureGraphStatistics(lookup)) + } + + def apply(lookup: DbStructureLookup, underlyingStatistics: GraphStatistics): LogicalPlanningConfiguration = { + val resolvedLabels: mutable.Map[String, LabelId] = resolveTokens(lookup.labels())(LabelId) + val resolvedPropertyKeys = resolveTokens(lookup.properties())(PropertyKeyId) + val resolvedRelTypeNames = resolveTokens(lookup.relationshipTypes())(RelTypeId) + + new RealLogicalPlanningConfiguration { + + override val computeSemanticTable: SemanticTable = new SemanticTable( + resolvedLabelIds = resolvedLabels, + resolvedPropertyKeyNames = resolvedPropertyKeys, + resolvedRelTypeNames = resolvedRelTypeNames + ) + + override val graphStatistics: GraphStatistics = + new StatisticsCompletingGraphStatistics(underlyingStatistics) + + override val indexes: Set[(String, String)] = indexSet(lookup.knownIndices()) + override val knownLabels: Set[String] = resolvedLabels.keys.toSet + override val uniqueIndexes: Set[(String, String)] = indexSet(lookup.knownUniqueIndices()) + } + } + + private def indexSet(indices: util.Iterator[Pair[String, String]]): Set[(String, String)] = + indices.asScala.map { pair => pair.first() -> pair.other() }.toSet + + private def resolveTokens[T](iterator: util.Iterator[Pair[Integer, String]])(f: Int => T): mutable.Map[String, T] = { + val builder = mutable.Map.newBuilder[String, T] + while (iterator.hasNext) { + val next = iterator.next() + builder += next.other() -> f(next.first()) + } + builder.result() + } +} diff --git a/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/LogicalPlanningConfiguration.scala b/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/LogicalPlanningConfiguration.scala index dfed0a84e1399..e47ef603ddd0c 100644 --- a/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/LogicalPlanningConfiguration.scala +++ b/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/LogicalPlanningConfiguration.scala @@ -19,18 +19,11 @@ */ package org.neo4j.cypher.internal.compiler.v2_2.planner -import org.neo4j.cypher.internal.compiler.v2_2.{RelTypeId, PropertyKeyId, LabelId, HardcodedGraphStatistics} -import org.neo4j.cypher.internal.compiler.v2_2.ast.Expression +import org.neo4j.cypher.internal.compiler.v2_2._ import org.neo4j.cypher.internal.compiler.v2_2.planner.logical.Metrics._ -import org.neo4j.cypher.internal.compiler.v2_2.planner.logical._ -import org.neo4j.cypher.internal.compiler.v2_2.planner.logical.plans.{NodeByLabelScan, LogicalPlan} +import org.neo4j.cypher.internal.compiler.v2_2.planner.logical.plans.LogicalPlan +import org.neo4j.cypher.internal.compiler.v2_2.planner.logical.{Cost, _} import org.neo4j.cypher.internal.compiler.v2_2.spi.GraphStatistics -import org.neo4j.helpers.collection.Visitable -import org.neo4j.kernel.api.constraints.UniquenessConstraint -import org.neo4j.kernel.api.index.IndexDescriptor -import org.neo4j.kernel.impl.util.dbstructure.DbStructureVisitor - -import scala.collection.mutable trait LogicalPlanningConfiguration { def computeSemanticTable: SemanticTable @@ -68,72 +61,3 @@ trait LogicalPlanningConfigurationAdHocSemanticTable { table } } -case class RealLogicalPlanningConfiguration() - extends LogicalPlanningConfiguration with LogicalPlanningConfigurationAdHocSemanticTable { - - def cardinalityModel(queryGraphCardinalityModel: QueryGraphCardinalityModel, semanticTable: SemanticTable) = { - val model: Metrics.CardinalityModel = new StatisticsBackedCardinalityModel(queryGraphCardinalityModel) - ({ case (plan: LogicalPlan, card: QueryGraphCardinalityInput) => model(plan, card) }) - } - - def costModel(cardinality: CardinalityModel): PartialFunction[LogicalPlan, Cost] = { - val model: Metrics.CostModel = new CardinalityCostModel(cardinality) - ({ case (plan: LogicalPlan) => model(plan, QueryGraphCardinalityInput.empty) }) - } - - def graphStatistics: GraphStatistics = HardcodedGraphStatistics - - def indexes = Set.empty - def uniqueIndexes = Set.empty - def labelCardinality = Map.empty - def knownLabels = Set.empty - - def qg: QueryGraph = ??? -} - -class StubbedLogicalPlanningConfiguration(parent: LogicalPlanningConfiguration) - extends LogicalPlanningConfiguration with LogicalPlanningConfigurationAdHocSemanticTable { - - self => - - var knownLabels: Set[String] = Set.empty - var cardinality: PartialFunction[LogicalPlan, Cardinality] = PartialFunction.empty - var cost: PartialFunction[LogicalPlan, Cost] = PartialFunction.empty - var selectivity: PartialFunction[Expression, Selectivity] = PartialFunction.empty - var labelCardinality: Map[String, Cardinality] = Map.empty - var statistics = null - var qg: QueryGraph = null - - var indexes: Set[(String, String)] = Set.empty - var uniqueIndexes: Set[(String, String)] = Set.empty - def indexOn(label: String, property: String) { - indexes = indexes + (label -> property) - } - def uniqueIndexOn(label: String, property: String) { - uniqueIndexes = uniqueIndexes + (label -> property) - } - - def costModel(cardinality: Metrics.CardinalityModel) = - cost.orElse(parent.costModel(cardinality)) - - def cardinalityModel(queryGraphCardinalityModel: QueryGraphCardinalityModel, semanticTable: SemanticTable): Metrics.CardinalityModel = { - val labelIdCardinality: Map[LabelId, Cardinality] = labelCardinality.map { - case (name: String, cardinality: Cardinality) => - semanticTable.resolvedLabelIds(name) -> cardinality - } - val labelScanCardinality: PartialFunction[LogicalPlan, Cardinality] = { - case NodeByLabelScan(_, label, _) if label.id(semanticTable).isDefined && - labelIdCardinality.contains(label.id(semanticTable).get) => - labelIdCardinality(label.id(semanticTable).get) - } - - val r: PartialFunction[LogicalPlan, Cardinality] = - labelScanCardinality.orElse(cardinality) - - (p: LogicalPlan, c: QueryGraphCardinalityInput) => - if(r.isDefinedAt(p)) r.apply(p) else parent.cardinalityModel(queryGraphCardinalityModel, semanticTable)(p, c) - } - - def graphStatistics: GraphStatistics = - Option(statistics).getOrElse(parent.graphStatistics) -} diff --git a/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/LogicalPlanningTestSupport2.scala b/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/LogicalPlanningTestSupport2.scala index 75bda335fa0c8..a98e5c6d68ec9 100644 --- a/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/LogicalPlanningTestSupport2.scala +++ b/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/LogicalPlanningTestSupport2.scala @@ -91,6 +91,16 @@ trait LogicalPlanningTestSupport2 extends CypherTestSupport with AstConstruction else None + def getUniquenessConstraint(labelName: String, propertyKey: String): Option[UniquenessConstraint] = { + if (config.uniqueIndexes((labelName, propertyKey))) + Some(new UniquenessConstraint( + semanticTable.resolvedLabelIds(labelName).id, + semanticTable.resolvedPropertyKeyNames(propertyKey).id + )) + else + None + } + def getIndexRule(labelName: String, propertyKey: String): Option[IndexDescriptor] = if (config.indexes((labelName, propertyKey))) Some(new IndexDescriptor( @@ -110,7 +120,6 @@ trait LogicalPlanningTestSupport2 extends CypherTestSupport with AstConstruction semanticTable.resolvedRelTypeNames.get(relType).map(_.id) def checkNodeIndex(idxName: String): Unit = ??? - def getUniquenessConstraint(labelName: String, propertyKey: String): Option[UniquenessConstraint] = ??? def checkRelIndex(idxName: String): Unit = ??? def getOrCreateFromSchemaState[T](key: Any, f: => T): T = ??? def getRelTypeName(id: Int): String = ??? diff --git a/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/RealLogicalPlanningConfiguration.scala b/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/RealLogicalPlanningConfiguration.scala new file mode 100644 index 0000000000000..a5fd8fe22e891 --- /dev/null +++ b/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/RealLogicalPlanningConfiguration.scala @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2002-2015 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.cypher.internal.compiler.v2_2.planner + +import org.neo4j.cypher.internal.compiler.v2_2.HardcodedGraphStatistics +import org.neo4j.cypher.internal.compiler.v2_2.planner.logical.plans.LogicalPlan +import org.neo4j.cypher.internal.compiler.v2_2.planner.logical.{CardinalityCostModel, Cost, StatisticsBackedCardinalityModel, Metrics} +import org.neo4j.cypher.internal.compiler.v2_2.planner.logical.Metrics.{CardinalityModel, QueryGraphCardinalityInput, QueryGraphCardinalityModel} +import org.neo4j.cypher.internal.compiler.v2_2.spi.GraphStatistics + +case class RealLogicalPlanningConfiguration() + extends LogicalPlanningConfiguration with LogicalPlanningConfigurationAdHocSemanticTable { + + def cardinalityModel(queryGraphCardinalityModel: QueryGraphCardinalityModel, semanticTable: SemanticTable) = { + val model: Metrics.CardinalityModel = new StatisticsBackedCardinalityModel(queryGraphCardinalityModel) + ({ + case (plan: LogicalPlan, card: QueryGraphCardinalityInput) => model(plan, card) + }) + } + + def costModel(cardinality: CardinalityModel): PartialFunction[LogicalPlan, Cost] = { + val model: Metrics.CostModel = new CardinalityCostModel(cardinality) + ({ + case (plan: LogicalPlan) => model(plan, QueryGraphCardinalityInput.empty) + }) + } + + def graphStatistics: GraphStatistics = HardcodedGraphStatistics + def indexes = Set.empty + def uniqueIndexes = Set.empty + def labelCardinality = Map.empty + def knownLabels = Set.empty + + def qg: QueryGraph = ??? +} diff --git a/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/StubbedLogicalPlanningConfiguration.scala b/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/StubbedLogicalPlanningConfiguration.scala new file mode 100644 index 0000000000000..20df5a335d41e --- /dev/null +++ b/community/cypher/cypher-compiler-2.2/src/test/scala/org/neo4j/cypher/internal/compiler/v2_2/planner/StubbedLogicalPlanningConfiguration.scala @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2002-2015 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.cypher.internal.compiler.v2_2.planner + +import org.neo4j.cypher.internal.compiler.v2_2.LabelId +import org.neo4j.cypher.internal.compiler.v2_2.ast.Expression +import org.neo4j.cypher.internal.compiler.v2_2.planner.logical.Metrics.{QueryGraphCardinalityInput, QueryGraphCardinalityModel} +import org.neo4j.cypher.internal.compiler.v2_2.planner.logical.{Metrics, Selectivity, Cost, Cardinality} +import org.neo4j.cypher.internal.compiler.v2_2.planner.logical.plans.{NodeByLabelScan, LogicalPlan} +import org.neo4j.cypher.internal.compiler.v2_2.spi.GraphStatistics + +class StubbedLogicalPlanningConfiguration(parent: LogicalPlanningConfiguration) + extends LogicalPlanningConfiguration with LogicalPlanningConfigurationAdHocSemanticTable { + + self => + + var knownLabels: Set[String] = Set.empty + var cardinality: PartialFunction[LogicalPlan, Cardinality] = PartialFunction.empty + var cost: PartialFunction[LogicalPlan, Cost] = PartialFunction.empty + var selectivity: PartialFunction[Expression, Selectivity] = PartialFunction.empty + var labelCardinality: Map[String, Cardinality] = Map.empty + var statistics = null + var qg: QueryGraph = null + + var indexes: Set[(String, String)] = Set.empty + var uniqueIndexes: Set[(String, String)] = Set.empty + + def indexOn(label: String, property: String) { + indexes = indexes + (label -> property) + } + + def uniqueIndexOn(label: String, property: String) { + uniqueIndexes = uniqueIndexes + (label -> property) + } + + def costModel(cardinality: Metrics.CardinalityModel) = + cost.orElse(parent.costModel(cardinality)) + + def cardinalityModel(queryGraphCardinalityModel: QueryGraphCardinalityModel, semanticTable: SemanticTable): Metrics.CardinalityModel = { + val labelIdCardinality: Map[LabelId, Cardinality] = labelCardinality.map { + case (name: String, cardinality: Cardinality) => + semanticTable.resolvedLabelIds(name) -> cardinality + } + val labelScanCardinality: PartialFunction[LogicalPlan, Cardinality] = { + case NodeByLabelScan(_, label, _) if label.id(semanticTable).isDefined && + labelIdCardinality.contains(label.id(semanticTable).get) => + labelIdCardinality(label.id(semanticTable).get) + } + + val r: PartialFunction[LogicalPlan, Cardinality] = + labelScanCardinality.orElse(cardinality) + + (p: LogicalPlan, c: QueryGraphCardinalityInput) => + if (r.isDefinedAt(p)) r.apply(p) else parent.cardinalityModel(queryGraphCardinalityModel, semanticTable)(p, c) + } + + def graphStatistics: GraphStatistics = + Option(statistics).getOrElse(parent.graphStatistics) +} diff --git a/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/compatibility/CompatibilityFor2_2.scala b/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/compatibility/CompatibilityFor2_2.scala index db403c2ccb2ad..daee8cdf09d35 100644 --- a/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/compatibility/CompatibilityFor2_2.scala +++ b/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/compatibility/CompatibilityFor2_2.scala @@ -155,7 +155,7 @@ trait CompatibilityFor2_2 { def isPeriodicCommit = inner.isPeriodicCommit def isStale(lastTxId: () => Long, statement: Statement) = - inner.isStale(lastTxId, new TransactionBoundGraphStatistics(statement)) + inner.isStale(lastTxId, TransactionBoundGraphStatistics(statement)) } } diff --git a/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v2_2/TransactionBoundGraphStatistics.scala b/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v2_2/TransactionBoundGraphStatistics.scala index 6b9a197d6cbac..016d3b99c6deb 100644 --- a/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v2_2/TransactionBoundGraphStatistics.scala +++ b/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v2_2/TransactionBoundGraphStatistics.scala @@ -20,53 +20,41 @@ package org.neo4j.cypher.internal.spi.v2_2 import org.neo4j.cypher.internal.compiler.v2_2.planner.logical.{Cardinality, Selectivity} -import org.neo4j.cypher.internal.compiler.v2_2.spi.GraphStatistics -import org.neo4j.cypher.internal.compiler.v2_2.{LabelId, NameId, PropertyKeyId, RelTypeId} +import org.neo4j.cypher.internal.compiler.v2_2.spi.{GraphStatistics, StatisticsCompletingGraphStatistics} +import org.neo4j.cypher.internal.compiler.v2_2.{LabelId, PropertyKeyId, RelTypeId} import org.neo4j.kernel.api.exceptions.index.IndexNotFoundKernelException import org.neo4j.kernel.api.index.IndexDescriptor import org.neo4j.kernel.api.{Statement => KernelStatement} -class TransactionBoundGraphStatistics(statement: KernelStatement) extends GraphStatistics { - import org.neo4j.cypher.internal.spi.v2_2.TransactionBoundGraphStatistics.{WILDCARD, toKernelEncode} - - def indexSelectivity(label: LabelId, property: PropertyKeyId): Option[Selectivity] = - try { - val indexDescriptor = new IndexDescriptor( label, property ) - val labeledNodes = statement.readOperations().countsForNode( label ).toDouble +object TransactionBoundGraphStatistics { + def apply(statement: KernelStatement) = new StatisticsCompletingGraphStatistics(new BaseTransactionBoundGraphStatistics(statement)) - // approximation of number of unique values / index size (between 0 and 1) - val indexEntrySelectivity = statement.readOperations( ).indexUniqueValuesSelectivity( indexDescriptor ) - val frequencyOfNodesWithSameValue = 1.0 / indexEntrySelectivity - val indexSelectivity = frequencyOfNodesWithSameValue / labeledNodes + private class BaseTransactionBoundGraphStatistics(statement: KernelStatement) extends GraphStatistics { - Selectivity.of(indexSelectivity) - } - catch { - case e: IndexNotFoundKernelException => None - } + import org.neo4j.cypher.internal.compiler.v2_2.NameId._ - def nodesWithLabelCardinality(labelId: Option[LabelId]): Cardinality = - statement.readOperations().countsForNode(labelId) + def indexSelectivity(label: LabelId, property: PropertyKeyId): Option[Selectivity] = + try { + val indexDescriptor = new IndexDescriptor( label, property ) + val labeledNodes = statement.readOperations().countsForNode( label ).toDouble - def cardinalityByLabelsAndRelationshipType(fromLabel: Option[LabelId], relTypeId: Option[RelTypeId], toLabel: Option[LabelId]): Cardinality = - (fromLabel, toLabel) match { - case (Some(_), Some(_)) => - // TODO: read real counts from readOperations when they are gonna be properly computed and updated - Math.min( - statement.readOperations().countsForRelationship(fromLabel, relTypeId, WILDCARD ), - statement.readOperations().countsForRelationship(WILDCARD, relTypeId, toLabel) - ) - case _ => - statement.readOperations().countsForRelationship(fromLabel, relTypeId, toLabel) - } -} + // approximation of number of unique values / index size (between 0 and 1) + val indexEntrySelectivity = statement.readOperations( ).indexUniqueValuesSelectivity( indexDescriptor ) + val frequencyOfNodesWithSameValue = 1.0 / indexEntrySelectivity + val indexSelectivity = frequencyOfNodesWithSameValue / labeledNodes -object TransactionBoundGraphStatistics { - val WILDCARD: Int = -1 + Selectivity.of(indexSelectivity) + } + catch { + case e: IndexNotFoundKernelException => None + } - private implicit def toKernelEncode(nameId: NameId): Int = - nameId.id + def nodesWithLabelCardinality(labelId: Option[LabelId]): Cardinality = + statement.readOperations().countsForNode(labelId) - private implicit def toKernelEncode(nameId: Option[NameId]): Int = - nameId.map(toKernelEncode).getOrElse(WILDCARD) + def cardinalityByLabelsAndRelationshipType(fromLabel: Option[LabelId], relTypeId: Option[RelTypeId], toLabel: Option[LabelId]): Cardinality = + statement.readOperations().countsForRelationship(fromLabel, relTypeId, toLabel) + } } + + diff --git a/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v2_2/TransactionBoundPlanContext.scala b/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v2_2/TransactionBoundPlanContext.scala index e3f78041be3f1..870b78d53b103 100644 --- a/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v2_2/TransactionBoundPlanContext.scala +++ b/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v2_2/TransactionBoundPlanContext.scala @@ -87,7 +87,7 @@ class TransactionBoundPlanContext(statement: Statement, val gdb: GraphDatabaseSe } val statistics: GraphStatistics = - InstrumentedGraphStatistics(new TransactionBoundGraphStatistics(statement), MutableGraphStatisticsSnapshot()) + InstrumentedGraphStatistics(TransactionBoundGraphStatistics(statement), MutableGraphStatisticsSnapshot()) val txIdProvider: () => Long = gdb.asInstanceOf[GraphDatabaseAPI] .getDependencyResolver diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureCollector.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureCollector.java new file mode 100644 index 0000000000000..6ffb71b7db18d --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureCollector.java @@ -0,0 +1,370 @@ +/** + * Copyright (c) 2002-2015 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.impl.util.dbstructure; + +import org.neo4j.function.Function; +import org.neo4j.helpers.Pair; +import org.neo4j.helpers.collection.Iterables; +import org.neo4j.kernel.api.constraints.UniquenessConstraint; +import org.neo4j.kernel.api.index.IndexDescriptor; + +import java.util.*; + +import static java.lang.String.format; + +public class DbStructureCollector implements DbStructureVisitor +{ + private final TokenMap labels = new TokenMap( "label" ); + private final TokenMap propertyKeys = new TokenMap( "property key" ); + private final TokenMap relationshipTypes = new TokenMap( "relationship types" ); + private final IndexDescriptorMap regularIndices = new IndexDescriptorMap( "regular" ); + private final IndexDescriptorMap uniqueIndices = new IndexDescriptorMap( "unique" ); + private final Set uniquenessConstraint = new HashSet<>(); + private final Map nodeCounts = new HashMap<>(); + private final Map relCounts = new HashMap<>(); + private long allNodesCount = -1l; + + public DbStructureLookup lookup() + { + return new DbStructureLookup() + { + @Override + public Iterator> labels() + { + return labels.iterator(); + } + + @Override + public Iterator> properties() + { + return propertyKeys.iterator(); + } + + @Override + public Iterator> relationshipTypes() + { + return relationshipTypes.iterator(); + } + + @Override + public Iterator> knownIndices() + { + return regularIndices.iterator(); + } + + @Override + public Iterator> knownUniqueIndices() + { + return uniqueIndices.iterator(); + } + + @Override + public Iterator> knownUniqueConstraints() + { + return Iterables.map( new Function>() + { + @Override + public Pair apply( UniquenessConstraint uniquenessConstraint ) throws RuntimeException + { + String label = labels.byIdOrFail( uniquenessConstraint.label() ); + String propertyKey = propertyKeys.byIdOrFail( uniquenessConstraint.propertyKeyId() ); + return Pair.of( label, propertyKey ); + } + }, uniquenessConstraint.iterator() ); + } + + @Override + public long nodesWithLabelCardinality( int labelId ) + { + Long result = labelId == -1 ? allNodesCount : nodeCounts.get( labelId ); + return result == null ? 0l : result; + } + + @Override + public long cardinalityByLabelsAndRelationshipType( int fromLabelId, int relTypeId, int toLabelId ) + { + RelSpecifier specifier = new RelSpecifier( fromLabelId, relTypeId, toLabelId ); + Long result = relCounts.get( specifier ); + return result == null ? 0l : result; + } + + @Override + public double indexSelectivity( int labelId, int propertyKeyId ) + { + Double result1 = regularIndices.getIndex( labelId, propertyKeyId ); + Double result2 = result1 == null ? uniqueIndices.getIndex( labelId, propertyKeyId ) : result1; + return result2 == null ? Double.NaN : result2; + } + }; + } + + @Override + public void visitLabel( int labelId, String labelName ) + { + labels.putToken( labelId, labelName ); + } + + @Override + public void visitPropertyKey( int propertyKeyId, String propertyKeyName ) + { + propertyKeys.putToken( propertyKeyId, propertyKeyName ); + } + + @Override + public void visitRelationshipType( int relTypeId, String relTypeName ) + { + relationshipTypes.putToken( relTypeId, relTypeName ); + } + + @Override + public void visitIndex( IndexDescriptor descriptor, String userDescription, double uniqueValuesPercentage ) + { + regularIndices.putIndex( descriptor, userDescription, uniqueValuesPercentage ); + } + + @Override + public void visitUniqueIndex( IndexDescriptor descriptor, String userDescription, double uniqueValuesPercentage ) + { + uniqueIndices.putIndex( descriptor, userDescription, uniqueValuesPercentage ); + } + + @Override + public void visitUniqueConstraint( UniquenessConstraint constraint, String userDescription ) + { + if ( !uniquenessConstraint.add( constraint ) ) + { + throw new IllegalArgumentException( + format( "Duplicate unique constraint %s for %s", constraint, userDescription ) + ); + } + } + + @Override + public void visitAllNodesCount( long nodeCount ) + { + if ( allNodesCount < 0 ) + { + allNodesCount = nodeCount; + } + else + { + throw new IllegalStateException( "Already received node count" ); + } + } + + @Override + public void visitNodeCount( int labelId, String labelName, long nodeCount ) + { + if ( nodeCounts.put( labelId, nodeCount ) != null ) + { + throw new IllegalArgumentException( + format( "Duplicate node count %s for label with id % s", nodeCount, labelName ) + ); + } + } + + @Override + public void visitRelCount( int startLabelId, int relTypeId, int endLabelId, String relCountQuery, long relCount ) + { + RelSpecifier specifier = new RelSpecifier( startLabelId, relTypeId, endLabelId ); + + if ( relCounts.put( specifier, relCount ) != null ) + { + throw new IllegalArgumentException( + format( "Duplicate rel count %s for relationship specifier %s (corresponding query: %s)", relCount, + specifier, relCountQuery ) + ); + } + } + + private static class RelSpecifier + { + public final int fromLabelId; + public final int relTypeId; + public final int toLabelId; + + public RelSpecifier( int fromLabelId, int relTypeId, int toLabelId ) + { + this.fromLabelId = fromLabelId; + this.relTypeId = relTypeId; + this.toLabelId = toLabelId; + } + + @Override + public String toString() + { + return format( + "RelSpecifier{fromLabelId=%d, relTypeId=%d, toLabelId=%d}", fromLabelId, relTypeId, toLabelId + ); + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + + RelSpecifier that = (RelSpecifier) o; + return fromLabelId == that.fromLabelId && relTypeId == that.relTypeId && toLabelId == that.toLabelId; + } + + @Override + public int hashCode() + { + int result = fromLabelId; + result = 31 * result + relTypeId; + result = 31 * result + toLabelId; + return result; + } + } + + private class IndexDescriptorMap implements Iterable> + { + private final String indexType; + private final Map indexMap = new HashMap<>(); + + public IndexDescriptorMap( String indexType ) + { + this.indexType = indexType; + } + + public void putIndex( IndexDescriptor descriptor, String userDescription, double uniqueValuesPercentage ) + { + if ( indexMap.containsKey( descriptor ) ) + { + throw new IllegalArgumentException( + format( "Duplicate index descriptor %s for %s index %s", descriptor, indexType, + userDescription ) + ); + } + + indexMap.put( descriptor, uniqueValuesPercentage ); + } + + public Double getIndex( int labelId, int propertyKeyId ) + { + return indexMap.get( new IndexDescriptor( labelId, propertyKeyId ) ); + } + + public Iterator> iterator() + { + final Iterator iterator = indexMap.keySet().iterator(); + return new Iterator>() + { + @Override + public boolean hasNext() + { + return iterator.hasNext(); + } + + @Override + public Pair next() + { + IndexDescriptor next = iterator.next(); + String label = labels.byIdOrFail( next.getLabelId() ); + String propertyKey = propertyKeys.byIdOrFail( next.getPropertyKeyId() ); + return Pair.of( label, propertyKey ); + } + + @Override + public void remove() + { + iterator.remove(); + } + }; + } + } + + private static class TokenMap implements Iterable> + { + private final String tokenType; + private final Map forward = new HashMap<>(); + private final Map backward = new HashMap<>(); + + public TokenMap( String tokenType ) + { + this.tokenType = tokenType; + } + + public String byIdOrFail( int token ) + { + String result = forward.get( token ); + if ( result == null ) + { + throw new IllegalArgumentException( format( "Didn't find %s token with id %s", tokenType, token ) ); + } + return result; + } + + public void putToken( int token, String name ) + { + if ( forward.containsKey( token ) ) + { + throw new IllegalArgumentException( + format( "Duplicate id %s for name %s in %s token map", token, name, tokenType ) + ); + } + + + if ( backward.containsKey( name ) ) + { + throw new IllegalArgumentException( + format( "Duplicate name %s for id %s in %s token map", name, token, tokenType ) + ); + } + + forward.put( token, name ); + backward.put( name, token ); + } + + @Override + public Iterator> iterator() + { + final Iterator> iterator = forward.entrySet().iterator(); + return new Iterator>() + { + @Override + public boolean hasNext() + { + return iterator.hasNext(); + } + + @Override + public Pair next() + { + Map.Entry next = iterator.next(); + return Pair.of( next.getKey(), next.getValue() ); + } + + @Override + public void remove() + { + iterator.remove(); + } + }; + } + } +} diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureLookup.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureLookup.java new file mode 100644 index 0000000000000..47a86b85d2966 --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureLookup.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2002-2015 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.impl.util.dbstructure; + +import org.neo4j.helpers.Pair; + +import java.util.Iterator; + +public interface DbStructureLookup +{ + Iterator> labels(); + Iterator> properties(); + Iterator> relationshipTypes(); + + Iterator> knownIndices(); + Iterator> knownUniqueIndices(); + Iterator> knownUniqueConstraints(); + + long nodesWithLabelCardinality( int labelId ); + long cardinalityByLabelsAndRelationshipType( int fromLabelId, int relTypeId, int toLabelId ); + double indexSelectivity( int labelId, int propertyKeyId ); +} diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureTool.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureTool.java index 4d5243e5020e4..1d242c4f3f7de 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureTool.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureTool.java @@ -99,7 +99,7 @@ private static void traceDb( String generator, ); DbStructureVisitor visitor = tracer.newProxy(); - DbStructureGuide guide = new DbStructureGuide( graph ); + GraphDbStructureGuide guide = new GraphDbStructureGuide( graph ); guide.accept( visitor ); tracer.close(); } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureGuide.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/GraphDbStructureGuide.java similarity index 97% rename from community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureGuide.java rename to community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/GraphDbStructureGuide.java index 957dc2afd3080..b1cdbfed0f768 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureGuide.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/util/dbstructure/GraphDbStructureGuide.java @@ -45,17 +45,8 @@ import static org.neo4j.kernel.api.ReadOperations.ANY_LABEL; import static org.neo4j.kernel.api.ReadOperations.ANY_RELATIONSHIP_TYPE; -public class DbStructureGuide implements Visitable +public class GraphDbStructureGuide implements Visitable { - private static Label WILDCARD_LABEL = new Label() - { - @Override - public String name() - { - return ""; - } - }; - private static RelationshipType WILDCARD_REL_TYPE = new RelationshipType() { @Override @@ -69,7 +60,7 @@ public String name() private final ThreadToStatementContextBridge bridge; private final GlobalGraphOperations glops; - public DbStructureGuide( GraphDatabaseService graph ) + public GraphDbStructureGuide( GraphDatabaseService graph ) { this.db = (GraphDatabaseAPI) graph; DependencyResolver dependencyResolver = db.getDependencyResolver(); diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureCollectorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureCollectorTest.java new file mode 100644 index 0000000000000..e19801fb599ad --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureCollectorTest.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2002-2015 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.impl.util.dbstructure; + +import org.junit.Test; + +import org.neo4j.kernel.api.constraints.UniquenessConstraint; +import org.neo4j.kernel.api.index.IndexDescriptor; + +import static java.util.Arrays.asList; + +import static org.junit.Assert.assertEquals; + +import static org.neo4j.helpers.Pair.of; +import static org.neo4j.helpers.collection.Iterables.toList; + +public class DbStructureCollectorTest +{ + @Test + public void collectsDbStructure() + { + // GIVEN + DbStructureCollector collector = new DbStructureCollector(); + collector.visitLabel( 1, "Person" ); + collector.visitLabel( 2, "City" ); + collector.visitPropertyKey( 1, "name" ); + collector.visitPropertyKey( 2, "income" ); + collector.visitRelationshipType( 1, "LIVES_IN" ); + collector.visitRelationshipType( 2, "FRIEND" ); + collector.visitUniqueIndex( new IndexDescriptor( 1, 1 ), ":Person(name)", 1.0d ); + collector.visitUniqueConstraint( new UniquenessConstraint( 2, 1 ), ":Person(name)" ); + collector.visitIndex( new IndexDescriptor( 2, 2 ), ":City(income)", 0.2d ); + collector.visitAllNodesCount( 50 ); + collector.visitNodeCount( 1, "Person", 20 ); + collector.visitNodeCount( 2, "City", 30 ); + collector.visitRelCount( 1, 2, -1, "(:Person)-[:FRIEND]->()", 500 ); + + // WHEN + DbStructureLookup lookup = collector.lookup(); + + // THEN + assertEquals( asList( of( 1, "Person" ), of( 2, "City" ) ), toList( lookup.labels() ) ); + assertEquals( asList( of( 1, "name" ), of( 2, "income" ) ), toList( lookup.properties() ) ); + assertEquals( asList( of( 1, "LIVES_IN" ), of( 2, "FRIEND" ) ), toList( lookup.relationshipTypes() ) ); + + assertEquals( asList( of( "City", "name" ) ), toList( lookup.knownUniqueConstraints() ) ); + assertEquals( asList( of( "Person", "name" ) ), toList( lookup.knownUniqueIndices() ) ); + assertEquals( asList( of( "City", "income" ) ), toList( lookup.knownIndices() ) ); + + assertEquals( 50, lookup.nodesWithLabelCardinality( -1 ) ); + assertEquals( 20, lookup.nodesWithLabelCardinality( 1 ) ); + assertEquals( 30, lookup.nodesWithLabelCardinality( 2 ) ); + assertEquals( 500, lookup.cardinalityByLabelsAndRelationshipType( 1, 2, -1 ) ); + assertEquals( 1.0d, lookup.indexSelectivity( 1, 1 ), 0.01d ); + assertEquals( 0.2d, lookup.indexSelectivity( 2, 2 ), 0.01d ); + } +} diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureGuideTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/util/dbstructure/GraphDbStructureGuideTest.java similarity index 98% rename from community/kernel/src/test/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureGuideTest.java rename to community/kernel/src/test/java/org/neo4j/kernel/impl/util/dbstructure/GraphDbStructureGuideTest.java index 5f365a275f0ea..a163f76e39b2b 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/util/dbstructure/DbStructureGuideTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/util/dbstructure/GraphDbStructureGuideTest.java @@ -46,7 +46,7 @@ import static org.neo4j.kernel.api.ReadOperations.ANY_LABEL; import static org.neo4j.kernel.api.ReadOperations.ANY_RELATIONSHIP_TYPE; -public class DbStructureGuideTest +public class GraphDbStructureGuideTest { @Test public void visitsLabelIds() throws Exception @@ -318,7 +318,7 @@ public void accept( DbStructureVisitor visitor ) throw new IllegalStateException( "Dangling transaction before running visitable" ); } - DbStructureGuide analyzer = new DbStructureGuide( graph ); + GraphDbStructureGuide analyzer = new GraphDbStructureGuide( graph ); analyzer.accept( visitor ); }