RunSafely is the same as Run, but returns an error in case of missing or extra templates.
(ctx context.Context, argPlaceholder, sql string, args []any)
| 125 | // RunSafely is the same as Run, but returns an error in case of missing or |
| 126 | // extra templates. |
| 127 | func (r *Replacer) RunSafely(ctx context.Context, argPlaceholder, sql string, args []any) (string, []any, error) { |
| 128 | var ( |
| 129 | container, containerOK = ctx.Value(contextKey{}).(*contextContainer) |
| 130 | sqlContainsTemplate = strings.Contains(sql, "/* TEMPLATE") |
| 131 | ) |
| 132 | switch { |
| 133 | case !containerOK && !sqlContainsTemplate: |
| 134 | // Neither context container or template in SQL; short circuit fast because there's no work to do. |
| 135 | return sql, args, nil |
| 136 | |
| 137 | case containerOK && !sqlContainsTemplate: |
| 138 | return "", nil, errors.New("sqlctemplate found context container but SQL contains no templates; bug?") |
| 139 | |
| 140 | case !containerOK && sqlContainsTemplate: |
| 141 | return "", nil, errors.New("sqlctemplate found template(s) in SQL, but no context container; bug?") |
| 142 | } |
| 143 | |
| 144 | cacheKey, cacheEligible := replacerCacheKeyFrom(sql, container) |
| 145 | if cacheEligible { |
| 146 | r.cacheMu.RLock() |
| 147 | var ( |
| 148 | cachedSQL string |
| 149 | cachedSQLOK bool |
| 150 | ) |
| 151 | if r.cache != nil { // protect against map not initialized yet |
| 152 | cachedSQL, cachedSQLOK = r.cache[cacheKey] |
| 153 | } |
| 154 | r.cacheMu.RUnlock() |
| 155 | |
| 156 | // If all input templates were stable, the finished SQL will have been cached. |
| 157 | if cachedSQLOK { |
| 158 | if len(container.NamedArgs) > 0 { |
| 159 | // Named args must be appended in sorted order to match the |
| 160 | // placeholder positions baked into the cached SQL during |
| 161 | // RunSafely's cache miss path. |
| 162 | sortedNamedArgs := maputil.Keys(container.NamedArgs) |
| 163 | slices.Sort(sortedNamedArgs) |
| 164 | for _, name := range sortedNamedArgs { |
| 165 | args = append(args, container.NamedArgs[name]) |
| 166 | } |
| 167 | } |
| 168 | return cachedSQL, args, nil |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | var ( |
| 173 | templatesExpected = maputil.Keys(container.Replacements) |
| 174 | templatesMissing []string // not preallocated because we don't expect any missing parameters in the common case |
| 175 | ) |
| 176 | |
| 177 | replaceTemplate := func(sql string, templateRE *regexp.Regexp) string { |
| 178 | return templateRE.ReplaceAllStringFunc(sql, func(templateStr string) string { |
| 179 | // Really dumb, but Go doesn't provide any way to get submatches in a |
| 180 | // function, so we have to match twice. |
| 181 | // https://github.com/golang/go/issues/5690 |
| 182 | matches := templateRE.FindStringSubmatch(templateStr) |
| 183 | |
| 184 | template := matches[1] |