• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

taosdata / TDengine / #5071

17 May 2026 01:15AM UTC coverage: 63.054% (-10.3%) from 73.326%
#5071

push

travis-ci

web-flow
feat (TDgpt): Dynamic Model Synchronization Enhancements (#35344)

* refactor: do some internal refactor.

* fix: fix multiprocess sync issue.

* feat: add dynamic anomaly detection and forecasting services

* fix: log error message for undeploying model in exception handling

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix: handle undeploy when model exists only on disk

Agent-Logs-Url: https://github.com/taosdata/TDengine/sessions/286aafa0-c3ce-4c27-b803-2707571e9dc1

Co-authored-by: hjxilinx <8252296+hjxilinx@users.noreply.github.com>

* fix: guard dynamic registry concurrent access

Agent-Logs-Url: https://github.com/taosdata/TDengine/sessions/5e4db858-6458-40f4-ac28-d1b1b7f97c18

Co-authored-by: hjxilinx <8252296+hjxilinx@users.noreply.github.com>

* fix: tighten service list locking scope

Agent-Logs-Url: https://github.com/taosdata/TDengine/sessions/5e4db858-6458-40f4-ac28-d1b1b7f97c18

Co-authored-by: hjxilinx <8252296+hjxilinx@users.noreply.github.com>

* fix: restore prophet support and update tests per review feedback

Agent-Logs-Url: https://github.com/taosdata/TDengine/sessions/92298ae1-7da6-4d07-b20e-101c7cd0b26b

Co-authored-by: hjxilinx <8252296+hjxilinx@users.noreply.github.com>

* fix: improve test name and move copy inside lock scope

Agent-Logs-Url: https://github.com/taosdata/TDengine/sessions/92298ae1-7da6-4d07-b20e-101c7cd0b26b

Co-authored-by: hjxilinx <8252296+hjxilinx@users.noreply.github.com>

* Potential fix for pull request finding

Co-au... (continued)

238317 of 377957 relevant lines covered (63.05%)

130539817.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

2.36
/source/client/src/clientFirewall.c
1
/*
2
 * Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
3
 */
4

5
#include <ctype.h>
6
#include <regex.h>
7
#include <string.h>
8
#include "cJSON.h"
9
#include "clientInt.h"
10
#include "clientLog.h"
11
#include "nodes.h"
12
#include "querynodes.h"
13
#include "os.h"
14

15
#include "tglobal.h"
16
#define SQL_SEC_MAX_RULES        128
17
#define SQL_SEC_RULE_NAME_LEN    128
18
#define SQL_SEC_RULE_PATTERN_LEN 512
19
#define SQL_SEC_MAX_PATTERNS     256
20
#define SQL_SEC_PATTERN_LEN      1024
21

22
typedef enum {
23
  SQL_SEC_ACTION_DENY = 0,
24
  SQL_SEC_ACTION_ALLOW = 1,
25
} ESqlSecAction;
26

27
typedef struct {
28
  int32_t       id;
29
  int32_t       priority;
30
  bool          enabled;
31
  ESqlSecAction action;
32
  char          name[SQL_SEC_RULE_NAME_LEN];
33
  char          pattern[SQL_SEC_RULE_PATTERN_LEN];
34
  regex_t       regex;
35
  bool          regexInited;
36
} SSqlSecRule;
37

38
typedef struct {
39
  bool          inited;
40
  TdThreadMutex lock;
41
  SSqlSecRule   rules[SQL_SEC_MAX_RULES];
42
  int32_t       numOfRules;
43
  int64_t       lastLoadTsMs;
44
  int64_t       lastRuleMtime;
45
  char          lastRulePath[256];
46
} SSqlSecCtx;
47

48
typedef struct {
49
  bool               denyMatched;
50
  bool               allowMatched;
51
  int32_t            denyPriority;
52
  int32_t            allowPriority;
53
  const SSqlSecRule* pDenyRule;
54
  const SSqlSecRule* pAllowRule;
55
} SSqlSecMatchResult;
56

57
typedef struct {
58
  bool hasOrTrue;
59
  bool hasDangerFunc;
60
  bool hasUnsafeJoin;
61
} SSqlSecAstResult;
62

63
typedef struct {
64
  char    pattern[SQL_SEC_PATTERN_LEN];
65
  int32_t count;
66
  int64_t firstSeenTs;
67
  int64_t lastSeenTs;
68
  bool    exported;  // Mark if this pattern has been exported
69
} SSqlPattern;
70

71
typedef struct {
72
  bool          enabled;
73
  TdThreadMutex lock;
74
  SSqlPattern   patterns[SQL_SEC_MAX_PATTERNS];
75
  int32_t       numOfPatterns;
76
  int64_t       startTs;
77
  int32_t       nextRuleId;
78
  // Thread related
79
  TdThread      thread;
80
  int8_t        threadRunning;  // atomic, use atomic_load_8/atomic_store_8
81
  int8_t        threadStop;     // atomic, use atomic_load_8/atomic_store_8
82
  TdThreadCond  cond;
83
  TdThreadMutex threadLock;
84
} SSqlLearningCtx;
85

86
static SSqlSecCtx      gSqlSecCtx = {0};
87
static SSqlLearningCtx gLearningCtx = {0};
88

89
static TdThreadOnce gLearningInitOnce = PTHREAD_ONCE_INIT;
90
static TdThreadOnce gSqlSecInitOnce   = PTHREAD_ONCE_INIT;
91

92
// Generalize SQL by replacing literals with placeholders
93
static void sqlSecGeneralizePattern(const char* sql, char* pattern, int32_t patternLen) {
×
94
  if (sql == NULL || pattern == NULL || patternLen <= 0) return;
×
95

96
  const char* p = sql;
×
97
  char*       out = pattern;
×
98
  int32_t     outLen = 0;
×
99
  bool        inString = false;
×
100
  bool        inNumber = false;
×
101

102
  while (*p && outLen < patternLen - 10) {
×
103
    if (*p == '\'') {
×
104
      if (!inString) {
×
105
        // Start of string literal
106
        inString = true;
×
107
        if (outLen + 3 < patternLen) {
×
108
          strcpy(out, "?");
×
109
          out += 1;
×
110
          outLen += 1;
×
111
        }
112
      } else {
113
        // End of string literal
114
        inString = false;
×
115
      }
116
      p++;
×
117
      continue;
×
118
    }
119

120
    if (inString) {
×
121
      p++;
×
122
      continue;
×
123
    }
124

125
    // Check for number (including floats like 25.5)
126
    if (isdigit(*p) || (*p == '-' && isdigit(*(p + 1)))) {
×
127
      if (!inNumber) {
×
128
        // Start of number
129
        inNumber = true;
×
130
        if (outLen + 3 < patternLen) {
×
131
          strcpy(out, "?");
×
132
          out += 1;
×
133
          outLen += 1;
×
134
        }
135
      }
136
      p++;
×
137
      continue;
×
138
    } else if (inNumber && *p == '.' && isdigit(*(p + 1))) {
×
139
      // Decimal point in a number, continue as part of the number
140
      p++;
×
141
      continue;
×
142
    } else {
143
      inNumber = false;
×
144
    }
145

146
    // Copy other characters (convert to lowercase)
147
    *out = tolower(*p);
×
148
    out++;
×
149
    outLen++;
×
150
    p++;
×
151
  }
152

153
  *out = '\0';
×
154
}
155

156
static int32_t sqlSecSaveLearnedRules(const char* ruleFile) {
×
157
  if (ruleFile == NULL || ruleFile[0] == 0) return -1;
×
158

159
  (void)taosThreadMutexLock(&gLearningCtx.lock);
×
160

161
  // Read existing rules
162
  cJSON* pRoot = NULL;
×
163
  cJSON* pRules = NULL;
×
164

165
  TdFilePtr fp = taosOpenFile(ruleFile, TD_FILE_READ);
×
166
  if (fp != NULL) {
×
167
    int64_t fsize = 0;
×
168
    if (taosFStatFile(fp, &fsize, NULL) == TSDB_CODE_SUCCESS && fsize > 0 && fsize < 4 * 1024 * 1024) {
×
169
      char* pBuf = taosMemoryCalloc(1, (size_t)fsize + 1);
×
170
      if (pBuf != NULL) {
×
171
        int64_t nread = taosReadFile(fp, pBuf, fsize);
×
172
        if (nread == fsize) {
×
173
          pRoot = cJSON_Parse(pBuf);
×
174
        }
175
        taosMemoryFree(pBuf);
×
176
      }
177
    }
178
    TAOS_UNUSED(taosCloseFile(&fp));
×
179
  }
180

181
  if (pRoot == NULL) {
×
182
    pRoot = cJSON_CreateObject();
×
183
    (void)cJSON_AddStringToObject(pRoot, "version", "1.0");
×
184
    pRules = cJSON_CreateArray();
×
185
    if(pRules != NULL) {
×
186
      (void)cJSON_AddItemToObject(pRoot, "rules", pRules);
×
187
    }
188
  } else {
189
    pRules = cJSON_GetObjectItemCaseSensitive(pRoot, "rules");
×
190
    if (!cJSON_IsArray(pRules)) {
×
191
      pRules = cJSON_CreateArray();
×
192
      if(pRules != NULL) {
×
193
        (void)cJSON_AddItemToObject(pRoot, "rules", pRules);
×
194
      }
195
    }
196
  }
197

198
  // Build a set of existing patterns to avoid duplicates
199
  SHashObj* existingPatterns = taosHashInit(64, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BINARY), false, HASH_NO_LOCK);
×
200
  if (existingPatterns != NULL) {
×
201
    int32_t rulesCount = cJSON_GetArraySize(pRules);
×
202
    for (int32_t i = 0; i < rulesCount; ++i) {
×
203
      cJSON* pRule = cJSON_GetArrayItem(pRules, i);
×
204
      cJSON* pDesc = cJSON_GetObjectItemCaseSensitive(pRule, "description");
×
205
      if (cJSON_IsString(pDesc) && pDesc->valuestring != NULL) {
×
206
        // Extract original pattern from description
207
        const char* desc = pDesc->valuestring;
×
208
        const char* prefix = "Learned pattern (count:";
×
209
        if (strncmp(desc, prefix, strlen(prefix)) == 0) {
×
210
          // This is a learned rule, mark it as existing
211
          // We use description as a simple way to identify duplicates
212
          // Better approach: store original pattern in description
213
          if (taosHashPut(existingPatterns, desc, strlen(desc), &i, sizeof(i)) != 0) {
×
214
            tscError("sql security: failed to add existing pattern to hash");
×
215
          }
216
        }
217
      }
218
    }
219
  }
220

221
  // Add learned patterns that reached threshold and haven't been exported
222
  int32_t added = 0;
×
223
  for (int32_t i = 0; i < gLearningCtx.numOfPatterns; ++i) {
×
224
    SSqlPattern* pPattern = &gLearningCtx.patterns[i];
×
225
    if (pPattern->count >= tsWhitelistLearningThreshold && !pPattern->exported) {
×
226
      // Check if this pattern already exists in the file
227
      char desc[256];
×
228
      snprintf(desc, sizeof(desc), "Pattern: %s", pPattern->pattern);
×
229

230
      bool exists = false;
×
231
      if (existingPatterns != NULL) {
×
232
        // Check if pattern already exists
233
        int32_t rulesCount = cJSON_GetArraySize(pRules);
×
234
        for (int32_t j = 0; j < rulesCount; ++j) {
×
235
          cJSON* pRule = cJSON_GetArrayItem(pRules, j);
×
236
          cJSON* pRuleDesc = cJSON_GetObjectItemCaseSensitive(pRule, "description");
×
237
          if (cJSON_IsString(pRuleDesc) && pRuleDesc->valuestring != NULL) {
×
238
            if (strstr(pRuleDesc->valuestring, pPattern->pattern) != NULL) {
×
239
              exists = true;
×
240
              break;
×
241
            }
242
          }
243
        }
244
      }
245

246
      if (exists) {
×
247
        pPattern->exported = true;
×
248
        continue;
×
249
      }
250

251
      cJSON* pRule = cJSON_CreateObject();
×
252
      if (pRule == NULL) continue;
×
253

254
      (void)cJSON_AddNumberToObject(pRule, "ruleId", gLearningCtx.nextRuleId++);
×
255

256
      char ruleName[128];
×
257
      snprintf(ruleName, sizeof(ruleName), "LEARNED_RULE_%d", gLearningCtx.nextRuleId - 1);
×
258
      (void)cJSON_AddStringToObject(pRule, "ruleName", ruleName);
×
259
      (void)cJSON_AddStringToObject(pRule, "action", "ALLOW");
×
260
      (void)cJSON_AddStringToObject(pRule, "priority", "MEDIUM");
×
261

262
      // Convert pattern to regex
263
      // Replace ? with regex pattern for any value
264
      char        regexPattern[SQL_SEC_PATTERN_LEN * 2] = {0};
×
265
      const char* src = pPattern->pattern;
×
266
      char*       dst = regexPattern;
×
267
      while (*src && (dst - regexPattern) < sizeof(regexPattern) - 30) {
×
268
        if (*src == '?') {
×
269
          // Replace ? with more flexible regex pattern
270
          // Match: number (int/float), 'string', "string", or identifier
271
          const char* placeholder = "[^[:space:],;)]+";
×
272
          strcpy(dst, placeholder);
×
273
          dst += strlen(placeholder);
×
274
          src++;
×
275
        } else if (*src == '*' || *src == '+' || *src == '.' || *src == '[' || *src == ']' || *src == '(' ||
×
276
                   *src == ')' || *src == '{' || *src == '}' || *src == '^' || *src == '$' || *src == '|' ||
×
277
                   *src == '\\' || *src == '?') {
×
278
          // Escape regex special characters
279
          *dst++ = '\\';
×
280
          *dst++ = *src++;
×
281
        } else {
282
          *dst++ = *src++;
×
283
        }
284
      }
285
      *dst = '\0';
×
286

287
      (void)cJSON_AddStringToObject(pRule, "pattern", regexPattern);
×
288

289
      snprintf(desc, sizeof(desc), "Learned pattern (count:%d) - Pattern: %s", pPattern->count, pPattern->pattern);
×
290
      (void)cJSON_AddStringToObject(pRule, "description", desc);
×
291
      (void)cJSON_AddBoolToObject(pRule, "enabled", true);
×
292

293
      if(!cJSON_AddItemToArray(pRules, pRule)) {
×
294
        tscError("sql security: failed to add rule to array");
×
295
      }
296
      pPattern->exported = true;
×
297
      added++;
×
298
    }
299
  }
300

301
  if (existingPatterns != NULL) {
×
302
    taosHashCleanup(existingPatterns);
×
303
  }
304

305
  // Write back to file
306
  if (added > 0) {
×
307
    char* jsonStr = cJSON_Print(pRoot);
×
308
    if (jsonStr != NULL) {
×
309
      fp = taosOpenFile(ruleFile, TD_FILE_WRITE | TD_FILE_CREATE | TD_FILE_TRUNC);
×
310
      if (fp != NULL) {
×
311
        if (taosWriteFile(fp, jsonStr, strlen(jsonStr)) != strlen(jsonStr)) {
×
312
          tscError("sql security: failed to write learned rules to %s", ruleFile);
×
313
        }
314
        TAOS_UNUSED(taosCloseFile(&fp));
×
315
        tscInfo("sql security: saved %d learned rules to %s", added, ruleFile);
×
316

317
        // Force reload rules by clearing the cache
318
        if (gSqlSecCtx.inited) {
×
319
          (void)taosThreadMutexLock(&gSqlSecCtx.lock);
×
320
          gSqlSecCtx.lastRuleMtime = 0;  // Force reload on next check
×
321
          (void)taosThreadMutexUnlock(&gSqlSecCtx.lock);
×
322
        }
323
      } else {
324
        tscWarn("sql security: failed to open rule file for writing: %s", ruleFile);
×
325
      }
326
      taosMemoryFree(jsonStr);
×
327
    }
328
  }
329

330
  cJSON_Delete(pRoot);
×
331
  (void)taosThreadMutexUnlock(&gLearningCtx.lock);
×
332

333
  return added;
×
334
}
335

336
static void sqlSecInitLearningOnce(void) {
×
337
  (void)taosThreadMutexInit(&gLearningCtx.lock, NULL);
×
338
  (void)taosThreadMutexInit(&gLearningCtx.threadLock, NULL);
×
339
  (void)taosThreadCondInit(&gLearningCtx.cond, NULL);
×
340
  gLearningCtx.enabled       = true;
×
341
  gLearningCtx.numOfPatterns = 0;
×
342
  gLearningCtx.startTs       = taosGetTimestampMs();
×
343
  gLearningCtx.nextRuleId    = 1000;  // Start from 1000 for learned rules
×
344
  atomic_store_8((int8_t volatile *)&gLearningCtx.threadRunning, 0);
×
345
  atomic_store_8((int8_t volatile *)&gLearningCtx.threadStop, 0);
×
346
  tscInfo("sql security: learning mode initialized");
×
347
}
×
348

349
static void sqlSecInitLearning(void) {
×
350
  (void)taosThreadOnce(&gLearningInitOnce, sqlSecInitLearningOnce);
×
351
}
×
352

353
static void sqlSecRecordPattern(const char* sql) {
×
354
  if (!tsWhitelistLearning || sql == NULL) return;
×
355

356
  if (!gLearningCtx.enabled) {
×
357
    sqlSecInitLearning();
×
358
  }
359

360
  char pattern[SQL_SEC_PATTERN_LEN] = {0};
×
361
  sqlSecGeneralizePattern(sql, pattern, sizeof(pattern));
×
362

363
  if (strlen(pattern) == 0) return;
×
364

365
  (void)taosThreadMutexLock(&gLearningCtx.lock);
×
366

367
  int64_t now = taosGetTimestampMs();
×
368
  int64_t periodMs = (int64_t)tsWhitelistLearningPeriod * 24 * 3600 * 1000;
×
369

370
  // Check if pattern already exists
371
  int32_t foundIdx = -1;
×
372
  for (int32_t i = 0; i < gLearningCtx.numOfPatterns; ++i) {
×
373
    if (strcmp(gLearningCtx.patterns[i].pattern, pattern) == 0) {
×
374
      foundIdx = i;
×
375
      break;
×
376
    }
377
  }
378

379
  if (foundIdx >= 0) {
×
380
    // Update existing pattern
381
    gLearningCtx.patterns[foundIdx].count++;
×
382
    gLearningCtx.patterns[foundIdx].lastSeenTs = now;
×
383

384
    // Check if threshold reached - log it
385
    if (gLearningCtx.patterns[foundIdx].count == tsWhitelistLearningThreshold) {
×
386
      tscInfo("sql security: pattern reached threshold, count:%d, pattern:%s", gLearningCtx.patterns[foundIdx].count,
×
387
              pattern);
388
    }
389
  } else {
390
    // Add new pattern
391
    if (gLearningCtx.numOfPatterns < SQL_SEC_MAX_PATTERNS) {
×
392
      SSqlPattern* pPattern = &gLearningCtx.patterns[gLearningCtx.numOfPatterns];
×
393
      tstrncpy(pPattern->pattern, pattern, SQL_SEC_PATTERN_LEN);
×
394
      pPattern->count = 1;
×
395
      pPattern->firstSeenTs = now;
×
396
      pPattern->lastSeenTs = now;
×
397
      pPattern->exported = false;
×
398
      gLearningCtx.numOfPatterns++;
×
399
    }
400
  }
401

402
  (void)taosThreadMutexUnlock(&gLearningCtx.lock);
×
403
}
404

405
// Learning thread function
406
static void* sqlSecLearningThreadFunc(void* arg) {
×
407
  tscInfo("sql security: learning export thread started");
×
408

409
  (void)taosThreadMutexLock(&gLearningCtx.threadLock);
×
410

411
  while (!atomic_load_8((int8_t volatile *)&gLearningCtx.threadStop)) {
×
412
    // Wait for 10 seconds or until signaled to stop
413
    struct timespec ts = {0};
×
414
    int64_t         nowMs = taosGetTimestampMs();
×
415
    int64_t         futMs = nowMs + 10 * 1000;  // check every 10 seconds
×
416
    ts.tv_sec = (time_t)(futMs / 1000);
×
417
    ts.tv_nsec = (long)((futMs % 1000) * 1000000);
×
418

419
    int ret = taosThreadCondTimedWait(&gLearningCtx.cond, &gLearningCtx.threadLock, &ts);
×
420

421
    if (atomic_load_8((int8_t volatile *)&gLearningCtx.threadStop)) {
×
422
      break;
×
423
    }
424

425
    // Check if there are patterns that reached threshold
426
    (void)taosThreadMutexLock(&gLearningCtx.lock);
×
427
    bool hasThresholdPatterns = false;
×
428
    for (int32_t i = 0; i < gLearningCtx.numOfPatterns; ++i) {
×
429
      if (gLearningCtx.patterns[i].count >= tsWhitelistLearningThreshold) {
×
430
        hasThresholdPatterns = true;
×
431
        break;
×
432
      }
433
    }
434
    (void)taosThreadMutexUnlock(&gLearningCtx.lock);
×
435

436
    // Export rules if there are patterns that reached threshold
437
    if (hasThresholdPatterns) {
×
438
      int32_t saved = sqlSecSaveLearnedRules(tsSqlSecurityRuleFile);
×
439
      if (saved > 0) {
×
440
        tscInfo("sql security: learning thread exported %d rules to %s", saved, tsSqlSecurityRuleFile);
×
441
      }
442
    }
443
  }
444

445
  (void)taosThreadMutexUnlock(&gLearningCtx.threadLock);
×
446

447
  tscInfo("sql security: learning export thread stopped");
×
448
  return NULL;
×
449
}
450

451
void sqlSecurityStartLearningThread() {
×
452
  if (!gLearningCtx.enabled) {
×
453
    sqlSecInitLearning();
×
454
  }
455

456
  (void)taosThreadMutexLock(&gLearningCtx.threadLock);
×
457

458
  if (atomic_load_8((int8_t volatile *)&gLearningCtx.threadRunning)) {
×
459
    (void)taosThreadMutexUnlock(&gLearningCtx.threadLock);
×
460
    tscInfo("sql security: learning thread already running");
×
461
    return;
×
462
  }
463

464
  atomic_store_8((int8_t volatile *)&gLearningCtx.threadStop, 0);
×
465

466
  TdThreadAttr attr;
×
467
  (void)taosThreadAttrInit(&attr);
×
468
  (void)taosThreadAttrSetDetachState(&attr, PTHREAD_CREATE_JOINABLE);
×
469

470
  if (taosThreadCreate(&gLearningCtx.thread, &attr, sqlSecLearningThreadFunc, NULL) != 0) {
×
471
    tscError("sql security: failed to create learning thread");
×
472
    (void)taosThreadMutexUnlock(&gLearningCtx.threadLock);
×
473
    return;
×
474
  }
475

476
  (void)taosThreadAttrDestroy(&attr);
×
477
  atomic_store_8((int8_t volatile *)&gLearningCtx.threadRunning, 1);
×
478

479
  (void)taosThreadMutexUnlock(&gLearningCtx.threadLock);
×
480

481
  tscInfo("sql security: learning thread started");
×
482
}
483

484
void sqlSecurityStopLearningThread() {
×
485
  (void)taosThreadMutexLock(&gLearningCtx.threadLock);
×
486

487
  if (!atomic_load_8((int8_t volatile *)&gLearningCtx.threadRunning)) {
×
488
    (void)taosThreadMutexUnlock(&gLearningCtx.threadLock);
×
489
    tscInfo("sql security: learning thread not running");
×
490
    return;
×
491
  }
492

493
  atomic_store_8((int8_t volatile *)&gLearningCtx.threadStop, 1);
×
494

495
  // Signal the thread to wake up
496
  (void)taosThreadCondSignal(&gLearningCtx.cond);
×
497

498
  (void)taosThreadMutexUnlock(&gLearningCtx.threadLock);
×
499

500
  // Wait for thread to finish
501
  (void)taosThreadJoin(gLearningCtx.thread, NULL);
×
502

503
  (void)taosThreadMutexLock(&gLearningCtx.threadLock);
×
504
  atomic_store_8((int8_t volatile *)&gLearningCtx.threadRunning, 0);
×
505
  (void)taosThreadMutexUnlock(&gLearningCtx.threadLock);
×
506

507
  tscInfo("sql security: learning thread stopped");
×
508
}
509

510
static void sqlSecClearRules(SSqlSecCtx* pCtx) {
×
511
  for (int32_t i = 0; i < pCtx->numOfRules; ++i) {
×
512
    if (pCtx->rules[i].regexInited) {
×
513
      regfree(&pCtx->rules[i].regex);
×
514
      pCtx->rules[i].regexInited = false;
×
515
    }
516
  }
517
  pCtx->numOfRules = 0;
×
518
}
×
519

520
static int32_t sqlSecParsePriority(const cJSON* pRule) {
×
521
  const cJSON* p = cJSON_GetObjectItemCaseSensitive((cJSON*)pRule, "priority");
×
522
  if (!cJSON_IsString(p) || p->valuestring == NULL) {
×
523
    return 2;
×
524
  }
525
  if (strcasecmp(p->valuestring, "HIGH") == 0) return 3;
×
526
  if (strcasecmp(p->valuestring, "LOW") == 0) return 1;
×
527
  return 2;
×
528
}
529

530
static ESqlSecAction sqlSecParseAction(const cJSON* pRule) {
×
531
  const cJSON* p = cJSON_GetObjectItemCaseSensitive((cJSON*)pRule, "action");
×
532
  if (cJSON_IsString(p) && p->valuestring != NULL && strcasecmp(p->valuestring, "ALLOW") == 0) {
×
533
    return SQL_SEC_ACTION_ALLOW;
×
534
  }
535
  return SQL_SEC_ACTION_DENY;
×
536
}
537

538
static bool sqlSecAppendRule(SSqlSecCtx* pCtx, const cJSON* pRule) {
×
539
  if (pCtx->numOfRules >= SQL_SEC_MAX_RULES) {
×
540
    return false;
×
541
  }
542

543
  const cJSON* pPattern = cJSON_GetObjectItemCaseSensitive((cJSON*)pRule, "pattern");
×
544
  if (!cJSON_IsString(pPattern) || pPattern->valuestring == NULL || strlen(pPattern->valuestring) == 0) {
×
545
    return false;
×
546
  }
547

548
  SSqlSecRule* pDst = &pCtx->rules[pCtx->numOfRules];
×
549
  memset(pDst, 0, sizeof(*pDst));
×
550

551
  const cJSON* pId = cJSON_GetObjectItemCaseSensitive((cJSON*)pRule, "ruleId");
×
552
  if (cJSON_IsNumber(pId)) {
×
553
    pDst->id = pId->valueint;
×
554
  }
555
  const cJSON* pName = cJSON_GetObjectItemCaseSensitive((cJSON*)pRule, "ruleName");
×
556
  if (cJSON_IsString(pName) && pName->valuestring != NULL) {
×
557
    tstrncpy(pDst->name, pName->valuestring, SQL_SEC_RULE_NAME_LEN);
×
558
  } else {
559
    tstrncpy(pDst->name, "unnamed_rule", SQL_SEC_RULE_NAME_LEN);
×
560
  }
561
  tstrncpy(pDst->pattern, pPattern->valuestring, SQL_SEC_RULE_PATTERN_LEN);
×
562
  pDst->priority = sqlSecParsePriority(pRule);
×
563
  pDst->action = sqlSecParseAction(pRule);
×
564

565
  const cJSON* pEnabled = cJSON_GetObjectItemCaseSensitive((cJSON*)pRule, "enabled");
×
566
  pDst->enabled = !cJSON_IsBool(pEnabled) || cJSON_IsTrue(pEnabled);
×
567

568
  if (!pDst->enabled) {
×
569
    pCtx->numOfRules++;
×
570
    return true;
×
571
  }
572

573
  if (regcomp(&pDst->regex, pDst->pattern, REG_EXTENDED | REG_NOSUB | REG_ICASE) != 0) {
×
574
    tscWarn("sql security: skip invalid regex rule:%s pattern:%s", pDst->name, pDst->pattern);
×
575
    return false;
×
576
  }
577

578
  pDst->regexInited = true;
×
579
  pCtx->numOfRules++;
×
580
  return true;
×
581
}
582

583
static void sqlSecLoadDefaultRules(SSqlSecCtx* pCtx) {
×
584
  cJSON* pRules = cJSON_CreateArray();
×
585
  if (pRules == NULL) return;
×
586

587
  cJSON* p1 = cJSON_CreateObject();
×
588
  if (p1 != NULL) {
×
589
    (void)cJSON_AddNumberToObject(p1, "ruleId", 1);
×
590
    (void)cJSON_AddStringToObject(p1, "ruleName", "DENY_UNION_SELECT");
×
591
    (void)cJSON_AddStringToObject(p1, "action", "DENY");
×
592
    (void)cJSON_AddStringToObject(p1, "pattern", "union[[:space:]]+select");
×
593
    (void)cJSON_AddBoolToObject(p1, "enabled", true);
×
594
    if(!cJSON_AddItemToArray(pRules, p1)) {
×
595
      tscError("sql security: failed to add rule to array");
×
596
    }
597
  }
598

599
  cJSON* p2 = cJSON_CreateObject();
×
600
  if (p2 != NULL) {
×
601
    (void)cJSON_AddNumberToObject(p2, "ruleId", 2);
×
602
    (void)cJSON_AddStringToObject(p2, "ruleName", "DENY_DROP_TABLE");
×
603
    (void)cJSON_AddStringToObject(p2, "action", "DENY");
×
604
    (void)cJSON_AddStringToObject(p2, "pattern", "drop[[:space:]]+table");
×
605
    (void)cJSON_AddBoolToObject(p2, "enabled", true);
×
606
    if(!cJSON_AddItemToArray(pRules, p2)) {
×
607
      tscError("sql security: failed to add rule to array");
×
608
    }
609
  }
610

611
  int32_t n = cJSON_GetArraySize(pRules);
×
612
  for (int32_t i = 0; i < n; ++i) {
×
613
    cJSON* pRule = cJSON_GetArrayItem(pRules, i);
×
614
    (void)sqlSecAppendRule(pCtx, pRule);
×
615
  }
616
  cJSON_Delete(pRules);
×
617
}
618

619
static int64_t sqlSecGetFileMtime(const char* path) {
×
620
  int64_t fsize = 0;
×
621
  int64_t mtime = 0;
×
622
  if (path == NULL || path[0] == 0) return -1;
×
623
  if (taosStatFile(path, &fsize, &mtime, NULL) < 0) return -1;
×
624
  return mtime;
×
625
}
626

627
static void sqlSecInitRulesOnce(void) {
×
628
  (void)taosThreadMutexInit(&gSqlSecCtx.lock, NULL);
×
629
  gSqlSecCtx.inited       = true;
×
630
  gSqlSecCtx.numOfRules   = 0;
×
631
  gSqlSecCtx.lastLoadTsMs = 0;
×
632
  gSqlSecCtx.lastRuleMtime = 0;
×
633
  gSqlSecCtx.lastRulePath[0] = '\0';
×
634
}
×
635

636
static void sqlSecReloadRulesIfNeeded(const char* ruleFile) {
×
637
  if (ruleFile == NULL || ruleFile[0] == 0) return;
×
638

639
  (void)taosThreadOnce(&gSqlSecInitOnce, sqlSecInitRulesOnce);
×
640

641
  // Check if reload is needed (with lock to avoid race condition)
642
  (void)taosThreadMutexLock(&gSqlSecCtx.lock);
×
643
  int64_t now = taosGetTimestampMs();
×
644
  if (now - gSqlSecCtx.lastLoadTsMs < 1000) {
×
645
    (void)taosThreadMutexUnlock(&gSqlSecCtx.lock);
×
646
    return;
×
647
  }
648
  gSqlSecCtx.lastLoadTsMs = now;
×
649

650
  int64_t mtime = sqlSecGetFileMtime(ruleFile);
×
651
  bool    pathChanged = (strcmp(ruleFile, gSqlSecCtx.lastRulePath) != 0);
×
652
  if (pathChanged) {
×
653
    tstrncpy(gSqlSecCtx.lastRulePath, ruleFile, sizeof(gSqlSecCtx.lastRulePath));
×
654
  }
655
  if (!pathChanged && mtime == gSqlSecCtx.lastRuleMtime && gSqlSecCtx.numOfRules > 0) {
×
656
    (void)taosThreadMutexUnlock(&gSqlSecCtx.lock);
×
657
    return;
×
658
  }
659

660
  sqlSecClearRules(&gSqlSecCtx);
×
661
  gSqlSecCtx.lastRuleMtime = mtime;
×
662

663
  if (mtime < 0) {
×
664
    sqlSecLoadDefaultRules(&gSqlSecCtx);
×
665
    (void)taosThreadMutexUnlock(&gSqlSecCtx.lock);
×
666
    return;
×
667
  }
668

669
  int64_t   fsize = 0;
×
670
  TdFilePtr fp = taosOpenFile(ruleFile, TD_FILE_READ);
×
671
  if (fp == NULL) {
×
672
    tscWarn("sql security: failed to open rule file:%s, using default rules", ruleFile);
×
673
    sqlSecLoadDefaultRules(&gSqlSecCtx);
×
674
    (void)taosThreadMutexUnlock(&gSqlSecCtx.lock);
×
675
    return;
×
676
  }
677

678
  if (taosFStatFile(fp, &fsize, NULL) != TSDB_CODE_SUCCESS) {
×
679
    TAOS_UNUSED(taosCloseFile(&fp));
×
680
    tscWarn("sql security: failed to stat rule file:%s, using default rules", ruleFile);
×
681
    sqlSecLoadDefaultRules(&gSqlSecCtx);
×
682
    (void)taosThreadMutexUnlock(&gSqlSecCtx.lock);
×
683
    return;
×
684
  }
685
  if (fsize <= 0 || fsize > 4 * 1024 * 1024) {
×
686
    TAOS_UNUSED(taosCloseFile(&fp));
×
687
    tscWarn("sql security: invalid rule file size:%" PRId64 ", using default rules", fsize);
×
688
    sqlSecLoadDefaultRules(&gSqlSecCtx);
×
689
    (void)taosThreadMutexUnlock(&gSqlSecCtx.lock);
×
690
    return;
×
691
  }
692

693
  char* pBuf = taosMemoryCalloc(1, (size_t)fsize + 1);
×
694
  if (pBuf == NULL) {
×
695
    TAOS_UNUSED(taosCloseFile(&fp));
×
696
    tscWarn("sql security: failed to allocate memory for rule file, using default rules");
×
697
    sqlSecLoadDefaultRules(&gSqlSecCtx);
×
698
    (void)taosThreadMutexUnlock(&gSqlSecCtx.lock);
×
699
    return;
×
700
  }
701
  int64_t nread = taosReadFile(fp, pBuf, (int64_t)fsize);
×
702
  TAOS_UNUSED(taosCloseFile(&fp));
×
703
  if (nread != fsize) {
×
704
    taosMemoryFree(pBuf);
×
705
    tscWarn("sql security: failed to read rule file, using default rules");
×
706
    sqlSecLoadDefaultRules(&gSqlSecCtx);
×
707
    (void)taosThreadMutexUnlock(&gSqlSecCtx.lock);
×
708
    return;
×
709
  }
710

711
  cJSON* pRoot = cJSON_Parse(pBuf);
×
712
  taosMemoryFree(pBuf);
×
713
  if (pRoot == NULL) {
×
714
    tscWarn("sql security: failed to parse rule file JSON, using default rules");
×
715
    sqlSecLoadDefaultRules(&gSqlSecCtx);
×
716
    (void)taosThreadMutexUnlock(&gSqlSecCtx.lock);
×
717
    return;
×
718
  }
719

720
  cJSON* pRules = cJSON_GetObjectItemCaseSensitive(pRoot, "rules");
×
721
  if (cJSON_IsArray(pRules)) {
×
722
    int32_t n = cJSON_GetArraySize(pRules);
×
723
    for (int32_t i = 0; i < n; ++i) {
×
724
      cJSON* pRule = cJSON_GetArrayItem(pRules, i);
×
725
      (void)sqlSecAppendRule(&gSqlSecCtx, pRule);
×
726
    }
727
  }
728
  cJSON_Delete(pRoot);
×
729

730
  if (gSqlSecCtx.numOfRules == 0) {
×
731
    tscWarn("sql security: no valid rules loaded, using default rules");
×
732
    sqlSecLoadDefaultRules(&gSqlSecCtx);
×
733
  } else {
734
    tscInfo("sql security: loaded %d rules from %s", gSqlSecCtx.numOfRules, ruleFile);
×
735
  }
736
  (void)taosThreadMutexUnlock(&gSqlSecCtx.lock);
×
737
}
738

739
static bool sqlSecIsModeWhitelist(int32_t mode) { return mode == 1 || mode == 3; }
×
740
static bool sqlSecIsModeBlacklist(int32_t mode) { return mode == 2 || mode == 3; }
×
741

742
static int32_t sqlSecDecideFromMatches(const SSqlSecMatchResult* pRes, int8_t enabled, int32_t mode) {
×
743
  if (!enabled || mode == 0) return TSDB_CODE_SUCCESS;
×
744

745
  bool whitelist = sqlSecIsModeWhitelist(mode);
×
746
  bool blacklist = sqlSecIsModeBlacklist(mode);
×
747

748
  // Blacklist mode: deny if matched and no whitelist override
749
  if (blacklist && pRes->denyMatched && (!whitelist || !pRes->allowMatched)) {
×
750
    return TSDB_CODE_PAR_PERMISSION_DENIED;
×
751
  }
752

753
  // Whitelist mode: deny if not matched
754
  if (whitelist && !pRes->allowMatched) {
×
755
    return TSDB_CODE_PAR_PERMISSION_DENIED;
×
756
  }
757

758
  // Mixed mode: both matched, compare priority
759
  // Rule: Higher priority wins. If equal, blacklist wins (deny >= allow means deny)
760
  if (blacklist && whitelist && pRes->denyMatched && pRes->allowMatched) {
×
761
    if (pRes->denyPriority >= pRes->allowPriority) {
×
762
      return TSDB_CODE_PAR_PERMISSION_DENIED;
×
763
    }
764
  }
765

766
  return TSDB_CODE_SUCCESS;
×
767
}
768

769
/* Simple substring check as fallback (sql already lowercased) */
770
static bool sqlSecSimpleDenyMatch(const char* sql, int32_t sqlLen) {
×
771
  if (sql == NULL || sqlLen <= 0) return false;
×
772
  if (strstr(sql, "union select") != NULL) return true;
×
773
  if (strstr(sql, "drop table") != NULL) return true;
×
774
  return false;
×
775
}
776

777
static SAppInstServerCFG* sqlSecGetCfg(SRequestObj* pRequest) {
1,343,625,054✔
778
  if (pRequest == NULL || pRequest->pTscObj == NULL || pRequest->pTscObj->pAppInfo == NULL) return NULL;
1,343,625,054✔
779
  return &pRequest->pTscObj->pAppInfo->serverCfg;
1,343,695,183✔
780
}
781

782
int32_t sqlSecurityCheckStringLevel(SRequestObj* pRequest, const char* sql, int32_t sqlLen) {
944,497,028✔
783
  SAppInstServerCFG* pCfg = sqlSecGetCfg(pRequest);
944,497,028✔
784

785
  // Manage learning thread based on tsWhitelistLearning
786
  if (tsWhitelistLearning) {
944,504,975✔
787
    // Learning mode enabled, ensure thread is running
788
    if (!atomic_load_8((int8_t volatile *)&gLearningCtx.threadRunning)) {
×
789
      sqlSecurityStartLearningThread();
×
790
    }
791
    sqlSecRecordPattern(sql);
×
792
  } else {
793
    // Learning mode disabled, ensure thread is stopped
794
    if (atomic_load_8((int8_t volatile *)&gLearningCtx.threadRunning)) {
944,504,975✔
795
      sqlSecurityStopLearningThread();
×
796
    }
797
  }
798

799
  if (pCfg == NULL || !tsSqlSecurityEnabled || !tsSqlSecurityStringCheck || sql == NULL || sqlLen <= 0) {
944,498,544✔
800
    return TSDB_CODE_SUCCESS;
944,498,544✔
801
  }
802
  if (sqlSecIsModeBlacklist(tsSqlSecurityWhitelistMode) && sqlSecSimpleDenyMatch(sql, sqlLen)) {
×
803
    tscWarn("req:0x%" PRIx64 ", sql security string check denied (simple match), sql:%s", pRequest->self, sql);
×
804
    return TSDB_CODE_PAR_PERMISSION_DENIED;
×
805
  }
806

807
  sqlSecReloadRulesIfNeeded(tsSqlSecurityRuleFile);
×
808

809
  SSqlSecMatchResult m = {0};
×
810
  m.denyPriority = -1;
×
811
  m.allowPriority = -1;
×
812

813
  (void)taosThreadMutexLock(&gSqlSecCtx.lock);
×
814
  for (int32_t i = 0; i < gSqlSecCtx.numOfRules; ++i) {
×
815
    SSqlSecRule* pRule = &gSqlSecCtx.rules[i];
×
816
    if (!pRule->enabled || !pRule->regexInited) {
×
817
      continue;
×
818
    }
819
    if (regexec(&pRule->regex, sql, 0, NULL, 0) != 0) {
×
820
      continue;
×
821
    }
822
    if (pRule->action == SQL_SEC_ACTION_DENY) {
×
823
      if (!m.denyMatched || pRule->priority > m.denyPriority) {
×
824
        m.denyMatched = true;
×
825
        m.denyPriority = pRule->priority;
×
826
        m.pDenyRule = pRule;
×
827
      }
828
    } else {
829
      if (!m.allowMatched || pRule->priority > m.allowPriority) {
×
830
        m.allowMatched = true;
×
831
        m.allowPriority = pRule->priority;
×
832
        m.pAllowRule = pRule;
×
833
      }
834
    }
835
  }
836
  (void)taosThreadMutexUnlock(&gSqlSecCtx.lock);
×
837

838
  int32_t code = sqlSecDecideFromMatches(&m, tsSqlSecurityEnabled, tsSqlSecurityWhitelistMode);
×
839
  if (code != TSDB_CODE_SUCCESS) {
×
840
    tscWarn("req:0x%" PRIx64 ", sql security string check denied, mode:%d, denyRule:%s, allowRule:%s, sql:%s",
×
841
            pRequest->self, tsSqlSecurityWhitelistMode, m.pDenyRule ? m.pDenyRule->name : "none",
842
            m.pAllowRule ? m.pAllowRule->name : "none", sql);
843
  }
844
  return code;
×
845
}
846

847
static bool sqlSecIsValueTrue(const SNode* pNode) {
×
848
  if (pNode == NULL) return false;
×
849
  if (nodeType((SNode*)pNode) != QUERY_NODE_VALUE) return false;
×
850

851
  const SValueNode* pVal = (const SValueNode*)pNode;
×
852
  if (pVal->isNull) return false;
×
853
  if (pVal->node.resType.type == TSDB_DATA_TYPE_BOOL) return pVal->datum.b;
×
854
  if (IS_INTEGER_TYPE(pVal->node.resType.type)) return pVal->datum.i != 0;
×
855
  if (pVal->literal != NULL && (strcasecmp(pVal->literal, "true") == 0 || strcmp(pVal->literal, "1") == 0)) {
×
856
    return true;
×
857
  }
858
  return false;
×
859
}
860

861
static bool sqlSecIsValueEqual(const SNode* pLeft, const SNode* pRight) {
×
862
  if (pLeft == NULL || pRight == NULL) return false;
×
863
  if (nodeType(pLeft) != QUERY_NODE_VALUE || nodeType(pRight) != QUERY_NODE_VALUE) return false;
×
864

865
  const SValueNode* l = (const SValueNode*)pLeft;
×
866
  const SValueNode* r = (const SValueNode*)pRight;
×
867

868
  if (l->isNull || r->isNull) return false;
×
869

870
  // Check if both are numbers and equal
871
  if (IS_NUMERIC_TYPE(l->node.resType.type) && IS_NUMERIC_TYPE(r->node.resType.type)) {
×
872
    if (IS_INTEGER_TYPE(l->node.resType.type) && IS_INTEGER_TYPE(r->node.resType.type)) {
×
873
      return l->datum.i == r->datum.i;
×
874
    }
875
    if (IS_FLOAT_TYPE(l->node.resType.type) && IS_FLOAT_TYPE(r->node.resType.type)) {
×
876
      return l->datum.d == r->datum.d;
×
877
    }
878
  }
879

880
  // Check if both are strings and equal
881
  if (l->literal != NULL && r->literal != NULL) {
×
882
    return strcmp(l->literal, r->literal) == 0;
×
883
  }
884

885
  return false;
×
886
}
887

888
static bool sqlSecIsConstEqTrue(const SNode* pNode) {
×
889
  if (pNode == NULL || nodeType((SNode*)pNode) != QUERY_NODE_OPERATOR) return false;
×
890
  const SOperatorNode* pOp = (const SOperatorNode*)pNode;
×
891
  if (pOp->opType != OP_TYPE_EQUAL || pOp->pLeft == NULL || pOp->pRight == NULL) return false;
×
892

893
  // Check if both sides are true values (TRUE, 1, etc.)
894
  if (sqlSecIsValueTrue(pOp->pLeft) && sqlSecIsValueTrue(pOp->pRight)) {
×
895
    return true;
×
896
  }
897

898
  // Check if both sides are equal constant values (1=1, 'a'='a', 0=0, etc.)
899
  if (sqlSecIsValueEqual(pOp->pLeft, pOp->pRight)) {
×
900
    return true;
×
901
  }
902

903
  return false;
×
904
}
905

906
static EDealRes sqlSecAstWalker(SNode* pNode, void* pContext) {
×
907
  if (pNode == NULL || pContext == NULL) {
×
908
    return DEAL_RES_CONTINUE;
×
909
  }
910

911
  SSqlSecAstResult* pRes = (SSqlSecAstResult*)pContext;
×
912

913
  // Check logic conditions (OR with true values)
914
  if (nodeType(pNode) == QUERY_NODE_LOGIC_CONDITION) {
×
915
    SLogicConditionNode* pCond = (SLogicConditionNode*)pNode;
×
916
    if (pCond->condType == LOGIC_COND_TYPE_OR) {
×
917
      SNode* pParam = NULL;
×
918
      FOREACH(pParam, pCond->pParameterList) {
×
919
        if (sqlSecIsValueTrue(pParam) || sqlSecIsConstEqTrue(pParam)) {
×
920
          pRes->hasOrTrue = true;
×
921
          return DEAL_RES_END;
×
922
        }
923
      }
924
    }
925
  }
926
  // Check operators (constant equal true like 1=1)
927
  else if (nodeType(pNode) == QUERY_NODE_OPERATOR) {
×
928
    if (sqlSecIsConstEqTrue(pNode)) {
×
929
      pRes->hasOrTrue = true;
×
930
      return DEAL_RES_END;
×
931
    }
932
  }
933
  // Check dangerous functions
934
  else if (nodeType(pNode) == QUERY_NODE_FUNCTION) {
×
935
    SFunctionNode* pFunc = (SFunctionNode*)pNode;
×
936
    if (strcasecmp(pFunc->functionName, "load_file") == 0 || strcasecmp(pFunc->functionName, "exec") == 0 ||
×
937
        strcasecmp(pFunc->functionName, "eval") == 0 || strcasecmp(pFunc->functionName, "sleep") == 0 ||
×
938
        strcasecmp(pFunc->functionName, "system") == 0 || strcasecmp(pFunc->functionName, "shell") == 0) {
×
939
      pRes->hasDangerFunc = true;
×
940
      return DEAL_RES_END;
×
941
    }
942
  }
943
  // Check JOIN conditions
944
  else if (nodeType(pNode) == QUERY_NODE_JOIN_TABLE) {
×
945
    SJoinTableNode* pJoin = (SJoinTableNode*)pNode;
×
946
    if (pJoin->pOnCond != NULL) {
×
947
      // Check if JOIN condition is always true (e.g., ON 1=1)
948
      if (sqlSecIsValueTrue(pJoin->pOnCond) || sqlSecIsConstEqTrue(pJoin->pOnCond)) {
×
949
        pRes->hasUnsafeJoin = true;
×
950
        return DEAL_RES_END;
×
951
      }
952
    }
953
  }
954

955
  return DEAL_RES_CONTINUE;
×
956
}
957

958
int32_t sqlSecurityCheckASTLevel(SRequestObj* pRequest, SQuery* pQuery) {
399,142,691✔
959
  SAppInstServerCFG* pCfg = sqlSecGetCfg(pRequest);
399,142,691✔
960
  if (pCfg == NULL || !tsSqlSecurityEnabled || !tsSqlSecurityASTCheck || pRequest == NULL || pQuery == NULL ||
399,205,020✔
961
      pQuery->pRoot == NULL) {
×
962
    return TSDB_CODE_SUCCESS;
399,180,854✔
963
  }
964

965
  SSqlSecAstResult res = {0};
×
966
  nodesWalkExpr((SNode*)pQuery->pRoot, sqlSecAstWalker, &res);
×
967

968
  if (res.hasOrTrue || res.hasDangerFunc || res.hasUnsafeJoin) {
×
969
        tscWarn(
×
970
        "req:0x%" PRIx64
971
        ", sql security AST check denied, hasOrTrue:%d, hasDangerFunc:%d, hasUnsafeJoin:%d, sql:%s",
972
        pRequest->self, res.hasOrTrue, res.hasDangerFunc, res.hasUnsafeJoin,
973
        pRequest->sqlstr ? pRequest->sqlstr : "");
974
    return TSDB_CODE_PAR_PERMISSION_DENIED;
×
975
  }
976

977
  return TSDB_CODE_SUCCESS;
×
978
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc