Line data Source code
1 : /*
2 : * function.c
3 : *
4 : * server-side function support
5 : *
6 : * Copyright (c) 2010-2026, PostgreSQL Global Development Group
7 : * src/bin/pg_upgrade/function.c
8 : */
9 :
10 : #include "postgres_fe.h"
11 :
12 : #include "access/transam.h"
13 : #include "catalog/pg_language_d.h"
14 : #include "common/int.h"
15 : #include "pg_upgrade.h"
16 :
17 : /*
18 : * qsort comparator for pointers to library names
19 : *
20 : * We sort first by name length, then alphabetically for names of the
21 : * same length, then database array index. This is to ensure that, eg,
22 : * "hstore_plpython" sorts after both "hstore" and "plpython"; otherwise
23 : * transform modules will probably fail their LOAD tests. (The backend
24 : * ought to cope with that consideration, but it doesn't yet, and even
25 : * when it does it'll still be a good idea to have a predictable order of
26 : * probing here.)
27 : */
28 : static int
29 0 : library_name_compare(const void *p1, const void *p2)
30 : {
31 0 : const char *str1 = ((const LibraryInfo *) p1)->name;
32 0 : const char *str2 = ((const LibraryInfo *) p2)->name;
33 0 : size_t slen1 = strlen(str1);
34 0 : size_t slen2 = strlen(str2);
35 0 : int cmp = strcmp(str1, str2);
36 :
37 0 : if (slen1 != slen2)
38 0 : return pg_cmp_size(slen1, slen2);
39 0 : if (cmp != 0)
40 0 : return cmp;
41 0 : return pg_cmp_s32(((const LibraryInfo *) p1)->dbnum,
42 0 : ((const LibraryInfo *) p2)->dbnum);
43 0 : }
44 :
45 : /*
46 : * Private state for get_loadable_libraries()'s UpgradeTask.
47 : */
48 : struct loadable_libraries_state
49 : {
50 : PGresult **ress; /* results for each database */
51 : int totaltups; /* number of tuples in all results */
52 : };
53 :
54 : /*
55 : * Callback function for processing results of query for
56 : * get_loadable_libraries()'s UpgradeTask. This function stores the results
57 : * for later use within get_loadable_libraries().
58 : */
59 : static void
60 0 : process_loadable_libraries(DbInfo *dbinfo, PGresult *res, void *arg)
61 : {
62 0 : struct loadable_libraries_state *state = (struct loadable_libraries_state *) arg;
63 :
64 0 : state->ress[dbinfo - old_cluster.dbarr.dbs] = res;
65 0 : state->totaltups += PQntuples(res);
66 0 : }
67 :
68 : /*
69 : * get_loadable_libraries()
70 : *
71 : * Fetch the names of all old libraries containing either C-language functions
72 : * or are corresponding to logical replication output plugins.
73 : *
74 : * We will later check that they all exist in the new installation.
75 : */
76 : void
77 0 : get_loadable_libraries(void)
78 : {
79 0 : int totaltups;
80 0 : int dbnum;
81 0 : int n_libinfos;
82 0 : UpgradeTask *task = upgrade_task_create();
83 0 : struct loadable_libraries_state state;
84 0 : char *query;
85 :
86 0 : state.ress = (PGresult **) pg_malloc(old_cluster.dbarr.ndbs * sizeof(PGresult *));
87 0 : state.totaltups = 0;
88 :
89 0 : query = psprintf("SELECT DISTINCT probin "
90 : "FROM pg_catalog.pg_proc "
91 : "WHERE prolang = %u AND "
92 : "probin IS NOT NULL AND "
93 : "oid >= %u",
94 : ClanguageId,
95 : FirstNormalObjectId);
96 :
97 0 : upgrade_task_add_step(task, query, process_loadable_libraries,
98 : false, &state);
99 :
100 0 : upgrade_task_run(task, &old_cluster);
101 0 : upgrade_task_free(task);
102 :
103 : /*
104 : * Allocate memory for required libraries and logical replication output
105 : * plugins.
106 : */
107 0 : n_libinfos = state.totaltups + count_old_cluster_logical_slots();
108 0 : os_info.libraries = (LibraryInfo *) pg_malloc(sizeof(LibraryInfo) * n_libinfos);
109 0 : totaltups = 0;
110 :
111 0 : for (dbnum = 0; dbnum < old_cluster.dbarr.ndbs; dbnum++)
112 : {
113 0 : PGresult *res = state.ress[dbnum];
114 0 : int ntups;
115 0 : int rowno;
116 0 : LogicalSlotInfoArr *slot_arr = &old_cluster.dbarr.dbs[dbnum].slot_arr;
117 :
118 0 : ntups = PQntuples(res);
119 0 : for (rowno = 0; rowno < ntups; rowno++)
120 : {
121 0 : char *lib = PQgetvalue(res, rowno, 0);
122 :
123 0 : os_info.libraries[totaltups].name = pg_strdup(lib);
124 0 : os_info.libraries[totaltups].dbnum = dbnum;
125 :
126 0 : totaltups++;
127 0 : }
128 0 : PQclear(res);
129 :
130 : /*
131 : * Store the names of output plugins as well. There is a possibility
132 : * that duplicated plugins are set, but the consumer function
133 : * check_loadable_libraries() will avoid checking the same library, so
134 : * we do not have to consider their uniqueness here.
135 : */
136 0 : for (int slotno = 0; slotno < slot_arr->nslots; slotno++)
137 : {
138 0 : if (slot_arr->slots[slotno].invalid)
139 0 : continue;
140 :
141 0 : os_info.libraries[totaltups].name = pg_strdup(slot_arr->slots[slotno].plugin);
142 0 : os_info.libraries[totaltups].dbnum = dbnum;
143 :
144 0 : totaltups++;
145 0 : }
146 0 : }
147 :
148 0 : pg_free(state.ress);
149 0 : pg_free(query);
150 :
151 0 : os_info.num_libraries = totaltups;
152 0 : }
153 :
154 :
155 : /*
156 : * check_loadable_libraries()
157 : *
158 : * Check that the new cluster contains all required libraries.
159 : * We do this by actually trying to LOAD each one, thereby testing
160 : * compatibility as well as presence.
161 : */
162 : void
163 0 : check_loadable_libraries(void)
164 : {
165 0 : PGconn *conn = connectToServer(&new_cluster, "template1");
166 0 : int libnum;
167 0 : int was_load_failure = false;
168 0 : FILE *script = NULL;
169 0 : char output_path[MAXPGPATH];
170 :
171 0 : prep_status("Checking for presence of required libraries");
172 :
173 0 : snprintf(output_path, sizeof(output_path), "%s/%s",
174 0 : log_opts.basedir, "loadable_libraries.txt");
175 :
176 : /*
177 : * Now we want to sort the library names into order. This avoids multiple
178 : * probes of the same library, and ensures that libraries are probed in a
179 : * consistent order, which is important for reproducible behavior if one
180 : * library depends on another.
181 : */
182 0 : qsort(os_info.libraries, os_info.num_libraries,
183 : sizeof(LibraryInfo), library_name_compare);
184 :
185 0 : for (libnum = 0; libnum < os_info.num_libraries; libnum++)
186 : {
187 0 : char *lib = os_info.libraries[libnum].name;
188 0 : int llen = strlen(lib);
189 0 : char cmd[7 + 2 * MAXPGPATH + 1];
190 0 : PGresult *res;
191 :
192 : /* Did the library name change? Probe it. */
193 0 : if (libnum == 0 || strcmp(lib, os_info.libraries[libnum - 1].name) != 0)
194 : {
195 0 : strcpy(cmd, "LOAD '");
196 0 : PQescapeStringConn(conn, cmd + strlen(cmd), lib, llen, NULL);
197 0 : strcat(cmd, "'");
198 :
199 0 : res = PQexec(conn, cmd);
200 :
201 0 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
202 : {
203 0 : was_load_failure = true;
204 :
205 0 : if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
206 0 : pg_fatal("could not open file \"%s\": %m", output_path);
207 0 : fprintf(script, _("could not load library \"%s\": %s"),
208 0 : lib,
209 0 : PQerrorMessage(conn));
210 0 : }
211 : else
212 0 : was_load_failure = false;
213 :
214 0 : PQclear(res);
215 0 : }
216 :
217 0 : if (was_load_failure)
218 0 : fprintf(script, _("In database: %s\n"),
219 0 : old_cluster.dbarr.dbs[os_info.libraries[libnum].dbnum].db_name);
220 0 : }
221 :
222 0 : PQfinish(conn);
223 :
224 0 : if (script)
225 : {
226 0 : fclose(script);
227 0 : pg_log(PG_REPORT, "fatal");
228 0 : pg_fatal("Your installation references loadable libraries that are missing from the\n"
229 : "new installation. You can add these libraries to the new installation,\n"
230 : "or remove the functions using them from the old installation. A list of\n"
231 : "problem libraries is in the file:\n"
232 0 : " %s", output_path);
233 : }
234 : else
235 0 : check_ok();
236 0 : }
|