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