-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
HHH-20092 SqlTypes.JSON on SQL Server expects varchar(max) even with use_nationalized_character_data=true #11680
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
mbellade
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @qlfyd123, left a couple comments on how to approach this.
hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java
Outdated
Show resolved
Hide resolved
hibernate-core/src/test/java/org/hibernate/orm/test/dialect/SQLServerDialectTest.java
Outdated
Show resolved
Hide resolved
| @Override | ||
| public boolean equivalentTypes(int typeCode1, int typeCode2) { | ||
| return typeCode1 == Types.NVARCHAR && typeCode2 == SqlTypes.JSON | ||
| || typeCode1 == SqlTypes.JSON && typeCode2 == Types.NVARCHAR | ||
| || super.equivalentTypes( typeCode1, typeCode2 ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is better, though I believe this issue might not be specific to SQL Server. Could you please check whether it happens with other DBs? The "problem" might be that we always default to non nationalized varchar columns for SqlTypes.JSON, whereas we should respect the hibernate.use_nationalized_character_data config property.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to me that it's better to change org.hibernate.tool.schema.internal.ColumnDefinitions#hasMatchingType to use column.getType().getJdbcType().getDdlTypeCode() instead of column.getSqlTypeCode( metadata ), since we're explicitly trying to check the DDL type code as reported by the database here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have investigated how JSON types are handled across various databases and found a specific issue with SQL Server regarding the use_nationalized_character_data configuration.
Here is the current behavior observed for different databases:
| Database | Expected (Hibernate) |
|---|---|
| MariaDB | json (Types#JSON) |
| MySQL | json (Types#JSON) |
| Oracle | json (Types#JSON) |
| PostgreSQL | jsonb (Types#JSON) |
| SQL Server | varchar(max) (Types#LONG32VARCHAR) |
For most databases (MySQL, PostgreSQL, etc.), the dialect explicitly registers a descriptor for SqlTypes.JSON (e.g., ddlTypeRegistry.addDescriptor(new DdlTypeImpl(JSON, "json", this))), so they consistently use the native JSON
type.
However, SQL Server Dialect does not explicitly register a descriptor for SqlTypes.JSON. As a result, it falls back to the default registration in MetadataBuildingProcess.java:
// MetadataBuildingProcess.java
jdbcTypeRegistry.addDescriptorIfAbsent( JsonAsStringJdbcType.VARCHAR_INSTANCE );The JsonAsStringJdbcType.VARCHAR_INSTANCE is defined as:
public static final JsonAsStringJdbcType VARCHAR_INSTANCE = new JsonAsStringJdbcType( SqlTypes.LONG32VARCHAR, null );And its constructor sets nationalized to false:
protected JsonAsStringJdbcType(int ddlTypeCode, EmbeddableMappingType embeddableMappingType) {
super( embeddableMappingType );
this.ddlTypeCode = ddlTypeCode;
// returns false for LONG32VARCHAR
this.nationalized = ddlTypeCode == SqlTypes.LONG32NVARCHAR || ddlTypeCode == SqlTypes.NCLOB;
}This means that for SQL Server, SqlTypes.JSON is mapped to a type that uses LONG32VARCHAR (mapped to varchar(max)) and has nationalized = false by default.
I've use column.getType().getJdbcType().getDdlTypeCode() method instead, but it returned same result type code 4001 long32varchar
I think when users apply @JdbcTypeCode(SqlTypes.JSON), Hibernate uses this registered instance directly, bypassing the adjustment logic that would normally check the use_nationalized_character_data setting. Consequently, the setting is ignored, and varchar(max) is always used instead of nvarchar(max).
|
@mbellade |
| typeContributions.contributeJdbcType( SQLServerCastingXmlJdbcType.INSTANCE ); | ||
| typeContributions.contributeJdbcType( UUIDJdbcType.INSTANCE ); | ||
| typeContributions.contributeJdbcTypeConstructor( SQLServerCastingXmlArrayJdbcTypeConstructor.INSTANCE ); | ||
| typeContributions.contributeJdbcType( JsonAsStringJdbcType.VARCHAR_INSTANCE ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this the default behavior? If the dialect doesn't explicitly register a JSON JDBC type, this should be added in org.hibernate.boot.model.process.spi.MetadataBuildingProcess#handleTypes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mbellade
I apologize, I think I made a few mistakes earlier. After re-checking the logic, I found that in the InferredBasicValueResolver#from method:
// explicitJavaType is null
// explicitJdbcType is not null
// reflectedJtd is not null
Due to these conditions, only the following code block is actually executed:
else if ( explicitJdbcType != null ) {
// we also have an explicit JdbcType
jdbcMapping = basicTypeRegistry.resolve( reflectedJtd, explicitJdbcType );
}The problem seems to be that in this specific code block, the resolveIndicatedType method implemented in JsonAsStringJdbcType is not being executed. Consequently, the value of JsonAsStringJdbcType.VARCHAR_INSTANCE, which was registered during the MetadataBuildingProcess, is being used as-is.
|
Hi @mbellade.
I would appreciate guidance on the correct design direction: I'm happy to adjust the implementation based on your feedback. For now, I've pushed the changes to facilitate discussion despite the test failure. |
https://hibernate.atlassian.net/browse/HHH-20092
[Please describe here what your change is about]
Added a descriptor to DdlTypeRegistry to map json to nvarchar.
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license
and can be relicensed under the terms of the LGPL v2.1 license in the future at the maintainers' discretion.
For more information on licensing, please check here.
Please make sure that the following tasks are completed:
Tasks specific to HHH-20092 (Bug):
migration-guide.adocOR check there are no breaking changes