Skip to content

BigDecimal and BigInteger can be serialized#85

Closed
ghajba wants to merge 7 commits intofirebase:masterfrom
ghajba:master
Closed

BigDecimal and BigInteger can be serialized#85
ghajba wants to merge 7 commits intofirebase:masterfrom
ghajba:master

Conversation

@ghajba
Copy link

@ghajba ghajba commented Oct 8, 2017

This is my solution to #75.

Because I didn't get any response and I need this change, I've solved it like this. But as mentioned in the related issue, I am open to suggestions.

And a question: when will be changes released that we can use the latest version instead of a snapshot?

@googlebot
Copy link

Thanks for your pull request. It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please visit https://cla.developers.google.com/ to sign.

Once you've signed, please reply here (e.g. I signed it!) and we'll verify. Thanks.


  • If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address. Check your existing CLA data and verify that your email is set on your git commits.
  • If your company signed a CLA, they designated a Point of Contact who decides which employees are authorized to participate. You may need to contact the Point of Contact for your company and ask to be added to the group of authorized contributors. If you don't know who your Point of Contact is, direct the project maintainer to go/cla#troubleshoot.
  • In order to pass this check, please resolve this problem and have the pull request author add another comment and the bot will run again.

@ghajba
Copy link
Author

ghajba commented Oct 8, 2017 via email

@googlebot
Copy link

CLAs look good, thanks!

Copy link
Contributor

@hiranya911 hiranya911 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the patch. Can you address a couple of questions, and also provide some test cases? See MapperTest, MutableDataTest and DataSnapshotTest.

} else if (value instanceof Boolean) {
return new BooleanNode((Boolean) value, priority);
} else if (value instanceof BigDecimal) {
return new DoubleNode(((BigDecimal) value).doubleValue(), priority);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lossy conversion. What happens when the BigDecimal is too large to fit in a double?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think BigDecimals should be serialized to String. The entire point of using BigDecimals is to avoid the rounding errors inherent in floating point representation.

} else if (value instanceof BigDecimal) {
return new DoubleNode(((BigDecimal) value).doubleValue(), priority);
} else if (value instanceof BigInteger) {
return new LongNode(((BigInteger)value).longValue(), priority);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question applies here. What happens when the BigInteger is too large to fit in a long?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto for BigInteger -- we should serialize these to String to maintain the full representation.

@ghajba
Copy link
Author

ghajba commented Oct 9, 2017

I know that these are lossy conversions. If the values don't fit, they'll be converted to infinity (positive or negative) for both types.

But I've already asked in #75 how the conversion should happen. After we clear this, I will adjust the code and write the tests too.

@hiranya911
Copy link
Contributor

I think you will have to implement a whole new Node type to support these types. In there you can just hold on to the string representation of the numbers.

@ghajba
Copy link
Author

ghajba commented Oct 9, 2017

Just to let you know: I am working on the changes but at a slow pace. I hope I can push my changes this week.

added new node types
I have still no idea how the tests should work.
Copy link
Contributor

@hiranya911 hiranya911 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ghajba

Implementation looks pretty close to what I had in mind. Just some minor comments. Also please add the test cases.


@Override
protected LeafType getLeafType() {
return LeafType.String;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this should be LeafType.Number

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And as a heads up, you will have to revise how leaf nodes are compared in the parent LeafNode.compareTo() method. You will see that longs and doubles have been special cased there. Perhaps you can implement a new comparison method for all number types, which converts values to BigDecimal and then does the comparison.


@Override
public String getHashRepresentation(HashVersion version) {
return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should do something similar to what the StringNode does.

serverCache = currentSyncPoint.getCompleteServerCache(relativePath);
}
} while (!pathToFollow.isEmpty() && serverCache == null);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert to keep the change footprint small as possible.

@@ -0,0 +1,60 @@
package com.google.firebase.database.snapshot;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add license header.

@@ -0,0 +1,60 @@
package com.google.firebase.database.snapshot;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lot of the comments made on BigDecimalNode are also applicable here. Please revise as appropriate.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add the tests, but I don't see the idea behind how you test your code. The structure is not clear to find an easy entry-point.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NodeTest might be a good place to start. Also check MapperTest. See if you can serialize/deserialize some made up big integers and big decimals via the new code.


@Override
public Object getValue() {
return this.value;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this return a BigDecimal? You can reconstruct the original value from the string.

@ghajba
Copy link
Author

ghajba commented Oct 12, 2017

Thanks for the feedback. A big part of the codebase and what is expected is really unclear because of the missing JavaDoc.
Anyway, I'll try my best to come up with a feasible solution for this issue and get it integrated :)

Gabor Hajba and others added 2 commits October 12, 2017 12:46
@googlebot
Copy link

We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for the commit author(s). If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google.
In order to pass this check, please resolve this problem and have the pull request author add another comment and the bot will run again.

Copy link
Contributor

@hiranya911 hiranya911 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ghajba for the PR. This is starting to look pretty good. I've made some more comments (mostly syntactic changes).

Btw would you be able to provide an integration test too? I think we can add something to DataTestIT to cover the new functionality. The test case should write some big numbers to the database, and try to retrieve them back.


public class BigDecimalNode extends LeafNode<BigDecimalNode> {

private final String value;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm starting to wonder whether we get any benefit by keeping this as a string. I think we can just store it as a BigDecimal here.


@Override
public int hashCode() {
return this.value.hashCode() + this.priority.hashCode();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a very good hash function. Please use MoreObjects.hashCode() here. See https://github.com/google/guava/wiki/CommonObjectUtilitiesExplained#hashcode


public class BigIntegerNode extends LeafNode<BigIntegerNode> {

private final String value;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use BigInteger directly.

public void bigIntegerDeserialize() {
BigIntegerBean beanBigInteger = deserialize("{'value':'1'}", BigIntegerBean.class);
assertEquals(0,BigInteger.ONE.compareTo(beanBigInteger.getValue()));

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add space after comma.

BigIntegerBean beanBigInteger = deserialize("{'value':'1'}", BigIntegerBean.class);
assertEquals(0,BigInteger.ONE.compareTo(beanBigInteger.getValue()));

BigIntegerBean beanBigIntegerBig =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use a value that is guaranteed to be larger then what can fit in a long? Something like:

BigInteger value = new BigInteger(String.valueOf(Long.MAX_VALUE)).add(BigInteger.ONE);

@Test
public void bigIntegerDeserialize() {
BigIntegerBean beanBigInteger = deserialize("{'value':'1'}", BigIntegerBean.class);
assertEquals(0,BigInteger.ONE.compareTo(beanBigInteger.getValue()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just do:

assertEquals(BigInteger.ONE, beanBigInteger.getValue());

BigDecimalBean.class);
assertEquals(0,BigDecimal.ONE.compareTo(beanBigDecimal.getValue()));

BigDecimalBean beanBigDecimalBig =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comments made on the test case above applies here.

@hiranya911
Copy link
Contributor

Instructions for integration tests can be found at: https://github.com/firebase/firebase-admin-java/blob/master/CONTRIBUTING.md#integration-testing

@ghajba
Copy link
Author

ghajba commented Oct 25, 2017

Want to give a note that I have not abandoned my PR, but had some busy days. I am back now and will add the required changes/tests and will take a look at the comparison method too @hiranya911 mentioned previously.

@googlebot
Copy link

CLAs look good, thanks!

@ghajba
Copy link
Author

ghajba commented Oct 26, 2017

Well, the integration testing description seems not working. I updated my sources to the latest version of firebase and the FirebaseCredentialsTest, using a new authentication which is not described in the mentioned documentation, fail.
This makes the contribution experience not the best.

@hiranya911
Copy link
Contributor

hiranya911 commented Oct 26, 2017

The instructions look correct to me. I use them fairly regularly in multiple dev environments, and haven't had any issues. What is the code/test you're trying to run, and what is the error you get?

FWIW, FirebaseCredentialsTest is not an integration test. It's only a unit test. And unit tests are invoked regularly by our CI: https://travis-ci.org/firebase/firebase-admin-java/builds

@ghajba
Copy link
Author

ghajba commented Oct 26, 2017

These are the errors I get when running mvn verify:

Running com.google.firebase.auth.FirebaseAuthTest
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.298 sec <<< FAILURE! - in com.google.firebase.auth.FirebaseAuthTest
initializationError(com.google.firebase.auth.FirebaseAuthTest)  Time elapsed: 0.006 sec  <<< ERROR!
java.io.IOException: The Application Default Credentials are not available. They are available if running in Google Compute Engine. Otherwise, the environment variable GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.
        at com.google.firebase.auth.FirebaseAuthTest.createApplicationDefaultCredential(FirebaseAuthTest.java:143)
        at com.google.firebase.auth.FirebaseAuthTest.data(FirebaseAuthTest.java:108)
Running com.google.firebase.auth.FirebaseCredentialsTest
Tests run: 15, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.118 sec <<< FAILURE! - in com.google.firebase.auth.FirebaseCredentialsTest
defaultCredentialDoesntRefetch(com.google.firebase.auth.FirebaseCredentialsTest)  Time elapsed: 0.012 sec  <<< ERROR!
java.lang.RuntimeException: java.io.IOException: The Application Default Credentials are not available. They are available if running in Google Compute Engine. Otherwise, the environment variable GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.
        at com.google.firebase.auth.FirebaseCredentialsTest.defaultCredentialDoesntRefetch(FirebaseCredentialsTest.java:86)
Caused by: java.io.IOException: The Application Default Credentials are not available. They are available if running in Google Compute Engine. Otherwise, the environment variable GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.
        at com.google.firebase.auth.FirebaseCredentialsTest.defaultCredentialDoesntRefetch(FirebaseCredentialsTest.java:86)

And unit tests are executed with mvn test too.

@hiranya911
Copy link
Contributor

The test case does set the GOOGLE_APPLICATION_CREDENTIALS variable using some reflection magic:

    Map<String, String> environmentVariables =
        ImmutableMap.<String, String>builder()
            .put("GOOGLE_APPLICATION_CREDENTIALS", credentialsFile.getAbsolutePath())
            .build();
    TestUtils.setEnvironmentVariables(environmentVariables);

Looks like this doesn't work in your environment. What is your OS and JVM build? We test this on Linux and Mac, where it works fine.

@ghajba
Copy link
Author

ghajba commented Oct 26, 2017

:)
it's currently Windows. I'll try it on a Mac too

@hiranya911
Copy link
Contributor

@ghajba Feel free to ignore those 2 failures. It's not related to your changes anyway. I'll try to come up with a way to skip those tests on Windows.

@dimipaun
Copy link

dimipaun commented Feb 7, 2018

Hi @ghajba and @hiranya911,

Sorry for jumping late into this, but I think BigDecimal should serialize to String. The number 1 reason for using BigDecimals (such as in code that deals with money) is for it's exact representation of the fraction, rather than for the large values that it holds.

Serializing it to double throws this out the window.

@hiranya911
Copy link
Contributor

Over to @schmidt-sebastian who's been looking at #75. Based on the comment there, I assume we are not going to implement these changes?

@schmidt-sebastian
Copy link
Contributor

Sorry for being slow to respond on this PR and thank you for your awesome contribution!
As you have probably seen, for now we have updated the error messages for both the RTDB and Firestore. The RTDB client uses JSON as its interop format, and FIrestore uses signed Int64 types. Both of these representations do not allow us to express BigIntegers correctly. While serializing to String is an accurate solution for this particular platform, the type doesn't round-trip very well and our other clients would not have the ability to treat these types as numbers. For that reason, I am leaning towards making this more explicit and asking you (and all other affected customers) to directly use String types instead.

@schmidt-sebastian
Copy link
Contributor

Closing this PR for now. If we get significant feedback from a number of customers, we can revisit this, in which case this PR can probably just be merged as is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants