001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Florent Guillaume
018 */
019
020package org.nuxeo.ecm.core.storage.sql.jdbc.db;
021
022import java.io.Serializable;
023import java.sql.JDBCType;
024import java.sql.PreparedStatement;
025import java.sql.ResultSet;
026import java.sql.SQLException;
027import java.sql.Types;
028import java.util.Calendar;
029
030import org.nuxeo.ecm.core.storage.sql.ColumnType;
031import org.nuxeo.ecm.core.storage.sql.jdbc.dialect.Dialect;
032import org.nuxeo.ecm.core.storage.sql.jdbc.dialect.Dialect.JDBCInfo;
033
034/**
035 * An SQL {@code column}.
036 *
037 * @author Florent Guillaume
038 */
039public class Column implements Serializable {
040
041    private static final long serialVersionUID = 1L;
042
043    protected final Table table;
044
045    protected final Dialect dialect;
046
047    protected final String physicalName;
048
049    private final String quotedName;
050
051    private final String freeVariableSetter;
052
053    /** The abstract type. */
054    private final ColumnType type;
055
056    /**
057     * The JDBC {@link java.sql.Types} type. Used for: - comparison with database introspected type - switch() to get
058     * from result set or set to prepared statement - setNull to prepared statement
059     */
060    private int jdbcType;
061
062    /** The JDBC type string. */
063    private final String jdbcTypeString;
064
065    /*
066     * {@see java.sql.Array.getBaseType() value is 0 if this is not an array column
067     */
068    private int jdbcBaseType;
069
070    /*
071     * {@see java.sql.Array.getBaseTypeName() value is null if this is not an array column
072     */
073    private final String jdbcBaseTypeString;
074
075    private final String key;
076
077    private boolean identity;
078
079    private boolean primary;
080
081    private boolean nullable = true;
082
083    private String defaultValue;
084
085    /** For foreign key reference. */
086    private Table foreignTable;
087
088    private String foreignKey;
089
090    /**
091     * Creates a new column with the given name and type.
092     *
093     * @param table the column's table
094     * @param physicalName the column physical name
095     * @param type the column's type
096     * @param key the associated field name
097     */
098    public Column(Table table, String physicalName, ColumnType type, String key) {
099        this.table = table;
100        dialect = table.getDialect();
101        this.physicalName = physicalName;
102        this.type = type;
103        JDBCInfo jdbcInfo = dialect.getJDBCTypeAndString(type);
104        jdbcType = jdbcInfo.jdbcType;
105        jdbcTypeString = jdbcInfo.string;
106        jdbcBaseType = jdbcInfo.jdbcBaseType;
107        jdbcBaseTypeString = jdbcInfo.jdbcBaseTypeString;
108        this.key = key;
109        quotedName = dialect.openQuote() + physicalName + dialect.closeQuote();
110        freeVariableSetter = dialect.getFreeVariableSetterForType(type);
111    }
112
113    /**
114     * Creates a column from an existing column and an aliased table.
115     */
116    public Column(Column column, Table table) {
117        this(table, column.physicalName, column.type, column.key);
118    }
119
120    public Table getTable() {
121        return table;
122    }
123
124    public String getPhysicalName() {
125        return physicalName;
126    }
127
128    public String getQuotedName() {
129        return quotedName;
130    }
131
132    public String getFullQuotedName() {
133        return table.getQuotedName() + '.' + quotedName;
134    }
135
136    public int getJdbcType() {
137        return jdbcType;
138    }
139
140    public int getJdbcBaseType() {
141        return jdbcBaseType;
142    }
143
144    public ColumnType getType() {
145        return type;
146    }
147
148    public ColumnType getBaseType() {
149        ColumnType baseType;
150        if (type == ColumnType.ARRAY_BLOBID) {
151            baseType = ColumnType.BLOBID;
152        } else if (type == ColumnType.ARRAY_BOOLEAN) {
153            baseType = ColumnType.BOOLEAN;
154        } else if (type == ColumnType.ARRAY_CLOB) {
155            baseType = ColumnType.CLOB;
156        } else if (type == ColumnType.ARRAY_DOUBLE) {
157            baseType = ColumnType.DOUBLE;
158        } else if (type == ColumnType.ARRAY_INTEGER) {
159            baseType = ColumnType.INTEGER;
160        } else if (type == ColumnType.ARRAY_LONG) {
161            baseType = ColumnType.LONG;
162        } else if (type == ColumnType.ARRAY_STRING) {
163            baseType = ColumnType.STRING;
164        } else if (type == ColumnType.ARRAY_TIMESTAMP) {
165            baseType = ColumnType.TIMESTAMP;
166        } else {
167            baseType = type;
168        }
169        return baseType;
170    }
171
172    public String getFreeVariableSetter() {
173        return freeVariableSetter;
174    }
175
176    public boolean isArray() {
177        return type.isArray();
178    }
179
180    public boolean isOpaque() {
181        return type == ColumnType.FTINDEXED || type == ColumnType.FTSTORED;
182    }
183
184    public String checkJdbcType(int actual, String actualName, int actualSize) {
185        int expected = jdbcType;
186        if (actual == expected) {
187            return null;
188        }
189        if (dialect.isAllowedConversion(expected, actual, actualName, actualSize)) {
190            return null;
191        }
192        return String.format("SQL type mismatch for %s: expected %s, database has %s / %s(%s)", getFullQuotedName(),
193                getJDBCTypeName(expected), getJDBCTypeName(actual), actualName, actualSize);
194    }
195
196    protected static String getJDBCTypeName(int expected) {
197        try {
198            return JDBCType.valueOf(expected).getName();
199        } catch (IllegalArgumentException e) {
200            return String.valueOf(expected);
201        }
202    }
203
204    public String getKey() {
205        return key;
206    }
207
208    public void setIdentity(boolean identity) {
209        this.identity = identity;
210    }
211
212    public boolean isIdentity() {
213        return identity;
214    }
215
216    public void setPrimary(boolean primary) {
217        this.primary = primary;
218    }
219
220    public boolean isPrimary() {
221        return primary;
222    }
223
224    public void setNullable(boolean nullable) {
225        this.nullable = nullable;
226    }
227
228    public boolean isNullable() {
229        return nullable;
230    }
231
232    public String getDefaultValue() {
233        return defaultValue;
234    }
235
236    public void setDefaultValue(String defaultValue) {
237        this.defaultValue = defaultValue;
238    }
239
240    public void setReferences(Table foreignTable, String foreignKey) {
241        this.foreignTable = foreignTable;
242        this.foreignKey = foreignKey;
243    }
244
245    public Table getForeignTable() {
246        return foreignTable;
247    }
248
249    public String getForeignKey() {
250        return foreignKey;
251    }
252
253    public String getSqlTypeString() {
254        return jdbcTypeString;
255    }
256
257    public String getSqlBaseTypeString() {
258        return jdbcBaseTypeString;
259    }
260
261    public void setToPreparedStatement(PreparedStatement ps, int index, Serializable value) throws SQLException {
262        if (value == null) {
263            ps.setNull(index, jdbcType);
264            return;
265        }
266        if ((jdbcType == Types.ARRAY) && !(value instanceof Object[])) {
267            throw new SQLException("Expected an array value instead of: " + value);
268        }
269        dialect.setToPreparedStatement(ps, index, value, this);
270    }
271
272    public Serializable getFromResultSet(ResultSet rs, int index) throws SQLException {
273        Serializable result = dialect.getFromResultSet(rs, index, this);
274        if (rs.wasNull()) {
275            result = null;
276        }
277        return result;
278    }
279
280    @Override
281    public String toString() {
282        return getClass().getSimpleName() + '(' + physicalName + ')';
283    }
284
285}