]> git.openstreetmap.org Git - rails.git/commitdiff
split_node_tags: Merge changes in main branch up to r7649.
authorGabriel Ebner <gabriel@svn.openstreetmap.org>
Sun, 4 May 2008 08:31:39 +0000 (08:31 +0000)
committerGabriel Ebner <gabriel@svn.openstreetmap.org>
Sun, 4 May 2008 08:31:39 +0000 (08:31 +0000)
18 files changed:
app/controllers/node_controller.rb
app/controllers/relation_controller.rb
app/controllers/way_controller.rb
app/models/node.rb
app/models/node_tag.rb [new file with mode: 0644]
app/models/old_node.rb
app/models/old_node_tag.rb [new file with mode: 0644]
app/models/old_relation.rb
app/models/old_way.rb
app/models/relation.rb
app/models/temp_old_node.rb [new file with mode: 0644]
app/models/way.rb
config/database.yml
db/migrate/013_populate_node_tags_and_remove.rb [new file with mode: 0644]
db/migrate/013_populate_node_tags_and_remove_helper.c [new file with mode: 0644]
db/migrate/014_move_to_innodb.rb [new file with mode: 0644]
db/migrate/015_key_constraints.rb [new file with mode: 0644]
lib/migrate.rb

index edc3675e58382fce0b8b5801a2e7180ab280cec2..9f8f4a38bdf48e200dae71ce99e14cbf9ded46c3 100644 (file)
@@ -15,6 +15,7 @@ class NodeController < ApplicationController
       node = Node.from_xml(request.raw_post, true)
 
       if node
+       node.version = 0
         node.user_id = @user.id
         node.visible = true
         node.save_with_history!
index 2b1ba6c753c70df6579d381facebc7bd451be754..f4e938176c214a3d78bb83571d2cfd0400a331e8 100644 (file)
@@ -15,6 +15,7 @@ class RelationController < ApplicationController
         if !relation.preconditions_ok?
           render :text => "", :status => :precondition_failed
         else
+         relation.version = 0
           relation.user_id = @user.id
           relation.save_with_history!
 
index 3b6491cf0b3ceda5ed90ee7e1da5e49579c6af21..a7f74e50c7e66e465581c3deb575a582df5670b2 100644 (file)
@@ -15,6 +15,7 @@ class WayController < ApplicationController
         if !way.preconditions_ok?
           render :text => "", :status => :precondition_failed
         else
+         way.version = 0
           way.user_id = @user.id
           way.save_with_history!
 
index cc646b768c1aeb82fd6ce62c0f26724ed5f6a55b..17521428ea260cc9bab6defcdcc9457958968f70 100644 (file)
@@ -1,5 +1,3 @@
-# The node model represents a current existing node, that is, the latest version. Use OldNode for historical nodes.
-
 class Node < GeoRecord
   require 'xml/libxml'
 
@@ -10,16 +8,21 @@ class Node < GeoRecord
   validates_numericality_of :latitude, :longitude
   validate :validate_position
 
-  has_many :ways, :through => :way_nodes
   has_many :old_nodes, :foreign_key => :id
   has_many :way_nodes
+  has_many :node_tags, :foreign_key => :id
   belongs_to :user
  
-  # Sanity check the latitude and longitude and add an error if it's broken
   def validate_position
     errors.add_to_base("Node is not in the world") unless in_world?
   end
 
+  def in_world?
+    return false if self.lat < -90 or self.lat > 90
+    return false if self.lon < -180 or self.lon > 180
+    return true
+  end
+
   #
   # Search for nodes matching tags within bounding_box
   #
@@ -80,10 +83,9 @@ class Node < GeoRecord
         tags = []
 
         pt.find('tag').each do |tag|
-          tags << [tag['k'],tag['v']]
+          node.add_tag_key_val(tag['k'],tag['v'])
         end
 
-        node.tags = Tags.join(tags)
       end
     rescue
       node = nil
@@ -92,24 +94,37 @@ class Node < GeoRecord
     return node
   end
 
-  # Save this node with the appropriate OldNode object to represent it's history.
   def save_with_history!
+    t = Time.now
     Node.transaction do
-      self.timestamp = Time.now
+      self.version += 1
+      self.timestamp = t
       self.save!
+
+      # Create a NodeTag
+      tags = self.tags
+      NodeTag.delete_all(['id = ?', self.id])
+      tags.each do |k,v|
+       tag = NodeTag.new
+       tag.k = k 
+       tag.v = v 
+       tag.id = self.id
+       tag.save!
+      end 
+
+      # Create an OldNode
       old_node = OldNode.from_node(self)
-      old_node.save!
+      old_node.timestamp = t
+      old_node.save_with_dependencies!
     end
   end
 
-  # Turn this Node in to a complete OSM XML object with <osm> wrapper
   def to_xml
     doc = OSM::API.new.get_xml_doc
     doc.root << to_xml_node()
     return doc
   end
 
-  # Turn this Node in to an XML Node without the <osm> wrapper.
   def to_xml_node(user_display_name_cache = nil)
     el1 = XML::Node.new 'node'
     el1['id'] = self.id.to_s
@@ -128,7 +143,7 @@ class Node < GeoRecord
 
     el1['user'] = user_display_name_cache[self.user_id] unless user_display_name_cache[self.user_id].nil?
 
-    Tags.split(self.tags) do |k,v|
+    self.tags.each do |k,v|
       el2 = XML::Node.new('tag')
       el2['k'] = k.to_s
       el2['v'] = v.to_s
@@ -140,7 +155,6 @@ class Node < GeoRecord
     return el1
   end
 
-  # Return the node's tags as a Hash of keys and their values
   def tags_as_hash
     hash = {}
     Tags.split(self.tags) do |k,v|
@@ -148,4 +162,26 @@ class Node < GeoRecord
     end
     hash
   end
+
+  def tags
+    unless @tags
+      @tags = {}
+      self.node_tags.each do |tag|
+        @tags[tag.k] = tag.v
+      end
+    end
+    @tags
+  end
+
+  def tags=(t)
+    @tags = t 
+  end 
+
+  def add_tag_key_val(k,v)
+    @tags = Hash.new unless @tags
+    @tags[k] = v
+  end
+
+
+
 end
diff --git a/app/models/node_tag.rb b/app/models/node_tag.rb
new file mode 100644 (file)
index 0000000..9795ff4
--- /dev/null
@@ -0,0 +1,5 @@
+class NodeTag < ActiveRecord::Base
+  set_table_name 'current_node_tags'
+
+  belongs_to :node, :foreign_key => 'id'
+end
index 2c3e93b2075fc5de263923694f50221123734ec1..2f960d8866a3239066a14f1198ddac08a06646ff 100644 (file)
@@ -27,6 +27,7 @@ class OldNode < GeoRecord
     old_node.timestamp = node.timestamp
     old_node.user_id = node.user_id
     old_node.id = node.id
+    old_node.version = node.version
     return old_node
   end
 
@@ -37,7 +38,7 @@ class OldNode < GeoRecord
     el1['lon'] = self.lon.to_s
     el1['user'] = self.user.display_name if self.user.data_public?
 
-    Tags.split(self.tags) do |k,v|
+    self.tags.each do |k,v|
       el2 = XML::Node.new('tag')
       el2['k'] = k.to_s
       el2['v'] = v.to_s
@@ -48,4 +49,38 @@ class OldNode < GeoRecord
     el1['timestamp'] = self.timestamp.xmlschema
     return el1
   end
+
+  def save_with_dependencies!
+    save!
+    #not sure whats going on here
+    clear_aggregation_cache
+    clear_association_cache
+    #ok from here
+    @attributes.update(OldNode.find(:first, :conditions => ['id = ? AND timestamp = ?', self.id, self.timestamp]).instance_variable_get('@attributes'))
+   
+    self.tags.each do |k,v|
+      tag = OldNodeTag.new
+      tag.k = k
+      tag.v = v
+      tag.id = self.id
+      tag.version = self.version
+      tag.save!
+    end
+  end
+
+  def tags
+    unless @tags
+        @tags = Hash.new
+        OldNodeTag.find(:all, :conditions => ["id = ? AND version = ?", self.id, self.version]).each do |tag|
+            @tags[tag.k] = tag.v
+        end
+    end
+    @tags = Hash.new unless @tags
+    @tags
+  end
+
+  def tags=(t)
+    @tags = t 
+  end 
+
 end
diff --git a/app/models/old_node_tag.rb b/app/models/old_node_tag.rb
new file mode 100644 (file)
index 0000000..26a6c92
--- /dev/null
@@ -0,0 +1,7 @@
+class OldNodeTag < ActiveRecord::Base
+  belongs_to :user
+
+  set_table_name 'node_tags'
+
+
+end
index 6da7814c28a40d442e856239e09149b2ca8727c5..076c03eec72dad790f09a3857038a071ca99d96a 100644 (file)
@@ -9,6 +9,7 @@ class OldRelation < ActiveRecord::Base
     old_relation.user_id = relation.user_id
     old_relation.timestamp = relation.timestamp
     old_relation.id = relation.id
+    old_relation.version = relation.version
     old_relation.members = relation.members
     old_relation.tags = relation.tags
     return old_relation
index a2b165e42716f5cfa7f095aa76654616274f95b5..cdc0c4717c1df571f8a289c7907a71586e69a9e4 100644 (file)
@@ -9,6 +9,7 @@ class OldWay < ActiveRecord::Base
     old_way.user_id = way.user_id
     old_way.timestamp = way.timestamp
     old_way.id = way.id
+    old_way.version = way.version
     old_way.nds = way.nds
     old_way.tags = way.tags
     return old_way
index 61344bdfb62021fad69b67c72cf4e97d0b59219f..3a9c0d9d5ff4b881b895b9c2492167814ee7a690 100644 (file)
@@ -167,13 +167,12 @@ class Relation < ActiveRecord::Base
   def save_with_history!
     Relation.transaction do
       t = Time.now
+      self.version += 1
       self.timestamp = t
       self.save!
 
       tags = self.tags
-
       RelationTag.delete_all(['id = ?', self.id])
-
       tags.each do |k,v|
         tag = RelationTag.new
         tag.k = k
@@ -183,9 +182,7 @@ class Relation < ActiveRecord::Base
       end
 
       members = self.members
-
       RelationMember.delete_all(['id = ?', self.id])
-
       members.each do |n|
         mem = RelationMember.new
         mem.id = self.id
diff --git a/app/models/temp_old_node.rb b/app/models/temp_old_node.rb
new file mode 100644 (file)
index 0000000..a024eaa
--- /dev/null
@@ -0,0 +1,50 @@
+class TempOldNode < ActiveRecord::Base
+  set_table_name 'temp_nodes'
+  
+  validates_presence_of :user_id, :timestamp
+  validates_inclusion_of :visible, :in => [ true, false ]
+  validates_numericality_of :latitude, :longitude
+  validate :validate_position
+
+  belongs_to :user
+  def validate_position
+    errors.add_to_base("Node is not in the world") unless in_world?
+  end
+
+  def in_world?
+    return true
+  end
+
+  def self.from_node(node)
+    old_node = OldNode.new
+    old_node.latitude = node.latitude
+    old_node.longitude = node.longitude
+    old_node.visible = node.visible
+    old_node.tags = node.tags
+    old_node.timestamp = node.timestamp
+    old_node.user_id = node.user_id
+    old_node.id = node.id
+    return old_node
+  end
+
+  def to_xml_node
+    el1 = XML::Node.new 'node'
+    el1['id'] = self.id.to_s
+    el1['lat'] = self.lat.to_s
+    el1['lon'] = self.lon.to_s
+    el1['user'] = self.user.display_name if self.user.data_public?
+
+    Tags.split(self.tags) do |k,v|
+      el2 = XML::Node.new('tag')
+      el2['k'] = k.to_s
+      el2['v'] = v.to_s
+      el1 << el2
+    end
+
+    el1['visible'] = self.visible.to_s
+    el1['timestamp'] = self.timestamp.xmlschema
+    return el1
+  end
+
+end
index f1dc76eb4c6b65053214e1e80f6665f70ad10052..56c0717a7ac1ffc1ff713703b55d7d0320877880 100644 (file)
@@ -158,15 +158,12 @@ class Way < ActiveRecord::Base
     t = Time.now
 
     Way.transaction do
+      self.version += 1
       self.timestamp = t
       self.save!
-    end
 
-    WayTag.transaction do
       tags = self.tags
-
       WayTag.delete_all(['id = ?', self.id])
-
       tags.each do |k,v|
         tag = WayTag.new
         tag.k = k
@@ -174,13 +171,9 @@ class Way < ActiveRecord::Base
         tag.id = self.id
         tag.save!
       end
-    end
 
-    WayNode.transaction do
       nds = self.nds
-
       WayNode.delete_all(['id = ?', self.id])
-
       sequence = 1
       nds.each do |n|
         nd = WayNode.new
@@ -189,11 +182,11 @@ class Way < ActiveRecord::Base
         nd.save!
         sequence += 1
       end
-    end
 
-    old_way = OldWay.from_way(self)
-    old_way.timestamp = t
-    old_way.save_with_dependencies!
+      old_way = OldWay.from_way(self)
+      old_way.timestamp = t
+      old_way.save_with_dependencies!
+    end
   end
 
   def preconditions_ok?
index b884f3b938fea8c5ea541e0d4af5fbbc16529dd7..3637829537ee6dfd46d20943474ec5b28ae3e859 100644 (file)
@@ -12,9 +12,9 @@
 #   http://dev.mysql.com/doc/refman/5.0/en/old-client.html
 development:
   adapter: mysql
-  database: openstreetmap
-  username: openstreetmap
-  password: openstreetmap
+  database: osm
+  username: osm
+  password: osm
   host: localhost
 
 # Warning: The database defined as 'test' will be erased and
diff --git a/db/migrate/013_populate_node_tags_and_remove.rb b/db/migrate/013_populate_node_tags_and_remove.rb
new file mode 100644 (file)
index 0000000..29a91c7
--- /dev/null
@@ -0,0 +1,62 @@
+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, "013_populate_node_tags_and_remove.#{$$}."
+
+      cmd = "db/migrate/013_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 --git a/db/migrate/013_populate_node_tags_and_remove_helper.c b/db/migrate/013_populate_node_tags_and_remove_helper.c
new file mode 100644 (file)
index 0000000..b1868ef
--- /dev/null
@@ -0,0 +1,234 @@
+#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, "013_populate_node_tags_and_remove_helper: MySQL error: %s\n", err);
+  } else {
+    fprintf(stderr, "013_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;
+
+  while (*i) {
+    if (*i == '\\') {
+      i++;
+      switch (*i++) {
+       case 's': *o++ = ';'; break;
+       case 'e': *o++ = '='; break;
+       case '\\': *o++ = '\\'; 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;
+  uint32_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: 013_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 = malloc(sizeof(uint32_t) * d->version_size);
+
+  prefix_len = strlen(argv[7]);
+  tempfn = (char *) malloc(prefix_len + 16);
+  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);
+}
diff --git a/db/migrate/014_move_to_innodb.rb b/db/migrate/014_move_to_innodb.rb
new file mode 100644 (file)
index 0000000..57065a5
--- /dev/null
@@ -0,0 +1,30 @@
+class MoveToInnodb < ActiveRecord::Migration
+  @@conv_tables = ['nodes', 'ways', 'way_tags', 'way_nodes',
+    'current_way_nodes', 'current_way_tags', 'relation_members',
+    'relations', 'relation_tags', 'current_relation_tags']
+
+  @@ver_tbl = ['nodes', 'ways', 'relations']
+
+  def self.up
+    execute 'DROP INDEX current_way_tags_v_idx ON current_way_tags'
+    execute 'DROP INDEX current_relation_tags_v_idx ON current_relation_tags'
+
+    @@ver_tbl.each { |tbl|
+      change_column tbl, "version", :bigint, :limit => 20, :null => false
+    }
+
+    @@conv_tables.each { |tbl|
+      execute "ALTER TABLE #{tbl} ENGINE = InnoDB"
+    }
+
+    @@ver_tbl.each { |tbl|
+      add_column "current_#{tbl}", "version", :bigint, :limit => 20, :null => false
+      execute "UPDATE current_#{tbl} SET version = " +
+       "(SELECT max(version)+1 FROM #{tbl} WHERE #{tbl}.id = current_#{tbl}.id)"
+    }
+  end
+
+  def self.down
+    raise IrreversibleMigration.new
+  end
+end
diff --git a/db/migrate/015_key_constraints.rb b/db/migrate/015_key_constraints.rb
new file mode 100644 (file)
index 0000000..6a070b2
--- /dev/null
@@ -0,0 +1,41 @@
+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]
+
+    # 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 1d32d175d77d0fb85055f8f60c19dff7cc86de2c..26e95a496264142d2414e63a199c300942142154 100644 (file)
@@ -1,6 +1,10 @@
 module ActiveRecord
   module ConnectionAdapters
     module SchemaStatements
+      def quote_column_names(column_name)
+        Array(column_name).map { |e| quote_column_name(e) }.join(", ")
+      end
+
       def add_primary_key(table_name, column_name, options = {})
         column_names = Array(column_name)
         quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
@@ -11,6 +15,12 @@ module ActiveRecord
         execute "ALTER TABLE #{table_name} DROP PRIMARY KEY"
       end
 
+      def add_foreign_key(table_name, column_name, reftbl, refcol = nil)
+        execute "ALTER TABLE #{table_name} ADD " +
+         "FOREIGN KEY (#{quote_column_names(column_name)}) " +
+         "REFERENCES #{reftbl} (#{quote_column_names(refcol || column_name)})"
+      end
+
       alias_method :old_options_include_default?, :options_include_default?
 
       def options_include_default?(options)