diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:07:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:07:14 +0000 |
commit | a175314c3e5827eb193872241446f2f8f5c9d33c (patch) | |
tree | cd3d60ca99ae00829c52a6ca79150a5b6e62528b /storage/csv | |
parent | Initial commit. (diff) | |
download | mariadb-10.5-9e4947182e0b875da38088fdd168e775f473b8ad.tar.xz mariadb-10.5-9e4947182e0b875da38088fdd168e775f473b8ad.zip |
Adding upstream version 1:10.5.12.upstream/1%10.5.12upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'storage/csv')
-rw-r--r-- | storage/csv/CMakeLists.txt | 17 | ||||
-rw-r--r-- | storage/csv/ha_tina.cc | 1836 | ||||
-rw-r--r-- | storage/csv/ha_tina.h | 180 | ||||
-rw-r--r-- | storage/csv/transparent_file.cc | 109 | ||||
-rw-r--r-- | storage/csv/transparent_file.h | 43 |
5 files changed, 2185 insertions, 0 deletions
diff --git a/storage/csv/CMakeLists.txt b/storage/csv/CMakeLists.txt new file mode 100644 index 00000000..3131ebd4 --- /dev/null +++ b/storage/csv/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. +# +# 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 + +SET(CSV_SOURCES ha_tina.cc ha_tina.h transparent_file.cc transparent_file.h) +MYSQL_ADD_PLUGIN(csv ${CSV_SOURCES} STORAGE_ENGINE MANDATORY) diff --git a/storage/csv/ha_tina.cc b/storage/csv/ha_tina.cc new file mode 100644 index 00000000..ec569fee --- /dev/null +++ b/storage/csv/ha_tina.cc @@ -0,0 +1,1836 @@ +/* Copyright (c) 2004, 2011, Oracle and/or its affiliates. + + 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 (tina_set *a, 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 uchar* tina_get_key(TINA_SHARE *share, size_t *length, + my_bool not_used __attribute__((unused))) +{ + *length=share->table_name_length; + return (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[] = { + CSV_EXT, + CSM_EXT, + CSN_EXT, + NullS +}; + +static int tina_init_func(void *p) +{ + handlerton *tina_hton; + +#ifdef HAVE_PSI_INTERFACE + init_tina_psi_keys(); +#endif + + tina_hton= (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, + system_charset_info, 32, 0, 0, (my_hash_get_key) + 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 counf, 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(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= table_share->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, + 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= table_share->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 ? my_errno : HA_ERR_CRASHED_ON_USAGE); + } + + 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 strucutre. + 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 Cash 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: + 0Foo 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 posiion 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 firts 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_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 + If the file is empty, change # of rows in the file and complete recovery. + Otherwise, 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. + + 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) + { + share->rows_recorded= 0; + 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. */ + 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)); + + 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_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 != + table_share->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 */ + 0x0100 /* 1.0 */, + NULL, /* status variables */ + NULL, /* system variables */ + "1.0", /* string version */ + MariaDB_PLUGIN_MATURITY_STABLE /* maturity */ +} +maria_declare_plugin_end; diff --git a/storage/csv/ha_tina.h b/storage/csv/ha_tina.h new file mode 100644 index 00000000..04318344 --- /dev/null +++ b/storage/csv/ha_tina.h @@ -0,0 +1,180 @@ +/* Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + + 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 */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <my_dir.h> +#include "transparent_file.h" + +#define DEFAULT_CHAIN_LENGTH 512 +/* + Version for file format. + 1 - Initial Version. That is, the version when the metafile was introduced. +*/ + +#define TINA_VERSION 1 + +typedef struct st_tina_share { + char *table_name; + char data_file_name[FN_REFLEN]; + uint table_name_length, use_count; + /* + Below flag is needed to make log tables work with concurrent insert. + For more details see comment to ha_tina::update_status. + */ + my_bool is_log_table; + /* + Here we save the length of the file for readers. This is updated by + inserts, updates and deletes. The var is initialized along with the + share initialization. + */ + my_off_t saved_data_file_length; + mysql_mutex_t mutex; + THR_LOCK lock; + bool update_file_opened; + bool tina_write_opened; + File meta_file; /* Meta file we use */ + File tina_write_filedes; /* File handler for readers */ + bool crashed; /* Meta file is crashed */ + ha_rows rows_recorded; /* Number of rows in tables */ + uint data_file_version; /* Version of the data file used */ +} TINA_SHARE; + +struct tina_set { + my_off_t begin; + my_off_t end; +}; + +class ha_tina final : public handler +{ + THR_LOCK_DATA lock; /* MySQL lock */ + TINA_SHARE *share; /* Shared lock info */ + 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 */ + my_off_t temp_file_length; + uchar byte_buffer[IO_SIZE]; + Transparent_file *file_buff; + File data_file; /* File handler for readers */ + File update_temp_file; + String buffer; + /* + The chain contains "holes" in the file, occurred because of + deletes/updates. It is used in rnd_end() to get rid of them + in the end of the query. + */ + tina_set chain_buffer[DEFAULT_CHAIN_LENGTH]; + tina_set *chain; + tina_set *chain_ptr; + uchar chain_alloced; + uint32 chain_size; + uint local_data_file_version; /* Saved version of the data file used */ + bool records_is_known, found_end_of_file; + MEM_ROOT blobroot; + +private: + int curr_lock_type; + + bool get_write_pos(my_off_t *end_pos, tina_set *closest_hole); + int open_update_temp_file_if_needed(); + int init_tina_writer(); + int init_data_file(); + +public: + ha_tina(handlerton *hton, TABLE_SHARE *table_arg); + ~ha_tina() + { + if (chain_alloced) + my_free(chain); + if (file_buff) + delete file_buff; + free_root(&blobroot, MYF(0)); + } + const char *index_type(uint inx) { return "NONE"; } + ulonglong table_flags() const + { + return (HA_NO_TRANSACTIONS | HA_REC_NOT_IN_SEQ | HA_NO_AUTO_INCREMENT | + HA_BINLOG_ROW_CAPABLE | HA_BINLOG_STMT_CAPABLE | HA_CAN_EXPORT | + HA_CAN_REPAIR | HA_SLOW_RND_POS); + } + ulong index_flags(uint idx, uint part, bool all_parts) const + { + /* + We will never have indexes so this will never be called(AKA we return + zero) + */ + return 0; + } + uint max_record_length() const { return HA_MAX_REC_LENGTH; } + uint max_keys() const { return 0; } + uint max_key_parts() const { return 0; } + uint max_key_length() const { return 0; } + /* + Called in test_quick_select to determine if indexes should be used. + */ + virtual double scan_time() { return (double) (stats.records+stats.deleted) / 20.0+10; } + /* The next method will never be called */ + virtual bool fast_key_read() { return 1;} + /* + TODO: return actual upper bound of number of records in the table. + (e.g. save number of records seen on full table scan and/or use file size + as upper bound) + */ + ha_rows estimate_rows_upper_bound() { return HA_POS_ERROR; } + + int open(const char *name, int mode, uint open_options); + int close(void); + int write_row(const uchar * buf); + int update_row(const uchar * old_data, const uchar * new_data); + int delete_row(const uchar * buf); + int rnd_init(bool scan=1); + int rnd_next(uchar *buf); + int rnd_pos(uchar * buf, uchar *pos); + bool check_and_repair(THD *thd); + int check(THD* thd, HA_CHECK_OPT* check_opt); + bool is_crashed() const; + int rnd_end(); + int repair(THD* thd, HA_CHECK_OPT* check_opt); + /* This is required for SQL layer to know that we support autorepair */ + bool auto_repair(int error) const + { return error == HA_ERR_CRASHED_ON_USAGE; } + bool auto_repair() const { return 1; } + void position(const uchar *record); + int info(uint); + int reset(); + int extra(enum ha_extra_function operation); + int delete_all_rows(void); + int create(const char *name, TABLE *form, HA_CREATE_INFO *create_info); + bool check_if_incompatible_data(HA_CREATE_INFO *info, + uint table_changes); + + int external_lock(THD *thd, int lock_type); + + THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to, + enum thr_lock_type lock_type); + + /* + These functions used to get/update status of the handler. + Needed to enable concurrent inserts. + */ + void get_status(); + void update_status(); + + /* The following methods were added just for TINA */ + int encode_quote(const uchar *buf); + int find_current_row(uchar *buf); + int chain_append(); +}; + diff --git a/storage/csv/transparent_file.cc b/storage/csv/transparent_file.cc new file mode 100644 index 00000000..443e61d0 --- /dev/null +++ b/storage/csv/transparent_file.cc @@ -0,0 +1,109 @@ +/* Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + + 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 */ + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#include <my_global.h> +#include "sql_priv.h" +#include <mysql/psi/mysql_file.h> +#include "transparent_file.h" +#include "my_sys.h" // MY_WME, MY_ALLOW_ZERO_PTR, MY_SEEK_SET + +PSI_memory_key csv_key_memory_Transparent_file; + +Transparent_file::Transparent_file() : lower_bound(0), buff_size(IO_SIZE) +{ + buff= (uchar *) my_malloc(csv_key_memory_Transparent_file, + buff_size*sizeof(uchar), MYF(MY_WME)); +} + +Transparent_file::~Transparent_file() +{ + my_free(buff); +} + +void Transparent_file::init_buff(File filedes_arg) +{ + filedes= filedes_arg; + /* read the beginning of the file */ + lower_bound= 0; + mysql_file_seek(filedes, 0, MY_SEEK_SET, MYF(0)); + if (filedes && buff) + upper_bound= mysql_file_read(filedes, buff, buff_size, MYF(0)); +} + +uchar *Transparent_file::ptr() +{ + return buff; +} + +my_off_t Transparent_file::start() +{ + return lower_bound; +} + +my_off_t Transparent_file::end() +{ + return upper_bound; +} + +my_off_t Transparent_file::read_next() +{ + size_t bytes_read; + + /* + No need to seek here, as the file managed by Transparent_file class + always points to upper_bound byte + */ + if ((bytes_read= mysql_file_read(filedes, buff, buff_size, MYF(0))) + == MY_FILE_ERROR) + return (my_off_t) -1; + + /* end of file */ + if (!bytes_read) + return (my_off_t) -1; + + lower_bound= upper_bound; + upper_bound+= bytes_read; + + return lower_bound; +} + + +char Transparent_file::get_value(my_off_t offset) +{ + size_t bytes_read; + + /* check boundaries */ + if ((lower_bound <= offset) && (((my_off_t) offset) < upper_bound)) + return buff[offset - lower_bound]; + + mysql_file_seek(filedes, offset, MY_SEEK_SET, MYF(0)); + /* read appropriate portion of the file */ + if ((bytes_read= mysql_file_read(filedes, buff, buff_size, + MYF(0))) == MY_FILE_ERROR) + return 0; + + lower_bound= offset; + upper_bound= lower_bound + bytes_read; + + /* end of file */ + if (upper_bound == (my_off_t) offset) + return 0; + + return buff[0]; +} diff --git a/storage/csv/transparent_file.h b/storage/csv/transparent_file.h new file mode 100644 index 00000000..545643d0 --- /dev/null +++ b/storage/csv/transparent_file.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2003, 2007 MySQL AB, 2009 Sun Microsystems, Inc. + Use is subject to license terms. + + 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 */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <my_dir.h> + +extern PSI_memory_key csv_key_memory_Transparent_file; + +class Transparent_file +{ + File filedes; + uchar *buff; /* in-memory window to the file or mmaped area */ + /* current window sizes */ + my_off_t lower_bound; + my_off_t upper_bound; + uint buff_size; + +public: + + Transparent_file(); + ~Transparent_file(); + + void init_buff(File filedes_arg); + uchar *ptr(); + my_off_t start(); + my_off_t end(); + char get_value (my_off_t offset); + my_off_t read_next(); +}; |