ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sboi...@apache.org
Subject [01/12] ignite git commit: IGNITE-6326: JDBC thin driver: as of now error is thrown before query execution in case of type mismatch (executeQuery, executeUpdate). This closes #2631.
Date Wed, 13 Sep 2017 08:06:45 GMT
Repository: ignite
Updated Branches:
  refs/heads/ignite-6149 072322f90 -> 16e52541e


IGNITE-6326: JDBC thin driver: as of now error is thrown before query execution in case of
type mismatch (executeQuery, executeUpdate). This closes #2631.


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/acd78616
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/acd78616
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/acd78616

Branch: refs/heads/ignite-6149
Commit: acd786160f612e300b82ac95d5470cd50a431622
Parents: 25f8c57
Author: tledkov-gridgain <tledkov@gridgain.com>
Authored: Tue Sep 12 10:52:04 2017 +0300
Committer: devozerov <vozerov@gridgain.com>
Committed: Tue Sep 12 10:52:04 2017 +0300

----------------------------------------------------------------------
 .../ignite/jdbc/thin/JdbcThinBatchSelfTest.java |  2 +-
 .../jdbc/thin/JdbcThinStatementSelfTest.java    | 41 ++++++++++++++++++++
 .../jdbc/thin/JdbcThinPreparedStatement.java    | 12 +++---
 .../internal/jdbc/thin/JdbcThinStatement.java   | 12 +++---
 .../internal/jdbc/thin/JdbcThinTcpIo.java       |  6 ++-
 .../internal/jdbc2/JdbcSqlFieldsQuery.java      |  2 +-
 .../odbc/jdbc/JdbcQueryExecuteRequest.java      | 29 +++++++++++++-
 .../odbc/jdbc/JdbcRequestHandler.java           | 27 ++++++++++---
 .../processors/odbc/jdbc/JdbcStatementType.java | 32 +++++++++++++++
 9 files changed, 141 insertions(+), 22 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/acd78616/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinBatchSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinBatchSelfTest.java
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinBatchSelfTest.java
index 5781e00..5e2e39e 100644
--- a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinBatchSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinBatchSelfTest.java
@@ -173,7 +173,7 @@ public class JdbcThinBatchSelfTest extends JdbcThinAbstractDmlStatementSelfTest
             for (int i = 0; i < BATCH_SIZE; ++i)
                 assertEquals("Invalid update count",i + 1, updCnts[i]);
 
-            if (!e.getMessage().contains("Query produced result set [qry=select * from Person,
args=[]]")) {
+            if (!e.getMessage().contains("Given statement type does not match that declared
by JDBC driver")) {
                 log.error("Invalid exception: ", e);
 
                 fail();

http://git-wip-us.apache.org/repos/asf/ignite/blob/acd78616/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinStatementSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinStatementSelfTest.java
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinStatementSelfTest.java
index 97e3300..234a319 100644
--- a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinStatementSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinStatementSelfTest.java
@@ -1013,6 +1013,47 @@ public class JdbcThinStatementSelfTest extends JdbcThinAbstractSelfTest
{
             "Statement is closed");
     }
 
+    /**
+     * @throws Exception If failed.
+     */
+    public void testStatementTypeMismatchSelect() throws Exception {
+        GridTestUtils.assertThrows(log,
+            new Callable<Object>() {
+                @Override public Object call() throws Exception {
+                    stmt.executeUpdate("select 1;");
+
+                    return null;
+                }
+            },
+            SQLException.class,
+            "Given statement type does not match that declared by JDBC driver");
+
+        assert stmt.getResultSet() == null : "Not results expected. Last statement is executed
with exception";
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testStatementTypeMismatchUpdate() throws Exception {
+        GridTestUtils.assertThrows(log,
+            new Callable<Object>() {
+                @Override public Object call() throws Exception {
+                    stmt.executeQuery("update test set val=28 where _key=1");
+
+                    return null;
+                }
+            },
+            SQLException.class,
+            "Given statement type does not match that declared by JDBC driver");
+
+        ResultSet rs = stmt.executeQuery("select val from test where _key=1");
+
+        assert rs.next();
+        assert rs.getInt(1) == 1 : "The data must not be updated. " +
+            "Because update statement is executed via 'executeQuery' method." +
+            " Data [val=" + rs.getInt(1) + ']';
+    }
+
     /** */
     private void fillCache() {
         IgniteCache<String, Person> cachePerson = grid(0).cache(DEFAULT_CACHE_NAME);

http://git-wip-us.apache.org/repos/asf/ignite/blob/acd78616/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinPreparedStatement.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinPreparedStatement.java
b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinPreparedStatement.java
index 49a1029..5aa503c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinPreparedStatement.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinPreparedStatement.java
@@ -44,6 +44,7 @@ import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.processors.odbc.SqlListenerUtils;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcMetaParamsResult;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcQuery;
+import org.apache.ignite.internal.processors.odbc.jdbc.JdbcStatementType;
 
 /**
  * JDBC prepared statement implementation.
@@ -73,7 +74,7 @@ public class JdbcThinPreparedStatement extends JdbcThinStatement implements
Prep
 
     /** {@inheritDoc} */
     @Override public ResultSet executeQuery() throws SQLException {
-        executeWithArguments();
+        executeWithArguments(JdbcStatementType.SELECT_STATEMENT_TYPE);
 
         ResultSet rs = getResultSet();
 
@@ -90,7 +91,7 @@ public class JdbcThinPreparedStatement extends JdbcThinStatement implements
Prep
 
     /** {@inheritDoc} */
     @Override public int executeUpdate() throws SQLException {
-        executeWithArguments();
+        executeWithArguments(JdbcStatementType.UPDATE_STMT_TYPE);
 
         int res = getUpdateCount();
 
@@ -230,7 +231,7 @@ public class JdbcThinPreparedStatement extends JdbcThinStatement implements
Prep
 
     /** {@inheritDoc} */
     @Override public boolean execute() throws SQLException {
-        executeWithArguments();
+        executeWithArguments(JdbcStatementType.ANY_STATEMENT_TYPE);
 
         return rs.isQuery();
     }
@@ -238,10 +239,11 @@ public class JdbcThinPreparedStatement extends JdbcThinStatement implements
Prep
     /**
      * Execute query with arguments and nullify them afterwards.
      *
+     * @param stmtType Expected statement type.
      * @throws SQLException If failed.
      */
-    private void executeWithArguments() throws SQLException {
-        execute0(sql, args);
+    private void executeWithArguments(JdbcStatementType stmtType) throws SQLException {
+        execute0(stmtType, sql, args);
     }
 
     /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/acd78616/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinStatement.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinStatement.java
b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinStatement.java
index df77261..44477cd 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinStatement.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinStatement.java
@@ -33,6 +33,7 @@ import org.apache.ignite.internal.processors.odbc.SqlListenerResponse;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcBatchExecuteResult;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcQuery;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcQueryExecuteResult;
+import org.apache.ignite.internal.processors.odbc.jdbc.JdbcStatementType;
 
 import static java.sql.ResultSet.CONCUR_READ_ONLY;
 import static java.sql.ResultSet.FETCH_FORWARD;
@@ -90,7 +91,7 @@ public class JdbcThinStatement implements Statement {
 
     /** {@inheritDoc} */
     @Override public ResultSet executeQuery(String sql) throws SQLException {
-        execute0(sql, null);
+        execute0(JdbcStatementType.SELECT_STATEMENT_TYPE, sql, null);
 
         ResultSet rs = getResultSet();
 
@@ -101,12 +102,13 @@ public class JdbcThinStatement implements Statement {
     }
 
     /**
+     * @param stmtType Expected statement type.
      * @param sql Sql query.
      * @param args Query parameters.
      *
      * @throws SQLException Onj error.
      */
-    protected void execute0(String sql, List<Object> args) throws SQLException {
+    protected void execute0(JdbcStatementType stmtType, String sql, List<Object> args)
throws SQLException {
         ensureNotClosed();
 
         if (rs != null) {
@@ -121,7 +123,7 @@ public class JdbcThinStatement implements Statement {
             throw new SQLException("SQL query is empty.");
 
         try {
-            JdbcQueryExecuteResult res = conn.io().queryExecute(conn.getSchema(), pageSize,
maxRows,
+            JdbcQueryExecuteResult res = conn.io().queryExecute(stmtType, conn.getSchema(),
pageSize, maxRows,
                 sql, args);
 
             assert res != null;
@@ -141,7 +143,7 @@ public class JdbcThinStatement implements Statement {
 
     /** {@inheritDoc} */
     @Override public int executeUpdate(String sql) throws SQLException {
-        execute0(sql, null);
+        execute0(JdbcStatementType.UPDATE_STMT_TYPE, sql, null);
 
         int res = getUpdateCount();
 
@@ -246,7 +248,7 @@ public class JdbcThinStatement implements Statement {
     @Override public boolean execute(String sql) throws SQLException {
         ensureNotClosed();
 
-        execute0(sql, null);
+        execute0(JdbcStatementType.ANY_STATEMENT_TYPE, sql, null);
 
         return rs.isQuery();
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/acd78616/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java
b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java
index f29f8b1..9775254 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java
@@ -57,6 +57,7 @@ import org.apache.ignite.internal.processors.odbc.jdbc.JdbcQueryMetadataResult;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcRequest;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcResponse;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcResult;
+import org.apache.ignite.internal.processors.odbc.jdbc.JdbcStatementType;
 import org.apache.ignite.internal.util.ipc.loopback.IpcClientTcpEndpoint;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.lang.IgniteProductVersion;
@@ -315,6 +316,7 @@ public class JdbcThinTcpIo {
     }
 
     /**
+     * @param stmtType Expected statement type.
      * @param cache Cache name.
      * @param fetchSize Fetch size.
      * @param maxRows Max rows.
@@ -324,10 +326,10 @@ public class JdbcThinTcpIo {
      * @throws IOException On error.
      * @throws IgniteCheckedException On error.
      */
-    public JdbcQueryExecuteResult queryExecute(String cache, int fetchSize, int maxRows,
+    public JdbcQueryExecuteResult queryExecute(JdbcStatementType stmtType, String cache,
int fetchSize, int maxRows,
         String sql, List<Object> args)
         throws IOException, IgniteCheckedException {
-        return sendRequest(new JdbcQueryExecuteRequest(cache, fetchSize, maxRows, sql,
+        return sendRequest(new JdbcQueryExecuteRequest(stmtType, cache, fetchSize, maxRows,
sql,
             args == null ? null : args.toArray(new Object[args.size()])), DYNAMIC_SIZE_MSG_CAP);
     }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/acd78616/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcSqlFieldsQuery.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcSqlFieldsQuery.java
b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcSqlFieldsQuery.java
index 3d4eaba..d8b9a26 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcSqlFieldsQuery.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcSqlFieldsQuery.java
@@ -35,7 +35,7 @@ public final class JdbcSqlFieldsQuery extends SqlFieldsQuery {
      * @param sql SQL query.
      * @param isQry Flag indicating whether this object denotes a query or an update operation.
      */
-    JdbcSqlFieldsQuery(String sql, boolean isQry) {
+    public JdbcSqlFieldsQuery(String sql, boolean isQry) {
         super(sql);
         this.isQry = isQry;
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/acd78616/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcQueryExecuteRequest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcQueryExecuteRequest.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcQueryExecuteRequest.java
index 0b26dce..1c6262e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcQueryExecuteRequest.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcQueryExecuteRequest.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.processors.odbc.jdbc;
 
+import java.io.IOException;
 import org.apache.ignite.binary.BinaryObjectException;
 import org.apache.ignite.internal.binary.BinaryReaderExImpl;
 import org.apache.ignite.internal.binary.BinaryWriterExImpl;
@@ -47,6 +48,9 @@ public class JdbcQueryExecuteRequest extends JdbcRequest {
     @GridToStringInclude(sensitive = true)
     private Object[] args;
 
+    /** Expected statement type. */
+    private JdbcStatementType stmtType;
+
     /**
      */
     JdbcQueryExecuteRequest() {
@@ -54,14 +58,15 @@ public class JdbcQueryExecuteRequest extends JdbcRequest {
     }
 
     /**
+     * @param stmtType Expected statement type.
      * @param schemaName Cache name.
      * @param pageSize Fetch size.
      * @param maxRows Max rows.
      * @param sqlQry SQL query.
      * @param args Arguments list.
      */
-    public JdbcQueryExecuteRequest(String schemaName, int pageSize, int maxRows, String sqlQry,
-        Object[] args) {
+    public JdbcQueryExecuteRequest(JdbcStatementType stmtType, String schemaName, int pageSize,
int maxRows,
+        String sqlQry, Object[] args) {
         super(QRY_EXEC);
 
         this.schemaName = F.isEmpty(schemaName) ? null : schemaName;
@@ -69,6 +74,7 @@ public class JdbcQueryExecuteRequest extends JdbcRequest {
         this.maxRows = maxRows;
         this.sqlQry = sqlQry;
         this.args = args;
+        this.stmtType = stmtType;
     }
 
     /**
@@ -106,6 +112,13 @@ public class JdbcQueryExecuteRequest extends JdbcRequest {
         return schemaName;
     }
 
+    /**
+     * @return Expected statement type.
+     */
+    public JdbcStatementType expectedStatementType() {
+        return stmtType;
+    }
+
     /** {@inheritDoc} */
     @Override public void writeBinary(BinaryWriterExImpl writer) throws BinaryObjectException
{
         super.writeBinary(writer);
@@ -121,6 +134,8 @@ public class JdbcQueryExecuteRequest extends JdbcRequest {
             for (Object arg : args)
                 SqlListenerUtils.writeObject(writer, arg, false);
         }
+
+        writer.writeByte((byte)stmtType.ordinal());
     }
 
     /** {@inheritDoc} */
@@ -138,6 +153,16 @@ public class JdbcQueryExecuteRequest extends JdbcRequest {
 
         for (int i = 0; i < argsNum; ++i)
             args[i] = SqlListenerUtils.readObject(reader, false);
+
+        try {
+            if (reader.available() > 0)
+                stmtType = JdbcStatementType.values()[reader.readByte()];
+            else
+                stmtType = JdbcStatementType.ANY_STATEMENT_TYPE;
+        }
+        catch (IOException e) {
+            throw new BinaryObjectException(e);
+        }
     }
 
     /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/acd78616/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java
index d2508a6..3ccc195 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java
@@ -28,13 +28,13 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
-import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteLogger;
 import org.apache.ignite.cache.query.FieldsQueryCursor;
 import org.apache.ignite.cache.query.SqlFieldsQuery;
 import org.apache.ignite.internal.GridKernalContext;
 import org.apache.ignite.internal.IgniteVersionUtils;
 import org.apache.ignite.internal.binary.BinaryWriterExImpl;
+import org.apache.ignite.internal.jdbc2.JdbcSqlFieldsQuery;
 import org.apache.ignite.internal.processors.cache.QueryCursorImpl;
 import org.apache.ignite.internal.processors.odbc.SqlListenerRequest;
 import org.apache.ignite.internal.processors.odbc.SqlListenerRequestHandler;
@@ -223,7 +223,24 @@ public class JdbcRequestHandler implements SqlListenerRequestHandler
{
         try {
             String sql = req.sqlQuery();
 
-            SqlFieldsQuery qry = new SqlFieldsQuery(sql);
+            SqlFieldsQuery qry;
+
+            switch(req.expectedStatementType()) {
+                case ANY_STATEMENT_TYPE:
+                    qry = new SqlFieldsQuery(sql);
+
+                    break;
+
+                case SELECT_STATEMENT_TYPE:
+                    qry = new JdbcSqlFieldsQuery(sql, true);
+
+                    break;
+
+                default:
+                    assert req.expectedStatementType() == JdbcStatementType.UPDATE_STMT_TYPE;
+
+                    qry = new JdbcSqlFieldsQuery(sql, false);
+            }
 
             qry.setArgs(req.arguments());
 
@@ -389,7 +406,7 @@ public class JdbcRequestHandler implements SqlListenerRequestHandler {
                 if (q.sql() != null)
                     sql = q.sql();
 
-                SqlFieldsQuery qry = new SqlFieldsQuery(sql);
+                SqlFieldsQuery qry = new JdbcSqlFieldsQuery(sql, false);
 
                 qry.setArgs(q.args());
 
@@ -404,9 +421,7 @@ public class JdbcRequestHandler implements SqlListenerRequestHandler {
                 QueryCursorImpl<List<?>> qryCur = (QueryCursorImpl<List<?>>)ctx.query()
                     .querySqlFieldsNoCache(qry, true);
 
-                if (qryCur.isQuery())
-                    throw new IgniteCheckedException("Query produced result set [qry=" +
q.sql() + ", args=" +
-                        Arrays.toString(q.args()) + ']');
+                assert !qryCur.isQuery();
 
                 List<List<?>> items = qryCur.getAll();
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/acd78616/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcStatementType.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcStatementType.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcStatementType.java
new file mode 100644
index 0000000..aec2d12
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcStatementType.java
@@ -0,0 +1,32 @@
+/*
+ * 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.jdbc;
+
+/**
+ * JDBC statement type.
+ */
+public enum JdbcStatementType {
+    /** Any statement type. */
+    ANY_STATEMENT_TYPE,
+
+    /** Select statement type. */
+    SELECT_STATEMENT_TYPE,
+
+    /** DML / DDL statement type. */
+    UPDATE_STMT_TYPE;
+}


Mime
View raw message