Skip to content

Fix jdbc-v2: report the IPv4/IPv6 Java class in getColumnClassName - #3191

Open
polyglotAI-bot wants to merge 1 commit into
mainfrom
polyglot/jdbcv2-ip-column-class-name
Open

polyglotAI-bot wants to merge 1 commit into
mainfrom
polyglot/jdbcv2-ip-column-class-name

Conversation

@polyglotAI-bot

Copy link
Copy Markdown
Collaborator

Description

Fixes #3189.

ResultSetMetaData#getColumnClassName returned java.lang.Object for IPv4 and IPv6 columns. The cause is JdbcUtils.DATA_TYPE_CLASS_MAP: it mapped both types to Object.class. That map is the single source of the column class for metadata and for getObject().

  • IPv4 now maps to java.net.Inet4Address. The reader always returns an Inet4Address for IPv4.
  • IPv6 now maps to java.net.InetAddress, not Inet6Address. BinaryStreamReader reads IPv6 with InetAddress.getByAddress(16 bytes), which returns an Inet4Address for an IPv4-mapped value (::ffff:a.b.c.d). The existing JdbcDataTypeTests.testIpAddressTypes requires getObject() to return that Inet4Address. JDBC defines getColumnClassName as the class of the instances that getObject() returns, so InetAddress is the correct class. With Inet6Address, JdbcUtils.convert would convert mapped values to Inet6Address, and getObject() would return different values than before.

getColumnType() stays Types.OTHER. ResultSet#getObject() returns the same values as before.

Alternative (needs a decision): to report Inet6Address for IPv6, as the issue expects, getObject() must return an Inet6Address for IPv4-mapped values. That changes the behavior of an existing value path. If you want that, I can change it.

Changes

  • JdbcUtils.getDataTypeClassMap(): IPv4 → Inet4Address.class, IPv6 → InetAddress.class.
  • ArrayResultSet.convertValue(): the "no conversion needed" check was an exact class match (targetType == value.getClass()). ArrayResultSet.getObject(2) uses the class from the same map, so an Array(IPv6) element failed with Value of class java.net.Inet6Address cannot be converted to class java.net.InetAddress. The check is now targetType.isInstance(value), the same as in JdbcUtils.convert. No ValueConverters target is a superclass of a value class that gets here, so no converter that ran before is skipped.
  • docs/features.md: new "IP address type mapping" bullet.
  • history/latest/3189.md: changelog entry.

Compatibility

  • getColumnClassName reports a different value for IPv4/IPv6 columns. That is the fix.
  • java.sql.Array#getArray() for Array(IPv4)/Array(IPv6) now returns an Inet4Address[] / InetAddress[], not an Object[], the same as other typed arrays (for example UUID[]). A cast to Object[] still works.
  • getObject(int, Class) on the result set of a java.sql.Array now also accepts a superclass or interface of the element value (for example Number.class for an Int32 element). Before, it threw.
  • For a user-made array, createArrayOf("IPv4", new String[]{...}).getResultSet().getObject(2) now throws: there is no String → Inet4Address converter. Before, it returned the String. A typed array of InetAddress values works.

Test

  • ResultSetMetaDataImplTest.testGetColumnClassNameOfIpAddressTypes (integration, @DataProvider): IPv4, IPv6, their Nullable and LowCardinality forms, and an IPv4-mapped IPv6 value. For each row, the test checks the type name, Types.OTHER, and the class name, and that getObject() returns an instance of the reported class. The IPv4-mapped row is the contrast case: getObject() still returns an Inet4Address. The test runs the same checks on the VALUE column of java.sql.Array#getResultSet(). All 5 original rows failed on main with expected [...] but found [java.lang.Object].
  • ArrayResultSetTest.testInetAddressValues (unit): getObject(2), getObject(2, InetAddress.class), and a null element. Negative case: getObject(2, Inet4Address.class) on a non-mapped IPv6 element still throws SQLException. Fails without the ArrayResultSet change.
  • Full jdbc-v2 module: 1963 unit tests pass. 775 of 777 integration tests pass. The 2 failures are ConnectionTest.testSSLModeVerifyCa and ConnectionTest.testSecureConnection. These SSL tests need a TLS setup that my local environment does not have. They are not related to this change. No existing tests were edited.

Pre-PR validation gate

  • Deterministic repro confirmed (new test fails on main)
  • Root cause documented above
  • Fix targets the root cause (the shared column class map)
  • Test fails without fix, passes with fix
  • No existing tests broken or edited
  • docs/changes_checklist.md "Conditional logic or guard changed": the ArrayResultSet guard now admits superclass targets. A negative test shows that unrelated targets still throw.
  • docs/features.md updated (jdbc-v2 behavior)
  • Changelog entry in history/latest/3189.md
  • Fix is on the live runtime path (integration test through Statement#executeQuery)

Not in this PR

Two related defects that I found while working on this. Neither is caused by this change, and they are tracked for separate investigation:

  • client-v2 BinaryStreamReader.readArrayItem creates an Inet6Address[] for Array(Nullable(IPv6)), so an IPv4-mapped element is likely to fail on read.
  • InetAddressConverter.convertToIpv4 compares bytes[10] != 0xFF (a byte with an int), so it rejects every IPv4-mapped Inet6Address.

🤖 Generated with Claude Code

ResultSetMetaData#getColumnClassName returned java.lang.Object for IPv4
and IPv6 columns because JdbcUtils.DATA_TYPE_CLASS_MAP mapped both types
to Object. IPv4 now maps to java.net.Inet4Address and IPv6 to
java.net.InetAddress: the reader returns an Inet4Address for an
IPv4-mapped IPv6 value, so InetAddress is the class that getObject
actually returns. getObject returns the same values as before.

ArrayResultSet.convertValue used an exact class match, so an array
element could not be returned for a superclass target such as
InetAddress. It now uses isInstance, the same as JdbcUtils.convert.

Fixes: #3189

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ventod

ventod commented Oct 7, 2026

Copy link
Copy Markdown

IMO an IPv6 column should report java.net.Inet6Address (and not InetAddress ) . If I save an IPv4 address in a IPv6 column I'm implicitly asking for a conversion ( like 10.5.2.1 -> ::ffff:10.5.2.1 ). Like trying to save a Short value in a Long column I expect to get a Long back when I do getObject. I do not think that the DB should "remember" what type of value I passed way after the storing of it is done.

Also calling ResultSet.getObject(index, java.net.Inet6Address.class) and ResultSet.getObject(index, java.net.InetAddress.class) on an IPv6 column should both work, while ResultSet.getObject(index, java.net.Inet4Address.class) should fail.

For the same reason calling ResultSet.getObject(index, java.net.Inet4Address.class) and ResultSet.getObject(index, java.net.InetAddress.class) on an IPv4 column should both work, while ResultSet.getObject(index, java.net.Inet6Address.class) should fail.

@polyglotAI-bot

Copy link
Copy Markdown
Collaborator Author

Thank you for the review, @ventod. I checked each of your cases on this branch with a live server (26.9). The getObject() results are the same as on main. This PR changes only getColumnClassName.

Query: SELECT toIPv4('10.5.2.1') AS v4, toIPv6('2001:db8::1') AS v6, toIPv6('10.5.2.1') AS v6mapped

column getObject() (…, InetAddress.class) (…, Inet4Address.class) (…, Inet6Address.class)
v4 (IPv4) Inet4Address 10.5.2.1 Inet4Address Inet4Address Inet6Address ::ffff:a05:201
v6 (IPv6) Inet6Address 2001:db8::1 Inet6Address throws SQLException Inet6Address
v6mapped (IPv6) Inet4Address 10.5.2.1 Inet4Address Inet4Address Inet6Address ::ffff:a05:201
  1. The server does not keep the type of the inserted value. It stores 16 bytes (::ffff:10.5.2.1). The Inet4Address comes from the client: client-v2 BinaryStreamReader decodes IPv6 with InetAddress.getByAddress(byte[16]), and the JDK returns an Inet4Address for each IPv4-mapped address. A value inserted as '::ffff:10.5.2.1' also reads back as an Inet4Address.
  2. On an IPv6 column, getObject(i, Inet6Address.class) and getObject(i, InetAddress.class) already work for all values.
  3. The two conversions that you expect to fail are intentional. Add IP conversion and encoding #2288 added them to fix [client-v2] Cannot read IPv4 address as IPv6 address #2140 (Metabase could not read an IPv4-mapped value as Inet6Address). JdbcDataTypeTests.testIpAddressTypes pins both: lines 1506 (ipv4_ip as Inet6Address) and 1512 (ipv4_as_ipv6 as Inet4Address). I will not remove them in this PR. That would break current callers, and it is not in the scope of JDBC metadata reports java.lang.Object for IPv4 and IPv6 column class names #3189.
  4. JDBC defines the class name as the class of the objects that getObject() returns. To report Inet6Address for IPv6, getObject() must return an Inet6Address for IPv4-mapped values. I tried this locally: I changed the jdbc-v2 type map for IPv6 to Inet6Address.class, and the existing Inet4Address → Inet6Address converter then promotes mapped values. No client-v2 change is necessary. But the existing assertion JdbcDataTypeTests.testIpAddressTypes:1511 then fails: expected [/90.176.75.97] but found [/0:0:0:0:0:ffff:5ab0:4b61]. Thus current callers get a different value. Your argument (the column type sets the Java type, as Int16 values in an Int64 column come back as Long) is consistent. But it changes an existing value path, so a maintainer must decide.

@chernser, which option do you want?

  • A (this PR now): IPv6 reports java.net.InetAddress. The values that getObject() returns do not change.
  • B: IPv6 reports java.net.Inet6Address. jdbc-v2 getObject() returns an Inet6Address (::ffff:a.b.c.d) for IPv4-mapped values. The change is in the jdbc-v2 type map only. The assertion at testIpAddressTypes:1511 must change to expect the mapped Inet6Address.

If you select B, I will update this PR.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JDBC metadata reports java.lang.Object for IPv4 and IPv6 column class names

2 participants