File: pre_prepare.c

package info (click to toggle)
preprepare 0.9-5
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, sid
  • size: 168 kB
  • sloc: ansic: 216; makefile: 40; sql: 37; sh: 10
file content (339 lines) | stat: -rw-r--r-- 8,884 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/*
 * pre_prepare allows to store statements in a table and will prepare them
 * all when the prepare_all() function is being called.
 *
 * Unfortunately it's not possible to SPI_connect() when LOAD is done via
 * local_preload_libraries, so the function call can't be made transparent
 * to the client connection.
 */
#include <stdio.h>
#include "postgres.h"

#include "executor/spi.h"
#include "utils/guc.h"
#include "utils/elog.h"
#include "utils/palloc.h"
#include "utils/builtins.h"
#include "libpq/pqformat.h"
#include "access/xact.h"

/*
#define  DEBUG
 */

/*
 * This code has only been tested with PostgreSQL 8.3.
 */
#ifdef PG_VERSION_NUM
#define PG_MAJOR_VERSION (PG_VERSION_NUM / 100)
#else
#error "Unknown postgresql version"
#endif

#if PG_MAJOR_VERSION < 803
#error "Unsupported postgresql version"
#endif

PG_MODULE_MAGIC;


/*
 * In 8.3 it seems that snapmgr.h is unavailable for module code
 *
 * That means there's no support for at_init and local_preload_libraries in
 * this version.
 */
#if PG_MAJOR_VERSION > 803
#include "utils/snapmgr.h"
static bool pre_prepare_at_init   = false;
#endif

static char *pre_prepare_relation = NULL;

void _PG_init(void);
Datum prepare_all(PG_FUNCTION_ARGS);

/*
 * Check that pre_prepare.relation is setup to an existing table.
 *
 * The catalog inquiry could use some optimisation (index use prevented).
 */
static inline
bool check_pre_prepare_relation(const char *relation_name) {
  int err;

  char *stmpl  = "SELECT 1 FROM pg_class WHERE " \
    "(SELECT nspname from pg_namespace WHERE oid = relnamespace) " \
    "|| '.' || relname = '%s';";

  int len      = (strlen(stmpl) - 2) + strlen(relation_name) + 1;
  char *select = (char *)palloc(len * sizeof(char));

  snprintf(select, len, stmpl, relation_name);

#ifdef DEBUG
  elog(NOTICE, select);
#endif

  err = SPI_execute(select, true, 1);

  if( err != SPI_OK_SELECT )
    elog(ERROR, "SPI_execute: %s", SPI_result_code_string(err));

  return 1 == SPI_processed;
}

/*
 * Prepare all the statements in pre_prepare.relation
 */
static inline
int pre_prepare_all(const char *relation_name) {
  int err, nbrows = 0;
  char *stmpl  = "SELECT name, statement FROM %s";
  int len      = (strlen(stmpl) - 2) + strlen(relation_name) + 1;
  char *select = (char *)palloc(len);

  snprintf(select, len, stmpl, relation_name);
  err = SPI_execute(select, true, 0);

  if( err != SPI_OK_SELECT ) {
    elog(ERROR, "SPI_execute: %s", SPI_result_code_string(err));
    return -1;
  }

  nbrows = SPI_processed;

  if( nbrows > 0 && SPI_tuptable != NULL ) {
    TupleDesc tupdesc = SPI_tuptable->tupdesc;
    SPITupleTable *tuptable = SPI_tuptable;
    int row;

    for (row = 0; row < nbrows; row++) {
      HeapTuple tuple = tuptable->vals[row];
      char *name = SPI_getvalue(tuple, tupdesc, 1);
      char *stmt = SPI_getvalue(tuple, tupdesc, 2);

      elog(NOTICE, "Preparing statement name: %s", name);

      err = SPI_execute(stmt, false, 0);

      if( err != SPI_OK_UTILITY ) {
	elog(ERROR, "SPI_execute: %s", SPI_result_code_string(err));
	return -1;
      }
    }
  }
  else
    elog(NOTICE, "No statement to prepare found in '%s'", pre_prepare_relation);

  return nbrows;
}

/*
 * _PG_init()			- library load-time initialization
 *
 * DO NOT make this static nor change its name!
 *
 * Init the module, all we have to do here is getting our GUC
 */
void
_PG_init(void) {
  PG_TRY();
  {
#if PG_MAJOR_VERSION == 804
    bool at_init = false;

    if( parse_bool(GetConfigOptionByName("prepare.at_init", NULL), &at_init) )
      pre_prepare_at_init = at_init;
#endif

    pre_prepare_relation = GetConfigOptionByName("prepare.relation", NULL
#if PG_MAJOR_VERSION >= 906
		    , false
#endif
		    );
  }
  PG_CATCH();
  {
    /*
     * From 8.4 the Custom variables take two new options, the default value
     * and a flags field
     */
#if PG_MAJOR_VERSION == 803
    DefineCustomStringVariable("preprepare.relation",
			       "Table name where to find statements to prepare",
			       "Can be schema qualified, must have columns "
			       "\"name\" and \"statement\"",
			       &pre_prepare_relation,
			       PGC_USERSET,
			       NULL,
			       NULL);

    /*
     * It's missing a way to use PushActiveSnapshot/PopActiveSnapshot from
     * within a module in 8.3 for this to be useful.
     *
     DefineCustomBoolVariable("preprepare.at_init",
			      "Do we prepare the statements at backend start",
			      "You have to setup local_preload_libraries too",
			      &pre_prepare_at_init,
			      PGC_USERSET,
			      NULL,
			      NULL);
    */
    EmitWarningsOnPlaceholders("prepare.relation");

#elif PG_MAJOR_VERSION == 804 || PG_MAJOR_VERSION == 900
    DefineCustomStringVariable("preprepare.relation",
			       "Table name where to find statements to prepare",
			       "Can be schema qualified, must have columns "
			       "\"name\" and \"statement\"",
			       &pre_prepare_relation,
			       "",
			       PGC_USERSET,
			       GUC_NOT_IN_SAMPLE,
			       NULL,
			       NULL);

    DefineCustomBoolVariable("preprepare.at_init",
			     "Do we prepare the statements at backend start",
			     "You have to setup local_preload_libraries too",
			     &pre_prepare_at_init,
			     false,
			     PGC_USERSET,
			     GUC_NOT_IN_SAMPLE,
			     NULL,
			     NULL);

    EmitWarningsOnPlaceholders("prepare.relation");
    EmitWarningsOnPlaceholders("prepare.at_init");
#else
    DefineCustomStringVariable("preprepare.relation",
			       "Table name where to find statements to prepare",
			       "Can be schema qualified, must have columns "
			       "\"name\" and \"statement\"",
			       &pre_prepare_relation,
			       "",
			       PGC_USERSET,
			       GUC_NOT_IN_SAMPLE,
			       NULL,
			       NULL,
			       NULL);

    DefineCustomBoolVariable("preprepare.at_init",
			     "Do we prepare the statements at backend start",
			     "You have to setup local_preload_libraries too",
			     &pre_prepare_at_init,
			     false,
			     PGC_USERSET,
			     GUC_NOT_IN_SAMPLE,
			     NULL,
			     NULL,
			     NULL);

    EmitWarningsOnPlaceholders("prepare.relation");
    EmitWarningsOnPlaceholders("prepare.at_init");
#endif
  }
  PG_END_TRY();

#if PG_MAJOR_VERSION >= 804
  if( pre_prepare_at_init ) {
    int err;

    /*
     * We want to use SPI, so we need to ensure there's a current started
     * transaction, then take a snapshot
    start_xact_command();
     */
    Snapshot snapshot;

    StartTransactionCommand();
    /* CommandCounterIncrement(); */
    snapshot = GetTransactionSnapshot();
    PushActiveSnapshot(snapshot);

    err = SPI_connect();
    if (err != SPI_OK_CONNECT)
      elog(ERROR, "SPI_connect: %s", SPI_result_code_string(err));

    if( ! check_pre_prepare_relation(pre_prepare_relation) ) {
      ereport(ERROR,
	      (errcode(ERRCODE_DATA_EXCEPTION),
	       errmsg("Can not find relation '%s'", pre_prepare_relation),
	       errhint("Set preprepare.relation to an existing table.")));
    }

    pre_prepare_all(pre_prepare_relation);

    err = SPI_finish();
    if (err != SPI_OK_FINISH)
      elog(ERROR, "SPI_finish: %s", SPI_result_code_string(err));

    PopActiveSnapshot();
    CommitTransactionCommand();
  }
#endif
}

/*
 * PostgreSQL interface, with the SPI_connect and SPI_finish calls.
 */
PG_FUNCTION_INFO_V1(prepare_all);
Datum
prepare_all(PG_FUNCTION_ARGS)
{
  char * relation = NULL;
  int err;

  /*
   * we support for the user to override the GUC by passing in the relation name
   * SELECT prepare_all('some_other_statements');
   */
  if( PG_NARGS() == 1 )
    relation = DatumGetCString(DirectFunctionCall1(textout,
						   PointerGetDatum(PG_GETARG_TEXT_P(0))));
  else
    {
      relation = pre_prepare_relation;

      /*
       * The function is STRICT so we don't check this error case in the
       * previous branch
       */
      if( relation == NULL )
	ereport(ERROR,
		(errcode(ERRCODE_DATA_EXCEPTION),
		 errmsg("The custom variable preprepare.relation is not set."),
		 errhint("Set preprepare.relation to an existing table.")));
    }

  err = SPI_connect();
  if (err != SPI_OK_CONNECT)
    elog(ERROR, "SPI_connect: %s", SPI_result_code_string(err));

  if( ! check_pre_prepare_relation(relation) )
    {
      char *hint = "Set preprepare.relation to an existing table, schema qualified";
      if( PG_NARGS() == 1 )
	hint = "prepare_all requires you to schema qualify the relation name";

      ereport(ERROR,
	      (errcode(ERRCODE_DATA_EXCEPTION),
	       errmsg("Can not find relation '%s'", relation),
	       errhint("%s", hint)));
    }

#ifdef DEBUG
  elog(NOTICE, "preprepare.relation is found, proceeding");
#endif

  pre_prepare_all(relation);

  /* done with SPI */
  err = SPI_finish();
  if (err != SPI_OK_FINISH)
    elog(ERROR, "SPI_finish: %s", SPI_result_code_string(err));

  PG_RETURN_VOID();
}