1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

CSV: Remove the partial row that a failed INSERT leaves

Cut the data file of a CSV table back to its length before the write
when an INSERT fails to write its row.

When the write of row 2 stops after its first byte, INSERT INTO t1
VALUES (2) fails, but that byte stays at the end of the data file.  The
next INSERT INTO t1 VALUES (3) appends its row after it.  Once the
table is opened again, SELECT * FROM t1 returns the rows 1 and 23, and
CHECK TABLE reports the table as OK.

If the cut fails too, the table is marked as crashed.  A new debug
injection point writes the first byte of a row and then fails as if
the disk were full, and the new test uses it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dave Gosselin
2026-10-07 13:59:00 -04:00
parent 1ca6a43853
commit 335eca6b25
3 changed files with 78 additions and 6 deletions

View File

@@ -0,0 +1,29 @@
#
# A failed write of a row during INSERT leaves no part of the row in
# the data file
#
CREATE TABLE t1 (v INT NOT NULL) ENGINE=CSV;
INSERT INTO t1 VALUES (1);
SET @save_debug_dbug= @@debug_dbug;
SET debug_dbug='+d,tina_write_row_short_write';
INSERT INTO t1 VALUES (2);
ERROR HY000: Error writing file 'FILE' (Errcode: 28)
SET debug_dbug= @save_debug_dbug;
INSERT INTO t1 VALUES (3);
SELECT * FROM t1;
v
1
3
CHECK TABLE t1;
Table Op Msg_type Msg_text
test.t1 check status OK
# The next open reads the length of the data file
FLUSH TABLES t1;
SELECT * FROM t1;
v
1
3
CHECK TABLE t1;
Table Op Msg_type Msg_text
test.t1 check status OK
DROP TABLE t1;

View File

@@ -0,0 +1,24 @@
--source include/have_debug.inc
--echo #
--echo # A failed write of a row during INSERT leaves no part of the row in
--echo # the data file
--echo #
CREATE TABLE t1 (v INT NOT NULL) ENGINE=CSV;
INSERT INTO t1 VALUES (1);
SET @save_debug_dbug= @@debug_dbug;
SET debug_dbug='+d,tina_write_row_short_write';
--replace_regex /'[^']*'/'FILE'/ /Errcode: .*/Errcode: 28)/
--error 3
INSERT INTO t1 VALUES (2);
SET debug_dbug= @save_debug_dbug;
INSERT INTO t1 VALUES (3);
--sorted_result
SELECT * FROM t1;
CHECK TABLE t1;
--echo # The next open reads the length of the data file
FLUSH TABLES t1;
--sorted_result
SELECT * FROM t1;
CHECK TABLE t1;
DROP TABLE t1;

View File

@@ -1069,9 +1069,30 @@ static size_t short_write(File file, const uchar *buf, size_t count)
/*
This is an INSERT. At the moment this handler just seeks to the end
of the file and appends the data. In an error case it really should
just truncate to the original position (this is not done yet).
Append a row of size bytes from buf to the data file. A failed write
can leave part of the row at the end of the file, where the next row
would be appended to it, so cut the file back to its length before the
write. If that fails too, mark the table crashed.
*/
static int append_row(TINA_SHARE *share, const uchar *buf, size_t size)
{
File file= share->tina_write_filedes;
my_off_t end= mysql_file_seek(file, 0, MY_SEEK_END, MYF(MY_WME));
if (end == MY_FILEPOS_ERROR)
return -1;
if (!(DBUG_IF("tina_write_row_short_write") ?
short_write(file, buf, 1) :
mysql_file_write(file, buf, size, MYF(MY_WME | MY_NABP))))
return 0;
if (mysql_file_chsize(file, end, 0, MYF(0)))
share->crashed= TRUE;
return -1;
}
/*
This is an INSERT. The row is appended to the end of the data file.
*/
int ha_tina::write_row(const uchar * buf)
{
@@ -1087,9 +1108,7 @@ int ha_tina::write_row(const uchar * buf)
if (init_tina_writer())
DBUG_RETURN(-1);
/* use pwrite, as concurrent reader could have changed the position */
if (mysql_file_write(share->tina_write_filedes, (uchar*)buffer.ptr(), size,
MYF(MY_WME | MY_NABP)))
if (append_row(share, (uchar*) buffer.ptr(), size))
DBUG_RETURN(-1);
/* update local copy of the max position to see our own changes */