Skip to content

Commit

Permalink
Add support for TDSType.GUID (#1582) (#2324)
Browse files Browse the repository at this point in the history
* Added support for TDSType.GUID (#1582)

* Fix UUID serialisation

* Add unit tests

* Fix typo, handle PR feedback

---------

Co-authored-by: Cédric de Launois <lnscdr@forem.be>
Co-authored-by: Cédric de Launois <>
  • Loading branch information
vxel and Cédric de Launois committed Mar 20, 2024
1 parent 1d4b7d6 commit 9a8849b
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 3 deletions.
27 changes: 27 additions & 0 deletions src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import java.util.Set;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
Expand Down Expand Up @@ -4784,6 +4785,32 @@ void writeRPCBigDecimal(String sName, BigDecimal bdValue, int nScale, boolean bO
writeBytes(val, 0, val.length);
}

/**
* Append a UUID in RPC transmission format.
*
* @param sName
* the optional parameter name
* @param uuidValue
* the data value
* @param bOut
* boolean true if the data value is being registered as an output parameter
*/
void writeRPCUUID(String sName, UUID uuidValue, boolean bOut) throws SQLServerException {
writeRPCNameValType(sName, bOut, TDSType.GUID);

if (uuidValue == null) {
writeByte((byte) 0);
writeByte((byte) 0);

} else {
writeByte((byte) 0x10); // maximum length = 16
writeByte((byte) 0x10); // length = 16

byte[] val = Util.asGuidByteArray(uuidValue);
writeBytes(val, 0, val.length);
}
}

/**
* Appends a standard v*max header for RPC parameter transmission.
*
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.time.OffsetTime;
import java.util.Calendar;
import java.util.Locale;
import java.util.UUID;


/**
Expand Down Expand Up @@ -1125,6 +1126,10 @@ void execute(DTV dtv, Boolean booleanValue) throws SQLServerException {
setTypeDefinition(dtv);
}

void execute(DTV dtv, UUID uuidValue) throws SQLServerException {
setTypeDefinition(dtv);
}

void execute(DTV dtv, byte[] byteArrayValue) throws SQLServerException {
// exclude JDBC typecasting for Geometry/Geography as these datatypes don't have a size limit.
if (null != byteArrayValue && byteArrayValue.length > DataTypes.SHORT_VARTYPE_MAX_BYTES
Expand Down
21 changes: 18 additions & 3 deletions src/main/java/com/microsoft/sqlserver/jdbc/dtv.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ abstract class DTVExecuteOp {

abstract void execute(DTV dtv, Boolean booleanValue) throws SQLServerException;

abstract void execute(DTV dtv, UUID uuidValue) throws SQLServerException;

abstract void execute(DTV dtv, byte[] byteArrayValue) throws SQLServerException;

abstract void execute(DTV dtv, Blob blobValue) throws SQLServerException;
Expand Down Expand Up @@ -291,7 +293,11 @@ final class SendByRPCOp extends DTVExecuteOp {
}

void execute(DTV dtv, String strValue) throws SQLServerException {
tdsWriter.writeRPCStringUnicode(name, strValue, isOutParam, collation, dtv.isNonPLP);
if (dtv.getJdbcType() == JDBCType.GUID) {
tdsWriter.writeRPCUUID(name, UUID.fromString(strValue), isOutParam);
} else {
tdsWriter.writeRPCStringUnicode(name, strValue, isOutParam, collation, dtv.isNonPLP);
}
}

void execute(DTV dtv, Clob clobValue) throws SQLServerException {
Expand Down Expand Up @@ -1126,6 +1132,10 @@ void execute(DTV dtv, Boolean booleanValue) throws SQLServerException {
tdsWriter.writeRPCBit(name, booleanValue, isOutParam);
}

void execute(DTV dtv, UUID uuidValue) throws SQLServerException {
tdsWriter.writeRPCUUID(name, uuidValue, isOutParam);
}

void execute(DTV dtv, byte[] byteArrayValue) throws SQLServerException {
if (null != cryptoMeta) {
tdsWriter.writeRPCNameValType(name, isOutParam, TDSType.BIGVARBINARY);
Expand Down Expand Up @@ -1537,10 +1547,13 @@ final void executeOp(DTVExecuteOp op) throws SQLServerException {
case VARCHAR:
case LONGVARCHAR:
case CLOB:
case GUID:
op.execute(this, (byte[]) null);
break;

case GUID:
op.execute(this, (UUID) null);
break;

case TINYINT:
if (null != cryptoMeta)
op.execute(this, (byte[]) null);
Expand Down Expand Up @@ -1619,7 +1632,7 @@ final void executeOp(DTVExecuteOp op) throws SQLServerException {
byte[] bArray = Util.asGuidByteArray((UUID) value);
op.execute(this, bArray);
} else {
op.execute(this, String.valueOf(value));
op.execute(this, UUID.fromString(String.valueOf(value)));
}
} else if (JDBCType.SQL_VARIANT == jdbcType) {
op.execute(this, String.valueOf(value));
Expand Down Expand Up @@ -2194,6 +2207,8 @@ void execute(DTV dtv, Short shortValue) throws SQLServerException {}

void execute(DTV dtv, Boolean booleanValue) throws SQLServerException {}

void execute(DTV dtv, UUID uuidValue) throws SQLServerException {}

void execute(DTV dtv, byte[] byteArrayValue) throws SQLServerException {}

void execute(DTV dtv, Blob blobValue) throws SQLServerException {
Expand Down
97 changes: 97 additions & 0 deletions src/test/java/com/microsoft/sqlserver/jdbc/datatypes/GuidTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.microsoft.sqlserver.jdbc.datatypes;

import com.microsoft.sqlserver.jdbc.RandomUtil;
import com.microsoft.sqlserver.jdbc.SQLServerResultSet;
import com.microsoft.sqlserver.jdbc.TestResource;
import com.microsoft.sqlserver.jdbc.TestUtils;
import com.microsoft.sqlserver.testframework.AbstractSQLGenerator;
import com.microsoft.sqlserver.testframework.AbstractTest;
import microsoft.sql.Types;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.UUID;

import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.jupiter.api.Assertions.assertEquals;


/*
* This test is for testing the serialisation of String as microsoft.sql.Types.GUID
*/
@RunWith(JUnitPlatform.class)
public class GuidTest extends AbstractTest {

final static String tableName = RandomUtil.getIdentifier("GuidTestTable");
final static String escapedTableName = AbstractSQLGenerator.escapeIdentifier(tableName);

@BeforeAll
public static void setupTests() throws Exception {
setConnection();
}

/*
* Test UUID conversions
*/
@Test
public void testGuid() throws Exception {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {

// Create the test table
TestUtils.dropTableIfExists(escapedTableName, stmt);

String query = "create table " + escapedTableName
+ " (uuid uniqueidentifier, id int IDENTITY primary key)";
stmt.executeUpdate(query);

UUID uuid = UUID.randomUUID();
String uuidString = uuid.toString();
int id = 1;

try (PreparedStatement pstmt = conn.prepareStatement("INSERT INTO " + escapedTableName
+ " VALUES(?) SELECT * FROM " + escapedTableName + " where id = ?")) {

pstmt.setObject(1, uuidString, Types.GUID);
pstmt.setObject(2, id++);
pstmt.execute();
pstmt.getMoreResults();
try (SQLServerResultSet rs = (SQLServerResultSet) pstmt.getResultSet()) {
rs.next();
assertEquals(uuid, UUID.fromString(rs.getUniqueIdentifier(1)));
}

// Test NULL GUID
pstmt.setObject(1, null, Types.GUID);
pstmt.setObject(2, id++);
pstmt.execute();
pstmt.getMoreResults();
try (SQLServerResultSet rs = (SQLServerResultSet) pstmt.getResultSet()) {
rs.next();
String s = rs.getUniqueIdentifier(1);
assertNull(s);
assertTrue(rs.wasNull());
}

// Test Illegal GUID
try {
pstmt.setObject(1, "garbage", Types.GUID);
fail(TestResource.getResource("R_expectedFailPassed"));
} catch (IllegalArgumentException e) {
assertEquals("Invalid UUID string: garbage", e.getMessage());
}
}
} finally {
try (Statement stmt = connection.createStatement()) {
TestUtils.dropTableIfExists(escapedTableName, stmt);
}
}
}

}

0 comments on commit 9a8849b

Please sign in to comment.