Executing insert statement throws a Missing input stream exception
Describe the bug
Throw an exception when I execute the "insert into test FORMAT JSONEachRow {"project_name":"jenny","client_type":"iphone","age":11}" statement
exception: ` org.springframework.jdbc.UncategorizedSQLException:
Error updating database. Cause: java.sql.SQLException: Missing input stream
The error may exist in com/cxmdata/das/mapper/EventMapper.java (best guess)
The error may involve com.cxmdata.das.mapper.EventMapper.batchInsertByJson-Inline
The error occurred while setting parameters
SQL: insert into test FORMAT JSONEachRow {"project_name":"jenny","client_type":"iphone","age":11}
Cause: java.sql.SQLException: Missing input stream
; uncategorized SQLException; SQL state [HY000]; error code [0]; Missing input stream; nested exception is java.sql.SQLException: Missing input stream at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:89) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:88) at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440) at com.sun.proxy.$Proxy124.insert(Unknown Source) at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:271) at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:60) at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$PlainMethodInvoker.invoke(MybatisMapperProxy.java:148) at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89) at com.sun.proxy.$Proxy125.batchInsertByJson(Unknown Source) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266) at java.util.concurrent.FutureTask.run(FutureTask.java) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Caused by: java.sql.SQLException: Missing input stream at com.clickhouse.jdbc.SqlExceptionUtils.clientError(SqlExceptionUtils.java:73) at com.clickhouse.jdbc.internal.StreamBasedPreparedStatement.ensureParams(StreamBasedPreparedStatement.java:64) at com.clickhouse.jdbc.internal.StreamBasedPreparedStatement.execute(StreamBasedPreparedStatement.java:268) at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:3446) at com.alibaba.druid.filter.FilterEventAdapter.preparedStatement_execute(FilterEventAdapter.java:434) at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:3444) at com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl.execute(PreparedStatementProxyImpl.java:152) at com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement.java:483) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.apache.ibatis.logging.jdbc.PreparedStatementLogger.invoke(PreparedStatementLogger.java:59) at com.sun.proxy.$Proxy207.execute(Unknown Source) at org.apache.ibatis.executor.statement.PreparedStatementHandler.update(PreparedStatementHandler.java:47) at org.apache.ibatis.executor.statement.RoutingStatementHandler.update(RoutingStatementHandler.java:74) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:63) at com.sun.proxy.$Proxy205.update(Unknown Source) at com.baomidou.mybatisplus.core.executor.MybatisSimpleExecutor.doUpdate(MybatisSimpleExecutor.java:56) at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117) at com.baomidou.mybatisplus.core.executor.MybatisCachingExecutor.update(MybatisCachingExecutor.java:85) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.apache.ibatis.plugin.Invocation.proceed(Invocation.java:49) at com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor.intercept(MybatisPlusInterceptor.java:106) at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61) at com.sun.proxy.$Proxy204.update(Unknown Source) at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:197) at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:184) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:426) `
Steps to reproduce
clickhouse-jdbc version: 0.4.0~0.4.6 clickhouse-server version: 22.5.1.2079
mapper: ` public interface TestMapper {
@Insert("insert into test FORMAT JSONEachRow ${eventRecord}")
int batchInsertByJson(@Param("eventRecord") String eventRecord);
} ` This exception was thrown by StreamBasedPreparedStatement.ensueParams(), because value is null. I wonder if this execution statement has no parameters, resulting in value being null; But should this situation not validate the value, because the hasParameter was determined in the ClickHouseConnectionImpl. prepareStatement() method, and therefore the StreamBasedPreparedStatement was executed
ClickHouseConnectionImpl.prepareStatement() code: ` public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { ensureOpen();
ClickHouseConfig config = clientRequest.getConfig();
// TODO remove the extra parsing
ClickHouseSqlStatement[] stmts = parse(sql, config, clientRequest.getSettings());
if (stmts.length != 1) {
throw SqlExceptionUtils
.clientError("Prepared statement only supports one query but we got: " + stmts.length);
}
ClickHouseSqlStatement parsedStmt = stmts[0];
ClickHouseParameterizedQuery preparedQuery;
try {
preparedQuery = jdbcConf.useNamedParameter()
? ClickHouseParameterizedQuery.of(clientRequest.getConfig(), parsedStmt.getSQL())
: JdbcParameterizedQuery.of(config, parsedStmt.getSQL());
} catch (RuntimeException e) {
throw SqlExceptionUtils.clientError(e);
}
PreparedStatement ps = null;
// **preparedQuery.hasParameter() is false**
if (preparedQuery.hasParameter()) {
if (parsedStmt.hasTempTable() || parsedStmt.hasInput()) {
throw SqlExceptionUtils
.clientError(
"External table, input function, and query parameter cannot be used together in PreparedStatement.");
} else if (parsedStmt.getStatementType() == StatementType.INSERT &&
!parsedStmt.containsKeyword("SELECT") && parsedStmt.hasValues() &&
(!parsedStmt.hasFormat() || clientRequest.getFormat().name().equals(parsedStmt.getFormat()))) {
String query = parsedStmt.getSQL();
boolean useStream = false;
Integer startIndex = parsedStmt.getPositions().get(ClickHouseSqlStatement.KEYWORD_VALUES_START);
if (startIndex != null) {
useStream = true;
int endIndex = parsedStmt.getPositions().get(ClickHouseSqlStatement.KEYWORD_VALUES_END);
for (int i = startIndex + 1; i < endIndex; i++) {
char ch = query.charAt(i);
if (ch != '?' && ch != ',' && !Character.isWhitespace(ch)) {
useStream = false;
break;
}
}
}
if (useStream) {
ps = new InputBasedPreparedStatement(this,
clientRequest.write().query(query.substring(0, parsedStmt.getStartPosition("VALUES")),
newQueryId()),
getTableColumns(parsedStmt.getDatabase(), parsedStmt.getTable(),
parsedStmt.getContentBetweenKeywords(
ClickHouseSqlStatement.KEYWORD_TABLE_COLUMNS_START,
ClickHouseSqlStatement.KEYWORD_TABLE_COLUMNS_END)),
resultSetType, resultSetConcurrency, resultSetHoldability);
}
}
} else {
if (parsedStmt.hasTempTable()) {
// queries using external/temporary table
ps = new TableBasedPreparedStatement(this,
clientRequest.copy().query(parsedStmt.getSQL(), newQueryId()), parsedStmt,
resultSetType, resultSetConcurrency, resultSetHoldability);
} else if (parsedStmt.getStatementType() == StatementType.INSERT) {
if (!ClickHouseChecker.isNullOrBlank(parsedStmt.getInput())) {
// an ugly workaround of https://github.com/ClickHouse/ClickHouse/issues/39866
// would be replace JSON and Object('json') types in the query to String
Mutation m = clientRequest.write();
if (parsedStmt.hasFormat()) {
m.format(ClickHouseFormat.valueOf(parsedStmt.getFormat()));
}
// insert query using input function
ps = new InputBasedPreparedStatement(this, m.query(parsedStmt.getSQL(), newQueryId()),
ClickHouseColumn.parse(parsedStmt.getInput()), resultSetType, resultSetConcurrency,
resultSetHoldability);
} else if (!parsedStmt.containsKeyword("SELECT") && !parsedStmt.hasValues()) {
// **parsedStmt.hasFormat() is true**
ps = parsedStmt.hasFormat()
? new StreamBasedPreparedStatement(this,
clientRequest.write().query(parsedStmt.getSQL(), newQueryId()), parsedStmt,
resultSetType, resultSetConcurrency, resultSetHoldability)
: new InputBasedPreparedStatement(this,
clientRequest.write().query(parsedStmt.getSQL(), newQueryId()),
getTableColumns(parsedStmt.getDatabase(), parsedStmt.getTable(),
parsedStmt.getContentBetweenKeywords(
ClickHouseSqlStatement.KEYWORD_TABLE_COLUMNS_START,
ClickHouseSqlStatement.KEYWORD_TABLE_COLUMNS_END)),
resultSetType, resultSetConcurrency, resultSetHoldability);
}
}
}
return ps != null ? ps
: new SqlBasedPreparedStatement(this, clientRequest.copy().query(preparedQuery, newQueryId()),
stmts[0], resultSetType, resultSetConcurrency, resultSetHoldability);
}
`
StreamBasedPreparedStatement.ensureParams() code:
protected void ensureParams() throws SQLException { if (value == null) { throw SqlExceptionUtils.clientError("Missing input stream"); } }