Age Owner Branch data TLA 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
3501 tgl@sss.pgh.pa.us 29 :CBC 2 : library_name_compare(const void *p1, const void *p2)
30 : : {
2838 bruce@momjian.us 31 : 2 : const char *str1 = ((const LibraryInfo *) p1)->name;
32 : 2 : const char *str2 = ((const LibraryInfo *) p2)->name;
809 nathan@postgresql.or 33 : 2 : size_t slen1 = strlen(str1);
34 : 2 : size_t slen2 = strlen(str2);
2838 bruce@momjian.us 35 : 2 : int cmp = strcmp(str1, str2);
36 : :
3501 tgl@sss.pgh.pa.us 37 [ - + ]: 2 : if (slen1 != slen2)
809 nathan@postgresql.or 38 :UBC 0 : return pg_cmp_size(slen1, slen2);
2838 bruce@momjian.us 39 [ - + ]:CBC 2 : if (cmp != 0)
2838 bruce@momjian.us 40 :UBC 0 : return cmp;
809 nathan@postgresql.or 41 :CBC 2 : return pg_cmp_s32(((const LibraryInfo *) p1)->dbnum,
42 : 2 : ((const LibraryInfo *) p2)->dbnum);
43 : : }
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
596 60 : 52 : process_loadable_libraries(DbInfo *dbinfo, PGresult *res, void *arg)
61 : : {
62 : 52 : struct loadable_libraries_state *state = (struct loadable_libraries_state *) arg;
63 : :
64 : 52 : state->ress[dbinfo - old_cluster.dbarr.dbs] = res;
65 : 52 : state->totaltups += PQntuples(res);
66 : 52 : }
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
5677 bruce@momjian.us 77 : 17 : get_loadable_libraries(void)
78 : : {
79 : : int totaltups;
80 : : int dbnum;
81 : : int n_libinfos;
596 nathan@postgresql.or 82 : 17 : UpgradeTask *task = upgrade_task_create();
83 : : struct loadable_libraries_state state;
84 : : char *query;
85 : :
67 michael@paquier.xyz 86 :GNC 17 : state.ress = pg_malloc_array(PGresult *, old_cluster.dbarr.ndbs);
596 nathan@postgresql.or 87 :CBC 17 : state.totaltups = 0;
88 : :
89 : 17 : 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 : 17 : upgrade_task_add_step(task, query, process_loadable_libraries,
98 : : false, &state);
99 : :
100 : 17 : upgrade_task_run(task, &old_cluster);
101 : 17 : upgrade_task_free(task);
102 : :
103 : : /*
104 : : * Allocate memory for required libraries and logical replication output
105 : : * plugins.
106 : : */
107 : 17 : n_libinfos = state.totaltups + count_old_cluster_logical_slots();
67 michael@paquier.xyz 108 :GNC 17 : os_info.libraries = pg_malloc_array(LibraryInfo, n_libinfos);
5837 bruce@momjian.us 109 :CBC 17 : totaltups = 0;
110 : :
5603 111 [ + + ]: 69 : for (dbnum = 0; dbnum < old_cluster.dbarr.ndbs; dbnum++)
112 : : {
596 nathan@postgresql.or 113 : 52 : PGresult *res = state.ress[dbnum];
114 : : int ntups;
115 : : int rowno;
922 akapila@postgresql.o 116 : 52 : LogicalSlotInfoArr *slot_arr = &old_cluster.dbarr.dbs[dbnum].slot_arr;
117 : :
5837 bruce@momjian.us 118 : 52 : ntups = PQntuples(res);
119 [ + + ]: 60 : for (rowno = 0; rowno < ntups; rowno++)
120 : : {
121 : 8 : char *lib = PQgetvalue(res, rowno, 0);
122 : :
123 : : /*
124 : : * Starting with version 19, for extensions with hardcoded
125 : : * '$libdir/' library names, we strip the prefix to allow the
126 : : * library search path to be used.
127 : : */
50 peter@eisentraut.org 128 [ + + ]:GNC 8 : if (strncmp(lib, "$libdir/", 8) == 0)
129 : 6 : lib += 8;
130 : :
2838 bruce@momjian.us 131 :CBC 8 : os_info.libraries[totaltups].name = pg_strdup(lib);
132 : 8 : os_info.libraries[totaltups].dbnum = dbnum;
133 : :
134 : 8 : totaltups++;
135 : : }
5837 136 : 52 : PQclear(res);
137 : :
138 : : /*
139 : : * Store the names of output plugins as well. There is a possibility
140 : : * that duplicated plugins are set, but the consumer function
141 : : * check_loadable_libraries() will avoid checking the same library, so
142 : : * we do not have to consider their uniqueness here.
143 : : */
922 akapila@postgresql.o 144 [ + + ]: 59 : for (int slotno = 0; slotno < slot_arr->nslots; slotno++)
145 : : {
146 [ - + ]: 7 : if (slot_arr->slots[slotno].invalid)
922 akapila@postgresql.o 147 :UBC 0 : continue;
148 : :
922 akapila@postgresql.o 149 :CBC 7 : os_info.libraries[totaltups].name = pg_strdup(slot_arr->slots[slotno].plugin);
150 : 7 : os_info.libraries[totaltups].dbnum = dbnum;
151 : :
152 : 7 : totaltups++;
153 : : }
154 : : }
155 : :
596 nathan@postgresql.or 156 : 17 : pg_free(state.ress);
157 : 17 : pg_free(query);
158 : :
3501 tgl@sss.pgh.pa.us 159 : 17 : os_info.num_libraries = totaltups;
5837 bruce@momjian.us 160 : 17 : }
161 : :
162 : :
163 : : /*
164 : : * check_loadable_libraries()
165 : : *
166 : : * Check that the new cluster contains all required libraries.
167 : : * We do this by actually trying to LOAD each one, thereby testing
168 : : * compatibility as well as presence.
169 : : */
170 : : void
5677 171 : 15 : check_loadable_libraries(void)
172 : : {
5603 173 : 15 : PGconn *conn = connectToServer(&new_cluster, "template1");
174 : : int libnum;
2838 175 : 15 : int was_load_failure = false;
5837 176 : 15 : FILE *script = NULL;
177 : : char output_path[MAXPGPATH];
178 : :
5677 179 : 15 : prep_status("Checking for presence of required libraries");
180 : :
1549 michael@paquier.xyz 181 : 15 : snprintf(output_path, sizeof(output_path), "%s/%s",
182 : : log_opts.basedir, "loadable_libraries.txt");
183 : :
184 : : /*
185 : : * Now we want to sort the library names into order. This avoids multiple
186 : : * probes of the same library, and ensures that libraries are probed in a
187 : : * consistent order, which is important for reproducible behavior if one
188 : : * library depends on another.
189 : : */
1183 peter@eisentraut.org 190 : 15 : qsort(os_info.libraries, os_info.num_libraries,
191 : : sizeof(LibraryInfo), library_name_compare);
192 : :
5677 bruce@momjian.us 193 [ + + ]: 27 : for (libnum = 0; libnum < os_info.num_libraries; libnum++)
194 : : {
2838 195 : 12 : char *lib = os_info.libraries[libnum].name;
5837 196 : 12 : int llen = strlen(lib);
197 : : char cmd[7 + 2 * MAXPGPATH + 1];
198 : : PGresult *res;
199 : :
200 : : /* Did the library name change? Probe it. */
2838 201 [ + + - + ]: 12 : if (libnum == 0 || strcmp(lib, os_info.libraries[libnum - 1].name) != 0)
202 : : {
203 : 10 : strcpy(cmd, "LOAD '");
204 : 10 : PQescapeStringConn(conn, cmd + strlen(cmd), lib, llen, NULL);
205 : 10 : strcat(cmd, "'");
206 : :
207 : 10 : res = PQexec(conn, cmd);
208 : :
209 [ - + ]: 10 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
210 : : {
2838 bruce@momjian.us 211 :UBC 0 : was_load_failure = true;
212 : :
213 [ # # # # ]: 0 : if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
784 michael@paquier.xyz 214 : 0 : pg_fatal("could not open file \"%s\": %m", output_path);
2838 bruce@momjian.us 215 : 0 : fprintf(script, _("could not load library \"%s\": %s"),
216 : : lib,
217 : : PQerrorMessage(conn));
218 : : }
219 : : else
2838 bruce@momjian.us 220 :CBC 10 : was_load_failure = false;
221 : :
222 : 10 : PQclear(res);
223 : : }
224 : :
225 [ - + ]: 12 : if (was_load_failure)
2401 bruce@momjian.us 226 :UBC 0 : fprintf(script, _("In database: %s\n"),
2540 tgl@sss.pgh.pa.us 227 : 0 : old_cluster.dbarr.dbs[os_info.libraries[libnum].dbnum].db_name);
228 : : }
229 : :
5837 bruce@momjian.us 230 :CBC 15 : PQfinish(conn);
231 : :
1343 dgustafsson@postgres 232 [ - + ]: 15 : if (script)
233 : : {
5837 bruce@momjian.us 234 :UBC 0 : fclose(script);
1393 tgl@sss.pgh.pa.us 235 : 0 : pg_log(PG_REPORT, "fatal");
4599 peter_e@gmx.net 236 : 0 : pg_fatal("Your installation references loadable libraries that are missing from the\n"
237 : : "new installation. You can add these libraries to the new installation,\n"
238 : : "or remove the functions using them from the old installation. A list of\n"
239 : : "problem libraries is in the file:\n"
240 : : " %s", output_path);
241 : : }
242 : : else
5677 bruce@momjian.us 243 :CBC 15 : check_ok();
5837 244 : 15 : }
|