1
0
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:
Dave Gosselin
2026-10-05 10:47:17 -04:00
parent 4ea6e54bdb
commit cd2b2ef881
4 changed files with 193 additions and 19 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);