split_node_tags: New migration (in C).
[rails.git] / db / migrate / 013_populate_node_tags_and_remove_helper.c
1 #include <mysql.h>
2 #include <stdint.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6
7 static void exit_mysql_err(MYSQL *mysql) {
8   const char *err = mysql_error(mysql);
9   if (err) {
10     fprintf(stderr, "013_populate_node_tags_and_remove_helper: MySQL error: %s\n", err);
11   } else {
12     fprintf(stderr, "013_populate_node_tags_and_remove_helper: MySQL error\n");
13   }
14   abort();
15   exit(EXIT_FAILURE);
16 }
17
18 static void write_csv_col(FILE *f, const char *str, char end) {
19   char *out = (char *) malloc(2 * strlen(str) + 4);
20   char *o = out;
21   size_t len;
22
23   *(o++) = '\"';
24   for (; *str; str++) {
25     if (*str == '\0') {
26       break;
27     } else if (*str == '\"') {
28       *(o++) = '\"';
29       *(o++) = '\"';
30     } else {
31       *(o++) = *str;
32     }
33   }
34   *(o++) = '\"';
35   *(o++) = end;
36   *(o++) = '\0';
37
38   len = strlen(out);
39   if (fwrite(out, len, 1, f) != 1) {
40     perror("fwrite");
41     exit(EXIT_FAILURE);
42   }
43
44   free(out);
45 }
46
47 static void unescape(char *str) {
48   char *i = str, *o = str;
49
50   while (*i) {
51     if (*i == '\\') {
52       i++;
53       switch (*i++) {
54         case 's': *o++ = ';'; break;
55         case 'e': *o++ = '='; break;
56         case '\\': *o++ = '\\'; break;
57       }
58     } else {
59       *o++ = *i++;
60     }
61   }
62 }
63
64 static int read_node_tags(char **tags, char **k, char **v) {
65   if (!**tags) return 0;
66   char *i = strchr(*tags, ';');
67   if (!i) i = *tags + strlen(*tags);
68   char *j = strchr(*tags, '=');
69   *k = *tags;
70   if (j && j < i) {
71     *v = j + 1;
72   } else {
73     *v = i;
74   }
75   *tags = *i ? i + 1 : i;
76   *i = '\0';
77   if (j) *j = '\0';
78
79   unescape(*k);
80   unescape(*v);
81
82   return 1;
83 }
84
85 struct data {
86   MYSQL *mysql;
87   size_t version_size;
88   uint32_t *version;
89 };
90
91 static void proc_nodes(struct data *d, const char *tbl, FILE *out, FILE *out_tags, int hist) {
92   MYSQL_RES *res;
93   MYSQL_ROW row;
94   char query[256];
95
96   snprintf(query, sizeof(query),  "SELECT id, latitude, longitude, "
97       "user_id, visible, tags, timestamp, tile FROM %s", tbl);
98   if (mysql_query(d->mysql, query))
99     exit_mysql_err(d->mysql);
100
101   res = mysql_use_result(d->mysql);
102   if (!res) exit_mysql_err(d->mysql);
103
104   while ((row = mysql_fetch_row(res))) {
105     unsigned long id = strtoul(row[0], NULL, 10);
106     uint32_t version;
107
108     if (id > d->version_size) {
109       fprintf(stderr, "preallocated nodes size exceeded");
110       abort();
111     }
112
113     if (hist) {
114       version = ++(d->version[id]);
115
116       fprintf(out, "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%u\"\n",
117         row[0], row[1], row[2], row[3], row[4], row[6], row[7], version);
118     } else {
119       /*fprintf(out, "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n",
120         row[0], row[1], row[2], row[3], row[4], row[6], row[7]);*/
121     }
122
123     char *tags_it = row[5], *k, *v;
124     while (read_node_tags(&tags_it, &k, &v)) {
125       if (hist) {
126         fprintf(out_tags, "\"%s\",\"%u\",", row[0], version);
127       } else {
128         fprintf(out_tags, "\"%s\",", row[0]);
129       }
130
131       write_csv_col(out_tags, k, ',');
132       write_csv_col(out_tags, v, '\n');
133     }
134   }
135   if (mysql_errno(d->mysql)) exit_mysql_err(d->mysql);
136
137   mysql_free_result(res);
138 }
139
140 static size_t select_size(MYSQL *mysql, const char *q) {
141   MYSQL_RES *res;
142   MYSQL_ROW row;
143   size_t ret;
144
145   if (mysql_query(mysql, q))
146     exit_mysql_err(mysql);
147
148   res = mysql_store_result(mysql);
149   if (!res) exit_mysql_err(mysql);
150
151   row = mysql_fetch_row(res);
152   if (!row) exit_mysql_err(mysql);
153
154   if (row[0]) {
155     ret = strtoul(row[0], NULL, 10);
156   } else {
157     ret = 0;
158   }
159
160   mysql_free_result(res);
161
162   return ret;
163 }
164
165 static MYSQL *connect_to_mysql(char **argv) {
166   MYSQL *mysql = mysql_init(NULL);
167   if (!mysql) exit_mysql_err(mysql);
168
169   if (!mysql_real_connect(mysql, argv[1], argv[2], argv[3], argv[4],
170       argv[5][0] ? atoi(argv[5]) : 0, argv[6][0] ? argv[6] : NULL, 0))
171     exit_mysql_err(mysql);
172
173   if (mysql_set_character_set(mysql, "utf8"))
174     exit_mysql_err(mysql);
175
176   return mysql;
177 }
178
179 static void open_file(FILE **f, char *fn) {
180   *f = fopen(fn, "w+");
181   if (!*f) {
182     perror("fopen");
183     exit(EXIT_FAILURE);
184   }
185 }
186
187 int main(int argc, char **argv) {
188   size_t prefix_len;
189   FILE *current_nodes, *current_node_tags, *nodes, *node_tags;
190   char *tempfn;
191   struct data data, *d = &data;
192
193   if (argc != 8) {
194     printf("Usage: 013_populate_node_tags_and_remove_helper host user passwd database port socket prefix\n");
195     exit(EXIT_FAILURE);
196   }
197
198   d->mysql = connect_to_mysql(argv);
199
200   d->version_size = 1 + select_size(d->mysql, "SELECT max(id) FROM current_nodes");
201   d->version = malloc(sizeof(uint32_t) * d->version_size);
202
203   prefix_len = strlen(argv[7]);
204   tempfn = (char *) malloc(prefix_len + 16);
205   strcpy(tempfn, argv[7]);
206
207   strcpy(tempfn + prefix_len, "current_nodes");
208   open_file(&current_nodes, tempfn);
209
210   strcpy(tempfn + prefix_len, "current_node_tags");
211   open_file(&current_node_tags, tempfn);
212
213   strcpy(tempfn + prefix_len, "nodes");
214   open_file(&nodes, tempfn);
215
216   strcpy(tempfn + prefix_len, "node_tags");
217   open_file(&node_tags, tempfn);
218
219   free(tempfn);
220
221   proc_nodes(d, "nodes", nodes, node_tags, 1);
222   proc_nodes(d, "current_nodes", current_nodes, current_node_tags, 0);
223
224   free(d->version);
225
226   mysql_close(d->mysql);
227
228   fclose(current_nodes);
229   fclose(current_node_tags);
230   fclose(nodes);
231   fclose(node_tags);
232
233   exit(EXIT_SUCCESS);
234 }