root/tags/release-1.02/core/src/shooting_star.c

Revision 162, 14.7 KB (checked in by anton, 3 years ago)

1.02 release tag added

Line 
1/*
2 * Shooting* Shortest path algorithm for PostgreSQL
3 *
4 * Copyright (c) 2007 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 <string.h>
31#include <time.h>
32
33#include "shooting_star.h"
34
35//-------------------------------------------------------------------------
36
37Datum shortest_path_shooting_star(PG_FUNCTION_ARGS);
38
39#undef DEBUG
40//#define DEBUG 1
41
42#ifdef DEBUG
43#define DBG(format, arg...)                     \
44    elog(NOTICE, format , ## arg)
45#else
46#define DBG(format, arg...) do { ; } while (0)
47#endif
48
49// The number of tuples to fetch from the SPI cursor at each iteration
50#define TUPLIMIT 1000
51
52static char *
53text2char(text *in)
54{
55  char *out = palloc(VARSIZE(in));
56
57  memcpy(out, VARDATA(in), VARSIZE(in) - VARHDRSZ);
58  out[VARSIZE(in) - VARHDRSZ] = '\0';
59  return out;
60}
61
62static int
63finish(int code, int ret)
64{
65  code = SPI_finish();
66  if (code  != SPI_OK_FINISH )
67    {
68      elog(ERROR,"couldn't disconnect from SPI");
69      return -1 ;
70    }
71 
72  return ret;
73}
74 
75typedef struct edge_shooting_star_columns
76{
77  int id;
78  int source;
79  int target;
80  int cost;
81  int reverse_cost;
82  int s_x;
83  int s_y;
84  int t_x;
85  int t_y;
86  int to_cost;//cost of transit to adjacent edge
87  int rule;
88} edge_shooting_star_columns_t;
89
90static int
91fetch_edge_shooting_star_columns(SPITupleTable *tuptable,
92                         edge_shooting_star_columns_t *edge_columns,
93                         bool has_reverse_cost)
94{
95  edge_columns->id = SPI_fnumber(SPI_tuptable->tupdesc, "id");
96  edge_columns->source = SPI_fnumber(SPI_tuptable->tupdesc, "source");
97  edge_columns->target = SPI_fnumber(SPI_tuptable->tupdesc, "target");
98  edge_columns->cost = SPI_fnumber(SPI_tuptable->tupdesc, "cost");
99  if (edge_columns->id == SPI_ERROR_NOATTRIBUTE ||
100      edge_columns->source == SPI_ERROR_NOATTRIBUTE ||
101      edge_columns->target == SPI_ERROR_NOATTRIBUTE ||
102      edge_columns->cost == SPI_ERROR_NOATTRIBUTE)
103    {
104      elog(ERROR, "Error, query must return columns "
105           "'id', 'source', 'target' and 'cost'");
106      return -1;
107    }
108
109  if (SPI_gettypeid(SPI_tuptable->tupdesc,
110                    edge_columns->source) != INT4OID ||
111      SPI_gettypeid(SPI_tuptable->tupdesc,
112                    edge_columns->target) != INT4OID ||
113      SPI_gettypeid(SPI_tuptable->tupdesc, edge_columns->cost) != FLOAT8OID)
114    {
115      elog(ERROR, "Error, columns 'source', 'target' must be of type int4, "
116           "'cost' must be of type float8");
117      return -1;
118    }
119
120  DBG("columns: id %i source %i target %i cost %i",
121      edge_columns->id, edge_columns->source,
122      edge_columns->target, edge_columns->cost);
123
124  if (has_reverse_cost)
125    {
126      edge_columns->reverse_cost = SPI_fnumber(SPI_tuptable->tupdesc,
127                                               "reverse_cost");
128
129      if (edge_columns->reverse_cost == SPI_ERROR_NOATTRIBUTE)
130        {
131          elog(ERROR, "Error, reverse_cost is used, but query did't return "
132               "'reverse_cost' column");
133          return -1;
134        }
135
136      if (SPI_gettypeid(SPI_tuptable->tupdesc,
137                        edge_columns->reverse_cost) != FLOAT8OID)
138        {
139          elog(ERROR, "Error, columns 'reverse_cost' must be of type float8");
140          return -1;
141        }
142
143      DBG("columns: reverse_cost cost %i", edge_columns->reverse_cost);
144    }
145
146  edge_columns->s_x = SPI_fnumber(SPI_tuptable->tupdesc, "x1");
147  edge_columns->s_y = SPI_fnumber(SPI_tuptable->tupdesc, "y1");
148  edge_columns->t_x = SPI_fnumber(SPI_tuptable->tupdesc, "x2");
149  edge_columns->t_y = SPI_fnumber(SPI_tuptable->tupdesc, "y2");
150
151  if (edge_columns->s_x == SPI_ERROR_NOATTRIBUTE ||
152      edge_columns->s_y == SPI_ERROR_NOATTRIBUTE ||
153      edge_columns->t_x == SPI_ERROR_NOATTRIBUTE ||
154      edge_columns->t_y == SPI_ERROR_NOATTRIBUTE)
155    {
156      elog(ERROR, "Error, query must return columns "
157           "'x1', 'x2', 'y1' and 'y2'");
158      return -1;
159    }
160
161  DBG("columns: x1 %i y1 %i x2 %i y2 %i",
162      edge_columns->s_x, edge_columns->s_y,
163      edge_columns->t_x,edge_columns->t_y);
164   
165
166  edge_columns->to_cost = SPI_fnumber(SPI_tuptable->tupdesc, "to_cost");
167  edge_columns->rule = SPI_fnumber(SPI_tuptable->tupdesc, "rule");
168
169  if (edge_columns->to_cost == SPI_ERROR_NOATTRIBUTE ||
170      edge_columns->rule == SPI_ERROR_NOATTRIBUTE)
171    {
172      elog(ERROR, "Error, query must return columns "
173           "'to_cost' and 'rule'");
174      return -1;
175    }
176
177  return 0;
178}
179
180//edges should be ordered by id or else we have to search for
181//existing edges every time we want to add adjacent edge
182static void
183fetch_edge_shooting_star(HeapTuple *tuple, TupleDesc *tupdesc,
184                 edge_shooting_star_columns_t *edge_columns,
185                 edge_shooting_star_t *target_edge)
186{
187  Datum binval;
188  bool isnull;
189  int t;
190
191  for(t=0; t<MAX_RULE_LENGTH;++t)
192    target_edge->rule[t] = -1;
193   
194  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->id, &isnull);
195  if (isnull)
196    elog(ERROR, "id contains a null value");
197  target_edge->id = DatumGetInt32(binval);
198 
199  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->source, &isnull);
200  if (isnull)
201    elog(ERROR, "source contains a null value");
202  target_edge->source = DatumGetInt32(binval);
203
204  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->target, &isnull);
205  if (isnull)
206    elog(ERROR, "target contains a null value");
207  target_edge->target = DatumGetInt32(binval);
208
209  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->cost, &isnull);
210  if (isnull)
211    elog(ERROR, "cost contains a null value");
212  target_edge->cost = DatumGetFloat8(binval);
213
214  if (edge_columns->reverse_cost != -1)
215    {
216      binval = SPI_getbinval(*tuple, *tupdesc,
217                             edge_columns->reverse_cost, &isnull);
218      if (isnull)
219        elog(ERROR, "reverse_cost contains a null value");
220      target_edge->reverse_cost =  DatumGetFloat8(binval);
221    }
222
223  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->s_x, &isnull);
224  if (isnull)
225    elog(ERROR, "source x contains a null value");
226  target_edge->s_x = DatumGetFloat8(binval);
227
228  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->s_y, &isnull);
229  if (isnull)
230    elog(ERROR, "source y contains a null value");
231  target_edge->s_y = DatumGetFloat8(binval);
232 
233  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->t_x, &isnull);
234  if (isnull)
235    elog(ERROR, "target x contains a null value");
236  target_edge->t_x = DatumGetFloat8(binval);
237 
238  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->t_y, &isnull);
239  if (isnull)
240    elog(ERROR, "target y contains a null value");
241  target_edge->t_y = DatumGetFloat8(binval);
242
243  binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->to_cost, &isnull);
244  if (isnull)
245    target_edge->to_cost = 0;
246   
247  else
248    target_edge->to_cost = DatumGetFloat8(binval);
249
250  char *str = DatumGetCString(SPI_getvalue(*tuple, *tupdesc, edge_columns->rule));
251
252  if(str!=NULL)
253  {
254    char* pch = NULL;
255    int ci = MAX_RULE_LENGTH;
256
257    pch = (char *)strtok (str," ,");
258 
259    while (pch != NULL)
260    {
261      --ci;
262      target_edge->rule[ci] = atoi(pch);
263      pch = (char *)strtok (NULL, " ,");
264    }
265  }
266}
267
268
269static int compute_shortest_path_shooting_star(char* sql, int source_edge_id,
270                                       int target_edge_id, bool directed,
271                                       bool has_reverse_cost,
272                                       path_element_t **path, int *path_count)
273{
274 
275  int SPIcode;
276  void *SPIplan;
277  Portal SPIportal;
278  bool moredata = TRUE;
279  int ntuples;
280  edge_shooting_star_t *edges = NULL;
281  int total_tuples = 0;
282 
283//  int v_max_id=0;
284//  int v_min_id=INT_MAX; 
285  int e_max_id=0;
286  int e_min_id=INT_MAX; 
287   
288  edge_shooting_star_columns_t edge_columns = {id: -1, source: -1, target: -1,
289                                       cost: -1, reverse_cost: -1,
290                                       s_x: -1, s_y: -1, t_x: -1, t_y: -1,
291                                       to_cost: -1, rule: -1};
292  char *err_msg;
293  int ret = -1;
294  register int z, t;
295 
296  int s_count=0;
297  int t_count=0;
298 
299  DBG("start shortest_path_shooting_star\n");
300       
301  SPIcode = SPI_connect();
302  if (SPIcode  != SPI_OK_CONNECT)
303    {
304      elog(ERROR, "shortest_path_shooting_star: couldn't open a connection to SPI");
305      return -1;
306    }
307
308  SPIplan = SPI_prepare(sql, 0, NULL);
309  if (SPIplan  == NULL)
310    {
311      elog(ERROR, "shortest_path_shooting_star: couldn't create query plan via SPI");
312      return -1;
313    }
314
315  if ((SPIportal = SPI_cursor_open(NULL, SPIplan, NULL, NULL, true)) == NULL)
316    {
317      elog(ERROR, "shortest_path_shooting_star: SPI_cursor_open('%s') returns NULL",
318           sql);
319      return -1;
320    }
321
322  while (moredata == TRUE)
323    {
324      SPI_cursor_fetch(SPIportal, TRUE, TUPLIMIT);
325
326      if (edge_columns.id == -1)
327        {
328          if (fetch_edge_shooting_star_columns(SPI_tuptable, &edge_columns,
329                                       has_reverse_cost) == -1)
330            return finish(SPIcode, ret);
331        }
332       
333        //DBG("***%i***", ret);
334
335      ntuples = SPI_processed;
336      total_tuples += ntuples;
337
338      if (!edges)
339        edges = palloc(total_tuples * sizeof(edge_shooting_star_t));
340      else
341        edges = repalloc(edges, total_tuples * sizeof(edge_shooting_star_t));
342
343      if (edges == NULL)
344        {
345          elog(ERROR, "Out of memory");
346          return finish(SPIcode, ret);
347        }
348
349      if (ntuples > 0)
350        {
351          int t;
352          SPITupleTable *tuptable = SPI_tuptable;
353          TupleDesc tupdesc = SPI_tuptable->tupdesc;
354         
355          for (t = 0; t < ntuples; t++)
356            {
357              HeapTuple tuple = tuptable->vals[t];
358              fetch_edge_shooting_star(&tuple, &tupdesc, &edge_columns,
359                               &edges[total_tuples - ntuples + t]);
360            }
361          SPI_freetuptable(tuptable);
362        }
363      else
364        {
365          moredata = FALSE;
366        }
367    }
368   
369     
370  DBG("Total %i tuples", total_tuples);
371
372   
373
374  for(z=0; z<total_tuples; z++)
375  {
376    if(edges[z].id<e_min_id)
377      e_min_id=edges[z].id;
378
379    if(edges[z].id>e_max_id)
380      e_max_id=edges[z].id;
381
382  }
383
384    DBG("E : %i <-> %i", e_min_id, e_max_id);
385
386  for(z=0; z<total_tuples; ++z)
387  {
388
389    //check if edges[] contains source and target
390    if(edges[z].id == source_edge_id ||
391       edges[z].id == source_edge_id)
392      ++s_count;
393    if(edges[z].id == target_edge_id ||
394       edges[z].id == target_edge_id)
395      ++t_count;
396
397
398    //edges[z].source-=v_min_id;
399    //edges[z].target-=v_min_id;
400   
401  }
402   
403  DBG("Total %i tuples", total_tuples);
404
405  if(s_count == 0)
406  {
407    elog(ERROR, "Start edge was not found.");
408    return -1;
409  }
410             
411  if(t_count == 0)
412  {
413    elog(ERROR, "Target edge was not found.");
414    return -1;
415  }
416                           
417  DBG("Total %i tuples", total_tuples);
418
419  DBG("Calling boost_shooting_star <%i>\n", total_tuples);
420
421  //time_t stime = time(NULL);   
422
423  ret = boost_shooting_star(edges, total_tuples, source_edge_id,
424                    target_edge_id,
425                    directed, has_reverse_cost,
426                    path, path_count, &err_msg, e_max_id);
427
428  //time_t etime = time(NULL);   
429
430  //DBG("Path was calculated in %f seconds. \n", difftime(etime, stime));
431
432  DBG("SIZE %i\n",*path_count);
433
434  DBG("ret =  %i\n",ret);
435 
436
437  if (ret < 0)
438    {
439      ereport(ERROR, (errcode(ERRCODE_E_R_E_CONTAINING_SQL_NOT_PERMITTED),
440        errmsg("Error computing path: %s", err_msg)));
441    }
442  return finish(SPIcode, ret);
443}
444
445
446PG_FUNCTION_INFO_V1(shortest_path_shooting_star);
447Datum
448shortest_path_shooting_star(PG_FUNCTION_ARGS)
449{
450  FuncCallContext     *funcctx;
451  int                  call_cntr;
452  int                  max_calls;
453  TupleDesc            tuple_desc;
454  path_element_t      *path;
455 
456  /* stuff done only on the first call of the function */
457  if (SRF_IS_FIRSTCALL())
458    {
459      MemoryContext   oldcontext;
460      int path_count = 0;
461      int ret;
462
463      /* create a function context for cross-call persistence */
464      funcctx = SRF_FIRSTCALL_INIT();
465     
466      /* switch to memory context appropriate for multiple function calls */
467      oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
468
469
470      ret = compute_shortest_path_shooting_star(text2char(PG_GETARG_TEXT_P(0)),
471                                        PG_GETARG_INT32(1),
472                                        PG_GETARG_INT32(2),
473                                        PG_GETARG_BOOL(3),
474                                        PG_GETARG_BOOL(4),
475                                        &path, &path_count);
476
477#ifdef DEBUG
478      DBG("Ret is %i", ret);
479      if (ret >= 0)
480        {
481          int i;
482          for (i = 0; i < path_count; i++)
483            {
484              DBG("Step # %i vertex_id  %i ", i, path[i].vertex_id);
485              DBG("        edge_id    %i ", path[i].edge_id);
486              DBG("        cost       %f ", path[i].cost);
487            }
488        }
489#endif
490
491      /* total number of tuples to be returned */
492      DBG("Conting tuples number\n");
493      funcctx->max_calls = path_count;
494      funcctx->user_fctx = path;
495     
496      DBG("Path count %i", path_count);
497     
498      funcctx->tuple_desc =
499        BlessTupleDesc(RelationNameGetTupleDesc("path_result"));
500
501      MemoryContextSwitchTo(oldcontext);
502    }
503
504  /* stuff done on every call of the function */
505
506  funcctx = SRF_PERCALL_SETUP();
507 
508  call_cntr = funcctx->call_cntr;
509  max_calls = funcctx->max_calls;
510  tuple_desc = funcctx->tuple_desc;
511  path = (path_element_t*) funcctx->user_fctx;
512 
513  DBG("Trying to allocate some memory\n");
514
515  if (call_cntr < max_calls)    /* do when there is more left to send */
516    {
517      HeapTuple    tuple;
518      Datum        result;
519      Datum *values;
520      char* nulls;
521     
522      /* This will work for some compilers. If it crashes with segfault, try to change the following block with this one   
523 
524      values = palloc(4 * sizeof(Datum));
525      nulls = palloc(4 * sizeof(char));
526 
527      values[0] = call_cntr;
528      nulls[0] = ' ';
529      values[1] = Int32GetDatum(path[call_cntr].vertex_id);
530      nulls[1] = ' ';
531      values[2] = Int32GetDatum(path[call_cntr].edge_id);
532      nulls[2] = ' ';
533      values[3] = Float8GetDatum(path[call_cntr].cost);
534      nulls[3] = ' ';
535      */
536                   
537      values = palloc(3 * sizeof(Datum));
538      nulls = palloc(3 * sizeof(char));
539
540      values[0] = Int32GetDatum(path[call_cntr].vertex_id);
541      nulls[0] = ' ';
542      values[1] = Int32GetDatum(path[call_cntr].edge_id);
543      nulls[1] = ' ';
544      values[2] = Float8GetDatum(path[call_cntr].cost);
545      nulls[2] = ' ';
546                                                     
547      tuple = heap_formtuple(tuple_desc, values, nulls);
548     
549      /* make the tuple into a datum */
550      result = HeapTupleGetDatum(tuple);
551     
552
553      /* clean up (this is not really necessary) */
554      pfree(values);
555      pfree(nulls);
556     
557      SRF_RETURN_NEXT(funcctx, result);
558    }
559  else    /* do when there is no more left */
560    {
561      SRF_RETURN_DONE(funcctx);
562    }
563}
Note: See TracBrowser for help on using the browser.