* CalDAV/calendar backported iterator from trunk to minimize resource usage

- move all filtering into SQL query in calendar_so, to be able to correctly return N rows starting from row M
- re-enabling propfind iterator again for calendar (fetching events in chunks of 500), to lower memory footprint
Please note: changed SQL queries used for CalDAV do not take changed participants (or status) in exceptions into account
- merged: r34529, r34584, r34592, r34594, r35948
This commit is contained in:
Ralf Becker 2011-08-03 16:53:22 +00:00
parent b6552771b2
commit 78b40243b7
4 changed files with 131 additions and 135 deletions

View File

@ -155,6 +155,10 @@ class calendar_bo
* @var array $cached_holidays holidays plus birthdays (gets cached in the session for performance reasons) * @var array $cached_holidays holidays plus birthdays (gets cached in the session for performance reasons)
*/ */
var $cached_holidays; var $cached_holidays;
/**
* @var boholiday
*/
var $holidays;
/** /**
* Instance of the socal class * Instance of the socal class
* *
@ -445,10 +449,17 @@ class calendar_bo
$this->check_move_horizont($end); $this->check_move_horizont($end);
} }
$daywise = !isset($params['daywise']) ? False : !!$params['daywise']; $daywise = !isset($params['daywise']) ? False : !!$params['daywise'];
$enum_recuring = $daywise || !isset($params['enum_recuring']) || !!$params['enum_recuring']; $params['enum_recuring'] = $enum_recuring = $daywise || !isset($params['enum_recuring']) || !!$params['enum_recuring'];
$cat_id = isset($params['cat_id']) ? $params['cat_id'] : 0; $cat_id = isset($params['cat_id']) ? $params['cat_id'] : 0;
$filter = isset($params['filter']) ? $params['filter'] : 'all'; $filter = isset($params['filter']) ? $params['filter'] : 'all';
$offset = isset($params['offset']) && $params['offset'] !== false ? (int) $params['offset'] : false; $offset = isset($params['offset']) && $params['offset'] !== false ? (int) $params['offset'] : false;
// socal::search() returns rejected group-invitations, as only the user not also the group is rejected
// as we cant remove them efficiantly in SQL, we kick them out here, but only if just one user is displayed
$users_in = (array)$params_in['users'];
$remove_rejected_by_user = !in_array($filter,array('all','rejected')) &&
count($users_in) == 1 && $users_in[0] > 0 ? $users_in[0] : null;
//error_log(__METHOD__.'('.array2string($params_in).", $sql_filter) params[users]=".array2string($params['users']).' --> remove_rejected_by_user='.array2string($remove_rejected_by_user));
if ($this->debug && ($this->debug > 1 || $this->debug == 'search')) if ($this->debug && ($this->debug > 1 || $this->debug == 'search'))
{ {
$this->debug_message('bocal::search(%1) start=%2, end=%3, daywise=%4, cat_id=%5, filter=%6, query=%7, offset=%8, num_rows=%9, order=%10, sql_filter=%11)', $this->debug_message('bocal::search(%1) start=%2, end=%3, daywise=%4, cat_id=%5, filter=%6, query=%7, offset=%8, num_rows=%9, order=%10, sql_filter=%11)',
@ -456,7 +467,7 @@ class calendar_bo
} }
// date2ts(,true) converts to server time, db2data converts again to user-time // date2ts(,true) converts to server time, db2data converts again to user-time
$events =& $this->so->search(isset($start) ? $this->date2ts($start,true) : null,isset($end) ? $this->date2ts($end,true) : null, $events =& $this->so->search(isset($start) ? $this->date2ts($start,true) : null,isset($end) ? $this->date2ts($end,true) : null,
$users,$cat_id,$filter,$offset,(int)$params['num_rows'],$params); $users,$cat_id,$filter,$offset,(int)$params['num_rows'],$params,$remove_rejected_by_user);
if (isset($params['cols'])) if (isset($params['cols']))
{ {
@ -465,24 +476,9 @@ class calendar_bo
$this->total = $this->so->total; $this->total = $this->so->total;
$this->db2data($events,isset($params['date_format']) ? $params['date_format'] : 'ts'); $this->db2data($events,isset($params['date_format']) ? $params['date_format'] : 'ts');
// socal::search() returns rejected group-invitations, as only the user not also the group is rejected
// as we cant remove them efficiantly in SQL, we kick them out here, but only if just one user is displayed
$remove_rejected_by_user = !in_array($filter,array('all','rejected','owner')) && count($params['users']) == 1 ? $params['users'][0] : false;
//echo "<p align=right>remove_rejected_by_user=$remove_rejected_by_user, filter=$filter, params[users]=".print_r($param['users'])."</p>\n"; //echo "<p align=right>remove_rejected_by_user=$remove_rejected_by_user, filter=$filter, params[users]=".print_r($param['users'])."</p>\n";
foreach($events as $id => $event) foreach($events as $id => $event)
{ {
if (isset($start) && $event['end'] < $start)
{
unset($events[$id]); // remove former events (e.g. whole day)
$this->total--;
continue;
}
if ($remove_rejected_by_user && $event['participants'][$remove_rejected_by_user] == 'R')
{
unset($events[$id]); // remove the rejected event
$this->total--;
continue;
}
if ($params['enum_groups'] && $this->enum_groups($event)) if ($params['enum_groups'] && $this->enum_groups($event))
{ {
$events[$id] = $event; $events[$id] = $event;
@ -538,29 +534,11 @@ class calendar_bo
$this->debug_message('socalendar::search daywise events=%1',False,$events); $this->debug_message('socalendar::search daywise events=%1',False,$events);
} }
} }
elseif(!$enum_recuring)
{
$recur_ids = array();
foreach($events as $k => $event)
{
if ($event['recur_type'] != MCAL_RECUR_NONE)
{
if (!in_array($event['id'],$recur_ids))
{
$recur_ids[] = $event['id'];
}
unset($events[$k]);
}
}
if (count($recur_ids))
{
$events = array_merge($this->read($recur_ids,null,false,$params['date_format']),$events);
}
}
if ($this->debug && ($this->debug > 0 || $this->debug == 'search')) if ($this->debug && ($this->debug > 0 || $this->debug == 'search'))
{ {
$this->debug_message('bocal::search(%1)=%2',True,$params,$events); $this->debug_message('bocal::search(%1)=%2',True,$params,$events);
} }
//error_log(__METHOD__."() returning ".count($events)." entries, total=$this->total ".function_backtrace());
return $events; return $events;
} }

View File

@ -122,7 +122,12 @@ class calendar_groupdav extends groupdav_handler
'enum_recuring' => false, 'enum_recuring' => false,
'daywise' => false, 'daywise' => false,
'date_format' => 'server', 'date_format' => 'server',
'no_total' => true, // we need no total number of rows (saves extra query)
); );
if ($this->client_shared_uid_exceptions) // do NOT return (non-virtual) exceptions
{
$filter['query'] = array('cal_reference' => 0);
}
if ($path == '/calendar/') if ($path == '/calendar/')
{ {
@ -140,8 +145,7 @@ class calendar_groupdav extends groupdav_handler
} }
if ($this->debug > 1) if ($this->debug > 1)
{ {
error_log(__METHOD__."($path,,,$user,$id) filter=". error_log(__METHOD__."($path,,,$user,$id) filter=".array2string($filter));
array2string($filter));
} }
// check if we have to return the full calendar data or just the etag's // check if we have to return the full calendar data or just the etag's
@ -157,12 +161,12 @@ class calendar_groupdav extends groupdav_handler
} }
} }
} }
/* disabling not working iterator
// return iterator, calling ourself to return result in chunks // return iterator, calling ourself to return result in chunks
$files['files'] = new groupdav_propfind_iterator($this,$path,$filter,$files['files']); $files['files'] = new groupdav_propfind_iterator($this,$path,$filter,$files['files']);
return true; return true;
} }
*/
/** /**
* Callback for profind interator * Callback for profind interator
* *
@ -171,15 +175,13 @@ class calendar_groupdav extends groupdav_handler
* @param array|boolean $start=false false=return all or array(start,num) * @param array|boolean $start=false false=return all or array(start,num)
* @return array with "files" array with values for keys path and props * @return array with "files" array with values for keys path and props
*/ */
/* disabling not working iterator
function propfind_callback($path,array $filter,$start=false) function propfind_callback($path,array $filter,$start=false)
{ {
*/
if ($this->debug) $starttime = microtime(true); if ($this->debug) $starttime = microtime(true);
$calendar_data = $filter['calendar_data']; $calendar_data = $filter['calendar_data'];
unset($filter['calendar_data']); unset($filter['calendar_data']);
/* disabling not working iterator
$files = array(); $files = array();
if (is_array($start)) if (is_array($start))
@ -187,8 +189,6 @@ class calendar_groupdav extends groupdav_handler
$filter['offset'] = $start[0]; $filter['offset'] = $start[0];
$filter['num_rows'] = $start[1]; $filter['num_rows'] = $start[1];
} }
error_log(__METHOD__."($path,,".array2string($start).") filter=".array2string($filter));
*/
$events =& $this->bo->search($filter); $events =& $this->bo->search($filter);
if ($events) if ($events)
{ {
@ -197,6 +197,7 @@ error_log(__METHOD__."($path,,".array2string($start).") filter=".array2string($f
{ {
if ($this->client_shared_uid_exceptions && $event['reference']) if ($this->client_shared_uid_exceptions && $event['reference'])
{ {
throw new egw_exception_assertion_failed(__METHOD__."() event=".array2string($event));
// this exception will be handled with the series master // this exception will be handled with the series master
unset($events[$k]); unset($events[$k]);
continue; continue;
@ -228,10 +229,7 @@ error_log(__METHOD__."($path,,".array2string($start).") filter=".array2string($f
{ {
$props[] = HTTP_WebDAV_Server::mkprop('getcontentlength', ''); // expensive to calculate and no CalDAV client uses it $props[] = HTTP_WebDAV_Server::mkprop('getcontentlength', ''); // expensive to calculate and no CalDAV client uses it
} }
/* disabling not working iterator
$files[] = array( $files[] = array(
*/
$files['files'][] = array(
'path' => $path.$this->get_path($event), 'path' => $path.$this->get_path($event),
'props' => $props, 'props' => $props,
); );
@ -242,10 +240,7 @@ error_log(__METHOD__."($path,,".array2string($start).") filter=".array2string($f
error_log(__METHOD__."($path) took ".(microtime(true) - $starttime). error_log(__METHOD__."($path) took ".(microtime(true) - $starttime).
' to return '.count($files['files']).' items'); ' to return '.count($files['files']).' items');
} }
/* disabling not working iterator
return $files; return $files;
*/
return true;
} }
/** /**

View File

@ -98,7 +98,6 @@ class calendar_so
*/ */
protected static $tz_cache = array(); protected static $tz_cache = array();
/** /**
* Constructor of the socal class * Constructor of the socal class
*/ */
@ -373,17 +372,18 @@ class calendar_so
* @param string $params['append'] SQL to append to the query before $order, eg. for a GROUP BY clause * @param string $params['append'] SQL to append to the query before $order, eg. for a GROUP BY clause
* @param array $params['cfs'] custom fields to query, null = none, array() = all, or array with cfs names * @param array $params['cfs'] custom fields to query, null = none, array() = all, or array with cfs names
* @param array $params['users'] raw parameter as passed to calendar_bo::search() no memberships resolved! * @param array $params['users'] raw parameter as passed to calendar_bo::search() no memberships resolved!
* @param int $remove_rejected_by_user=null add join to remove entry, if given user has rejected it
* @return array of cal_ids, or false if error in the parameters * @return array of cal_ids, or false if error in the parameters
* *
* ToDo: search custom-fields too * ToDo: search custom-fields too
*/ */
function &search($start,$end,$users,$cat_id=0,$filter='all',$offset=False,$num_rows=0,array $params=array()) function &search($start,$end,$users,$cat_id=0,$filter='all',$offset=False,$num_rows=0,array $params=array(),$remove_rejected_by_user=null)
{ {
//error_log('*** '.__METHOD__.'('.($start ? date('Y-m-d H:i',$start) : '').','.($end ? date('Y-m-d H:i',$end) : '').','.array2string($users).','.array2string($cat_id).",'$filter',".array2string($offset).",$num_rows,".array2string($params).') '.function_backtrace()); //error_log(__METHOD__.'('.($start ? date('Y-m-d H:i',$start) : '').','.($end ? date('Y-m-d H:i',$end) : '').','.array2string($users).','.array2string($cat_id).",'$filter',".array2string($offset).",$num_rows,".array2string($params).') '.function_backtrace());
$cols = self::get_columns('calendar', $this->cal_table); $cols = self::get_columns('calendar', $this->cal_table);
$cols[0] = $this->db->to_varchar($this->cal_table.'.cal_id'); $cols[0] = $this->db->to_varchar($this->cal_table.'.cal_id');
$cols = isset($params['cols']) ? $params['cols'] : "$this->repeats_table.recur_type,$this->repeats_table.recur_enddate,$this->repeats_table.recur_interval,$this->repeats_table.recur_data,$this->repeats_table.recur_exception,".implode(',',$cols).",cal_start,cal_end,cal_recur_date"; $cols = isset($params['cols']) ? $params['cols'] : "$this->repeats_table.recur_type,$this->repeats_table.recur_enddate,$this->repeats_table.recur_interval,$this->repeats_table.recur_data,$this->repeats_table.recur_exception,".implode(',',$cols).",cal_start,cal_end,$this->user_table.cal_recur_date";
$where = array(); $where = array();
if (is_array($params['query'])) if (is_array($params['query']))
@ -423,7 +423,8 @@ class calendar_so
$users_by_type[$user[0]][] = (int) substr($user,1); $users_by_type[$user[0]][] = (int) substr($user,1);
} }
} }
$to_or = $user_or = $owner_or = array(); $to_or = $user_or = array();
$owner_or = null;
$useUnionQuery = $this->db->capabilities['distinct_on_text'] && $this->db->capabilities['union']; $useUnionQuery = $this->db->capabilities['distinct_on_text'] && $this->db->capabilities['union'];
$table_def = $this->db->get_table_definitions('calendar',$this->user_table); $table_def = $this->db->get_table_definitions('calendar',$this->user_table);
foreach($users_by_type as $type => $ids) foreach($users_by_type as $type => $ids)
@ -431,20 +432,22 @@ class calendar_so
// when we are able to use Union Querys, we do not OR our query, we save the needed parts for later construction of the union // when we are able to use Union Querys, we do not OR our query, we save the needed parts for later construction of the union
if ($useUnionQuery) if ($useUnionQuery)
{ {
$user_or[] = $this->db->expression($table_def,array( $user_or[] = $this->db->expression($table_def,$this->user_table.'.',array(
'cal_user_type' => $type, 'cal_user_type' => $type,
),' AND '.$this->user_table.'.',array(
'cal_user_id' => $ids, 'cal_user_id' => $ids,
)); ));
if ($type == 'u' && ($filter == 'owner')) if ($type == 'u' && $filter == 'owner')
{ {
$cal_table_def = $this->db->get_table_definitions('calendar',$this->cal_table); $cal_table_def = $this->db->get_table_definitions('calendar',$this->cal_table);
$owner_or[] = $this->db->expression($cal_table_def,array('cal_owner' => $ids)); $owner_or = $this->db->expression($cal_table_def,array('cal_owner' => $ids));
} }
} }
else else
{ {
$to_or[] = $this->db->expression($table_def,array( $to_or[] = $this->db->expression($table_def,$this->user_table.'.',array(
'cal_user_type' => $type, 'cal_user_type' => $type,
),' AND '.$this->user_table.'.',array(
'cal_user_id' => $ids, 'cal_user_id' => $ids,
)); ));
if ($type == 'u' && ($filter == 'owner')) if ($type == 'u' && ($filter == 'owner'))
@ -465,24 +468,24 @@ class calendar_so
{ {
case 'showonlypublic': case 'showonlypublic':
$where['cal_public'] = 1; $where['cal_public'] = 1;
$where[] = "cal_status != 'R'"; break; $where[] = "$this->user_table.cal_status != 'R'"; break;
case 'deleted': case 'deleted':
$where['cal_deleted'] = true; break; $where['cal_deleted'] = true; break;
case 'unknown': case 'unknown':
$where[] = "cal_status='U'"; break; $where[] = "$this->user_table.cal_status='U'"; break;
case 'accepted': case 'accepted':
$where[] = "cal_status='A'"; break; $where[] = "$this->user_table.cal_status='A'"; break;
case 'tentative': case 'tentative':
$where[] = "cal_status='T'"; break; $where[] = "$this->user_table.cal_status='T'"; break;
case 'rejected': case 'rejected':
$where[] = "cal_status='R'"; break; $where[] = "$this->user_table.cal_status='R'"; break;
case 'delegated': case 'delegated':
$where[] = "cal_status='D'"; break; $where[] = "$this->user_table.cal_status='D'"; break;
case 'all': case 'all':
case 'owner': case 'owner':
break; break;
default: default:
$where[] = "cal_status != 'R'"; $where[] = "$this->user_table.cal_status != 'R'";
break; break;
} }
} }
@ -490,11 +493,44 @@ class calendar_so
{ {
$where[] = $this->cat_filter($cat_id); $where[] = $this->cat_filter($cat_id);
} }
if ($start) $where[] = (int)$start.' < cal_end'; if ($start)
{
if ($params['enum_recuring'])
{
$where[] = (int)$start.' < cal_end';
}
else
{
// we check recur_endate!=0, because it can be NULL, 0 or !=0 !!!
$where[] = (int)$start.' < (CASE WHEN recur_type IS NULL THEN cal_end ELSE (CASE WHEN recur_enddate!=0 THEN recur_enddate ELSE 9999999999 END) END)';
}
}
// if not enum recuring events, we have to use minimum start- AND end-dates, otherwise we get more then one event per cal_id!
if (!$params['enum_recuring'])
{
$where[] = "$this->user_table.cal_recur_date=0";
$group_by = 'GROUP BY '.str_replace(array('cal_start,','cal_end,'),'',implode(', ',(array)$cols));
$cols = str_replace(array('cal_start','cal_end'),array('MIN(cal_start) AS cal_start','MIN(cal_end) AS cal_end'),$cols);
}
if ($end) $where[] = 'cal_start < '.(int)$end; if ($end) $where[] = 'cal_start < '.(int)$end;
if (!preg_match('/^[a-z_ ,]+$/i',$params['order'])) $params['order'] = 'cal_start'; // gard against SQL injection if (!preg_match('/^[a-z_ ,c]+$/i',$params['order'])) $params['order'] = 'cal_start'; // gard against SQL injection
if ($remove_rejected_by_user)
{
$rejected_by_user_join = "LEFT JOIN $this->user_table rejected_by_user".
" ON $this->cal_table.cal_id=rejected_by_user.cal_id".
" AND rejected_by_user.cal_user_type='u'".
" AND rejected_by_user.cal_user_id=".$this->db->quote($remove_rejected_by_user).
" AND (recur_type IS NULL AND rejected_by_user.cal_recur_date=0 OR cal_start=rejected_by_user.cal_recur_date)";
$or_required = array(
'rejected_by_user.cal_status IS NULL',
"rejected_by_user.cal_status!='R'",
);
if ($filter == 'owner') $or_required[] = 'cal_owner='.(int)$remove_rejected_by_user;
$where[] = '('.implode(' OR ',$or_required).')';
}
//$starttime = microtime(true);
if ($useUnionQuery) if ($useUnionQuery)
{ {
// allow apps to supply participants and/or icons // allow apps to supply participants and/or icons
@ -503,89 +539,72 @@ class calendar_so
// changed the original OR in the query into a union, to speed up the query execution under MySQL 5 // changed the original OR in the query into a union, to speed up the query execution under MySQL 5
$select = array( $select = array(
'table' => $this->cal_table, 'table' => $this->cal_table,
'join' => "JOIN $this->dates_table ON $this->cal_table.cal_id=$this->dates_table.cal_id JOIN $this->user_table ON $this->cal_table.cal_id=$this->user_table.cal_id LEFT JOIN $this->repeats_table ON $this->cal_table.cal_id=$this->repeats_table.cal_id", 'join' => "JOIN $this->dates_table ON $this->cal_table.cal_id=$this->dates_table.cal_id JOIN $this->user_table ON $this->cal_table.cal_id=$this->user_table.cal_id LEFT JOIN $this->repeats_table ON $this->cal_table.cal_id=$this->repeats_table.cal_id $rejected_by_user_join",
'cols' => $cols, 'cols' => $cols,
'where' => $where, 'where' => $where,
'app' => 'calendar', 'app' => 'calendar',
'append'=> $params['append'], 'append'=> $params['append'].' '.$group_by,
); );
$selects = array();
// we check if there are parts to use for the construction of our UNION query, // we check if there are parts to use for the construction of our UNION query,
// as replace the OR by construction of a suitable UNION for performance reasons // as replace the OR by construction of a suitable UNION for performance reasons
if (!empty($owner_or)||!empty($user_or)) if ($owner_or || $user_or)
{ {
if (!empty($owner_or) && !empty($user_or)) foreach($user_or as $user_sql)
{ {
// if the query is to be filtered by owner OR user we need 4 selects for the union $selects[] = $select;
//_debug_array($owner_or); $selects[count($selects)-1]['where'][] = $user_sql;
$selects = array(); if ($params['enum_recuring'])
foreach(array_keys($user_or) as $key)
{ {
$selects[count($selects)-1]['where'][] = "recur_type IS NULL AND $this->user_table.cal_recur_date=0";
$selects[] = $select; $selects[] = $select;
$selects[count($selects)-1]['where'][] = $user_or[$key]; $selects[count($selects)-1]['where'][] = $user_sql;
$selects[count($selects)-1]['where'][] = 'recur_type IS NULL AND cal_recur_date=0'; $selects[count($selects)-1]['where'][] = "$this->user_table.cal_recur_date=cal_start";
$selects[] = $select;
$selects[count($selects)-1]['where'][] = $user_or[$key];
$selects[count($selects)-1]['where'][] = 'cal_recur_date=cal_start';
} }
$selects[] = $select;
$selects[count($selects)-1]['where'][] = $owner_or;
$selects[count($selects)-1]['where'][] = 'recur_type IS NULL AND cal_recur_date=0';
$selects[] = $select;
$selects[count($selects)-1]['where'][] = $owner_or;
$selects[count($selects)-1]['where'][] = 'cal_recur_date=cal_start';
} }
else // if the query is to be filtered by owner we need to add more selects for the union
if ($owner_or)
{ {
// if the query is to be filtered only by user we need 2 selects for the union $selects[] = $select;
$selects = array(); $selects[count($selects)-1]['where'][] = $owner_or;
foreach(array_keys($user_or) as $key) if ($params['enum_recuring'])
{ {
$selects[count($selects)-1]['where'][] = "recur_type IS NULL AND $this->user_table.cal_recur_date=0";
$selects[] = $select; $selects[] = $select;
$selects[count($selects)-1]['where'][] = $user_or[$key]; $selects[count($selects)-1]['where'][] = $owner_or;
$selects[count($selects)-1]['where'][] = 'recur_type IS NULL AND cal_recur_date=0'; $selects[count($selects)-1]['where'][] = "$this->user_table.cal_recur_date=cal_start";
$selects[] = $select;
$selects[count($selects)-1]['where'][] = $user_or[$key];
$selects[count($selects)-1]['where'][] = 'cal_recur_date=cal_start';
} }
} }
} }
else else
{ {
// if the query is to be filtered by neither by user nor owner (should not happen?) we need 2 selects for the union // if the query is to be filtered by neither by user nor owner (should not happen?) we need 2 selects for the union
$selects = array($select,$select); $selects[] = $select;
$selects[0]['where'][] = 'recur_type IS NULL AND cal_recur_date=0'; if ($params['enum_recuring'])
$selects[1]['where'][] = 'cal_recur_date=cal_start'; {
$selects[count($selects)-1]['where'][] = "recur_type IS NULL AND $this->user_table.cal_recur_date=0";
$selects[] = $select;
$selects[count($selects)-1]['where'][] = "$this->user_table.cal_recur_date=cal_start";
}
} }
if (is_numeric($offset)) // get the total too if (is_numeric($offset) && !$params['no_total']) // get the total too
{ {
$save_selects = $selects;
// we only select cal_table.cal_id (and not cal_table.*) to be able to use DISTINCT (eg. MsSQL does not allow it for text-columns) // we only select cal_table.cal_id (and not cal_table.*) to be able to use DISTINCT (eg. MsSQL does not allow it for text-columns)
$countSelects = count($selects);
foreach(array_keys($selects) as $key) foreach(array_keys($selects) as $key)
{ {
$selects[$key]['cols'] = "DISTINCT $this->repeats_table.recur_type,$this->repeats_table.recur_enddate,$this->repeats_table.recur_interval,$this->repeats_table.recur_data,$this->repeats_table.recur_exception,".$this->db->to_varchar($this->cal_table.'.cal_id').",cal_start,cal_end,cal_recur_date"; $selects[$key]['cols'] = "DISTINCT $this->repeats_table.recur_type,$this->repeats_table.recur_enddate,$this->repeats_table.recur_interval,$this->repeats_table.recur_data,$this->repeats_table.recur_exception,".$this->db->to_varchar($this->cal_table.'.cal_id').",cal_start,cal_end,$this->user_table.cal_recur_date";
//$selects[0]['cols'] = $selects[1]['cols'] = "DISTINCT $this->repeats_table.*,$this->cal_table.cal_id,cal_start,cal_end,cal_recur_date"; if (!$params['enum_recuring'])
{
$selects[$key]['cols'] = str_replace('cal_start','MIN(cal_start) AS cal_start',$selects[$key]['cols']);
}
} }
if (!isset($param['cols'])) self::get_union_selects($selects,$start,$end,$users,$cat_id,$filter,$params['query'],$params['users']); if (!isset($param['cols'])) self::get_union_selects($selects,$start,$end,$users,$cat_id,$filter,$params['query'],$params['users']);
$this->total = $this->db->union($selects,__LINE__,__FILE__)->NumRows(); $this->total = $this->db->union($selects,__LINE__,__FILE__)->NumRows();
$i = 0;
foreach(array_keys($selects) as $key)
{
if ($i >= $countSelects) continue;
$i++;
$selects[$key]['cols'] = $select['cols']; // restore the original cols
//$selects[0]['cols'] = $selects[1]['cols'] = $select['cols']; // restore the original cols
}
$i = 0;
$selections = array();
foreach(array_keys($selects) as $key)
{
if ($i >= $countSelects) continue;
$i++;
$selections[] = $selects[$key];
}
$selects = $selections; // restore original cols / selects
$selects = $save_selects; unset($save_selects);
} }
if (!isset($param['cols'])) self::get_union_selects($selects,$start,$end,$users,$cat_id,$filter,$params['query'],$params['users']); if (!isset($param['cols'])) self::get_union_selects($selects,$start,$end,$users,$cat_id,$filter,$params['query'],$params['users']);
@ -593,7 +612,7 @@ class calendar_so
} }
else // MsSQL oder MySQL 3.23 else // MsSQL oder MySQL 3.23
{ {
$where[] = '(recur_type IS NULL AND cal_recur_date=0 OR cal_recur_date=cal_start)'; $where[] = "(recur_type IS NULL AND $this->user_table.cal_recur_date=0 OR $this->user_table.cal_recur_date=cal_start)";
//_debug_array($where); //_debug_array($where);
if (is_numeric($offset)) // get the total too if (is_numeric($offset)) // get the total too
@ -601,12 +620,13 @@ class calendar_so
// we only select cal_table.cal_id (and not cal_table.*) to be able to use DISTINCT (eg. MsSQL does not allow it for text-columns) // we only select cal_table.cal_id (and not cal_table.*) to be able to use DISTINCT (eg. MsSQL does not allow it for text-columns)
$this->total = $this->db->select($this->cal_table,"DISTINCT $this->repeats_table.*,$this->cal_table.cal_id,cal_start,cal_end,cal_recur_date", $this->total = $this->db->select($this->cal_table,"DISTINCT $this->repeats_table.*,$this->cal_table.cal_id,cal_start,cal_end,cal_recur_date",
$where,__LINE__,__FILE__,false,'','calendar',0, $where,__LINE__,__FILE__,false,'','calendar',0,
"JOIN $this->dates_table ON $this->cal_table.cal_id=$this->dates_table.cal_id JOIN $this->user_table ON $this->cal_table.cal_id=$this->user_table.cal_id LEFT JOIN $this->repeats_table ON $this->cal_table.cal_id=$this->repeats_table.cal_id")->NumRows(); "JOIN $this->dates_table ON $this->cal_table.cal_id=$this->dates_table.cal_id JOIN $this->user_table ON $this->cal_table.cal_id=$this->user_table.cal_id $rejected_by_user_join LEFT JOIN $this->repeats_table ON $this->cal_table.cal_id=$this->repeats_table.cal_id")->NumRows();
} }
$rs = $this->db->select($this->cal_table,($this->db->capabilities['distinct_on_text'] ? 'DISTINCT ' : '').$cols, $rs = $this->db->select($this->cal_table,($this->db->capabilities['distinct_on_text'] ? 'DISTINCT ' : '').$cols,
$where,__LINE__,__FILE__,$offset,$params['append'].' ORDER BY '.$params['order'],'calendar',$num_rows, $where,__LINE__,__FILE__,$offset,$params['append'].' ORDER BY '.$params['order'],'calendar',$num_rows,
"JOIN $this->dates_table ON $this->cal_table.cal_id=$this->dates_table.cal_id JOIN $this->user_table ON $this->cal_table.cal_id=$this->user_table.cal_id LEFT JOIN $this->repeats_table ON $this->cal_table.cal_id=$this->repeats_table.cal_id"); "JOIN $this->dates_table ON $this->cal_table.cal_id=$this->dates_table.cal_id JOIN $this->user_table ON $this->cal_table.cal_id=$this->user_table.cal_id $rejected_by_user_join LEFT JOIN $this->repeats_table ON $this->cal_table.cal_id=$this->repeats_table.cal_id");
} }
//error_log(__METHOD__."() useUnionQuery=$useUnionQuery --> query took ".(microtime(true)-$starttime));
if (isset($params['cols'])) if (isset($params['cols']))
{ {
return $rs; // if colums are specified we return the recordset / iterator return $rs; // if colums are specified we return the recordset / iterator
@ -651,7 +671,7 @@ class calendar_so
foreach($this->db->select($utcal_id_view,'*',array( foreach($this->db->select($utcal_id_view,'*',array(
//'cal_id' => array_unique($ids), //'cal_id' => array_unique($ids),
'cal_recur_date' => $recur_dates, 'cal_recur_date' => $recur_dates,
),__LINE__,__FILE__,false,'ORDER BY cal_id,cal_user_type DESC,'.self::STATUS_SORT,'calendar',$num_rows=0,$join='', ),__LINE__,__FILE__,false,'ORDER BY cal_id,cal_user_type DESC,'.self::STATUS_SORT,'calendar',$num_rows,$join='',
$this->db->get_table_definitions('calendar',$this->user_table)) as $row) // DESC puts users before resources and contacts $this->db->get_table_definitions('calendar',$this->user_table)) as $row) // DESC puts users before resources and contacts
{ {
$id = $row['cal_id']; $id = $row['cal_id'];
@ -715,6 +735,7 @@ class calendar_so
} }
} }
//echo "<p>socal::search\n"; _debug_array($events); //echo "<p>socal::search\n"; _debug_array($events);
//error_log(__METHOD__."(,filter=".array2string($params['query']).",offset=$offset, num_rows=$num_rows) returning ".count($events)." entries".($offset!==false?" total=$this->total":'').' '.function_backtrace());
return $events; return $events;
} }

View File

@ -460,16 +460,15 @@ class groupdav_propfind_iterator implements Iterator
* *
* @param groupdav_handler $handler * @param groupdav_handler $handler
* @param array $filter filter for propfind call * @param array $filter filter for propfind call
* @param array $files=null extra files/responses to return too * @param array $files=array() extra files/responses to return too
*/ */
public function __construct(groupdav_handler $handler, $path, array $filter,array &$files=null) public function __construct(groupdav_handler $handler, $path, array $filter,array &$files=array())
{ {
if ($this->debug) error_log(__METHOD__."('$path', ".array2string($filter).",)"); if ($this->debug) error_log(__METHOD__."('$path', ".array2string($filter).",)");
$this->path = $path; $this->path = $path;
$this->handler = $handler; $this->handler = $handler;
$this->filter = $filter; $this->filter = $filter;
$this->files = $files; $this->files = $this->common_files = $files;
$this->common_files = $files;
reset($this->files); reset($this->files);
} }
@ -507,6 +506,12 @@ class groupdav_propfind_iterator implements Iterator
if ($this->debug) error_log(__METHOD__."() returning TRUE"); if ($this->debug) error_log(__METHOD__."() returning TRUE");
return true; return true;
} }
// check if previous query gave less then CHUNK_SIZE entries --> we're done
if ($this->start && count($this->files) < self::CHUNK_SIZE)
{
if ($this->debug) error_log(__METHOD__."() returning FALSE (no more entries)");
return false;
}
// try query further files via propfind callback of handler and store result in $this->files // try query further files via propfind callback of handler and store result in $this->files
$this->files = $this->handler->propfind_callback($this->path,$this->filter,array($this->start,self::CHUNK_SIZE)); $this->files = $this->handler->propfind_callback($this->path,$this->filter,array($this->start,self::CHUNK_SIZE));
if (!is_array($this->files) || !($entries = count($this->files))) if (!is_array($this->files) || !($entries = count($this->files)))
@ -514,10 +519,10 @@ class groupdav_propfind_iterator implements Iterator
if ($this->debug) error_log(__METHOD__."() returning FALSE (no more entries)"); if ($this->debug) error_log(__METHOD__."() returning FALSE (no more entries)");
return false; // no further entries return false; // no further entries
} }
$this->start += $entries; $this->start += self::CHUNK_SIZE;
reset($this->files); reset($this->files);
if ($this->debug) error_log(__METHOD__."() returning ".array2string(current($this->files) !== false)); if ($this->debug) error_log(__METHOD__."() this->start=$this->start, entries=$entries, count(this->files)=".count($this->files)." returning ".array2string(current($this->files) !== false));
return current($this->files) !== false; return current($this->files) !== false;
} }
@ -529,11 +534,8 @@ class groupdav_propfind_iterator implements Iterator
{ {
if ($this->debug) error_log(__METHOD__."()"); if ($this->debug) error_log(__METHOD__."()");
// query first set of files via propfind callback of handler and store result in $this->files
$this->start = 0; $this->start = 0;
$files = $this->handler->propfind_callback($this->path,$this->filter,array($this->start,self::CHUNK_SIZE)); $this->files = $this->common_files;
$this->files = array_merge($this->common_files, $files);
$this->start += self::CHUNK_SIZE;
reset($this->files); reset($this->files);
} }