rails_port_0.5: Merge rails_port.
[rails.git] / db / migrate / 008_remove_segments_helper.cc
1 #include <mysql.h>
2 #include <stdint.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <vector>
7 #include <list>
8 #include <sstream>
9 #include <map>
10 #include <string>
11
12 #ifdef __amd64__
13
14 #define F_U64 "%lu"
15 #define F_U32 "%u"
16
17 #else
18
19 #define F_U64 "%Lu"
20 #define F_U32 "%u"
21
22 #endif
23
24 using namespace std;
25
26 template <typename T>
27 static T parse(const char *str) {
28   istringstream in(str);
29   T t;
30   in >> t;
31   return t;
32 }
33
34 static void exit_mysql_err(MYSQL *mysql) {
35   const char *err = mysql_error(mysql);
36   if (err) {
37     fprintf(stderr, "008_remove_segments_helper: MySQL error: %s\n", err);
38   } else {
39     fprintf(stderr, "008_remove_segments_helper: MySQL error\n");
40   }
41   abort();
42   exit(EXIT_FAILURE);
43 }
44
45 static void exit_stmt_err(MYSQL_STMT *stmt) {
46   const char *err = mysql_stmt_error(stmt);
47   if (err) {
48     fprintf(stderr, "008_remove_segments_helper: MySQL stmt error: %s\n", err);
49   } else {
50     fprintf(stderr, "008_remove_segments_helper: MySQL stmt error\n");
51   }
52   abort();
53   exit(EXIT_FAILURE);
54 }
55
56 struct segment {
57   uint32_t from, to;
58 };
59
60 struct data {
61   MYSQL *mysql, *mysql2;
62
63   uint64_t seg_maxid, way_maxid;
64   uint64_t new_way_id;
65   uint64_t new_relation_id;
66
67   size_t segs_len;
68   struct segment *segs;
69   unsigned char *rem_segs;
70
71   FILE *ways, *way_nodes, *way_tags,
72     *relations, *relation_members, *relation_tags;
73 };
74
75 static uint64_t select_u64(MYSQL *mysql, const char *q) {
76   MYSQL_RES *res;
77   MYSQL_ROW row;
78   uint64_t ret;
79
80   if (mysql_query(mysql, q))
81     exit_mysql_err(mysql);
82
83   res = mysql_store_result(mysql);
84   if (!res) exit_mysql_err(mysql);
85
86   row = mysql_fetch_row(res);
87   if (!row) exit_mysql_err(mysql);
88
89   if (row[0]) {
90     ret = parse<uint64_t>(row[0]);
91   } else {
92     ret = 0;
93   }
94
95   mysql_free_result(res);
96
97   return ret;
98 }
99
100 static void find_maxids(struct data *d) {
101   d->seg_maxid = select_u64(d->mysql, "SELECT max(id) FROM current_segments");
102   d->segs_len = d->seg_maxid + 1;
103   d->way_maxid = select_u64(d->mysql, "SELECT max(id) FROM current_ways");
104   d->new_way_id = d->way_maxid + 1;
105   d->new_relation_id = select_u64(d->mysql, "SELECT max(id) FROM current_relations") + 1;
106 }
107
108 static void populate_segs(struct data *d) {
109   MYSQL_RES *res;
110   MYSQL_ROW row;
111   size_t id;
112
113   d->segs = (segment *) malloc(sizeof(struct segment) * d->segs_len);
114   memset(d->segs, 0, sizeof(struct segment) * d->segs_len);
115
116   d->rem_segs = (unsigned char *) malloc(d->segs_len);
117   memset(d->rem_segs, 0, d->segs_len);
118
119   if (mysql_query(d->mysql, "SELECT id, node_a, node_b "
120       "FROM current_segments WHERE visible"))
121     exit_mysql_err(d->mysql);
122
123   res = mysql_use_result(d->mysql);
124   if (!res) exit_mysql_err(d->mysql);
125
126   while ((row = mysql_fetch_row(res))) {
127     id = parse<size_t>(row[0]);
128     if (id >= d->segs_len) continue;
129     d->segs[id].from = parse<uint32_t>(row[1]);
130     d->segs[id].to   = parse<uint32_t>(row[2]);
131     d->rem_segs[id] = 1;
132   }
133   if (mysql_errno(d->mysql)) exit_mysql_err(d->mysql);
134
135   mysql_free_result(res);
136 }
137
138 static void write_csv_col(FILE *f, const char *str, char end) {
139   char *out = (char *) malloc(2 * strlen(str) + 4);
140   char *o = out;
141   size_t len;
142
143   *(o++) = '\"';
144   for (; *str; str++) {
145     if (*str == '\0') {
146       break;
147     } else if (*str == '\"') {
148       *(o++) = '\"';
149       *(o++) = '\"';
150     } else {
151       *(o++) = *str;
152     }
153   }
154   *(o++) = '\"';
155   *(o++) = end;
156   *(o++) = '\0';
157
158   len = strlen(out);
159   if (fwrite(out, len, 1, f) != 1) {
160     perror("fwrite");
161     exit(EXIT_FAILURE);
162   }
163
164   free(out);
165 }
166
167 static void convert_ways(struct data *d) {
168   MYSQL_RES *res;
169   MYSQL_ROW row;
170   MYSQL_STMT *load_segs, *load_tags;
171   const char
172     load_segs_stmt[] = "SELECT segment_id FROM current_way_segments "
173       "WHERE id = ? ORDER BY sequence_id",
174     load_tags_stmt[] = "SELECT k, v FROM current_way_tags WHERE id = ?";
175   char *k, *v;
176   const size_t max_tag_len = 1 << 16;
177   long long mysql_id, mysql_seg_id;
178   unsigned long res_len;
179   my_bool res_error;
180   MYSQL_BIND bind[1], seg_bind[1], tag_bind[2];
181
182   /* F***ing libmysql only support fixed size buffers for string results of
183    * prepared statements.  So allocate 65k for the tag key and the tag value
184    * and hope it'll suffice. */
185   k = (char *) malloc(max_tag_len);
186   v = (char *) malloc(max_tag_len);
187
188   load_segs = mysql_stmt_init(d->mysql2);
189   if (!load_segs) exit_mysql_err(d->mysql2);
190   if (mysql_stmt_prepare(load_segs, load_segs_stmt, sizeof(load_segs_stmt)))
191     exit_stmt_err(load_segs);
192
193   memset(bind, 0, sizeof(bind));
194   bind[0].buffer_type = MYSQL_TYPE_LONGLONG;
195   bind[0].buffer = (char *) &mysql_id;
196   bind[0].is_null = 0;
197   bind[0].length = 0;
198   if (mysql_stmt_bind_param(load_segs, bind))
199     exit_stmt_err(load_segs);
200
201   memset(seg_bind, 0, sizeof(seg_bind));
202   seg_bind[0].buffer_type = MYSQL_TYPE_LONGLONG;
203   seg_bind[0].buffer = (char *) &mysql_seg_id;
204   seg_bind[0].is_null = 0;
205   seg_bind[0].length = 0;
206   seg_bind[0].error = &res_error;
207   if (mysql_stmt_bind_result(load_segs, seg_bind))
208     exit_stmt_err(load_segs);
209
210   load_tags = mysql_stmt_init(d->mysql2);
211   if (!load_tags) exit_mysql_err(d->mysql2);
212   if (mysql_stmt_prepare(load_tags, load_tags_stmt, sizeof(load_tags_stmt)))
213     exit_stmt_err(load_tags);
214
215   memset(bind, 0, sizeof(bind));
216   bind[0].buffer_type = MYSQL_TYPE_LONGLONG;
217   bind[0].buffer = (char *) &mysql_id;
218   bind[0].is_null = 0;
219   bind[0].length = 0;
220
221   if (mysql_stmt_bind_param(load_tags, bind))
222     exit_stmt_err(load_tags);
223
224   memset(tag_bind, 0, sizeof(tag_bind));
225   tag_bind[0].buffer_type = MYSQL_TYPE_STRING;
226   tag_bind[0].buffer = k;
227   tag_bind[0].is_null = 0;
228   tag_bind[0].length = &res_len;
229   tag_bind[0].error = &res_error;
230   tag_bind[0].buffer_length = max_tag_len;
231   tag_bind[1].buffer_type = MYSQL_TYPE_STRING;
232   tag_bind[1].buffer = v;
233   tag_bind[1].is_null = 0;
234   tag_bind[1].length = &res_len;
235   tag_bind[1].error = &res_error;
236   tag_bind[1].buffer_length = max_tag_len;
237   if (mysql_stmt_bind_result(load_tags, tag_bind))
238     exit_stmt_err(load_tags);
239
240   if (mysql_query(d->mysql, "SELECT id, user_id, timestamp "
241       "FROM current_ways WHERE visible"))
242     exit_mysql_err(d->mysql);
243
244   res = mysql_use_result(d->mysql);
245   if (!res) exit_mysql_err(d->mysql);
246
247   while ((row = mysql_fetch_row(res))) {
248     uint64_t id;
249     const char *user_id, *timestamp;
250
251     id = parse<uint64_t>(row[0]);
252     user_id = row[1];
253     timestamp = row[2];
254
255     mysql_id = (long long) id;
256
257     if (mysql_stmt_execute(load_segs))
258       exit_stmt_err(load_segs);
259
260     if (mysql_stmt_store_result(load_segs))
261       exit_stmt_err(load_segs);
262
263     list<segment> segs;
264     while (!mysql_stmt_fetch(load_segs)) {
265       if (((uint64_t) mysql_seg_id) >= d->segs_len) continue;
266       segs.push_back(d->segs[mysql_seg_id]);
267       d->rem_segs[mysql_seg_id] = 0;
268     }
269
270     list<list<uint32_t> > node_lists;
271     while (segs.size()) {
272       list<uint32_t> node_list;
273       node_list.push_back(segs.front().from);
274       node_list.push_back(segs.front().to);
275       segs.pop_front();
276       while (true) {
277         bool found = false;
278         for (list<segment>::iterator it = segs.begin();
279             it != segs.end(); ) {
280           if (it->from == node_list.back()) {
281             node_list.push_back(it->to);
282             segs.erase(it++);
283             found = true;
284           } else if (it->to == node_list.front()) {
285             node_list.insert(node_list.begin(), it->from);
286             segs.erase(it++);
287             found = true;
288           } else {
289             ++it;
290           }
291         }
292         if (!found) break;
293       }
294       node_lists.push_back(node_list);
295     }
296
297     vector<uint64_t> ids; ids.reserve(node_lists.size());
298     bool orig_id_used = false;
299     for (list<list<uint32_t> >::iterator it = node_lists.begin();
300         it != node_lists.end(); ++it) {
301       uint64_t way_id;
302       int sid;
303       if (orig_id_used) {
304         way_id = d->new_way_id++;
305       } else {
306         way_id = id;
307         orig_id_used = true;
308       }
309       ids.push_back(way_id);
310
311       fprintf(d->ways, "\"" F_U64 "\",", way_id);
312       write_csv_col(d->ways, user_id, ',');
313       write_csv_col(d->ways, timestamp, '\n');
314
315       sid = 1;
316       for (list<uint32_t>::iterator nit = it->begin();
317           nit != it->end(); ++nit) {
318         fprintf(d->way_nodes, "\"" F_U64 "\",\"" F_U32 "\",\"%i\"\n", way_id, *nit, sid++);
319       }
320     }
321
322     if (mysql_stmt_execute(load_tags))
323       exit_stmt_err(load_tags);
324
325     if (mysql_stmt_store_result(load_tags))
326       exit_stmt_err(load_tags);
327
328     bool multiple_parts = ids.size() > 1,
329       create_multipolygon = false;
330
331     while (!mysql_stmt_fetch(load_tags)) {
332       if (multiple_parts && !create_multipolygon) {
333         if (!strcmp(k, "natural")) {
334           if (strcmp(v, "coastline")) {
335             create_multipolygon = true;
336           }
337         } else if (!strcmp(k, "waterway")) {
338           if (!strcmp(v, "riverbank")) {
339             create_multipolygon = true;
340           }
341         } else if (!strcmp(k, "leisure") || !strcmp(k, "landuse")
342             || !strcmp(k, "sport") || !strcmp(k, "amenity")
343             || !strcmp(k, "tourism") || !strcmp(k, "building")) {
344           create_multipolygon = true;
345         }
346       }
347
348       for (vector<uint64_t>::iterator it = ids.begin();
349           it != ids.end(); ++it) {
350         fprintf(d->way_tags, "\"" F_U64 "\",", *it);
351         write_csv_col(d->way_tags, k, ',');
352         write_csv_col(d->way_tags, v, '\n');
353       }
354     }
355
356     if (multiple_parts && create_multipolygon) {
357       uint64_t ent_id = d->new_relation_id++;
358
359       fprintf(d->relations, "\"" F_U64 "\",", ent_id);
360       write_csv_col(d->relations, user_id, ',');
361       write_csv_col(d->relations, timestamp, '\n');
362
363       fprintf(d->relation_tags,
364         "\"" F_U64 "\",\"type\",\"multipolygon\"\n", ent_id);
365
366       for (vector<uint64_t>::iterator it = ids.begin();
367           it != ids.end(); ++it) {
368         fprintf(d->relation_members,
369           "\"" F_U64 "\",\"way\",\"" F_U64 "\",\"\"\n", ent_id, *it);
370       }
371     }
372   }
373   if (mysql_errno(d->mysql)) exit_stmt_err(load_tags);
374
375   mysql_stmt_close(load_segs);
376   mysql_stmt_close(load_tags);
377
378   mysql_free_result(res);
379   free(k);
380   free(v);
381 }
382
383 static int read_seg_tags(char **tags, char **k, char **v) {
384   if (!**tags) return 0;
385   char *i = strchr(*tags, ';');
386   if (!i) i = *tags + strlen(*tags);
387   char *j = strchr(*tags, '=');
388   *k = *tags;
389   if (j && j < i) {
390     *v = j + 1;
391   } else {
392     *v = i;
393   }
394   *tags = *i ? i + 1 : i;
395   *i = '\0';
396   if (j) *j = '\0';
397   return 1;
398 }
399
400 static void mark_tagged_segs(struct data *d) {
401   MYSQL_RES *res;
402   MYSQL_ROW row;
403   MYSQL_STMT *way_tags;
404   const char
405     way_tags_stmt[] = "SELECT k, v FROM current_way_segments INNER JOIN "
406       "current_way_tags ON current_way_segments.id = "
407       "current_way_tags.id WHERE segment_id = ?";
408   char *wk, *wv;
409   const size_t max_tag_len = 1 << 16;
410   long long mysql_seg_id;
411   unsigned long res_len;
412   my_bool res_error;
413   MYSQL_BIND in_bind[1], out_bind[1];
414
415   /* F***ing libmysql only support fixed size buffers for string results of
416    * prepared statements.  So allocate 65k for the tag key and the tag value
417    * and hope it'll suffice. */
418   wk = (char *) malloc(max_tag_len);
419   wv = (char *) malloc(max_tag_len);
420
421   way_tags = mysql_stmt_init(d->mysql2);
422   if (!way_tags) exit_mysql_err(d->mysql2);
423   if (mysql_stmt_prepare(way_tags, way_tags_stmt, sizeof(way_tags_stmt)))
424     exit_stmt_err(way_tags);
425
426   memset(in_bind, 0, sizeof(in_bind));
427   in_bind[0].buffer_type = MYSQL_TYPE_LONGLONG;
428   in_bind[0].buffer = (char *) &mysql_seg_id;
429   in_bind[0].is_null = 0;
430   in_bind[0].length = 0;
431
432   if (mysql_stmt_bind_param(way_tags, in_bind))
433     exit_stmt_err(way_tags);
434
435   memset(out_bind, 0, sizeof(out_bind));
436   out_bind[0].buffer_type = MYSQL_TYPE_STRING;
437   out_bind[0].buffer = wk;
438   out_bind[0].is_null = 0;
439   out_bind[0].length = &res_len;
440   out_bind[0].error = &res_error;
441   out_bind[0].buffer_length = max_tag_len;
442   out_bind[1].buffer_type = MYSQL_TYPE_STRING;
443   out_bind[1].buffer = wv;
444   out_bind[1].is_null = 0;
445   out_bind[1].length = &res_len;
446   out_bind[1].error = &res_error;
447   out_bind[1].buffer_length = max_tag_len;
448   if (mysql_stmt_bind_result(way_tags, out_bind))
449     exit_stmt_err(way_tags);
450
451   if (mysql_query(d->mysql, "SELECT id, tags FROM current_segments "
452       "WHERE visible && tags != '' && tags != 'created_by=JOSM'"))
453     exit_mysql_err(d->mysql);
454
455   res = mysql_use_result(d->mysql);
456   if (!res) exit_mysql_err(d->mysql);
457
458   while ((row = mysql_fetch_row(res))) {
459     size_t id = parse<size_t>(row[0]);
460     if (d->rem_segs[id]) continue;
461
462     map<string, string> interesting_tags;
463
464     char *tags_it = row[1], *k, *v;
465     while (read_seg_tags(&tags_it, &k, &v)) {
466       if (strcmp(k, "created_by") &&
467           strcmp(k, "tiger:county") &&
468           strcmp(k, "tiger:upload_uuid") &&
469           strcmp(k, "converted_by") &&
470           (strcmp(k, "width") || strcmp(v, "4")) &&
471           (strcmp(k, "natural") || strcmp(v, "coastline")) &&
472           (strcmp(k, "source") || strncmp(v, "PGS", 3))) {
473         interesting_tags.insert(make_pair(string(k), string(v)));
474       }
475     }
476
477     if (interesting_tags.size() == 0) continue;
478
479     mysql_seg_id = id;
480
481     if (mysql_stmt_execute(way_tags))
482       exit_stmt_err(way_tags);
483
484     if (mysql_stmt_store_result(way_tags))
485       exit_stmt_err(way_tags);
486
487     while (!mysql_stmt_fetch(way_tags)) {
488       for (map<string, string>::iterator it = interesting_tags.find(wk);
489           it != interesting_tags.end() && it->first == wk; ++it) {
490         if (it->second == wv) {
491           interesting_tags.erase(it);
492           break;
493         }
494       }
495     }
496
497     if (interesting_tags.size() > 0) {
498       d->rem_segs[id] = 1;
499     }
500   }
501
502   mysql_free_result(res);
503
504   mysql_stmt_close(way_tags);
505   free(wk);
506   free(wv);
507 }
508
509 static void convert_remaining_segs(struct data *d) {
510   MYSQL_STMT *load_seg;
511   MYSQL_BIND args[1], res[3];
512   const size_t max_tag_len = 1 << 16;
513   char *tags, timestamp[100];
514   char *k, *v;
515   int user_id;
516   long long mysql_id;
517   unsigned long res_len;
518   my_bool res_error;
519   const char load_seg_stmt[] =
520     "SELECT user_id, tags, CAST(timestamp AS CHAR) FROM current_segments "
521     "WHERE visible && id = ?";
522
523   tags = (char *) malloc(max_tag_len);
524
525   load_seg = mysql_stmt_init(d->mysql);
526   if (!load_seg) exit_mysql_err(d->mysql);
527   if (mysql_stmt_prepare(load_seg, load_seg_stmt, sizeof(load_seg_stmt)))
528     exit_stmt_err(load_seg);
529
530   memset(args, 0, sizeof(args));
531   args[0].buffer_type = MYSQL_TYPE_LONGLONG;
532   args[0].buffer = (char *) &mysql_id;
533   args[0].is_null = 0;
534   args[0].length = 0;
535   if (mysql_stmt_bind_param(load_seg, args))
536     exit_stmt_err(load_seg);
537
538   memset(res, 0, sizeof(res));
539   res[0].buffer_type = MYSQL_TYPE_LONG;
540   res[0].buffer = (char *) &user_id;
541   res[0].is_null = 0;
542   res[0].length = 0;
543   res[0].error = &res_error;
544   res[1].buffer_type = MYSQL_TYPE_STRING;
545   res[1].buffer = tags;
546   res[1].is_null = 0;
547   res[1].length = &res_len;
548   res[1].error = &res_error;
549   res[1].buffer_length = max_tag_len;
550   res[2].buffer_type = MYSQL_TYPE_STRING;
551   res[2].buffer = timestamp;
552   res[2].is_null = 0;
553   res[2].length = &res_len;
554   res[2].error = &res_error;
555   res[2].buffer_length = sizeof(timestamp);
556   if (mysql_stmt_bind_result(load_seg, res))
557     exit_stmt_err(load_seg);
558
559   for (size_t seg_id = 0; seg_id < d->segs_len; seg_id++) {
560     if (!d->rem_segs[seg_id]) continue;
561     segment seg = d->segs[seg_id];
562
563     mysql_id = seg_id;
564     if (mysql_stmt_execute(load_seg)) exit_stmt_err(load_seg);
565     if (mysql_stmt_store_result(load_seg)) exit_stmt_err(load_seg);
566
567     while (!mysql_stmt_fetch(load_seg)) {
568       uint64_t way_id = d->new_way_id++;
569
570       fprintf(d->ways, "\"" F_U64 "\",\"%i\",", way_id, user_id);
571       write_csv_col(d->ways, timestamp, '\n');
572
573       fprintf(d->way_nodes, "\"" F_U64 "\",\"" F_U32 "\",\"%i\"\n", way_id, seg.from, 1);
574       fprintf(d->way_nodes, "\"" F_U64 "\",\"" F_U32 "\",\"%i\"\n", way_id, seg.to, 2);
575
576       char *tags_it = tags;
577       while (read_seg_tags(&tags_it, &k, &v)) {
578         fprintf(d->way_tags, "\"" F_U64 "\",", way_id);
579         write_csv_col(d->way_tags, k, ',');
580         write_csv_col(d->way_tags, v, '\n');
581       }
582     }
583   }
584
585   mysql_stmt_close(load_seg);
586
587   free(tags);
588 }
589
590 static MYSQL *connect_to_mysql(char **argv) {
591   MYSQL *mysql = mysql_init(NULL);
592   if (!mysql) exit_mysql_err(mysql);
593
594   if (!mysql_real_connect(mysql, argv[1], argv[2], argv[3], argv[4],
595       argv[5][0] ? atoi(argv[5]) : 0, argv[6][0] ? argv[6] : NULL, 0))
596     exit_mysql_err(mysql);
597
598   if (mysql_set_character_set(mysql, "utf8"))
599     exit_mysql_err(mysql);
600
601   return mysql;
602 }
603
604 static void open_file(FILE **f, char *fn) {
605   *f = fopen(fn, "w+");
606   if (!*f) {
607     perror("fopen");
608     exit(EXIT_FAILURE);
609   }
610 }
611
612 int main(int argc, char **argv) {
613   struct data data;
614   struct data *d = &data;
615   size_t prefix_len;
616   char *tempfn;
617
618   if (argc != 8) {
619     printf("Usage: 008_remove_segments_helper host user passwd database port socket prefix\n");
620     exit(EXIT_FAILURE);
621   }
622
623   d->mysql = connect_to_mysql(argv);
624   d->mysql2 = connect_to_mysql(argv);
625
626   prefix_len = strlen(argv[7]);
627   tempfn = (char *) malloc(prefix_len + 15);
628   strcpy(tempfn, argv[7]);
629
630   strcpy(tempfn + prefix_len, "ways");
631   open_file(&d->ways, tempfn);
632
633   strcpy(tempfn + prefix_len, "way_nodes");
634   open_file(&d->way_nodes, tempfn);
635
636   strcpy(tempfn + prefix_len, "way_tags");
637   open_file(&d->way_tags, tempfn);
638
639   strcpy(tempfn + prefix_len, "relations");
640   open_file(&d->relations, tempfn);
641
642   strcpy(tempfn + prefix_len, "relation_members");
643   open_file(&d->relation_members, tempfn);
644
645   strcpy(tempfn + prefix_len, "relation_tags");
646   open_file(&d->relation_tags, tempfn);
647
648   free(tempfn);
649
650   find_maxids(d);
651   populate_segs(d);
652   convert_ways(d);
653   mark_tagged_segs(d);
654   convert_remaining_segs(d);
655
656   mysql_close(d->mysql);
657   mysql_close(d->mysql2);
658
659   fclose(d->ways);
660   fclose(d->way_nodes);
661   fclose(d->way_tags);
662
663   fclose(d->relations);
664   fclose(d->relation_members);
665   fclose(d->relation_tags);
666
667   free(d->segs);
668   free(d->rem_segs);
669
670   exit(EXIT_SUCCESS);
671 }