This is an automated email from the ASF dual-hosted git repository.

CritasWang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iotdb-client-rust.git

commit f5be5860721c4fadb86bddbc524ef99c950a3950
Author: CritasWang <[email protected]>
AuthorDate: Mon Jul 13 10:11:28 2026 +0800

    V1: DATE live adjudication test; re-tag logical types 
(DATE/TIMESTAMP/STRING/BLOB) from dataTypeList in row assembly
---
 src/client/dataset.rs | 60 ++++++++++++++++++++++++++++++++++++-
 src/client/session.rs | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 141 insertions(+), 1 deletion(-)

diff --git a/src/client/dataset.rs b/src/client/dataset.rs
index 457b213..6c18ff5 100644
--- a/src/client/dataset.rs
+++ b/src/client/dataset.rs
@@ -173,12 +173,29 @@ impl<'a> SessionDataSet<'a> {
                         block.columns.len()
                     ))
                 })?;
-            values.push(column[i].clone());
+            values.push(Self::apply_logical_type(
+                column[i].clone(),
+                self.data_type_list.get(ordinal).map(String::as_str),
+            ));
         }
         let timestamp = (!self.ignore_time_stamp).then(|| block.timestamps[i]);
         Ok(Row { timestamp, values })
     }
 
+    /// Re-tag a decoded value with the column's logical type from the
+    /// response's `dataTypeList`. TsBlock headers carry the *physical* type
+    /// (DATE arrives as INT32, TIMESTAMP as INT64, STRING as TEXT), so the
+    /// block decoder alone cannot distinguish them.
+    fn apply_logical_type(value: Value, logical: Option<&str>) -> Value {
+        match (logical, value) {
+            (Some("DATE"), Value::Int32(v)) => Value::Date(v),
+            (Some("TIMESTAMP"), Value::Int64(v)) => Value::Timestamp(v),
+            (Some("STRING"), Value::Text(s)) => Value::String(s),
+            (Some("BLOB"), Value::Text(s)) => Value::Blob(s.into_bytes()),
+            (_, v) => v,
+        }
+    }
+
     /// Close the query (best-effort `closeOperation`, errors swallowed) and
     /// release the session borrow's obligations. Idempotent; also invoked
     /// automatically on exhaustion and on drop.
@@ -226,6 +243,47 @@ mod tests {
         }
     }
 
+    #[test]
+    fn logical_type_retagging_from_data_type_list() {
+        // DATE arrives physically as INT32 in the TsBlock; dataTypeList says 
DATE.
+        let mut session = offline_session();
+        let mut h = handle(vec![int32_block(&[1], &[20260713])], false);
+        h.data_type_list = vec!["DATE".into()];
+        let mut ds = SessionDataSet::new(&mut session, h);
+        let row = ds.next_row().unwrap().unwrap();
+        assert_eq!(row.values[0], Value::Date(20260713));
+        assert!(ds.next_row().unwrap().is_none());
+    }
+
+    #[test]
+    fn apply_logical_type_variants() {
+        use Value::*;
+        // Physical → logical re-tags.
+        assert_eq!(
+            SessionDataSet::apply_logical_type(Int32(20260713), Some("DATE")),
+            Date(20260713)
+        );
+        assert_eq!(
+            SessionDataSet::apply_logical_type(Int64(99), Some("TIMESTAMP")),
+            Timestamp(99)
+        );
+        assert_eq!(
+            SessionDataSet::apply_logical_type(Text("s".into()), 
Some("STRING")),
+            String("s".into())
+        );
+        assert_eq!(
+            SessionDataSet::apply_logical_type(Text("b".into()), Some("BLOB")),
+            Blob(b"b".to_vec())
+        );
+        // Pass-throughs: matching physical types and nulls stay untouched.
+        assert_eq!(
+            SessionDataSet::apply_logical_type(Int32(5), Some("INT32")),
+            Int32(5)
+        );
+        assert_eq!(SessionDataSet::apply_logical_type(Null, Some("DATE")), 
Null);
+        assert_eq!(SessionDataSet::apply_logical_type(Int32(5), None), 
Int32(5));
+    }
+
     #[test]
     fn drains_rows_across_multiple_cached_blocks() {
         let mut session = offline_session();
diff --git a/src/client/session.rs b/src/client/session.rs
index 775391b..f84ad43 100644
--- a/src/client/session.rs
+++ b/src/client/session.rs
@@ -542,6 +542,88 @@ mod tests {
         assert!(!session.is_open());
     }
 
+    /// DATE wire-format adjudication test (goal V1). Inserts one DATE row via
+    /// the tablet binary path (i32 yyyyMMdd) and one via a SQL date literal
+    /// (parsed server-side), then reads both back: if the tablet encoding is
+    /// correct, both rows decode to the same i32 for the same calendar date.
+    /// This breaks the write-read circularity a plain roundtrip would have.
+    /// Skipped when no IoTDB instance is reachable on localhost:6667.
+    #[test]
+    fn live_date_encoding_adjudication() {
+        use crate::data::{tablet::Tablet, TSDataType, Value};
+        use std::net::TcpStream;
+        if TcpStream::connect_timeout(
+            &"127.0.0.1:6667".parse().unwrap(),
+            Duration::from_millis(300),
+        )
+        .is_err()
+        {
+            eprintln!(
+                "skipping live_date_encoding_adjudication: no IoTDB server on 
127.0.0.1:6667"
+            );
+            return;
+        }
+
+        const DB: &str = "root.rusttest_date";
+        // 2026-07-13 as yyyyMMdd; deliberately not near epoch so a
+        // days-since-epoch misinterpretation cannot coincide.
+        const DATE_YYYYMMDD: i32 = 20260713;
+
+        let mut session = Session::new(SessionConfig::default());
+        session.open().expect("open session");
+        let _ = session.execute_non_query(&format!("DELETE DATABASE {DB}"));
+        session
+            .execute_non_query(&format!("CREATE DATABASE {DB}"))
+            .expect("create database");
+
+        // Row at ts=1: tablet binary path.
+        let mut tablet = Tablet::new(
+            format!("{DB}.d1"),
+            vec!["dt".into()],
+            vec![TSDataType::Date],
+        )
+        .expect("tablet");
+        tablet
+            .add_row(1, vec![Some(Value::Date(DATE_YYYYMMDD))])
+            .expect("add_row");
+        session.insert_tablet(&tablet).expect("insert_tablet");
+
+        // Row at ts=2: SQL literal path — server parses the calendar date 
itself.
+        session
+            .execute_non_query(&format!(
+                "INSERT INTO {DB}.d1(timestamp, dt) VALUES (2, '2026-07-13')"
+            ))
+            .expect("insert via SQL literal");
+
+        // Read both back; they must decode identically.
+        let mut got: Vec<(i64, Value)> = Vec::new();
+        {
+            let mut dataset = session
+                .execute_query(&format!("SELECT dt FROM {DB}.d1 ORDER BY 
time"))
+                .expect("query");
+            while let Some(row) = dataset.next_row().expect("next_row") {
+                got.push((row.timestamp.expect("timestamp"), 
row.values[0].clone()));
+            }
+        }
+        assert_eq!(got.len(), 2, "expected both rows back");
+        assert_eq!(
+            got[0].1,
+            Value::Date(DATE_YYYYMMDD),
+            "tablet-path DATE readback"
+        );
+        assert_eq!(
+            got[1].1,
+            Value::Date(DATE_YYYYMMDD),
+            "SQL-literal DATE must decode to the same i32 as the tablet path — 
\
+             proves yyyyMMdd is the server's wire semantics"
+        );
+
+        session
+            .execute_non_query(&format!("DELETE DATABASE {DB}"))
+            .expect("cleanup");
+        session.close().expect("close session");
+    }
+
     /// Value-asserting live roundtrip: unsorted input, nulls, and a row count
     /// that is a multiple of 8 (stresses the rows/8+1 bitmap padding byte).
     /// Skipped when no IoTDB instance is reachable on localhost:6667.

Reply via email to