From 2c036273dd5f009d0ea6850cdf7bbd81cef31dd1 Mon Sep 17 00:00:00 2001 From: actiontech-zihan Date: Thu, 24 Sep 2026 16:44:36 +0800 Subject: [PATCH 1/2] fix: compat DATETIME(6) and RFC3339 watermark readback GetLatestStartTimeAuditPlanSQLV2 COALESCE parses space DATETIME(6), RFC3339 with T, and second-precision DATETIME so historical rows keep working. Co-authored-by: Cursor --- sqle/model/instance_audit_plan.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sqle/model/instance_audit_plan.go b/sqle/model/instance_audit_plan.go index 158bf99c5..eeb098307 100644 --- a/sqle/model/instance_audit_plan.go +++ b/sqle/model/instance_audit_plan.go @@ -176,8 +176,13 @@ func (s *Storage) GetLatestStartTimeAuditPlanSQLV2(sourceId uint, typ string) (s info := struct { StartTime string `gorm:"column:max_start_time"` }{} - err := s.db.Raw(`SELECT MAX(STR_TO_DATE(JSON_UNQUOTE(JSON_EXTRACT(info, '$.start_time_of_last_scraped_sql')), '%Y-%m-%dT%H:%i:%s.%f')) - AS max_start_time FROM sql_manage_records WHERE source_id = ? AND source = ? AND deleted_at is NULL`, sourceId, typ).Scan(&info).Error + // Compatible with DATETIME(6) (space separator) and historical RFC3339-like values (T separator). + // Trailing timezone suffixes on RFC3339 are ignored once the format is exhausted. + err := s.db.Raw(`SELECT MAX(COALESCE( + STR_TO_DATE(JSON_UNQUOTE(JSON_EXTRACT(info, '$.start_time_of_last_scraped_sql')), '%Y-%m-%d %H:%i:%s.%f'), + STR_TO_DATE(JSON_UNQUOTE(JSON_EXTRACT(info, '$.start_time_of_last_scraped_sql')), '%Y-%m-%dT%H:%i:%s.%f'), + STR_TO_DATE(JSON_UNQUOTE(JSON_EXTRACT(info, '$.start_time_of_last_scraped_sql')), '%Y-%m-%d %H:%i:%s') + )) AS max_start_time FROM sql_manage_records WHERE source_id = ? AND source = ? AND deleted_at is NULL`, sourceId, typ).Scan(&info).Error return info.StartTime, err } From 6727349b16180da8698b75258637c16ce2a0eeb9 Mon Sep 17 00:00:00 2001 From: actiontech-zihan Date: Thu, 24 Sep 2026 16:44:36 +0800 Subject: [PATCH 2/2] fix: make TaskWrapper stop non-blocking via close(cancel) Stop previously sent on an unbuffered cancel channel and could deadlock while ExtractSQL was mid-page; use sync.Once + close instead. Co-authored-by: Cursor --- sqle/server/auditplan/task_wrap.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sqle/server/auditplan/task_wrap.go b/sqle/server/auditplan/task_wrap.go index ff4c03933..88975669c 100644 --- a/sqle/server/auditplan/task_wrap.go +++ b/sqle/server/auditplan/task_wrap.go @@ -101,6 +101,7 @@ type TaskWrapper struct { sync.WaitGroup isStarted bool cancel chan struct{} + cancelOnce sync.Once loopInterval func() time.Duration } @@ -219,7 +220,11 @@ func (at *TaskWrapper) stopCollect() error { if !at.isStarted { return nil } - at.cancel <- struct{}{} + // S2 scheme B: close once so Stop is non-blocking while ExtractSQL is still running. + // loop's `select { case <-cancel: }` unblocks on close; avoid unbuffered send deadlock. + at.cancelOnce.Do(func() { + close(at.cancel) + }) at.isStarted = false at.WaitGroup.Wait() at.logger.Infof("stop task")