-
Notifications
You must be signed in to change notification settings - Fork 451
Introduce new prepareMethod exec for PreparedStatement #2844
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
Open
divang
wants to merge
22
commits into
main
Choose a base branch
from
dev/divang/sybase-migration-dynamic-prepare
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,962
−33
Open
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
f8811ea
Implement Sybase migration support with dynamicPrepare property
divang 7dac544
Revert "Implement Sybase migration support with dynamicPrepare property"
divang cfb0e43
Add SqlServerPreparedStatementExpander with comprehensive test coverage
divang 73cc7e2
Fix SqlServerPreparedStatementExpander test failures
divang b5f2299
Add 7 new E2E test cases for various SQL Server data types
divang 9acdfc8
Added Sybase test code
divang c49e2d3
Merge branch 'main' of https://github.com/microsoft/mssql-jdbc into d…
divang 47009b3
Add comprehensive test coverage for EXEC prepare method (#2834)
Ananya2 70d8b30
Fixed PrepareMethod - EXEC method related test cases
divang 6f5de5c
Fixed pom.xml typo
divang 0615022
Remove Sybase-related test files
divang 676f27d
Consolidate SQL parameter expansion into SQLServerConnection
divang 665951c
Fix parameter expansion to respect sendStringParametersAsUnicode and …
divang caf775c
Apply sendStringParametersAsUnicode config to Clob and fallback strin…
divang f1d26c1
Refactor replaceParameterMarkers to simplify parameter substitution
divang ba3b584
Fixed batch update count issue and cleaned the code
divang 37ebc5c
When prepareMethod is set to 'exec', batch execution was failing enti…
divang c7712a9
Update count in case of batch related sql statements
divang ed847f5
Batching of SQL statement in case of exec preparedmethod
divang d01859f
Added replaceParameterMarkersWithValues() method and propogated execp…
divang 8e2db67
Correct temp table naming and constraint violation assertions in Batc…
divang 9adf6bf
Cleaned up unused code.
divang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,7 +15,9 @@ | |
| import java.net.InetSocketAddress; | ||
| import java.net.SocketException; | ||
| import java.net.UnknownHostException; | ||
| import java.sql.Blob; | ||
| import java.sql.CallableStatement; | ||
| import java.sql.Clob; | ||
| import java.sql.Connection; | ||
| import java.sql.DatabaseMetaData; | ||
| import java.sql.PreparedStatement; | ||
|
|
@@ -29,6 +31,7 @@ | |
| import java.sql.Savepoint; | ||
| import java.sql.Statement; | ||
| import java.text.MessageFormat; | ||
| import java.text.SimpleDateFormat; | ||
| import java.time.Duration; | ||
| import java.time.Instant; | ||
| import java.util.ArrayList; | ||
|
|
@@ -230,6 +233,11 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial | |
| /** Engine Edition 11 = Azure Synapse serverless SQL pool */ | ||
| private static final int ENGINE_EDITION_SQL_AZURE_SYNAPSE_SERVERLESS_SQL_POOL = 11; | ||
|
|
||
| // --- SQL Parameter Expansion Methods --- | ||
| private static final SimpleDateFormat DATE_FMT = new SimpleDateFormat("yyyy-MM-dd"); | ||
| private static final SimpleDateFormat TIME_FMT = new SimpleDateFormat("HH:mm:ss"); | ||
| private static final SimpleDateFormat TS_FMT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); | ||
|
|
||
| /** | ||
| * Azure SQL server endpoints | ||
| */ | ||
|
|
@@ -8453,6 +8461,163 @@ String replaceParameterMarkers(String sqlSrc, int[] paramPositions, Parameter[] | |
| return new String(sqlDst, 0, dstBegin); | ||
| } | ||
|
|
||
| String replaceParameterMarkersWithValues(String sqlSrc, int[] paramPositions, Parameter[] params, | ||
| boolean isReturnValueSyntax) throws SQLServerException { | ||
|
|
||
| // EXEC method: substitute actual parameter values directly into SQL | ||
| if (params == null || params.length == 0) { | ||
| return sqlSrc; | ||
| } | ||
|
|
||
| StringBuilder result = new StringBuilder(sqlSrc.length() + params.length * 20); | ||
| try { | ||
| // Estimate capacity for the result | ||
| int srcBegin = 0; | ||
|
|
||
| for (int paramIndex = 0; paramIndex < paramPositions.length; paramIndex++) { | ||
| int srcEnd = paramPositions[paramIndex]; | ||
|
|
||
| // Append SQL text before this parameter marker | ||
| result.append(sqlSrc, srcBegin, srcEnd); | ||
|
|
||
| // Get parameter value and format it | ||
| Object value = null; | ||
| if (params[paramIndex] != null) { | ||
| value = params[paramIndex].getSetterValue(); | ||
| } | ||
|
|
||
| // Append formatted literal value | ||
| result.append(formatLiteralValue(value)); | ||
|
|
||
| // Move past the '?' marker | ||
| srcBegin = srcEnd + 1; | ||
| } | ||
|
|
||
| // Append remaining SQL after last parameter | ||
| result.append(sqlSrc, srcBegin, sqlSrc.length()); | ||
| } catch (Exception e) { | ||
| throw new SQLServerException("Error during parameter replacement", e); | ||
| } | ||
|
|
||
| return result.toString(); | ||
| } | ||
|
|
||
| // Format Java value into a T-SQL literal safe for SQL Server | ||
| private String formatLiteralValue(Object value) throws SQLException { | ||
| if (value == null) | ||
| return "NULL"; | ||
|
|
||
| else if (value instanceof String) { | ||
| String prefix = sendStringParametersAsUnicode() ? "N'" : "'"; | ||
| return prefix + escapeSQLString((String) value) + "'"; | ||
| } | ||
|
|
||
| else if (value instanceof Character) { | ||
| String prefix = sendStringParametersAsUnicode() ? "N'" : "'"; | ||
| return prefix + escapeSQLString(value.toString()) + "'"; | ||
| } | ||
|
|
||
| else if (value instanceof Boolean) { | ||
| return ((Boolean) value) ? "1" : "0"; | ||
| } | ||
|
|
||
| else if (value instanceof java.math.BigDecimal) { | ||
| // Use toPlainString() to avoid scientific notation | ||
| java.math.BigDecimal bd = (java.math.BigDecimal) value; | ||
| String plainStr = bd.toPlainString(); | ||
|
|
||
| // For very large or high-precision decimals, wrap in CAST to preserve precision | ||
| // SQL Server decimal max precision is 38, scale max 38 | ||
| int precision = bd.precision(); | ||
| int scale = bd.scale(); | ||
|
|
||
| if (precision > 18 || scale > 6) { | ||
| // Need explicit CAST for high precision numbers | ||
| // Clamp to SQL Server limits: decimal(38, min(scale, 38)) | ||
| // Ensure scale <= precision | ||
| int sqlPrecision = Math.min(precision, 38); | ||
| int sqlScale = Math.min(Math.min(scale, 38), sqlPrecision); | ||
| return "CAST(" + plainStr + " AS DECIMAL(" + sqlPrecision + "," + sqlScale + "))"; | ||
| } | ||
| return plainStr; | ||
| } | ||
|
|
||
| // Integer types can use toString() safely | ||
| else if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long) { | ||
| return value.toString(); | ||
| } | ||
|
|
||
| // Float and Double need special handling to avoid precision loss and scientific | ||
| // notation | ||
| else if (value instanceof Float) { | ||
| // Convert Float to BigDecimal for precise representation | ||
| return new java.math.BigDecimal(value.toString()).toPlainString(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should test cases for MIN and MAX values of decimal, BigDecimal, Float, Double, Integer, BigInteger. |
||
| } | ||
|
|
||
| else if (value instanceof Double) { | ||
| // Convert Double to BigDecimal for precise representation | ||
| return new java.math.BigDecimal(value.toString()).toPlainString(); | ||
| } | ||
|
|
||
| else if (value instanceof java.sql.Date) { | ||
| return "CAST('" + DATE_FMT.format((java.util.Date) value) + "' AS DATE)"; | ||
| } | ||
|
|
||
| else if (value instanceof java.sql.Time) { | ||
| return "CAST('" + TIME_FMT.format((java.util.Date) value) + "' AS TIME)"; | ||
| } | ||
|
|
||
| else if (value instanceof java.sql.Timestamp) { | ||
| return "CAST('" + TS_FMT.format((java.util.Date) value) + "' AS DATETIME2)"; | ||
| } | ||
|
|
||
| else if (value instanceof java.util.Date) { | ||
| // generic java.util.Date -> timestamp | ||
| return "CAST('" + TS_FMT.format((java.util.Date) value) + "' AS DATETIME2)"; | ||
| } | ||
|
|
||
| else if (value instanceof byte[]) { | ||
| return bytesToHexLiteral((byte[]) value); | ||
| } | ||
|
|
||
| else if (value instanceof Blob) { | ||
| Blob b = (Blob) value; | ||
| int len = (int) b.length(); | ||
| byte[] bytes = b.getBytes(1, len); | ||
| return bytesToHexLiteral(bytes); | ||
| } | ||
|
|
||
| else if (value instanceof Clob) { | ||
| Clob c = (Clob) value; | ||
| String s = c.getSubString(1, (int) c.length()); | ||
| String prefix = sendStringParametersAsUnicode() ? "N'" : "'"; | ||
| return prefix + escapeSQLString(s) + "'"; | ||
| } | ||
|
|
||
| else { | ||
| // fallback | ||
| String prefix = sendStringParametersAsUnicode() ? "N'" : "'"; | ||
| return prefix + escapeSQLString(value.toString()) + "'"; | ||
| } | ||
| } | ||
|
|
||
| private String escapeSQLString(String s) { | ||
| if (s == null || s.isEmpty()) | ||
| return ""; | ||
| // double single quotes | ||
| return s.replace("'", "''"); | ||
| } | ||
|
|
||
| private String bytesToHexLiteral(byte[] bytes) { | ||
| if (bytes == null || bytes.length == 0) | ||
| return "0x"; | ||
| StringBuilder sb = new StringBuilder(bytes.length * 2 + 2); | ||
| sb.append("0x"); | ||
| for (byte b : bytes) | ||
| sb.append(String.format("%02X", b)); | ||
| return sb.toString(); | ||
| } | ||
|
|
||
| /** | ||
| * Makes a SQL Server style parameter name. | ||
| * | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did we confirm if client-side formatting is really needed or can SQL Server's cast can handle formats automatically?