Add next-generation SQL Share driver.
authorJan Schneider <jan@horde.org>
Fri, 14 Jan 2011 22:16:47 +0000 (23:16 +0100)
committerJan Schneider <jan@horde.org>
Fri, 14 Jan 2011 22:50:53 +0000 (23:50 +0100)
It doesn't use any bitmasks anymore that can't be indexed in MySQL. Each
permission is a boolean column instead. User and group permissions are
no longer retrieved with a LEFT JOIN but in separate queries. This is by
far outweighed by the performance improvement which is about factor 25
for MySQL and PostgreSQL using the stress test scenario.

16 files changed:
.gitignore
framework/Share/lib/Horde/Share/Base.php
framework/Share/lib/Horde/Share/Object/Sql.php
framework/Share/lib/Horde/Share/Object/Sqlng.php [new file with mode: 0644]
framework/Share/lib/Horde/Share/Sql.php
framework/Share/lib/Horde/Share/Sqlng.php [new file with mode: 0644]
framework/Share/package.xml
framework/Share/test/Horde/Share/Sqlng/Base.php [new file with mode: 0644]
framework/Share/test/Horde/Share/Sqlng/MysqlTest.php [new file with mode: 0644]
framework/Share/test/Horde/Share/Sqlng/MysqliTest.php [new file with mode: 0644]
framework/Share/test/Horde/Share/Sqlng/Pdo/MysqlTest.php [new file with mode: 0644]
framework/Share/test/Horde/Share/Sqlng/Pdo/PgsqlTest.php [new file with mode: 0644]
framework/Share/test/Horde/Share/Sqlng/Pdo/SqliteTest.php [new file with mode: 0644]
framework/Share/test/Horde/Share/Sqlng/Pdo/conf.php [new symlink]
framework/Share/test/Horde/Share/migration/sqlng.php [new file with mode: 0644]
framework/Share/test/Horde/Share/stress-test

index 75d9f9f..f629507 100644 (file)
@@ -44,6 +44,8 @@ framework/Share/test/Horde/Share/Sql/conf.php
 framework/Share/test/Horde/Share/Sql/Pdo/conf.php
 framework/Share/test/Horde/Share/SqlHierarchical/conf.php
 framework/Share/test/Horde/Share/SqlHierarchical/Pdo/conf.php
+framework/Share/test/Horde/Share/Sqlng/conf.php
+framework/Share/test/Horde/Share/Sqlng/Pdo/conf.php
 
 # Dynamically generated content that may live in the repo directories
 /lib/
index 65a3f09..e362443 100644 (file)
@@ -156,7 +156,7 @@ abstract class Horde_Share_Base
     }
 
     /**
-     * (re)connect the share object to this share driver.
+     * (Re)connects the share object to this share driver.
      *
      * @param Horde_Share_Object $object
      */
index c5c75bf..a8f8656 100644 (file)
@@ -87,15 +87,13 @@ class Horde_Share_Object_Sql extends Horde_Share_Object implements Serializable
      *
      * @param string $attribute  The attribute to set.
      * @param mixed $value       The value for $attribute.
-     *
-     * @return boolean
      */
     public function set($attribute, $value)
     {
         if ($attribute == 'owner') {
-            return $this->data['share_owner'] = $value;
+            $this->data['share_owner'] = $value;
         } else {
-            return $this->data['attribute_' . $attribute] = $value;
+            $this->data['attribute_' . $attribute] = $value;
         }
     }
 
diff --git a/framework/Share/lib/Horde/Share/Object/Sqlng.php b/framework/Share/lib/Horde/Share/Object/Sqlng.php
new file mode 100644 (file)
index 0000000..0a40f3b
--- /dev/null
@@ -0,0 +1,219 @@
+<?php
+/**
+ * Extension of the Horde_Share_Object class for storing share information in
+ * the Sqlng driver.
+ *
+ * @author  Jan Schneider <jan@horde.org>
+ * @package Horde_Share
+ */
+class Horde_Share_Object_Sqlng extends Horde_Share_Object_Sql
+{
+    /**
+     * Serializable version.
+     */
+    const VERSION = 1;
+
+    /**
+     * A list of available permission.
+     *
+     * This is necessary to unset certain permission when updating existing
+     * share objects.
+     *
+     * @param array
+     */
+    public $availablePermissions = array();
+
+    /**
+     * Constructor.
+     *
+     * @param array $data Share data array.
+     */
+    public function __construct($data)
+    {
+        parent::__construct($data);
+        $this->_setAvailablePermissions();
+    }
+
+    /**
+     * Serialize this object.
+     *
+     * @return string  The serialized data.
+     */
+    public function serialize()
+    {
+        return serialize(array(
+            self::VERSION,
+            $this->data,
+            $this->_shareCallback,
+            $this->availablePermissions,
+        ));
+    }
+
+    /**
+     * Reconstruct the object from serialized data.
+     *
+     * @param string $data  The serialized data.
+     */
+    public function unserialize($data)
+    {
+        $data = @unserialize($data);
+        if (!is_array($data) ||
+            !isset($data[0]) ||
+            ($data[0] != self::VERSION)) {
+            throw new Exception('Cache version change');
+        }
+
+        $this->data = $data[1];
+        if (empty($data[2])) {
+            throw new Exception('Missing callback for Horde_Share_Object unserializing');
+        }
+        $this->_shareCallback = $data[2];
+        $this->availablePermissions = $data[3];
+    }
+
+    /**
+     * Saves the current attribute values.
+     */
+    protected function _save()
+    {
+        $db = $this->getShareOb()->getStorage();
+        $table = $this->getShareOb()->getTable();
+
+        // Build the parameter arrays for the sql statement.
+        $fields = $params = array();
+        foreach ($this->getShareOb()->toDriverCharset($this->data) as $key => $value) {
+            if ($key != 'share_id' && $key != 'perm' && $key != 'share_flags') {
+                $fields[] = $key;
+                $params[] = $value;
+            }
+        }
+
+        $fields[] = 'share_flags';
+        $flags = 0;
+        if (!empty($this->data['perm']['users'])) {
+            $flags |= Horde_Share_Sql::SQL_FLAG_USERS;
+        }
+        if (!empty($this->data['perm']['groups'])) {
+            $flags |= Horde_Share_Sql::SQL_FLAG_GROUPS;
+        }
+        $params[] = $flags;
+
+        // Insert new share record, or update existing
+        if (empty($this->data['share_id'])) {
+            foreach ($this->data['perm'] as $base => $perms) {
+                if ($base == 'type' || $base == 'users' || $base == 'groups') {
+                    continue;
+                }
+                foreach (Horde_Share_Sqlng::convertBitmaskToArray($perms) as $perm) {
+                    $fields[] = 'perm_' . $base . '_' . $perm;
+                    $params[] = true;
+                }
+            }
+            $sql = 'INSERT INTO ' . $table . ' (' . implode(', ', $fields) . ') VALUES (?' . str_repeat(', ?', count($fields) - 1) . ')';
+            try {
+                $this->data['share_id'] = $db->insert($sql, $params);
+            } catch (Horde_Db_Exception $e) {
+                throw new Horde_Share_Exception($e);
+            }
+        } else {
+            foreach ($this->data['perm'] as $base => $perms) {
+                if ($base == 'type' || $base == 'users' || $base == 'groups') {
+                    continue;
+                }
+                $perms = array_flip(Horde_Share_Sqlng::convertBitmaskToArray($perms));
+                foreach ($this->availablePermissions as $perm) {
+                    $fields[] = 'perm_' . $base . '_' . $perm;
+                    $params[] = isset($perms[$perm]) ? true : false;
+                }
+            }
+            $sql = 'UPDATE ' . $table . ' SET ' . implode(' = ?, ', $fields) . ' = ? WHERE share_id = ?';
+            $params[] = $this->data['share_id'];
+            try {
+                $db->update($sql, $params);
+            } catch (Horde_Db_Exception $e) {
+                throw new Horde_Share_Exception($e);
+            }
+        }
+
+        // Update the share's user permissions
+        $db->delete('DELETE FROM ' . $table . '_users WHERE share_id = ?', array($this->data['share_id']));
+        if (!empty($this->data['perm']['users'])) {
+            $data = array();
+            foreach ($this->data['perm']['users'] as $user => $perms) {
+                $fields = $params = array();
+                foreach (Horde_Share_Sqlng::convertBitmaskToArray($perms) as $perm) {
+                    $fields[] = 'perm_' . $perm;
+                    $params[] = true;
+                }
+                if (!$fields) {
+                    continue;
+                }
+                array_unshift($params, $user);
+                array_unshift($params, $this->data['share_id']);
+                $db->insert('INSERT INTO ' . $table . '_users (share_id, user_uid, ' . implode(', ', $fields) . ') VALUES (?, ?' . str_repeat(', ?', count($fields)) . ')', $params);
+            }
+        }
+
+        // Update the share's group permissions
+        $db->delete('DELETE FROM ' . $table . '_groups WHERE share_id = ?', array($this->data['share_id']));
+        if (!empty($this->data['perm']['groups'])) {
+            $data = array();
+            foreach ($this->data['perm']['groups'] as $group => $perms) {
+                $fields = $params = array();
+                foreach (Horde_Share_Sqlng::convertBitmaskToArray($perms) as $perm) {
+                    $fields[] = 'perm_' . $perm;
+                    $params[] = true;
+                }
+                if (!$fields) {
+                    continue;
+                }
+                array_unshift($params, $group);
+                array_unshift($params, $this->data['share_id']);
+                $db->insert('INSERT INTO ' . $table . '_groups (share_id, group_uid, ' . implode(', ', $fields) . ') VALUES (?, ?' . str_repeat(', ?', count($fields)) . ')', $params);
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Sets the permission of this share.
+     *
+     * @param Horde_Perms_Permission $perm  Permission object.
+     * @param boolean $update               Should the share be saved
+     *                                      after this operation?
+     */
+    public function setPermission($perm, $update = true)
+    {
+        parent::setPermission($perm, $update);
+        $this->_setAvailablePermissions();
+    }
+
+    /**
+     * Populates the $availablePermissions property with all seen permissions.
+     *
+     * This is necessary because the share tables might be extended with
+     * arbitrary permissions.
+     */
+    protected function _setAvailablePermissions()
+    {
+        $available = array();
+        foreach ($this->availablePermissions as $perm) {
+            $available[$perm] = true;
+        }
+        foreach ($this->data['perm'] as $base => $perms) {
+            if ($base == 'type') {
+                continue;
+            }
+            if ($base != 'users' && $base != 'groups') {
+                $perms = array($perms);
+            }
+            foreach ($perms as $subperms) {
+                foreach (Horde_Share_Sqlng::convertBitmaskToArray($subperms) as $perm) {
+                    $available[$perm] = true;
+                }
+            }
+        }
+        $this->availablePermissions = array_keys($available);
+    }
+}
index d00297b..837a30d 100644 (file)
@@ -119,14 +119,19 @@ class Horde_Share_Sql extends Horde_Share_Base
      */
     protected function _getShareUsers(&$share)
     {
-        if ($this->_hasUsers($share)) {
-            try {
-                $rows = $this->_db->selectAll('SELECT user_uid, perm FROM ' . $this->_table . '_users WHERE share_id = ?', array($share['share_id']));
-                foreach ($rows as $row) {
-                    $share['perm']['users'][$row['user_uid']] = (int)$row['perm'];
-                }
-            } catch (Horde_Db_Exception $e) {
-                throw new Horde_Share_Exception($e->getMessage());
+        if (!$this->_hasUsers($share)) {
+            return;
+        }
+
+        try {
+            $rows = $this->_db->selectAll('SELECT * FROM ' . $this->_table . '_users WHERE share_id = ?', array($share['share_id']));
+        } catch (Horde_Db_Exception $e) {
+            throw new Horde_Share_Exception($e->getMessage());
+        }
+
+        foreach ($rows as $row) {
+            foreach ($row as $column => $value) {
+                $share['perm']['users'] = $this->_buildPermsFromRow($row, 'user_uid');
             }
         }
     }
@@ -140,14 +145,19 @@ class Horde_Share_Sql extends Horde_Share_Base
      */
     protected function _getShareGroups(&$share)
     {
-        if ($this->_hasGroups($share)) {
-            try {
-                $rows = $this->_db->selectAll('SELECT group_uid, perm FROM ' . $this->_table . '_groups WHERE share_id = ?', array($share['share_id']));
-                foreach ($rows as $row) {
-                    $share['perm']['groups'][$row['group_uid']] = (int)$row['perm'];
-                }
-            } catch (Horde_Db_Exception $e) {
-                throw new Horde_Share_Exception($e->getMessage());
+        if (!$this->_hasGroups($share)) {
+            return;
+        }
+
+        try {
+            $rows = $this->_db->selectAll('SELECT * FROM ' . $this->_table . '_groups WHERE share_id = ?', array($share['share_id']));
+        } catch (Horde_Db_Exception $e) {
+            throw new Horde_Share_Exception($e->getMessage());
+        }
+
+        foreach ($rows as $row) {
+            foreach ($row as $column => $value) {
+                $share['perm']['groups'] = $this->_buildPermsFromRow($row, 'group_uid');
             }
         }
     }
@@ -298,22 +308,22 @@ class Horde_Share_Sql extends Horde_Share_Base
 
         // Get users permissions
         try {
-            $rows = $this->_db->selectAll('SELECT share_id, user_uid, perm FROM ' . $this->_table . '_users');
+            $rows = $this->_db->selectAll('SELECT * FROM ' . $this->_table . '_users');
         } catch (Horde_Db_Exception $e) {
             throw new Horde_Share_Exception($e);
         }
-        foreach ($rows as $share) {
-            $shares[$share['share_id']]['perm']['users'][$share['user_uid']] = (int)$share['perm'];
+        foreach ($rows as $row) {
+            $shares[$row['share_id']]['perm']['users'] = $this->_buildPermsFromRow($row, 'user_uid');
         }
 
         // Get groups permissions
         try {
-            $rows = $this->_db->selectAll('SELECT share_id, group_uid, perm FROM ' . $this->_table . '_groups');
+            $rows = $this->_db->selectAll('SELECT * FROM ' . $this->_table . '_groups');
         } catch (Horde_Db_Exception $e) {
             throw new Horde_Share_Exception($e->getMessage());
         }
-        foreach ($rows as $share) {
-            $shares[$share['share_id']]['perm']['groups'][$share['group_uid']] = (int)$share['perm'];
+        foreach ($rows as $row) {
+            $shares[$row['share_id']]['perm']['groups'] = $this->_buildPermsFromRow($row, 'group_uid');
         }
 
         $sharelist = array();
@@ -388,7 +398,7 @@ class Horde_Share_Sql extends Horde_Share_Base
 
         // Get users permissions
         if (!empty($users)) {
-            $query = 'SELECT share_id, user_uid, perm FROM ' . $this->_table
+            $query = 'SELECT * FROM ' . $this->_table
                  . '_users WHERE share_id IN (' . implode(', ', $users)
                  . ')';
             try {
@@ -396,14 +406,14 @@ class Horde_Share_Sql extends Horde_Share_Base
             } catch (Horde_Db_Exception $e) {
                 throw new Horde_Share_Exception($e->getMessage());
             }
-            foreach ($rows as $share) {
-                $shares[$share['share_id']]['perm']['users'][$share['user_uid']] = (int)$share['perm'];
+            foreach ($rows as $row) {
+                $shares[$row['share_id']]['perm']['users'] = $this->_buildPermsFromRow($row, 'user_uid');
             }
         }
 
         // Get groups permissions
         if (!empty($groups)) {
-            $query = 'SELECT share_id, group_uid, perm FROM ' . $this->_table
+            $query = 'SELECT * FROM ' . $this->_table
                      . '_groups WHERE share_id IN (' . implode(', ', $groups)
                      . ')';
             try {
@@ -411,8 +421,8 @@ class Horde_Share_Sql extends Horde_Share_Base
             } catch (Horde_Db_Exception $e) {
                 throw new Horde_Share_Exception($e->getMessage());
             }
-            foreach ($rows as $share) {
-                $shares[$share['share_id']]['perm']['groups'][$share['group_uid']] = (int)$share['perm'];
+            foreach ($rows as $row) {
+                $shares[$row['share_id']]['perm']['groups'] = $this->_buildPermsFromRow($row, 'group_uid');
             }
         }
 
@@ -663,6 +673,20 @@ class Horde_Share_Sql extends Horde_Share_Base
     }
 
     /**
+     * Builds a list of permission bit masks from the "perm" column.
+     *
+     * @param array $row     A data row including permission columns.
+     * @param string $index  Name of the column that should be used as the key
+     *                       for the permissions list.
+     *
+     * @return array  A permission hash.
+     */
+    protected function _buildPermsFromRow($row, $index)
+    {
+        return array($row[$index] => (int)$row['perm']);
+    }
+
+    /**
      * Utility function to convert from the SQL server's charset.
      */
     protected function _fromDriverCharset($data)
diff --git a/framework/Share/lib/Horde/Share/Sqlng.php b/framework/Share/lib/Horde/Share/Sqlng.php
new file mode 100644 (file)
index 0000000..9efc14b
--- /dev/null
@@ -0,0 +1,369 @@
+<?php
+/**
+ * Horde_Share_Sqlng provides the next-generation SQL backend driver for the
+ * Horde_Share library.
+ *
+ * Copyright 2011 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Jan Schneider <jan@horde.org>
+ * @package Horde_Share
+ */
+
+/**
+ * @package Horde_Share
+ */
+class Horde_Share_Sqlng extends Horde_Share_Sql
+{
+    /* Serializable version */
+    const VERSION = 1;
+
+    /**
+     * The Horde_Share_Object subclass to instantiate objects as
+     *
+     * @var string
+     */
+    protected $_shareObject = 'Horde_Share_Object_Sqlng';
+
+    /**
+     * A list of available permission.
+     *
+     * This is necessary to unset certain permission when updating existing
+     * share objects.
+     *
+     * @param array
+     */
+    protected $_availablePermissions = array();
+
+    /**
+     * Passes the available permissions to the share object.
+     *
+     * @param Horde_Share_Object $object
+     */
+    public function initShareObject(Horde_Share_Object $object)
+    {
+        parent::initShareObject($object);
+        $object->availablePermissions = array_keys($this->_availablePermissions);
+    }
+
+    /**
+     * Returns an array of all shares that $userid has access to.
+     *
+     * @param string $userid  The userid of the user to check access for.
+     * @param array $params   Additional parameters for the search.
+     *  - 'perm':       Require this level of permissions. Horde_Perms constant.
+     *  - 'attributes': Restrict shares to these attributes. A hash or username.
+     *  - 'from':       Offset. Start at this share
+     *  - 'count':      Limit.  Only return this many.
+     *  - 'sort_by':    Sort by attribute.
+     *  - 'direction':  Sort by direction.
+     *
+     * @return array  The shares the user has access to.
+     * @throws Horde_Share_Exception
+     */
+    public function listShares($userid, array $params = array())
+    {
+        $params = array_merge(array('perm' => Horde_Perms::SHOW,
+                                    'attributes' => null,
+                                    'from' => 0,
+                                    'count' => 0,
+                                    'sort_by' => null,
+                                    'direction' => 0),
+                              $params);
+
+        $key = md5(serialize(array($userid, $params)));
+        if (!empty($this->_listcache[$key])) {
+            return $this->_listcache[$key];
+        }
+
+        $perms = $this->convertBitmaskToArray($params['perm']);
+        $shareids = null;
+        if (!empty($userid)) {
+            list($users, $groups, $shareids) = $this->_getUserAndGroupShares($userid, $perms);
+        }
+
+        $shares = array();
+        if (is_null($params['sort_by'])) {
+            $sortfield = 'share_name';
+        } elseif ($params['sort_by'] == 'owner' || $params['sort_by'] == 'id') {
+            $sortfield = 'share_' . $params['sort_by'];
+        } else {
+            $sortfield = 'attribute_' . $params['sort_by'];
+        }
+
+        $query = 'SELECT DISTINCT * FROM ' . $this->_table . ' WHERE '
+            . $this->_getShareCriteria($userid, $perms, $params['attributes'], $shareids)
+            . ' ORDER BY ' . $sortfield
+            . (($params['direction'] == 0) ? ' ASC' : ' DESC');
+
+        $query = $this->_db->addLimitOffset($query, array('limit' => $params['count'], 'offset' => $params['from']));
+        try {
+            $rows = $this->_db->selectAll($query);
+        } catch (Horde_Db_Exception $e) {
+            throw new Horde_Share_Exception($e->getMessage());
+        }
+        foreach ($rows as $share) {
+            $shares[(int)$share['share_id']] = $this->_fromDriverCharset($share);
+        }
+
+        if (!empty($userid)) {
+            foreach ($users as $user) {
+                if (isset($shares[$user['share_id']])) {
+                    $shares[$user['share_id']]['perm']['users'] = $this->_buildPermsFromRow($user, 'user_uid');
+                }
+            }
+            foreach ($groups as $group) {
+                if (isset($shares[$group['share_id']])) {
+                    $shares[$group['share_id']]['perm']['groups'] = $this->_buildPermsFromRow($group, 'group_uid');
+                }
+            }
+        }
+
+        $sharelist = array();
+        foreach ($shares as $data) {
+            $this->_getSharePerms($data);
+            $sharelist[$data['share_name']] = $this->_createObject($data);
+        }
+        unset($shares);
+
+        // Run the results through the callback, if configured.
+        if (!empty($this->_callbacks['list'])) {
+            $sharelist = $this->runCallback('list', array($userid, $sharelist, $params));
+        }
+        $this->_listcache[$key] = $sharelist;
+
+        return $this->_listcache[$key];
+    }
+
+    /**
+     * Returns the number of shares that $userid has access to.
+     *
+     * @param string $userid     The userid of the user to check access for.
+     * @param integer $perm      The level of permissions required.
+     * @param mixed $attributes  Restrict the shares counted to those
+     *                           matching $attributes. An array of
+     *                           attribute/values pairs or a share owner
+     *                           username.
+     *
+     * @return integer  The number of shares
+     * @throws Horde_Share_Exception
+     */
+    public function countShares($userid, $perm = Horde_Perms::SHOW,
+                                $attributes = null)
+    {
+        $perms = $this->convertBitmaskToArray($perm);
+        $shareids = null;
+        if (!empty($userid)) {
+            list(, , $shareids) = $this->_getUserAndGroupShares($userid, $perms);
+        }
+
+        $query = 'SELECT COUNT(DISTINCT share_id) FROM '
+            . $this->_table . ' WHERE '
+            . $this->_getShareCriteria($userid, $perms, $attributes, $shareids);
+
+        try {
+            return $this->_db->selectValue($query);
+        } catch (Horde_Db_Exception $e) {
+            throw new Horde_Share_Exception($e);
+        }
+    }
+
+    /**
+     * Converts a bit mask number to a bit mask array.
+     *
+     * @param integer  A bit mask.
+     *
+     * @return array  The bit mask as an array.
+     */
+    static public function convertBitmaskToArray($perm)
+    {
+        $perms = array();
+        for ($bit = 1; $perm; $bit *= 2, $perm >>= 1) {
+            if ($perm % 2) {
+                $perms[] = $bit;
+            }
+        }
+        return $perms;
+    }
+
+    /**
+     * Builds a list of permission bit masks from all columns in a data row
+     * prefixed with "perm_".
+     *
+     * @param array $row     A data row including permission columns.
+     * @param string $index  Name of the column that should be used as the key
+     *                       for the permissions list.
+     *
+     * @return array  A permission hash.
+     */
+    protected function _buildPermsFromRow($row, $index)
+    {
+        $perms = array();
+        foreach ($row as $column => $value) {
+            if (substr($column, 0, 5) != 'perm_') {
+                continue;
+            }
+            if (!isset($perms[$row[$index]])) {
+                $perms[$row[$index]] = 0;
+            }
+            $perm = (int)substr($column, 5);
+            $this->_availablePermissions[$perm] = true;
+            if ($value) {
+                $perms[$row[$index]] |= $perm;
+            }
+        }
+        return $perms;
+    }
+
+    /**
+     * Converts the permissions from the database table format into the
+     * Horde_Share format.
+     *
+     * @param array $data  The share object data to convert.
+     */
+    protected function _getSharePerms(&$data)
+    {
+        $data['perm']['type'] = 'matrix';
+        $data['perm']['default'] = $data['perm']['guest'] = $data['perm']['creator'] = 0;
+        foreach ($data as $column => $value) {
+            $perm = explode('_', $column, 3);
+            if ($perm[0] != 'perm' || count($perm) != 3) {
+                continue;
+            }
+            $permvalue = (int)$perm[2];
+            $this->_availablePermissions[$permvalue] = true;
+            if ($value) {
+                $data['perm'][$perm[1]] |= $permvalue;
+            }
+            unset($data[$column]);
+        }
+    }
+
+    /**
+     * Returns the records and share IDs from the user and group tables that
+     * match the search criteria.
+     *
+     * @param string $userid     The userid of the user to check access for.
+     * @param array $perms       The level of permissions required.
+     *
+     * @return array  A set of user, groups, and shareids.
+     */
+    protected function _getUserAndGroupShares($userid, array $perms)
+    {
+        $shareids = array();
+
+        // Get users permissions.
+        $query = 'SELECT * FROM ' . $this->_table
+            . '_users WHERE user_uid = ' .  $this->_db->quote($userid)
+            . ' AND (' . $this->_getPermsCriteria('perm', $perms) . ')';
+        try {
+            $users = $this->_db->selectAll($query);
+        } catch (Horde_Db_Exception $e) {
+            throw new Horde_Share_Exception($e->getMessage());
+        }
+        foreach ($users as $user) {
+            $shareids[] = $user['share_id'];
+        }
+
+        // Get groups permissions.
+        $groups = array();
+        try {
+            $groupNames = $this->_groups->getGroupMemberships($userid, true);
+            if ($groupNames) {
+                $group_ids = array();
+                foreach (array_keys($groupNames) as $id) {
+                    $group_ids[] = $this->_db->quote((string)$id);
+                }
+                $query = 'SELECT * FROM ' . $this->_table
+                    . '_groups WHERE group_uid IN ('
+                    . implode(',', $group_ids) . ')' . ' AND ('
+                    . $this->_getPermsCriteria('perm', $perms) . ')';
+                try {
+                    $groups = $this->_db->selectAll($query);
+                } catch (Horde_Db_Exception $e) {
+                    throw new Horde_Share_Exception($e->getMessage());
+                }
+                foreach ($groups as $group) {
+                    $shareids[] = $group['share_id'];
+                }
+            }
+        } catch (Horde_Group_Exception $e) {
+            $this->_logger->err($e);
+        }
+
+        return array($users, $groups, array_unique($shareids));
+    }
+
+    /**
+     * Returns a criteria statement for querying shares.
+     *
+     * @param string $userid     The userid of the user to check access for.
+     * @param array $perms       The level of permissions required.
+     * @param array $attributes  Restrict the shares returned to those who
+     *                           have these attribute values.
+     * @param array $shareids    Additional share IDs from user and group
+     *                           permissions.
+     *
+     * @return string  The criteria string for fetching this user's shares.
+     */
+    protected function _getShareCriteria($userid, array $perms, $attributes,
+                                         $shareids = null)
+    {
+        /* Convert to driver's keys */
+        $attributes = $this->_toDriverKeys($attributes);
+
+        /* ...and to driver charset */
+        $attributes = $this->toDriverCharset($attributes);
+
+        $where = '';
+        if (empty($userid)) {
+            $where = $this->_getPermsCriteria('perm_guest', $perms);
+        } else {
+            // (owner == $userid)
+            $where .= 'share_owner = ' . $this->_db->quote($userid);
+
+            // (name == perm_creator and val & $perm)
+            $where .= ' OR ' . $this->_getPermsCriteria('perm_creator', $perms);
+
+            // (name == perm_creator and val & $perm)
+            $where .= ' OR ' . $this->_getPermsCriteria('perm_default', $perms);
+
+            if ($shareids) {
+                $where .= ' OR share_id IN (' . implode(',', $shareids) . ')';
+            }
+        }
+
+        if (is_array($attributes)) {
+            // Build attribute/key filter.
+            $where = '(' . $where . ') ';
+            foreach ($attributes as $key => $value) {
+                $where .= ' AND ' . $key . ' = ' . $this->_db->quote($value);
+            }
+        } elseif (!empty($attributes)) {
+            // Restrict to shares owned by the user specified in the
+            // $attributes string.
+            $where = '(' . $where . ') AND share_owner = ' . $this->_db->quote($attributes);
+        }
+
+        return $where;
+    }
+
+    /**
+     * Builds an ANDed criteria snippet for a set or permissions.
+     *
+     * @param string $base  A column name prefix.
+     * @param array $perms  A list of permissions.
+     *
+     * @return string  The generated criteria string.
+     */
+    protected function _getPermsCriteria($base, $perms)
+    {
+        $criteria = array();
+        foreach ($perms as $perm) {
+            $criteria[] = $base . '_' . $perm . ' = ' . $this->_db->quoteTrue();
+        }
+        return implode(' OR ', $criteria);
+    }
+}
index 520443a..347bfad 100644 (file)
@@ -11,8 +11,8 @@ owns or has access to.</description>
   <email>chuck@horde.org</email>
   <active>yes</active>
  </lead>
- <date>2011-01-03</date>
- <time>22:15:46</time>
+ <date>2011-01-14</date>
+ <time>22:44:18</time>
  <version>
   <release>0.0.4</release>
   <api>0.0.4</api>
@@ -40,6 +40,7 @@ owns or has access to.</description>
        <file name="Datatree.php" role="php" />
        <file name="Kolab.php" role="php" />
        <file name="Sql.php" role="php" />
+       <file name="Sqlng.php" role="php" />
       </dir> <!-- /lib/Horde/Share/Object -->
       <dir name="Sql">
        <file name="Hierarchical.php" role="php" />
@@ -50,6 +51,7 @@ owns or has access to.</description>
       <file name="Kolab.php" role="php" />
       <file name="Object.php" role="php" />
       <file name="Sql.php" role="php" />
+      <file name="Sqlng.php" role="php" />
       <file name="Translation.php" role="php">
        <tasks:replace from="@data_dir@" to="data_dir" type="pear-config" />
       </file>
@@ -320,6 +322,15 @@ owns or has access to.</description>
    <dir name="test">
     <dir name="Horde">
      <dir name="Share">
+      <dir name="Kolab">
+       <file name="MockTest.php" role="test" />
+       <file name="UnitTest.php" role="test" />
+      </dir> <!-- /test/Horde/Share/Kolab -->
+      <dir name="migration">
+       <file name="sql.php" role="test" />
+       <file name="sqlng.php" role="test" />
+       <file name="sql_hierarchical.php" role="test" />
+      </dir> <!-- /test/Horde/Share/migration -->
       <dir name="Sql">
        <dir name="Pdo">
         <file name="MysqlTest.php" role="test" />
@@ -340,10 +351,25 @@ owns or has access to.</description>
        <file name="MysqliTest.php" role="test" />
        <file name="MysqlTest.php" role="test" />
       </dir> <!-- /test/Horde/Share/SqlHierarchical -->
+      <dir name="Sqlng">
+       <dir name="Pdo">
+        <file name="MysqlTest.php" role="test" />
+        <file name="PgsqlTest.php" role="test" />
+        <file name="SqliteTest.php" role="test" />
+       </dir> <!-- /test/Horde/Share/Sqlng/Pdo -->
+       <file name="Base.php" role="test" />
+       <file name="MysqliTest.php" role="test" />
+       <file name="MysqlTest.php" role="test" />
+      </dir> <!-- /test/Horde/Share/Sqlng -->
+      <dir name="Stub">
+       <file name="Group.php" role="test" />
+      </dir> <!-- /test/Horde/Share/Stub -->
       <file name="AllTests.php" role="test" />
       <file name="Autoload.php" role="test" />
       <file name="Base.php" role="test" />
       <file name="conf.php.dist" role="test" />
+      <file name="phpunit.xml" role="test" />
+      <file name="stress-test" role="test" />
      </dir> <!-- /test/Horde/Share -->
     </dir> <!-- /test/Horde -->
    </dir> <!-- /test -->
@@ -385,10 +411,12 @@ owns or has access to.</description>
    <install as="Horde/Share/Kolab.php" name="lib/Horde/Share/Kolab.php" />
    <install as="Horde/Share/Object.php" name="lib/Horde/Share/Object.php" />
    <install as="Horde/Share/Sql.php" name="lib/Horde/Share/Sql.php" />
+   <install as="Horde/Share/Sqlng.php" name="lib/Horde/Share/Sqlng.php" />
    <install as="Horde/Share/Translation.php" name="lib/Horde/Share/Translation.php" />
    <install as="Horde/Share/Object/Datatree.php" name="lib/Horde/Share/Object/Datatree.php" />
    <install as="Horde/Share/Object/Kolab.php" name="lib/Horde/Share/Object/Kolab.php" />
    <install as="Horde/Share/Object/Sql.php" name="lib/Horde/Share/Object/Sql.php" />
+   <install as="Horde/Share/Object/Sqlng.php" name="lib/Horde/Share/Object/Sqlng.php" />
    <install as="Horde/Share/Object/Datatree/Share.php" name="lib/Horde/Share/Object/Datatree/Share.php" />
    <install as="Horde/Share/Object/Sql/Hierarchical.php" name="lib/Horde/Share/Object/Sql/Hierarchical.php" />
    <install as="Horde/Share/Sql/Hierarchical.php" name="lib/Horde/Share/Sql/Hierarchical.php" />
@@ -483,6 +511,13 @@ owns or has access to.</description>
    <install as="Horde/Share/Autoload.php" name="test/Horde/Share/Autoload.php" />
    <install as="Horde/Share/Base.php" name="test/Horde/Share/Base.php" />
    <install as="Horde/Share/conf.php.dist" name="test/Horde/Share/conf.php.dist" />
+   <install as="Horde/Share/phpunit.xml" name="test/Horde/Share/phpunit.xml" />
+   <install as="Horde/Share/stress-test" name="test/Horde/Share/stress-test" />
+   <install as="Horde/Share/Kolab/MockTest.php" name="test/Horde/Share/Kolab/MockTest.php" />
+   <install as="Horde/Share/Kolab/UnitTest.php" name="test/Horde/Share/Kolab/UnitTest.php" />
+   <install as="Horde/Share/migration/sql.php" name="test/Horde/Share/migration/sql.php" />
+   <install as="Horde/Share/migration/sqlng.php" name="test/Horde/Share/migration/sqlng.php" />
+   <install as="Horde/Share/migration/sql_hierarchical.php" name="test/Horde/Share/migration/sql_hierarchical.php" />
    <install as="Horde/Share/Sql/Base.php" name="test/Horde/Share/Sql/Base.php" />
    <install as="Horde/Share/Sql/MysqliTest.php" name="test/Horde/Share/Sql/MysqliTest.php" />
    <install as="Horde/Share/Sql/MysqlTest.php" name="test/Horde/Share/Sql/MysqlTest.php" />
@@ -495,6 +530,13 @@ owns or has access to.</description>
    <install as="Horde/Share/SqlHierarchical/Pdo/MysqlTest.php" name="test/Horde/Share/SqlHierarchical/Pdo/MysqlTest.php" />
    <install as="Horde/Share/SqlHierarchical/Pdo/PgsqlTest.php" name="test/Horde/Share/SqlHierarchical/Pdo/PgsqlTest.php" />
    <install as="Horde/Share/SqlHierarchical/Pdo/SqliteTest.php" name="test/Horde/Share/SqlHierarchical/Pdo/SqliteTest.php" />
+   <install as="Horde/Share/Sqlng/Base.php" name="test/Horde/Share/Sqlng/Base.php" />
+   <install as="Horde/Share/Sqlng/MysqliTest.php" name="test/Horde/Share/Sqlng/MysqliTest.php" />
+   <install as="Horde/Share/Sqlng/MysqlTest.php" name="test/Horde/Share/Sqlng/MysqlTest.php" />
+   <install as="Horde/Share/Sqlng/Pdo/MysqlTest.php" name="test/Horde/Share/Sqlng/Pdo/MysqlTest.php" />
+   <install as="Horde/Share/Sqlng/Pdo/PgsqlTest.php" name="test/Horde/Share/Sqlng/Pdo/PgsqlTest.php" />
+   <install as="Horde/Share/Sqlng/Pdo/SqliteTest.php" name="test/Horde/Share/Sqlng/Pdo/SqliteTest.php" />
+   <install as="Horde/Share/Stub/Group.php" name="test/Horde/Share/Stub/Group.php" />
   </filelist>
  </phprelease>
  <changelog>
@@ -563,7 +605,7 @@ Initial release as a PEAR package
     <release>beta</release>
     <api>beta</api>
    </stability>
-   <date>2011-01-03</date>
+   <date>2011-01-14</date>
    <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
    <notes>
 * Converted to Horde 4 coding standards
diff --git a/framework/Share/test/Horde/Share/Sqlng/Base.php b/framework/Share/test/Horde/Share/Sqlng/Base.php
new file mode 100644 (file)
index 0000000..7d859a1
--- /dev/null
@@ -0,0 +1,178 @@
+<?php
+/**
+ * Prepare the test setup.
+ */
+require_once dirname(__FILE__) . '/../Base.php';
+
+/**
+ * @author     Jan Schneider <jan@horde.org>
+ * @category   Horde
+ * @package    Share
+ * @subpackage UnitTests
+ * @copyright  2010 The Horde Project (http://www.horde.org/)
+ * @license    http://www.fsf.org/copyleft/lgpl.html LGPL
+ */
+class Horde_Share_Test_Sqlng_Base extends Horde_Share_Test_Base
+{
+    protected static $db;
+
+    public function testSetTable()
+    {
+        $this->assertEquals('test_shares', self::$share->getTable());
+        self::$share->setTable('foo');
+        $this->assertEquals('foo', self::$share->getTable());
+        self::$share->setTable('test_shares');
+    }
+
+    public function testSetStorage()
+    {
+        self::$share->setStorage(self::$db);
+        $this->assertEquals(self::$db, self::$share->getStorage());
+    }
+
+    public function testAddShare()
+    {
+        $share = parent::addShare();
+        $this->assertInstanceOf('Horde_Share_Object_Sqlng', $share);
+        return $share->getId();
+    }
+
+    /**
+     * @depends testAddShare
+     */
+    public function testPermissions($myshareid)
+    {
+        return parent::permissions($myshareid);
+    }
+
+    /**
+     * @depends testAddShare
+     */
+    public function testExists()
+    {
+        parent::exists();
+    }
+
+    /**
+     * @depends testPermissions
+     */
+    public function testCountShares()
+    {
+        parent::countShares();
+    }
+
+    /**
+     * @depends testPermissions
+     */
+    public function testGetShare()
+    {
+        $shares = parent::getShare();
+        $this->assertInstanceOf('Horde_Share_Object_Sqlng', $shares[0]);
+        $this->assertInstanceOf('Horde_Share_Object_Sqlng', $shares[1]);
+        $this->assertInstanceOf('Horde_Share_Object_Sqlng', $shares[2]);
+        return $shares;
+    }
+
+    /**
+     * @depends testGetShare
+     */
+    public function testGetShareById(array $shares)
+    {
+        parent::getShareById($shares);
+    }
+
+    /**
+     * @depends testGetShare
+     */
+    public function testGetShares(array $shares)
+    {
+        parent::getShares($shares);
+    }
+
+    /**
+     * @depends testPermissions
+     */
+    public function testListAllShares()
+    {
+        parent::listAllShares();
+    }
+
+    /**
+     * @depends testPermissions
+     */
+    public function testListShares(array $shareids)
+    {
+        parent::listShares($shareids);
+    }
+
+    /**
+     * @depends testPermissions
+     */
+    public function testListSystemShares()
+    {
+        parent::listSystemShares();
+    }
+
+    /**
+     * @depends testPermissions
+     */
+    public function testRemoveUserPermissions(array $shareids)
+    {
+        return parent::removeUserPermissions($shareids);
+    }
+
+    /**
+     * @depends testRemoveUserPermissions
+     */
+    public function testRemoveGroupPermissions(array $shareids)
+    {
+        parent::removeGroupPermissions($shareids);
+    }
+
+    /**
+     * @depends testGetShare
+     */
+    public function testRemoveShare(array $share)
+    {
+        parent::removeShare($share);
+    }
+
+    public function testCallback()
+    {
+        parent::callback(new Horde_Share_Object_Sqlng(array()));
+    }
+
+    public static function setUpBeforeClass()
+    {
+        require_once dirname(__FILE__) . '/../migration/sqlng.php';
+        migrate_sqlng(self::$db);
+
+        $group = new Horde_Group_Test();
+        self::$share = new Horde_Share_Sqlng('test', 'john', new Horde_Perms(), $group);
+        self::$share->setStorage(self::$db);
+
+        // FIXME
+        $GLOBALS['injector'] = new Horde_Injector(new Horde_Injector_TopLevel());
+        $GLOBALS['injector']->setInstance('Horde_Group', $group);
+    }
+
+    public static function tearDownAfterClass()
+    {
+        if (self::$db) {
+            /*
+            $migration = new Horde_Db_Migration_Base(self::$db);
+            $migration->dropTable('test_shares');
+            $migration->dropTable('test_shares_groups');
+            $migration->dropTable('test_shares_users');
+            */
+            self::$db = null;
+        }
+    }
+
+    public function setUp()
+    {
+        if (!self::$db) {
+            $this->markTestSkipped('No sqlite extension or no sqlite PDO driver.');
+        }
+    }
+}
diff --git a/framework/Share/test/Horde/Share/Sqlng/MysqlTest.php b/framework/Share/test/Horde/Share/Sqlng/MysqlTest.php
new file mode 100644 (file)
index 0000000..2fce9db
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+/**
+ * Prepare the test setup.
+ */
+require_once dirname(__FILE__) . '/Base.php';
+
+/**
+ * @author     Jan Schneider <jan@horde.org>
+ * @category   Horde
+ * @package    Share
+ * @subpackage UnitTests
+ * @copyright  2010 The Horde Project (http://www.horde.org/)
+ * @license    http://www.fsf.org/copyleft/lgpl.html LGPL
+ */
+class Horde_Share_Sqlng_MysqlTest extends Horde_Share_Test_Sqlng_Base
+{
+    public static function setUpBeforeClass()
+    {
+        if (!extension_loaded('mysql')) {
+            return;
+        }
+        $config = self::getConfig('SHARE_SQL_MYSQL_TEST_CONFIG');
+        if ($config) {
+            self::$db = new Horde_Db_Adapter_Mysql($config['share']['sql']['mysql']);
+            parent::setUpBeforeClass();
+        }
+    }
+}
diff --git a/framework/Share/test/Horde/Share/Sqlng/MysqliTest.php b/framework/Share/test/Horde/Share/Sqlng/MysqliTest.php
new file mode 100644 (file)
index 0000000..46255db
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+/**
+ * Prepare the test setup.
+ */
+require_once dirname(__FILE__) . '/Base.php';
+
+/**
+ * @author     Jan Schneider <jan@horde.org>
+ * @category   Horde
+ * @package    Share
+ * @subpackage UnitTests
+ * @copyright  2010 The Horde Project (http://www.horde.org/)
+ * @license    http://www.fsf.org/copyleft/lgpl.html LGPL
+ */
+class Horde_Share_Sqlng_MysqliTest extends Horde_Share_Test_Sqlng_Base
+{
+    public static function setUpBeforeClass()
+    {
+        if (!extension_loaded('mysqli')) {
+            return;
+        }
+        $config = self::getConfig('SHARE_SQL_MYSQLI_TEST_CONFIG');
+        if ($config) {
+            self::$db = new Horde_Db_Adapter_Mysqli($config['share']['sql']['mysqli']);
+            parent::setUpBeforeClass();
+        }
+    }
+}
diff --git a/framework/Share/test/Horde/Share/Sqlng/Pdo/MysqlTest.php b/framework/Share/test/Horde/Share/Sqlng/Pdo/MysqlTest.php
new file mode 100644 (file)
index 0000000..887c21e
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Prepare the test setup.
+ */
+require_once dirname(__FILE__) . '/../Base.php';
+
+/**
+ * @author     Jan Schneider <jan@horde.org>
+ * @category   Horde
+ * @package    Share
+ * @subpackage UnitTests
+ * @copyright  2010 The Horde Project (http://www.horde.org/)
+ * @license    http://www.fsf.org/copyleft/lgpl.html LGPL
+ */
+class Horde_Share_Sqlng_Pdo_MysqlTest extends Horde_Share_Test_Sqlng_Base
+{
+    public static function setUpBeforeClass()
+    {
+        if (!extension_loaded('pdo') ||
+            !in_array('mysql', PDO::getAvailableDrivers())) {
+            return;
+        }
+        $config = self::getConfig('SHARE_SQL_PDO_MYSQL_TEST_CONFIG');
+        if ($config) {
+            self::$db = new Horde_Db_Adapter_Pdo_Mysql($config['share']['sql']['pdo_mysql']);
+            parent::setUpBeforeClass();
+        }
+    }
+}
diff --git a/framework/Share/test/Horde/Share/Sqlng/Pdo/PgsqlTest.php b/framework/Share/test/Horde/Share/Sqlng/Pdo/PgsqlTest.php
new file mode 100644 (file)
index 0000000..a36062d
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Prepare the test setup.
+ */
+require_once dirname(__FILE__) . '/../Base.php';
+
+/**
+ * @author     Jan Schneider <jan@horde.org>
+ * @category   Horde
+ * @package    Share
+ * @subpackage UnitTests
+ * @copyright  2010 The Horde Project (http://www.horde.org/)
+ * @license    http://www.fsf.org/copyleft/lgpl.html LGPL
+ */
+class Horde_Share_Sqlng_Pdo_PgsqlTest extends Horde_Share_Test_Sqlng_Base
+{
+    public static function setUpBeforeClass()
+    {
+        if (!extension_loaded('pdo') ||
+            !in_array('pgsql', PDO::getAvailableDrivers())) {
+            return;
+        }
+        $config = self::getConfig('SHARE_SQL_PDO_PGSQL_TEST_CONFIG');
+        if ($config) {
+            self::$db = new Horde_Db_Adapter_Pdo_Pgsql($config['share']['sql']['pdo_pgsql']);
+            parent::setUpBeforeClass();
+        }
+    }
+}
diff --git a/framework/Share/test/Horde/Share/Sqlng/Pdo/SqliteTest.php b/framework/Share/test/Horde/Share/Sqlng/Pdo/SqliteTest.php
new file mode 100644 (file)
index 0000000..430242b
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+/**
+ * Prepare the test setup.
+ */
+require_once dirname(__FILE__) . '/../Base.php';
+
+/**
+ * @author     Jan Schneider <jan@horde.org>
+ * @category   Horde
+ * @package    Share
+ * @subpackage UnitTests
+ * @copyright  2010 The Horde Project (http://www.horde.org/)
+ * @license    http://www.fsf.org/copyleft/lgpl.html LGPL
+ */
+class Horde_Share_Sqlng_Pdo_SqliteTest extends Horde_Share_Test_Sqlng_Base
+{
+    public static function setUpBeforeClass()
+    {
+        if (!extension_loaded('pdo') ||
+            !in_array('sqlite', PDO::getAvailableDrivers())) {
+            return;
+        }
+        self::$db = new Horde_Db_Adapter_Pdo_Sqlite(array('dbname' => ':memory:'));
+        parent::setUpBeforeClass();
+    }
+}
diff --git a/framework/Share/test/Horde/Share/Sqlng/Pdo/conf.php b/framework/Share/test/Horde/Share/Sqlng/Pdo/conf.php
new file mode 120000 (symlink)
index 0000000..c63b511
--- /dev/null
@@ -0,0 +1 @@
+../conf.php
\ No newline at end of file
diff --git a/framework/Share/test/Horde/Share/migration/sqlng.php b/framework/Share/test/Horde/Share/migration/sqlng.php
new file mode 100644 (file)
index 0000000..16dda68
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+
+function migrate_sqlng($db)
+{
+    $migration = new Horde_Db_Migration_Base($db);
+
+    /* Cleanup potential left-overs. */
+    try {
+        $migration->dropTable('test_shares');
+        $migration->dropTable('test_shares_groups');
+        $migration->dropTable('test_shares_users');
+    } catch (Horde_Db_Exception $e) {
+    }
+
+    $t = $migration->createTable('test_shares', array('primaryKey' => 'share_id'));
+    //$t->column('share_id', 'integer', array('null' => false, 'autoincrement' => true));
+    $t->column('share_name', 'string', array('limit' => 255, 'null' => false));
+    $t->column('share_owner', 'string', array('limit' => 255));
+    $t->column('share_flags', 'integer', array('default' => 0, 'null' => false));
+    $t->column('perm_creator_' . Horde_Perms::SHOW, 'boolean', array('default' => false, 'null' => false));
+    $t->column('perm_creator_' . Horde_Perms::READ, 'boolean', array('default' => false, 'null' => false));
+    $t->column('perm_creator_' . Horde_Perms::EDIT, 'boolean', array('default' => false, 'null' => false));
+    $t->column('perm_creator_' . Horde_Perms::DELETE, 'boolean', array('default' => false, 'null' => false));
+    $t->column('perm_default_' . Horde_Perms::SHOW, 'boolean', array('default' => false, 'null' => false));
+    $t->column('perm_default_' . Horde_Perms::READ, 'boolean', array('default' => false, 'null' => false));
+    $t->column('perm_default_' . Horde_Perms::EDIT, 'boolean', array('default' => false, 'null' => false));
+    $t->column('perm_default_' . Horde_Perms::DELETE, 'boolean', array('default' => false, 'null' => false));
+    $t->column('perm_guest_' . Horde_Perms::SHOW, 'boolean', array('default' => false, 'null' => false));
+    $t->column('perm_guest_' . Horde_Perms::READ, 'boolean', array('default' => false, 'null' => false));
+    $t->column('perm_guest_' . Horde_Perms::EDIT, 'boolean', array('default' => false, 'null' => false));
+    $t->column('perm_guest_' . Horde_Perms::DELETE, 'boolean', array('default' => false, 'null' => false));
+    $t->column('attribute_name', 'string', array('limit' => 255));
+    $t->column('attribute_desc', 'string', array('limit' => 255));
+    $t->end();
+
+    $migration->addIndex('test_shares', array('share_name'));
+    $migration->addIndex('test_shares', array('share_owner'));
+    $migration->addIndex('test_shares', array('perm_creator_' . Horde_Perms::SHOW));
+    $migration->addIndex('test_shares', array('perm_creator_' . Horde_Perms::READ));
+    $migration->addIndex('test_shares', array('perm_creator_' . Horde_Perms::EDIT));
+    $migration->addIndex('test_shares', array('perm_creator_' . Horde_Perms::DELETE));
+    $migration->addIndex('test_shares', array('perm_default_' . Horde_Perms::SHOW));
+    $migration->addIndex('test_shares', array('perm_default_' . Horde_Perms::READ));
+    $migration->addIndex('test_shares', array('perm_default_' . Horde_Perms::EDIT));
+    $migration->addIndex('test_shares', array('perm_default_' . Horde_Perms::DELETE));
+    $migration->addIndex('test_shares', array('perm_guest_' . Horde_Perms::SHOW));
+    $migration->addIndex('test_shares', array('perm_guest_' . Horde_Perms::READ));
+    $migration->addIndex('test_shares', array('perm_guest_' . Horde_Perms::EDIT));
+    $migration->addIndex('test_shares', array('perm_guest_' . Horde_Perms::DELETE));
+
+    $t = $migration->createTable('test_shares_groups');
+    $t->column('share_id', 'integer', array('null' => false));
+    $t->column('group_uid', 'string', array('limit' => 255, 'null' => false));
+    $t->column('perm_' . Horde_Perms::SHOW, 'boolean', array('default' => false, 'null' => false));
+    $t->column('perm_' . Horde_Perms::READ, 'boolean', array('default' => false, 'null' => false));
+    $t->column('perm_' . Horde_Perms::EDIT, 'boolean', array('default' => false, 'null' => false));
+    $t->column('perm_' . Horde_Perms::DELETE, 'boolean', array('default' => false, 'null' => false));
+    $t->end();
+
+    $migration->addIndex('test_shares_groups', array('share_id'));
+    $migration->addIndex('test_shares_groups', array('group_uid'));
+    $migration->addIndex('test_shares_groups', array('perm_' . Horde_Perms::SHOW));
+    $migration->addIndex('test_shares_groups', array('perm_' . Horde_Perms::READ));
+    $migration->addIndex('test_shares_groups', array('perm_' . Horde_Perms::EDIT));
+    $migration->addIndex('test_shares_groups', array('perm_' . Horde_Perms::DELETE));
+
+    $t = $migration->createTable('test_shares_users');
+    $t->column('share_id', 'integer', array('null' => false));
+    $t->column('user_uid', 'string', array('limit' => 255));
+    $t->column('perm_' . Horde_Perms::SHOW, 'boolean', array('default' => false, 'null' => false));
+    $t->column('perm_' . Horde_Perms::READ, 'boolean', array('default' => false, 'null' => false));
+    $t->column('perm_' . Horde_Perms::EDIT, 'boolean', array('default' => false, 'null' => false));
+    $t->column('perm_' . Horde_Perms::DELETE, 'boolean', array('default' => false, 'null' => false));
+    $t->end();
+
+    $migration->addIndex('test_shares_users', array('share_id'));
+    $migration->addIndex('test_shares_users', array('user_uid'));
+    $migration->addIndex('test_shares_users', array('perm_' . Horde_Perms::SHOW));
+    $migration->addIndex('test_shares_users', array('perm_' . Horde_Perms::READ));
+    $migration->addIndex('test_shares_users', array('perm_' . Horde_Perms::EDIT));
+    $migration->addIndex('test_shares_users', array('perm_' . Horde_Perms::DELETE));
+
+    $migration->migrate('up');
+}
index 962d750..cc66d51 100755 (executable)
@@ -15,7 +15,7 @@ $argv = new Horde_Argv_Parser(array('description' => $description));
 $argv->addOption(
     '-b', '--backend',
     array('type' => 'choice',
-          'choices' => array('sql', 'sql_hierarchical', 'kolab', 'datatree'),
+          'choices' => array('sql', 'sql_hierarchical', 'sqlng', 'kolab', 'datatree'),
           'help' => 'The backend to test. Must be configured in the conf.php configuration file.'));
 $argv->addOption(
     '-c', '--configuration',
@@ -71,6 +71,7 @@ $config = isset($conf['share']['sql'][$options['configuration']])
 /* Create storage backend. */
 switch ($options['backend']) {
 case 'sql':
+case 'sqlng':
 case 'sql_hierarchical':
     $class = 'Horde_Db_Adapter_' . implode('_', array_map(array('Horde_String', 'ucwords'), explode('_', $options['configuration'])));
     $storage = new $class($config);