3333import org .apache .iotdb .confignode .persistence .cq .CQInfo ;
3434import org .apache .iotdb .confignode .rpc .thrift .TCreateCQReq ;
3535import org .apache .iotdb .confignode .rpc .thrift .TDropCQReq ;
36+ import org .apache .iotdb .confignode .rpc .thrift .TNodeVersionInfo ;
3637import org .apache .iotdb .confignode .rpc .thrift .TShowCQResp ;
3738import org .apache .iotdb .consensus .common .DataSet ;
3839import org .apache .iotdb .consensus .exception .ConsensusException ;
@@ -90,7 +91,16 @@ public TSStatus createCQ(TCreateCQReq req) {
9091
9192 private TSStatus validateDurationEncoding (TCreateCQReq req ) {
9293 if (!req .isSetDurationEncodingVersion ()) {
93- // Requests from older DataNodes contain only the legacy fixed-duration fields.
94+ // Preserve wire compatibility for legacy fixed-duration clients. A request that starts
95+ // sending structured fields must include the version marker so it cannot be misread by an
96+ // older ConfigNode.
97+ if (req .isSetEveryDuration ()
98+ || req .isSetStartOffsetDuration ()
99+ || req .isSetEndOffsetDuration ()
100+ || req .isSetBoundaryExplicit ()) {
101+ return new TSStatus (TSStatusCode .SEMANTIC_ERROR .getStatusCode ())
102+ .setMessage (ManagerMessages .MESSAGE_CQ_DURATION_ENCODING_MARKER_REQUIRED_9035980A );
103+ }
94104 return null ;
95105 }
96106 if (req .getDurationEncodingVersion () != 1
@@ -112,6 +122,16 @@ private TSStatus validateDurationEncoding(TCreateCQReq req) {
112122 return new TSStatus (TSStatusCode .SEMANTIC_ERROR .getStatusCode ())
113123 .setMessage (ManagerMessages .MESSAGE_CQ_DURATIONS_MUST_BE_NON_NEGATIVE_BE23CE04 );
114124 }
125+ if (req .getEveryDuration ().getMonthPart () > Integer .MAX_VALUE
126+ || req .getStartOffsetDuration ().getMonthPart () > Integer .MAX_VALUE
127+ || req .getEndOffsetDuration ().getMonthPart () > Integer .MAX_VALUE ) {
128+ return new TSStatus (TSStatusCode .SEMANTIC_ERROR .getStatusCode ())
129+ .setMessage (ManagerMessages .MESSAGE_CQ_DURATIONS_MUST_BE_NON_NEGATIVE_BE23CE04 );
130+ }
131+ boolean hasCalendarDuration =
132+ req .getEveryDuration ().getMonthPart () != 0
133+ || req .getStartOffsetDuration ().getMonthPart () != 0
134+ || req .getEndOffsetDuration ().getMonthPart () != 0 ;
115135 if ((req .getEveryDuration ().getMonthPart () == 0
116136 && req .everyInterval != req .getEveryDuration ().getNonMonthDuration ())
117137 || (req .getStartOffsetDuration ().getMonthPart () == 0
@@ -120,15 +140,57 @@ private TSStatus validateDurationEncoding(TCreateCQReq req) {
120140 && req .endTimeOffset != req .getEndOffsetDuration ().getNonMonthDuration ())
121141 || (req .getEveryDuration ().getMonthPart () != 0 && req .everyInterval != 0 )
122142 || (req .getStartOffsetDuration ().getMonthPart () != 0 && req .startTimeOffset != 0 )
123- || (req .getEndOffsetDuration ().getMonthPart () != 0 && req .endTimeOffset != 0 )) {
143+ || (req .getEndOffsetDuration ().getMonthPart () != 0 && req .endTimeOffset != 0 )
144+ || (hasCalendarDuration
145+ && (req .everyInterval != 0 || req .startTimeOffset != 0 || req .endTimeOffset != 0 ))) {
124146 return new TSStatus (TSStatusCode .SEMANTIC_ERROR .getStatusCode ())
125147 .setMessage (
126148 ManagerMessages
127149 .MESSAGE_CQ_LEGACY_DURATION_FIELDS_CONFLICT_WITH_STRUCTURED_DURATION_FIELDS_4D6C6D67 );
128150 }
151+ if (hasCalendarDuration && !allClusterNodesSupportDurationEncodingV1 ()) {
152+ return new TSStatus (TSStatusCode .SEMANTIC_ERROR .getStatusCode ())
153+ .setMessage (
154+ ManagerMessages .MESSAGE_CQ_CALENDAR_DURATION_REQUIRES_ALL_NODES_SUPPORT_49534072 );
155+ }
129156 return null ;
130157 }
131158
159+ private boolean allClusterNodesSupportDurationEncodingV1 () {
160+ java .util .Map <Integer , TNodeVersionInfo > versionInfo =
161+ configManager .getNodeManager ().getNodeVersionInfo ();
162+ if (versionInfo .isEmpty ()) {
163+ return false ;
164+ }
165+ boolean hasRegisteredNode = false ;
166+ // Check every registered node explicitly. A missing heartbeat/version entry must not allow a
167+ // calendar CQ to be created during a rolling upgrade.
168+ for (org .apache .iotdb .common .rpc .thrift .TConfigNodeLocation node :
169+ configManager .getNodeManager ().getRegisteredConfigNodes ()) {
170+ hasRegisteredNode = true ;
171+ if (!supportsDurationEncodingV1 (versionInfo .get (node .getConfigNodeId ()))) {
172+ return false ;
173+ }
174+ }
175+ for (org .apache .iotdb .common .rpc .thrift .TDataNodeConfiguration node :
176+ configManager .getNodeManager ().getRegisteredDataNodes ()) {
177+ hasRegisteredNode = true ;
178+ if (!supportsDurationEncodingV1 (versionInfo .get (node .getLocation ().getDataNodeId ()))) {
179+ return false ;
180+ }
181+ }
182+ return hasRegisteredNode ;
183+ }
184+
185+ private boolean supportsDurationEncodingV1 (TNodeVersionInfo info ) {
186+ if (info == null
187+ || !info .isSetSupportedCQDurationEncodingVersions ()
188+ || !info .getSupportedCQDurationEncodingVersions ().contains ((short ) 1 )) {
189+ return false ;
190+ }
191+ return true ;
192+ }
193+
132194 public TSStatus dropCQ (TDropCQReq req ) {
133195 lock .readLock ().lock ();
134196 try {
0 commit comments