package org.h2.result;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import org.h2.api.ErrorCode;
import org.h2.jdbc.JdbcConnection;
import org.h2.message.DbException;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
import org.h2.value.DataType;
import org.h2.value.Value;
import org.h2.value.ValueNull;
public class UpdatableRow {
private final JdbcConnection conn;
private final ResultInterface result;
private final int columnCount;
private String schemaName;
private String tableName;
private ArrayList<String> key;
private boolean isUpdatable;
public UpdatableRow(JdbcConnection conn, ResultInterface result)
throws SQLException {
this.conn = conn;
this.result = result;
columnCount = result.getVisibleColumnCount();
for (int i = 0; i < columnCount; i++) {
String t = result.getTableName(i);
String s = result.getSchemaName(i);
if (t == null || s == null) {
return;
}
if (tableName == null) {
tableName = t;
} else if (!tableName.equals(t)) {
return;
}
if (schemaName == null) {
schemaName = s;
} else if (!schemaName.equals(s)) {
return;
}
}
final DatabaseMetaData meta = conn.getMetaData();
ResultSet rs = meta.getTables(null,
StringUtils.escapeMetaDataPattern(schemaName),
StringUtils.escapeMetaDataPattern(tableName),
new String[] { "TABLE" });
if (!rs.next()) {
return;
}
if (rs.getString("SQL") == null) {
return;
}
String table = rs.getString("TABLE_NAME");
boolean toUpper = !table.equals(tableName) && table.equalsIgnoreCase(tableName);
key = Utils.newSmallArrayList();
rs = meta.getPrimaryKeys(null,
StringUtils.escapeMetaDataPattern(schemaName),
tableName);
while (rs.next()) {
String c = rs.getString("COLUMN_NAME");
key.add(toUpper ? StringUtils.toUpperEnglish(c) : c);
}
if (isIndexUsable(key)) {
isUpdatable = true;
return;
}
key.clear();
rs = meta.getIndexInfo(null,
StringUtils.escapeMetaDataPattern(schemaName),
tableName, true, true);
while (rs.next()) {
int pos = rs.getShort("ORDINAL_POSITION");
if (pos == 1) {
if (isIndexUsable(key)) {
isUpdatable = true;
return;
}
key.clear();
}
String c = rs.getString("COLUMN_NAME");
key.add(toUpper ? StringUtils.toUpperEnglish(c) : c);
}
if (isIndexUsable(key)) {
isUpdatable = true;
return;
}
key = null;
}
private boolean isIndexUsable(ArrayList<String> indexColumns) {
if (indexColumns.isEmpty()) {
return false;
}
for (String c : indexColumns) {
if (findColumnIndex(c) < 0) {
return false;
}
}
return true;
}
public boolean isUpdatable() {
return isUpdatable;
}
private int findColumnIndex(String columnName) {
for (int i = 0; i < columnCount; i++) {
String col = result.getColumnName(i);
if (col.equals(columnName)) {
return i;
}
}
return -1;
}
private int getColumnIndex(String columnName) {
int index = findColumnIndex(columnName);
if (index < 0) {
throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, columnName);
}
return index;
}
private void appendColumnList(StringBuilder builder, boolean set) {
for (int i = 0; i < columnCount; i++) {
if (i > 0) {
builder.append(',');
}
String col = result.getColumnName(i);
StringUtils.quoteIdentifier(builder, col);
if (set) {
builder.append("=? ");
}
}
}
private void appendKeyCondition(StringBuilder builder) {
builder.append(" WHERE ");
for (int i = 0; i < key.size(); i++) {
if (i > 0) {
builder.append(" AND ");
}
StringUtils.quoteIdentifier(builder, key.get(i)).append("=?");
}
}
private void setKey(PreparedStatement prep, int start, Value[] current)
throws SQLException {
for (int i = 0, size = key.size(); i < size; i++) {
String col = key.get(i);
int idx = getColumnIndex(col);
Value v = current[idx];
if (v == null || v == ValueNull.INSTANCE) {
throw DbException.get(ErrorCode.NO_DATA_AVAILABLE);
}
v.set(prep, start + i);
}
}
private void appendTableName(StringBuilder builder) {
if (schemaName != null && schemaName.length() > 0) {
StringUtils.quoteIdentifier(builder, schemaName).append('.');
}
StringUtils.quoteIdentifier(builder, tableName);
}
public Value[] readRow(Value[] row) throws SQLException {
StringBuilder builder = new StringBuilder("SELECT ");
appendColumnList(builder, false);
builder.append(" FROM ");
appendTableName(builder);
appendKeyCondition(builder);
PreparedStatement prep = conn.prepareStatement(builder.toString());
setKey(prep, 1, row);
ResultSet rs = prep.executeQuery();
if (!rs.next()) {
throw DbException.get(ErrorCode.NO_DATA_AVAILABLE);
}
Value[] newRow = new Value[columnCount];
for (int i = 0; i < columnCount; i++) {
int type = result.getColumnType(i).getValueType();
newRow[i] = DataType.readValue(conn.getSession(), rs, i + 1, type);
}
return newRow;
}
public void deleteRow(Value[] current) throws SQLException {
StringBuilder builder = new StringBuilder("DELETE FROM ");
appendTableName(builder);
appendKeyCondition(builder);
PreparedStatement prep = conn.prepareStatement(builder.toString());
setKey(prep, 1, current);
int count = prep.executeUpdate();
if (count != 1) {
throw DbException.get(ErrorCode.NO_DATA_AVAILABLE);
}
}
public void updateRow(Value[] current, Value[] updateRow) throws SQLException {
StringBuilder builder = new StringBuilder("UPDATE ");
appendTableName(builder);
builder.append(" SET ");
appendColumnList(builder, true);
appendKeyCondition(builder);
PreparedStatement prep = conn.prepareStatement(builder.toString());
int j = 1;
for (int i = 0; i < columnCount; i++) {
Value v = updateRow[i];
if (v == null) {
v = current[i];
}
v.set(prep, j++);
}
setKey(prep, j, current);
int count = prep.executeUpdate();
if (count != 1) {
throw DbException.get(ErrorCode.NO_DATA_AVAILABLE);
}
}
public void insertRow(Value[] row) throws SQLException {
StringBuilder builder = new StringBuilder("INSERT INTO ");
appendTableName(builder);
builder.append('(');
appendColumnList(builder, false);
builder.append(")VALUES(");
for (int i = 0; i < columnCount; i++) {
if (i > 0) {
builder.append(',');
}
Value v = row[i];
if (v == null) {
builder.append("DEFAULT");
} else {
builder.append('?');
}
}
builder.append(')');
PreparedStatement prep = conn.prepareStatement(builder.toString());
for (int i = 0, j = 0; i < columnCount; i++) {
Value v = row[i];
if (v != null) {
v.set(prep, j++ + 1);
}
}
int count = prep.executeUpdate();
if (count != 1) {
throw DbException.get(ErrorCode.NO_DATA_AVAILABLE);
}
}
}