/* * This function fetches the edge columns from the SPITupleTable. * */ static int fetch_edge_columns(SPITupleTable *tuptable, edge_columns_t *edge_columns, bool has_reverse_cost) { edge_columns->id = SPI_fnumber(SPI_tuptable->tupdesc, "id"); edge_columns->source = SPI_fnumber(SPI_tuptable->tupdesc, "source"); edge_columns->target = SPI_fnumber(SPI_tuptable->tupdesc, "target"); edge_columns->cost = SPI_fnumber(SPI_tuptable->tupdesc, "cost"); if (edge_columns->id == SPI_ERROR_NOATTRIBUTE || edge_columns->source == SPI_ERROR_NOATTRIBUTE || edge_columns->target == SPI_ERROR_NOATTRIBUTE || edge_columns->cost == SPI_ERROR_NOATTRIBUTE) { elog(ERROR, "Error, query must return columns " "'id', 'source', 'target' and 'cost'"); return -1; } if (SPI_gettypeid(SPI_tuptable->tupdesc, edge_columns->source) != INT4OID || SPI_gettypeid(SPI_tuptable->tupdesc, edge_columns->target) != INT4OID || SPI_gettypeid(SPI_tuptable->tupdesc, edge_columns->cost) != FLOAT8OID) { elog(ERROR, "Error, columns 'source', 'target' must be of type int4, 'cost' must be of type float8"); return -1; } PGR_DBG("columns: id %i source %i target %i cost %i", edge_columns->id, edge_columns->source, edge_columns->target, edge_columns->cost); if (has_reverse_cost) { edge_columns->reverse_cost = SPI_fnumber(SPI_tuptable->tupdesc, "reverse_cost"); if (edge_columns->reverse_cost == SPI_ERROR_NOATTRIBUTE) { elog(ERROR, "Error, reverse_cost is used, but query did't return " "'reverse_cost' column"); return -1; } if (SPI_gettypeid(SPI_tuptable->tupdesc, edge_columns->reverse_cost) != FLOAT8OID) { elog(ERROR, "Error, columns 'reverse_cost' must be of type float8"); return -1; } PGR_DBG("columns: reverse_cost cost %i", edge_columns->reverse_cost); } return 0; }
double pgr_SPI_getFloat8(HeapTuple *tuple, TupleDesc *tupdesc, Column_info_t info) { Datum binval; bool isnull; double value = 0.0; binval = SPI_getbinval(*tuple, *tupdesc, info.colNumber, &isnull); if (isnull) elog(ERROR, "Unexpected Null value in column %s", info.name); switch (info.type) { case INT2OID: value = (double) DatumGetInt16(binval); break; case INT4OID: value = (double) DatumGetInt32(binval); break; case INT8OID: value = (double) DatumGetInt64(binval); break; case FLOAT4OID: value = (double) DatumGetFloat4(binval); break; case FLOAT8OID: value = DatumGetFloat8(binval); break; default: elog(ERROR, "Unexpected Column type of %s. Expected ANY-NUMERICAL", info.name); } PGR_DBG("Variable: %s Value: %lf", info.name, value); return value; }
int64_t pgr_SPI_getBigInt(HeapTuple *tuple, TupleDesc *tupdesc, Column_info_t info) { Datum binval; bool isnull; int64_t value = 0; binval = SPI_getbinval(*tuple, *tupdesc, info.colNumber, &isnull); if (isnull) elog(ERROR, "Unexpected Null value in column %s", info.name); switch (info.type) { case INT2OID: value = (int64_t) DatumGetInt16(binval); break; case INT4OID: value = (int64_t) DatumGetInt32(binval); break; case INT8OID: value = DatumGetInt64(binval); break; default: elog(ERROR, "Unexpected Column type of %s. Expected ANY-INTEGER", info.name); } PGR_DBG("Variable: %s Value: %lld", info.name, value); return value; }
// http://www.postgresql.org/docs/9.4/static/spi-spi-finish.html void pgr_SPI_finish(void) { PGR_DBG("Disconnecting SPI"); int code = SPI_OK_FINISH; code = SPI_finish(); if (code != SPI_OK_FINISH) { // SPI_ERROR_UNCONNECTED elog(ERROR, "There was no connection to SPI"); } }
void pgr_SPI_connect(void) { PGR_DBG("Connecting to SPI"); int SPIcode; SPIcode = SPI_connect(); if (SPIcode != SPI_OK_CONNECT) { elog(ERROR, "Couldn't open a connection to SPI"); } }
static void process(char* edges_sql, bool *result_bool) { pgr_SPI_connect(); PGR_DBG("Load data"); Pgr_edge_xy_t *edges = NULL; size_t total_edges = 0; pgr_get_edges_xy(edges_sql, &edges, &total_edges); if (total_edges == 0) { PGR_DBG("No edges found"); (*result_bool) = true; pfree(edges); pgr_SPI_finish(); return; } PGR_DBG("Total %ld tuples in query:", total_edges); size_t i; for (i = 0; i < total_edges; ++i) { PGR_DBG("id = %li \t source = %li \t target = %ld cost = %lf reverse_cost = %lf", edges[i].id, edges[i].source, edges[i].target, edges[i].cost, edges[i].reverse_cost); PGR_DBG(" (x1,y1) = (%.32lf ,%.32lf) (x2,y2) = (%.32lf,.%.32lf)", edges[i].x1, edges[i].y1, edges[i].x2, edges[i].y2); } PGR_DBG("Starting processing"); char *err_msg = NULL; char *log_msg = NULL; (*result_bool) = do_pgr_testXYedges( edges, total_edges, &log_msg, &err_msg); pfree(edges); PGR_DBG("Returned log message = %s\n", log_msg); if (log_msg) { elog(DEBUG1, "%s", log_msg); free(log_msg); } PGR_DBG("Returned error message = %s\n", err_msg); if (err_msg) { pgr_SPI_finish(); elog(ERROR, "%s", err_msg); free(err_msg); } pgr_SPI_finish(); }
Portal pgr_SPI_cursor_open(SPIPlanPtr SPIplan) { PGR_DBG("Opening Portal"); Portal SPIportal; SPIportal = SPI_cursor_open(NULL, SPIplan, NULL, NULL, true); if (SPIportal == NULL) { elog(ERROR, "SPI_cursor_open returns NULL"); } return SPIportal; }
SPIPlanPtr pgr_SPI_prepare(char* sql) { PGR_DBG("Preparing Plan"); SPIPlanPtr SPIplan; SPIplan = SPI_prepare(sql, 0, NULL); if (SPIplan == NULL) { elog(ERROR, "Couldn't create query plan via SPI: %s", sql); } return SPIplan; }
static void annealing(TSP *tsp) { Path p; int i, j, pathchg; int numOnPath, numNotOnPath; DTYPE pathlen; int n = tsp->n; double energyChange, T; pathlen = pathLength(tsp); for (T = T_INIT; T > FINAL_T; T *= COOLING) /* annealing schedule */ { pathchg = 0; for (j = 0; j < TRIES_PER_T; j++) { do { p[0] = unifRand(n); p[1] = unifRand(n); /* non-empty path */ if (p[0] == p[1]) p[1] = MOD(p[0]+1, n); numOnPath = MOD(p[1]-p[0], n) + 1; numNotOnPath = n - numOnPath; } while (numOnPath < 2 || numNotOnPath < 2); /* non-empty path */ if (RANDOM() % 2) /* threeWay */ { do { p[2] = MOD(unifRand(numNotOnPath)+p[1]+1, n); } while (p[0] == MOD(p[2]+1, n)); /* avoids a non-change */ energyChange = getThreeWayCost(tsp, p); if (energyChange < 0 || RREAL < exp(-energyChange/T)) { pathchg++; pathlen += energyChange; doThreeWay(tsp, p); } } else { /* path Reverse */ energyChange = getReverseCost(tsp, p); if (energyChange < 0 || RREAL < exp(-energyChange/T)) { pathchg++; pathlen += energyChange; doReverse(tsp, p); } } // if the new length is better than best then save it as best if (pathlen < tsp->bestlen) { tsp->bestlen = pathlen; for (i = 0; i < tsp->n; i++) tsp->border[i] = tsp->iorder[i]; } if (pathchg > IMPROVED_PATH_PER_T) break; /* finish early */ } PGR_DBG("T:%f L:%f B:%f C:%d", T, pathlen, tsp->bestlen, pathchg); if (pathchg == 0) break; /* if no change then quit */ } }
static bool fetch_column_info( Column_info_t *info) { PGR_DBG("Fetching column info of %s", info->name); info->colNumber = SPI_fnumber(SPI_tuptable->tupdesc, info->name); if (info->strict && !column_found(info->colNumber)) { elog(ERROR, "Column '%s' not Found", info->name); } if (column_found(info->colNumber)) { (info->type) = SPI_gettypeid(SPI_tuptable->tupdesc, (info->colNumber)); if (SPI_result == SPI_ERROR_NOATTRIBUTE) { elog(ERROR, "Type of column '%s' not Found", info->name); } PGR_DBG("Column %s found: %llu", info->name, info->type); return true; } PGR_DBG("Column %s not found", info->name); return false; }
static int finish(int code, int ret) { PGR_DBG("In finish, trying to disconnect from spi %d",ret); code = SPI_finish(); if (code != SPI_OK_FINISH ) { elog(ERROR,"couldn't disconnect from SPI"); return -1 ; } return ret; }
/* MODIFY AS NEEDED */ static void process( char* edges_sql, int64_t start_vid, int64_t *end_vidsArr, size_t size_end_vidsArr, bool directed, MY_RETURN_VALUE_TYPE **result_tuples, size_t *result_count) { pgr_SPI_connect(); PGR_DBG("Load data"); MY_EDGE_TYPE *edges = NULL; size_t total_edges = 0; MY_EDGE_FUNCTION(edges_sql, &edges, &total_edges); PGR_DBG("Total %ld edges in query:", total_edges); if (total_edges == 0) { PGR_DBG("No edges found"); (*result_count) = 0; (*result_tuples) = NULL; pgr_SPI_finish(); return; } PGR_DBG("Starting processing"); char *err_msg = NULL; char *log_msg = NULL; // Code standard: // Pass the arrays and the sizes on the same line clock_t start_t = clock(); do_pgr_MY_FUNCTION_NAME( edges, total_edges, start_vid, end_vidsArr, size_end_vidsArr, directed, result_tuples, result_count, &log_msg, &err_msg); time_msg(" processing pgr_funnyDijkstra", start_t, clock()); PGR_DBG("Returning %ld tuples\n", *result_count); PGR_DBG("LOG: %s\n", log_msg); if (log_msg) free(log_msg); if (err_msg) { if (*result_tuples) free(*result_tuples); if (end_vidsArr) free(end_vidsArr); elog(ERROR, "%s", err_msg); free(err_msg); } pfree(edges); pgr_SPI_finish(); }
/* MODIFY AS NEEDED */ static void process( char* edges_sql, int64_t start_vid, int64_t end_vid, bool directed, bool only_cost, General_path_element_t **result_tuples, size_t *result_count) { pgr_SPI_connect(); PGR_DBG("Load data"); pgr_edge_t *edges = NULL; if (start_vid == end_vid) { (*result_count) = 0; (*result_tuples) = NULL; pgr_SPI_finish(); return; } size_t total_tuples = 0; pgr_get_edges(edges_sql, &edges, &total_tuples); if (total_tuples == 0) { PGR_DBG("No edges found"); (*result_count) = 0; (*result_tuples) = NULL; pgr_SPI_finish(); return; } PGR_DBG("Total %ld tuples in query:", total_tuples); PGR_DBG("Starting processing"); clock_t start_t = clock(); char *err_msg = NULL; do_pgr_one_to_one_dijkstra( edges, total_tuples, start_vid, end_vid, directed, only_cost, result_tuples, result_count, &err_msg); time_msg(" processing Dijkstra one to one", start_t, clock()); PGR_DBG("Returning %ld tuples\n", *result_count); PGR_DBG("Returned message = %s\n", err_msg); free(err_msg); pfree(edges); pgr_SPI_finish(); }
/* MODIFY AS NEEDED */ static void process( char *edges_sql, int64_t *source_vertices, size_t size_source_verticesArr, int64_t sink_vertex, bool directed, General_path_element_t **result_tuples, size_t *result_count) { pgr_SPI_connect(); PGR_DBG("Load data"); pgr_basic_edge_t *edges = NULL; size_t total_tuples = 0; pgr_get_basic_edges(edges_sql, &edges, &total_tuples); if (total_tuples == 0) { PGR_DBG("No edges found"); (*result_count) = 0; (*result_tuples) = NULL; pgr_SPI_finish(); return; } PGR_DBG("Total %ld tuples in query:", total_tuples); PGR_DBG("Starting processing"); clock_t start_t = clock(); char *err_msg = NULL; do_pgr_edge_disjoint_paths_many_to_one( edges, total_tuples, source_vertices, size_source_verticesArr, sink_vertex, directed, result_tuples, result_count, &err_msg); time_msg("processing edge disjoint paths", start_t, clock()); PGR_DBG("Returning %ld tuples\n", *result_count); PGR_DBG("Returned message = %s\n", err_msg); free(err_msg); pfree(edges); pgr_SPI_finish(); }
/* MODIFY AS NEEDED */ static void process( char *edges_sql, bool directed, pgr_basic_edge_t **result_tuples, size_t *result_count) { pgr_SPI_connect(); PGR_DBG("Load data"); pgr_basic_edge_t *edges = NULL; size_t total_tuples = 0; pgr_get_basic_edges(edges_sql, &edges, &total_tuples); if (total_tuples == 0) { PGR_DBG("No edges found"); (*result_count) = 0; (*result_tuples) = NULL; pgr_SPI_finish(); return; } PGR_DBG("Total %ld tuples in query:", total_tuples); PGR_DBG("Starting processing"); clock_t start_t = clock(); char *err_msg = NULL; do_pgr_maximum_cardinality_matching( edges, directed, total_tuples, result_tuples, result_count, &err_msg); time_msg("processing max flow", start_t, clock()); PGR_DBG("Returning %ld tuples\n", *result_count); PGR_DBG("Returned message = %s\n", err_msg); free(err_msg); pfree(edges); pgr_SPI_finish(); }
/* MODIFY AS NEEDED */ static void process( char* distances_sql, int64_t start_vid, int64_t end_vid, General_path_element_t **result_tuples, size_t *result_count) { pgr_SPI_connect(); PGR_DBG("Load data"); Matrix_cell_t *distances = NULL; size_t total_distances = 0; pgr_get_distances(distances_sql, &distances, &total_distances); if (total_distances == 0) { PGR_DBG("No distances found"); (*result_count) = 0; (*result_tuples) = NULL; pgr_SPI_finish(); return; } PGR_DBG("Total %ld tuples in query:", total_distances); PGR_DBG("Starting processing"); char *err_msg = NULL; do_pgr_tsp( distances, total_distances, start_vid, end_vid, result_tuples, result_count, &err_msg); PGR_DBG("Returning %ld tuples\n", *result_count); PGR_DBG("Returned message = %s\n", err_msg); free(err_msg); pfree(distances); pgr_SPI_finish(); }
/* MODIFY AS NEEDED */ static void process( char* edges_sql, bool directed, Matrix_cell_t **result_tuples, size_t *result_count) { pgr_SPI_connect(); PGR_DBG("Load data"); pgr_edge_t *edges = NULL; size_t total_tuples = 0; pgr_get_data_4_columns(edges_sql, &edges, &total_tuples); if (total_tuples == 0) { PGR_DBG("No edges found"); (*result_count) = 0; (*result_tuples) = NULL; pgr_SPI_finish(); return; } PGR_DBG("Total %ld tuples in query:", total_tuples); PGR_DBG("Starting processing"); char *err_msg = (char *)""; clock_t start_t = clock(); do_pgr_johnson( edges, total_tuples, directed, result_tuples, result_count, &err_msg); time_msg(" processing Johnson", start_t, clock()); PGR_DBG("Returning %ld tuples\n", *result_count); PGR_DBG("Returned message = %s\n", err_msg); free(err_msg); pfree(edges); pgr_SPI_finish(); }
Datum #else // _MSC_VER PGDLLEXPORT Datum #endif johnson(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; uint32_t call_cntr; uint32_t max_calls; TupleDesc tuple_desc; /**************************************************************************/ /* MODIFY AS NEEDED */ /* */ Matrix_cell_t *result_tuples = 0; size_t result_count = 0; /* */ /**************************************************************************/ if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /*********************************************************************/ /* MODIFY AS NEEDED */ // CREATE OR REPLACE FUNCTION pgr_johnson( // edges_sql TEXT, // directed BOOLEAN, PGR_DBG("Calling process"); process( pgr_text2char(PG_GETARG_TEXT_P(0)), PG_GETARG_BOOL(1), &result_tuples, &result_count); /* */ /*********************************************************************/ funcctx->max_calls = (uint32_t)result_count; funcctx->user_fctx = result_tuples; if (get_call_result_type(fcinfo, NULL, &tuple_desc) != TYPEFUNC_COMPOSITE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record"))); funcctx->tuple_desc = tuple_desc; MemoryContextSwitchTo(oldcontext); } funcctx = SRF_PERCALL_SETUP(); call_cntr = funcctx->call_cntr; max_calls = funcctx->max_calls; tuple_desc = funcctx->tuple_desc; result_tuples = (Matrix_cell_t*) funcctx->user_fctx; if (call_cntr < max_calls) { HeapTuple tuple; Datum result; Datum *values; bool* nulls; /*********************************************************************/ /* MODIFY AS NEEDED */ // OUT seq BIGINT, // OUT from_vid BIGINT, // OUT to_vid BIGINT, // OUT cost float) values = palloc(3 * sizeof(Datum)); nulls = palloc(3 * sizeof(bool)); // postgres starts counting from 1 values[0] = Int64GetDatum(result_tuples[call_cntr].from_vid); nulls[0] = false; values[1] = Int64GetDatum(result_tuples[call_cntr].to_vid); nulls[1] = false; values[2] = Float8GetDatum(result_tuples[call_cntr].cost); nulls[2] = false; /*********************************************************************/ tuple = heap_form_tuple(tuple_desc, values, nulls); result = HeapTupleGetDatum(tuple); SRF_RETURN_NEXT(funcctx, result); } else { // cleanup if (result_tuples) free(result_tuples); SRF_RETURN_DONE(funcctx); } }
static void process(char* edges_sql, int64_t start_vid, int64_t end_vid, bool directed, int heuristic, double factor, double epsilon, bool only_cost, General_path_element_t **result_tuples, size_t *result_count) { check_parameters(heuristic, factor, epsilon); pgr_SPI_connect(); PGR_DBG("Load data"); Pgr_edge_xy_t *edges = NULL; size_t total_edges = 0; pgr_get_edges_xy(edges_sql, &edges, &total_edges); PGR_DBG("Total %ld edges in query:", total_edges); if (total_edges == 0) { PGR_DBG("No edges found"); (*result_count) = 0; (*result_tuples) = NULL; pgr_SPI_finish(); return; } PGR_DBG("Starting processing"); char* log_msg = NULL; char* notice_msg = NULL; char* err_msg = NULL; clock_t start_t = clock(); do_pgr_astarManyToMany( edges, total_edges, &start_vid, 1, &end_vid, 1, directed, heuristic, factor, epsilon, only_cost, true, result_tuples, result_count, &log_msg, ¬ice_msg, &err_msg); if (only_cost) { time_msg("processing pgr_astarCost(one to one)", start_t, clock()); } else { time_msg("processing pgr_astar(one to one)", start_t, clock()); } if (err_msg && (*result_tuples)) { pfree(*result_tuples); (*result_count) = 0; (*result_tuples) = NULL; } pgr_global_report(log_msg, notice_msg, err_msg); if (log_msg) pfree(log_msg); if (notice_msg) pfree(notice_msg); if (err_msg) pfree(err_msg); if (edges) pfree(edges); pgr_SPI_finish(); }
Datum #else // _MSC_VER PGDLLEXPORT Datum #endif one_to_one_withPoints(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; uint32_t call_cntr; uint32_t max_calls; TupleDesc tuple_desc; /*******************************************************************************/ /* MODIFY AS NEEDED */ /* */ General_path_element_t *result_tuples = NULL; size_t result_count = 0; /* */ /*******************************************************************************/ if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /*******************************************************************************/ /* MODIFY AS NEEDED */ // CREATE OR REPLACE FUNCTION pgr_withPoint( // edges_sql TEXT, // points_sql TEXT, // start_pid BIGINT, // end_pid BIGINT, // directed BOOLEAN -- DEFAULT true, // driving_side CHAR -- DEFAULT 'b', // details BOOLEAN -- DEFAULT true, // only_cost BOOLEAN DEFAULT false, PGR_DBG("Calling process"); PGR_DBG("initial driving side:%s", pgr_text2char(PG_GETARG_TEXT_P(5))); process( pgr_text2char(PG_GETARG_TEXT_P(0)), pgr_text2char(PG_GETARG_TEXT_P(1)), PG_GETARG_INT64(2), PG_GETARG_INT64(3), PG_GETARG_BOOL(4), pgr_text2char(PG_GETARG_TEXT_P(5)), PG_GETARG_BOOL(6), PG_GETARG_BOOL(7), &result_tuples, &result_count); /* */ /*******************************************************************************/ funcctx->max_calls = (uint32_t)result_count; funcctx->user_fctx = result_tuples; if (get_call_result_type(fcinfo, NULL, &tuple_desc) != TYPEFUNC_COMPOSITE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record"))); funcctx->tuple_desc = tuple_desc; MemoryContextSwitchTo(oldcontext); } funcctx = SRF_PERCALL_SETUP(); call_cntr = funcctx->call_cntr; max_calls = funcctx->max_calls; tuple_desc = funcctx->tuple_desc; result_tuples = (General_path_element_t*) funcctx->user_fctx; if (call_cntr < max_calls) { HeapTuple tuple; Datum result; Datum *values; char* nulls; /*******************************************************************************/ /* MODIFY AS NEEDED */ // OUT seq BIGINT, // OUT path_seq, // OUT node BIGINT, // OUT edge BIGINT, // OUT cost FLOAT, // OUT agg_cost FLOAT) values = palloc(6 * sizeof(Datum)); nulls = palloc(6 * sizeof(char)); size_t i; for(i = 0; i < 6; ++i) { nulls[i] = ' '; } // postgres starts counting from 1 values[0] = Int32GetDatum(call_cntr + 1); values[1] = Int32GetDatum(result_tuples[call_cntr].seq); values[2] = Int64GetDatum(result_tuples[call_cntr].node); values[3] = Int64GetDatum(result_tuples[call_cntr].edge); values[4] = Float8GetDatum(result_tuples[call_cntr].cost); values[5] = Float8GetDatum(result_tuples[call_cntr].agg_cost); /*******************************************************************************/ tuple = heap_formtuple(tuple_desc, values, nulls); result = HeapTupleGetDatum(tuple); SRF_RETURN_NEXT(funcctx, result); } else { // cleanup if (result_tuples) free(result_tuples); SRF_RETURN_DONE(funcctx); } }
/* MODIFY AS NEEDED */ static void process( char* edges_sql, char* points_sql, int64_t start_pid, int64_t end_pid, bool directed, char *driving_side, bool details, bool only_cost, General_path_element_t **result_tuples, size_t *result_count) { driving_side[0] = tolower(driving_side[0]); PGR_DBG("driving side:%c",driving_side[0]); if (! ((driving_side[0] == 'r') || (driving_side[0] == 'l'))) { driving_side[0] = 'b'; } PGR_DBG("estimated driving side:%c",driving_side[0]); pgr_SPI_connect(); PGR_DBG("load the points"); Point_on_edge_t *points = NULL; size_t total_points = 0; pgr_get_points(points_sql, &points, &total_points); #ifdef DEBUG size_t i = 0; for (i = 0; i < total_points; i ++) { PGR_DBG("%ld\t%ld\t%f\t%c",points[i].pid, points[i].edge_id, points[i].fraction, points[i].side); } #endif PGR_DBG(" -- change the query"); char *edges_of_points_query = NULL; char *edges_no_points_query = NULL; get_new_queries( edges_sql, points_sql, &edges_of_points_query, &edges_no_points_query); PGR_DBG("edges_of_points_query:\n%s", edges_of_points_query); PGR_DBG("edges_no_points_query:\n%s", edges_no_points_query); PGR_DBG("load the edges that match the points"); pgr_edge_t *edges_of_points = NULL; size_t total_edges_of_points = 0; pgr_get_data_5_columns(edges_of_points_query, &edges_of_points, &total_edges_of_points); PGR_DBG("Total %ld edges in query:", total_edges_of_points); #ifdef DEBUG for (i = 0; i < total_edges_of_points; i ++) { PGR_DBG("%ld\t%ld\t%ld\t%f\t%f", edges_of_points[i].id, edges_of_points[i].source, edges_of_points[i].target, edges_of_points[i].cost, edges_of_points[i].reverse_cost); } #endif PGR_DBG("load the edges that dont match the points"); pgr_edge_t *edges = NULL; size_t total_edges = 0; pgr_get_data_5_columns(edges_no_points_query, &edges, &total_edges); PGR_DBG("Total %ld edges in query:", total_edges); #ifdef DEBUG for (i = 0; i < total_edges; i ++) { PGR_DBG("%ld\t%ld\t%ld\t%f\t%f", edges[i].id, edges[i].source, edges[i].target, edges[i].cost, edges[i].reverse_cost); } #endif PGR_DBG("freeing allocated memory not used anymore"); free(edges_of_points_query); free(edges_no_points_query); if ( (total_edges + total_edges_of_points) == 0) { PGR_DBG("No edges found"); (*result_count) = 0; (*result_tuples) = NULL; pgr_SPI_finish(); return; } PGR_DBG("Starting processing"); char *err_msg = NULL; clock_t start_t = clock(); int errcode = do_pgr_withPoints( edges, total_edges, points, total_points, edges_of_points, total_edges_of_points, start_pid, end_pid, directed, driving_side[0], details, only_cost, result_tuples, result_count, &err_msg); time_msg(" processing withPoints one to one", start_t, clock()); PGR_DBG("Returning %ld tuples\n", *result_count); PGR_DBG("Returned message = %s\n", err_msg); if (!err_msg) free(err_msg); pfree(edges); pgr_SPI_finish(); if (errcode) { pgr_send_error(errcode); } }
PGDLLEXPORT Datum one_to_one_dijkstra(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; uint32_t call_cntr; uint32_t max_calls; TupleDesc tuple_desc; /**************************************************************************/ /* MODIFY AS NEEDED */ /* */ General_path_element_t *result_tuples = 0; size_t result_count = 0; /* */ /**************************************************************************/ if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /**********************************************************************/ /* MODIFY AS NEEDED */ // CREATE OR REPLACE FUNCTION pgr_dijkstra( // sql text, start_vids BIGINT, // end_vid BIGINT, // directed BOOLEAN default true, PGR_DBG("Calling process"); process( pgr_text2char(PG_GETARG_TEXT_P(0)), PG_GETARG_INT64(1), PG_GETARG_INT64(2), PG_GETARG_BOOL(3), PG_GETARG_BOOL(4), &result_tuples, &result_count); /* */ /**********************************************************************/ funcctx->max_calls = (uint32_t)result_count; funcctx->user_fctx = result_tuples; if (get_call_result_type(fcinfo, NULL, &tuple_desc) != TYPEFUNC_COMPOSITE) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record"))); } funcctx->tuple_desc = tuple_desc; MemoryContextSwitchTo(oldcontext); } funcctx = SRF_PERCALL_SETUP(); call_cntr = (uint32_t)funcctx->call_cntr; max_calls = (uint32_t)funcctx->max_calls; tuple_desc = funcctx->tuple_desc; result_tuples = (General_path_element_t*) funcctx->user_fctx; if (call_cntr < max_calls) { HeapTuple tuple; Datum result; Datum *values; bool* nulls; /**********************************************************************/ /* MODIFY AS NEEDED */ // OUT seq INTEGER, // OUT path_seq INTEGER, // OUT node BIGINT, // OUT edge BIGINT, // OUT cost FLOAT, // OUT agg_cost FLOAT values = palloc(6 * sizeof(Datum)); nulls = palloc(6 * sizeof(bool)); size_t i; for (i = 0; i < 6; ++i) { nulls[i] = false; } // postgres starts counting from 1 values[0] = Int32GetDatum(call_cntr + 1); values[1] = Int32GetDatum(result_tuples[call_cntr].seq); values[2] = Int64GetDatum(result_tuples[call_cntr].node); values[3] = Int64GetDatum(result_tuples[call_cntr].edge); values[4] = Float8GetDatum(result_tuples[call_cntr].cost); values[5] = Float8GetDatum(result_tuples[call_cntr].agg_cost); /**********************************************************************/ tuple = heap_form_tuple(tuple_desc, values, nulls); result = HeapTupleGetDatum(tuple); SRF_RETURN_NEXT(funcctx, result); } else { // cleanup if (result_tuples) free(result_tuples); SRF_RETURN_DONE(funcctx); } }
/* MODIFY AS NEEDED */ static void process( char* edges_sql, ArrayType *starts, ArrayType *ends, bool directed, bool only_cost, General_path_element_t **result_tuples, size_t *result_count) { pgr_SPI_connect(); int64_t* start_vidsArr = NULL; size_t size_start_vidsArr = 0; start_vidsArr = (int64_t*) pgr_get_bigIntArray(&size_start_vidsArr, starts); int64_t* end_vidsArr = NULL; size_t size_end_vidsArr = 0; end_vidsArr = (int64_t*) pgr_get_bigIntArray(&size_end_vidsArr, ends); pgr_edge_t *edges = NULL; size_t total_edges = 0; pgr_get_edges(edges_sql, &edges, &total_edges); PGR_DBG("Total %ld edges in query:", total_edges); if (total_edges == 0) { PGR_DBG("No edges found"); pgr_SPI_finish(); return; } PGR_DBG("Starting processing"); clock_t start_t = clock(); char *log_msg = NULL; char *notice_msg = NULL; char *err_msg = NULL; do_pgr_bdDijkstra( edges, total_edges, start_vidsArr, size_start_vidsArr, end_vidsArr, size_end_vidsArr, directed, only_cost, result_tuples, result_count, &log_msg, ¬ice_msg, &err_msg); time_msg(" processing pgr_bdDijkstra", start_t, clock()); PGR_DBG("Returning %ld tuples", *result_count); if (err_msg) { if (*result_tuples) free(*result_tuples); } pgr_global_report(log_msg, notice_msg, err_msg); pfree(edges); pgr_SPI_finish(); }
/* MODIFY AS NEEDED */ static void process( char* edges_sql, char* points_sql, int64_t start_pid, int64_t end_pid, int k, bool directed, bool heap_paths, char *driving_side, bool details, General_path_element_t **result_tuples, size_t *result_count) { driving_side[0] = (char) tolower(driving_side[0]); PGR_DBG("driving side:%c",driving_side[0]); if (! ((driving_side[0] == 'r') || (driving_side[0] == 'l'))) { driving_side[0] = 'b'; } pgr_SPI_connect(); Point_on_edge_t *points = NULL; size_t total_points = 0; pgr_get_points(points_sql, &points, &total_points); char *edges_of_points_query = NULL; char *edges_no_points_query = NULL; get_new_queries( edges_sql, points_sql, &edges_of_points_query, &edges_no_points_query); pgr_edge_t *edges_of_points = NULL; size_t total_edges_of_points = 0; pgr_get_edges(edges_of_points_query, &edges_of_points, &total_edges_of_points); pgr_edge_t *edges = NULL; size_t total_edges = 0; pgr_get_edges(edges_no_points_query, &edges, &total_edges); PGR_DBG("freeing allocated memory not used anymore"); free(edges_of_points_query); free(edges_no_points_query); if ((total_edges + total_edges_of_points) == 0) { PGR_DBG("No edges found"); (*result_count) = 0; (*result_tuples) = NULL; pgr_SPI_finish(); return; } PGR_DBG("Starting processing"); clock_t start_t = clock(); char *log_msg = NULL; char *notice_msg = NULL; char *err_msg = NULL; do_pgr_withPointsKsp( edges, total_edges, points, total_points, edges_of_points, total_edges_of_points, start_pid, end_pid, k, directed, heap_paths, driving_side[0], details, result_tuples, result_count, &log_msg, ¬ice_msg, &err_msg); time_msg(" processing withPointsKSP", start_t, clock()); PGR_DBG("Returned message = %s\n", err_msg); if (err_msg) { if (*result_tuples) free(*result_tuples); } pgr_global_report(log_msg, notice_msg, err_msg); pfree(edges); pfree(edges_of_points); pfree(points); pgr_SPI_finish(); }
/*! * bigint start_vid, * bigint end_vid, * float agg_cost, */ void pgr_get_matrixRows( char *sql, Matrix_cell_t **rows, size_t *total_rows) { clock_t start_t = clock(); const int tuple_limit = 1000000; size_t ntuples; size_t total_tuples = 0; Column_info_t info[3]; int i; for (i = 0; i < 3; ++i) { info[i].colNumber = -1; info[i].type = 0; info[i].strict = true; info[i].eType = ANY_INTEGER; } info[0].name = strdup("start_vid"); info[1].name = strdup("end_vid"); info[2].name = strdup("agg_cost"); info[2].eType = ANY_NUMERICAL; void *SPIplan; SPIplan = pgr_SPI_prepare(sql); Portal SPIportal; SPIportal = pgr_SPI_cursor_open(SPIplan); bool moredata = TRUE; (*total_rows) = total_tuples; while (moredata == TRUE) { SPI_cursor_fetch(SPIportal, TRUE, tuple_limit); if (total_tuples == 0) pgr_fetch_column_info(info, 3); ntuples = SPI_processed; total_tuples += ntuples; if (ntuples > 0) { if ((*rows) == NULL) (*rows) = (Matrix_cell_t *)palloc0( total_tuples * sizeof(Matrix_cell_t)); else (*rows) = (Matrix_cell_t *)repalloc( (*rows), total_tuples * sizeof(Matrix_cell_t)); if ((*rows) == NULL) { elog(ERROR, "Out of memory"); } SPITupleTable *tuptable = SPI_tuptable; TupleDesc tupdesc = SPI_tuptable->tupdesc; PGR_DBG("processing %ld edge tupĺes", ntuples); size_t t; for (t = 0; t < ntuples; t++) { HeapTuple tuple = tuptable->vals[t]; pgr_fetch_row(&tuple, &tupdesc, info, &(*rows)[total_tuples - ntuples + t]); } SPI_freetuptable(tuptable); } else { moredata = FALSE; } } SPI_cursor_close(SPIportal); if (total_tuples == 0) { (*total_rows) = 0; PGR_DBG("NO rows"); return; } (*total_rows) = total_tuples; time_msg(" reading Edges", start_t, clock()); }
PGDLLEXPORT Datum withPoints_ksp(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; TupleDesc tuple_desc; /*******************************************************************************/ /* MODIFY AS NEEDED */ /* */ General_path_element_t *result_tuples = 0; size_t result_count = 0; /* */ /*******************************************************************************/ if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /*******************************************************************************/ /* MODIFY AS NEEDED */ // CREATE OR REPLACE FUNCTION pgr_withPoint( // edges_sql TEXT, // points_sql TEXT, // start_pid INTEGER, // end_pid BIGINT, // k BIGINT, // // directed BOOLEAN -- DEFAULT true, // heap_paths BOOLEAN -- DEFAULT false, // driving_side CHAR -- DEFAULT 'b', // details BOOLEAN -- DEFAULT false, PGR_DBG("Calling process"); PGR_DBG("initial driving side:%s", text_to_cstring(PG_GETARG_TEXT_P(7))); process( text_to_cstring(PG_GETARG_TEXT_P(0)), text_to_cstring(PG_GETARG_TEXT_P(1)), PG_GETARG_INT64(2), PG_GETARG_INT64(3), PG_GETARG_INT32(4), PG_GETARG_BOOL(5), PG_GETARG_BOOL(6), text_to_cstring(PG_GETARG_TEXT_P(7)), PG_GETARG_BOOL(8), &result_tuples, &result_count); /* */ /*******************************************************************************/ #if PGSQL_VERSION > 95 funcctx->max_calls = result_count; #else funcctx->max_calls = (uint32_t)result_count; #endif funcctx->user_fctx = result_tuples; if (get_call_result_type(fcinfo, NULL, &tuple_desc) != TYPEFUNC_COMPOSITE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record"))); funcctx->tuple_desc = tuple_desc; MemoryContextSwitchTo(oldcontext); } funcctx = SRF_PERCALL_SETUP(); tuple_desc = funcctx->tuple_desc; result_tuples = (General_path_element_t*) funcctx->user_fctx; if (funcctx->call_cntr < funcctx->max_calls) { HeapTuple tuple; Datum result; Datum *values; bool* nulls; /*******************************************************************************/ /* MODIFY AS NEEDED */ values = palloc(7 * sizeof(Datum)); nulls = palloc(7 * sizeof(bool)); size_t i; for(i = 0; i < 7; ++i) { nulls[i] = false; } /* OUT seq INTEGER, OUT path_id INTEGER, OUT path_seq INTEGER, OUT node BIGINT, OUT edge BIGINT, OUT cost FLOAT, OUT agg_cost FLOAT) */ // postgres starts counting from 1 values[0] = Int32GetDatum(funcctx->call_cntr + 1); values[1] = Int32GetDatum((int)(result_tuples[funcctx->call_cntr].start_id + 1)); values[2] = Int32GetDatum(result_tuples[funcctx->call_cntr].seq); values[3] = Int64GetDatum(result_tuples[funcctx->call_cntr].node); values[4] = Int64GetDatum(result_tuples[funcctx->call_cntr].edge); values[5] = Float8GetDatum(result_tuples[funcctx->call_cntr].cost); values[6] = Float8GetDatum(result_tuples[funcctx->call_cntr].agg_cost); /*******************************************************************************/ tuple =heap_form_tuple(tuple_desc, values, nulls); result = HeapTupleGetDatum(tuple); SRF_RETURN_NEXT(funcctx, result); } else { SRF_RETURN_DONE(funcctx); } }
static void compute( char* edges_sql, int64_t start_vertex, int64_t end_vertex, int k, bool directed, bool heap_paths, General_path_element_t **result_tuples, size_t *result_count) { pgr_SPI_connect(); PGR_DBG("Load data"); pgr_edge_t *edges = NULL; size_t total_edges = 0; if (start_vertex == end_vertex) { pgr_SPI_finish(); return; } pgr_get_edges(edges_sql, &edges, &total_edges); PGR_DBG("Total %ld edges in query:", total_edges); if (total_edges == 0) { PGR_DBG("No edges found"); pgr_SPI_finish(); return; } PGR_DBG("Calling do_pgr_ksp\n"); PGR_DBG("heap_paths = %i\n", heap_paths); clock_t start_t = clock(); char *log_msg = NULL; char *notice_msg = NULL; char *err_msg = NULL; do_pgr_ksp( edges, total_edges, start_vertex, end_vertex, k, directed, heap_paths, result_tuples, result_count, &log_msg, ¬ice_msg, &err_msg); time_msg(" processing KSP", start_t, clock()); if (err_msg && (*result_tuples)) { pfree(*result_tuples); (*result_tuples) = NULL; (*result_count) = 0; } pgr_global_report(log_msg, notice_msg, err_msg); if (log_msg) pfree(log_msg); if (notice_msg) pfree(notice_msg); if (err_msg) pfree(err_msg); pgr_global_report(log_msg, notice_msg, err_msg); pfree(edges); pgr_SPI_finish(); }
PGDLLEXPORT Datum kshortest_path(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; TupleDesc tuple_desc; General_path_element_t *path = NULL; size_t result_count = 0; if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* CREATE OR REPLACE FUNCTION _pgr_ksp( sql text, start_vid bigint, end_vid bigint, k integer, directed boolean, heap_paths boolean */ PGR_DBG("Calling process"); compute( text_to_cstring(PG_GETARG_TEXT_P(0)), PG_GETARG_INT64(1), PG_GETARG_INT64(2), PG_GETARG_INT32(3), PG_GETARG_BOOL(4), PG_GETARG_BOOL(5), &path, &result_count); PGR_DBG("Total number of tuples to be returned %ld \n", result_count); #if PGSQL_VERSION > 95 funcctx->max_calls = result_count; #else funcctx->max_calls = (uint32_t)result_count; #endif funcctx->user_fctx = path; if (get_call_result_type(fcinfo, NULL, &tuple_desc) != TYPEFUNC_COMPOSITE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record\n"))); funcctx->tuple_desc = tuple_desc; MemoryContextSwitchTo(oldcontext); } funcctx = SRF_PERCALL_SETUP(); tuple_desc = funcctx->tuple_desc; path = (General_path_element_t*) funcctx->user_fctx; if (funcctx->call_cntr < funcctx->max_calls) { HeapTuple tuple; Datum result; Datum *values; bool* nulls; values = palloc(7 * sizeof(Datum)); nulls = palloc(7 * sizeof(bool)); size_t i; for (i = 0; i < 7; ++i) { nulls[i] = false; } values[0] = Int32GetDatum(funcctx->call_cntr + 1); values[1] = Int32GetDatum(path[funcctx->call_cntr].start_id + 1); values[2] = Int32GetDatum(path[funcctx->call_cntr].seq); values[3] = Int64GetDatum(path[funcctx->call_cntr].node); values[4] = Int64GetDatum(path[funcctx->call_cntr].edge); values[5] = Float8GetDatum(path[funcctx->call_cntr].cost); values[6] = Float8GetDatum(path[funcctx->call_cntr].agg_cost); tuple = heap_form_tuple(tuple_desc, values, nulls); result = HeapTupleGetDatum(tuple); SRF_RETURN_NEXT(funcctx, result); } else { /* do when there is no more left */ SRF_RETURN_DONE(funcctx); } }
Datum #else // _MSC_VER PGDLLEXPORT Datum #endif driving_many_to_dist(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; int call_cntr; int max_calls; TupleDesc tuple_desc; pgr_path_element3_t *ret_path = 0; /* stuff done only on the first call of the function */ if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; int path_count = 0; /* create a function context for cross-call persistence */ funcctx = SRF_FIRSTCALL_INIT(); /* switch to memory context appropriate for multiple function calls */ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); int64_t* sourcesArr; int num; sourcesArr = (int64_t*) pgr_get_bigIntArray(&num, PG_GETARG_ARRAYTYPE_P(1)); PGR_DBG("sourcesArr size %d ", num); PGR_DBG("Calling driving_many_to_dist_driver"); driving_many_to_dist_driver( pgr_text2char(PG_GETARG_TEXT_P(0)), // sql sourcesArr, num, // array of sources PG_GETARG_FLOAT8(2), // distance PG_GETARG_BOOL(3), // directed PG_GETARG_BOOL(4), // equicost PG_GETARG_BOOL(5), // has_rcost &ret_path, &path_count); free(sourcesArr); /* total number of tuples to be returned */ funcctx->max_calls = path_count; funcctx->user_fctx = ret_path; if (get_call_result_type(fcinfo, NULL, &tuple_desc) != TYPEFUNC_COMPOSITE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record"))); funcctx->tuple_desc = tuple_desc; MemoryContextSwitchTo(oldcontext); } /* stuff done on every call of the function */ funcctx = SRF_PERCALL_SETUP(); call_cntr = funcctx->call_cntr; max_calls = funcctx->max_calls; tuple_desc = funcctx->tuple_desc; ret_path = (pgr_path_element3_t*) funcctx->user_fctx; /* do when there is more left to send */ if (call_cntr < max_calls) { HeapTuple tuple; Datum result; Datum *values; char* nulls; values = palloc(6 * sizeof(Datum)); nulls = palloc(6 * sizeof(char)); // id, start_v, node, edge, cost, tot_cost values[0] = Int32GetDatum(call_cntr + 1); nulls[0] = ' '; values[1] = Int64GetDatum(ret_path[call_cntr].from); nulls[1] = ' '; values[2] = Int64GetDatum(ret_path[call_cntr].vertex); nulls[2] = ' '; values[3] = Int64GetDatum(ret_path[call_cntr].edge); nulls[3] = ' '; values[4] = Float8GetDatum(ret_path[call_cntr].cost); nulls[4] = ' '; values[5] = Float8GetDatum(ret_path[call_cntr].tot_cost); nulls[5] = ' '; tuple = heap_formtuple(tuple_desc, values, nulls); /* make the tuple into a datum */ result = HeapTupleGetDatum(tuple); /* clean up (this is not really necessary) */ pfree(values); pfree(nulls); SRF_RETURN_NEXT(funcctx, result); } else { /* do when there is no more left */ if (ret_path) free(ret_path); SRF_RETURN_DONE(funcctx); } }
PGDLLEXPORT Datum maximum_cardinality_matching(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; uint32_t call_cntr; uint32_t max_calls; TupleDesc tuple_desc; /**************************************************************************/ /* MODIFY AS NEEDED */ /* */ pgr_basic_edge_t *result_tuples = 0; size_t result_count = 0; /* */ /**************************************************************************/ if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /**********************************************************************/ /* MODIFY AS NEEDED */ PGR_DBG("Calling process"); process( pgr_text2char(PG_GETARG_TEXT_P(0)), PG_GETARG_BOOL(1), &result_tuples, &result_count); /* */ /**********************************************************************/ funcctx->max_calls = (uint32_t) result_count; funcctx->user_fctx = result_tuples; if (get_call_result_type(fcinfo, NULL, &tuple_desc) != TYPEFUNC_COMPOSITE) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record"))); } funcctx->tuple_desc = tuple_desc; MemoryContextSwitchTo(oldcontext); } funcctx = SRF_PERCALL_SETUP(); call_cntr = (uint32_t)funcctx->call_cntr; max_calls = (uint32_t)funcctx->max_calls; tuple_desc = funcctx->tuple_desc; result_tuples = (pgr_basic_edge_t *) funcctx->user_fctx; if (call_cntr < max_calls) { HeapTuple tuple; Datum result; Datum *values; bool *nulls; /**********************************************************************/ /* MODIFY AS NEEDED */ values = palloc(4 * sizeof(Datum)); nulls = palloc(4 * sizeof(bool)); size_t i; for (i = 0; i < 4; ++i) { nulls[i] = false; } // postgres starts counting from 1 values[0] = Int32GetDatum(call_cntr + 1); values[1] = Int64GetDatum(result_tuples[call_cntr].edge_id); values[2] = Int64GetDatum(result_tuples[call_cntr].source); values[3] = Int64GetDatum(result_tuples[call_cntr].target); /**********************************************************************/ tuple = heap_form_tuple(tuple_desc, values, nulls); result = HeapTupleGetDatum(tuple); SRF_RETURN_NEXT(funcctx, result); } else { // cleanup if (result_tuples) free(result_tuples); SRF_RETURN_DONE(funcctx); } }