35#include "executor/spi.h"
36#include "catalog/pg_type.h"
37#include "utils/array.h"
38#include "utils/builtins.h"
39#include "utils/tuplestore.h"
40#if PG_VERSION_NUM >= 160000
65std::string text_to_string(text *t)
67 return std::string(VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t));
71Datum string_vector_to_text_array(
const std::vector<std::string> &v)
74 return PointerGetDatum(construct_empty_array(TEXTOID));
76 std::vector<Datum> elems;
77 elems.reserve(v.size());
78 for (
const auto &s : v)
79 elems.push_back(PointerGetDatum(cstring_to_text_with_len(s.data(),
82 ArrayType *arr = construct_array(elems.data(),
83 static_cast<int>(elems.size()),
85 return PointerGetDatum(arr);
90std::vector<std::string> text_array_to_string_vector(ArrayType *arr)
92 std::vector<std::string> out;
98 for (
int i = 0; i < n; ++i) {
101 out.push_back(text_to_string(DatumGetTextPP(elems[i])));
107void require_superuser(
const char *fn)
110 provsql_error(
"%s: must be superuser (a tool record can run arbitrary "
111 "commands as the PostgreSQL OS user)", fn);
117std::vector<std::string> spi_text_array(HeapTuple t, TupleDesc td,
int col)
120 Datum d = SPI_getbinval(t, td, col, &isnull);
123 return text_array_to_string_vector(DatumGetArrayTypeP(d));
127std::string spi_text(HeapTuple t, TupleDesc td,
int col)
129 char *s = SPI_getvalue(t, td, col);
130 return s ? std::string(s) :
std::string();
135bool overrides_table_exists()
137 if (SPI_execute(
"SELECT to_regclass('provsql.tool_overrides') IS NOT NULL",
138 true, 1) != SPI_OK_SELECT || SPI_processed != 1)
141 Datum d = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc,
143 return !isnull && DatumGetBool(d);
149 Oid types[13] = {TEXTOID, TEXTOID, TEXTOID, TEXTARRAYOID, TEXTARRAYOID,
150 TEXTOID, TEXTOID, INT4OID, BOOLOID, TEXTARRAYOID,
151 TEXTOID, TEXTOID, TEXTOID};
153 CStringGetTextDatum(rec.
name.c_str()),
154 CStringGetTextDatum(rec.
kind.c_str()),
155 CStringGetTextDatum(rec.
binary.c_str()),
159 CStringGetTextDatum(rec.
parser.c_str()),
163 CStringGetTextDatum(rec.
argtpl.c_str()),
165 CStringGetTextDatum(rec.
endpoint.c_str()),
167 SPI_execute_with_args(
168 "INSERT INTO provsql.tool_overrides "
169 "(name, removed, kind, executable, operations, input_formats, "
170 " output_format, parser, preference, enabled, dependencies, argtpl, "
171 " argtpl_circuit, endpoint) "
172 "VALUES ($1, false, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) "
173 "ON CONFLICT (name) DO UPDATE SET "
174 " removed=false, kind=$2, executable=$3, operations=$4, "
175 " input_formats=$5, output_format=$6, parser=$7, preference=$8, "
176 " enabled=$9, dependencies=$10, argtpl=$11, argtpl_circuit=$12, "
178 13, types, vals, NULL,
false, 0);
182void tombstone_override(
const std::string &name)
184 Oid types[1] = {TEXTOID};
185 Datum vals[1] = {CStringGetTextDatum(name.c_str())};
186 SPI_execute_with_args(
187 "INSERT INTO provsql.tool_overrides (name, removed) VALUES ($1, true) "
188 "ON CONFLICT (name) DO UPDATE SET removed=true, kind=NULL, "
189 " executable=NULL, operations=NULL, input_formats=NULL, "
190 " output_format=NULL, parser=NULL, preference=NULL, enabled=NULL, "
191 " dependencies=NULL, argtpl=NULL, argtpl_circuit=NULL, endpoint=NULL",
192 1, types, vals, NULL,
false, 0);
206 if (SPI_connect() != SPI_OK_CONNECT)
208 if (overrides_table_exists()) {
210 "SELECT name, removed, kind, executable, operations, input_formats, "
211 " output_format, parser, preference, enabled, dependencies, argtpl, "
212 " argtpl_circuit, endpoint FROM provsql.tool_overrides",
true, 0)
214 TupleDesc td = SPI_tuptable->tupdesc;
215 for (uint64 i = 0; i < SPI_processed; ++i) {
216 HeapTuple t = SPI_tuptable->vals[i];
217 std::string name = spi_text(t, td, 1);
219 Datum rd = SPI_getbinval(t, td, 2, &isnull);
220 if (!isnull && DatumGetBool(rd)) {
226 rec.
kind = spi_text(t, td, 3);
227 rec.
binary = spi_text(t, td, 4);
231 rec.
parser = spi_text(t, td, 8);
232 Datum pd = SPI_getbinval(t, td, 9, &isnull);
233 rec.
preference = isnull ? 0 : DatumGetInt32(pd);
234 Datum ed = SPI_getbinval(t, td, 10, &isnull);
235 rec.
enabled = isnull ? true : DatumGetBool(ed);
237 rec.
argtpl = spi_text(t, td, 12);
265 ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
267 MemoryContext per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
268 MemoryContext oldcontext = MemoryContextSwitchTo(per_query_ctx);
271 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) {
272 MemoryContextSwitchTo(oldcontext);
273 provsql_error(
"tool_registry_list: function must return a row type");
275 tupdesc = BlessTupleDesc(tupdesc);
277 Tuplestorestate *tupstore = tuplestore_begin_heap(
278 rsinfo->allowedModes & SFRM_Materialize_Random,
false, work_mem);
279 rsinfo->returnMode = SFRM_Materialize;
280 rsinfo->setResult = tupstore;
281 rsinfo->setDesc = tupdesc;
286 bool nulls[13] = {
false,
false,
false,
false,
false,
false,
false,
287 false,
false,
false,
false,
false,
false};
289 values[0] = PointerGetDatum(cstring_to_text_with_len(rec.name.data(),
291 values[1] = PointerGetDatum(cstring_to_text_with_len(rec.kind.data(),
293 values[2] = PointerGetDatum(cstring_to_text_with_len(rec.binary.data(),
295 values[3] = string_vector_to_text_array(rec.operations);
296 values[4] = string_vector_to_text_array(rec.input_formats);
297 values[5] = PointerGetDatum(cstring_to_text_with_len(
298 rec.output_format.data(), rec.output_format.size()));
299 values[6] = PointerGetDatum(cstring_to_text_with_len(rec.parser.data(),
301 values[7] = Int32GetDatum(rec.preference);
302 values[8] = BoolGetDatum(rec.enabled);
303 values[9] = PointerGetDatum(cstring_to_text_with_len(rec.argtpl.data(),
305 values[10] = PointerGetDatum(cstring_to_text_with_len(
306 rec.argtpl_circuit.data(), rec.argtpl_circuit.size()));
307 values[11] = PointerGetDatum(cstring_to_text_with_len(
308 rec.endpoint.data(), rec.endpoint.size()));
311 tuplestore_putvalues(tupstore, tupdesc, values, nulls);
313 }
catch (
const std::exception &e) {
314 MemoryContextSwitchTo(oldcontext);
317 MemoryContextSwitchTo(oldcontext);
321 MemoryContextSwitchTo(oldcontext);
339 require_superuser(
"register_tool");
346 rec.
name = text_to_string(PG_GETARG_TEXT_PP(0));
348 : text_to_string(PG_GETARG_TEXT_PP(1));
349 rec.
kind = PG_ARGISNULL(2) ? std::string(
"cli")
350 : text_to_string(PG_GETARG_TEXT_PP(2));
351 if (!PG_ARGISNULL(3))
352 rec.
operations = text_array_to_string_vector(PG_GETARG_ARRAYTYPE_P(3));
353 if (!PG_ARGISNULL(4))
354 rec.
input_formats = text_array_to_string_vector(PG_GETARG_ARRAYTYPE_P(4));
355 if (!PG_ARGISNULL(5))
357 if (!PG_ARGISNULL(6))
358 rec.
parser = text_to_string(PG_GETARG_TEXT_PP(6));
359 if (!PG_ARGISNULL(7))
360 rec.
argtpl = text_to_string(PG_GETARG_TEXT_PP(7));
361 if (!PG_ARGISNULL(8))
363 rec.
preference = PG_ARGISNULL(9) ? 0 : PG_GETARG_INT32(9);
364 rec.
enabled = PG_ARGISNULL(10) ? true : PG_GETARG_BOOL(10);
365 if (!PG_ARGISNULL(11))
366 rec.
endpoint = text_to_string(PG_GETARG_TEXT_PP(11));
368 if (rec.
name.empty())
372 if (SPI_connect() != SPI_OK_CONNECT)
374 upsert_override(rec);
376 }
catch (
const std::exception &e) {
391 require_superuser(
"unregister_tool");
392 std::string name = text_to_string(PG_GETARG_TEXT_PP(0));
396 provsql_error(
"unregister_tool: no tool named '%s' is registered",
399 if (SPI_connect() != SPI_OK_CONNECT)
401 tombstone_override(name);
415 provsql_error(
"%s: no tool named '%s' is registered", fn, name.c_str());
418 if (SPI_connect() != SPI_OK_CONNECT)
420 upsert_override(rec);
428 require_superuser(
"set_tool_enabled");
429 std::string name = text_to_string(PG_GETARG_TEXT_PP(0));
430 bool enabled = PG_GETARG_BOOL(1);
440 require_superuser(
"set_tool_preference");
441 std::string name = text_to_string(PG_GETARG_TEXT_PP(0));
442 int preference = PG_GETARG_INT32(1);
PostgreSQL cross-version compatibility shims for ProvSQL.
#define TYPALIGN_INT
Alignment codes for the array routines (construct_array / deconstruct_array).
ToolRegistry & tool_registry()
Shorthand for ToolRegistry::instance().
Uniform error-reporting macros for ProvSQL.
#define provsql_error(fmt,...)
Report a fatal ProvSQL error and abort the current transaction.