]> git.openstreetmap.org Git - rails.git/commitdiff
rails_port_0.5: Merge rails_port r4664.
authorGabriel Ebner <gabriel@svn.openstreetmap.org>
Sun, 23 Sep 2007 20:37:16 +0000 (20:37 +0000)
committerGabriel Ebner <gabriel@svn.openstreetmap.org>
Sun, 23 Sep 2007 20:37:16 +0000 (20:37 +0000)
1  2 
config/routes.rb
db/migrate/007_add_relations.rb
db/migrate/008_remove_segments.rb
db/migrate/008_remove_segments_helper.cc

diff --combined config/routes.rb
index d8fb6b95a921c91a3c1cdca9dd7fdde5c301d9cb,0cbdd36a47dbab8447472e752008487a08f4aa04..a1ddb4f497600e59a85d175da08bcdf47021509a
@@@ -2,13 -2,21 +2,13 @@@ ActionController::Routing::Routes.draw 
  
    # API
    map.connect "api/#{API_VERSION}/node/create", :controller => 'node', :action => 'create'
 -  map.connect "api/#{API_VERSION}/node/:id/segments", :controller => 'segment', :action => 'segments_for_node', :id => /\d+/
 +  map.connect "api/#{API_VERSION}/node/:id/ways", :controller => 'way', :action => 'ways_for_node', :id => /\d+/
    map.connect "api/#{API_VERSION}/node/:id/history", :controller => 'old_node', :action => 'history', :id => /\d+/
    map.connect "api/#{API_VERSION}/node/:id", :controller => 'node', :action => 'read', :id => /\d+/, :conditions => { :method => :get }
    map.connect "api/#{API_VERSION}/node/:id", :controller => 'node', :action => 'update', :id => /\d+/, :conditions => { :method => :put }
    map.connect "api/#{API_VERSION}/node/:id", :controller => 'node', :action => 'delete', :id => /\d+/, :conditions => { :method => :delete }
    map.connect "api/#{API_VERSION}/nodes", :controller => 'node', :action => 'nodes', :id => nil
    
 -  map.connect "api/#{API_VERSION}/segment/create", :controller => 'segment', :action => 'create'
 -  map.connect "api/#{API_VERSION}/segment/:id/ways", :controller => 'way', :action => 'ways_for_segment', :id => /\d+/
 -  map.connect "api/#{API_VERSION}/segment/:id/history", :controller => 'old_segment', :action => 'history', :id => /\d+/
 -  map.connect "api/#{API_VERSION}/segment/:id", :controller => 'segment', :action => 'read', :id => /\d+/, :conditions => { :method => :get }
 -  map.connect "api/#{API_VERSION}/segment/:id", :controller => 'segment', :action => 'update', :id => /\d+/, :conditions => { :method => :put }
 -  map.connect "api/#{API_VERSION}/segment/:id", :controller => 'segment', :action => 'delete', :id => /\d+/, :conditions => { :method => :delete }
 -  map.connect "api/#{API_VERSION}/segments", :controller => 'segment', :action => 'segments', :id => nil
 -  
    map.connect "api/#{API_VERSION}/way/create", :controller => 'way', :action => 'create'
    map.connect "api/#{API_VERSION}/way/:id/history", :controller => 'old_way', :action => 'history', :id => /\d+/
    map.connect "api/#{API_VERSION}/way/:id/full", :controller => 'way', :action => 'full', :id => /\d+/
    map.connect "api/#{API_VERSION}/ways", :controller => 'way', :action => 'ways', :id => nil
  
    map.connect "api/#{API_VERSION}/capabilities", :controller => 'api', :action => 'capabilities'
 +  map.connect "api/#{API_VERSION}/relation/create", :controller => 'relation', :action => 'create'
 +  map.connect "api/#{API_VERSION}/relation/:id/history", :controller => 'old_relation', :action => 'history', :id => /\d+/
 +  map.connect "api/#{API_VERSION}/relation/:id/full", :controller => 'relation', :action => 'full', :id => /\d+/
 +  map.connect "api/#{API_VERSION}/relation/:id", :controller => 'relation', :action => 'read', :id => /\d+/, :conditions => { :method => :get }
 +  map.connect "api/#{API_VERSION}/relation/:id", :controller => 'relation', :action => 'update', :id => /\d+/, :conditions => { :method => :put }
 +  map.connect "api/#{API_VERSION}/relation/:id", :controller => 'relation', :action => 'delete', :id => /\d+/, :conditions => { :method => :delete }
 +  map.connect "api/#{API_VERSION}/relations", :controller => 'relation', :action => 'relations', :id => nil
  
    map.connect "api/#{API_VERSION}/map", :controller => 'api', :action => 'map'
    
@@@ -32,6 -33,7 +32,6 @@@
    
    map.connect "api/#{API_VERSION}/search", :controller => 'search', :action => 'search_all'
    map.connect "api/#{API_VERSION}/ways/search", :controller => 'search', :action => 'search_ways'
 -  map.connect "api/#{API_VERSION}/segments/search", :controller => 'search', :action => 'search_segments'
    map.connect "api/#{API_VERSION}/nodes/search", :controller => 'search', :action => 'search_nodes'
    
    map.connect "api/#{API_VERSION}/user/details", :controller => 'user', :action => 'api_details'
@@@ -73,6 -75,7 +73,7 @@@
    map.connect '/traces/mine/tag/:tag', :controller => 'trace', :action => 'mine'
    map.connect '/traces/mine/tag/:tag/page/:page', :controller => 'trace', :action => 'mine'
    map.connect '/trace/create', :controller => 'trace', :action => 'create'
+   map.connect '/trace/:id/edit', :controller => 'trace', :action => 'edit'
    map.connect '/trace/:id/delete', :controller => 'trace', :action => 'delete'
    map.connect '/trace/:id/make_public', :controller => 'trace', :action => 'make_public'
    map.connect '/user/:display_name/traces', :controller => 'trace', :action => 'list'
index a30642e32a801127e576c21c4a7db0e7c581db61,0000000000000000000000000000000000000000..a30642e32a801127e576c21c4a7db0e7c581db61
mode 100644,000000..100644
--- /dev/null
@@@ -1,83 -1,0 +1,83 @@@
 +class AddRelations < ActiveRecord::Migration
 +  def self.up
 +    # a relation can have members much like a way can have nodes.
 +    # differences:
 +    # way: only nodes / relation: any kind of member
 +    # way: ordered sequence of nodes / relation: free-form "role" string
 +    create_table "current_relation_members", innodb_table do |t|
 +      t.column "id",          :bigint, :limit => 64, :null => false
 +      t.column "member_type", :string, :limit => 11, :null => false
 +      t.column "member_id",   :bigint, :limit => 11, :null => false
 +      t.column "member_role", :string
 +    end
 +    # enums work like strings but are more efficient
 +    execute "alter table current_relation_members change column member_type member_type enum('node','way','relation');"
 +
 +    add_primary_key "current_relation_members", ["id", "member_type", "member_id", "member_role"]
 +    add_index "current_relation_members", ["member_type", "member_id"], :name => "current_relation_members_member_idx"
 +    # the following is obsolete given the primary key, is it not?
 +    # add_index "current_relation_members", ["id"], :name => "current_relation_members_id_idx"
 +    create_table "current_relation_tags", myisam_table do |t|
 +      t.column "id", :bigint, :limit => 64, :null => false
 +      t.column "k",  :string, :default => "", :null => false
 +      t.column "v",  :string, :default => "", :null => false
 +    end
 +
 +    add_index "current_relation_tags", ["id"], :name => "current_relation_tags_id_idx"
 +    execute "CREATE FULLTEXT INDEX `current_relation_tags_v_idx` ON `current_relation_tags` (`v`)"
 +
 +    create_table "current_relations", innodb_table do |t|
 +      t.column "id",        :bigint,   :limit => 64, :null => false
 +      t.column "user_id",   :bigint,   :limit => 20, :null => false
 +      t.column "timestamp", :datetime, :null => false
 +      t.column "visible",   :boolean,  :null => false
 +    end
 +
 +    add_primary_key "current_relations", ["id"]
 +    change_column "current_relations", "id", :bigint, :limit => 64, :null => false, :options => "AUTO_INCREMENT"
 +
 +    create_table "relation_members", myisam_table do |t|
 +      t.column "id",          :bigint,  :limit => 64, :default => 0, :null => false
 +      t.column "member_type", :string, :limit => 11, :null => false
 +      t.column "member_id",   :bigint, :limit => 11, :null => false
 +      t.column "member_role", :string
 +      t.column "version",     :bigint,  :limit => 20, :default => 0, :null => false
 +    end
 +
 +    execute "alter table relation_members change column member_type member_type enum('node','way','relation');" 
 +    add_primary_key "relation_members", ["id", "version", "member_type", "member_id", "member_role"]
 +    add_index "relation_members", ["member_type", "member_id"], :name => "relation_members_member_idx"
 +
 +    create_table "relation_tags", myisam_table do |t|
 +      t.column "id",      :bigint,  :limit => 64, :default => 0, :null => false
 +      t.column "k",       :string, :null => false, :default => ""
 +      t.column "v",       :string, :null => false, :default => ""
 +      t.column "version", :bigint,  :limit => 20, :null => false
 +    end
 +
 +    add_index "relation_tags", ["id", "version"], :name => "relation_tags_id_version_idx"
 +
 +    create_table "relations", myisam_table do |t|
 +      t.column "id",        :bigint,   :limit => 64, :null => false, :default => 0
 +      t.column "user_id",   :bigint,   :limit => 20, :null => false
 +      t.column "timestamp", :datetime,               :null => false
 +      t.column "version",   :bigint,   :limit => 20, :null => false
 +      t.column "visible",   :boolean,                :null => false, :default => true
 +    end
 +
 +    add_primary_key "relations", ["id", "version"]
 +    add_index "relations", ["timestamp"], :name => "relations_timestamp_idx"
 +    
 +    change_column "relations", "version", :bigint, :limit => 20, :null => false, :options => "AUTO_INCREMENT"
 +  end
 +
 +
 +  def self.down
 +    drop_table :relations
 +    drop_table :current_relations
 +    drop_table :relation_tags
 +    drop_table :current_relation_tags
 +    drop_table :relation_members
 +    drop_table :current_relation_members
 +  end
 +end
index 492beda63d70048575b9740c503512bb40f02488,0000000000000000000000000000000000000000..cfa99adad00333405184e16521a30bb7dbf093c1
mode 100644,000000..100644
--- /dev/null
@@@ -1,89 -1,0 +1,89 @@@
-       prefix = File.join Dir.tmpdir, "007_remove_segments.#{$$}."
 +require 'lib/migrate'
 +
 +class RemoveSegments < ActiveRecord::Migration
 +  def self.up
 +    have_segs = select_value("SELECT count(*) FROM current_segments").to_i != 0
 +
 +    if have_segs
-       cmd = "db/migrate/007_remove_segments_helper"
++      prefix = File.join Dir.tmpdir, "008_remove_segments.#{$$}."
 +
++      cmd = "db/migrate/008_remove_segments_helper"
 +      src = "#{cmd}.cc"
 +      if not File.exists? cmd or File.mtime(cmd) < File.mtime(src) then 
 +      system 'c++ -O3 -Wall `mysql_config --cflags --libs` ' +
 +        "#{src} -o #{cmd}" or fail
 +      end
 +
 +      conn_opts = ActiveRecord::Base.connection.
 +      instance_eval { @connection_options }
 +      args = conn_opts.map { |arg| arg.to_s } + [prefix]
 +      fail "#{cmd} failed" unless system cmd, *args
 +
 +      tempfiles = ['ways', 'way_nodes', 'way_tags',
 +      'relations', 'relation_members', 'relation_tags'].
 +      map { |base| prefix + base }
 +      ways, way_nodes, way_tags,
 +      relations, relation_members, relation_tags = tempfiles
 +    end
 +
 +    drop_table :segments
 +    drop_table :way_segments
 +    create_table :way_nodes, myisam_table do |t|
 +      t.column :id,          :bigint, :limit => 64, :null => false
 +      t.column :node_id,     :bigint, :limit => 64, :null => false
 +      t.column :version,     :bigint, :limit => 20, :null => false
 +      t.column :sequence_id, :bigint, :limit => 11, :null => false
 +    end
 +    add_primary_key :way_nodes, [:id, :version, :sequence_id]
 +
 +    drop_table :current_segments
 +    drop_table :current_way_segments
 +    create_table :current_way_nodes, innodb_table do |t|
 +      t.column :id,          :bigint, :limit => 64, :null => false
 +      t.column :node_id,     :bigint, :limit => 64, :null => false
 +      t.column :sequence_id, :bigint, :limit => 11, :null => false
 +    end
 +    add_primary_key :current_way_nodes, [:id, :sequence_id]
 +
 +    execute "TRUNCATE way_tags"
 +    execute "TRUNCATE ways"
 +    execute "TRUNCATE current_way_tags"
 +    execute "TRUNCATE current_ways"
 +
 +    # now get the data back
 +    csvopts = "FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY '\"' LINES TERMINATED BY '\\n'"
 +
 +    tempfiles.each { |fn| File.chmod 0644, fn } if have_segs
 +
 +    if have_segs
 +      execute "LOAD DATA LOCAL INFILE '#{ways}' INTO TABLE ways #{csvopts} (id, user_id, timestamp) SET visible = 1, version = 1"
 +      execute "LOAD DATA LOCAL INFILE '#{way_nodes}' INTO TABLE way_nodes #{csvopts} (id, node_id, sequence_id) SET version = 1"
 +      execute "LOAD DATA LOCAL INFILE '#{way_tags}' INTO TABLE way_tags #{csvopts} (id, k, v) SET version = 1"
 +
 +      execute "INSERT INTO current_ways SELECT id, user_id, timestamp, visible FROM ways"
 +      execute "INSERT INTO current_way_nodes SELECT id, node_id, sequence_id FROM way_nodes"
 +      execute "INSERT INTO current_way_tags SELECT id, k, v FROM way_tags"
 +    end
 +
 +    # and then readd the index
 +    add_index :current_way_nodes, [:node_id], :name => "current_way_nodes_node_idx"
 +
 +    if have_segs
 +      execute "LOAD DATA LOCAL INFILE '#{relations}' INTO TABLE relations #{csvopts} (id, user_id, timestamp) SET visible = 1, version = 1"
 +      execute "LOAD DATA LOCAL INFILE '#{relation_members}' INTO TABLE relation_members #{csvopts} (id, member_type, member_id, member_role) SET version = 1"
 +      execute "LOAD DATA LOCAL INFILE '#{relation_tags}' INTO TABLE relation_tags #{csvopts} (id, k, v) SET version = 1"
 +
 +      # FIXME: This will only work if there were no relations before the
 +      # migration!
 +      execute "INSERT INTO current_relations SELECT id, user_id, timestamp, visible FROM relations"
 +      execute "INSERT INTO current_relation_members SELECT id, member_type, member_id, member_role FROM relation_members"
 +      execute "INSERT INTO current_relation_tags SELECT id, k, v FROM relation_tags"
 +    end
 +
 +    tempfiles.each { |fn| File.unlink fn } if have_segs
 +  end
 +
 +  def self.down
 +    raise IrreversibleMigration.new
 +  end
 +end
index 3034a8f5363b97c60723db3cbad5f70dcd108f7f,0000000000000000000000000000000000000000..8f9c25bd59398e906db9f3f47dfc9f7d1bfad94f
mode 100644,000000..100644
--- /dev/null
@@@ -1,670 -1,0 +1,670 @@@
-     fprintf(stderr, "007_remove_segments_helper: MySQL error: %s\n", err);
 +#include <mysql.h>
 +#include <stdint.h>
 +#include <stdio.h>
 +#include <stdlib.h>
 +#include <string.h>
 +#include <vector>
 +#include <list>
 +#include <sstream>
 +#include <map>
 +#include <string>
 +
 +#ifdef __amd64__
 +
 +#define F_U64 "%lu"
 +#define F_U32 "%u"
 +
 +#else
 +
 +#define F_U64 "%Lu"
 +#define F_U32 "%u"
 +
 +#endif
 +
 +using namespace std;
 +
 +template <typename T>
 +static T parse(const char *str) {
 +  istringstream in(str);
 +  T t;
 +  in >> t;
 +  return t;
 +}
 +
 +static void exit_mysql_err(MYSQL *mysql) {
 +  const char *err = mysql_error(mysql);
 +  if (err) {
-     fprintf(stderr, "007_remove_segments_helper: MySQL error\n");
++    fprintf(stderr, "008_remove_segments_helper: MySQL error: %s\n", err);
 +  } else {
-     fprintf(stderr, "007_remove_segments_helper: MySQL stmt error: %s\n", err);
++    fprintf(stderr, "008_remove_segments_helper: MySQL error\n");
 +  }
 +  abort();
 +  exit(EXIT_FAILURE);
 +}
 +
 +static void exit_stmt_err(MYSQL_STMT *stmt) {
 +  const char *err = mysql_stmt_error(stmt);
 +  if (err) {
-     fprintf(stderr, "007_remove_segments_helper: MySQL stmt error\n");
++    fprintf(stderr, "008_remove_segments_helper: MySQL stmt error: %s\n", err);
 +  } else {
-     printf("Usage: 007_remove_segments_helper host user passwd database port socket prefix\n");
++    fprintf(stderr, "008_remove_segments_helper: MySQL stmt error\n");
 +  }
 +  abort();
 +  exit(EXIT_FAILURE);
 +}
 +
 +struct segment {
 +  uint32_t from, to;
 +};
 +
 +struct data {
 +  MYSQL *mysql, *mysql2;
 +
 +  uint64_t seg_maxid, way_maxid;
 +  uint64_t new_way_id;
 +  uint64_t new_relation_id;
 +
 +  size_t segs_len;
 +  struct segment *segs;
 +  unsigned char *rem_segs;
 +
 +  FILE *ways, *way_nodes, *way_tags,
 +    *relations, *relation_members, *relation_tags;
 +};
 +
 +static uint64_t select_u64(MYSQL *mysql, const char *q) {
 +  MYSQL_RES *res;
 +  MYSQL_ROW row;
 +  uint64_t ret;
 +
 +  if (mysql_query(mysql, q))
 +    exit_mysql_err(mysql);
 +
 +  res = mysql_store_result(mysql);
 +  if (!res) exit_mysql_err(mysql);
 +
 +  row = mysql_fetch_row(res);
 +  if (!row) exit_mysql_err(mysql);
 +
 +  if (row[0]) {
 +    ret = parse<uint64_t>(row[0]);
 +  } else {
 +    ret = 0;
 +  }
 +
 +  mysql_free_result(res);
 +
 +  return ret;
 +}
 +
 +static void find_maxids(struct data *d) {
 +  d->seg_maxid = select_u64(d->mysql, "SELECT max(id) FROM current_segments");
 +  d->segs_len = d->seg_maxid + 1;
 +  d->way_maxid = select_u64(d->mysql, "SELECT max(id) FROM current_ways");
 +  d->new_way_id = d->way_maxid + 1;
 +  d->new_relation_id = select_u64(d->mysql, "SELECT max(id) FROM current_relations") + 1;
 +}
 +
 +static void populate_segs(struct data *d) {
 +  MYSQL_RES *res;
 +  MYSQL_ROW row;
 +  size_t id;
 +
 +  d->segs = (segment *) malloc(sizeof(struct segment) * d->segs_len);
 +  memset(d->segs, 0, sizeof(struct segment) * d->segs_len);
 +
 +  d->rem_segs = (unsigned char *) malloc(d->segs_len);
 +  memset(d->rem_segs, 0, d->segs_len);
 +
 +  if (mysql_query(d->mysql, "SELECT id, node_a, node_b "
 +      "FROM current_segments WHERE visible"))
 +    exit_mysql_err(d->mysql);
 +
 +  res = mysql_use_result(d->mysql);
 +  if (!res) exit_mysql_err(d->mysql);
 +
 +  while ((row = mysql_fetch_row(res))) {
 +    id = parse<size_t>(row[0]);
 +    if (id >= d->segs_len) continue;
 +    d->segs[id].from = parse<uint32_t>(row[1]);
 +    d->segs[id].to   = parse<uint32_t>(row[2]);
 +    d->rem_segs[id] = 1;
 +  }
 +  if (mysql_errno(d->mysql)) exit_mysql_err(d->mysql);
 +
 +  mysql_free_result(res);
 +}
 +
 +static void write_csv_col(FILE *f, const char *str, char end) {
 +  char *out = (char *) malloc(2 * strlen(str) + 4);
 +  char *o = out;
 +  size_t len;
 +
 +  *(o++) = '\"';
 +  for (; *str; str++) {
 +    if (*str == '\0') {
 +      break;
 +    } else if (*str == '\"') {
 +      *(o++) = '\"';
 +      *(o++) = '\"';
 +    } else {
 +      *(o++) = *str;
 +    }
 +  }
 +  *(o++) = '\"';
 +  *(o++) = end;
 +  *(o++) = '\0';
 +
 +  len = strlen(out);
 +  if (fwrite(out, len, 1, f) != 1) {
 +    perror("fwrite");
 +    exit(EXIT_FAILURE);
 +  }
 +
 +  free(out);
 +}
 +
 +static void convert_ways(struct data *d) {
 +  MYSQL_RES *res;
 +  MYSQL_ROW row;
 +  MYSQL_STMT *load_segs, *load_tags;
 +  const char
 +    load_segs_stmt[] = "SELECT segment_id FROM current_way_segments "
 +      "WHERE id = ? ORDER BY sequence_id",
 +    load_tags_stmt[] = "SELECT k, v FROM current_way_tags WHERE id = ?";
 +  char *k, *v;
 +  const size_t max_tag_len = 1 << 16;
 +  long long mysql_id, mysql_seg_id;
 +  unsigned long res_len;
 +  my_bool res_error;
 +  MYSQL_BIND bind[1], seg_bind[1], tag_bind[2];
 +
 +  /* F***ing libmysql only support fixed size buffers for string results of
 +   * prepared statements.  So allocate 65k for the tag key and the tag value
 +   * and hope it'll suffice. */
 +  k = (char *) malloc(max_tag_len);
 +  v = (char *) malloc(max_tag_len);
 +
 +  load_segs = mysql_stmt_init(d->mysql2);
 +  if (!load_segs) exit_mysql_err(d->mysql2);
 +  if (mysql_stmt_prepare(load_segs, load_segs_stmt, sizeof(load_segs_stmt)))
 +    exit_stmt_err(load_segs);
 +
 +  memset(bind, 0, sizeof(bind));
 +  bind[0].buffer_type = MYSQL_TYPE_LONGLONG;
 +  bind[0].buffer = (char *) &mysql_id;
 +  bind[0].is_null = 0;
 +  bind[0].length = 0;
 +  if (mysql_stmt_bind_param(load_segs, bind))
 +    exit_stmt_err(load_segs);
 +
 +  memset(seg_bind, 0, sizeof(seg_bind));
 +  seg_bind[0].buffer_type = MYSQL_TYPE_LONGLONG;
 +  seg_bind[0].buffer = (char *) &mysql_seg_id;
 +  seg_bind[0].is_null = 0;
 +  seg_bind[0].length = 0;
 +  seg_bind[0].error = &res_error;
 +  if (mysql_stmt_bind_result(load_segs, seg_bind))
 +    exit_stmt_err(load_segs);
 +
 +  load_tags = mysql_stmt_init(d->mysql2);
 +  if (!load_tags) exit_mysql_err(d->mysql2);
 +  if (mysql_stmt_prepare(load_tags, load_tags_stmt, sizeof(load_tags_stmt)))
 +    exit_stmt_err(load_tags);
 +
 +  memset(bind, 0, sizeof(bind));
 +  bind[0].buffer_type = MYSQL_TYPE_LONGLONG;
 +  bind[0].buffer = (char *) &mysql_id;
 +  bind[0].is_null = 0;
 +  bind[0].length = 0;
 +
 +  if (mysql_stmt_bind_param(load_tags, bind))
 +    exit_stmt_err(load_tags);
 +
 +  memset(tag_bind, 0, sizeof(tag_bind));
 +  tag_bind[0].buffer_type = MYSQL_TYPE_STRING;
 +  tag_bind[0].buffer = k;
 +  tag_bind[0].is_null = 0;
 +  tag_bind[0].length = &res_len;
 +  tag_bind[0].error = &res_error;
 +  tag_bind[0].buffer_length = max_tag_len;
 +  tag_bind[1].buffer_type = MYSQL_TYPE_STRING;
 +  tag_bind[1].buffer = v;
 +  tag_bind[1].is_null = 0;
 +  tag_bind[1].length = &res_len;
 +  tag_bind[1].error = &res_error;
 +  tag_bind[1].buffer_length = max_tag_len;
 +  if (mysql_stmt_bind_result(load_tags, tag_bind))
 +    exit_stmt_err(load_tags);
 +
 +  if (mysql_query(d->mysql, "SELECT id, user_id, timestamp "
 +      "FROM current_ways WHERE visible"))
 +    exit_mysql_err(d->mysql);
 +
 +  res = mysql_use_result(d->mysql);
 +  if (!res) exit_mysql_err(d->mysql);
 +
 +  while ((row = mysql_fetch_row(res))) {
 +    uint64_t id;
 +    const char *user_id, *timestamp;
 +
 +    id = parse<uint64_t>(row[0]);
 +    user_id = row[1];
 +    timestamp = row[2];
 +
 +    mysql_id = (long long) id;
 +
 +    if (mysql_stmt_execute(load_segs))
 +      exit_stmt_err(load_segs);
 +
 +    if (mysql_stmt_store_result(load_segs))
 +      exit_stmt_err(load_segs);
 +
 +    list<segment> segs;
 +    while (!mysql_stmt_fetch(load_segs)) {
 +      if (((uint64_t) mysql_seg_id) >= d->segs_len) continue;
 +      segs.push_back(d->segs[mysql_seg_id]);
 +      d->rem_segs[mysql_seg_id] = 0;
 +    }
 +
 +    list<list<uint32_t> > node_lists;
 +    while (segs.size()) {
 +      list<uint32_t> node_list;
 +      node_list.push_back(segs.front().from);
 +      node_list.push_back(segs.front().to);
 +      segs.pop_front();
 +      while (true) {
 +      bool found = false;
 +      for (list<segment>::iterator it = segs.begin();
 +          it != segs.end(); ) {
 +        if (it->from == node_list.back()) {
 +          node_list.push_back(it->to);
 +          segs.erase(it++);
 +          found = true;
 +        } else if (it->to == node_list.front()) {
 +          node_list.insert(node_list.begin(), it->from);
 +          segs.erase(it++);
 +          found = true;
 +        } else {
 +          ++it;
 +        }
 +      }
 +      if (!found) break;
 +      }
 +      node_lists.push_back(node_list);
 +    }
 +
 +    vector<uint64_t> ids; ids.reserve(node_lists.size());
 +    bool orig_id_used = false;
 +    for (list<list<uint32_t> >::iterator it = node_lists.begin();
 +      it != node_lists.end(); ++it) {
 +      uint64_t way_id;
 +      int sid;
 +      if (orig_id_used) {
 +      way_id = d->new_way_id++;
 +      } else {
 +      way_id = id;
 +      orig_id_used = true;
 +      }
 +      ids.push_back(way_id);
 +
 +      fprintf(d->ways, "\"" F_U64 "\",", way_id);
 +      write_csv_col(d->ways, user_id, ',');
 +      write_csv_col(d->ways, timestamp, '\n');
 +
 +      sid = 1;
 +      for (list<uint32_t>::iterator nit = it->begin();
 +        nit != it->end(); ++nit) {
 +      fprintf(d->way_nodes, "\"" F_U64 "\",\"" F_U32 "\",\"%i\"\n", way_id, *nit, sid++);
 +      }
 +    }
 +
 +    if (mysql_stmt_execute(load_tags))
 +      exit_stmt_err(load_tags);
 +
 +    if (mysql_stmt_store_result(load_tags))
 +      exit_stmt_err(load_tags);
 +
 +    bool multiple_parts = ids.size() > 1,
 +      create_multipolygon = false;
 +
 +    while (!mysql_stmt_fetch(load_tags)) {
 +      if (multiple_parts && !create_multipolygon) {
 +      if (!strcmp(k, "natural")) {
 +        if (strcmp(v, "coastline")) {
 +          create_multipolygon = true;
 +        }
 +      } else if (!strcmp(k, "waterway")) {
 +        if (!strcmp(v, "riverbank")) {
 +          create_multipolygon = true;
 +        }
 +      } else if (!strcmp(k, "leisure") || !strcmp(k, "landuse")
 +          || !strcmp(k, "sport") || !strcmp(k, "amenity")
 +          || !strcmp(k, "tourism") || !strcmp(k, "building")) {
 +        create_multipolygon = true;
 +      }
 +      }
 +
 +      for (vector<uint64_t>::iterator it = ids.begin();
 +        it != ids.end(); ++it) {
 +      fprintf(d->way_tags, "\"" F_U64 "\",", *it);
 +      write_csv_col(d->way_tags, k, ',');
 +      write_csv_col(d->way_tags, v, '\n');
 +      }
 +    }
 +
 +    if (multiple_parts && create_multipolygon) {
 +      uint64_t ent_id = d->new_relation_id++;
 +
 +      fprintf(d->relations, "\"" F_U64 "\",", ent_id);
 +      write_csv_col(d->relations, user_id, ',');
 +      write_csv_col(d->relations, timestamp, '\n');
 +
 +      fprintf(d->relation_tags,
 +      "\"" F_U64 "\",\"type\",\"multipolygon\"\n", ent_id);
 +
 +      for (vector<uint64_t>::iterator it = ids.begin();
 +        it != ids.end(); ++it) {
 +      fprintf(d->relation_members,
 +        "\"" F_U64 "\",\"way\",\"" F_U64 "\",\"\"\n", ent_id, *it);
 +      }
 +    }
 +  }
 +  if (mysql_errno(d->mysql)) exit_stmt_err(load_tags);
 +
 +  mysql_stmt_close(load_segs);
 +  mysql_stmt_close(load_tags);
 +
 +  mysql_free_result(res);
 +  free(k);
 +  free(v);
 +}
 +
 +static int read_seg_tags(char **tags, char **k, char **v) {
 +  if (!**tags) return 0;
 +  char *i = strchr(*tags, ';');
 +  if (!i) i = *tags + strlen(*tags);
 +  char *j = strchr(*tags, '=');
 +  *k = *tags;
 +  if (j && j < i) {
 +    *v = j + 1;
 +  } else {
 +    *v = i;
 +  }
 +  *tags = *i ? i + 1 : i;
 +  *i = '\0';
 +  if (j) *j = '\0';
 +  return 1;
 +}
 +
 +static void mark_tagged_segs(struct data *d) {
 +  MYSQL_RES *res;
 +  MYSQL_ROW row;
 +  MYSQL_STMT *way_tags;
 +  const char
 +    way_tags_stmt[] = "SELECT k, v FROM current_way_segments INNER JOIN "
 +      "current_way_tags ON current_way_segments.id = "
 +      "current_way_tags.id WHERE segment_id = ?";
 +  char *wk, *wv;
 +  const size_t max_tag_len = 1 << 16;
 +  long long mysql_seg_id;
 +  unsigned long res_len;
 +  my_bool res_error;
 +  MYSQL_BIND in_bind[1], out_bind[1];
 +
 +  /* F***ing libmysql only support fixed size buffers for string results of
 +   * prepared statements.  So allocate 65k for the tag key and the tag value
 +   * and hope it'll suffice. */
 +  wk = (char *) malloc(max_tag_len);
 +  wv = (char *) malloc(max_tag_len);
 +
 +  way_tags = mysql_stmt_init(d->mysql2);
 +  if (!way_tags) exit_mysql_err(d->mysql2);
 +  if (mysql_stmt_prepare(way_tags, way_tags_stmt, sizeof(way_tags_stmt)))
 +    exit_stmt_err(way_tags);
 +
 +  memset(in_bind, 0, sizeof(in_bind));
 +  in_bind[0].buffer_type = MYSQL_TYPE_LONGLONG;
 +  in_bind[0].buffer = (char *) &mysql_seg_id;
 +  in_bind[0].is_null = 0;
 +  in_bind[0].length = 0;
 +
 +  if (mysql_stmt_bind_param(way_tags, in_bind))
 +    exit_stmt_err(way_tags);
 +
 +  memset(out_bind, 0, sizeof(out_bind));
 +  out_bind[0].buffer_type = MYSQL_TYPE_STRING;
 +  out_bind[0].buffer = wk;
 +  out_bind[0].is_null = 0;
 +  out_bind[0].length = &res_len;
 +  out_bind[0].error = &res_error;
 +  out_bind[0].buffer_length = max_tag_len;
 +  out_bind[1].buffer_type = MYSQL_TYPE_STRING;
 +  out_bind[1].buffer = wv;
 +  out_bind[1].is_null = 0;
 +  out_bind[1].length = &res_len;
 +  out_bind[1].error = &res_error;
 +  out_bind[1].buffer_length = max_tag_len;
 +  if (mysql_stmt_bind_result(way_tags, out_bind))
 +    exit_stmt_err(way_tags);
 +
 +  if (mysql_query(d->mysql, "SELECT id, tags FROM current_segments "
 +      "WHERE visible && tags != '' && tags != 'created_by=JOSM'"))
 +    exit_mysql_err(d->mysql);
 +
 +  res = mysql_use_result(d->mysql);
 +  if (!res) exit_mysql_err(d->mysql);
 +
 +  while ((row = mysql_fetch_row(res))) {
 +    size_t id = parse<size_t>(row[0]);
 +    if (d->rem_segs[id]) continue;
 +
 +    map<string, string> interesting_tags;
 +
 +    char *tags_it = row[1], *k, *v;
 +    while (read_seg_tags(&tags_it, &k, &v)) {
 +      if (strcmp(k, "created_by") &&
 +        strcmp(k, "tiger:county") &&
 +        strcmp(k, "tiger:upload_uuid") &&
 +        strcmp(k, "converted_by") &&
 +        (strcmp(k, "natural") || strcmp(v, "coastline")) &&
 +        (strcmp(k, "source") || strcmp(v, "PGS"))) {
 +      interesting_tags.insert(make_pair(string(k), string(v)));
 +      }
 +    }
 +
 +    if (interesting_tags.size() == 0) continue;
 +
 +    mysql_seg_id = id;
 +
 +    if (mysql_stmt_execute(way_tags))
 +      exit_stmt_err(way_tags);
 +
 +    if (mysql_stmt_store_result(way_tags))
 +      exit_stmt_err(way_tags);
 +
 +    while (!mysql_stmt_fetch(way_tags)) {
 +      for (map<string, string>::iterator it = interesting_tags.find(wk);
 +        it != interesting_tags.end() && it->first == wk; ++it) {
 +      if (it->second == wv) {
 +        interesting_tags.erase(it);
 +        break;
 +      }
 +      }
 +    }
 +
 +    if (interesting_tags.size() > 0) {
 +      d->rem_segs[id] = 1;
 +    }
 +  }
 +
 +  mysql_free_result(res);
 +
 +  mysql_stmt_close(way_tags);
 +  free(wk);
 +  free(wv);
 +}
 +
 +static void convert_remaining_segs(struct data *d) {
 +  MYSQL_STMT *load_seg;
 +  MYSQL_BIND args[1], res[3];
 +  const size_t max_tag_len = 1 << 16;
 +  char *tags, timestamp[100];
 +  char *k, *v;
 +  int user_id;
 +  long long mysql_id;
 +  unsigned long res_len;
 +  my_bool res_error;
 +  const char load_seg_stmt[] =
 +    "SELECT user_id, tags, CAST(timestamp AS CHAR) FROM current_segments "
 +    "WHERE visible && id = ?";
 +
 +  tags = (char *) malloc(max_tag_len);
 +
 +  load_seg = mysql_stmt_init(d->mysql);
 +  if (!load_seg) exit_mysql_err(d->mysql);
 +  if (mysql_stmt_prepare(load_seg, load_seg_stmt, sizeof(load_seg_stmt)))
 +    exit_stmt_err(load_seg);
 +
 +  memset(args, 0, sizeof(args));
 +  args[0].buffer_type = MYSQL_TYPE_LONGLONG;
 +  args[0].buffer = (char *) &mysql_id;
 +  args[0].is_null = 0;
 +  args[0].length = 0;
 +  if (mysql_stmt_bind_param(load_seg, args))
 +    exit_stmt_err(load_seg);
 +
 +  memset(res, 0, sizeof(res));
 +  res[0].buffer_type = MYSQL_TYPE_LONG;
 +  res[0].buffer = (char *) &user_id;
 +  res[0].is_null = 0;
 +  res[0].length = 0;
 +  res[0].error = &res_error;
 +  res[1].buffer_type = MYSQL_TYPE_STRING;
 +  res[1].buffer = tags;
 +  res[1].is_null = 0;
 +  res[1].length = &res_len;
 +  res[1].error = &res_error;
 +  res[1].buffer_length = max_tag_len;
 +  res[2].buffer_type = MYSQL_TYPE_STRING;
 +  res[2].buffer = timestamp;
 +  res[2].is_null = 0;
 +  res[2].length = &res_len;
 +  res[2].error = &res_error;
 +  res[2].buffer_length = sizeof(timestamp);
 +  if (mysql_stmt_bind_result(load_seg, res))
 +    exit_stmt_err(load_seg);
 +
 +  for (size_t seg_id = 0; seg_id < d->segs_len; seg_id++) {
 +    if (!d->rem_segs[seg_id]) continue;
 +    segment seg = d->segs[seg_id];
 +
 +    mysql_id = seg_id;
 +    if (mysql_stmt_execute(load_seg)) exit_stmt_err(load_seg);
 +    if (mysql_stmt_store_result(load_seg)) exit_stmt_err(load_seg);
 +
 +    while (!mysql_stmt_fetch(load_seg)) {
 +      uint64_t way_id = d->new_way_id++;
 +
 +      fprintf(d->ways, "\"" F_U64 "\",\"%i\",", way_id, user_id);
 +      write_csv_col(d->ways, timestamp, '\n');
 +
 +      fprintf(d->way_nodes, "\"" F_U64 "\",\"" F_U32 "\",\"%i\"\n", way_id, seg.from, 1);
 +      fprintf(d->way_nodes, "\"" F_U64 "\",\"" F_U32 "\",\"%i\"\n", way_id, seg.to, 2);
 +
 +      char *tags_it = tags;
 +      while (read_seg_tags(&tags_it, &k, &v)) {
 +      fprintf(d->way_tags, "\"" F_U64 "\",", way_id);
 +      write_csv_col(d->way_tags, k, ',');
 +      write_csv_col(d->way_tags, v, '\n');
 +      }
 +    }
 +  }
 +
 +  mysql_stmt_close(load_seg);
 +
 +  free(tags);
 +}
 +
 +static MYSQL *connect_to_mysql(char **argv) {
 +  MYSQL *mysql = mysql_init(NULL);
 +  if (!mysql) exit_mysql_err(mysql);
 +
 +  if (!mysql_real_connect(mysql, argv[1], argv[2], argv[3], argv[4],
 +      argv[5][0] ? atoi(argv[5]) : 0, argv[6][0] ? argv[6] : NULL, 0))
 +    exit_mysql_err(mysql);
 +
 +  if (mysql_set_character_set(mysql, "utf8"))
 +    exit_mysql_err(mysql);
 +
 +  return mysql;
 +}
 +
 +static void open_file(FILE **f, char *fn) {
 +  *f = fopen(fn, "w+");
 +  if (!*f) {
 +    perror("fopen");
 +    exit(EXIT_FAILURE);
 +  }
 +}
 +
 +int main(int argc, char **argv) {
 +  struct data data;
 +  struct data *d = &data;
 +  size_t prefix_len;
 +  char *tempfn;
 +
 +  if (argc != 8) {
++    printf("Usage: 008_remove_segments_helper host user passwd database port socket prefix\n");
 +    exit(EXIT_FAILURE);
 +  }
 +
 +  d->mysql = connect_to_mysql(argv);
 +  d->mysql2 = connect_to_mysql(argv);
 +
 +  prefix_len = strlen(argv[7]);
 +  tempfn = (char *) malloc(prefix_len + 15);
 +  strcpy(tempfn, argv[7]);
 +
 +  strcpy(tempfn + prefix_len, "ways");
 +  open_file(&d->ways, tempfn);
 +
 +  strcpy(tempfn + prefix_len, "way_nodes");
 +  open_file(&d->way_nodes, tempfn);
 +
 +  strcpy(tempfn + prefix_len, "way_tags");
 +  open_file(&d->way_tags, tempfn);
 +
 +  strcpy(tempfn + prefix_len, "relations");
 +  open_file(&d->relations, tempfn);
 +
 +  strcpy(tempfn + prefix_len, "relation_members");
 +  open_file(&d->relation_members, tempfn);
 +
 +  strcpy(tempfn + prefix_len, "relation_tags");
 +  open_file(&d->relation_tags, tempfn);
 +
 +  free(tempfn);
 +
 +  find_maxids(d);
 +  populate_segs(d);
 +  convert_ways(d);
 +  mark_tagged_segs(d);
 +  convert_remaining_segs(d);
 +
 +  mysql_close(d->mysql);
 +  mysql_close(d->mysql2);
 +
 +  fclose(d->ways);
 +  fclose(d->way_nodes);
 +  fclose(d->way_tags);
 +
 +  fclose(d->relations);
 +  fclose(d->relation_members);
 +  fclose(d->relation_tags);
 +
 +  free(d->segs);
 +  free(d->rem_segs);
 +
 +  exit(EXIT_SUCCESS);
 +}