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
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nexedi
MariaDB
Commits
72bde5e6
Commit
72bde5e6
authored
Dec 16, 2010
by
Sergey Petrunya
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
MWL#121-125 DS-MRR improvements
- Address Monty's review feedback, portion 2
parent
c3009dcb
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
67 additions
and
75 deletions
+67
-75
sql/multi_range_read.cc
sql/multi_range_read.cc
+35
-44
sql/multi_range_read.h
sql/multi_range_read.h
+4
-7
sql/sql_lifo_buffer.h
sql/sql_lifo_buffer.h
+28
-24
No files found.
sql/multi_range_read.cc
View file @
72bde5e6
...
@@ -351,7 +351,7 @@ int Mrr_ordered_index_reader::get_next(char **range_info)
...
@@ -351,7 +351,7 @@ int Mrr_ordered_index_reader::get_next(char **range_info)
scanning_key_val_iter
=
TRUE
;
scanning_key_val_iter
=
TRUE
;
}
}
if
((
res
=
kv_it
.
get_next
()))
if
((
res
=
kv_it
.
get_next
(
range_info
)))
{
{
scanning_key_val_iter
=
FALSE
;
scanning_key_val_iter
=
FALSE
;
if
((
res
!=
HA_ERR_KEY_NOT_FOUND
&&
res
!=
HA_ERR_END_OF_FILE
))
if
((
res
!=
HA_ERR_KEY_NOT_FOUND
&&
res
!=
HA_ERR_END_OF_FILE
))
...
@@ -359,17 +359,14 @@ int Mrr_ordered_index_reader::get_next(char **range_info)
...
@@ -359,17 +359,14 @@ int Mrr_ordered_index_reader::get_next(char **range_info)
kv_it
.
move_to_next_key_value
();
kv_it
.
move_to_next_key_value
();
continue
;
continue
;
}
}
char
*
range_info
;
if
(
!
skip_index_tuple
(
*
range_info
)
&&
memcpy
(
&
range_info
,
cur_range_info
,
sizeof
(
char
*
));
!
skip_record
(
*
range_info
,
NULL
))
if
(
!
skip_index_tuple
(
range_info
)
&&
!
skip_record
(
range_info
,
NULL
))
{
{
break
;
break
;
}
}
/* Go get another (record, range_id) combination */
/* Go get another (record, range_id) combination */
}
/* while */
}
/* while */
memcpy
(
range_info
,
cur_range_info
,
sizeof
(
void
*
));
DBUG_RETURN
(
0
);
DBUG_RETURN
(
0
);
}
}
...
@@ -430,7 +427,7 @@ int Mrr_ordered_index_reader::refill_buffer(bool initial)
...
@@ -430,7 +427,7 @@ int Mrr_ordered_index_reader::refill_buffer(bool initial)
key_buffer
->
reset
();
key_buffer
->
reset
();
key_buffer
->
setup_writing
(
&
key_ptr
,
keypar
.
key_size_in_keybuf
,
key_buffer
->
setup_writing
(
&
key_ptr
,
keypar
.
key_size_in_keybuf
,
is_mrr_assoc
?
(
uchar
**
)
&
range_info_ptr
:
NULL
,
is_mrr_assoc
?
(
uchar
**
)
&
range_info_ptr
:
NULL
,
sizeof
(
uchar
*
)
);
is_mrr_assoc
?
sizeof
(
uchar
*
)
:
0
);
while
(
key_buffer
->
can_write
()
&&
while
(
key_buffer
->
can_write
()
&&
!
(
source_exhausted
=
mrr_funcs
.
next
(
mrr_iter
,
&
cur_range
)))
!
(
source_exhausted
=
mrr_funcs
.
next
(
mrr_iter
,
&
cur_range
)))
...
@@ -603,7 +600,7 @@ int Mrr_ordered_rndpos_reader::refill_from_index_reader()
...
@@ -603,7 +600,7 @@ int Mrr_ordered_rndpos_reader::refill_from_index_reader()
rowid_buffer
->
reset
();
rowid_buffer
->
reset
();
rowid_buffer
->
setup_writing
(
&
index_rowid
,
file
->
ref_length
,
rowid_buffer
->
setup_writing
(
&
index_rowid
,
file
->
ref_length
,
is_mrr_assoc
?
(
uchar
**
)
&
range_info_ptr
:
NULL
,
is_mrr_assoc
?
(
uchar
**
)
&
range_info_ptr
:
NULL
,
sizeof
(
void
*
)
);
is_mrr_assoc
?
sizeof
(
char
*
)
:
0
);
last_identical_rowid
=
NULL
;
last_identical_rowid
=
NULL
;
...
@@ -630,9 +627,8 @@ int Mrr_ordered_rndpos_reader::refill_from_index_reader()
...
@@ -630,9 +627,8 @@ int Mrr_ordered_rndpos_reader::refill_from_index_reader()
/* Sort the buffer contents by rowid */
/* Sort the buffer contents by rowid */
rowid_buffer
->
sort
((
qsort2_cmp
)
rowid_cmp_reverse
,
(
void
*
)
file
);
rowid_buffer
->
sort
((
qsort2_cmp
)
rowid_cmp_reverse
,
(
void
*
)
file
);
rowid_buffer
->
setup_reading
(
&
rowid
,
file
->
ref_length
,
rowid_buffer
->
setup_reading
(
file
->
ref_length
,
is_mrr_assoc
?
(
uchar
**
)
&
rowids_range_id
:
NULL
,
is_mrr_assoc
?
sizeof
(
char
*
)
:
0
);
sizeof
(
void
*
));
DBUG_RETURN
(
rowid_buffer
->
is_empty
()
?
HA_ERR_END_OF_FILE
:
0
);
DBUG_RETURN
(
rowid_buffer
->
is_empty
()
?
HA_ERR_END_OF_FILE
:
0
);
}
}
...
@@ -662,14 +658,14 @@ int Mrr_ordered_rndpos_reader::get_next(char **range_info)
...
@@ -662,14 +658,14 @@ int Mrr_ordered_rndpos_reader::get_next(char **range_info)
*/
*/
(
void
)
rowid_buffer
->
read
();
(
void
)
rowid_buffer
->
read
();
if
(
rowid
==
last_identical_rowid
)
if
(
rowid
_buffer
->
read_ptr1
==
last_identical_rowid
)
last_identical_rowid
=
NULL
;
/* reached the last of identical rowids */
last_identical_rowid
=
NULL
;
/* reached the last of identical rowids */
if
(
!
is_mrr_assoc
)
if
(
!
is_mrr_assoc
)
return
0
;
return
0
;
memcpy
(
range_info
,
rowid
s_range_id
,
sizeof
(
uchar
*
));
memcpy
(
range_info
,
rowid
_buffer
->
read_ptr2
,
sizeof
(
uchar
*
));
if
(
!
index_reader
->
skip_record
((
char
*
)
*
range_info
,
rowid
))
if
(
!
index_reader
->
skip_record
((
char
*
)
*
range_info
,
rowid
_buffer
->
read_ptr1
))
return
0
;
return
0
;
}
}
...
@@ -685,13 +681,13 @@ int Mrr_ordered_rndpos_reader::get_next(char **range_info)
...
@@ -685,13 +681,13 @@ int Mrr_ordered_rndpos_reader::get_next(char **range_info)
if
(
is_mrr_assoc
)
if
(
is_mrr_assoc
)
{
{
memcpy
(
range_info
,
rowids_range_id
,
sizeof
(
uchar
*
));
memcpy
(
range_info
,
rowid_buffer
->
read_ptr2
,
sizeof
(
uchar
*
));
if
(
index_reader
->
skip_record
(
*
range_info
,
rowid_buffer
->
read_ptr1
))
if
(
index_reader
->
skip_record
(
*
range_info
,
rowid
))
continue
;
continue
;
}
}
res
=
file
->
ha_rnd_pos
(
file
->
get_table
()
->
record
[
0
],
rowid
);
res
=
file
->
ha_rnd_pos
(
file
->
get_table
()
->
record
[
0
],
rowid_buffer
->
read_ptr1
);
if
(
res
==
HA_ERR_RECORD_DELETED
)
if
(
res
==
HA_ERR_RECORD_DELETED
)
{
{
...
@@ -709,19 +705,17 @@ int Mrr_ordered_rndpos_reader::get_next(char **range_info)
...
@@ -709,19 +705,17 @@ int Mrr_ordered_rndpos_reader::get_next(char **range_info)
Check if subsequent buffer elements have the same rowid value as this
Check if subsequent buffer elements have the same rowid value as this
one. If yes, remember this fact so that we don't make any more rnd_pos()
one. If yes, remember this fact so that we don't make any more rnd_pos()
calls with this value.
calls with this value.
*/
uchar
*
cur_rowid
=
rowid
;
/*
Note: this implies that SQL layer doesn't touch table->record[0]
Note: this implies that SQL layer doesn't touch table->record[0]
between calls.
between calls.
*/
*/
Lifo_buffer_iterator
it
;
Lifo_buffer_iterator
it
;
it
.
init
(
rowid_buffer
);
it
.
init
(
rowid_buffer
);
while
(
!
it
.
read
())
// reads to (rowid, ...)
while
(
!
it
.
read
())
{
{
if
(
file
->
cmp_ref
(
rowid
,
cur_rowid
))
if
(
file
->
cmp_ref
(
it
.
read_ptr1
,
rowid_buffer
->
read_ptr1
))
break
;
break
;
last_identical_rowid
=
rowid
;
last_identical_rowid
=
it
.
read_ptr1
;
}
}
return
0
;
return
0
;
}
}
...
@@ -1202,38 +1196,32 @@ int Key_value_records_iterator::init(Mrr_ordered_index_reader *owner_arg)
...
@@ -1202,38 +1196,32 @@ int Key_value_records_iterator::init(Mrr_ordered_index_reader *owner_arg)
owner
=
owner_arg
;
owner
=
owner_arg
;
identical_key_it
.
init
(
owner
->
key_buffer
);
identical_key_it
.
init
(
owner
->
key_buffer
);
/* Get the first pair into (cur_index_tuple, cur_range_info) */
owner
->
key_buffer
->
setup_reading
(
owner
->
keypar
.
key_size_in_keybuf
,
owner
->
key_buffer
->
setup_reading
(
&
cur_index_tuple
,
owner
->
is_mrr_assoc
?
sizeof
(
void
*
)
:
0
);
owner
->
keypar
.
key_size_in_keybuf
,
owner
->
is_mrr_assoc
?
(
uchar
**
)
&
owner
->
cur_range_info
:
NULL
,
sizeof
(
void
*
));
if
(
identical_key_it
.
read
())
if
(
identical_key_it
.
read
())
return
HA_ERR_END_OF_FILE
;
return
HA_ERR_END_OF_FILE
;
uchar
*
key_in_buf
=
cur_index_tuple
;
uchar
*
key_in_buf
=
last_identical_key_ptr
=
identical_key_it
.
read_ptr1
;
last_identical_key_ptr
=
cur_index_tuple
;
uchar
*
index_tuple
=
key_in_buf
;
if
(
owner
->
keypar
.
use_key_pointers
)
if
(
owner
->
keypar
.
use_key_pointers
)
memcpy
(
&
cur_
index_tuple
,
key_in_buf
,
sizeof
(
char
*
));
memcpy
(
&
index_tuple
,
key_in_buf
,
sizeof
(
char
*
));
/* Check out how many more identical keys are following */
/* Check out how many more identical keys are following */
uchar
*
save_cur_index_tuple
=
cur_index_tuple
;
while
(
!
identical_key_it
.
read
())
while
(
!
identical_key_it
.
read
())
{
{
if
(
owner
->
disallow_identical_key_handling
||
if
(
owner
->
disallow_identical_key_handling
||
Mrr_ordered_index_reader
::
compare_keys
(
owner
,
key_in_buf
,
Mrr_ordered_index_reader
::
compare_keys
(
owner
,
key_in_buf
,
cur_index_tuple
))
identical_key_it
.
read_ptr1
))
break
;
break
;
last_identical_key_ptr
=
cur_index_tuple
;
last_identical_key_ptr
=
identical_key_it
.
read_ptr1
;
}
}
identical_key_it
.
init
(
owner
->
key_buffer
);
identical_key_it
.
init
(
owner
->
key_buffer
);
cur_index_tuple
=
save_cur_index_tuple
;
res
=
owner
->
file
->
ha_index_read_map
(
owner
->
file
->
get_table
()
->
record
[
0
],
res
=
owner
->
file
->
ha_index_read_map
(
owner
->
file
->
get_table
()
->
record
[
0
],
cur_
index_tuple
,
index_tuple
,
owner
->
keypar
.
key_tuple_map
,
owner
->
keypar
.
key_tuple_map
,
HA_READ_KEY_EXACT
);
HA_READ_KEY_EXACT
);
if
(
res
)
if
(
res
)
{
{
...
@@ -1247,7 +1235,7 @@ int Key_value_records_iterator::init(Mrr_ordered_index_reader *owner_arg)
...
@@ -1247,7 +1235,7 @@ int Key_value_records_iterator::init(Mrr_ordered_index_reader *owner_arg)
}
}
int
Key_value_records_iterator
::
get_next
()
int
Key_value_records_iterator
::
get_next
(
char
**
range_info
)
{
{
int
res
;
int
res
;
...
@@ -1261,7 +1249,7 @@ int Key_value_records_iterator::get_next()
...
@@ -1261,7 +1249,7 @@ int Key_value_records_iterator::get_next()
handler
*
h
=
owner
->
file
;
handler
*
h
=
owner
->
file
;
if
((
res
=
h
->
ha_index_next_same
(
h
->
get_table
()
->
record
[
0
],
if
((
res
=
h
->
ha_index_next_same
(
h
->
get_table
()
->
record
[
0
],
cur_index_tuple
,
identical_key_it
.
read_ptr1
,
owner
->
keypar
.
key_tuple_length
)))
owner
->
keypar
.
key_tuple_length
)))
{
{
/* It's either HA_ERR_END_OF_FILE or some other error */
/* It's either HA_ERR_END_OF_FILE or some other error */
...
@@ -1273,7 +1261,10 @@ int Key_value_records_iterator::get_next()
...
@@ -1273,7 +1261,10 @@ int Key_value_records_iterator::get_next()
}
}
identical_key_it
.
read
();
/* This gets us next range_id */
identical_key_it
.
read
();
/* This gets us next range_id */
if
(
!
last_identical_key_ptr
||
(
cur_index_tuple
==
last_identical_key_ptr
))
memcpy
(
range_info
,
identical_key_it
.
read_ptr2
,
sizeof
(
char
*
));
if
(
!
last_identical_key_ptr
||
(
identical_key_it
.
read_ptr1
==
last_identical_key_ptr
))
{
{
/*
/*
We've reached the last of the identical keys that current record is a
We've reached the last of the identical keys that current record is a
...
@@ -1289,7 +1280,7 @@ int Key_value_records_iterator::get_next()
...
@@ -1289,7 +1280,7 @@ int Key_value_records_iterator::get_next()
void
Key_value_records_iterator
::
move_to_next_key_value
()
void
Key_value_records_iterator
::
move_to_next_key_value
()
{
{
while
(
!
owner
->
key_buffer
->
read
()
&&
while
(
!
owner
->
key_buffer
->
read
()
&&
(
cur_index_tuple
!=
last_identical_key_ptr
))
{}
(
owner
->
key_buffer
->
read_ptr1
!=
last_identical_key_ptr
))
{}
}
}
...
...
sql/multi_range_read.h
View file @
72bde5e6
...
@@ -133,10 +133,10 @@ class Key_value_records_iterator
...
@@ -133,10 +133,10 @@ class Key_value_records_iterator
*/
*/
bool
get_next_row
;
bool
get_next_row
;
uchar
*
cur_index_tuple
;
/* key_buffer.read() reads to here */
//
uchar *cur_index_tuple; /* key_buffer.read() reads to here */
public:
public:
int
init
(
Mrr_ordered_index_reader
*
owner_arg
);
int
init
(
Mrr_ordered_index_reader
*
owner_arg
);
int
get_next
();
int
get_next
(
char
**
range_info
);
void
move_to_next_key_value
();
void
move_to_next_key_value
();
};
};
...
@@ -281,9 +281,6 @@ class Mrr_ordered_index_reader : public Mrr_index_reader
...
@@ -281,9 +281,6 @@ class Mrr_ordered_index_reader : public Mrr_index_reader
bool
scanning_key_val_iter
;
bool
scanning_key_val_iter
;
/* Key_value_records_iterator::read() will place range_info here */
char
*
cur_range_info
;
/* Buffer to store (key, range_id) pairs */
/* Buffer to store (key, range_id) pairs */
Lifo_buffer
*
key_buffer
;
Lifo_buffer
*
key_buffer
;
...
@@ -367,8 +364,8 @@ class Mrr_ordered_rndpos_reader : public Mrr_reader
...
@@ -367,8 +364,8 @@ class Mrr_ordered_rndpos_reader : public Mrr_reader
Lifo_buffer
*
rowid_buffer
;
Lifo_buffer
*
rowid_buffer
;
/* rowid_buffer.read() will set the following: */
/* rowid_buffer.read() will set the following: */
uchar
*
rowid
;
//
uchar *rowid;
uchar
*
rowids_range_id
;
//
uchar *rowids_range_id;
int
refill_from_index_reader
();
int
refill_from_index_reader
();
};
};
...
...
sql/sql_lifo_buffer.h
View file @
72bde5e6
...
@@ -39,14 +39,16 @@ class Lifo_buffer
...
@@ -39,14 +39,16 @@ class Lifo_buffer
uchar
**
write_ptr2
;
uchar
**
write_ptr2
;
size_t
size2
;
size_t
size2
;
public:
/**
/**
read() will do reading by storing pointer
to read data into *read_ptr1 (if
read() will do reading by storing pointer
s to read data into read_ptr1 or
the buffer stores atomic elements), or into {*read_ptr1, *read_ptr2} (if
into (read_ptr1, read_ptr2), depending on whether the buffer was set to
the buffer stores pairs)
.
store single objects or pairs
.
*/
*/
uchar
*
*
read_ptr1
;
uchar
*
read_ptr1
;
uchar
*
*
read_ptr2
;
uchar
*
read_ptr2
;
protected:
uchar
*
start
;
/**< points to start of buffer space */
uchar
*
start
;
/**< points to start of buffer space */
uchar
*
end
;
/**< points to just beyond the end of buffer space */
uchar
*
end
;
/**< points to just beyond the end of buffer space */
public:
public:
...
@@ -85,11 +87,9 @@ class Lifo_buffer
...
@@ -85,11 +87,9 @@ class Lifo_buffer
Specify where read() should store pointers to read data, as well as read
Specify where read() should store pointers to read data, as well as read
data size. The sizes must match those passed to setup_writing().
data size. The sizes must match those passed to setup_writing().
*/
*/
void
setup_reading
(
uchar
**
data1
,
size_t
len1
,
uchar
**
data2
,
size_t
len2
)
void
setup_reading
(
size_t
len1
,
size_t
len2
)
{
{
read_ptr1
=
data1
;
DBUG_ASSERT
(
len1
==
size1
);
DBUG_ASSERT
(
len1
==
size1
);
read_ptr2
=
data2
;
DBUG_ASSERT
(
len2
==
size2
);
DBUG_ASSERT
(
len2
==
size2
);
}
}
...
@@ -116,7 +116,7 @@ class Lifo_buffer
...
@@ -116,7 +116,7 @@ class Lifo_buffer
/* To be used only by iterator class: */
/* To be used only by iterator class: */
virtual
uchar
*
get_pos
()
=
0
;
virtual
uchar
*
get_pos
()
=
0
;
virtual
bool
read
(
uchar
**
position
)
=
0
;
virtual
bool
read
(
uchar
**
position
,
uchar
**
ptr1
,
uchar
**
ptr2
)
=
0
;
friend
class
Lifo_buffer_iterator
;
friend
class
Lifo_buffer_iterator
;
public:
public:
virtual
bool
have_space_for
(
size_t
bytes
)
=
0
;
virtual
bool
have_space_for
(
size_t
bytes
)
=
0
;
...
@@ -184,14 +184,14 @@ class Forward_lifo_buffer: public Lifo_buffer
...
@@ -184,14 +184,14 @@ class Forward_lifo_buffer: public Lifo_buffer
*
position
=
(
*
position
)
-
bytes
;
*
position
=
(
*
position
)
-
bytes
;
return
*
position
;
return
*
position
;
}
}
bool
read
()
{
return
read
(
&
pos
);
}
bool
read
()
{
return
read
(
&
pos
,
&
read_ptr1
,
&
read_ptr2
);
}
bool
read
(
uchar
**
position
)
bool
read
(
uchar
**
position
,
uchar
**
ptr1
,
uchar
**
ptr2
)
{
{
if
(
!
have_data
(
*
position
,
size1
+
(
read_ptr2
?
size2
:
0
)
))
if
(
!
have_data
(
*
position
,
size1
+
size2
))
return
TRUE
;
return
TRUE
;
if
(
read_ptr
2
)
if
(
size
2
)
*
read_
ptr2
=
read_bytes
(
position
,
size2
);
*
ptr2
=
read_bytes
(
position
,
size2
);
*
read_
ptr1
=
read_bytes
(
position
,
size1
);
*
ptr1
=
read_bytes
(
position
,
size1
);
return
FALSE
;
return
FALSE
;
}
}
void
remove_unused_space
(
uchar
**
unused_start
,
uchar
**
unused_end
)
void
remove_unused_space
(
uchar
**
unused_start
,
uchar
**
unused_end
)
...
@@ -268,15 +268,15 @@ class Backward_lifo_buffer: public Lifo_buffer
...
@@ -268,15 +268,15 @@ class Backward_lifo_buffer: public Lifo_buffer
}
}
bool
read
()
bool
read
()
{
{
return
read
(
&
pos
);
return
read
(
&
pos
,
&
read_ptr1
,
&
read_ptr2
);
}
}
bool
read
(
uchar
**
position
)
bool
read
(
uchar
**
position
,
uchar
**
ptr1
,
uchar
**
ptr2
)
{
{
if
(
!
have_data
(
*
position
,
size1
+
(
read_ptr2
?
size2
:
0
)
))
if
(
!
have_data
(
*
position
,
size1
+
size2
))
return
TRUE
;
return
TRUE
;
*
read_
ptr1
=
read_bytes
(
position
,
size1
);
*
ptr1
=
read_bytes
(
position
,
size1
);
if
(
read_ptr
2
)
if
(
size
2
)
*
read_
ptr2
=
read_bytes
(
position
,
size2
);
*
ptr2
=
read_bytes
(
position
,
size2
);
return
FALSE
;
return
FALSE
;
}
}
bool
have_data
(
uchar
*
position
,
size_t
bytes
)
bool
have_data
(
uchar
*
position
,
size_t
bytes
)
...
@@ -312,13 +312,17 @@ class Backward_lifo_buffer: public Lifo_buffer
...
@@ -312,13 +312,17 @@ class Backward_lifo_buffer: public Lifo_buffer
};
};
/** Iterator to walk over contents of the buffer without reading from it */
/** Iterator to walk over contents of the buffer without reading it. */
class
Lifo_buffer_iterator
class
Lifo_buffer_iterator
{
{
uchar
*
pos
;
uchar
*
pos
;
Lifo_buffer
*
buf
;
Lifo_buffer
*
buf
;
public:
public:
/* The data is read to here */
uchar
*
read_ptr1
;
uchar
*
read_ptr2
;
void
init
(
Lifo_buffer
*
buf_arg
)
void
init
(
Lifo_buffer
*
buf_arg
)
{
{
buf
=
buf_arg
;
buf
=
buf_arg
;
...
@@ -333,7 +337,7 @@ class Lifo_buffer_iterator
...
@@ -333,7 +337,7 @@ class Lifo_buffer_iterator
*/
*/
bool
read
()
bool
read
()
{
{
return
buf
->
read
(
&
pos
);
return
buf
->
read
(
&
pos
,
&
read_ptr1
,
&
read_ptr2
);
}
}
};
};
...
...
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