Skip to content

Commit

Permalink
JAVA-2338: Revisit toString() for data container types
Browse files Browse the repository at this point in the history
  • Loading branch information
olim7t committed Jul 26, 2019
1 parent ca4a7e3 commit 321696b
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 11 deletions.
1 change: 1 addition & 0 deletions changelog/README.md
Expand Up @@ -4,6 +4,7 @@

### 4.2.0 (in progress)

- [improvement] JAVA-2338: Revisit toString() for data container types
- [bug] JAVA-2367: Fix column names in EntityHelper.updateByPrimaryKey
- [bug] JAVA-2358: Fix list of reserved CQL keywords
- [improvement] JAVA-2359: Allow default keyspace at the mapper level
Expand Down
15 changes: 15 additions & 0 deletions core/revapi.json
Expand Up @@ -4794,6 +4794,21 @@
"new": "method java.util.Spliterator<ElementT> com.datastax.oss.driver.api.core.PagingIterable<ElementT>::spliterator() @ com.datastax.oss.driver.api.core.cql.ResultSet",
"annotation": "@edu.umd.cs.findbugs.annotations.NonNull",
"justification": "JAVA-2247: PagingIterable implementations should implement spliterator()"
},
{
"code": "java.method.addedToInterface",
"new": "method java.lang.String com.datastax.oss.driver.api.core.cql.Row::toString()",
"justification": "False positive -- all objects implicitly have toString()"
},
{
"code": "java.method.addedToInterface",
"new": "method java.lang.String com.datastax.oss.driver.api.core.data.TupleValue::toString()",
"justification": "False positive -- all objects implicitly have toString()"
},
{
"code": "java.method.addedToInterface",
"new": "method java.lang.String com.datastax.oss.driver.api.core.data.UdtValue::toString()",
"justification": "False positive -- all objects implicitly have toString()"
}
]
}
Expand Down
52 changes: 52 additions & 0 deletions core/src/main/java/com/datastax/oss/driver/api/core/cql/Row.java
Expand Up @@ -19,6 +19,7 @@
import com.datastax.oss.driver.api.core.data.GettableByIndex;
import com.datastax.oss.driver.api.core.data.GettableByName;
import com.datastax.oss.driver.api.core.detach.Detachable;
import com.datastax.oss.driver.api.core.type.codec.TypeCodec;
import edu.umd.cs.findbugs.annotations.NonNull;

/**
Expand All @@ -33,4 +34,55 @@ public interface Row extends GettableByIndex, GettableByName, GettableById, Deta
/** @return the column definitions contained in this result set. */
@NonNull
ColumnDefinitions getColumnDefinitions();

/**
* Returns a string representation of the contents of this row.
*
* <p>This produces a comma-separated list enclosed in square brackets. Each column is represented
* by its name, followed by a column and the value as a CQL literal. For example:
*
* <pre>
* [id:1, name:'test']
* </pre>
*
* Notes:
*
* <ul>
* <li>This method does not sanitize its output in any way. In particular, no effort is made to
* limit output size: all columns are included, and large strings or blobs will be appended
* as-is.
* <li>Be mindful of how you expose the result. For example, in high-security environments, it
* might be undesirable to leak data in application logs.
* </ul>
*/
@NonNull
default String getFormattedContents() {
StringBuilder result = new StringBuilder("[");
ColumnDefinitions definitions = getColumnDefinitions();
for (int i = 0; i < definitions.size(); i++) {
if (i > 0) {
result.append(", ");
}
ColumnDefinition definition = definitions.get(i);
String name = definition.getName().asCql(true);
TypeCodec<Object> codec = codecRegistry().codecFor(definition.getType());
Object value = codec.decode(getBytesUnsafe(i), protocolVersion());
result.append(name).append(':').append(codec.format(value));
}
return result.append("]").toString();
}

/**
* Returns an abstract representation of this object, <b>that may not include the row's
* contents</b>.
*
* <p>The driver's built-in {@link Row} implementation returns the default format of {@link
* Object#toString()}: the class name, followed by the at-sign and the hash code of the object.
*
* <p>Omitting the contents was a deliberate choice, because we feel it would make it too easy to
* accidentally leak data (e.g. in application logs). If you want the contents, use {@link
* #getFormattedContents()}.
*/
@Override
String toString();
}
Expand Up @@ -34,4 +34,42 @@ public interface TupleValue extends GettableByIndex, SettableByIndex<TupleValue>

@NonNull
TupleType getType();

/**
* Returns a string representation of the contents of this tuple.
*
* <p>This produces a CQL literal, for example:
*
* <pre>
* (1,'test')
* </pre>
*
* Notes:
*
* <ul>
* <li>This method does not sanitize its output in any way. In particular, no effort is made to
* limit output size: all fields are included, and large strings or blobs will be appended
* as-is.
* <li>Be mindful of how you expose the result. For example, in high-security environments, it
* might be undesirable to leak data in application logs.
* </ul>
*/
@NonNull
default String getFormattedContents() {
return codecRegistry().codecFor(getType()).format(this);
}

/**
* Returns an abstract representation of this object, <b>that may not include the tuple's
* contents</b>.
*
* <p>The driver's built-in {@link TupleValue} implementation returns the default format of {@link
* Object#toString()}: the class name, followed by the at-sign and the hash code of the object.
*
* <p>Omitting the contents was a deliberate choice, because we feel it would make it too easy to
* accidentally leak data (e.g. in application logs). If you want the contents, use {@link
* #getFormattedContents()}.
*/
@Override
String toString();
}
Expand Up @@ -35,4 +35,42 @@ public interface UdtValue

@NonNull
UserDefinedType getType();

/**
* Returns a string representation of the contents of this UDT.
*
* <p>This produces a CQL literal, for example:
*
* <pre>
* {street:'42 Main Street',zip:12345}
* </pre>
*
* Notes:
*
* <ul>
* <li>This method does not sanitize its output in any way. In particular, no effort is made to
* limit output size: all fields are included, and large strings or blobs will be appended
* as-is.
* <li>Be mindful of how you expose the result. For example, in high-security environments, it
* might be undesirable to leak data in application logs.
* </ul>
*/
@NonNull
default String getFormattedContents() {
return codecRegistry().codecFor(getType()).format(this);
}

/**
* Returns an abstract representation of this object, <b>that may not include the UDT's
* contents</b>.
*
* <p>The driver's built-in {@link UdtValue} implementation returns the default format of {@link
* Object#toString()}: the class name, followed by the at-sign and the hash code of the object.
*
* <p>Omitting the contents was a deliberate choice, because we feel it would make it too easy to
* accidentally leak data (e.g. in application logs). If you want the contents, use {@link
* #getFormattedContents()}.
*/
@Override
String toString();
}
Expand Up @@ -167,11 +167,6 @@ public int hashCode() {
return result;
}

@Override
public String toString() {
return codecRegistry().codecFor(type).format(this);
}

private static class SerializationProxy implements Serializable {

private static final long serialVersionUID = 1;
Expand Down
Expand Up @@ -171,11 +171,6 @@ public int hashCode() {
return result;
}

@Override
public String toString() {
return codecRegistry().codecFor(type).format(this);
}

/**
* @serialData The type of the tuple, followed by an array of byte arrays representing the values
* (null values are represented by {@code null}).
Expand Down
Expand Up @@ -123,7 +123,7 @@ public void should_format_to_string() {

UdtValue udt = type.newValue().setString("t", "foobar").setDouble("d", 3.14);

assertThat(udt.toString()).isEqualTo("{t:'foobar',i:NULL,d:3.14}");
assertThat(udt.getFormattedContents()).isEqualTo("{t:'foobar',i:NULL,d:3.14}");
}

@Test
Expand Down

0 comments on commit 321696b

Please sign in to comment.