Index: branches/neb_distrib_20081210/Nebulous-Server/Build.PL
===================================================================
--- branches/neb_distrib_20081210/Nebulous-Server/Build.PL	(revision 23754)
+++ branches/neb_distrib_20081210/Nebulous-Server/Build.PL	(revision 23755)
@@ -34,4 +34,5 @@
         'Test::More'            => '0.49',
         'Test::URI'             => '1.06',
+        'Test::DBUnit'          => '0.20',
     },
     recommends          => {
Index: branches/neb_distrib_20081210/Nebulous-Server/Changes
===================================================================
--- branches/neb_distrib_20081210/Nebulous-Server/Changes	(revision 23754)
+++ branches/neb_distrib_20081210/Nebulous-Server/Changes	(revision 23755)
@@ -12,4 +12,5 @@
     - disallow Nebulous::Server->rename_object() when it would cause the db
       hash of a key to change
+    - create a pseduo directory structure on key creation
       
 0.16
Index: branches/neb_distrib_20081210/Nebulous-Server/lib/Nebulous/Server.pm
===================================================================
--- branches/neb_distrib_20081210/Nebulous-Server/lib/Nebulous/Server.pm	(revision 23754)
+++ branches/neb_distrib_20081210/Nebulous-Server/lib/Nebulous/Server.pm	(revision 23755)
@@ -15,5 +15,5 @@
 use DBI;
 use Digest::SHA1 qw( sha1_hex );
-use File::Basename qw( dirname );
+use File::Basename qw( basename dirname fileparse );
 use File::ExtAttr qw( setfattr );
 use File::Path;
@@ -210,4 +210,6 @@
         = $self->_get_storage_volume($key, $vol_name, $key->soft_volume);
 
+    my $parent_id = $self->_resolve_dir_parent_id($key);
+
     my $uri;
 TRANS: while (1) {
@@ -216,5 +218,5 @@
                 # create storage_object
                 my $query = $db->prepare_cached( $sql->new_object ); 
-                $query->execute('NULL', $key->path);
+                $query->execute('NULL', $key->path, basename($key->path), $parent_id);
             }
 
@@ -294,4 +296,88 @@
 
 
+sub _resolve_dir_parent_id
+{
+    my $self = shift;
+
+    my ($key) = validate_pos(@_,
+        {
+            isa => 'Nebulous::Key',
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  = $self->db($key);
+
+    # resolve parent directory
+    my @dirs;
+
+    # File::Spec->splitpath was causing ->splitdir to always an extra dir
+    # named "" because of a trailing /
+    @dirs = File::Spec->splitdir(dirname($key->path));
+    # dirname returns "." if there is no dir component to the path, we have
+    # to filter this out
+    @dirs = grep(!/^\.$/, @dirs);
+
+    # start at the root dir; '/' == 1
+    my $parent_id = 1;
+TRANS: while (1) {
+        eval {
+            foreach my $dir (@dirs) {
+                my $dir_id;
+                {
+                    my $query = $db->prepare_cached($sql->get_directory); 
+                    $query->execute($parent_id, $dir);
+                    if ($query->rows) {
+                        $dir_id = $query->fetchrow_hashref->{'dir_id'};
+                    }
+                    $query->finish;
+                }
+
+                # if we found a dir_id, a row for this directory already exists
+                if (defined $dir_id) {
+                    $parent_id = $dir_id;
+                    # note that you can't exit an eval {} with next
+                    next;
+                }
+
+                {
+                    # dir doesn't exist, create it
+                    my $query = $db->prepare_cached($sql->new_directory);
+                    $query->execute($dir, $parent_id);
+                }
+
+                # get the dir_id of the new directory entry 
+                {
+                    my $query = $db->prepare_cached($sql->last_insert_id);
+                    $query->execute();
+
+                    # the new dir_id will be the parent_id of the next
+                    # descendent directory
+                    ($parent_id) = $query->fetchrow_array;
+                    $query->finish;
+                }
+                $log->logdie("failed to get LAST_INSERT_ID()")
+                    unless $parent_id;
+
+                $db->commit;
+            }
+        };
+        if ($@) {
+            $db->rollback;
+            $log->debug("rollback");
+            if ($@ =~ /Deadlock found/) {
+                $log->warn("database deadlock retrying transaction: $@");
+                redo TRANS;
+            }
+            $log->logdie("error: $@");
+        }
+        last;
+    }
+
+    return $parent_id;
+}
+
+
 sub rename_object
 {
@@ -314,6 +400,4 @@
     );
 
-    # XXX this may require database migration!
-
     # ignore volumes
     $key    = parse_neb_key($key);
@@ -326,4 +410,5 @@
     $log->debug("entered - @_");
 
+    # XXX this may require database migration in the future
     unless ($self->_db_index_for_key($key)
          == $self->_db_index_for_key($newkey)) {
@@ -336,5 +421,5 @@
             my $query = $db->prepare_cached($sql->rename_object); 
             # this SQL statment takes the new key name as the first param
-            my $rows = $query->execute($newkey->path, $key->path);
+            my $rows = $query->execute($newkey->path, basename($newkey->path), $key->path);
 
             # if we affected more then one row something very bad has happened.
@@ -412,5 +497,5 @@
               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);
+              my $rows = $query->execute($key1->path . ".swap", basename($key1->path) . ".swap", $key1->path);
 
               # if we affected more then one row something very bad has happened.
@@ -425,5 +510,5 @@
               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);
+              my $rows = $query->execute($key1->path, basename($key1->path), $key2->path);
 
               # if we affected more then one row something very bad has happened.
@@ -438,5 +523,5 @@
               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");
+              my $rows = $query->execute($key2->path, basename($key2->path), $key1->path . ".swap");
 
               # if we affected more then one row something very bad has happened.
Index: branches/neb_distrib_20081210/Nebulous-Server/lib/Nebulous/Server/SQL.pm
===================================================================
--- branches/neb_distrib_20081210/Nebulous-Server/lib/Nebulous/Server/SQL.pm	(revision 23754)
+++ branches/neb_distrib_20081210/Nebulous-Server/lib/Nebulous/Server/SQL.pm	(revision 23755)
@@ -8,5 +8,5 @@
 use warnings FATAL => qw( all );
 
-our $VERSION = '0.03';
+our $VERSION = '0.04';
 
 use base qw( Class::Accessor::Fast );
@@ -26,6 +26,6 @@
     new_object          => qq{
         INSERT INTO storage_object
-        (so_id, ext_id, type)
-        VALUES (?, ?, 'REG_FILE')
+        (so_id, ext_id, ext_id_basename, type, dir_id)
+        VALUES (?, ?, ?, 'REG_FILE', ?)
     },
     new_object_attr  => qq{
@@ -60,4 +60,16 @@
         USING (so_id)
         WHERE ext_id = ?
+    },
+    get_directory       => qq{
+        SELECT
+            dir_id
+        FROM directory
+        WHERE parent_id = ?
+            AND dirname = ?
+    },
+    new_directory       => qq{
+        INSERT INTO directory
+        (dirname, parent_id)
+        VALUES (?, ?)
     },
     check_object_name => qq{
@@ -304,5 +316,5 @@
     rename_object => qq{
         UPDATE storage_object
-        SET ext_id = ?
+        SET ext_id = ?, ext_id_basename = ?
         WHERE ext_id = ?
     },
@@ -410,4 +422,5 @@
 DROP TABLE IF EXISTS log;
 DROP TABLE IF EXISTS mountedvol;
+DROP TABLE IF EXISTS directory;
 DROP PROCEDURE IF EXISTS getmountedvol;
 SET FOREIGN_KEY_CHECKS=1
@@ -431,7 +444,25 @@
 
 __DATA__
+CREATE TABLE directory (
+    dir_id BIGINT NOT NULL AUTO_INCREMENT,
+    dirname CHAR(255) NOT NULL,
+    parent_id BIGINT NOT NULL,
+    FOREIGN KEY(parent_id) REFERENCES directory(dir_id),
+    PRIMARY KEY(dir_id),
+    KEY(parent_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+###
+
+INSERT INTO directory (dir_id, dirname, parent_id) VALUES (1, '/', 1);
+
+###
+
 CREATE TABLE storage_object (
     so_id BIGINT NOT NULL AUTO_INCREMENT,
     ext_id VARCHAR(255) NOT NULL UNIQUE,
+    ext_id_basename VARCHAR(255) NOT NULL,
+    dir_id BIGINT NOT NULL,
+    FOREIGN KEY(dir_id) REFERENCES directory(dir_id),
     type enum('REG_FILE'),
     PRIMARY KEY(so_id),
Index: branches/neb_distrib_20081210/Nebulous-Server/t/03_server_create_object.t
===================================================================
--- branches/neb_distrib_20081210/Nebulous-Server/t/03_server_create_object.t	(revision 23754)
+++ branches/neb_distrib_20081210/Nebulous-Server/t/03_server_create_object.t	(revision 23755)
@@ -8,8 +8,9 @@
 use warnings FATAL => qw( all );
 
-use Test::More tests => 89;
+use Test::More tests => 99;
 
 use lib qw( ./t ./lib );
 
+use File::Basename qw( basename );
 use File::ExtAttr qw( getfattr );
 use Nebulous::Server;
@@ -334,4 +335,175 @@
 }
 
+# test for properly row creation in the directories table
+use Test::DBUnit dsn => $NEB_DB, username => $NEB_USER, password => $NEB_PASS;
+
+Test::Nebulous->setup;
+
+{
+    my $key = "foo";
+    $neb->create_object($key);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        storage_object  => [so_id => 1, ext_id => $key, dir_id => 1],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key = "a/foo";
+    $neb->create_object($key);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        storage_object  => [so_id => 1, ext_id => $key, ext_id_basename => basename($key), dir_id => 2],
+    );
+
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key = "a/b/foo";
+    $neb->create_object($key);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
+        storage_object  => [so_id => 1, ext_id => $key, ext_id_basename => basename($key), dir_id => 3],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key = "a/b/c/foo";
+    $neb->create_object($key);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
+        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
+        storage_object  => [so_id => 1, ext_id => $key, ext_id_basename => basename($key), dir_id => 4],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key1 = "a/b/c/foo";
+    my $key2 = "foo";
+    $neb->create_object($key1);
+    $neb->create_object($key2);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        storage_object  => [so_id => 2, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
+        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
+        storage_object  => [so_id => 1, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 4],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key1 = "a/b/c/foo";
+    my $key2 = "a/foo";
+    $neb->create_object($key1);
+    $neb->create_object($key2);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        storage_object  => [so_id => 2, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 2],
+        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
+        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
+        storage_object  => [so_id => 1, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 4],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key1 = "a/b/c/foo";
+    my $key2 = "d/foo";
+    $neb->create_object($key1);
+    $neb->create_object($key2);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
+        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
+        storage_object  => [so_id => 1, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 4],
+        directory       => [dir_id => 5, dirname => 'd', parent_id => 1],
+        storage_object  => [so_id => 2, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 5],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key1 = "a/b/c/foo";
+    my $key2 = "a/d/foo";
+    $neb->create_object($key1);
+    $neb->create_object($key2);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
+        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
+        storage_object  => [so_id => 1, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 4],
+        directory       => [dir_id => 5, dirname => 'd', parent_id => 2],
+        storage_object  => [so_id => 2, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 5],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key1 = "a/b/c/foo";
+    my $key2 = "d/a/foo";
+    $neb->create_object($key1);
+    $neb->create_object($key2);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
+        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
+        storage_object  => [so_id => 1, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 4],
+        directory       => [dir_id => 5, dirname => 'd', parent_id => 1],
+        directory       => [dir_id => 6, dirname => 'a', parent_id => 5],
+        storage_object  => [so_id => 2, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 6],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key1 = "a/b/c/foo";
+    my $key2 = "a/b/c/d/foo";
+    $neb->create_object($key1);
+    $neb->create_object($key2);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
+        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
+        storage_object  => [so_id => 1, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 4],
+        directory       => [dir_id => 5, dirname => 'd', parent_id => 4],
+        storage_object  => [so_id => 2, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 5],
+    );
+}
+
 Test::Nebulous->setup;
 
