001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.table;
007:
008: import java.sql.SQLException;
009: import java.sql.Time;
010: import java.sql.Timestamp;
011:
012: import org.h2.command.Parser;
013: import org.h2.constant.ErrorCode;
014: import org.h2.engine.Constants;
015: import org.h2.engine.Mode;
016: import org.h2.engine.Session;
017: import org.h2.expression.ConditionAndOr;
018: import org.h2.expression.Expression;
019: import org.h2.expression.ExpressionVisitor;
020: import org.h2.expression.SequenceValue;
021: import org.h2.expression.ValueExpression;
022: import org.h2.message.Message;
023: import org.h2.result.Row;
024: import org.h2.schema.Schema;
025: import org.h2.schema.Sequence;
026: import org.h2.util.MathUtils;
027: import org.h2.util.StringUtils;
028: import org.h2.value.DataType;
029: import org.h2.value.Value;
030: import org.h2.value.ValueInt;
031: import org.h2.value.ValueLong;
032: import org.h2.value.ValueNull;
033: import org.h2.value.ValueString;
034: import org.h2.value.ValueTime;
035: import org.h2.value.ValueTimestamp;
036: import org.h2.value.ValueUuid;
037:
038: /**
039: * This class represents a column in a table.
040: */
041: public class Column {
042: private final int type;
043: private final long precision;
044: private final int scale;
045: private final int displaySize;
046: private Table table;
047: private String name;
048: private int columnId;
049: private boolean nullable = true;
050: private Expression defaultExpression;
051: private Expression checkConstraint;
052: private String checkConstraintSQL;
053: private String originalSQL;
054: private boolean autoIncrement;
055: private long start;
056: private long increment;
057: private boolean convertNullToDefault;
058: private Sequence sequence;
059: private boolean isComputed;
060: private TableFilter computeTableFilter;
061: private int selectivity;
062: private SingleColumnResolver resolver;
063: private String comment;
064: private boolean primaryKey;
065:
066: // must be equal to ResultSetMetaData columnNoNulls, columnNullable,
067: // columnNullableUnknown
068: public static final int NOT_NULLABLE = 0, NULLABLE = 1,
069: NULLABLE_UNKNOWN = 2;
070:
071: public Column(String name, int type) {
072: this (name, type, -1, -1, -1);
073: }
074:
075: public boolean equals(Object o) {
076: if (o == this ) {
077: return true;
078: } else if (!(o instanceof Column)) {
079: return false;
080: }
081: Column other = (Column) o;
082: if (table == null || other.table == null || name == null
083: || other.name == null) {
084: return false;
085: }
086: return table == other.table && name.equals(other.name);
087: }
088:
089: public int hashCode() {
090: if (table == null || name == null) {
091: return 0;
092: }
093: return table.getId() ^ name.hashCode();
094: }
095:
096: public Column(String name, int type, long precision, int scale,
097: int displaySize) {
098: this .name = name;
099: this .type = type;
100: if (precision == -1 && scale == -1 && displaySize == -1) {
101: DataType dt = DataType.getDataType(type);
102: precision = dt.defaultPrecision;
103: scale = dt.defaultScale;
104: displaySize = dt.defaultDisplaySize;
105: }
106: this .precision = precision;
107: this .scale = scale;
108: this .displaySize = displaySize;
109: }
110:
111: public Column getClone() {
112: Column newColumn = new Column(name, type, precision, scale,
113: displaySize);
114: // table is not set
115: // columnId is not set
116: newColumn.nullable = nullable;
117: newColumn.defaultExpression = defaultExpression;
118: newColumn.originalSQL = originalSQL;
119: // autoIncrement, start, increment is not set
120: newColumn.convertNullToDefault = convertNullToDefault;
121: newColumn.sequence = sequence;
122: newColumn.comment = comment;
123: newColumn.isComputed = isComputed;
124: newColumn.selectivity = selectivity;
125: newColumn.primaryKey = primaryKey;
126: return newColumn;
127: }
128:
129: public boolean getComputed() {
130: return isComputed;
131: }
132:
133: public Value computeValue(Session session, Row row)
134: throws SQLException {
135: synchronized (this ) {
136: computeTableFilter.setSession(session);
137: computeTableFilter.set(row);
138: return defaultExpression.getValue(session);
139: }
140: }
141:
142: public void setComputed(boolean computed, Expression expression) {
143: this .isComputed = computed;
144: this .defaultExpression = expression;
145: }
146:
147: void setTable(Table table, int columnId) {
148: this .table = table;
149: this .columnId = columnId;
150: }
151:
152: public Table getTable() {
153: return table;
154: }
155:
156: public void setDefaultExpression(Session session,
157: Expression defaultExpression) throws SQLException {
158: // also to test that no column names are used
159: if (defaultExpression != null) {
160: defaultExpression = defaultExpression.optimize(session);
161: if (defaultExpression.isConstant()) {
162: defaultExpression = ValueExpression
163: .get(defaultExpression.getValue(session));
164: }
165: }
166: this .defaultExpression = defaultExpression;
167: }
168:
169: public int getColumnId() {
170: return columnId;
171: }
172:
173: public String getSQL() {
174: return Parser.quoteIdentifier(name);
175: }
176:
177: public String getName() {
178: return name;
179: }
180:
181: public int getType() {
182: return type;
183: }
184:
185: public long getPrecision() {
186: return precision;
187: }
188:
189: public int getDisplaySize() {
190: return displaySize;
191: }
192:
193: public int getScale() {
194: return scale;
195: }
196:
197: public void setNullable(boolean b) {
198: nullable = b;
199: }
200:
201: public Value validateConvertUpdateSequence(Session session,
202: Value value) throws SQLException {
203: if (value == null) {
204: if (defaultExpression == null) {
205: value = ValueNull.INSTANCE;
206: } else {
207: synchronized (this ) {
208: value = defaultExpression.getValue(session)
209: .convertTo(type);
210: }
211: if (primaryKey) {
212: session.setLastIdentity(value);
213: }
214: }
215: }
216: Mode mode = session.getDatabase().getMode();
217: if (value == ValueNull.INSTANCE) {
218: if (convertNullToDefault) {
219: synchronized (this ) {
220: value = defaultExpression.getValue(session)
221: .convertTo(type);
222: }
223: }
224: if (value == ValueNull.INSTANCE && !nullable) {
225: if (mode.convertInsertNullToZero) {
226: DataType dt = DataType.getDataType(type);
227: if (dt.decimal) {
228: value = ValueInt.get(0).convertTo(type);
229: } else if (dt.type == Value.TIMESTAMP) {
230: value = ValueTimestamp.getNoCopy(new Timestamp(
231: System.currentTimeMillis()));
232: } else if (dt.type == Value.TIME) {
233: // need to normalize
234: value = ValueTime.get(Time.valueOf("0:0:0"));
235: } else if (dt.type == Value.DATE) {
236: value = ValueTimestamp.getNoCopy(
237: new Timestamp(System
238: .currentTimeMillis()))
239: .convertTo(dt.type);
240: } else {
241: value = ValueString.get("").convertTo(type);
242: }
243: } else {
244: throw Message.getSQLException(
245: ErrorCode.NULL_NOT_ALLOWED, name);
246: }
247: }
248: }
249: if (checkConstraint != null) {
250: resolver.setValue(value);
251: Value v;
252: synchronized (this ) {
253: v = checkConstraint.getValue(session);
254: }
255: // Both TRUE and NULL are ok
256: if (Boolean.FALSE.equals(v.getBoolean())) {
257: throw Message.getSQLException(
258: ErrorCode.CHECK_CONSTRAINT_VIOLATED_1,
259: checkConstraint.getSQL());
260: }
261: }
262: value = value.convertScale(mode.convertOnlyToSmallerScale,
263: scale);
264: if (precision > 0) {
265: if (!value.checkPrecision(precision)) {
266: throw Message.getSQLException(
267: ErrorCode.VALUE_TOO_LONG_2, new String[] {
268: name, value.getSQL() });
269: }
270: }
271: updateSequenceIfRequired(session, value);
272: return value;
273: }
274:
275: private void updateSequenceIfRequired(Session session, Value value)
276: throws SQLException {
277: if (sequence != null) {
278: long current = sequence.getCurrentValue();
279: long increment = sequence.getIncrement();
280: long now = value.getLong();
281: boolean update = false;
282: if (increment > 0 && now > current) {
283: update = true;
284: } else if (increment < 0 && now < current) {
285: update = true;
286: }
287: if (update) {
288: sequence.setStartValue(now + increment);
289: session.setLastIdentity(ValueLong.get(now));
290: sequence.flush();
291: }
292: }
293: }
294:
295: /**
296: * Convert the auto-increment flag to a sequence that is linked with this
297: * table.
298: *
299: * @param session the session
300: * @param schema the schema where the sequence should be generated
301: * @param id the object id
302: * @param temporary true if the sequence is temporary and does not need to
303: * be stored
304: */
305: public void convertAutoIncrementToSequence(Session session,
306: Schema schema, int id, boolean temporary)
307: throws SQLException {
308: if (!autoIncrement) {
309: throw Message.getInternalError();
310: }
311: if ("IDENTITY".equals(originalSQL)) {
312: originalSQL = "BIGINT";
313: }
314: String sequenceName;
315: for (int i = 0;; i++) {
316: ValueUuid uuid = ValueUuid.getNewRandom();
317: String s = uuid.getString();
318: s = s.replace('-', '_').toUpperCase();
319: sequenceName = "SYSTEM_SEQUENCE_" + s;
320: if (schema.findSequence(sequenceName) == null) {
321: break;
322: }
323: }
324: Sequence sequence = new Sequence(schema, id, sequenceName, true);
325: sequence.setStartValue(start);
326: sequence.setIncrement(increment);
327: if (!temporary) {
328: session.getDatabase().addSchemaObject(session, sequence);
329: }
330: setAutoIncrement(false, 0, 0);
331: SequenceValue seq = new SequenceValue(sequence);
332: setDefaultExpression(session, seq);
333: setSequence(sequence);
334: }
335:
336: public void prepareExpression(Session session) throws SQLException {
337: if (defaultExpression != null) {
338: computeTableFilter = new TableFilter(session, table, null,
339: false, null);
340: defaultExpression.mapColumns(computeTableFilter, 0);
341: defaultExpression = defaultExpression.optimize(session);
342: }
343: }
344:
345: public String getCreateSQL() {
346: StringBuffer buff = new StringBuffer();
347: if (name != null) {
348: buff.append(Parser.quoteIdentifier(name));
349: buff.append(' ');
350: }
351: if (originalSQL != null) {
352: buff.append(originalSQL);
353: } else {
354: buff.append(DataType.getDataType(type).name);
355: switch (type) {
356: case Value.DECIMAL:
357: buff.append("(");
358: buff.append(precision);
359: buff.append(", ");
360: buff.append(scale);
361: buff.append(")");
362: break;
363: case Value.BYTES:
364: case Value.STRING:
365: case Value.STRING_IGNORECASE:
366: case Value.STRING_FIXED:
367: if (precision < Integer.MAX_VALUE) {
368: buff.append("(");
369: buff.append(precision);
370: buff.append(")");
371: }
372: break;
373: default:
374: }
375: }
376: if (defaultExpression != null) {
377: String sql = defaultExpression.getSQL();
378: if (sql != null) {
379: if (isComputed) {
380: buff.append(" AS ");
381: buff.append(sql);
382: } else if (defaultExpression != null) {
383: buff.append(" DEFAULT ");
384: buff.append(sql);
385: }
386: }
387: }
388: if (!nullable) {
389: buff.append(" NOT NULL");
390: }
391: if (convertNullToDefault) {
392: buff.append(" NULL_TO_DEFAULT");
393: }
394: if (sequence != null) {
395: buff.append(" SEQUENCE ");
396: buff.append(sequence.getSQL());
397: }
398: if (selectivity != 0) {
399: buff.append(" SELECTIVITY ");
400: buff.append(selectivity);
401: }
402: if (checkConstraint != null) {
403: buff.append(" CHECK ");
404: buff.append(checkConstraintSQL);
405: }
406: if (comment != null) {
407: buff.append(" COMMENT ");
408: buff.append(StringUtils.quoteStringSQL(comment));
409: }
410: return buff.toString();
411: }
412:
413: public boolean getNullable() {
414: return nullable;
415: }
416:
417: public void setOriginalSQL(String original) {
418: originalSQL = original;
419: }
420:
421: public String getOriginalSQL() {
422: return originalSQL;
423: }
424:
425: public Expression getDefaultExpression() {
426: return defaultExpression;
427: }
428:
429: public boolean getAutoIncrement() {
430: return autoIncrement;
431: }
432:
433: public void setAutoIncrement(boolean autoInc, long start,
434: long increment) {
435: this .autoIncrement = autoInc;
436: this .start = start;
437: this .increment = increment;
438: this .nullable = false;
439: if (autoInc) {
440: convertNullToDefault = true;
441: }
442: }
443:
444: public void setConvertNullToDefault(boolean convert) {
445: this .convertNullToDefault = convert;
446: }
447:
448: public void rename(String newName) {
449: this .name = newName;
450: }
451:
452: public void setSequence(Sequence sequence) {
453: this .sequence = sequence;
454: }
455:
456: public Sequence getSequence() {
457: return sequence;
458: }
459:
460: /**
461: * Get the selectivity of the column. Selectivity 100 means values are
462: * unique, 10 means every distinct value appears 10 times on average.
463: *
464: * @return the selectivity
465: */
466: public int getSelectivity() {
467: return selectivity == 0 ? Constants.SELECTIVITY_DEFAULT
468: : selectivity;
469: }
470:
471: /**
472: * Set the new selectivity of a column.
473: *
474: * @param selectivity the new value
475: */
476: public void setSelectivity(int selectivity) {
477: selectivity = selectivity < 0 ? 0 : (selectivity > 100 ? 100
478: : selectivity);
479: this .selectivity = selectivity;
480: }
481:
482: public void addCheckConstraint(Session session, Expression expr)
483: throws SQLException {
484: resolver = new SingleColumnResolver(this );
485: synchronized (this ) {
486: String oldName = name;
487: if (name == null) {
488: name = "VALUE";
489: }
490: expr.mapColumns(resolver, 0);
491: name = oldName;
492: }
493: expr = expr.optimize(session);
494: resolver.setValue(ValueNull.INSTANCE);
495: // check if the column is mapped
496: synchronized (this ) {
497: expr.getValue(session);
498: }
499: if (checkConstraint == null) {
500: checkConstraint = expr;
501: } else {
502: checkConstraint = new ConditionAndOr(ConditionAndOr.AND,
503: checkConstraint, expr);
504: }
505: checkConstraintSQL = getCheckConstraintSQL(session, name);
506: }
507:
508: public Expression getCheckConstraint(Session session,
509: String asColumnName) throws SQLException {
510: if (checkConstraint == null) {
511: return null;
512: }
513: Parser parser = new Parser(session);
514: String sql;
515: synchronized (this ) {
516: String oldName = name;
517: name = asColumnName;
518: sql = checkConstraint.getSQL();
519: name = oldName;
520: }
521: Expression expr = parser.parseExpression(sql);
522: return expr;
523: }
524:
525: public String getDefaultSQL() {
526: return defaultExpression == null ? null : defaultExpression
527: .getSQL();
528: }
529:
530: public int getPrecisionAsInt() {
531: return MathUtils.convertLongToInt(precision);
532: }
533:
534: public DataType getDataType() {
535: return DataType.getDataType(type);
536: }
537:
538: public String getCheckConstraintSQL(Session session, String name)
539: throws SQLException {
540: Expression constraint = getCheckConstraint(session, name);
541: return constraint == null ? "" : constraint.getSQL();
542: }
543:
544: public void setComment(String comment) {
545: this .comment = comment;
546: }
547:
548: public String getComment() {
549: return comment;
550: }
551:
552: public void setPrimaryKey(boolean primaryKey) {
553: this .primaryKey = primaryKey;
554: }
555:
556: boolean isEverything(ExpressionVisitor visitor) {
557: if (visitor.type == ExpressionVisitor.GET_DEPENDENCIES) {
558: if (sequence != null) {
559: visitor.getDependencies().add(sequence);
560: }
561: }
562: if (defaultExpression != null
563: && !defaultExpression.isEverything(visitor)) {
564: return false;
565: }
566: if (checkConstraint != null
567: && !checkConstraint.isEverything(visitor)) {
568: return false;
569: }
570: return true;
571: }
572:
573: }
|