Skip to content

Commit 4cebecb

Browse files
authored
fix: parse table name with prefix and quote (#606)
* fix: parse table name with prefix and quote * refactor: simple
1 parent 2131aba commit 4cebecb

7 files changed

Lines changed: 57 additions & 29 deletions

File tree

src/api/interceptor.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,22 @@ export interface Auth {
2121
}
2222

2323
// todo: can we use env and proxy at the same time?
24-
export const TableNameReg = /(?<=from|FROM)\s+([^\s;]+)/
25-
export function parseTable(sql: string) {
24+
export const TableNameReg = /(?<=from|FROM)\s+([^\s;]+)/i
25+
/**
26+
* Parse table reference from SQL after FROM. Returns the raw table reference as-is (e.g. "temp_data"."cpu_metrics"), no conversion.
27+
*/
28+
function parseTableFromSql(sql: string): string {
2629
try {
2730
sql = decodeURIComponent(sql)
2831
const result = sql.match(TableNameReg)
29-
if (result && result.length) {
30-
const arr = result[1].trim().split('.')
31-
return arr[arr.length - 1]
32-
}
33-
} catch (e) {
32+
if (!result?.[1]) return ''
33+
return result[1].trim()
34+
} catch {
3435
return ''
3536
}
36-
return ''
37+
}
38+
export function parseTable(sql: string) {
39+
return parseTableFromSql(sql)
3740
}
3841

3942
axios.interceptors.request.use(

src/components/count-chart/index.vue

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ VCharts(
1414
import * as echarts from 'echarts'
1515
import editorAPI from '@/api/editor'
1616
import type { QueryState } from '@/types/query'
17-
import { replaceTimePlaceholders } from '@/utils/sql'
17+
import { replaceTimePlaceholders, getTableRefForSql } from '@/utils/sql'
1818
import { convertTimestampToMilliseconds } from '@/utils/date-time'
1919
import { useDateTimeFormat } from '@/hooks'
2020
@@ -170,8 +170,7 @@ VCharts(
170170
171171
const whereClause = whereMatch ? `WHERE ${whereMatch[1]}` : ''
172172
173-
// Build table name with database prefix if available
174-
const tableName = database ? `"${database}"."${table}"` : `"${table}"`
173+
const tableName = getTableRefForSql({ table, database })
175174
176175
return `SELECT
177176
date_bin('${intervalSeconds.value} seconds', "${tsColumn.name}") AS time_bucket,

src/hooks/use-query-execution.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ref, reactive, computed, watch, shallowRef } from 'vue'
2-
import { replaceTimePlaceholders } from '@/utils/sql'
2+
import { replaceTimePlaceholders, getTableRefForSql } from '@/utils/sql'
33
import type { ColumnType, QueryState } from '@/types/query'
44

55
const useQueryExecution = (builder, textEditor, timeRange) => {
@@ -53,8 +53,7 @@ const useQueryExecution = (builder, textEditor, timeRange) => {
5353
const whereMatch = currentSql.match(/WHERE\s+([\s\S]+?)(?:\s+ORDER\s+BY|\s+LIMIT\s+|\s*$)/i)
5454
const whereClause = whereMatch ? `WHERE ${whereMatch[1]}` : ''
5555

56-
// Build COUNT query with database prefix if available
57-
const tableName = queryState.database ? `"${queryState.database}"."${queryState.table}"` : `"${queryState.table}"`
56+
const tableName = getTableRefForSql(queryState)
5857
const countSql = `SELECT COUNT(*) FROM ${tableName} ${whereClause}`
5958

6059
const { default: editorAPI } = await import('@/api/editor')

src/hooks/use-text-editor-state.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ref, type Ref } from 'vue'
1+
import { reactive } from 'vue'
22
import { TextEditorFormState } from '@/types/query'
33
import { replaceTimePlaceholders } from '@/utils/sql'
44

src/utils/sql.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,29 @@ export function replaceTimePlaceholders(sql: string, timeRanges: any[]): string
201201

202202
return result
203203
}
204+
205+
/**
206+
* Table reference for use in SQL (e.g. in FROM clause).
207+
* - When database is provided (builder mode), both database and table are quoted safely.
208+
* - When only table is provided, it may already contain schema prefix (e.g. "temp_data"."cpu_metrics")
209+
* or be unqualified (e.g. my_schema.my_table). In the latter case each part is safely quoted.
210+
*/
211+
export function getTableRefForSql(state: { table: string; database?: string }): string {
212+
const quote = (id: string) => `"${id.replace(/"/g, '""')}"`
213+
const { table, database } = state
214+
215+
if (database) {
216+
return `${quote(database)}.${quote(table)}`
217+
}
218+
219+
// Text / free-sql mode: respect fully quoted references
220+
if (/^["'`]/.test(table)) {
221+
return table
222+
}
223+
224+
// Quote each identifier segment (schema.table or just table)
225+
return table
226+
.split('.')
227+
.map((part) => quote(part))
228+
.join('.')
229+
}

src/views/dashboard/logs/query/FunnelChart.vue

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ VCharts(
1414
import { watchOnce } from '@vueuse/core'
1515
import editorAPI from '@/api/editor'
1616
import type { QueryState } from '@/types/query'
17-
import { replaceTimePlaceholders } from '@/utils/sql'
17+
import { replaceTimePlaceholders, getTableRefForSql } from '@/utils/sql'
1818
import { getWhereClause } from './until'
1919
2020
interface Props {
@@ -84,10 +84,7 @@ VCharts(
8484
const currentSql = replaceTimePlaceholders(props.queryState.sql, [startTs, endTs])
8585
const whereClause = getWhereClause(currentSql)
8686
const condition = whereClause ? `WHERE ${whereClause}` : ''
87-
// Build table name with database prefix if available
88-
const tableName = props.queryState.database
89-
? `"${props.queryState.database}"."${props.queryState.table}"`
90-
: `"${props.queryState.table}"`
87+
const tableName = getTableRefForSql(props.queryState)
9188
return `SELECT ${props.column} ,count(*) AS c FROM ${tableName} ${condition} GROUP BY ${props.column} ORDER BY c DESC`
9289
})
9390

src/views/dashboard/logs/query/until.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import dayjs from 'dayjs'
22
import type { SchemaType } from '@/store/modules/code-run/types'
33
import type { TSColumn } from '@/types/query'
4+
import { getTableRefForSql } from '@/utils/sql'
45

56
function findWhereClausePosition(sql: string) {
67
// Normalize case for easier comparison
@@ -93,17 +94,20 @@ export function addTsCondition(sql: string, column: string, start: number | stri
9394
return `${sql.slice(0, whereIndex - 1)} WHERE ${column} >= ${start} and ${column} < ${end} ${sql.slice(whereIndex)}`
9495
}
9596

96-
export const TableNameReg = /(?<=from|FROM)\s+([^\s;]+)/
97+
export const TableNameReg = /(?<=from|FROM)\s+([^\s;]+)/i
98+
99+
/**
100+
* Parse table reference after FROM and normalize it to a safe, quoted identifier:
101+
* - "db"."table" → "db"."table"
102+
* - db.table → "db"."table"
103+
* - table → "table"
104+
*/
97105
export function parseTable(sql: string) {
98106
const result = sql.match(TableNameReg)
99-
if (result && result.length) {
100-
const tableName = result[1].trim()
101-
// Remove quotes if they exist
102-
const cleanTableName = tableName.replace(/^["'`]|["'`]$/g, '')
103-
const arr = cleanTableName.split('.')
104-
return arr[arr.length - 1]
105-
}
106-
return ''
107+
if (!result?.[1]) return ''
108+
const raw = result[1].trim()
109+
// Let SQL utils handle proper quoting / escaping for identifiers
110+
return getTableRefForSql({ table: raw })
107111
}
108112

109113
export function parseTimeRange(sql: string, tsColumn: string, multiple: number): string[] | number {

0 commit comments

Comments
 (0)