/*
* Copyright 2008-2012 Amazon Technologies, Inc.
*
* 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://aws.amazon.com/apache2.0
*
* This file 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 com.amazonaws.eclipse.datatools.enablement.simpledb.internal.driver;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.datatools.modelbase.sql.query.PredicateBasic;
import org.eclipse.datatools.modelbase.sql.query.QueryDeleteStatement;
import org.eclipse.datatools.modelbase.sql.query.QueryInsertStatement;
import org.eclipse.datatools.modelbase.sql.query.QuerySearchCondition;
import org.eclipse.datatools.modelbase.sql.query.QuerySelectStatement;
import org.eclipse.datatools.modelbase.sql.query.QueryUpdateStatement;
import org.eclipse.datatools.modelbase.sql.query.UpdateAssignmentExpression;
import org.eclipse.datatools.modelbase.sql.query.ValueExpressionColumn;
import org.eclipse.datatools.modelbase.sql.query.util.SQLQuerySourceFormat;
import org.eclipse.datatools.sqltools.parsers.sql.query.SQLQueryParseResult;
import org.eclipse.datatools.sqltools.parsers.sql.query.SQLQueryParserFactory;
import org.eclipse.datatools.sqltools.parsers.sql.query.SQLQueryParserManager;
import org.eclipse.emf.common.util.EList;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.eclipse.datatools.enablement.simpledb.driver.JdbcConnection;
import com.amazonaws.eclipse.datatools.enablement.simpledb.driver.SimpleDBItemName;
import com.amazonaws.services.simpledb.model.Attribute;
import com.amazonaws.services.simpledb.model.BatchPutAttributesRequest;
import com.amazonaws.services.simpledb.model.CreateDomainRequest;
import com.amazonaws.services.simpledb.model.DeleteAttributesRequest;
import com.amazonaws.services.simpledb.model.DeleteDomainRequest;
import com.amazonaws.services.simpledb.model.Item;
import com.amazonaws.services.simpledb.model.PutAttributesRequest;
import com.amazonaws.services.simpledb.model.ReplaceableAttribute;
import com.amazonaws.services.simpledb.model.SelectRequest;
import com.amazonaws.services.simpledb.model.SelectResult;
/**
* JDBC Statement implementation for the Amazon SimpleDB. Converts queries into Amazon API calls.
*/
public class JdbcStatement implements Statement {
private static final Pattern PATTERN_LIMIT = Pattern.compile("\\s+limit\\s+\\d+"); //$NON-NLS-1$
private static final Pattern PATTERN_SELECT_STAR = Pattern.compile("^\\s*select\\s+\\*\\s+.*"); //$NON-NLS-1$
private static final Pattern PATTERN_SELECT_COUNT = Pattern.compile("^\\s*select\\s+count\\s*\\(\\s*\\*\\s*\\).*"); //$NON-NLS-1$
private static final Pattern PATTERN_WHITESPACE_END = Pattern.compile("\\s+$"); //$NON-NLS-1$
private static final Pattern PATTERN_WHITESPACE_BEGIN = Pattern.compile("^\\s+"); //$NON-NLS-1$
private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s+"); //$NON-NLS-1$
private static final Pattern PATTERN_FROM_CLAUSE = Pattern.compile("from\\s+[\\S&&[^,]]+"); //$NON-NLS-1$
/** Amazon SDB prefers all the identifiers in the select clause to be quoted with the given character. */
public static final char DELIMITED_IDENTIFIER_QUOTE = '`';
private static final int MAX_ITEMS_PER_QUERY_RESPONSE = 251;
JdbcConnection conn;
String sql = null;
JdbcResultSet resultSet;
int maxRows = 0; // max. number of rows which can be returned by this statement
RawData data;
/** PreparedStatement parameters or filled from usual statement upon parsing */
List<Object> params = null;
boolean cancel = false;
public JdbcStatement(final JdbcConnection conn) {
this.conn = conn;
this.resultSet = new JdbcResultSet(this);
}
public void close() throws SQLException {
this.resultSet.close();
this.data = new RawData();
}
protected final void checkOpen() throws SQLException {
if (this.resultSet == null || !this.resultSet.isOpen()) {
throw new SQLException("statement is closed"); //$NON-NLS-1$
}
}
public boolean execute(final String sql) throws SQLException {
close();
this.sql = sql;
if (this.sql == null) {
throw new SQLException("sql is null");
}
trimSQL();
String lowcaseSql = this.sql.toLowerCase();
if (!lowcaseSql.startsWith("select ")) {
executeUpdate(this.sql);
return false; // no ResultSet
}
if (this.sql.length() == 0) {
throw new SQLException("empty sql");
}
int maxRows = getMaxRows();
// System.out.println("GET MAXROWS: " + maxRows);
int limit = -1;
boolean countQuery = PATTERN_SELECT_COUNT.matcher(lowcaseSql).matches();
if (!countQuery) {
// NB! Assuming here that limit word is never a part of an identifier, e.g. attribute
Matcher m = PATTERN_LIMIT.matcher(lowcaseSql);
if (m.find()) {
int limitPos = m.start();
int endPos = m.end();
Pattern p = Pattern.compile("\\d+"); //$NON-NLS-1$
String limitExpression = lowcaseSql.substring(limitPos, endPos);
m = p.matcher(limitExpression);
if (m.find()) {
limit = Integer.parseInt(limitExpression.substring(m.start(), m.end()).trim());
if (limit >= 0 && (limit < maxRows || maxRows <= 0)) {
maxRows = limit;
}
}
}
if (limit < 0 && maxRows > 0) {
this.sql += " limit " + maxRows;
}
} else {
maxRows = 1;
}
// System.out.println("EFFECTIVE MAXROWS: " + maxRows);
int row = 0;
ExecutionResult result = new ExecutionResult(null, -1);
do {
result = execute(this.sql, row, maxRows, MAX_ITEMS_PER_QUERY_RESPONSE, result.nextToken);
if (result == null) {
break;
}
this.resultSet.open();
row += result.items;
if (maxRows > 0 && row >= maxRows) { // reached the limit
// if (result.nextToken != null) {
// this.resultSet.warning = new SQLWarning("This request has exceeded the limits. The NextToken value is: "
// + result.nextToken);
// }
break;
}
// System.out.println("NEXT TOKEN: " + result.nextToken);
} while (result.nextToken != null && result.nextToken.length() > 0);
return true; //this.data.getRowNum() > 0;
}
private void extractColumnNamesFromSelect() throws SQLException {
String sqlToParse = this.sql;
String lowcaseSql = sqlToParse.toLowerCase();
/*
* If we know that the query doesn't explicitly specify column names
* (ex: "select *" or "select count(...)") then we can optimize and bail
* out rather than trying to parse the query.
*/
if (PATTERN_SELECT_STAR.matcher(lowcaseSql).find() || PATTERN_SELECT_COUNT.matcher(lowcaseSql).find()) {
return;
}
// strip 'limit', generic parser doesn't like it
// NB! Assuming here that limit word is never a part of an identifier, e.g. attribute
Matcher m = PATTERN_LIMIT.matcher(lowcaseSql);
if (m.find()) {
int limitPos = m.start();
int endPos = m.end();
sqlToParse = this.sql.substring(0, limitPos);
if (this.sql.length() - endPos > 0) {
sqlToParse += this.sql.substring(endPos, this.sql.length());
}
}
// Convert double quotes to single quotes since the generic parser chokes on double
// quotes but they're perfectly valid for SimpleDB
sqlToParse = sqlToParse.replace('"', '\'');
try {
SQLQueryParserManager manager = createParserManager();
SQLQueryParseResult res = manager.parseQuery(sqlToParse);
if (res.getSQLStatement() instanceof QuerySelectStatement) {
QuerySelectStatement stmt = (QuerySelectStatement) res.getSQLStatement();
EList<?> columns = stmt.getQueryExpr().getQuery().getColumnList();
if (columns != null) {
String[] columnNames = new String[columns.size()];
for (int i = 0; i < columns.size(); i++) {
Object column = columns.get(i);
if (!(column instanceof ValueExpressionColumn)) {
continue;
}
String name = ((ValueExpressionColumn) column).getName();
if ("*".equals(name)) { // just in case
continue;
}
columnNames[i] = name;
}
// validate names
for (String columnName : columnNames) {
if (columnName == null) {
return; // failed to get all the column names, putting it to data map will break everything
}
}
for (String columnName : columnNames) {
this.data.addAttribute(columnName);
}
}
}
} catch (Exception e) {
// ignore atm - most probably this is a custom query from scrapbook where column order is not important
// throw wrapIntoSqlException(e);
}
}
private SQLQueryParserManager createParserManager() {
SQLQueryParserManager manager = SQLQueryParserManager.getInstance();
SQLQuerySourceFormat format = SQLQuerySourceFormat.copyDefaultFormat();
format.setDelimitedIdentifierQuote(DELIMITED_IDENTIFIER_QUOTE);
format.setPreserveSourceFormat(true);
manager.configParser(format, null /*Arrays.asList(new PostParseProcessor[] { new DataTypeResolver(false) })*/);
manager.setParserFactory(new SQLQueryParserFactory(manager.getSourceFormat()) {
@Override
public ValueExpressionColumn createColumnExpression(final String aColumnName) {
//if (statementTypeOnly) {return null;}
ValueExpressionColumn colExpr = super.createColumnExpression(aColumnName);
colExpr.setName(convertSQLIdentifierToCatalogFormat(aColumnName, getDelimitedIdentifierQuote()));
return colExpr;
}
});
return manager;
}
SQLException wrapIntoSqlException(final Exception e) {
SQLException ex;
if (e instanceof SQLException) {
ex = (SQLException) e;
} else {
ex = new SQLException(e.getLocalizedMessage());
ex.initCause(e);
}
return ex;
}
static String convertSQLIdentifierToCatalogFormat(final String sqlIdentifier, final char idDelimiterQuote) {
String catalogIdentifier = sqlIdentifier;
if (sqlIdentifier != null) {
String delimiter = String.valueOf(idDelimiterQuote);
boolean isDelimited = sqlIdentifier.startsWith(delimiter) && sqlIdentifier.endsWith(delimiter);
boolean containsQuotedDelimiters = sqlIdentifier.indexOf(delimiter + delimiter) > -1;
if (isDelimited) {
catalogIdentifier = sqlIdentifier.substring(1, sqlIdentifier.length() - 1);
if (containsQuotedDelimiters) {
catalogIdentifier = catalogIdentifier.replaceAll(delimiter + delimiter, delimiter);
}
} else {
catalogIdentifier = sqlIdentifier;
}
}
return catalogIdentifier;
}
private void trimSQL() {
this.sql = this.sql.trim();
Matcher m = PATTERN_WHITESPACE_BEGIN.matcher(this.sql);
if (m.find()) {
this.sql = this.sql.substring(m.end());
}
m = PATTERN_WHITESPACE_END.matcher(this.sql);
if (m.find()) {
this.sql = this.sql.substring(0, m.start());
}
if (this.sql.endsWith(";")) {
this.sql = this.sql.substring(0, this.sql.length() - 1);
}
}
public String getDomainName() {
Matcher m = PATTERN_FROM_CLAUSE.matcher(this.sql);
if (m.find()) {
String fromExpression = this.sql.substring(m.start(), m.end());
m = PATTERN_WHITESPACE.matcher(fromExpression);
if (m.find()) {
String domainName = convertSQLIdentifierToCatalogFormat(fromExpression.substring(m.end()),
DELIMITED_IDENTIFIER_QUOTE);
return domainName;
}
}
return null;
}
/*
* Collect as many items as SimpleDB allows and return the NextToken, which
* is used to continue the query in a subsequent call to SimpleDB.
*/
ExecutionResult execute(final String queryText, final int startingRow, final int maxRows, final int requestSize,
final String nextToken) throws SQLException {
if (this.data.getPersistedColumnNum() == 0) {
extractColumnNamesFromSelect();
}
// System.out.println("FINAL QUERY: " + queryText);
SelectRequest request = new SelectRequest();
request.setSelectExpression(queryText);
if (nextToken != null) {
request.setNextToken(nextToken);
}
SelectResult queryResult;
try {
queryResult = this.conn.getClient().select(request);
} catch (Exception e) {
throw wrapIntoSqlException(e);
}
boolean shouldAddItemName = this.data.getPersistedColumnNum() == 0
|| this.data.getAttributes().contains(SimpleDBItemName.ITEM_HEADER);
int row = startingRow;
// List<GetAttributesResponse> responses = new ArrayList<GetAttributesResponse>();
for (Item item : queryResult.getItems()) {
if (this.cancel) {
break;
}
if (shouldAddItemName) {
this.data.addItemName(item.getName(), row);
}
List<Attribute> attributes = item.getAttributes();
for (Attribute attr : attributes) {
this.data.add(attr.getName(), attr.getValue(), row);
}
if (attributes.isEmpty()) {
this.data.ensureRows(row);
}
// GetAttributesRequest aRequest = new GetAttributesRequest();
// aRequest.setItemName(item.getName());
// aRequest.setDomainName(getDomainName() /*request.getDomainName()*/);
// try {
// responses.add(this.conn.service.getAttributes(aRequest));
// } catch (Exception e) {
// throw wrapIntoSqlException(e);
// }
row++;
}
// row = startingRow;
// for (GetAttributesResponse aResponse : responses) {
// if (this.cancel) {
// break;
// }
//
// try {
// GetAttributesResult aResult = aResponse.getGetAttributesResult();
// for (Attribute attribute : aResult.getAttribute()) {
// this.data.add(attribute.getName(), attribute.getValue(), row);
// }
// } catch (Exception e) {
// throw wrapIntoSqlException(e);
// }
//
// row++;
// }
String newNextToken = queryResult.getNextToken();
return new ExecutionResult(newNextToken, row - startingRow);
}
public ResultSet executeQuery(final String sql) throws SQLException {
if (execute(sql)) {
return getResultSet();
} else {
throw new SQLException("query didn't return a ResultSet");
}
}
@SuppressWarnings("unchecked")
public int executeUpdate(final String inSql) throws SQLException {
this.sql = inSql;
if (this.sql == null) {
throw new SQLException("sql is null");
}
trimSQL();
if (this.sql.length() == 0) {
throw new SQLException("empty sql");
}
String lowcaseSql = this.sql.toLowerCase();
Object req = null;
// TODO use patterns
if (lowcaseSql.startsWith("create domain") || lowcaseSql.startsWith("create table")) { //$NON-NLS-1$
int pos = this.sql.lastIndexOf(" ");
String domain = convertSQLIdentifierToCatalogFormat(this.sql.substring(pos + 1).trim(),
DELIMITED_IDENTIFIER_QUOTE);
req = new CreateDomainRequest().withDomainName(domain);
} else if (lowcaseSql.startsWith("delete domain") || lowcaseSql.startsWith("delete table") //$NON-NLS-1$
|| lowcaseSql.startsWith("drop table")) {
int pos = this.sql.lastIndexOf(" ");
String domain = convertSQLIdentifierToCatalogFormat(this.sql.substring(pos + 1).trim(),
DELIMITED_IDENTIFIER_QUOTE);
List<String> pending = this.conn.getPendingColumns(domain);
if (pending != null) {
pending = new ArrayList<String>(pending);
for (String attr : pending) {
this.conn.removePendingColumn(domain, attr);
}
}
req = new DeleteDomainRequest().withDomainName(domain);
} else if (lowcaseSql.startsWith("delete from")) {
req = prepareDeleteRowRequest();
} else if (lowcaseSql.startsWith("alter table ")) {
req = prepareDropAttributeRequest();
} else if (lowcaseSql.startsWith("insert ")) {
req = prepareInsertRequest();
} else if (lowcaseSql.startsWith("update ")) {
req = prepareUpdateRequest();
} else if (lowcaseSql.startsWith("create testdomain ")) {
req = new ArrayList<Object>();
String domain = convertSQLIdentifierToCatalogFormat(this.sql.substring(this.sql.lastIndexOf(" ") + 1).trim(), //$NON-NLS-1$
DELIMITED_IDENTIFIER_QUOTE);
((List<Object>) req).add(new CreateDomainRequest().withDomainName(domain));
ReplaceableAttribute attr = new ReplaceableAttribute().withName("attr1").withValue("val1").withReplace(Boolean.TRUE);
for (int i = 0; i < 570; i++) {
((List<Object>) req).add(new PutAttributesRequest().withDomainName(domain).withItemName("item" + i).withAttributes(attr));
}
}
if (req != null) {
int result = executeSDBRequest(req);
if (this.params != null) {
for (Object obj : this.params) {
if (obj instanceof SimpleDBItemName) {
((SimpleDBItemName) obj).setPersisted(true);
}
}
}
return result;
}
throw new SQLException("unsupported update: " + this.sql);
}
@SuppressWarnings("unchecked")
int executeSDBRequest(final Object req) throws SQLException {
try {
if (req == null) {
// do nothing
return 0;
} else if (req instanceof Collection) {
int sum = 0;
for (Object singleReq : (Collection<Object>) req) {
sum += executeSDBRequest(singleReq);
}
return sum;
} else if (req instanceof CreateDomainRequest) {
this.conn.getClient().createDomain((CreateDomainRequest) req);
return 0;
} else if (req instanceof DeleteDomainRequest) {
this.conn.getClient().deleteDomain((DeleteDomainRequest) req);
return 0;
} else if (req instanceof PutAttributesRequest) {
this.conn.getClient().putAttributes((PutAttributesRequest) req);
return 1;
} else if (req instanceof BatchPutAttributesRequest) {
this.conn.getClient().batchPutAttributes((BatchPutAttributesRequest) req);
return ((BatchPutAttributesRequest) req).getItems().size();
} else if (req instanceof DeleteAttributesRequest) {
this.conn.getClient().deleteAttributes((DeleteAttributesRequest) req);
List<Attribute> attribute = ((DeleteAttributesRequest) req).getAttributes();
return attribute == null || attribute.isEmpty() ? 1 : 0;
} else {
throw new SQLException("unsupported query");
}
} catch (AmazonServiceException e) {
throw wrapIntoSqlException(e);
}
}
List<Object> prepareUpdateRequest() throws SQLException {
if (this.sql.toLowerCase().indexOf(" set ") < 0) { // workaround for DTP bug - sends update statements without set of any columns
return new ArrayList<Object>();
}
try {
SQLQueryParserManager manager = createParserManager();
SQLQueryParseResult res = manager.parseQuery(this.sql);
QueryUpdateStatement qs = (QueryUpdateStatement) res.getQueryStatement();
QuerySearchCondition whereClause = qs.getWhereClause();
if (!(whereClause instanceof PredicateBasic)) {
throw new SQLException("current SDB JDBC version supports only simple expression `" //$NON-NLS-1$
+ SimpleDBItemName.ITEM_HEADER + "`='<something>' in WHERE clause");
}
if (this.params == null) {
// TODO some time later extract the parameters from the parsed simple statement
}
if (this.params == null) {
throw new SQLException("current SDB JDBC version supports only parameterized queries");
}
String domain = qs.getTargetTable().getName();
String item = unwrapItemValue(this.params.get(this.params.size() - 1));
EList<?> assignmentClause = qs.getAssignmentClause();
if (this.params != null && this.params.size() - 1 != assignmentClause.size()) { // last param is an Item name, thus -1
throw new SQLException("number of set params doesn't match");
}
int tally = 0;
List<ReplaceableAttribute> attrs = new ArrayList<ReplaceableAttribute>();
for (Object assign : assignmentClause) {
UpdateAssignmentExpression assignExp = (UpdateAssignmentExpression) assign;
EList<?> cols = assignExp.getTargetColumnList();
ValueExpressionColumn col = (ValueExpressionColumn) cols.get(0);
String colName = col.getName();
String colValue = (String) this.params.get(tally);
if (colValue != null) {
ReplaceableAttribute attr = new ReplaceableAttribute().withName(colName).withValue(colValue).withReplace(Boolean.TRUE);
attrs.add(attr);
}
++tally;
}
tally = 0;
List<Attribute> deleteAttrs = new ArrayList<Attribute>();
for (Object assign : assignmentClause) {
UpdateAssignmentExpression assignExp = (UpdateAssignmentExpression) assign;
EList<?> cols = assignExp.getTargetColumnList();
ValueExpressionColumn col = (ValueExpressionColumn) cols.get(0);
String colName = col.getName();
if (SimpleDBItemName.ITEM_HEADER.equals(colName)) { // TODO how we could use ColumnType here instead of hardcoded ColumnName?
throw new SQLException("item name cannot be edited once created");
}
String colValue = (String) this.params.get(tally);
if (colValue == null) {
Attribute attr = new Attribute().withName(colName).withValue(colValue);
deleteAttrs.add(attr);
}
++tally;
}
List<Object> reqs = new ArrayList<Object>();
if (!attrs.isEmpty()) {
PutAttributesRequest req = new PutAttributesRequest().withDomainName(domain).withItemName(item);
req.setAttributes(attrs);
reqs.add(req);
}
if (!deleteAttrs.isEmpty()) {
DeleteAttributesRequest dreq = new DeleteAttributesRequest().withDomainName(domain).withItemName(item);
dreq.setAttributes(deleteAttrs);
reqs.add(dreq);
}
return reqs;
} catch (Exception e) {
throw wrapIntoSqlException(e);
}
}
PutAttributesRequest prepareInsertRequest() throws SQLException {
try {
SQLQueryParserManager manager = createParserManager();
SQLQueryParseResult res = manager.parseQuery(this.sql);
QueryInsertStatement qs = (QueryInsertStatement) res.getQueryStatement();
String domain = qs.getTargetTable().getName();
if (this.params == null) {
// TODO some time later extract the parameters from the parsed simple statement
}
if (this.params == null) {
throw new SQLException("current SDB JDBC version supports only parameterized queries");
}
EList<?> targetColumns = qs.getTargetColumnList();
// ValuesRow values = (ValuesRow)qs.getSourceValuesRowList().get(0);
if (this.params != null && this.params.size() != targetColumns.size()) {
throw new SQLException("number of set params doesn't match");
}
int tally = 0;
String item = null;
List<ReplaceableAttribute> attrs = new ArrayList<ReplaceableAttribute>();
for (Object assign : targetColumns) {
ValueExpressionColumn col = (ValueExpressionColumn) assign;
String colName = col.getName();
if (tally == 0 && !SimpleDBItemName.ITEM_HEADER.equals(colName)) {
throw new SQLException("first parameter must be " + DELIMITED_IDENTIFIER_QUOTE + SimpleDBItemName.ITEM_HEADER //$NON-NLS-1$
+ DELIMITED_IDENTIFIER_QUOTE);
}
Object colValue = this.params.get(tally);
if (colValue != null) {
if (SimpleDBItemName.ITEM_HEADER.equals(colName)) { // TODO how we could use ColumnType here instead of hardcoded ColumnName?
item = unwrapItemValue(colValue);
} else {
if (colValue instanceof String[]) {
for (String val : (String[]) colValue) {
ReplaceableAttribute attr = new ReplaceableAttribute().withName(colName).withValue(val).withReplace(Boolean.TRUE);
attrs.add(attr);
}
} else {
ReplaceableAttribute attr = new ReplaceableAttribute().withName(colName).withValue((String) colValue).withReplace(Boolean.TRUE);
attrs.add(attr);
}
}
}
++tally;
}
PutAttributesRequest req = new PutAttributesRequest().withDomainName(domain).withItemName(item);
req.setAttributes(attrs);
return req;
} catch (Exception e) {
throw wrapIntoSqlException(e);
}
}
Object prepareDeleteRowRequest() throws SQLException {
try {
SQLQueryParserManager manager = createParserManager();
SQLQueryParseResult res = manager.parseQuery(this.sql);
QueryDeleteStatement qs = (QueryDeleteStatement) res.getQueryStatement();
String domain = qs.getTargetTable().getName();
if (this.params == null) {
// TODO some time later extract the parameters from the parsed simple statement
}
if (this.params == null) {
throw new SQLException("current SDB JDBC version supports only parameterized queries");
}
Object firstParam = this.params.get(0);
String item = unwrapItemValue(firstParam);
DeleteAttributesRequest req = new DeleteAttributesRequest().withDomainName(domain).withItemName(item);
return req;
} catch (Exception e) {
throw wrapIntoSqlException(e);
}
}
Object prepareDropAttributeRequest() throws SQLException {
try {
// SQLQueryParserManager manager = SQLQueryParserManager.getInstance();
// SQLQuerySourceFormat format = SQLQuerySourceFormat.copyDefaultFormat();
// format.setDelimitedIdentifierQuote('`');
// manager.configParser(format, null);
//
// SQLQueryParseResult res = manager.parseQuery(this.sql);
// QueryDeleteStatement qs = (QueryDeleteStatement) res.getQueryStatement();
//
// String domain = qs.getTargetTable().getName();
// TODO use patterns
if (!this.sql.startsWith("alter table") || this.sql.indexOf(" drop ") < 0) { //$NON-NLS-1$
throw new SQLException("unsupported alter table statement");
}
int pos = this.sql.indexOf(" ", "alter table ".length() + 1); //$NON-NLS-1$ //$NON-NLS-2$
String domain = convertSQLIdentifierToCatalogFormat(this.sql.substring("alter table ".length(), pos).trim(), //$NON-NLS-1$
DELIMITED_IDENTIFIER_QUOTE);
pos = this.sql.indexOf("drop "); //$NON-NLS-1$
String attrName = convertSQLIdentifierToCatalogFormat(this.sql.substring(pos + "drop ".length()).trim(),
DELIMITED_IDENTIFIER_QUOTE);
this.conn.removePendingColumn(domain, attrName);
Attribute attr = new Attribute().withName(attrName).withValue(null);
List<Attribute> attrs = new ArrayList<Attribute>();
attrs.add(attr);
this.sql = "select itemName from " + DELIMITED_IDENTIFIER_QUOTE + domain + DELIMITED_IDENTIFIER_QUOTE //$NON-NLS-1$
+ " where " + DELIMITED_IDENTIFIER_QUOTE + attrName + DELIMITED_IDENTIFIER_QUOTE + " is not null";
ResultSet rs = executeQuery(this.sql);
List<DeleteAttributesRequest> reqs = new ArrayList<DeleteAttributesRequest>();
while (rs.next()) {
String item = rs.getString(1);
DeleteAttributesRequest dar = new DeleteAttributesRequest().withDomainName(domain).withItemName(item);
dar.setAttributes(attrs);
reqs.add(dar);
}
return reqs;
} catch (Exception e) {
throw wrapIntoSqlException(e);
}
}
@SuppressWarnings("unchecked")
private String unwrapItemValue(final Object param) {
String item;
if (param instanceof SimpleDBItemName) {
item = ((SimpleDBItemName) param).getItemName();
} else if (param instanceof Collection) {
item = ((Collection<String>) param).iterator().next();
} else if (param instanceof String[]) {
item = ((String[]) param)[0];
} else {
item = (String) param;
}
return item;
}
public ResultSet getGeneratedKeys() throws SQLException {
throw new SQLException("unsupported by SDB"); //$NON-NLS-1$
}
public ResultSet getResultSet() throws SQLException {
return this.resultSet;
}
public int getUpdateCount() throws SQLException {
return -1; // we return ResultSet
}
public void setCursorName(final String name) throws SQLException {
}
public SQLWarning getWarnings() throws SQLException {
return null;
}
public void clearWarnings() throws SQLException {
}
public Connection getConnection() throws SQLException {
return this.conn;
}
public void cancel() throws SQLException {
// this.resultSet.checkOpen();
this.cancel = true;
}
public int getMaxRows() throws SQLException {
return this.maxRows;
}
public void setMaxRows(final int maxRows) throws SQLException {
// System.out.println("SETTING MAXROWS: " + maxRows);
if (maxRows < 0) {
throw new SQLException("max row count must be >= 0"); //$NON-NLS-1$
}
this.maxRows = maxRows;
}
public int getMaxFieldSize() throws SQLException {
return 0;
}
public void setMaxFieldSize(final int max) throws SQLException {
if (max < 0) {
throw new SQLException("max field size " + max + " cannot be negative"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
public int getFetchSize() throws SQLException {
return this.resultSet.getFetchSize();
}
public void setFetchSize(final int r) throws SQLException {
this.resultSet.setFetchSize(r);
}
public int getFetchDirection() throws SQLException {
return this.resultSet.getFetchDirection();
}
public void setFetchDirection(final int d) throws SQLException {
this.resultSet.setFetchDirection(d);
}
public boolean getMoreResults() throws SQLException {
return getMoreResults(0);
}
public boolean getMoreResults(final int c) throws SQLException {
// checkOpen();
close();
return false;
}
public int getResultSetConcurrency() throws SQLException {
return getResultSet().getConcurrency();
}
public int getResultSetHoldability() throws SQLException {
return ResultSet.CLOSE_CURSORS_AT_COMMIT;
}
public int getResultSetType() throws SQLException {
return getResultSet().getType();
}
public void setEscapeProcessing(final boolean enable) {
}
// NOT SUPPORTED ////////////////////////////////////////////////////////////
public int getQueryTimeout() throws SQLException {
throw new SQLException("unsupported by SDB yet"); //$NON-NLS-1$
// return this.timeout;
}
public void setQueryTimeout(final int seconds) throws SQLException {
throw new SQLException("unsupported by SDB yet"); //$NON-NLS-1$
// if (seconds < 0) {
// throw new SQLException("query timeout must be >= 0");
// }
// this.timeout = seconds;
}
public void addBatch(final String sql) throws SQLException {
throw new SQLException("unsupported by SDB yet"); //$NON-NLS-1$
}
public void clearBatch() throws SQLException {
throw new SQLException("unsupported by SDB yet"); //$NON-NLS-1$
}
public int[] executeBatch() throws SQLException {
throw new SQLException("unsupported by SDB yet"); //$NON-NLS-1$
}
public boolean execute(final String sql, final int[] colinds) throws SQLException {
throw new SQLException("unsupported by SDB"); //$NON-NLS-1$
}
public boolean execute(final String sql, final String[] colnames) throws SQLException {
throw new SQLException("unsupported by SDB"); //$NON-NLS-1$
}
public int executeUpdate(final String sql, final int autoKeys) throws SQLException {
throw new SQLException("unsupported by SDB"); //$NON-NLS-1$
}
public int executeUpdate(final String sql, final int[] colinds) throws SQLException {
throw new SQLException("unsupported by SDB"); //$NON-NLS-1$
}
public int executeUpdate(final String sql, final String[] cols) throws SQLException {
throw new SQLException("unsupported by SDB"); //$NON-NLS-1$
}
public boolean execute(final String sql, final int autokeys) throws SQLException {
throw new SQLException("unsupported by SDB"); //$NON-NLS-1$
}
// INNER CLASSES ////////////////////////////////////////////////////////////
/**
* Collects item information in the format given by SimpleDB and aggregates it into a tabular format.
*/
class RawData {
// A list of rows, where each row is a map of columnIndex to values
private List<Map<Integer, List<String>>> rows;
// A list of column names (attributes)
private List<String> columns;
private List<Integer> itemNameColumn;
/**
* Constructor
*/
public RawData() {
this.rows = new ArrayList<Map<Integer, List<String>>>();
this.columns = new ArrayList<String>();
this.itemNameColumn = new ArrayList<Integer>();
}
/**
* Returns the attribute values at the given row and column.
*
* @param row
* Corresponds to the n'th item in the result
* @param column
* Corresponds to the n'th overall attribute. The attribute may or may not apply to this item.
* @return A list of values or null if the attribute doesn't apply to this item.
*/
public List<String> get(final int row, final int column) {
if (this.rows.size() > row) {
return this.rows.get(row).get(column);
} else {
return null;
}
}
/**
* A convenience method to return a delimited string of the attribute value at the specified row and column.
*
* @param row
* @param column
* @param delimiter
* @return found value; multi-value separated by given delimiter
* @see #get(int,int)
*/
public String getString(final int row, final int column, final String delimiter) {
return join(get(row, column), delimiter);
}
/**
* Add the given value as an item name value at the given row. A new column is added to the table if there was no
* such yet.
*
* @param value
* @param rowNum
*/
public void addItemName(final String value, final int rowNum) {
ensureItemNameColumn(rowNum);
int column = add(SimpleDBItemName.ITEM_HEADER, value, rowNum);
this.itemNameColumn.set(rowNum, column);
}
public boolean isItemNameColumn(final int row, final int column) {
if (row < 0 || row >= this.rows.size()) {
List<String> attrs = getAttributes();
return !attrs.isEmpty() && SimpleDBItemName.ITEM_HEADER.equals(attrs.get(column));
}
ensureItemNameColumn(row);
Integer itemName = this.itemNameColumn.get(row);
return itemName != null && itemName.intValue() == column;
}
public int getItemNameColumn(final int row) {
if (row < 0 || row >= this.rows.size()) {
List<String> attrs = getAttributes();
return attrs.indexOf(SimpleDBItemName.ITEM_HEADER);
}
ensureItemNameColumn(row);
Integer itemName = this.itemNameColumn.get(row);
return itemName != null ? itemName.intValue() : -1;
}
private void ensureItemNameColumn(final int row) {
for (int i = this.itemNameColumn.size() - 1; i < row; i++) {
this.itemNameColumn.add(null);
}
}
public int addAttribute(final String attribute) {
int column = this.columns.indexOf(attribute);
if (column < 0) {
column = this.columns.size();
this.columns.add(attribute);
}
return column;
}
/**
* Add the given attribute/value pair at the given row. The attribute may already exist, in which case the value is
* added to the list of existing attributes. Otherwise, a new column is added to the table.
*
* @param attribute
* @param value
* @param rowNum
* @return index of the attribute
*/
public int add(final String attribute, final String value, final int rowNum) {
int column = this.columns.indexOf(attribute);
if (column < 0) {
column = this.columns.size();
this.columns.add(attribute);
JdbcStatement.this.conn.removePendingColumn(getDomainName(), attribute); // real data from SDB came, safe to remove the pending column
}
ensureRows(rowNum);
Map<Integer, List<String>> row = this.rows.get(rowNum);
List<String> values = row.get(column);
if (values == null) {
values = new ArrayList<String>();
row.put(column, values);
}
values.add(value);
return column;
}
public void ensureRows(final int rowNum) {
for (int i = this.rows.size() - 1; i < rowNum; i++) {
this.rows.add(new HashMap<Integer, List<String>>());
}
}
/**
* @return The number of rows/items in the query
*/
public int getRowNum() {
return this.rows.size();
}
/**
* @return The number of columns/attributes in the query
*/
public int getColumnNum() {
int size = this.columns.size();
List<String> pendings = JdbcStatement.this.conn.getPendingColumns(JdbcStatement.this.getDomainName());
if (pendings != null && !pendings.isEmpty() && this.columns.isEmpty()) {
++size; // +1 for ItemName - special case when there is just freshly added attributes and there is no content in the domain
}
if (pendings != null) {
pendings = new ArrayList<String>(pendings);
pendings.removeAll(this.columns);
size += pendings.size();
}
return size;
}
/**
* @return The number of columns/attributes existing in the SDB, i.e. no pending columns
*/
public int getPersistedColumnNum() {
return this.columns.size();
}
/**
* @return A list of attributes in the order as they exist in the table
*/
public List<String> getAttributes() {
ArrayList<String> attrs = new ArrayList<String>(this.columns);
List<String> pendings = JdbcStatement.this.conn.getPendingColumns(JdbcStatement.this.getDomainName());
if (pendings != null && !pendings.isEmpty() && this.columns.isEmpty()) {
attrs.add(SimpleDBItemName.ITEM_HEADER); // special case when there is just freshly added attributes and there is no content in the domain
}
if (pendings != null) {
pendings = new ArrayList<String>(pendings);
pendings.removeAll(this.columns);
attrs.addAll(pendings);
}
return attrs;
}
/*
* Private interface
*/
/*
* Join the items in a Collection of Strings with the given delimiter
*/
private String join(final Collection<String> s, final String delimiter) {
if (s == null) {
return ""; //$NON-NLS-1$
}
StringBuilder builder = new StringBuilder();
Iterator<String> iter = s.iterator();
while (iter.hasNext()) {
builder.append(iter.next());
if (iter.hasNext()) {
builder.append(delimiter);
}
}
return builder.toString();
}
// TODO reimplement one day to use map
/* @return index (starts from 0) of the attribute with the given name */
public int findAttribute(final String name) throws SQLException {
int c = -1;
for (int i = 0; i < this.columns.size(); i++) {
String cur = this.columns.get(i);
if (name.equalsIgnoreCase(cur)
|| (cur.toUpperCase().endsWith(name.toUpperCase()) && cur.charAt(cur.length() - name.length()) == '.')) {
if (c == -1) {
c = i;
} else {
throw new SQLException("ambiguous column: '" + name + "'"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
if (c == -1) {
List<String> pendings = JdbcStatement.this.conn.getPendingColumns(JdbcStatement.this.getDomainName());
if (pendings != null) {
pendings = new ArrayList<String>(pendings);
pendings.removeAll(this.columns);
for (int i = 0; i < pendings.size(); i++) {
String cur = pendings.get(i);
if (name.equalsIgnoreCase(cur)
|| (cur.toUpperCase().endsWith(name.toUpperCase()) && cur.charAt(cur.length() - name.length()) == '.')) {
if (c == -1) {
c = i + Math.max(this.columns.size(), 1); // 1 - special case when there is just freshly added attributes and there is no content in the domain
} else {
throw new SQLException("ambiguous column: '" + name + "'"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
}
}
if (c == -1) {
throw new SQLException("no such column: '" + name + "'"); //$NON-NLS-1$ //$NON-NLS-2$
} else {
return c;
}
}
}
/*
* Bundle the NextToken and number of items fetched in the last query.
*/
static class ExecutionResult {
public ExecutionResult(final String nextToken, final int items) {
this.nextToken = nextToken;
this.items = items;
}
public final String nextToken;
public final int items;
}
public boolean isClosed() throws SQLException {
// TODO Auto-generated method stub
return false;
}
public boolean isPoolable() throws SQLException {
// TODO Auto-generated method stub
return false;
}
public void setPoolable(final boolean poolable) throws SQLException {
// TODO Auto-generated method stub
}
public boolean isWrapperFor(final Class<?> iface) throws SQLException {
// TODO Auto-generated method stub
return false;
}
public <T> T unwrap(final Class<T> iface) throws SQLException {
// TODO Auto-generated method stub
return null;
}
public void closeOnCompletion() throws SQLException {
}
public boolean isCloseOnCompletion() throws SQLException {
return false;
}
}