ExecInDatabasesFromQuery uses "bash" and "psql" to execute sql in every database returned by the databases query. The sql statement(s) may contain psql variables that are assigned from the variables map. - https://www.postgresql.org/docs/current/app-psql.html#APP-PSQL-VARIABLES
( ctx context.Context, databases, sql string, variables map[string]string, )
| 68 | // psql variables that are assigned from the variables map. |
| 69 | // - https://www.postgresql.org/docs/current/app-psql.html#APP-PSQL-VARIABLES |
| 70 | func (exec Executor) ExecInDatabasesFromQuery( |
| 71 | ctx context.Context, databases, sql string, variables map[string]string, |
| 72 | ) (string, string, error) { |
| 73 | // Use a Bash loop to call `psql` multiple times. The query to run in every |
| 74 | // database is passed via standard input while the database query is passed |
| 75 | // as the first argument. Remaining arguments are passed through to `psql`. |
| 76 | stdin := strings.NewReader(sql) |
| 77 | |
| 78 | // Using make to preallocate args (using 1 (databases) + len(variables)) triggered |
| 79 | // a CodeQL scanning error that a potentially large value might cause an overflow. |
| 80 | // To address this, don't preallocate args and have the linter ignore this line. |
| 81 | args := []string{databases} //nolint:prealloc |
| 82 | for k, v := range variables { |
| 83 | args = append(args, "--set="+k+"="+v) |
| 84 | } |
| 85 | |
| 86 | // The map iteration above is nondeterministic. Sort the variable arguments |
| 87 | // so that calls to exec are deterministic. |
| 88 | // - https://golang.org/ref/spec#For_range |
| 89 | sort.Strings(args[1:]) |
| 90 | |
| 91 | const script = ` |
| 92 | sql_target=$(< /dev/stdin) |
| 93 | sql_databases="$1" |
| 94 | shift 1 |
| 95 | |
| 96 | databases=$(psql "$@" -Xw -Aqt --file=- <<< "${sql_databases}") |
| 97 | while IFS= read -r database; do |
| 98 | PGDATABASE="${database}" psql "$@" -Xw --file=- <<< "${sql_target}" |
| 99 | done <<< "${databases}" |
| 100 | ` |
| 101 | |
| 102 | // Execute the script with some error handling enabled. |
| 103 | var stdout, stderr bytes.Buffer |
| 104 | err := exec(ctx, stdin, &stdout, &stderr, |
| 105 | append([]string{"bash", "-ceu", "--", script, "-"}, args...)...) |
| 106 | return stdout.String(), stderr.String(), err |
| 107 | } |