From: Shaun McDonald Date: Fri, 12 Dec 2008 19:29:27 +0000 (+0000) Subject: resync from rails_port 11795:12304 X-Git-Tag: live~8595^2~97 X-Git-Url: https://git.openstreetmap.org./rails.git/commitdiff_plain/721dd9c27d299455b9159457a095716f797389f1?hp=-c resync from rails_port 11795:12304 --- 721dd9c27d299455b9159457a095716f797389f1 diff --combined app/controllers/trace_controller.rb index 06ae5dc3b,bcac11844..022c304fb --- a/app/controllers/trace_controller.rb +++ b/app/controllers/trace_controller.rb @@@ -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 @@@ -33,26 -34,26 +34,27 @@@ # 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], @@@ -78,17 -79,7 +80,7 @@@ 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 @@@ -140,7 -131,7 +132,7 @@@ 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 @@@ -158,7 -149,7 +150,7 @@@ end end else - render :nothing, :status => :forbidden + render :nothing => true, :status => :forbidden end rescue ActiveRecord::RecordNotFound render :nothing => true, :status => :not_found @@@ -174,10 -165,10 +166,10 @@@ 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 @@@ -193,17 -184,17 +185,17 @@@ 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 = ?" @@@ -234,7 -225,7 +226,7 @@@ 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 @@@ -250,7 -241,7 +242,7 @@@ 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 0eddb259d,fae037110..ce244fe02 --- a/app/models/user.rb +++ b/app/models/user.rb @@@ -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 @@@ -106,8 -104,17 +106,21 @@@ 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 diff --combined db/migrate/018_add_timestamp_indexes.rb index c6b3bc7c2,000000000..c6b3bc7c2 mode 100644,000000..100644 --- a/db/migrate/018_add_timestamp_indexes.rb +++ b/db/migrate/018_add_timestamp_indexes.rb @@@ -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 diff --combined db/migrate/019_populate_node_tags_and_remove.rb index 2a3f3c988,000000000..860358646 mode 100644,000000..100644 --- a/db/migrate/019_populate_node_tags_and_remove.rb +++ b/db/migrate/019_populate_node_tags_and_remove.rb @@@ -1,60 -1,0 +1,60 @@@ +class PopulateNodeTagsAndRemove < ActiveRecord::Migration + def self.up + have_nodes = select_value("SELECT count(*) FROM current_nodes").to_i != 0 + + if have_nodes - prefix = File.join Dir.tmpdir, "017_populate_node_tags_and_remove.#{$$}." ++ prefix = File.join Dir.tmpdir, "019_populate_node_tags_and_remove.#{$$}." + - cmd = "db/migrate/018_populate_node_tags_and_remove_helper" ++ 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 diff --combined db/migrate/019_populate_node_tags_and_remove_helper.c index 83c1b1743,000000000..c41ea33da mode 100644,000000..100644 --- a/db/migrate/019_populate_node_tags_and_remove_helper.c +++ b/db/migrate/019_populate_node_tags_and_remove_helper.c @@@ -1,241 -1,0 +1,241 @@@ +#include +#include +#include +#include +#include + +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: %s\n", err); ++ fprintf(stderr, "019_populate_node_tags_and_remove_helper: MySQL error: %s\n", err); + } else { - fprintf(stderr, "018_populate_node_tags_and_remove_helper: MySQL error\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: 018_populate_node_tags_and_remove_helper host user passwd database port socket prefix\n"); ++ 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(¤t_nodes, tempfn); + + strcpy(tempfn + prefix_len, "current_node_tags"); + open_file(¤t_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); +} diff --combined db/migrate/020_move_to_innodb.rb index da0488ca5,000000000..da0488ca5 mode 100644,000000..100644 --- a/db/migrate/020_move_to_innodb.rb +++ b/db/migrate/020_move_to_innodb.rb @@@ -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 diff --combined db/migrate/021_key_constraints.rb index 40f98be02,000000000..40f98be02 mode 100644,000000..100644 --- a/db/migrate/021_key_constraints.rb +++ b/db/migrate/021_key_constraints.rb @@@ -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 diff --combined db/migrate/022_add_changesets.rb index e0cf3904a,000000000..e0cf3904a mode 100644,000000..100644 --- a/db/migrate/022_add_changesets.rb +++ b/db/migrate/022_add_changesets.rb @@@ -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 diff --combined db/migrate/023_order_relation_members.rb index 5500edfcf,000000000..5500edfcf mode 100644,000000..100644 --- a/db/migrate/023_order_relation_members.rb +++ b/db/migrate/023_order_relation_members.rb @@@ -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 diff --combined db/migrate/024_add_end_time_to_changesets.rb index b87ce3fde,000000000..b87ce3fde mode 100644,000000..100644 --- a/db/migrate/024_add_end_time_to_changesets.rb +++ b/db/migrate/024_add_end_time_to_changesets.rb @@@ -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