root/sandbox/sigis/christian.gonzalez/trunk/core/src/dijkstra.c

Revision 332, 14.6 KB (checked in by christian.gonzalez, 18 months ago)

- "has_reverse_cost" --> deprecated, no used by the function internally. Now however, with only passing the

argument "directed" and "reverse_cost" in the SQL does the same

- added parameter "bidirectional" --> it
- added file README.shortest_path.
- elog --> ereport.
- text2char --> eliminated, now using -->DatumGetCString(DirectFunctionCall?1(textout,PointerGetDatum?( ))).
- palloc --> SPI_palloc.
- repalloc --> SPI_repalloc.
- pfree --> SPI_pfree.
- the proccess for calculating the min_id was improved, now calculated in function "fetch_edge".
- minor improvements.
- added some comments.
- added some macro fucntions.
- added constant definitions.
- reorganized the source code

Line 
1/*
2 * Shortest path algorithm for PostgreSQL
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 *
18 */
19
20#include "postgres.h"
21#include "executor/spi.h"
22#include "funcapi.h"
23#include "fmgr.h"
24
25#include "dijkstra.h"
26
27// The number of tuples to fetch from the SPI cursor at each iteration
28#define TUPLIMIT 1000
29
30#undef DEBUG
31//#define DEBUG 1
32
33#ifdef DEBUG
34  #define DBG(format, arg...) ereport(NOTICE, (errmsg_internal(format, ## arg)));
35#else
36  #define DBG(format, arg...) do { ; } while (0)
37#endif
38
39/*
40 * FUNCTIONS PROTOTYPES
41 */
42Datum shortest_path(PG_FUNCTION_ARGS);
43
44static int finish(int code, int ret);
45
46static int fetch_edge_columns(SPITupleTable *tuptable, edge_columns_t *edge_columns,
47    bool bidirectional);
48
49static void fetch_edge(HeapTuple *tuple, TupleDesc *tupdesc, edge_columns_t *edge_columns,
50    edge_t *target_edge, int *minid);
51
52static int compute_shortest_path(char* sql, int start_vertex,
53    int end_vertex, bool directed, path_element_t **path, int *path_count);
54
55
56#ifdef PG_MODULE_MAGIC
57PG_MODULE_MAGIC;
58#endif
59
60/*
61 * Interface function to PostgreSQL
62 */
63PG_FUNCTION_INFO_V1(shortest_path);
64
65Datum
66shortest_path(PG_FUNCTION_ARGS)
67{
68  FuncCallContext     *funcctx;
69  Datum               result;
70  MemoryContext       oldcontext;
71
72  int                 tuplen;
73  int                 call_cntr;
74  int                 max_calls;
75  TupleDesc           tuple_desc;
76  path_element_t      *path;
77  int                 path_count;
78  int                 ret;
79
80  /* stuff done only on the first call of the function */
81  if (SRF_IS_FIRSTCALL())
82  {
83      path_count = 0;
84
85      /* create a function context for cross-call persistence */
86      funcctx = SRF_FIRSTCALL_INIT();
87
88      /* switch to memory context appropriate for multiple function calls */
89      oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
90
91
92      ret = compute_shortest_path(DatumGetCString(DirectFunctionCall1(textout,PointerGetDatum(PG_GETARG_DATUM(0)))),
93                                  PG_GETARG_INT32(1), //id
94                                  PG_GETARG_INT32(2), //destination id
95                                  PG_GETARG_BOOL(3), //if is directed
96                                  //PG_GETARG_BOOL(4), //has reverse cost, not used
97                                  &path,//for return the route
98                                  &path_count //number of returned routes
99                                  );
100
101      /* total number of tuples to be returned */
102      funcctx->max_calls = path_count;
103      funcctx->user_fctx = path;
104
105      /*Getting tuple_desc  from our type 'path_result'*/
106      funcctx->tuple_desc = BlessTupleDesc(RelationNameGetTupleDesc("path_result"));
107
108      MemoryContextSwitchTo(oldcontext);
109  }
110
111  /* stuff done on every call of the function */
112  funcctx = SRF_PERCALL_SETUP();
113
114  call_cntr = funcctx->call_cntr;
115  max_calls = funcctx->max_calls;
116  tuple_desc = funcctx->tuple_desc;
117  path = (path_element_t*) funcctx->user_fctx;
118
119  /*
120   * do when there is more left to send
121   */
122  if (call_cntr < max_calls)
123  {
124    HeapTuple   tuple;
125    Datum       *values;
126    bool        *nulls;
127
128    /*Number of attributes in the tuple */
129    tuplen = tuple_desc->natts;
130
131    values = SPI_palloc(tuplen * sizeof(Datum));
132    nulls = SPI_palloc(tuplen * sizeof(bool));
133
134    values[0] = Int32GetDatum(path[call_cntr].vertex_id);
135    values[1] = Int32GetDatum(path[call_cntr].edge_id);
136    values[2] = Float8GetDatum(path[call_cntr].cost);
137
138    /* build tuple from datum array*/
139    tuple = heap_form_tuple(tuple_desc,values,nulls);
140
141    /* make the tuple into a datum */
142    result = HeapTupleGetDatum(tuple);
143
144    /* clean up (this is not really necessary) */
145    SPI_pfree(values);
146    SPI_pfree(nulls);
147
148    SRF_RETURN_NEXT(funcctx, result);
149  }
150  else    /* do when there is no more left */
151  {
152    SRF_RETURN_DONE(funcctx);
153  }
154
155  SPI_pfree(path);
156
157}
158
159/*
160 *
161 */
162static int
163compute_shortest_path(char* sql, int start_vertex, int end_vertex,
164                                 bool directed, path_element_t **path, int *path_count)
165{
166
167  int             SPIcode;
168  void            *SPIplan;
169  Portal          SPIportal;
170  bool            moredata = TRUE;
171  int             ntuples;
172  edge_t          *edges = NULL;
173  int             total_tuples = 0;
174  edge_columns_t  edge_columns = {id: PGR_COLUMN_NOTEXIST ,
175                                 source: PGR_COLUMN_NOTEXIST,
176                                 target: PGR_COLUMN_NOTEXIST,
177                                 cost: PGR_COLUMN_NOTEXIST,
178                                 bidirectional: PGR_COLUMN_NOTEXIST,
179                                 reverse_cost: PGR_COLUMN_NOTEXIST};
180  int             v_min_id = INT_MAX;
181  int             *minid;
182  int             s_count = 0;
183  int             t_count = 0;
184  char            *err_msg;
185  int             ret = -1;
186  register int    z;
187
188  /*pointer for manipulating the 'minimum id' in fetch_edge function*/
189  minid = &v_min_id;
190
191  DBG("Minimum source and target id = %i", v_min_id);
192
193  DBG("#---- executing function 'compute_shortest_path' ----#\n");
194
195  if ((SPIcode = SPI_connect() ) != SPI_OK_CONNECT)
196  {
197    ereport(ERROR, (errmsg_internal("shortest_path: couldn't open a connection to SPI")));
198    return -1;
199  }
200
201  if ( (SPIplan = SPI_prepare(sql, 0, NULL) )== NULL)
202  {
203    ereport(ERROR, (errmsg_internal("shortest_path: couldn't create query plan via SPI")));
204    return -1;
205  }
206
207  if ((SPIportal = SPI_cursor_open(NULL, SPIplan, NULL, NULL, true)) == NULL)
208  {
209    ereport(ERROR, (errmsg_internal("shortest_path: SPI_cursor_open('%s') returns NULL", sql)));
210    return -1;
211  }
212
213  while (moredata == TRUE)
214  {
215    SPI_cursor_fetch(SPIportal, TRUE, TUPLIMIT);
216
217    if (edge_columns.id == PGR_COLUMN_NOTEXIST)
218    {
219      if (fetch_edge_columns(SPI_tuptable, &edge_columns, directed) == -1)
220      {
221        return finish(SPIcode, ret);
222      }
223    }
224
225    ntuples = SPI_processed;
226    total_tuples += ntuples;
227
228    if (!edges)
229    {
230      edges = SPI_palloc(total_tuples * sizeof(edge_t));
231    }
232    else
233    {
234      edges = SPI_repalloc(edges, total_tuples * sizeof(edge_t));
235    }
236
237    if (edges == NULL)
238    {
239      ereport(ERROR, (errmsg_internal("Out of memory")));
240      return finish(SPIcode, ret);
241    }
242
243    if (ntuples > 0)
244    {
245      int t;
246      SPITupleTable *tuptable = SPI_tuptable;
247      TupleDesc tupdesc = SPI_tuptable->tupdesc;
248
249      for (t = 0; t < ntuples; t++)
250      {
251        HeapTuple tuple = tuptable->vals[t];
252        fetch_edge(&tuple, &tupdesc, &edge_columns, &edges[total_tuples - ntuples + t], minid);
253
254        /*
255        DBG("Query columns values: id=%i source=%i target=%i cost=%f bidirectional=%i reverse_cost=%f",
256              edges[total_tuples - ntuples + t].id,
257              edges[total_tuples - ntuples + t].source,
258              edges[total_tuples - ntuples + t].target,
259              edges[total_tuples - ntuples + t].cost,
260              edges[total_tuples - ntuples + t].bidirectional,
261              edges[total_tuples - ntuples + t].reverse_cost);
262         */
263      }
264      SPI_freetuptable(tuptable);
265    }
266    else
267    {
268        moredata = FALSE;
269    }
270  }
271
272  DBG("Total %i tuples", total_tuples);
273  DBG("'New' Minimum source and target id = %i", v_min_id);
274
275  // reducing vertex id (renumbering)
276  for(z=0; z<total_tuples; z++)
277  {
278    //check if edges[] contains source and target
279    if(edges[z].source == start_vertex || edges[z].target == start_vertex)
280    {
281      ++s_count;
282    }
283    if(edges[z].source == end_vertex || edges[z].target == end_vertex)
284    {
285      ++t_count;
286    }
287
288    /*re-assign the news id to source and target*/
289    DBG("Original source id -> %i -- Original target id ->%i", edges[z].source, edges[z].target);
290
291    edges[z].source-=v_min_id;
292    edges[z].target-=v_min_id;
293
294    DBG("New source id -> %i -- New target id ->%i \n", edges[z].source, edges[z].target);
295
296  }
297
298  if(s_count == 0)
299  {
300    ereport(ERROR, (errmsg_internal("Start vertex was not found.")));
301    return -1;
302  }
303
304  if(t_count == 0)
305  {
306    ereport(ERROR, (errmsg_internal("Target vertex was not found.")));
307    return -1;
308  }
309
310  start_vertex -= v_min_id;
311  end_vertex   -= v_min_id;
312
313  DBG("#---- executing function 'boost_dijkstra' ----#\n");
314
315  ret = boost_dijkstra(edges, total_tuples, start_vertex, end_vertex,
316                       directed, path, path_count, &err_msg);
317
318  DBG("SIZE %i\n",*path_count);
319
320
321  //restoring original vertex id
322  for(z=0;z<*path_count;z++)
323  {
324    //DBG("vetex %i\n",(*path)[z].vertex_id);
325    (*path)[z].vertex_id+=v_min_id;
326  }
327
328  DBG("ret = %i\n", ret);
329  DBG("*path_count = %i\n", *path_count);
330
331  if (ret < 0)
332  {
333    ereport(ERROR, (errcode(ERRCODE_E_R_E_CONTAINING_SQL_NOT_PERMITTED),
334            errmsg("Error computing path: %s", err_msg)));
335  }
336
337  return finish(SPIcode, ret);
338}
339
340/*
341 *
342 */
343static int
344finish(int code, int ret)
345{
346  DBG("#---- executing function 'finish' ----#\n");
347
348  code = SPI_finish();
349  if (code  != SPI_OK_FINISH )
350  {
351    ereport(ERROR, (errmsg_internal("couldn't disconnect from SPI")));
352    return -1 ;
353  }
354  return ret;
355}
356
357 /*
358  * verify if the "mandatory and additional columns" were passed to query
359  */
360static int
361fetch_edge_columns(SPITupleTable *tuptable, edge_columns_t *edge_columns, bool directed)
362{
363
364  DBG("#---- executing function 'fetch_edge_columns' ----#\n");
365  /*
366   * Getting the column number from the query
367   */
368  edge_columns->id = SPI_fnumber(tuptable->tupdesc, "id");
369  edge_columns->source = SPI_fnumber(tuptable->tupdesc, "source");
370  edge_columns->target = SPI_fnumber(tuptable->tupdesc, "target");
371  edge_columns->cost = SPI_fnumber(tuptable->tupdesc, "cost");
372
373  /*
374   *Additional columns
375   */
376  edge_columns->bidirectional = SPI_fnumber(tuptable->tupdesc, "bidirectional");
377  edge_columns->reverse_cost = SPI_fnumber(tuptable->tupdesc, "reverse_cost");
378
379  /*
380   * verify if the "mandatory columns" were passed to query string really exists
381   */
382  if (edge_columns->id == SPI_ERROR_NOATTRIBUTE || edge_columns->source == SPI_ERROR_NOATTRIBUTE ||
383      edge_columns->target == SPI_ERROR_NOATTRIBUTE || edge_columns->cost == SPI_ERROR_NOATTRIBUTE)
384  {
385    ereport(ERROR, (errmsg_internal("Error!: Query must return columns 'id', 'source', 'target' and 'cost'")));
386    return -1;
387  }
388
389  /*
390   * verify if the "mandatory columns" data type are corrects
391   */
392  if (SPI_gettypeid(tuptable->tupdesc, edge_columns->id) != INT4OID ||
393      SPI_gettypeid(tuptable->tupdesc, edge_columns->source) != INT4OID ||
394      SPI_gettypeid(tuptable->tupdesc, edge_columns->target) != INT4OID ||
395      SPI_gettypeid(tuptable->tupdesc, edge_columns->cost) != FLOAT8OID)
396  {
397    ereport(ERROR, (errmsg_internal("Error, columns 'source', 'target' must be of type int4 and 'cost' must be of type float8")));
398    return -1;
399  }
400
401  DBG("Query columns numbers: id=%i source=%i target=%i cost=%i",
402      edge_columns->id, edge_columns->source, edge_columns->target, edge_columns->cost);
403
404  /*
405   * verify if the "additional column bidirectional" were passed to query string
406   */
407
408  if (edge_columns->bidirectional == SPI_ERROR_NOATTRIBUTE)
409  {
410    edge_columns->bidirectional = PGR_COLUMN_NOTEXIST;
411  }
412  else
413  {
414    DBG("Additional column number: bidirectional=%i", edge_columns->bidirectional);
415
416    if (directed)
417    {
418      if (SPI_gettypeid(tuptable->tupdesc, edge_columns->bidirectional) != BOOLOID)
419      {
420        ereport(ERROR, (errmsg_internal("Error, columns 'bidirectional' must be of type bool")));
421        return -1;
422      }
423    }
424    else
425    {
426     DBG("Additional column 'bidirectional' exists in the query, but it is not used because the 'directed' parameter is false");
427    }
428  }
429
430  /*
431   * verify if the "additional column reverse_cost" were passed to query
432   */
433  if (edge_columns->reverse_cost == SPI_ERROR_NOATTRIBUTE)
434  {
435    edge_columns->reverse_cost = PGR_COLUMN_NOTEXIST;
436  }
437  else
438  {
439    DBG("Additional column number: reverse_cost=%i", edge_columns->reverse_cost);
440
441    if(directed)
442    {
443      if (SPI_gettypeid(tuptable->tupdesc, edge_columns->reverse_cost) != FLOAT8OID)
444      {
445        ereport(ERROR, (errmsg_internal("Error, columns 'reverse_cost' must be of type float8")));
446        return -1;
447      }
448    }
449    else
450    {
451      DBG("Additional column 'reverse_cost' exists in the query, but it is not used because the 'directed' parameter is false");
452    }
453  }
454
455  return 0;
456}
457
458/*
459 *
460 */
461static void
462fetch_edge(HeapTuple *tuple, TupleDesc *tupdesc, edge_columns_t *edge_columns, edge_t *target_edge,int *minid)
463{
464  Datum binval;
465  bool isnull;
466
467  DBG("#---- Executing function 'fetch_edge' ----#\n");
468
469  /*
470   * Getting values in the columns
471   */
472  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->id, &isnull);
473  if (isnull)
474  {
475    ereport(ERROR, (errmsg_internal("id contains a null value")));
476  }
477  target_edge->id = DatumGetInt32(binval);
478
479  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->source, &isnull);
480  if (isnull)
481  {
482    ereport(ERROR, (errmsg_internal("source contains a null value")));
483  }
484  target_edge->source = DatumGetInt32(binval);
485
486  //getting the min vertex id
487  *minid = PGR_FUNC_MIN(target_edge->source,*minid);
488
489  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->target, &isnull);
490  if (isnull)
491  {
492    ereport(ERROR, (errmsg_internal("target contains a null value")));
493  }
494  target_edge->target = DatumGetInt32(binval);
495
496  //getting the min vertex id
497  *minid = PGR_FUNC_MIN(target_edge->target,*minid);
498
499  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->cost, &isnull);
500  if (isnull)
501  {
502    ereport(ERROR, (errmsg_internal("cost contains a null value")));
503  }
504  target_edge->cost = DatumGetFloat8(binval);
505
506  /*
507   * Get values from additional columns
508   */
509  if (edge_columns->bidirectional != PGR_COLUMN_NOTEXIST)
510  {
511    binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->bidirectional, &isnull);
512    if (isnull)
513    {
514      ereport(ERROR, (errmsg_internal("bidirectional contains a null value")));
515    }
516    target_edge->bidirectional =  DatumGetBool(binval);
517  }
518
519  if (edge_columns->reverse_cost != PGR_COLUMN_NOTEXIST)
520  {
521    binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->reverse_cost, &isnull);
522    if (isnull)
523    {
524      ereport(ERROR, (errmsg_internal("reverse_cost contains a null value")));
525    }
526    target_edge->reverse_cost =  DatumGetFloat8(binval);
527  }
528}
Note: See TracBrowser for help on using the browser.