2525import org .apache .iotdb .rpc .stmt .PreparedParameterSerde ;
2626import org .apache .iotdb .service .rpc .thrift .IClientRPCService .Iface ;
2727import org .apache .iotdb .service .rpc .thrift .TSCancelOperationReq ;
28+ import org .apache .iotdb .service .rpc .thrift .TSCloseOperationReq ;
2829import org .apache .iotdb .service .rpc .thrift .TSExecutePreparedReq ;
2930import org .apache .iotdb .service .rpc .thrift .TSExecuteStatementReq ;
3031import org .apache .iotdb .service .rpc .thrift .TSExecuteStatementResp ;
3132import org .apache .iotdb .service .rpc .thrift .TSPrepareReq ;
3233import org .apache .iotdb .service .rpc .thrift .TSPrepareResp ;
3334
35+ import org .apache .tsfile .enums .TSDataType ;
36+ import org .apache .tsfile .read .common .block .TsBlockBuilder ;
37+ import org .apache .tsfile .read .common .block .column .TsBlockSerde ;
38+ import org .apache .tsfile .utils .Binary ;
3439import org .junit .Before ;
3540import org .junit .Test ;
3641import org .mockito .ArgumentCaptor ;
3944import org .mockito .invocation .InvocationOnMock ;
4045import org .mockito .stubbing .Answer ;
4146
47+ import java .io .IOException ;
4248import java .nio .ByteBuffer ;
49+ import java .nio .charset .Charset ;
50+ import java .sql .ResultSet ;
4351import java .sql .SQLException ;
4452import java .sql .Timestamp ;
4553import java .sql .Types ;
4654import java .time .Instant ;
4755import java .time .LocalDate ;
4856import java .time .ZoneId ;
57+ import java .util .ArrayList ;
4958import java .util .Arrays ;
59+ import java .util .Collections ;
5060import java .util .List ;
5161import java .util .stream .Collectors ;
5262
63+ import static org .junit .Assert .assertArrayEquals ;
5364import static org .junit .Assert .assertEquals ;
5465import static org .junit .Assert .assertThrows ;
5566import static org .junit .Assert .assertTrue ;
@@ -75,6 +86,7 @@ public class IoTDBTablePreparedStatementTest {
7586 public void before () throws Exception {
7687 MockitoAnnotations .initMocks (this );
7788 when (connection .getSqlDialect ()).thenReturn ("table" );
89+ when (connection .getTimeFactor ()).thenReturn (1_000 );
7890 when (execStatementResp .getStatus ()).thenReturn (Status_SUCCESS );
7991 when (execStatementResp .getQueryId ()).thenReturn (queryId );
8092
@@ -98,6 +110,8 @@ public TSPrepareResp answer(InvocationOnMock invocation) throws Throwable {
98110 // Mock for executePreparedStatement
99111 when (client .executePreparedStatement (any (TSExecutePreparedReq .class )))
100112 .thenReturn (execStatementResp );
113+
114+ when (client .closeOperation (any (TSCloseOperationReq .class ))).thenReturn (Status_SUCCESS );
101115 }
102116
103117 /** Count the number of '?' placeholders in a SQL string, ignoring those inside quotes */
@@ -255,6 +269,174 @@ public void testTableModelStringWithNull() throws Exception {
255269 assertTrue (argument .getValue ().getParameters () != null );
256270 }
257271
272+ // ========== Table Model result column mapping tests ==========
273+
274+ private static TsBlockBuilder int32BlockBuilder (int columnCount ) {
275+ TsBlockBuilder builder = new TsBlockBuilder (Collections .nCopies (columnCount , TSDataType .INT32 ));
276+ builder .getTimeColumnBuilder ().writeLong (1000L );
277+ builder .declarePosition ();
278+ return builder ;
279+ }
280+
281+ private TSExecuteStatementResp buildQueryResp (
282+ List <String > columns , List <Integer > mapping , TsBlockBuilder blockBuilder ) throws IOException {
283+ return buildQueryResp (
284+ columns , Collections .nCopies (columns .size (), TSDataType .INT32 ), mapping , blockBuilder );
285+ }
286+
287+ private TSExecuteStatementResp buildQueryResp (
288+ List <String > columns ,
289+ List <TSDataType > dataTypes ,
290+ List <Integer > mapping ,
291+ TsBlockBuilder blockBuilder )
292+ throws IOException {
293+ TSExecuteStatementResp resp = new TSExecuteStatementResp ();
294+ resp .setStatus (Status_SUCCESS );
295+ resp .setQueryId (1L );
296+ resp .setColumns (columns );
297+ List <String > dataTypeList = new ArrayList <>();
298+ for (TSDataType dataType : dataTypes ) {
299+ dataTypeList .add (dataType .name ());
300+ }
301+ resp .setDataTypeList (dataTypeList );
302+ resp .setIgnoreTimeStamp (true );
303+ resp .setTableModel (true );
304+ if (mapping != null ) {
305+ resp .setColumnIndex2TsBlockColumnIndexList (mapping );
306+ }
307+ resp .setQueryResult (
308+ Collections .singletonList (new TsBlockSerde ().serialize (blockBuilder .build ())));
309+ resp .setMoreData (false );
310+ return resp ;
311+ }
312+
313+ @ SuppressWarnings ("resource" )
314+ @ Test
315+ public void testTableModelPreparedStatementReorderedColumns () throws Exception {
316+ TsBlockBuilder blockBuilder = int32BlockBuilder (2 );
317+ blockBuilder .getColumnBuilder (0 ).writeInt (10 ); // physical column 0
318+ blockBuilder .getColumnBuilder (1 ).writeInt (20 ); // physical column 1
319+
320+ when (client .executePreparedStatement (any (TSExecutePreparedReq .class )))
321+ .thenReturn (buildQueryResp (Arrays .asList ("b" , "a" ), Arrays .asList (1 , 0 ), blockBuilder ));
322+
323+ IoTDBTablePreparedStatement ps =
324+ new IoTDBTablePreparedStatement (
325+ connection , client , sessionId , "SELECT b, a FROM t WHERE a > ?" , zoneId );
326+ ps .setInt (1 , 0 );
327+ try (ResultSet rs = ps .executeQuery ()) {
328+ assertTrue (rs .next ());
329+ assertEquals (20 , rs .getInt (1 ));
330+ assertEquals (10 , rs .getInt (2 ));
331+ assertEquals (20 , rs .getInt ("b" ));
332+ assertEquals (10 , rs .getInt ("a" ));
333+ }
334+ }
335+
336+ @ SuppressWarnings ("resource" )
337+ @ Test
338+ public void testTableModelPreparedStatementRepeatedProjection () throws Exception {
339+ TsBlockBuilder blockBuilder = int32BlockBuilder (1 );
340+ blockBuilder .getColumnBuilder (0 ).writeInt (10 ); // physical column 0, referenced twice
341+
342+ when (client .executePreparedStatement (any (TSExecutePreparedReq .class )))
343+ .thenReturn (buildQueryResp (Arrays .asList ("x" , "y" ), Arrays .asList (0 , 0 ), blockBuilder ));
344+
345+ IoTDBTablePreparedStatement ps =
346+ new IoTDBTablePreparedStatement (
347+ connection , client , sessionId , "SELECT a AS x, a AS y FROM t WHERE a > ?" , zoneId );
348+ ps .setInt (1 , 0 );
349+ try (ResultSet rs = ps .executeQuery ()) {
350+ assertTrue (rs .next ());
351+ assertEquals (10 , rs .getInt (1 ));
352+ assertEquals (10 , rs .getInt (2 ));
353+ assertEquals (10 , rs .getInt ("x" ));
354+ assertEquals (10 , rs .getInt ("y" ));
355+ }
356+ }
357+
358+ @ SuppressWarnings ("resource" )
359+ @ Test
360+ public void testTableModelPreparedStatementIdentityMapping () throws Exception {
361+ TsBlockBuilder blockBuilder = int32BlockBuilder (2 );
362+ blockBuilder .getColumnBuilder (0 ).writeInt (10 );
363+ blockBuilder .getColumnBuilder (1 ).writeInt (20 );
364+
365+ when (client .executePreparedStatement (any (TSExecutePreparedReq .class )))
366+ .thenReturn (buildQueryResp (Arrays .asList ("a" , "b" ), Arrays .asList (0 , 1 ), blockBuilder ));
367+
368+ IoTDBTablePreparedStatement ps =
369+ new IoTDBTablePreparedStatement (
370+ connection , client , sessionId , "SELECT a, b FROM t WHERE a > ?" , zoneId );
371+ ps .setInt (1 , 0 );
372+ try (ResultSet rs = ps .executeQuery ()) {
373+ assertTrue (rs .next ());
374+ assertEquals (10 , rs .getInt (1 ));
375+ assertEquals (20 , rs .getInt (2 ));
376+ assertEquals (10 , rs .getInt ("a" ));
377+ assertEquals (20 , rs .getInt ("b" ));
378+ }
379+ }
380+
381+ @ SuppressWarnings ("resource" )
382+ @ Test
383+ public void testTableModelPreparedStatementMissingMappingFallsBack () throws Exception {
384+ TsBlockBuilder blockBuilder = int32BlockBuilder (2 );
385+ blockBuilder .getColumnBuilder (0 ).writeInt (10 );
386+ blockBuilder .getColumnBuilder (1 ).writeInt (20 );
387+
388+ // No columnIndex2TsBlockColumnIndexList: the client must keep its own derivation
389+ when (client .executePreparedStatement (any (TSExecutePreparedReq .class )))
390+ .thenReturn (buildQueryResp (Arrays .asList ("a" , "b" ), null , blockBuilder ));
391+
392+ IoTDBTablePreparedStatement ps =
393+ new IoTDBTablePreparedStatement (
394+ connection , client , sessionId , "SELECT a, b FROM t WHERE a > ?" , zoneId );
395+ ps .setInt (1 , 0 );
396+ try (ResultSet rs = ps .executeQuery ()) {
397+ assertTrue (rs .next ());
398+ assertEquals (10 , rs .getInt (1 ));
399+ assertEquals (20 , rs .getInt (2 ));
400+ assertEquals (10 , rs .getInt ("a" ));
401+ assertEquals (20 , rs .getInt ("b" ));
402+ }
403+ }
404+
405+ // ========== Charset forwarding ==========
406+
407+ @ SuppressWarnings ("resource" )
408+ @ Test
409+ public void testTableModelPreparedStatementGetBytesUsesConnectionCharset () throws Exception {
410+ // A STRING column holding 中文; the value travels as UTF-8 in the TsBlock, but getBytes must
411+ // encode the decoded String with the charset the connection was opened with.
412+ TsBlockBuilder blockBuilder = new TsBlockBuilder (Collections .singletonList (TSDataType .STRING ));
413+ blockBuilder .getTimeColumnBuilder ().writeLong (1000L );
414+ blockBuilder .declarePosition ();
415+ blockBuilder .getColumnBuilder (0 ).writeBinary (new Binary ("中文" , Charset .forName ("UTF-8" )));
416+ when (client .executePreparedStatement (any (TSExecutePreparedReq .class )))
417+ .thenReturn (
418+ buildQueryResp (
419+ Collections .singletonList ("name" ),
420+ Collections .singletonList (TSDataType .STRING ),
421+ null ,
422+ blockBuilder ));
423+
424+ IoTDBTablePreparedStatement ps =
425+ new IoTDBTablePreparedStatement (
426+ connection ,
427+ client ,
428+ sessionId ,
429+ "SELECT name FROM t WHERE name = ?" ,
430+ zoneId ,
431+ Charset .forName ("GBK" ));
432+ ps .setString (1 , "中文" );
433+ try (ResultSet rs = ps .executeQuery ()) {
434+ assertTrue (rs .next ());
435+ assertArrayEquals ("中文" .getBytes (Charset .forName ("GBK" )), rs .getBytes (1 ));
436+ assertArrayEquals ("中文" .getBytes (Charset .forName ("GBK" )), rs .getBytes ("name" ));
437+ }
438+ }
439+
258440 // ========== Cancel, fetch size and timestamp precision ==========
259441
260442 @ SuppressWarnings ("resource" )
0 commit comments