-
Notifications
You must be signed in to change notification settings - Fork 435
/
Copy pathColumn.java
474 lines (392 loc) · 18.9 KB
/
Column.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
/*
* 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;
import java.text.MessageFormat;
import java.util.Calendar;
/**
* Column represents a database column definition (meta data) within a result set.
*/
final class Column {
private TypeInfo typeInfo;
private CryptoMetadata cryptoMetadata;
private SqlVariant internalVariant;
final void setInternalVariant(SqlVariant type) {
this.internalVariant = type;
}
final SqlVariant getInternalVariant() {
return this.internalVariant;
}
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,
SQLServerStatement statement) throws SQLServerException {
Object value = getterDTV.getValue(jdbcType, typeInfo.getScale(), getterArgs, cal, typeInfo, cryptoMetadata,
tdsReader, statement);
setInternalVariant(getterDTV.getInternalVariant());
return (null != filter) ? filter.apply(value, jdbcType) : value;
}
int getInt(TDSReader tdsReader, SQLServerStatement statement) throws SQLServerException {
return (Integer) getValue(JDBCType.INTEGER, null, null, tdsReader, statement);
}
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 ((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)
&& (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) && 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
&& (JDBCType.NCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType()
|| JDBCType.NVARCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType()
|| JDBCType.LONGNVARCHAR == cryptoMetadata.getBaseTypeInfo().getSSType()
.getJDBCType())) {
this.valueLength = valueLength * 2;
}
}
} else {
if (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 || JavaType.OBJECT == 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 || SSType.JSON == 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;
return jdbcType;
default:
return jdbcType;
}
}
boolean hasUpdates() {
return null != updaterDTV;
}
void cancelUpdates() {
updaterDTV = null;
}
void sendByRPC(TDSWriter tdsWriter, SQLServerStatement statement) 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.setJdbcTypeSetByUser(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, statement);
} 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;
}