Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.dialect.function.CaseLeastGreatestEmulation;
import org.hibernate.dialect.function.CastingConcatFunction;
import org.hibernate.dialect.function.TransactSQLStrFunction;
Expand All @@ -26,13 +27,16 @@
import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.internal.TransactSQLLockingClauseStrategy;
import org.hibernate.sql.ast.spi.LockingClauseStrategy;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JsonAsStringJdbcType;
import org.hibernate.type.descriptor.jdbc.XmlAsStringJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;

import java.sql.CallableStatement;
Expand Down Expand Up @@ -374,4 +378,12 @@ public void appendBinaryLiteral(SqlAppender appender, byte[] bytes) {
appender.appendSql( "0x" );
PrimitiveByteArrayJavaType.INSTANCE.appendString( appender, bytes );
}

@Override
protected void registerColumnTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.registerColumnTypes( typeContributions, serviceRegistry );
final var jdbcTypeRegistry = typeContributions.getTypeConfiguration().getJdbcTypeRegistry();
jdbcTypeRegistry.addDescriptor( JsonAsStringJdbcType.NVARCHAR_INSTANCE );
jdbcTypeRegistry.addDescriptor( XmlAsStringJdbcType.NVARCHAR_INSTANCE );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1272,5 +1272,4 @@ public boolean supportsRowValueConstructorSyntaxInQuantifiedPredicates() {
public boolean supportsRowValueConstructorSyntaxInInList() {
return false;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.mapping.basic;

import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.annotations.Nationalized;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.SqlTypes;

import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

/**
* SQL Server 환경에서 JSON 타입 컬럼의 Nationalized 매핑 여부를 확인하는 테스트.
* 기본적으로 SQL Server는 JSON에 대해 NVARCHAR를 사용해야 합니다.
*/
public class SqlServerNationalizedMappingTests {

private static final String SQL_SERVER_COMPATIBILITY_SETTING = "hibernate.dialect.sqlserver.compatibility_level";
private static final String UNICODE_JSON = "{\"name\":\"🦑 Unicode Test 🦀\", \"note\":\"한글 테스트\"}";

@Nested
@DomainModel(annotatedClasses = { PlainJsonEntity.class })
@ServiceRegistry(settings = {
@Setting(name = AvailableSettings.DIALECT, value = "org.hibernate.dialect.SQLServerDialect"),
@Setting(name = SQL_SERVER_COMPATIBILITY_SETTING, value = "150")
})
@SessionFactory
public class PlainJsonTests {

@Test
public void testDefaultJsonIsNvarchar(SessionFactoryScope scope) {
// Nationalized 설정이 없어도 기본적으로 NVARCHAR(-9)여야 함
verifyMapping( scope, PlainJsonEntity.class, "jsonData", SqlTypes.NVARCHAR );
}
}

@Nested
@DomainModel(annotatedClasses = { NationalizedJsonEntity.class })
@ServiceRegistry(settings = {
@Setting(name = AvailableSettings.DIALECT, value = "org.hibernate.dialect.SQLServerDialect"),
@Setting(name = SQL_SERVER_COMPATIBILITY_SETTING, value = "150")
})
@SessionFactory
public class NationalizedAnnotationTests {

@Test
public void testNationalizedJsonMappingAndIntegrity(SessionFactoryScope scope) {
verifyMapping( scope, NationalizedJsonEntity.class, "jsonData", SqlTypes.NVARCHAR );

scope.inTransaction( session -> {
NationalizedJsonEntity entity = new NationalizedJsonEntity();
entity.id = 1;
entity.jsonData = UNICODE_JSON;
session.persist( entity );
} );

scope.inSession( session -> {
NationalizedJsonEntity retrieved = session.get( NationalizedJsonEntity.class, 1 );
assertThat( retrieved.jsonData, is( UNICODE_JSON ) );
} );
}
}

@Nested
@DomainModel(annotatedClasses = { JsonEntity.class })
@ServiceRegistry(settings = {
@Setting(name = AvailableSettings.DIALECT, value = "org.hibernate.dialect.SQLServerDialect"),
@Setting(name = SQL_SERVER_COMPATIBILITY_SETTING, value = "150"),
@Setting(name = AvailableSettings.USE_NATIONALIZED_CHARACTER_DATA, value = "true")
})
@SessionFactory
public class GlobalNationalizedSettingsTests {

@Test
public void testGlobalNationalizedJsonMappingAndIntegrity(SessionFactoryScope scope) {
verifyMapping( scope, JsonEntity.class, "jsonData", SqlTypes.NVARCHAR );

scope.inTransaction( session -> {
JsonEntity entity = new JsonEntity();
entity.id = 1;
entity.jsonData = UNICODE_JSON;
session.persist( entity );
} );

scope.inSession( session -> {
JsonEntity retrieved = session.get( JsonEntity.class, 1 );
assertThat( retrieved.jsonData, is( UNICODE_JSON ) );
} );
}
}

private static void verifyMapping(SessionFactoryScope scope, Class<?> entityClass, String attributeName, int expectedTypeCode) {
final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
final EntityPersister entityDescriptor = mappingMetamodel.findEntityDescriptor( entityClass );

final BasicAttributeMapping attribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( attributeName );
final JdbcMapping jdbcMapping = attribute.getJdbcMapping();

assertThat( "JDBC Type code should be " + expectedTypeCode,
jdbcMapping.getJdbcType().getJdbcTypeCode(), is( expectedTypeCode ) );
}

@Entity(name = "PlainJsonEntity")
public static class PlainJsonEntity {
@Id Integer id;
@JdbcTypeCode(SqlTypes.JSON) String jsonData;
}

@Entity(name = "NationalizedJsonEntity")
public static class NationalizedJsonEntity {
@Id Integer id;
@JdbcTypeCode(SqlTypes.JSON) @Nationalized String jsonData;
}

@Entity(name = "JsonEntity")
public static class JsonEntity {
@Id Integer id;
@JdbcTypeCode(SqlTypes.JSON) String jsonData;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.mapping.basic;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.annotations.Nationalized;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.SybaseASEDialect;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.hibernate.type.SqlTypes;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

public class SybaseNationalizedMappingTests {

private static final String UNICODE_JSON = "{\"name\":\"Quantity 🦑 Sybase 🦀\"}";

private static void verifyMapping(SessionFactoryScope scope, Class<?> entityClass, String attributeName, int expectedTypeCode) {
final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
final EntityPersister entityDescriptor = mappingMetamodel.findEntityDescriptor( entityClass );

final BasicAttributeMapping attribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping(
attributeName );
final JdbcMapping jdbcMapping = attribute.getJdbcMapping();

assertThat( "JDBC Type code should be " + expectedTypeCode,
jdbcMapping.getJdbcType().getJdbcTypeCode(), is( expectedTypeCode ) );
}

public static class TestSybaseDialect extends SybaseASEDialect {
@Override
public String getTableTypeString() {
return "";
}

@Override
protected String columnType(int sqlTypeCode) {
if ( sqlTypeCode == SqlTypes.NVARCHAR || sqlTypeCode == SqlTypes.JSON || sqlTypeCode == SqlTypes.NCLOB ) {
return "nvarchar(max)";
}
return super.columnType( sqlTypeCode );
}
}

@Entity(name = "PlainJsonEntity")
public static class PlainJsonEntity {
@Id
Integer id;
@JdbcTypeCode(SqlTypes.JSON)
String jsonData;
}

@Entity(name = "NationalizedJsonEntity")
@Table(name = "NationalizedJsonEntity")
public static class NationalizedJsonEntity {
@Id
Integer id;
@JdbcTypeCode(SqlTypes.JSON)
@Nationalized
String jsonData;
}

@Entity(name = "JsonEntity")
@Table(name = "JsonEntity")
public static class JsonEntity {
@Id
Integer id;
@JdbcTypeCode(SqlTypes.JSON)
String jsonData;
}

@Nested
@DomainModel(annotatedClasses = {PlainJsonEntity.class})
@ServiceRegistry(settings = {
@Setting(name = AvailableSettings.DIALECT,
value = "org.hibernate.orm.test.mapping.basic.SybaseNationalizedMappingTests$TestSybaseDialect")
})
@SessionFactory
public class PlainJsonTests {

@Test
public void testDefaultJsonIsNvarchar(SessionFactoryScope scope) {
verifyMapping( scope, PlainJsonEntity.class, "jsonData", SqlTypes.NVARCHAR );
scope.inTransaction( session -> {
PlainJsonEntity entity = new PlainJsonEntity();
entity.id = 1;
entity.jsonData = UNICODE_JSON;
session.persist( entity );
} );

scope.inSession( session -> {
PlainJsonEntity retrieved = session.find( PlainJsonEntity.class, 1 );
assertThat( retrieved.jsonData, is( UNICODE_JSON ) );
} );
}
}

@Nested
@DomainModel(annotatedClasses = {NationalizedJsonEntity.class})
@ServiceRegistry(settings = {
@Setting(name = AvailableSettings.DIALECT,
value = "org.hibernate.orm.test.mapping.basic.SybaseNationalizedMappingTests$TestSybaseDialect")
})
@SessionFactory
public class NationalizedAnnotationTests {

@Test
public void testNationalizedJsonMappingAndIntegrity(SessionFactoryScope scope) {
verifyMapping( scope, NationalizedJsonEntity.class, "jsonData", SqlTypes.NVARCHAR );

scope.inTransaction( session -> {
NationalizedJsonEntity entity = new NationalizedJsonEntity();
entity.id = 1;
entity.jsonData = UNICODE_JSON;
session.persist( entity );
} );

scope.inSession( session -> {
NationalizedJsonEntity retrieved = session.find( NationalizedJsonEntity.class, 1 );
assertThat( retrieved.jsonData, is( UNICODE_JSON ) );
} );
}
}

@Nested
@DomainModel(annotatedClasses = {JsonEntity.class})
@ServiceRegistry(settings = {
@Setting(name = AvailableSettings.DIALECT,
value = "org.hibernate.orm.test.mapping.basic.SybaseNationalizedMappingTests$TestSybaseDialect"),
@Setting(name = AvailableSettings.USE_NATIONALIZED_CHARACTER_DATA, value = "true")
})
@SessionFactory
public class GlobalNationalizedSettingsTests {

@Test
public void testGlobalNationalizedJsonMappingAndIntegrity(SessionFactoryScope scope) {
verifyMapping( scope, JsonEntity.class, "jsonData", SqlTypes.NVARCHAR );

scope.inTransaction( session -> {
JsonEntity entity = new JsonEntity();
entity.id = 1;
entity.jsonData = UNICODE_JSON;
session.persist( entity );
} );

scope.inSession( session -> {
JsonEntity retrieved = session.find( JsonEntity.class, 1 );
assertThat( retrieved.jsonData, is( UNICODE_JSON ) );
} );
}
}
}
Loading