/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.ignite.internal.processors.query.h2.ddl; import java.sql.PreparedStatement; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.cache.QueryIndex; import org.apache.ignite.cache.query.QueryCursor; import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.IgniteInternalFuture; import org.apache.ignite.internal.processors.cache.QueryCursorImpl; import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode; import org.apache.ignite.internal.processors.query.GridQueryProperty; import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor; import org.apache.ignite.internal.processors.query.IgniteSQLException; import org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing; import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlCreateIndex; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlDropIndex; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlQueryParser; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlStatement; import org.apache.ignite.internal.processors.query.schema.SchemaOperationException; import org.h2.command.Prepared; import org.h2.command.ddl.CreateIndex; import org.h2.command.ddl.DropIndex; import org.h2.jdbc.JdbcPreparedStatement; import static org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing.UPDATE_RESULT_META; /** * DDL statements processor.<p> * Contains higher level logic to handle operations as a whole and communicate with the client. */ public class DdlStatementsProcessor { /** Kernal context. */ GridKernalContext ctx; /** Indexing. */ IgniteH2Indexing idx; /** * Initialize message handlers and this' fields needed for further operation. * * @param ctx Kernal context. * @param idx Indexing. */ public void start(final GridKernalContext ctx, IgniteH2Indexing idx) { this.ctx = ctx; this.idx = idx; } /** * Execute DDL statement. * * @param sql SQL. * @param stmt H2 statement to parse and execute. */ @SuppressWarnings("unchecked") public QueryCursor<List<?>> runDdlStatement(String sql, PreparedStatement stmt) throws IgniteCheckedException { assert stmt instanceof JdbcPreparedStatement; IgniteInternalFuture fut; try { GridSqlStatement gridStmt = new GridSqlQueryParser(false).parse(GridSqlQueryParser.prepared(stmt)); if (gridStmt instanceof GridSqlCreateIndex) { GridSqlCreateIndex createIdx = (GridSqlCreateIndex)gridStmt; String spaceName = idx.space(createIdx.schemaName()); QueryIndex newIdx = new QueryIndex(); newIdx.setName(createIdx.index().getName()); newIdx.setIndexType(createIdx.index().getIndexType()); LinkedHashMap<String, Boolean> flds = new LinkedHashMap<>(); GridH2Table tbl = idx.dataTable(createIdx.schemaName(), createIdx.tableName()); if (tbl == null) throw new SchemaOperationException(SchemaOperationException.CODE_TABLE_NOT_FOUND, createIdx.tableName()); assert tbl.rowDescriptor() != null; // Let's replace H2's table and property names by those operated by GridQueryProcessor. GridQueryTypeDescriptor typeDesc = tbl.rowDescriptor().type(); for (Map.Entry<String, Boolean> e : createIdx.index().getFields().entrySet()) { GridQueryProperty prop = typeDesc.property(e.getKey()); if (prop == null) throw new SchemaOperationException(SchemaOperationException.CODE_COLUMN_NOT_FOUND, e.getKey()); flds.put(prop.name(), e.getValue()); } newIdx.setFields(flds); fut = ctx.query().dynamicIndexCreate(spaceName, typeDesc.tableName(), newIdx, createIdx.ifNotExists()); } else if (gridStmt instanceof GridSqlDropIndex) { GridSqlDropIndex dropIdx = (GridSqlDropIndex)gridStmt; String spaceName = idx.space(dropIdx.schemaName()); fut = ctx.query().dynamicIndexDrop(spaceName, dropIdx.name(), dropIdx.ifExists()); } else throw new IgniteSQLException("Unsupported DDL operation: " + sql, IgniteQueryErrorCode.UNSUPPORTED_OPERATION); fut.get(); QueryCursorImpl<List<?>> resCur = (QueryCursorImpl<List<?>>)new QueryCursorImpl(Collections.singletonList (Collections.singletonList(0L)), null, false); resCur.fieldsMeta(UPDATE_RESULT_META); return resCur; } catch (SchemaOperationException e) { throw convert(e); } catch (IgniteSQLException e) { throw e; } catch (Exception e) { throw new IgniteSQLException("Unexpected DLL operation failure: " + e.getMessage(), e); } } /** * @return {@link IgniteSQLException} with the message same as of {@code this}'s and */ private IgniteSQLException convert(SchemaOperationException e) { int sqlCode; switch (e.code()) { case SchemaOperationException.CODE_CACHE_NOT_FOUND: sqlCode = IgniteQueryErrorCode.CACHE_NOT_FOUND; break; case SchemaOperationException.CODE_TABLE_NOT_FOUND: sqlCode = IgniteQueryErrorCode.TABLE_NOT_FOUND; break; case SchemaOperationException.CODE_TABLE_EXISTS: sqlCode = IgniteQueryErrorCode.TABLE_ALREADY_EXISTS; break; case SchemaOperationException.CODE_COLUMN_NOT_FOUND: sqlCode = IgniteQueryErrorCode.COLUMN_NOT_FOUND; break; case SchemaOperationException.CODE_COLUMN_EXISTS: sqlCode = IgniteQueryErrorCode.COLUMN_ALREADY_EXISTS; break; case SchemaOperationException.CODE_INDEX_NOT_FOUND: sqlCode = IgniteQueryErrorCode.INDEX_NOT_FOUND; break; case SchemaOperationException.CODE_INDEX_EXISTS: sqlCode = IgniteQueryErrorCode.INDEX_ALREADY_EXISTS; break; default: sqlCode = IgniteQueryErrorCode.UNKNOWN; } return new IgniteSQLException(e.getMessage(), sqlCode); } /** * @param cmd Statement. * @return Whether {@code cmd} is a DDL statement we're able to handle. */ public static boolean isDdlStatement(Prepared cmd) { return cmd instanceof CreateIndex || cmd instanceof DropIndex; } }