]> git.openstreetmap.org Git - rails.git/commitdiff
resync from rails_port 11795:12304
authorShaun McDonald <shaun@shaunmcdonald.me.uk>
Fri, 12 Dec 2008 19:29:27 +0000 (19:29 +0000)
committerShaun McDonald <shaun@shaunmcdonald.me.uk>
Fri, 12 Dec 2008 19:29:27 +0000 (19:29 +0000)
1  2 
app/controllers/trace_controller.rb
app/models/user.rb
db/migrate/018_add_timestamp_indexes.rb
db/migrate/019_populate_node_tags_and_remove.rb
db/migrate/019_populate_node_tags_and_remove_helper.c
db/migrate/020_move_to_innodb.rb
db/migrate/021_key_constraints.rb
db/migrate/022_add_changesets.rb
db/migrate/023_order_relation_members.rb
db/migrate/024_add_end_time_to_changesets.rb

index 06ae5dc3b7e1910d6614295d9f5e791b0ec876dc,bcac1184454aa1bffd5ab5694bbdbb88398ee85e..022c304fb15b685dfb0bb39b20e097d25d34ec2d
@@@ -2,6 -2,7 +2,7 @@@ class TraceController < ApplicationCont
    layout 'site'
  
    before_filter :authorize_web  
+   before_filter :require_user, :only => [:mine, :edit, :delete, :make_public]
    before_filter :authorize, :only => [:api_details, :api_data, :api_create]
    before_filter :check_database_availability, :except => [:api_details, :api_data, :api_create]
    before_filter :check_read_availability, :only => [:api_details, :api_data, :api_create]
@@@ -12,7 -13,7 +13,7 @@@
      # from display name, pick up user id if one user's traces only
      display_name = params[:display_name]
      if target_user.nil? and !display_name.blank?
 -      target_user = User.find(:first, :conditions => [ "visible = 1 and display_name = ?", display_name])
 +      target_user = User.find(:first, :conditions => [ "visible = ? and display_name = ?", true, display_name])
      end
  
      # set title
      # 4 - user's traces, not logged in as that user = all user's public traces
      if target_user.nil? # all traces
        if @user
 -        conditions = ["(gpx_files.public = 1 OR gpx_files.user_id = ?)", @user.id] #1
 +        conditions = ["(gpx_files.public = ? OR gpx_files.user_id = ?)", true, @user.id] #1
        else
 -        conditions  = ["gpx_files.public = 1"] #2
 +        conditions  = ["gpx_files.public = ?", true] #2
        end
      else
        if @user and @user == target_user
          conditions = ["gpx_files.user_id = ?", @user.id] #3 (check vs user id, so no join + can't pick up non-public traces by changing name)
        else
 -        conditions = ["gpx_files.public = 1 AND gpx_files.user_id = ?", target_user.id] #4
 +        conditions = ["gpx_files.public = ? AND gpx_files.user_id = ?", true, target_user.id] #4
        end
      end
      
      if params[:tag]
        @tag = params[:tag]
-       conditions[0] += " AND EXISTS (SELECT * FROM gpx_file_tags AS gft WHERE gft.gpx_id = gpx_files.id AND gft.tag = ?)"
-       conditions << @tag
+       files = Tracetag.find_all_by_tag(params[:tag]).collect { |tt| tt.gpx_id }
+       conditions[0] += " AND gpx_files.id IN (#{files.join(',')})"
      end
      
 -    conditions[0] += " AND gpx_files.visible = 1"
 +    conditions[0] += " AND gpx_files.visible = ?"
 +    conditions << true
  
      @trace_pages, @traces = paginate(:traces,
                                       :include => [:user, :tags],
    end
  
    def mine
-     if @user
-       @trace = Trace.new
-       unless @user.trace_public_default.nil?
-         @trace.public = true
-       else 
-         @trace.public = false
-       end
-       list(@user, "mine") unless @user.nil?
-     else
-       redirect_to :controller => 'user', :action => 'login', :referer => request.request_uri
-     end
+     list(@user, "mine")
    end
  
    def view
          send_file(trace.trace_name, :filename => "#{trace.id}#{trace.extension_name}", :type => trace.mime_type, :disposition => 'attachment')
        end
      else
-       render :nothing, :status => :not_found
+       render :nothing => true, :status => :not_found
      end
    rescue ActiveRecord::RecordNotFound
      render :nothing => true, :status => :not_found
          end        
        end
      else
-       render :nothing, :status => :forbidden
+       render :nothing => true, :status => :forbidden
      end
    rescue ActiveRecord::RecordNotFound
      render :nothing => true, :status => :not_found
          flash[:notice] = 'Track scheduled for deletion'
          redirect_to :controller => 'traces', :action => 'mine'
        else
-         render :nothing, :status => :bad_request
+         render :nothing => true, :status => :bad_request
        end
      else
-       render :nothing, :status => :forbidden
+       render :nothing => true, :status => :forbidden
      end
    rescue ActiveRecord::RecordNotFound
      render :nothing => true, :status => :not_found
          flash[:notice] = 'Track made public'
          redirect_to :controller => 'trace', :action => 'view', :id => params[:id]
        else
-         render :nothing, :status => :bad_request
+         render :nothing => true, :status => :bad_request
        end
      else
-       render :nothing, :status => :forbidden
+       render :nothing => true, :status => :forbidden
      end
    rescue ActiveRecord::RecordNotFound
      render :nothing => true, :status => :not_found
    end
  
    def georss
 -    conditions = ["gpx_files.public = 1"]
 +    conditions = ["gpx_files.public = ?", true]
  
      if params[:display_name]
        conditions[0] += " AND users.display_name = ?"
        if trace.public? or (@user and @user == trace.user)
          send_file(trace.large_picture_name, :filename => "#{trace.id}.gif", :type => 'image/gif', :disposition => 'inline')
        else
-         render :nothing, :status => :forbidden
+         render :nothing => true, :status => :forbidden
        end
      else
        render :nothing => true, :status => :not_found
        if trace.public? or (@user and @user == trace.user)
          send_file(trace.icon_picture_name, :filename => "#{trace.id}_icon.gif", :type => 'image/gif', :disposition => 'inline')
        else
-         render :nothing, :status => :forbidden
+         render :nothing => true, :status => :forbidden
        end
      else
        render :nothing => true, :status => :not_found
@@@ -318,17 -309,6 +310,17 @@@ privat
      else
        FileUtils.rm_f(filename)
      end
 +    
 +    # Finally save whether the user marked the trace as being public
 +    if @trace.public?
 +      if @user.trace_public_default.nil?
 +        @user.preferences.create(:k => "gps.trace.public", :v => "default")
 +      end
 +    else
 +      pref = @user.trace_public_default
 +      pref.destroy unless pref.nil?
 +    end
 +    
    end
  
  end
diff --combined app/models/user.rb
index 0eddb259d01213506df8aa502aa71076b0a669aa,fae037110951ffe64d847fba40b99a0c37d40d9e..ce244fe02448bce114dbb79d25b6a009a4848f28
@@@ -4,21 -4,19 +4,21 @@@ class User < ActiveRecord::Bas
    has_many :traces
    has_many :diary_entries, :order => 'created_at DESC'
    has_many :messages, :foreign_key => :to_user_id, :order => 'sent_on DESC'
 -  has_many :new_messages, :class_name => "Message", :foreign_key => :to_user_id, :conditions => "message_read = 0", :order => 'sent_on DESC'
 +  has_many :new_messages, :class_name => "Message", :foreign_key => :to_user_id, :conditions => {:message_read => false}, :order => 'sent_on DESC'
    has_many :sent_messages, :class_name => "Message", :foreign_key => :from_user_id, :order => 'sent_on DESC'
 -  has_many :friends, :include => :befriendee, :conditions => "users.visible = 1"
 +  has_many :friends, :include => :befriendee, :conditions => ["users.visible = ?", true]
    has_many :tokens, :class_name => "UserToken"
    has_many :preferences, :class_name => "UserPreference"
 +  has_many :changesets
  
    validates_presence_of :email, :display_name
    validates_confirmation_of :email, :message => 'Email addresses must match'
    validates_confirmation_of :pass_crypt, :message => 'Password must match the confirmation password'
    validates_uniqueness_of :display_name, :allow_nil => true
    validates_uniqueness_of :email
 -  validates_length_of :pass_crypt, :minimum => 8
 -  validates_length_of :display_name, :minimum => 3, :allow_nil => true
 +  validates_length_of :pass_crypt, :within => 8..255
 +  validates_length_of :display_name, :within => 3..255, :allow_nil => true
 +  validates_length_of :email, :within => 6..255
    validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
    validates_format_of :display_name, :with => /^[^\/;.,?]*$/
    validates_numericality_of :home_lat, :allow_nil => true
@@@ -82,7 -80,7 +82,7 @@@
      if self.home_lon and self.home_lat 
        gc = OSM::GreatCircle.new(self.home_lat, self.home_lon)
        bounds = gc.bounds(radius)
 -      nearby = User.find(:all, :conditions => "visible = 1 and home_lat between #{bounds[:minlat]} and #{bounds[:maxlat]} and home_lon between #{bounds[:minlon]} and #{bounds[:maxlon]} and data_public = 1 and id != #{self.id}")
 +      nearby = User.find(:all, :conditions => ["visible = ? and home_lat between #{bounds[:minlat]} and #{bounds[:maxlat]} and home_lon between #{bounds[:minlon]} and #{bounds[:maxlon]} and data_public = ? and id != #{self.id}", true, true])
        nearby.delete_if { |u| gc.distance(u.home_lat, u.home_lon) > radius }
        nearby.sort! { |u1,u2| gc.distance(u1.home_lat, u1.home_lon) <=> gc.distance(u2.home_lat, u2.home_lon) }
      else
      return false
    end
  
 +  def trace_public_default
 +    return self.preferences.find(:first, :conditions => {:k => "gps.trace.public", :v => "default"})
 +  end
 +
+   def delete
+     self.active = false
+     self.display_name = "user_#{self.id}"
+     self.description = nil
+     self.home_lat = nil
+     self.home_lon = nil
+     self.image = nil
+     self.email_valid = false
+     self.new_email = nil
+     self.visible = false
+     self.save
+   end
  end
index c6b3bc7c2cd3e303f606dabd234483bcd6750b1b,0000000000000000000000000000000000000000..c6b3bc7c2cd3e303f606dabd234483bcd6750b1b
mode 100644,000000..100644
--- /dev/null
@@@ -1,11 -1,0 +1,11 @@@
 +class AddTimestampIndexes < ActiveRecord::Migration
 +  def self.up
 +    add_index :current_ways, :timestamp, :name => :current_ways_timestamp_idx
 +    add_index :current_relations, :timestamp, :name => :current_relations_timestamp_idx
 +  end
 +
 +  def self.down
 +    remove_index :current_ways, :name => :current_ways_timestamp_idx
 +    remove_index :current_relations, :name => :current_relations_timestamp_idx
 +  end
 +end
index 2a3f3c9881f13d0e6f0c2415364eda229a967bd2,0000000000000000000000000000000000000000..8603586463f8b8330583a33bf67d87dd737d5257
mode 100644,000000..100644
--- /dev/null
@@@ -1,60 -1,0 +1,60 @@@
-       prefix = File.join Dir.tmpdir, "017_populate_node_tags_and_remove.#{$$}."
 +class PopulateNodeTagsAndRemove < ActiveRecord::Migration
 +  def self.up
 +    have_nodes = select_value("SELECT count(*) FROM current_nodes").to_i != 0
 +
 +    if have_nodes
-       cmd = "db/migrate/018_populate_node_tags_and_remove_helper"
++      prefix = File.join Dir.tmpdir, "019_populate_node_tags_and_remove.#{$$}."
 +
++      cmd = "db/migrate/019_populate_node_tags_and_remove_helper"
 +      src = "#{cmd}.c"
 +      if not File.exists? cmd or File.mtime(cmd) < File.mtime(src) then 
 +        system 'cc -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 = ['nodes', 'node_tags', 'current_nodes', 'current_node_tags'].
 +        map { |base| prefix + base }
 +      nodes, node_tags, current_nodes, current_node_tags = tempfiles
 +    end
 +
 +    execute "TRUNCATE nodes"
 +    remove_column :nodes, :tags
 +    remove_column :current_nodes, :tags
 +
 +    add_column :nodes, :version, :bigint, :limit => 20, :null => false
 +
 +    create_table :current_node_tags, innodb_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
 +
 +    create_table :node_tags, innodb_table do |t|
 +      t.column :id,          :bigint, :limit => 64, :null => false
 +      t.column :version,     :bigint, :limit => 20, :null => false
 +      t.column :k,         :string, :default => "", :null => false
 +      t.column :v,         :string, :default => "", :null => false
 +    end
 +
 +    # now get the data back
 +    csvopts = "FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY '\"' LINES TERMINATED BY '\\n'"
 +
 +    if have_nodes
 +      execute "LOAD DATA INFILE '#{nodes}' INTO TABLE nodes #{csvopts} (id, latitude, longitude, user_id, visible, timestamp, tile, version)";
 +      execute "LOAD DATA INFILE '#{node_tags}' INTO TABLE node_tags #{csvopts} (id, version, k, v)"
 +      execute "LOAD DATA INFILE '#{current_node_tags}' INTO TABLE current_node_tags #{csvopts} (id, k, v)"
 +    end
 +
 +    tempfiles.each { |fn| File.unlink fn } if have_nodes
 +  end
 +
 +  def self.down
 +    raise IrreversibleMigration.new
 +#    add_column :nodes, "tags", :text, :default => "", :null => false
 +#    add_column :current_nodes, "tags", :text, :default => "", :null => false
 +  end
 +end
index 83c1b174367fd0ac4b473ed8f7939914614d1a1b,0000000000000000000000000000000000000000..c41ea33da77e1912bd0ceba7291b6ce04ebf919d
mode 100644,000000..100644
--- /dev/null
@@@ -1,241 -1,0 +1,241 @@@
-     fprintf(stderr, "018_populate_node_tags_and_remove_helper: MySQL error: %s\n", err);
 +#include <mysql.h>
 +#include <stdint.h>
 +#include <stdio.h>
 +#include <stdlib.h>
 +#include <string.h>
 +
 +static void exit_mysql_err(MYSQL *mysql) {
 +  const char *err = mysql_error(mysql);
 +  if (err) {
-     fprintf(stderr, "018_populate_node_tags_and_remove_helper: MySQL error\n");
++    fprintf(stderr, "019_populate_node_tags_and_remove_helper: MySQL error: %s\n", err);
 +  } else {
-     printf("Usage: 018_populate_node_tags_and_remove_helper host user passwd database port socket prefix\n");
++    fprintf(stderr, "019_populate_node_tags_and_remove_helper: MySQL error\n");
 +  }
 +  abort();
 +  exit(EXIT_FAILURE);
 +}
 +
 +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 unescape(char *str) {
 +  char *i = str, *o = str, tmp;
 +
 +  while (*i) {
 +    if (*i == '\\') {
 +      i++;
 +      switch (tmp = *i++) {
 +        case 's': *o++ = ';'; break;
 +        case 'e': *o++ = '='; break;
 +        case '\\': *o++ = '\\'; break;
 +        default: *o++ = tmp; break;
 +      }
 +    } else {
 +      *o++ = *i++;
 +    }
 +  }
 +}
 +
 +static int read_node_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';
 +
 +  unescape(*k);
 +  unescape(*v);
 +
 +  return 1;
 +}
 +
 +struct data {
 +  MYSQL *mysql;
 +  size_t version_size;
 +  uint16_t *version;
 +};
 +
 +static void proc_nodes(struct data *d, const char *tbl, FILE *out, FILE *out_tags, int hist) {
 +  MYSQL_RES *res;
 +  MYSQL_ROW row;
 +  char query[256];
 +
 +  snprintf(query, sizeof(query),  "SELECT id, latitude, longitude, "
 +      "user_id, visible, tags, timestamp, tile FROM %s", tbl);
 +  if (mysql_query(d->mysql, query))
 +    exit_mysql_err(d->mysql);
 +
 +  res = mysql_use_result(d->mysql);
 +  if (!res) exit_mysql_err(d->mysql);
 +
 +  while ((row = mysql_fetch_row(res))) {
 +    unsigned long id = strtoul(row[0], NULL, 10);
 +    uint32_t version;
 +
 +    if (id >= d->version_size) {
 +      fprintf(stderr, "preallocated nodes size exceeded");
 +      abort();
 +    }
 +
 +    if (hist) {
 +      version = ++(d->version[id]);
 +
 +      fprintf(out, "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%u\"\n",
 +        row[0], row[1], row[2], row[3], row[4], row[6], row[7], version);
 +    } else {
 +      /*fprintf(out, "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n",
 +      row[0], row[1], row[2], row[3], row[4], row[6], row[7]);*/
 +    }
 +
 +    char *tags_it = row[5], *k, *v;
 +    while (read_node_tags(&tags_it, &k, &v)) {
 +      if (hist) {
 +        fprintf(out_tags, "\"%s\",\"%u\",", row[0], version);
 +      } else {
 +        fprintf(out_tags, "\"%s\",", row[0]);
 +      }
 +
 +      write_csv_col(out_tags, k, ',');
 +      write_csv_col(out_tags, v, '\n');
 +    }
 +  }
 +  if (mysql_errno(d->mysql)) exit_mysql_err(d->mysql);
 +
 +  mysql_free_result(res);
 +}
 +
 +static size_t select_size(MYSQL *mysql, const char *q) {
 +  MYSQL_RES *res;
 +  MYSQL_ROW row;
 +  size_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 = strtoul(row[0], NULL, 10);
 +  } else {
 +    ret = 0;
 +  }
 +
 +  mysql_free_result(res);
 +
 +  return ret;
 +}
 +
 +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) {
 +  size_t prefix_len;
 +  FILE *current_nodes, *current_node_tags, *nodes, *node_tags;
 +  char *tempfn;
 +  struct data data, *d = &data;
 +
 +  if (argc != 8) {
++    printf("Usage: 019_populate_node_tags_and_remove_helper host user passwd database port socket prefix\n");
 +    exit(EXIT_FAILURE);
 +  }
 +
 +  d->mysql = connect_to_mysql(argv);
 +
 +  d->version_size = 1 + select_size(d->mysql, "SELECT max(id) FROM current_nodes");
 +  d->version = (uint16_t *) malloc(sizeof(uint16_t) * d->version_size);
 +  if (!d->version) {
 +    perror("malloc");
 +    abort();
 +    exit(EXIT_FAILURE);
 +  }
 +  memset(d->version, 0, sizeof(uint16_t) * d->version_size);
 +
 +  prefix_len = strlen(argv[7]);
 +  tempfn = (char *) malloc(prefix_len + 32);
 +  strcpy(tempfn, argv[7]);
 +
 +  strcpy(tempfn + prefix_len, "current_nodes");
 +  open_file(&current_nodes, tempfn);
 +
 +  strcpy(tempfn + prefix_len, "current_node_tags");
 +  open_file(&current_node_tags, tempfn);
 +
 +  strcpy(tempfn + prefix_len, "nodes");
 +  open_file(&nodes, tempfn);
 +
 +  strcpy(tempfn + prefix_len, "node_tags");
 +  open_file(&node_tags, tempfn);
 +
 +  free(tempfn);
 +
 +  proc_nodes(d, "nodes", nodes, node_tags, 1);
 +  proc_nodes(d, "current_nodes", current_nodes, current_node_tags, 0);
 +
 +  free(d->version);
 +
 +  mysql_close(d->mysql);
 +
 +  fclose(current_nodes);
 +  fclose(current_node_tags);
 +  fclose(nodes);
 +  fclose(node_tags);
 +
 +  exit(EXIT_SUCCESS);
 +}
index da0488ca5f7a3d6bb40f6ccc7717ceb34487096e,0000000000000000000000000000000000000000..da0488ca5f7a3d6bb40f6ccc7717ceb34487096e
mode 100644,000000..100644
--- /dev/null
@@@ -1,45 -1,0 +1,45 @@@
 +class MoveToInnodb < ActiveRecord::Migration
 +  @@conv_tables = ['nodes', 'ways', 'way_tags', 'way_nodes',
 +    'current_way_tags', 'relation_members',
 +    'relations', 'relation_tags', 'current_relation_tags']
 +
 +  @@ver_tbl = ['nodes', 'ways', 'relations']
 +
 +  def self.up
 +    remove_index :current_way_tags, :name=> :current_way_tags_v_idx
 +    remove_index :current_relation_tags, :name=> :current_relation_tags_v_idx
 +
 +    @@ver_tbl.each { |tbl|
 +      change_column tbl, "version", :bigint, :limit => 20, :null => false
 +    }
 +
 +    @@conv_tables.each { |tbl|
 +      change_engine (tbl, "InnoDB")
 +    }
 +
 +    @@ver_tbl.each { |tbl|
 +      add_column "current_#{tbl}", "version", :bigint, :limit => 20, :null => false
 +      # As the initial version of all nodes, ways and relations is 0, we set the 
 +      # current version to something less so that we can update the version in 
 +      # batches of 10000
 +      tbl.classify.constantize.update_all("version=-1")
 +      while tbl.classify.constantize.count(:conditions => {:version => -1}) > 0
 +        tbl.classify.constantize.update_all("version=(SELECT max(version) FROM #{tbl} WHERE #{tbl}.id = current_#{tbl}.id)", {:version => -1}, :limit => 10000)
 +      end
 +     # execute "UPDATE current_#{tbl} SET version = " +
 +      #  "(SELECT max(version) FROM #{tbl} WHERE #{tbl}.id = current_#{tbl}.id)"
 +        # The above update causes a MySQL error:
 +        # -- add_column("current_nodes", "version", :bigint, {:null=>false, :limit=>20})
 +        # -> 1410.9152s
 +        # -- execute("UPDATE current_nodes SET version = (SELECT max(version) FROM nodes WHERE nodes.id = current_nodes.id)")
 +        # rake aborted!
 +        # Mysql::Error: The total number of locks exceeds the lock table size: UPDATE current_nodes SET version = (SELECT max(version) FROM nodes WHERE nodes.id = current_nodes.id)
 +
 +        # The above rails version will take longer, however will no run out of locks
 +    }
 +  end
 +
 +  def self.down
 +    raise IrreversibleMigration.new
 +  end
 +end
index 40f98be02b9eb3dd61cc0538612fff288d353492,0000000000000000000000000000000000000000..40f98be02b9eb3dd61cc0538612fff288d353492
mode 100644,000000..100644
--- /dev/null
@@@ -1,50 -1,0 +1,50 @@@
 +class KeyConstraints < ActiveRecord::Migration
 +  def self.up
 +    # Primary keys
 +    add_primary_key :current_node_tags, [:id, :k]
 +    add_primary_key :current_way_tags, [:id, :k]
 +    add_primary_key :current_relation_tags, [:id, :k]
 +
 +    add_primary_key :node_tags, [:id, :version, :k]
 +    add_primary_key :way_tags, [:id, :version, :k]
 +    add_primary_key :relation_tags, [:id, :version, :k]
 +
 +    add_primary_key :nodes, [:id, :version]
 +
 +    # Remove indexes superseded by primary keys
 +    remove_index :current_way_tags, :name => :current_way_tags_id_idx
 +    remove_index :current_relation_tags, :name => :current_relation_tags_id_idx
 +
 +    remove_index :way_tags, :name => :way_tags_id_version_idx
 +    remove_index :relation_tags, :name => :relation_tags_id_version_idx
 +
 +    remove_index :nodes, :name => :nodes_uid_idx
 +
 +    # Foreign keys (between ways, way_tags, way_nodes, etc.)
 +    add_foreign_key :current_node_tags, [:id], :current_nodes
 +    add_foreign_key :node_tags, [:id, :version], :nodes
 +
 +    add_foreign_key :current_way_tags, [:id], :current_ways
 +    add_foreign_key :current_way_nodes, [:id], :current_ways
 +    add_foreign_key :way_tags, [:id, :version], :ways
 +    add_foreign_key :way_nodes, [:id, :version], :ways
 +
 +    add_foreign_key :current_relation_tags, [:id], :current_relations
 +    add_foreign_key :current_relation_members, [:id], :current_relations
 +    add_foreign_key :relation_tags, [:id, :version], :relations
 +    add_foreign_key :relation_members, [:id, :version], :relations
 +
 +    # Foreign keys (between different types of primitives)
 +    add_foreign_key :current_way_nodes, [:node_id], :current_nodes, [:id]
 +
 +    # FIXME: We don't have foreign keys for relation members since the id
 +    # might point to a different table depending on the `type' column.
 +    # We'd probably need different current_relation_member_nodes,
 +    # current_relation_member_ways and current_relation_member_relations
 +    # tables for this to work cleanly.
 +  end
 +
 +  def self.down
 +    raise IrreversibleMigration.new
 +  end
 +end
index e0cf3904a50d8ea98f79e40d1e20f5f8707336ae,0000000000000000000000000000000000000000..e0cf3904a50d8ea98f79e40d1e20f5f8707336ae
mode 100644,000000..100644
--- /dev/null
@@@ -1,46 -1,0 +1,46 @@@
 +class AddChangesets < ActiveRecord::Migration
 +  @@conv_user_tables = ['current_nodes',
 +  'current_relations', 'current_ways', 'nodes', 'relations', 'ways' ]
 +  
 +  def self.up
 +    create_table "changesets", innodb_table do |t|
 +      t.column "id",             :bigint_pk,              :null => false
 +      t.column "user_id",        :bigint,   :limit => 20, :null => false
 +      t.column "created_at",     :datetime,               :null => false
 +      t.column "open",           :boolean,                :null => false, :default => true
 +      t.column "min_lat",        :integer,                :null => true
 +      t.column "max_lat",        :integer,                :null => true
 +      t.column "min_lon",        :integer,                :null => true
 +      t.column "max_lon",        :integer,                :null => true
 +    end
 +
 +    create_table "changeset_tags", innodb_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 "changeset_tags", ["id"], :name => "changeset_tags_id_idx"
 +    
 +    #
 +    # Initially we will have one changeset for every user containing 
 +    # all edits up to the API change,  
 +    # all the changesets will have the id of the user that made them.
 +    # We need to generate a changeset for each user in the database
 +    execute "INSERT INTO changesets (id, user_id, created_at, open)" + 
 +      "SELECT id, id, creation_time, false from users;"
 +
 +    @@conv_user_tables.each { |tbl|
 +      rename_column tbl, :user_id, :changeset_id
 +      #foreign keys too
 +      add_foreign_key tbl, [:changeset_id], :changesets, [:id]
 +    }
 +  end
 +
 +  def self.down
 +    # It's not easy to generate the user ids from the changesets
 +    raise IrreversibleMigration.new
 +    #drop_table "changesets"
 +    #drop_table "changeset_tags"
 +  end
 +end
index 5500edfcfbf4ac42e9a35e9d754dfdc049a96a47,0000000000000000000000000000000000000000..5500edfcfbf4ac42e9a35e9d754dfdc049a96a47
mode 100644,000000..100644
--- /dev/null
@@@ -1,33 -1,0 +1,33 @@@
 +class OrderRelationMembers < ActiveRecord::Migration
 +  def self.up
 +    # add sequence column. rails won't let us define an ordering here,
 +    # as defaults must be constant.
 +    add_column(:relation_members, :sequence_id, :integer,
 +               :default => 0, :null => false)
 +
 +    # update the sequence column with default (partial) ordering by 
 +    # element ID. the sequence ID is a smaller int type, so we can't
 +    # just copy the member_id.
 +    execute("update relation_members set sequence_id = mod(member_id, 16384)")
 +
 +    # need to update the primary key to include the sequence number, 
 +    # otherwise the primary key will barf when we have repeated members.
 +    # mysql barfs on this anyway, so we need a single command. this may
 +    # not work in postgres... needs testing.
 +    alter_primary_key("relation_members", [:id, :version, :member_type, :member_id, :member_role, :sequence_id])
 +
 +    # do the same for the current tables
 +    add_column(:current_relation_members, :sequence_id, :integer,
 +               :default => 0, :null => false)
 +    execute("update current_relation_members set sequence_id = mod(member_id, 16384)")
 +    alter_primary_key("current_relation_members", [:id, :member_type, :member_id, :member_role, :sequence_id])
 +  end
 +
 +  def self.down
 +    alter_primary_key("current_relation_members", [:id, :member_type, :member_id, :member_role])
 +    remove_column :relation_members, :sequence_id
 +
 +    alter_primary_key("relation_members", [:id, :version, :member_type, :member_id, :member_role])
 +    remove_column :current_relation_members, :sequence_id
 +  end
 +end
index b87ce3fdebabcf12abd2d1a192eb36547b9f155b,0000000000000000000000000000000000000000..b87ce3fdebabcf12abd2d1a192eb36547b9f155b
mode 100644,000000..100644
--- /dev/null
@@@ -1,34 -1,0 +1,34 @@@
 +class AddEndTimeToChangesets < ActiveRecord::Migration
 +  def self.up
 +    # swap the boolean closed-or-not for a time when the changeset will
 +    # close or has closed.
 +    add_column(:changesets, :closed_at, :datetime, :null => false)
 +    
 +    # it appears that execute will only accept string arguments, so
 +    # this is an ugly, ugly hack to get some sort of mysql/postgres
 +    # independence. now i have to go wash my brain with bleach.
 +    execute("update changesets set closed_at=(now()-'1 hour') where open=(1=0)")
 +    execute("update changesets set closed_at=(now()+'1 hour') where open=(1=1)")
 +
 +    # remove the open column as it is unnecessary now and denormalises 
 +    # the table.
 +    remove_column :changesets, :open
 +
 +    # add a column to keep track of the number of changes in a changeset.
 +    # could probably work out how many changes there are here, but i'm not
 +    # sure its actually important.
 +    add_column(:changesets, :num_changes, :integer, 
 +               :null => false, :default => 0)
 +  end
 +
 +  def self.down
 +    # in the reverse direction, we can look at the closed_at to figure out
 +    # if changesets are closed or not.
 +    add_column(:changesets, :open, :boolean, :null => false, :default => true)
 +    execute("update changesets set open=(closed_at > now())")
 +    remove_column :changesets, :closed_at
 +
 +    # remove the column for tracking number of changes
 +    remove_column :changesets, :num_changes
 +  end
 +end