root/tags/release-1.0-beta/astar.c

Revision 39, 14.9 KB (checked in by anton, 3 years ago)

1.0.0b tag added

Line 
1/*
2 * A* Shortest path algorithm for PostgreSQL
3 *
4 * Copyright (c) 2006 Anton A. Patrushev, Orkney, Inc.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 *
20 */
21
22#include "postgres.h"
23#include "executor/spi.h"
24#include "funcapi.h"
25
26#include <stdio.h>
27#include <stdlib.h>
28#include <search.h>
29
30#include "astar.h"
31
32//-------------------------------------------------------------------------
33
34/*
35 * Define this to have profiling enabled
36 */
37//#define PROFILE
38
39#ifdef PROFILE
40#include <sys/time.h>
41
42struct timeval prof_astar, prof_store, prof_extract, prof_total;
43long proftime[5];
44long profipts1, profipts2, profopts;
45
46#define profstart(x) do { gettimeofday(&x, NULL); } while (0);
47#define profstop(n, x) do { struct timeval _profstop;   \
48        long _proftime;                         \
49        gettimeofday(&_profstop, NULL);                         \
50        _proftime = ( _profstop.tv_sec*1000000+_profstop.tv_usec) -     \
51                ( x.tv_sec*1000000+x.tv_usec); \
52        elog(NOTICE, \
53                "PRF(%s) %lu (%f ms)", \
54                (n), \
55             _proftime, _proftime / 1000.0);    \
56        } while (0);
57
58#else
59
60#define profstart(x) do { } while (0);
61#define profstop(n, x) do { } while (0);
62
63#endif // PROFILE
64
65
66//-------------------------------------------------------------------------
67
68Datum shortest_path_astar(PG_FUNCTION_ARGS);
69
70#undef DEBUG
71//#define DEBUG 1
72
73#ifdef DEBUG
74#define DBG(format, arg...)                     \
75    elog(NOTICE, format , ## arg)
76#else
77#define DBG(format, arg...) do { ; } while (0)
78#endif
79
80// The number of tuples to fetch from the SPI cursor at each iteration
81#define TUPLIMIT 1000
82
83static char *
84text2char(text *in)
85{
86  char *out = palloc(VARSIZE(in));
87
88  memcpy(out, VARDATA(in), VARSIZE(in) - VARHDRSZ);
89  out[VARSIZE(in) - VARHDRSZ] = '\0';
90  return out;
91}
92
93static int
94finish(int code, int ret)
95{
96  code = SPI_finish();
97  if (code  != SPI_OK_FINISH )
98    {
99      elog(ERROR,"couldn't disconnect from SPI");
100      return -1 ;
101    }
102 
103  return ret;
104}
105 
106typedef struct edge_astar_columns
107{
108  int id;
109  int source;
110  int target;
111  int cost;
112  int reverse_cost;
113  int s_x;
114  int s_y;
115  int t_x;
116  int t_y;
117} edge_astar_columns_t;
118
119static int
120fetch_edge_astar_columns(SPITupleTable *tuptable,
121                         edge_astar_columns_t *edge_columns,
122                         bool has_reverse_cost)
123{
124  edge_columns->id = SPI_fnumber(SPI_tuptable->tupdesc, "id");
125  edge_columns->source = SPI_fnumber(SPI_tuptable->tupdesc, "source");
126  edge_columns->target = SPI_fnumber(SPI_tuptable->tupdesc, "target");
127  edge_columns->cost = SPI_fnumber(SPI_tuptable->tupdesc, "cost");
128  if (edge_columns->id == SPI_ERROR_NOATTRIBUTE ||
129      edge_columns->source == SPI_ERROR_NOATTRIBUTE ||
130      edge_columns->target == SPI_ERROR_NOATTRIBUTE ||
131      edge_columns->cost == SPI_ERROR_NOATTRIBUTE)
132    {
133      elog(ERROR, "Error, query must return columns "
134           "'id', 'source', 'target' and 'cost'");
135      return -1;
136    }
137
138  if (SPI_gettypeid(SPI_tuptable->tupdesc,
139                    edge_columns->source) != INT4OID ||
140      SPI_gettypeid(SPI_tuptable->tupdesc,
141                    edge_columns->target) != INT4OID ||
142      SPI_gettypeid(SPI_tuptable->tupdesc, edge_columns->cost) != FLOAT8OID)
143    {
144      elog(ERROR, "Error, columns 'source', 'target' must be of type int4, "
145           "'cost' must be of type float8");
146      return -1;
147    }
148
149  DBG("columns: id %i source %i target %i cost %i",
150      edge_columns->id, edge_columns->source,
151      edge_columns->target, edge_columns->cost);
152
153  if (has_reverse_cost)
154    {
155      edge_columns->reverse_cost = SPI_fnumber(SPI_tuptable->tupdesc,
156                                               "reverse_cost");
157
158      if (edge_columns->reverse_cost == SPI_ERROR_NOATTRIBUTE)
159        {
160          elog(ERROR, "Error, reverse_cost is used, but query did't return "
161               "'reverse_cost' column");
162          return -1;
163        }
164
165      if (SPI_gettypeid(SPI_tuptable->tupdesc,
166                        edge_columns->reverse_cost) != FLOAT8OID)
167        {
168          elog(ERROR, "Error, columns 'reverse_cost' must be of type float8");
169          return -1;
170        }
171
172      DBG("columns: reverse_cost cost %i", edge_columns->reverse_cost);
173    }
174
175  edge_columns->s_x = SPI_fnumber(SPI_tuptable->tupdesc, "x1");
176  edge_columns->s_y = SPI_fnumber(SPI_tuptable->tupdesc, "y1");
177  edge_columns->t_x = SPI_fnumber(SPI_tuptable->tupdesc, "x2");
178  edge_columns->t_y = SPI_fnumber(SPI_tuptable->tupdesc, "y2");
179
180  if (edge_columns->s_x == SPI_ERROR_NOATTRIBUTE ||
181      edge_columns->s_y == SPI_ERROR_NOATTRIBUTE ||
182      edge_columns->t_x == SPI_ERROR_NOATTRIBUTE ||
183      edge_columns->t_y == SPI_ERROR_NOATTRIBUTE)
184    {
185      elog(ERROR, "Error, query must return columns "
186           "'x1', 'x2', 'y1' and 'y2'");
187      return -1;
188    }
189
190  DBG("columns: x1 %i y1 %i x2 %i y2 %i",
191      edge_columns->s_x, edge_columns->s_y,
192      edge_columns->t_x,edge_columns->t_y);
193   
194  return 0;
195}
196
197static void
198fetch_edge_astar(HeapTuple *tuple, TupleDesc *tupdesc,
199                 edge_astar_columns_t *edge_columns,
200                 edge_astar_t *target_edge)
201{
202  Datum binval;
203  bool isnull;
204
205  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->id, &isnull);
206  if (isnull)
207    elog(ERROR, "id contains a null value");
208  target_edge->id = DatumGetInt32(binval);
209
210  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->source, &isnull);
211  if (isnull)
212    elog(ERROR, "source contains a null value");
213  target_edge->source = DatumGetInt32(binval);
214
215  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->target, &isnull);
216  if (isnull)
217    elog(ERROR, "target contains a null value");
218  target_edge->target = DatumGetInt32(binval);
219
220  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->cost, &isnull);
221  if (isnull)
222    elog(ERROR, "cost contains a null value");
223  target_edge->cost = DatumGetFloat8(binval);
224
225  if (edge_columns->reverse_cost != -1)
226    {
227      binval = SPI_getbinval(*tuple, *tupdesc,
228                             edge_columns->reverse_cost, &isnull);
229      if (isnull)
230        elog(ERROR, "reverse_cost contains a null value");
231      target_edge->reverse_cost =  DatumGetFloat8(binval);
232    }
233
234  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->s_x, &isnull);
235  if (isnull)
236    elog(ERROR, "source x contains a null value");
237  target_edge->s_x = DatumGetFloat8(binval);
238
239  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->s_y, &isnull);
240  if (isnull)
241    elog(ERROR, "source y contains a null value");
242  target_edge->s_y = DatumGetFloat8(binval);
243 
244  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->t_x, &isnull);
245  if (isnull)
246    elog(ERROR, "target x contains a null value");
247  target_edge->t_x = DatumGetFloat8(binval);
248 
249  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->t_y, &isnull);
250  if (isnull)
251    elog(ERROR, "target y contains a null value");
252  target_edge->t_y = DatumGetFloat8(binval);
253}
254
255
256static int compute_shortest_path_astar(char* sql, int source_vertex_id,
257                                       int target_vertex_id, bool directed,
258                                       bool has_reverse_cost,
259                                       path_element_t **path, int *path_count)
260{
261 
262  int SPIcode;
263  void *SPIplan;
264  Portal SPIportal;
265  bool moredata = TRUE;
266  int ntuples;
267  edge_astar_t *edges = NULL;
268  int total_tuples = 0;
269 
270  int v_max_id=0;
271  int v_min_id=INT_MAX; 
272   
273  edge_astar_columns_t edge_columns = {id: -1, source: -1, target: -1,
274                                       cost: -1, reverse_cost: -1,
275                                       s_x: -1, s_y: -1, t_x: -1, t_y: -1};
276  char *err_msg;
277  int ret = -1;
278  register int z;
279 
280  int s_count=0;
281  int t_count=0;
282 
283  struct vItem
284  {
285    int id;
286    int key;
287  };
288 
289  DBG("start shortest_path_astar\n");
290       
291  SPIcode = SPI_connect();
292  if (SPIcode  != SPI_OK_CONNECT)
293    {
294      elog(ERROR, "shortest_path_astar: couldn't open a connection to SPI");
295      return -1;
296    }
297
298  SPIplan = SPI_prepare(sql, 0, NULL);
299  if (SPIplan  == NULL)
300    {
301      elog(ERROR, "shortest_path_astar: couldn't create query plan via SPI");
302      return -1;
303    }
304
305  if ((SPIportal = SPI_cursor_open(NULL, SPIplan, NULL, NULL, true)) == NULL)
306    {
307      elog(ERROR, "shortest_path_astar: SPI_cursor_open('%s') returns NULL",
308           sql);
309      return -1;
310    }
311
312  while (moredata == TRUE)
313    {
314      SPI_cursor_fetch(SPIportal, TRUE, TUPLIMIT);
315
316      if (edge_columns.id == -1)
317        {
318          if (fetch_edge_astar_columns(SPI_tuptable, &edge_columns,
319                                       has_reverse_cost) == -1)
320            return finish(SPIcode, ret);
321        }
322
323      ntuples = SPI_processed;
324      total_tuples += ntuples;
325      if (!edges)
326        edges = palloc(total_tuples * sizeof(edge_astar_t));
327      else
328        edges = repalloc(edges, total_tuples * sizeof(edge_astar_t));
329
330      if (edges == NULL)
331        {
332          elog(ERROR, "Out of memory");
333          return finish(SPIcode, ret);
334        }
335
336      if (ntuples > 0)
337        {
338          int t;
339          SPITupleTable *tuptable = SPI_tuptable;
340          TupleDesc tupdesc = SPI_tuptable->tupdesc;
341         
342          for (t = 0; t < ntuples; t++)
343            {
344              HeapTuple tuple = tuptable->vals[t];
345              fetch_edge_astar(&tuple, &tupdesc, &edge_columns,
346                               &edges[total_tuples - ntuples + t]);
347            }
348          SPI_freetuptable(tuptable);
349        }
350      else
351        {
352          moredata = FALSE;
353        }
354    }
355   
356  //defining min and max vertex id
357     
358  DBG("Total %i tuples", total_tuples);
359   
360  for(z=0; z<total_tuples; z++)
361  {
362    if(edges[z].source<v_min_id)
363    v_min_id=edges[z].source;
364 
365    if(edges[z].source>v_max_id)
366      v_max_id=edges[z].source;
367                                   
368    if(edges[z].target<v_min_id)
369      v_min_id=edges[z].target;
370
371    if(edges[z].target>v_max_id)
372      v_max_id=edges[z].target;     
373                                                                       
374    DBG("%i <-> %i", v_min_id, v_max_id);
375                                                               
376  }
377
378  //:::::::::::::::::::::::::::::::::::: 
379  //:: reducing vertex id (renumbering)
380  //::::::::::::::::::::::::::::::::::::
381  for(z=0; z<total_tuples; z++)
382  {
383
384    //check if edges[] contains source and target
385    if(edges[z].source == source_vertex_id ||
386       edges[z].target == source_vertex_id)
387      ++s_count;
388    if(edges[z].source == target_vertex_id ||
389       edges[z].target == target_vertex_id)
390      ++t_count;
391
392    edges[z].source-=v_min_id;
393    edges[z].target-=v_min_id;
394    DBG("%i - %i", edges[z].source, edges[z].target);
395                                                               
396
397  }
398   
399  DBG("Total %i tuples", total_tuples);
400
401  if(s_count == 0)
402  {
403    elog(ERROR, "Start vertex was not found.");
404    return -1;
405  }
406             
407  if(t_count == 0)
408  {
409    elog(ERROR, "Target vertex was not found.");
410    return -1;
411  }
412                           
413  DBG("Total %i tuples", total_tuples);
414
415  profstop("extract", prof_extract);
416  profstart(prof_astar);
417 
418  DBG("Calling boost_astar <%i>\n", total_tuples);
419   
420  ret = boost_astar(edges, total_tuples, source_vertex_id-v_min_id,
421                    target_vertex_id-v_min_id,
422                    directed, has_reverse_cost,
423                    path, path_count, &err_msg);
424
425  DBG("SIZE %i\n",*path_count);
426
427  DBG("ret =  %i\n",ret);
428 
429  //::::::::::::::::::::::::::::::::
430  //:: restoring original vertex id
431  //::::::::::::::::::::::::::::::::
432  for(z=0;z<*path_count;z++)
433  {
434    //DBG("vetex %i\n",(*path)[z].vertex_id);
435    (*path)[z].vertex_id+=v_min_id;
436  } 
437
438  profstop("astar", prof_astar);
439  profstart(prof_store);
440
441  if (ret < 0)
442    {
443      //elog(ERROR, "Error computing path: %s", err_msg);
444      ereport(ERROR, (errcode(ERRCODE_E_R_E_CONTAINING_SQL_NOT_PERMITTED),
445        errmsg("Error computing path: %s", err_msg)));
446    }
447  return finish(SPIcode, ret);
448}
449
450
451PG_FUNCTION_INFO_V1(shortest_path_astar);
452Datum
453shortest_path_astar(PG_FUNCTION_ARGS)
454{
455  FuncCallContext     *funcctx;
456  int                  call_cntr;
457  int                  max_calls;
458  TupleDesc            tuple_desc;
459  path_element_t      *path;
460 
461  /* stuff done only on the first call of the function */
462  if (SRF_IS_FIRSTCALL())
463    {
464      MemoryContext   oldcontext;
465      int path_count = 0;
466      int ret;
467
468      // XXX profiling messages are not thread safe
469      profstart(prof_total);
470      profstart(prof_extract);
471     
472      /* create a function context for cross-call persistence */
473      funcctx = SRF_FIRSTCALL_INIT();
474     
475      /* switch to memory context appropriate for multiple function calls */
476      oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
477
478
479      ret = compute_shortest_path_astar(text2char(PG_GETARG_TEXT_P(0)),
480                                        PG_GETARG_INT32(1),
481                                        PG_GETARG_INT32(2),
482                                        PG_GETARG_BOOL(3),
483                                        PG_GETARG_BOOL(4),
484                                        &path, &path_count);
485
486#ifdef DEBUG
487      DBG("Ret is %i", ret);
488      if (ret >= 0)
489        {
490          int i;
491          for (i = 0; i < path_count; i++)
492            {
493              DBG("Step # %i vertex_id  %i ", i, path[i].vertex_id);
494              DBG("        edge_id    %i ", path[i].edge_id);
495              DBG("        cost       %f ", path[i].cost);
496            }
497        }
498#endif
499
500      /* total number of tuples to be returned */
501      DBG("Conting tuples number\n");
502      funcctx->max_calls = path_count;
503      funcctx->user_fctx = path;
504     
505      DBG("Path count %i", path_count);
506     
507      funcctx->tuple_desc =
508        BlessTupleDesc(RelationNameGetTupleDesc("path_result"));
509
510      MemoryContextSwitchTo(oldcontext);
511    }
512
513  /* stuff done on every call of the function */
514  DBG("Strange stuff doing\n");
515
516  funcctx = SRF_PERCALL_SETUP();
517 
518  call_cntr = funcctx->call_cntr;
519  max_calls = funcctx->max_calls;
520  tuple_desc = funcctx->tuple_desc;
521  path = (path_element_t*) funcctx->user_fctx;
522 
523  DBG("Trying to allocate some memory\n");
524
525  if (call_cntr < max_calls)    /* do when there is more left to send */
526    {
527      HeapTuple    tuple;
528      Datum        result;
529      Datum *values;
530      char* nulls;
531     
532      DBG("Before allocation\n");
533
534      values = palloc(3 * sizeof(Datum));
535      nulls = palloc(3 * sizeof(char));
536     
537      DBG("After allocation\n");
538     
539      values[0] = Int32GetDatum(path[call_cntr].vertex_id);
540      nulls[0] = ' ';
541      values[1] = Int32GetDatum(path[call_cntr].edge_id);
542      nulls[1] = ' ';
543      values[2] = Float8GetDatum(path[call_cntr].cost);
544      nulls[2] = ' ';
545
546      DBG("Heap making\n");
547     
548      tuple = heap_formtuple(tuple_desc, values, nulls);
549     
550      DBG("Datum making\n");
551     
552      /* make the tuple into a datum */
553      result = HeapTupleGetDatum(tuple);
554     
555
556      DBG("Trying to free some memory\n");
557   
558      /* clean up (this is not really necessary) */
559      pfree(values);
560      pfree(nulls);
561     
562      SRF_RETURN_NEXT(funcctx, result);
563    }
564  else    /* do when there is no more left */
565    {
566      profstop("store", prof_store);
567      profstop("total", prof_total);
568#ifdef PROFILE
569      elog(NOTICE, "_________");
570#endif
571      SRF_RETURN_DONE(funcctx);
572    }
573}
Note: See TracBrowser for help on using the browser.