/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jkiss.dbeaver.model.impl.jdbc.data;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBConstants;
import org.jkiss.dbeaver.model.DBPDataKind;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.*;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.exec.DBCResultSet;
import org.jkiss.dbeaver.model.exec.DBCSession;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCArrayImpl;
import org.jkiss.dbeaver.model.impl.jdbc.exec.JDBCColumnMetaData;
import org.jkiss.dbeaver.model.impl.jdbc.exec.JDBCResultSetImpl;
import org.jkiss.dbeaver.model.impl.jdbc.struct.JDBCDataType;
import org.jkiss.dbeaver.model.messages.ModelMessages;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
import org.jkiss.dbeaver.model.sql.SQLConstants;
import org.jkiss.dbeaver.model.sql.SQLUtils;
import org.jkiss.dbeaver.model.struct.DBSDataType;
import org.jkiss.dbeaver.model.struct.DBSTypedObject;
import org.jkiss.dbeaver.model.struct.DBSTypedObjectEx;
import org.jkiss.utils.CommonUtils;
import java.sql.Array;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
/**
* Array holder
*/
public class JDBCCollection implements DBDCollection, DBDValueCloneable {
private static final Log log = Log.getLog(JDBCCollection.class);
private Object[] contents;
private final DBSDataType type;
private final DBDValueHandler valueHandler;
private boolean modified;
public JDBCCollection(DBSDataType type, DBDValueHandler valueHandler, @Nullable Object[] contents) {
this.type = type;
this.valueHandler = valueHandler;
this.contents = contents;
}
@NotNull
@Override
public DBSDataType getComponentType() {
return type;
}
@NotNull
@Override
public DBDValueHandler getComponentValueHandler() {
return valueHandler;
}
@Override
public DBDValueCloneable cloneValue(DBRProgressMonitor monitor) {
return new JDBCCollection(type, valueHandler, contents);
}
@Override
public Object getRawValue() {
return contents;
}
@Override
public boolean isNull() {
return contents == null;
}
@Override
public boolean isModified() {
return modified;
}
@Override
public void release() {
contents = null;
}
public String toString() {
if (isNull()) {
return DBConstants.NULL_VALUE_LABEL;
} else {
return makeArrayString(DBDDisplayFormat.UI);
}
}
@NotNull
public String makeArrayString(DBDDisplayFormat format) {
if (isNull()) {
return SQLConstants.NULL_VALUE;
}
if (contents.length == 0) {
return "";
} else if (contents.length == 1) {
return valueHandler.getValueDisplayString(type, contents[0], format);
} else {
StringBuilder str = new StringBuilder(contents.length * 32);
str.append("[");
for (int i = 0; i < contents.length; i++) {
Object item = contents[i];
if (i > 0) str.append(','); //$NON-NLS-1$
String itemString = valueHandler.getValueDisplayString(type, item, format);
SQLUtils.appendValue(str, type, itemString);
}
str.append("]");
return str.toString();
}
}
@Override
public int getItemCount() {
return contents == null ? 0 : contents.length;
}
@Override
public Object getItem(int index) {
return contents[index];
}
@Override
public void setItem(int index, Object value) {
contents[index] = value;
modified = true;
}
@Override
public void setContents(Object[] contents) {
this.contents = contents;
this.modified = true;
}
public Array getArrayValue() throws DBCException {
Object[] attrs = new Object[contents.length];
for (int i = 0; i < contents.length; i++) {
Object attr = contents[i];
if (attr instanceof DBDValue) {
attr = ((DBDValue) attr).getRawValue();
}
attrs[i] = attr;
}
final DBSDataType dataType = getComponentType();
try (DBCSession session = DBUtils.openUtilSession(new VoidProgressMonitor(), dataType.getDataSource(), "Create JDBC array")) {
if (session instanceof Connection) {
return ((Connection) session).createArrayOf(dataType.getTypeName(), attrs);
} else {
return new JDBCArrayImpl(dataType.getTypeName(), dataType.getTypeID(), attrs);
}
} catch (Throwable e) {
throw new DBCException("Error creating struct", e);
}
}
/////////////////////////////////////////////////////////////////////////////////////
// Utilities
/////////////////////////////////////////////////////////////////////////////////////
@NotNull
public static JDBCCollection makeCollectionFromArray(@NotNull JDBCSession session, @NotNull DBSTypedObject column, Array array) throws DBCException {
DBRProgressMonitor monitor = session.getProgressMonitor();
DBSDataType elementType = null;
if (column instanceof DBSTypedObjectEx) {
DBSDataType arrayType = ((DBSTypedObjectEx) column).getDataType();
if (arrayType != null) {
elementType = arrayType.getComponentType(monitor);
}
}
if (elementType == null) {
try {
if (array == null) {
String arrayTypeName = column.getTypeName();
DBSDataType arrayType = session.getDataSource().resolveDataType(monitor, arrayTypeName);
if (arrayType != null) {
elementType = arrayType.getComponentType(monitor);
}
} else {
String baseTypeName = array.getBaseTypeName();
elementType = session.getDataSource().resolveDataType(monitor, baseTypeName);
}
} catch (Exception e) {
throw new DBCException("Error resolving data type", e);
}
}
try {
if (elementType == null) {
if (array == null) {
throw new DBCException("Can't resolve NULL array data type");
}
try {
return makeCollectionFromResultSet(session, array, null);
} catch (SQLException e) {
throw new DBCException(e, session.getDataSource()); //$NON-NLS-1$
}
}
try {
return makeCollectionFromArray(session, array, elementType);
} catch (SQLException e) {
if (array == null) {
throw new DBCException(e, session.getDataSource()); //$NON-NLS-1$
}
try {
return makeCollectionFromResultSet(session, array, elementType);
} catch (SQLException e1) {
throw new DBCException(e1, session.getDataSource()); //$NON-NLS-1$
}
}
} catch (DBException e) {
throw new DBCException("Can't extract array data from JDBC array", e); //$NON-NLS-1$
}
}
@NotNull
private static JDBCCollection makeCollectionFromResultSet(@NotNull JDBCSession session, @NotNull Array array, @Nullable DBSDataType elementType) throws SQLException, DBException {
ResultSet dbResult = array.getResultSet();
if (dbResult == null) {
throw new DBCException("JDBC array type was not resolved and result set was not provided by driver. Return NULL.");
}
DBDValueHandler valueHandler;
if (elementType == null) {
JDBCColumnMetaData itemMeta = new JDBCColumnMetaData(session.getDataSource(), dbResult.getMetaData(), 1);
elementType = DBUtils.resolveDataType(session.getProgressMonitor(), session.getDataSource(), itemMeta.getTypeName());
if (elementType == null) {
elementType = new JDBCDataType(session.getDataSource(), itemMeta);
}
valueHandler = DBUtils.findValueHandler(session, itemMeta);
} else {
valueHandler = DBUtils.findValueHandler(session, elementType);
}
try {
try (DBCResultSet resultSet = JDBCResultSetImpl.makeResultSet(session, null, dbResult, ModelMessages.model_jdbc_array_result_set, true)) {
List<Object> data = new ArrayList<>();
while (dbResult.next()) {
// Fetch second column - it contains value
data.add(valueHandler.fetchValueObject(session, resultSet, elementType, 1));
}
return new JDBCCollection(elementType, valueHandler, data.toArray());
}
} finally {
try {
dbResult.close();
} catch (SQLException e) {
log.debug("Can't close array result set", e); //$NON-NLS-1$
}
}
}
@NotNull
private static JDBCCollection makeCollectionFromArray(@NotNull JDBCSession session, @Nullable Array array, @NotNull DBSDataType elementType) throws SQLException, DBCException {
final DBDValueHandler elementValueHandler = DBUtils.findValueHandler(session, elementType);
if (array == null) {
return new JDBCCollection(elementType, elementValueHandler, null);
}
Object arrObject = array.getArray();
return makeCollectionFromJavaArray(session, elementType, elementValueHandler, arrObject);
}
@NotNull
private static JDBCCollection makeCollectionFromJavaArray(
@NotNull JDBCSession session,
@NotNull DBSDataType elementType,
@NotNull DBDValueHandler elementValueHandler,
@Nullable Object arrObject)
throws DBCException
{
int arrLength = arrObject == null ? 0 : java.lang.reflect.Array.getLength(arrObject);
Object[] contents = new Object[arrLength];
Object itemValue;
for (int i = 0; i < arrLength; i++) {
Object item = java.lang.reflect.Array.get(arrObject, i);
if (item != null && item.getClass().isArray() && elementType.getDataKind() != DBPDataKind.ARRAY) {
// This may happen in case of multidimensional array
itemValue = makeCollectionFromJavaArray(session, elementType, elementValueHandler, item);
} else {
itemValue = elementValueHandler.getValueFromObject(session, elementType, item, false);
}
contents[i] = itemValue;
}
return new JDBCCollection(elementType, elementValueHandler, contents);
}
@NotNull
public static DBDCollection makeCollectionFromString(@NotNull JDBCSession session, String value) throws DBCException {
String stringType = DBUtils.getDefaultDataTypeName(session.getDataSource(), DBPDataKind.STRING);
if (stringType == null) {
throw new DBCException("String data type not supported by database");
}
DBSDataType dataType = DBUtils.getLocalDataType(session.getDataSource(), stringType);
if (dataType == null) {
throw new DBCException("String data type '" + stringType + "' not supported by database");
}
DBDValueHandler valueHandler = DBUtils.findValueHandler(session, dataType);
// Try to divide on string elements
if (!CommonUtils.isEmpty(value)) {
if (value.startsWith("[") && value.endsWith("]")) {
// FIXME: use real parser (nested arrays, quotes escape, etc)
String arrayString = value.substring(1, value.length() - 1);
List<Object> items = new ArrayList<>();
StringTokenizer st = new StringTokenizer(arrayString, ",", false);
while (st.hasMoreTokens()) {
String token = st.nextToken().trim();
if (token.startsWith("\"") && token.endsWith("\"")) {
token = token.substring(1, token.length() - 1);
}
items.add(token);
}
return new JDBCCollectionString(dataType, valueHandler, value, items.toArray() );
}
}
return new JDBCCollectionString(dataType, valueHandler, value);
}
}