Source code: com/mysql/jdbc/Field.java
1 /*
2 Copyright (C) 2002-2004 MySQL AB
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of version 2 of the GNU General Public License as
6 published by the Free Software Foundation.
7
8
9 There are special exceptions to the terms and conditions of the GPL
10 as it is applied to this software. View the full text of the
11 exception exception in file EXCEPTIONS-CONNECTOR-J in the directory of this
12 software distribution.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
23 */
24 package com.mysql.jdbc;
25
26 import java.io.UnsupportedEncodingException;
27 import java.sql.SQLException;
28 import java.sql.Types;
29
30
31 /**
32 * Field is a class used to describe fields in a
33 * ResultSet
34 *
35 * @author Mark Matthews
36 * @version $Id: Field.java,v 1.15.2.15 2004/10/21 19:31:03 mmatthew Exp $
37 */
38 public class Field {
39 //~ Static fields/initializers ---------------------------------------------
40
41 private static final int AUTO_INCREMENT_FLAG = 512;
42 private Connection connection = null;
43 private String charsetName = null;
44 private String databaseName = null;
45 private String defaultValue = null;
46 private String fullName = null;
47 private String fullNameWithDatabase = null;
48 private String fullOriginalName = null;
49 private String fullOriginalNameWithDatabase = null;
50 private String name; // The Field name
51 private String originalColumnName = null;
52 private String originalTableName = null;
53 private String tableName; // The Name of the Table
54 private byte[] buffer;
55 private int charsetIndex = 0;
56 private int colDecimals;
57 private int databaseNameLength = -1;
58
59 // database name info
60 private int databaseNameStart = -1;
61 private int defaultValueLength = -1;
62
63 // default value info - from COM_LIST_FIELDS execution
64 private int defaultValueStart = -1;
65 private long length; // Internal length of the field;
66 private int mysqlType = -1; // the MySQL type
67 private int nameLength;
68 private int nameStart;
69 private int originalColumnNameLength = -1;
70
71 // column name info (before aliasing)
72 private int originalColumnNameStart = -1;
73 private int originalTableNameLength = -1;
74
75 // table name info (before aliasing)
76 private int originalTableNameStart = -1;
77 private int precisionAdjustFactor = 0;
78 private int sqlType = -1; // the java.sql.Type
79 private int tableNameLength;
80 private int tableNameStart;
81 private short colFlag;
82
83 //~ Constructors -----------------------------------------------------------
84
85 /**
86 * Constructor used by DatabaseMetaData methods.
87 */
88 Field(String tableName, String columnName, int jdbcType, long length) {
89 this.tableName = tableName;
90 this.name = columnName;
91 this.length = length;
92 sqlType = jdbcType;
93 colFlag = 0;
94 colDecimals = 0;
95 }
96
97 /**
98 * Constructor used when communicating with pre 4.1 servers
99 */
100 Field(Connection conn, byte[] buffer, int nameStart, int nameLength,
101 int tableNameStart, int tableNameLength, long length, int mysqlType,
102 short colFlag, int colDecimals) throws SQLException {
103 this(conn, buffer, -1, -1, tableNameStart, tableNameLength, -1, -1,
104 nameStart, nameLength, -1, -1, length, mysqlType, colFlag,
105 colDecimals, -1, -1, MysqlDefs.NO_CHARSET_INFO);
106 }
107
108 /**
109 * Constructor used when communicating with 4.1 and newer
110 * servers
111 */
112 Field(Connection conn, byte[] buffer, int databaseNameStart,
113 int databaseNameLength, int tableNameStart, int tableNameLength,
114 int originalTableNameStart, int originalTableNameLength, int nameStart,
115 int nameLength, int originalColumnNameStart,
116 int originalColumnNameLength, long length, int mysqlType, short colFlag,
117 int colDecimals, int defaultValueStart, int defaultValueLength,
118 int charsetIndex) throws SQLException {
119 this.connection = conn;
120 this.buffer = buffer;
121 this.nameStart = nameStart;
122 this.nameLength = nameLength;
123 this.tableNameStart = tableNameStart;
124 this.tableNameLength = tableNameLength;
125 this.length = length;
126 this.colFlag = colFlag;
127 this.colDecimals = colDecimals;
128 this.mysqlType = mysqlType;
129
130 // 4.1 field info...
131 this.databaseNameStart = databaseNameStart;
132 this.databaseNameLength = databaseNameLength;
133
134 this.originalTableNameStart = originalTableNameStart;
135 this.originalTableNameLength = originalTableNameLength;
136
137 this.originalColumnNameStart = originalColumnNameStart;
138 this.originalColumnNameLength = originalColumnNameLength;
139
140 this.defaultValueStart = defaultValueStart;
141 this.defaultValueLength = defaultValueLength;
142
143 // Re-map to 'real' blob type, if we're a BLOB
144
145 if (this.mysqlType == MysqlDefs.FIELD_TYPE_BLOB) {
146 setBlobTypeBasedOnLength();
147 }
148
149 // Map MySqlTypes to java.sql Types
150 this.sqlType = MysqlDefs.mysqlToJavaType(this.mysqlType);
151
152 if (this.sqlType == Types.TINYINT && this.length == 1
153 && (this.connection != null && this.connection.getTinyint1isBit())) {
154 this.sqlType = Types.BIT;
155 }
156
157 // If we're not running 4.1 or newer, use the connection's
158 // charset
159 this.charsetIndex = charsetIndex;
160
161 this.charsetName = this.connection.getCharsetNameForIndex(this.charsetIndex);
162
163 boolean isBinary = isBinary();
164
165 //
166 // Handle TEXT type (special case), Fix proposed by Peter McKeown
167 //
168 if ((sqlType == java.sql.Types.LONGVARBINARY) && !isBinary) {
169 sqlType = java.sql.Types.LONGVARCHAR;
170 } else if ((sqlType == java.sql.Types.VARBINARY) && !isBinary) {
171 sqlType = java.sql.Types.VARCHAR;
172 }
173
174 //
175 // Handle odd values for 'M' for floating point/decimal numbers
176 //
177 if (!isUnsigned()) {
178 switch (this.mysqlType) {
179 case MysqlDefs.FIELD_TYPE_DECIMAL:
180 this.precisionAdjustFactor = -1;
181
182 break;
183
184 case MysqlDefs.FIELD_TYPE_DOUBLE:
185 case MysqlDefs.FIELD_TYPE_FLOAT:
186 this.precisionAdjustFactor = 1;
187
188 break;
189 }
190 } else {
191 switch (this.mysqlType) {
192 case MysqlDefs.FIELD_TYPE_DOUBLE:
193 case MysqlDefs.FIELD_TYPE_FLOAT:
194 this.precisionAdjustFactor = 1;
195
196 break;
197 }
198 }
199 }
200
201 //~ Methods ----------------------------------------------------------------
202
203 /**
204 * DOCUMENT ME!
205 *
206 * @return DOCUMENT ME!
207 */
208 public boolean isAutoIncrement() {
209 return ((colFlag & AUTO_INCREMENT_FLAG) > 0);
210 }
211
212 /**
213 * DOCUMENT ME!
214 *
215 * @return DOCUMENT ME!
216 */
217 public boolean isBinary() {
218 return ((colFlag & 128) > 0);
219 }
220
221 /**
222 * DOCUMENT ME!
223 *
224 * @return DOCUMENT ME!
225 */
226 public boolean isBlob() {
227 return ((colFlag & 16) > 0);
228 }
229
230 /**
231 * Returns the character set (if known) for this
232 * field.
233 *
234 * @return the character set
235 */
236 public String getCharacterSet() {
237 return this.charsetName;
238 }
239
240 /**
241 * DOCUMENT ME!
242 *
243 * @param conn DOCUMENT ME!
244 */
245 public void setConnection(Connection conn) {
246 this.connection = conn;
247
248 this.charsetName = this.connection.getEncoding();
249 }
250
251 /**
252 * DOCUMENT ME!
253 *
254 * @return DOCUMENT ME!
255 */
256 public String getDatabaseName() {
257 if ((this.databaseName == null) && (this.databaseNameStart != -1)
258 && (this.databaseNameLength != -1)) {
259 this.databaseName = getStringFromBytes(this.databaseNameStart,
260 this.databaseNameLength);
261 }
262
263 return this.databaseName;
264 }
265
266 /**
267 * DOCUMENT ME!
268 *
269 * @return DOCUMENT ME!
270 */
271 public String getFullName() {
272 if (fullName == null) {
273 StringBuffer fullNameBuf = new StringBuffer(getTableName().length()
274 + 1 + getName().length());
275 fullNameBuf.append(tableName);
276
277 // much faster to append a char than a String
278 fullNameBuf.append('.');
279 fullNameBuf.append(name);
280 fullName = fullNameBuf.toString();
281 fullNameBuf = null;
282 }
283
284 return fullName;
285 }
286
287 /**
288 * DOCUMENT ME!
289 *
290 * @return DOCUMENT ME!
291 */
292 public String getFullOriginalName() {
293 getOriginalName();
294
295 if (this.originalColumnName == null) {
296 return null; // we don't have this information
297 }
298
299 if (fullName == null) {
300 StringBuffer fullOriginalNameBuf = new StringBuffer(getOriginalTableName()
301 .length()
302 + 1 + getOriginalName().length());
303 fullOriginalNameBuf.append(this.originalTableName);
304
305 // much faster to append a char than a String
306 fullOriginalNameBuf.append('.');
307 fullOriginalNameBuf.append(this.originalColumnName);
308 this.fullOriginalName = fullOriginalNameBuf.toString();
309 fullOriginalNameBuf = null;
310 }
311
312 return this.fullOriginalName;
313 }
314
315 /**
316 * DOCUMENT ME!
317 *
318 * @return DOCUMENT ME!
319 */
320 public long getLength() {
321 return length;
322 }
323
324 /**
325 * DOCUMENT ME!
326 *
327 * @return DOCUMENT ME!
328 */
329 public boolean isMultipleKey() {
330 return ((colFlag & 8) > 0);
331 }
332
333 /**
334 * DOCUMENT ME!
335 *
336 * @return DOCUMENT ME!
337 */
338 public int getMysqlType() {
339 return mysqlType;
340 }
341
342 /**
343 * DOCUMENT ME!
344 *
345 * @return DOCUMENT ME!
346 */
347 public String getName() {
348 if (this.name == null) {
349 this.name = getStringFromBytes(this.nameStart, this.nameLength);
350 }
351
352 return name;
353 }
354
355 /**
356 * DOCUMENT ME!
357 *
358 * @return DOCUMENT ME!
359 */
360 public String getOriginalName() {
361 if ((this.originalColumnName == null)
362 && (this.originalColumnNameStart != -1)
363 && (this.originalColumnNameLength != -1)) {
364 this.originalColumnName = getStringFromBytes(this.originalColumnNameStart,
365 this.originalColumnNameLength);
366 }
367
368 return this.originalColumnName;
369 }
370
371 /**
372 * DOCUMENT ME!
373 *
374 * @return DOCUMENT ME!
375 */
376 public String getOriginalTableName() {
377 if ((this.originalTableName == null)
378 && (this.originalTableNameStart != -1)
379 && (this.originalTableNameLength != -1)) {
380 this.originalTableName = getStringFromBytes(this.originalTableNameStart,
381 this.originalTableNameLength);
382 }
383
384 return this.originalTableName;
385 }
386
387 /**
388 * Returns amount of correction that
389 * should be applied to the precision value.
390 *
391 * Different versions of MySQL report different
392 * precision values.
393 *
394 * @return the amount to adjust precision value by.
395 */
396 public int getPrecisionAdjustFactor() {
397 return this.precisionAdjustFactor;
398 }
399
400 /**
401 * DOCUMENT ME!
402 *
403 * @return DOCUMENT ME!
404 */
405 public boolean isPrimaryKey() {
406 return ((colFlag & 2) > 0);
407 }
408
409 /**
410 * DOCUMENT ME!
411 *
412 * @return DOCUMENT ME!
413 */
414 public int getSQLType() {
415 return sqlType;
416 }
417
418 /**
419 * DOCUMENT ME!
420 *
421 * @return DOCUMENT ME!
422 */
423 public String getTable() {
424 return getTableName();
425 }
426
427 /**
428 * DOCUMENT ME!
429 *
430 * @return DOCUMENT ME!
431 */
432 public String getTableName() {
433 if (tableName == null) {
434 tableName = getStringFromBytes(tableNameStart, tableNameLength);
435 }
436
437 return tableName;
438 }
439
440 /**
441 * DOCUMENT ME!
442 *
443 * @return DOCUMENT ME!
444 */
445 public boolean isUniqueKey() {
446 return ((colFlag & 4) > 0);
447 }
448
449 /**
450 * DOCUMENT ME!
451 *
452 * @return DOCUMENT ME!
453 */
454 public boolean isUnsigned() {
455 return ((colFlag & 32) > 0);
456 }
457
458 /**
459 * DOCUMENT ME!
460 *
461 * @return DOCUMENT ME!
462 */
463 public boolean isZeroFill() {
464 return ((colFlag & 64) > 0);
465 }
466
467 /**
468 * DOCUMENT ME!
469 *
470 * @return DOCUMENT ME!
471 */
472 public String toString() {
473 return this.getDatabaseName() + " . " + this.getTableName() + "(" + this.getOriginalTableName() + ") . " + this.getName() + "(" + this.getOriginalName() + ")";
474
475 }
476
477 int getDecimals() {
478 return colDecimals;
479 }
480
481 boolean isNotNull() {
482 return ((colFlag & 1) > 0);
483 }
484
485 /**
486 * Is this field _definitely_ not writable?
487 *
488 * @return true if this field can not be written to in an INSERT/UPDATE
489 * statement.
490 */
491 boolean isReadOnly() throws SQLException {
492 if (this.connection.getIO().versionMeetsMinimum(4, 1, 0)) {
493 String orgColumnName = getOriginalName();
494 String orgTableName = getOriginalTableName();
495
496 return !(orgColumnName != null && orgColumnName.length() > 0 &&
497 orgTableName != null && orgTableName.length() > 0);
498 } else {
499 return false;
500 }
501 }
502
503 /**
504 * Create a string with the correct charset encoding from the
505 * byte-buffer that contains the data for this field
506 */
507 private String getStringFromBytes(int stringStart, int stringLength) {
508 if ((stringStart == -1) || (stringLength == -1)) {
509 return null;
510 }
511
512 String stringVal = null;
513
514 if (connection != null) {
515 if (connection.useUnicode()) {
516
517 String encoding = this.connection.getCharacterSetMetadata();
518
519 if (encoding == null) {
520 encoding = connection.getEncoding();
521 }
522
523 if (encoding != null) {
524
525 SingleByteCharsetConverter converter = null;
526
527 if (this.connection != null) {
528 converter = this.connection.getCharsetConverter(encoding);
529 }
530
531 if (converter != null) { // we have a converter
532 stringVal = converter.toString(buffer, stringStart,
533 stringLength);
534 } else {
535 // we have no converter, use JVM converter
536 byte[] stringBytes = new byte[stringLength];
537
538 int endIndex = stringStart + stringLength;
539 int pos = 0;
540
541 for (int i = stringStart; i < endIndex; i++) {
542 stringBytes[pos++] = buffer[i];
543 }
544
545 try {
546 stringVal = new String(stringBytes, encoding);
547 } catch (UnsupportedEncodingException ue) {
548 throw new RuntimeException(
549 "Unsupported character encoding '" + encoding
550 + "'");
551 }
552 }
553 } else {
554 // we have no encoding, use JVM standard charset
555 stringVal = StringUtils.toAsciiString(buffer, stringStart,
556 stringLength);
557 }
558 } else {
559 // we are not using unicode, so use JVM standard charset
560 stringVal = StringUtils.toAsciiString(buffer, stringStart,
561 stringLength);
562 }
563 } else {
564 // we don't have a connection, so punt
565 stringVal = StringUtils.toAsciiString(buffer, stringStart,
566 stringLength);
567 }
568
569 return stringVal;
570 }
571
572 //
573 // MySQL only has one protocol-level BLOB type that it exposes
574 // which is FIELD_TYPE_BLOB, although we can divine what the
575 // actual type is by the length reported ...
576 //
577 private void setBlobTypeBasedOnLength() {
578 if (this.length == MysqlDefs.LENGTH_TINYBLOB) {
579 this.mysqlType = MysqlDefs.FIELD_TYPE_TINY_BLOB;
580 } else if (this.length == MysqlDefs.LENGTH_BLOB) {
581 this.mysqlType = MysqlDefs.FIELD_TYPE_BLOB;
582 } else if (this.length == MysqlDefs.LENGTH_MEDIUMBLOB) {
583 this.mysqlType = MysqlDefs.FIELD_TYPE_MEDIUM_BLOB;
584 } else if (this.length == MysqlDefs.LENGTH_LONGBLOB) {
585 this.mysqlType = MysqlDefs.FIELD_TYPE_LONG_BLOB;
586 }
587 }
588 }