File D:\maven-qcr-plugin-testsunitaires\qcr4java\h2-1.3.170-sources\target\site\qc\lecode\table\Column.java.html

1 /*
2 * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
3 * Version 1.0, and under the Eclipse Public License, Version 1.0
4 * (http://h2database.com/html/license.html).
5 * Initial Developer: H2 Group
6 */
7 package org.h2.table;
8
9 import java.sql.Date;
10 import java.sql.ResultSetMetaData;
11 import java.sql.Timestamp;
12 import org.h2.command.Parser;
13 import org.h2.constant.ErrorCode;
14 import org.h2.engine.Constants;
15 import org.h2.engine.Mode;
16 import org.h2.engine.Session;
17 import org.h2.expression.ConditionAndOr;
18 import org.h2.expression.Expression;
19 import org.h2.expression.ExpressionVisitor;
20 import org.h2.expression.SequenceValue;
21 import org.h2.expression.ValueExpression;
22 import org.h2.message.DbException;
23 import org.h2.result.Row;
24 import org.h2.schema.Schema;
25 import org.h2.schema.Sequence;
26 import org.h2.util.MathUtils;
27 import org.h2.util.StringUtils;
28 import org.h2.value.DataType;
29 import org.h2.value.Value;
30 import org.h2.value.ValueDate;
31 import org.h2.value.ValueInt;
32 import org.h2.value.ValueLong;
33 import org.h2.value.ValueNull;
34 import org.h2.value.ValueString;
35 import org.h2.value.ValueTime;
36 import org.h2.value.ValueTimestamp;
37 import org.h2.value.ValueUuid;
38
39 /**
40 * This class represents a column in a table.
41 */
42 public class Column {
43
44 /**
45 * The name of the rowid pseudo column.
46 */
47 public static final String ROWID = "_ROWID_";
48
49 /**
50 * This column is not nullable.
51 */
52 public static final int NOT_NULLABLE = ResultSetMetaData.columnNoNulls;
53
54 /**
55 * This column is nullable.
56 */
57 public static final int NULLABLE = ResultSetMetaData.columnNullable;
58
59 /**
60 * It is not know whether this column is nullable.
61 */
62 public static final int NULLABLE_UNKNOWN = ResultSetMetaData.columnNullableUnknown;
63
64 private final int type;
65 private long precision;
66 private int scale;
67 private int displaySize;
68 private Table table;
69 private String name;
70 private int columnId;
71 private boolean nullable = true;
72 private Expression defaultExpression;
73 private Expression checkConstraint;
74 private String checkConstraintSQL;
75 private String originalSQL;
76 private boolean autoIncrement;
77 private long start;
78 private long increment;
79 private boolean convertNullToDefault;
80 private Sequence sequence;
81 private boolean isComputed;
82 private TableFilter computeTableFilter;
83 private int selectivity;
84 private SingleColumnResolver resolver;
85 private String comment;
86 private boolean primaryKey;
87
88 public Column(String name, int type) {
89 this(name, type, -1, -1, -1);
90 }
91
92 public Column(String name, int type, long precision, int scale, int displaySize) {
93 this.name = name;
94 this.type = type;
95 if (precision == -1 && scale == -1 && displaySize == -1) {
96 DataType dt = DataType.getDataType(type);
97 precision = dt.defaultPrecision;
98 scale = dt.defaultScale;
99 displaySize = dt.defaultDisplaySize;
100 }
101 this.precision = precision;
102 this.scale = scale;
103 this.displaySize = displaySize;
104 }
105
106 public boolean equals(Object o) {
107 if (o == this) {
108 return true;
109 } else if (!(o instanceof Column)) {
110 return false;
111 }
112 Column other = (Column) o;
113 if (table == null || other.table == null || name == null || other.name == null) {
114 return false;
115 }
116 if (table != other.table) {
117 return false;
118 }
119 return name.equals(other.name);
120 }
121
122 public int hashCode() {
123 if (table == null || name == null) {
124 return 0;
125 }
126 return table.getId() ^ name.hashCode();
127 }
128
129 public Column getClone() {
130 Column newColumn = new Column(name, type, precision, scale, displaySize);
131 newColumn.copy(this);
132 return newColumn;
133 }
134
135 /**
136 * Convert a value to this column's type.
137 *
138 * @param v the value
139 * @return the value
140 */
141 public Value convert(Value v) {
142 try {
143 return v.convertTo(type);
144 } catch (DbException e) {
145 if (e.getErrorCode() == ErrorCode.DATA_CONVERSION_ERROR_1) {
146 String target = (table == null ? "" : table.getName() + ": ") + getCreateSQL();
147 throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, v.getSQL() + " (" + target + ")");
148 }
149 throw e;
150 }
151 }
152
153 boolean getComputed() {
154 return isComputed;
155 }
156
157 /**
158 * Compute the value of this computed column.
159 *
160 * @param session the session
161 * @param row the row
162 * @return the value
163 */
164 synchronized Value computeValue(Session session, Row row) {
165 computeTableFilter.setSession(session);
166 computeTableFilter.set(row);
167 return defaultExpression.getValue(session);
168 }
169
170 /**
171 * Set the default value in the form of a computed expression of other
172 * columns.
173 *
174 * @param expression the computed expression
175 */
176 public void setComputedExpression(Expression expression) {
177 this.isComputed = true;
178 this.defaultExpression = expression;
179 }
180
181 /**
182 * Set the table and column id.
183 *
184 * @param table the table
185 * @param columnId the column index
186 */
187 public void setTable(Table table, int columnId) {
188 this.table = table;
189 this.columnId = columnId;
190 }
191
192 public Table getTable() {
193 return table;
194 }
195
196 /**
197 * Set the default expression.
198 *
199 * @param session the session
200 * @param defaultExpression the default expression
201 */
202 public void setDefaultExpression(Session session, Expression defaultExpression) {
203 // also to test that no column names are used
204 if (defaultExpression != null) {
205 defaultExpression = defaultExpression.optimize(session);
206 if (defaultExpression.isConstant()) {
207 defaultExpression = ValueExpression.get(defaultExpression.getValue(session));
208 }
209 }
210 this.defaultExpression = defaultExpression;
211 }
212
213 public int getColumnId() {
214 return columnId;
215 }
216
217 public String getSQL() {
218 return Parser.quoteIdentifier(name);
219 }
220
221 public String getName() {
222 return name;
223 }
224
225 public int getType() {
226 return type;
227 }
228
229 public long getPrecision() {
230 return precision;
231 }
232
233 public void setPrecision(long p) {
234 precision = p;
235 }
236
237 public int getDisplaySize() {
238 return displaySize;
239 }
240
241 public int getScale() {
242 return scale;
243 }
244
245 public void setNullable(boolean b) {
246 nullable = b;
247 }
248
249 /**
250 * Validate the value, convert it if required, and update the sequence value
251 * if required. If the value is null, the default value (NULL if no default
252 * is set) is returned. Check constraints are validated as well.
253 *
254 * @param session the session
255 * @param value the value or null
256 * @return the new or converted value
257 */
258 public Value validateConvertUpdateSequence(Session session, Value value) {
259 if (value == null) {
260 if (defaultExpression == null) {
261 value = ValueNull.INSTANCE;
262 } else {
263 synchronized (this) {
264 value = defaultExpression.getValue(session).convertTo(type);
265 }
266 if (primaryKey) {
267 session.setLastIdentity(value);
268 }
269 }
270 }
271 Mode mode = session.getDatabase().getMode();
272 if (value == ValueNull.INSTANCE) {
273 if (convertNullToDefault) {
274 synchronized (this) {
275 value = defaultExpression.getValue(session).convertTo(type);
276 }
277 }
278 if (value == ValueNull.INSTANCE && !nullable) {
279 if (mode.convertInsertNullToZero) {
280 DataType dt = DataType.getDataType(type);
281 if (dt.decimal) {
282 value = ValueInt.get(0).convertTo(type);
283 } else if (dt.type == Value.TIMESTAMP) {
284 value = ValueTimestamp.get(new Timestamp(session.getTransactionStart()));
285 } else if (dt.type == Value.TIME) {
286 value = ValueTime.fromNanos(0);
287 } else if (dt.type == Value.DATE) {
288 value = ValueDate.get(new Date(session.getTransactionStart()));
289 } else {
290 value = ValueString.get("").convertTo(type);
291 }
292 } else {
293 throw DbException.get(ErrorCode.NULL_NOT_ALLOWED, name);
294 }
295 }
296 }
297 if (checkConstraint != null) {
298 resolver.setValue(value);
299 Value v;
300 synchronized (this) {
301 v = checkConstraint.getValue(session);
302 }
303 // Both TRUE and NULL are ok
304 if (Boolean.FALSE.equals(v.getBoolean())) {
305 throw DbException.get(ErrorCode.CHECK_CONSTRAINT_VIOLATED_1, checkConstraint.getSQL());
306 }
307 }
308 value = value.convertScale(mode.convertOnlyToSmallerScale, scale);
309 if (precision > 0) {
310 if (!value.checkPrecision(precision)) {
311 String s = value.getTraceSQL();
312 if (s.length() > 127) {
313 s = s.substring(0, 128) + "...";
314 }
315 throw DbException.get(ErrorCode.VALUE_TOO_LONG_2,
316 getCreateSQL(), s + " (" + value.getPrecision() + ")");
317 }
318 }
319 updateSequenceIfRequired(session, value);
320 return value;
321 }
322
323 private void updateSequenceIfRequired(Session session, Value value) {
324 if (sequence != null) {
325 long current = sequence.getCurrentValue();
326 long inc = sequence.getIncrement();
327 long now = value.getLong();
328 boolean update = false;
329 if (inc > 0 && now > current) {
330 update = true;
331 } else if (inc < 0 && now < current) {
332 update = true;
333 }
334 if (update) {
335 sequence.setStartValue(now + inc);
336 session.setLastIdentity(ValueLong.get(now));
337 sequence.flush(session);
338 }
339 }
340 }
341
342 /**
343 * Convert the auto-increment flag to a sequence that is linked with this
344 * table.
345 *
346 * @param session the session
347 * @param schema the schema where the sequence should be generated
348 * @param id the object id
349 * @param temporary true if the sequence is temporary and does not need to
350 * be stored
351 */
352 public void convertAutoIncrementToSequence(Session session, Schema schema, int id, boolean temporary) {
353 if (!autoIncrement) {
354 DbException.throwInternalError();
355 }
356 if ("IDENTITY".equals(originalSQL)) {
357 originalSQL = "BIGINT";
358 }
359 String sequenceName;
360 while (true) {
361 ValueUuid uuid = ValueUuid.getNewRandom();
362 String s = uuid.getString();
363 s = s.replace('-', '_').toUpperCase();
364 sequenceName = "SYSTEM_SEQUENCE_" + s;
365 if (schema.findSequence(sequenceName) == null) {
366 break;
367 }
368 }
369 Sequence seq = new Sequence(schema, id, sequenceName, true);
370 seq.setStartValue(start);
371 seq.setIncrement(increment);
372 if (!temporary) {
373 session.getDatabase().addSchemaObject(session, seq);
374 }
375 setAutoIncrement(false, 0, 0);
376 SequenceValue seqValue = new SequenceValue(seq);
377 setDefaultExpression(session, seqValue);
378 setSequence(seq);
379 }
380
381 /**
382 * Prepare all expressions of this column.
383 *
384 * @param session the session
385 */
386 public void prepareExpression(Session session) {
387 if (defaultExpression != null) {
388 computeTableFilter = new TableFilter(session, table, null, false, null);
389 defaultExpression.mapColumns(computeTableFilter, 0);
390 defaultExpression = defaultExpression.optimize(session);
391 }
392 }
393
394 public String getCreateSQL() {
395 StringBuilder buff = new StringBuilder();
396 if (name != null) {
397 buff.append(Parser.quoteIdentifier(name)).append(' ');
398 }
399 if (originalSQL != null) {
400 buff.append(originalSQL);
401 } else {
402 buff.append(DataType.getDataType(type).name);
403 switch (type) {
404 case Value.DECIMAL:
405 buff.append('(').append(precision).append(", ").append(scale).append(')');
406 break;
407 case Value.BYTES:
408 case Value.STRING:
409 case Value.STRING_IGNORECASE:
410 case Value.STRING_FIXED:
411 if (precision < Integer.MAX_VALUE) {
412 buff.append('(').append(precision).append(')');
413 }
414 break;
415 default:
416 }
417 }
418 if (defaultExpression != null) {
419 String sql = defaultExpression.getSQL();
420 if (sql != null) {
421 if (isComputed) {
422 buff.append(" AS ").append(sql);
423 } else if (defaultExpression != null) {
424 buff.append(" DEFAULT ").append(sql);
425 }
426 }
427 }
428 if (!nullable) {
429 buff.append(" NOT NULL");
430 }
431 if (convertNullToDefault) {
432 buff.append(" NULL_TO_DEFAULT");
433 }
434 if (sequence != null) {
435 buff.append(" SEQUENCE ").append(sequence.getSQL());
436 }
437 if (selectivity != 0) {
438 buff.append(" SELECTIVITY ").append(selectivity);
439 }
440 if (comment != null) {
441 buff.append(" COMMENT ").append(StringUtils.quoteStringSQL(comment));
442 }
443 if (checkConstraint != null) {
444 buff.append(" CHECK ").append(checkConstraintSQL);
445 }
446 return buff.toString();
447 }
448
449 public boolean isNullable() {
450 return nullable;
451 }
452
453 public void setOriginalSQL(String original) {
454 originalSQL = original;
455 }
456
457 public String getOriginalSQL() {
458 return originalSQL;
459 }
460
461 public Expression getDefaultExpression() {
462 return defaultExpression;
463 }
464
465 public boolean isAutoIncrement() {
466 return autoIncrement;
467 }
468
469 /**
470 * Set the autoincrement flag and related properties of this column.
471 *
472 * @param autoInc the new autoincrement flag
473 * @param start the sequence start value
474 * @param increment the sequence increment
475 */
476 public void setAutoIncrement(boolean autoInc, long start, long increment) {
477 this.autoIncrement = autoInc;
478 this.start = start;
479 this.increment = increment;
480 this.nullable = false;
481 if (autoInc) {
482 convertNullToDefault = true;
483 }
484 }
485
486 public void setConvertNullToDefault(boolean convert) {
487 this.convertNullToDefault = convert;
488 }
489
490 /**
491 * Rename the column. This method will only set the column name to the new
492 * value.
493 *
494 * @param newName the new column name
495 */
496 public void rename(String newName) {
497 this.name = newName;
498 }
499
500 public void setSequence(Sequence sequence) {
501 this.sequence = sequence;
502 }
503
504 public Sequence getSequence() {
505 return sequence;
506 }
507
508 /**
509 * Get the selectivity of the column. Selectivity 100 means values are
510 * unique, 10 means every distinct value appears 10 times on average.
511 *
512 * @return the selectivity
513 */
514 public int getSelectivity() {
515 return selectivity == 0 ? Constants.SELECTIVITY_DEFAULT : selectivity;
516 }
517
518 /**
519 * Set the new selectivity of a column.
520 *
521 * @param selectivity the new value
522 */
523 public void setSelectivity(int selectivity) {
524 selectivity = selectivity < 0 ? 0 : (selectivity > 100 ? 100 : selectivity);
525 this.selectivity = selectivity;
526 }
527
528 /**
529 * Add a check constraint expression to this column. An existing check
530 * constraint constraint is added using AND.
531 *
532 * @param session the session
533 * @param expr the (additional) constraint
534 */
535 public void addCheckConstraint(Session session, Expression expr) {
536 resolver = new SingleColumnResolver(this);
537 synchronized (this) {
538 String oldName = name;
539 if (name == null) {
540 name = "VALUE";
541 }
542 expr.mapColumns(resolver, 0);
543 name = oldName;
544 }
545 expr = expr.optimize(session);
546 resolver.setValue(ValueNull.INSTANCE);
547 // check if the column is mapped
548 synchronized (this) {
549 expr.getValue(session);
550 }
551 if (checkConstraint == null) {
552 checkConstraint = expr;
553 } else {
554 checkConstraint = new ConditionAndOr(ConditionAndOr.AND, checkConstraint, expr);
555 }
556 checkConstraintSQL = getCheckConstraintSQL(session, name);
557 }
558
559 /**
560 * Get the check constraint expression for this column if set.
561 *
562 * @param session the session
563 * @param asColumnName the column name to use
564 * @return the constraint expression
565 */
566 public Expression getCheckConstraint(Session session, String asColumnName) {
567 if (checkConstraint == null) {
568 return null;
569 }
570 Parser parser = new Parser(session);
571 String sql;
572 synchronized (this) {
573 String oldName = name;
574 name = asColumnName;
575 sql = checkConstraint.getSQL();
576 name = oldName;
577 }
578 Expression expr = parser.parseExpression(sql);
579 return expr;
580 }
581
582 String getDefaultSQL() {
583 return defaultExpression == null ? null : defaultExpression.getSQL();
584 }
585
586 int getPrecisionAsInt() {
587 return MathUtils.convertLongToInt(precision);
588 }
589
590 DataType getDataType() {
591 return DataType.getDataType(type);
592 }
593
594 /**
595 * Get the check constraint SQL snippet.
596 *
597 * @param session the session
598 * @param asColumnName the column name to use
599 * @return the SQL snippet
600 */
601 String getCheckConstraintSQL(Session session, String asColumnName) {
602 Expression constraint = getCheckConstraint(session, asColumnName);
603 return constraint == null ? "" : constraint.getSQL();
604 }
605
606 public void setComment(String comment) {
607 this.comment = comment;
608 }
609
610 public String getComment() {
611 return comment;
612 }
613
614 public void setPrimaryKey(boolean primaryKey) {
615 this.primaryKey = primaryKey;
616 }
617
618 /**
619 * Visit the default expression, the check constraint, and the sequence (if
620 * any).
621 *
622 * @param visitor the visitor
623 * @return true if every visited expression returned true, or if there are
624 * no expressions
625 */
626 boolean isEverything(ExpressionVisitor visitor) {
627 if (visitor.getType() == ExpressionVisitor.GET_DEPENDENCIES) {
628 if (sequence != null) {
629 visitor.getDependencies().add(sequence);
630 }
631 }
632 if (defaultExpression != null && !defaultExpression.isEverything(visitor)) {
633 return false;
634 }
635 if (checkConstraint != null && !checkConstraint.isEverything(visitor)) {
636 return false;
637 }
638 return true;
639 }
640
641 public boolean isPrimaryKey() {
642 return primaryKey;
643 }
644
645 public String toString() {
646 return name;
647 }
648
649 /**
650 * Check whether the new column is of the same type and not more restricted
651 * than this column.
652 *
653 * @param newColumn the new (target) column
654 * @return true if the new column is compatible
655 */
656 public boolean isWideningConversion(Column newColumn) {
657 if (type != newColumn.type) {
658 return false;
659 }
660 if (precision > newColumn.precision) {
661 return false;
662 }
663 if (scale != newColumn.scale) {
664 return false;
665 }
666 if (nullable && !newColumn.nullable) {
667 return false;
668 }
669 if (convertNullToDefault != newColumn.convertNullToDefault) {
670 return false;
671 }
672 if (primaryKey != newColumn.primaryKey) {
673 return false;
674 }
675 if (autoIncrement || newColumn.autoIncrement) {
676 return false;
677 }
678 if (checkConstraint != null || newColumn.checkConstraint != null) {
679 return false;
680 }
681 if (convertNullToDefault || newColumn.convertNullToDefault) {
682 return false;
683 }
684 if (defaultExpression != null || newColumn.defaultExpression != null) {
685 return false;
686 }
687 if (isComputed || newColumn.isComputed) {
688 return false;
689 }
690 return true;
691 }
692
693 /**
694 * Copy the data of the source column into the current column.
695 *
696 * @param source the source column
697 */
698 public void copy(Column source) {
699 checkConstraint = source.checkConstraint;
700 checkConstraintSQL = source.checkConstraintSQL;
701 displaySize = source.displaySize;
702 name = source.name;
703 precision = source.precision;
704 scale = source.scale;
705 // table is not set
706 // columnId is not set
707 nullable = source.nullable;
708 defaultExpression = source.defaultExpression;
709 originalSQL = source.originalSQL;
710 // autoIncrement, start, increment is not set
711 convertNullToDefault = source.convertNullToDefault;
712 sequence = source.sequence;
713 comment = source.comment;
714 computeTableFilter = source.computeTableFilter;
715 isComputed = source.isComputed;
716 selectivity = source.selectivity;
717 primaryKey = source.primaryKey;
718 }
719
720 }
721