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

teableio / teable / 17546956543

08 Sep 2025 09:57AM UTC coverage: 79.704%. First build
17546956543

Pull #1829

github

web-flow
Merge 79c69488c into 41a0becf9
Pull Request #1829: perf: add dataloader to reduce duplicate field queries

8555 of 9088 branches covered (94.14%)

190 of 402 new or added lines in 19 files covered. (47.26%)

40339 of 50611 relevant lines covered (79.7%)

1691.88 hits per line

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

85.07
/apps/nestjs-backend/src/features/base-sql-executor/utils.ts
1
import { BadRequestException } from '@nestjs/common';
4✔
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) => {
4✔
7
  const removeQuotedContent = (sql: string) => {
18✔
8
    return sql.replace(/'[^']*'|"[^"]*"/g, ' ');
18✔
9
  };
18✔
10

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

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

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

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

28
const collectWithNames = (ast?: AST) => {
4✔
29
  if (!ast) {
22!
30
    return [];
×
31
  }
×
32
  const withNames: string[] = [];
22✔
33
  if (ast.type === 'select' && ast.with) {
22✔
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;
22✔
41
};
22✔
42

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

65
  try {
12✔
66
    const error = parser.whiteListCheck(sql, whiteColumnList, opt);
12✔
67
    if (error) {
12!
68
      throw error;
×
69
    }
×
70
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
12✔
71
  } catch (error: any) {
12✔
72
    throw new BadRequestException(
2✔
73
      error?.message || 'An error occurred while checking table access.'
2!
74
    );
75
  }
2✔
76
};
12✔
77

78
export const getTableNames = (sql: string) => {
4✔
NEW
79
  const parser = new Parser();
×
NEW
80
  const opt = {
×
NEW
81
    database: databaseTypeMap[DriverClient.Pg],
×
NEW
82
  };
×
NEW
83
  return parser.tableList(sql, opt);
×
NEW
84
};
×
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