Skip to content

Code improvements in SQLServerDataTable > 'internalAddRow()'#538

Closed
cheenamalhotra wants to merge 8 commits intomicrosoft:devfrom
cheenamalhotra:explicitConversions
Closed

Code improvements in SQLServerDataTable > 'internalAddRow()'#538
cheenamalhotra wants to merge 8 commits intomicrosoft:devfrom
cheenamalhotra:explicitConversions

Conversation

@cheenamalhotra
Copy link
Copy Markdown
Member

@cheenamalhotra cheenamalhotra commented Nov 1, 2017

Replaced with #990

@codecov-io
Copy link
Copy Markdown

codecov-io commented Nov 2, 2017

Codecov Report

Merging #538 into dev will decrease coverage by 0.11%.
The diff coverage is 55.55%.

Impacted file tree graph

@@             Coverage Diff              @@
##                dev     #538      +/-   ##
============================================
- Coverage     48.17%   48.06%   -0.12%     
+ Complexity     2585     2584       -1     
============================================
  Files           114      114              
  Lines         26578    26574       -4     
  Branches       4453     4445       -8     
============================================
- Hits          12805    12772      -33     
- Misses        11634    11677      +43     
+ Partials       2139     2125      -14
Flag Coverage Δ Complexity Δ
#JDBC42 47.97% <55.55%> (-0.01%) 2582 <17> (+7)
#JDBC43 47.98% <55.55%> (-0.07%) 2580 <17> (+1)
Impacted Files Coverage Δ Complexity Δ
...m/microsoft/sqlserver/jdbc/SQLServerDataTable.java 64.75% <55.55%> (+6.02%) 25 <17> (+2) ⬆️
...rc/main/java/com/microsoft/sqlserver/jdbc/DDC.java 43.4% <0%> (-1.57%) 106% <0%> (-1%)
...n/java/com/microsoft/sqlserver/jdbc/Parameter.java 61.92% <0%> (-0.84%) 63% <0%> (ø)
...in/java/com/microsoft/sqlserver/jdbc/IOBuffer.java 54.9% <0%> (-0.43%) 0% <0%> (ø)
...oft/sqlserver/jdbc/SQLServerPreparedStatement.java 50.44% <0%> (-0.25%) 157% <0%> (ø)
...ncurrentlinkedhashmap/ConcurrentLinkedHashMap.java 39.18% <0%> (-0.22%) 44% <0%> (+1%)
...m/microsoft/sqlserver/jdbc/SQLServerResultSet.java 33.21% <0%> (-0.2%) 245% <0%> (-2%)
...om/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java 52.48% <0%> (-0.2%) 238% <0%> (-1%)
...rc/main/java/com/microsoft/sqlserver/jdbc/dtv.java 63.29% <0%> (-0.12%) 0% <0%> (ø)
...om/microsoft/sqlserver/jdbc/ReaderInputStream.java 44.94% <0%> (ø) 16% <0%> (ø) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 58f4f1d...3a7f7ae. Read the comment docs.

@cogman
Copy link
Copy Markdown
Contributor

cogman commented Nov 2, 2017

Pretty much exactly what I was thinking for the linked issue.

@cheenamalhotra
Copy link
Copy Markdown
Member Author

Fix added for #539 - Support for Bit values (1,0) added for Boolean datatype in TVP.

@alexnixon
Copy link
Copy Markdown

I think this introduces regressions, see my comment on #539

switch (jdbcType) {
case BIGINT:
rowValues[pair.getKey()] = (val instanceof Number ? ((Number)val).longValue() : Long.parseLong(val.toString()));
rowValues[pair.getKey()] = (val instanceof Number ? (Long)val : Long.parseLong(val.toString()));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If val is an Integer this will throw a ClassCastException. Same for most of the instanceof checks below

@alexnixon
Copy link
Copy Markdown

alexnixon commented Nov 13, 2017

I think this would benefit from some more tests, perhaps similar to the examples I gave on #539.

As for the implementation, something like this is what I had in mind:

    private static long coerceToLong(Object val, String fieldTypeName) {
        if (val instanceof Byte) {
            return ((Byte) val).longValue();
        } else if (val instanceof Short) {
            return ((Short)val).longValue();
        } else if (val instanceof Integer) {
            return ((Integer)val).longValue();
        } else if (val instanceof Long) {
            return (Long) val;
        } else if (val instanceof BigDecimal) {
            return ((BigDecimal)val).longValueExact();
        } else if (val instanceof BigInteger) {
            return ((BigInteger)val).longValueExact();
        } else if (val instanceof Float || val instanceof Double) {
            throw new IllegalArgumentException(String.format("%s fields cannot be set with floating point numbers",
                    fieldTypeName));
        } else {
            throw new IllegalArgumentException(String.format("%s fields cannot be set with object %s of type %s",
                    fieldTypeName, val, val.getClass().getName()));
        }
    }

    private static boolean coerceToBoolean(Object val, String fieldTypeName) {
        if (val instanceof Boolean) {
            return (Boolean)val;
        } else {
            long l = coerceToLong(val, fieldTypeName);
            if (l == 0) {
                return false;
            } else if (l == 1) {
                return true;
            } else {
                throw new IllegalArgumentException(String.format("Cannot coerce value %s of type %s to %s", val,
                        val.getClass().getName(), fieldTypeName));
            }
        }
    }

    private static short coerceToShort(Object val, String fieldTypeName) {
        if (val instanceof Short) {
            return (Short)val;
        } else {
            Long l = coerceToLong(val, fieldTypeName);
            if (l.shortValue() == l) {
                return l.shortValue();
            } else {
                throw new IllegalArgumentException(String.format("Cannot coerce value %s of type %s to %s", val,
                        val.getClass().getName(), fieldTypeName));
            }
        }
    }

    private static int coerceToInt(Object val, String fieldTypeName) {
        if (val instanceof Integer) {
            return (Integer)val;
        } else {
            Long l = coerceToLong(val, fieldTypeName);
            if (l.intValue() == l) {
                return l.intValue();
            } else {
                throw new IllegalArgumentException(String.format("Cannot coerce value %s of type %s to %s", val,
                        val.getClass().getName(), fieldTypeName));
            }
        }
    }

And likewise for other types. Then call these from the jdbcType switch, eg:

switch (jdbcType) {
  [ ... ]
  case BIT:
    rowValues[pair.getKey()] = coerceToBoolean(val, "BIT");
    break;
  [ ... ]
  case SMALLINT:
    rowValues[pair.getKey()] = coerceToShort(val, "SMALLINT");
    break;
  case TINYINT:
    rowValues[pair.getKey()] = coerceToShort(val, "TINYINT");
    break;
}

As a disclaimer I haven't tested this.

@cheenamalhotra
Copy link
Copy Markdown
Member Author

cheenamalhotra commented Nov 14, 2017

Hi @alexnixon Thanks for your suggestions above. I agree to check for object instance for specific types for casting, but with this code, there are 2 downsides:

  • Support for String datatype will be gone for setting all numeric types. Is that intended?
    (Will be a breaking change for support to String values in TVP)
    If not, I would like to add another layer of conversion from String values to support String inputs.

  • There will be an extra data conversion to long and then to desired type again (for else cases) - can anything be done to prevent that?
    (Not a major impact, just an observation)

I am still investigating on changes, and will let you know if I find some gaps. Appreciate your involvements, looking forward to your comments. Since the PR is still in progress, we do want to make sure we cover all scenarios and don't break any existing support before merging this change.

@cheenamalhotra cheenamalhotra added the Work in Progress The pull request is a work in progress label Nov 15, 2017
@alexnixon
Copy link
Copy Markdown

Thanks for the reply @cheenamalhotra

Indeed it would break support for strings. If I was designing from scratch I would omit it, but I understand you might want to keep that for backwards compatibility. So I guess that's a choice for the project maintainer (which might be you?).

But if it is to be included I would still argue for being strict, i.e. ensure it throws an exception for out-of-range values and decimals.

I think the time/memory of an extra long conversion would be negligible, especially given we're about to write to a database, I just wrote the example code that way as it's shorter.

int nValueLen;
switch (jdbcType) {
case BIGINT:
rowValues[pair.getKey()] = (val instanceof Number ? (Long)val : Long.parseLong(val.toString()));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should this really check for Number and then cast to Long? I would either change to instanceof Long or use ((Number)val).longValue() (which may introduce an unnecessary reboxing if val is Long)

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

Labels

Work in Progress The pull request is a work in progress

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants