com.microsoft.sqlserver.jdbc.Column Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mssql-jdbc Show documentation
Show all versions of mssql-jdbc Show documentation
Microsoft JDBC 4.2 Driver for SQL Server.
The Azure Key Vault feature in Microsoft JDBC Driver for SQL Server depends on
Azure SDK for JAVA and Azure Active Directory Library For Java.
//---------------------------------------------------------------------------------------------------------------------------------
// File: Column.java
//
//
// Microsoft JDBC Driver for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""),
// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
package com.microsoft.sqlserver.jdbc;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.util.*;
/**
* Column represents a database column definition (meta data) within a result set.
*/
final class Column {
private TypeInfo typeInfo;
private CryptoMetadata cryptoMetadata;
final TypeInfo getTypeInfo() { return typeInfo; }
private DTV updaterDTV;
private final DTV getterDTV=new DTV();
//updated if sendStringParametersAsUnicode=true for setNString, setNCharacterStream, and setNClob methods
private JDBCType jdbcTypeSetByUser = null;
//set length of value for variable length type (String)
private int valueLength = 0;
// The column name, which may be an alias, that is used with value setters and getters.
private String columnName;
final void setColumnName(String name) { columnName = name; }
final String getColumnName() { return columnName; }
// The base column name which is the actual column name in an underlying table.
// This name must be used, rather than the column name above, when inserting or
// updating rows in the table.
private String baseColumnName;
final void setBaseColumnName(String name) { baseColumnName = name; }
final String getBaseColumnName() { return baseColumnName; }
private int tableNum;
final void setTableNum(int num) { tableNum = num; }
final int getTableNum() { return tableNum; }
private int infoStatus;
final void setInfoStatus(int status) { infoStatus = status; }
final boolean hasDifferentName() { return 0 != (infoStatus & TDS.COLINFO_STATUS_DIFFERENT_NAME); }
final boolean isHidden() { return 0 != (infoStatus & TDS.COLINFO_STATUS_HIDDEN); }
final boolean isKey() { return 0 != (infoStatus & TDS.COLINFO_STATUS_KEY); }
final boolean isExpression() { return 0 != (infoStatus & TDS.COLINFO_STATUS_EXPRESSION); }
final boolean isUpdatable()
{
return
!isExpression() &&
!isHidden() &&
tableName.getObjectName().length() > 0;
}
private SQLIdentifier tableName;
final void setTableName(SQLIdentifier name) { tableName = name; }
final SQLIdentifier getTableName() { return tableName; }
ColumnFilter filter;
/**
* Create a new column
* @param typeInfo the column TYPE_INFO
* @param columnName the column name
* @param tableName the column's table name
* @param cryptoMeta the column's crypto metadata
*/
Column(TypeInfo typeInfo, String columnName, SQLIdentifier tableName, CryptoMetadata cryptoMeta)
{
this.typeInfo = typeInfo;
this.columnName = columnName;
this.baseColumnName = columnName;
this.tableName = tableName;
this.cryptoMetadata = cryptoMeta;
}
CryptoMetadata getCryptoMetadata(){
return cryptoMetadata;
}
/**
* Clears the values associated with this column.
*/
final void clear()
{
getterDTV.clear();
}
/**
* Skip this column.
*
* The column's value may or may not already be marked. If this
* column's value has not yet been marked, this function assumes
* that the value is located at the current position in the response.
*/
final void skipValue(TDSReader tdsReader, boolean isDiscard) throws SQLServerException
{
getterDTV.skipValue(typeInfo, tdsReader, isDiscard);
}
/**
* Sets Null value on the getterDTV of a column
*/
final void initFromCompressedNull()
{
getterDTV.initFromCompressedNull();
}
void setFilter(ColumnFilter filter)
{
this.filter = filter;
}
/**
* Returns whether the value of this column is SQL NULL.
*
* If the column has not yet been read from the response then
* this method returns false.
*/
final boolean isNull()
{
return getterDTV.isNull();
}
/**
* Returns true if the column value is initialized to
* some value by reading the stream from server
* i.e. it returns true, if impl of getterDTV is not set to null
*/
final boolean isInitialized()
{
return getterDTV.isInitialized();
}
/**
* Retrieves this colum's value.
*
* If the column has not yet been read from the response then
* this method reads it.
*/
Object getValue(JDBCType jdbcType, InputStreamGetterArgs getterArgs, Calendar cal, TDSReader tdsReader) throws SQLServerException
{
Object value = getterDTV.getValue(jdbcType, typeInfo.getScale(), getterArgs, cal, typeInfo, cryptoMetadata, tdsReader);
return (null != filter) ? filter.apply(value, jdbcType) : value;
}
int getInt(TDSReader tdsReader) throws SQLServerException
{
return ((Integer) getValue(JDBCType.INTEGER, null, null, tdsReader)).intValue();
}
void updateValue(
JDBCType jdbcType,
Object value,
JavaType javaType,
StreamSetterArgs streamSetterArgs,
Calendar cal,
Integer scale,
SQLServerConnection con,
SQLServerStatementColumnEncryptionSetting stmtColumnEncriptionSetting,
Integer precision,
boolean forceEncrypt,
int parameterIndex) throws SQLServerException
{
SSType ssType = typeInfo.getSSType();
if (null != cryptoMetadata )
{
if(SSType.VARBINARYMAX == cryptoMetadata.baseTypeInfo.getSSType() && JDBCType.BINARY == jdbcType){
jdbcType = cryptoMetadata.baseTypeInfo.getSSType().getJDBCType();
}
if(null != value){
//for encrypted tinyint, we need to convert short value to byte value, otherwise it would be sent as smallint
if(JDBCType.TINYINT == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType() && javaType == JavaType.SHORT){
if(value instanceof Boolean){
if(true == ((boolean)value)){
value = 1;
}
else{
value = 0;
}
}
String stringValue = "" + value;
Short shortValue = Short.valueOf(stringValue);
if(shortValue >= 0 && shortValue <= 255){
value = shortValue.byteValue();
javaType = JavaType.BYTE;
jdbcType = JDBCType.TINYINT;
}
}
}
//if the column is encrypted and value is null, get the real column type instead of binary types
else if (jdbcType.isBinary()){
jdbcType = cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType();
}
}
if(null == scale && null != cryptoMetadata){
scale = cryptoMetadata.getBaseTypeInfo().getScale();
}
//if jdbcType is char or varchar, check if the column is actually char/varchar or nchar/nvarchar
//in order to make updateString() work with encrypted Nchar typpes
if(null != cryptoMetadata && (JDBCType.CHAR == jdbcType || JDBCType.VARCHAR == jdbcType)){
if(JDBCType.NVARCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType()
|| JDBCType.NCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType()
|| JDBCType.LONGNVARCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType()){
jdbcType = cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType();
}
}
if(Util.shouldHonorAEForParameters(stmtColumnEncriptionSetting, con))
{
if((null == cryptoMetadata) && true == forceEncrypt){
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ForceEncryptionTrue_HonorAETrue_UnencryptedColumnRS"));
Object[] msgArgs = {parameterIndex};
throw new SQLServerException(
null,
form.format(msgArgs),
null,
0,
false);
}
else{
setJdbcTypeSetByUser(jdbcType);
this.valueLength = Util.getValueLengthBaseOnJavaType(value, javaType, precision, scale, jdbcType);
//for update encrypted nchar or nvarchar value on result set, must double the value length,
//otherwise, the data is truncated.
if(null != cryptoMetadata){
if(JDBCType.NCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType()
|| JDBCType.NVARCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType()
|| JDBCType.LONGNVARCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType()){
this.valueLength = valueLength * 2;
}
}
}
}
else{
if(true == forceEncrypt){
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ForceEncryptionTrue_HonorAEFalseRS"));
Object[] msgArgs = {parameterIndex};
throw new SQLServerException(
null,
form.format(msgArgs),
null,
0,
false);
}
}
if (null != streamSetterArgs)
{
if (!streamSetterArgs.streamType.convertsTo(typeInfo))
DataTypes.throwConversionError(streamSetterArgs.streamType.toString(), ssType.toString());
}
else
{
if(null != cryptoMetadata)
{
// For GUID, set the JDBCType before checking for conversion
if ((JDBCType.UNKNOWN == jdbcType)
&& (value instanceof java.util.UUID))
{
javaType = JavaType.STRING;
jdbcType = JDBCType.GUID;
setJdbcTypeSetByUser(jdbcType);
}
SSType basicSSType = cryptoMetadata.baseTypeInfo.getSSType();
if (!jdbcType.convertsTo(basicSSType))
DataTypes.throwConversionError(jdbcType.toString(), ssType.toString());
JDBCType jdbcTypeFromSSType = getJDBCTypeFromBaseSSType(basicSSType, jdbcType);
if( jdbcTypeFromSSType != jdbcType)
{
setJdbcTypeSetByUser(jdbcTypeFromSSType);
jdbcType = jdbcTypeFromSSType;
this.valueLength = Util.getValueLengthBaseOnJavaType(value, javaType, precision, scale, jdbcType);
}
}
else
{
if (!jdbcType.convertsTo(ssType))
DataTypes.throwConversionError(jdbcType.toString(), ssType.toString());
}
}
// DateTimeOffset is not supported with SQL Server versions earlier than Katmai
if ((JDBCType.DATETIMEOFFSET == jdbcType || JavaType.DATETIMEOFFSET == javaType) &&
!con.isKatmaiOrLater())
{
throw new SQLServerException(
SQLServerException.getErrString("R_notSupported"),
SQLState.DATA_EXCEPTION_NOT_SPECIFIC,
DriverError.NOT_SET,
null);
}
// sendStringParametersAsUnicode
// If set to true, this connection property tells the driver to send textual parameters
// to the server as Unicode rather than MBCS. This is accomplished here by re-tagging
// the value with the appropriate corresponding Unicode type.
if ((null != cryptoMetadata) && (con.sendStringParametersAsUnicode()) &&
(JavaType.STRING == javaType ||
JavaType.READER == javaType ||
JavaType.CLOB == javaType))
{
jdbcType = getSSPAUJDBCType(jdbcType);
}
// Cheesy checks determine whether updating is allowed, but do not determine HOW to do
// the update (i.e. what JDBC type to use for the update). The JDBC type to use depends
// on the SQL Server type of the column and the JDBC type requested.
//
// In most cases the JDBCType to use is just the requested JDBCType. But in some cases
// a client side type conversion is necessary because SQL Server does not directly support
// conversion from the requested JDBCType to the column SSType, or the driver needs to
// provide special data conversion.
// 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) &&
(JDBCType.CHAR == jdbcType ||
JDBCType.VARCHAR == jdbcType ||
JDBCType.LONGVARCHAR == jdbcType ||
JDBCType.CLOB == jdbcType)
)
{
jdbcType = (JDBCType.CLOB == jdbcType) ? JDBCType.NCLOB : JDBCType.NVARCHAR;
}
// Update of binary SSType from textual JDBCType: Convert hex to binary.
else if (
(SSType.BINARY == ssType ||
SSType.VARBINARY == ssType ||
SSType.VARBINARYMAX == ssType ||
SSType.IMAGE == ssType ||
SSType.UDT == ssType) &&
(JDBCType.CHAR == jdbcType ||
JDBCType.VARCHAR == jdbcType ||
JDBCType.LONGVARCHAR == jdbcType)
)
{
jdbcType = JDBCType.VARBINARY;
}
// Update of textual SSType from temporal JDBCType requires
// client-side conversion from temporal to textual.
else if (
(JDBCType.TIMESTAMP == jdbcType ||
JDBCType.DATE == jdbcType ||
JDBCType.TIME == jdbcType ||
JDBCType.DATETIMEOFFSET == jdbcType) &&
(SSType.CHAR == ssType ||
SSType.VARCHAR == ssType ||
SSType.VARCHARMAX == ssType ||
SSType.TEXT == ssType ||
SSType.NCHAR == ssType ||
SSType.NVARCHAR == ssType ||
SSType.NVARCHARMAX == ssType ||
SSType.NTEXT == ssType)
)
{
jdbcType = JDBCType.NCHAR;
}
// Lazily create the updater DTV on first update of the column
if (null == updaterDTV)
updaterDTV = new DTV();
// Set the column's value
updaterDTV.setValue(typeInfo.getSQLCollation(), jdbcType, value, javaType, streamSetterArgs, cal, scale, con, false);
}
/**
* Used when sendStringParametersAsUnicode=true to derive the appropriate National Character Set
* JDBC type corresponding to the specified JDBC type.
*/
private static JDBCType getSSPAUJDBCType(JDBCType jdbcType)
{
switch (jdbcType)
{
case CHAR: return JDBCType.NCHAR;
case VARCHAR: return JDBCType.NVARCHAR;
case LONGVARCHAR: return JDBCType.LONGNVARCHAR;
case CLOB: return JDBCType.NCLOB;
default: return jdbcType;
}
}
private static JDBCType getJDBCTypeFromBaseSSType(SSType basicSSType,JDBCType jdbcType)
{
switch (jdbcType)
{
case TIMESTAMP:
if(SSType.DATETIME == basicSSType)
return JDBCType.DATETIME;
else if (SSType.SMALLDATETIME == basicSSType)
return JDBCType.SMALLDATETIME;
return jdbcType;
case NUMERIC:
case DECIMAL:
if(SSType.MONEY == basicSSType)
return JDBCType.MONEY;
if (SSType.SMALLMONEY == basicSSType)
return JDBCType.SMALLMONEY;
return jdbcType;
case CHAR:
if(SSType.GUID == basicSSType)
return JDBCType.GUID;
if(SSType.VARCHARMAX == basicSSType)
return JDBCType.LONGVARCHAR;
default:
return jdbcType;
}
}
boolean hasUpdates()
{
return null != updaterDTV;
}
void cancelUpdates()
{
updaterDTV = null;
}
void sendByRPC(
TDSWriter tdsWriter,
SQLServerConnection conn) throws SQLServerException
{
// If the column has had no updates then there is nothing to send
if (null == updaterDTV)
return;
try{
//this is for updateRow() stuff
updaterDTV.sendCryptoMetaData(cryptoMetadata, tdsWriter);
updaterDTV.jdbcTypeSetByUser(getJdbcTypeSetByUser(), getValueLength());
// Otherwise, send the updated value via RPC
updaterDTV.sendByRPC(
baseColumnName,
typeInfo,
null != cryptoMetadata ? cryptoMetadata.getBaseTypeInfo().getSQLCollation():typeInfo.getSQLCollation(),
null != cryptoMetadata ? cryptoMetadata.getBaseTypeInfo().getPrecision():typeInfo.getPrecision(),
null != cryptoMetadata ? cryptoMetadata.getBaseTypeInfo().getScale():typeInfo.getScale(),
false, // isOutParameter (always false for column updates)
tdsWriter,
conn);
}
finally{
//this is for updateRow() stuff
updaterDTV.sendCryptoMetaData(null, tdsWriter);
}
}
JDBCType getJdbcTypeSetByUser() {
return jdbcTypeSetByUser;
}
void setJdbcTypeSetByUser(JDBCType jdbcTypeSetByUser) {
this.jdbcTypeSetByUser = jdbcTypeSetByUser;
}
int getValueLength() {
return valueLength;
}
}
abstract class ColumnFilter
{
abstract Object apply(Object value, JDBCType jdbcType) throws SQLServerException;
}