@@ -71,14 +71,14 @@ function interpolateSqlIntoFragment (
71
71
const delimiter = delimiters [ i - 1 ] ;
72
72
const value = values [ i - 1 ] ;
73
73
74
- if ( delimiter ) {
75
- result += escapeDelimitedValue (
76
- value , delimiter , timeZone , forbidQualified ) ;
77
- } else {
78
- result += SqlString . escape ( value , stringifyObjects , timeZone ) ;
79
- }
80
-
81
- result += chunk ;
74
+ let escaped = delimiter
75
+ ? escapeDelimitedValue ( value , delimiter , timeZone , forbidQualified )
76
+ : defangMergeHazard (
77
+ result ,
78
+ SqlString . escape ( value , stringifyObjects , timeZone ) ,
79
+ chunk ) ;
80
+
81
+ result += escaped + chunk ;
82
82
}
83
83
84
84
return SqlString . raw ( result ) ;
@@ -95,6 +95,24 @@ function escapeDelimitedValue (value, delimiter, timeZone, forbidQualified) {
95
95
return escaped . substring ( 1 , escaped . length - 1 ) ;
96
96
}
97
97
98
+ function defangMergeHazard ( before , escaped , after ) {
99
+ const escapedLast = escaped [ escaped . length - 1 ] ;
100
+ if ( '\"\'`' . indexOf ( escapedLast ) < 0 ) {
101
+ // Not a merge hazard.
102
+ return escaped ;
103
+ }
104
+
105
+ let escapedSetOff = escaped ;
106
+ const lastBefore = before [ before . length - 1 ] ;
107
+ if ( escapedLast === escaped [ 0 ] && escapedLast === lastBefore ) {
108
+ escapedSetOff = ' ' + escapedSetOff ;
109
+ }
110
+ if ( escapedLast === after [ 0 ] ) {
111
+ escapedSetOff += ' ' ;
112
+ }
113
+ return escapedSetOff ;
114
+ }
115
+
98
116
/**
99
117
* Template tag function that contextually autoescapes values
100
118
* producing a SqlFragment.
0 commit comments