mirror of
https://github.com/MariaDB/server.git
synced 2026-10-23 18:49:29 +03:00
CSV: Share the data file length among handlers of one lock
Under LOCK TABLES t1 WRITE, t1 AS a1 WRITE, rows added or changed through t1 were missing when read through a1, and after UNLOCK TABLES a row could stay hidden from every later query. Each handler kept its own copy of the data file length, taken when the table was locked, and the last handler to unlock wrote its copy back to the share. The CSV engine now registers a copy_status callback, as MyISAM does. When one lock call holds a table more than once, all handlers of that table use the length of the first handler, so a write through any of them is seen by the others. Only the first handler stores that length in the share, because the others can unlock after it is closed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5558,3 +5558,84 @@ a", e
|
||||
d ,"a
|
||||
a", e
|
||||
e "
|
||||
, f
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# Two handlers of one CSV table locked by LOCK TABLES share the length
|
||||
# of the data file
|
||||
#
|
||||
CREATE TABLE t1 (v INT NOT NULL) ENGINE=CSV;
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
LOCK TABLES t1 WRITE, t1 AS a1 WRITE;
|
||||
SELECT * FROM t1 AS a1;
|
||||
v
|
||||
1
|
||||
2
|
||||
3
|
||||
# makes the file longer through t1
|
||||
UPDATE t1 SET v=v*10;
|
||||
SELECT * FROM t1 AS a1;
|
||||
v
|
||||
10
|
||||
20
|
||||
30
|
||||
INSERT INTO t1 VALUES (4);
|
||||
SELECT * FROM t1 AS a1;
|
||||
v
|
||||
10
|
||||
20
|
||||
30
|
||||
4
|
||||
# makes the file shorter through a1
|
||||
DELETE a1 FROM t1 AS a1 WHERE a1.v=10;
|
||||
SELECT * FROM t1;
|
||||
v
|
||||
20
|
||||
30
|
||||
4
|
||||
UNLOCK TABLES;
|
||||
SELECT * FROM t1;
|
||||
v
|
||||
20
|
||||
30
|
||||
4
|
||||
CHECK TABLE t1;
|
||||
Table Op Msg_type Msg_text
|
||||
test.t1 check status OK
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# A handler that uses the data file length of another handler of the
|
||||
# table unlocks after that handler is closed
|
||||
#
|
||||
CREATE TABLE t1 (v INT NOT NULL) ENGINE=CSV;
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
# t1 uses the length of a1, which holds the stronger lock
|
||||
LOCK TABLES t1 WRITE CONCURRENT, t1 AS a1 WRITE;
|
||||
INSERT INTO t1 VALUES (4);
|
||||
# closes a1 before it unlocks t1
|
||||
FLUSH TABLES t1;
|
||||
UNLOCK TABLES;
|
||||
SELECT * FROM t1;
|
||||
v
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
CHECK TABLE t1;
|
||||
Table Op Msg_type Msg_text
|
||||
test.t1 check status OK
|
||||
# t1 uses the length of a1, which is listed first
|
||||
LOCK TABLES t1 AS a1 WRITE, t1 WRITE;
|
||||
INSERT INTO t1 VALUES (5);
|
||||
# closes a1 and keeps t1 open and locked until the end of the ALTER
|
||||
ALTER TABLE t1 ALTER COLUMN v SET DEFAULT 5, LOCK=EXCLUSIVE;
|
||||
UNLOCK TABLES;
|
||||
SELECT * FROM t1;
|
||||
v
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
CHECK TABLE t1;
|
||||
Table Op Msg_type Msg_text
|
||||
|
||||
@@ -1970,3 +1970,55 @@ ALTER TABLE t1 IETF_QUOTES=no;
|
||||
SELECT * FROM t1;
|
||||
|
||||
DROP TABLE t1;
|
||||
|
||||
--echo #
|
||||
--echo # Two handlers of one CSV table locked by LOCK TABLES share the length
|
||||
--echo # of the data file
|
||||
--echo #
|
||||
CREATE TABLE t1 (v INT NOT NULL) ENGINE=CSV;
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
LOCK TABLES t1 WRITE, t1 AS a1 WRITE;
|
||||
--sorted_result
|
||||
SELECT * FROM t1 AS a1;
|
||||
--echo # makes the file longer through t1
|
||||
UPDATE t1 SET v=v*10;
|
||||
--sorted_result
|
||||
SELECT * FROM t1 AS a1;
|
||||
INSERT INTO t1 VALUES (4);
|
||||
--sorted_result
|
||||
SELECT * FROM t1 AS a1;
|
||||
--echo # makes the file shorter through a1
|
||||
DELETE a1 FROM t1 AS a1 WHERE a1.v=10;
|
||||
--sorted_result
|
||||
SELECT * FROM t1;
|
||||
UNLOCK TABLES;
|
||||
--sorted_result
|
||||
SELECT * FROM t1;
|
||||
CHECK TABLE t1;
|
||||
DROP TABLE t1;
|
||||
|
||||
--echo #
|
||||
--echo # A handler that uses the data file length of another handler of the
|
||||
--echo # table unlocks after that handler is closed
|
||||
--echo #
|
||||
CREATE TABLE t1 (v INT NOT NULL) ENGINE=CSV;
|
||||
INSERT INTO t1 VALUES (1),(2),(3);
|
||||
--echo # t1 uses the length of a1, which holds the stronger lock
|
||||
LOCK TABLES t1 WRITE CONCURRENT, t1 AS a1 WRITE;
|
||||
INSERT INTO t1 VALUES (4);
|
||||
--echo # closes a1 before it unlocks t1
|
||||
FLUSH TABLES t1;
|
||||
UNLOCK TABLES;
|
||||
--sorted_result
|
||||
SELECT * FROM t1;
|
||||
CHECK TABLE t1;
|
||||
--echo # t1 uses the length of a1, which is listed first
|
||||
LOCK TABLES t1 AS a1 WRITE, t1 WRITE;
|
||||
INSERT INTO t1 VALUES (5);
|
||||
--echo # closes a1 and keeps t1 open and locked until the end of the ALTER
|
||||
ALTER TABLE t1 ALTER COLUMN v SET DEFAULT 5, LOCK=EXCLUSIVE;
|
||||
UNLOCK TABLES;
|
||||
--sorted_result
|
||||
SELECT * FROM t1;
|
||||
CHECK TABLE t1;
|
||||
DROP TABLE t1;
|
||||
|
||||
@@ -81,6 +81,7 @@ static int write_meta_file(File meta_file, ha_rows rows, bool dirty);
|
||||
extern "C" void tina_get_status(void* param, int concurrent_insert);
|
||||
extern "C" void tina_update_status(void* param);
|
||||
extern "C" my_bool tina_check_status(void* param);
|
||||
extern "C" void tina_copy_status(void* to, void* from);
|
||||
|
||||
/* Stuff for shares */
|
||||
mysql_mutex_t tina_mutex;
|
||||
@@ -524,6 +525,7 @@ ha_tina::ha_tina(handlerton *hton, TABLE_SHARE *table_arg)
|
||||
They are not probably completely right.
|
||||
*/
|
||||
current_position(0), next_position(0), local_saved_data_file_length(0),
|
||||
saved_length(&local_saved_data_file_length),
|
||||
file_buff(0), chain_alloced(0), chain_size(DEFAULT_CHAIN_LENGTH),
|
||||
local_data_file_version(0), records_is_known(0)
|
||||
{
|
||||
@@ -687,12 +689,12 @@ int ha_tina::find_current_row(uchar *buf)
|
||||
free_root(&blobroot, MYF(0));
|
||||
|
||||
/*
|
||||
We do not read further then local_saved_data_file_length in order
|
||||
not to conflict with undergoing concurrent insert.
|
||||
Do not read past the data file length of this handler, which excludes
|
||||
rows that concurrent inserts appended after thr_lock granted its lock.
|
||||
*/
|
||||
if ((end_offset=
|
||||
find_eoln_buff(file_buff, current_position,
|
||||
local_saved_data_file_length, &eoln_len)) == 0)
|
||||
*saved_length, &eoln_len)) == 0)
|
||||
DBUG_RETURN(HA_ERR_END_OF_FILE);
|
||||
|
||||
/* We must read all columns in case a table is opened for update */
|
||||
@@ -882,8 +884,10 @@ err:
|
||||
}
|
||||
|
||||
/*
|
||||
Three functions below are needed to enable concurrent insert functionality
|
||||
for CSV engine. For more details see mysys/thr_lock.c
|
||||
The four functions below are the thr_lock callbacks of the table. They
|
||||
enable concurrent inserts, and let the handlers of one table that the
|
||||
same thr_multi_lock() call locks use one data file length. For more
|
||||
details see mysys/thr_lock.c
|
||||
*/
|
||||
|
||||
my_bool tina_get_status(void* param, my_bool concurrent_insert)
|
||||
@@ -905,6 +909,12 @@ my_bool tina_check_status(void* param)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tina_copy_status(void* to, void* from)
|
||||
{
|
||||
ha_tina *tina= (ha_tina*) to;
|
||||
tina->copy_status((ha_tina*) from);
|
||||
}
|
||||
|
||||
/*
|
||||
Save the state of the table
|
||||
|
||||
@@ -919,6 +929,7 @@ my_bool tina_check_status(void* param)
|
||||
|
||||
void ha_tina::get_status()
|
||||
{
|
||||
saved_length= &local_saved_data_file_length;
|
||||
if (share->is_log_table)
|
||||
{
|
||||
/*
|
||||
@@ -957,8 +968,28 @@ void ha_tina::get_status()
|
||||
|
||||
void ha_tina::update_status()
|
||||
{
|
||||
/* correct local_saved_data_file_length for writers */
|
||||
share->saved_data_file_length= local_saved_data_file_length;
|
||||
/*
|
||||
A handler that uses the length of the first handler of the table can
|
||||
unlock after that handler is closed, so only a handler that uses its
|
||||
own length stores it. The first handler holds the strongest lock of
|
||||
the table, so it holds a write lock whenever another handler of the
|
||||
table does, and releasing that lock stores the length.
|
||||
*/
|
||||
if (saved_length == &local_saved_data_file_length)
|
||||
share->saved_data_file_length= *saved_length;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Use the data file length of "from", the first handler of this table
|
||||
locked by the same thr_multi_lock() call. A write through any of these
|
||||
handlers changes the one length that all of them read, and unlocking
|
||||
"from" stores that length in the share.
|
||||
*/
|
||||
|
||||
void ha_tina::copy_status(ha_tina *from)
|
||||
{
|
||||
saved_length= &from->local_saved_data_file_length;
|
||||
}
|
||||
|
||||
|
||||
@@ -991,7 +1022,7 @@ int ha_tina::open(const char *name, int mode, uint open_options)
|
||||
|
||||
/*
|
||||
Init locking. Pass handler object to the locking routines,
|
||||
so that they could save/update local_saved_data_file_length value
|
||||
so that they could load and store the data file length of the handler
|
||||
during locking. This is needed to enable concurrent inserts.
|
||||
*/
|
||||
thr_lock_data_init(&share->lock, &lock, (void*) this);
|
||||
@@ -1002,6 +1033,7 @@ int ha_tina::open(const char *name, int mode, uint open_options)
|
||||
share->lock.get_status= tina_get_status;
|
||||
share->lock.update_status= tina_update_status;
|
||||
share->lock.check_status= tina_check_status;
|
||||
share->lock.copy_status= tina_copy_status;
|
||||
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
@@ -1045,7 +1077,7 @@ int ha_tina::write_row(const uchar * buf)
|
||||
DBUG_RETURN(-1);
|
||||
|
||||
/* update local copy of the max position to see our own changes */
|
||||
local_saved_data_file_length+= size;
|
||||
*saved_length+= size;
|
||||
|
||||
/* update shared info */
|
||||
mysql_mutex_lock(&share->mutex);
|
||||
@@ -1259,7 +1291,7 @@ int ha_tina::rnd_next(uchar *buf)
|
||||
current_position= next_position;
|
||||
|
||||
/* don't scan an empty file */
|
||||
if (!local_saved_data_file_length)
|
||||
if (!*saved_length)
|
||||
{
|
||||
rc= HA_ERR_END_OF_FILE;
|
||||
goto end;
|
||||
@@ -1493,11 +1525,8 @@ int ha_tina::rnd_end()
|
||||
Here we record this fact to the meta-file.
|
||||
*/
|
||||
(void)write_meta_file(share->meta_file, share->rows_recorded, FALSE);
|
||||
/*
|
||||
Update local_saved_data_file_length with the real length of the
|
||||
data file.
|
||||
*/
|
||||
local_saved_data_file_length= temp_file_length;
|
||||
/* Set the data file length of this handler to that of the new file. */
|
||||
*saved_length= temp_file_length;
|
||||
}
|
||||
|
||||
DBUG_RETURN(0);
|
||||
@@ -1555,7 +1584,7 @@ int ha_tina::repair(THD* thd, HA_CHECK_OPT* check_opt)
|
||||
Sometimes this is not getting executed before ::repair (e.g. for
|
||||
the log tables). We set it manually here.
|
||||
*/
|
||||
local_saved_data_file_length= share->saved_data_file_length;
|
||||
*saved_length= share->saved_data_file_length;
|
||||
/* set current position to the beginning of the file */
|
||||
current_position= next_position= 0;
|
||||
|
||||
@@ -1647,7 +1676,7 @@ int ha_tina::repair(THD* thd, HA_CHECK_OPT* check_opt)
|
||||
DBUG_RETURN(my_errno ? my_errno : -1);
|
||||
|
||||
/* Set new file size. The file size will be updated by ::update_status() */
|
||||
local_saved_data_file_length= (size_t) current_position;
|
||||
*saved_length= (size_t) current_position;
|
||||
|
||||
end:
|
||||
share->crashed= FALSE;
|
||||
@@ -1678,7 +1707,7 @@ int ha_tina::delete_all_rows()
|
||||
mysql_mutex_lock(&share->mutex);
|
||||
share->rows_recorded= 0;
|
||||
mysql_mutex_unlock(&share->mutex);
|
||||
local_saved_data_file_length= 0;
|
||||
*saved_length= 0;
|
||||
DBUG_RETURN(rc);
|
||||
}
|
||||
|
||||
@@ -1686,6 +1715,9 @@ int ha_tina::external_lock(THD *thd __attribute__((unused)), int lock_type)
|
||||
{
|
||||
if (lock_type==F_UNLCK && curr_lock_type == F_WRLCK)
|
||||
update_status();
|
||||
/* The handler whose length this one uses can be closed after unlock */
|
||||
if (lock_type == F_UNLCK)
|
||||
saved_length= &local_saved_data_file_length;
|
||||
curr_lock_type= lock_type;
|
||||
return 0;
|
||||
}
|
||||
@@ -1772,7 +1804,7 @@ int ha_tina::check(THD* thd, HA_CHECK_OPT* check_opt)
|
||||
Check does not use store_lock in certain cases. So, we set it
|
||||
manually here.
|
||||
*/
|
||||
local_saved_data_file_length= share->saved_data_file_length;
|
||||
*saved_length= share->saved_data_file_length;
|
||||
/* set current position to the beginning of the file */
|
||||
current_position= next_position= 0;
|
||||
|
||||
|
||||
@@ -64,6 +64,14 @@ class ha_tina final : public handler
|
||||
my_off_t current_position; /* Current position in the file during a file scan */
|
||||
my_off_t next_position; /* Next position in the file scan */
|
||||
my_off_t local_saved_data_file_length; /* save position for reads */
|
||||
/*
|
||||
The data file length used by this handler. It points to
|
||||
local_saved_data_file_length, except while one thr_multi_lock() call
|
||||
holds this table more than once. Then it points to the length of the
|
||||
first handler of the table in that call, so that every handler of the
|
||||
table reads and changes the same length.
|
||||
*/
|
||||
my_off_t *saved_length;
|
||||
my_off_t temp_file_length;
|
||||
uchar byte_buffer[IO_SIZE];
|
||||
Transparent_file *file_buff;
|
||||
@@ -176,6 +184,7 @@ public:
|
||||
*/
|
||||
void get_status();
|
||||
void update_status();
|
||||
void copy_status(ha_tina *from);
|
||||
|
||||
/* The following methods were added just for TINA */
|
||||
int encode_quote(const uchar *buf);
|
||||
|
||||
Reference in New Issue
Block a user