Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
M
mariadb
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
mariadb
Commits
7655f05d
Commit
7655f05d
authored
Jun 26, 2003
by
monty@mashka.mysql.fi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
LEFT JOIN optimization: Change LEFT JOIN to normal join if possible
parent
039554f3
Changes
9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
147 additions
and
33 deletions
+147
-33
mysql-test/r/select.result
mysql-test/r/select.result
+32
-2
mysql-test/t/select.test
mysql-test/t/select.test
+14
-0
sql/item.h
sql/item.h
+17
-0
sql/item_cmpfunc.cc
sql/item_cmpfunc.cc
+31
-10
sql/item_cmpfunc.h
sql/item_cmpfunc.h
+13
-5
sql/item_func.cc
sql/item_func.cc
+11
-3
sql/item_func.h
sql/item_func.h
+7
-2
sql/item_strfunc.cc
sql/item_strfunc.cc
+10
-7
sql/sql_base.cc
sql/sql_base.cc
+12
-4
No files found.
mysql-test/r/select.result
View file @
7655f05d
...
...
@@ -2569,16 +2569,46 @@ fld1 fld1
250503 250505
250504 250505
250505 250505
insert into t2 (fld1, companynr) values (999999,99);
select t2.companynr,companyname from t2 left join t4 using (companynr) where t4.companynr is null;
companynr companyname
99 NULL
select count(*) from t2 left join t4 using (companynr) where t4.companynr is not null;
count(*)
1199
explain select t2.companynr,companyname from t2 left join t4 using (companynr) where t4.companynr is null;
table type possible_keys key key_len ref rows Extra
t2 ALL NULL NULL NULL NULL 1
199
t2 ALL NULL NULL NULL NULL 1
200
t4 eq_ref PRIMARY PRIMARY 1 test.t2.companynr 1 Using where; Not exists
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where t2.companynr is null;
table type possible_keys key key_len ref rows Extra
t4 ALL NULL NULL NULL NULL 12
t2 ALL NULL NULL NULL NULL 1199 Using where; Not exists
t2 ALL NULL NULL NULL NULL 1200 Using where; Not exists
delete from t2 where fld1=999999;
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where t2.companynr > 0;
table type possible_keys key key_len ref rows Extra
t2 ALL NULL NULL NULL NULL 1199 Using where
t4 eq_ref PRIMARY PRIMARY 1 test.t2.companynr 1
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where t2.companynr > 0 or t2.companynr < 0;
table type possible_keys key key_len ref rows Extra
t2 ALL NULL NULL NULL NULL 1199 Using where
t4 eq_ref PRIMARY PRIMARY 1 test.t2.companynr 1
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where t2.companynr > 0 and t4.companynr > 0;
table type possible_keys key key_len ref rows Extra
t2 ALL NULL NULL NULL NULL 1199 Using where
t4 eq_ref PRIMARY PRIMARY 1 test.t2.companynr 1 Using where
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where t2.companynr > 0 or t2.companynr is null;
table type possible_keys key key_len ref rows Extra
t4 ALL NULL NULL NULL NULL 12
t2 ALL NULL NULL NULL NULL 1199 Using where
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where t2.companynr > 0 or t2.companynr < 0 or t4.companynr > 0;
table type possible_keys key key_len ref rows Extra
t4 ALL PRIMARY NULL NULL NULL 12
t2 ALL NULL NULL NULL NULL 1199 Using where
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where ifnull(t2.companynr,1)>0;
table type possible_keys key key_len ref rows Extra
t4 ALL NULL NULL NULL NULL 12
t2 ALL NULL NULL NULL NULL 1199 Using where
select distinct t2.companynr,t4.companynr from t2,t4 where t2.companynr=t4.companynr+1;
companynr companynr
37 36
...
...
mysql-test/t/select.test
View file @
7655f05d
...
...
@@ -1527,10 +1527,24 @@ select t2.fld1,t22.fld1 from t2,t2 t22 where t2.fld1 >= 250501 and t2.fld1 <= 25
#
# Test of left join.
#
insert
into
t2
(
fld1
,
companynr
)
values
(
999999
,
99
);
select
t2
.
companynr
,
companyname
from
t2
left
join
t4
using
(
companynr
)
where
t4
.
companynr
is
null
;
select
count
(
*
)
from
t2
left
join
t4
using
(
companynr
)
where
t4
.
companynr
is
not
null
;
explain
select
t2
.
companynr
,
companyname
from
t2
left
join
t4
using
(
companynr
)
where
t4
.
companynr
is
null
;
explain
select
t2
.
companynr
,
companyname
from
t4
left
join
t2
using
(
companynr
)
where
t2
.
companynr
is
null
;
delete
from
t2
where
fld1
=
999999
;
#
# Test left join optimization
explain
select
t2
.
companynr
,
companyname
from
t4
left
join
t2
using
(
companynr
)
where
t2
.
companynr
>
0
;
explain
select
t2
.
companynr
,
companyname
from
t4
left
join
t2
using
(
companynr
)
where
t2
.
companynr
>
0
or
t2
.
companynr
<
0
;
explain
select
t2
.
companynr
,
companyname
from
t4
left
join
t2
using
(
companynr
)
where
t2
.
companynr
>
0
and
t4
.
companynr
>
0
;
# Following can't be optimized
explain
select
t2
.
companynr
,
companyname
from
t4
left
join
t2
using
(
companynr
)
where
t2
.
companynr
>
0
or
t2
.
companynr
is
null
;
explain
select
t2
.
companynr
,
companyname
from
t4
left
join
t2
using
(
companynr
)
where
t2
.
companynr
>
0
or
t2
.
companynr
<
0
or
t4
.
companynr
>
0
;
explain
select
t2
.
companynr
,
companyname
from
t4
left
join
t2
using
(
companynr
)
where
ifnull
(
t2
.
companynr
,
1
)
>
0
;
#
# Joins with forms.
...
...
sql/item.h
View file @
7655f05d
...
...
@@ -71,7 +71,24 @@ public:
virtual
double
val_result
()
{
return
val
();
}
virtual
longlong
val_int_result
()
{
return
val_int
();
}
virtual
String
*
str_result
(
String
*
tmp
)
{
return
val_str
(
tmp
);
}
/* bit map of tables used by item */
virtual
table_map
used_tables
()
const
{
return
(
table_map
)
0L
;
}
/*
Return table map of tables that can't be NULL tables (tables that are
used in a context where if they would contain a NULL row generated
by a LEFT or RIGHT join, the item would not be true).
This expression is used on WHERE item to determinate if a LEFT JOIN can be
converted to a normal join.
Generally this function should return used_tables() if the function
would return null if any of the arguments are null
As this is only used in the beginning of optimization, the value don't
have to be updated in update_used_tables()
*/
virtual
table_map
not_null_tables
()
const
{
return
used_tables
();
}
/*
Returns true if this is a simple constant item like an integer, not
a constant expression
*/
virtual
bool
basic_const_item
()
const
{
return
0
;
}
virtual
Item
*
new_item
()
{
return
0
;
}
/* Only for const items */
virtual
cond_result
eq_cmp_result
()
const
{
return
COND_OK
;
}
...
...
sql/item_cmpfunc.cc
View file @
7655f05d
...
...
@@ -292,10 +292,12 @@ void Item_func_interval::fix_length_and_dec()
}
}
maybe_null
=
0
;
max_length
=
2
;
used_tables_cache
|=
item
->
used_tables
();
used_tables_cache
|=
item
->
used_tables
();
not_null_tables_cache
&=
item
->
not_null_tables
();
with_sum_func
=
with_sum_func
||
item
->
with_sum_func
;
}
void
Item_func_interval
::
split_sum_func
(
List
<
Item
>
&
fields
)
{
if
(
item
->
with_sum_func
&&
item
->
type
()
!=
SUM_FUNC_ITEM
)
...
...
@@ -1073,8 +1075,9 @@ void Item_func_in::fix_length_and_dec()
}
maybe_null
=
item
->
maybe_null
;
max_length
=
2
;
used_tables_cache
|=
item
->
used_tables
();
const_item_cache
&=
item
->
const_item
();
used_tables_cache
|=
item
->
used_tables
();
not_null_tables_cache
&=
item
->
not_null_tables
();
const_item_cache
&=
item
->
const_item
();
}
...
...
@@ -1174,14 +1177,21 @@ Item_cond::fix_fields(THD *thd,TABLE_LIST *tables)
char
buff
[
sizeof
(
char
*
)];
// Max local vars in function
used_tables_cache
=
0
;
const_item_cache
=
0
;
/*
and_table_cache is the value that Item_cond_or() returns for
not_null_tables()
*/
and_tables_cache
=
~
(
table_map
)
0
;
if
(
thd
&&
check_stack_overrun
(
thd
,
buff
))
return
0
;
// Fatal error flag is set!
while
((
item
=
li
++
))
{
table_map
tmp_table_map
;
while
(
item
->
type
()
==
Item
::
COND_ITEM
&&
((
Item_cond
*
)
item
)
->
functype
()
==
functype
())
{
// Identical function
li
.
replace
(((
Item_cond
*
)
item
)
->
list
);
((
Item_cond
*
)
item
)
->
list
.
empty
();
#ifdef DELETE_ITEMS
...
...
@@ -1193,9 +1203,12 @@ Item_cond::fix_fields(THD *thd,TABLE_LIST *tables)
item
->
top_level_item
();
if
(
item
->
fix_fields
(
thd
,
tables
))
return
1
;
/* purecov: inspected */
used_tables_cache
|=
item
->
used_tables
();
used_tables_cache
|=
item
->
used_tables
();
tmp_table_map
=
item
->
not_null_tables
();
not_null_tables_cache
|=
tmp_table_map
;
and_tables_cache
&=
tmp_table_map
;
const_item_cache
&=
item
->
const_item
();
with_sum_func
=
with_sum_func
||
item
->
with_sum_func
;
const_item_cache
&=
item
->
const_item
();
if
(
item
->
maybe_null
)
maybe_null
=
1
;
}
...
...
@@ -1234,16 +1247,18 @@ Item_cond::used_tables() const
return
used_tables_cache
;
}
void
Item_cond
::
update_used_tables
()
{
used_tables_cache
=
0
;
const_item_cache
=
1
;
List_iterator_fast
<
Item
>
li
(
list
);
Item
*
item
;
used_tables_cache
=
0
;
const_item_cache
=
1
;
while
((
item
=
li
++
))
{
item
->
update_used_tables
();
used_tables_cache
|=
item
->
used_tables
();
used_tables_cache
|=
item
->
used_tables
();
const_item_cache
&=
item
->
const_item
();
}
}
...
...
@@ -1348,12 +1363,16 @@ Item *and_expressions(Item *a, Item *b, Item **org_item)
{
Item_cond
*
res
;
if
((
res
=
new
Item_cond_and
(
a
,
(
Item
*
)
b
)))
{
res
->
used_tables_cache
=
a
->
used_tables
()
|
b
->
used_tables
();
res
->
not_null_tables_cache
=
a
->
not_null_tables
()
|
b
->
not_null_tables
();
}
return
res
;
}
if
(((
Item_cond_and
*
)
a
)
->
add
((
Item
*
)
b
))
return
0
;
((
Item_cond_and
*
)
a
)
->
used_tables_cache
|=
b
->
used_tables
();
((
Item_cond_and
*
)
a
)
->
not_null_tables_cache
|=
b
->
not_null_tables
();
return
a
;
}
...
...
@@ -1489,6 +1508,8 @@ Item_func_regex::fix_fields(THD *thd,TABLE_LIST *tables)
max_length
=
1
;
decimals
=
0
;
binary
=
args
[
0
]
->
binary
||
args
[
1
]
->
binary
;
used_tables_cache
=
args
[
0
]
->
used_tables
()
|
args
[
1
]
->
used_tables
();
not_null_tables_cache
=
(
args
[
0
]
->
not_null_tables
()
|
args
[
1
]
->
not_null_tables
());
const_item_cache
=
args
[
0
]
->
const_item
()
&&
args
[
1
]
->
const_item
();
if
(
!
regex_compiled
&&
args
[
1
]
->
const_item
())
{
...
...
sql/item_cmpfunc.h
View file @
7655f05d
...
...
@@ -204,7 +204,7 @@ public:
enum
Item_result
result_type
()
const
{
return
cached_result_type
;
}
void
fix_length_and_dec
();
const
char
*
func_name
()
const
{
return
"ifnull"
;
}
unsigned
int
size_of
()
{
return
sizeof
(
*
this
);}
table_map
not_null_tables
()
const
{
return
0
;
}
};
...
...
@@ -224,7 +224,7 @@ public:
}
void
fix_length_and_dec
();
const
char
*
func_name
()
const
{
return
"if"
;
}
unsigned
int
size_of
()
{
return
sizeof
(
*
this
);}
table_map
not_null_tables
()
const
{
return
0
;
}
};
...
...
@@ -239,7 +239,7 @@ public:
enum
Item_result
result_type
()
const
{
return
cached_result_type
;
}
void
fix_length_and_dec
();
const
char
*
func_name
()
const
{
return
"nullif"
;
}
unsigned
int
size_of
()
{
return
sizeof
(
*
this
);}
table_map
not_null_tables
()
const
{
return
0
;
}
};
...
...
@@ -254,9 +254,10 @@ public:
void
fix_length_and_dec
();
enum
Item_result
result_type
()
const
{
return
cached_result_type
;
}
const
char
*
func_name
()
const
{
return
"coalesce"
;
}
unsigned
int
size_of
()
{
return
sizeof
(
*
this
);}
table_map
not_null_tables
()
const
{
return
0
;
}
};
class
Item_func_case
:
public
Item_func
{
Item
*
first_expr
,
*
else_expr
;
...
...
@@ -270,6 +271,7 @@ public:
String
*
val_str
(
String
*
);
void
fix_length_and_dec
();
void
update_used_tables
();
table_map
not_null_tables
()
const
{
return
0
;
}
enum
Item_result
result_type
()
const
{
return
cached_result_type
;
}
const
char
*
func_name
()
const
{
return
"case"
;
}
void
print
(
String
*
str
);
...
...
@@ -479,10 +481,12 @@ public:
}
}
}
table_map
not_null_tables
()
const
{
return
0
;
}
optimize_type
select_optimize
()
const
{
return
OPTIMIZE_NULL
;
}
unsigned
int
size_of
()
{
return
sizeof
(
*
this
);}
};
class
Item_func_isnotnull
:
public
Item_bool_func
{
public:
...
...
@@ -495,9 +499,10 @@ public:
}
const
char
*
func_name
()
const
{
return
"isnotnull"
;
}
optimize_type
select_optimize
()
const
{
return
OPTIMIZE_NULL
;
}
unsigned
int
size_of
()
{
return
sizeof
(
*
this
);}
table_map
not_null_tables
()
const
{
return
0
;
}
};
class
Item_func_like
:
public
Item_bool_func2
{
char
escape
;
...
...
@@ -572,6 +577,8 @@ class Item_cond :public Item_bool_func
protected:
List
<
Item
>
list
;
bool
abort_on_null
;
table_map
and_tables_cache
;
public:
/* Item_cond() is only used to create top level items */
Item_cond
()
:
Item_bool_func
(),
abort_on_null
(
1
)
{
const_item_cache
=
0
;
}
...
...
@@ -611,6 +618,7 @@ public:
enum
Functype
functype
()
const
{
return
COND_OR_FUNC
;
}
longlong
val_int
();
const
char
*
func_name
()
const
{
return
"or"
;
}
table_map
not_null_tables
()
const
{
return
and_tables_cache
;
}
};
...
...
sql/item_func.cc
View file @
7655f05d
...
...
@@ -61,7 +61,7 @@ Item_func::fix_fields(THD *thd,TABLE_LIST *tables)
Item
**
arg
,
**
arg_end
;
char
buff
[
STACK_BUFF_ALLOC
];
// Max argument in function
binary
=
0
;
used_tables_cache
=
0
;
used_tables_cache
=
not_null_tables_cache
=
0
;
const_item_cache
=
1
;
if
(
thd
&&
check_stack_overrun
(
thd
,
buff
))
...
...
@@ -78,7 +78,8 @@ Item_func::fix_fields(THD *thd,TABLE_LIST *tables)
if
(
item
->
binary
)
binary
=
1
;
with_sum_func
=
with_sum_func
||
item
->
with_sum_func
;
used_tables_cache
|=
item
->
used_tables
();
used_tables_cache
|=
item
->
used_tables
();
not_null_tables_cache
|=
item
->
not_null_tables
();
const_item_cache
&=
item
->
const_item
();
}
}
...
...
@@ -122,6 +123,13 @@ table_map Item_func::used_tables() const
return
used_tables_cache
;
}
table_map
Item_func
::
not_null_tables
()
const
{
return
not_null_tables_cache
;
}
void
Item_func
::
print
(
String
*
str
)
{
str
->
append
(
func_name
());
...
...
sql/item_func.h
View file @
7655f05d
...
...
@@ -34,7 +34,7 @@ protected:
Item
**
args
,
*
tmp_arg
[
2
];
public:
uint
arg_count
;
table_map
used_tables_cache
;
table_map
used_tables_cache
,
not_null_tables_cache
;
bool
const_item_cache
;
enum
Functype
{
UNKNOWN_FUNC
,
EQ_FUNC
,
EQUAL_FUNC
,
NE_FUNC
,
LT_FUNC
,
LE_FUNC
,
GE_FUNC
,
GT_FUNC
,
FT_FUNC
,
...
...
@@ -97,6 +97,7 @@ public:
bool
fix_fields
(
THD
*
,
struct
st_table_list
*
);
void
make_field
(
Send_field
*
field
);
table_map
used_tables
()
const
;
table_map
not_null_tables
()
const
;
void
update_used_tables
();
bool
eq
(
const
Item
*
item
,
bool
binary_cmp
)
const
;
virtual
optimize_type
select_optimize
()
const
{
return
OPTIMIZE_NONE
;
}
...
...
@@ -588,7 +589,8 @@ public:
void
split_sum_func
(
List
<
Item
>
&
fields
);
void
update_used_tables
()
{
item
->
update_used_tables
()
;
Item_func
::
update_used_tables
();
item
->
update_used_tables
();
Item_func
::
update_used_tables
();
used_tables_cache
|=
item
->
used_tables
();
const_item_cache
&=
item
->
const_item
();
}
...
...
@@ -597,6 +599,7 @@ public:
{
maybe_null
=
0
;
max_length
=
3
;
used_tables_cache
|=
item
->
used_tables
();
not_null_tables_cache
&=
item
->
not_null_tables
();
const_item_cache
&=
item
->
const_item
();
with_sum_func
=
with_sum_func
||
item
->
with_sum_func
;
}
...
...
@@ -736,6 +739,7 @@ public:
return
res
;
}
Item_result
result_type
()
const
{
return
udf
.
result_type
();
}
table_map
not_null_tables
()
const
{
return
0
;
}
unsigned
int
size_of
()
{
return
sizeof
(
*
this
);}
};
...
...
@@ -969,6 +973,7 @@ public:
}
enum
Functype
functype
()
const
{
return
FT_FUNC
;
}
void
update_used_tables
()
{}
table_map
not_null_tables
()
const
{
return
0
;
}
bool
fix_fields
(
THD
*
thd
,
struct
st_table_list
*
tlist
);
bool
eq
(
const
Item
*
,
bool
binary_cmp
)
const
;
longlong
val_int
()
{
return
val
()
!=
0.0
;
}
...
...
sql/item_strfunc.cc
View file @
7655f05d
...
...
@@ -606,8 +606,9 @@ void Item_func_concat_ws::fix_length_and_dec()
max_length
=
MAX_BLOB_WIDTH
;
maybe_null
=
1
;
}
used_tables_cache
|=
separator
->
used_tables
();
const_item_cache
&=
separator
->
const_item
();
used_tables_cache
|=
separator
->
used_tables
();
not_null_tables_cache
&=
separator
->
not_null_tables
();
const_item_cache
&=
separator
->
const_item
();
with_sum_func
=
with_sum_func
||
separator
->
with_sum_func
;
}
...
...
@@ -1509,8 +1510,9 @@ void Item_func_elt::fix_length_and_dec()
}
maybe_null
=
1
;
// NULL if wrong first arg
with_sum_func
=
with_sum_func
||
item
->
with_sum_func
;
used_tables_cache
|=
item
->
used_tables
();
const_item_cache
&=
item
->
const_item
();
used_tables_cache
|=
item
->
used_tables
();
not_null_tables_cache
&=
item
->
not_null_tables
();
const_item_cache
&=
item
->
const_item
();
}
...
...
@@ -1591,8 +1593,9 @@ void Item_func_make_set::fix_length_and_dec()
max_length
=
arg_count
-
1
;
for
(
uint
i
=
1
;
i
<
arg_count
;
i
++
)
max_length
+=
args
[
i
]
->
max_length
;
used_tables_cache
|=
item
->
used_tables
();
const_item_cache
&=
item
->
const_item
();
used_tables_cache
|=
item
->
used_tables
();
not_null_tables_cache
&=
item
->
not_null_tables
();
const_item_cache
&=
item
->
const_item
();
with_sum_func
=
with_sum_func
||
item
->
with_sum_func
;
}
...
...
sql/sql_base.cc
View file @
7655f05d
...
...
@@ -1903,11 +1903,11 @@ bool setup_tables(TABLE_LIST *tables)
table
->
used_fields
=
0
;
table
->
const_table
=
0
;
table
->
outer_join
=
table
->
null_row
=
0
;
table
->
null_row
=
0
;
table
->
status
=
STATUS_NO_RECORD
;
table
->
keys_in_use_for_query
=
table
->
keys_in_use
;
table
->
used_keys
=
table
->
keys_for_keyread
;
table
->
maybe_null
=
test
(
table
->
outer_join
=
table_list
->
outer_join
);
table
->
maybe_null
=
test
(
table
->
outer_join
=
table_list
->
outer_join
);
table
->
tablenr
=
tablenr
;
table
->
map
=
(
table_map
)
1
<<
tablenr
;
table
->
force_index
=
table_list
->
force_index
;
...
...
@@ -2027,6 +2027,7 @@ insert_fields(THD *thd,TABLE_LIST *tables, const char *db_name,
int
setup_conds
(
THD
*
thd
,
TABLE_LIST
*
tables
,
COND
**
conds
)
{
table_map
not_null_tables
=
0
;
DBUG_ENTER
(
"setup_conds"
);
thd
->
set_query_id
=
1
;
thd
->
cond_count
=
0
;
...
...
@@ -2036,6 +2037,7 @@ int setup_conds(THD *thd,TABLE_LIST *tables,COND **conds)
thd
->
where
=
"where clause"
;
if
((
*
conds
)
->
fix_fields
(
thd
,
tables
))
DBUG_RETURN
(
1
);
not_null_tables
=
(
*
conds
)
->
not_null_tables
();
}
/* Check if we are using outer joins */
...
...
@@ -2049,9 +2051,15 @@ int setup_conds(THD *thd,TABLE_LIST *tables,COND **conds)
DBUG_RETURN
(
1
);
thd
->
cond_count
++
;
/* If it's a normal join, add the ON/USING expression to the WHERE */
if
(
!
table
->
outer_join
)
/*
If it's a normal join or a LEFT JOIN which can be optimized away
add the ON/USING expression to the WHERE
*/
if
(
!
table
->
outer_join
||
((
table
->
table
->
map
&
not_null_tables
)
&&
!
(
specialflag
&
SPECIAL_NO_NEW_FUNC
)))
{
table
->
outer_join
=
0
;
if
(
!
(
*
conds
=
and_conds
(
*
conds
,
table
->
on_expr
)))
DBUG_RETURN
(
1
);
table
->
on_expr
=
0
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment