#At file:///export/home/jl208045/mysql/mysql-trunk-11882131/ based on revid:mattias.jonsson@stripped
3351 Jorgen Loland 2011-04-01
BUG#11882131: OPTIMIZER CHOOSES FILESORT WHEN REVERSE
INDEX SCAN COULD BE USED
Consider the following case:
CREATE TABLE t1 (a INT,KEY (a));
INSERT INTO t1 VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10);
SELECT DISTINCT a,1 FROM t1 WHERE a <> 1 ORDER BY a DESC;
This query could have been resolved by GROUP range access
if it hadn't been for the descending ordering [1].
To access this table, covering index scan is first chosen.
Later an attempt to avoid sorting is made by calling
test_if_skip_sort_order(). Range analysis now decides that
GROUP range access is the most efficient access method, but
since this access method cannot produce records in
descending order, it is scrapped by
test_if_skip_sort_order() before concluding that filesort
is required after all.
In this case, test_if_skip_sort_order() fails to check if
the descending ordering can be resolved by scanning the
covering index in reverse order instead. Because of this,
the resulting execution plan is to 1) scan the index and 2)
sort the result instead of simply do 1) scan the index in
reverse order.
This patch adds an interesting_order parameter to
test_quick_select(). This parameter ensures that only range
access plans that can produce rows in requested order are
considered.
The gains from this change include:
1) Optimizer will not spend time to calculate whether or not
an unusable range access plan is cheap.
2) Before, if two range access plans P1 and P2 were considered,
and P1 could produce the requested ordering but P2 could not,
P2 would still be returned from test_quick_select() if it was
cheaper than P1. test_if_skip_sort_order() would then discard
the range access plan as not usable. With this patch, P2 will
not be considered, so test_quick_select() will instead return
the best *usable* plan P1.
3) Due to #2, the aforementioned deficiency in
test_if_skip_sort_order() is no longer an issue: If
test_quick_select() returns a range access plan, that plan
will be able to resolve the requested ordering.
@ mysql-test/r/order_by_icp_mrr.result
BUG#11882131: Changed test output
@ mysql-test/r/order_by_none.result
BUG#11882131: Changed test output
@ sql/item_sum.cc
Struct ORDER variable "bool asc" replaced with "enum_order order"
@ sql/opt_range.cc
Add parameter "interesting_order" to test_quick_select() and make the method only consider range access plans that are able to produce rows in requested order. Also add variable PARAM::order, defining whether the range access plan needs to produce ASC/DESC ordering (or no order)
@ sql/opt_range.h
Add method QUICK_SELECT_I::reverse_sort_possible().
@ sql/sql_lex.cc
Struct ORDER variable "bool asc" replaced with "enum_order order"
@ sql/sql_parse.cc
Struct ORDER variable "bool asc" replaced with "enum_order order"
@ sql/sql_select.cc
When calling test_quick_select(): define whether ascending/descending ordering will be required.
@ sql/sql_update.cc
Struct ORDER variable "bool asc" replaced with "enum_order order"
@ sql/table.h
Struct ORDER variable "bool asc" replaced with "enum_order order"
modified:
mysql-test/r/order_by_icp_mrr.result
mysql-test/r/order_by_none.result
sql/item_sum.cc
sql/opt_range.cc
sql/opt_range.h
sql/sql_lex.cc
sql/sql_parse.cc
sql/sql_select.cc
sql/sql_update.cc
sql/table.h
=== modified file 'mysql-test/r/order_by_icp_mrr.result'
--- a/mysql-test/r/order_by_icp_mrr.result 2011-02-07 09:46:53 +0000
+++ b/mysql-test/r/order_by_icp_mrr.result 2011-04-01 14:04:52 +0000
@@ -2536,7 +2536,7 @@ CREATE TABLE t1 (a INT,KEY (a));
INSERT INTO t1 VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10);
EXPLAIN SELECT DISTINCT a,1 FROM t1 WHERE a <> 1 ORDER BY a DESC;
id select_type table type possible_keys key key_len ref rows Extra
-1 SIMPLE t1 index a a 5 NULL 10 Using where; Using index; Using filesort
+1 SIMPLE t1 index a a 5 NULL 10 Using where; Using index
SELECT DISTINCT a,1 FROM t1 WHERE a <> 1 ORDER BY a DESC;
a 1
10 1
=== modified file 'mysql-test/r/order_by_none.result'
--- a/mysql-test/r/order_by_none.result 2011-02-07 09:46:53 +0000
+++ b/mysql-test/r/order_by_none.result 2011-04-01 14:04:52 +0000
@@ -2535,7 +2535,7 @@ CREATE TABLE t1 (a INT,KEY (a));
INSERT INTO t1 VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10);
EXPLAIN SELECT DISTINCT a,1 FROM t1 WHERE a <> 1 ORDER BY a DESC;
id select_type table type possible_keys key key_len ref rows Extra
-1 SIMPLE t1 index a a 5 NULL 10 Using where; Using index; Using filesort
+1 SIMPLE t1 index a a 5 NULL 10 Using where; Using index
SELECT DISTINCT a,1 FROM t1 WHERE a <> 1 ORDER BY a DESC;
a 1
10 1
=== modified file 'sql/item_sum.cc'
--- a/sql/item_sum.cc 2011-03-22 11:44:40 +0000
+++ b/sql/item_sum.cc 2011-04-01 14:04:52 +0000
@@ -2891,7 +2891,8 @@ int group_concat_key_cmp_with_order(cons
uint offset= (field->offset(field->table->record[0]) -
table->s->null_bytes);
if ((res= field->cmp((uchar*)key1 + offset, (uchar*)key2 + offset)))
- return (*order_item)->asc ? res : -res;
+ return ((*order_item)->direction == ORDER::ORDER_ASC) ? res : -res;
+
}
}
/*
@@ -3432,7 +3433,7 @@ void Item_func_group_concat::print(Strin
if (i)
str->append(',');
orig_args[i + arg_count_field]->print(str, query_type);
- if (order[i]->asc)
+ if (order[i]->direction == ORDER::ORDER_ASC)
str->append(STRING_WITH_LEN(" ASC"));
else
str->append(STRING_WITH_LEN(" DESC"));
=== modified file 'sql/opt_range.cc'
--- a/sql/opt_range.cc 2011-03-22 11:44:40 +0000
+++ b/sql/opt_range.cc 2011-04-01 14:04:52 +0000
@@ -742,6 +742,12 @@ public:
bool is_ror_scan;
/* Number of ranges in the last checked tree->key */
uint n_ranges;
+
+ /*
+ The sort order the range access method must be able
+ to provide. Three-value logic: asc/desc/don't care
+ */
+ ORDER::enum_order order_direction;
};
class TABLE_READ_PLAN;
@@ -2142,6 +2148,8 @@ static int fill_used_fields_bitmap(PARAM
limit Query limit
force_quick_range Prefer to use range (instead of full table scan) even
if it is more expensive.
+ interesting_order The sort order the range access method must be able
+ to provide. Three-value logic: asc/desc/don't care
NOTES
Updates the following in the select parameter:
@@ -2197,9 +2205,9 @@ static int fill_used_fields_bitmap(PARAM
*/
int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use,
- table_map prev_tables,
- ha_rows limit, bool force_quick_range,
- bool ordered_output)
+ table_map prev_tables,
+ ha_rows limit, bool force_quick_range,
+ const ORDER::enum_order interesting_order)
{
uint idx;
double scan_time;
@@ -2260,7 +2268,8 @@ int SQL_SELECT::test_quick_select(THD *t
param.imerge_cost_buff_size= 0;
param.using_real_indexes= TRUE;
param.remove_jump_scans= TRUE;
- param.force_default_mrr= ordered_output;
+ param.force_default_mrr= (interesting_order != ORDER::ORDER_NOT_RELEVANT);
+ param.order_direction= interesting_order;
thd->no_errors=1; // Don't warn about NULL
init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0);
@@ -2386,10 +2395,12 @@ int SQL_SELECT::test_quick_select(THD *t
/*
Simultaneous key scans and row deletes on several handler
objects are not allowed so don't use ROR-intersection for
- table deletes.
+ table deletes. Also, ROR-intersection cannot return rows in
+ descending order
*/
if ((thd->lex->sql_command != SQLCOM_DELETE) &&
- thd->optimizer_switch_flag(OPTIMIZER_SWITCH_INDEX_MERGE))
+ thd->optimizer_switch_flag(OPTIMIZER_SWITCH_INDEX_MERGE) &&
+ interesting_order != ORDER::ORDER_DESC)
{
/*
Get best non-covering ROR-intersection plan and prepare data for
@@ -2413,7 +2424,9 @@ int SQL_SELECT::test_quick_select(THD *t
}
else
{
- if (thd->optimizer_switch_flag(OPTIMIZER_SWITCH_INDEX_MERGE))
+ // Cannot return rows in descending order.
+ if (thd->optimizer_switch_flag(OPTIMIZER_SWITCH_INDEX_MERGE) &&
+ interesting_order != ORDER::ORDER_DESC)
{
/* Try creating index_merge/ROR-union scan. */
SEL_IMERGE *imerge;
@@ -4576,6 +4589,9 @@ TRP_ROR_INTERSECT *get_best_ror_intersec
!param->thd->optimizer_switch_flag(OPTIMIZER_SWITCH_INDEX_MERGE_INTERSECT))
DBUG_RETURN(NULL);
+ if (param->order_direction == ORDER::ORDER_DESC)
+ DBUG_RETURN(NULL);
+
/*
Step1: Collect ROR-able SEL_ARGs and create ROR_SCAN_INFO for each of
them. Also find and save clustered PK scan if there is one.
@@ -9653,6 +9669,9 @@ get_best_group_min_max(PARAM *param, SEL
DBUG_RETURN(NULL);
if (table->s->keys == 0) /* There are no indexes to use. */
DBUG_RETURN(NULL);
+ /* Cannot do reverse ordering */
+ if (param->order_direction == ORDER::ORDER_DESC)
+ DBUG_RETURN(NULL);
/* Check (SA1,SA4) and store the only MIN/MAX argument - the C attribute.*/
if (join->make_sum_func_list(join->all_fields, join->fields_list, 1))
=== modified file 'sql/opt_range.h'
--- a/sql/opt_range.h 2011-03-22 11:44:40 +0000
+++ b/sql/opt_range.h 2011-04-01 14:04:52 +0000
@@ -276,7 +276,15 @@ public:
/* Range end should be called when we have looped over the whole index */
virtual void range_end() {}
- virtual bool reverse_sorted() = 0;
+ /**
+ Whether the range access method returns records in reverse order.
+ */
+ virtual bool reverse_sorted() const = 0;
+ /**
+ Whether the range access method is capable of returning records
+ in reverse order.
+ */
+ virtual bool reverse_sort_possible() const = 0;
virtual bool unique_key_range() { return false; }
virtual bool clustered_pk_range() { return false; }
@@ -472,7 +480,8 @@ public:
void range_end();
int get_next_prefix(uint prefix_length, uint group_key_parts,
uchar *cur_prefix);
- bool reverse_sorted() { return 0; }
+ bool reverse_sorted() const { return false; }
+ bool reverse_sort_possible() const { return true; }
bool unique_key_range();
int init_ror_merged_scan(bool reuse_handler);
void save_last_pos()
@@ -572,7 +581,8 @@ public:
void need_sorted_output(bool sort) { DBUG_ASSERT(!sort); /* Can't do it */ }
int reset(void);
int get_next();
- bool reverse_sorted() { return false; }
+ bool reverse_sorted() const { return false; }
+ bool reverse_sort_possible() const { return false; }
bool unique_key_range() { return false; }
int get_type() { return QS_TYPE_INDEX_MERGE; }
void add_keys_and_lengths(String *key_names, String *used_lengths);
@@ -650,7 +660,8 @@ public:
void need_sorted_output(bool sort) { DBUG_ASSERT(!sort); /* Can't do it */ }
int reset(void);
int get_next();
- bool reverse_sorted() { return false; }
+ bool reverse_sorted() const { return false; }
+ bool reverse_sort_possible() const { return false; }
bool unique_key_range() { return false; }
int get_type() { return QS_TYPE_ROR_INTERSECT; }
void add_keys_and_lengths(String *key_names, String *used_lengths);
@@ -721,7 +732,8 @@ public:
void need_sorted_output(bool sort) { DBUG_ASSERT(!sort); /* Can't do it */ }
int reset(void);
int get_next();
- bool reverse_sorted() { return false; }
+ bool reverse_sorted() const { return false; }
+ bool reverse_sort_possible() const { return false; }
bool unique_key_range() { return false; }
int get_type() { return QS_TYPE_ROR_UNION; }
void add_keys_and_lengths(String *key_names, String *used_lengths);
@@ -863,7 +875,8 @@ public:
void need_sorted_output(bool sort) { /* always do it */ }
int reset();
int get_next();
- bool reverse_sorted() { return false; }
+ bool reverse_sorted() const { return false; }
+ bool reverse_sort_possible() const { return false; }
bool unique_key_range() { return false; }
int get_type() { return QS_TYPE_GROUP_MIN_MAX; }
void add_keys_and_lengths(String *key_names, String *used_lengths);
@@ -885,7 +898,8 @@ public:
QUICK_SELECT_DESC(QUICK_RANGE_SELECT *q, uint used_key_parts,
bool *create_err);
int get_next();
- bool reverse_sorted() { return 1; }
+ bool reverse_sorted() const { return true; }
+ bool reverse_sort_possible() const { return true; }
int get_type() { return QS_TYPE_RANGE_DESC; }
QUICK_SELECT_I *make_reverse(uint used_key_parts_arg)
{
@@ -921,7 +935,8 @@ class SQL_SELECT :public Sql_alloc {
bool check_quick(THD *thd, bool force_quick_range, ha_rows limit)
{
key_map tmp(key_map::ALL_BITS);
- return test_quick_select(thd, tmp, 0, limit, force_quick_range, FALSE) < 0;
+ return test_quick_select(thd, tmp, 0, limit, force_quick_range,
+ ORDER::ORDER_NOT_RELEVANT) < 0;
}
inline bool skip_record(THD *thd, bool *skip_record)
{
@@ -929,8 +944,8 @@ class SQL_SELECT :public Sql_alloc {
return thd->is_error();
}
int test_quick_select(THD *thd, key_map keys, table_map prev_tables,
- ha_rows limit, bool force_quick_range,
- bool ordered_output);
+ ha_rows limit, bool force_quick_range,
+ const ORDER::enum_order interesting_order);
};
=== modified file 'sql/sql_lex.cc'
--- a/sql/sql_lex.cc 2011-03-11 09:35:38 +0000
+++ b/sql/sql_lex.cc 2011-04-01 14:04:52 +0000
@@ -2196,7 +2196,7 @@ void st_select_lex::print_order(String *
}
else
(*order->item)->print(str, query_type);
- if (!order->asc)
+ if (order->direction == ORDER::ORDER_DESC)
str->append(STRING_WITH_LEN(" desc"));
if (order->next)
str->append(',');
=== modified file 'sql/sql_parse.cc'
--- a/sql/sql_parse.cc 2011-03-17 17:39:31 +0000
+++ b/sql/sql_parse.cc 2011-04-01 14:04:52 +0000
@@ -5796,7 +5796,7 @@ bool add_to_list(THD *thd, SQL_I_List<OR
DBUG_RETURN(1);
order->item_ptr= item;
order->item= &order->item_ptr;
- order->asc = asc;
+ order->direction= (asc ? ORDER::ORDER_ASC : ORDER::ORDER_DESC);
order->free_me=0;
order->used=0;
order->counter_used= 0;
=== modified file 'sql/sql_select.cc'
--- a/sql/sql_select.cc 2011-03-29 07:20:17 +0000
+++ b/sql/sql_select.cc 2011-04-01 14:04:52 +0000
@@ -4516,7 +4516,6 @@ static ha_rows get_quick_record_count(TH
TABLE *table,
const key_map *keys,ha_rows limit)
{
- int error;
DBUG_ENTER("get_quick_record_count");
uchar buff[STACK_BUFF_ALLOC];
if (check_stack_overrun(thd, STACK_MIN_SIZE, buff))
@@ -4524,8 +4523,13 @@ static ha_rows get_quick_record_count(TH
if (select)
{
select->head=table;
- if ((error= select->test_quick_select(thd, *(key_map *)keys,(table_map) 0,
- limit, 0, FALSE)) == 1)
+ int error= select->test_quick_select(thd,
+ *keys,
+ 0, //empty table_map
+ limit,
+ false, //don't force quick range
+ ORDER::ORDER_NOT_RELEVANT);
+ if (error == 1)
DBUG_RETURN(select->quick->records);
if (error == -1)
{
@@ -9895,13 +9899,14 @@ static bool make_join_select(JOIN *join,
if (sel->cond && !sel->cond->fixed)
sel->cond->quick_fix_field();
- if (sel->test_quick_select(thd, tab->keys,
- used_tables & ~ current_map,
- (join->select_options &
- OPTION_FOUND_ROWS ?
- HA_POS_ERROR :
- join->unit->select_limit_cnt), 0,
- FALSE) < 0)
+ if (sel->test_quick_select(thd, tab->keys,
+ used_tables & ~ current_map,
+ (join->select_options &
+ OPTION_FOUND_ROWS ?
+ HA_POS_ERROR :
+ join->unit->select_limit_cnt),
+ false, // don't force quick range
+ ORDER::ORDER_NOT_RELEVANT) < 0)
{
/*
Before reporting "Impossible WHERE" for the whole query
@@ -9914,8 +9919,9 @@ static bool make_join_select(JOIN *join,
(join->select_options &
OPTION_FOUND_ROWS ?
HA_POS_ERROR :
- join->unit->select_limit_cnt),0,
- FALSE) < 0)
+ join->unit->select_limit_cnt),
+ false, //don't force quick range
+ ORDER::ORDER_NOT_RELEVANT) < 0)
DBUG_RETURN(1); // Impossible WHERE
}
else
@@ -18424,9 +18430,12 @@ test_if_quick_select(JOIN_TAB *tab)
{
delete tab->select->quick;
tab->select->quick=0;
- return tab->select->test_quick_select(tab->join->thd, tab->keys,
- (table_map) 0, HA_POS_ERROR, 0,
- FALSE);
+ return tab->select->test_quick_select(tab->join->thd,
+ tab->keys,
+ 0, // empty table map
+ HA_POS_ERROR,
+ false, // don't force quick range
+ ORDER::ORDER_NOT_RELEVANT);
}
@@ -19651,9 +19660,11 @@ static int test_if_order_by_key(ORDER *o
if (key_part->field != field || !field->part_of_sortkey.is_set(idx))
DBUG_RETURN(0);
+ const ORDER::enum_order keypart_order=
+ (key_part->key_part_flag & HA_REVERSE_SORT) ?
+ ORDER::ORDER_DESC : ORDER::ORDER_ASC;
/* set flag to 1 if we can use read-next on key, else to -1 */
- flag= ((order->asc == !(key_part->key_part_flag & HA_REVERSE_SORT)) ?
- 1 : -1);
+ flag= (order->direction == keypart_order) ? 1 : -1;
if (reverse && flag != reverse)
DBUG_RETURN(0);
reverse=flag; // Remember if reverse
@@ -20110,13 +20121,15 @@ test_if_skip_sort_order(JOIN_TAB *tab,OR
new_ref_key_map.set_bit(new_ref_key); // only for new_ref_key.
select->quick= 0;
- if (select->test_quick_select(tab->join->thd, new_ref_key_map, 0,
+ if (select->test_quick_select(tab->join->thd,
+ new_ref_key_map,
+ 0, // empty table_map
(tab->join->select_options &
OPTION_FOUND_ROWS) ?
HA_POS_ERROR :
- tab->join->unit->select_limit_cnt,0,
- TRUE) <=
- 0)
+ tab->join->unit->select_limit_cnt,
+ false, // don't force quick range
+ order->direction) <= 0)
goto use_filesort;
}
ref_key= new_ref_key;
@@ -20161,11 +20174,14 @@ test_if_skip_sort_order(JOIN_TAB *tab,OR
key_map map; // Force the creation of quick select
map.set_bit(best_key); // only best_key.
select->quick= 0;
- select->test_quick_select(join->thd, map, 0,
+ select->test_quick_select(join->thd,
+ map,
+ 0, // empty table_map
join->select_options & OPTION_FOUND_ROWS ?
HA_POS_ERROR :
join->unit->select_limit_cnt,
- TRUE, FALSE);
+ true, // force quick range
+ order->direction);
}
order_direction= best_key_direction;
/*
@@ -20195,18 +20211,13 @@ check_reverse_order:
*/
if (select->quick->reverse_sorted())
goto skipped_filesort;
- else
- {
- int quick_type= select->quick->get_type();
- if (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE ||
- quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT ||
- quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION ||
- quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
- {
- tab->limit= 0;
- goto use_filesort; // Use filesort
- }
- }
+
+ /*
+ test_quick_select() should not create a quick that cannot do
+ reverse ordering
+ */
+ DBUG_ASSERT((select->quick == save_quick) ||
+ select->quick->reverse_sort_possible());
}
}
@@ -20316,7 +20327,7 @@ check_reverse_order:
/*
Cleanup:
We may have both a 'select->quick' and 'save_quick' (original)
- at this point. Delete the one that we wan't use.
+ at this point. Delete the one that we won't use.
*/
skipped_filesort:
@@ -20823,7 +20834,7 @@ SORT_FIELD *make_unireg_sortorder(ORDER
}
else
pos->item= *order->item;
- pos->reverse=! order->asc;
+ pos->reverse= (order->direction == ORDER::ORDER_DESC);
DBUG_ASSERT(pos->field != NULL || pos->item != NULL);
}
*length=count;
@@ -21336,7 +21347,7 @@ create_distinct_group(THD *thd, Item **r
*/
ord->item= ref_pointer_array;
}
- ord->asc=1;
+ ord->direction= ORDER::ORDER_ASC;
*prev=ord;
prev= &ord->next;
}
@@ -21413,7 +21424,7 @@ test_if_subpart(ORDER *a,ORDER *b)
for (; a && b; a=a->next,b=b->next)
{
if ((*a->item)->eq(*b->item,1))
- a->asc=b->asc;
+ a->direction= b->direction;
else
return 0;
}
=== modified file 'sql/sql_update.cc'
--- a/sql/sql_update.cc 2011-02-25 16:41:57 +0000
+++ b/sql/sql_update.cc 2011-04-01 14:04:52 +0000
@@ -1732,7 +1732,7 @@ loop_end:
/* Make an unique key over the first field to avoid duplicated updates */
bzero((char*) &group, sizeof(group));
- group.asc= 1;
+ group.direction= ORDER::ORDER_ASC;
group.item= (Item**) temp_fields.head_ref();
tmp_param->quick_group=1;
=== modified file 'sql/table.h'
--- a/sql/table.h 2011-03-30 11:43:32 +0000
+++ b/sql/table.h 2011-04-01 14:04:52 +0000
@@ -190,16 +190,22 @@ private:
typedef struct st_order {
struct st_order *next;
- Item **item; /* Point at item in select fields */
- Item *item_ptr; /* Storage for initial item */
+ Item **item; /* Point at item in select fields */
+ Item *item_ptr; /* Storage for initial item */
int counter; /* position in SELECT list, correct
- only if counter_used is true*/
- bool asc; /* true if ascending */
- bool free_me; /* true if item isn't shared */
- bool in_field_list; /* true if in select field list */
+ only if counter_used is true */
+ enum enum_order {
+ ORDER_NOT_RELEVANT,
+ ORDER_ASC,
+ ORDER_DESC
+ };
+
+ enum_order direction; /* Requested direction of ordering */
+ bool free_me; /* true if item isn't shared */
+ bool in_field_list; /* true if in select field list */
bool counter_used; /* parameter was counter of columns */
- Field *field; /* If tmp-table group */
- char *buff; /* If tmp-table group */
+ Field *field; /* If tmp-table group */
+ char *buff; /* If tmp-table group */
table_map used, depend_map;
} ORDER;
Attachment: [text/bzr-bundle] bzr/jorgen.loland@oracle.com-20110401140452-dc3lfm7m6ii3auna.bundle
| Thread |
|---|
| • bzr commit into mysql-trunk branch (jorgen.loland:3351) Bug#11882131 | Jorgen Loland | 1 Apr |