From c88b8514c462a5bffd58a9c559aae0c9b7c247dd Mon Sep 17 00:00:00 2001 From: Chris Vest Date: Fri, 4 May 2018 13:14:20 +0200 Subject: [PATCH] Implement batching token create for labels. --- .../neo4j/internal/kernel/api/TokenWrite.java | 14 +++ .../impl/factory/GraphDatabaseFacade.java | 12 ++- .../neo4j/kernel/impl/newapi/KernelToken.java | 22 +++++ .../impl/newapi/KernelTokenArgumentTest.java | 62 ------------- .../kernel/impl/newapi/KernelTokenTest.java | 90 +++++++++++++++++++ 5 files changed, 136 insertions(+), 64 deletions(-) delete mode 100644 community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/KernelTokenArgumentTest.java create mode 100644 community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/KernelTokenTest.java diff --git a/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/TokenWrite.java b/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/TokenWrite.java index b8caf6e42edf1..c08946c078879 100644 --- a/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/TokenWrite.java +++ b/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/TokenWrite.java @@ -30,6 +30,20 @@ public interface TokenWrite */ int labelGetOrCreateForName( String labelName ) throws IllegalTokenNameException, TooManyLabelsException; + /** + * Get or create the label token ids for each of the given {@code labelNames}, and store them at the corresponding + * index in the given {@code labelIds} array. + * + * This is effectively a batching version of {@link #labelGetOrCreateForName(String)}. + * + * @param labelNames The array of label names for which to resolve or create their id. + * @param labelIds The array into which the resulting token ids will be stored. + * @throws TooManyLabelsException if too many labels would bve created by this call, compared to the token id space + * available. + */ + void labelGetOrCreateForNames( String[] labelNames, int[] labelIds ) + throws IllegalTokenNameException, TooManyLabelsException; + /** * Creates a label with the given id * @param labelName the name of the label diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/factory/GraphDatabaseFacade.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/factory/GraphDatabaseFacade.java index b59209c905255..6dcfc09322a9b 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/factory/GraphDatabaseFacade.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/factory/GraphDatabaseFacade.java @@ -68,6 +68,7 @@ import org.neo4j.internal.kernel.api.Read; import org.neo4j.internal.kernel.api.RelationshipScanCursor; import org.neo4j.internal.kernel.api.TokenRead; +import org.neo4j.internal.kernel.api.TokenWrite; import org.neo4j.internal.kernel.api.Write; import org.neo4j.internal.kernel.api.exceptions.EntityNotFoundException; import org.neo4j.internal.kernel.api.exceptions.InvalidTransactionTypeKernelException; @@ -264,9 +265,16 @@ public Node createNode( Label... labels ) { Write write = transaction.dataWrite(); long nodeId = write.nodeCreate(); - for ( Label label : labels ) + TokenWrite tokenWrite = transaction.tokenWrite(); + int[] labelIds = new int[labels.length]; + String[] labelNames = new String[labels.length]; + for ( int i = 0; i < labelNames.length; i++ ) + { + labelNames[i] = labels[i].name(); + } + tokenWrite.labelGetOrCreateForNames( labelNames, labelIds ); + for ( int labelId : labelIds ) { - int labelId = transaction.tokenWrite().labelGetOrCreateForName( label.name() ); try { write.nodeAddLabel( nodeId, labelId ); diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/KernelToken.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/KernelToken.java index 3896aaae88581..7df220670363f 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/KernelToken.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/KernelToken.java @@ -58,6 +58,28 @@ public int labelGetOrCreateForName( String labelName ) throws IllegalTokenNameEx return store.labelGetOrCreateForName( labelName ); } + + @Override + public void labelGetOrCreateForNames( String[] labelNames, int[] labelIds ) + throws IllegalTokenNameException, TooManyLabelsException + { + ktx.assertOpen(); + if ( labelNames.length != labelIds.length ) + { + throw new IllegalArgumentException( "Name and id arrays have different length." ); + } + for ( int i = 0; i < labelNames.length; i++ ) + { + labelIds[i] = store.labelGetForName( checkValidTokenName( labelNames[i] ) ); + if ( labelIds[i] == TokenHolder.NO_ID ) + { + ktx.assertAllows( AccessMode::allowsTokenCreates, "Token create" ); + store.labelGetOrCreateForNames( labelNames, labelIds ); + return; + } + } + } + @Override public void labelCreateForName( String labelName, int id ) throws IllegalTokenNameException, TooManyLabelsException { diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/KernelTokenArgumentTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/KernelTokenArgumentTest.java deleted file mode 100644 index 0b7e796d23d0b..0000000000000 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/KernelTokenArgumentTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2002-2018 "Neo4j," - * Neo4j Sweden AB [http://neo4j.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.newapi; - -import org.junit.Test; - -import org.neo4j.function.ThrowingAction; -import org.neo4j.internal.kernel.api.exceptions.KernelException; -import org.neo4j.internal.kernel.api.exceptions.schema.IllegalTokenNameException; -import org.neo4j.kernel.impl.api.KernelTransactionImplementation; -import org.neo4j.storageengine.api.StorageReader; - -import static org.mockito.Mockito.mock; -import static org.neo4j.test.assertion.Assert.assertException; - -public class KernelTokenArgumentTest -{ - private KernelToken token = new KernelToken( mock( StorageReader.class ), mock( KernelTransactionImplementation.class) ); - - @Test - public void labelGetOrCreateForName() - { - assertIllegalToken( () -> token.labelGetOrCreateForName( null ) ); - assertIllegalToken( () -> token.labelGetOrCreateForName( "" ) ); - } - - @Test - public void propertyKeyGetOrCreateForName() - { - assertIllegalToken( () -> token.propertyKeyGetOrCreateForName( null ) ); - assertIllegalToken( () -> token.propertyKeyGetOrCreateForName( "" ) ); - } - - @Test - public void relationshipTypeGetOrCreateForName() - { - assertIllegalToken( () -> token.relationshipTypeGetOrCreateForName( null ) ); - assertIllegalToken( () -> token.relationshipTypeGetOrCreateForName( "" ) ); - } - - private void assertIllegalToken( ThrowingAction f ) - { - assertException( f, IllegalTokenNameException.class ); - } -} diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/KernelTokenTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/KernelTokenTest.java new file mode 100644 index 0000000000000..19b3c6c913ba5 --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/newapi/KernelTokenTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2002-2018 "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.newapi; + +import org.junit.Test; + +import org.neo4j.function.ThrowingAction; +import org.neo4j.internal.kernel.api.exceptions.KernelException; +import org.neo4j.internal.kernel.api.exceptions.schema.IllegalTokenNameException; +import org.neo4j.kernel.impl.api.KernelTransactionImplementation; +import org.neo4j.kernel.impl.core.TokenHolder; +import org.neo4j.storageengine.api.StoreReadLayer; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.neo4j.test.assertion.Assert.assertException; + +public class KernelTokenTest +{ + private final StoreReadLayer storeReadLayer = mock( StoreReadLayer.class ); + private final KernelTransactionImplementation ktx = mock( KernelTransactionImplementation.class ); + private KernelToken token = new KernelToken( storeReadLayer, ktx ); + + @Test + public void labelGetOrCreateForName() throws Exception + { + assertIllegalToken( () -> token.labelGetOrCreateForName( null ) ); + assertIllegalToken( () -> token.labelGetOrCreateForName( "" ) ); + when( storeReadLayer.labelGetForName( "label" ) ).thenReturn( TokenHolder.NO_ID ); + when( storeReadLayer.labelGetOrCreateForName( "label" ) ).thenReturn( 42 ); + assertThat( token.labelGetOrCreateForName( "label" ), is( 42 ) ); + } + + @Test + public void labelGetOrCreateForNames() throws Exception + { + assertIllegalToken( () -> token.labelGetOrCreateForNames( new String[]{null}, new int[1] ) ); + assertIllegalToken( () -> token.labelGetOrCreateForNames( new String[]{""}, new int[1] ) ); + String[] names = {"a", "b"}; + int[] ids = new int[2]; + when( storeReadLayer.labelGetForName( "a" ) ).thenReturn( TokenHolder.NO_ID ); + token.labelGetOrCreateForNames( names, ids ); + verify( storeReadLayer ).labelGetOrCreateForNames( names, ids ); + } + + @Test + public void propertyKeyGetOrCreateForName() throws IllegalTokenNameException + { + assertIllegalToken( () -> token.propertyKeyGetOrCreateForName( null ) ); + assertIllegalToken( () -> token.propertyKeyGetOrCreateForName( "" ) ); + when( storeReadLayer.propertyKeyGetForName( "prop" ) ).thenReturn( TokenHolder.NO_ID ); + when( storeReadLayer.propertyKeyGetOrCreateForName( "prop" ) ).thenReturn( 42 ); + assertThat( token.propertyKeyGetOrCreateForName( "prop" ), is( 42 ) ); + } + + @Test + public void relationshipTypeGetOrCreateForName() throws IllegalTokenNameException + { + assertIllegalToken( () -> token.relationshipTypeGetOrCreateForName( null ) ); + assertIllegalToken( () -> token.relationshipTypeGetOrCreateForName( "" ) ); + when( storeReadLayer.relationshipTypeGetForName( "rel" ) ).thenReturn( TokenHolder.NO_ID ); + when( storeReadLayer.relationshipTypeGetOrCreateForName( "rel" ) ).thenReturn( 42 ); + assertThat( token.relationshipTypeGetOrCreateForName( "rel" ), is( 42 ) ); + } + + private void assertIllegalToken( ThrowingAction f ) + { + assertException( f, IllegalTokenNameException.class ); + } +}