buildFilterConditions constructs WHERE clause conditions from a ServerFilter nolint:unparam // argIndex is always 1 currently but kept for API flexibility
(filter *ServerFilter, argIndex int)
| 87 | // |
| 88 | //nolint:unparam // argIndex is always 1 currently but kept for API flexibility |
| 89 | func buildFilterConditions(filter *ServerFilter, argIndex int) ([]string, []any, int) { |
| 90 | var conditions []string |
| 91 | var args []any |
| 92 | |
| 93 | if filter == nil { |
| 94 | return conditions, args, argIndex |
| 95 | } |
| 96 | |
| 97 | if filter.Name != nil { |
| 98 | conditions = append(conditions, fmt.Sprintf("server_name = $%d", argIndex)) |
| 99 | args = append(args, *filter.Name) |
| 100 | argIndex++ |
| 101 | } |
| 102 | if filter.RemoteURL != nil { |
| 103 | // Use a JSONB containment predicate so the planner can use the GIN index |
| 104 | // idx_servers_json_remotes on (value -> 'remotes'). The previously-used |
| 105 | // EXISTS / jsonb_array_elements / ->> form is logically equivalent but |
| 106 | // the planner can't translate the per-row array unfolding into a GIN |
| 107 | // search — it falls back to scanning every row. validateNoDuplicateRemoteURLs |
| 108 | // in the publish path ran this filter and was measured at ~10s on prod's |
| 109 | // 21K-row table on 2026-04-28; the containment form is GIN-indexable and |
| 110 | // completes in low single-digit ms. |
| 111 | conditions = append(conditions, fmt.Sprintf("value -> 'remotes' @> jsonb_build_array(jsonb_build_object('url', $%d::text))", argIndex)) |
| 112 | args = append(args, *filter.RemoteURL) |
| 113 | argIndex++ |
| 114 | } |
| 115 | if filter.UpdatedSince != nil { |
| 116 | conditions = append(conditions, fmt.Sprintf("updated_at > $%d", argIndex)) |
| 117 | args = append(args, *filter.UpdatedSince) |
| 118 | argIndex++ |
| 119 | } |
| 120 | if filter.SubstringName != nil { |
| 121 | // Escape LIKE metacharacters so that user input cannot expand into |
| 122 | // wildcard matches (e.g. `?search=_` matching every single-char name, |
| 123 | // `?search=%` matching everything). Order matters: backslashes must be |
| 124 | // escaped first so subsequent escape backslashes are not double-escaped. |
| 125 | escaped := strings.NewReplacer(`\`, `\\`, `%`, `\%`, `_`, `\_`).Replace(*filter.SubstringName) |
| 126 | conditions = append(conditions, fmt.Sprintf("server_name ILIKE $%d ESCAPE '\\'", argIndex)) |
| 127 | args = append(args, "%"+escaped+"%") |
| 128 | argIndex++ |
| 129 | } |
| 130 | if filter.Version != nil { |
| 131 | conditions = append(conditions, fmt.Sprintf("version = $%d", argIndex)) |
| 132 | args = append(args, *filter.Version) |
| 133 | argIndex++ |
| 134 | } |
| 135 | if filter.IsLatest != nil { |
| 136 | conditions = append(conditions, fmt.Sprintf("is_latest = $%d", argIndex)) |
| 137 | args = append(args, *filter.IsLatest) |
| 138 | argIndex++ |
| 139 | } |
| 140 | if filter.IncludeDeleted == nil || !*filter.IncludeDeleted { |
| 141 | conditions = append(conditions, "status != 'deleted'") |
| 142 | } |
| 143 | |
| 144 | return conditions, args, argIndex |
| 145 | } |
| 146 |
no outgoing calls
no test coverage detected
searching dependent graphs…