Feature | Send uniqueidentifier columns natively in bulk copy - #3006
Feature | Send uniqueidentifier columns natively in bulk copy#3006Siarhei (huzaus) wants to merge 3 commits into
Conversation
|
@microsoft-github-policy-service agree |
Bulk copy declared a source column of type microsoft.sql.Types.GUID as CHAR(n) on the wire, so the server ran CONVERT_IMPLICIT(uniqueidentifier, ...) for every row inserted into a uniqueidentifier column. The native uniqueidentifier TDS type was only ever emitted for Always Encrypted base type metadata. Declare the column as uniqueidentifier and send the value in its native 16 byte representation. The new connection property sendGuidAsStringForBulkCopy restores the previous behavior.
a342b5c to
88d4851
Compare
|
Siarhei (@huzaus) |
Native uniqueidentifier is unconditional when the destination column is uniqueidentifier and the source column is declared as microsoft.sql.Types.GUID. Removes sendGuidAsStringForBulkCopy and the tests covering it.
|
Muskan Gupta (@muskan124947) Property removed, uniqueidentifier is now always sent natively. Ready for review. |
|
Siarhei (@huzaus)
Also, please update the PR description |
Report an unparsable GUID as a SQLServerException instead of letting an IllegalArgumentException escape writeToServer in the middle of a batch, and accept the registry format in braces so that parsing on the client keeps the renderings the server accepted. Move the tests into BulkCopyGuidTest, where a CHAR source column carries the character wire format used before and lets every rendering be compared between both formats, add the CHAR control to the Extended Events test, and cover an encrypted uniqueidentifier column.
|
Muskan Gupta (@muskan124947) All five done in
Same comparison showed whitespace was always rejected, so the On AE: a GUID source into an encrypted column is rejected on Tests moved to |
|
Thanks Siarhei (@huzaus) , will take a look |
Brief Description
Bulk copy declared GUID columns as
CHAR(36)on the wire, so the server converted every single row on the way into auniqueidentifiercolumn. This sends the native type instead.What changed in
SQLServerBulkCopy:getDestTypeFromSrcTypenow returnsuniqueidentifierrather thanCHAR(n)writeTypeInfoemits theTDSType.GUIDtoken. It could only be reached via theisBaseTypebranch before, which is written for Always Encrypted base type metadata only, so normal columns always fell through toBIGCHARUtil.asGuidByteArray, same aswriteRPCUUID; null is a0x00length byteSQLServerExceptionusingR_errorConvertingValue, chaining theIllegalArgumentExceptionfromUUID.fromString, so nothing unchecked escapeswriteToServerin the middle of a batchOnly applies when the source declares
microsoft.sql.Types.GUIDand the destination isuniqueidentifier.CHAR/VARCHARsources and the Always Encrypted path are untouched.There is no connection property. Following the review, the registry format in braces is normalized rather than rejected, so this stays a wire format change.
Fixes existing GitHub issue
Fixes #2986
New Public APIs
None.
Behavior
A source column of type
java.sql.Types.CHARwith the same precision reproduces the previous character wire format exactly, which makes the old and the new behavior comparable inside one build. Measured on SQL Server 2022, per rendering of the same GUID:6f9619ff-...-00c04fc964ff6F9619FF-...-00C04FC964FF{6f9619ff-...-00c04fc964ff}declared 38{6f9619ff-...-00c04fc964ff}declared 366f9619ff-...-00c04fc964ff6f9619ff8b86d011b42d00c04fc964ff(...),urn:uuid:...,not-a-guid, emptyThe one difference is the row in bold above: the character format truncated the value to the declared precision before the server converted it, so the registry format did not fit a column declared with 36 characters. Parsing on the client no longer depends on the declared precision. Braces are accepted at every precision, whitespace is rejected as before, and rejections that used to come from the server now come from the driver.
Verification
New
BulkCopyGuidTest, 29 cases, run against SQL Server 2022 (16.0.4265.3):testBulkCopyGuidNativeFormatKeepsCharacterFormatBehavior— every rendering above through both wire formats, asserting equal outcomestestBulkCopyGuidNativeFormatIgnoresDeclaredPrecision— pins the single deliberate differencetestBulkCopyGuidUnparsableValueFailsOnClient— 7 unparsable renderings, asserting the exact driver message;testBulkCopyGuidByteArrayValueFailsOnClientcovers abyte[]value. These run everywhere, including Azure SQL Database, and are the deterministic check that the native format is in use, since the character format would have failed on the server insteadtestBulkCopyGuidConversionOnServer— Extended Events onplan_affecting_convert, parameterized over both source types: nothing is reported against[!BulkInsert]for a GUID source, and a conversion is reported for a CHAR source. The CHAR case is the positive control, so the GUID case cannot pass by capturing nothing at all. A conversion issued by the test acts as a dispatch barrier, and the session is closed throughAutoCloseable, so a failedSTATE = STARTcannot leave a server scoped session behind. The whole test is skipped unlessHAS_PERMS_BY_NAMEreports both permissions on a server that is not Azure SQL DatabasetestBulkCopyGuidIntoCharacterDestination— a GUID source intochar,varchar,ncharandnvarchardestinations still goes out as a character stringtestBulkCopyGuidRoundTripsValues—UUID, lowercase string and null throughISQLServerBulkDataNew
BulkCopyGuidAETest, run against a local Java key store column master key:testBulkCopyGuidIntoEncryptedColumn— an encrypteduniqueidentifiercolumn still receives ciphertext, verified by reading the column over a connection without column encryptiontestBulkCopyGuidSourceIntoEncryptedColumnIsRejected— pins that a GUID source into an encrypted column is rejected, which is also the behavior onmain, sincevalidateDataTypeConversionschecks against the base type of the encrypted columnAgainst pristine
main,testBulkCopyGuidConversionOnServerfails for the GUID source and passes for the CHAR source, reporting:After the change the GUID source reports no conversion.
Full bulk copy suite: 113 tests, the only failure being the pre-existing temporal flake in
BulkCopyColumnMappingTest, which passes on a rerun with unchanged code. A GUID source into avarchar(max)destination fails identically onmainand is left alone.