/* Copyright (c) 2004, 2011, Oracle and/or its affiliates.
Copyright ( c ) 2026 , MariaDB plc
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; version 2 of the License .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
/*
Make sure to look at ha_tina . h for more details .
First off , this is a play thing for me , there are a number of things
wrong with it :
* ) It was designed for csv and therefore its performance is highly
questionable .
* ) Indexes have not been implemented . This is because the files can
be traded in and out of the table directory without having to worry
about rebuilding anything .
* ) NULLs and " " are treated equally ( like a spreadsheet ) .
* ) There was in the beginning no point to anyone seeing this other
then me , so there is a good chance that I haven ' t quite documented
it well .
* ) Less design , more " make it work "
Now there are a few cool things with it :
* ) Errors can result in corrupted data files .
* ) Data files can be read by spreadsheets directly .
TODO :
* ) Move to a block system for larger files
* ) Error recovery , its all there , just need to finish it
* ) Document how the chains work .
- Brian
*/
#include <my_global.h>
#include "sql_priv.h"
#include "sql_class.h" // SSV
#include <mysql/psi/mysql_file.h>
#include "ha_tina.h"
#include "probes_mysql.h"
/*
uchar + uchar + ulonglong + ulonglong + ulonglong + ulonglong + uchar
*/
#define META_BUFFER_SIZE sizeof (uchar) + sizeof (uchar) + sizeof (ulonglong) \
+ sizeof (ulonglong) + sizeof (ulonglong) + sizeof (ulonglong) + sizeof (uchar)
#define TINA_CHECK_HEADER 254 // The number we use to determine corruption
#define BLOB_MEMROOT_ALLOC_SIZE 8192
/* The file extension */
#define CSV_EXT ".CSV" // The data file
#define CSN_EXT ".CSN" // Files used during repair and update
#define CSM_EXT ".CSM" // Meta file
struct ha_table_option_struct
{
bool ietf_quotes;
};
ha_create_table_option csv_table_option_list[]=
{
HA_TOPTION_BOOL("IETF_QUOTES" , ietf_quotes, 0 ),
HA_TOPTION_END
};
static TINA_SHARE *get_share(const char *table_name, TABLE *table);
static int free_share(TINA_SHARE *share);
static int read_meta_file(File meta_file, ha_rows *rows);
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);
/* Stuff for shares */
mysql_mutex_t tina_mutex;
static HASH tina_open_tables;
static handler *tina_create_handler(handlerton *hton,
TABLE_SHARE *table,
MEM_ROOT *mem_root);
/*****************************************************************************
* * TINA tables
*****************************************************************************/
/*
Used for sorting chains with qsort ( ) .
*/
int sort_set (const void *a_, const void *b_)
{
const tina_set *a= static_cast <const tina_set*>(a_);
const tina_set *b= static_cast <const tina_set*>(b_);
/*
We assume that intervals do not intersect . So , it is enought to compare
any two points . Here we take start of intervals for comparison .
*/
return ( a->begin > b->begin ? 1 : ( a->begin < b->begin ? -1 : 0 ) );
}
static const uchar *tina_get_key(const void *share_, size_t *length, my_bool)
{
const TINA_SHARE *share= static_cast <const TINA_SHARE *>(share_);
*length=share->table_name_length;
return reinterpret_cast <const uchar *>(share->table_name);
}
static PSI_memory_key csv_key_memory_tina_share;
static PSI_memory_key csv_key_memory_blobroot;
static PSI_memory_key csv_key_memory_tina_set;
static PSI_memory_key csv_key_memory_row;
#ifdef HAVE_PSI_INTERFACE
static PSI_mutex_key csv_key_mutex_tina, csv_key_mutex_TINA_SHARE_mutex;
static PSI_mutex_info all_tina_mutexes[]=
{
{ &csv_key_mutex_tina, "tina" , PSI_FLAG_GLOBAL},
{ &csv_key_mutex_TINA_SHARE_mutex, "TINA_SHARE::mutex" , 0 }
};
static PSI_file_key csv_key_file_metadata, csv_key_file_data,
csv_key_file_update;
static PSI_file_info all_tina_files[]=
{
{ &csv_key_file_metadata, "metadata" , 0 },
{ &csv_key_file_data, "data" , 0 },
{ &csv_key_file_update, "update" , 0 }
};
static PSI_memory_info all_tina_memory[]=
{
{ &csv_key_memory_tina_share, "TINA_SHARE" , PSI_FLAG_GLOBAL},
{ &csv_key_memory_blobroot, "blobroot" , 0 },
{ &csv_key_memory_tina_set, "tina_set" , 0 },
{ &csv_key_memory_row, "row" , 0 },
{ &csv_key_memory_Transparent_file, "Transparent_file" , 0 }
};
static void init_tina_psi_keys(void )
{
const char * category= "csv" ;
int count;
count= array_elements(all_tina_mutexes);
mysql_mutex_register(category, all_tina_mutexes, count);
count= array_elements(all_tina_files);
mysql_file_register(category, all_tina_files, count);
count= array_elements(all_tina_memory);
mysql_memory_register(category, all_tina_memory, count);
}
#endif /* HAVE_PSI_INTERFACE */
/*
If frm_error ( ) is called in table . cc this is called to find out what file
extensions exist for this handler .
*/
static const char *ha_tina_exts[] = {
CSN_EXT,
CSV_EXT,
CSM_EXT,
NullS
};
static int tina_init_func(void *p)
{
handlerton *tina_hton;
#ifdef HAVE_PSI_INTERFACE
init_tina_psi_keys();
#endif
tina_hton= static_cast <handlerton *>(p);
mysql_mutex_init(csv_key_mutex_tina, &tina_mutex, MY_MUTEX_INIT_FAST);
(void ) my_hash_init(csv_key_memory_tina_share, &tina_open_tables,
Lex_ident_table::charset_info(), 32 , 0 , 0 , tina_get_key,
0 , 0 );
tina_hton->db_type= DB_TYPE_CSV_DB;
tina_hton->create= tina_create_handler;
tina_hton->flags= (HTON_CAN_RECREATE | HTON_SUPPORT_LOG_TABLES |
HTON_NO_PARTITION);
tina_hton->tablefile_extensions= ha_tina_exts;
tina_hton->table_options= csv_table_option_list;
return 0 ;
}
static int tina_done_func(void *p)
{
my_hash_free(&tina_open_tables);
mysql_mutex_destroy(&tina_mutex);
return 0 ;
}
/*
Simple lock controls .
*/
static TINA_SHARE *get_share(const char *table_name, TABLE *table)
{
TINA_SHARE *share;
char meta_file_name[FN_REFLEN];
MY_STAT file_stat; /* Stat information for the data file */
char *tmp_name;
uint length;
mysql_mutex_lock(&tina_mutex);
length=(uint) strlen(table_name);
/*
If share is not present in the hash , create a new share and
initialize its members .
*/
if (!(share=(TINA_SHARE*) my_hash_search(&tina_open_tables, (uchar*)
table_name, length)))
{
if (!my_multi_malloc(csv_key_memory_tina_share, MYF(MY_WME | MY_ZEROFILL),
&share, sizeof (*share), &tmp_name, length+1 , NullS))
{
mysql_mutex_unlock(&tina_mutex);
return NULL;
}
share->use_count= 0 ;
share->is_log_table= FALSE ;
share->table_name_length= length;
share->table_name= tmp_name;
share->crashed= FALSE ;
share->rows_recorded= 0 ;
share->update_file_opened= FALSE ;
share->tina_write_opened= FALSE ;
share->data_file_version= 0 ;
strmov(share->table_name, table_name);
fn_format(share->data_file_name, table_name, "" , CSV_EXT,
MY_REPLACE_EXT|MY_UNPACK_FILENAME);
fn_format(meta_file_name, table_name, "" , CSM_EXT,
MY_REPLACE_EXT|MY_UNPACK_FILENAME);
if (mysql_file_stat(csv_key_file_data,
share->data_file_name, &file_stat, MYF(MY_WME)) == NULL)
goto error;
share->saved_data_file_length= file_stat.st_size;
if (my_hash_insert(&tina_open_tables, (uchar*) share))
goto error;
thr_lock_init(&share->lock);
mysql_mutex_init(csv_key_mutex_TINA_SHARE_mutex,
&share->mutex, MY_MUTEX_INIT_FAST);
/*
Open or create the meta file . In the latter case , we ' ll get
an error during read_meta_file and mark the table as crashed .
Usually this will result in auto - repair , and we will get a good
meta - file in the end .
*/
if (((share->meta_file= mysql_file_open(csv_key_file_metadata,
meta_file_name,
O_RDWR|O_CREAT,
MYF(MY_WME))) == -1 ) ||
read_meta_file(share->meta_file, &share->rows_recorded))
share->crashed= TRUE ;
}
share->use_count++;
mysql_mutex_unlock(&tina_mutex);
return share;
error:
mysql_mutex_unlock(&tina_mutex);
my_free(share);
return NULL;
}
/*
Read CSV meta - file
SYNOPSIS
read_meta_file ( )
meta_file The meta - file filedes
ha_rows Pointer to the var we use to store rows count .
These are read from the meta - file .
DESCRIPTION
Read the meta - file info . For now we are only interested in
rows count , crashed bit and magic number .
RETURN
0 - OK
non - zero - error occurred
*/
static int read_meta_file(File meta_file, ha_rows *rows)
{
uchar meta_buffer[META_BUFFER_SIZE];
uchar *ptr= meta_buffer;
DBUG_ENTER("ha_tina::read_meta_file" );
mysql_file_seek(meta_file, 0 , MY_SEEK_SET, MYF(0 ));
if (mysql_file_read(meta_file, (uchar*)meta_buffer, META_BUFFER_SIZE, 0 )
!= META_BUFFER_SIZE)
DBUG_RETURN(my_errno= HA_ERR_CRASHED_ON_USAGE);
/*
Parse out the meta data , we ignore version at the moment
*/
ptr+= sizeof (uchar)*2 ; // Move past header
*rows= (ha_rows)uint8korr(ptr);
ptr+= sizeof (ulonglong); // Move past rows
/*
Move past check_point , auto_increment and forced_flushes fields .
They are present in the format , but we do not use them yet .
*/
ptr+= 3 *sizeof (ulonglong);
/* check crashed bit and magic number */
if ((meta_buffer[0 ] != (uchar)TINA_CHECK_HEADER) ||
((bool )(*ptr)== TRUE ))
DBUG_RETURN(my_errno= HA_ERR_CRASHED_ON_USAGE);
mysql_file_sync(meta_file, MYF(MY_WME));
DBUG_RETURN(0 );
}
/*
Write CSV meta - file
SYNOPSIS
write_meta_file ( )
meta_file The meta - file filedes
ha_rows The number of rows we have in the datafile .
dirty A flag , which marks whether we have a corrupt table
DESCRIPTION
Write meta - info the the file . Only rows count , crashed bit and
magic number matter now .
RETURN
0 - OK
non - zero - error occurred
*/
static int write_meta_file(File meta_file, ha_rows rows, bool dirty)
{
uchar meta_buffer[META_BUFFER_SIZE];
uchar *ptr= meta_buffer;
DBUG_ENTER("ha_tina::write_meta_file" );
*ptr= (uchar)TINA_CHECK_HEADER;
ptr+= sizeof (uchar);
*ptr= (uchar)TINA_VERSION;
ptr+= sizeof (uchar);
int8store(ptr, (ulonglong)rows);
ptr+= sizeof (ulonglong);
memset(ptr, 0 , 3 *sizeof (ulonglong));
/*
Skip over checkpoint , autoincrement and forced_flushes fields .
We ' ll need them later .
*/
ptr+= 3 *sizeof (ulonglong);
*ptr= (uchar)dirty;
mysql_file_seek(meta_file, 0 , MY_SEEK_SET, MYF(0 ));
if (mysql_file_write(meta_file, (uchar *)meta_buffer, META_BUFFER_SIZE, 0 )
!= META_BUFFER_SIZE)
DBUG_RETURN(-1 );
mysql_file_sync(meta_file, MYF(MY_WME));
DBUG_RETURN(0 );
}
bool ha_tina::check_and_repair(THD *thd)
{
HA_CHECK_OPT check_opt;
DBUG_ENTER("ha_tina::check_and_repair" );
check_opt.init();
DBUG_RETURN(repair(thd, &check_opt));
}
int ha_tina::init_tina_writer()
{
DBUG_ENTER("ha_tina::init_tina_writer" );
/*
Mark the file as crashed . We will set the flag back when we close
the file . In the case of the crash it will remain marked crashed ,
which enforce recovery .
*/
(void )write_meta_file(share->meta_file, share->rows_recorded, TRUE );
if ((share->tina_write_filedes=
mysql_file_open(csv_key_file_data,
share->data_file_name, O_RDWR|O_APPEND,
MYF(MY_WME))) == -1 )
{
DBUG_PRINT("info" , ("Could not open tina file writes" ));
share->crashed= TRUE ;
DBUG_RETURN(my_errno ? my_errno : -1 );
}
share->tina_write_opened= TRUE ;
DBUG_RETURN(0 );
}
bool ha_tina::is_crashed() const
{
DBUG_ENTER("ha_tina::is_crashed" );
DBUG_RETURN(share->crashed);
}
/*
Free lock controls .
*/
static int free_share(TINA_SHARE *share)
{
DBUG_ENTER("ha_tina::free_share" );
mysql_mutex_lock(&tina_mutex);
int result_code= 0 ;
if (!--share->use_count){
/* Write the meta file. Mark it as crashed if needed. */
if (share->meta_file != -1 )
{
(void )write_meta_file(share->meta_file, share->rows_recorded,
share->crashed ? TRUE :FALSE );
if (mysql_file_close(share->meta_file, MYF(0 )))
result_code= 1 ;
}
if (share->tina_write_opened)
{
if (mysql_file_close(share->tina_write_filedes, MYF(0 )))
result_code= 1 ;
share->tina_write_opened= FALSE ;
}
my_hash_delete(&tina_open_tables, (uchar*) share);
thr_lock_delete(&share->lock);
mysql_mutex_destroy(&share->mutex);
my_free(share);
}
mysql_mutex_unlock(&tina_mutex);
DBUG_RETURN(result_code);
}
/*
This function finds the end of a line and returns the length
of the line ending .
We support three kinds of line endings :
' \ r ' - - Old Mac OS line ending
' \ n ' - - Traditional Unix and Mac OS X line ending
' \ r ' ' \ n ' - - DOS \ Windows line ending
*/
my_off_t find_eoln_buff(Transparent_file *data_buff, my_off_t begin,
my_off_t end, int *eoln_len)
{
*eoln_len= 0 ;
for (my_off_t x= begin; x < end; x++)
{
/* Unix (includes Mac OS X) */
if (data_buff->get_value(x) == '\n' )
*eoln_len= 1 ;
else
if (data_buff->get_value(x) == '\r' ) // Mac or Dos
{
/* old Mac line ending */
if (x + 1 == end || (data_buff->get_value(x + 1 ) != '\n' ))
*eoln_len= 1 ;
else // DOS style ending
*eoln_len= 2 ;
}
if (*eoln_len) // end of line was found
return x;
}
return 0 ;
}
static handler *tina_create_handler(handlerton *hton,
TABLE_SHARE *table,
MEM_ROOT *mem_root)
{
return new (mem_root) ha_tina(hton, table);
}
ha_tina::ha_tina(handlerton *hton, TABLE_SHARE *table_arg)
:handler(hton, table_arg),
/*
These definitions are found in handler . h
They are not probably completely right .
*/
current_position(0 ), next_position(0 ), local_saved_data_file_length(0 ),
file_buff(0 ), chain_alloced(0 ), chain_size(DEFAULT_CHAIN_LENGTH),
local_data_file_version(0 ), records_is_known(0 )
{
/* Set our original buffers from pre-allocated memory */
buffer.set((char *)byte_buffer, IO_SIZE, &my_charset_bin);
chain= chain_buffer;
file_buff= new Transparent_file();
init_alloc_root(csv_key_memory_blobroot, &blobroot, BLOB_MEMROOT_ALLOC_SIZE,
0 , MYF(0 ));
}
/*
Encode a buffer into the quoted format .
*/
int ha_tina::encode_quote(const uchar *buf)
{
char attribute_buffer[1024 ];
String attribute(attribute_buffer, sizeof (attribute_buffer),
&my_charset_bin);
bool ietf_quotes= option_struct->ietf_quotes;
MY_BITMAP *org_bitmap= dbug_tmp_use_all_columns(table, &table->read_set);
buffer.length(0 );
for (Field **field=table->field ; *field ; field++)
{
const char *ptr;
const char *end_ptr;
const bool was_null= (*field)->is_null();
/*
assistance for backwards compatibility in production builds .
note : this will not work for ENUM columns .
*/
if (was_null)
{
(*field)->set_default();
(*field)->set_notnull();
}
(*field)->val_str(&attribute,&attribute);
if (was_null)
(*field)->set_null();
if ((*field)->str_needs_quotes())
{
ptr= attribute.ptr();
end_ptr= attribute.length() + ptr;
/*
Ensure that buffer is big enough . This will also speed things up
as we don ' t have to do any new allocation in the loop below
*/
if (buffer.realloc(buffer.length() + attribute.length()*2 +2 ))
return 0 ; // Failure
buffer.append('"' );
for (; ptr < end_ptr; ptr++)
{
if (*ptr == '"' )
{
buffer.append(ietf_quotes ? '"' : '\\' );
buffer.append('"' );
}
else if (*ptr == '\r' )
{
buffer.append('\\' );
buffer.append('r' );
}
else if (*ptr == '\\' )
{
buffer.append('\\' );
buffer.append('\\' );
}
else if (*ptr == '\n' )
{
buffer.append('\\' );
buffer.append('n' );
}
else
buffer.append(*ptr);
}
buffer.append('"' );
}
else
{
buffer.append(attribute);
}
buffer.append(',' );
}
// Remove the comma, add a line feed
buffer.length(buffer.length() - 1 );
buffer.append('\n' );
//buffer.replace(buffer.length(), 0, "\n", 1);
dbug_tmp_restore_column_map(&table->read_set, org_bitmap);
return (buffer.length());
}
/*
chain_append ( ) adds delete positions to the chain that we use to keep
track of space . Then the chain will be used to cleanup " holes " , occurred
due to deletes and updates .
*/
int ha_tina::chain_append()
{
if ( chain_ptr != chain && (chain_ptr -1 )->end == current_position)
(chain_ptr -1 )->end= next_position;
else
{
/* We set up for the next position */
if ((off_t)(chain_ptr - chain) == (chain_size -1 ))
{
my_off_t location= chain_ptr - chain;
chain_size += DEFAULT_CHAIN_LENGTH;
if (chain_alloced)
{
if ((chain= (tina_set *) my_realloc(csv_key_memory_tina_set,
(uchar*)chain,
chain_size * sizeof (tina_set),
MYF(MY_WME))) == NULL)
return -1 ;
}
else
{
tina_set *ptr= (tina_set *) my_malloc(csv_key_memory_tina_set,
chain_size * sizeof (tina_set),
MYF(MY_WME));
memcpy(ptr, chain, DEFAULT_CHAIN_LENGTH * sizeof (tina_set));
chain= ptr;
chain_alloced++;
}
chain_ptr= chain + location;
}
chain_ptr->begin= current_position;
chain_ptr->end= next_position;
chain_ptr++;
}
return 0 ;
}
/*
Scans for a row .
*/
int ha_tina::find_current_row(uchar *buf)
{
my_off_t end_offset, curr_offset= current_position;
int eoln_len;
int error;
bool read_all;
bool ietf_quotes= option_struct->ietf_quotes;
DBUG_ENTER("ha_tina::find_current_row" );
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 .
*/
if ((end_offset=
find_eoln_buff(file_buff, current_position,
local_saved_data_file_length, &eoln_len)) == 0 )
DBUG_RETURN(HA_ERR_END_OF_FILE);
/* We must read all columns in case a table is opened for update */
read_all= !bitmap_is_clear_all(table->write_set);
/* Avoid asserts in ::store() for columns that are not going to be updated */
MY_BITMAP *org_bitmap= dbug_tmp_use_all_columns(table, &table->write_set);
error= HA_ERR_CRASHED_ON_USAGE;
memset(buf, 0 , table->s->null_bytes);
/*
Parse the line obtained using the following algorithm
BEGIN
1 ) Store the EOL ( end of line ) for the current row
2 ) Until all the fields in the current query have not been
filled
2 . 1 ) If the current character is a quote
2 . 1 . 1 ) Until EOL has not been reached
a ) If end of current field is reached , move
to next field and jump to step 2 . 3
b ) If current character is a \ \ handle
\ \ n , \ \ r , \ \ , and \ \ " if not in ietf_quotes mode
c ) if in ietf_quotes mode and the current character is
a " , handle " "
d ) else append the current character into the buffer
before checking that EOL has not been reached .
2 . 2 ) If the current character does not begin with a quote
2 . 2 . 1 ) Until EOL has not been reached
a ) If the end of field has been reached move to the
next field and jump to step 2 . 3
b ) If current character begins with \ \ handle
\ \ n , \ \ r , \ \ , \ \ "
c ) else append the current character into the buffer
before checking that EOL has not been reached .
2 . 3 ) Store the current field value and jump to 2 )
TERMINATE
*/
for (Field **field=table->field ; *field ; field++)
{
char curr_char;
buffer.length(0 );
if (curr_offset >= end_offset)
goto err;
curr_char= file_buff->get_value(curr_offset);
/* Handle the case where the first character is a quote */
if (curr_char == '"' )
{
/* Increment past the first quote */
curr_offset++;
/* Loop through the row to extract the values for the current field */
for ( ; curr_offset < end_offset; curr_offset++)
{
curr_char= file_buff->get_value(curr_offset);
/* check for end of the current field */
if (curr_char == '"' &&
(curr_offset == end_offset - 1 ||
file_buff->get_value(curr_offset + 1 ) == ',' ))
{
/* Move past the , and the " */
curr_offset+= 2 ;
break ;
}
if (ietf_quotes && curr_char == '"'
&& file_buff->get_value(curr_offset + 1 ) == '"' )
{
/* Embedded IETF quote */
curr_offset++;
buffer.append('"' );
}
else if (curr_char == '\\' && curr_offset != (end_offset - 1 ))
{
/* A quote followed by something else than a comma, end of line, or
( in IETF mode ) another quote will be handled as a regular
character. */
curr_offset++;
curr_char= file_buff->get_value(curr_offset);
if (curr_char == 'r' )
buffer.append('\r' );
else if (curr_char == 'n' )
buffer.append('\n' );
else if (curr_char == '\\' || (!ietf_quotes && curr_char == '"' ))
buffer.append(curr_char);
else /* This could only happed with an externally created file */
{
buffer.append('\\' );
buffer.append(curr_char);
}
}
else // ordinary symbol
{
/*
If we are at final symbol and no last quote was found = >
we are working with a damaged file .
*/
if (curr_offset == end_offset - 1 )
goto err;
buffer.append(curr_char);
}
}
}
else
{
for ( ; curr_offset < end_offset; curr_offset++)
{
curr_char= file_buff->get_value(curr_offset);
/* Move past the ,*/
if (curr_char == ',' )
{
curr_offset++;
break ;
}
if (curr_char == '\\' && curr_offset != (end_offset - 1 ))
{
curr_offset++;
curr_char= file_buff->get_value(curr_offset);
if (curr_char == 'r' )
buffer.append('\r' );
else if (curr_char == 'n' )
buffer.append('\n' );
else if (curr_char == '\\' || curr_char == '"' )
buffer.append(curr_char);
else /* This could only happed with an externally created file */
{
buffer.append('\\' );
buffer.append(curr_char);
}
}
else
{
/*
We are at the final symbol and a quote was found for the
unquoted field = > We are working with a damaged field .
*/
if (curr_offset == end_offset - 1 && curr_char == '"' )
goto err;
buffer.append(curr_char);
}
}
}
if (read_all || bitmap_is_set(table->read_set, (*field)->field_index))
{
bool is_enum= ((*field)->real_type() == MYSQL_TYPE_ENUM);
/*
Here CHECK_FIELD_WARN checks that all values in the csv file are valid
which is normally the case , if they were written by
INSERT - > ha_tina : : write_row . ' 0 ' values on ENUM fields are considered
invalid by Field_enum : : store ( ) but it can store them on INSERT anyway .
Thus , for enums we silence the warning , as it doesn ' t really mean
an invalid value .
*/
if ((*field)->store_text(buffer.ptr(), buffer.length(), buffer.charset(),
is_enum ? CHECK_FIELD_IGNORE : CHECK_FIELD_WARN))
{
if (!is_enum)
goto err;
}
if ((*field)->flags & BLOB_FLAG)
{
Field_blob *blob= *(Field_blob**) field;
uchar *src, *tgt;
uint length, packlength;
packlength= blob->pack_length_no_ptr();
length= blob->get_length(blob->ptr);
memcpy(&src, blob->ptr + packlength, sizeof (char *));
if (src)
{
tgt= (uchar*) alloc_root(&blobroot, length);
bmove(tgt, src, length);
memcpy(blob->ptr + packlength, &tgt, sizeof (char *));
}
}
}
}
next_position= end_offset + eoln_len;
error= 0 ;
err:
dbug_tmp_restore_column_map(&table->write_set, org_bitmap);
DBUG_RETURN(error);
}
/*
Three functions below are needed to enable concurrent insert functionality
for CSV engine . For more details see mysys / thr_lock . c
*/
my_bool tina_get_status(void * param, my_bool concurrent_insert)
{
ha_tina *tina= (ha_tina*) param;
tina->get_status();
return 0 ;
}
void tina_update_status(void * param)
{
ha_tina *tina= (ha_tina*) param;
tina->update_status();
}
/* this should exist and return 0 for concurrent insert to work */
my_bool tina_check_status(void * param)
{
return 0 ;
}
/*
Save the state of the table
SYNOPSIS
get_status ( )
DESCRIPTION
This function is used to retrieve the file length . During the lock
phase of concurrent insert . For more details see comment to
ha_tina : : update_status below .
*/
void ha_tina::get_status()
{
if (share->is_log_table)
{
/*
We have to use mutex to follow pthreads memory visibility
rules for share - > saved_data_file_length
*/
mysql_mutex_lock(&share->mutex);
local_saved_data_file_length= share->saved_data_file_length;
mysql_mutex_unlock(&share->mutex);
return ;
}
local_saved_data_file_length= share->saved_data_file_length;
}
/*
Correct the state of the table . Called by unlock routines
before the write lock is released .
SYNOPSIS
update_status ( )
DESCRIPTION
When we employ concurrent insert lock , we save current length of the file
during the lock phase . We do not read further saved value , as we don ' t
want to interfere with undergoing concurrent insert . Writers update file
length info during unlock with update_status ( ) .
NOTE
For log tables concurrent insert works different . The reason is that
log tables are always opened and locked . And as they do not unlock
tables , the file length after writes should be updated in a different
way . For this purpose we need is_log_table flag . When this flag is set
we call update_status ( ) explicitly after each row write .
*/
void ha_tina::update_status()
{
/* correct local_saved_data_file_length for writers */
share->saved_data_file_length= local_saved_data_file_length;
}
/*
Open a database file . Keep in mind that tables are caches , so
this will not be called for every request . Any sort of positions
that need to be reset should be kept in the : : extra ( ) call .
*/
int ha_tina::open(const char *name, int mode, uint open_options)
{
DBUG_ENTER("ha_tina::open" );
if (!(share= get_share(name, table)))
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
if (share->crashed && !(open_options & HA_OPEN_FOR_REPAIR))
{
free_share(share);
DBUG_RETURN(my_errno);
}
local_data_file_version= share->data_file_version;
if ((data_file= mysql_file_open(csv_key_file_data,
share->data_file_name,
O_RDONLY, MYF(MY_WME))) == -1 )
{
free_share(share);
DBUG_RETURN(my_errno ? my_errno : -1 );
}
/*
Init locking . Pass handler object to the locking routines ,
so that they could save / update local_saved_data_file_length value
during locking . This is needed to enable concurrent inserts .
*/
thr_lock_data_init(&share->lock, &lock, (void *) this );
ref_length= sizeof (my_off_t);
init_alloc_root(csv_key_memory_blobroot, &blobroot, BLOB_MEMROOT_ALLOC_SIZE,
0 , MYF(0 ));
share->lock.get_status= tina_get_status;
share->lock.update_status= tina_update_status;
share->lock.check_status= tina_check_status;
DBUG_RETURN(0 );
}
/*
Close a database file . We remove ourselves from the shared structure .
If it is empty we destroy it .
*/
int ha_tina::close(void )
{
int rc= 0 ;
DBUG_ENTER("ha_tina::close" );
free_root(&blobroot, MYF(0 ));
rc= mysql_file_close(data_file, MYF(0 ));
DBUG_RETURN(free_share(share) || rc);
}
/*
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 ) .
*/
int ha_tina::write_row(const uchar * buf)
{
int size;
DBUG_ENTER("ha_tina::write_row" );
if (share->crashed)
DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
size= encode_quote(buf);
if (!share->tina_write_opened)
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)))
DBUG_RETURN(-1 );
/* update local copy of the max position to see our own changes */
local_saved_data_file_length+= size;
/* update shared info */
mysql_mutex_lock(&share->mutex);
share->rows_recorded++;
/* update status for the log tables */
if (share->is_log_table)
update_status();
mysql_mutex_unlock(&share->mutex);
stats.records++;
DBUG_RETURN(0 );
}
int ha_tina::open_update_temp_file_if_needed()
{
char updated_fname[FN_REFLEN];
if (!share->update_file_opened)
{
if ((update_temp_file=
mysql_file_create(csv_key_file_update,
fn_format(updated_fname, share->table_name,
"" , CSN_EXT,
MY_REPLACE_EXT | MY_UNPACK_FILENAME),
0 , O_RDWR | O_TRUNC, MYF(MY_WME))) < 0 )
return 1 ;
share->update_file_opened= TRUE ;
temp_file_length= 0 ;
}
return 0 ;
}
/*
This is called for an update .
Make sure you put in code to increment the auto increment .
Currently auto increment is not being
fixed since autoincrements have yet to be added to this table handler .
This will be called in a table scan right before the previous : : rnd_next ( )
call .
*/
int ha_tina::update_row(const uchar * old_data, const uchar * new_data)
{
int size;
int rc= -1 ;
DBUG_ENTER("ha_tina::update_row" );
size= encode_quote(new_data);
/*
During update we mark each updating record as deleted
( see the chain_append ( ) ) then write new one to the temporary data file .
At the end of the sequence in the rnd_end ( ) we append all non - marked
records from the data file to the temporary data file then rename it .
The temp_file_length is used to calculate new data file length .
*/
if (chain_append())
goto err;
if (open_update_temp_file_if_needed())
goto err;
if (mysql_file_write(update_temp_file, (uchar*)buffer.ptr(), size,
MYF(MY_WME | MY_NABP)))
goto err;
temp_file_length+= size;
rc= 0 ;
/* UPDATE should never happen on the log tables */
DBUG_ASSERT(!share->is_log_table);
err:
DBUG_PRINT("info" ,("rc = %d" , rc));
DBUG_RETURN(rc);
}
/*
Deletes a row . First the database will find the row , and then call this
method . In the case of a table scan , the previous call to this will be
the : : rnd_next ( ) that found this row .
The exception to this is an ORDER BY . This will cause the table handler
to walk the table noting the positions of all rows that match a query .
The table will then be deleted / positioned based on the ORDER ( so RANDOM ,
DESC , ASC ) .
*/
int ha_tina::delete_row(const uchar * buf)
{
DBUG_ENTER("ha_tina::delete_row" );
if (chain_append())
DBUG_RETURN(-1 );
stats.records--;
/* Update shared info */
DBUG_ASSERT(share->rows_recorded);
mysql_mutex_lock(&share->mutex);
share->rows_recorded--;
mysql_mutex_unlock(&share->mutex);
/* DELETE should never happen on the log table */
DBUG_ASSERT(!share->is_log_table);
DBUG_RETURN(0 );
}
/**
@ brief Initialize the data file .
@ details Compare the local version of the data file with the shared one .
If they differ , there are some changes behind and we have to reopen
the data file to make the changes visible .
Call @ c file_buff - > init_buff ( ) at the end to read the beginning of the
data file into buffer .
@ retval 0 OK .
@ retval 1 There was an error .
*/
int ha_tina::init_data_file()
{
if (local_data_file_version != share->data_file_version)
{
local_data_file_version= share->data_file_version;
if (mysql_file_close(data_file, MYF(0 )) ||
(data_file= mysql_file_open(csv_key_file_data,
share->data_file_name, O_RDONLY,
MYF(MY_WME))) == -1 )
return my_errno ? my_errno : -1 ;
}
file_buff->init_buff(data_file);
return 0 ;
}
/*
All table scans call this first .
The order of a table scan is :
ha_tina : : store_lock
ha_tina : : external_lock
ha_tina : : info
ha_tina : : rnd_init
ha_tina : : extra
ENUM HA_EXTRA_CACHE Cache record in HA_rrnd ( )
ha_tina : : rnd_next
ha_tina : : rnd_next
ha_tina : : rnd_next
ha_tina : : rnd_next
ha_tina : : rnd_next
ha_tina : : rnd_next
ha_tina : : rnd_next
ha_tina : : rnd_next
ha_tina : : rnd_next
ha_tina : : extra
ENUM HA_EXTRA_NO_CACHE End cacheing of records ( def )
ha_tina : : external_lock
ha_tina : : extra
ENUM HA_EXTRA_RESET Reset database to after open
Each call to : : rnd_next ( ) represents a row returned in the can . When no more
rows can be returned , rnd_next ( ) returns a value of HA_ERR_END_OF_FILE .
The : : info ( ) call is just for the optimizer .
*/
int ha_tina::rnd_init(bool scan)
{
DBUG_ENTER("ha_tina::rnd_init" );
/* set buffer to the beginning of the file */
if (share->crashed || init_data_file())
DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
current_position= next_position= 0 ;
stats.records= 0 ;
records_is_known= found_end_of_file= 0 ;
chain_ptr= chain;
DBUG_RETURN(0 );
}
/*
: : rnd_next ( ) does all the heavy lifting for a table scan . You will need to
populate * buf with the correct field data . You can walk the field to
determine at what position you should store the data ( take a look at how
: : find_current_row ( ) works ) . The structure is something like :
0 Foo Dog Friend
The first offset is for the first attribute . All space before that is
reserved for null count .
Basically this works as a mask for which rows are nulled ( compared to just
empty ) .
This table handler doesn ' t do nulls and does not know the difference between
NULL and " " . This is ok since this table handler is for spreadsheets and
they don ' t know about them either : )
*/
int ha_tina::rnd_next(uchar *buf)
{
int rc;
DBUG_ENTER("ha_tina::rnd_next" );
MYSQL_READ_ROW_START(table_share->db.str, table_share->table_name.str,
TRUE );
if (share->crashed)
{
rc= HA_ERR_CRASHED_ON_USAGE;
goto end;
}
current_position= next_position;
/* don't scan an empty file */
if (!local_saved_data_file_length)
{
rc= HA_ERR_END_OF_FILE;
goto end;
}
if ((rc= find_current_row(buf)))
goto end;
stats.records++;
rc= 0 ;
end:
found_end_of_file= (rc == HA_ERR_END_OF_FILE);
MYSQL_READ_ROW_DONE(rc);
DBUG_RETURN(rc);
}
/*
In the case of an order by rows will need to be sorted .
: : position ( ) is called after each call to : : rnd_next ( ) ,
the data it stores is to a byte array . You can store this
data via my_store_ptr ( ) . ref_length is a variable defined to the
class that is the sizeof ( ) of position being stored . In our case
its just a position . Look at the bdb code if you want to see a case
where something other then a number is stored .
*/
void ha_tina::position(const uchar *record)
{
DBUG_ENTER("ha_tina::position" );
my_store_ptr(ref, ref_length, current_position);
DBUG_VOID_RETURN;
}
/*
Used to fetch a row from a position stored with : : position ( ) .
my_get_ptr ( ) retrieves the data for you .
*/
int ha_tina::rnd_pos(uchar * buf, uchar *pos)
{
int rc;
DBUG_ENTER("ha_tina::rnd_pos" );
MYSQL_READ_ROW_START(table_share->db.str, table_share->table_name.str,
FALSE );
current_position= my_get_ptr(pos,ref_length);
rc= find_current_row(buf);
MYSQL_READ_ROW_DONE(rc);
DBUG_RETURN(rc);
}
/*
: : info ( ) is used to return information to the optimizer .
Currently this table handler doesn ' t implement most of the fields
really needed . SHOW also makes use of this data
*/
int ha_tina::info(uint flag)
{
DBUG_ENTER("ha_tina::info" );
/* This is a lie, but you don't want the optimizer to see zero or 1 */
if (!records_is_known && stats.records < 2 )
stats.records= 2 ;
DBUG_RETURN(0 );
}
/*
Grab bag of flags that are sent to the able handler every so often .
HA_EXTRA_RESET and HA_EXTRA_RESET_STATE are the most frequently called .
You are not required to implement any of these .
*/
int ha_tina::extra(enum ha_extra_function operation)
{
DBUG_ENTER("ha_tina::extra" );
switch (operation) {
case HA_EXTRA_MARK_AS_LOG_TABLE:
{
mysql_mutex_lock(&share->mutex);
share->is_log_table= TRUE ;
mysql_mutex_unlock(&share->mutex);
}
break ;
case HA_EXTRA_FLUSH:
mysql_mutex_lock(&share->mutex);
if (share->tina_write_opened)
{
(void )write_meta_file(share->meta_file, share->rows_recorded,
share->crashed ? TRUE :FALSE );
mysql_file_close(share->tina_write_filedes, MYF(0 ));
share->tina_write_opened= FALSE ;
}
mysql_mutex_unlock(&share->mutex);
break ;
default :
break ;
}
DBUG_RETURN(0 );
}
/*
Set end_pos to the last valid byte of continuous area , closest
to the given " hole " , stored in the buffer . " Valid " here means ,
not listed in the chain of deleted records ( " holes " ) .
*/
bool ha_tina::get_write_pos(my_off_t *end_pos, tina_set *closest_hole)
{
if (closest_hole == chain_ptr) /* no more chains */
*end_pos= file_buff->end();
else
*end_pos= MY_MIN(file_buff->end(), closest_hole->begin);
return (closest_hole != chain_ptr) && (*end_pos == closest_hole->begin);
}
/*
Called after each table scan . In particular after deletes ,
and updates . In the last case we employ chain of deleted
slots to clean up all of the dead space we have collected while
performing deletes / updates .
*/
int ha_tina::rnd_end()
{
char updated_fname[FN_REFLEN];
my_off_t file_buffer_start= 0 ;
DBUG_ENTER("ha_tina::rnd_end" );
records_is_known= found_end_of_file;
if ((chain_ptr - chain) > 0 )
{
tina_set *ptr= chain;
/*
Re - read the beginning of a file ( as the buffer should point to the
end of file after the scan ) .
*/
file_buff->init_buff(data_file);
/*
The sort is needed when there were updates / deletes with random orders .
It sorts so that we move the first blocks to the beginning .
*/
my_qsort(chain, (size_t)(chain_ptr - chain), sizeof (tina_set),
(qsort_cmp)sort_set);
my_off_t write_begin= 0 , write_end;
/* create the file to write updated table if it wasn't yet created */
if (open_update_temp_file_if_needed())
DBUG_RETURN(-1 );
/* write the file with updated info */
while ((file_buffer_start != (my_off_t)-1 )) // while not end of file
{
bool in_hole= get_write_pos(&write_end, ptr);
my_off_t write_length= write_end - write_begin;
/* if there is something to write, write it */
if (write_length)
{
if (mysql_file_write(update_temp_file,
(uchar*) (file_buff->ptr() +
(write_begin - file_buff->start())),
(size_t)write_length, MYF(MY_WME+MY_NABP)))
goto error;
temp_file_length+= write_length;
}
if (in_hole)
{
/* skip hole */
while (file_buff->end() <= ptr->end &&
file_buffer_start != (my_off_t)-1 )
file_buffer_start= file_buff->read_next();
write_begin= ptr->end;
ptr++;
}
else
write_begin= write_end;
if (write_end == file_buff->end())
file_buffer_start= file_buff->read_next(); /* shift the buffer */
}
if (mysql_file_sync(update_temp_file, MYF(MY_WME)) ||
mysql_file_close(update_temp_file, MYF(0 )))
DBUG_RETURN(-1 );
share->update_file_opened= FALSE ;
if (share->tina_write_opened)
{
if (mysql_file_close(share->tina_write_filedes, MYF(0 )))
DBUG_RETURN(-1 );
/*
Mark that the writer fd is closed , so that init_tina_writer ( )
will reopen it later .
*/
share->tina_write_opened= FALSE ;
}
/*
Close opened fildes ' s . Then move updated file in place
of the old datafile .
*/
if (mysql_file_close(data_file, MYF(0 )) ||
mysql_file_delete(csv_key_file_data, share->data_file_name, MYF(0 )) ||
mysql_file_rename(csv_key_file_data,
fn_format(updated_fname, share->table_name,
"" , CSN_EXT,
MY_REPLACE_EXT | MY_UNPACK_FILENAME),
share->data_file_name, MYF(0 )))
DBUG_RETURN(-1 );
/* Open the file again */
if ((data_file= mysql_file_open(csv_key_file_data,
share->data_file_name,
O_RDONLY, MYF(MY_WME))) == -1 )
DBUG_RETURN(my_errno ? my_errno : -1 );
/*
As we reopened the data file , increase share - > data_file_version
in order to force other threads waiting on a table lock and
have already opened the table to reopen the data file .
That makes the latest changes become visible to them .
Update local_data_file_version as no need to reopen it in the
current thread .
*/
share->data_file_version++;
local_data_file_version= share->data_file_version;
/*
The datafile is consistent at this point and the write filedes is
closed , so nothing worrying will happen to it in case of a crash .
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;
}
DBUG_RETURN(0 );
error:
mysql_file_close(update_temp_file, MYF(0 ));
share->update_file_opened= FALSE ;
DBUG_RETURN(-1 );
}
/*
Repair CSV table in the case , it is crashed .
SYNOPSIS
repair ( )
thd The thread , performing repair
check_opt The options for repair . We do not use it currently .
DESCRIPTION
Scan the table looking for bad rows . If none were found ,
we mark file as a good one and return . If a bad row was encountered ,
we truncate the datafile up to the last good row .
If the file is empty , then do nothing and complete recovery .
TODO : Make repair more clever - it should try to recover subsequent
rows ( after the first bad one ) as well .
*/
int ha_tina::repair(THD* thd, HA_CHECK_OPT* check_opt)
{
char repaired_fname[FN_REFLEN];
uchar *buf;
File repair_file;
int rc;
ha_rows rows_repaired= 0 ;
my_off_t write_begin= 0 , write_end;
DBUG_ENTER("ha_tina::repair" );
/* empty file */
if (!share->saved_data_file_length)
goto end;
/* Don't assert in field::val() functions */
table->use_all_columns();
/* position buffer to the start of the file */
if (init_data_file())
DBUG_RETURN(HA_ERR_CRASHED_ON_REPAIR);
if (!(buf= (uchar*) my_malloc(csv_key_memory_row, table->s->reclength,
MYF(MY_WME))))
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
/*
Local_saved_data_file_length is initialized during the lock phase .
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;
/* set current position to the beginning of the file */
current_position= next_position= 0 ;
/* Read the file row-by-row. If everything is ok, repair is not needed. */
thd_inc_error_row(thd);
while (!(rc= find_current_row(buf)))
{
thd_inc_error_row(thd);
rows_repaired++;
current_position= next_position;
}
free_root(&blobroot, MYF(0 ));
my_free(buf);
if (rc == HA_ERR_END_OF_FILE)
{
/*
All rows were read ok until end of file , the file does not need repair .
If rows_recorded ! = rows_repaired , we should update rows_recorded value
to the current amount of rows .
*/
share->rows_recorded= rows_repaired;
goto end;
}
/*
Otherwise we ' ve encountered a bad row = > repair is needed .
Let us create a temporary file .
*/
if ((repair_file= mysql_file_create(csv_key_file_update,
fn_format(repaired_fname,
share->table_name,
"" , CSN_EXT,
MY_REPLACE_EXT|MY_UNPACK_FILENAME),
0 , O_RDWR | O_TRUNC, MYF(MY_WME))) < 0 )
DBUG_RETURN(HA_ERR_CRASHED_ON_REPAIR);
file_buff->init_buff(data_file);
/* we just truncated the file up to the first bad row. update rows count. */
share->rows_recorded= rows_repaired;
/* write repaired file */
while (1 )
{
write_end= MY_MIN(file_buff->end(), current_position);
if ((write_end - write_begin) &&
(mysql_file_write(repair_file, (uchar*)file_buff->ptr(),
(size_t) (write_end - write_begin), MYF(MY_WME+MY_NABP))))
DBUG_RETURN(-1 );
write_begin= write_end;
if (write_end== current_position)
break ;
else
file_buff->read_next(); /* shift the buffer */
}
/*
Close the files and rename repaired file to the datafile .
We have to close the files , as on Windows one cannot rename
a file , which descriptor is still open . EACCES will be returned
when trying to delete the " to " - file in mysql_file_rename ( ) .
*/
if (share->tina_write_opened)
{
/*
Data file might be opened twice , on table opening stage and
during write_row execution . We need to close both instances
to satisfy Win .
*/
if (mysql_file_close(share->tina_write_filedes, MYF(0 )))
DBUG_RETURN(my_errno ? my_errno : -1 );
share->tina_write_opened= FALSE ;
}
mysql_file_close(data_file, MYF(0 ));
mysql_file_close(repair_file, MYF(0 ));
if (mysql_file_rename(csv_key_file_data,
repaired_fname, share->data_file_name, MYF(0 )))
DBUG_RETURN(-1 );
/* Open the file again, it should now be repaired */
if ((data_file= mysql_file_open(csv_key_file_data,
share->data_file_name, O_RDWR|O_APPEND,
MYF(MY_WME))) == -1 )
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;
end:
share->crashed= FALSE ;
DBUG_RETURN(HA_ADMIN_OK);
}
/*
DELETE without WHERE calls this
*/
int ha_tina::delete_all_rows()
{
int rc;
DBUG_ENTER("ha_tina::delete_all_rows" );
if (!records_is_known)
DBUG_RETURN(my_errno=HA_ERR_WRONG_COMMAND);
if (!share->tina_write_opened)
if (init_tina_writer())
DBUG_RETURN(-1 );
/* Truncate the file to zero size */
rc= mysql_file_chsize(share->tina_write_filedes, 0 , 0 , MYF(MY_WME)) > 0 ;
stats.records=0 ;
/* Update shared info */
mysql_mutex_lock(&share->mutex);
share->rows_recorded= 0 ;
mysql_mutex_unlock(&share->mutex);
local_saved_data_file_length= 0 ;
DBUG_RETURN(rc);
}
int ha_tina::external_lock(THD *thd __attribute__((unused)), int lock_type)
{
if (lock_type==F_UNLCK && curr_lock_type == F_WRLCK)
update_status();
curr_lock_type= lock_type;
return 0 ;
}
/*
Called by the database to lock the table . Keep in mind that this
is an internal lock .
*/
THR_LOCK_DATA **ha_tina::store_lock(THD *thd,
THR_LOCK_DATA **to,
enum thr_lock_type lock_type)
{
if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK)
lock.type=lock_type;
*to++= &lock;
return to;
}
/*
Create a table . You do not want to leave the table open after a call to
this ( the database will call : : open ( ) if it needs to ) .
*/
int ha_tina::create(const char *name, TABLE *table_arg,
HA_CREATE_INFO *create_info)
{
char name_buff[FN_REFLEN];
File create_file;
DBUG_ENTER("ha_tina::create" );
/*
check columns
*/
for (Field **field= table_arg->s->field; *field; field++)
{
if ((*field)->real_maybe_null())
{
my_error(ER_CHECK_NOT_IMPLEMENTED, MYF(0 ), "nullable columns" );
DBUG_RETURN(HA_ERR_UNSUPPORTED);
}
}
if (create_info->data_file_name)
my_error(WARN_OPTION_IGNORED, ME_NOTE, "DATA DIRECTORY" );
if (create_info->index_file_name && table_arg->s->keys)
my_error(WARN_OPTION_IGNORED, ME_NOTE, "INDEX DIRECTORY" );
if ((create_file= mysql_file_create(csv_key_file_metadata,
fn_format(name_buff, name, "" , CSM_EXT,
MY_REPLACE_EXT|MY_UNPACK_FILENAME),
0 , O_RDWR | O_TRUNC, MYF(MY_WME))) < 0 )
DBUG_RETURN(-1 );
write_meta_file(create_file, 0 , FALSE );
mysql_file_close(create_file, MYF(0 ));
if ((create_file= mysql_file_create(csv_key_file_data,
fn_format(name_buff, name, "" , CSV_EXT,
MY_REPLACE_EXT|MY_UNPACK_FILENAME),
0 , O_RDWR | O_TRUNC, MYF(MY_WME))) < 0 )
DBUG_RETURN(-1 );
mysql_file_close(create_file, MYF(0 ));
DBUG_RETURN(0 );
}
int ha_tina::check(THD* thd, HA_CHECK_OPT* check_opt)
{
int rc= 0 ;
uchar *buf;
const char *old_proc_info;
ha_rows count= share->rows_recorded;
DBUG_ENTER("ha_tina::check" );
old_proc_info= thd_proc_info(thd, "Checking table" );
/* position buffer to the start of the file */
if (init_data_file())
DBUG_RETURN(HA_ERR_CRASHED);
if (!(buf= (uchar*) my_malloc(csv_key_memory_row, table->s->reclength,
MYF(MY_WME))))
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
/*
Local_saved_data_file_length is initialized during the lock phase .
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;
/* set current position to the beginning of the file */
current_position= next_position= 0 ;
/* Read the file row-by-row. If everything is ok, repair is not needed. */
while (!(rc= find_current_row(buf)))
{
thd_inc_error_row(thd);
count--;
current_position= next_position;
}
free_root(&blobroot, MYF(0 ));
my_free(buf);
thd_proc_info(thd, old_proc_info);
if ((rc != HA_ERR_END_OF_FILE) || count)
{
share->crashed= TRUE ;
DBUG_RETURN(HA_ADMIN_CORRUPT);
}
DBUG_RETURN(HA_ADMIN_OK);
}
int ha_tina::reset(void )
{
free_root(&blobroot, MYF(0 ));
return 0 ;
}
bool ha_tina::check_if_incompatible_data(HA_CREATE_INFO *info_arg,
uint table_changes)
{
if (info_arg->option_struct->ietf_quotes != option_struct->ietf_quotes)
return COMPATIBLE_DATA_NO;
return COMPATIBLE_DATA_YES;
}
struct st_mysql_storage_engine csv_storage_engine=
{ MYSQL_HANDLERTON_INTERFACE_VERSION };
maria_declare_plugin(csv)
{
MYSQL_STORAGE_ENGINE_PLUGIN,
&csv_storage_engine,
"CSV" ,
"Brian Aker, MySQL AB" ,
"Stores tables as CSV files" ,
PLUGIN_LICENSE_GPL,
tina_init_func, /* Plugin Init */
tina_done_func, /* Plugin Deinit */
0 x0100 /* 1.0 */,
NULL, /* status variables */
NULL, /* system variables */
"1.0" , /* string version */
MariaDB_PLUGIN_MATURITY_STABLE /* maturity */
}
maria_declare_plugin_end;
Messung V0.5 in Prozent C=93 H=88 G=90
¤ Dauer der Verarbeitung: 0.48 Sekunden
(vorverarbeitet am 2026-10-08)
¤
*© Formatika GbR, Deutschland