diff --git a/community/common/src/main/java/org/neo4j/string/UTF8.java b/community/common/src/main/java/org/neo4j/string/UTF8.java index ff07bae095615..0fc0b9178d4d7 100644 --- a/community/common/src/main/java/org/neo4j/string/UTF8.java +++ b/community/common/src/main/java/org/neo4j/string/UTF8.java @@ -56,6 +56,34 @@ public static String getDecodedStringFrom( ByteBuffer source ) return UTF8.decode( data ); } + public static String getDecodedNullTerminatedStringFrom( ByteBuffer source ) + { + int start = source.position(); + int nullPos = -1; + int cursor = start; + while ( cursor < source.limit() ) + { + if ( source.get( cursor ) == 0 ) + { + nullPos = cursor; + break; + } + cursor++; + } + if ( nullPos == -1 ) + { + return null; + } + int length = nullPos - start; + if ( length == 0 ) + { + return ""; + } + byte[] data = new byte[length]; + source.get( data ); + return UTF8.decode( data ); + } + public static void putEncodedStringInto( String text, ByteBuffer target ) { byte[] data = encode( text ); @@ -63,11 +91,23 @@ public static void putEncodedStringInto( String text, ByteBuffer target ) target.put( data ); } + public static void putEncodedNullTerminatedStringInto( String text, ByteBuffer target ) + { + byte[] data = encode( text ); + target.put( data ); + target.put( (byte) 0 ); + } + public static int computeRequiredByteBufferSize( String text ) { return encode( text ).length + 4; } + public static int computeRequiredNullTerminatedByteBufferSize( String text ) + { + return encode( text ).length + 1; + } + private UTF8() { // No need to instantiate, all methods are static diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/ConstraintRule.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/ConstraintRule.java index 862374b1325ee..ccaa308b4a4a3 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/ConstraintRule.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/ConstraintRule.java @@ -28,18 +28,11 @@ import static org.neo4j.kernel.api.schema_new.SchemaUtil.idTokenNameLookup; -public class ConstraintRule implements SchemaRule, ConstraintDescriptor.Supplier +public class ConstraintRule extends SchemaRule implements ConstraintDescriptor.Supplier { - private final long id; private final Long ownedIndexRule; private final ConstraintDescriptor descriptor; - @Override - public final long getId() - { - return this.id; - } - public static ConstraintRule constraintRule( long id, ConstraintDescriptor descriptor ) { @@ -54,7 +47,7 @@ public static ConstraintRule constraintRule( ConstraintRule( long id, ConstraintDescriptor descriptor, Long ownedIndexRule ) { - this.id = id; + super( id ); this.descriptor = descriptor; this.ownedIndexRule = ownedIndexRule; } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/IndexRule.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/IndexRule.java index 12559f2c4a4a0..b2513b102e1a5 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/IndexRule.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/IndexRule.java @@ -32,9 +32,8 @@ /** * A {@link Label} can have zero or more index rules which will have data specified in the rules indexed. */ -public class IndexRule implements SchemaRule, NewIndexDescriptor.Supplier +public class IndexRule extends SchemaRule implements NewIndexDescriptor.Supplier { - private final long id; private final SchemaIndexProvider.Descriptor providerDescriptor; private final NewIndexDescriptor descriptor; private final Long owningConstraint; @@ -55,7 +54,7 @@ public static IndexRule constraintIndexRule( long id, NewIndexDescriptor descrip IndexRule( long id, SchemaIndexProvider.Descriptor providerDescriptor, NewIndexDescriptor descriptor, Long owningConstraint ) { - this.id = id; + super( id ); if ( providerDescriptor == null ) { throw new IllegalArgumentException( "null provider descriptor prohibited" ); @@ -107,12 +106,6 @@ public IndexRule withOwningConstraint( long constraintId ) return constraintIndexRule( id, descriptor, providerDescriptor, constraintId ); } - @Override - public long getId() - { - return id; - } - @Override public int length() { diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/SchemaRuleSerialization.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/SchemaRuleSerialization.java index 11b074b9d8205..2c0eda6ee3c66 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/SchemaRuleSerialization.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/SchemaRuleSerialization.java @@ -31,6 +31,7 @@ import org.neo4j.kernel.api.schema_new.SchemaProcessor; import org.neo4j.kernel.api.schema_new.constaints.ConstraintDescriptor; import org.neo4j.kernel.api.schema_new.constaints.ConstraintDescriptorFactory; +import org.neo4j.kernel.api.schema_new.constaints.UniquenessConstraintDescriptor; import org.neo4j.kernel.api.schema_new.index.NewIndexDescriptor; import org.neo4j.kernel.api.schema_new.index.NewIndexDescriptorFactory; import org.neo4j.storageengine.api.schema.SchemaRule; @@ -126,6 +127,7 @@ public static void serialize( IndexRule indexRule, ByteBuffer target ) } indexDescriptor.schema().processWith( new SchemaDescriptorSerializer( target ) ); + UTF8.putEncodedNullTerminatedStringInto( indexRule.getName(), target ); } /** @@ -157,6 +159,7 @@ public static void serialize( ConstraintRule constraintRule, ByteBuffer target ) } constraintDescriptor.schema().processWith( new SchemaDescriptorSerializer( target ) ); + UTF8.putEncodedNullTerminatedStringInto( constraintRule.getName(), target ); } /** @@ -181,6 +184,7 @@ public static int lengthOf( IndexRule indexRule ) } length += indexDescriptor.schema().computeWith( schemaSizeComputer ); + length += UTF8.computeRequiredNullTerminatedByteBufferSize( indexRule.getName() ); return length; } @@ -202,6 +206,7 @@ public static int lengthOf( ConstraintRule constraintRule ) } length += constraintDescriptor.schema().computeWith( schemaSizeComputer ); + length += UTF8.computeRequiredNullTerminatedByteBufferSize( constraintRule.getName() ); return length; } @@ -214,26 +219,23 @@ private static IndexRule readIndexRule( long id, ByteBuffer source ) throws Malf SchemaIndexProvider.Descriptor indexProvider = readIndexProviderDescriptor( source ); LabelSchemaDescriptor schema; byte indexRuleType = source.get(); + IndexRule rule; switch ( indexRuleType ) { case GENERAL_INDEX: schema = readLabelSchema( source ); - return IndexRule.indexRule( - id, - NewIndexDescriptorFactory.forSchema( schema ), - indexProvider - ); + rule = IndexRule.indexRule( id, NewIndexDescriptorFactory.forSchema( schema ), indexProvider ); + readRuleName( rule, source ); + return rule; case UNIQUE_INDEX: long owningConstraint = source.getLong(); schema = readLabelSchema( source ); - - return IndexRule.constraintIndexRule( - id, - NewIndexDescriptorFactory.uniqueForSchema( schema ), - indexProvider, - owningConstraint == NO_OWNING_CONSTRAINT_YET ? null : owningConstraint - ); + NewIndexDescriptor descriptor = NewIndexDescriptorFactory.uniqueForSchema( schema ); + rule = IndexRule.constraintIndexRule( id, descriptor, indexProvider, + owningConstraint == NO_OWNING_CONSTRAINT_YET ? null : owningConstraint ); + readRuleName( rule, source ); + return rule; default: throw new MalformedSchemaRuleException( format( "Got unknown index rule type '%d'.", indexRuleType ) ); @@ -264,29 +266,38 @@ private static ConstraintRule readConstraintRule( long id, ByteBuffer source ) t { SchemaDescriptor schema; byte constraintRuleType = source.get(); + ConstraintRule rule; switch ( constraintRuleType ) { case EXISTS_CONSTRAINT: schema = readSchema( source ); - return ConstraintRule.constraintRule( - id, - ConstraintDescriptorFactory.existsForSchema( schema ) - ); + rule = ConstraintRule.constraintRule( id, ConstraintDescriptorFactory.existsForSchema( schema ) ); + readRuleName( rule, source ); + return rule; case UNIQUE_CONSTRAINT: long ownedIndex = source.getLong(); schema = readSchema( source ); - return ConstraintRule.constraintRule( - id, - ConstraintDescriptorFactory.uniqueForSchema( schema ), - ownedIndex - ); + UniquenessConstraintDescriptor descriptor = ConstraintDescriptorFactory.uniqueForSchema( schema ); + rule = ConstraintRule.constraintRule( id, descriptor, ownedIndex ); + readRuleName( rule, source ); + return rule; default: throw new MalformedSchemaRuleException( format( "Got unknown constraint rule type '%d'.", constraintRuleType ) ); } } + private static void readRuleName( SchemaRule rule, ByteBuffer source ) + { + String ruleName = UTF8.getDecodedNullTerminatedStringFrom( source ); + if ( ruleName == null || ruleName.isEmpty() ) + { + ruleName = SchemaRule.generateName( rule ); + } + rule.setName( ruleName ); + } + // READ HELP private static SchemaDescriptor readSchema( ByteBuffer source ) throws MalformedSchemaRuleException diff --git a/community/kernel/src/main/java/org/neo4j/storageengine/api/schema/SchemaRule.java b/community/kernel/src/main/java/org/neo4j/storageengine/api/schema/SchemaRule.java index f06b93f863beb..6945cadd714a8 100644 --- a/community/kernel/src/main/java/org/neo4j/storageengine/api/schema/SchemaRule.java +++ b/community/kernel/src/main/java/org/neo4j/storageengine/api/schema/SchemaRule.java @@ -26,23 +26,97 @@ import org.neo4j.kernel.api.schema_new.SchemaDescriptor; import org.neo4j.kernel.api.schema_new.constaints.ConstraintDescriptor; import org.neo4j.kernel.api.schema_new.index.NewIndexDescriptor; +import org.neo4j.kernel.impl.store.record.ConstraintRule; +import org.neo4j.kernel.impl.store.record.IndexRule; import org.neo4j.kernel.impl.store.record.RecordSerializable; /** * Represents a stored schema rule. */ -public interface SchemaRule extends SchemaDescriptor.Supplier, RecordSerializable +public abstract class SchemaRule implements SchemaDescriptor.Supplier, RecordSerializable { + protected final long id; + protected String name; + + protected SchemaRule( long id ) + { + this.id = id; + } + /** * The persistence id for this rule. */ - long getId(); + public final long getId() + { + return this.id; + } + + /** + * @return The (possibly user supplied) name of this schema rule. + */ + public final String getName() + { + if ( name == null ) + { + name = SchemaRule.generateName( this ); + } + return name; + } + + /** + * Set the name of this rule, if it has not been set already. + * + * Note that once a name has been observed - either via this method or via {@link #getName()} – for this rule, + * it cannot be changed. + * @param name The name desired for this rule. + * @return {@code true} if the name was set, {@code false} if this rule already has a name. + */ + public final boolean setName( String name ) + { + if ( this.name == null ) + { + if ( name == null ) + { + return true; + } + if ( name.length() == 0 ) + { + throw new IllegalArgumentException( "Rule name cannot be the empty string" ); + } + for ( int i = 0; i < name.length(); i++ ) + { + char ch = name.charAt( i ); + if ( ch == 0 ) + { + throw new IllegalArgumentException( "Illegal rule name: '" + name + "'" ); + } + } + this.name = name; + return true; + } + return false; + } + + public static String generateName( SchemaRule rule ) + { + Class type = rule.getClass(); + long id = rule.getId(); + if ( type == IndexRule.class ) + { + return "index_" + id; + } + if ( type == ConstraintRule.class ) + { + return "constraint_" + id; + } + return "schema_" + id; + } /** * This enum is used for the legacy schema store, and should not be extended. */ @Deprecated - enum Kind + public enum Kind { INDEX_RULE( "Index" ), CONSTRAINT_INDEX_RULE( "Constraint index" ), diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/store/record/SchemaRuleSerializationTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/store/record/SchemaRuleSerializationTest.java index 9fa585f1962eb..fbe625630a760 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/store/record/SchemaRuleSerializationTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/store/record/SchemaRuleSerializationTest.java @@ -22,6 +22,7 @@ import org.junit.Test; import java.nio.ByteBuffer; +import java.util.Arrays; import java.util.Base64; import java.util.stream.IntStream; @@ -29,12 +30,16 @@ import org.neo4j.kernel.api.index.SchemaIndexProvider; import org.neo4j.kernel.api.schema_new.constaints.ConstraintDescriptor; import org.neo4j.kernel.api.schema_new.constaints.ConstraintDescriptorFactory; +import org.neo4j.kernel.api.schema_new.constaints.UniquenessConstraintDescriptor; import org.neo4j.kernel.api.schema_new.index.NewIndexDescriptor; import org.neo4j.kernel.api.schema_new.index.NewIndexDescriptorFactory; import org.neo4j.storageengine.api.schema.SchemaRule; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.neo4j.test.assertion.Assert.assertException; @@ -75,6 +80,100 @@ public class SchemaRuleSerializationTest extends SchemaRuleTestBase // INDEX RULES + @Test + public void rulesCreatedWithoutNameMustHaveComputedName() throws Exception + { + assertThat( indexRegular.getName(), is( "index_1" ) ); + assertThat( indexUnique.getName(), is( "index_2" ) ); + assertThat( indexCompositeRegular.getName(), is( "index_1" ) ); + assertThat( indexCompositeUnique.getName(), is( "index_2" ) ); + assertThat( indexBigComposite.getName(), is( "index_1" ) ); + assertThat( constraintExistsLabel.getName(), is( "constraint_1" ) ); + assertThat( constraintUniqueLabel.getName(), is( "constraint_2" ) ); + assertThat( constraintExistsRelType.getName(), is( "constraint_2" ) ); + assertThat( constraintCompositeLabel.getName(), is( "constraint_1" ) ); + assertThat( constraintCompositeRelType.getName(), is( "constraint_2" ) ); + + assertThat( SchemaRule.generateName( indexRegular ), is( "index_1" ) ); + assertThat( SchemaRule.generateName( indexUnique ), is( "index_2" ) ); + assertThat( SchemaRule.generateName( indexCompositeRegular ), is( "index_1" ) ); + assertThat( SchemaRule.generateName( indexCompositeUnique ), is( "index_2" ) ); + assertThat( SchemaRule.generateName( indexBigComposite ), is( "index_1" ) ); + assertThat( SchemaRule.generateName( constraintExistsLabel ), is( "constraint_1" ) ); + assertThat( SchemaRule.generateName( constraintUniqueLabel ), is( "constraint_2" ) ); + assertThat( SchemaRule.generateName( constraintExistsRelType ), is( "constraint_2" ) ); + assertThat( SchemaRule.generateName( constraintCompositeLabel ), is( "constraint_1" ) ); + assertThat( SchemaRule.generateName( constraintCompositeRelType ), is( "constraint_2" ) ); + } + + @Test + public void rulesCreatedWithoutNameMustRetainComputedNameAfterDeserialisation() throws Exception + { + assertThat( serialiseAndDeserialise( indexRegular ).getName(), is( "index_1" ) ); + assertThat( serialiseAndDeserialise( indexUnique ).getName(), is( "index_2" ) ); + assertThat( serialiseAndDeserialise( indexCompositeRegular ).getName(), is( "index_1" ) ); + assertThat( serialiseAndDeserialise( indexCompositeUnique ).getName(), is( "index_2" ) ); + assertThat( serialiseAndDeserialise( indexBigComposite ).getName(), is( "index_1" ) ); + assertThat( serialiseAndDeserialise( constraintExistsLabel ).getName(), is( "constraint_1" ) ); + assertThat( serialiseAndDeserialise( constraintUniqueLabel ).getName(), is( "constraint_2" ) ); + assertThat( serialiseAndDeserialise( constraintExistsRelType ).getName(), is( "constraint_2" ) ); + assertThat( serialiseAndDeserialise( constraintCompositeLabel ).getName(), is( "constraint_1" ) ); + assertThat( serialiseAndDeserialise( constraintCompositeRelType ).getName(), is( "constraint_2" ) ); + } + + @Test + public void rulesCreatedWithNameMustRetainGivenNameAfterDeserialisation() throws Exception + { + String name = "custom_rule"; + + assertTrue( indexRegular.setName( name ) ); + assertThat( serialiseAndDeserialise( indexRegular ).getName(), is( name ) ); + assertTrue( indexUnique.setName( name ) ); + assertThat( serialiseAndDeserialise( indexUnique ).getName(), is( name ) ); + assertTrue( indexCompositeRegular.setName( name ) ); + assertThat( serialiseAndDeserialise( indexCompositeRegular ).getName(), is( name ) ); + assertTrue( indexCompositeUnique.setName( name ) ); + assertThat( serialiseAndDeserialise( indexCompositeUnique ).getName(), is( name ) ); + assertTrue( indexBigComposite.setName( name ) ); + assertThat( serialiseAndDeserialise( indexBigComposite ).getName(), is( name ) ); + assertTrue( constraintExistsLabel.setName( name ) ); + assertThat( serialiseAndDeserialise( constraintExistsLabel ).getName(), is( name ) ); + assertTrue( constraintUniqueLabel.setName( name ) ); + assertThat( serialiseAndDeserialise( constraintUniqueLabel ).getName(), is( name ) ); + assertTrue( constraintExistsRelType.setName( name ) ); + assertThat( serialiseAndDeserialise( constraintExistsRelType ).getName(), is( name ) ); + assertTrue( constraintCompositeLabel.setName( name ) ); + assertThat( serialiseAndDeserialise( constraintCompositeLabel ).getName(), is( name ) ); + assertTrue( constraintCompositeRelType.setName( name ) ); + assertThat( serialiseAndDeserialise( constraintCompositeRelType ).getName(), is( name ) ); + } + + @Test + public void settingNameOfUnnamedRuleToNullMustHaveNoEffect() throws Exception + { + assertTrue( indexRegular.setName( null ) ); + assertTrue( indexRegular.setName( null ) ); + assertTrue( indexRegular.setName( null ) ); + assertTrue( indexRegular.setName( "not null" ) ); + assertFalse( indexRegular.setName( "not null" ) ); + assertFalse( indexRegular.setName( "also not null" ) ); + assertFalse( indexRegular.setName( null ) ); + } + + @Test( expected = IllegalArgumentException.class ) + public void nameMustNotContainNullCharacter() throws Exception + { + String name = "a\0b"; + indexRegular.setName( name ); + } + + @Test( expected = IllegalArgumentException.class ) + public void nameMustNotBeTheEmptyString() throws Exception + { + //noinspection RedundantStringConstructorCall + indexRegular.setName( new String( "" ) ); + } + @Test public void shouldSerializeAndDeserializeIndexRules() throws MalformedSchemaRuleException { @@ -130,48 +229,76 @@ public void shouldReturnCorrectLengthForConstraintRules() throws MalformedSchema // BACKWARDS COMPATIBILITY + @Test + public void mustNotBeAbleToSetNameOfUnnamedDeserialisedIndexRule() throws Exception + { + byte[] bytes = decodeBase64( "/////wsAAAAOaW5kZXgtcHJvdmlkZXIAAAAEMjUuMB9bAAACAAABAAAABA==" ); + IndexRule rule = assertIndexRule( SchemaRuleSerialization.deserialize( 24, ByteBuffer.wrap( bytes ) ) ); + assertFalse( "should not be able to set name of deserialised rule", rule.setName( "my_rule" ) ); + assertThat( rule.getName(), is( "index_24" ) ); + } + + @Test + public void mustNotBeAbleToSetNameOfUnnamedDeserialisedConstraintRule() throws Exception + { + byte[] bytes = decodeBase64( "/////ww9WwAAAC0AAQAAADM=" ); + ConstraintRule rule = assertConstraintRule( SchemaRuleSerialization.deserialize( 87, ByteBuffer.wrap( bytes ) ) ); + assertFalse( "should not be able to set name of deserialised rule", rule.setName( "my_rule" ) ); + assertThat( rule.getName(), is( "constraint_87" ) ); + } + @Test public void shouldParseIndexRule() throws Exception { - assertParseIndexRule( "/////wsAAAAOaW5kZXgtcHJvdmlkZXIAAAAEMjUuMB9bAAACAAABAAAABA==" ); - assertParseIndexRule( "AAACAAEAAAAOaW5kZXgtcHJvdmlkZXIAAAAEMjUuMAABAAAAAAAAAAQ=" ); // LEGACY + assertParseIndexRule( "/////wsAAAAOaW5kZXgtcHJvdmlkZXIAAAAEMjUuMB9bAAACAAABAAAABA==", "index_24" ); + assertParseIndexRule( "AAACAAEAAAAOaW5kZXgtcHJvdmlkZXIAAAAEMjUuMAABAAAAAAAAAAQ=", "index_24" ); // LEGACY + assertParseIndexRule( "/////wsAAAAOaW5kZXgtcHJvdmlkZXIAAAAEMjUuMB9bAAACAAABAAAABGN1c3RvbV9uYW1lAA==", "custom_name" ); // named rule + assertParseIndexRule( addNullByte( "/////wsAAAAOaW5kZXgtcHJvdmlkZXIAAAAEMjUuMB9bAAACAAABAAAABA==" ), "index_24" ); // empty name } @Test public void shouldParseUniqueIndexRule() throws Exception { - assertParseUniqueIndexRule( "/////wsAAAAOaW5kZXgtcHJvdmlkZXIAAAAEMjUuMCAAAAAAAAAAC1sAAAA9AAEAAAPc" ); - assertParseUniqueIndexRule( "AAAAPQIAAAAOaW5kZXgtcHJvdmlkZXIAAAAEMjUuMAABAAAAAAAAA9wAAAAAAAAACw==" ); // LEGACY + assertParseUniqueIndexRule( "/////wsAAAAOaW5kZXgtcHJvdmlkZXIAAAAEMjUuMCAAAAAAAAAAC1sAAAA9AAEAAAPc", "index_33" ); + assertParseUniqueIndexRule( "AAAAPQIAAAAOaW5kZXgtcHJvdmlkZXIAAAAEMjUuMAABAAAAAAAAA9wAAAAAAAAACw==", "index_33" ); // LEGACY + assertParseUniqueIndexRule( "/////wsAAAAOaW5kZXgtcHJvdmlkZXIAAAAEMjUuMCAAAAAAAAAAC1sAAAA9AAEAAAPcY3VzdG9tX25hbWUA", "custom_name" ); // named rule + assertParseUniqueIndexRule( addNullByte( "/////wsAAAAOaW5kZXgtcHJvdmlkZXIAAAAEMjUuMCAAAAAAAAAAC1sAAAA9AAEAAAPc" ), "index_33" ); // empty name } @Test public void shouldParseUniqueConstraintRule() throws Exception { - assertParseUniqueConstraintRule( "/////ww+AAAAAAAAAAJbAAAANwABAAAAAw==" ); - assertParseUniqueConstraintRule( "AAAANwMBAAAAAAAAAAMAAAAAAAAAAg==" ); // LEGACY + assertParseUniqueConstraintRule( "/////ww+AAAAAAAAAAJbAAAANwABAAAAAw==", "constraint_1" ); + assertParseUniqueConstraintRule( "AAAANwMBAAAAAAAAAAMAAAAAAAAAAg==", "constraint_1" ); // LEGACY + assertParseUniqueConstraintRule( "/////ww+AAAAAAAAAAJbAAAANwABAAAAA2N1c3RvbV9uYW1lAA==", "custom_name" ); // named rule + assertParseUniqueConstraintRule( addNullByte( "/////ww+AAAAAAAAAAJbAAAANwABAAAAAw==" ), "constraint_1" ); // empty name } @Test public void shouldParseNodePropertyExistsRule() throws Exception { - assertParseNodePropertyExistsRule( "/////ww9WwAAAC0AAQAAADM=" ); - assertParseNodePropertyExistsRule( "AAAALQQAAAAz" ); // LEGACY + assertParseNodePropertyExistsRule( "/////ww9WwAAAC0AAQAAADM=", "constraint_87" ); + assertParseNodePropertyExistsRule( "AAAALQQAAAAz", "constraint_87" ); // LEGACY + assertParseNodePropertyExistsRule( "/////ww9WwAAAC0AAQAAADNjdXN0b21fbmFtZQA=", "custom_name" ); // named rule + assertParseNodePropertyExistsRule( addNullByte( "/////ww9WwAAAC0AAQAAADM=" ), "constraint_87" ); // empty name } @Test public void shouldParseRelationshipPropertyExistsRule() throws Exception { - assertParseRelationshipPropertyExistsRule( "/////ww9XAAAIUAAAQAAF+c=" ); // LEGACY6 - assertParseRelationshipPropertyExistsRule( "AAAhQAUAABfn" ); // LEGACY6 + assertParseRelationshipPropertyExistsRule( "/////ww9XAAAIUAAAQAAF+c=", "constraint_51" ); + assertParseRelationshipPropertyExistsRule( "AAAhQAUAABfn", "constraint_51" ); // LEGACY6 + assertParseRelationshipPropertyExistsRule( "/////ww9XAAAIUAAAQAAF+djdXN0b21fbmFtZQA=", "custom_name" ); // named rule + assertParseRelationshipPropertyExistsRule( addNullByte( "/////ww9XAAAIUAAAQAAF+c=" ), "constraint_51" ); // empty name } - private void assertParseIndexRule( String serialized ) throws Exception + private void assertParseIndexRule( String serialized, String name ) throws Exception { // GIVEN long ruleId = 24; NewIndexDescriptor index = NewIndexDescriptorFactory.forLabel( 512, 4 ); SchemaIndexProvider.Descriptor indexProvider = new SchemaIndexProvider.Descriptor( "index-provider", "25.0" ); - byte[] bytes = Base64.getDecoder().decode( serialized ); + byte[] bytes = decodeBase64( serialized ); // WHEN IndexRule deserialized = assertIndexRule( SchemaRuleSerialization.deserialize( ruleId, ByteBuffer.wrap( bytes ) ) ); @@ -181,17 +308,18 @@ private void assertParseIndexRule( String serialized ) throws Exception assertThat( deserialized.getIndexDescriptor(), equalTo( index ) ); assertThat( deserialized.schema(), equalTo( index.schema() ) ); assertThat( deserialized.getProviderDescriptor(), equalTo( indexProvider ) ); + assertThat( deserialized.getName(), is( name ) ); assertException( deserialized::getOwningConstraint, IllegalStateException.class, "" ); } - private void assertParseUniqueIndexRule( String serialized ) throws MalformedSchemaRuleException + private void assertParseUniqueIndexRule( String serialized, String name ) throws MalformedSchemaRuleException { // GIVEN long ruleId = 33; long constraintId = 11; NewIndexDescriptor index = NewIndexDescriptorFactory.uniqueForLabel( 61, 988 ); SchemaIndexProvider.Descriptor indexProvider = new SchemaIndexProvider.Descriptor( "index-provider", "25.0" ); - byte[] bytes = Base64.getDecoder().decode( serialized ); + byte[] bytes = decodeBase64( serialized ); // WHEN IndexRule deserialized = assertIndexRule( SchemaRuleSerialization.deserialize( ruleId, ByteBuffer.wrap( bytes ) ) ); @@ -202,17 +330,18 @@ private void assertParseUniqueIndexRule( String serialized ) throws MalformedSch assertThat( deserialized.schema(), equalTo( index.schema() ) ); assertThat( deserialized.getProviderDescriptor(), equalTo( indexProvider ) ); assertThat( deserialized.getOwningConstraint(), equalTo( constraintId ) ); + assertThat( deserialized.getName(), is( name ) ); } - private void assertParseUniqueConstraintRule( String serialized ) throws MalformedSchemaRuleException + private void assertParseUniqueConstraintRule( String serialized, String name ) throws MalformedSchemaRuleException { // GIVEN long ruleId = 1; int propertyKey = 3; int labelId = 55; long ownedIndexId = 2; - ConstraintDescriptor constraint = ConstraintDescriptorFactory.uniqueForLabel( labelId, propertyKey ); - byte[] bytes = Base64.getDecoder().decode( serialized ); + UniquenessConstraintDescriptor constraint = ConstraintDescriptorFactory.uniqueForLabel( labelId, propertyKey ); + byte[] bytes = decodeBase64( serialized ); // WHEN ConstraintRule deserialized = assertConstraintRule( SchemaRuleSerialization.deserialize( ruleId, ByteBuffer.wrap( bytes ) ) ); @@ -222,16 +351,17 @@ private void assertParseUniqueConstraintRule( String serialized ) throws Malform assertThat( deserialized.getConstraintDescriptor(), equalTo( constraint ) ); assertThat( deserialized.schema(), equalTo( constraint.schema() ) ); assertThat( deserialized.getOwnedIndex(), equalTo( ownedIndexId ) ); + assertThat( deserialized.getName(), is( name ) ); } - private void assertParseNodePropertyExistsRule( String serialized ) throws Exception + private void assertParseNodePropertyExistsRule( String serialized, String name ) throws Exception { // GIVEN long ruleId = 87; int propertyKey = 51; int labelId = 45; ConstraintDescriptor constraint = ConstraintDescriptorFactory.existsForLabel( labelId, propertyKey ); - byte[] bytes = Base64.getDecoder().decode( serialized ); + byte[] bytes = decodeBase64( serialized ); // WHEN ConstraintRule deserialized = assertConstraintRule( SchemaRuleSerialization.deserialize( ruleId, ByteBuffer.wrap( bytes ) ) ); @@ -241,16 +371,17 @@ private void assertParseNodePropertyExistsRule( String serialized ) throws Excep assertThat( deserialized.getConstraintDescriptor(), equalTo( constraint ) ); assertThat( deserialized.schema(), equalTo( constraint.schema() ) ); assertException( deserialized::getOwnedIndex, IllegalStateException.class, "" ); + assertThat( deserialized.getName(), is( name ) ); } - private void assertParseRelationshipPropertyExistsRule( String serialized ) throws Exception + private void assertParseRelationshipPropertyExistsRule( String serialized, String name ) throws Exception { // GIVEN long ruleId = 51; int propertyKey = 6119; int relTypeId = 8512; ConstraintDescriptor constraint = ConstraintDescriptorFactory.existsForRelType( relTypeId, propertyKey ); - byte[] bytes = Base64.getDecoder().decode( serialized ); + byte[] bytes = decodeBase64( serialized ); // WHEN ConstraintRule deserialized = assertConstraintRule( SchemaRuleSerialization.deserialize( ruleId, ByteBuffer.wrap( bytes ) ) ); @@ -260,6 +391,7 @@ private void assertParseRelationshipPropertyExistsRule( String serialized ) thro assertThat( deserialized.getConstraintDescriptor(), equalTo( constraint ) ); assertThat( deserialized.schema(), equalTo( constraint.schema() ) ); assertException( deserialized::getOwnedIndex, IllegalStateException.class, "" ); + assertThat( deserialized.getName(), is( name ) ); } // HELPERS @@ -267,15 +399,8 @@ private void assertParseRelationshipPropertyExistsRule( String serialized ) thro private void assertSerializeAndDeserializeIndexRule( IndexRule indexRule ) throws MalformedSchemaRuleException { - // GIVEN - ByteBuffer buffer = ByteBuffer.allocate( VERY_LARGE_CAPACITY ); + IndexRule deserialized = assertIndexRule( serialiseAndDeserialise( indexRule ) ); - // WHEN - SchemaRuleSerialization.serialize( indexRule, buffer ); - buffer.flip(); - IndexRule deserialized = assertIndexRule( SchemaRuleSerialization.deserialize( indexRule.getId(), buffer ) ); - - // THEN assertThat( deserialized.getId(), equalTo( indexRule.getId() ) ); assertThat( deserialized.getIndexDescriptor(), equalTo( indexRule.getIndexDescriptor() ) ); assertThat( deserialized.schema(), equalTo( indexRule.schema() ) ); @@ -294,21 +419,29 @@ private IndexRule assertIndexRule( SchemaRule schemaRule ) private void assertSerializeAndDeserializeConstraintRule( ConstraintRule constraintRule ) throws MalformedSchemaRuleException { - // GIVEN - ByteBuffer buffer = ByteBuffer.allocate( VERY_LARGE_CAPACITY ); - - // WHEN - SchemaRuleSerialization.serialize( constraintRule, buffer ); - buffer.flip(); - ConstraintRule deserialized = - assertConstraintRule( SchemaRuleSerialization.deserialize( constraintRule.getId(), buffer ) ); + ConstraintRule deserialized = assertConstraintRule( serialiseAndDeserialise( constraintRule ) ); - // THEN assertThat( deserialized.getId(), equalTo( constraintRule.getId() ) ); assertThat( deserialized.getConstraintDescriptor(), equalTo( constraintRule.getConstraintDescriptor() ) ); assertThat( deserialized.schema(), equalTo( constraintRule.schema() ) ); } + private SchemaRule serialiseAndDeserialise( ConstraintRule constraintRule ) throws MalformedSchemaRuleException + { + ByteBuffer buffer = ByteBuffer.allocate( VERY_LARGE_CAPACITY ); + SchemaRuleSerialization.serialize( constraintRule, buffer ); + buffer.flip(); + return SchemaRuleSerialization.deserialize( constraintRule.getId(), buffer ); + } + + private SchemaRule serialiseAndDeserialise( IndexRule indexRule ) throws MalformedSchemaRuleException + { + ByteBuffer buffer = ByteBuffer.allocate( VERY_LARGE_CAPACITY ); + SchemaRuleSerialization.serialize( indexRule, buffer ); + buffer.flip(); + return SchemaRuleSerialization.deserialize( indexRule.getId(), buffer ); + } + private ConstraintRule assertConstraintRule( SchemaRule schemaRule ) { if ( !(schemaRule instanceof ConstraintRule) ) @@ -337,4 +470,31 @@ private void assertCorrectLength( ConstraintRule constraintRule ) throws Malform // THEN assertThat( SchemaRuleSerialization.lengthOf( constraintRule ), equalTo( buffer.position() ) ); } + + private byte[] decodeBase64( String serialized ) + { + return Base64.getDecoder().decode( serialized ); + } + + private String encodeBase64( byte[] bytes ) + { + return Base64.getEncoder().encodeToString( bytes ); + } + + /** + * Used to append a null-byte to the end of the base64 input and return the resulting base64 output. + * The reason we need this, is because the rule names are null-terminated strings at the end of the encoded + * schema rules. + * By appending a null-byte, we effectively an empty string as the rule name. However, this is not really an + * allowed rule name, so when we deserialise these rules, we should get the generated rule name back. + * This can potentially be used in the future in case we don't want to give a rule a name, but still want to put + * fields after where the name would be. + * In that case, a single null-byte would suffice to indicate that the name field is (almost) not there. + */ + private String addNullByte( String input ) + { + byte[] inputBytes = decodeBase64( input ); + byte[] outputBytes = Arrays.copyOf( inputBytes, inputBytes.length + 1 ); + return encodeBase64( outputBytes ); + } } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/store/record/SchemaRuleTestBase.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/store/record/SchemaRuleTestBase.java index 747ea0739b8d0..3203b1cb50399 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/store/record/SchemaRuleTestBase.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/store/record/SchemaRuleTestBase.java @@ -26,7 +26,7 @@ abstract class SchemaRuleTestBase { protected static final long RULE_ID = 1; - protected static final long RULE_ID_2 = 1; + protected static final long RULE_ID_2 = 2; protected static final int LABEL_ID = 10; protected static final int REL_TYPE_ID = 20; protected static final int PROPERTY_ID_1 = 30;