Add hexadecimal binary literals with standard SQL syntax

And fix up the parsing and formatting in VarbinaryTypeDescriptor which
was inconsistent with how the JDBC drivers handle the conversion to
signed bytes.
This commit is contained in:
gavinking 2020-02-02 03:57:52 +01:00 committed by Steve Ebersole
parent eb43734658
commit ddaff28838
13 changed files with 89 additions and 5 deletions

View File

@ -93,6 +93,10 @@ UNICODE_ESCAPE
: 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT : 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
; ;
BINARY_LITERAL
: [xX] SINGLE_QUOTE (HEX_DIGIT HEX_DIGIT)* SINGLE_QUOTE
;
// ESCAPE start tokens // ESCAPE start tokens
TIMESTAMP_ESCAPE_START : '{ts'; TIMESTAMP_ESCAPE_START : '{ts';
DATE_ESCAPE_START : '{d'; DATE_ESCAPE_START : '{d';

View File

@ -501,6 +501,7 @@ nullifFunction
literal literal
: STRING_LITERAL : STRING_LITERAL
| BINARY_LITERAL
| INTEGER_LITERAL | INTEGER_LITERAL
| LONG_LITERAL | LONG_LITERAL
| BIG_INTEGER_LITERAL | BIG_INTEGER_LITERAL

View File

@ -3285,6 +3285,10 @@ public abstract class Dialect implements ConversionContext {
return true; return true;
} }
public String formatBinaryliteral(byte[] bytes) {
return "X'" + StandardBasicTypes.BINARY.toString( bytes ) + "'";
}
/** /**
* Pluggable strategy for determining the Size to use for columns of * Pluggable strategy for determining the Size to use for columns of
* a given SQL type when no explicit Size has been given. * a given SQL type when no explicit Size has been given.

View File

@ -1085,6 +1085,11 @@ public class OracleDialect extends Dialect {
.replace("x", "TZH"); //note special case .replace("x", "TZH"); //note special case
} }
@Override
public String formatBinaryliteral(byte[] bytes) {
return "hextoraw('" + StandardBasicTypes.BINARY.toString( bytes ) + "')";
}
@Override @Override
public ResultSet getResultSet(CallableStatement statement, int position) throws SQLException { public ResultSet getResultSet(CallableStatement statement, int position) throws SQLException {
return (ResultSet) statement.getObject( position ); return (ResultSet) statement.getObject( position );

View File

@ -691,6 +691,11 @@ public class PostgreSQLDialect extends Dialect {
} }
} }
@Override
public String formatBinaryliteral(byte[] bytes) {
return "bytea '\\x" + StandardBasicTypes.BINARY.toString( bytes ) + "'";
}
@Override @Override
protected String wrapDateLiteral(String date) { protected String wrapDateLiteral(String date) {
return wrapAsAnsiDateLiteral(date); return wrapAsAnsiDateLiteral(date);

View File

@ -584,6 +584,11 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
private static final Pattern OFFSET_PATTERN = compile(".*[-+]\\d{2}(:\\d{2})?$"); private static final Pattern OFFSET_PATTERN = compile(".*[-+]\\d{2}(:\\d{2})?$");
@Override
public String formatBinaryliteral(byte[] bytes) {
return "0x" + StandardBasicTypes.BINARY.toString( bytes );
}
@Override @Override
protected String wrapTimestampLiteral(String timestamp) { protected String wrapTimestampLiteral(String timestamp) {
//needed because the {ts ... } JDBC escape chokes on microseconds //needed because the {ts ... } JDBC escape chokes on microseconds

View File

@ -1746,6 +1746,9 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
if ( ctx.literal().STRING_LITERAL() != null ) { if ( ctx.literal().STRING_LITERAL() != null ) {
return stringLiteral( ctx.literal().STRING_LITERAL().getText() ); return stringLiteral( ctx.literal().STRING_LITERAL().getText() );
} }
else if ( ctx.literal().BINARY_LITERAL() != null ) {
return binaryLiteral( ctx.literal().BINARY_LITERAL().getText() );
}
else if ( ctx.literal().INTEGER_LITERAL() != null ) { else if ( ctx.literal().INTEGER_LITERAL() != null ) {
return integerLiteral( ctx.literal().INTEGER_LITERAL().getText() ); return integerLiteral( ctx.literal().INTEGER_LITERAL().getText() );
} }
@ -2034,6 +2037,14 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
); );
} }
private SqmLiteral<byte[]> binaryLiteral(String text) {
return new SqmLiteral(
StandardBasicTypes.BINARY.fromStringValue( text.substring( 2, text.length()-1 ) ),
resolveExpressableTypeBasic( byte[].class ),
creationContext.getNodeBuilder()
);
}
private SqmLiteral<Integer> integerLiteral(String text) { private SqmLiteral<Integer> integerLiteral(String text) {
try { try {
final Integer value = Integer.valueOf( text ); final Integer value = Integer.valueOf( text );

View File

@ -48,7 +48,7 @@ public class ByteArrayTypeDescriptor extends AbstractTypeDescriptor<Byte[]> {
public String toString(Byte[] bytes) { public String toString(Byte[] bytes) {
final StringBuilder buf = new StringBuilder(); final StringBuilder buf = new StringBuilder();
for ( Byte aByte : bytes ) { for ( Byte aByte : bytes ) {
final String hexStr = Integer.toHexString( aByte - Byte.MIN_VALUE ); final String hexStr = Integer.toHexString( Byte.toUnsignedInt(aByte) );
if ( hexStr.length() == 1 ) { if ( hexStr.length() == 1 ) {
buf.append( '0' ); buf.append( '0' );
} }
@ -67,7 +67,7 @@ public class ByteArrayTypeDescriptor extends AbstractTypeDescriptor<Byte[]> {
Byte[] bytes = new Byte[string.length() / 2]; Byte[] bytes = new Byte[string.length() / 2];
for ( int i = 0; i < bytes.length; i++ ) { for ( int i = 0; i < bytes.length; i++ ) {
final String hexStr = string.substring( i * 2, (i + 1) * 2 ); final String hexStr = string.substring( i * 2, (i + 1) * 2 );
bytes[i] = (byte) ( Integer.parseInt( hexStr, 16 ) + Byte.MIN_VALUE ); bytes[i] = (byte) Integer.parseInt( hexStr, 16 );
} }
return bytes; return bytes;
} }

View File

@ -49,7 +49,7 @@ public class PrimitiveByteArrayTypeDescriptor extends AbstractTypeDescriptor<byt
public String toString(byte[] bytes) { public String toString(byte[] bytes) {
final StringBuilder buf = new StringBuilder( bytes.length * 2 ); final StringBuilder buf = new StringBuilder( bytes.length * 2 );
for ( byte aByte : bytes ) { for ( byte aByte : bytes ) {
final String hexStr = Integer.toHexString( aByte - Byte.MIN_VALUE ); final String hexStr = Integer.toHexString( Byte.toUnsignedInt(aByte) );
if ( hexStr.length() == 1 ) { if ( hexStr.length() == 1 ) {
buf.append( '0' ); buf.append( '0' );
} }
@ -60,7 +60,7 @@ public class PrimitiveByteArrayTypeDescriptor extends AbstractTypeDescriptor<byt
@Override @Override
public String extractLoggableRepresentation(byte[] value) { public String extractLoggableRepresentation(byte[] value) {
return (value == null) ? super.extractLoggableRepresentation( null ) : Arrays.toString( value ); return value == null ? super.extractLoggableRepresentation( null ) : Arrays.toString( value );
} }
public byte[] fromString(String string) { public byte[] fromString(String string) {
@ -73,7 +73,7 @@ public class PrimitiveByteArrayTypeDescriptor extends AbstractTypeDescriptor<byt
byte[] bytes = new byte[string.length() / 2]; byte[] bytes = new byte[string.length() / 2];
for ( int i = 0; i < bytes.length; i++ ) { for ( int i = 0; i < bytes.length; i++ ) {
final String hexStr = string.substring( i * 2, (i + 1) * 2 ); final String hexStr = string.substring( i * 2, (i + 1) * 2 );
bytes[i] = (byte) (Integer.parseInt(hexStr, 16) + Byte.MIN_VALUE); bytes[i] = (byte) Integer.parseInt( hexStr, 16 );
} }
return bytes; return bytes;
} }

View File

@ -21,6 +21,7 @@ import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@FunctionalInterface
public interface JdbcLiteralFormatter<T> { public interface JdbcLiteralFormatter<T> {
String NULL = "null"; String NULL = "null";

View File

@ -17,6 +17,7 @@ import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.BasicJavaDescriptor;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.sql.internal.JdbcLiteralFormatterBinary;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
/** /**
@ -44,6 +45,12 @@ public class VarbinaryTypeDescriptor implements SqlTypeDescriptor {
return (BasicJavaDescriptor<T>) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( byte[].class ); return (BasicJavaDescriptor<T>) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( byte[].class );
} }
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaTypeDescriptor<T> javaTypeDescriptor) {
//noinspection unchecked
return new JdbcLiteralFormatterBinary( javaTypeDescriptor );
}
public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) { public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicBinder<X>( javaTypeDescriptor, this ) { return new BasicBinder<X>( javaTypeDescriptor, this ) {

View File

@ -0,0 +1,28 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.type.descriptor.sql.internal;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.sql.spi.BasicJdbcLiteralFormatter;
/**
* JdbcLiteralFormatter implementation for handling binary literals
*
* @author Gavin King
*/
public class JdbcLiteralFormatterBinary extends BasicJdbcLiteralFormatter {
public JdbcLiteralFormatterBinary(JavaTypeDescriptor javaTypeDescriptor) {
super( javaTypeDescriptor );
}
@Override
public String toJdbcLiteral(Object value, Dialect dialect, SharedSessionContractImplementor session) {
return dialect.formatBinaryliteral( unwrap( value, byte[].class, session ) );
}
}

View File

@ -18,6 +18,7 @@ import java.math.BigInteger;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hibernate.type.StandardBasicTypes.BINARY;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
@ -28,6 +29,18 @@ import static org.hamcrest.Matchers.is;
@SessionFactory @SessionFactory
public class LiteralTests { public class LiteralTests {
@Test
public void testBinaryLiteral(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
byte[] bytes1 = (byte[]) session.createQuery( "select X'DEADBEEF'" ).getSingleResult();
assertThat( BINARY.toString(bytes1), is("deadbeef") );
byte[] bytes2 = (byte[]) session.createQuery( "select X'deadbeef'" ).getSingleResult();
assertThat( BINARY.toString(bytes2), is("deadbeef") );
}
);
}
@Test @Test
public void testJdbcTimeLiteral(SessionFactoryScope scope) { public void testJdbcTimeLiteral(SessionFactoryScope scope) {
scope.inTransaction( scope.inTransaction(