diff --git a/pom.xml b/pom.xml
index 321c4226d..7967c4c9b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,10 +48,11 @@
clientCertAuth - - For tests requiring client certificate authentication
setup (excluded by default) - - - - - - - - - - - - - - - - - - - - - - -
requireSecret - For tests requiring setting up secrets manually
+ JSONTest - For tests using JSON data type
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Default testing enabled with SQL Server 2019 (SQLv15) -->
- xSQLv12,xSQLv15,NTLM,MSI,reqExternalSetup,clientCertAuth,fedAuth,kerberos,requireSecret
+ xSQLv12,xSQLv15,NTLM,MSI,reqExternalSetup,clientCertAuth,fedAuth,kerberos,requireSecret,JSONTest
-preview
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Column.java b/src/main/java/com/microsoft/sqlserver/jdbc/Column.java
index 50958571d..979d413c7 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/Column.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/Column.java
@@ -336,7 +336,7 @@ else if (jdbcType.isBinary()) {
// Update of Unicode SSType from textual JDBCType: Use Unicode.
if ((SSType.NCHAR == ssType || SSType.NVARCHAR == ssType || SSType.NVARCHARMAX == ssType
- || SSType.NTEXT == ssType || SSType.XML == ssType) &&
+ || SSType.NTEXT == ssType || SSType.XML == ssType || SSType.JSON == ssType) &&
(JDBCType.CHAR == jdbcType || JDBCType.VARCHAR == jdbcType || JDBCType.LONGVARCHAR == jdbcType
|| JDBCType.CLOB == jdbcType)) {
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java b/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java
index 018c483f8..ac22871b3 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java
@@ -65,6 +65,7 @@ enum TDSType {
NTEXT(0x63), // 99
UDT(0xF0), // -16
XML(0xF1), // -15
+ JSON(0xF4), // -12
// LONGLEN types
SQL_VARIANT(0x62); // 98
@@ -148,7 +149,8 @@ enum SSType {
XML(Category.XML, "xml", JDBCType.LONGNVARCHAR),
TIMESTAMP(Category.TIMESTAMP, "timestamp", JDBCType.BINARY),
GEOMETRY(Category.UDT, "geometry", JDBCType.GEOMETRY),
- GEOGRAPHY(Category.UDT, "geography", JDBCType.GEOGRAPHY);
+ GEOGRAPHY(Category.UDT, "geography", JDBCType.GEOGRAPHY),
+ JSON(Category.JSON, "json", JDBCType.JSON);
final Category category;
private final String name;
@@ -204,7 +206,8 @@ enum Category {
TIMESTAMP,
UDT,
SQL_VARIANT,
- XML;
+ XML,
+ JSON;
private static final Category[] VALUES = values();
}
@@ -266,7 +269,12 @@ enum GetterConversion {
SQL_VARIANT(SSType.Category.SQL_VARIANT, EnumSet.of(JDBCType.Category.CHARACTER, JDBCType.Category.SQL_VARIANT,
JDBCType.Category.NUMERIC, JDBCType.Category.DATE, JDBCType.Category.TIME, JDBCType.Category.BINARY,
- JDBCType.Category.TIMESTAMP, JDBCType.Category.NCHARACTER, JDBCType.Category.GUID));
+ JDBCType.Category.TIMESTAMP, JDBCType.Category.NCHARACTER, JDBCType.Category.GUID)),
+
+ JSON(SSType.Category.JSON, EnumSet.of(JDBCType.Category.CHARACTER, JDBCType.Category.LONG_CHARACTER,
+ JDBCType.Category.CLOB, JDBCType.Category.NCHARACTER, JDBCType.Category.LONG_NCHARACTER,
+ JDBCType.Category.NCLOB, JDBCType.Category.BINARY, JDBCType.Category.LONG_BINARY,
+ JDBCType.Category.BLOB, JDBCType.Category.JSON));
private final SSType.Category from;
private final EnumSet to;
@@ -452,7 +460,9 @@ JDBCType getJDBCType(SSType ssType, JDBCType jdbcTypeFromApp) {
case NTEXT:
jdbcType = JDBCType.LONGVARCHAR;
break;
-
+ case JSON:
+ jdbcType = JDBCType.JSON;
+ break;
case XML:
default:
jdbcType = JDBCType.LONGVARBINARY;
@@ -673,8 +683,9 @@ enum JDBCType {
SQL_VARIANT(Category.SQL_VARIANT, microsoft.sql.Types.SQL_VARIANT, Object.class.getName()),
GEOMETRY(Category.GEOMETRY, microsoft.sql.Types.GEOMETRY, Object.class.getName()),
GEOGRAPHY(Category.GEOGRAPHY, microsoft.sql.Types.GEOGRAPHY, Object.class.getName()),
- LOCALDATETIME(Category.TIMESTAMP, java.sql.Types.TIMESTAMP, LocalDateTime.class.getName());
-
+ LOCALDATETIME(Category.TIMESTAMP, java.sql.Types.TIMESTAMP, LocalDateTime.class.getName()),
+ JSON(Category.JSON, microsoft.sql.Types.JSON, Object.class.getName());
+
final Category category;
private final int intValue;
private final String className;
@@ -722,7 +733,8 @@ enum Category {
GUID,
SQL_VARIANT,
GEOMETRY,
- GEOGRAPHY;
+ GEOGRAPHY,
+ JSON;
private static final Category[] VALUES = values();
}
@@ -733,7 +745,7 @@ enum SetterConversion {
JDBCType.Category.TIME, JDBCType.Category.TIMESTAMP, JDBCType.Category.DATETIMEOFFSET,
JDBCType.Category.CHARACTER, JDBCType.Category.LONG_CHARACTER, JDBCType.Category.NCHARACTER,
JDBCType.Category.LONG_NCHARACTER, JDBCType.Category.BINARY, JDBCType.Category.LONG_BINARY,
- JDBCType.Category.GUID, JDBCType.Category.SQL_VARIANT)),
+ JDBCType.Category.GUID, JDBCType.Category.SQL_VARIANT, JDBCType.Category.JSON)),
LONG_CHARACTER(JDBCType.Category.LONG_CHARACTER, EnumSet.of(JDBCType.Category.CHARACTER,
JDBCType.Category.LONG_CHARACTER, JDBCType.Category.NCHARACTER, JDBCType.Category.LONG_NCHARACTER,
@@ -795,7 +807,8 @@ enum SetterConversion {
GEOMETRY(JDBCType.Category.GEOMETRY, EnumSet.of(JDBCType.Category.GEOMETRY)),
- GEOGRAPHY(JDBCType.Category.GEOGRAPHY, EnumSet.of(JDBCType.Category.GEOGRAPHY));
+ GEOGRAPHY(JDBCType.Category.GEOGRAPHY, EnumSet.of(JDBCType.Category.GEOGRAPHY)),
+ JSON(JDBCType.Category.JSON, EnumSet.of(JDBCType.Category.JSON));
private final JDBCType.Category from;
private final EnumSet to;
@@ -832,7 +845,7 @@ enum UpdaterConversion {
SSType.Category.DATETIMEOFFSET, SSType.Category.CHARACTER, SSType.Category.LONG_CHARACTER,
SSType.Category.NCHARACTER, SSType.Category.LONG_NCHARACTER, SSType.Category.XML,
SSType.Category.BINARY, SSType.Category.LONG_BINARY, SSType.Category.UDT, SSType.Category.GUID,
- SSType.Category.TIMESTAMP, SSType.Category.SQL_VARIANT)),
+ SSType.Category.TIMESTAMP, SSType.Category.SQL_VARIANT, SSType.Category.JSON)),
LONG_CHARACTER(JDBCType.Category.LONG_CHARACTER, EnumSet.of(SSType.Category.CHARACTER,
SSType.Category.LONG_CHARACTER, SSType.Category.NCHARACTER, SSType.Category.LONG_NCHARACTER,
@@ -895,7 +908,9 @@ enum UpdaterConversion {
SSType.Category.DATETIMEOFFSET, SSType.Category.CHARACTER, SSType.Category.LONG_CHARACTER,
SSType.Category.NCHARACTER, SSType.Category.LONG_NCHARACTER)),
- SQL_VARIANT(JDBCType.Category.SQL_VARIANT, EnumSet.of(SSType.Category.SQL_VARIANT));
+ SQL_VARIANT(JDBCType.Category.SQL_VARIANT, EnumSet.of(SSType.Category.SQL_VARIANT)),
+
+ JSON(JDBCType.Category.JSON, EnumSet.of(SSType.Category.JSON));
private final JDBCType.Category from;
private final EnumSet to;
@@ -970,7 +985,7 @@ boolean isBinary() {
* @return true if the JDBC type is textual
*/
private final static EnumSet textualCategories = EnumSet.of(Category.CHARACTER, Category.LONG_CHARACTER,
- Category.CLOB, Category.NCHARACTER, Category.LONG_NCHARACTER, Category.NCLOB);
+ Category.CLOB, Category.NCHARACTER, Category.LONG_NCHARACTER, Category.NCLOB);
boolean isTextual() {
return textualCategories.contains(category);
@@ -997,6 +1012,7 @@ int asJavaSqlType() {
return java.sql.Types.CHAR;
case NVARCHAR:
case SQLXML:
+ case JSON:
return java.sql.Types.VARCHAR;
case LONGNVARCHAR:
return java.sql.Types.LONGVARCHAR;
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
index f51a5430c..a8bded545 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
@@ -169,6 +169,11 @@ final class TDS {
static final byte TDS_FEATURE_EXT_AZURESQLDNSCACHING = 0x0B;
static final byte TDS_FEATURE_EXT_SESSIONRECOVERY = 0x01;
+ // JSON support
+ static final byte TDS_FEATURE_EXT_JSONSUPPORT = 0x0D;
+ static final byte JSONSUPPORT_NOT_SUPPORTED = 0x00;
+ static final byte MAX_JSONSUPPORT_VERSION = 0x01;
+
static final int TDS_TVP = 0xF3;
static final int TVP_ROW = 0x01;
static final int TVP_NULL_TOKEN = 0xFFFF;
@@ -237,6 +242,9 @@ static final String getTokenName(int tdsTokenType) {
return "TDS_FEATURE_EXT_AZURESQLDNSCACHING (0x0B)";
case TDS_FEATURE_EXT_SESSIONRECOVERY:
return "TDS_FEATURE_EXT_SESSIONRECOVERY (0x01)";
+ case TDS_FEATURE_EXT_JSONSUPPORT:
+ return "TDS_FEATURE_EXT_JSONSUPPORT (0x0D)";
+
default:
return "unknown token (0x" + Integer.toHexString(tdsTokenType).toUpperCase() + ")";
}
@@ -4856,6 +4864,11 @@ void writeRPCStringUnicode(String sValue) throws SQLServerException {
writeRPCStringUnicode(null, sValue, false, null);
}
+ void writeRPCJson(String sName, String sValue, boolean bOut) throws SQLServerException {
+ writeRPCNameValType(sName, bOut, TDSType.JSON);
+ writeLong(0xFFFFFFFFFFFFFFFFL);
+ }
+
/**
* Writes a string value as Unicode for RPC
*
@@ -5241,6 +5254,7 @@ private void writeInternalTVPRowValues(JDBCType jdbcType, String currentColumnSt
case LONGVARCHAR:
case LONGNVARCHAR:
case SQLXML:
+ case JSON:
isShortValue = (2L * columnPair.getValue().precision) <= DataTypes.SHORT_VARTYPE_MAX_BYTES;
isNull = (null == currentColumnStringValue);
dataLength = isNull ? 0 : currentColumnStringValue.length() * 2;
@@ -5476,6 +5490,7 @@ void writeTVPColumnMetaData(TVP value) throws SQLServerException {
case LONGVARCHAR:
case LONGNVARCHAR:
case SQLXML:
+ case JSON:
writeByte(TDSType.NVARCHAR.byteValue());
isShortValue = (2L * pair.getValue().precision) <= DataTypes.SHORT_VARTYPE_MAX_BYTES;
// Use PLP encoding on Yukon and later with long values
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java
index 807bf3250..c6eaac835 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java
@@ -899,7 +899,9 @@ private void setTypeDefinition(DTV dtv) {
case SQLXML:
param.typeDefinition = SSType.XML.toString();
break;
-
+ case JSON:
+ param.typeDefinition = SSType.JSON.toString();
+ break;
case TVP:
// definition should contain the TVP name and the keyword READONLY
String schema = param.schemaName;
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCSVFileRecord.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCSVFileRecord.java
index 1b21544f9..8dfa54c51 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCSVFileRecord.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCSVFileRecord.java
@@ -545,6 +545,10 @@ else if ((null != columnNames) && (columnNames.length >= positionInSource))
columnMetadata.put(positionInSource,
new ColumnMetadata(colName, java.sql.Types.LONGNVARCHAR, precision, scale, dateTimeFormatter));
break;
+ case microsoft.sql.Types.JSON:
+ columnMetadata.put(positionInSource,
+ new ColumnMetadata(colName, microsoft.sql.Types.JSON, precision, scale, dateTimeFormatter));
+ break;
/*
* Redirecting Float as Double based on data type mapping
* https://msdn.microsoft.com/library/ms378878%28v=sql.110%29.aspx
@@ -601,11 +605,13 @@ public void setEscapeColumnDelimitersCSV(boolean escapeDelimiters) {
this.escapeDelimiters = escapeDelimiters;
}
+
private static String[] escapeQuotesRFC4180(String[] tokens) throws SQLServerException {
if (null == tokens) {
return tokens;
}
for (int i = 0; i < tokens.length; i++) {
+
boolean escaped = false;
int j = 0;
StringBuilder sb = new StringBuilder();
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java
index 35f239608..515cbb607 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java
@@ -780,7 +780,8 @@ private void writeColumnMetaDataColumnData(TDSWriter tdsWriter, int idx) throws
collation = connection.getDatabaseCollation();
if ((java.sql.Types.NCHAR == bulkJdbcType) || (java.sql.Types.NVARCHAR == bulkJdbcType)
- || (java.sql.Types.LONGNVARCHAR == bulkJdbcType)) {
+ || (java.sql.Types.LONGNVARCHAR == bulkJdbcType)
+ || (microsoft.sql.Types.JSON == bulkJdbcType)) {
isStreaming = (DataTypes.SHORT_VARTYPE_MAX_CHARS < bulkPrecision)
|| (DataTypes.SHORT_VARTYPE_MAX_CHARS < destPrecision);
} else {
@@ -837,7 +838,8 @@ else if (((java.sql.Types.CHAR == bulkJdbcType) || (java.sql.Types.VARCHAR == bu
int baseDestPrecision = destCryptoMeta.baseTypeInfo.getPrecision();
if ((java.sql.Types.NCHAR == baseDestJDBCType) || (java.sql.Types.NVARCHAR == baseDestJDBCType)
- || (java.sql.Types.LONGNVARCHAR == baseDestJDBCType))
+ || (java.sql.Types.LONGNVARCHAR == baseDestJDBCType)
+ || (microsoft.sql.Types.JSON == baseDestJDBCType))
isStreaming = (DataTypes.SHORT_VARTYPE_MAX_CHARS < baseDestPrecision);
else
isStreaming = (DataTypes.SHORT_VARTYPE_MAX_BYTES < baseDestPrecision);
@@ -997,6 +999,7 @@ private void writeTypeInfo(TDSWriter tdsWriter, int srcJdbcType, int srcScale, i
case java.sql.Types.LONGVARCHAR:
case java.sql.Types.VARCHAR: // 0xA7
+ case microsoft.sql.Types.JSON:
if (unicodeConversionRequired(srcJdbcType, destSSType)) {
tdsWriter.writeByte(TDSType.NVARCHAR.byteValue());
if (isStreaming) {
@@ -1025,7 +1028,6 @@ private void writeTypeInfo(TDSWriter tdsWriter, int srcJdbcType, int srcScale, i
}
collation.writeCollation(tdsWriter);
break;
-
case java.sql.Types.BINARY: // 0xAD
tdsWriter.writeByte(TDSType.BIGBINARY.byteValue());
tdsWriter.writeShort((short) (srcPrecision));
@@ -1470,6 +1472,8 @@ private String getDestTypeFromSrcType(int srcColIndx, int destColIndx,
}
case microsoft.sql.Types.SQL_VARIANT:
return SSType.SQL_VARIANT.toString();
+ case microsoft.sql.Types.JSON:
+ return SSType.JSON.toString();
default: {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_BulkTypeNotSupported"));
Object[] msgArgs = {JDBCType.of(bulkJdbcType).toString().toLowerCase(Locale.ENGLISH)};
@@ -2090,6 +2094,7 @@ private void writeNullToTdsWriter(TDSWriter tdsWriter, int srcJdbcType,
case java.sql.Types.LONGVARCHAR:
case java.sql.Types.LONGNVARCHAR:
case java.sql.Types.LONGVARBINARY:
+ case microsoft.sql.Types.JSON:
if (isStreaming) {
tdsWriter.writeLong(PLPInputStream.PLP_NULL);
} else {
@@ -2322,6 +2327,7 @@ else if (null != sourceCryptoMeta) {
case java.sql.Types.LONGVARCHAR:
case java.sql.Types.CHAR: // Fixed-length, non-Unicode string data.
case java.sql.Types.VARCHAR: // Variable-length, non-Unicode string data.
+ case microsoft.sql.Types.JSON:
if (isStreaming) // PLP
{
// PLP_BODY rule in TDS
@@ -2460,7 +2466,6 @@ else if (null != sourceCryptoMeta) {
}
}
break;
-
case java.sql.Types.LONGVARBINARY:
case java.sql.Types.BINARY:
case java.sql.Types.VARBINARY:
@@ -2986,6 +2991,7 @@ private Object readColumnFromResultSet(int srcColOrdinal, int srcJdbcType, boole
case java.sql.Types.LONGNVARCHAR:
case java.sql.Types.NCHAR:
case java.sql.Types.NVARCHAR:
+ case microsoft.sql.Types.JSON:
// PLP if stream type and both the source and destination are not encrypted
// This is because AE does not support streaming types.
// Therefore an encrypted source or destination means the data must not actually be streaming data
@@ -3060,7 +3066,8 @@ private void writeColumn(TDSWriter tdsWriter, int srcColOrdinal, int destColOrdi
destPrecision = destColumnMetadata.get(destColOrdinal).precision;
if ((java.sql.Types.NCHAR == srcJdbcType) || (java.sql.Types.NVARCHAR == srcJdbcType)
- || (java.sql.Types.LONGNVARCHAR == srcJdbcType)) {
+ || (java.sql.Types.LONGNVARCHAR == srcJdbcType)
+ || (microsoft.sql.Types.JSON == srcJdbcType)) {
isStreaming = (DataTypes.SHORT_VARTYPE_MAX_CHARS < srcPrecision)
|| (DataTypes.SHORT_VARTYPE_MAX_CHARS < destPrecision);
} else {
@@ -3771,6 +3778,7 @@ void setDestinationTableMetadata(SQLServerResultSet rs) {
private boolean unicodeConversionRequired(int jdbcType, SSType ssType) {
return ((java.sql.Types.CHAR == jdbcType || java.sql.Types.VARCHAR == jdbcType
|| java.sql.Types.LONGNVARCHAR == jdbcType)
- && (SSType.NCHAR == ssType || SSType.NVARCHAR == ssType || SSType.NVARCHARMAX == ssType));
+ && (SSType.NCHAR == ssType || SSType.NVARCHAR == ssType || SSType.NVARCHARMAX == ssType
+ || SSType.JSON == ssType));
}
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
index f5e6bc317..06b27b845 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
@@ -1190,6 +1190,16 @@ byte getServerSupportedDataClassificationVersion() {
return serverSupportedDataClassificationVersion;
}
+ /** whether server supports JSON */
+ private boolean serverSupportsJSON = false;
+
+ /** server supported JSON version */
+ private byte serverSupportedJSONVersion = TDS.JSONSUPPORT_NOT_SUPPORTED;
+
+ boolean getServerSupportsJSON() {
+ return serverSupportsJSON;
+ }
+
/** Boolean that indicates whether LOB objects created by this connection should be loaded into memory */
private boolean delayLoadingLobs = SQLServerDriverBooleanProperty.DELAY_LOADING_LOBS.getDefaultValue();
@@ -5544,6 +5554,17 @@ int writeDNSCacheFeatureRequest(boolean write, /* if false just calculates the l
return len;
}
+ int writeJSONSupportFeatureRequest(boolean write, /* if false just calculates the length */
+ TDSWriter tdsWriter) throws SQLServerException {
+ int len = 6; // 1byte = featureID, 4bytes = featureData length, 1 bytes = Version
+ if (write) {
+ tdsWriter.writeByte(TDS.TDS_FEATURE_EXT_JSONSUPPORT);
+ tdsWriter.writeInt(1);
+ tdsWriter.writeByte(TDS.MAX_JSONSUPPORT_VERSION);
+ }
+ return len;
+ }
+
int writeIdleConnectionResiliencyRequest(boolean write, TDSWriter tdsWriter) throws SQLServerException {
SessionStateTable ssTable = sessionRecovery.getSessionStateTable();
int len = 1;
@@ -6674,6 +6695,24 @@ private void onFeatureExtAck(byte featureId, byte[] data) throws SQLServerExcept
sessionRecovery.setConnectionRecoveryPossible(true);
break;
}
+
+ case TDS.TDS_FEATURE_EXT_JSONSUPPORT: {
+ if (connectionlogger.isLoggable(Level.FINER)) {
+ connectionlogger.fine(toString() + " Received feature extension acknowledgement for JSON Support.");
+ }
+
+ if (1 != data.length) {
+ throw new SQLServerException(SQLServerException.getErrString("R_unknownJSONSupportValue"), null);
+ }
+
+ serverSupportedJSONVersion = data[0];
+ if (0 == serverSupportedJSONVersion || serverSupportedJSONVersion > TDS.MAX_JSONSUPPORT_VERSION) {
+ throw new SQLServerException(SQLServerException.getErrString("R_InvalidJSONVersionNumber"), null);
+ }
+ serverSupportsJSON = true;
+ break;
+ }
+
default: {
// Unknown feature ack
throw new SQLServerException(SQLServerException.getErrString("R_UnknownFeatureAck"), null);
@@ -6974,6 +7013,9 @@ final boolean complete(LogonCommand logonCommand, TDSReader tdsReader) throws SQ
len = len + writeDNSCacheFeatureRequest(false, tdsWriter);
+ // request JSON support
+ len += writeJSONSupportFeatureRequest(false, tdsWriter);
+
len = len + 1; // add 1 to length because of FeatureEx terminator
// Idle Connection Resiliency is requested
@@ -7170,6 +7212,7 @@ final boolean complete(LogonCommand logonCommand, TDSReader tdsReader) throws SQ
writeDataClassificationFeatureRequest(true, tdsWriter);
writeUTF8SupportFeatureRequest(true, tdsWriter);
writeDNSCacheFeatureRequest(true, tdsWriter);
+ writeJSONSupportFeatureRequest(true, tdsWriter);
// Idle Connection Resiliency is requested
if (connectRetryCount > 0) {
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java
index 6abdaa174..9afbb5bd2 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java
@@ -300,6 +300,7 @@ private void internalAddrow(JDBCType jdbcType, Object val, Object[] rowValues,
case LONGVARCHAR:
case LONGNVARCHAR:
case SQLXML:
+ case JSON:
if (val instanceof UUID)
val = val.toString();
nValueLen = (2 * ((String) val).length());
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java
index 2a0c79d97..95d2f6191 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java
@@ -1674,8 +1674,12 @@ public final void setObject(int n, Object obj, int jdbcType) throws SQLServerExc
checkClosed();
if (microsoft.sql.Types.STRUCTURED == jdbcType) {
tvpName = getTVPNameFromObject(n, obj);
- }
- setObject(setterGetParam(n), obj, JavaType.of(obj), JDBCType.of(jdbcType), null, null, false, n, tvpName);
+ }
+ if (microsoft.sql.Types.JSON == jdbcType) {
+ setObjectNoType(n, obj, false);
+ } else {
+ setObject(setterGetParam(n), obj, JavaType.of(obj), JDBCType.of(jdbcType), null, null, false, n, tvpName);
+ }
loggerExternal.exiting(getClassNameLogging(), "setObject");
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
index 942bfdc26..bf6c946eb 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
@@ -311,6 +311,7 @@ protected Object[][] getContents() {
{"R_StreamingDataTypeAE", "Data of length greater than {0} is not supported in encrypted {1} column."},
{"R_AE_NotSupportedByServer", "SQL Server in use does not support column encryption."},
{"R_InvalidAEVersionNumber", "Received invalid version number \"{0}\" for Always Encrypted."}, // From server
+ {"R_InvalidJSONVersionNumber", "Received invalid version number \"{0}\" for JSON."},
{"R_NullEncryptedColumnEncryptionKey", "Internal error. Encrypted column encryption key cannot be null."},
{"R_EmptyEncryptedColumnEncryptionKey", "Internal error. Empty encrypted column encryption key specified."},
{"R_InvalidMasterKeyDetails", "Invalid master key details specified."},
@@ -477,6 +478,7 @@ protected Object[][] getContents() {
{"R_InvalidDataClsVersionNumber", "Invalid version number {0} for Data Classification."}, // From Server
{"R_unknownUTF8SupportValue", "Unknown value for UTF8 support."},
{"R_unknownAzureSQLDNSCachingValue", "Unknown value for Azure SQL DNS Caching."},
+ {"R_unknownJSONSupportValue", "Unknown value for JSON support."},
{"R_illegalWKT", "Illegal Well-Known text. Please make sure Well-Known text is valid."},
{"R_illegalTypeForGeometry", "{0} is not supported for Geometry."},
{"R_illegalWKTposition", "Illegal character in Well-Known text at position {0}."},
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSetMetaData.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSetMetaData.java
index 8095f71e2..43ffd66c0 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSetMetaData.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSetMetaData.java
@@ -288,6 +288,7 @@ public boolean isSearchable(int column) throws SQLServerException {
case NTEXT:
case UDT:
case XML:
+ case JSON:
return false;
default:
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java
index 9ba2e83ce..7eca85253 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java
@@ -294,6 +294,8 @@ final class SendByRPCOp extends DTVExecuteOp {
void execute(DTV dtv, String strValue) throws SQLServerException {
if (dtv.getJdbcType() == JDBCType.GUID) {
tdsWriter.writeRPCUUID(name, UUID.fromString(strValue), isOutParam);
+ } else if (dtv.getJdbcType() == JDBCType.JSON) {
+ tdsWriter.writeRPCJson(name, strValue, isOutParam);
} else {
tdsWriter.writeRPCStringUnicode(name, strValue, isOutParam, collation);
}
@@ -1459,6 +1461,7 @@ final void executeOp(DTVExecuteOp op) throws SQLServerException {
case NVARCHAR:
case LONGNVARCHAR:
case NCLOB:
+ case JSON:
if (null != cryptoMeta)
op.execute(this, (byte[]) null);
else
@@ -2989,6 +2992,25 @@ public void apply(TypeInfo typeInfo, TDSReader tdsReader) throws SQLServerExcept
typeInfo.maxLength = tdsReader.readInt();
typeInfo.ssType = SSType.SQL_VARIANT;
}
+ }),
+
+ JSON(TDSType.JSON, new Strategy() {
+ /**
+ * Sets the fields of typeInfo to the correct values
+ *
+ * @param typeInfo
+ * the TypeInfo whos values are being corrected
+ * @param tdsReader
+ * the TDSReader used to set the fields of typeInfo to the correct values
+ * @throws SQLServerException
+ * when an error occurs
+ */
+ public void apply(TypeInfo typeInfo, TDSReader tdsReader) throws SQLServerException {
+ typeInfo.ssLenType = SSLenType.PARTLENTYPE;
+ typeInfo.ssType = SSType.JSON;
+ typeInfo.displaySize = typeInfo.precision = Integer.MAX_VALUE;
+ typeInfo.charset = Encoding.UTF8.charset();
+ }
});
private final TDSType tdsType;
@@ -3737,6 +3759,7 @@ Object getValue(DTV dtv, JDBCType jdbcType, int scale, InputStreamGetterArgs str
case VARBINARYMAX:
case VARCHARMAX:
case NVARCHARMAX:
+ case JSON:
case UDT: {
convertedValue = DDC.convertStreamToObject(
PLPInputStream.makeStream(tdsReader, streamGetterArgs, this), typeInfo, jdbcType,
diff --git a/src/main/java/microsoft/sql/Types.java b/src/main/java/microsoft/sql/Types.java
index ec326fe3c..3e952faa4 100644
--- a/src/main/java/microsoft/sql/Types.java
+++ b/src/main/java/microsoft/sql/Types.java
@@ -74,4 +74,10 @@ private Types() {
* Microsoft SQL type GEOGRAPHY.
*/
public static final int GEOGRAPHY = -158;
+
+ /**
+ * The constant in the Java programming language, sometimes referred to as a type code, that identifies the
+ * Microsoft SQL type JSON.
+ */
+ public static final int JSON = -159;
}
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyCSVTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyCSVTest.java
index 057c5068c..a41db2555 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyCSVTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyCSVTest.java
@@ -64,6 +64,7 @@
public class BulkCopyCSVTest extends AbstractTest {
static String inputFile = "BulkCopyCSVTestInput.csv";
+ static String jsonInputFile = "BulkCopyCSVTestInputWithJson.csv";
static String inputFileNoColumnName = "BulkCopyCSVTestInputNoColumnName.csv";
static String inputFileDelimiterEscape = "BulkCopyCSVTestInputDelimiterEscape.csv";
static String inputFileDelimiterEscapeNoNewLineAtEnd = "BulkCopyCSVTestInputDelimiterEscapeNoNewLineAtEnd.csv";
@@ -446,6 +447,57 @@ public void testCSV2400() {
}
}
+ @Test
+ @DisplayName("Test Bulk Copy with JSON Data")
+ @Tag(Constants.JSONTest)
+ public void testBulkCopyWithJson() throws Exception {
+ String tableName = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("BulkJsonTest"));
+ String fileName = filePath + jsonInputFile;
+
+ // Expected values as read from the CSV file
+ String[][] expectedValues = new String[][]{
+ {"0", "testing", "{\"age\":25,\"address\":{\"pincode\":123456,\"state\":\"NY\"}}"},
+ {"1","test }","{\"age\":25,\"city\":\"Los Angeles\"}"},
+ {"0","test {0}","{\"age\":40,\"city\":\"Chicago\"}"}
+ };
+
+ try (Connection con = getConnection(); Statement stmt = con.createStatement();
+ SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(con);
+ SQLServerBulkCSVFileRecord fileRecord = new SQLServerBulkCSVFileRecord(fileName, encoding, ",", false)) {
+ bulkCopy.setDestinationTableName(tableName);
+
+ // Define column metadata
+ fileRecord.addColumnMetadata(1, null, java.sql.Types.BIT, 0, 0);
+ fileRecord.addColumnMetadata(2, null, java.sql.Types.NCHAR, 10, 0);
+ fileRecord.addColumnMetadata(3, null, microsoft.sql.Types.JSON, 0, 0); // JSON column
+
+ // Create table
+ stmt.executeUpdate("CREATE TABLE " + tableName + " ("
+ + "c1 BIT, c2 nchar(50), c3 JSON)");
+
+ // Perform bulk copy
+ fileRecord.setEscapeColumnDelimitersCSV(true);
+ bulkCopy.writeToServer(fileRecord);
+
+ // Verify the data
+ int i = 0;
+ try (ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName);
+ BufferedReader br = new BufferedReader(new FileReader(fileName))) {
+
+ while (rs.next()) {
+ for (int j = 1; j <= 3; j++) {
+ String actual = rs.getString(j);
+ String expected = expectedValues[i][j - 1];
+ assertEquals(expected.trim(), actual.trim(), "Mismatch in column " + j);
+ }
+ i++;
+ }
+ } finally {
+ TestUtils.dropTableIfExists(tableName, stmt);
+ }
+ }
+ }
+
/**
* drop source table after testing bulk copy
*
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyISQLServerBulkRecordTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyISQLServerBulkRecordTest.java
index 56ee5c1dd..837ecbdaf 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyISQLServerBulkRecordTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyISQLServerBulkRecordTest.java
@@ -4,6 +4,7 @@
*/
package com.microsoft.sqlserver.jdbc.bulkCopy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -43,7 +44,6 @@
import com.microsoft.sqlserver.testframework.DBTable;
import com.microsoft.sqlserver.testframework.sqlType.SqlType;
-
/**
* Test bulk copy decimal scale and precision
*/
@@ -119,26 +119,370 @@ public void testBulkCopyDateTimePrecision() throws SQLException {
bulkCopy.writeToServer(new BulkRecordDT(data8));
String select = "SELECT * FROM " + dstTable + " order by Dataid";
- ResultSet rs = dstStmt.executeQuery(select);
-
- assertTrue(rs.next());
- assertTrue(data.equals(rs.getObject(2, LocalDateTime.class)));
- assertTrue(rs.next());
- assertTrue(data1.equals(rs.getObject(2, LocalDateTime.class)));
- assertTrue(rs.next());
- assertTrue(data2.equals(rs.getObject(2, LocalDateTime.class)));
- assertTrue(rs.next());
- assertTrue(data3.equals(rs.getObject(2, LocalDateTime.class)));
- assertTrue(rs.next());
- assertTrue(data4.equals(rs.getObject(2, LocalDateTime.class)));
- assertTrue(rs.next());
- assertTrue(data5.equals(rs.getObject(2, LocalDateTime.class)));
- assertTrue(rs.next());
- assertTrue(data6.equals(rs.getObject(2, LocalDateTime.class)));
- assertTrue(rs.next());
- assertTrue(data7.equals(rs.getObject(2, LocalDateTime.class)));
- assertTrue(rs.next());
- assertTrue(data8.equals(rs.getObject(2, LocalDateTime.class)));
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertTrue(data.equals(rs.getObject(2, LocalDateTime.class)));
+ assertTrue(rs.next());
+ assertTrue(data1.equals(rs.getObject(2, LocalDateTime.class)));
+ assertTrue(rs.next());
+ assertTrue(data2.equals(rs.getObject(2, LocalDateTime.class)));
+ assertTrue(rs.next());
+ assertTrue(data3.equals(rs.getObject(2, LocalDateTime.class)));
+ assertTrue(rs.next());
+ assertTrue(data4.equals(rs.getObject(2, LocalDateTime.class)));
+ assertTrue(rs.next());
+ assertTrue(data5.equals(rs.getObject(2, LocalDateTime.class)));
+ assertTrue(rs.next());
+ assertTrue(data6.equals(rs.getObject(2, LocalDateTime.class)));
+ assertTrue(rs.next());
+ assertTrue(data7.equals(rs.getObject(2, LocalDateTime.class)));
+ assertTrue(rs.next());
+ assertTrue(data8.equals(rs.getObject(2, LocalDateTime.class)));
+ }
+
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test bulk copy with a single JSON row.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testBulkCopyJSON() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement();
+ SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(conn)) {
+
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (testCol JSON);");
+
+ bulkCopy.setDestinationTableName(dstTable);
+ String data = "{\"key\":\"value\"}";
+ bulkCopy.writeToServer(new BulkRecordJSON(data));
+
+ String select = "SELECT * FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertTrue(data.equals(rs.getObject(1)));
+ }
+
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test bulk copy with empty JSON document
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testBulkCopyWithEmptyJsonDocument() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement();
+ SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(conn)) {
+
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (testCol JSON);");
+
+ bulkCopy.setDestinationTableName(dstTable);
+ String data1 = "{}";
+ String data2 = "{\"key2\":\"value2\",\"key3\":123}";
+ bulkCopy.writeToServer(new BulkRecordJSON(data1));
+ bulkCopy.writeToServer(new BulkRecordJSON(data2));
+
+ String select = "SELECT * FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals(data1, rs.getString(1));
+ assertTrue(rs.next());
+ assertEquals(data2, rs.getString(1));
+ }
+
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Test bulk copy with multiple JSON rows containing different structures
+ * and compared using getString(columnIndex)
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testBulkCopyMultipleJsonRowsWithDifferentStructures() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement();
+ SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(conn)) {
+
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (testCol JSON);");
+
+ bulkCopy.setDestinationTableName(dstTable);
+ String data1 = "{\"key1\":\"value1\"}";
+ String data2 = "{\"key2\":\"value2\",\"key3\":123}";
+ String data3 = "{\"key3\":123,\"key4\":\"value4\",\"key5\":\"value5\"}";
+ bulkCopy.writeToServer(new BulkRecordJSON(data1));
+ bulkCopy.writeToServer(new BulkRecordJSON(data2));
+ bulkCopy.writeToServer(new BulkRecordJSON(data3));
+
+ String select = "SELECT * FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals(data1, rs.getString(1));
+ assertTrue(rs.next());
+ assertEquals(data2, rs.getString(1));
+ assertTrue(rs.next());
+ assertEquals(data3, rs.getString(1));
+ }
+
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test bulk copy with multiple JSON rows.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testBulkCopyMultipleJsonRows() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement();
+ SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(conn)) {
+
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (testCol JSON);");
+
+ bulkCopy.setDestinationTableName(dstTable);
+ String data1 = "{\"key1\":\"value1\"}";
+ String data2 = "{\"key2\":\"value2\"}";
+ String data3 = "{\"key3\":\"value3\"}";
+ bulkCopy.writeToServer(new BulkRecordJSON(data1));
+ bulkCopy.writeToServer(new BulkRecordJSON(data2));
+ bulkCopy.writeToServer(new BulkRecordJSON(data3));
+
+ String select = "SELECT * FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertTrue(data1.equals(rs.getObject(1)));
+ assertTrue(rs.next());
+ assertTrue(data2.equals(rs.getObject(1)));
+ assertTrue(rs.next());
+ assertTrue(data3.equals(rs.getObject(1)));
+ }
+
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test bulk copy with multiple JSON rows and columns.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testBulkCopyMultipleJsonRowsAndColumns() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement();
+ SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(conn)) {
+
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (testCol1 JSON, testCol2 JSON);");
+
+ bulkCopy.setDestinationTableName(dstTable);
+ String data1Col1 = "{\"key1\":\"value1\"}";
+ String data1Col2 = "{\"key2\":\"value2\"}";
+ String data2Col1 = "{\"key3\":\"value3\"}";
+ String data2Col2 = "{\"key4\":\"value4\"}";
+ bulkCopy.writeToServer(new BulkRecordJSONMultipleColumns(data1Col1, data1Col2));
+ bulkCopy.writeToServer(new BulkRecordJSONMultipleColumns(data2Col1, data2Col2));
+
+ String select = "SELECT * FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertTrue(data1Col1.equals(rs.getObject(1)));
+ assertTrue(data1Col2.equals(rs.getObject(2)));
+ assertTrue(rs.next());
+ assertTrue(data2Col1.equals(rs.getObject(1)));
+ assertTrue(data2Col2.equals(rs.getObject(2)));
+ }
+
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test bulk copy with nested JSON documents.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testBulkCopyNestedJsonRows() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement();
+ SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(conn)) {
+
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (testCol JSON);");
+
+ bulkCopy.setDestinationTableName(dstTable);
+ String data1 = "{\"key1\":{\"nestedKey1\":\"nestedValue1\"}}";
+ String data2 = "{\"key2\":{\"nestedKey2\":{\"nestedKey3\":\"nestedValue3\"}}}";
+ String data3 = "{\"key3\":{\"nestedKey4\":{\"nestedKey5\":{\"nestedKey6\":\"nestedValue6\"}}}}";
+ bulkCopy.writeToServer(new BulkRecordJSON(data1));
+ bulkCopy.writeToServer(new BulkRecordJSON(data2));
+ bulkCopy.writeToServer(new BulkRecordJSON(data3));
+
+ String select = "SELECT * FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertTrue(data1.equals(rs.getObject(1)));
+ assertTrue(rs.next());
+ assertTrue(data2.equals(rs.getObject(1)));
+ assertTrue(rs.next());
+ assertEquals(data3, rs.getString(1));
+ }
+
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test bulk copy with various data types in JSON.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testBulkCopyWithVariousDataTypes() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement();
+ SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(conn)) {
+
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (testCol JSON)");
+
+ bulkCopy.setDestinationTableName(dstTable);
+
+ // JSON data to be inserted
+ String data = "{\"bitCol\":true,\"tinyIntCol\":2,\"smallIntCol\":-32768,\"intCol\":0,\"bigIntCol\":0,\"floatCol\":-1700.0000000000,\"realCol\":-3400.0000000000,\"decimalCol\":22.335600,\"numericCol\":22.3356,\"moneyCol\":-922337203685477.5808,\"smallMoneyCol\":-214748.3648,\"charCol\":\"a5()b\",\"nCharCol\":\"?????\",\"varcharCol\":\"test to test csv files\",\"nVarcharCol\":\"???\",\"binaryCol\":\"6163686974\",\"varBinaryCol\":\"6163686974\",\"dateCol\":\"1922-11-02\",\"datetimeCol\":\"2004-05-23 14:25:10.487\",\"datetime2Col\":\"2007-05-02 19:58:47.1234567\",\"datetimeOffsetCol\":\"2025-12-10 12:32:10.1234567+01:00\"}";
+
+ bulkCopy.writeToServer(new BulkRecordJSON(data));
+
+ String select = "SELECT * FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ String jsonData = rs.getString(1);
+ assertEquals(data, jsonData);
+ }
+
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test bulk copy with count verification.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testBulkCopyWithCountVerification() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement();
+ SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(conn)) {
+
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (testCol JSON);");
+
+ bulkCopy.setDestinationTableName(dstTable);
+ String data1 = "{\"key1\":\"value1\"}";
+ String data2 = "{\"key2\":\"value2\"}";
+ bulkCopy.writeToServer(new BulkRecordJSON(data1));
+ bulkCopy.writeToServer(new BulkRecordJSON(data2));
+
+ String selectCount = "SELECT COUNT(*) FROM " + dstTable;
+ int count1 = 0;
+ try (ResultSet rs = dstStmt.executeQuery(selectCount)) {
+ if (rs.next()) {
+ count1 = rs.getInt(1);
+ }
+ }
+
+ String select = "SELECT * FROM " + dstTable;
+ int count2 = 0;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ while (rs.next()) {
+ count2++;
+ }
+ }
+
+ assertEquals(count1, count2);
} catch (Exception e) {
fail(e.getMessage());
@@ -322,4 +666,110 @@ public boolean next() {
return true;
}
}
+
+ private static class BulkRecordJSON implements ISQLServerBulkData {
+ boolean anyMoreData = true;
+ Object[] data;
+
+ BulkRecordJSON(Object data) {
+ this.data = new Object[1];
+ this.data[0] = data;
+ }
+
+ @Override
+ public Set getColumnOrdinals() {
+ Set ords = new HashSet<>();
+ ords.add(1);
+ return ords;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ return "testCol";
+ }
+
+ @Override
+ public int getColumnType(int column) {
+ return microsoft.sql.Types.JSON;
+ }
+
+ @Override
+ public int getPrecision(int column) {
+ return 0;
+ }
+
+ @Override
+ public int getScale(int column) {
+ return 0;
+ }
+
+ @Override
+ public Object[] getRowData() {
+ return data;
+ }
+
+ @Override
+ public boolean next() {
+ if (!anyMoreData)
+ return false;
+ anyMoreData = false;
+ return true;
+ }
+ }
+
+ private static class BulkRecordJSONMultipleColumns implements ISQLServerBulkData {
+ boolean anyMoreData = true;
+ Object[] data;
+
+ BulkRecordJSONMultipleColumns(Object data1, Object data2) {
+ this.data = new Object[2];
+ this.data[0] = data1;
+ this.data[1] = data2;
+ }
+
+ @Override
+ public Set getColumnOrdinals() {
+ Set ords = new HashSet<>();
+ ords.add(1);
+ ords.add(2);
+ return ords;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ if (column == 1) {
+ return "testCol1";
+ } else {
+ return "testCol2";
+ }
+ }
+
+ @Override
+ public int getColumnType(int column) {
+ return microsoft.sql.Types.JSON;
+ }
+
+ @Override
+ public int getPrecision(int column) {
+ return 0;
+ }
+
+ @Override
+ public int getScale(int column) {
+ return 0;
+ }
+
+ @Override
+ public Object[] getRowData() {
+ return data;
+ }
+
+ @Override
+ public boolean next() {
+ if (!anyMoreData)
+ return false;
+ anyMoreData = false;
+ return true;
+ }
+ }
}
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/SqlTypeMapping.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/SqlTypeMapping.java
index 820ce02d5..342bc0436 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/SqlTypeMapping.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/SqlTypeMapping.java
@@ -15,6 +15,7 @@
import com.microsoft.sqlserver.testframework.sqlType.SqlDecimal;
import com.microsoft.sqlserver.testframework.sqlType.SqlFloat;
import com.microsoft.sqlserver.testframework.sqlType.SqlInt;
+import com.microsoft.sqlserver.testframework.sqlType.SqlJson;
import com.microsoft.sqlserver.testframework.sqlType.SqlMoney;
import com.microsoft.sqlserver.testframework.sqlType.SqlNChar;
import com.microsoft.sqlserver.testframework.sqlType.SqlNVarChar;
@@ -62,7 +63,8 @@ public enum SqlTypeMapping {
DATETIMEOFFSET(new SqlDateTimeOffset()),
// Binary
BINARY(new SqlBinary()),
- VARBINARY(new SqlVarBinary()),;
+ VARBINARY(new SqlVarBinary()),
+ JSON(new SqlJson()),;
public SqlType sqlType;
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java
index f2d102d92..6d86180d3 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java
@@ -75,6 +75,9 @@ public class CallableStatementTest extends AbstractTest {
.escapeIdentifier(RandomUtil.getIdentifier("manyParam_definedType"));
private static String zeroParamSproc = AbstractSQLGenerator
.escapeIdentifier(RandomUtil.getIdentifier("zeroParamSproc"));
+ private static String tableNameJSON = "TestJSONTable";
+ private static String procedureNameJSON = AbstractSQLGenerator
+ .escapeIdentifier(RandomUtil.getIdentifier("TestJSONProcedure"));
/**
* Setup before test
@@ -98,6 +101,8 @@ public static void setupTest() throws Exception {
TestUtils.dropUserDefinedTypeIfExists(manyParamUserDefinedType, stmt);
TestUtils.dropProcedureIfExists(manyParamProc, stmt);
TestUtils.dropTableIfExists(manyParamsTable, stmt);
+ TestUtils.dropTableIfExists(tableNameJSON, stmt);
+ TestUtils.dropProcedureIfExists(procedureNameJSON, stmt);
createGUIDTable(stmt);
createGUIDStoredProcedure(stmt);
@@ -112,6 +117,8 @@ public static void setupTest() throws Exception {
createGetObjectOffsetDateTimeProcedure(stmt);
createConditionalProcedure();
createSimpleRetValSproc();
+ createJSONTestTable(stmt);
+ createJSONStoredProcedure(stmt);
}
}
@@ -597,6 +604,44 @@ public void testTimestampStringConversion() throws SQLException {
stmt.getObject("currentTimeStamp");
}
}
+
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONColumnInTableWithSetObject() throws SQLException {
+
+ try (Connection con = DriverManager.getConnection(connectionString); Statement stmt = con.createStatement()) {
+ String jsonString = "{\"key\":\"value\"}";
+ try (CallableStatement callableStatement = con
+ .prepareCall("INSERT INTO " + tableNameJSON + " (col1) VALUES (?)")) {
+ callableStatement.setObject(1, jsonString);
+ callableStatement.execute();
+ }
+
+ try (Statement queryStmt = con.createStatement();
+ ResultSet rs = queryStmt.executeQuery("SELECT col1 FROM " + tableNameJSON)) {
+ assertTrue(rs.next());
+ assertEquals(jsonString, rs.getObject(1));
+ }
+ }
+ }
+
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONProcedureWithSetObject() throws SQLException {
+
+ try (Connection con = DriverManager.getConnection(connectionString); Statement stmt = con.createStatement()) {
+ String jsonString = "{\"key\":\"value\"}";
+ try (CallableStatement callableStatement = con.prepareCall("{call " + procedureNameJSON + " (?)}")) {
+ callableStatement.setObject(1, jsonString);
+ callableStatement.execute();
+
+ try (ResultSet rs = callableStatement.getResultSet()) {
+ assertTrue(rs.next());
+ assertEquals(jsonString, rs.getObject("col1"));
+ }
+ }
+ }
+ }
/**
* Cleanup after test
@@ -617,6 +662,8 @@ public static void cleanup() throws SQLException {
TestUtils.dropProcedureIfExists(conditionalSproc, stmt);
TestUtils.dropProcedureIfExists(simpleRetValSproc, stmt);
TestUtils.dropProcedureIfExists(zeroParamSproc, stmt);
+ TestUtils.dropTableIfExists(tableNameJSON, stmt);
+ TestUtils.dropProcedureIfExists(procedureNameJSON, stmt);
}
}
@@ -715,4 +762,15 @@ private static void createUserDefinedType() throws SQLException {
stmt.executeUpdate(TVPCreateCmd);
}
}
+
+ private static void createJSONTestTable(Statement stmt) throws SQLException {
+ String sql = "CREATE TABLE " + tableNameJSON + " (" + "id INT PRIMARY KEY IDENTITY(1,1), " + "col1 JSON)";
+ stmt.execute(sql);
+ }
+
+ private static void createJSONStoredProcedure(Statement stmt) throws SQLException {
+ String sql = "CREATE PROCEDURE " + procedureNameJSON + " (@jsonInput JSON) " + "AS " + "BEGIN "
+ + " SELECT @jsonInput AS col1; " + "END";
+ stmt.execute(sql);
+ }
}
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java
index 65b76ae12..65bfd63ab 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java
@@ -1034,6 +1034,46 @@ public void shouldEscapeSchemaName() throws SQLException {
}
}
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONMetaData() throws SQLException {
+ String jsonTableName = RandomUtil.getIdentifier("try_SQLJSON_Table");
+
+ try (Statement stmt = connection.createStatement()) {
+ String sql = "create table " + AbstractSQLGenerator.escapeIdentifier(jsonTableName)
+ + " (c1 JSON null);";
+ stmt.execute(sql);
+
+ String query = "SELECT * FROM " + AbstractSQLGenerator.escapeIdentifier(jsonTableName);
+ try (Statement statement = connection.createStatement();
+ ResultSet resultSet = statement.executeQuery(query)) {
+
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ int columnCount = metaData.getColumnCount();
+ assertEquals(1, columnCount, "Column count should be 1");
+
+ String columnName = metaData.getColumnName(1);
+ assertEquals("c1", columnName, "Column name should be 'c1'");
+
+ String columnType = metaData.getColumnTypeName(1);
+ assertTrue("JSON".equalsIgnoreCase(columnType), "Column type should be 'JSON'");
+
+ int columnTypeInt = metaData.getColumnType(1);
+ assertEquals(microsoft.sql.Types.JSON, columnTypeInt, "Column type should be microsoft.sql.Types.JSON");
+
+ int columnDisplaySize = metaData.getColumnDisplaySize(1);
+ assertTrue(columnDisplaySize > 0, "Column display size should be greater than 0");
+
+ String columnClassName = metaData.getColumnClassName(1);
+ assertEquals(Object.class.getName(), columnClassName, "Column class name should be 'java.lang.Object'");
+ }
+ } finally {
+ try (Statement stmt = connection.createStatement()) {
+ stmt.execute("DROP TABLE IF EXISTS " + AbstractSQLGenerator.escapeIdentifier(jsonTableName));
+ }
+ }
+ }
+
@BeforeAll
public static void setupTable() throws Exception {
setConnection();
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/JSONFunctionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/JSONFunctionTest.java
new file mode 100644
index 000000000..d51cb35b8
--- /dev/null
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/JSONFunctionTest.java
@@ -0,0 +1,1788 @@
+/*
+ * Microsoft JDBC Driver for SQL Server Copyright(c) Microsoft Corporation All rights reserved. This program is made
+ * available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
+package com.microsoft.sqlserver.jdbc.datatypes;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.platform.runner.JUnitPlatform;
+import org.junit.runner.RunWith;
+import org.junit.jupiter.api.Tag;
+
+import com.microsoft.sqlserver.jdbc.RandomUtil;
+import com.microsoft.sqlserver.jdbc.TestUtils;
+import com.microsoft.sqlserver.testframework.AbstractSQLGenerator;
+import com.microsoft.sqlserver.testframework.AbstractTest;
+import com.microsoft.sqlserver.testframework.Constants;
+
+@RunWith(JUnitPlatform.class)
+@DisplayName("Test Json Functions")
+public class JSONFunctionTest extends AbstractTest {
+
+ @BeforeAll
+ public static void setupTests() throws Exception {
+ setConnection();
+ }
+
+ private static final String JSON_FILE_PATH = "large_json.json";
+
+ /**
+ * Test ISJSON function with JSON.
+ * ISJSON -> Tests whether a string contains valid JSON.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testISJSON() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement()) {
+
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (testCol JSON);");
+
+ String validJson = "{\"key1\":\"value1\"}";
+ dstStmt.executeUpdate(
+ "INSERT INTO " + dstTable + " (testCol) VALUES ('" + validJson + "')");
+
+ String select = "SELECT testCol, " +
+ "ISJSON(testCol) AS isJsonValid " +
+ "FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals(validJson, rs.getString("testCol"));
+ assertEquals(1, rs.getInt("isJsonValid"));
+ }
+
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test ISJSON function with valid and invalid JSON value.
+ * ISJSON -> Tests whether a string contains valid JSON.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testISJSONWithVariousInputs() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier("dstTable"));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement stmt = conn.createStatement()) {
+
+ stmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (testCol NVARCHAR(MAX));");
+
+ String validJson = "{\"key1\":\"value1\"}";
+ String invalidJson = "Not a JSON string";
+ String jsonScalar = "123";
+
+ stmt.executeUpdate("INSERT INTO " + dstTable + " (testCol) VALUES ('" + validJson + "')");
+ stmt.executeUpdate("INSERT INTO " + dstTable + " (testCol) VALUES ('" + invalidJson + "')");
+ stmt.executeUpdate("INSERT INTO " + dstTable + " (testCol) VALUES ('" + jsonScalar + "')");
+
+ String select = "SELECT testCol, " +
+ "ISJSON(testCol) AS isJsonValid " +
+ "FROM " + dstTable;
+ try (ResultSet rs = stmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals(validJson, rs.getString("testCol"));
+ assertEquals(1, rs.getInt("isJsonValid"));
+
+ assertTrue(rs.next());
+ assertEquals(invalidJson, rs.getString("testCol"));
+ assertEquals(0, rs.getInt("isJsonValid"));
+
+ assertTrue(rs.next());
+ assertEquals(jsonScalar, rs.getString("testCol"));
+ assertEquals(0, rs.getInt("isJsonValid"));
+ }
+
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test JSON_ARRAY function without NULL values.
+ * JSON_ARRAY -> Constructs JSON array text from zero or more expressions.
+ * input: JSON_ARRAY('value1', 123, NULL) ->
+ * output: ["value1",123]
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONArrayWithoutNulls() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement()) {
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (testCol JSON);");
+
+ String data1 = "SELECT JSON_ARRAY('value1', 123, NULL) AS jsonArray";
+ dstStmt.executeUpdate("INSERT INTO " + dstTable + " (testCol) " + data1);
+
+ String select = "SELECT testCol FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("[\"value1\",123]", rs.getString("testCol"));
+ }
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test JSON_ARRAY function with NULL values included.
+ * JSON_ARRAY -> Constructs JSON array text from zero or more expressions.
+ * input: JSON_ARRAY('value1', 123, NULL, 'value2' NULL ON NULL) ->
+ * output: ["value1",123,null,"value2"]
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONArrayWithNulls() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement()) {
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (testCol JSON);");
+
+ String data2 = "SELECT JSON_ARRAY('value1', 123, NULL, 'value2' NULL ON NULL) AS jsonArray";
+ dstStmt.executeUpdate("INSERT INTO " + dstTable + " (testCol) " + data2);
+
+ String select = "SELECT testCol FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("[\"value1\",123,null,\"value2\"]", rs.getString("testCol"));
+ }
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test JSON_ARRAY with string, JSON object, and JSON array.
+ * JSON_ARRAY -> Constructs JSON array text from zero or more expressions.
+ * input: JSON_ARRAY('a', JSON_OBJECT('name':'value', 'type':1), JSON_ARRAY(1,
+ * null, 2 NULL ON NULL)) ->
+ * output: ["a",{"name":"value","type":1},[1,null,2]]
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONArrayWithMixedElements() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement()) {
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (testCol JSON);");
+
+ String data = "SELECT JSON_ARRAY('a', JSON_OBJECT('name':'value', 'type':1), JSON_ARRAY(1, null, 2 NULL ON NULL)) AS jsonArray";
+ dstStmt.executeUpdate("INSERT INTO " + dstTable + " (testCol) " + data);
+
+ String select = "SELECT testCol FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("[\"a\",{\"name\":\"value\",\"type\":1},[1,null,2]]", rs.getString("testCol"));
+ }
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test JSON_ARRAY with variables and SQL expressions.
+ * JSON_ARRAY -> Constructs JSON array text from zero or more expressions.
+ * input: JSON_ARRAY(1, @id_value, (SELECT @@SPID)) ->
+ * output: [1,"",""]
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONArrayWithVariablesAndExpressions() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement()) {
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (testCol NVARCHAR(MAX));");
+
+ String data = "DECLARE @id_value nvarchar(64) = NEWID(); " +
+ "INSERT INTO " + dstTable + " (testCol) " +
+ "SELECT JSON_ARRAY(1, @id_value, (SELECT @@SPID)) AS jsonArray";
+
+ dstStmt.execute(data);
+
+ String select = "SELECT testCol FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ String jsonArray = rs.getString("testCol");
+ assertTrue(jsonArray.startsWith("[1,\""));
+ assertTrue(jsonArray.contains("\","));
+ assertTrue(jsonArray.endsWith("]"));
+ }
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test JSON_ARRAY per row in the query.
+ * JSON_ARRAY -> Constructs JSON array text from zero or more expressions.
+ * input: JSON_ARRAY(s.host_name, s.program_name, s.client_interface_name) ->
+ * output: ["","",""]
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONArrayPerRow() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement()) {
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (session_id INT, info JSON);");
+
+ String data = "SELECT s.session_id, JSON_ARRAY(s.host_name, s.program_name, s.client_interface_name) AS info "
+ +
+ "FROM sys.dm_exec_sessions AS s " +
+ "WHERE s.is_user_process = 1";
+ dstStmt.executeUpdate("INSERT INTO " + dstTable + " (session_id, info) " + data);
+
+ String select = "SELECT session_id, info FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ while (rs.next()) {
+ int sessionId = rs.getInt("session_id");
+ String info = rs.getString("info");
+ assertTrue(info.startsWith("[\""));
+ assertTrue(info.endsWith("\"]"));
+ }
+ }
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test JSON_ARRAYAGG function.
+ * JSON_ARRAYAGG -> Constructs a JSON array from an aggregation of SQL data or
+ * columns.
+ * input: JSON_ARRAYAGG(testCol) ->
+ * output: ["",""]
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONArrayAgg() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement()) {
+
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (testCol JSON);");
+
+ dstStmt.executeUpdate("INSERT INTO " + dstTable + " VALUES ('{\"key\":\"value1\"}');");
+ dstStmt.executeUpdate("INSERT INTO " + dstTable + " VALUES ('{\"key\":\"value2\"}');");
+
+ String select = "SELECT JSON_ARRAYAGG(testCol) AS jsonArrayAgg FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("[{\"key\":\"value1\"},{\"key\":\"value2\"}]", rs.getString("jsonArrayAgg"));
+ }
+
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test JSON_ARRAYAGG with three elements from a result set.
+ * JSON_ARRAYAGG -> Constructs a JSON array from an aggregation of SQL data or
+ * columns.
+ * input: JSON_ARRAYAGG(c1) ->
+ * output: ["c","b","a"]
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONArrayAggWithThreeElements() throws SQLException {
+ String select = "SELECT JSON_ARRAYAGG(c1) AS jsonArrayAgg FROM (VALUES ('c'), ('b'), ('a')) AS t(c1)";
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("[\"c\",\"b\",\"a\"]", rs.getString("jsonArrayAgg"));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Test JSON_ARRAYAGG with three elements ordered by the value of the column.
+ * JSON_ARRAYAGG -> Constructs a JSON array from an aggregation of SQL data or
+ * columns.
+ * input: JSON_ARRAYAGG(c1 ORDER BY c1) ->
+ * output: ["a","b","c"]
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONArrayAggWithOrderedElements() throws SQLException {
+ String select = "SELECT JSON_ARRAYAGG(c1 ORDER BY c1) AS jsonArrayAgg FROM (VALUES ('c'), ('b'), ('a')) AS t(c1)";
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("[\"a\",\"b\",\"c\"]", rs.getString("jsonArrayAgg"));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Test JSON_ARRAYAGG with two columns.
+ * JSON_ARRAYAGG -> Constructs a JSON array from an aggregation of SQL data or
+ * columns.
+ * input: JSON_ARRAYAGG(c.name ORDER BY c.column_id) ->
+ * output: ["column1","column2"]
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONArrayAggWithTwoColumns() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement stmt = conn.createStatement()) {
+ // Create table and insert data
+ stmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (object_id INT, name NVARCHAR(50), column_id INT);");
+
+ stmt.executeUpdate(
+ "INSERT INTO " + dstTable + " (object_id, name, column_id) VALUES (1, 'column1', 1);");
+ stmt.executeUpdate(
+ "INSERT INTO " + dstTable + " (object_id, name, column_id) VALUES (1, 'column2', 2);");
+ stmt.executeUpdate(
+ "INSERT INTO " + dstTable + " (object_id, name, column_id) VALUES (2, 'column3', 1);");
+ stmt.executeUpdate(
+ "INSERT INTO " + dstTable + " (object_id, name, column_id) VALUES (2, 'column4', 2);");
+
+ String select = "SELECT object_id, JSON_ARRAYAGG(name ORDER BY column_id) AS column_list " +
+ "FROM " + dstTable + " " +
+ "GROUP BY object_id";
+ try (ResultSet rs = stmt.executeQuery(select)) {
+ while (rs.next()) {
+ int objectId = rs.getInt("object_id");
+ String columnList = rs.getString("column_list");
+ if (objectId == 1) {
+ assertEquals("[\"column1\",\"column2\"]", columnList);
+ } else if (objectId == 2) {
+ assertEquals("[\"column3\",\"column4\"]", columnList);
+ } else {
+ fail("Unexpected object_id: " + objectId);
+ }
+ }
+ }
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test JSON_MODIFY function with various operations.
+ * JSON_MODIFY -> Updates the value of a property in a JSON string and returns
+ * the updated JSON string.
+ * input: JSON_MODIFY(testCol, '$.key', 'value2') ->
+ * output: {"key":"value2"}
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONModify() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement()) {
+
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (testCol JSON);");
+
+ String data = "{\"key\":\"value1\"}";
+ dstStmt.executeUpdate(
+ "INSERT INTO " + dstTable + " (testCol) VALUES ('" + data + "')");
+
+ String update = "UPDATE " + dstTable + " SET testCol = JSON_MODIFY(testCol, '$.key', 'value2')";
+ dstStmt.executeUpdate(update);
+
+ String select = "SELECT testCol FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("{\"key\":\"value2\"}", rs.getString("testCol"));
+ }
+
+ String newRow = "{\"name\":\"John\",\"skills\":[\"C#\",\"SQL\"]}";
+ dstStmt.executeUpdate("INSERT INTO " + dstTable + " (testCol) VALUES ('" + newRow + "')");
+
+ select = "SELECT testCol FROM " + dstTable + " WHERE JSON_VALUE(testCol, '$.name') = 'John'";
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("{\"name\":\"John\",\"skills\":[\"C#\",\"SQL\"]}", rs.getString("testCol"));
+ }
+
+ String delete = "DELETE FROM " + dstTable + " WHERE JSON_VALUE(testCol, '$.key') = 'value2'";
+ dstStmt.executeUpdate(delete);
+
+ select = "SELECT testCol FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("{\"name\":\"John\",\"skills\":[\"C#\",\"SQL\"]}", rs.getString("testCol"));
+ }
+
+ String addKeyValue = "UPDATE " + dstTable + " SET testCol = JSON_MODIFY(testCol, '$.surname', 'Smith')";
+ dstStmt.executeUpdate(addKeyValue);
+
+ select = "SELECT testCol FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("{\"name\":\"John\",\"skills\":[\"C#\",\"SQL\"],\"surname\":\"Smith\"}",
+ rs.getString("testCol"));
+ }
+
+ String addSkill = "UPDATE " + dstTable
+ + " SET testCol = JSON_MODIFY(testCol, 'append $.skills', 'Azure')";
+ dstStmt.executeUpdate(addSkill);
+
+ select = "SELECT testCol FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("{\"name\":\"John\",\"skills\":[\"C#\",\"SQL\",\"Azure\"],\"surname\":\"Smith\"}",
+ rs.getString("testCol"));
+ }
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test JSON_MODIFY with multiple updates.
+ * JSON_MODIFY -> Updates the value of a property in a JSON string and returns
+ * the updated JSON string.
+ * input: JSON_MODIFY(JSON_MODIFY(JSON_MODIFY(@info, '$.name', 'Mike'),
+ * '$.surname', 'Smith'), 'append $.skills', 'Azure') ->
+ * output: {"name":"Mike","skills":["C#","SQL","Azure"],"surname":"Smith"}
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONModifyMultipleUpdates() throws SQLException {
+ String json = "{\"name\":\"John\",\"skills\":[\"C#\",\"SQL\"]}";
+ String expectedJson = "{\"name\":\"Mike\",\"skills\":[\"C#\",\"SQL\",\"Azure\"],\"surname\":\"Smith\"}";
+
+ String update = "DECLARE @info JSON = '" + json + "'; " +
+ "SET @info = JSON_MODIFY(JSON_MODIFY(JSON_MODIFY(@info, '$.name', 'Mike'), '$.surname', 'Smith'), 'append $.skills', 'Azure'); "
+ +
+ "SELECT @info AS info;";
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(update)) {
+ assertTrue(rs.next());
+ assertEquals(expectedJson, rs.getString("info"));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Test JSON_MODIFY to rename a key.
+ * JSON_MODIFY -> Updates the value of a property in a JSON string and returns
+ * the updated JSON string.
+ * input: JSON_MODIFY(JSON_MODIFY(@product, '$.Price', CAST(JSON_VALUE(@product,
+ * '$.price') AS NUMERIC(4, 2))), '$.price', NULL) ->
+ * output: {"Price":49.99}
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONModifyRenameKey() throws SQLException {
+ String json = "{\"price\":49.99}";
+ String expectedJson = "{\"Price\":49.99}";
+
+ String update = "DECLARE @product JSON = '" + json + "'; " +
+ "SET @product = JSON_MODIFY(JSON_MODIFY(@product, '$.Price', CAST(JSON_VALUE(@product, '$.price') AS NUMERIC(4, 2))), '$.price', NULL); "
+ +
+ "SELECT @product AS product;";
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(update)) {
+ assertTrue(rs.next());
+ assertEquals(expectedJson, rs.getString("product"));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Test JSON_MODIFY to increment a value.
+ * JSON_MODIFY -> Updates the value of a property in a JSON string and returns
+ * the updated JSON string.
+ * input: JSON_MODIFY(@stats, '$.click_count', CAST(JSON_VALUE(@stats,
+ * '$.click_count') AS INT) + 1) ->
+ * output: {"click_count":174}
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONModifyIncrementValue() throws SQLException {
+ String json = "{\"click_count\":173}";
+ String expectedJson = "{\"click_count\":174}";
+
+ String update = "DECLARE @stats JSON = '" + json + "'; " +
+ "SET @stats = JSON_MODIFY(@stats, '$.click_count', CAST(JSON_VALUE(@stats, '$.click_count') AS INT) + 1); "
+ +
+ "SELECT @stats AS stats;";
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(update)) {
+ assertTrue(rs.next());
+ assertEquals(expectedJson, rs.getString("stats"));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Test JSON_MODIFY to update a JSON column.
+ * JSON_MODIFY -> Updates the value of a property in a JSON string and returns
+ * the updated JSON string.
+ * input: JSON_MODIFY(jsonCol, '$.info.address.town', 'London') ->
+ * output: {"info":{"address":{"town":"London"}}}
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONModifyUpdateJsonColumn() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement()) {
+
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (EmployeeID INT, jsonCol JSON);");
+
+ String data = "{\"info\":{\"address\":{\"town\":\"OldTown\"}}}";
+ dstStmt.executeUpdate(
+ "INSERT INTO " + dstTable + " (EmployeeID, jsonCol) VALUES (17, '" + data + "')");
+
+ String update = "UPDATE " + dstTable
+ + " SET jsonCol = JSON_MODIFY(jsonCol, '$.info.address.town', 'London') WHERE EmployeeID = 17";
+ dstStmt.executeUpdate(update);
+
+ String select = "SELECT jsonCol FROM " + dstTable + " WHERE EmployeeID = 17";
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("{\"info\":{\"address\":{\"town\":\"London\"}}}", rs.getString("jsonCol"));
+ }
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test JSON_OBJECT function to return an empty JSON object.
+ * JSON_OBJECT() -> Constructs JSON object text from zero or more expressions.
+ * input: JSON_OBJECT() ->
+ * output: {}
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONObjectEmpty() throws SQLException {
+ String select = "SELECT JSON_OBJECT() AS jsonObject";
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("{}", rs.getString("jsonObject"));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Test JSON_OBJECT function to return a JSON object with one key since the
+ * value for one of the keys is NULL and the ABSENT ON NULL option is specified.
+ * JSON_OBJECT() -> Constructs JSON object text from zero or more expressions.
+ * input: JSON_OBJECT('name':'value', 'type':NULL ABSENT ON NULL) ->
+ * output: {"name":"value"}
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONObjectWithMultipleKeys() throws SQLException {
+ String select = "SELECT JSON_OBJECT('name':'value', 'type':NULL ABSENT ON NULL) AS jsonObject";
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("{\"name\":\"value\"}", rs.getString("jsonObject"));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Test JSON_OBJECT function to return a JSON object with two keys. One key
+ * contains a JSON string and another key contains a JSON array.
+ * JSON_OBJECT() -> Constructs JSON object text from zero or more expressions.
+ * input: JSON_OBJECT('name':'value', 'type':JSON_ARRAY(1, 2)) ->
+ * output: {"name":"value","type":[1,2]}
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONObjectWithJsonArray() throws SQLException {
+ String select = "SELECT JSON_OBJECT('name':'value', 'type':JSON_ARRAY(1, 2)) AS jsonObject";
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("{\"name\":\"value\",\"type\":[1,2]}", rs.getString("jsonObject"));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Test JSON_OBJECT function to return a JSON object with two keys. One key
+ * contains a JSON string and another key contains a JSON object.
+ * JSON_OBJECT() -> Constructs JSON object text from zero or more expressions.
+ * input: JSON_OBJECT('name':'value', 'type':JSON_OBJECT('type_id':1,
+ * 'name':'a')) ->
+ * output: {"name":"value","type":{"type_id":1,"name":"a"}}
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONObjectWithNestedJsonObject() throws SQLException {
+ String select = "SELECT JSON_OBJECT('name':'value', 'type':JSON_OBJECT('type_id':1, 'name':'a')) AS jsonObject";
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("{\"name\":\"value\",\"type\":{\"type_id\":1,\"name\":\"a\"}}", rs.getString("jsonObject"));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Test JSON_OBJECT function to return a JSON object per row in the query.
+ * JSON_OBJECT() -> Constructs a JSON object per row in the query.
+ * input: JSON_OBJECT('security_id':s.security_id, 'login':s.login_name,
+ * 'status':s.status) ->
+ * output:
+ * {"security_id":"","login":"","status":""}
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONObjectPerRow() throws SQLException {
+ String select = "SELECT s.session_id, JSON_OBJECT('security_id':s.security_id, 'login':s.login_name, 'status':s.status) AS info "
+ +
+ "FROM sys.dm_exec_sessions AS s " +
+ "WHERE s.is_user_process = 1";
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(select)) {
+ while (rs.next()) {
+ int sessionId = rs.getInt("session_id");
+ String info = rs.getString("info");
+ assertTrue(info.contains("\"security_id\":\""));
+ assertTrue(info.contains("\"login\":\""));
+ assertTrue(info.contains("\"status\":\""));
+ }
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Test JSON_OBJECTAGG function to construct a JSON object with three properties
+ * from a result set.
+ * JSON_OBJECTAGG() -> Constructs a JSON object with three properties.
+ * input: JSON_OBJECTAGG(c1:c2) ->
+ * output: {"key1":"c","key2":"b","key3":"a"}
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONObjectAggWithThreeProperties() throws SQLException {
+ String select = "SELECT JSON_OBJECTAGG(c1:c2) AS jsonObjectAgg FROM (VALUES('key1', 'c'), ('key2', 'b'), ('key3','a')) AS t(c1, c2)";
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("{\"key1\":\"c\",\"key2\":\"b\",\"key3\":\"a\"}", rs.getString("jsonObjectAgg"));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Test JSON_OBJECTAGG function to return a result with two columns. The first
+ * column contains the object_id value.
+ * The second column contains a JSON object where the key is the column name and
+ * value is the column_id.
+ * JSON_OBJECTAGG() -> Constructs a JSON object with column names and column
+ * IDs.
+ * input: JSON_OBJECTAGG(c.name:c.column_id) ->
+ * output: {"column1":1,"column2":2}
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONObjectAggWithColumnNamesAndIDs() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (object_id INT, name NVARCHAR(50), column_id INT);");
+
+ stmt.executeUpdate(
+ "INSERT INTO " + dstTable + " (object_id, name, column_id) VALUES (1, 'column1', 1);");
+ stmt.executeUpdate(
+ "INSERT INTO " + dstTable + " (object_id, name, column_id) VALUES (1, 'column2', 2);");
+ stmt.executeUpdate(
+ "INSERT INTO " + dstTable + " (object_id, name, column_id) VALUES (2, 'column3', 1);");
+ stmt.executeUpdate(
+ "INSERT INTO " + dstTable + " (object_id, name, column_id) VALUES (2, 'column4', 2);");
+
+ String select = "SELECT object_id, JSON_OBJECTAGG(name:column_id) AS columns " +
+ "FROM " + dstTable + " " +
+ "GROUP BY object_id";
+ try (ResultSet rs = stmt.executeQuery(select)) {
+ while (rs.next()) {
+ int objectId = rs.getInt("object_id");
+ String columns = rs.getString("columns");
+ if (objectId == 1) {
+ assertEquals("{\"column1\":1,\"column2\":2}", columns);
+ } else if (objectId == 2) {
+ assertEquals("{\"column3\":1,\"column4\":2}", columns);
+ } else {
+ fail("Unexpected object_id: " + objectId);
+ }
+ }
+ }
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test JSON_PATH_EXISTS function to return 1 since the input JSON string
+ * contains the specified SQL/JSON path.
+ * JSON_PATH_EXISTS() -> Checks if a specified SQL/JSON path exists in the input
+ * JSON string.
+ * input: JSON_PATH_EXISTS(@jsonInfo, '$.info.address') ->
+ * output: 1
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONPathExistsTrue() throws SQLException {
+ String jsonInfo = "{\"info\":{\"address\":[{\"town\":\"Paris\"},{\"town\":\"London\"}]}}";
+ String select = "DECLARE @jsonInfo AS JSON = N'" + jsonInfo + "'; " +
+ "SELECT JSON_PATH_EXISTS(@jsonInfo, '$.info.address') AS pathExists";
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals(1, rs.getInt("pathExists"));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Test JSON_PATH_EXISTS function to return 0 since the input JSON string
+ * doesn't contain the specified SQL/JSON path.
+ * JSON_PATH_EXISTS() -> Checks if a specified SQL/JSON path exists in the input
+ * JSON string.
+ * input: JSON_PATH_EXISTS(@jsonInfo, '$.info.addresses') ->
+ * output: 0
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONPathExistsFalse() throws SQLException {
+ String jsonInfo = "{\"info\":{\"address\":[{\"town\":\"Paris\"},{\"town\":\"London\"}]}}";
+ String select = "DECLARE @jsonInfo AS JSON = N'" + jsonInfo + "'; " +
+ "SELECT JSON_PATH_EXISTS(@jsonInfo, '$.info.addresses') AS pathExists";
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals(0, rs.getInt("pathExists"));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Test JSON_QUERY function to return a JSON fragment from a CustomFields column
+ * in query results.
+ * JSON_QUERY() -> Extracts a JSON fragment from a JSON string.
+ * input: JSON_QUERY(CustomFields,'$.OtherLanguages') ->
+ * output: JSON fragment of OtherLanguages
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONQueryFragment() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement()) {
+
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable + " (PersonID INT, FullName NVARCHAR(100), CustomFields JSON);");
+
+ String insert = "INSERT INTO " + dstTable + " (PersonID, FullName, CustomFields) VALUES " +
+ "(1, 'John Doe', '{\"OtherLanguages\":[\"French\",\"Spanish\"]}'), " +
+ "(2, 'Jane Smith', '{\"OtherLanguages\":[\"German\",\"Italian\"]}')";
+ dstStmt.executeUpdate(insert);
+
+ String select = "SELECT PersonID, FullName, JSON_QUERY(CustomFields, '$.OtherLanguages') AS Languages FROM "
+ + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals(1, rs.getInt("PersonID"));
+ assertEquals("John Doe", rs.getString("FullName"));
+ assertEquals("[\"French\",\"Spanish\"]", rs.getString("Languages"));
+
+ assertTrue(rs.next());
+ assertEquals(2, rs.getInt("PersonID"));
+ assertEquals("Jane Smith", rs.getString("FullName"));
+ assertEquals("[\"German\",\"Italian\"]", rs.getString("Languages"));
+ }
+
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test JSON_QUERY function to include JSON fragments in the output of the FOR
+ * JSON clause.
+ * JSON_QUERY() -> Extracts a JSON fragment from a JSON string.
+ * input: JSON_QUERY(Tags),
+ * JSON_QUERY(CONCAT('[\"',ValidFrom,'\",\"',ValidTo,'\"]')) ValidityPeriod ->
+ * output: JSON fragments in the output of the FOR JSON clause
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONQueryForJSONClause() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement()) {
+
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable
+ + " (StockItemID INT, StockItemName NVARCHAR(100), Tags JSON, ValidFrom DATE, ValidTo DATE);");
+
+ String insert = "INSERT INTO " + dstTable
+ + " (StockItemID, StockItemName, Tags, ValidFrom, ValidTo) VALUES " +
+ "(1, 'Item1', '[\"Tag1\",\"Tag2\"]', '2023-01-01', '2023-12-31'), " +
+ "(2, 'Item2', '[\"Tag3\",\"Tag4\"]', '2023-02-01', '2023-11-30')";
+ dstStmt.executeUpdate(insert);
+
+ String select = "SELECT StockItemID, StockItemName, JSON_QUERY(Tags) AS Tags, " +
+ "JSON_QUERY(CONCAT('[\"', ValidFrom, '\",\"', ValidTo, '\"]')) AS ValidityPeriod " +
+ "FROM " + dstTable + " FOR JSON PATH";
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ String jsonResult = rs.getString(1);
+ assertTrue(jsonResult.contains("\"StockItemID\":1"));
+ assertTrue(jsonResult.contains("\"StockItemName\":\"Item1\""));
+ assertTrue(jsonResult.contains("\"Tags\":[\"Tag1\",\"Tag2\"]"));
+ assertTrue(jsonResult.contains("\"ValidityPeriod\":[\"2023-01-01\",\"2023-12-31\"]"));
+
+ assertTrue(jsonResult.contains("\"StockItemID\":2"));
+ assertTrue(jsonResult.contains("\"StockItemName\":\"Item2\""));
+ assertTrue(jsonResult.contains("\"Tags\":[\"Tag3\",\"Tag4\"]"));
+ assertTrue(jsonResult.contains("\"ValidityPeriod\":[\"2023-02-01\",\"2023-11-30\"]"));
+ }
+
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test JSON_VALUE function to use JSON properties in query results.
+ * JSON_VALUE() -> Extracts a scalar value from a JSON string.
+ * input: JSON_VALUE(jsonInfo,'$.info.address.town') ->
+ * output: JSON property value of town
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONValueInQueryResults() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement()) {
+
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable
+ + " (FirstName NVARCHAR(50), LastName NVARCHAR(50), jsonInfo JSON);");
+
+ String insert = "INSERT INTO " + dstTable + " (FirstName, LastName, jsonInfo) VALUES " +
+ "('John', 'Doe', '{\"info\":{\"address\":{\"town\":\"New York\",\"state\":\"US-NY\"}}}'), " +
+ "('Jane', 'Smith', '{\"info\":{\"address\":{\"town\":\"Los Angeles\",\"state\":\"US-CA\"}}}')";
+ dstStmt.executeUpdate(insert);
+
+ String select = "SELECT FirstName, LastName, JSON_VALUE(jsonInfo,'$.info.address.town') AS Town " +
+ "FROM " + dstTable + " " +
+ "WHERE JSON_VALUE(jsonInfo,'$.info.address.state') LIKE 'US%' " +
+ "ORDER BY JSON_VALUE(jsonInfo,'$.info.address.town')";
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("Jane", rs.getString("FirstName"));
+ assertEquals("Smith", rs.getString("LastName"));
+ assertEquals("Los Angeles", rs.getString("Town"));
+
+ assertTrue(rs.next());
+ assertEquals("John", rs.getString("FirstName"));
+ assertEquals("Doe", rs.getString("LastName"));
+ assertEquals("New York", rs.getString("Town"));
+ }
+
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test JSON_VALUE function to create computed columns based on the values of
+ * JSON properties.
+ * JSON_VALUE() -> Extracts a scalar value from a JSON string.
+ * input: JSON_VALUE(jsonContent, '$.address[0].longitude') ->
+ * output: JSON property value of longitude
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONValueComputedColumns() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);) {
+ try (Statement dstStmt = conn.createStatement()) {
+
+ dstStmt.executeUpdate(
+ "CREATE TABLE " + dstTable
+ + " (StoreID INT IDENTITY(1,1) NOT NULL, Address VARCHAR(500), jsonContent NVARCHAR(4000), "
+ +
+ "Longitude AS JSON_VALUE(jsonContent, '$.address[0].longitude'), " +
+ "Latitude AS JSON_VALUE(jsonContent, '$.address[0].latitude'));");
+
+ String insert = "INSERT INTO " + dstTable + " (Address, jsonContent) VALUES " +
+ "('123 Main St', '{\"address\":[{\"longitude\":\"-73.935242\",\"latitude\":\"40.730610\"}]}'), "
+ +
+ "('456 Elm St', '{\"address\":[{\"longitude\":\"-118.243683\",\"latitude\":\"34.052235\"}]}')";
+ dstStmt.executeUpdate(insert);
+
+ String select = "SELECT StoreID, Address, Longitude, Latitude FROM " + dstTable;
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals(1, rs.getInt("StoreID"));
+ assertEquals("123 Main St", rs.getString("Address"));
+ assertEquals("-73.935242", rs.getString("Longitude"));
+ assertEquals("40.730610", rs.getString("Latitude"));
+
+ assertTrue(rs.next());
+ assertEquals(2, rs.getInt("StoreID"));
+ assertEquals("456 Elm St", rs.getString("Address"));
+ assertEquals("-118.243683", rs.getString("Longitude"));
+ assertEquals("34.052235", rs.getString("Latitude"));
+ }
+
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test OPENJSON function to parse JSON data.
+ * OPENJSON -> Parses JSON data and returns a set of rows.
+ * input: OPENJSON((SELECT jsonCol FROM dstTable WHERE EmployeeID = 17)) ->
+ * output: Parsed JSON data
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testOpenJsonParseJson() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString)) {
+ try (Statement dstStmt = conn.createStatement()) {
+
+ dstStmt.executeUpdate("CREATE TABLE " + dstTable + " (EmployeeID INT, jsonCol NVARCHAR(MAX));");
+
+ String jsonData = "{\"id\":1, \"name\":\"John\"}";
+ dstStmt.executeUpdate(
+ "INSERT INTO " + dstTable + " (EmployeeID, jsonCol) VALUES (17, '" + jsonData + "')");
+
+ String select = "SELECT [key], [value] FROM OPENJSON((SELECT jsonCol FROM " + dstTable
+ + " WHERE EmployeeID = 17))";
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("id", rs.getString("key"));
+ assertEquals("1", rs.getString("value"));
+ assertTrue(rs.next());
+ assertEquals("name", rs.getString("key"));
+ assertEquals("John", rs.getString("value"));
+ }
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement()) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test OPENJSON function to parse nested JSON data.
+ * OPENJSON -> Parses JSON data and returns a set of rows.
+ * input: OPENJSON((SELECT jsonCol FROM dstTable WHERE EmployeeID = 18),
+ * '$.person') ->
+ * output: Parsed nested JSON data
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testOpenJsonParseNestedJson() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString)) {
+ try (Statement dstStmt = conn.createStatement()) {
+
+ dstStmt.executeUpdate("CREATE TABLE " + dstTable + " (EmployeeID INT, jsonCol NVARCHAR(MAX));");
+
+ String jsonData = "{\"person\": {\"name\": \"John\", \"age\": 30}}";
+ dstStmt.executeUpdate(
+ "INSERT INTO " + dstTable + " (EmployeeID, jsonCol) VALUES (18, '" + jsonData + "')");
+
+ String select = "SELECT [key], [value] FROM OPENJSON((SELECT jsonCol FROM " + dstTable
+ + " WHERE EmployeeID = 18), '$.person')";
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("name", rs.getString("key"));
+ assertEquals("John", rs.getString("value"));
+ assertTrue(rs.next());
+ assertEquals("age", rs.getString("key"));
+ assertEquals("30", rs.getString("value"));
+ }
+ } finally {
+ try (Statement stmt = conn.createStatement()) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test OPENJSON function to parse JSON array.
+ * OPENJSON -> Parses JSON data and returns a set of rows.
+ * input: OPENJSON((SELECT jsonCol FROM dstTable WHERE EmployeeID = 19),
+ * '$.colors') ->
+ * output: Parsed JSON array data
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testOpenJsonParseArray() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString)) {
+ try (Statement dstStmt = conn.createStatement()) {
+
+ dstStmt.executeUpdate("CREATE TABLE " + dstTable + " (EmployeeID INT, jsonCol NVARCHAR(MAX));");
+
+ String jsonData = "{\"colors\": [\"red\", \"green\", \"blue\"]}";
+ dstStmt.executeUpdate(
+ "INSERT INTO " + dstTable + " (EmployeeID, jsonCol) VALUES (19, '" + jsonData + "')");
+
+ String select = "SELECT [key], [value] FROM OPENJSON((SELECT jsonCol FROM " + dstTable
+ + " WHERE EmployeeID = 19), '$.colors')";
+ try (ResultSet rs = dstStmt.executeQuery(select)) {
+ assertTrue(rs.next());
+ assertEquals("0", rs.getString("key"));
+ assertEquals("red", rs.getString("value"));
+ assertTrue(rs.next());
+ assertEquals("1", rs.getString("key"));
+ assertEquals("green", rs.getString("value"));
+ assertTrue(rs.next());
+ assertEquals("2", rs.getString("key"));
+ assertEquals("blue", rs.getString("value"));
+ }
+ } finally {
+ try (Statement stmt = conn.createStatement()) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test JSON insertion and retrieval in a global temporary table.
+ * Global temporary tables (##TempJson) are shared across sessions and persist
+ * until all sessions using them close.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJsonInsertionInGlobalTempTable() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("##TempJson")));
+ try (Connection conn = getConnection()) {
+ String createTableSQL = "CREATE TABLE " + dstTable + " (id INT PRIMARY KEY, data JSON)";
+ String insertSQL = "INSERT INTO " + dstTable + " VALUES (?, ?)";
+ String selectSQL = "SELECT data FROM " + dstTable + " WHERE id = ?";
+
+ try (Statement stmt = conn.createStatement()) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ stmt.execute(createTableSQL);
+ }
+
+ try (PreparedStatement pstmt = conn.prepareStatement(insertSQL)) {
+ pstmt.setInt(1, 1);
+ pstmt.setString(2, "{\"status\": \"success\", \"code\": 200}");
+ pstmt.executeUpdate();
+ }
+
+ try (PreparedStatement pstmt = conn.prepareStatement(selectSQL)) {
+ pstmt.setInt(1, 1);
+ try (ResultSet rs = pstmt.executeQuery()) {
+ assertTrue(rs.next());
+ String jsonData = rs.getString(1);
+ assertEquals("{\"status\":\"success\",\"code\":200}", jsonData);
+ }
+ }
+ } finally {
+ // Ensure cleanup of the global temporary table
+ try (Connection conn = getConnection();
+ Statement stmt = conn.createStatement()) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+
+ /**
+ * Test JSON insertion and retrieval in a local temporary table.
+ * Local temporary tables (#TempJson) are session-bound and deleted
+ * automatically when the session ends.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJsonInsertionInLocalTempTable() throws SQLException {
+ try (Connection conn = getConnection()) {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("#TempJson")));
+ String createTableSQL = "CREATE TABLE " + dstTable + " (id INT PRIMARY KEY, data JSON)";
+ String insertSQL = "INSERT INTO " + dstTable + " VALUES (?, ?)";
+ String selectSQL = "SELECT data FROM " + dstTable + " WHERE id = ?";
+
+ try (Statement stmt = conn.createStatement()) {
+ stmt.execute(createTableSQL);
+ }
+
+ try (PreparedStatement pstmt = conn.prepareStatement(insertSQL)) {
+ pstmt.setInt(1, 1);
+ pstmt.setString(2, "{\"status\": \"success\", \"code\": 200}");
+ pstmt.executeUpdate();
+ }
+
+ try (PreparedStatement pstmt = conn.prepareStatement(selectSQL)) {
+ pstmt.setInt(1, 1);
+ try (ResultSet rs = pstmt.executeQuery()) {
+ assertTrue(rs.next());
+ String jsonData = rs.getString(1);
+ assertEquals("{\"status\":\"success\",\"code\":200}", jsonData);
+ }
+ }
+ } // Connection auto-closes here, so #TempJson is automatically dropped
+ }
+
+ /**
+ * Test `SELECT INTO` query to copy JSON data into a new table.
+ * `SELECT INTO` creates a new table and inserts the result of the select
+ * statement.
+ * input: `SELECT id, data INTO TargetJsonTable FROM SourceJsonTable`
+ * output: A new table `TargetJsonTable` with copied JSON data.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testSelectIntoWithJsonType() throws SQLException {
+ String sourceTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("SourceJsonTable")));
+ String targetTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("TargetJsonTable")));
+
+ try (Connection conn = getConnection()) {
+ try (Statement stmt = conn.createStatement()) {
+ TestUtils.dropTableIfExists(targetTable, stmt);
+ TestUtils.dropTableIfExists(sourceTable, stmt);
+
+ String createSourceTableSQL = "CREATE TABLE " + sourceTable + " (id INT PRIMARY KEY, data JSON)";
+ stmt.execute(createSourceTableSQL);
+
+ String insertSQL = "INSERT INTO " + sourceTable + " VALUES (?, ?)";
+ try (PreparedStatement pstmt = conn.prepareStatement(insertSQL)) {
+ pstmt.setInt(1, 1);
+ pstmt.setString(2, "{\"name\": \"Alice\", \"age\": 25}");
+ pstmt.executeUpdate();
+
+ pstmt.setInt(1, 2);
+ pstmt.setString(2, "{\"name\": \"Bob\", \"age\": 30}");
+ pstmt.executeUpdate();
+ }
+
+ // Perform `SELECT INTO` to copy data into TargetJsonTable
+ String selectIntoSQL = "SELECT id, data INTO " + targetTable + " FROM " + sourceTable;
+ stmt.execute(selectIntoSQL);
+
+ String selectSQL = "SELECT id, data FROM " + targetTable + " ORDER BY id";
+ try (ResultSet rs = stmt.executeQuery(selectSQL)) {
+ assertTrue(rs.next());
+ assertEquals(1, rs.getInt("id"));
+ assertEquals("{\"name\":\"Alice\",\"age\":25}", rs.getString("data"));
+
+ assertTrue(rs.next());
+ assertEquals(2, rs.getInt("id"));
+ assertEquals("{\"name\":\"Bob\",\"age\":30}", rs.getString("data"));
+ }
+ }
+ } finally {
+ try (Connection conn = getConnection();
+ Statement stmt = conn.createStatement()) {
+ TestUtils.dropTableIfExists(targetTable, stmt);
+ TestUtils.dropTableIfExists(sourceTable, stmt);
+ }
+ }
+ }
+
+ /**
+ * Test `JOIN` query to validate JSON support.
+ * This test checks if a `JOIN` operation correctly retrieves JSON data
+ * from multiple tables using a foreign key relationship.
+ * input: `SELECT u.id, JSON_VALUE(u.data, '$.name'), o.orderDetails FROM
+ * UsersTable u JOIN OrdersTable o ON u.id = o.userId`
+ * output: Joined data with extracted JSON fields.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJoinQueryWithJsonType() throws SQLException {
+ String usersTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("UsersTable")));
+ String ordersTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("OrdersTable")));
+
+ try (Connection conn = getConnection()) {
+ try (Statement stmt = conn.createStatement()) {
+ TestUtils.dropTableIfExists(ordersTable, stmt);
+ TestUtils.dropTableIfExists(usersTable, stmt);
+
+ String createUsersTableSQL = "CREATE TABLE " + usersTable + " (id INT PRIMARY KEY, data JSON)";
+ stmt.execute(createUsersTableSQL);
+
+ String createOrdersTableSQL = "CREATE TABLE " + ordersTable
+ + " (orderId INT PRIMARY KEY, userId INT, orderDetails JSON, FOREIGN KEY (userId) REFERENCES "
+ + usersTable + "(id))";
+ stmt.execute(createOrdersTableSQL);
+
+ String insertUserSQL = "INSERT INTO " + usersTable + " VALUES (?, ?)";
+ try (PreparedStatement pstmt = conn.prepareStatement(insertUserSQL)) {
+ pstmt.setInt(1, 1);
+ pstmt.setString(2, "{\"name\": \"Alice\", \"age\": 25}");
+ pstmt.executeUpdate();
+
+ pstmt.setInt(1, 2);
+ pstmt.setString(2, "{\"name\": \"Bob\", \"age\": 30}");
+ pstmt.executeUpdate();
+ }
+ String insertOrderSQL = "INSERT INTO " + ordersTable + " VALUES (?, ?, ?)";
+ try (PreparedStatement pstmt = conn.prepareStatement(insertOrderSQL)) {
+ pstmt.setInt(1, 101);
+ pstmt.setInt(2, 1);
+ pstmt.setString(3, "{\"product\": \"Laptop\", \"price\": 1200}");
+ pstmt.executeUpdate();
+
+ pstmt.setInt(1, 102);
+ pstmt.setInt(2, 2);
+ pstmt.setString(3, "{\"product\": \"Phone\", \"price\": 800}");
+ pstmt.executeUpdate();
+ }
+
+ // Perform `JOIN` to extract JSON values
+ String joinQuery = "SELECT u.id, JSON_VALUE(u.data, '$.name') AS userName, JSON_VALUE(o.orderDetails, '$.product') AS product "
+ +
+ "FROM " + usersTable + " u " +
+ "JOIN " + ordersTable + " o ON u.id = o.userId " +
+ "ORDER BY u.id";
+
+ try (ResultSet rs = stmt.executeQuery(joinQuery)) {
+ assertTrue(rs.next());
+ assertEquals(1, rs.getInt("id"));
+ assertEquals("Alice", rs.getString("userName"));
+ assertEquals("Laptop", rs.getString("product"));
+
+ assertTrue(rs.next());
+ assertEquals(2, rs.getInt("id"));
+ assertEquals("Bob", rs.getString("userName"));
+ assertEquals("Phone", rs.getString("product"));
+ }
+ }
+ } finally {
+ try (Connection conn = getConnection();
+ Statement stmt = conn.createStatement()) {
+ TestUtils.dropTableIfExists(ordersTable, stmt);
+ TestUtils.dropTableIfExists(usersTable, stmt);
+ }
+ }
+ }
+
+ /**
+ * Test JSON input and output with a User-Defined Function (UDF).
+ * This test ensures that JSON data can be processed via UDFs
+ * in SELECT, WHERE, and FROM clauses.
+ * input: UDF `GetAgeFromJson(JSON) RETURNS INT`
+ * output: Extracted JSON age field in various queries.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJsonInputOutputWithUdf() throws SQLException {
+ String personsTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("Persons")));
+ String udfName = "dbo.GetAgeFromJson";
+
+ try (Connection conn = getConnection()) {
+ try (Statement stmt = conn.createStatement()) {
+ TestUtils.dropTableIfExists(personsTable, stmt);
+ String dropUdfSQL = "IF OBJECT_ID('" + udfName + "', 'FN') IS NOT NULL DROP FUNCTION " + udfName;
+ stmt.execute(dropUdfSQL);
+ String createUdfSQL = "CREATE FUNCTION " + udfName + " (@json JSON) " +
+ "RETURNS INT " +
+ "AS BEGIN " +
+ "RETURN CAST(JSON_VALUE(@json, '$.age') AS INT) " +
+ "END";
+ stmt.execute(createUdfSQL);
+
+ String createTableSQL = "CREATE TABLE " + personsTable + " (id INT PRIMARY KEY, data JSON)";
+ stmt.execute(createTableSQL);
+ String insertSQL = "INSERT INTO " + personsTable + " VALUES (?, ?)";
+ try (PreparedStatement pstmt = conn.prepareStatement(insertSQL)) {
+ pstmt.setInt(1, 1);
+ pstmt.setString(2, "{\"name\": \"Alice\", \"age\": 25}");
+ pstmt.executeUpdate();
+
+ pstmt.setInt(1, 2);
+ pstmt.setString(2, "{\"name\": \"Bob\", \"age\": 30}");
+ pstmt.executeUpdate();
+ }
+
+ // Test JSON UDF in SELECT clause
+ String selectSQL = "SELECT id, " + udfName + "(data) AS extractedAge FROM " + personsTable
+ + " ORDER BY id";
+ try (ResultSet rs = stmt.executeQuery(selectSQL)) {
+ assertTrue(rs.next());
+ assertEquals(1, rs.getInt("id"));
+ assertEquals(25, rs.getInt("extractedAge"));
+
+ assertTrue(rs.next());
+ assertEquals(2, rs.getInt("id"));
+ assertEquals(30, rs.getInt("extractedAge"));
+ }
+
+ // Test JSON UDF in WHERE clause
+ String whereSQL = "SELECT id FROM " + personsTable + " WHERE " + udfName + "(data) > 25 ORDER BY id";
+ try (ResultSet rs = stmt.executeQuery(whereSQL)) {
+ assertTrue(rs.next());
+ assertEquals(2, rs.getInt("id"));
+ }
+
+ // Test JSON UDF in FROM clause (as part of a subquery)
+ String fromSQL = "SELECT extractedAge FROM (SELECT " + udfName + "(data) AS extractedAge FROM "
+ + personsTable + ") AS AgeTable";
+ try (ResultSet rs = stmt.executeQuery(fromSQL)) {
+ assertTrue(rs.next());
+ assertEquals(25, rs.getInt("extractedAge"));
+
+ assertTrue(rs.next());
+ assertEquals(30, rs.getInt("extractedAge"));
+ }
+ }
+ } finally {
+ try (Connection conn = getConnection();
+ Statement stmt = conn.createStatement()) {
+ TestUtils.dropFunctionIfExists(udfName, stmt);
+ TestUtils.dropTableIfExists(personsTable, stmt);
+ }
+ }
+ }
+
+ /**
+ * Test a User-Defined Function (UDF) that returns JSON data.
+ * This test ensures that the UDF can be used in SELECT queries
+ * to return JSON-formatted results.
+ * input: UDF `GetPersonJson(INT, NVARCHAR(100)) RETURNS JSON`
+ * output: JSON object with id and name fields.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testUdfReturningJson() throws SQLException {
+ String personsTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("Persons")));
+ String udfName = "dbo.GetPersonJson";
+
+ try (Connection conn = getConnection()) {
+ try (Statement stmt = conn.createStatement()) {
+ TestUtils.dropTableIfExists(personsTable, stmt);
+ String dropUdfSQL = "IF OBJECT_ID('" + udfName + "', 'FN') IS NOT NULL DROP FUNCTION " + udfName;
+ stmt.execute(dropUdfSQL);
+
+ String createUdfSQL = "CREATE FUNCTION " + udfName + " (@id INT, @name NVARCHAR(100)) " +
+ "RETURNS JSON " +
+ "AS BEGIN " +
+ "RETURN JSON_QUERY('{\"id\": ' + CAST(@id AS NVARCHAR) + ', \"name\": \"' + @name + '\"}') " +
+ "END";
+ stmt.execute(createUdfSQL);
+ String createTableSQL = "CREATE TABLE " + personsTable + " (id INT PRIMARY KEY, name NVARCHAR(100))";
+ stmt.execute(createTableSQL);
+
+ String insertSQL = "INSERT INTO " + personsTable + " VALUES (?, ?)";
+ try (PreparedStatement pstmt = conn.prepareStatement(insertSQL)) {
+ pstmt.setInt(1, 1);
+ pstmt.setString(2, "Alice");
+ pstmt.executeUpdate();
+
+ pstmt.setInt(1, 2);
+ pstmt.setString(2, "Bob");
+ pstmt.executeUpdate();
+ }
+
+ String selectSQL = "SELECT id, name, " + udfName + "(id, name) AS personJson FROM " + personsTable
+ + " ORDER BY id";
+ try (ResultSet rs = stmt.executeQuery(selectSQL)) {
+ assertTrue(rs.next());
+ assertEquals(1, rs.getInt("id"));
+ assertEquals("Alice", rs.getString("name"));
+ assertEquals("{\"id\":1,\"name\":\"Alice\"}", rs.getString("personJson"));
+
+ assertTrue(rs.next());
+ assertEquals(2, rs.getInt("id"));
+ assertEquals("Bob", rs.getString("name"));
+ assertEquals("{\"id\":2,\"name\":\"Bob\"}", rs.getString("personJson"));
+ }
+ }
+ } finally {
+ try (Connection conn = getConnection();
+ Statement stmt = conn.createStatement()) {
+ TestUtils.dropFunctionIfExists(udfName, stmt);
+ TestUtils.dropTableIfExists(personsTable, stmt);
+ }
+ }
+ }
+
+ /*
+ * Test inserting a 1 GB JSON file into a table.
+ * And verify there is no data loss.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testInsert1GBJson() throws SQLException, IOException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ Path tempFile = Files.createTempFile("json_output", ".json");
+
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement()) {
+
+ stmt.executeUpdate("CREATE TABLE " + dstTable + " (jsonColumn JSON);");
+
+ generateHugeJsonFile(1L * 1024 * 1024 * 1024); // 1GB JSON file
+
+ try (PreparedStatement pstmt = conn
+ .prepareStatement("INSERT INTO " + dstTable + " (jsonColumn) VALUES (?)");
+ FileReader reader = new FileReader(JSON_FILE_PATH)) {
+ pstmt.setCharacterStream(1, reader);
+ pstmt.executeUpdate();
+ }
+
+ try (PreparedStatement pstmt = conn.prepareStatement("SELECT jsonColumn FROM " + dstTable);
+ ResultSet rs = pstmt.executeQuery()) {
+
+ assertTrue(rs.next());
+ Clob jsonClob = rs.getClob(1);
+
+ try (Reader clobReader = jsonClob.getCharacterStream();
+ BufferedWriter writer = Files.newBufferedWriter(tempFile, StandardCharsets.UTF_8)) {
+
+ char[] buffer = new char[1024];
+ int charsRead;
+ while ((charsRead = clobReader.read(buffer)) != -1) {
+ writer.write(buffer, 0, charsRead);
+ }
+ }
+ }
+
+ assertTrue(Files.mismatch(Path.of(JSON_FILE_PATH), tempFile) == -1);
+
+ } catch (Exception e) {
+ fail("Test failed due to: " + e.getMessage());
+ } finally {
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement()) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ Files.deleteIfExists(tempFile);
+ }
+ }
+
+ /*
+ * Test inserting a 1.98 GB JSON file into a table.
+ * And verify there is no data loss.
+ * Note: This test took around 4 mins to run
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testInsertHugeJsonData() throws SQLException, IOException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ Path tempFile = Files.createTempFile("json_output", ".json");
+
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement()) {
+
+ stmt.executeUpdate("CREATE TABLE " + dstTable + " (jsonColumn JSON);");
+
+ generateHugeJsonFile(2L * 1024 * 1024 * 1015); // 1.98GB JSON file
+
+ try (PreparedStatement pstmt = conn
+ .prepareStatement("INSERT INTO " + dstTable + " (jsonColumn) VALUES (?)");
+ FileReader reader = new FileReader(JSON_FILE_PATH)) {
+ pstmt.setCharacterStream(1, reader);
+ pstmt.executeUpdate();
+ }
+
+ try (PreparedStatement pstmt = conn.prepareStatement("SELECT jsonColumn FROM " + dstTable);
+ ResultSet rs = pstmt.executeQuery()) {
+
+ assertTrue(rs.next());
+ Clob jsonClob = rs.getClob(1);
+
+ try (Reader clobReader = jsonClob.getCharacterStream();
+ BufferedWriter writer = Files.newBufferedWriter(tempFile, StandardCharsets.UTF_8)) {
+
+ char[] buffer = new char[1024];
+ int charsRead;
+ while ((charsRead = clobReader.read(buffer)) != -1) {
+ writer.write(buffer, 0, charsRead);
+ }
+ }
+ }
+
+ assertTrue(Files.mismatch(Path.of(JSON_FILE_PATH), tempFile) == -1);
+
+ } catch (Exception e) {
+ fail("Test failed due to: " + e.getMessage());
+ } finally {
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement()) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ Files.deleteIfExists(tempFile);
+ }
+ }
+
+ /*
+ * Test inserting around 2 GB JSON file into a table.
+ * Note: This test is expected to fail due to the maximum allowed size for a LOB.
+ * The test is designed to validate the error handling for large JSON data.
+ * Expected error -> org.opentest4j.AssertionFailedError: Test failed due to: Attempting to grow LOB beyond maximum allowed size of 216895848447 bytes.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testInsert2GBData() throws SQLException, FileNotFoundException, IOException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement()) {
+
+ stmt.executeUpdate("CREATE TABLE " + dstTable + " (jsonColumn JSON);");
+
+ generateHugeJsonFile(2L * 1024 * 1024 * 1022); // ~2 GB JSON file
+
+ try (PreparedStatement pstmt = conn
+ .prepareStatement("INSERT INTO " + dstTable + " (jsonColumn) VALUES (?)");
+ FileReader reader = new FileReader(JSON_FILE_PATH)) {
+
+ pstmt.setCharacterStream(1, reader);
+ pstmt.executeUpdate();
+ fail("Expected an exception due to exceeding the maximum allowed size for a LOB.");
+ } catch (SQLException e) {
+ assertTrue(e.getMessage().contains("Attempting to grow LOB beyond maximum allowed size"));
+ }
+
+ } finally {
+ try (Connection conn = DriverManager.getConnection(connectionString);
+ Statement stmt = conn.createStatement()) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+
+ private void generateHugeJsonFile(long targetSize) {
+ File file = new File(JSON_FILE_PATH);
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
+ writer.write("{\"data\":[");
+
+ long currentSize = 10;
+ boolean firstGroup = true;
+
+ while (currentSize < targetSize - 10) {
+ if (!firstGroup) {
+ writer.write(",");
+ }
+ writer.write("{\"group\":[");
+
+ boolean firstElement = true;
+ for (int i = 0; i < 500; i++) {
+ if (!firstElement) {
+ writer.write(",");
+ }
+ String jsonChunk = "{\"value\":\"" + "a".repeat(1000) + "\"}";
+ writer.write(jsonChunk);
+ currentSize += jsonChunk.length();
+ firstElement = false;
+ }
+
+ writer.write("]}");
+ firstGroup = false;
+ }
+
+ writer.write("]}");
+ } catch (IOException e) {
+ fail("Failed to create large JSON file: " + e.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java
index c1917fd68..4bbeafb0e 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java
@@ -803,6 +803,122 @@ public void testComputedCols() throws Exception {
}
}
+ /**
+ * Test inserting complex JSON data using prepared statement with bulk copy enabled.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testInsertJsonWithBulkCopy() throws Exception {
+ String tableName = RandomUtil.getIdentifier("BulkCopyComplexJsonTest");
+ String valid = "insert into " + AbstractSQLGenerator.escapeIdentifier(tableName) + " (jsonCol) values (?)";
+
+ try (Connection connection = PrepUtil.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;");
+ SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(valid);
+ Statement stmt = (SQLServerStatement) connection.createStatement()) {
+
+ TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt);
+ String createTable = "create table " + AbstractSQLGenerator.escapeIdentifier(tableName) + " (jsonCol JSON)";
+ stmt.execute(createTable);
+
+ String complexJsonData = "{"
+ + "\"name\":\"John\","
+ + "\"age\":30,"
+ + "\"address\":{"
+ + "\"street\":\"123 Main St\","
+ + "\"city\":\"New York\","
+ + "\"zipcode\":\"10001\""
+ + "},"
+ + "\"phoneNumbers\":["
+ + "{\"type\":\"home\",\"number\":\"212-555-1234\"},"
+ + "{\"type\":\"work\",\"number\":\"646-555-4567\"}"
+ + "],"
+ + "\"children\":["
+ + "{\"name\":\"Jane\",\"age\":10},"
+ + "{\"name\":\"Doe\",\"age\":8}"
+ + "]"
+ + "}";
+
+ pstmt.setString(1, complexJsonData);
+ pstmt.addBatch();
+ pstmt.executeBatch();
+
+ try (ResultSet rs = stmt.executeQuery("select jsonCol from " + AbstractSQLGenerator.escapeIdentifier(tableName))) {
+ assertTrue(rs.next());
+ assertEquals(complexJsonData, rs.getString(1));
+ }
+ } finally {
+ try (Statement stmt = connection.createStatement()) {
+ TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt);
+ }
+ }
+ }
+
+ /**
+ * Test select, update, create, and delete operations on JSON data and verify at each step.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testCRUDOperationsWithJson() throws Exception {
+ String tableName = RandomUtil.getIdentifier("CRUDJsonTest");
+ String createTableSQL = "CREATE TABLE " + AbstractSQLGenerator.escapeIdentifier(tableName) + " (id INT PRIMARY KEY, jsonCol JSON)";
+ String insertSQL = "INSERT INTO " + AbstractSQLGenerator.escapeIdentifier(tableName) + " (id, jsonCol) VALUES (?, ?)";
+ String selectSQL = "SELECT jsonCol FROM " + AbstractSQLGenerator.escapeIdentifier(tableName) + " WHERE id = ?";
+ String updateSQL = "UPDATE " + AbstractSQLGenerator.escapeIdentifier(tableName) + " SET jsonCol = ? WHERE id = ?";
+ String deleteSQL = "DELETE FROM " + AbstractSQLGenerator.escapeIdentifier(tableName) + " WHERE id = ?";
+
+ try (Connection connection = PrepUtil.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;");
+ Statement stmt = (SQLServerStatement) connection.createStatement()) {
+
+ stmt.execute(createTableSQL);
+
+ String initialJsonData = "{\"name\":\"John\",\"age\":30}";
+ try (PreparedStatement pstmt = connection.prepareStatement(insertSQL)) {
+ pstmt.setInt(1, 1);
+ pstmt.setString(2, initialJsonData);
+ pstmt.executeUpdate();
+ }
+
+ try (PreparedStatement pstmt = connection.prepareStatement(selectSQL)) {
+ pstmt.setInt(1, 1);
+ try (ResultSet rs = pstmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertEquals(initialJsonData, rs.getString(1));
+ }
+ }
+
+ String updatedJsonData = "{\"name\":\"Jane\",\"age\":25}";
+ try (PreparedStatement pstmt = connection.prepareStatement(updateSQL)) {
+ pstmt.setString(1, updatedJsonData);
+ pstmt.setInt(2, 1);
+ pstmt.executeUpdate();
+ }
+
+ try (PreparedStatement pstmt = connection.prepareStatement(selectSQL)) {
+ pstmt.setInt(1, 1);
+ try (ResultSet rs = pstmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertEquals(updatedJsonData, rs.getString(1));
+ }
+ }
+
+ try (PreparedStatement pstmt = connection.prepareStatement(deleteSQL)) {
+ pstmt.setInt(1, 1);
+ pstmt.executeUpdate();
+ }
+
+ try (PreparedStatement pstmt = connection.prepareStatement(selectSQL)) {
+ pstmt.setInt(1, 1);
+ try (ResultSet rs = pstmt.executeQuery()) {
+ assertFalse(rs.next());
+ }
+ }
+ } finally {
+ try (Statement stmt = connection.createStatement()) {
+ TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt);
+ }
+ }
+ }
+
/**
* Test bulk insert with no space after table name
*
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/resultset/ResultSetTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/resultset/ResultSetTest.java
index 9ad054095..8e59e4436 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/resultset/ResultSetTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/resultset/ResultSetTest.java
@@ -16,6 +16,7 @@
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
+import java.sql.DriverManager;
import java.sql.NClob;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -32,6 +33,7 @@
import com.microsoft.sqlserver.jdbc.SQLServerConnection;
import com.microsoft.sqlserver.jdbc.SQLServerException;
+import com.microsoft.sqlserver.jdbc.SQLServerResultSet;
import com.microsoft.sqlserver.jdbc.TestResource;
import com.microsoft.sqlserver.testframework.PrepUtil;
import org.junit.jupiter.api.AfterEach;
@@ -49,7 +51,6 @@
import com.microsoft.sqlserver.testframework.AbstractTest;
import com.microsoft.sqlserver.testframework.Constants;
-
@RunWith(JUnitPlatform.class)
public class ResultSetTest extends AbstractTest {
private static final String tableName = RandomUtil.getIdentifier("StatementParam");
@@ -94,13 +95,14 @@ public void cleanUp() throws Exception {
*/
@Test
@Tag(Constants.xAzureSQLDW)
+ @Tag(Constants.JSONTest)
public void testJdbc41ResultSetMethods() throws SQLException {
try (Connection con = getConnection(); Statement stmt = con.createStatement()) {
stmt.executeUpdate("create table " + AbstractSQLGenerator.escapeIdentifier(tableName) + " ( " + "col1 int, "
+ "col2 varchar(512), " + "col3 float, " + "col4 decimal(10,5), " + "col5 uniqueidentifier, "
+ "col6 xml, " + "col7 varbinary(max), " + "col8 text, " + "col9 ntext, " + "col10 varbinary(max), "
+ "col11 date, " + "col12 time, " + "col13 datetime2, " + "col14 datetimeoffset, "
- + "col15 decimal(10,9), " + "col16 decimal(38,38), "
+ + "col15 decimal(10,9), " + "col16 decimal(38,38), " + "col17 json, "
+ "order_column int identity(1,1) primary key)");
try {
@@ -120,12 +122,14 @@ public void testJdbc41ResultSetMethods() throws SQLException {
+ "'2017-05-19T10:47:15.1234567'," // col13
+ "'2017-05-19T10:47:15.1234567+02:00'," // col14
+ "0.123456789, " // col15
- + "0.1234567890123456789012345678901234567" // col16
+ + "0.1234567890123456789012345678901234567, " // col16
+ + "'{\"test\":\"123\"}'" // col17
+ ")");
stmt.executeUpdate("Insert into " + AbstractSQLGenerator.escapeIdentifier(tableName) + " values("
+ "null, " + "null, " + "null, " + "null, " + "null, " + "null, " + "null, " + "null, "
- + "null, " + "null, " + "null, " + "null, " + "null, " + "null, " + "null, " + "null)");
+ + "null, " + "null, " + "null, " + "null, " + "null, " + "null, " + "null, " + "null, "
+ + "null)");
try (ResultSet rs = stmt.executeQuery("select * from "
+ AbstractSQLGenerator.escapeIdentifier(tableName) + " order by order_column")) {
@@ -223,6 +227,9 @@ public void testJdbc41ResultSetMethods() throws SQLException {
.compareTo(new BigDecimal("0.12345678901234567890123456789012345670")));
assertEquals(0, rs.getObject("col16", BigDecimal.class)
.compareTo(new BigDecimal("0.12345678901234567890123456789012345670")));
+ String expectedJsonValue = "{\"test\":\"123\"}";
+ assertEquals(expectedJsonValue, rs.getObject(17).toString());
+ assertEquals(expectedJsonValue, rs.getObject("col17").toString());
// test null values, mostly to verify primitive wrappers do not return default values
assertTrue(rs.next());
@@ -284,6 +291,9 @@ public void testJdbc41ResultSetMethods() throws SQLException {
assertNull(rs.getObject(16, BigDecimal.class));
assertNull(rs.getObject("col16", BigDecimal.class));
+ assertNull(rs.getObject(17));
+ assertNull(rs.getObject("col17"));
+
assertFalse(rs.next());
}
} finally {
@@ -709,6 +719,43 @@ public void testResultSetClientCursorInitializerSqlErrorState() {
}
}
+ /**
+ * Test casting JSON data and retrieving it as various data types.
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testCastOnJSON() throws SQLException {
+ String dstTable = TestUtils
+ .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dstTable")));
+
+ String jsonData = "{\"key\":\"123\"}";
+
+ try (Connection conn = DriverManager.getConnection(connectionString)) {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.executeUpdate("CREATE TABLE " + dstTable + " (jsonData JSON)");
+ stmt.executeUpdate("INSERT INTO " + dstTable + " VALUES (CAST('" + jsonData + "' AS JSON))");
+
+ String select = "SELECT JSON_VALUE(jsonData, '$.key') AS c1 FROM " + dstTable;
+
+ try (SQLServerResultSet rs = (SQLServerResultSet) stmt.executeQuery(select)) {
+ rs.next();
+ assertEquals(123, rs.getShort("c1"));
+ assertEquals(123, rs.getInt("c1"));
+ assertEquals(123f, rs.getFloat("c1"));
+ assertEquals(123L, rs.getLong("c1"));
+ assertEquals(123d, rs.getDouble("c1"));
+ assertEquals(new BigDecimal(123), rs.getBigDecimal("c1"));
+ }
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ try (Statement stmt = conn.createStatement();) {
+ TestUtils.dropTableIfExists(dstTable, stmt);
+ }
+ }
+ }
+ }
+
private void ambiguousUpdateRowTestSetup(Connection conn) throws SQLException {
try (Statement stmt = conn.createStatement()) {
stmt.execute("CREATE TABLE " + tableName1 + " (i INT, data VARCHAR(30))");
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java
index 5239d2fff..0a3ebd12f 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java
@@ -149,6 +149,37 @@ public void testXML() throws SQLException {
}
}
+ /**
+ * Test JSON support
+ *
+ * @throws SQLException
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSON() throws SQLException {
+ createTables("json");
+ createTVPS("json");
+ value = "{\"severity\":\"TRACE\",\"duration\":200,\"date\":\"2024-12-17T15:45:56\"}";
+
+ tvp = new SQLServerDataTable();
+ tvp.addColumnMetadata("c1", microsoft.sql.Types.JSON);
+ tvp.addRow(value);
+
+ try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(
+ "INSERT INTO " + AbstractSQLGenerator.escapeIdentifier(tableName) + " select * from ? ;")) {
+ pstmt.setStructured(1, tvpName, tvp);
+
+ pstmt.execute();
+
+ try (Connection con = getConnection(); Statement stmt = con.createStatement();
+ ResultSet rs = stmt.executeQuery(
+ "select c1 from " + AbstractSQLGenerator.escapeIdentifier(tableName) + " ORDER BY rowId")) {
+ while (rs.next())
+ assertEquals(rs.getString("c1"), value);
+ }
+ }
+ }
+
/**
* Test ntext support
*
@@ -349,6 +380,39 @@ public void testTVPXMLStoredProcedure() throws SQLException {
}
}
+ /**
+ * JSON with StoredProcedure
+ *
+ * @throws SQLException
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testTVPJSONStoredProcedure() throws SQLException {
+ createTables("json");
+ createTVPS("json");
+ createProcedure();
+
+ value = "{\"severity\":\"TRACE\",\"duration\":200,\"date\":\"2024-12-17T15:45:56\"}";
+
+ tvp = new SQLServerDataTable();
+ tvp.addColumnMetadata("c1", microsoft.sql.Types.JSON);
+ tvp.addRow(value);
+
+ final String sql = "{call " + AbstractSQLGenerator.escapeIdentifier(procedureName) + "(?)}";
+
+ try (SQLServerCallableStatement callableStmt = (SQLServerCallableStatement) connection.prepareCall(sql)) {
+ callableStmt.setStructured(1, tvpName, tvp);
+ callableStmt.execute();
+
+ try (Connection con = getConnection(); Statement stmt = con.createStatement();
+ ResultSet rs = stmt.executeQuery(
+ "select c1 from " + AbstractSQLGenerator.escapeIdentifier(tableName) + " ORDER BY rowId")) {
+ while (rs.next())
+ assertEquals(rs.getString(1), value);
+ }
+ }
+ }
+
/**
* Text with StoredProcedure
*
@@ -693,6 +757,34 @@ public String toString() {
}
}
}
+
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJSONTVPCallableAPI() throws SQLException {
+ createTables("json");
+ createTVPS("json");
+ createProcedure();
+
+ value = "{\"Name\":\"Alice\",\"Age\":25}";
+
+ tvp = new SQLServerDataTable();
+ tvp.addColumnMetadata("c1", microsoft.sql.Types.JSON);
+ tvp.addRow(value);
+
+ final String sql = "{call " + AbstractSQLGenerator.escapeIdentifier(procedureName) + "(?)}";
+
+ try (SQLServerCallableStatement callableStmt = (SQLServerCallableStatement) connection.prepareCall(sql)) {
+ callableStmt.setObject(1, tvp);
+ callableStmt.execute();
+
+ try (Connection con = getConnection(); Statement stmt = con.createStatement();
+ ResultSet rs = stmt.executeQuery(
+ "select c1 from " + AbstractSQLGenerator.escapeIdentifier(tableName) + " ORDER BY rowId")) {
+ while (rs.next())
+ assertEquals(rs.getObject(1), value);
+ }
+ }
+ }
@BeforeAll
public static void setupTests() throws Exception {
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/RegressionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/RegressionTest.java
index 9785f0eda..f6cdc7a1b 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/RegressionTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/RegressionTest.java
@@ -238,6 +238,75 @@ public void testXmlQuery() throws SQLException {
}
}
+ /**
+ * Tests Json query
+ *
+ * @throws SQLException
+ */
+ @Test
+ @Tag(Constants.JSONTest)
+ public void testJsonQuery() throws SQLException {
+ try (Connection connection = getConnection(); Statement stmt = connection.createStatement()) {
+ tableName = RandomUtil.getIdentifier("try_SQLJSON_Table");
+ TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt);
+ stmt.execute("CREATE TABLE " + AbstractSQLGenerator.escapeIdentifier(tableName)
+ + " ([c1] int NOT NULL PRIMARY KEY, [c2] json, [c3] json)");
+
+ int pkRow1 = 1;
+ int pkRow2 = 2;
+ int pkRow3 = 3;
+ String sql = "insert into " + AbstractSQLGenerator.escapeIdentifier(tableName) + " values (?, ?,?)";
+ try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(sql)) {
+ pstmt.setInt(1, pkRow1);
+ pstmt.setObject(2, "{\"key11\":\"value11\"}");
+ pstmt.setObject(3, "{\"key12\":\"value12\"}");
+ pstmt.addBatch();
+
+ pstmt.setInt(1, pkRow2);
+ pstmt.setObject(2, "{\"key21\":\"value21\"}");
+ pstmt.setObject(3, "{\"key22\":\"value22\"}");
+ pstmt.addBatch();
+
+ pstmt.setInt(1, pkRow3);
+ pstmt.setObject(2, "{\"key31\":\"value31\"}");
+ pstmt.setObject(3, "{\"key32\":\"value32\"}");
+ pstmt.addBatch();
+
+ pstmt.executeBatch();
+ }
+
+ sql = "DELETE " + AbstractSQLGenerator.escapeIdentifier(tableName) + " where [c1] = ?";
+ try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(sql)) {
+ pstmt.setInt(1, pkRow1);
+ pstmt.executeUpdate();
+ }
+
+ sql = "UPDATE " + AbstractSQLGenerator.escapeIdentifier(tableName) + " SET [c2] = ?, [c3] = ? where [c1] = ?";
+ try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(sql)) {
+ pstmt.setObject(1, "{\"key21.1\":\"value21.1\"}");
+ pstmt.setObject(2, "{\"key22.1\":\"value22.1\"}");
+ pstmt.setInt(3, pkRow2);
+ pstmt.executeUpdate();
+ }
+
+ sql = "DELETE " + AbstractSQLGenerator.escapeIdentifier(tableName) + " where [c1] = ?";
+ try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(sql)) {
+ pstmt.setInt(1, pkRow3);
+ pstmt.executeUpdate();
+ }
+
+ try (ResultSet rs = stmt
+ .executeQuery("select * from " + AbstractSQLGenerator.escapeIdentifier(tableName))) {
+ rs.next();
+ assertEquals(rs.getInt(1), 2, "Value mismatch");
+ assertEquals(rs.getObject(2), "{\"key21.1\":\"value21.1\"}", "Value mismatch");
+ assertEquals(rs.getObject(3), "{\"key22.1\":\"value22.1\"}", "Value mismatch");
+ } finally {
+ TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt);
+ }
+ }
+ }
+
private void createTable(Statement stmt) throws SQLException {
String sql = "CREATE TABLE " + AbstractSQLGenerator.escapeIdentifier(tableName)
diff --git a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java
index eda7b8847..ad04eae26 100644
--- a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java
+++ b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java
@@ -158,8 +158,7 @@ public static void setup() throws Exception {
// no config file used
}
- connectionString = getConfiguredPropertyOrEnv(Constants.MSSQL_JDBC_TEST_CONNECTION_PROPERTIES);
-
+ connectionString = getConfiguredPropertyOrEnv(Constants.MSSQL_JDBC_TEST_CONNECTION_PROPERTIES);
applicationClientID = getConfiguredProperty("applicationClientID");
applicationKey = getConfiguredProperty("applicationKey");
tenantID = getConfiguredProperty("tenantID");
diff --git a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java
index 3009d2213..733ff07f1 100644
--- a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java
+++ b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java
@@ -29,6 +29,7 @@ private Constants() {}
* reqExternalSetup - For tests requiring external setup
* clientCertAuth - - For tests requiring client certificate authentication setup
* Fedauth - - - - - - For Fedauth tests
+ * JSONTest - - - - - For tests requiring JSON setup
*
*/
public static final String xJDBC42 = "xJDBC42";
@@ -48,6 +49,7 @@ private Constants() {}
public static final String clientCertAuth = "clientCertAuth";
public static final String fedAuth = "fedAuth";
public static final String requireSecret = "requireSecret";
+ public static final String JSONTest = "JSONTest";
public static final ThreadLocalRandom RANDOM = ThreadLocalRandom.current();
public static final Logger LOGGER = Logger.getLogger("AbstractTest");
diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlJson.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlJson.java
new file mode 100644
index 000000000..75fd65532
--- /dev/null
+++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlJson.java
@@ -0,0 +1,19 @@
+/*
+ * Microsoft JDBC Driver for SQL Server Copyright(c) Microsoft Corporation All rights reserved. This program is made
+ * available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
+package com.microsoft.sqlserver.testframework.sqlType;
+
+public class SqlJson extends SqlType {
+
+ public SqlJson() {
+ super("json", microsoft.sql.Types.JSON, 0, 0, SqlTypeValue.JSON.minValue, SqlTypeValue.JSON.maxValue,
+ SqlTypeValue.JSON.nullValue, VariableLengthType.Fixed, String.class);
+ }
+
+ @Override
+ public Object createdata() {
+ return "{}";
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlType.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlType.java
index edba5f703..e38fe3661 100644
--- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlType.java
+++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlType.java
@@ -21,6 +21,7 @@ public abstract class SqlType extends DBItems {
// exact data for debugging
protected String name = null; // type name for creating SQL query
protected JDBCType jdbctype = JDBCType.NULL;
+ protected int vendorTypeNumber = 0;
protected int precision = 0;
protected int scale = 0;
protected Object minvalue = null;
@@ -79,6 +80,34 @@ public abstract class SqlType extends DBItems {
this.type = type;
}
+ /**
+ *
+ * @param name
+ * @param vendorTypeNumber
+ * @param precision
+ * @param scale
+ * @param min
+ * minimum allowed value for the SQL type
+ * @param max
+ * maximum allowed value for the SQL type
+ * @param nullvalue
+ * default null value for the SQL type
+ * @param variableLengthType
+ * {@link VariableLengthType}
+ */
+ SqlType(String name, int vendorTypeNumber, int precision, int scale, Object min, Object max, Object nullvalue,
+ VariableLengthType variableLengthType, Class type) {
+ this.name = name;
+ this.vendorTypeNumber = vendorTypeNumber;
+ this.precision = precision;
+ this.scale = scale;
+ this.minvalue = min;
+ this.maxvalue = max;
+ this.nullvalue = nullvalue;
+ this.variableLengthType = variableLengthType;
+ this.type = type;
+ }
+
/**
*
* @return valid random value for the SQL type
@@ -262,4 +291,20 @@ public boolean canConvert(Class> target, int flag, DBConnection conn) throws E
return false;
}
+ /**
+ *
+ * @return vendorTypeNumber of SqlType object
+ */
+ public int getVendorTypeNumber() {
+ return vendorTypeNumber;
+ }
+
+ /**
+ *
+ * @param vendorTypeNumber
+ * set vendorTypeNumber of SqlType object
+ */
+ public void setVendorTypeNumber(int vendorTypeNumber) {
+ this.vendorTypeNumber = vendorTypeNumber;
+ }
}
diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlTypeValue.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlTypeValue.java
index 91c51210b..61b208134 100644
--- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlTypeValue.java
+++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlTypeValue.java
@@ -32,7 +32,8 @@ enum SqlTypeValue {
TIME("00:00:00.0000000", "23:59:59.9999999", null),
SMALLDATETIME("19000101T00:00:00", "20790606T23:59:59", null),
DATETIME2("00010101T00:00:00.0000000", "99991231T23:59:59.9999999", null),
- DATETIMEOFFSET("0001-01-01 00:00:00", "9999-12-31 23:59:59", null),;
+ DATETIMEOFFSET("0001-01-01 00:00:00", "9999-12-31 23:59:59", null),
+ JSON(null, null, null),;
Object minValue;
Object maxValue;
diff --git a/src/test/resources/BulkCopyCSVTestInputWithJson.csv b/src/test/resources/BulkCopyCSVTestInputWithJson.csv
new file mode 100644
index 000000000..f905e9b66
--- /dev/null
+++ b/src/test/resources/BulkCopyCSVTestInputWithJson.csv
@@ -0,0 +1,3 @@
+0,testing,"{""age"": 25, ""address"": {""pincode"": 123456, ""state"": ""NY""}}"
+1,test },"{""age"": 25, ""city"": ""Los Angeles""}"
+0,test {0},"{""age"": 40, ""city"": ""Chicago""}"
\ No newline at end of file