Commit e69bf055 authored by unknown's avatar unknown

Merge whalegate.ndb.mysql.com:/home/tomas/cge-5.1

into  whalegate.ndb.mysql.com:/home/tomas/mysql-5.1-new-ndb-bj


storage/ndb/src/kernel/blocks/ERROR_codes.txt:
  manual merge
parents 20a7ea72 78ac242f
......@@ -139,6 +139,7 @@ public:
/**
* setField - Set bitfield at given position and length (max 32 bits)
* Note : length == 0 not supported.
*/
static void setField(unsigned size, Uint32 data[],
unsigned pos, unsigned len, Uint32 val);
......@@ -146,6 +147,7 @@ public:
/**
* getField - Get bitfield at given position and length
* Note : length == 0 not supported.
*/
static void getField(unsigned size, const Uint32 data[],
unsigned pos, unsigned len, Uint32 dst[]);
......@@ -918,6 +920,9 @@ BitmaskImpl::getField(unsigned size, const Uint32 src[],
unsigned pos, unsigned len, Uint32 dst[])
{
assert(pos + len <= (size << 5));
assert (len != 0);
if (len == 0)
return;
src += (pos >> 5);
Uint32 offset = pos & 31;
......@@ -937,6 +942,9 @@ BitmaskImpl::setField(unsigned size, Uint32 dst[],
unsigned pos, unsigned len, const Uint32 src[])
{
assert(pos + len <= (size << 5));
assert(len != 0);
if (len == 0)
return;
dst += (pos >> 5);
Uint32 offset = pos & 31;
......
......@@ -317,22 +317,32 @@ TCP_Transporter::doSend() {
// Empty the SendBuffers
bool sent_any = true;
while (m_sendBuffer.dataSize > 0)
{
const char * const sendPtr = m_sendBuffer.sendPtr;
const Uint32 sizeToSend = m_sendBuffer.sendDataSize;
if (sizeToSend > 0){
const int nBytesSent = send(theSocket, sendPtr, sizeToSend, 0);
if (nBytesSent > 0) {
if (nBytesSent > 0)
{
sent_any = true;
m_sendBuffer.bytesSent(nBytesSent);
sendCount ++;
sendSize += nBytesSent;
if(sendCount == reportFreq){
if(sendCount == reportFreq)
{
reportSendLen(get_callback_obj(), remoteNodeId, sendCount, sendSize);
sendCount = 0;
sendSize = 0;
}
} else {
}
else
{
if (nBytesSent < 0 && InetErrno == EAGAIN && sent_any)
break;
// Send failed
#if defined DEBUG_TRANSPORTER
g_eventLogger.error("Send Failure(disconnect==%d) to node = %d nBytesSent = %d "
......
This diff is collapsed.
......@@ -11,9 +11,10 @@ Next CMVMI 9000
Next BACKUP 10038
Next DBUTIL 11002
Next DBTUX 12008
Next SUMA 13034
Next SUMA 13036
Next LGMAN 15001
Next TSMAN 16001
Next SUMA 13034
TESTING NODE FAILURE, ARBITRATION
---------------------------------
......
......@@ -4908,6 +4908,21 @@ Suma::release_gci(Signal* signal, Uint32 buck, Uint32 gci)
if(gci >= head.m_max_gci)
{
jam();
if (ERROR_INSERTED(13034))
{
jam();
SET_ERROR_INSERT_VALUE(13035);
return;
}
if (ERROR_INSERTED(13035))
{
CLEAR_ERROR_INSERT_VALUE;
NodeReceiverGroup rg(CMVMI, c_nodes_in_nodegroup_mask);
rg.m_nodes.clear(getOwnNodeId());
signal->theData[0] = 9999;
sendSignal(rg, GSN_NDB_TAMPER, signal, 1, JBA);
return;
}
head.m_page_pos = 0;
head.m_max_gci = gci;
head.m_last_gci = 0;
......@@ -4979,7 +4994,6 @@ Suma::start_resend(Signal* signal, Uint32 buck)
if(min > max)
{
ndbrequire(pos.m_page_pos <= 2);
ndbrequire(pos.m_page_id == bucket->m_buffer_tail);
m_active_buckets.set(buck);
m_gcp_complete_rep_count ++;
......
/* Copyright (C) 2003 MySQL AB
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#ifndef ATRT_CLIENT_HPP
#define ATRT_CLIENT_HPP
#include <DbUtil.hpp>
class AtrtClient: public DbUtil {
public:
enum AtrtCommandType {
ATCT_CHANGE_VERSION= 1,
ATCT_RESET_PROC= 2
};
AtrtClient(const char* _user= "root",
const char* _password= "",
const char* _suffix= ".1.atrt");
AtrtClient(MYSQL*);
~AtrtClient();
// Command functions
bool changeVersion(int process_id, const char* process_args);
bool resetProc(int process_id);
// Query functions
bool getConnectString(int cluster_id, SqlResultSet& result);
bool getClusters(SqlResultSet& result);
bool getMgmds(int cluster_id, SqlResultSet& result);
bool getNdbds(int cluster_id, SqlResultSet& result);
private:
int writeCommand(AtrtCommandType _type,
const Properties& args);
bool readCommand(uint command_id,
SqlResultSet& result);
bool doCommand(AtrtCommandType _type,
const Properties& args);
};
#endif
......@@ -19,17 +19,11 @@
#ifndef DBUTIL_HPP
#define DBUTIL_HPP
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <NDBT.hpp>
#include <BaseString.hpp>
#include <Properties.hpp>
#include <Vector.hpp>
#include <mysql.h>
//include "rand.h"
#include <stdlib.h>
#include "BaseString.hpp"
#include "NDBT.hpp"
//#define DEBUG
#define DIE_UNLESS(expr) \
......@@ -52,8 +46,41 @@ if (r) \
DIE_UNLESS(r == 0);\
}
#define DBU_TRUE 1
#define DBU_FALSE 0
class SqlResultSet : public Properties {
public:
// Get row with number
bool get_row(int row_num);
// Load next row
bool next(void);
// Reset iterator
void reset(void);
// Remove current row from resultset
void remove();
SqlResultSet();
~SqlResultSet();
const char* column(const char* col_name);
uint columnAsInt(const char* col_name);
uint insertId();
uint affectedRows();
uint numRows(void);
uint mysqlErrno();
const char* mysqlError();
const char* mysqlSqlstate();
private:
uint get_int(const char* name);
const char* get_string(const char* name);
const Properties* m_curr_row;
uint m_curr_row_num;
};
#define DBU_FAILED 1
#define DBU_OK 0
......@@ -61,11 +88,23 @@ class DbUtil
{
public:
/* Deprecated, see DbUtil(dbname, suffix) */
DbUtil(const char * databaseName);
DbUtil(const char* dbname, const char* suffix = NULL);
DbUtil(MYSQL* mysql);
DbUtil(const char* dbname = "mysql",
const char* user = "root",
const char* pass = "",
const char* suffix = NULL);
~DbUtil();
bool doQuery(const char* query);
bool doQuery(const char* query, SqlResultSet& result);
bool doQuery(const char* query, const Properties& args, SqlResultSet& result);
bool doQuery(BaseString& str);
bool doQuery(BaseString& str, SqlResultSet& result);
bool doQuery(BaseString& str, const Properties& args, SqlResultSet& result);
bool waitConnected(int timeout);
/* Deprecated, see connect() */
void databaseLogin(const char * system,
const char * usr,
......@@ -79,25 +118,35 @@ public:
const char * getPassword(){return m_pass.c_str();};
const char * getHost() {return m_host.c_str();};
const char * getSocket() {return m_socket.c_str();};
const char * getServerType(){return mysql_get_server_info(mysql);};
const char * getServerType(){return mysql_get_server_info(m_mysql);};
const char * getError();
MYSQL * getMysql(){return mysql;};
MYSQL * getMysql(){return m_mysql;};
MYSQL_STMT * STDCALL mysqlSimplePrepare(const char *query);
void databaseLogout();
void mysqlCloseStmHandle(MYSQL_STMT *my_stmt);
int connect();
void disconnect();
int selectDb();
int selectDb(const char *);
int createDb(BaseString&);
int doQuery(BaseString&);
int doQuery(const char *);
int getErrorNumber();
unsigned long selectCountTable(const char * table);
protected:
bool runQuery(const char* query,
const Properties& args,
SqlResultSet& rows);
bool isConnected();
MYSQL * m_mysql;
bool m_free_mysql; /* Don't free mysql* if allocated elsewhere */
private:
bool m_connected;
......@@ -107,15 +156,11 @@ private:
BaseString m_pass; // MySQL User Password
BaseString m_dbname; // Database to use
BaseString m_socket; // MySQL Server Unix Socket
BaseString default_file;
BaseString default_group;
BaseString m_default_file;
BaseString m_default_group;
unsigned int m_port; // MySQL Server port
MYSQL * mysql;
MYSQL_RES * m_result;
MYSQL_ROW m_row;
void setDbName(const char * name){m_dbname.assign(name);};
void setUser(const char * user_name){m_user.assign(user_name);};
void setPassword(const char * password){m_pass.assign(password);};
......
......@@ -52,7 +52,9 @@ testBitfield \
DbCreate DbAsyncGenerator \
testSRBank \
test_event_merge \
testIndexStat
testIndexStat \
testNDBT \
NdbRepStress
EXTRA_PROGRAMS = \
test_event \
......@@ -98,7 +100,10 @@ ndbapi_slow_select_SOURCES = slow_select.cpp
testReadPerf_SOURCES = testReadPerf.cpp
testLcp_SOURCES = testLcp.cpp
testPartitioning_SOURCES = testPartitioning.cpp
testNDBT_SOURCES = testNDBT.cpp
testNDBT_LDADD = $(LDADD) $(top_srcdir)/libmysql_r/libmysqlclient_r.la
testBitfield_SOURCES = testBitfield.cpp
NdbRepStress_SOURCES = acrt/NdbRepStress.cpp
DbCreate_SOURCES = bench/mainPopulate.cpp bench/dbPopulate.cpp bench/userInterface.cpp bench/dbPopulate.h bench/userInterface.h bench/testData.h bench/testDefinitions.h bench/ndb_schema.hpp bench/ndb_error.hpp
DbAsyncGenerator_SOURCES = bench/mainAsyncGenerator.cpp bench/asyncGenerator.cpp bench/ndb_async2.cpp bench/dbGenerator.h bench/macros.h bench/userInterface.h bench/testData.h bench/testDefinitions.h bench/ndb_schema.hpp bench/ndb_error.hpp
testSRBank_SOURCES = testSRBank.cpp
......@@ -115,8 +120,10 @@ include $(top_srcdir)/storage/ndb/config/type_ndbapitest.mk.am
##testIndex_INCLUDES = $(INCLUDES) -I$(top_srcdir)/ndb/include/kernel
##testSystemRestart_INCLUDES = $(INCLUDES) -I$(top_srcdir)/ndb/include/kernel
##testTransactions_INCLUDES = $(INCLUDES) -I$(top_srcdir)/ndb/include/kernel
NdbRepStress_INCLUDES = $(INCLUDES) -I$(top_srcdir)/ndb/test/include -I$(top_srcdir)/include
testBackup_LDADD = $(LDADD) bank/libbank.a
testSRBank_LDADD = bank/libbank.a $(LDADD)
NdbRepStress_LDADD = $(LDADD) $(top_builddir)/libmysql_r/libmysqlclient_r.la
# Don't update the files from bitkeeper
%::SCCS/s.%
......
This diff is collapsed.
/* Copyright (C) 2003 MySQL AB
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include <NDBT.hpp>
#include <NDBT_Test.hpp>
#include <DbUtil.hpp>
#include <AtrtClient.hpp>
int runTestAtrtClient(NDBT_Context* ctx, NDBT_Step* step){
AtrtClient atrt;
SqlResultSet clusters;
if (!atrt.getClusters(clusters))
return NDBT_FAILED;
int i= 0;
while(clusters.next())
{
ndbout << clusters.column("name") << endl;
if (i++ == 1){
ndbout << "removing: " << clusters.column("name") << endl;
clusters.remove();
}
}
clusters.reset();
while(clusters.next())
{
ndbout << clusters.column("name") << endl;
}
return NDBT_OK;
}
int runTestDbUtil(NDBT_Context* ctx, NDBT_Step* step){
DbUtil sql;
{
// Select all rows from mysql.user
SqlResultSet result;
if (!sql.doQuery("SELECT * FROM mysql.user", result))
return NDBT_FAILED;
// result.print();
while(result.next())
{
ndbout << result.column("host") << ", "
<< result.column("uSer") << ", "
<< result.columnAsInt("max_updates") << ", "
<< endl;
}
result.reset();
while(result.next())
{
ndbout << result.column("host") << endl;
}
}
{
// No column name, query should fail
Properties args;
SqlResultSet result;
if (sql.doQuery("SELECT * FROM mysql.user WHERE name=?", args, result))
return NDBT_FAILED;
result.print();
}
{
// Select nonexisiting rows from mysql.user
Properties args;
SqlResultSet result;
args.put("0", "no_such_host");
if (!sql.doQuery("SELECT * FROM mysql.user WHERE host=?", args, result))
return NDBT_FAILED;
ndbout << "no rows" << endl;
result.print();
// Change args to an find one row
args.clear();
args.put("0", "localhost");
if (!sql.doQuery("SELECT host, user FROM mysql.user WHERE host=?",
args, result))
return NDBT_FAILED;
result.print();
}
{
if (!sql.doQuery("CREATE TABLE sql_client_test (a int, b varchar(255))"))
return NDBT_FAILED;
if (!sql.doQuery("INSERT INTO sql_client_test VALUES(1, 'hello'), (2, 'bye')"))
return NDBT_FAILED;
// Select all rows from sql_client_test
SqlResultSet result;
if (!sql.doQuery("SELECT * FROM sql_client_test", result))
return NDBT_FAILED;
// result.print();
while(result.next())
{
}
// Select second row from sql_client_test
Properties args;
args.put("0", 2);
if (!sql.doQuery("SELECT * FROM sql_client_test WHERE a=?", args,result))
return NDBT_FAILED;
result.print();
result.reset();
while(result.next())
{
ndbout << "a: " << result.columnAsInt("a") << endl;
ndbout << "b: " << result.column("b") << endl;
if (result.columnAsInt("a") != 2){
ndbout << "hepp1" << endl;
return NDBT_FAILED;
}
if (strcmp(result.column("b"), "bye")){
ndbout << "hepp2" << endl;
return NDBT_FAILED;
}
}
if (sql.selectCountTable("sql_client_test") != 2)
{
ndbout << "Got wrong count" << endl;
return NDBT_FAILED;
}
if (!sql.doQuery("DROP TABLE sql_client_test"))
return NDBT_FAILED;
}
return NDBT_OK;
}
NDBT_TESTSUITE(testNDBT);
TESTCASE("AtrtClient",
"Test AtrtClient class"){
INITIALIZER(runTestAtrtClient);
}
TESTCASE("DbUtil",
"Test DbUtil class"){
INITIALIZER(runTestDbUtil);
}
NDBT_TESTSUITE_END(testNDBT);
int main(int argc, const char** argv){
ndb_init();
return testNDBT.execute(argc, argv);
}
......@@ -1838,6 +1838,61 @@ runBug31701(NDBT_Context* ctx, NDBT_Step* step)
return NDBT_OK;
}
int
runBug33793(NDBT_Context* ctx, NDBT_Step* step)
{
int result = NDBT_OK;
int loops = ctx->getNumLoops();
NdbRestarter restarter;
if (restarter.getNumDbNodes() < 2){
ctx->stopTest();
return NDBT_OK;
}
// This should really wait for applier to start...10s is likely enough
NdbSleep_SecSleep(10);
while (loops-- && ctx->isTestStopped() == false)
{
int nodeId = restarter.getDbNodeId(rand() % restarter.getNumDbNodes());
int nodecount = 0;
int nodes[255];
printf("nodeid: %u : victims: ", nodeId);
for (int i = 0; i<restarter.getNumDbNodes(); i++)
{
int id = restarter.getDbNodeId(i);
if (id == nodeId)
continue;
if (restarter.getNodeGroup(id) == restarter.getNodeGroup(nodeId))
{
nodes[nodecount++] = id;
printf("%u ", id);
int val2[] = { DumpStateOrd::CmvmiSetRestartOnErrorInsert, 1 };
if (restarter.dumpStateOneNode(id, val2, 2))
return NDBT_FAILED;
}
}
printf("\n"); fflush(stdout);
restarter.insertErrorInNode(nodeId, 13034);
if (restarter.waitNodesNoStart(nodes, nodecount))
return NDBT_FAILED;
if (restarter.startNodes(nodes, nodecount))
return NDBT_FAILED;
if (restarter.waitClusterStarted())
return NDBT_FAILED;
}
ctx->stopTest();
return NDBT_OK;
}
NDBT_TESTSUITE(test_event);
TESTCASE("BasicEventOperation",
"Verify that we can listen to Events"
......@@ -1975,6 +2030,12 @@ TESTCASE("Bug31701", ""){
FINALIZER(runDropEvent);
FINALIZER(runDropShadowTable);
}
TESTCASE("Bug33793", ""){
INITIALIZER(runCreateEvent);
STEP(runEventListenerUntilStopped);
STEP(runBug33793);
FINALIZER(runDropEvent);
}
NDBT_TESTSUITE_END(test_event);
int main(int argc, const char** argv){
......
......@@ -1046,3 +1046,7 @@ max-time: 300
cmd: testSystemRestart
args: -n Bug22696 T1
max-time: 300
cmd: test_event
args: -n Bug33793 T1
/* Copyright (C) 2008 MySQL AB
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include <AtrtClient.hpp>
#include <NDBT_Output.hpp>
#include <NdbSleep.h>
AtrtClient::AtrtClient(const char* _user,
const char* _password,
const char* _group_suffix)
: DbUtil(_user, _password, _group_suffix)
{
}
AtrtClient::AtrtClient(MYSQL* mysql)
: DbUtil(mysql)
{
}
AtrtClient::~AtrtClient(){
}
int
AtrtClient::writeCommand(AtrtCommandType _type,
const Properties& args){
if (!isConnected())
return false;
BaseString sql;
sql.assfmt("INSERT command ( ");
const char* name;
{
Properties::Iterator iter(&args);
while((name= iter.next())){
sql.appfmt("%s, ", name);
}
}
sql.appfmt(" state, cmd) VALUES (");
{
Properties::Iterator iter(&args);
while((name= iter.next())){
PropertiesType t;
Uint32 val_i;
BaseString val_s;
args.getTypeOf(name, &t);
switch(t) {
case PropertiesType_Uint32:
args.get(name, &val_i);
sql.appfmt("%d, ", val_i);
break;
case PropertiesType_char:
args.get(name, val_s);
sql.appfmt("'%s', ", val_s.c_str());
break;
default:
assert(false);
break;
}
}
}
sql.appfmt("'new', %d)", _type);
if (!doQuery(sql)){
return -1;
}
return mysql_insert_id(m_mysql);
}
bool
AtrtClient::readCommand(uint command_id,
SqlResultSet& result){
Properties args;
args.put("0", command_id);
return runQuery("SELECT * FROM command WHERE id = ?",
args,
result);
}
bool
AtrtClient::doCommand(AtrtCommandType type,
const Properties& args){
int running_timeout= 10;
int total_timeout= 120;
int commandId= writeCommand(type,
args);
if (commandId == -1){
g_err << "Failed to write command" << endl;
return false;
}
while (true){
SqlResultSet result;
if (!readCommand(commandId, result))
{
result.print();
g_err << "Failed to read command "<< commandId << endl;
return false;
}
// Get first row
result.next();
// Check if command has completed
BaseString state(result.column("state"));
if (state == "done") {
return true;
}
if (state == "new"){
if (!running_timeout--){
g_err << "Timeout while waiting for command "
<< commandId << " to start run" << endl;
return false;
}
}
else if (!total_timeout--){
g_err << "Timeout while waiting for result of command "
<< commandId << endl;
return false;
}
NdbSleep_SecSleep(1);
}
return false;
}
bool
AtrtClient::changeVersion(int process_id,
const char* process_args){
Properties args;
args.put("process_id", process_id);
args.put("process_args", process_args);
return doCommand(ATCT_CHANGE_VERSION, args);
}
bool
AtrtClient::resetProc(int process_id){
Properties args;
args.put("process_id", process_id);
return doCommand(ATCT_RESET_PROC, args);
}
bool
AtrtClient::getConnectString(int cluster_id, SqlResultSet& result){
Properties args;
args.put("0", cluster_id);
return doQuery("SELECT value as connectstring " \
"FROM cluster c, process p, host h, options o " \
"WHERE c.id=p.cluster_id AND p.host_id=h.id AND " \
"p.id=o.process_id AND c.id=? AND " \
"o.name='--ndb-connectstring=' AND type='ndb_mgmd'",
args,
result);
}
bool
AtrtClient::getClusters(SqlResultSet& result){
Properties args;
return runQuery("SELECT id, name FROM cluster WHERE name != '.atrt'",
args,
result);
}
bool
AtrtClient::getMgmds(int cluster_id, SqlResultSet& result){
Properties args;
args.put("0", cluster_id);
return runQuery("SELECT * FROM process WHERE cluster_id=? and type='ndb_mgmd'",
args,
result);
}
bool
AtrtClient::getNdbds(int cluster_id, SqlResultSet& result){
Properties args;
args.put("0", cluster_id);
return runQuery("SELECT * FROM process WHERE cluster_id=? and type='ndbd'",
args,
result);
}
This diff is collapsed.
......@@ -23,10 +23,10 @@ libNDBT_a_SOURCES = \
HugoAsynchTransactions.cpp UtilTransactions.cpp \
NdbRestarter.cpp NdbRestarts.cpp NDBT_Output.cpp \
NdbBackup.cpp NdbConfig.cpp NdbGrep.cpp NDBT_Table.cpp \
NdbSchemaCon.cpp NdbSchemaOp.cpp getarg.c \
CpcClient.cpp NdbMixRestarter.cpp NDBT_Thread.cpp dbutil.cpp
NdbSchemaCon.cpp NdbSchemaOp.cpp getarg.c AtrtClient.cpp \
CpcClient.cpp NdbMixRestarter.cpp NDBT_Thread.cpp DbUtil.cpp
INCLUDES_LOC = -I$(top_srcdir)/storage/ndb/src/common/mgmcommon -I$(top_srcdir)/storage/ndb/include/mgmcommon -I$(top_srcdir)/storage/ndb/include/kernel -I$(top_srcdir)/storage/ndb/src/mgmapi
INCLUDES_LOC = -I$(top_srcdir)/storage/ndb/src/common/mgmcommon -I$(top_srcdir)/storage/ndb/include/mgmcommon -I$(top_srcdir)/storage/ndb/include/kernel -I$(top_srcdir)/storage/ndb/src/mgmapi -I$(top_srcdir)/include
include $(top_srcdir)/storage/ndb/config/common.mk.am
include $(top_srcdir)/storage/ndb/config/type_ndbapitest.mk.am
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment