Index: trunk/Nebulous-Server/Changes
===================================================================
--- trunk/Nebulous-Server/Changes	(revision 20090)
+++ trunk/Nebulous-Server/Changes	(revision 20091)
@@ -29,4 +29,5 @@
     - change Nebulous::Server->new() to call ->db() instead of setting up the
       DBH itself; cleanup Nebulous::Server->db()
+    - add Nebulous::Server->swap_objects() method
 
 0.15 Thu Sep 11 13:00:59 HST 2008
Index: trunk/Nebulous-Server/MANIFEST
===================================================================
--- trunk/Nebulous-Server/MANIFEST	(revision 20090)
+++ trunk/Nebulous-Server/MANIFEST	(revision 20091)
@@ -49,3 +49,4 @@
 t/14_server_xattr.t
 t/15_mounts.t
+t/16_server_swap_objects.t
 t/75_parse_neb_key.t
Index: trunk/Nebulous-Server/lib/Nebulous/Server.pm
===================================================================
--- trunk/Nebulous-Server/lib/Nebulous/Server.pm	(revision 20090)
+++ trunk/Nebulous-Server/lib/Nebulous/Server.pm	(revision 20091)
@@ -1,5 +1,5 @@
 # Copyright (c) 2004-2008  Joshua Hoblitt
 #
-# $Id: Server.pm,v 1.92 2008-10-13 20:09:38 jhoblitt Exp $
+# $Id: Server.pm,v 1.93 2008-10-13 20:41:17 jhoblitt Exp $
 
 package Nebulous::Server;
@@ -296,4 +296,93 @@
 
 
+sub swap_objects
+{
+    my $self = shift;
+
+    my ($key1, $key2) = validate_pos(@_,
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is valid object key' => sub { $self->_is_valid_object_key($_[0]) },
+            },
+        },
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is valid object key' => sub { $self->_is_valid_object_key($_[0]) },
+            },
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug("entered - @_");
+
+    # ignore volumes
+    $key1 = parse_neb_key($key1);
+    $key2 = parse_neb_key($key2);
+
+    # order of operations for the swap:
+    # key1 -> key1.swap
+    # key2 -> key1
+    # key1.swap -> key2
+
+    eval {
+        {
+            # key1 -> key1.swap
+            my $query = $db->prepare_cached($sql->rename_object); 
+            # this SQL statment takes the new key name as the first param
+            my $rows = $query->execute($key1->path . ".swap", $key1->path);
+
+            # if we affected more then one row something very bad has happened.
+            unless ($rows == 1) {
+                $query->finish;
+                $log->logdie("affected row count is $rows instead of 1");
+            }
+        }
+
+        {
+            # key2 -> key1
+            my $query = $db->prepare_cached($sql->rename_object); 
+            # this SQL statment takes the new key name as the first param
+            my $rows = $query->execute($key1->path, $key2->path);
+
+            # if we affected more then one row something very bad has happened.
+            unless ($rows == 1) {
+                $query->finish;
+                $log->logdie("affected row count is $rows instead of 1");
+            }
+        }
+
+        {
+            # key1.swap -> key2
+            my $query = $db->prepare_cached($sql->rename_object); 
+            # this SQL statment takes the new key name as the first param
+            my $rows = $query->execute($key2->path, $key1->path . ".swap");
+
+            # if we affected more then one row something very bad has happened.
+            unless ($rows == 1) {
+                $query->finish;
+                $log->logdie("affected row count is $rows instead of 1");
+            }
+        }
+
+        $db->commit;
+        $log->debug("commit");
+    };
+    if ($@) {
+        $db->rollback;
+        $log->debug("rollback");
+        $log->logdie("database error: $@");
+    }
+
+    $log->debug("leaving");
+
+    return 1;
+}
+
+
 sub replicate_object
 {
Index: trunk/Nebulous-Server/t/16_server_swap_objects.t
===================================================================
--- trunk/Nebulous-Server/t/16_server_swap_objects.t	(revision 20091)
+++ trunk/Nebulous-Server/t/16_server_swap_objects.t	(revision 20091)
@@ -0,0 +1,82 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2007  Joshua Hoblitt
+#
+# $Id: 16_server_swap_objects.t,v 1.1 2008-10-13 20:41:17 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 8;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+{
+    my $uri1 = $neb->create_object("foo1");
+    my $uri2 = $neb->create_object("foo2");
+
+    ok($neb->swap_objects("foo1", "foo2"), "swap succeeded");
+
+    my $new_uri1 = ($neb->find_instances("foo1"))->[0];
+    my $new_uri2 = ($neb->find_instances("foo2"))->[0];
+
+    is($uri1, $new_uri2, "key1 -> key2");
+    is($uri2, $new_uri1, "key2 -> key1");
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('bar');
+
+    $neb->swap_objects('foo', 'bar');
+};
+like($@, qr/is valid object key/, "key1 doesn't exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->swap_objects('foo', 'bar');
+};
+like($@, qr/is valid object key/, "key2 doesn't exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->swap_objects();
+};
+like($@, qr/2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->swap_objects("foo");
+};
+like($@, qr/2 were expected/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+    $neb->create_object("bar");
+
+    $neb->swap_objects("foo", "bar", "baz");
+};
+like($@, qr/2 were expected/, "too many params");
+
+Test::Nebulous->cleanup;
