From: Jan Schneider Date: Fri, 14 Jan 2011 22:16:47 +0000 (+0100) Subject: Add next-generation SQL Share driver. X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=c731bcace9f8d5568cf6088c3a3f9c33beb84ce3;p=horde.git Add next-generation SQL Share driver. 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. --- diff --git a/.gitignore b/.gitignore index 75d9f9ffe..f62950701 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/framework/Share/lib/Horde/Share/Base.php b/framework/Share/lib/Horde/Share/Base.php index 65a3f093f..e362443ba 100644 --- a/framework/Share/lib/Horde/Share/Base.php +++ b/framework/Share/lib/Horde/Share/Base.php @@ -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 */ diff --git a/framework/Share/lib/Horde/Share/Object/Sql.php b/framework/Share/lib/Horde/Share/Object/Sql.php index c5c75bf7c..a8f8656ed 100644 --- a/framework/Share/lib/Horde/Share/Object/Sql.php +++ b/framework/Share/lib/Horde/Share/Object/Sql.php @@ -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 index 000000000..0a40f3bcc --- /dev/null +++ b/framework/Share/lib/Horde/Share/Object/Sqlng.php @@ -0,0 +1,219 @@ + + * @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); + } +} diff --git a/framework/Share/lib/Horde/Share/Sql.php b/framework/Share/lib/Horde/Share/Sql.php index d00297bd5..837a30d28 100644 --- a/framework/Share/lib/Horde/Share/Sql.php +++ b/framework/Share/lib/Horde/Share/Sql.php @@ -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 index 000000000..9efc14b06 --- /dev/null +++ b/framework/Share/lib/Horde/Share/Sqlng.php @@ -0,0 +1,369 @@ + + * @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); + } +} diff --git a/framework/Share/package.xml b/framework/Share/package.xml index 520443aee..347bfad5a 100644 --- a/framework/Share/package.xml +++ b/framework/Share/package.xml @@ -11,8 +11,8 @@ owns or has access to. chuck@horde.org yes - 2011-01-03 - + 2011-01-14 + 0.0.4 0.0.4 @@ -40,6 +40,7 @@ owns or has access to. + @@ -50,6 +51,7 @@ owns or has access to. + @@ -320,6 +322,15 @@ owns or has access to. + + + + + + + + + @@ -340,10 +351,25 @@ owns or has access to. + + + + + + + + + + + + + + + @@ -385,10 +411,12 @@ owns or has access to. + + @@ -483,6 +511,13 @@ owns or has access to. + + + + + + + @@ -495,6 +530,13 @@ owns or has access to. + + + + + + + @@ -563,7 +605,7 @@ Initial release as a PEAR package beta beta - 2011-01-03 + 2011-01-14 LGPL * 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 index 000000000..7d859a18a --- /dev/null +++ b/framework/Share/test/Horde/Share/Sqlng/Base.php @@ -0,0 +1,178 @@ + + * @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 index 000000000..2fce9db85 --- /dev/null +++ b/framework/Share/test/Horde/Share/Sqlng/MysqlTest.php @@ -0,0 +1,28 @@ + + * @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 index 000000000..46255db4b --- /dev/null +++ b/framework/Share/test/Horde/Share/Sqlng/MysqliTest.php @@ -0,0 +1,28 @@ + + * @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 index 000000000..887c21e2c --- /dev/null +++ b/framework/Share/test/Horde/Share/Sqlng/Pdo/MysqlTest.php @@ -0,0 +1,29 @@ + + * @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 index 000000000..a36062d45 --- /dev/null +++ b/framework/Share/test/Horde/Share/Sqlng/Pdo/PgsqlTest.php @@ -0,0 +1,29 @@ + + * @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 index 000000000..430242b99 --- /dev/null +++ b/framework/Share/test/Horde/Share/Sqlng/Pdo/SqliteTest.php @@ -0,0 +1,26 @@ + + * @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 index 000000000..c63b511ff --- /dev/null +++ b/framework/Share/test/Horde/Share/Sqlng/Pdo/conf.php @@ -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 index 000000000..16dda6870 --- /dev/null +++ b/framework/Share/test/Horde/Share/migration/sqlng.php @@ -0,0 +1,84 @@ +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'); +} diff --git a/framework/Share/test/Horde/Share/stress-test b/framework/Share/test/Horde/Share/stress-test index 962d75011..cc66d51a8 100755 --- a/framework/Share/test/Horde/Share/stress-test +++ b/framework/Share/test/Horde/Share/stress-test @@ -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);