diff --git a/community/values/src/main/java/org/neo4j/values/storable/StringValue.java b/community/values/src/main/java/org/neo4j/values/storable/StringValue.java index cf7cbe2367dd..890134819989 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/StringValue.java +++ b/community/values/src/main/java/org/neo4j/values/storable/StringValue.java @@ -92,13 +92,6 @@ public TextValue replace( String find, String replace ) return Values.stringValue( value().replace( find, replace ) ); } - @Override - public TextValue reverse() - { - StringBuilder stringBuilder = new StringBuilder( value() ); - return Values.stringValue( stringBuilder.reverse().toString() ); - } - @Override public Object asObjectCopy() { @@ -184,6 +177,37 @@ public TextValue rtrim() return this; } + @Override + public TextValue reverse() + { + return this; + } + + @Override + public TextValue toLower() + { + return this; + } + + @Override + public TextValue toUpper() + { + return this; + } + + @Override + public TextValue replace( String find, String replace ) + { + if ( find.isEmpty() ) + { + return Values.stringValue( replace ); + } + else + { + return this; + } + } + @Override public int compareTo( TextValue other ) { diff --git a/community/values/src/main/java/org/neo4j/values/storable/StringWrappingStringValue.java b/community/values/src/main/java/org/neo4j/values/storable/StringWrappingStringValue.java index b9a162f553d1..282f3647f736 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/StringWrappingStringValue.java +++ b/community/values/src/main/java/org/neo4j/values/storable/StringWrappingStringValue.java @@ -95,6 +95,13 @@ public TextValue rtrim() return Values.stringValue( value.substring( 0, end ) ); } + @Override + public TextValue reverse() + { + StringBuilder stringBuilder = new StringBuilder( value() ); + return Values.stringValue( stringBuilder.reverse().toString() ); + } + private int ltrimIndex( String value ) { int start = 0, length = value.length(); diff --git a/community/values/src/main/java/org/neo4j/values/storable/UTF8StringValue.java b/community/values/src/main/java/org/neo4j/values/storable/UTF8StringValue.java index 3d89b5ba01d3..8d0047e6e56c 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/UTF8StringValue.java +++ b/community/values/src/main/java/org/neo4j/values/storable/UTF8StringValue.java @@ -284,7 +284,6 @@ public TextValue reverse() int i = offset, len = offset + byteLength; byte[] newValues = new byte[byteLength]; - int newIndex = byteLength -1; while ( i < len ) { byte b = values[i]; @@ -292,8 +291,9 @@ public TextValue reverse() //we are dealing with an ascii value and use a single byte for storing the value. if ( b >= 0 ) { - newValues[newIndex] = b; - newIndex--; + //a single byte is trivial to reverse + //just put it at the opposite end of the new array + newValues[len - 1 - i] = b; i++; continue; } @@ -310,9 +310,11 @@ public TextValue reverse() bytesNeeded++; b = (byte) (b << 1); } - System.arraycopy( values, i, newValues, newIndex - bytesNeeded + 1, bytesNeeded ); + //reversing when multiple bytes are needed for the code point we cannot just reverse + //since we need to preserve the code point while moving it, + //e.g. [A, b1,b2, B] -> [B, b1,b2, A] + System.arraycopy( values, i, newValues, len - i - bytesNeeded, bytesNeeded ); i += bytesNeeded; - newIndex -= bytesNeeded; } return new UTF8StringValue( newValues, 0, newValues.length ); diff --git a/community/values/src/test/java/org/neo4j/values/storable/TextValueTest.java b/community/values/src/test/java/org/neo4j/values/storable/TextValueTest.java index 429d341a155d..ac32cab0f70c 100644 --- a/community/values/src/test/java/org/neo4j/values/storable/TextValueTest.java +++ b/community/values/src/test/java/org/neo4j/values/storable/TextValueTest.java @@ -56,6 +56,8 @@ public void replace() assertThat( value.apply( "hello" ).replace( "l", "w" ), equalTo( value.apply( "hewwo" ) ) ); assertThat( value.apply( "hello" ).replace( "ell", "ipp" ), equalTo( value.apply( "hippo" ) ) ); assertThat( value.apply( "hello" ).replace( "a", "x" ), equalTo( value.apply( "hello" ) ) ); + assertThat( value.apply( "hello" ).replace( "e", "" ), equalTo( value.apply( "hllo" ) ) ); + assertThat( value.apply( "" ).replace( "", "⁻" ), equalTo( value.apply( "⁻" ) ) ); } @Test @@ -68,10 +70,10 @@ public void substring() assertThat( value.apply( "0123456789" ).substring( 1 ), equalTo( value.apply( "123456789" ) ) ); assertThat( value.apply( "0123456789" ).substring( 5 ), equalTo( value.apply( "56789" ) ) ); assertThat( value.apply( "0123456789" ).substring( 15 ), equalTo( StringValue.EMTPY ) ); - assertThat( value.apply( "\uD83D\uDE21\uD83D\uDE21\uD83D\uDE21" ).substring( 1, 1 ), - equalTo( value.apply( "\uD83D\uDE21" ) ) ); - assertThat( value.apply( "\uD83D\uDE21\uD83D\uDE21\uD83D\uDE21" ).substring( 1, 2 ), - equalTo( value.apply( "\uD83D\uDE21\uD83D\uDE21" ) ) ); + assertThat( value.apply( "\uD83D\uDE21\uD83D\uDCA9\uD83D\uDC7B" ).substring( 1, 1 ), + equalTo( value.apply( "\uD83D\uDCA9" ) ) ); + assertThat( value.apply( "\uD83D\uDE21\uD83D\uDCA9\uD83D\uDC7B" ).substring( 1, 2 ), + equalTo( value.apply( "\uD83D\uDCA9\uD83D\uDC7B" ) ) ); exception.expect( IndexOutOfBoundsException.class ); value.apply( "hello" ).substring( -4, 2 ); @@ -83,6 +85,7 @@ public void toLower() assertThat( value.apply( "HELLO" ).toLower(), equalTo( value.apply( "hello" ) ) ); assertThat( value.apply( "Hello" ).toLower(), equalTo( value.apply( "hello" ) ) ); assertThat( value.apply( "hello" ).toLower(), equalTo( value.apply( "hello" ) ) ); + assertThat( value.apply( "" ).toLower(), equalTo( value.apply( "" ) ) ); } @Test @@ -91,6 +94,7 @@ public void toUpper() assertThat( value.apply( "HELLO" ).toUpper(), equalTo( value.apply( "HELLO" ) ) ); assertThat( value.apply( "Hello" ).toUpper(), equalTo( value.apply( "HELLO" ) ) ); assertThat( value.apply( "hello" ).toUpper(), equalTo( value.apply( "HELLO" ) ) ); + assertThat( value.apply( "" ).toUpper(), equalTo( value.apply( "" ) ) ); } @Test @@ -124,11 +128,15 @@ public void trim() @Test public void reverse() { -// assertThat( value.apply( "Foo" ).reverse(), equalTo( value.apply( "ooF" ) ) ); -// assertThat( value.apply( "" ).reverse(), equalTo( StringValue.EMTPY ) ); -// assertThat( value.apply( " L" ).reverse(), equalTo( value.apply( "L " ) ) ); -// assertThat( value.apply( "\r\n" ).reverse(), equalTo( value.apply( "\n\r" ) ) ); + assertThat( value.apply( "Foo" ).reverse(), equalTo( value.apply( "ooF" ) ) ); + assertThat( value.apply( "" ).reverse(), equalTo( StringValue.EMTPY ) ); + assertThat( value.apply( " L" ).reverse(), equalTo( value.apply( "L " ) ) ); + assertThat( value.apply( "\r\n" ).reverse(), equalTo( value.apply( "\n\r" ) ) ); assertThat( value.apply( "\uD801\uDC37" ).reverse(), equalTo( value.apply( "\uD801\uDC37" ) ) ); + assertThat( value.apply( "This is literally a pile of crap \uD83D\uDCA9, it is fantastic" ).reverse(), + equalTo( value.apply( "citsatnaf si ti ,\uD83D\uDCA9 parc fo elip a yllaretil si sihT" ) ) ); + assertThat( value.apply( "\uD83D\uDE21\uD83D\uDCA9\uD83D\uDC7B" ).reverse(), equalTo( value.apply( + "\uD83D\uDC7B\uD83D\uDCA9\uD83D\uDE21" ) ) ); } @Test @@ -137,5 +145,7 @@ public void split() assertThat( value.apply( "HELLO" ).split( "LL" ), equalTo( stringArray( "HE", "O" ) ) ); assertThat( value.apply( "Separating,by,comma,is,a,common,use,case" ).split( "," ), equalTo( stringArray( "Separating", "by", "comma", "is", "a", "common", "use", "case" ) ) ); + assertThat( value.apply( "HELLO" ).split( "HELLO" ), equalTo( stringArray( "", "" ) ) ); + } } diff --git a/enterprise/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/TextValueSpecification.scala b/enterprise/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/TextValueSpecification.scala index 3e76b3f1aed5..86d4f1848cd6 100644 --- a/enterprise/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/TextValueSpecification.scala +++ b/enterprise/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/TextValueSpecification.scala @@ -88,25 +88,33 @@ object TextValueSpecification extends Properties("TextValue") with Configuration equivalent(value.toUpper, utf8.toUpper) }} - property("replace") = forAll { (x: String, find: String, replace: String) => { - val value = stringValue(x) - val utf8 = utf8Value(x.getBytes(StandardCharsets.UTF_8)) - equivalent(stringValue(x.replace(find, replace)), value.replace(find, replace)) && - equivalent(value.replace(find, replace), utf8.replace(find, replace)) - }} + private val replaceGen = for {x <- Arbitrary.arbitrary[String] + find <-Gen.alphaStr + replace <- Arbitrary.arbitrary[String]} yield (x,find, replace) + property("replace") = forAll(replaceGen) { + case (x: String, find: String, replace: String) => + val value = stringValue(x) + val utf8 = utf8Value(x.getBytes(StandardCharsets.UTF_8)) + equivalent(stringValue(x.replace(find, replace)), value.replace(find, replace)) && + equivalent(value.replace(find, replace), utf8.replace(find, replace)) + } - property("split") = forAll { (x: String, find: String) => { - val value = stringValue(x) - val utf8 = utf8Value(x.getBytes(StandardCharsets.UTF_8)) - val split = x.split(find) - if (x != find) { - stringArray(split: _*) == value.split(find) && - value.split(find) == utf8.split(find) - } else { - value.split(find) == utf8.split(find) && value.split(find) == stringArray("", "") - } + private val splitGen = for {x <- Arbitrary.arbitrary[String] + find <-Gen.alphaStr} yield (x,find) + + property("split") = forAll(splitGen) { + case (x, find) => + val value = stringValue(x) + val utf8 = utf8Value(x.getBytes(StandardCharsets.UTF_8)) + val split = x.split(find) + if (x != find) { + stringArray(split: _*) == value.split(find) && + value.split(find) == utf8.split(find) + } else { + value.split(find) == utf8.split(find) && value.split(find) == stringArray("", "") + } - }} + } property("compareTo") = forAll { (x: String, y: String) => val stringX = stringValue(x)