1 /*
2  *
3  * XASTIR, Amateur Station Tracking and Information Reporting
4  * Copyright (C) 2000-2019 The Xastir Group
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (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  * Look at the README for more information on the program.
21  */
22 
23 // include postgresql library for postgis support
24 #ifdef HAVE_POSTGIS
25   #include <libpq-fe.h>
26   // pg_type.h contains constants for OID values to use in paramTypes arrays
27   // in prepared queries.
28   #include <pg_type.h>
29 #endif // HAVE_POSTGIS
30 
31 // mysql error library for mysql error code constants
32 #ifdef HAVE_MYSQL
33   #include <my_global.h>
34   #include <my_sys.h>
35   #include <mysql.h>
36   #include <errmsg.h>
37   #include <time.h>
38 #endif // HAVE_MYSQL
39 
40 
41 #ifdef HAVE_CONFIG_H
42   #include "config.h"
43 #endif  // HAVE_CONFIG_H
44 
45 // Some systems don't have strtof
46 #ifndef HAVE_STRTOF
47   #define strtof(a,b) atof(a)
48 #endif
49 
50 #include "snprintf.h"
51 
52 #include <stdio.h>
53 #include <ctype.h>
54 #include <Xm/XmAll.h>
55 #include <stdlib.h>
56 #include <unistd.h>
57 #include <sys/types.h>
58 
59 #include "database.h"
60 #include "main.h"
61 #include "util.h"
62 #include "xastir.h"
63 #include "db_gis.h"
64 
65 #ifdef HAVE_DB
66 /* db_gis.c
67  *
68  * Functions supporting connections to databases, including GIS enabled
69  * databases that hold OpenGIS objects and can apply spatial indicies.
70  *
71  * XASTIR GIS database code is separated into three layers
72  *
73  *  1) Supporting XASTIR logic (ui elements, cad integration,
74  *     map drawing, etc).
75  *  2a) Generic db storage/retrieval code - wrappers for layer 3
76  *  2b) Connection management code
77  *  3a) DBMS specific db storage/retrieval code for spatial databases
78  *  3b) DBMS specific db storage/retrieval code for non-spatial databases
79  *
80  * Data structures in an underlying database can be considered as a fourth
81  * level.
82  *
83  *  Code for layers 2 and 3 is in this file.
84  *
85  *  Layer 2 functions should be extern and called from elsewhere to
86  *    perform spatial database operations.  Xastir shouldn't need to care
87  *    if an underlying database has spatial support or not for simple data.
88  *    Some functionality might require spatial object support and might be
89  *    included only if a spatial database is available.  Thus 3b code may only
90  *    support a subset of the 2a functions, while 3a code should support all
91  *    2a functions.
92  *    Layer 2a wrappers should take and return values in xastir coordinates,
93  *    and convert them to decimal degrees to pass on to layer 3.  Likewise
94  *    return values from layer 3 to layer 2a should be in decimal degrees,
95  *    limiting the number of different places at which the xastir/decimal
96  *    degree conversion code needs to be invoked.  This would not be true if
97  *    data are fed directly from decimal degree feeds into the database, so
98  *    there may also be a need for layer 2 functions that deal only with
99  *    latitude and longitude in decimal degrees.
100  *
101  *  Layer 3 functions should not be extern and should only be called
102  *    by layer 2 functions from within this file.
103  *    Layer 3 functions should take and return values in decimal degrees.
104  *    Xastir objects should be passed down into layer 3, as doing
105  *    so should make code easier to maintain (but harder to extend) than using
106  *    generic structures for transport of data between layers 2 and 3.
107  *    Passing a station struct from layer 2 to 3 makes layer 2 a very simple
108  *    wrapper, but requires new layer 3 code to write station data to a map
109  *    layer rather than to a DataRow (to, for example, prepare a layer of
110  *    temperature data at points for analysis and generation of a temperature
111  *    grid.)  [Using generic structures for transport would let the layer 3
112  *    code remain unchanged while layer 2 functions are added or extended, but
113  *    requires added maintainance to synchronise xastir structs, the generic
114  *    structs, and database structures.]
115  *
116  *  A spatially enabled database is expected to support OpenGIS spatial
117  *  objects and be able to apply spatial indicies to the data.  A
118  *  non-spatially enabled database is expected to hold coordinates using
119  *  separate fields for latitude and longitude.  Layer 3 functions that
120  *  interact with a spatial database will need to convert decimal degrees
121  *  to well known text (WKT, and perhaps also well known binary, WKB)
122  *  representations.  Layer 3 functions that interact with non-spatially
123  *  enabled databases can just pass raw latitudes and longitudes.
124  *
125  *  All spatial data are expected to be in WGS84 projection EPSG:4326
126  *
127  *  Support for five sets of underlying database schema elements is envisioned
128  *   - a very simple station at point and time table
129  *   - schema elements to support CAD objects with arbitrary associated data
130  *     tables.
131  *   - a schema capable of holding the full range of aprs data using spatial
132  *     elements (Points, Polygons, etc).
133  *   - full support for APRSWorld tables (using latitude and longitude fields
134  *     rather than spatial elements).
135  *   - arbitrary tables with schema discovery for arbitrary GIS databases
136  *     such as Tiger data.
137  *   The first three of these will require schema version awareness and will
138  *   produce compatability/database lifecycle issues.
139  *
140  *  Descriptions of how to make connections to databases are stored in
141  *  connection descriptors.  Connection descriptors describe the DBMS, whether
142  *  the database has/lacks spatial support, the schema type (simple,
143  *  simple+cad, xastir full, APRSWorld, etc for the database, and connection
144  *  parameters (server, user, database).  The layer 2/3 separation is intended
145  *  to allow functions (layer 2) to be called from within xastir (layer 1)
146  *  without the need to test which function to call for which dbms.  Some
147  *  functions may be schema specific, others may be able to use any of
148  *  several different schemas.  Connections can be opened from a database
149  *  descriptor, and more than one descriptor can point to the same database.
150  *  (Thus a single MySQL database may contain simple xastir tables, xastir
151  *  CAD object tables, and APRSWorld tables, but two different descriptors
152  *  would be used to define connections to talk to the APRSWorld tables and
153  *  the simple+cad tables within what MySQL considers one schema.  A given
154  *  version of xastir will expect a particular version or range of versions
155  *  for database schemas - an older version of xastir may expect fields that
156  *  no longer exist in a database created for a newer version of xastir and
157  *  vice versa.
158  *
159  *  Data selected from a spatial database might be brought into xastir as
160  *   stations just like an internet feed or findu fetch trail query, as
161  *   editable CAD objects, or as map layers.
162  */
163 
164 /******************* DATABASE SUPPORT IS EXPERIMENTAL ***********************/
165 /**************** CODE IN THIS FILE MAY CHANGE AT ANY TIME ******************/
166 // Layer 3 declarations
167 
168 // xastir_dbms_type is used in interface_gui.c to set up cb_items to populate
169 // database picklist.  Define and internationalise here.
170 char xastir_dbms_type[4][XASTIR_DB_DESCRIPTOR_MAX_SIZE+1] = {"","MySQL (lat/long)","Postgresql/Postgis","MySQL Spatial"} ;
171 // xastir_schema_type is used in interface_gui.c to set up cb_item to populate
172 // schema type picklist Sql_Database_schema_type_data. Define and internationalize here.
173 char xastir_schema_type[5][XASTIR_SCHEMA_DESCRIPTOR_MAX_SIZE+1] = {"","Xastir Simple","Xastir CAD","Xastir Full","APRSWorld"} ;
174 
175 const char *POSTGIS_TIMEFORMAT = "%Y-%m-%d %H:%M:%S%z";
176 const char *MYSQL_TIMEFORMAT = "%Y-%m-%d %H:%M:%S";
177 
178 /*
179 // store integer values for picklist items, but use localized strings on picklists
180 char xastir_dbms_type[3][XASTIR_DB_DESCRIPTOR_MAX_SIZE+1];   // array of xastir database type strings
181 xastir_snprintf(&xastir_dbms_type[DB_MYSQL][0],
182      XASTIR_DB_DESCRIPTOR_MAX_SIZE,
183      "%s",langcode("XADBMST001"));
184 xastir_snprintf(&xastir_dbms_type[DB_POSTGIS][0],
185      sizeof(&xastir_dbms_type[DB_POSTGIS][0]),
186      "%s", langcode("XADBMST002"));
187 xastir_snprintf(&xastir_dbms_type[DB_MYSQL_SPATIAL][0],
188      sizeof(&xastir_dbms_type[DB_MYSQL_SPATIAL][0]),
189      "%s",langcode("XADBMST003"));
190 
191 char xastir_schema_type[4][XASTIR_SCHEMA_DESCRIPTOR_MAX_SIZE+1];  // array of xastir schema type strings
192 xastir_snprintf(xastir_schema_type[XASTIR_SCHEMA_SIMPLE],
193      sizeof(xastir_schema_type[XASTIR_SCHEMA_SIMPLE][0]),
194       "%s",langcode ("XASCHEMA01"));
195 xastir_snprintf(xastir_schema_type[XASTIR_SCHEMA_CAD][0],
196      sizeof(xastir_schema_type[XASTIR_SCHEMA_CAD][0]),
197       "%s", langcode("XASCHEMA02"));
198 xastir_snprintf(xastir_schema_type[XASTIR_SCHEMA_COMPLEX][0],
199      sizeof(xastir_schema_type[XASTIR_SCHEMA_COMPLEX][0]),
200      "%s", langcode("XASCHEMA03"));
201 xastir_snprintf(xastir_schema_type[XASTIR_SCHEMA_APRSWORLD],
202      sizeof(xastir_schema_type[XASTIR_SCHEMA_APRSWORLD][0]),
203      "%s", langcode("XASCHEMA04"));
204 */
205 
206 #ifdef HAVE_SPATIAL_DB
207   #ifdef HAVE_POSTGIS
208     int storeStationToGisDbPostgis(Connection *aDbConnection, DataRow *aStation);
209     int storeCadToGisDbPostgis(Connection *aDbConnection, CADRow *aCadObject);
210     int storeStationSimplePointToGisDbPostgis(Connection *aDbConnection, DataRow *aStation);
211     int testXastirVersionPostgis(Connection *aDbConnection);
212     int getAllSimplePositionsPostgis(Connection *aDbConnection);
213     int getAllSimplePositionsPostgisInBoundingBox(Connection *aDbConnection, char* str_e_long, char* str_w_long, char* str_n_lat, char* str_s_lat);
214     //PGconn postgres_conn_struct[MAX_DB_CONNECTIONS];
215   #endif /* HAVE_POSTGIS*/
216   #ifdef HAVE_MYSQL_SPATIAL
217     int storeStationToGisDbMysql(Connection *aDbConnection, DataRow *aStation);
218     int storeCadToGisDbMysql(Connection *aDbConnection, CADRow *aCadObject);
219     int storeStationSimplePointToGisDbMysql(Connection *aDbConnection, DataRow *aStation);
220     int getAllSimplePositionsMysqlSpatial(Connection *aDbConnection);
221     int getAllCadFromGisDbMysql(Connection *aDbConnection);
222     int getAllSimplePositionsMysqlSpatialInBoundingBox(Connection *aDbConnection, char* str_e_long, char* str_w_long, char* str_n_lat, char* str_s_lat);
223   #endif /* HAVE_MYSQL_SPATIAL */
224 #endif /* HAVE_SPATIAL_DB */
225 //Connection connection_struc[MAX_DB_CONNECTIONS];
226 Connection connections[MAX_IFACE_DEVICES];
227 int connections_initialized = 0;
228 #ifdef HAVE_MYSQL
229   //MYSQL mysql_conn_struct, *mysql_connection = &mysql_conn_struct;
230   //MYSQL mcs[MAX_DB_CONNECTIONS];
231   Connection dbc_struct, *dbc = &dbc_struct;
232   int testXastirVersionMysql(Connection *aDbConnection);
233   int storeStationSimplePointToDbMysql(Connection *aDbConnection, DataRow *aStation);
234   int getAllSimplePositionsMysql(Connection *aDbConnection);
235   int getAllSimplePositionsMysqlInBoundingBox(Connection *aDbConnection, char *str_e_long, char *str_w_long, char *str_n_lat, char *str_s_lat);
236   int storeStationToDbMysql(Connection *aDbConnection, DataRow *aStation);
237   void mysql_interpret_error(int errorcode, Connection *aDbConnection);
238 #endif /* HAVE_MYSQL*/
239 
240 // Layer 2a: Generic GIS db storage code. ************************************
241 // Wrapper functions for actual DBMS specific actions
242 
243 #ifdef HAVE_SPATIAL_DB
244 
245 // ******** Functions that require spatialy enabled database support *********
246 
247 
248 
249 
250 
251 /* function storeStationToGisDb()
252  * Stores the information about a station and its most recent position
253  * to a spatial database.
254  * @param aDbConnection generic database connection to the database in
255  * which the station information is to be stored.
256  * @param aStation the station to store.
257  * @returns 0 on failure, 1 on success.  On failure, stores error message
258  * in connection.
259  */
storeStationToGisDb(Connection * aDbConnection,DataRow * aStation)260 int storeStationToGisDb(Connection *aDbConnection, DataRow *aStation)
261 {
262   int returnvalue = 0;
263   if (aDbConnection==NULL || aStation==NULL)
264   {
265     return returnvalue;
266   }
267   // This function is dbms agnostic, and hands the call off to a
268   // function for the relevant database type.  That function picks the
269   // relevant schema and either handles the query or passes it on to
270   // a function to handle that schema.
271 
272 
273 
274   switch (aDbConnection->type)
275   {
276 #ifdef HAVE_POSTGIS
277     case DB_POSTGIS :
278       returnvalue = storeStationToGisDbPostgis(aDbConnection, aStation);
279       break;
280 #endif /* HAVE_POSTGIS */
281 #ifdef HAVE_MYSQL_SPATIAL
282     case DB_MYSQL_SPATIAL :
283       returnvalue = storeStationToGisDbMysql(aDbConnection, aStation);
284       break;
285 #endif /* HAVE_MYSQL_SPATIAL */
286 #ifdef HAVE_MYSQL
287     case DB_MYSQL :
288       returnvalue = storeStationToDbMysql(aDbConnection, aStation);
289       break;
290 #endif /* HAVE_MYSQL*/
291   }
292   return returnvalue;
293 }
294 
295 
296 
297 
298 
299 
300 /* function storeCadToGisDb()
301  * Stores current data about objects (including CAD objects) and their
302  * most recent positions to a spatial database.  Objects are treated as
303  * points
304 */
storeCadToGisDb(Connection * aDbConnection,CADRow * aCadObject)305 int storeCadToGisDb(Connection *aDbConnection, CADRow *aCadObject)
306 {
307   int returnvalue = 0;
308   if (aDbConnection==NULL || aCadObject==NULL)
309   {
310     return returnvalue;
311   }
312 
313 
314   // check that connection has cad support in schema
315   return returnvalue;
316 }
317 
318 
319 
320 
321 
322 
323 /* function storeStationTrackToGisDb()
324  * Stores information about a station and track of all recieved positions from
325  * that station (including weather information if present) to a spatial
326  * database.
327  * @param aDbConnection generic database connection to the database in
328  * which the station information is to be stored.
329  * @param aStation the station to store.
330  * @returns 0 on failure, 1 on success.  On failure, stores error message
331  * in connection.
332  */
storeStationTrackToGisDb(Connection * aDbConnection,DataRow * aStation)333 int storeStationTrackToGisDb(Connection *aDbConnection, DataRow *aStation)
334 {
335   int returnvalue = 0;
336   if (aDbConnection==NULL || aStation==NULL)
337   {
338     return returnvalue;
339   }
340 
341   return returnvalue;
342 }
343 
344 
345 
346 
347 
348 
349 #endif /* HAVE_SPATIAL_DB */
350 
351 
352 
353 
354 
355 // ***** Functions that do not require spatialy enabled database support ******
356 // Include "Simple" in these function names.  They should only deal with point
357 // data, not polygons or complex spatial objects.  Station positions and times
358 // demarking implicit tracks should be ok.
359 
360 
361 
362 
363 
364 
365 /* function storeStationSimpleToGisDb()
366  * Stores basic information about a station and its most recent position
367  * to a spatial database. Stores only callsign, most recent position,
368  * and time.  Intended for testing and simple logging uses.
369  * Underlying table should have structure:
370  * create table simpleStation (
371  *    simpleStationId int primary key not null auto_increment
372  *    station varchar(9) not null,  // max_callsign
373  *    time date not null default now(),
374  *    position POINT  // or latitude float, longitude float for simple db.
375  * );
376  ****  or perhaps it should be an APRSWorld table??  ****
377  ****  or perhaps it should be an APRSWorld table, but with POINT when supported??  ****
378  *
379  * ********* generalize to lat/lon fields or position POINT. ******
380  * @param aDbConnection generic database connection to the database in
381  * which the station information is to be stored.
382  * @param aStation the station to store.
383  * @returns 0 on failure, 1 on success.  On failure, stores error message
384  * in connection.
385  */
storeStationSimpleToGisDb(Connection * aDbConnection,DataRow * aStation)386 int storeStationSimpleToGisDb(Connection *aDbConnection, DataRow *aStation)
387 {
388   int returnvalue = 0;
389   int triedDatabase = 0;
390   if (debug_level & 4096)
391   {
392     fprintf(stderr,"in storeStationSimpleToGisDb() ");
393   }
394   if (aDbConnection==NULL || aStation==NULL)
395   {
396     return returnvalue;
397   }
398   if (aStation->data_via == DATA_VIA_DATABASE)
399   {
400     if (debug_level & 4096)
401     {
402       fprintf(stderr,"skipping station heard from Database\n");
403     }
404     returnvalue = 1;
405     return returnvalue;
406   }
407   if (debug_level & 4096)
408   {
409     fprintf(stderr,"with connection->type: %d\n",aDbConnection->type);
410   }
411 
412   switch (aDbConnection->type)
413   {
414 #ifdef HAVE_POSTGIS
415     case DB_POSTGIS :
416       returnvalue = storeStationSimplePointToGisDbPostgis(aDbConnection, aStation);
417       triedDatabase++;
418       break;
419 #endif /* HAVE_POSTGIS */
420 #ifdef HAVE_MYSQL_SPATIAL
421     case DB_MYSQL_SPATIAL :
422       returnvalue = storeStationSimplePointToGisDbMysql(aDbConnection, aStation);
423       triedDatabase++;
424       break;
425 #endif /* HAVE_MYSQL_SPATIAL */
426 #ifdef HAVE_MYSQL
427     case DB_MYSQL :
428       returnvalue = storeStationSimplePointToDbMysql(aDbConnection, aStation);
429       triedDatabase++;
430       break;
431 #endif /* HAVE_MYSQL*/
432   }
433   if (triedDatabase==0)
434   {
435   }
436   return returnvalue;
437 }
438 
439 
440 
441 
442 
443 
444 /* function getAllSimplePositions()
445  * Given a database connection, return all simple station positions stored in
446  * that database.
447  */
getAllSimplePositions(Connection * aDbConnection)448 int getAllSimplePositions(Connection *aDbConnection)
449 {
450   int returnvalue = 0;
451   int triedDatabase = 0;
452   if (aDbConnection==NULL)
453   {
454     return returnvalue;
455   }
456   if (debug_level & 4096)
457   {
458     fprintf(stderr,"in getAllSimplePositions ");
459     fprintf(stderr,"with aDbConnection->type %d\n",aDbConnection->type);
460   }
461 
462   switch (aDbConnection->type)
463   {
464 #ifdef HAVE_POSTGIS
465     case DB_POSTGIS :
466       //fprintf(stderr,"connection [%p]\n",aDbConnection);
467       //fprintf(stderr,"connection->phandle [%p]\n",aDbConnection->phandle);
468       returnvalue = getAllSimplePositionsPostgis(aDbConnection);
469       triedDatabase++;
470       break;
471 #endif /* HAVE_POSTGIS */
472 #ifdef HAVE_MYSQL_SPATIAL
473     case DB_MYSQL_SPATIAL :
474       returnvalue = getAllSimplePositionsMysqlSpatial(aDbConnection);
475       triedDatabase++;
476       break;
477 #endif /* HAVE_MYSQL_SPATIAL */
478 #ifdef HAVE_MYSQL
479     case DB_MYSQL :
480       returnvalue = getAllSimplePositionsMysql(aDbConnection);
481       triedDatabase++;
482       break;
483 #endif /* HAVE_MYSQL*/
484   }
485   if (triedDatabase==0)
486   {
487   }
488   return returnvalue;
489 }
490 
491 
492 
493 
494 
495 /* function getAllSimplePositionsInBoundingBox()
496  * Given a database connection and a bounding box, return all simple station
497  * positions stored in that database that fall within the bounds of the box.
498  * Takes eastern, western, northern, and southern bounds of box in xastir
499  * coordinates.
500  */
getAllSimplePositionsInBoundingBox(Connection * aDbConnection,int east,int west,int north,int south)501 int getAllSimplePositionsInBoundingBox(Connection *aDbConnection, int east, int west, int north, int south)
502 {
503   int returnvalue = 0;
504   int triedDatabase = 0;
505   char str_e_long[11];
506   char str_n_lat[10];
507   char str_w_long[11];
508   char str_s_lat[10];
509   if (aDbConnection==NULL)
510   {
511     return returnvalue;
512   }
513   // convert from xastir coordinates to decimal degrees
514   convert_lon_l2s(east, str_e_long, sizeof(str_e_long), CONVERT_DEC_DEG);
515   convert_lat_l2s(north, str_n_lat, sizeof(str_n_lat), CONVERT_DEC_DEG);
516   convert_lon_l2s(west, str_w_long, sizeof(str_w_long), CONVERT_DEC_DEG);
517   convert_lat_l2s(south, str_s_lat, sizeof(str_s_lat), CONVERT_DEC_DEG);
518   switch (aDbConnection->type)
519   {
520 #ifdef HAVE_POSTGIS
521     case DB_POSTGIS :
522       returnvalue = getAllSimplePositionsPostgisInBoundingBox(aDbConnection,str_e_long,str_w_long,str_n_lat,str_s_lat);
523       triedDatabase++;
524       break;
525 #endif /* HAVE_POSTGIS */
526 #ifdef HAVE_MYSQL_SPATIAL
527     case DB_MYSQL_SPATIAL :
528       returnvalue = getAllSimplePositionsMysqlSpatialInBoundingBox(aDbConnection,str_e_long,str_w_long,str_n_lat,str_s_lat);
529       triedDatabase++;
530       break;
531 #endif /* HAVE_MYSQL_SPATIAL */
532 #ifdef HAVE_MYSQL
533     case DB_MYSQL :
534       returnvalue = getAllSimplePositionsMysqlInBoundingBox(aDbConnection,str_e_long,str_w_long,str_n_lat,str_s_lat);
535       triedDatabase++;
536       break;
537 #endif /* HAVE_MYSQL*/
538   }
539   if (triedDatabase==0)
540   {
541   }
542   return returnvalue;
543 }
544 
545 
546 // Layer 2b: Connection managment. *******************************************
547 /* It should be possible to maintain a list of an arbitrary number of defined
548  * data sources of different types, and to have an arbitrary number of
549  * connections to these data sources open at the same time.
550  *
551  * Some issues: How to handle login credentials for databases?  Request on
552  * connection?  How to perform multiple operations with the same datasource
553  * (e.g. logging to the database from feeds while querying CAD objects).
554  * Probably want to be able to store password, request password on connect,
555  * or use configuration file (e.g. my.ini) for password) - let user tune
556  * choices to environment.
557  *
558  * The existing interface code seems better suited to having a fixed number
559  * of interfaces with zero or one database connection associated with each
560  * interface than handing an arbitrary number of connections per interface.
561  */
562 
563 
564 
565 // simple testing hardcoded database connection testing function
566 // remove this function and call in main.c  when integration with
567 // interfaces is working.
568 // fill in password, uncomment, and uncomment code in main.c for
569 // simple database write test - writes station in n_first to simple mysql db
570 /*
571 ioparam simpleDbTest(void) {
572    ioparam test;
573    Connection conn;
574    int ok;
575    xastir_snprintf(test.device_name, sizeof(test.device_name), "Test Connection");
576    test.database_type = DB_MYSQL;
577    xastir_snprintf(test.device_host_name, sizeof(test.device_host_name), "localhost");
578    test.sp = 3306;
579    xastir_snprintf(test.database_username, sizeof(test.database_username), "xastir_test");
580    // hardcode a test password here for simple test
581    xastir_snprintf(test.device_host_pswd, sizeof(test.device_host_pswd), "hardcoded test password");
582    xastir_snprintf(test.database_schema, sizeof(test.database_schema), "xastir");
583    test.database_schema_type = XASTIR_SCHEMA_SIMPLE;
584    xastir_snprintf(test.database_unix_socket, sizeof(test.database_unix_socket), "/var/lib/mysql/mysql.sock");
585 
586    got_conn=openConnection(&test, conn);
587    ok = storeStationSimpleToGisDb(&conn, n_first);
588 
589    return test;
590 }
591 */
592 
593 
initConnections()594 int initConnections()
595 {
596   int x;
597   if (debug_level & 4096)
598   {
599     fprintf(stderr,"initConnections()\n");
600   }
601   for (x=0; x<MAX_IFACE_DEVICES; x++)
602   {
603     initAConnection(&connections[x], x);
604   }
605   if (debug_level & 4096)
606   {
607     for (x=0; x<MAX_IFACE_DEVICES; x++)
608     {
609 #ifdef HAVE_POSTGIS
610       fprintf(stderr,"Initialized connection %d [%p] type=%d phandle=[%p]\n",x,&connections[x],connections[x].type,connections[x].phandle);
611 #endif /* HAVE_POSTGIS */
612 #ifdef HAVE_MYSQL
613       fprintf(stderr,"Initialized connection %d mhandle=[%p]\n",x,&connections[x].mhandle);
614 #endif /* HAVE_MYSQL */
615     }
616   }
617   return 1;
618 }
619 
620 /* Function initAConnections()
621  * Given a connection structure, initialize the storage for the
622  * database connections, link the connection to the relevant
623  * interface, and set default values for other parameters.  */
initAConnection(Connection * connection,int x)624 int initAConnection(Connection *connection, int x)
625 {
626   int y;
627   connection->descriptor = &devices[x];
628   connection->type = 0;              // assign no type by default
629   connection->interface_number = x;  // so we can reference port_data[] from a connection
630   // without knowing the connection's position in
631   // connections[]
632   // malloc for the PGconn will cause segfault on trying to
633   // open the connection
634 #ifdef HAVE_POSTGIS
635   connection->phandle = (PGconn*)malloc(sizeof(PGconn*));
636 #endif /* HAVE_POSTGIS */
637 #ifdef HAVE_MYSQL
638   //connection->mhandle = (MYSQL)malloc(sizeof(MYSQL));
639   mysql_init(&connection->mhandle);
640 #endif /* HAVE_MYSQL */
641   for(y=0; y<MAX_CONNECTION_ERROR_MESSAGE; y++)
642   {
643     connection->errormessage[y]=' ';
644   }
645   connection->errormessage[MAX_CONNECTION_ERROR_MESSAGE-1]='\0';
646   if (debug_level & 4096)
647   {
648     fprintf(stderr,"initAConnection() [%d]\n",x);
649   }
650   return 1;
651 }
652 
653 /* Function openConnection()
654  * Opens the specified database connection.
655  * @param anIface a database connection description (host username etc).
656  * @param connection a generic database connection for which the
657  * appropriate MySQL or Postgresql connection handle will be used
658  * for the open connection on success.
659  * @returns 0 on any error, 1 for successful connection
660  * on connection failure, returns 0 and sets error message in
661  * the connection descriptor.
662  */
openConnection(ioparam * anIface,Connection * connection)663 int openConnection(ioparam *anIface, Connection *connection)
664 {
665   int returnvalue = 0;
666   int connection_made = 0;
667 #ifdef HAVE_POSTGIS
668   char connection_string[900];
669   int connected;   // status of connection polling loop
670   time_t start_time;
671   PGconn *postgres_connection;
672   PostgresPollingStatusType poll;
673 #endif /* HAVE_POSTGIS */
674 #ifdef HAVE_MYSQL
675   unsigned long client_flag = 0; // parameter used for mysql connection, is normally 0.
676   unsigned int port;  // port to make connection on
677 #endif /* HAVE_MYSQL */
678 
679 
680   if (anIface==NULL)
681   {
682     fprintf(stderr,"Null iface\n");
683     return returnvalue;
684   }
685   if (anIface==NULL || connection==NULL)
686   {
687     fprintf(stderr,"Null connection\n");
688     return returnvalue;
689   }
690   if (debug_level & 4096)
691   {
692     fprintf(stderr,"opening connection [%p] \n",connection);
693   }
694 //    #ifdef HAVE_MYSQL
695 //    switch (anIface->database_type) {
696 //        #ifdef HAVE_MYSQL_SPATIAL
697 //        case DB_MYSQL_SPATIAL :
698 //        #endif /* HAVE_MYSQL_SPATIAL */
699 //        #ifdef HAVE_MYSQL
700 //        case DB_MYSQL :
701 //        #endif /* HAVE_MYSQL */
702 //        // instantiate the MYSQL structure for the connection
703 //        //fprintf(stderr,"calling mysql_init\n");
704 //        //connection->mhandle = mysql_init(&connection->mhandle);
705 //        //fprintf(stderr,"called mysql_init\n");
706 //    }
707 //    #endif /* HAVE_MYSQL */
708   // clear any existing error message
709   xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), " ");
710   if (debug_level & 4096)
711   {
712     fprintf(stderr,"Entering openConnection with anIface [%p] and conn [%p]\n",anIface,connection);
713   }
714 
715   connection->type = anIface->database_type;
716   //connection->descriptor = anIface;
717 
718   if (connections_initialized == 0)
719   {
720     connections_initialized = initConnections();
721     connections_initialized = 1;
722   }
723 
724   // TODO: need some sort of connection listener to handle reconnection attempts when a connection fails...
725 
726   // try to open connection
727   if (!(anIface==NULL))
728   {
729     switch (anIface->database_type)
730     {
731 #ifdef HAVE_POSTGIS
732       case DB_POSTGIS :
733         if (debug_level & 4096)
734         {
735           fprintf(stderr,"Opening Connection to a Postgresql/Postgis database.\n");
736         }
737         // If type is postgis, connect to postgis database.
738         // build connection string from parameters
739         xastir_snprintf(connection_string, sizeof(connection_string), \
740                         "host=%s user=%s password=%s dbname=%s port=%d", \
741                         anIface->device_host_name, anIface->database_username, anIface->device_host_pswd, anIface->database_schema, anIface->sp);
742         // Use nonblocking connection (connectStart and connectPoll)
743         //connection->phandle = PQconnectStart(connection_string);
744         postgres_connection = PQconnectStart(connection_string);
745         //if (connection->phandle == NULL) {
746         if (postgres_connection == NULL)
747         {
748           xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), "Insufficient memory to open connection.");
749         }
750         else
751         {
752           connected = 0;
753           // can connect, run PQ_connect_poll loop
754           // Note: xastir needs to decide when to time out
755           start_time = sec_now();
756           statusline("Connecting to Postgresql database",1);
757           while ((connected==0) & (sec_now()<(start_time+30)))
758           {
759             // need to add a timer to polling loop
760             //poll = PQconnectPoll(connection->phandle);
761             poll = PQconnectPoll(postgres_connection);
762             if (poll == PGRES_POLLING_FAILED || poll == PGRES_POLLING_OK)
763             {
764               connected = 1;
765             }
766             // add connection status feedback here if desired
767           }
768           //if (PQstatus(connection->phandle)==CONNECTION_OK) {
769           if (PQstatus(postgres_connection)==CONNECTION_OK)
770           {
771             if (debug_level & 4096)
772             {
773               fprintf(stderr,"Connected to Postgresql database on %s\n",anIface->device_host_name);
774             }
775             // connection successfull
776             connection->phandle = postgres_connection;
777             connection->type=DB_POSTGIS;
778             //connection->descriptor = anIface;
779             xastir_snprintf(connection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, " ");
780             connection_made = 1;
781           }
782           else
783           {
784             // connection attempt failed
785             fprintf(stderr,"Failed to connect to Postgresql database on %s\n",anIface->device_host_name);
786             fprintf(stderr,"Postgres Error: %s\n", PQerrorMessage(postgres_connection));
787             xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), "Unable to make Postgresql connection %s. %s", PQerrorMessage(postgres_connection), connection_string);
788           }
789         }
790         break;
791 #endif /* HAVE_POSTGIS */
792 #ifdef HAVE_MYSQL_SPATIAL
793       case DB_MYSQL_SPATIAL :
794         // if type is mysql (=>4.1), connect to mysql database
795         if (debug_level & 4096)
796         {
797           fprintf(stderr,"Opening connection to a MySQL (spatial) database.\n");
798         }
799         if (&connection->mhandle == NULL)
800         {
801           // insufficient memory to initalize a new database handle
802           xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), "Insufficient memory to open connection.");
803         }
804         else
805         {
806           port = anIface->sp;
807           statusline("Connecting to MySQL database",1);
808           if (debug_level & 4096)
809           {
810             fprintf(stderr,"Opening connection to %s.\n",anIface->device_host_name);
811           }
812           mysql_real_connect(&connection->mhandle, anIface->device_host_name, anIface->database_username, anIface->device_host_pswd, anIface->database_schema, port, anIface->database_unix_socket, client_flag);
813           //MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long client_flag)
814           if (&connection->mhandle == NULL)
815           {
816             // unable to establish connection
817             xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), "Unable to establish connection: %s", mysql_error(&connection->mhandle));
818             fprintf(stderr,"Failed to connect to MySQL database on %s\n",anIface->device_host_name);
819             fprintf(stderr, "MySQL Error: %s", mysql_error(&connection->mhandle));
820           }
821           else
822           {
823 
824 // mysql_real_connect is coming back with non-null failed connection.
825 
826             // connected to database
827             // make sure error message for making connection is empty.
828             xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), " ");
829             xastir_snprintf(connection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, " ");
830 
831             // ping the server
832             if (mysql_ping(&connection->mhandle)==0)
833             {
834               fprintf(stderr,"mysql ping ok [0]\n");
835               connection_made = 1;
836               // store connection information
837               connection->type = DB_MYSQL_SPATIAL;
838               //connection->descriptor = anIface;
839               if (debug_level & 4096)
840               {
841                 fprintf(stderr,"Connected to MySQL database, connection stored\n");
842               }
843             }
844             else
845             {
846               fprintf(stderr,"mysql ping failed [1]\n");
847               fprintf(stderr,"Can't connect to MySQL database: Can't ping server.\n");
848               xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), "Unable to ping MySQL server.  Server may be down. Check connection paramenters.");
849             }
850           }
851         }
852         break;
853 #endif /* HAVE_MYSQL_SPATIAL */
854 #ifdef HAVE_MYSQL
855       case DB_MYSQL :
856         // if type is mysql (<4.1), connect to mysql database
857         if (debug_level & 4096)
858         {
859           fprintf(stderr,"Opening connection to a MySQL database.\n");
860         }
861         if (&connection->mhandle == NULL)
862         {
863           // insufficient memory to initalize a new database handle
864           xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), "Insufficient memory to open connection.");
865           fprintf(stderr,"Insufficient memory to open mysql connection [mysql_init(*MYSQL) returned null].\n");
866         }
867         else
868         {
869           client_flag = CLIENT_COMPRESS;
870           port = anIface->sp;
871 // **** fails if database_unix_socket doesn't exist
872           mysql_real_connect(&connection->mhandle, anIface->device_host_name, anIface->database_username, anIface->device_host_pswd, anIface->database_schema, port, anIface->database_unix_socket, client_flag);
873           if (&connection->mhandle == NULL)
874           {
875             fprintf(stderr,"Unable to establish connection to MySQL database\nHost: %s Schema: %s Username: %s\n",anIface->device_host_name, anIface->database_schema, anIface->database_username);
876             // unable to establish connection
877             xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), "Unable to establish MySQL connection. Host: %s Username: %s Password: %s Schema %s Port: %d", anIface->device_host_name, anIface->database_username, anIface->device_host_pswd, anIface->database_schema, port);
878             fprintf(stderr,"Failed to connect to MySQL database on %s\n",anIface->device_host_name);
879             fprintf(stderr, "MySQL Error: %s", mysql_error(&connection->mhandle));
880           }
881           else
882           {
883             fprintf(stderr,"Connected to MySQL database on %s\n",anIface->device_host_name);
884             // connected to database
885             // make sure error message for making connection is empty.
886             xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), " ");
887             xastir_snprintf(connection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, " ");
888 
889             // ping the server
890             if (mysql_ping(&connection->mhandle)==0)
891             {
892               fprintf(stderr,"mysql ping ok [0]\n");
893               connection_made = 1;
894               // store connection information
895               connection->type = DB_MYSQL;
896               //connection->descriptor = anIface;
897               if (debug_level & 4096)
898               {
899                 fprintf(stderr,"Connected to MySQL database, connection stored\n");
900               }
901             }
902             else
903             {
904               fprintf(stderr,"mysql ping failed [1]\n");
905               xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), "Unable to ping MySQL server.  Server may be down. Check connection paramenters.");
906               fprintf(stderr,"Can't connect to MySQL database: Can't ping server.\n");
907             }
908           }
909         }
910         break;
911 #endif /* HAVE_MYSQL*/
912     }  /* end switch */
913   }  /* end test for null interface */
914 
915   if (connection_made==1)
916   {
917     if (debug_level & 4096)
918     {
919       fprintf(stderr,"Connection made: ");
920       fprintf(stderr,"connection->type [%d]\n",connection->type);
921     }
922     if (testConnection((Connection*)connection)==True)
923     {
924       returnvalue = 1;
925       statusline("Connected to database",1);
926     }
927     else
928     {
929       statusline("Incompatable database schema",1);
930       fprintf(stderr,"Connection OK, but incompatable schema. [%s]\n",connection->errormessage);
931       xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), "%s",connection->errormessage);
932       closeConnection(connection,-1);
933       //free(connection);
934     }
935   }
936   else
937   {
938     // Detailed error message should have been returned above, but make sure
939     // there is at least a minimal failure message regardless of the problem.
940     statusline("Failed to connect to database",1);
941     fprintf(stderr,"Failed to make database connection.\n");
942     //free(connection);   // not pointing to the right thing ??
943     port_data[connection->interface_number].status = DEVICE_ERROR;
944   }
945   return returnvalue;
946 }
947 
948 
949 
950 
951 
952 /* Function closeConnection()
953  * Closes the specified database connection.
954  * @param aDbConnection a generic database connection handle.
955  */
closeConnection(Connection * aDbConnection,int port_number)956 int closeConnection(Connection *aDbConnection, int port_number)
957 {
958   //ioparam db =  aDbConnection->descriptor;
959   fprintf(stderr,"Closing connection on port %d\n",port_number);
960   if (aDbConnection==NULL)
961   {
962     return 0;
963   }
964   // free up connection resources
965   switch (aDbConnection->type)
966   {
967 #ifdef HAVE_POSTGIS
968     case DB_POSTGIS :
969       fprintf(stderr,"Connection type is postgis.\n");
970       // if type is postgis, close connection to postgis database
971       if (aDbConnection->phandle!=NULL)
972       {
973         if (port_data[port_number].status==DEVICE_UP)
974         {
975           PQfinish(aDbConnection->phandle);
976         }
977         //free(aDbConnection->phandle);
978       }
979       break;
980 #endif /* HAVE_POSTGIS */
981 #ifdef HAVE_MYSQL_SPATIAL
982     case DB_MYSQL_SPATIAL :
983       // if type is mysql, close connection to mysql database
984       if (&aDbConnection->mhandle!=NULL)
985       {
986         if (debug_level & 4096)
987         {
988           fprintf(stderr,"Connection type to close is mysql spatial.\n");
989           fprintf(stderr,"mysql_stat [%s]\n",mysql_stat(&aDbConnection->mhandle));
990         }
991         mysql_close(&aDbConnection->mhandle);
992         //free(aDbConnection->mhandle);
993       }
994       break;
995 #endif /* HAVE_MYSQL_SPATIAL */
996 #ifdef HAVE_MYSQL
997     case DB_MYSQL :
998       fprintf(stderr,"Connection type is mysql.\n");
999       // if type is mysql, close connection to mysql database
1000       if (&aDbConnection->mhandle!=NULL)
1001       {
1002         mysql_close(&aDbConnection->mhandle);
1003         //free(aDbConnection->mhandle);
1004       }
1005       break;
1006 #endif /* HAVE_MYSQL*/
1007   }
1008 
1009   return 1;
1010 }
1011 
1012 
1013 
1014 
1015 
1016 /* Tests a database connection to see if the server is responding.
1017  * @param aDbConnection pointer to a generic connection handle.
1018  * @returns 0 on any error, 1 for successful ping.
1019  */
pingConnection(Connection * aDbConnection)1020 int pingConnection(Connection *aDbConnection)
1021 {
1022   int returnvalue = True;
1023   int dbreturn;
1024 #ifdef HAVE_POSTGIS
1025   ConnStatusType psql_status;
1026 #endif /* HAVE_POSTGIS */
1027 
1028   if (aDbConnection==NULL)
1029   {
1030     return 0;
1031   }
1032 
1033   if (debug_level & 4096)
1034   {
1035     fprintf(stderr,"Pinging database server type=[%d]\n",aDbConnection->type);
1036     //} else {
1037     //fprintf(stderr,"Pinging database server.\n");
1038   }
1039 
1040   switch (aDbConnection->type)
1041   {
1042 #ifdef HAVE_POSTGIS
1043     case DB_POSTGIS:
1044       returnvalue = False;
1045       // is the connection open  [required]
1046       if (aDbConnection->phandle!=NULL)
1047       {
1048         psql_status = PQstatus(aDbConnection->phandle);
1049         if (psql_status!=CONNECTION_OK)
1050         {
1051           xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Postgresql connection failed");
1052           fprintf(stderr, "PQstatus returned CONNECTION_BAD, probably unable to connect to server.\n");
1053 
1054         }
1055         else
1056         {
1057           if (debug_level & 4096)
1058           {
1059             fprintf(stderr, "PQstatus returned CONNECTION_OK.\n");
1060           }
1061           returnvalue = True;
1062         }
1063       }
1064       break;
1065 #endif /* HAVE_POSTGIS */
1066 #ifdef HAVE_MYSQL_SPATIAL
1067     case DB_MYSQL_SPATIAL:
1068       returnvalue = False;
1069       // is the connection open  [required]
1070       if (&aDbConnection->mhandle!=NULL)
1071       {
1072         // can we ping the server [required]
1073         dbreturn = mysql_ping(&aDbConnection->mhandle);
1074         if (dbreturn>0)
1075         {
1076           mysql_interpret_error(dbreturn, aDbConnection);
1077           fprintf(stderr, "MySQL Ping failed, probably unable to connect to server.\n");
1078         }
1079         else
1080         {
1081           if (debug_level & 4096)
1082           {
1083             fprintf(stderr, "MySQL Ping OK.\n");
1084             fprintf(stderr,"mysql_stat [%s]\n",mysql_stat(&aDbConnection->mhandle));
1085           }
1086           returnvalue = True;
1087         }
1088       }
1089       break;
1090 #endif /* HAVE_MYSQL_SPATIAL */
1091 #ifdef HAVE_MYSQL
1092     case DB_MYSQL:
1093       // is the connection open  [required]
1094       if (&aDbConnection->mhandle != NULL)
1095       {
1096         dbreturn = mysql_ping(&aDbConnection->mhandle);
1097         if (dbreturn>0)
1098         {
1099           mysql_interpret_error(dbreturn, aDbConnection);
1100           fprintf(stderr, "MySQL Ping failed, probably unable to connect to server.\n");
1101         }
1102         else
1103         {
1104           if (debug_level & 4096)
1105           {
1106             fprintf(stderr, "MySQL Ping OK.\n");
1107             fprintf(stderr,"mysql_stat [%s]\n",mysql_stat(&aDbConnection->mhandle));
1108           }
1109           returnvalue = True;
1110         }
1111       }
1112       break;
1113 #endif /* HAVE_MYSQL*/
1114   }
1115   if (returnvalue==0)
1116   {
1117     fprintf(stderr,"\n[%s]\n",aDbConnection->errormessage);
1118     statusline("Database Ping Failed",1);
1119     port_data[aDbConnection->interface_number].status = DEVICE_ERROR;
1120   }
1121   return returnvalue;
1122 }
1123 
1124 
1125 
1126 
1127 
1128 /* Tests a database connection and the underlying schema to see
1129  * if the connection is open, the schema version is supported by
1130  * this version of the code, and to see what permissions are
1131  * available */
testConnection(Connection * aDbConnection)1132 int testConnection(Connection *aDbConnection)
1133 {
1134   int returnvalue = True;
1135   int dbreturn;
1136   int major_version;
1137   int minor_version;
1138   char warning[100];
1139 
1140 #ifdef HAVE_POSTGIS
1141   ConnStatusType psql_status;
1142   PGresult *result;
1143   const char *postgis_sql = "SELECT COUNT(*) FROM geometry_columns";  // test to see if schema used in connection has postgis support added
1144 #endif /* HAVE_POSTGIS */
1145   if (aDbConnection==NULL)
1146   {
1147     return 0;
1148   }
1149   xastir_snprintf(warning, 100, " ");  // make sure warning is empty
1150   switch (aDbConnection->type)
1151   {
1152 #ifdef HAVE_POSTGIS
1153     case DB_POSTGIS:
1154       returnvalue = False;
1155       // is the connection open  [required]
1156       if (aDbConnection->phandle!=NULL)
1157       {
1158         psql_status = PQstatus(aDbConnection->phandle);
1159         if (psql_status!=CONNECTION_OK)
1160         {
1161           xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Postgresql connection failed");
1162           fprintf(stderr, "PQstatus returned CONNECTION_BAD, probably unable to connect to server.\n");
1163 
1164         }
1165         else
1166         {
1167           fprintf(stderr, "PQstatus returned CONNECTION_OK.\n");
1168           // which version of postgresql are we running
1169           dbreturn = PQserverVersion(aDbConnection->phandle);
1170           major_version = dbreturn / 10000;
1171           minor_version =  (dbreturn - (major_version*10000)) / 100;
1172           fprintf(stderr,"Postgresql version [%d] %d.%d\n",dbreturn,major_version,minor_version);
1173 
1174           // is the database spatially enabled [required]
1175           result = PQexec(aDbConnection->phandle,postgis_sql);
1176           if (result==NULL)
1177           {
1178             // PQexec probably couldn't allocate memory for the result set.
1179             xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Null result: %s\n",PQerrorMessage(aDbConnection->phandle));
1180             fprintf(stderr, "testConnection() Null result\nPostgresql Error : %s\n",PQerrorMessage(aDbConnection->phandle));
1181           }
1182           else
1183           {
1184             // PQexec returned a result, but it may not be valid, check to see.
1185             if (PQresultStatus(result)==PGRES_COMMAND_OK || PQresultStatus(result)==PGRES_TUPLES_OK)
1186             {
1187               // PQexec returned a valid result set, meaning that a geometry_types table exists.
1188 
1189 
1190               // are the needed tables present [required]
1191               // check schema type (simple, simple+cad, full, aprsworld)
1192               // check version of database schema for compatability
1193               if (testXastirVersionPostgis(aDbConnection)==1)
1194               {
1195                 returnvalue = True;
1196               }
1197               // does the user have select privileges [required]
1198               // does the user have update privileges [optional]
1199               // does the user have inesrt privileges [optional]
1200               // does the user have delete privileges [optional]
1201             }
1202             else
1203             {
1204               // schema lacks a geometry_columns table, either schema or database lacks postgis support
1205               xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "No geometry_columns table found.  Is PostGIS installed and added to this schema?\n");
1206               xastir_snprintf(warning, 100, "No geometry_columns table found. PostGIS may not be installed, or the schema may not have PostGIS support added.");
1207               fprintf(stderr, "No geometry_columns table found.\nPostGIS may not be installed, or the schema may not have PostGIS support added.\n");
1208               fprintf(stderr, "Postgresql Error : %s\n",PQerrorMessage(aDbConnection->phandle));
1209             }
1210           }
1211         }
1212       }
1213       break;
1214 #endif /* HAVE_POSTGIS */
1215 #ifdef HAVE_MYSQL_SPATIAL
1216     case DB_MYSQL_SPATIAL:
1217       returnvalue = False;
1218       // is the connection open  [required]
1219       if (&aDbConnection->mhandle!=NULL)
1220       {
1221         // can we ping the server [required]
1222         dbreturn = mysql_ping(&aDbConnection->mhandle);
1223         if (dbreturn>0)
1224         {
1225           mysql_interpret_error(dbreturn, aDbConnection);
1226           fprintf(stderr,"Ping of mysql server failed.\n");
1227           xastir_snprintf(warning, 100, "%s",aDbConnection->errormessage);
1228 
1229         }
1230         else
1231         {
1232           if (debug_level & 4096)
1233           {
1234             fprintf(stderr,"mysql_stat [%s]\n",mysql_stat(&aDbConnection->mhandle));
1235           }
1236           // is the database spatially enabled [required]
1237           // determine from db version >= 4.2
1238           // MySQL 4.1 is past end of life, 4.2 at end of life but still in widespread use, e.g. RHEL4 (in early 2008).
1239           // mysql_server_version is new to mysql 4.1, prepared queries stabilized in 4.2
1240           dbreturn = mysql_get_server_version(&aDbConnection->mhandle);
1241           if (dbreturn>0)
1242           {
1243             major_version = dbreturn / 10000;
1244             minor_version =  (dbreturn - (major_version*10000)) / 100;
1245             if (major_version>=5 || (major_version==4 && minor_version >=2))
1246             {
1247               fprintf(stderr,"MySQL Server version %d.%d OK.\n",major_version,minor_version);
1248               // check version of database schema for compatability
1249               dbreturn = testXastirVersionMysql(aDbConnection);
1250               if (dbreturn==1)
1251               {
1252                 fprintf(stderr,"Compatible Xastir database version found on server.\n");
1253 
1254                 // are the needed tables present [required]
1255                 // check schema type (simple, simple+cad, full, aprsworld)
1256                 // does the user have select privileges [required]
1257                 // does the user have update privileges [optional]
1258                 // does the user have insert privileges [optional]
1259                 // does the user have delete privileges [optional]
1260                 returnvalue = True;
1261               }
1262               else
1263               {
1264                 fprintf(stderr,"Xastir database version on server is not compatable with this version of Xastir.\n");
1265                 // aDbConnection->errormessage should have been set in testXastirVersionMysql
1266                 xastir_snprintf(warning, 100, "%s",aDbConnection->errormessage);
1267               }
1268             }
1269             else
1270             {
1271               // version too low
1272               fprintf(stderr,"MySQL Server version %d.%d is too low and is not supported in Xastir.\n",major_version,minor_version);
1273               xastir_snprintf(warning, 100, "MySQL Server version %d.%d is too low and is not supported in Xastir.",major_version,minor_version);
1274             }
1275           }
1276           else
1277           {
1278             // ? mysql<4.1
1279           }
1280         }
1281       }
1282       break;
1283 #endif /* HAVE_MYSQL_SPATIAL */
1284 #ifdef HAVE_MYSQL
1285     case DB_MYSQL:
1286       // is the connection open  [required]
1287       if (&aDbConnection->mhandle != NULL)
1288       {
1289         dbreturn = mysql_ping(&aDbConnection->mhandle);
1290         if (dbreturn>0)
1291         {
1292           mysql_interpret_error(dbreturn, aDbConnection);
1293         }
1294         else
1295         {
1296           if (debug_level & 4096)
1297           {
1298             fprintf(stderr,"mysql_stat [%s]\n",mysql_stat(&aDbConnection->mhandle));
1299           }
1300           // is the database spatially enabled [optional]
1301           // determine from db version >= 4.1
1302 #ifdef HAVE_MYSQL_SPATIAL
1303           // mysql_server_version is new to mysql 4.1
1304           dbreturn = mysql_get_server_version(&aDbConnection->mhandle);
1305 #endif /* HAVE_MYSQL_SPATIAL */
1306           // are the needed tables present [required]
1307           // check schema type (simple, simple+cad, aprsworld)
1308           // full requires objects, not supported here.
1309           // check version of database schema for compatability
1310           dbreturn = testXastirVersionMysql(aDbConnection);
1311           // does the user have select privileges [required]
1312           // does the user have update privileges [optional]
1313           // does the user have insert privileges [optional]
1314           // does the user have delete privileges [optional]
1315         }
1316       }
1317       break;
1318 #endif /* HAVE_MYSQL*/
1319   }
1320   if (returnvalue==0)
1321   {
1322     fprintf(stderr,"\n[%s]\n",aDbConnection->errormessage);
1323     xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Incompatable schema: %s",warning);
1324     fprintf(stderr,"\n[%s]\n",aDbConnection->errormessage);
1325     // Note: Don't close connection here, we haven't handed the error to the user yet.
1326     //closeConnection(aDbConnection,-1);
1327     fprintf(stderr,"\n[%s]\n",aDbConnection->errormessage);
1328   }
1329   return returnvalue;
1330 }
1331 
1332 
1333 
1334 
1335 // Layer 3: DBMS specific db storage code *************************************
1336 // Functions in this section should be local to this file and not exported
1337 // Export functions in section 2a above.
1338 //
1339 // Layer 3a: DBMS specific GIS db storage code ********************************
1340 // Functions supporting queries to specific types of GIS enabled databasesa
1341 //
1342 
1343 #ifdef HAVE_SPATIAL_DB
1344 
1345 #ifdef HAVE_POSTGIS
1346 // Postgis implementation of spatial database functions
1347 
1348 
1349 
1350 
1351 
1352 /* postgresql+postgis implementation of storeStationToGisDb().  */
storeStationToGisDbPostgis(Connection * aDbConnection,DataRow * aStation)1353 int storeStationToGisDbPostgis(Connection *aDbConnection, DataRow *aStation)
1354 {
1355   int returnvalue = 0;
1356   //ioparam *device = aDbConnection->descriptor;
1357   // check type of schema to use (XASTIR simple, full or APRSWorld)
1358   switch (devices[aDbConnection->interface_number].database_schema_type)
1359   {
1360     case XASTIR_SCHEMA_SIMPLE :
1361       returnvalue = storeStationSimplePointToGisDbPostgis(aDbConnection,aStation);
1362       break;
1363     case XASTIR_SCHEMA_APRSWORLD :
1364       break;
1365     case XASTIR_SCHEMA_COMPLEX :
1366       break;
1367       // otherwise error message
1368   }
1369   return returnvalue;
1370 }
1371 
1372 
1373 
1374 
1375 
1376 /* postgresql+postgis implementation of storeCadToGisDb().  */
storeCadToGisDbPostgis(Connection * aDbConnection,CADRow * aCadObject)1377 int storeCadToGisDbPostgis(Connection *aDbConnection, CADRow *aCadObject)
1378 {
1379   int returnvalue = 0;
1380 
1381   return returnvalue;
1382 }
1383 
1384 
1385 
1386 
1387 
1388 /* function storeStationSimplePointToGisDbPostgis()
1389  * Postgresql/Postgis implementation of wrapper storeStationSimplePointToGisDb().
1390  * Should only be called through wrapper function.  Do not call directly.
1391  */
storeStationSimplePointToGisDbPostgis(Connection * aDbConnection,DataRow * aStation)1392 int storeStationSimplePointToGisDbPostgis(Connection *aDbConnection, DataRow *aStation)
1393 {
1394   int returnvalue = 0;  // Default return value is failure.
1395   int ok;  // Holds results of tests when building query.
1396   char wkt[MAX_WKT];  // well know text representation of latitude and longitude of point
1397   char timestring[101];  // string representation of the time heard or the current time
1398   char call_sign[(MAX_CALLSIGN*2)+1];  // temporary holding for escaped callsign
1399   char aprs_symbol[2];  // temporary holding for escaped aprs symbol
1400   char aprs_type[2];    // temporary holding for escaped aprs type
1401   char special_overlay[2];  // temporary holding for escaped overlay
1402   char origin[(MAX_CALLSIGN*2)+1]; // temporary holding for escaped origin
1403   char node_path[(NODE_PATH_SIZE*2)+1];  // temporary holding for escaped node_path
1404   char record_type[2];  // temporary holding for escaped record_type
1405   //PGconn *conn = aDbConnection->phandle;
1406   PGresult *prepared = NULL;
1407   PGresult *result = NULL;
1408   int count;  // returned value from count query
1409   const int PARAMETERS = 9;
1410   // parameter arrays for prepared query
1411   const char *paramValues[PARAMETERS];
1412   // To use native Postgres POINT for position instead of postgis geometry point.
1413   //const Oid paramTypes[6] = { VARCHAROID, TIMESTAMPTZOID, POINTOID, VARCHAROID, VARCHAROID, VARCHAROID };
1414   //const Oid paramTypes[6] = { 1043, 1184, 600, 1043, 1043, 1043 };
1415   // Native postgres (8.2) geometries don't have spatial support as rich as Postgis extensions.
1416   // use postgis geometry Point instead:
1417   // lookup OID for geometry:  select OID from pg_type where typname = 'geometry'; returns 19480
1418   //const Oid paramTypes[6] = { VARCHAROID, TIMESTAMPTZOID, 19480, VARCHAROID, VARCHAROID, VARCHAROID };
1419   //const Oid paramTypes[6] = { 1043, 1184, 19480, 1043, 1043, 1043 };
1420   // Value 18480 is probably installation specific, use unknownOID instead:
1421   //const Oid paramTypes[9] = { VARCHAROID, TIMESTAMPTZOID, UNKNOWNOID, VARCHAROID, VARCHAROID, VARCHAROID, VARCHAROID, VARCHAROID, VARCHAROID };
1422   const Oid paramTypes[9] = { 1043, 1184, 705, 1043, 1043, 1043, 1043, 1043, 1043 };
1423   const char *sql = "insert into simpleStation (station, transmit_time, position, symbol, overlay, aprstype, origin, record_type, node_path) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)";
1424   const char *StatementName = "InsertSimpleStation";
1425   const char *StatementExists = "select count(*) from pg_prepared_statements where name = 'InsertSimpleStation'";
1426 
1427 
1428   if (debug_level & 4096)
1429   {
1430     fprintf(stderr,"In postgres simple station insert\n");
1431     fprintf(stderr,"with connection [%p] \n",aDbConnection);
1432     fprintf(stderr,"connection->phandle [%p]\n",aDbConnection->phandle);
1433   }
1434   if (aDbConnection->phandle==NULL)
1435   {
1436     fprintf(stderr,"Trying to save station on null postgresql connection\n");
1437     return returnvalue;
1438   }
1439   if (PQserverVersion(aDbConnection->phandle)==0)
1440   {
1441     // no connection to server
1442     fprintf(stderr,"Trying to save station on closed postgresql connection\n");
1443     return returnvalue;
1444   }
1445   if (debug_level & 4096)
1446   {
1447     fprintf(stderr,"Postgresql version=%d\n",PQserverVersion(aDbConnection->phandle));
1448   }
1449 
1450   // Check to see if this prepared statement exists in the current session
1451   // and create it if it does not.
1452   // Query adds connection overhead - should probably track with a global variable,
1453   // and query/recreate statment only on failure.
1454   ok = 0;
1455   // pg_prepared_statements system view added in postgresql 8.2
1456   if (PQserverVersion(aDbConnection->phandle)>80199)
1457   {
1458     result = PQexec(aDbConnection->phandle, "select count(*) from pg_prepared_statements where name = 'InsertSimpleStation'");
1459     result = PQexec(aDbConnection->phandle, StatementExists);
1460     if (result==NULL)
1461     {
1462       fprintf(stderr,"Postgres Check for Prepared Query exec Failed: %s\n", PQerrorMessage(aDbConnection->phandle));
1463       xastir_snprintf(aDbConnection->errormessage,MAX_CONNECTION_ERROR_MESSAGE,PQerrorMessage(aDbConnection->phandle));
1464     }
1465     else
1466     {
1467       count = 0;
1468       if (PQresultStatus(result) == PGRES_TUPLES_OK)
1469       {
1470         count = atoi(PQgetvalue(result,0,0));
1471       }
1472       if (count==0)
1473       {
1474         // Statement doesn't exist, so prepare it, let PQprepare report on any error that got us a NULL result.
1475         prepared = PQprepare(aDbConnection->phandle, StatementName, sql, PARAMETERS, paramTypes);
1476         if (PQresultStatus(prepared)==PGRES_COMMAND_OK)
1477         {
1478           ok = 1;
1479         }
1480         else
1481         {
1482           // error condition - can't prepare statement
1483           fprintf(stderr,"Postgres Prepare Query Failed: %s\n", PQerrorMessage(aDbConnection->phandle));
1484           xastir_snprintf(aDbConnection->errormessage,MAX_CONNECTION_ERROR_MESSAGE,PQerrorMessage(aDbConnection->phandle));
1485           exit(1);
1486 
1487         }
1488       }
1489       else if (count==1)
1490       {
1491         // prepared statement exists, we can go ahead with query.
1492         ok = 1;
1493       }
1494       else
1495       {
1496         fprintf(stderr,"Postgres Check for Prepared Query getvalue (count=%d) failed: %s\n",count, PQresultErrorMessage(result));
1497         xastir_snprintf(aDbConnection->errormessage,MAX_CONNECTION_ERROR_MESSAGE,PQresultErrorMessage(result));
1498       }
1499     }
1500   }
1501   else
1502   {
1503     prepared = PQprepare(aDbConnection->phandle, StatementName, sql, PARAMETERS, paramTypes);
1504     ok = 1;
1505   }
1506   if (ok==1)
1507   {
1508     // native postgis POINT is (99.999 099.999) instead of POINT (99.999 099.999)
1509     // ok = xastirCoordToLatLongPosgresPoint(aStation->coord_lon, aStation->coord_lat, wkt);
1510     //
1511     // Prepared query is ready, get and fill in the parameter values
1512     // from the station provided, then fire the query.
1513     ok = xastirCoordToLatLongWKT(aStation->coord_lon, aStation->coord_lat, wkt);
1514     if (ok==1)
1515     {
1516       // Postgresql 8 documentation indicates that escape string should not be performed
1517       // when calling PQexecParams or its sibling routines, not explicit, but implication
1518       // is that PQexecPrepared with passed parameters is a sibling routine and we
1519       // shouldn't be running PQescapeStringConn() on the parameters.
1520       // If used, form would be:
1521       // PQescapeStringConn(conn,call_sign,aStation->call_sign,(MAX_CALLSIGN*2)+1,escape_error);
1522       xastir_snprintf(call_sign,MAX_CALLSIGN+1,"%s",aStation->call_sign);
1523       if (strlen(aStation->origin) > 0)
1524       {
1525         xastir_snprintf(origin,sizeof(origin),"%s",aStation->origin);
1526       }
1527       else
1528       {
1529         xastir_snprintf(origin,1,"%c",'\0');
1530       }
1531       xastir_snprintf(record_type,2,"%c",aStation->record_type);
1532       if (aStation->node_path_ptr==NULL)
1533       {
1534         xastir_snprintf(node_path,2," ");
1535       }
1536       else
1537       {
1538         xastir_snprintf(node_path,sizeof(node_path),"%s",aStation->node_path_ptr);
1539       }
1540       if (debug_level & 4096)
1541       {
1542         fprintf(stderr,"node_path   (12345678901234567890123456789012345678901234567890123456)\n");
1543         fprintf(stderr,"node_path = [%s]\n",node_path);
1544       }
1545       // Get time in seconds, adjust to datetime
1546       // If aStation is my station or another unset sec_heard is
1547       // encountered, use current time instead. Conversely, use time
1548       // provided in sec_heard if sec_heard is an invalid time.
1549       get_iso_datetime(aStation->sec_heard,timestring,True,False);
1550 
1551       // set parameter values to call, transmit_time, and position
1552       paramValues[0]=call_sign;
1553       paramValues[1]=timestring;
1554       paramValues[2]=wkt;
1555       if (aStation->aprs_symbol.aprs_symbol==NULL)
1556       {
1557         xastir_snprintf(aprs_symbol,2," ");
1558         paramValues[3]=&aprs_symbol;
1559       }
1560       else
1561       {
1562         xastir_snprintf(aprs_symbol,2,"%c",aStation->aprs_symbol.aprs_symbol);
1563         paramValues[3]=aprs_symbol;
1564       }
1565       if (aStation->aprs_symbol.special_overlay==NULL)
1566       {
1567         xastir_snprintf(special_overlay,2," ");
1568         paramValues[4]=&special_overlay;
1569       }
1570       else
1571       {
1572         xastir_snprintf(special_overlay,2,"%c",aStation->aprs_symbol.special_overlay);
1573         paramValues[4]=&special_overlay;
1574       }
1575       if (aStation->aprs_symbol.aprs_type==NULL)
1576       {
1577         xastir_snprintf(aprs_type,2," ");
1578         paramValues[5]=&aprs_type;
1579       }
1580       else
1581       {
1582         xastir_snprintf(aprs_type,2,"%c",aStation->aprs_symbol.aprs_type);
1583         paramValues[5]=aprs_type;
1584       }
1585       paramValues[6]=origin;
1586       paramValues[7]=record_type;
1587       paramValues[8]=node_path;
1588 
1589       if (debug_level & 4096)
1590       {
1591         fprintf(stderr,"Inserting: Call: %s, Time: %s, Position: %s, Symbol:%s,%s,%s Origin:%s, Node_path:%s, Record type:%s\n",paramValues[0],paramValues[1],paramValues[2],paramValues[3],paramValues[4],paramValues[5],paramValues[6],paramValues[8],paramValues[7]);
1592       }
1593 
1594       // send query
1595       result = PQexecPrepared(aDbConnection->phandle,StatementName,PARAMETERS,paramValues,NULL,NULL,POSTGRES_RESULTFORMAT_TEXT);
1596       if (PQresultStatus(result)!=PGRES_COMMAND_OK)
1597       {
1598         fprintf(stderr,"Postgres Insert query failed:%s\n",PQresultErrorMessage(result));
1599         // error, get error message.
1600         xastir_snprintf(aDbConnection->errormessage,MAX_CONNECTION_ERROR_MESSAGE,PQresultErrorMessage(result));
1601       }
1602       else
1603       {
1604         // query was successfull
1605         returnvalue=1;
1606       }
1607     }
1608     else
1609     {
1610       // problem with coordinates of station
1611       fprintf(stderr,"Unable to save station to Postgres db, Error converting latitude or longitude from xastir coordinates\n");
1612       xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Error converting latitude or longitude from xastir coordinates: %ld,%ld",aStation->coord_lat,aStation->coord_lon);
1613     }
1614   }
1615   if (result!=NULL)
1616   {
1617     PQclear(result);
1618   }
1619   if (prepared!=NULL)
1620   {
1621     PQclear(prepared);
1622   }
1623   return returnvalue;
1624 }
1625 
1626 
1627 
1628 
1629 
1630 /* function testXastirVersionPostgis()
1631  * Postgresql/Postgis implementation of wrapper testXastirVersionPostgis().
1632  * Should only be called through wrapper function.  Do not call directly.
1633  */
testXastirVersionPostgis(Connection * aDbConnection)1634 int testXastirVersionPostgis(Connection *aDbConnection)
1635 {
1636   int returnvalue = 0;
1637   int version_number;
1638   int compatable_series;
1639   const char sql[100] = "select version_number, compatable_series from version order by version_number desc limit 1";
1640   PGresult *result;
1641   PGconn *conn = aDbConnection->phandle;
1642 
1643   result = PQexec(conn,sql);
1644   if (result==NULL)
1645   {
1646     // PQexec probably couldn't allocate memory for the result set.
1647     xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Null result: %s\n",PQerrorMessage(conn));
1648     fprintf(stderr, "testXastirVersionPostgis() Null result\nPostgresql Error : %s\n",PQerrorMessage(conn));
1649   }
1650   else
1651   {
1652     // PQexec returned a result, but it may not be valid, check to see.
1653     if (PQresultStatus(result)==PGRES_COMMAND_OK || PQresultStatus(result)==PGRES_TUPLES_OK)
1654     {
1655       if (PQntuples(result)!=1)
1656       {
1657         fprintf(stderr,"Version table doesn't appear to contain any rows.\n");
1658         xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Version table doesn't appear to contain any rows.");
1659       }
1660       else
1661       {
1662         version_number = atoi(PQgetvalue(result,0,0));
1663         compatable_series = atoi(PQgetvalue(result,0,1));
1664         if (version_number == XASTIR_SPATIAL_DB_VERSION)
1665         {
1666           returnvalue = 1;
1667         }
1668         else
1669         {
1670           if (version_number < XASTIR_SPATIAL_DB_VERSION && compatable_series == XASTIR_SPATIAL_DB_COMPATABLE_SERIES)
1671           {
1672             returnvalue = 1;
1673             fprintf(stderr,"Version in schema (%d) is compatible with this version of xastir (%d).\n",version_number,XASTIR_SPATIAL_DB_VERSION);
1674           }
1675           else
1676           {
1677             fprintf(stderr,"Version in schema (%d) is not compatible with this version of xastir (%d).\n",version_number,XASTIR_SPATIAL_DB_VERSION);
1678             xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Version in schema (%d) is not compatible with this version of xastir (%d).",version_number,XASTIR_SPATIAL_DB_VERSION);
1679             fprintf(stderr,"%s",aDbConnection->errormessage);
1680             returnvalue = 0;
1681           }
1682 
1683         }
1684       }
1685 
1686     }
1687     PQclear(result);
1688   }
1689   return returnvalue;
1690 }
1691 
1692 
1693 
1694 
1695 
1696 /* function getAllSimplePositionsPostgis()
1697  * Postgresql/Postgis implementation of wrapper getAllSimplePositions().
1698  * Should only be called through wrapper function.  Do not call directly.
1699  */
getAllSimplePositionsPostgis(Connection * aDbConnection)1700 int getAllSimplePositionsPostgis(Connection *aDbConnection)
1701 {
1702   int returnvalue = 0;  // value to return from function, 1 for success, 0 for failure
1703   int row;  // row counter for result set loop
1704   int station_count = 0;  // number of new stations retrieved
1705   unsigned long x;  // xastir coordinate for longitude
1706   unsigned long y;  // xastir coordinate for latitide
1707   unsigned long u_long;
1708   unsigned long u_lat;
1709   char *s_lat[13];  // string latitude
1710   char *s_lon[13];  // string longitude
1711   float lat;  // latitude converted from retrieved string
1712   float lon;  // longitude converted from retrieved string
1713   const char *sql = "select station, symbol, overlay, aprstype, transmit_time, AsText(position), origin, record_type, node_path, X(position), Y(position) from simpleStation order by station, transmit_time asc";
1714   // station is column 0, symbol is column 1, etc.
1715   PGconn *conn = aDbConnection->phandle;
1716   char feedback[100];
1717   char lastcall[MAX_CALLSIGN+1];  //holds last retrieved callsign
1718   int  exists;            //shortcut to skip db check if currently retrieved callsign equals last retrieved callsign
1719   DataRow *p_new_station;  // pointer to new station record
1720   DataRow *p_time;  // pointer to new station record
1721   int skip;
1722   int points_this_station;  // number of times this station has been heard.
1723   char empty[MAX_ALTITUDE];
1724   struct tm time;
1725   time_t sec;
1726   empty[0]='\0';
1727   xastir_snprintf(feedback,100,"Retrieving Postgis records\n");
1728   stderr_and_statusline(feedback);
1729   //fprintf(stderr,"connection->phandle [%p]\n",aDbConnection->phandle);
1730   // run query and retrieve result set
1731   PGresult *result = PQexec(conn,sql);
1732 
1733   if (result==NULL)
1734   {
1735     // PQexec probably couldn't allocate memory for the result set.
1736     xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Null result: %s\n",PQerrorMessage(conn));
1737     fprintf(stderr, "getAllSimplePositionsPostgis() Null result\nPostgresql Error : %s\n",PQerrorMessage(conn));
1738   }
1739   else
1740   {
1741     // PQexec returned a result, but it may not be valid, check to see.
1742     if (PQresultStatus(result)==PGRES_COMMAND_OK || PQresultStatus(result)==PGRES_TUPLES_OK)
1743     {
1744       // PQexec returned a valid result set.
1745       xastir_snprintf(feedback,100,"Retrieving %i Postgis records\n",PQntuples(result));
1746       stderr_and_statusline(feedback);
1747       xastir_snprintf(lastcall,MAX_CALLSIGN+1," ");
1748       points_this_station = 0;
1749       for (row=0; row<PQntuples(result); row++)
1750       {
1751         // step through rows in result set and add each to xastir db as a minimal DataRow
1752         if (PQgetisnull(result,row,0))
1753         {
1754           // station name is null, skip.
1755         }
1756         else
1757         {
1758           // check if station exists
1759           p_new_station = NULL;
1760           exists = 0;
1761           // Shortcut check to see if this station has been loaded already
1762           // works as returned rows are ordered by station.
1763           // TODO: add_simple_station will not update the linked list of station properly,
1764           // when data from more than one database for the same station is loaded
1765           // and a more recently loaded database contains station data with timestamps
1766           // older than the timestamp from a previously loaded database.
1767           // This shows up as search_station_name failing to locate
1768           // existing stations and stations not being found on station info
1769           // (because they have expired from the list).
1770           // This will also be a problem if old data for stations heard live are
1771           // retrieved from a database while xastir is running.
1772           if (strcmp(PQgetvalue(result,row,0),lastcall)==0)
1773           {
1774             points_this_station++;
1775             exists = 1;
1776           }
1777           else
1778           {
1779             if (search_station_name(&p_new_station,PQgetvalue(result,row,0),1))
1780             {
1781               points_this_station++;
1782               exists = 1;
1783             }
1784             else
1785             {
1786               points_this_station=1;
1787             }
1788           }
1789           xastir_snprintf(lastcall,MAX_CALLSIGN+1,PQgetvalue(result,row,0));
1790           if (exists==1)
1791           {
1792             // This station allready exists as a DataRow in the xastir db.
1793             // Don't create a duplicate record, but add to the DataRow's track.
1794 
1795             //  check if it is a mobile station
1796             // We can't easily identify mobile stations from position position
1797             // becaue of rounding errors, therefore exclude stations that are likely to be fixed.
1798             // _/ = wx
1799             skip = 0;
1800             if ((PQgetvalue(result,row,1)[0]=='_') && (PQgetvalue(result,row,3)[0]=='/'))
1801             {
1802               skip = 1;   // wx
1803             }
1804             if ((PQgetvalue(result,row,1)[0]=='-') && (PQgetvalue(result,row,3)[0]=='/'))
1805             {
1806               skip = 1;   // house
1807             }
1808 
1809 
1810             if (skip==0)
1811             {
1812               // add to track
1813               if (search_station_name(&p_new_station,PQgetvalue(result,row,0),1))
1814               {
1815                 if (points_this_station<3)
1816                 {
1817                   //existing station record needs to be added as a trailpoint
1818                   (void)store_trail_point(p_new_station, p_new_station->coord_lon, p_new_station->coord_lat, p_new_station->sec_heard, empty, empty, empty, 0);
1819                 }
1820 
1821                 // store this trail point
1822                 lat = atof(PQgetvalue(result,row,10));
1823                 lon = atof(PQgetvalue(result,row,9));
1824                 if (strlen(PQgetvalue(result,row,4)) > 0)
1825                 {
1826                   strptime(PQgetvalue(result,row,4), "%Y-%m-%d %H:%M:%S%z", &time);
1827                   sec = mktime(&time);
1828                 }
1829                 if(convert_to_xastir_coordinates( &u_long, &u_lat, lon, lat))
1830                 {
1831                   (void)store_trail_point(p_new_station, u_long, u_lat, sec, empty, empty, empty, 0);
1832                 }
1833 
1834                 if (p_new_station->sec_heard < sec)
1835                 {
1836                   // update the station record to this position
1837                   if(convert_to_xastir_coordinates(&u_long, &u_lat, lon, lat))
1838                   {
1839                     p_new_station->coord_lat = u_lat;
1840                     p_new_station->coord_lon = u_long;
1841                     p_new_station->sec_heard = sec;
1842                   }
1843                 }
1844               }
1845             }
1846 
1847           }
1848           else
1849           {
1850             // This station isn't in the xastir db.
1851             //int add_simple_station(DataRow *p_new_station,char *station, char *origin, char *symbol, char *overlay, char *aprs_type, char *latitude, char *longitude, char *record_type, char *node_path, char *transmit_time) {
1852             //const char *sql = "select station, symbol, overlay, aprstype, transmit_time, AsText(position), origin, record_type, node_path, X(position), Y(position) from simpleStation order by station, transmit_time asc";
1853             add_simple_station(p_new_station,PQgetvalue(result,row,0), PQgetvalue(result,row,6), PQgetvalue(result,row,1), PQgetvalue(result,row,2), PQgetvalue(result,row,3), PQgetvalue(result,row,10), PQgetvalue(result,row,9), PQgetvalue(result,row,7), PQgetvalue(result,row,8), PQgetvalue(result,row,4), POSTGIS_TIMEFORMAT);
1854 
1855             station_count ++;
1856           }  // end else, new station
1857         } // end else, station is not null
1858       } // end for loop stepping through rows
1859       redo_list = (int)TRUE;      // update active station lists
1860       xastir_snprintf(feedback,100,"Added %d stations from Postgis\n",station_count);
1861       stderr_and_statusline(feedback);
1862     }
1863     else
1864     {
1865       // sql query had a problem retrieving result set.
1866       xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "%s %s\n",PQresStatus(PQresultStatus(result)),PQerrorMessage(conn));
1867       fprintf(stderr, "getAllSimplePositionsPostgis() %s\nPostgresql Error : %s\n",PQresStatus(PQresultStatus(result)),PQerrorMessage(conn));
1868     }
1869     // done with result set, so free the resource.
1870     PQclear(result);
1871   }
1872   return returnvalue;
1873 }
1874 
1875 
1876 
1877 
1878 
1879 /* function getAllSimplePositionsPostgisInBoundingBox()
1880  * Postgresql/Postgis implementation of wrapper getAllSimplePositionsInBoundingBox().
1881  * Should only be called through wrapper function.  Do not call directly.
1882  */
getAllSimplePositionsPostgisInBoundingBox(Connection * aDbConnection,char * str_e_long,char * str_w_long,char * str_n_lat,char * str_s_lat)1883 int getAllSimplePositionsPostgisInBoundingBox(Connection *aDbConnection, char* str_e_long, char* str_w_long, char* str_n_lat, char* str_s_lat)
1884 {
1885   int returnvalue = 0;
1886   // set up prepared query with bounding box
1887   // postgis simple table uses POINT
1888   char sql[100] = "select call, transmit_time, position from simpleStation where ";
1889   PGconn *conn = aDbConnection->phandle;
1890 
1891 
1892   return returnvalue;
1893 }
1894 
1895 
1896 
1897 
1898 #endif /* HAVE_POSTGIS */
1899 
1900 #ifdef HAVE_MYSQL_SPATIAL
1901 // Mysql 5 implementation of spatial database functions
1902 
1903 
1904 
1905 
1906 
1907 /* function storeStationToGisDbMysql
1908  * MySQL implemenation of storeStationToGisDb
1909  * Should be private to db_gis.c
1910  * Should only be called through wrapper function.  Do not call directly.
1911  * @param aDbConnection an exastir database connection struct describing
1912  * the connection.
1913  * @param aStation
1914  * Returns 0 for failure, 1 for success.
1915  * If failure, stores error message in aDbConnection->errormessage.
1916  */
storeStationToGisDbMysql(Connection * aDbConnection,DataRow * aStation)1917 int storeStationToGisDbMysql(Connection *aDbConnection, DataRow *aStation)
1918 {
1919   int returnvalue = 0;
1920   //ioparam *device = aDbConnection->descriptor;
1921   // check type of schema to use (XASTIR simple, full or APRSWorld)
1922   switch (devices[aDbConnection->interface_number].database_schema_type)
1923   {
1924     case XASTIR_SCHEMA_SIMPLE :
1925       returnvalue = storeStationSimplePointToGisDbMysql(aDbConnection,aStation);
1926       break;
1927     case XASTIR_SCHEMA_APRSWORLD :
1928       break;
1929     case XASTIR_SCHEMA_COMPLEX :
1930       break;
1931       // otherwise error message
1932   }
1933   return returnvalue;
1934 }
1935 
1936 
1937 
1938 
1939 
1940 
1941 /* function storeCadToGisDbMysql
1942  * MySQL implementation of storeCadToGisDbMysql
1943  * Should be private to db_gis.c
1944  * Should only be called through wrapper function.  Do not call directly.
1945  * @param aDbConnection an exastir database connection struct describing
1946  * the connection.
1947  * @param aCadObject
1948  * Returns 0 for failure, 1 for success.
1949  * If failure, stores error message in aDbConnection->errormessage.
1950  */
storeCadToGisDbMysql(Connection * aDbConnection,CADRow * aCadObject)1951 int storeCadToGisDbMysql(Connection *aDbConnection, CADRow *aCadObject)
1952 {
1953   int returnvalue = 0;
1954 
1955   return returnvalue;
1956 }
1957 
1958 
1959 
1960 
1961 
1962 
1963 /* support function for prepared statements
1964 int bind_mysql_string_parameter(MYSQL_BIND *bind, int bind_number, char* buffer, int provided_length, int buffer_length, my_bool is_null) {
1965        bind[bind_number]->buffer = buffer;
1966        bind[bind_number]->length = provided_length;
1967        bind[bind_number]->buffer_length = buffer_length;
1968        bind[bind_number]->buffer_type = MYSQL_TYPE_STRING
1969        bind[bind_number]->is_null = is_null;
1970 }
1971 */
1972 
1973 
1974 
1975 
1976 
1977 /* function storeStationSimplePointToGisDbMysql()
1978  * MySQL implementation of wrapper storeStationSimplePointToGisDb().
1979  * Should be private to db_gis.c
1980  * Should only be called through wrapper function.  Do not call directly.
1981  * @param aDbConnection an xastir database connection struct describing
1982  * the connection.
1983  * @param aStation
1984  * Returns 0 for failure, 1 for success.
1985  * On failure sets error message in aDbConnection->errormessage.
1986  */
storeStationSimplePointToGisDbMysql(Connection * aDbConnection,DataRow * aStation)1987 int storeStationSimplePointToGisDbMysql(Connection *aDbConnection, DataRow *aStation)
1988 {
1989   int returnvalue = 0;
1990   int mysqlreturn;  // hold return value of mysql query
1991   int param_count;  // check on the number of parameters present in the prepared statement
1992   int ok;    // variable to store results of tests preparatory to firing query
1993   // temporary holding variables for bind buffers
1994   char wkt[MAX_WKT];  // well know text representation of latitude and longitude of point
1995   char aprs_symbol[2];  // temporary holding for escaped aprs symbol
1996   char aprs_type[2];    // temporary holding for escaped aprs type
1997   char special_overlay[2];  // temporary holding for escaped overlay
1998   char record_type[2];              // temporary holding for escaped record type
1999   char origin[MAX_CALLSIGN+1];  // temporary holding for escaped origin
2000   char node_path[NODE_PATH_SIZE+1];         // temporary holding for escaped node_path_ptr
2001   MYSQL_STMT *statement;
2002   // bind string lengths
2003   unsigned long call_sign_length;
2004   unsigned long wkt_length;
2005   unsigned long aprs_symbol_length;
2006   unsigned long aprs_type_length;
2007   unsigned long special_overlay_length;
2008   unsigned long origin_length;
2009   unsigned long record_type_length;
2010   unsigned long node_path_length;
2011   // time
2012   MYSQL_TIME timestamp;
2013   char timestring[100+1];
2014   time_t secs_now;
2015   struct tm *ts;  // to convert time to component parts for bind.buffer_type MYSQL_TYPE_DATETIME
2016   // define prepared statement and matching bind array
2017 #define SQL "INSERT INTO simpleStationSpatial (station, transmit_time, position, symbol, overlay, aprstype, origin, record_type, node_path) VALUES (?,?,PointFromText(?),?,?,?,?,?,?)"
2018   MYSQL_BIND bind[9];   // bind array for prepared query.
2019   int parameters = 9;
2020   // Note:
2021   // bind[9], SQL "?????????", and param_count must all match value of parameters
2022   // nine bound parameters, nine question marks in the statement, and param_count returned as nine.
2023 
2024   if (debug_level & 4096)
2025   {
2026     fprintf(stderr,"in storeStationSimplePointToGisDbMysql\n");
2027     fprintf(stderr,"with connection [%p] \n",aDbConnection);
2028   }
2029 
2030   if (&aDbConnection->mhandle==NULL)
2031   {
2032     return returnvalue;
2033   }
2034 
2035   statement = mysql_stmt_init(&aDbConnection->mhandle);
2036   if (!statement)
2037   {
2038     fprintf(stderr,"Unable to create mysql prepared statement.  May be out of memmory.\n");
2039   }
2040   mysql_stmt_prepare(statement, SQL, strlen(SQL));
2041   if (!statement)
2042   {
2043     mysql_interpret_error(*mysql_error(&aDbConnection->mhandle),aDbConnection);
2044   }
2045   else
2046   {
2047     // test to make sure that statement has the correct number of parameters
2048     param_count=mysql_stmt_param_count(statement);
2049     if (param_count!=parameters)
2050     {
2051       fprintf(stderr,"Number of bound parameters %d does not match expected value %d\nFor query[%s]",param_count,parameters,SQL);
2052       fprintf(stderr, " %s\n", mysql_stmt_error(statement));
2053     }
2054     else
2055     {
2056       // set up the buffers
2057       memset(bind, 0, sizeof(bind));
2058 
2059       bind[0].buffer =  (char *)&aStation->call_sign;
2060       bind[0].length = &call_sign_length;
2061       bind[0].buffer_length = MAX_CALLSIGN;
2062       bind[0].buffer_type = MYSQL_TYPE_STRING;
2063       bind[0].is_null = 0;
2064 
2065       bind[1].buffer = (char *)&timestamp;
2066       bind[1].length = 0;
2067       bind[1].buffer_type = MYSQL_TYPE_DATETIME;
2068       bind[1].is_null = 0;
2069 
2070       bind[2].buffer = (char *)&wkt;
2071       bind[2].length = &wkt_length;
2072       bind[2].buffer_length = MAX_WKT;
2073       bind[2].buffer_type = MYSQL_TYPE_STRING;
2074       bind[2].is_null = 0;
2075 
2076       bind[3].buffer = (char *)&aprs_symbol;
2077       bind[3].length = &aprs_symbol_length;
2078       bind[3].buffer_length = 2;
2079       bind[3].buffer_type = MYSQL_TYPE_STRING;
2080       bind[3].is_null = 0;
2081 
2082       bind[4].buffer = (char *)&special_overlay;
2083       bind[4].length = &special_overlay_length;
2084       bind[4].buffer_length = 2;
2085       bind[4].buffer_type = MYSQL_TYPE_STRING;
2086       bind[4].is_null = 0;
2087 
2088       bind[5].buffer = (char *)&aprs_type;
2089       bind[5].length = &aprs_type_length;
2090       bind[5].buffer_length = 2;
2091       bind[5].buffer_type = MYSQL_TYPE_STRING;
2092       bind[5].is_null = 0;
2093 
2094       bind[6].buffer = (char *)&origin;  // segfaults with origin of zero length, otherwise writes bad data
2095       bind[6].length = &origin_length;
2096       bind[6].buffer_length = MAX_CALLSIGN;
2097       bind[6].buffer_type = MYSQL_TYPE_STRING;
2098       bind[6].is_null = 0;
2099 
2100       bind[7].buffer = (char *)&record_type;
2101       bind[7].length = &record_type_length;
2102       bind[7].buffer_length = 2;
2103       bind[7].buffer_type = MYSQL_TYPE_STRING;
2104       bind[7].is_null = 0;
2105 
2106       bind[8].buffer = (char *)&node_path;
2107       bind[8].length = &node_path_length;
2108       bind[8].buffer_length = NODE_PATH_SIZE;
2109       bind[8].buffer_type = MYSQL_TYPE_STRING;
2110       bind[8].is_null = 0;
2111 
2112       ok = mysql_stmt_bind_param(statement,bind);
2113       if (ok!=0)
2114       {
2115         fprintf(stderr,"Error binding parameters to mysql prepared statement.\n");
2116         mysql_interpret_error(mysql_errno(&aDbConnection->mhandle),aDbConnection);
2117         fprintf(stderr,mysql_stmt_error(statement));
2118       }
2119       else
2120       {
2121 
2122         // get call, time, and position
2123         // call is required
2124         if (aStation->call_sign!=NULL && strlen(aStation->call_sign)>0)
2125         {
2126           call_sign_length = strlen(aStation->call_sign);
2127 
2128           // get time in seconds, adjust to datetime
2129           // If my station or another unset sec_heard is
2130           // encountered, use current time instead, use time
2131           // provided if it was invalid.
2132           get_iso_datetime(aStation->sec_heard,timestring,True,False);
2133           if ((int)aStation->sec_heard==0 )
2134           {
2135             secs_now = sec_now();
2136             ts = localtime(&secs_now);
2137           }
2138           else
2139           {
2140             ts = localtime(&aStation->sec_heard);
2141           }
2142           timestamp.year = ts->tm_year + 1900;  // tm_year is from 1900
2143           timestamp.month = ts->tm_mon + 1;     // tm_mon is from 0
2144           timestamp.day = ts->tm_mday;          // tm_mday is from 1
2145           timestamp.hour = ts->tm_hour;
2146           timestamp.minute = ts->tm_min;
2147           timestamp.second = ts->tm_sec;
2148           ok = xastirCoordToLatLongWKT(aStation->coord_lon, aStation->coord_lat, wkt);
2149           if (ok==1)
2150           {
2151             wkt_length = strlen(wkt);
2152 
2153 
2154             if (aStation->aprs_symbol.aprs_symbol)
2155             {
2156               xastir_snprintf(aprs_symbol,2,"%c",aStation->aprs_symbol.aprs_symbol);
2157             }
2158             else
2159             {
2160               xastir_snprintf(aprs_symbol,2,"%c",'\0');
2161             }
2162             aprs_symbol_length = strlen(aprs_symbol);
2163 
2164             if (aStation->aprs_symbol.aprs_type)
2165             {
2166               xastir_snprintf(aprs_type,2,"%c",aStation->aprs_symbol.aprs_type);
2167             }
2168             else
2169             {
2170               xastir_snprintf(aprs_type,2,"%c",'\0');
2171             }
2172             aprs_type_length = strlen(aprs_type);
2173 
2174             if (aStation->aprs_symbol.special_overlay)
2175             {
2176               xastir_snprintf(special_overlay,2,"%c",aStation->aprs_symbol.special_overlay);
2177             }
2178             else
2179             {
2180               xastir_snprintf(special_overlay,2,"%c",'\0');
2181             }
2182             special_overlay_length = strlen(special_overlay);
2183 
2184             if (aStation->origin)
2185             {
2186               xastir_snprintf(origin,MAX_CALLSIGN+1,"%s",aStation->origin);
2187             }
2188             else
2189             {
2190               //xastir_snprintf(origin,2,"%c",'\0');
2191               origin[0]='\0';
2192             }
2193             origin_length = strlen(origin);
2194 
2195             if (aStation->record_type)
2196             {
2197               xastir_snprintf(record_type,2,"%c",aStation->record_type);
2198             }
2199             else
2200             {
2201               //xastir_snprintf(record_type,2,"%c",'\0');
2202               record_type[0]='\0';
2203             }
2204             record_type_length = strlen(record_type);
2205 
2206             if (aStation->node_path_ptr)
2207             {
2208               if (debug_level & 4096)
2209               {
2210                 fprintf(stderr,"node_path   (12345678901234567890123456789012345678901234567890123456)\n");
2211                 fprintf(stderr,"node_path = [%s]\n",aStation->node_path_ptr);
2212               }
2213               xastir_snprintf(node_path,NODE_PATH_SIZE+1,"%s",aStation->node_path_ptr);
2214             }
2215             else
2216             {
2217               //xastir_snprintf(node_path,2,"%c",'\0');
2218               node_path[0]='\0';
2219             }
2220             node_path_length = strlen(node_path);
2221 
2222             // all the bound parameters should be available and correct
2223             if (debug_level & 4096)
2224             {
2225               fprintf(stderr,"saving station %s  %d %d %d %d:%d:%d wkt=%s [%s][%s][%s] \n",aStation->call_sign,ts->tm_year,ts->tm_mon,ts->tm_mday,ts->tm_hour,ts->tm_min,ts->tm_sec,wkt,aprs_type,aprs_symbol,record_type);
2226             }
2227             // send query
2228             mysqlreturn = mysql_stmt_execute(statement);
2229             if (mysqlreturn!=0)
2230             {
2231               returnvalue=0;
2232               fprintf(stderr,"%s\n",mysql_stmt_error(statement));
2233               mysql_interpret_error(mysqlreturn,aDbConnection);
2234             }
2235             else
2236             {
2237               returnvalue=1;
2238             }
2239           }
2240           else
2241           {
2242             fprintf(stderr,"Unable to save station to mysql db, Error converting latitude or longitude from xastir coordinates\n");
2243             xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Error converting latitude or longitude from xastir coordinates: %ld,%ld",aStation->coord_lat,aStation->coord_lon);
2244           }
2245         }
2246         else
2247         {
2248           // set call not null error message
2249           fprintf(stderr,"Unable to save station to mysql db, Station call sign was blank or null.\n");
2250           xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Station callsign is required and was blank or null.");
2251         }
2252       } // end of bind check
2253     }  // end of parameter count check
2254   }
2255   mysql_stmt_free_result(statement);
2256   mysql_stmt_close(statement);
2257   if (returnvalue==0)
2258   {
2259     pingConnection(aDbConnection);
2260   }
2261 
2262   return returnvalue;
2263 }
2264 
2265 
2266 
2267 
2268 
2269 
getAllSimplePositionsMysqlSpatial(Connection * aDbConnection)2270 int getAllSimplePositionsMysqlSpatial(Connection *aDbConnection)
2271 {
2272   int returnvalue = 0;
2273   DataRow *p_new_station;
2274   int station_count = 0;  // number of new stations retrieved
2275   char *s_lat[13];  // string latitude
2276   char *s_lon[13];  // string longitude
2277   float lat;  // latitude converted from retrieved string
2278   float lon;  // longitude converted from retrieved string
2279   unsigned long u_lat;
2280   unsigned long u_long;
2281   int points_this_station;
2282   char feedback[100];
2283   struct tm time;
2284   time_t sec;
2285   int skip; // used in identifying mobile stations
2286   char sql[] = "select station, transmit_time, AsText(position), symbol, overlay, aprstype, origin, record_type, node_path from simpleStationSpatial order by station, transmit_time asc";
2287   char lastcall[MAX_CALLSIGN+1];  //holds last retrieved callsign
2288   int  exists;            //shortcut to skip db check if currently retrieved callsign equals last retrieved callsign
2289   MYSQL_RES *result;
2290   MYSQL_ROW row;
2291   char empty[MAX_ALTITUDE];
2292   int ok;   // to hold mysql_query return value
2293   empty[0]='\0';
2294   ok = mysql_query(&aDbConnection->mhandle,sql);
2295   if (ok==0)
2296   {
2297     result = mysql_use_result(&aDbConnection->mhandle);
2298     if (result!=NULL)
2299     {
2300       xastir_snprintf(feedback,100,"Retrieving MySQL records\n");
2301       stderr_and_statusline(feedback);
2302       // with mysql_use_result each call to mysql_fetch_row retrieves
2303       // a row of data from the server.  Mysql_store_result might use
2304       // too much memory in retrieving a large result set all at once.
2305       xastir_snprintf(lastcall,MAX_CALLSIGN+1," ");
2306       points_this_station=0;
2307       while ((row = mysql_fetch_row(result)))
2308       {
2309         // retrieve data from the row
2310         // test to see if this is a valid station
2311         if (row[0]==NULL)
2312         {
2313           // station is null, skip
2314         }
2315         else
2316         {
2317           p_new_station = NULL;
2318           exists = 0;
2319           // Shortcut check to see if station has allready been heard
2320           // works as query is ordered by station.
2321           if (strcmp(lastcall,row[0])==1)
2322           {
2323             exists = 1;
2324             points_this_station++;
2325           }
2326           else
2327           {
2328             if (search_station_name(&p_new_station,row[0],1))
2329             {
2330               exists = 1;
2331               points_this_station++;
2332             }
2333             else
2334             {
2335               points_this_station=1;
2336             }
2337           }
2338           xastir_snprintf(lastcall,MAX_CALLSIGN+1,row[0]);
2339           if (exists==1)
2340           {
2341             // This station is allready in present as a DataRow in the xastir db.
2342             // check to see if this is likely to be a mobile station
2343 
2344             // We can't easily identify mobile stations from position position
2345             // becaue of rounding errors, therefore exclude stations that are likely to be fixed.
2346             // _/ = wx
2347             skip = 0;
2348             if ((strcmp(row[3],"_")==0) & (strcmp(row[5],"/")==0))
2349             {
2350               skip = 1;   // wx
2351             }
2352             if ((strcmp(row[3],"-")==0) & (strcmp(row[5],"/")==0))
2353             {
2354               skip = 1;   // house
2355             }
2356 
2357             if (skip==0)
2358             {
2359               // add to track
2360 
2361               if (search_station_name(&p_new_station,row[0],1))
2362               {
2363                 if (points_this_station<3)
2364                 {
2365                   //existing station record needs to be added as a trailpoint
2366                   (void)store_trail_point(p_new_station, p_new_station->coord_lon, p_new_station->coord_lat, p_new_station->sec_heard, empty, empty, empty, 0);
2367                 }
2368                 // store this trail point
2369                 lat = xastirWKTPointToLatitude(row[2]);
2370                 lon = xastirWKTPointToLongitude(row[2]);
2371                 if (strlen(row[1]) > 0)
2372                 {
2373                   strptime(row[1], "%Y-%m-%d %H:%M:%S", &time);
2374                   sec = mktime(&time);
2375                   //fprintf(stderr,"trailpoint time:  %ld  [%s]\n", sec, row[1]);
2376                 }
2377                 if(convert_to_xastir_coordinates( &u_long, &u_lat, lon, lat))
2378                 {
2379                   (void)store_trail_point(p_new_station, u_long, u_lat, sec, empty, empty, empty, 0);
2380                 }
2381                 if (p_new_station->sec_heard < sec)
2382                 {
2383                   // update the station record to this position
2384                   if(convert_to_xastir_coordinates(&u_long, &u_lat, lon, lat))
2385                   {
2386                     p_new_station->coord_lat = u_lat;
2387                     p_new_station->coord_lon = u_long;
2388                     p_new_station->sec_heard = sec;
2389                   }
2390                 }
2391               } // search_station_name
2392             }  // !skip
2393           }
2394           else
2395           {
2396             // This station isn't in the xastir db.
2397             // Add a datarow using the retrieved station record from the postgis database.
2398             lat = xastirWKTPointToLatitude(row[2]);
2399             lon = xastirWKTPointToLongitude(row[2]);
2400             xastir_snprintf(s_lat,13,"%3.6f",lat);
2401             xastir_snprintf(s_lon,13,"%3.6f",lon);
2402             add_simple_station(p_new_station, row[0], row[6], row[3], row[4], row[5], s_lat, s_lon, row[7], row[8], row[1],(char*)MYSQL_TIMEFORMAT);
2403 
2404             station_count++;
2405           }
2406         }
2407       }
2408     }
2409     else
2410     {
2411       // error fetching the result set
2412       fprintf(stderr,"mysql error: %s\n",mysql_error(&aDbConnection->mhandle));
2413       mysql_interpret_error(mysql_errno(&aDbConnection->mhandle),aDbConnection);
2414     }
2415     xastir_snprintf(feedback,100,"Retreived %d new stations from MySQL\n",station_count);
2416     stderr_and_statusline(feedback);
2417     mysql_free_result(result);
2418   }
2419   else
2420   {
2421     // query didn't execute correctly
2422     mysql_interpret_error(ok,aDbConnection);
2423   }
2424 
2425   return returnvalue;
2426 }
2427 
2428 
2429 
2430 
2431 
2432 
getAllCadFromGisDbMysql(Connection * aDbConnection)2433 int getAllCadFromGisDbMysql(Connection *aDbConnection)
2434 {
2435   int returnvalue = 0;
2436   int mysqlreturn;
2437   MYSQL *conn = &aDbConnection->mhandle;
2438 
2439   return returnvalue;
2440 }
2441 
2442 
2443 
2444 
2445 
getAllSimplePositionsMysqlSpatialInBoundingBox(Connection * aDbConnection,char * str_e_long,char * str_w_long,char * str_n_lat,char * str_s_lat)2446 int getAllSimplePositionsMysqlSpatialInBoundingBox(Connection *aDbConnection, char* str_e_long, char* str_w_long, char* str_n_lat, char* str_s_lat)
2447 {
2448   int returnvalue = 0;
2449   int mysqlreturn;
2450   MYSQL *conn = &aDbConnection->mhandle;
2451 
2452   return returnvalue;
2453 
2454 }
2455 
2456 
2457 
2458 
2459 /*
2460 
2461   // some thoughts on database schema elements
2462 
2463   create database xastir;
2464   grant select on xastir to user xastir_user@localhost identified by encrypted password '<password>';
2465 
2466   create table version (
2467      version_number int,
2468      compatable_series int
2469   );
2470   grant select on version to xastir_user@localhost
2471   insert into version (version_number) values (XASTIR_SPATIAL_DB_VERSION);
2472   insert into version (version_number) values (XASTIR_SPATIAL_DB_COMPATIBLE_SERIES);
2473 
2474   # should be minimum fields needed to populate a DataRow and a related
2475   # APRS_Symbol in xastir
2476   create table simpleStation (
2477      simpleStationId int primary key not null auto_increment
2478      station varchar(MAX_CALLSIGN) not null,  # callsign of station, length up to max_callsign
2479      symbol varchar(1),     # aprs symbol character
2480      overlay varchar(1),    # aprs overlay table character
2481      aprstype varchar(1),    # aprs type, required???
2482      transmit_time datetime not null default now(),  # transmission time, if available, otherwise storage time
2483      position POINT   # position of station or null if latitude and longitude are not available
2484   );
2485 
2486 
2487 
2488   grant select, insert on simpleStation to xastir_user@localhost;
2489 
2490 
2491   create table datarow (
2492       datarow_id int not null primary key auto_increment,
2493       call_sign varchar(10) not null,
2494       tactical_call_sign varchar() not null default '',
2495       c_aprs_symbol_id int
2496       location POINT,
2497       time_sn int,
2498       sec_heard long,
2499       heard_via_tnc_last_time long,
2500       direct_heard long,
2501       packet_time varchar,
2502       pos_time varchar,
2503       flag int,
2504       pos_amb varchar(1),
2505       error_ellipse_radius int,
2506       lon_precision int,
2507       lat_precision int,
2508       trail_color int,
2509       record_type varchar(1),
2510       data_via varchar(1),
2511       heard_via_tnc_port int,
2512       last_port_heard int,
2513       num_packets int,
2514       altitude varchar([MAX_ALTITUDE]),
2515       speed varchar([MAX_SPEED+1]),
2516       course varchar([MAX_COURSE+1]),
2517       bearing varchar([MAX_COURSE+1]),
2518       NRQ varchar([MAX_COURSE+1]),
2519       power_gain varchar([MAX_POWERGAIN+1]),
2520       signal_gain varchar([MAX_POWERGAIN+1])
2521   );
2522 
2523 
2524 
2525 */
2526 
2527 #endif /* HAVE_MYSQL_SPATIAL */
2528 
2529 #endif /* HAVE_SPATIAL_DB */
2530 
2531 
2532 
2533 
2534 
2535 
2536 // Layer 3b: DBMS specific db storage code for non spatial databases **********
2537 // Functions supporting queries to specific types of databases that lack
2538 // spatial extensions.  Limited to storing points using latitude and longitude
2539 // fields without spatial objects or spatial indexing.
2540 //
2541 
2542 #ifdef HAVE_MYSQL
2543 // functions for MySQL database version < 4.1, or MySQL schema objects that don't
2544 // include spatial indicies.
2545 //
2546 //********* Support for MySQL < 4.1 is depreciated  *****************************
2547 //********* Expect MySQL support to be limited to MySQL 5+ **********************
2548 //
2549 
2550 
2551 
2552 
2553 
2554 /* function storeStationSimplePointToDbMysql()
2555  * MySQL implementation of wrapper storeStationSimplePointToGisDb().
2556  * Should be private to db_gis.c
2557  * Should only be called through wrapper function.  Do not call directly.
2558  * Returns 0 for failure, 1 for success.
2559  * If failure, stores error message in aDbConnection->errormessage.
2560  */
storeStationSimplePointToDbMysql(Connection * aDbConnection,DataRow * aStation)2561 int storeStationSimplePointToDbMysql(Connection *aDbConnection, DataRow *aStation)
2562 {
2563   int returnvalue = 0;  // default return value is failure.
2564   int mysqlreturn = 1;  // result of sending mysql query.
2565   char sql[400];
2566   // Next three variables are one character in two bytes plus one character for
2567   // filling by mysql_real_escape_string().
2568   char aprs_symbol[3];  // temporary holding for escaped aprs symbol
2569   char aprs_type[3];    // temporary holding for escaped aprs type
2570   char special_overlay[3];  // temporary holding for escaped overlay
2571   char record_type[3];              // temporary holding for escaped record type
2572   char from[3];  // temporary holding for all of the above length 3 variables
2573   char call_sign[(MAX_CALLSIGN)*2+1];   // temporary holding for escaped callsign
2574   char origin[(MAX_CALLSIGN)*2+1];  // temporary holding for escaped origin
2575   char node_path[(NODE_PATH_SIZE*2)+1];         // temporary holding for escaped node_path_ptr
2576   float longitude;
2577   float latitude;
2578   int ok;
2579   char timestring[100+1];
2580 
2581   if (debug_level & 4096)
2582   {
2583     fprintf(stderr,"In storestationsimpletodbmysql()\n");
2584   }
2585 
2586   // prepared statements not present below MySQL version 4.1
2587   // details of prepared statement support changed between versions 4.1 and 5.0.
2588   // char [] sql = "insert into simpleStation (call, transmit_time, latitude, longitude) values ('%1','%2','%3','%4'))";
2589   // call is a required element for a simple station
2590   if (aStation!=NULL && aStation->call_sign!=NULL && strlen(aStation->call_sign)>0)
2591   {
2592     // get time in seconds, adjust to datetime
2593     // If my station or another unset sec_heard is
2594     // encountered, use current time instead, use time
2595     // provided if it was invalid.
2596     get_iso_datetime(aStation->sec_heard,timestring,True,False);
2597     // get coord_lat, coord_long in xastir coordinates and convert to decimal degrees
2598     ok = convert_from_xastir_coordinates (&longitude, &latitude, aStation->coord_lon, aStation->coord_lat);
2599     // latitude and longitude are required elements for a simple station record.
2600     if (ok==1)
2601     {
2602       // build insert query with call, time, and position
2603       // handle special cases of null, \ and ' characters in type, symbol, and overlay.
2604       if (aStation->aprs_symbol.aprs_symbol)
2605       {
2606         xastir_snprintf(from,2,"%c",aStation->aprs_symbol.aprs_symbol);
2607         mysql_real_escape_string(&aDbConnection->mhandle,aprs_symbol,from,1);
2608       }
2609       else
2610       {
2611         xastir_snprintf(aprs_symbol,2,"%c",'\0');
2612       }
2613       if (aStation->aprs_symbol.aprs_type)
2614       {
2615         xastir_snprintf(from,2,"%c",aStation->aprs_symbol.aprs_type);
2616         mysql_real_escape_string(&aDbConnection->mhandle,aprs_type,from,1);
2617       }
2618       else
2619       {
2620         xastir_snprintf(aprs_type,2,"%c",'\0');
2621       }
2622       if (aStation->aprs_symbol.special_overlay)
2623       {
2624         xastir_snprintf(from,2,"%c",aStation->aprs_symbol.special_overlay);
2625         mysql_real_escape_string(&aDbConnection->mhandle,special_overlay,from,1);
2626       }
2627       else
2628       {
2629         xastir_snprintf(special_overlay,2,"%c",'\0');
2630       }
2631 
2632       // Need to escape call sign - may contain special characters:
2633       // insert into simpleStation (station, symbol, overlay, aprstype, transmit_time, latitude, longitude)
2634       // values ('Fry's','/\0\0',' ','//\0','2007-08-07 21:55:43 -0400','47.496834','-122.198166')
2635       mysql_real_escape_string(&aDbConnection->mhandle,call_sign,(aStation->call_sign),strlen(aStation->call_sign));
2636       // just in case, set a default value for record_type and escape it.
2637       if (aStation->record_type)
2638       {
2639         fprintf(stderr,"record_type: %c\n",aStation->record_type);
2640         xastir_snprintf(from,2,"%c",aStation->record_type);
2641         mysql_real_escape_string(&aDbConnection->mhandle,record_type,from,1);
2642       }
2643       else
2644       {
2645         xastir_snprintf(record_type,2,"%c",NORMAL_APRS);
2646       }
2647 
2648       if (strlen(aStation->origin) > 0)
2649       {
2650         mysql_real_escape_string(&aDbConnection->mhandle,origin,(aStation->origin),strlen(aStation->origin));
2651       }
2652       else
2653       {
2654         xastir_snprintf(origin,2,"%c",'\0');
2655       }
2656       if (aStation->node_path_ptr)
2657       {
2658         //mysql_real_escape_string(conn,&node_path,aStation->node_path_ptr,((strlen(aStation->node_path_ptr)*2)+1));
2659         xastir_snprintf(node_path,sizeof(node_path),"%s",aStation->node_path_ptr);
2660       }
2661       else
2662       {
2663         xastir_snprintf(node_path,2,"%c",'\0');
2664       }
2665 
2666       xastir_snprintf(sql,sizeof(sql),"insert into simpleStation (station, symbol, overlay, aprstype, transmit_time, latitude, longitude, origin, record_type, node_path) values ('%s','%s','%s','%s','%s','%3.6f','%3.6f','%s','%s','%s')", call_sign, aprs_symbol, special_overlay, aprs_type,timestring,latitude,longitude,origin,record_type,node_path);
2667 
2668       if (debug_level & 4096)
2669       {
2670         fprintf(stderr,"MySQL Query:\n%s\n",sql);
2671       }
2672 
2673       // send query
2674       mysql_ping(&aDbConnection->mhandle);
2675       mysqlreturn = mysql_real_query(&aDbConnection->mhandle, sql, strlen(sql)+1);
2676       if (mysqlreturn!=0)
2677       {
2678         // get the mysql error message
2679         fprintf(stderr,mysql_error(&aDbConnection->mhandle));
2680         fprintf(stderr,"\n");
2681         mysql_interpret_error(mysqlreturn,aDbConnection);
2682       }
2683       else
2684       {
2685         // insert query was successfull, return value is ok.
2686         returnvalue=1;
2687       }
2688     }
2689     else
2690     {
2691       xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Error converting latitude or longitude from xastir coordinates: %ld,%ld",aStation->coord_lat,aStation->coord_lon);
2692     }
2693   }
2694   else
2695   {
2696     // set call not null error message
2697     xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Station callsign is required and was blank or null.");
2698   }
2699   return returnvalue;
2700 }
2701 
2702 
2703 
2704 
2705 
2706 /* function testXastirVersionMysql()
2707  * checks the xastir database version number of a connected MySQL database against the
2708  * version range supported by the running copy of xastir.
2709  * @param aDbConnection pointer to a Connection struct describing the connection
2710  * @returns 0 if incompatable, 1 if compatable, -1 on connection failure.
2711  *
2712  * db       program
2713  * v  cs    v   cs    compatable
2714  * 1  1     1   1     1    identical
2715  * 2  1     1   1     1    database newer than program (added fields, not queried)
2716  * 1  1     2   1     0    program newer than database (added fields, queries fail).
2717  * 3  2     2   1     0    different series
2718  * 2  1     3   2     0    different series
2719  *
2720  * TODO: Need function to test for available schemas with mysql_list_tables()
2721  */
testXastirVersionMysql(Connection * aDbConnection)2722 int testXastirVersionMysql(Connection *aDbConnection)
2723 {
2724   int returnvalue = -1;
2725   MYSQL_RES *result;
2726   MYSQL_ROW row;
2727   int version_number;
2728   int compatible_series;
2729   char sql[] = "select version_number, compatable_series from version order by version_number desc limit 1";
2730   int ok;   // to hold mysql_query return value
2731   ok = mysql_query(&aDbConnection->mhandle,sql);
2732   if (ok==0)
2733   {
2734     result = mysql_use_result(&aDbConnection->mhandle);
2735     if (result!=NULL)
2736     {
2737       if ((row = mysql_fetch_row(result)))
2738       {
2739         version_number = atoi((char *)row[0]);
2740         if (version_number == XASTIR_SPATIAL_DB_VERSION)
2741         {
2742           returnvalue = 1;
2743           fprintf(stderr,"Version in schema (%d) is the same as this version of xastir (%d).\n",version_number,XASTIR_SPATIAL_DB_VERSION);
2744         }
2745         else
2746         {
2747           compatible_series = atoi((char *)row[1]);
2748           if (version_number < XASTIR_SPATIAL_DB_VERSION && compatible_series == XASTIR_SPATIAL_DB_COMPATABLE_SERIES)
2749           {
2750             returnvalue = 1;
2751             fprintf(stderr,"Version in schema (%d) is compatible with this version of xastir (%d).\n",version_number,XASTIR_SPATIAL_DB_VERSION);
2752           }
2753           else
2754           {
2755             fprintf(stderr,"Version in schema (%d) is not compatible with this version of xastir (%d).\n",version_number,XASTIR_SPATIAL_DB_VERSION);
2756             xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Version in schema (%d) is not compatible with this version of xastir (%d).",version_number,XASTIR_SPATIAL_DB_VERSION);
2757             fprintf(stderr,"%s",aDbConnection->errormessage);
2758             returnvalue = 0;
2759           }
2760         }
2761       }
2762       else
2763       {
2764         // result returned, but no rows = incompatable
2765         returnvalue = 0;
2766         fprintf(stderr,"Version table doesn't appear to contain any rows.\n");
2767         xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Version table doesn't appear to contain any rows.");
2768       }
2769     }
2770     else
2771     {
2772       fprintf(stderr,"Schema doesn't appear to contain a version table.\n");
2773       xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Schema doesn't appear to contain a version table.");
2774     }
2775     mysql_free_result(result);
2776   }
2777   else
2778   {
2779     fprintf(stderr,"Query failed, Schema doesn't appear to contain a version table.\n");
2780     xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Query for version table failed.");
2781   }
2782   return returnvalue;
2783 }
2784 
2785 
2786 
2787 
2788 
2789 /* function storeStationToDbMysql()
2790  */
storeStationToDbMysql(Connection * aDbConnection,DataRow * aStation)2791 int storeStationToDbMysql(Connection *aDbConnection, DataRow *aStation)
2792 {
2793   int returnvalue = 0;
2794   //ioparam *device = aDbConnection->descriptor;
2795   // check type of schema to use (XASTIR simple, full or APRSWorld)
2796   switch (devices[aDbConnection->interface_number].database_schema_type)
2797   {
2798     case XASTIR_SCHEMA_SIMPLE :
2799       returnvalue = storeStationSimplePointToDbMysql(aDbConnection,aStation);
2800       break;
2801     case XASTIR_SCHEMA_APRSWORLD :
2802       break;
2803     case XASTIR_SCHEMA_COMPLEX :
2804       break;
2805       // otherwise error message
2806   }
2807   return returnvalue;
2808 }
2809 
2810 
2811 
2812 
2813 
2814 /* function getAllSimplePositionsMysql()
2815  * MySQL implementation of getAllSimplePositions for a MySQL database that
2816  * does not include spatial support.
2817  * @param aDbConnection an exastir database connection struct describing
2818  * the connection.
2819  * Returns 0 for failure, 1 for success.
2820  * If failure, stores error message in aDbConnection->errormessage.
2821  */
getAllSimplePositionsMysql(Connection * aDbConnection)2822 int getAllSimplePositionsMysql(Connection *aDbConnection)
2823 {
2824   int returnvalue = 0;
2825   DataRow *p_new_station;
2826   //DataRow *p_time;
2827   int station_count = 0;  // number of new stations retrieved
2828   //unsigned long x;  // xastir coordinate for longitude
2829   //unsigned long y;  // xastir coordinate for latitide
2830   //float lat;  // latitude converted from retrieved string
2831   //float lon;  // longitude converted from retrieved string
2832   char feedback[100];
2833   //struct tm time;
2834   char sql[] = "select station, transmit_time, latitude, longitude, symbol, overlay, aprstype, origin, record_type, node_path from simpleStation order by station, transmit_time";
2835   MYSQL_RES *result;
2836   MYSQL_ROW row;
2837   int ok;   // to hold mysql_query return value
2838   ok = mysql_query(&aDbConnection->mhandle,sql);
2839   if (ok==0)
2840   {
2841     result = mysql_use_result(&aDbConnection->mhandle);
2842     if (result!=NULL)
2843     {
2844       xastir_snprintf(feedback,100,"Retrieving MySQL records\n");
2845       stderr_and_statusline(feedback);
2846       // with mysql_use_result each call to mysql_fetch_row retrieves
2847       // a row of data from the server.  Mysql_store_result might use
2848       // too much memory in retrieving a large result set all at once.
2849       while ((row = mysql_fetch_row(result)))
2850       {
2851         // retrieve data from the row
2852         // test to see if this is a valid station
2853         if (row[0]==NULL)
2854         {
2855           // station is null, skip
2856         }
2857         else
2858         {
2859           p_new_station = NULL;
2860           if (search_station_name(&p_new_station,row[0],1))
2861           {
2862             // This station is allready in present as a DataRow in the xastir db.
2863             // Add data to the station's track.
2864           }
2865           else
2866           {
2867             // This station isn't in the xastir db.
2868             // Add a datarow using the retrieved station record from the postgis database.
2869             add_simple_station(p_new_station, row[0], row[7], row[4], row[5], row[6], row[2], row[3], row[8], row[9], row[1],(char*)MYSQL_TIMEFORMAT);
2870 
2871             station_count++;
2872           }
2873         }
2874       }
2875     }
2876     else
2877     {
2878       // error fetching the result set
2879       fprintf(stderr,"mysql error: %s\n",mysql_error(&aDbConnection->mhandle));
2880       mysql_interpret_error(mysql_errno(&aDbConnection->mhandle),aDbConnection);
2881     }
2882     xastir_snprintf(feedback,100,"Retreived %d new stations from MySQL\n",station_count);
2883     stderr_and_statusline(feedback);
2884     mysql_free_result(result);
2885   }
2886   else
2887   {
2888     // query didn't execute correctly
2889     mysql_interpret_error(ok,aDbConnection);
2890   }
2891   return returnvalue;
2892 }
2893 
2894 
getAllSimplePositionsMysqlInBoundingBox(Connection * aDbConnection,char * str_e_long,char * str_w_long,char * str_n_lat,char * str_s_lat)2895 int getAllSimplePositionsMysqlInBoundingBox(Connection *aDbConnection, char *str_e_long, char *str_w_long, char *str_n_lat, char *str_s_lat)
2896 {
2897   int returnvalue = 0;
2898 
2899   return returnvalue;
2900 }
2901 
2902 
2903 
2904 /* function mysql_interpret_error()
2905  * given a mysql error code and an xastir connection, sets an appropriate
2906  * error message in the errormessage field of the connection.  Interprets
2907  * numeric error codes returned by mysql functions.
2908  * @param errorcode A result returned by a mysql function that can be
2909  * interpreted as an error code.
2910  * @param aDbConnection an xastir database connection struct describing the
2911  * connection and its current state.
2912  * Note - it is possible to give this function a connection on which an
2913  * error has not occured along with an error code.  This function does
2914  * not check the connection or assess whether an error actually occured
2915  * on it or not, it simply interprets an error code and writes the
2916  * interpretation into the connection that was passed to it.
2917  */
mysql_interpret_error(int errorcode,Connection * aDbConnection)2918 void mysql_interpret_error(int errorcode, Connection *aDbConnection)
2919 {
2920   fprintf(stderr,"Error communicating with MySQL database. Error code=%d\n",errorcode);
2921   switch (errorcode)
2922   {
2923     case CR_OUT_OF_MEMORY :
2924       // insufficient memory for query
2925       xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "MySQL: Out of Memory");
2926       // notify the connection status listener
2927       break;
2928     // mysql_query errors
2929     case CR_COMMANDS_OUT_OF_SYNC :
2930       // commands in improper order
2931       xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "MySQL: Commands out of sync");
2932       break;
2933     case CR_SERVER_GONE_ERROR :
2934       // mysql server has gone away
2935       xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "MySQL: Connection to server lost");
2936       // notify the connection status listener
2937       break;
2938     case CR_SERVER_LOST :
2939       // server connection was lost during query
2940       xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "MySQL: Connection to server lost during query");
2941       // notify the connection status listener
2942       break;
2943     case CR_UNKNOWN_ERROR :
2944       xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "MySQL: Unknown Error");
2945       break;
2946     default:
2947       xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "MySQL: Unrecognized error Code [%d]", errorcode);
2948   }
2949   fprintf(stderr,"%s\n",aDbConnection->errormessage);
2950 }
2951 
2952 #endif /* HAVE_MYSQL*/
2953 
2954 // add code for a lightweight database here
2955 
2956 
2957 
2958 
2959 
2960 
2961 
2962 #endif /* HAVE_DB*/
2963 
2964 // Functions related to GIS, but not database specific ************************
2965 
2966 
2967 
2968 
2969 /* Function  xastirCoordToLatLongPostgresPoint
2970  * converts a point in xastir coordinates to a native postgres representation
2971  * of a point using latitude and longitude in decimal degrees in the WGS84
2972  * projection EPSG:4326. Format is similar to WKT, but without leading POINT.
2973  * @param x longitude in xastir coordinates = decimal 100ths of a second.
2974  * @param y latitude in xastir coordinates = decimal 100ths of a second.
2975  * @param pointer to a char[ at least 24] string to hold point representation.
2976  * returns 1 on success, 0 on failure.
2977  */
xastirCoordToLatLongPostgresPoint(long x,long y,char * wkt)2978 int xastirCoordToLatLongPostgresPoint(long x, long y, char *wkt)
2979 {
2980   // 1 xastir coordinate = 1/100 of a second
2981   // 100*60*60 xastir coordinates (=360000 xastir coordinates) = 1 degree
2982   // 360000   xastir coordinates = 1 degree
2983   // conversion to string decimal degrees handled by utility fuctions
2984   int returnvalue = 0;  // defaults to failure
2985   float latitude;
2986   float longitude;
2987   int ok;
2988   ok = convert_from_xastir_coordinates (&longitude,&latitude, x, y);
2989   if (ok>0)
2990   {
2991     xastir_snprintf(wkt, MAX_WKT, "(%3.6f, %3.6f)", latitude, longitude);
2992     returnvalue = 1;
2993   }
2994   return returnvalue;
2995 }
2996 
2997 
2998 
2999 
3000 
3001 
3002 /* Function  xastirCoordToLatLongWKT
3003  * converts a point in xastir coordinates to a well known text string (WKT)
3004  * representation of a point using latitude and longitude in decimal degrees
3005  * in the WGS84 projection EPSG:4326
3006  * @param x longitude in xastir coordinates = decimal 100ths of a second.
3007  * @param y latitude in xastir coordinates = decimal 100ths of a second.
3008  * @param pointer to a char[29] string to hold well known text representation.
3009  * returns 1 on success, 0 on failure.
3010  */
xastirCoordToLatLongWKT(long x,long y,char * wkt)3011 int xastirCoordToLatLongWKT(long x, long y, char *wkt)
3012 {
3013   // 1 xastir coordinate = 1/100 of a second
3014   // 100*60*60 xastir coordinates (=360000 xastir coordinates) = 1 degree
3015   // 360000   xastir coordinates = 1 degree
3016   // conversion to string decimal degrees handled by utility fuctions
3017   int returnvalue = 0;  // defaults to failure
3018   float latitude;
3019   float longitude;
3020   int ok;
3021   ok = convert_from_xastir_coordinates (&longitude,&latitude, x, y);
3022   if (ok>0)
3023   {
3024     xastir_snprintf(wkt, MAX_WKT, "POINT(%3.6f %3.6f)", longitude, latitude);
3025     returnvalue = 1;
3026   }
3027   return returnvalue;
3028 }
3029 
3030 
3031 
3032 
3033 
xastirWKTPointToLongitude(char * wkt)3034 float xastirWKTPointToLongitude(char *wkt)
3035 {
3036   float returnvalue = 0.0;
3037   char temp[MAX_WKT];
3038   char *space = NULL;
3039   int x;
3040   if (wkt[0]=='P' && wkt[1]=='O' && wkt[2]=='I' && wkt[3]=='N' && wkt[4]=='T' && wkt[5]=='(')
3041   {
3042     // this is a point
3043     xastir_snprintf(temp, MAX_WKT, "%s", wkt);
3044     // truncate at the space
3045     space = strchr(temp,' ');
3046     if (space != NULL)
3047     {
3048       *space = '\0';
3049     }
3050     // remove the leading "POINT("
3051     for (x=0; x<6; x++)
3052     {
3053       temp[x]=' ';
3054     }
3055     returnvalue = atof(temp);
3056   }
3057   return returnvalue;
3058 }
3059 
3060 
3061 
3062 
3063 
xastirWKTPointToLatitude(char * wkt)3064 float xastirWKTPointToLatitude(char *wkt)
3065 {
3066   float returnvalue = 0.0;
3067   char temp[MAX_WKT];
3068   char *paren = NULL;
3069   int x;
3070   if (wkt[0]=='P' && wkt[1]=='O' && wkt[2]=='I' && wkt[3]=='N' && wkt[4]=='T' && wkt[5]=='(')
3071   {
3072     // this is a point
3073     xastir_snprintf(temp, MAX_WKT, "%s", wkt);
3074     // truncate at the trailing parenthesis
3075     paren = strchr(temp,')');
3076     if (paren != NULL)
3077     {
3078       *paren = '\0';
3079     }
3080     // convert all leading characters up to the space to spaces.
3081     for (x=0; x<(int)(strlen(temp)); x++)
3082     {
3083       if (temp[x]==' ')
3084       {
3085         x = (int)(strlen(temp));
3086       }
3087       else
3088       {
3089         temp[x] = ' ';
3090       }
3091     }
3092     returnvalue = atof(temp);
3093   }
3094   return returnvalue;
3095 
3096 }
3097 
3098