Summary
When a Session loses its transport (e.g. the server it is connected to restarts) and the client transparently reconnects, the reconnected session is opened without the sql_dialect, db, and version values the original Open() negotiated. The session silently reverts to server defaults. For table-model sessions this is severe: every subsequent statement fails to parse, because the session is now speaking the tree dialect while the caller keeps sending table-dialect SQL. It also affects any session that relied on a bound database (db).
Affected versions
Present on current main, and on released v2.0.3-1 and v2.0.8. The code path is unchanged across all three.
Root cause
The session-open request is hand-built in three places, and only two populate TSOpenSessionReq.Configuration:
Session.Open() — sets Configuration["sql_dialect"], ["version"], and (when non-empty) ["db"] (client/session.go, the block after the TSOpenSessionReq literal in Open).
Session.OpenCluster() — sets the same three.
Session.initClusterConn() — builds TSOpenSessionReq with no Configuration map at all.
reconnect() is written purely in terms of initClusterConn(), and it is the sole transparent-reconnect path — invoked from every insert/query/execute method on a transport-level error (err != nil && resp == nil), ~29 call sites. So any session, single-host or cluster, that survives a server restart and then issues a statement reconnects through initClusterConn() and loses its dialect/db/version. The values needed to rebuild Configuration are already on the session (s.config) at reconnect time — they are simply not used.
The reference (Java) client does this correctly
In apache/iotdb, SessionConnection.reconnect() routes through init(), which re-sends the configuration on every (re)open — putToConfiguration("version", ...), ("sql_dialect", ...), ("db", ...). The Go client's omission looks like an oversight (one of three copies of the same block was missed), not an intentional difference.
Why it's hard to diagnose
- The failure surfaces as a valid server response (
TSStatus code 700 SqlParseError, or 701 SemanticError "database is not specified"), converted into an *ExecutionError — not a Go transport error.
TableSessionPool therefore keeps the poisoned session: isConnectionError returns false for *ExecutionError/*BatchError, so the session is never dropped and Close() returns it to the pool. Every subsequent caller that draws it fails the same way until the process is restarted.
- It only manifests when a server actually restarts mid-session — invisible to unit tests and to any deployment that doesn't bounce a server under load.
Reproduction (verified against a live server)
Server: apache/iotdb:2.0.3-standalone. Client: iotdb-client-go/v2 v2.0.8, TableSessionPool (size 1), session bound to a database.
- Open a table-model session bound to a database; create a table;
INSERT a row (succeeds).
- Return the session to the pool.
- Restart the server (
docker restart) so the socket drops but the endpoint returns.
GetSession() (reuses the pooled session, which reconnects) and INSERT again.
Expected: the client reconnects transparently and the insert succeeds.
Actual (stock v2.0.8):
error code: 700, message: Error occurred while parsing SQL to physical plan: line 1:12 mismatched input 't' expecting ROOT
The reconnected session reverted to the tree dialect, so the tree parser expects a root.-prefixed path and rejects the table name. Every subsequent statement on that session keeps failing. (A session relying on a bound db with the dialect intact would instead see code 701, "database is not specified".)
Suggested fix
Populate TSOpenSessionReq.Configuration on the reconnect path the same way Open()/OpenCluster() do, from s.config. Because the same block is now hand-duplicated across all three open paths — and this bug exists precisely because one copy was missed — a clean fix extracts a single helper and wires all three sites through it. I verified locally that adding the Configuration map to initClusterConn makes the reproduction above succeed after the restart, with no other change. A PR follows.
A smaller, related gap for maintainers' awareness: initClusterConn also never sets s.timeFactor via getTimeFactor(resp), which Open/OpenCluster do — a reconnected session keeps a stale timeFactor. Benign on homogeneous clusters; happy to include it if wanted.
Environment
- iotdb-client-go:
main (also reproduced on v2.0.8, v2.0.3-1)
- IoTDB server:
2.0.3-standalone
- Go: 1.25
Summary
When a
Sessionloses its transport (e.g. the server it is connected to restarts) and the client transparently reconnects, the reconnected session is opened without thesql_dialect,db, andversionvalues the originalOpen()negotiated. The session silently reverts to server defaults. For table-model sessions this is severe: every subsequent statement fails to parse, because the session is now speaking the tree dialect while the caller keeps sending table-dialect SQL. It also affects any session that relied on a bound database (db).Affected versions
Present on current
main, and on releasedv2.0.3-1andv2.0.8. The code path is unchanged across all three.Root cause
The session-open request is hand-built in three places, and only two populate
TSOpenSessionReq.Configuration:Session.Open()— setsConfiguration["sql_dialect"],["version"], and (when non-empty)["db"](client/session.go, the block after theTSOpenSessionReqliteral inOpen).Session.OpenCluster()— sets the same three.Session.initClusterConn()— buildsTSOpenSessionReqwith noConfigurationmap at all.reconnect()is written purely in terms ofinitClusterConn(), and it is the sole transparent-reconnect path — invoked from every insert/query/execute method on a transport-level error (err != nil && resp == nil), ~29 call sites. So any session, single-host or cluster, that survives a server restart and then issues a statement reconnects throughinitClusterConn()and loses its dialect/db/version. The values needed to rebuildConfigurationare already on the session (s.config) at reconnect time — they are simply not used.The reference (Java) client does this correctly
In
apache/iotdb,SessionConnection.reconnect()routes throughinit(), which re-sends the configuration on every (re)open —putToConfiguration("version", ...),("sql_dialect", ...),("db", ...). The Go client's omission looks like an oversight (one of three copies of the same block was missed), not an intentional difference.Why it's hard to diagnose
TSStatuscode 700SqlParseError, or 701SemanticError"database is not specified"), converted into an*ExecutionError— not a Go transport error.TableSessionPooltherefore keeps the poisoned session:isConnectionErrorreturnsfalsefor*ExecutionError/*BatchError, so the session is never dropped andClose()returns it to the pool. Every subsequent caller that draws it fails the same way until the process is restarted.Reproduction (verified against a live server)
Server:
apache/iotdb:2.0.3-standalone. Client:iotdb-client-go/v2 v2.0.8,TableSessionPool(size 1), session bound to a database.INSERTa row (succeeds).docker restart) so the socket drops but the endpoint returns.GetSession()(reuses the pooled session, which reconnects) andINSERTagain.Expected: the client reconnects transparently and the insert succeeds.
Actual (stock v2.0.8):
The reconnected session reverted to the tree dialect, so the tree parser expects a
root.-prefixed path and rejects the table name. Every subsequent statement on that session keeps failing. (A session relying on a bounddbwith the dialect intact would instead see code 701, "database is not specified".)Suggested fix
Populate
TSOpenSessionReq.Configurationon the reconnect path the same wayOpen()/OpenCluster()do, froms.config. Because the same block is now hand-duplicated across all three open paths — and this bug exists precisely because one copy was missed — a clean fix extracts a single helper and wires all three sites through it. I verified locally that adding theConfigurationmap toinitClusterConnmakes the reproduction above succeed after the restart, with no other change. A PR follows.A smaller, related gap for maintainers' awareness:
initClusterConnalso never setss.timeFactorviagetTimeFactor(resp), whichOpen/OpenClusterdo — a reconnected session keeps a staletimeFactor. Benign on homogeneous clusters; happy to include it if wanted.Environment
main(also reproduced onv2.0.8,v2.0.3-1)2.0.3-standalone