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

teableio / teable / 15549652450

10 Jun 2025 03:11AM UTC coverage: 81.093% (-0.04%) from 81.129%
15549652450

push

github

web-flow
feat: enhance base chat (#1571)

* fix: send chat message when status is error

* fix: react error in ChatTriggerButton

* fix: deep with cte in checkTableAccess

* fix: getPluginInstall endpoints is view|read permission

* feat: base chat support select view context

* fix: typecheck error

* chore: opentelemetry exports logs

* chore: update pnpm lock

7943 of 8453 branches covered (93.97%)

5 of 29 new or added lines in 2 files covered. (17.24%)

2 existing lines in 1 file now uncovered.

38109 of 46994 relevant lines covered (81.09%)

1756.08 hits per line

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

93.33
/apps/nestjs-backend/src/features/base-sql-executor/utils.ts
1
import { BadRequestException } from '@nestjs/common';
2✔
2
import { DriverClient } from '@teable/core';
3
import type { AST } from 'node-sql-parser';
4
import { Parser } from 'node-sql-parser';
5

6
export const validateRoleOperations = (sql: string) => {
2✔
7
  const removeQuotedContent = (sql: string) => {
16✔
8
    return sql.replace(/'[^']*'|"[^"]*"/g, ' ');
16✔
9
  };
16✔
10

11
  const normalizedSql = sql.toLowerCase().replace(/\s+/g, ' ');
16✔
12
  const sqlWithoutQuotes = removeQuotedContent(normalizedSql);
16✔
13

14
  const roleOperationPatterns = [/set\s+role/, /reset\s+role/, /set\s+session/];
16✔
15

16
  for (const pattern of roleOperationPatterns) {
16✔
17
    if (pattern.test(sqlWithoutQuotes)) {
28✔
18
      throw new BadRequestException(`not allowed to execute sql with keyword: ${pattern.source}`);
12✔
19
    }
12✔
20
  }
28✔
21
};
4✔
22

23
const databaseTypeMap = {
2✔
24
  [DriverClient.Pg]: 'postgresql',
2✔
25
  [DriverClient.Sqlite]: 'sqlite',
2✔
26
};
2✔
27

28
const collectWithNames = (ast?: AST) => {
2✔
29
  if (!ast) {
20!
NEW
30
    return [];
×
NEW
31
  }
×
32
  const withNames: string[] = [];
20✔
33
  if (ast.type === 'select' && ast.with) {
20✔
34
    ast.with.forEach((withItem) => {
10✔
35
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
10✔
36
      const names = (withItem.stmt as any) ? collectWithNames(withItem.stmt as any) : [];
10!
37
      withNames.push(...names, withItem.name.value);
10✔
38
    });
10✔
39
  }
10✔
40
  return withNames;
20✔
41
};
20✔
42

43
export const checkTableAccess = (
2✔
44
  sql: string,
10✔
45
  {
10✔
46
    tableNames,
10✔
47
    database,
10✔
48
  }: {
49
    tableNames: string[];
50
    database: DriverClient;
51
  }
10✔
52
) => {
53
  const parser = new Parser();
10✔
54
  const opt = {
10✔
55
    database: databaseTypeMap[database],
10✔
56
  };
10✔
57
  const { ast } = parser.parse(sql, opt);
10✔
58
  const withNames = Array.isArray(ast) ? ast.map(collectWithNames).flat() : collectWithNames(ast);
10!
59
  const allWithNames = new Set([...withNames, ...tableNames]);
10✔
60
  const whiteColumnList = Array.from(allWithNames).map((table) => {
10✔
61
    const [schema, tableName] = table.includes('.') ? table.split('.') : [null, table];
24✔
62
    return `select::${schema}::${tableName}`;
24✔
63
  });
24✔
64

65
  try {
10✔
66
    const error = parser.whiteListCheck(sql, whiteColumnList, opt);
10✔
67
    if (error) {
10!
68
      throw error;
×
69
    }
×
70
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
10✔
71
  } catch (error: any) {
10✔
72
    throw new BadRequestException(
2✔
73
      error?.message || 'An error occurred while checking table access.'
2!
74
    );
75
  }
2✔
76
};
10✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc