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