sql-parameters.ts114 lines · main
1export interface Parameter {
2 name: string
3 value: string
4 defaultValue?: string
5 type?: string
6 possibleValues?: string[]
7 occurrences: number
8}
9
10// [Joshen TODO] We'll want tests for this to ensure that this runs properly
11
12export const parseParameters = (sql: string | undefined) => {
13 if (!sql) return []
14
15 // Parse @set parameter defaults with optional type information
16 const setParamRegex = /@set\s+(\w+)(?::([^=]+))?\s*=\s*([^;\n]+)/g
17 const paramDefaults: Record<string, { value: string; type?: string; possibleValues?: string[] }> =
18 {}
19 let match
20
21 while ((match = setParamRegex.exec(sql)) !== null) {
22 const [_, paramName, paramType, paramValue] = match
23 if (!paramName || !paramValue?.trim()) continue
24
25 const typeInfo = paramType?.trim()
26
27 let type: string | undefined
28 let possibleValues: string[] | undefined
29
30 if (typeInfo) {
31 // Handle union types (value1 | value2 | value3)
32 if (typeInfo.includes('|')) {
33 possibleValues = typeInfo.split('|').map((v) => v.trim())
34 type = 'enum'
35 } else {
36 type = typeInfo.trim()
37 }
38 }
39
40 paramDefaults[paramName] = {
41 value: paramValue.trim(),
42 type,
43 possibleValues,
44 }
45 }
46
47 // Find all :parameter occurrences and count them
48 const paramRegex = /:(\w+)/g
49 const paramOccurrences: Record<string, number> = {}
50 const uniqueParams = new Set<string>()
51
52 while ((match = paramRegex.exec(sql)) !== null) {
53 const [_, paramName] = match
54 paramOccurrences[paramName] = (paramOccurrences[paramName] || 0) + 1
55 uniqueParams.add(paramName)
56 }
57
58 // Create parameter objects for unique parameters
59 return Array.from(uniqueParams).map((paramName) => ({
60 name: paramName,
61 value: paramDefaults[paramName]?.value || '',
62 defaultValue: paramDefaults[paramName]?.value,
63 type: paramDefaults[paramName]?.type,
64 possibleValues: paramDefaults[paramName]?.possibleValues,
65 occurrences: paramOccurrences[paramName],
66 }))
67}
68
69export const processParameterizedSql = (sql: string, parameters: Record<string, string>) => {
70 // Parse @set parameter defaults with type information from SQL
71 const setParamRegex = /@set\s+(\w+)(?::([^=]+))?\s*=\s*([^;\n]+)/g
72 const paramDefaults: Record<string, { value: string; type?: string; possibleValues?: string[] }> =
73 {}
74 let match
75
76 while ((match = setParamRegex.exec(sql)) !== null) {
77 const [_, paramName, paramType, paramValue] = match
78 if (!paramName || !paramValue?.trim()) continue
79
80 const typeInfo = paramType?.trim()
81 let type: string | undefined
82 let possibleValues: string[] | undefined
83
84 if (typeInfo) {
85 if (typeInfo.includes('|')) {
86 possibleValues = typeInfo.split('|').map((v) => v.trim())
87 type = 'enum'
88 } else {
89 type = typeInfo.trim()
90 }
91 }
92
93 paramDefaults[paramName] = {
94 value: paramValue.trim(),
95 type,
96 possibleValues,
97 }
98 }
99
100 // Remove @set lines from SQL
101 let processedSql = sql.replace(/@set\s+\w+(?:\s*:\s*[^=]+)?\s*=\s*[^;\n]+[\n;]*/g, '')
102
103 // Replace :parameters with values
104 const paramRegex = /:(\w+)/g
105 processedSql = processedSql.replace(paramRegex, (_match, paramName) => {
106 const value = parameters[paramName] ?? paramDefaults[paramName]?.value
107 if (value === undefined) {
108 throw new Error(`Missing value for parameter: ${paramName}`)
109 }
110 return value
111 })
112
113 return processedSql
114}