/src/MapServer/src/mappostgresql.c
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * $Id$ |
3 | | * |
4 | | * Project: MapServer |
5 | | * Purpose: Postgres CONNECTIONTYPE support. |
6 | | * Author: Mark Leslie, Refractions Research |
7 | | * |
8 | | ****************************************************************************** |
9 | | * Copyright (c) 1996-2005 Regents of the University of Minnesota. |
10 | | * |
11 | | * Permission is hereby granted, free of charge, to any person obtaining a |
12 | | * copy of this software and associated documentation files (the "Software"), |
13 | | * to deal in the Software without restriction, including without limitation |
14 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
15 | | * and/or sell copies of the Software, and to permit persons to whom the |
16 | | * Software is furnished to do so, subject to the following conditions: |
17 | | * |
18 | | * The above copyright notice and this permission notice shall be included in |
19 | | * all copies of this Software or works derived from this Software. |
20 | | * |
21 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
22 | | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
23 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
24 | | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
25 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
26 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
27 | | * DEALINGS IN THE SOFTWARE. |
28 | | ****************************************************************************/ |
29 | | |
30 | | /* $Id$ */ |
31 | | #include <assert.h> |
32 | | #include "mapserver.h" |
33 | | #include "maptime.h" |
34 | | |
35 | | #ifndef FLT_MAX |
36 | | #define FLT_MAX 25000000.0 |
37 | | #endif |
38 | | |
39 | | #ifdef USE_POSTGIS |
40 | | |
41 | | #ifndef LITTLE_ENDIAN |
42 | | #define LITTLE_ENDIAN 1 |
43 | | #endif |
44 | | #ifndef BIG_ENDIAN |
45 | | #define BIG_ENDIAN 2 |
46 | | #endif |
47 | | |
48 | | #include "libpq-fe.h" |
49 | | #include <string.h> |
50 | | #include <ctype.h> /* tolower() */ |
51 | | |
52 | | typedef struct { |
53 | | PGconn *conn; /* connection to db */ |
54 | | long row_num; /* what row is the NEXT to be read (for random access) */ |
55 | | PGresult *query_result; /* for fetching rows from the db */ |
56 | | int from_index; |
57 | | char *to_column; |
58 | | char *from_value; |
59 | | int layer_debug; /* there's no debug on the join, so use the layer */ |
60 | | } msPOSTGRESQLJoinInfo; |
61 | | |
62 | | /************************************************************************/ |
63 | | /* msPOSTGRESQLJoinConnect() */ |
64 | | /* */ |
65 | | /* Creates and populates the joininfo struct, including establishing */ |
66 | | /* a connection to the database. Since the join and layer won't always */ |
67 | | /* share connection details, there is currently no mechanism to use */ |
68 | | /* pooled connections with joins. */ |
69 | | /************************************************************************/ |
70 | | |
71 | | int msPOSTGRESQLJoinConnect(layerObj *layer, joinObj *join) { |
72 | | char *maskeddata, *temp, *sql, *column; |
73 | | char *conn_decrypted; |
74 | | int i, test; |
75 | | PGresult *query_result; |
76 | | msPOSTGRESQLJoinInfo *joininfo; |
77 | | |
78 | | if (join->joininfo) |
79 | | return MS_SUCCESS; |
80 | | |
81 | | joininfo = (msPOSTGRESQLJoinInfo *)malloc(sizeof(msPOSTGRESQLJoinInfo)); |
82 | | if (!joininfo) { |
83 | | msSetError(MS_MEMERR, "Error allocating join info struct.", |
84 | | "msPOSTGRESQLJoinConnect()"); |
85 | | return MS_FAILURE; |
86 | | } |
87 | | joininfo->conn = NULL; |
88 | | joininfo->row_num = 0; |
89 | | joininfo->query_result = NULL; |
90 | | joininfo->from_index = 0; |
91 | | joininfo->to_column = join->to; |
92 | | joininfo->from_value = NULL; |
93 | | joininfo->layer_debug = layer->debug; |
94 | | join->joininfo = joininfo; |
95 | | |
96 | | /* |
97 | | * We need three things at a minimum, the connection string, a table |
98 | | * name, and a column to join on. |
99 | | */ |
100 | | if (!join->connection) { |
101 | | msSetError(MS_QUERYERR, "No connection information provided.", |
102 | | "MSPOSTGRESQLJoinConnect()"); |
103 | | return MS_FAILURE; |
104 | | } |
105 | | if (!join->table) { |
106 | | msSetError(MS_QUERYERR, "No join table name found.", |
107 | | "msPOSTGRESQLJoinConnect()"); |
108 | | return MS_FAILURE; |
109 | | } |
110 | | if (!joininfo->to_column) { |
111 | | msSetError(MS_QUERYERR, "No join to column name found.", |
112 | | "msPOSTGRESQLJoinConnect()"); |
113 | | return MS_FAILURE; |
114 | | } |
115 | | |
116 | | /* Establish database connection */ |
117 | | conn_decrypted = msDecryptStringTokens(layer->map, join->connection); |
118 | | if (conn_decrypted != NULL) { |
119 | | joininfo->conn = PQconnectdb(conn_decrypted); |
120 | | free(conn_decrypted); |
121 | | } |
122 | | if (!joininfo->conn || PQstatus(joininfo->conn) == CONNECTION_BAD) { |
123 | | maskeddata = (char *)malloc(strlen(layer->connection) + 1); |
124 | | strcpy(maskeddata, join->connection); |
125 | | temp = strstr(maskeddata, "password="); |
126 | | if (temp) { |
127 | | temp = (char *)(temp + 9); |
128 | | while (*temp != '\0' && *temp != ' ') { |
129 | | *temp = '*'; |
130 | | temp++; |
131 | | } |
132 | | } |
133 | | msSetError(MS_QUERYERR, |
134 | | "Unable to connect to PostgreSQL using the string %s.\n Error " |
135 | | "reported: %s\n", |
136 | | "msPOSTGRESQLJoinConnect()", maskeddata, |
137 | | PQerrorMessage(joininfo->conn)); |
138 | | free(maskeddata); |
139 | | if (!joininfo->conn) { |
140 | | free(joininfo->conn); |
141 | | } |
142 | | free(joininfo); |
143 | | join->joininfo = NULL; |
144 | | return MS_FAILURE; |
145 | | } |
146 | | |
147 | | /* Determine the number and names of columns in the join table. */ |
148 | | sql = (char *)malloc(36 + strlen(join->table) + 1); |
149 | | sprintf(sql, "SELECT * FROM %s WHERE false LIMIT 0", join->table); |
150 | | |
151 | | if (joininfo->layer_debug) { |
152 | | msDebug("msPOSTGRESQLJoinConnect(): executing %s.\n", sql); |
153 | | } |
154 | | |
155 | | query_result = PQexec(joininfo->conn, sql); |
156 | | if (!query_result || PQresultStatus(query_result) != PGRES_TUPLES_OK) { |
157 | | msSetError(MS_QUERYERR, "Error determining join items: %s.", |
158 | | "msPOSTGRESQLJoinConnect()", PQerrorMessage(joininfo->conn)); |
159 | | if (query_result) { |
160 | | PQclear(query_result); |
161 | | query_result = NULL; |
162 | | } |
163 | | free(sql); |
164 | | return MS_FAILURE; |
165 | | } |
166 | | free(sql); |
167 | | join->numitems = PQnfields(query_result); |
168 | | join->items = malloc(sizeof(char *) * (join->numitems)); |
169 | | |
170 | | /* We want the join-to column to be first in the list. */ |
171 | | test = 1; |
172 | | for (i = 0; i < join->numitems; i++) { |
173 | | column = PQfname(query_result, i); |
174 | | if (strcmp(column, joininfo->to_column) != 0) { |
175 | | join->items[i + test] = (char *)malloc(strlen(column) + 1); |
176 | | strcpy(join->items[i + test], column); |
177 | | } else { |
178 | | test = 0; |
179 | | join->items[0] = (char *)malloc(strlen(column) + 1); |
180 | | strcpy(join->items[0], column); |
181 | | } |
182 | | } |
183 | | PQclear(query_result); |
184 | | query_result = NULL; |
185 | | if (test == 1) { |
186 | | msSetError(MS_QUERYERR, "Unable to find join to column: %s", |
187 | | "msPOSTGRESQLJoinConnect()", joininfo->to_column); |
188 | | return MS_FAILURE; |
189 | | } |
190 | | |
191 | | if (joininfo->layer_debug) { |
192 | | for (i = 0; i < join->numitems; i++) { |
193 | | msDebug("msPOSTGRESQLJoinConnect(): Column %d named %s\n", i, |
194 | | join->items[i]); |
195 | | } |
196 | | } |
197 | | |
198 | | /* Determine the index of the join from column. */ |
199 | | for (i = 0; i < layer->numitems; i++) { |
200 | | if (strcasecmp(layer->items[i], join->from) == 0) { |
201 | | joininfo->from_index = i; |
202 | | break; |
203 | | } |
204 | | } |
205 | | |
206 | | if (i == layer->numitems) { |
207 | | msSetError(MS_JOINERR, "Item %s not found in layer %s.", |
208 | | "msPOSTGRESQLJoinConnect()", join->from, layer->name); |
209 | | return MS_FAILURE; |
210 | | } |
211 | | |
212 | | return MS_SUCCESS; |
213 | | } |
214 | | |
215 | | /************************************************************************/ |
216 | | /* */ |
217 | | /* msPOSTGRESQLJoinPrepare() */ |
218 | | /* Sets up the joininfo to be ready to join against the given shape. */ |
219 | | /* There's not much involved here, just freeing previous results and */ |
220 | | /* resources, and setting the next value to join to. */ |
221 | | /************************************************************************/ |
222 | | |
223 | | int msPOSTGRESQLJoinPrepare(joinObj *join, shapeObj *shape) { |
224 | | |
225 | | /* We need a connection, and a shape with values to join to. */ |
226 | | msPOSTGRESQLJoinInfo *joininfo = join->joininfo; |
227 | | if (!joininfo) { |
228 | | msSetError(MS_JOINERR, "Join has not been connected.", |
229 | | "msPOSTGRESQLJoinPrepare()"); |
230 | | return MS_FAILURE; |
231 | | } |
232 | | |
233 | | if (!shape) { |
234 | | msSetError(MS_JOINERR, "Null shape provided for join.", |
235 | | "msPOSTGRESQLJoinPrepare()"); |
236 | | return MS_FAILURE; |
237 | | } |
238 | | |
239 | | if (!shape->values) { |
240 | | msSetError(MS_JOINERR, |
241 | | "Shape has no attributes. Kinda hard to join against.", |
242 | | "msPOSTGRESQLJoinPrepare()"); |
243 | | return MS_FAILURE; |
244 | | } |
245 | | joininfo->row_num = 0; |
246 | | |
247 | | /* Free the previous join value, if any. */ |
248 | | if (joininfo->from_value) { |
249 | | free(joininfo->from_value); |
250 | | } |
251 | | |
252 | | /* Free the previous results, if any. */ |
253 | | if (joininfo->query_result) { |
254 | | PQclear(joininfo->query_result); |
255 | | joininfo->query_result = NULL; |
256 | | } |
257 | | |
258 | | /* Copy the next join value from the shape. */ |
259 | | joininfo->from_value = msStrdup(shape->values[joininfo->from_index]); |
260 | | |
261 | | if (joininfo->layer_debug) { |
262 | | msDebug("msPOSTGRESQLJoinPrepare() preping for value %s.\n", |
263 | | joininfo->from_value); |
264 | | } |
265 | | |
266 | | return MS_SUCCESS; |
267 | | } |
268 | | |
269 | | /************************************************************************/ |
270 | | /* msPOSTGRESQLJoinNext() */ |
271 | | /* */ |
272 | | /* The goal here is to populate join->values with the detail of the */ |
273 | | /* join against the previously prepared shapeObj. This will be called */ |
274 | | /* only once for a one-to-one join, with msPOSTGRESQLJoinPrepare() */ |
275 | | /* being called before each. It will be called repeatedly for */ |
276 | | /* one-to-many joins, until in returns MS_DONE. To accommodate this, */ |
277 | | /* we store the next row number and query results in the joininfo and */ |
278 | | /* process the next tuple on each call. */ |
279 | | /************************************************************************/ |
280 | | int msPOSTGRESQLJoinNext(joinObj *join) { |
281 | | msPOSTGRESQLJoinInfo *joininfo = join->joininfo; |
282 | | int i, length, row_count; |
283 | | char *sql, *columns; |
284 | | |
285 | | /* We need a connection, and a join value. */ |
286 | | if (!joininfo || !joininfo->conn) { |
287 | | msSetError(MS_JOINERR, "Join has not been connected.\n", |
288 | | "msPOSTGRESQLJoinNext()"); |
289 | | return MS_FAILURE; |
290 | | } |
291 | | |
292 | | if (!joininfo->from_value) { |
293 | | msSetError(MS_JOINERR, "Join has not been prepared.\n", |
294 | | "msPOSTGRESQLJoinNext()"); |
295 | | return MS_FAILURE; |
296 | | } |
297 | | |
298 | | /* Free the previous results. */ |
299 | | if (join->values) { |
300 | | msFreeCharArray(join->values, join->numitems); |
301 | | join->values = NULL; |
302 | | } |
303 | | |
304 | | /* We only need to execute the query if no results exist. */ |
305 | | if (!joininfo->query_result) { |
306 | | /* Write the list of column names. */ |
307 | | length = 0; |
308 | | for (i = 0; i < join->numitems; i++) { |
309 | | length += 8 + strlen(join->items[i]) + 2; |
310 | | } |
311 | | if (length > 1024 * 1024) { |
312 | | msSetError(MS_MEMERR, "Too many joins.\n", "msPOSTGRESQLJoinNext()"); |
313 | | return MS_FAILURE; |
314 | | } |
315 | | |
316 | | columns = (char *)malloc(length + 1); |
317 | | if (!columns) { |
318 | | msSetError(MS_MEMERR, "Failure to malloc.\n", "msPOSTGRESQLJoinNext()"); |
319 | | return MS_FAILURE; |
320 | | } |
321 | | |
322 | | columns[0] = 0; |
323 | | for (i = 0; i < join->numitems; i++) { |
324 | | strcat(columns, "\""); |
325 | | strcat(columns, join->items[i]); |
326 | | strcat(columns, "\"::text"); |
327 | | if (i != join->numitems - 1) { |
328 | | strcat(columns, ", "); |
329 | | } |
330 | | } |
331 | | |
332 | | /* Create the query string. */ |
333 | | const size_t nSize = 26 + strlen(columns) + strlen(join->table) + |
334 | | strlen(join->to) + strlen(joininfo->from_value); |
335 | | sql = (char *)malloc(nSize); |
336 | | if (!sql) { |
337 | | msSetError(MS_MEMERR, "Failure to malloc.\n", "msPOSTGRESQLJoinNext()"); |
338 | | return MS_FAILURE; |
339 | | } |
340 | | snprintf(sql, nSize, "SELECT %s FROM %s WHERE %s = '%s'", columns, |
341 | | join->table, join->to, joininfo->from_value); |
342 | | if (joininfo->layer_debug) { |
343 | | msDebug("msPOSTGRESQLJoinNext(): executing %s.\n", sql); |
344 | | } |
345 | | |
346 | | free(columns); |
347 | | |
348 | | joininfo->query_result = PQexec(joininfo->conn, sql); |
349 | | |
350 | | if (!joininfo->query_result || |
351 | | PQresultStatus(joininfo->query_result) != PGRES_TUPLES_OK) { |
352 | | msSetError(MS_QUERYERR, "Error executing queri %s: %s\n", |
353 | | "msPOSTGRESQLJoinNext()", sql, PQerrorMessage(joininfo->conn)); |
354 | | if (joininfo->query_result) { |
355 | | PQclear(joininfo->query_result); |
356 | | joininfo->query_result = NULL; |
357 | | } |
358 | | free(sql); |
359 | | return MS_FAILURE; |
360 | | } |
361 | | free(sql); |
362 | | } |
363 | | row_count = PQntuples(joininfo->query_result); |
364 | | |
365 | | /* see if we're done processing this set */ |
366 | | if (joininfo->row_num >= row_count) { |
367 | | return (MS_DONE); |
368 | | } |
369 | | if (joininfo->layer_debug) { |
370 | | msDebug("msPOSTGRESQLJoinNext(): fetching row %ld.\n", joininfo->row_num); |
371 | | } |
372 | | |
373 | | /* Copy the resulting values into the joinObj. */ |
374 | | join->values = (char **)malloc(sizeof(char *) * join->numitems); |
375 | | for (i = 0; i < join->numitems; i++) { |
376 | | join->values[i] = |
377 | | msStrdup(PQgetvalue(joininfo->query_result, joininfo->row_num, i)); |
378 | | } |
379 | | |
380 | | joininfo->row_num++; |
381 | | |
382 | | return MS_SUCCESS; |
383 | | } |
384 | | |
385 | | /************************************************************************/ |
386 | | /* msPOSTGRESQLJoinClose() */ |
387 | | /* */ |
388 | | /* Closes the connection and frees the resources used by the joininfo. */ |
389 | | /************************************************************************/ |
390 | | |
391 | | int msPOSTGRESQLJoinClose(joinObj *join) { |
392 | | msPOSTGRESQLJoinInfo *joininfo = join->joininfo; |
393 | | |
394 | | if (!joininfo) { |
395 | | msDebug("msPOSTGRESQLJoinClose() already close or never opened.\n"); |
396 | | return MS_SUCCESS; |
397 | | } |
398 | | |
399 | | if (joininfo->query_result) { |
400 | | msDebug("msPOSTGRESQLJoinClose(): clearing query_result.\n"); |
401 | | PQclear(joininfo->query_result); |
402 | | joininfo->query_result = NULL; |
403 | | } |
404 | | |
405 | | if (joininfo->conn) { |
406 | | msDebug("msPOSTGRESQLJoinClose(): closing connection.\n"); |
407 | | PQfinish(joininfo->conn); |
408 | | joininfo->conn = NULL; |
409 | | } |
410 | | |
411 | | /* removed free(joininfo->to_column), see bug #2936 */ |
412 | | |
413 | | if (joininfo->from_value) { |
414 | | free(joininfo->from_value); |
415 | | } |
416 | | |
417 | | free(joininfo); |
418 | | join->joininfo = NULL; |
419 | | |
420 | | return MS_SUCCESS; |
421 | | } |
422 | | |
423 | | #else /* not USE_POSTGIS */ |
424 | 0 | int msPOSTGRESQLJoinConnect(layerObj *layer, joinObj *join) { |
425 | 0 | msSetError(MS_QUERYERR, "PostgreSQL support not available.", |
426 | 0 | "msPOSTGRESQLJoinConnect()"); |
427 | 0 | return MS_FAILURE; |
428 | 0 | } |
429 | | |
430 | 0 | int msPOSTGRESQLJoinPrepare(joinObj *join, shapeObj *shape) { |
431 | 0 | msSetError(MS_QUERYERR, "PostgreSQL support not available.", |
432 | 0 | "msPOSTGRESQLJoinPrepare()"); |
433 | 0 | return MS_FAILURE; |
434 | 0 | } |
435 | | |
436 | 0 | int msPOSTGRESQLJoinNext(joinObj *join) { |
437 | 0 | msSetError(MS_QUERYERR, "PostgreSQL support not available.", |
438 | 0 | "msPOSTGRESQLJoinNext()"); |
439 | 0 | return MS_FAILURE; |
440 | 0 | } |
441 | | |
442 | 0 | int msPOSTGRESQLJoinClose(joinObj *join) { |
443 | 0 | msSetError(MS_QUERYERR, "PostgreSQL support not available.", |
444 | 0 | "msPOSTGRESQLJoinClose()"); |
445 | 0 | return MS_FAILURE; |
446 | 0 | } |
447 | | #endif |