TableEntity.utils.ts139 lines · main
1import { SupaTable } from '@/components/grid/types'
2import { Lint } from '@/data/lint/lint-query'
3
4export const getEntityLintDetails = (
5 entityName: string,
6 lintName: string,
7 lintLevels: ('ERROR' | 'WARN' | 'INFO')[],
8 lints: Lint[],
9 schema: string
10): { hasLint: boolean; count: number; matchingLint: Lint | null } => {
11 const matchingLint =
12 lints?.find(
13 (lint) =>
14 lint?.metadata?.name === entityName &&
15 lint?.metadata?.schema === schema &&
16 lint?.name === lintName &&
17 lintLevels.includes(lint?.level)
18 ) || null
19
20 return {
21 hasLint: matchingLint !== null,
22 count: matchingLint ? 1 : 0,
23 matchingLint,
24 }
25}
26
27export const getTablePoliciesUrl = (
28 projectRef: string | undefined,
29 schema: string | undefined,
30 name: string | undefined
31): string => {
32 return `/project/${projectRef ?? ''}/auth/policies?search=${encodeURIComponent(
33 name ?? ''
34 )}&schema=${encodeURIComponent(schema ?? '')}`
35}
36
37export const formatTableRowsToSQL = (table: SupaTable, rows: any[]) => {
38 if (rows.length === 0) return ''
39
40 const columns = table.columns.map((col) => `"${col.name}"`).join(', ')
41
42 const valuesSets = rows
43 .map((row) => {
44 const filteredRow = { ...row }
45 if ('idx' in filteredRow) delete filteredRow.idx
46
47 const values = Object.entries(filteredRow).map(([key, val]) => {
48 const { dataType, format } = table.columns.find((col) => col.name === key) ?? {}
49
50 // We only check for NULL, array and JSON types, everything else we stringify
51 // given that Postgres can implicitly cast the right type based on the column type
52 // For string types, we need to deal with escaping single quotes
53 const stringFormats = ['text', 'varchar']
54
55 if (val === null) {
56 return 'null'
57 } else if (dataType === 'ARRAY') {
58 const array = Array.isArray(val) ? val : JSON.parse(val as string)
59 return `${formatArrayForSql(array as unknown[])}`
60 } else if (format?.includes('json')) {
61 return `${JSON.stringify(val).replace(/\\"/g, '"').replace(/'/g, "''").replace('"', "'").replace(/.$/, "'")}`
62 } else if (
63 typeof format === 'string' &&
64 typeof val === 'string' &&
65 stringFormats.includes(format)
66 ) {
67 return `'${val.replaceAll("'", "''")}'`
68 } else if (typeof val === 'number' || typeof val === 'boolean') {
69 return `${val}`
70 } else if (typeof val === 'string') {
71 return `'${val.replaceAll("'", "''")}'`
72 } else {
73 return `'${val}'`
74 }
75 })
76
77 return `(${values.join(', ')})`
78 })
79 .join(', ')
80
81 return `INSERT INTO "${table.schema}"."${table.name}" (${columns}) VALUES ${valuesSets};`
82}
83
84/**
85 * Generate a random tag for dollar-quoting of SQL strings
86 *
87 * @return A random tag in the format `$tag$`
88 */
89const generateRandomTag = (): `$${string}$` => {
90 const inner = Math.random().toString(36).substring(2, 15)
91 // Ensure the tag starts with a character not a digit to avoid conflicts with
92 // Postgres parameter syntax
93 return `$x${inner}$`
94}
95
96/**
97 * Wrap a string in dollar-quote tags, ensuring the tag does not appear in the string
98 *
99 * @throws Error if unable to generate a unique dollar-quote tag after multiple attempts
100 */
101const safeDollarQuote = (str: string): string => {
102 let tag = generateRandomTag()
103
104 let attempts = 0
105 const maxAttempts = 100
106 while (str.includes(tag)) {
107 if (attempts >= maxAttempts) {
108 throw new Error('Unable to generate a unique dollar-quote tag after multiple attempts.')
109 }
110
111 attempts++
112 tag = generateRandomTag()
113 }
114 return `${tag}${str}${tag}`
115}
116
117const formatArrayForSql = (arr: unknown[]): string => {
118 let result = 'ARRAY['
119
120 arr.forEach((item, index) => {
121 if (Array.isArray(item)) {
122 result += formatArrayForSql(item)
123 } else if (typeof item === 'string') {
124 result += `'${item.replaceAll("'", "''")}'`
125 } else if (!!item && typeof item === 'object') {
126 result += `${safeDollarQuote(JSON.stringify(item))}::json`
127 } else {
128 result += `${item}`
129 }
130
131 if (index < arr.length - 1) {
132 result += ','
133 }
134 })
135
136 result += ']'
137
138 return result
139}