Skip to content
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

Add support for TDSType.GUID (#1582) #2324

Merged
merged 4 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
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
96 changes: 96 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,96 @@
package com.microsoft.sqlserver.jdbc.datatypes;

import com.microsoft.sqlserver.jdbc.RandomUtil;
import com.microsoft.sqlserver.jdbc.SQLServerResultSet;
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 GUFID
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo?? same in line 81?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this is indeed a typo, copy-pasted to line 81... Thanks. This is fixed in my latest commit.

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 GUFID
try {
pstmt.setObject(1, "garbage", Types.GUID);
fail("Must throw an IllegalArgumentException for this illegal UUID");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fail("Must throw an IllegalArgumentException for this illegal UUID");
fail(TestResource.getResource("R_expectedFailPassed"));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I applied your suggestion in the latest commit.

} catch (IllegalArgumentException e) {
assertEquals("Invalid UUID string: garbage", e.getMessage());
}
}
} finally {
try (Statement stmt = connection.createStatement()) {
TestUtils.dropTableIfExists(escapedTableName, stmt);
}
}
}

}
Loading