Branch data Line data Source code
1 : : /*
2 : : * Copyright (C) 2021 James Westman <james@jwestman.net>
3 : : *
4 : : * This library is free software; you can redistribute it and/or
5 : : * modify it under the terms of the GNU Lesser General Public
6 : : * License as published by the Free Software Foundation; either
7 : : * version 2.1 of the License, or (at your option) any later version.
8 : : *
9 : : * This library is distributed in the hope that it will be useful,
10 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 : : * Lesser General Public License for more details.
13 : : *
14 : : * You should have received a copy of the GNU Lesser General Public
15 : : * License along with this library; if not, see <https://www.gnu.org/licenses/>.
16 : : */
17 : :
18 : : #include "shumate-vector-render-scope-private.h"
19 : :
20 : : enum {
21 : : MOVE_TO = 1,
22 : : LINE_TO = 2,
23 : : CLOSE_PATH = 7,
24 : : };
25 : :
26 : : static int
27 : 69 : zigzag (guint value)
28 : : {
29 : 69 : return (value >> 1) ^ (-(value & 1));
30 : : }
31 : :
32 : : static void
33 : 15 : apply_transforms (ShumateVectorRenderScope *self,
34 : : float extent,
35 : : float *x,
36 : : float *y)
37 : : {
38 : 15 : *x = ((*x / extent) - self->overzoom_x) * self->overzoom_scale;
39 : 15 : *y = ((*y / extent) - self->overzoom_y) * self->overzoom_scale;
40 : 0 : }
41 : :
42 : : /* Draws the current feature as a path onto the scope's cairo context. */
43 : : void
44 : 12 : shumate_vector_render_scope_exec_geometry (ShumateVectorRenderScope *self)
45 : : {
46 : 12 : VectorTile__Tile__Feature *feature = shumate_vector_reader_iter_get_feature_struct (self->reader);
47 : :
48 [ + - ]: 12 : g_return_if_fail (feature != NULL);
49 : :
50 : 12 : cairo_new_path (self->cr);
51 : 12 : cairo_move_to (self->cr, 0, 0);
52 : :
53 [ + + ]: 42 : for (int i = 0; i < feature->n_geometry; i ++)
54 : : {
55 : 30 : int cmd = feature->geometry[i];
56 : 30 : double dx, dy;
57 : :
58 : : /* See https://github.com/mapbox/vector-tile-spec/tree/master/2.1#43-geometry-encoding */
59 : 30 : int op = cmd & 0x7;
60 : 30 : int repeat = cmd >> 3;
61 : :
62 [ + + ]: 90 : for (int j = 0; j < repeat; j ++)
63 : : {
64 [ + + + - ]: 60 : switch (op) {
65 : 12 : case MOVE_TO:
66 [ - + ]: 12 : g_return_if_fail (i + 2 < feature->n_geometry);
67 : 12 : dx = zigzag (feature->geometry[++i]);
68 : 12 : dy = zigzag (feature->geometry[++i]);
69 : 12 : cairo_rel_move_to (self->cr, dx, dy);
70 : 12 : break;
71 : 42 : case LINE_TO:
72 [ - + ]: 42 : g_return_if_fail (i + 2 < feature->n_geometry);
73 : 42 : dx = zigzag (feature->geometry[++i]);
74 : 42 : dy = zigzag (feature->geometry[++i]);
75 : 42 : cairo_rel_line_to (self->cr, dx, dy);
76 : 42 : break;
77 : 6 : case CLOSE_PATH:
78 : 6 : cairo_get_current_point (self->cr, &dx, &dy);
79 : 6 : cairo_close_path (self->cr);
80 : 6 : cairo_move_to (self->cr, dx, dy);
81 : 6 : break;
82 : 0 : default:
83 : 60 : g_assert_not_reached ();
84 : : }
85 : : }
86 : : }
87 : : }
88 : :
89 : :
90 : : GPtrArray *
91 : 15 : shumate_vector_render_scope_get_geometry (ShumateVectorRenderScope *self)
92 : : {
93 : 15 : GPtrArray *lines = g_ptr_array_new_with_free_func ((GDestroyNotify)shumate_vector_line_string_free);
94 : 15 : ShumateVectorLineString *current_line = NULL;
95 : 15 : VectorTile__Tile__Feature *feature = shumate_vector_reader_iter_get_feature_struct (self->reader);
96 : 15 : VectorTile__Tile__Layer *layer = shumate_vector_reader_iter_get_layer_struct (self->reader);
97 : 15 : float x = 0, y = 0;
98 : 15 : float x_tf, y_tf;
99 : :
100 [ - + ]: 15 : g_return_val_if_fail (feature != NULL, NULL);
101 : :
102 [ + + ]: 30 : for (int i = 0; i < feature->n_geometry; i ++)
103 : : {
104 : 15 : int cmd = feature->geometry[i];
105 : 15 : double start_x = 0, start_y = 0;
106 : :
107 : 15 : int op = cmd & 0x7;
108 : 15 : int repeat = cmd >> 3;
109 : :
110 [ - + ]: 15 : if (current_line != NULL)
111 : 0 : current_line->points = g_realloc (current_line->points, (repeat + current_line->n_points) * sizeof (ShumateVectorPoint));
112 : :
113 [ + + ]: 30 : for (int j = 0; j < repeat; j ++)
114 : : {
115 [ + - - - ]: 15 : switch (op) {
116 : 15 : case MOVE_TO:
117 [ - + ]: 15 : g_return_val_if_fail (i + 2 < feature->n_geometry, NULL);
118 : :
119 [ - + ]: 15 : if (current_line != NULL)
120 : 0 : g_ptr_array_add (lines, current_line);
121 : :
122 : 15 : current_line = g_new (ShumateVectorLineString, 1);
123 : 15 : current_line->points = g_new (ShumateVectorPoint, 1);
124 : 15 : current_line->n_points = 1;
125 : :
126 : 15 : x += zigzag (feature->geometry[++i]);
127 : 15 : y += zigzag (feature->geometry[++i]);
128 : :
129 : 15 : x_tf = x;
130 : 15 : y_tf = y;
131 : 15 : apply_transforms (self, layer->extent, &x_tf, &y_tf);
132 : :
133 : 15 : start_x = x_tf;
134 : 15 : start_y = y_tf;
135 : :
136 : 15 : current_line->points[0] = (ShumateVectorPoint) {
137 : : .x = x_tf,
138 : : .y = y_tf,
139 : : };
140 : 15 : break;
141 : 0 : case LINE_TO:
142 [ # # ]: 0 : g_return_val_if_fail (i + 2 < feature->n_geometry, NULL);
143 [ # # ]: 0 : g_return_val_if_fail (current_line != NULL, NULL);
144 : :
145 : 0 : x += zigzag (feature->geometry[++i]);
146 : 0 : y += zigzag (feature->geometry[++i]);
147 : :
148 : 0 : x_tf = x;
149 : 0 : y_tf = y;
150 : 0 : apply_transforms (self, layer->extent, &x_tf, &y_tf);
151 : :
152 : 0 : current_line->points[current_line->n_points++] = (ShumateVectorPoint) {
153 : : .x = x_tf,
154 : : .y = y_tf,
155 : : };
156 : 0 : break;
157 : 0 : case CLOSE_PATH:
158 [ # # ]: 0 : g_return_val_if_fail (current_line != NULL, NULL);
159 : :
160 : 0 : current_line->points[current_line->n_points++] = (ShumateVectorPoint) {
161 : : .x = start_x,
162 : : .y = start_y,
163 : : };
164 : 0 : break;
165 : 0 : default:
166 : 0 : g_assert_not_reached ();
167 : : }
168 : : }
169 : : }
170 : :
171 [ + - ]: 15 : if (current_line != NULL)
172 : 15 : g_ptr_array_add (lines, current_line);
173 : :
174 : : return lines;
175 : : }
176 : :
177 : :
178 : : void
179 : 0 : shumate_vector_render_scope_get_bounds (ShumateVectorRenderScope *self,
180 : : float *min_x,
181 : : float *min_y,
182 : : float *max_x,
183 : : float *max_y)
184 : : {
185 : 0 : VectorTile__Tile__Layer *layer = shumate_vector_reader_iter_get_layer_struct (self->reader);
186 : 0 : VectorTile__Tile__Feature *feature = shumate_vector_reader_iter_get_feature_struct (self->reader);
187 : 0 : double x = 0, y = 0;
188 : :
189 : 0 : *min_x = G_MAXFLOAT;
190 : 0 : *min_y = G_MAXFLOAT;
191 : 0 : *max_x = G_MINFLOAT;
192 : 0 : *max_y = G_MINFLOAT;
193 : :
194 [ # # ]: 0 : g_return_if_fail (feature != NULL);
195 : :
196 [ # # ]: 0 : for (int i = 0; i < feature->n_geometry; i ++)
197 : : {
198 : 0 : int cmd = feature->geometry[i];
199 : :
200 : : /* See https://github.com/mapbox/vector-tile-spec/tree/master/2.1#43-geometry-encoding */
201 : 0 : int op = cmd & 0x7;
202 : 0 : int repeat = cmd >> 3;
203 : :
204 [ # # ]: 0 : for (int j = 0; j < repeat; j ++)
205 : : {
206 [ # # # # ]: 0 : switch (op) {
207 : 0 : case 1:
208 [ # # ]: 0 : g_return_if_fail (i + 2 < feature->n_geometry);
209 : 0 : x += zigzag (feature->geometry[++i]);
210 : 0 : y += zigzag (feature->geometry[++i]);
211 : 0 : break;
212 : 0 : case 2:
213 [ # # ]: 0 : g_return_if_fail (i + 2 < feature->n_geometry);
214 : 0 : x += zigzag (feature->geometry[++i]);
215 : 0 : y += zigzag (feature->geometry[++i]);
216 : 0 : break;
217 : : case 7:
218 : : break;
219 : 0 : default:
220 : 0 : g_assert_not_reached ();
221 : : }
222 : :
223 [ # # ]: 0 : *min_x = MIN (*min_x, x);
224 [ # # ]: 0 : *min_y = MIN (*min_y, y);
225 [ # # ]: 0 : *max_x = MAX (*max_x, x);
226 [ # # ]: 0 : *max_y = MAX (*max_y, y);
227 : : }
228 : : }
229 : :
230 : 0 : apply_transforms (self, layer->extent, min_x, min_y);
231 : 0 : apply_transforms (self, layer->extent, max_x, max_y);
232 : : }
233 : :
234 : :
235 : : ShumateVectorGeometryType
236 : 30 : shumate_vector_render_scope_get_geometry_type (ShumateVectorRenderScope *self)
237 : : {
238 : 30 : VectorTile__Tile__Feature *feature = shumate_vector_reader_iter_get_feature_struct (self->reader);
239 [ + - ]: 30 : g_return_val_if_fail (feature != NULL, 0);
240 : 30 : return (ShumateVectorGeometryType) feature->type;
241 : : }
242 : :
243 : : void
244 : 0 : shumate_vector_render_scope_get_geometry_center (ShumateVectorRenderScope *self,
245 : : double *x,
246 : : double *y)
247 : : {
248 : 0 : float min_x, min_y, max_x, max_y;
249 : 0 : shumate_vector_render_scope_get_bounds (self, &min_x, &min_y, &max_x, &max_y);
250 : 0 : *x = (min_x + max_x) / 2.0;
251 : 0 : *y = (min_y + max_y) / 2.0;
252 : 0 : }
253 : :
254 : : static void
255 : 48 : convert_vector_value (VectorTile__Tile__Value *v, ShumateVectorValue *value)
256 : : {
257 [ + + ]: 48 : if (v->has_int_value)
258 : 3 : shumate_vector_value_set_number (value, v->int_value);
259 [ - + ]: 45 : else if (v->has_uint_value)
260 : 0 : shumate_vector_value_set_number (value, v->uint_value);
261 [ - + ]: 45 : else if (v->has_sint_value)
262 : 0 : shumate_vector_value_set_number (value, v->sint_value);
263 [ - + ]: 45 : else if (v->has_float_value)
264 : 0 : shumate_vector_value_set_number (value, v->float_value);
265 [ - + ]: 45 : else if (v->has_double_value)
266 : 0 : shumate_vector_value_set_number (value, v->double_value);
267 [ - + ]: 45 : else if (v->has_bool_value)
268 : 0 : shumate_vector_value_set_boolean (value, v->bool_value);
269 [ + - ]: 45 : else if (v->string_value != NULL)
270 : 45 : shumate_vector_value_set_string (value, v->string_value);
271 : : else
272 : 0 : shumate_vector_value_unset (value);
273 : 48 : }
274 : :
275 : : void
276 : 54 : shumate_vector_render_scope_get_variable (ShumateVectorRenderScope *self, const char *variable, ShumateVectorValue *value)
277 : : {
278 : 54 : VectorTile__Tile__Layer *layer = shumate_vector_reader_iter_get_layer_struct (self->reader);
279 : 54 : VectorTile__Tile__Feature *feature = shumate_vector_reader_iter_get_feature_struct (self->reader);
280 : :
281 [ + + ]: 69 : for (int i = 0; i + 1 < feature->n_tags; i += 2)
282 : : {
283 [ + + ]: 54 : if (strcmp (layer->keys[feature->tags[i]], variable) == 0)
284 : : {
285 : 39 : convert_vector_value (layer->values[feature->tags[i + 1]], value);
286 : 39 : return;
287 : : }
288 : : }
289 : :
290 : 15 : shumate_vector_value_unset (value);
291 : : }
292 : :
293 : :
294 : : GHashTable *
295 : 15 : shumate_vector_render_scope_create_tag_table (ShumateVectorRenderScope *self)
296 : : {
297 : 15 : g_autoptr(GHashTable) tags = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
298 : 30 : g_auto(ShumateVectorValue) value = SHUMATE_VECTOR_VALUE_INIT;
299 : 15 : VectorTile__Tile__Layer *layer = shumate_vector_reader_iter_get_layer_struct (self->reader);
300 : 15 : VectorTile__Tile__Feature *feature = shumate_vector_reader_iter_get_feature_struct (self->reader);
301 : :
302 [ - + ]: 15 : for (int i = 1; i < feature->n_tags; i += 2)
303 : : {
304 : 0 : g_auto(ShumateVectorValue) value = SHUMATE_VECTOR_VALUE_INIT;
305 : 0 : int key = feature->tags[i - 1];
306 : 0 : int val = feature->tags[i];
307 : :
308 [ # # # # ]: 0 : if (key >= layer->n_keys || val >= layer->n_values)
309 : 0 : continue;
310 : :
311 : 0 : convert_vector_value (layer->values[val], &value);
312 [ # # ]: 0 : g_hash_table_insert (tags, g_strdup (layer->keys[key]), shumate_vector_value_as_string (&value));
313 : : }
314 : :
315 : 15 : return g_steal_pointer (&tags);
316 : : }
317 : :
318 : : ShumateVectorIndexBitset *
319 : 0 : shumate_vector_render_scope_get_bitset (ShumateVectorRenderScope *self,
320 : : const char *field,
321 : : ShumateVectorValue *value)
322 : : {
323 : 0 : return shumate_vector_index_get_bitset (self->index, self->layer_idx, field, value);
324 : : }
325 : :
326 : : /* Temporary data for shumate_vector_render_scope_index_layer */
327 : : typedef struct {
328 : : ShumateVectorIndexBitset *used_values;
329 : : ShumateVectorIndexBitset *unused_values;
330 : : GHashTable *indexes;
331 : : ShumateVectorIndexBitset *has_index;
332 : : guint8 is_unused;
333 : : } FieldIndexingData;
334 : :
335 : : void
336 : 21 : shumate_vector_render_scope_index_layer (ShumateVectorRenderScope *self)
337 : : {
338 : 21 : const char *layer_name = shumate_vector_reader_iter_get_layer_name (self->reader);
339 : 21 : VectorTile__Tile__Layer *layer;
340 : 21 : FieldIndexingData *fields;
341 : 21 : int feature_idx = 0;
342 : 21 : ShumateVectorIndexBitset *broad_geometry_indexes[3] = { NULL, NULL, NULL };
343 : 21 : ShumateVectorIndexBitset *geometry_indexes[7] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL };
344 : :
345 [ + - ]: 21 : if (self->index == NULL)
346 : 21 : self->index = shumate_vector_index_new ();
347 : :
348 [ + - ]: 21 : if (shumate_vector_index_has_layer (self->index, self->source_layer_idx))
349 : 9 : return;
350 : :
351 [ + + ]: 21 : if (!shumate_vector_index_description_has_layer (self->index_description, layer_name))
352 : : return;
353 : :
354 : 12 : layer = shumate_vector_reader_iter_get_layer_struct (self->reader);
355 [ - + - - ]: 12 : fields = g_new0 (FieldIndexingData, layer->n_keys);
356 : :
357 [ + + ]: 12 : if (shumate_vector_index_description_has_broad_geometry_type (self->index_description, layer_name))
358 : : {
359 [ + + ]: 12 : for (int i = 0; i < 3; i ++)
360 : 9 : broad_geometry_indexes[i] = shumate_vector_index_bitset_new (layer->n_features);
361 : :
362 : 3 : shumate_vector_index_add_bitset_broad_geometry_type (self->index, self->source_layer_idx, SHUMATE_GEOMETRY_TYPE_POINT, broad_geometry_indexes[0]);
363 : 3 : shumate_vector_index_add_bitset_broad_geometry_type (self->index, self->source_layer_idx, SHUMATE_GEOMETRY_TYPE_LINESTRING, broad_geometry_indexes[1]);
364 : 3 : shumate_vector_index_add_bitset_broad_geometry_type (self->index, self->source_layer_idx, SHUMATE_GEOMETRY_TYPE_POLYGON, broad_geometry_indexes[2]);
365 : : }
366 : :
367 [ + + ]: 12 : if (shumate_vector_index_description_has_geometry_type (self->index_description, layer_name))
368 : : {
369 [ + + ]: 21 : for (int i = 1; i < 7; i ++)
370 : : {
371 : 18 : geometry_indexes[i] = shumate_vector_index_bitset_new (layer->n_features);
372 : 18 : shumate_vector_index_add_bitset_geometry_type (self->index, self->source_layer_idx, i, geometry_indexes[i]);
373 : : }
374 : : }
375 : :
376 : : /* Read all of the features and build every requested index */
377 : 12 : shumate_vector_reader_iter_read_feature (self->reader, 0);
378 : 36 : while (TRUE)
379 : 12 : {
380 : 24 : VectorTile__Tile__Feature *feature = shumate_vector_reader_iter_get_feature_struct (self->reader);
381 : :
382 [ + + ]: 24 : if (broad_geometry_indexes[0] != NULL)
383 : : {
384 [ - + - - ]: 6 : switch (feature->type)
385 : : {
386 : 0 : case VECTOR_TILE__TILE__GEOM_TYPE__POINT:
387 : 0 : shumate_vector_index_bitset_set (broad_geometry_indexes[0], feature_idx);
388 : 0 : break;
389 : 6 : case VECTOR_TILE__TILE__GEOM_TYPE__LINESTRING:
390 : 6 : shumate_vector_index_bitset_set (broad_geometry_indexes[1], feature_idx);
391 : 6 : break;
392 : 0 : case VECTOR_TILE__TILE__GEOM_TYPE__POLYGON:
393 : 0 : shumate_vector_index_bitset_set (broad_geometry_indexes[2], feature_idx);
394 : 0 : break;
395 : : default:
396 : : break;
397 : : }
398 : : }
399 : :
400 [ + + ]: 24 : if (geometry_indexes[feature->type] != NULL)
401 : : {
402 : 6 : ShumateGeometryType type = shumate_vector_reader_iter_get_feature_geometry_type (self->reader);
403 : 6 : shumate_vector_index_bitset_set (geometry_indexes[type], feature_idx);
404 : : }
405 : :
406 [ + + ]: 36 : for (int i = 1; i < feature->n_tags; i += 2)
407 : : {
408 : 12 : ShumateVectorIndexBitset *bitset;
409 : 12 : int key = feature->tags[i - 1];
410 : 12 : int val = feature->tags[i];
411 : :
412 [ + - - + ]: 12 : if (key >= layer->n_keys || val >= layer->n_values)
413 : 0 : continue;
414 : :
415 [ + - ]: 12 : if (fields[key].indexes == NULL)
416 : : {
417 [ - + ]: 12 : if (fields[key].is_unused)
418 : : /* We have already looked up this field, and it is not in the description */
419 : 0 : continue;
420 : :
421 [ + + ]: 12 : if (shumate_vector_index_description_has_field (self->index_description, layer_name, layer->keys[key]))
422 : : {
423 : 6 : fields[key].indexes = g_hash_table_new (g_direct_hash, g_direct_equal);
424 : 6 : fields[key].used_values = shumate_vector_index_bitset_new (layer->n_values);
425 : 6 : fields[key].unused_values = shumate_vector_index_bitset_new (layer->n_values);
426 [ + + ]: 6 : if (shumate_vector_index_description_has_field_has_index (self->index_description, layer_name, layer->keys[key]))
427 : 3 : fields[key].has_index = shumate_vector_index_bitset_new (layer->n_features);
428 : : }
429 : : else
430 : : {
431 : 6 : fields[key].is_unused = TRUE;
432 : 6 : continue;
433 : : }
434 : : }
435 : :
436 [ + + ]: 6 : if (fields[key].has_index != NULL)
437 : 3 : shumate_vector_index_bitset_set (fields[key].has_index, feature_idx);
438 : :
439 [ - + ]: 6 : if (shumate_vector_index_bitset_get (fields[key].unused_values, val))
440 : 0 : continue;
441 : :
442 [ - + ]: 6 : if (shumate_vector_index_bitset_get (fields[key].used_values, val))
443 : 0 : bitset = g_hash_table_lookup (fields[key].indexes, GINT_TO_POINTER (val));
444 : : else
445 : : {
446 : 6 : g_auto(ShumateVectorValue) value = SHUMATE_VECTOR_VALUE_INIT;
447 : 6 : VectorTile__Tile__Value *v = layer->values[val];
448 : 6 : const char *field_name = layer->keys[key];
449 : :
450 : 6 : convert_vector_value (v, &value);
451 : :
452 [ + + ]: 6 : if (shumate_vector_index_description_has_value (self->index_description, layer_name, field_name, &value))
453 : 3 : bitset = shumate_vector_index_bitset_new (layer->n_features);
454 : : else
455 : : {
456 : 3 : shumate_vector_index_bitset_set (fields[key].unused_values, val);
457 : 3 : continue;
458 : : }
459 : :
460 : 3 : g_hash_table_insert (fields[key].indexes, GINT_TO_POINTER (val), bitset);
461 : 3 : shumate_vector_index_bitset_set (fields[key].used_values, val);
462 : : }
463 : :
464 : 3 : shumate_vector_index_bitset_set (bitset, feature_idx);
465 : : }
466 : :
467 [ + + ]: 24 : if (!shumate_vector_reader_iter_next_feature (self->reader))
468 : : break;
469 : 12 : feature_idx ++;
470 : : }
471 : :
472 [ + + ]: 24 : for (int key = 0; key < layer->n_keys; key ++)
473 : : {
474 : 12 : gpointer valp;
475 : 12 : const char *field_name;
476 : 12 : GHashTableIter field_iter;
477 : 12 : ShumateVectorIndexBitset *bitset;
478 : 12 : FieldIndexingData *field = &fields[key];
479 : :
480 [ + + ]: 12 : if (field->indexes == NULL)
481 : 6 : continue;
482 : :
483 : 6 : field_name = layer->keys[key];
484 : :
485 : 6 : g_hash_table_iter_init (&field_iter, field->indexes);
486 [ + + ]: 9 : while (g_hash_table_iter_next (&field_iter, &valp, (gpointer *)&bitset))
487 : : {
488 : 3 : int val = GPOINTER_TO_INT(valp);
489 : 3 : g_auto(ShumateVectorValue) value = SHUMATE_VECTOR_VALUE_INIT;
490 : 3 : VectorTile__Tile__Value *v;
491 : :
492 : 3 : v = layer->values[val];
493 : 3 : convert_vector_value (v, &value);
494 : :
495 : 3 : shumate_vector_index_add_bitset (self->index, self->source_layer_idx, field_name, &value, bitset);
496 : : }
497 : :
498 : 6 : g_hash_table_destroy (field->indexes);
499 : 6 : shumate_vector_index_bitset_free (field->used_values);
500 : 6 : shumate_vector_index_bitset_free (field->unused_values);
501 : :
502 [ + + ]: 6 : if (field->has_index != NULL)
503 : 3 : shumate_vector_index_add_bitset_has (self->index, self->source_layer_idx, field_name, field->has_index);
504 : : }
505 : :
506 : 12 : g_free (fields);
507 : : }
|