Repository: ignite Updated Branches: refs/heads/master a8fcb0b1a -> 174528fa1
http://git-wip-us.apache.org/repos/asf/ignite/blob/4e5a82d6/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeUtils.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeUtils.java deleted file mode 100644 index 01f32d1..0000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeUtils.java +++ /dev/null @@ -1,440 +0,0 @@ -/* - * 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.odbc.escape; - -import org.apache.ignite.IgniteException; -import org.apache.ignite.internal.processors.odbc.OdbcUtils; -import java.util.LinkedList; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * ODBC escape sequence parse. - */ -public class OdbcEscapeUtils { - /** Odbc date regexp pattern: '2016-08-23' */ - private static final Pattern DATE_PATTERN = Pattern.compile("^'\\d{4}-\\d{2}-\\d{2}'$"); - - /** Odbc time regexp pattern: '14:33:44' */ - private static final Pattern TIME_PATTERN = Pattern.compile("^'\\d{2}:\\d{2}:\\d{2}'$"); - - /** Odbc timestamp regexp pattern: '2016-08-23 14:33:44.12345' */ - private static final Pattern TIMESTAMP_PATTERN = - Pattern.compile("^'\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}(\\.\\d+)?'$"); - - /** GUID regexp pattern: '12345678-9abc-def0-1234-123456789abc' */ - private static final Pattern GUID_PATTERN = - Pattern.compile("^'\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}'$"); - - /** CONVERT function data type parameter pattern: last parameter, after comma */ - private static final Pattern CONVERT_TYPE_PATTERN = - Pattern.compile(",\\s*(SQL_[\\w_]+)\\s*(?:\\(\\s*\\d+\\s*(?:,\\s*\\d+\\s*)?\\))?\\s*\\)\\s*$", - Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); - - /** - * Parse escape sequence. - * - * @param text Original text. - * @return Result. - */ - public static String parse(String text) { - if (text == null) - throw new IgniteException("Text cannot be null."); - - return parse0(text.trim(), 0, false).result(); - } - - /** - * Internal parse routine. - * - * @param text Text. - * @param startPos Start position. - * @param earlyExit When set to {@code true} we must return as soon as single expression is parsed. - * @return Parse result. - */ - private static OdbcEscapeParseResult parse0(String text, int startPos, boolean earlyExit) { - StringBuilder res = new StringBuilder(); - - int curPos = startPos; - - int plainPos = startPos; - int openPos = -1; - - boolean insideLiteral = false; - - LinkedList<OdbcEscapeParseResult> nested = null; - - while (curPos < text.length()) { - char curChar = text.charAt(curPos); - - if (curChar == '\'') - /* Escaped quote in odbc is two successive singe quotes. They'll flip flag twice without side-effect. */ - insideLiteral = !insideLiteral; - else if (!insideLiteral) { - if (curChar == '{') { - if (openPos == -1) { - // Top-level opening brace. Append previous portion and remember current position. - res.append(text, plainPos, curPos); - - openPos = curPos; - } - else { - // Nested opening brace -> perform recursion. - OdbcEscapeParseResult nestedRes = parse0(text, curPos, true); - - if (nested == null) - nested = new LinkedList<>(); - - nested.add(nestedRes); - - curPos += nestedRes.originalLength() - 1; - - plainPos = curPos + 1; - } - } - else if (curChar == '}') { - if (openPos == -1) - // Close without open -> exception. - throw new IgniteException("Malformed escape sequence " + - "(closing curly brace without opening curly brace): " + text); - else { - String parseRes; - - if (nested == null) - // Found sequence without nesting, process it. - parseRes = parseEscapeSequence(text, openPos, curPos + 1 - openPos); - else { - // Special case to process nesting. - String res0 = appendNested(text, openPos, curPos + 1, nested); - - nested = null; - - parseRes = parseEscapeSequence(res0, 0, res0.length()); - } - - if (earlyExit) - return new OdbcEscapeParseResult(startPos, curPos + 1 - startPos, parseRes); - else - res.append(parseRes); - - openPos = -1; - - plainPos = curPos + 1; - } - } - } - - curPos++; - } - - if (openPos != -1) - throw new IgniteException("Malformed escape sequence (closing curly brace missing): " + text); - - if (insideLiteral) - throw new IgniteException("Malformed literal expression (closing quote missing): " + text); - - if (curPos > plainPos) - res.append(text, plainPos, curPos); - - return new OdbcEscapeParseResult(startPos, curPos - startPos + 1, res.toString()); - } - - /** - * Parse escape sequence: {escape_sequence}. - * - * @param text Text. - * @param startPos Start position within text. - * @param len Length. - * @return Result. - */ - private static String parseEscapeSequence(String text, int startPos, int len) { - assert validSubstring(text, startPos, len); - - char firstChar = text.charAt(startPos); - - if (firstChar == '{') { - char lastChar = text.charAt(startPos + len - 1); - - if (lastChar != '}') - throw new IgniteException("Failed to parse escape sequence because it is not enclosed: " + - substring(text, startPos, len)); - - OdbcEscapeToken token = parseToken(text, startPos, len); - - return parseEscapeSequence(text, startPos, len, token); - } - else { - // Nothing to escape, return original string. - if (startPos == 0 || text.length() == len) - return text; - else - return substring(text, startPos, len); - } - } - - /** - * Get escape sequence info. - * - * @param text Text. - * @param startPos Start position. - * @return Escape sequence info. - */ - private static OdbcEscapeToken parseToken(String text, int startPos, int len) { - assert validSubstring(text, startPos, len); - assert text.charAt(startPos) == '{'; - - int pos = startPos + 1; - - while (Character.isWhitespace(text.charAt(pos))) - pos++; - - OdbcEscapeType curTyp = null; - boolean empty = false; - - for (OdbcEscapeType typ : OdbcEscapeType.sortedValues()) { - if (text.startsWith(typ.body(), pos)) { - if (typ.standard()) - pos += typ.body().length(); - - empty = (startPos + len == pos + 1); - - if (!empty && typ.standard()) { - char charAfter = text.charAt(pos); - - if (!Character.isWhitespace(charAfter)) - throw new IgniteException("Unexpected escape sequence token: " + - substring(text, startPos, len)); - } - - curTyp = typ; - - break; - } - } - - if (curTyp == null) - throw new IgniteException("Unsupported escape sequence: " + substring(text, startPos, len)); - - if (empty && !curTyp.allowEmpty()) - throw new IgniteException("Escape sequence cannot be empty: " + substring(text, startPos, len)); - - return new OdbcEscapeToken(curTyp, pos - (startPos + 1)); - } - - /** - * Parse standard expression: {TOKEN expression} - * - * @param text Text. - * @param startPos Start position. - * @param len Length. - * @param token Token. - * @return Result. - */ - private static String parseEscapeSequence(String text, int startPos, int len, OdbcEscapeToken token) { - assert validSubstring(text, startPos, len); - - // Get expression borders. - int startPos0 = startPos + 1 /* open brace */ + token.length() /* token. */; - int len0 = len - 1 /* open brace */ - token.length() /* token */ - 1 /* close brace */; - - switch (token.type()) { - case SCALAR_FUNCTION: - return parseScalarFunctionExpression(text, startPos0, len0); - - case GUID: { - String res = parseExpression(text, startPos0, len0, token.type(), GUID_PATTERN); - - return "CAST(" + res + " AS UUID)"; - } - - case DATE: - return parseExpression(text, startPos0, len0, token.type(), DATE_PATTERN); - - case TIME: - return parseExpression(text, startPos0, len0, token.type(), TIME_PATTERN); - - case TIMESTAMP: - return parseExpression(text, startPos0, len0, token.type(), TIMESTAMP_PATTERN); - - case OUTER_JOIN: - return parseExpression(text, startPos0, len0); - - case CALL: { - String val = parseExpression(text, startPos0, len0); - - return "CALL " + val; - } - - case ESCAPE: - case ESCAPE_WO_TOKEN: - return parseLikeEscCharacterExpression(text, startPos0, len0); - - default: - throw new IgniteException("Unsupported escape sequence token [text=" + - substring(text, startPos, len) + ", token=" + token.type().body() + ']'); - } - } - - /** - * Parse simple expression. - * - * @param text Text. - * @param startPos Start position. - * @param len Length. - * @return Parsed expression. - */ - private static String parseExpression(String text, int startPos, int len) { - return substring(text, startPos, len).trim(); - } - - /** - * Parse LIKE escape character expression. - * - * @param text Text. - * @param startPos Start position. - * @param len Length. - * @return Parsed expression. - */ - private static String parseLikeEscCharacterExpression(String text, int startPos, int len) { - return "ESCAPE " + substring(text, startPos, len).trim(); - } - - /** - * Parse expression and validate against ODBC specification with regex pattern. - * - * @param text Text. - * @param startPos Start position. - * @param len Length. - * @return Parsed expression. - */ - private static String parseExpression(String text, int startPos, int len, OdbcEscapeType type, Pattern pattern) { - String val = parseExpression(text, startPos, len); - - if (!pattern.matcher(val).matches()) - throw new IgniteException("Invalid " + type + " escape sequence: " + substring(text, startPos, len)); - - return val; - } - - /** - * Parse scalar function expression. - * - * @param text Text. - * @param startPos Start position. - * @param len Length. - * @return Parsed expression. - */ - private static String parseScalarFunctionExpression(String text, int startPos, int len) { - int pos = startPos; - int endPos = startPos + len; - final String errPrefix = "Malformed scalar function escape sequence."; - - while ((++pos < endPos) && Character.isWhitespace(text.charAt(pos))); - if (pos == endPos) - throw new IgniteException(errPrefix + " Expected function name."); - - int funcNamePos = pos; - while ((++pos < endPos) && Character.isAlphabetic(text.charAt(pos))); - if (pos == endPos) - throw new IgniteException(errPrefix + " Expected function parameter list: " + - substring(text, startPos, len)); - - String funcName = text.substring(funcNamePos, pos); - - switch (funcName.toUpperCase()) { - case "CONVERT": { - Matcher matcher = CONVERT_TYPE_PATTERN.matcher(text.substring(startPos, endPos)); - - if (!matcher.find()) - throw new IgniteException(errPrefix + " Invalid arguments :" + - substring(text, startPos, len)); - - return (text.substring(startPos, startPos + matcher.start(1)) + - OdbcUtils.getIgniteTypeFromOdbcType(matcher.group(1)) + - text.substring(startPos + matcher.end(1), startPos + len)).trim(); - } - default: - return substring(text, startPos, len).trim(); - } - } - - /** - * Append nested results. - * - * @param text Original text. - * @param startPos Start position. - * @param endPos End position. - * @param nestedRess Nested results. - * @return Result. - */ - private static String appendNested(String text, int startPos, int endPos, - LinkedList<OdbcEscapeParseResult> nestedRess) { - StringBuilder res = new StringBuilder(); - - int curPos = startPos; - - for (OdbcEscapeParseResult nestedRes : nestedRess) { - // Append text between current position and replace. - res.append(text, curPos, nestedRes.originalStart()); - - // Append replaced text. - res.append(nestedRes.result()); - - // Advance position. - curPos = nestedRes.originalStart() + nestedRes.originalLength(); - } - - // Append remainder. - res.append(text, curPos, endPos); - - return res.toString(); - } - - /** - * Perform "substring" using start position and length. - * - * @param text Text. - * @param startPos Start position. - * @param len Length. - * @return Substring. - */ - private static String substring(String text, int startPos, int len) { - assert validSubstring(text, startPos, len); - - return text.substring(startPos, startPos + len); - } - - /** - * Check whether substring is valid. - * - * @param text Substring. - * @param startPos Start position. - * @param len Length. - * @return {@code True} if valid. - */ - private static boolean validSubstring(String text, int startPos, int len) { - return text != null && startPos + len <= text.length(); - } - - /** - * Private constructor. - */ - private OdbcEscapeUtils() { - // No-op. - } -} http://git-wip-us.apache.org/repos/asf/ignite/blob/4e5a82d6/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcMessageParser.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcMessageParser.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcMessageParser.java new file mode 100644 index 0000000..37fe3bf --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcMessageParser.java @@ -0,0 +1,322 @@ +/* + * 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.odbc.odbc; + +import org.apache.ignite.IgniteException; +import org.apache.ignite.IgniteLogger; +import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.binary.BinaryReaderExImpl; +import org.apache.ignite.internal.binary.BinaryWriterExImpl; +import org.apache.ignite.internal.binary.GridBinaryMarshaller; +import org.apache.ignite.internal.binary.streams.BinaryHeapInputStream; +import org.apache.ignite.internal.binary.streams.BinaryHeapOutputStream; +import org.apache.ignite.internal.binary.streams.BinaryInputStream; +import org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl; +import org.apache.ignite.internal.processors.odbc.SqlListenerHandshakeRequest; +import org.apache.ignite.internal.processors.odbc.SqlListenerHandshakeResult; +import org.apache.ignite.internal.processors.odbc.OdbcQueryGetColumnsMetaRequest; +import org.apache.ignite.internal.processors.odbc.OdbcQueryGetColumnsMetaResult; +import org.apache.ignite.internal.processors.odbc.OdbcQueryGetParamsMetaRequest; +import org.apache.ignite.internal.processors.odbc.OdbcQueryGetParamsMetaResult; +import org.apache.ignite.internal.processors.odbc.OdbcQueryGetTablesMetaRequest; +import org.apache.ignite.internal.processors.odbc.OdbcQueryGetTablesMetaResult; +import org.apache.ignite.internal.processors.odbc.OdbcTableMeta; +import org.apache.ignite.internal.processors.odbc.SqlListenerColumnMeta; +import org.apache.ignite.internal.processors.odbc.SqlListenerMessageParser; +import org.apache.ignite.internal.processors.odbc.SqlListenerQueryCloseRequest; +import org.apache.ignite.internal.processors.odbc.SqlListenerQueryCloseResult; +import org.apache.ignite.internal.processors.odbc.SqlListenerQueryExecuteRequest; +import org.apache.ignite.internal.processors.odbc.SqlListenerQueryExecuteResult; +import org.apache.ignite.internal.processors.odbc.SqlListenerQueryFetchRequest; +import org.apache.ignite.internal.processors.odbc.SqlListenerQueryFetchResult; +import org.apache.ignite.internal.processors.odbc.SqlListenerRequest; +import org.apache.ignite.internal.processors.odbc.SqlListenerResponse; + +import java.util.Collection; + +/** + * ODBC message parser. + */ +public class OdbcMessageParser implements SqlListenerMessageParser { + /** Initial output stream capacity. */ + private static final int INIT_CAP = 1024; + + /** Marshaller. */ + private final GridBinaryMarshaller marsh; + + /** Logger. */ + private final IgniteLogger log; + + /** Protocol version confirmation flag. */ + private boolean verConfirmed = false; + + /** + * @param ctx Context. + */ + public OdbcMessageParser(final GridKernalContext ctx) { + CacheObjectBinaryProcessorImpl cacheObjProc = (CacheObjectBinaryProcessorImpl)ctx.cacheObjects(); + + this.marsh = cacheObjProc.marshaller(); + + this.log = ctx.log(getClass()); + } + + /** {@inheritDoc} */ + @Override public SqlListenerRequest decode(byte[] msg) { + assert msg != null; + + BinaryInputStream stream = new BinaryHeapInputStream(msg); + + BinaryReaderExImpl reader = new BinaryReaderExImpl(null, stream, null, true); + + byte cmd = reader.readByte(); + + // This is a special case because we can not decode protocol messages until + // we has not confirmed that the remote client uses the same protocol version. + if (!verConfirmed) { + if (cmd == SqlListenerRequest.HANDSHAKE) + { + long longVersion = reader.readLong(); + + SqlListenerHandshakeRequest res = new SqlListenerHandshakeRequest(longVersion); + + res.distributedJoins(reader.readBoolean()); + res.enforceJoinOrder(reader.readBoolean()); + + return res; + } + else + throw new IgniteException("Unexpected ODBC command " + + "(first message is not a handshake request): [cmd=" + cmd + ']'); + } + + SqlListenerRequest res; + + switch (cmd) { + case SqlListenerRequest.QRY_EXEC: { + String cache = reader.readString(); + String sql = reader.readString(); + int argsNum = reader.readInt(); + + Object[] params = new Object[argsNum]; + + for (int i = 0; i < argsNum; ++i) + params[i] = reader.readObjectDetached(); + + res = new SqlListenerQueryExecuteRequest(cache, sql, params); + + break; + } + + case SqlListenerRequest.QRY_FETCH: { + long queryId = reader.readLong(); + int pageSize = reader.readInt(); + + res = new SqlListenerQueryFetchRequest(queryId, pageSize); + + break; + } + + case SqlListenerRequest.QRY_CLOSE: { + long queryId = reader.readLong(); + + res = new SqlListenerQueryCloseRequest(queryId); + + break; + } + + case SqlListenerRequest.META_COLS: { + String cache = reader.readString(); + String table = reader.readString(); + String column = reader.readString(); + + res = new OdbcQueryGetColumnsMetaRequest(cache, table, column); + + break; + } + + case SqlListenerRequest.META_TBLS: { + String catalog = reader.readString(); + String schema = reader.readString(); + String table = reader.readString(); + String tableType = reader.readString(); + + res = new OdbcQueryGetTablesMetaRequest(catalog, schema, table, tableType); + + break; + } + + case SqlListenerRequest.META_PARAMS: { + String cacheName = reader.readString(); + String sqlQuery = reader.readString(); + + res = new OdbcQueryGetParamsMetaRequest(cacheName, sqlQuery); + + break; + } + + default: + throw new IgniteException("Unknown ODBC command: [cmd=" + cmd + ']'); + } + + return res; + } + + /** {@inheritDoc} */ + @Override public byte[] encode(SqlListenerResponse msg) { + assert msg != null; + + // Creating new binary writer + BinaryWriterExImpl writer = marsh.writer(new BinaryHeapOutputStream(INIT_CAP)); + + // Writing status. + writer.writeByte((byte) msg.status()); + + if (msg.status() != SqlListenerResponse.STATUS_SUCCESS) { + writer.writeString(msg.error()); + + return writer.array(); + } + + Object res0 = msg.response(); + + if (res0 == null) + return writer.array(); + if (res0 instanceof SqlListenerHandshakeResult) { + SqlListenerHandshakeResult res = (SqlListenerHandshakeResult) res0; + + if (log.isDebugEnabled()) + log.debug("Handshake result: " + (res.accepted() ? "accepted" : "rejected")); + + verConfirmed = res.accepted(); + + if (res.accepted()) { + verConfirmed = true; + + writer.writeBoolean(true); + } + else { + writer.writeBoolean(false); + writer.writeString(res.protocolVersionSince()); + writer.writeString(res.currentVersion()); + } + } + else if (res0 instanceof SqlListenerQueryExecuteResult) { + SqlListenerQueryExecuteResult res = (SqlListenerQueryExecuteResult) res0; + + if (log.isDebugEnabled()) + log.debug("Resulting query ID: " + res.getQueryId()); + + writer.writeLong(res.getQueryId()); + + Collection<SqlListenerColumnMeta> metas = res.getColumnsMetadata(); + + assert metas != null; + + writer.writeInt(metas.size()); + + for (SqlListenerColumnMeta meta : metas) + meta.write(writer); + } + else if (res0 instanceof SqlListenerQueryFetchResult) { + SqlListenerQueryFetchResult res = (SqlListenerQueryFetchResult) res0; + + if (log.isDebugEnabled()) + log.debug("Resulting query ID: " + res.queryId()); + + writer.writeLong(res.queryId()); + + Collection<?> items0 = res.items(); + + assert items0 != null; + + writer.writeBoolean(res.last()); + + writer.writeInt(items0.size()); + + for (Object row0 : items0) { + if (row0 != null) { + Collection<?> row = (Collection<?>)row0; + + writer.writeInt(row.size()); + + for (Object obj : row) { + if (obj == null) { + writer.writeObjectDetached(null); + continue; + } + + Class<?> cls = obj.getClass(); + + if (cls == java.sql.Time.class) + writer.writeTime((java.sql.Time)obj); + else if (cls == java.sql.Timestamp.class) + writer.writeTimestamp((java.sql.Timestamp)obj); + else if (cls == java.sql.Date.class) + writer.writeDate((java.util.Date)obj); + else + writer.writeObjectDetached(obj); + } + } + } + } + else if (res0 instanceof SqlListenerQueryCloseResult) { + SqlListenerQueryCloseResult res = (SqlListenerQueryCloseResult) res0; + + if (log.isDebugEnabled()) + log.debug("Resulting query ID: " + res.getQueryId()); + + writer.writeLong(res.getQueryId()); + } + else if (res0 instanceof OdbcQueryGetColumnsMetaResult) { + OdbcQueryGetColumnsMetaResult res = (OdbcQueryGetColumnsMetaResult) res0; + + Collection<SqlListenerColumnMeta> columnsMeta = res.meta(); + + assert columnsMeta != null; + + writer.writeInt(columnsMeta.size()); + + for (SqlListenerColumnMeta columnMeta : columnsMeta) + columnMeta.write(writer); + } + else if (res0 instanceof OdbcQueryGetTablesMetaResult) { + OdbcQueryGetTablesMetaResult res = (OdbcQueryGetTablesMetaResult) res0; + + Collection<OdbcTableMeta> tablesMeta = res.meta(); + + assert tablesMeta != null; + + writer.writeInt(tablesMeta.size()); + + for (OdbcTableMeta tableMeta : tablesMeta) + tableMeta.writeBinary(writer); + } + else if (res0 instanceof OdbcQueryGetParamsMetaResult) { + OdbcQueryGetParamsMetaResult res = (OdbcQueryGetParamsMetaResult) res0; + + byte[] typeIds = res.typeIds(); + + writer.writeObjectDetached(typeIds); + } + else + assert false : "Should not reach here."; + + return writer.array(); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/4e5a82d6/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcRequestHandler.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcRequestHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcRequestHandler.java new file mode 100644 index 0000000..815f650 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcRequestHandler.java @@ -0,0 +1,540 @@ +/* + * 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.odbc.odbc; + +import org.apache.ignite.IgniteCache; +import org.apache.ignite.IgniteLogger; +import org.apache.ignite.cache.query.QueryCursor; +import org.apache.ignite.cache.query.SqlFieldsQuery; +import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.binary.GridBinaryMarshaller; +import org.apache.ignite.internal.processors.cache.QueryCursorImpl; +import org.apache.ignite.internal.processors.odbc.SqlListenerHandshakeRequest; +import org.apache.ignite.internal.processors.odbc.SqlListenerHandshakeResult; +import org.apache.ignite.internal.processors.odbc.OdbcQueryGetColumnsMetaRequest; +import org.apache.ignite.internal.processors.odbc.OdbcQueryGetColumnsMetaResult; +import org.apache.ignite.internal.processors.odbc.OdbcQueryGetParamsMetaRequest; +import org.apache.ignite.internal.processors.odbc.OdbcQueryGetParamsMetaResult; +import org.apache.ignite.internal.processors.odbc.OdbcQueryGetTablesMetaRequest; +import org.apache.ignite.internal.processors.odbc.OdbcQueryGetTablesMetaResult; +import org.apache.ignite.internal.processors.odbc.OdbcTableMeta; +import org.apache.ignite.internal.processors.odbc.OdbcUtils; +import org.apache.ignite.internal.processors.odbc.SqlListenerColumnMeta; +import org.apache.ignite.internal.processors.odbc.SqlListenerProtocolVersion; +import org.apache.ignite.internal.processors.odbc.SqlListenerQueryCloseRequest; +import org.apache.ignite.internal.processors.odbc.SqlListenerQueryCloseResult; +import org.apache.ignite.internal.processors.odbc.SqlListenerQueryExecuteRequest; +import org.apache.ignite.internal.processors.odbc.SqlListenerQueryExecuteResult; +import org.apache.ignite.internal.processors.odbc.SqlListenerQueryFetchRequest; +import org.apache.ignite.internal.processors.odbc.SqlListenerQueryFetchResult; +import org.apache.ignite.internal.processors.odbc.SqlListenerRequest; +import org.apache.ignite.internal.processors.odbc.SqlListenerRequestHandler; +import org.apache.ignite.internal.processors.odbc.SqlListenerResponse; +import org.apache.ignite.internal.processors.odbc.odbc.escape.OdbcEscapeUtils; +import org.apache.ignite.internal.processors.query.GridQueryFieldMetadata; +import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor; +import org.apache.ignite.internal.util.GridSpinBusyLock; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.lang.IgniteBiTuple; +import org.apache.ignite.lang.IgniteProductVersion; + +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.Types; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +import static org.apache.ignite.internal.processors.odbc.SqlListenerRequest.*; + +/** + * SQL query handler. + */ +public class OdbcRequestHandler implements SqlListenerRequestHandler { + /** Query ID sequence. */ + private static final AtomicLong QRY_ID_GEN = new AtomicLong(); + + /** Kernel context. */ + private final GridKernalContext ctx; + + /** Logger. */ + private final IgniteLogger log; + + /** Busy lock. */ + private final GridSpinBusyLock busyLock; + + /** Maximum allowed cursors. */ + private final int maxCursors; + + /** Current queries cursors. */ + private final ConcurrentHashMap<Long, IgniteBiTuple<QueryCursor, Iterator>> qryCursors = new ConcurrentHashMap<>(); + + /** Distributed joins flag. */ + private boolean distributedJoins = false; + + /** Enforce join order flag. */ + private boolean enforceJoinOrder = false; + + /** + * Constructor. + * + * @param ctx Context. + * @param busyLock Shutdown latch. + * @param maxCursors Maximum allowed cursors. + */ + public OdbcRequestHandler(GridKernalContext ctx, GridSpinBusyLock busyLock, int maxCursors) { + this.ctx = ctx; + this.busyLock = busyLock; + this.maxCursors = maxCursors; + + log = ctx.log(getClass()); + } + + /** {@inheritDoc} */ + @Override public SqlListenerResponse handle(SqlListenerRequest req) { + assert req != null; + + if (!busyLock.enterBusy()) + return new SqlListenerResponse(SqlListenerResponse.STATUS_FAILED, + "Failed to handle ODBC request because node is stopping: " + req); + + try { + switch (req.command()) { + case HANDSHAKE: + return performHandshake((SqlListenerHandshakeRequest)req); + + case QRY_EXEC: + return executeQuery((SqlListenerQueryExecuteRequest)req); + + case QRY_FETCH: + return fetchQuery((SqlListenerQueryFetchRequest)req); + + case QRY_CLOSE: + return closeQuery((SqlListenerQueryCloseRequest)req); + + case META_COLS: + return getColumnsMeta((OdbcQueryGetColumnsMetaRequest)req); + + case META_TBLS: + return getTablesMeta((OdbcQueryGetTablesMetaRequest)req); + + case META_PARAMS: + return getParamsMeta((OdbcQueryGetParamsMetaRequest)req); + } + + return new SqlListenerResponse(SqlListenerResponse.STATUS_FAILED, "Unsupported ODBC request: " + req); + } + finally { + busyLock.leaveBusy(); + } + } + + /** + * {@link SqlListenerHandshakeRequest} command handler. + * + * @param req Handshake request. + * @return Response. + */ + private SqlListenerResponse performHandshake(SqlListenerHandshakeRequest req) { + try { + SqlListenerProtocolVersion version = req.version(); + + if (version == SqlListenerProtocolVersion.UNKNOWN) { + IgniteProductVersion ver = ctx.grid().version(); + + String verStr = Byte.toString(ver.major()) + '.' + ver.minor() + '.' + ver.maintenance(); + + SqlListenerHandshakeResult res = new SqlListenerHandshakeResult(false, OdbcUtils.VER_LATEST.since(), verStr); + + return new SqlListenerResponse(res); + } + + SqlListenerHandshakeResult res = new SqlListenerHandshakeResult(true, null, null); + + distributedJoins = req.distributedJoins(); + enforceJoinOrder = req.enforceJoinOrder(); + + return new SqlListenerResponse(res); + } + catch (Exception e) { + U.error(log, "Failed to perform handshake [reqId=" + req.requestId() + ", req=" + req + ']', e); + + return new SqlListenerResponse(SqlListenerResponse.STATUS_FAILED, e.toString()); + } + } + + /** + * {@link SqlListenerQueryExecuteRequest} command handler. + * + * @param req Execute query request. + * @return Response. + */ + private SqlListenerResponse executeQuery(SqlListenerQueryExecuteRequest req) { + int cursorCnt = qryCursors.size(); + + if (maxCursors > 0 && cursorCnt >= maxCursors) + return new SqlListenerResponse(SqlListenerResponse.STATUS_FAILED, "Too many opened cursors (either close other " + + "opened cursors or increase the limit through OdbcConfiguration.setMaxOpenCursors()) " + + "[maximum=" + maxCursors + ", current=" + cursorCnt + ']'); + + long qryId = QRY_ID_GEN.getAndIncrement(); + + try { + String sql = OdbcEscapeUtils.parse(req.sqlQuery()); + + if (log.isDebugEnabled()) + log.debug("ODBC query parsed [reqId=" + req.requestId() + ", original=" + req.sqlQuery() + + ", parsed=" + sql + ']'); + + SqlFieldsQuery qry = new SqlFieldsQuery(sql); + + qry.setArgs(req.arguments()); + + qry.setDistributedJoins(distributedJoins); + qry.setEnforceJoinOrder(enforceJoinOrder); + + IgniteCache<Object, Object> cache0 = ctx.grid().cache(req.cacheName()); + + if (cache0 == null) + return new SqlListenerResponse(SqlListenerResponse.STATUS_FAILED, + "Cache doesn't exist (did you configure it?): " + req.cacheName()); + + IgniteCache<Object, Object> cache = cache0.withKeepBinary(); + + if (cache == null) + return new SqlListenerResponse(SqlListenerResponse.STATUS_FAILED, + "Can not get cache with keep binary: " + req.cacheName()); + + QueryCursor qryCur = cache.query(qry); + + qryCursors.put(qryId, new IgniteBiTuple<QueryCursor, Iterator>(qryCur, null)); + + List<?> fieldsMeta = ((QueryCursorImpl) qryCur).fieldsMeta(); + + SqlListenerQueryExecuteResult res = new SqlListenerQueryExecuteResult(qryId, convertMetadata(fieldsMeta)); + + return new SqlListenerResponse(res); + } + catch (Exception e) { + qryCursors.remove(qryId); + + U.error(log, "Failed to execute SQL query [reqId=" + req.requestId() + ", req=" + req + ']', e); + + return new SqlListenerResponse(SqlListenerResponse.STATUS_FAILED, e.toString()); + } + } + + /** + * {@link SqlListenerQueryCloseRequest} command handler. + * + * @param req Execute query request. + * @return Response. + */ + private SqlListenerResponse closeQuery(SqlListenerQueryCloseRequest req) { + try { + IgniteBiTuple<QueryCursor, Iterator> tuple = qryCursors.get(req.queryId()); + + if (tuple == null) + return new SqlListenerResponse(SqlListenerResponse.STATUS_FAILED, + "Failed to find query with ID: " + req.queryId()); + + QueryCursor cur = tuple.get1(); + + assert(cur != null); + + cur.close(); + + qryCursors.remove(req.queryId()); + + SqlListenerQueryCloseResult res = new SqlListenerQueryCloseResult(req.queryId()); + + return new SqlListenerResponse(res); + } + catch (Exception e) { + qryCursors.remove(req.queryId()); + + U.error(log, "Failed to close SQL query [reqId=" + req.requestId() + ", req=" + req.queryId() + ']', e); + + return new SqlListenerResponse(SqlListenerResponse.STATUS_FAILED, e.toString()); + } + } + + /** + * {@link SqlListenerQueryFetchRequest} command handler. + * + * @param req Execute query request. + * @return Response. + */ + private SqlListenerResponse fetchQuery(SqlListenerQueryFetchRequest req) { + try { + IgniteBiTuple<QueryCursor, Iterator> tuple = qryCursors.get(req.queryId()); + + if (tuple == null) + return new SqlListenerResponse(SqlListenerResponse.STATUS_FAILED, + "Failed to find query with ID: " + req.queryId()); + + Iterator iter = tuple.get2(); + + if (iter == null) { + QueryCursor cur = tuple.get1(); + + iter = cur.iterator(); + + tuple.put(cur, iter); + } + + List<Object> items = new ArrayList<>(); + + for (int i = 0; i < req.pageSize() && iter.hasNext(); ++i) + items.add(iter.next()); + + SqlListenerQueryFetchResult res = new SqlListenerQueryFetchResult(req.queryId(), items, !iter.hasNext()); + + return new SqlListenerResponse(res); + } + catch (Exception e) { + U.error(log, "Failed to fetch SQL query result [reqId=" + req.requestId() + ", req=" + req + ']', e); + + return new SqlListenerResponse(SqlListenerResponse.STATUS_FAILED, e.toString()); + } + } + + /** + * {@link OdbcQueryGetColumnsMetaRequest} command handler. + * + * @param req Get columns metadata request. + * @return Response. + */ + private SqlListenerResponse getColumnsMeta(OdbcQueryGetColumnsMetaRequest req) { + try { + List<SqlListenerColumnMeta> meta = new ArrayList<>(); + + String cacheName; + String tableName; + + if (req.tableName().contains(".")) { + // Parsing two-part table name. + String[] parts = req.tableName().split("\\."); + + cacheName = OdbcUtils.removeQuotationMarksIfNeeded(parts[0]); + + tableName = parts[1]; + } + else { + cacheName = OdbcUtils.removeQuotationMarksIfNeeded(req.cacheName()); + + tableName = req.tableName(); + } + + Collection<GridQueryTypeDescriptor> tablesMeta = ctx.query().types(cacheName); + + for (GridQueryTypeDescriptor table : tablesMeta) { + if (!matches(table.name(), tableName)) + continue; + + for (Map.Entry<String, Class<?>> field : table.fields().entrySet()) { + if (!matches(field.getKey(), req.columnName())) + continue; + + SqlListenerColumnMeta columnMeta = new SqlListenerColumnMeta(req.cacheName(), table.name(), + field.getKey(), field.getValue()); + + if (!meta.contains(columnMeta)) + meta.add(columnMeta); + } + } + + OdbcQueryGetColumnsMetaResult res = new OdbcQueryGetColumnsMetaResult(meta); + + return new SqlListenerResponse(res); + } + catch (Exception e) { + U.error(log, "Failed to get columns metadata [reqId=" + req.requestId() + ", req=" + req + ']', e); + + return new SqlListenerResponse(SqlListenerResponse.STATUS_FAILED, e.toString()); + } + } + + /** + * {@link OdbcQueryGetTablesMetaRequest} command handler. + * + * @param req Get tables metadata request. + * @return Response. + */ + private SqlListenerResponse getTablesMeta(OdbcQueryGetTablesMetaRequest req) { + try { + List<OdbcTableMeta> meta = new ArrayList<>(); + + String realSchema = OdbcUtils.removeQuotationMarksIfNeeded(req.schema()); + + for (String cacheName : ctx.cache().cacheNames()) + { + if (!matches(cacheName, realSchema)) + continue; + + Collection<GridQueryTypeDescriptor> tablesMeta = ctx.query().types(cacheName); + + for (GridQueryTypeDescriptor table : tablesMeta) { + if (!matches(table.name(), req.table())) + continue; + + if (!matches("TABLE", req.tableType())) + continue; + + OdbcTableMeta tableMeta = new OdbcTableMeta(null, cacheName, table.name(), "TABLE"); + + if (!meta.contains(tableMeta)) + meta.add(tableMeta); + } + } + + OdbcQueryGetTablesMetaResult res = new OdbcQueryGetTablesMetaResult(meta); + + return new SqlListenerResponse(res); + } + catch (Exception e) { + U.error(log, "Failed to get tables metadata [reqId=" + req.requestId() + ", req=" + req + ']', e); + + return new SqlListenerResponse(SqlListenerResponse.STATUS_FAILED, e.toString()); + } + } + + /** + * {@link OdbcQueryGetParamsMetaRequest} command handler. + * + * @param req Get params metadata request. + * @return Response. + */ + private SqlListenerResponse getParamsMeta(OdbcQueryGetParamsMetaRequest req) { + try { + PreparedStatement stmt = ctx.query().prepareNativeStatement(req.cacheName(), req.query()); + + ParameterMetaData pmd = stmt.getParameterMetaData(); + + byte[] typeIds = new byte[pmd.getParameterCount()]; + + for (int i = 1; i <= pmd.getParameterCount(); ++i) { + int sqlType = pmd.getParameterType(i); + + typeIds[i - 1] = sqlTypeToBinary(sqlType); + } + + OdbcQueryGetParamsMetaResult res = new OdbcQueryGetParamsMetaResult(typeIds); + + return new SqlListenerResponse(res); + } + catch (Exception e) { + U.error(log, "Failed to get params metadata [reqId=" + req.requestId() + ", req=" + req + ']', e); + + return new SqlListenerResponse(SqlListenerResponse.STATUS_FAILED, e.toString()); + } + } + + /** + * Convert {@link java.sql.Types} to binary type constant (See {@link GridBinaryMarshaller} constants). + * + * @param sqlType SQL type. + * @return Binary type. + */ + private static byte sqlTypeToBinary(int sqlType) { + switch (sqlType) { + case Types.BIGINT: + return GridBinaryMarshaller.LONG; + + case Types.BOOLEAN: + return GridBinaryMarshaller.BOOLEAN; + + case Types.DATE: + return GridBinaryMarshaller.DATE; + + case Types.DOUBLE: + return GridBinaryMarshaller.DOUBLE; + + case Types.FLOAT: + case Types.REAL: + return GridBinaryMarshaller.FLOAT; + + case Types.NUMERIC: + case Types.DECIMAL: + return GridBinaryMarshaller.DECIMAL; + + case Types.INTEGER: + return GridBinaryMarshaller.INT; + + case Types.SMALLINT: + return GridBinaryMarshaller.SHORT; + + case Types.TIME: + return GridBinaryMarshaller.TIME; + + case Types.TIMESTAMP: + return GridBinaryMarshaller.TIMESTAMP; + + case Types.TINYINT: + return GridBinaryMarshaller.BYTE; + + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGNVARCHAR: + return GridBinaryMarshaller.STRING; + + case Types.NULL: + return GridBinaryMarshaller.NULL; + + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + default: + return GridBinaryMarshaller.BYTE_ARR; + } + } + + /** + * Convert metadata in collection from {@link GridQueryFieldMetadata} to + * {@link SqlListenerColumnMeta}. + * + * @param meta Internal query field metadata. + * @return Odbc query field metadata. + */ + private static Collection<SqlListenerColumnMeta> convertMetadata(Collection<?> meta) { + List<SqlListenerColumnMeta> res = new ArrayList<>(); + + if (meta != null) { + for (Object info : meta) { + assert info instanceof GridQueryFieldMetadata; + + res.add(new SqlListenerColumnMeta((GridQueryFieldMetadata)info)); + } + } + + return res; + } + + /** + * Checks whether string matches SQL pattern. + * + * @param str String. + * @param ptrn Pattern. + * @return Whether string matches pattern. + */ + private static boolean matches(String str, String ptrn) { + return str != null && (F.isEmpty(ptrn) || + str.toUpperCase().matches(ptrn.toUpperCase().replace("%", ".*").replace("_", "."))); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/4e5a82d6/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/escape/OdbcEscapeParseResult.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/escape/OdbcEscapeParseResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/escape/OdbcEscapeParseResult.java new file mode 100644 index 0000000..369e7ac --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/escape/OdbcEscapeParseResult.java @@ -0,0 +1,73 @@ +/* + * 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.odbc.odbc.escape; + +import org.apache.ignite.internal.util.typedef.internal.S; + +/** + * ODBC escape sequence parse result. + */ +public class OdbcEscapeParseResult { + /** Original start position. */ + private final int originalStart; + + /** Original length. */ + private final int originalLen; + + /** Resulting text. */ + private final String res; + + /** + * Constructor. + * + * @param originalStart Original start position. + * @param originalLen Original length. + * @param res Resulting text. + */ + public OdbcEscapeParseResult(int originalStart, int originalLen, String res) { + this.originalStart = originalStart; + this.originalLen = originalLen; + this.res = res; + } + + /** + * @return Original start position. + */ + public int originalStart() { + return originalStart; + } + + /** + * @return Original length. + */ + public int originalLength() { + return originalLen; + } + + /** + * @return Resulting text. + */ + public String result() { + return res; + } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(OdbcEscapeParseResult.class, this); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/4e5a82d6/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/escape/OdbcEscapeToken.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/escape/OdbcEscapeToken.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/escape/OdbcEscapeToken.java new file mode 100644 index 0000000..a8b40fa --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/escape/OdbcEscapeToken.java @@ -0,0 +1,61 @@ +/* + * 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.odbc.odbc.escape; + +import org.apache.ignite.internal.util.typedef.internal.S; + +/** + * ODBC escape sequence token. + */ +public class OdbcEscapeToken { + /** Escape sequence type. */ + private final OdbcEscapeType type; + + /** Token length. */ + private final int len; + + /** + * Constructor. + * + * @param type Escape sequence type. + * @param len Token length. + */ + public OdbcEscapeToken(OdbcEscapeType type, int len) { + this.type = type; + this.len = len; + } + + /** + * @return Escape sequence type. + */ + public OdbcEscapeType type() { + return type; + } + + /** + * @return Token length. + */ + public int length() { + return len; + } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(OdbcEscapeToken.class, this); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/4e5a82d6/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/escape/OdbcEscapeType.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/escape/OdbcEscapeType.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/escape/OdbcEscapeType.java new file mode 100644 index 0000000..748263d --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/escape/OdbcEscapeType.java @@ -0,0 +1,112 @@ +/* + * 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.odbc.odbc.escape; + +/** + * ODBC escape sequence type. + */ +public enum OdbcEscapeType { + /** Scalar function. */ + SCALAR_FUNCTION("fn", true, false), + + /** Outer join. */ + OUTER_JOIN("oj", true, false), + + /** Stored procedure call */ + CALL("call", true, false), + + /** Date. */ + DATE("d", true, false), + + /** Timestamp. */ + TIMESTAMP("ts", true, false), + + /** Time. */ + TIME("t", true, false), + + /** GUID. */ + GUID("guid", true, false), + + /** LIKE escape character clause. */ + ESCAPE_WO_TOKEN("\'", false, false), + + /** LIKE escape character clause. */ + ESCAPE("escape", true, false); + + /** Values in convenient order. */ + private static final OdbcEscapeType[] VALS = new OdbcEscapeType[] { + SCALAR_FUNCTION, // Assume that scalar functions are very frequent. + DATE, TIMESTAMP, // Date and timestamp are relatively frequent as well; also TS must go before T. + OUTER_JOIN, // Joins are less frequent, + CALL, // Procedure calls are less frequent than joins. + ESCAPE_WO_TOKEN, ESCAPE, TIME, GUID // LIKE, TIME and GUID are even less frequent. + }; + + /** + * Get values in convenient order, where the most frequent values goes first, and "startsWith" invocation is + * enough to get type (i.e. "ts" goes before "t"). + * + * @return Values. + */ + public static OdbcEscapeType[] sortedValues() { + return VALS; + } + + /** Escape sequence body. */ + private final String body; + + /** Whether this is a standard token with no special handling. */ + private final boolean standard; + + /** Whether empty escape sequence is allowed. */ + private final boolean allowEmpty; + + /** + * Constructor. + * + * @param body Escape sequence body. + * @param standard Whether this is a standard token with no special handling. + * @param allowEmpty Whether empty escape sequence is allowed. + */ + OdbcEscapeType(String body, boolean standard, boolean allowEmpty) { + this.body = body; + this.standard = standard; + this.allowEmpty = allowEmpty; + } + + /** + * @return Escape sequence body. + */ + public String body() { + return body; + } + + /** + * @return Whether this is a standard token with no special handling. + */ + public boolean standard() { + return standard; + } + + /** + * @return Whether empty escape sequence is allowed. + */ + public boolean allowEmpty() { + return allowEmpty; + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/4e5a82d6/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/escape/OdbcEscapeUtils.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/escape/OdbcEscapeUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/escape/OdbcEscapeUtils.java new file mode 100644 index 0000000..cd97667 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/escape/OdbcEscapeUtils.java @@ -0,0 +1,441 @@ +/* + * 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.odbc.odbc.escape; + +import org.apache.ignite.IgniteException; +import org.apache.ignite.internal.processors.odbc.OdbcUtils; + +import java.util.LinkedList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * ODBC escape sequence parse. + */ +public class OdbcEscapeUtils { + /** Odbc date regexp pattern: '2016-08-23' */ + private static final Pattern DATE_PATTERN = Pattern.compile("^'\\d{4}-\\d{2}-\\d{2}'$"); + + /** Odbc time regexp pattern: '14:33:44' */ + private static final Pattern TIME_PATTERN = Pattern.compile("^'\\d{2}:\\d{2}:\\d{2}'$"); + + /** Odbc timestamp regexp pattern: '2016-08-23 14:33:44.12345' */ + private static final Pattern TIMESTAMP_PATTERN = + Pattern.compile("^'\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}(\\.\\d+)?'$"); + + /** GUID regexp pattern: '12345678-9abc-def0-1234-123456789abc' */ + private static final Pattern GUID_PATTERN = + Pattern.compile("^'\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}'$"); + + /** CONVERT function data type parameter pattern: last parameter, after comma */ + private static final Pattern CONVERT_TYPE_PATTERN = + Pattern.compile(",\\s*(SQL_[\\w_]+)\\s*(?:\\(\\s*\\d+\\s*(?:,\\s*\\d+\\s*)?\\))?\\s*\\)\\s*$", + Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); + + /** + * Parse escape sequence. + * + * @param text Original text. + * @return Result. + */ + public static String parse(String text) { + if (text == null) + throw new IgniteException("Text cannot be null."); + + return parse0(text.trim(), 0, false).result(); + } + + /** + * Internal parse routine. + * + * @param text Text. + * @param startPos Start position. + * @param earlyExit When set to {@code true} we must return as soon as single expression is parsed. + * @return Parse result. + */ + private static OdbcEscapeParseResult parse0(String text, int startPos, boolean earlyExit) { + StringBuilder res = new StringBuilder(); + + int curPos = startPos; + + int plainPos = startPos; + int openPos = -1; + + boolean insideLiteral = false; + + LinkedList<OdbcEscapeParseResult> nested = null; + + while (curPos < text.length()) { + char curChar = text.charAt(curPos); + + if (curChar == '\'') + /* Escaped quote in odbc is two successive singe quotes. They'll flip flag twice without side-effect. */ + insideLiteral = !insideLiteral; + else if (!insideLiteral) { + if (curChar == '{') { + if (openPos == -1) { + // Top-level opening brace. Append previous portion and remember current position. + res.append(text, plainPos, curPos); + + openPos = curPos; + } + else { + // Nested opening brace -> perform recursion. + OdbcEscapeParseResult nestedRes = parse0(text, curPos, true); + + if (nested == null) + nested = new LinkedList<>(); + + nested.add(nestedRes); + + curPos += nestedRes.originalLength() - 1; + + plainPos = curPos + 1; + } + } + else if (curChar == '}') { + if (openPos == -1) + // Close without open -> exception. + throw new IgniteException("Malformed escape sequence " + + "(closing curly brace without opening curly brace): " + text); + else { + String parseRes; + + if (nested == null) + // Found sequence without nesting, process it. + parseRes = parseEscapeSequence(text, openPos, curPos + 1 - openPos); + else { + // Special case to process nesting. + String res0 = appendNested(text, openPos, curPos + 1, nested); + + nested = null; + + parseRes = parseEscapeSequence(res0, 0, res0.length()); + } + + if (earlyExit) + return new OdbcEscapeParseResult(startPos, curPos + 1 - startPos, parseRes); + else + res.append(parseRes); + + openPos = -1; + + plainPos = curPos + 1; + } + } + } + + curPos++; + } + + if (openPos != -1) + throw new IgniteException("Malformed escape sequence (closing curly brace missing): " + text); + + if (insideLiteral) + throw new IgniteException("Malformed literal expression (closing quote missing): " + text); + + if (curPos > plainPos) + res.append(text, plainPos, curPos); + + return new OdbcEscapeParseResult(startPos, curPos - startPos + 1, res.toString()); + } + + /** + * Parse escape sequence: {escape_sequence}. + * + * @param text Text. + * @param startPos Start position within text. + * @param len Length. + * @return Result. + */ + private static String parseEscapeSequence(String text, int startPos, int len) { + assert validSubstring(text, startPos, len); + + char firstChar = text.charAt(startPos); + + if (firstChar == '{') { + char lastChar = text.charAt(startPos + len - 1); + + if (lastChar != '}') + throw new IgniteException("Failed to parse escape sequence because it is not enclosed: " + + substring(text, startPos, len)); + + OdbcEscapeToken token = parseToken(text, startPos, len); + + return parseEscapeSequence(text, startPos, len, token); + } + else { + // Nothing to escape, return original string. + if (startPos == 0 || text.length() == len) + return text; + else + return substring(text, startPos, len); + } + } + + /** + * Get escape sequence info. + * + * @param text Text. + * @param startPos Start position. + * @return Escape sequence info. + */ + private static OdbcEscapeToken parseToken(String text, int startPos, int len) { + assert validSubstring(text, startPos, len); + assert text.charAt(startPos) == '{'; + + int pos = startPos + 1; + + while (Character.isWhitespace(text.charAt(pos))) + pos++; + + OdbcEscapeType curTyp = null; + boolean empty = false; + + for (OdbcEscapeType typ : OdbcEscapeType.sortedValues()) { + if (text.startsWith(typ.body(), pos)) { + if (typ.standard()) + pos += typ.body().length(); + + empty = (startPos + len == pos + 1); + + if (!empty && typ.standard()) { + char charAfter = text.charAt(pos); + + if (!Character.isWhitespace(charAfter)) + throw new IgniteException("Unexpected escape sequence token: " + + substring(text, startPos, len)); + } + + curTyp = typ; + + break; + } + } + + if (curTyp == null) + throw new IgniteException("Unsupported escape sequence: " + substring(text, startPos, len)); + + if (empty && !curTyp.allowEmpty()) + throw new IgniteException("Escape sequence cannot be empty: " + substring(text, startPos, len)); + + return new OdbcEscapeToken(curTyp, pos - (startPos + 1)); + } + + /** + * Parse standard expression: {TOKEN expression} + * + * @param text Text. + * @param startPos Start position. + * @param len Length. + * @param token Token. + * @return Result. + */ + private static String parseEscapeSequence(String text, int startPos, int len, OdbcEscapeToken token) { + assert validSubstring(text, startPos, len); + + // Get expression borders. + int startPos0 = startPos + 1 /* open brace */ + token.length() /* token. */; + int len0 = len - 1 /* open brace */ - token.length() /* token */ - 1 /* close brace */; + + switch (token.type()) { + case SCALAR_FUNCTION: + return parseScalarFunctionExpression(text, startPos0, len0); + + case GUID: { + String res = parseExpression(text, startPos0, len0, token.type(), GUID_PATTERN); + + return "CAST(" + res + " AS UUID)"; + } + + case DATE: + return parseExpression(text, startPos0, len0, token.type(), DATE_PATTERN); + + case TIME: + return parseExpression(text, startPos0, len0, token.type(), TIME_PATTERN); + + case TIMESTAMP: + return parseExpression(text, startPos0, len0, token.type(), TIMESTAMP_PATTERN); + + case OUTER_JOIN: + return parseExpression(text, startPos0, len0); + + case CALL: { + String val = parseExpression(text, startPos0, len0); + + return "CALL " + val; + } + + case ESCAPE: + case ESCAPE_WO_TOKEN: + return parseLikeEscCharacterExpression(text, startPos0, len0); + + default: + throw new IgniteException("Unsupported escape sequence token [text=" + + substring(text, startPos, len) + ", token=" + token.type().body() + ']'); + } + } + + /** + * Parse simple expression. + * + * @param text Text. + * @param startPos Start position. + * @param len Length. + * @return Parsed expression. + */ + private static String parseExpression(String text, int startPos, int len) { + return substring(text, startPos, len).trim(); + } + + /** + * Parse LIKE escape character expression. + * + * @param text Text. + * @param startPos Start position. + * @param len Length. + * @return Parsed expression. + */ + private static String parseLikeEscCharacterExpression(String text, int startPos, int len) { + return "ESCAPE " + substring(text, startPos, len).trim(); + } + + /** + * Parse expression and validate against ODBC specification with regex pattern. + * + * @param text Text. + * @param startPos Start position. + * @param len Length. + * @return Parsed expression. + */ + private static String parseExpression(String text, int startPos, int len, OdbcEscapeType type, Pattern pattern) { + String val = parseExpression(text, startPos, len); + + if (!pattern.matcher(val).matches()) + throw new IgniteException("Invalid " + type + " escape sequence: " + substring(text, startPos, len)); + + return val; + } + + /** + * Parse scalar function expression. + * + * @param text Text. + * @param startPos Start position. + * @param len Length. + * @return Parsed expression. + */ + private static String parseScalarFunctionExpression(String text, int startPos, int len) { + int pos = startPos; + int endPos = startPos + len; + final String errPrefix = "Malformed scalar function escape sequence."; + + while ((++pos < endPos) && Character.isWhitespace(text.charAt(pos))); + if (pos == endPos) + throw new IgniteException(errPrefix + " Expected function name."); + + int funcNamePos = pos; + while ((++pos < endPos) && Character.isAlphabetic(text.charAt(pos))); + if (pos == endPos) + throw new IgniteException(errPrefix + " Expected function parameter list: " + + substring(text, startPos, len)); + + String funcName = text.substring(funcNamePos, pos); + + switch (funcName.toUpperCase()) { + case "CONVERT": { + Matcher matcher = CONVERT_TYPE_PATTERN.matcher(text.substring(startPos, endPos)); + + if (!matcher.find()) + throw new IgniteException(errPrefix + " Invalid arguments :" + + substring(text, startPos, len)); + + return (text.substring(startPos, startPos + matcher.start(1)) + + OdbcUtils.getIgniteTypeFromOdbcType(matcher.group(1)) + + text.substring(startPos + matcher.end(1), startPos + len)).trim(); + } + default: + return substring(text, startPos, len).trim(); + } + } + + /** + * Append nested results. + * + * @param text Original text. + * @param startPos Start position. + * @param endPos End position. + * @param nestedRess Nested results. + * @return Result. + */ + private static String appendNested(String text, int startPos, int endPos, + LinkedList<OdbcEscapeParseResult> nestedRess) { + StringBuilder res = new StringBuilder(); + + int curPos = startPos; + + for (OdbcEscapeParseResult nestedRes : nestedRess) { + // Append text between current position and replace. + res.append(text, curPos, nestedRes.originalStart()); + + // Append replaced text. + res.append(nestedRes.result()); + + // Advance position. + curPos = nestedRes.originalStart() + nestedRes.originalLength(); + } + + // Append remainder. + res.append(text, curPos, endPos); + + return res.toString(); + } + + /** + * Perform "substring" using start position and length. + * + * @param text Text. + * @param startPos Start position. + * @param len Length. + * @return Substring. + */ + private static String substring(String text, int startPos, int len) { + assert validSubstring(text, startPos, len); + + return text.substring(startPos, startPos + len); + } + + /** + * Check whether substring is valid. + * + * @param text Substring. + * @param startPos Start position. + * @param len Length. + * @return {@code True} if valid. + */ + private static boolean validSubstring(String text, int startPos, int len) { + return text != null && startPos + len <= text.length(); + } + + /** + * Private constructor. + */ + private OdbcEscapeUtils() { + // No-op. + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/4e5a82d6/modules/core/src/main/resources/META-INF/classnames.properties ---------------------------------------------------------------------- diff --git a/modules/core/src/main/resources/META-INF/classnames.properties b/modules/core/src/main/resources/META-INF/classnames.properties index ca5f756..8349687 100644 --- a/modules/core/src/main/resources/META-INF/classnames.properties +++ b/modules/core/src/main/resources/META-INF/classnames.properties @@ -1220,7 +1220,7 @@ org.apache.ignite.internal.processors.marshaller.MarshallerMappingItem org.apache.ignite.internal.processors.marshaller.MissingMappingRequestMessage org.apache.ignite.internal.processors.marshaller.MissingMappingResponseMessage org.apache.ignite.internal.processors.odbc.OdbcProtocolVersion -org.apache.ignite.internal.processors.odbc.escape.OdbcEscapeType +org.apache.ignite.internal.processors.odbc.odbc.escape.OdbcEscapeType org.apache.ignite.internal.processors.platform.PlatformAbstractConfigurationClosure org.apache.ignite.internal.processors.platform.PlatformAbstractPredicate org.apache.ignite.internal.processors.platform.PlatformEventFilterListener http://git-wip-us.apache.org/repos/asf/ignite/blob/4e5a82d6/modules/core/src/test/java/org/apache/ignite/internal/processors/odbc/OdbcEscapeSequenceSelfTest.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/odbc/OdbcEscapeSequenceSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/odbc/OdbcEscapeSequenceSelfTest.java index ecb6c2d..c08c40c 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/odbc/OdbcEscapeSequenceSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/odbc/OdbcEscapeSequenceSelfTest.java @@ -18,7 +18,7 @@ package org.apache.ignite.internal.processors.odbc; import org.apache.ignite.IgniteException; -import org.apache.ignite.internal.processors.odbc.escape.OdbcEscapeUtils; +import org.apache.ignite.internal.processors.odbc.odbc.escape.OdbcEscapeUtils; import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; http://git-wip-us.apache.org/repos/asf/ignite/blob/4e5a82d6/modules/platforms/cpp/odbc-test/src/configuration_test.cpp ---------------------------------------------------------------------- diff --git a/modules/platforms/cpp/odbc-test/src/configuration_test.cpp b/modules/platforms/cpp/odbc-test/src/configuration_test.cpp index 3893ad9..d182f93 100644 --- a/modules/platforms/cpp/odbc-test/src/configuration_test.cpp +++ b/modules/platforms/cpp/odbc-test/src/configuration_test.cpp @@ -276,8 +276,7 @@ BOOST_AUTO_TEST_CASE(TestConnectStringInvalidVersion) BOOST_AUTO_TEST_CASE(TestConnectStringValidVersion) { - CheckValidProtocolVersion("Protocol_Version=1.6.0;", ignite::odbc::ProtocolVersion::VERSION_1_6_0); - CheckValidProtocolVersion("Protocol_Version=1.8.0;", ignite::odbc::ProtocolVersion::VERSION_1_8_0); + CheckValidProtocolVersion("Protocol_Version=2.0.0;", ignite::odbc::ProtocolVersion::VERSION_2_0_0); } BOOST_AUTO_TEST_CASE(TestConnectStringInvalidBoolKeys) http://git-wip-us.apache.org/repos/asf/ignite/blob/4e5a82d6/modules/platforms/cpp/odbc-test/src/queries_test.cpp ---------------------------------------------------------------------- diff --git a/modules/platforms/cpp/odbc-test/src/queries_test.cpp b/modules/platforms/cpp/odbc-test/src/queries_test.cpp index fdf3503..0f9eaee 100644 --- a/modules/platforms/cpp/odbc-test/src/queries_test.cpp +++ b/modules/platforms/cpp/odbc-test/src/queries_test.cpp @@ -378,14 +378,9 @@ BOOST_AUTO_TEST_CASE(TestLegacyConnection) Connect("DRIVER={Apache Ignite};SERVER=127.0.0.1;PORT=11110;CACHE=cache"); } -BOOST_AUTO_TEST_CASE(TestConnectionProtocolVersion_1_6_0) +BOOST_AUTO_TEST_CASE(TestConnectionProtocolVERSION_2_0_0) { - Connect("DRIVER={Apache Ignite};ADDRESS=127.0.0.1:11110;CACHE=cache;PROTOCOL_VERSION=1.6.0"); -} - -BOOST_AUTO_TEST_CASE(TestConnectionProtocolVersion_1_8_0) -{ - Connect("DRIVER={Apache Ignite};ADDRESS=127.0.0.1:11110;CACHE=cache;PROTOCOL_VERSION=1.8.0"); + Connect("DRIVER={Apache Ignite};ADDRESS=127.0.0.1:11110;CACHE=cache;PROTOCOL_VERSION=2.0.0"); } BOOST_AUTO_TEST_CASE(TestTwoRowsInt8) @@ -950,7 +945,6 @@ BOOST_AUTO_TEST_CASE(TestNullFields) BOOST_CHECK(ret == SQL_NO_DATA); } - BOOST_AUTO_TEST_CASE(TestDistributedJoins) { // Starting additional node. @@ -1025,58 +1019,6 @@ BOOST_AUTO_TEST_CASE(TestDistributedJoins) BOOST_CHECK_EQUAL(rowsNum, entriesNum); } -BOOST_AUTO_TEST_CASE(TestDistributedJoinsWithOldVersion) -{ - // Starting additional node. - Ignite node1 = StartAdditionalNode("Node1"); - Ignite node2 = StartAdditionalNode("Node2"); - - const int entriesNum = 1000; - - // Filling cache with data. - for (int i = 0; i < entriesNum; ++i) - { - TestType entry; - - entry.i32Field = i; - entry.i64Field = entriesNum - i - 1; - - cache1.Put(i, entry); - } - - Connect("DRIVER={Apache Ignite};ADDRESS=127.0.0.1:11110;CACHE=cache;DISTRIBUTED_JOINS=true;PROTOCOL_VERSION=1.6.0"); - - SQLRETURN ret; - - const size_t columnsCnt = 2; - - SQLBIGINT columns[columnsCnt] = { 0 }; - - // Binding colums. - for (SQLSMALLINT i = 0; i < columnsCnt; ++i) - { - ret = SQLBindCol(stmt, i + 1, SQL_C_SLONG, &columns[i], 0, 0); - - if (!SQL_SUCCEEDED(ret)) - BOOST_FAIL(GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt)); - } - - SQLCHAR request[] = - "SELECT T0.i32Field, T1.i64Field FROM TestType AS T0 " - "INNER JOIN TestType AS T1 " - "ON (T0.i32Field = T1.i64Field)"; - - ret = SQLExecDirect(stmt, request, SQL_NTS); - - if (!SQL_SUCCEEDED(ret)) - BOOST_FAIL(GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt)); - - int rowsNum = CountRows(stmt); - - BOOST_CHECK_GT(rowsNum, 0); - BOOST_CHECK_LT(rowsNum, entriesNum); -} - BOOST_AUTO_TEST_CASE(TestInsertSelect) { Connect("DRIVER={Apache Ignite};ADDRESS=127.0.0.1:11110;CACHE=cache"); http://git-wip-us.apache.org/repos/asf/ignite/blob/4e5a82d6/modules/platforms/cpp/odbc/include/ignite/odbc/protocol_version.h ---------------------------------------------------------------------- diff --git a/modules/platforms/cpp/odbc/include/ignite/odbc/protocol_version.h b/modules/platforms/cpp/odbc/include/ignite/odbc/protocol_version.h index d3d288e..bb42dd4 100644 --- a/modules/platforms/cpp/odbc/include/ignite/odbc/protocol_version.h +++ b/modules/platforms/cpp/odbc/include/ignite/odbc/protocol_version.h @@ -37,11 +37,8 @@ namespace ignite /** Version to string map type alias. */ typedef std::map<ProtocolVersion, std::string> VersionToStringMap; - /** First version of the protocol that was introduced in Ignite 1.6.0. */ - static const ProtocolVersion VERSION_1_6_0; - - /** First version of the protocol that was introduced in Ignite 1.8.0. */ - static const ProtocolVersion VERSION_1_8_0; + /** First version of the protocol that was introduced in Ignite 2.0.0. */ + static const ProtocolVersion VERSION_2_0_0; /** Unknown version of the protocol. */ static const ProtocolVersion VERSION_UNKNOWN; @@ -93,13 +90,6 @@ namespace ignite bool IsUnknown() const; /** - * Check if the distributed joins supported. - * - * @retuen True if the distributed joins supported. - */ - bool IsDistributedJoinsSupported() const; - - /** * Comparison operator. * * @param val1 First value. http://git-wip-us.apache.org/repos/asf/ignite/blob/4e5a82d6/modules/platforms/cpp/odbc/os/win/src/system/ui/dsn_configuration_window.cpp ---------------------------------------------------------------------- diff --git a/modules/platforms/cpp/odbc/os/win/src/system/ui/dsn_configuration_window.cpp b/modules/platforms/cpp/odbc/os/win/src/system/ui/dsn_configuration_window.cpp index c839999..4a88052 100644 --- a/modules/platforms/cpp/odbc/os/win/src/system/ui/dsn_configuration_window.cpp +++ b/modules/platforms/cpp/odbc/os/win/src/system/ui/dsn_configuration_window.cpp @@ -159,12 +159,6 @@ namespace ignite enforceJoinOrderCheckBox = CreateCheckBox(editPosX + checkBoxSize + interval, rowPos, checkBoxSize, rowSize, "Enforce Join Order", ChildId::ENFORCE_JOIN_ORDER_CHECK_BOX, config.IsEnforceJoinOrder()); - if (!config.GetProtocolVersion().IsDistributedJoinsSupported()) - { - distributedJoinsCheckBox->SetEnabled(false); - enforceJoinOrderCheckBox->SetEnabled(false); - } - rowPos += interval * 2 + rowSize; connectionSettingsGroupBox = CreateGroupBox(margin, sectionBegin, width - 2 * margin, @@ -206,31 +200,6 @@ namespace ignite break; } - case ChildId::PROTOCOL_VERSION_COMBO_BOX: - { - if (HIWORD(wParam) == CBN_SELCHANGE) - { - std::string text; - - protocolVersionComboBox->GetText(text); - - ProtocolVersion version = ProtocolVersion::FromString(text); - - if (!version.IsUnknown() && !version.IsDistributedJoinsSupported()) - { - distributedJoinsCheckBox->SetEnabled(false); - enforceJoinOrderCheckBox->SetEnabled(false); - } - else - { - distributedJoinsCheckBox->SetEnabled(true); - enforceJoinOrderCheckBox->SetEnabled(true); - } - } - - break; - } - case IDCANCEL: case ChildId::CANCEL_BUTTON: { @@ -253,6 +222,7 @@ namespace ignite break; } + case ChildId::PROTOCOL_VERSION_COMBO_BOX: default: return false; } http://git-wip-us.apache.org/repos/asf/ignite/blob/4e5a82d6/modules/platforms/cpp/odbc/src/protocol_version.cpp ---------------------------------------------------------------------- diff --git a/modules/platforms/cpp/odbc/src/protocol_version.cpp b/modules/platforms/cpp/odbc/src/protocol_version.cpp index ebd3b6a..859135d 100644 --- a/modules/platforms/cpp/odbc/src/protocol_version.cpp +++ b/modules/platforms/cpp/odbc/src/protocol_version.cpp @@ -25,21 +25,18 @@ namespace ignite { namespace odbc { - const ProtocolVersion ProtocolVersion::VERSION_1_6_0(1); - const ProtocolVersion ProtocolVersion::VERSION_1_8_0(MakeVersion(1,8,0)); + const ProtocolVersion ProtocolVersion::VERSION_2_0_0(MakeVersion(2,0,0)); const ProtocolVersion ProtocolVersion::VERSION_UNKNOWN(INT64_MIN); ProtocolVersion::StringToVersionMap::value_type s2vInitVals[] = { - std::make_pair("1.6.0", ProtocolVersion::VERSION_1_6_0), - std::make_pair("1.8.0", ProtocolVersion::VERSION_1_8_0) + std::make_pair("2.0.0", ProtocolVersion::VERSION_2_0_0) }; const ProtocolVersion::StringToVersionMap ProtocolVersion::stringToVersionMap(s2vInitVals, s2vInitVals + (sizeof(s2vInitVals) / sizeof(s2vInitVals[0]))); ProtocolVersion::VersionToStringMap::value_type v2sInitVals[] = { - std::make_pair(ProtocolVersion::VERSION_1_6_0, "1.6.0"), - std::make_pair(ProtocolVersion::VERSION_1_8_0, "1.8.0") + std::make_pair(ProtocolVersion::VERSION_2_0_0, "2.0.0") }; const ProtocolVersion::VersionToStringMap ProtocolVersion::versionToStringMap(v2sInitVals, @@ -64,7 +61,7 @@ namespace ignite const ProtocolVersion& ProtocolVersion::GetCurrent() { - return VERSION_1_8_0; + return VERSION_2_0_0; } ProtocolVersion ProtocolVersion::FromString(const std::string& version) @@ -106,11 +103,6 @@ namespace ignite return *this == VERSION_UNKNOWN; } - bool ProtocolVersion::IsDistributedJoinsSupported() const - { - return *this >= VERSION_1_8_0; - } - bool operator==(const ProtocolVersion& val1, const ProtocolVersion& val2) { return val1.val == val2.val;