diff options
author | unknown <acurtis@xiphis.org> | 2005-12-21 10:26:39 -0800 |
---|---|---|
committer | unknown <acurtis@xiphis.org> | 2005-12-21 10:26:39 -0800 |
commit | 5459c9709e047e3424001504f6cb99265ea650ff (patch) | |
tree | f13afe1a04c3cdd5a7aa52065913ee888f3eccb2 /storage | |
parent | ec6121fb40519f75e5a6e5b2a77025db6f7b14c9 (diff) | |
parent | 613dd50a33ac3e64073abdbdae66ce3a93e69e30 (diff) | |
download | mariadb-git-5459c9709e047e3424001504f6cb99265ea650ff.tar.gz |
Merge xiphis.org:/home/antony/work2/mysql-5.1
into xiphis.org:/home/antony/work3/mysql-5.1-plugable-2
configure.in:
Auto merged
mysql-test/r/bdb.result:
Auto merged
mysql-test/r/information_schema.result:
Auto merged
mysql-test/t/bdb.test:
Auto merged
sql/Makefile.am:
Auto merged
sql/ha_archive.cc:
Auto merged
sql/ha_berkeley.cc:
Auto merged
sql/ha_federated.cc:
Auto merged
sql/ha_innodb.cc:
Auto merged
sql/ha_myisam.cc:
Auto merged
sql/ha_ndbcluster.cc:
Auto merged
sql/ha_partition.cc:
Auto merged
sql/handler.cc:
Auto merged
sql/handler.h:
Auto merged
sql/item_sum.cc:
Auto merged
sql/log.cc:
Auto merged
sql/mysql_priv.h:
Auto merged
sql/mysqld.cc:
Auto merged
sql/set_var.cc:
Auto merged
sql/sql_base.cc:
Auto merged
sql/sql_cache.cc:
Auto merged
sql/sql_class.h:
Auto merged
sql/sql_delete.cc:
Auto merged
sql/sql_insert.cc:
Auto merged
sql/sql_lex.h:
Auto merged
sql/sql_parse.cc:
Auto merged
sql/sql_partition.cc:
Auto merged
sql/sql_select.cc:
Auto merged
sql/sql_show.cc:
Auto merged
sql/sql_view.cc:
Auto merged
sql/sql_yacc.yy:
Auto merged
sql/table.cc:
Auto merged
sql/table.h:
Auto merged
sql/sql_table.cc:
SCCS merged
Diffstat (limited to 'storage')
-rw-r--r-- | storage/csv/Makefile.am | 39 | ||||
-rw-r--r-- | storage/csv/ha_tina.cc | 959 | ||||
-rw-r--r-- | storage/csv/ha_tina.h | 130 | ||||
-rw-r--r-- | storage/example/Makefile.am | 39 | ||||
-rw-r--r-- | storage/example/ha_example.cc | 740 | ||||
-rw-r--r-- | storage/example/ha_example.h | 155 |
6 files changed, 2062 insertions, 0 deletions
diff --git a/storage/csv/Makefile.am b/storage/csv/Makefile.am new file mode 100644 index 00000000000..1d3c47bd650 --- /dev/null +++ b/storage/csv/Makefile.am @@ -0,0 +1,39 @@ +# Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB +# +# 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; either version 2 of the License, or +# (at your option) any later version. +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#called from the top level Makefile + +MYSQLDATAdir = $(localstatedir) +MYSQLSHAREdir = $(pkgdatadir) +MYSQLBASEdir= $(prefix) +MYSQLLIBdir= $(pkglibdir) +INCLUDES = -I$(top_srcdir)/include \ + -I$(top_srcdir)/regex \ + -I$(top_srcdir)/sql \ + -I$(srcdir) +WRAPLIBS= + +pkglib_LTLIBRARIES = ha_csv.la + +ha_csv_la_LDFLAGS = -module +ha_csv_la_SOURCES = ha_tina.cc + +LDADD = + +DEFS = -DMYSQL_SERVER @DEFS@ + +# Don't update the files from bitkeeper +%::SCCS/s.% diff --git a/storage/csv/ha_tina.cc b/storage/csv/ha_tina.cc new file mode 100644 index 00000000000..7bb68d4502b --- /dev/null +++ b/storage/csv/ha_tina.cc @@ -0,0 +1,959 @@ +/* Copyright (C) 2003 MySQL AB + + 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; either version 2 of the License, or + (at your option) any later version. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 +*/ + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#include "mysql_priv.h" + +#include "ha_tina.h" +#include <sys/mman.h> + +#include <plugin.h> + +/* Stuff for shares */ +pthread_mutex_t tina_mutex; +static HASH tina_open_tables; +static int tina_init= 0; +static handler *tina_create_handler(TABLE_SHARE *table); +static int tina_init_func(); + +handlerton tina_hton= { + MYSQL_HANDLERTON_INTERFACE_VERSION, + "CSV", + SHOW_OPTION_YES, + "CSV storage engine", + DB_TYPE_CSV_DB, + (bool (*)()) tina_init_func, + 0, /* slot */ + 0, /* savepoint size. */ + NULL, /* close_connection */ + NULL, /* savepoint */ + NULL, /* rollback to savepoint */ + NULL, /* release savepoint */ + NULL, /* commit */ + NULL, /* rollback */ + NULL, /* prepare */ + NULL, /* recover */ + NULL, /* commit_by_xid */ + NULL, /* rollback_by_xid */ + NULL, /* create_cursor_read_view */ + NULL, /* set_cursor_read_view */ + NULL, /* close_cursor_read_view */ + tina_create_handler, /* Create a new handler */ + NULL, /* Drop a database */ + tina_end, /* Panic call */ + NULL, /* Start Consistent Snapshot */ + NULL, /* Flush logs */ + NULL, /* Show status */ + HTON_CAN_RECREATE +}; + +/***************************************************************************** + ** 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 byte* tina_get_key(TINA_SHARE *share,uint *length, + my_bool not_used __attribute__((unused))) +{ + *length=share->table_name_length; + return (byte*) share->table_name; +} + +/* + Reloads the mmap file. +*/ +int get_mmap(TINA_SHARE *share, int write) +{ + DBUG_ENTER("ha_tina::get_mmap"); + if (share->mapped_file && my_munmap(share->mapped_file, + share->file_stat.st_size)) + DBUG_RETURN(1); + + if (my_fstat(share->data_file, &share->file_stat, MYF(MY_WME)) == -1) + DBUG_RETURN(1); + + if (share->file_stat.st_size) + { + if (write) + share->mapped_file= (byte *)my_mmap(NULL, share->file_stat.st_size, + PROT_READ|PROT_WRITE, MAP_SHARED, + share->data_file, 0); + else + share->mapped_file= (byte *)my_mmap(NULL, share->file_stat.st_size, + PROT_READ, MAP_PRIVATE, + share->data_file, 0); + if ((share->mapped_file ==(caddr_t)-1)) + { + /* + Bad idea you think? See the problem is that nothing actually checks + the return value of ::rnd_init(), so tossing an error is about + it for us. + Never going to happen right? :) + */ + my_message(errno, "Woops, blew up opening a mapped file", 0); + DBUG_ASSERT(0); + DBUG_RETURN(1); + } + } + else + share->mapped_file= NULL; + + DBUG_RETURN(0); +} + + +static int tina_init_func() +{ + if (!tina_init) + { + tina_init++; + VOID(pthread_mutex_init(&tina_mutex,MY_MUTEX_INIT_FAST)); + (void) hash_init(&tina_open_tables,system_charset_info,32,0,0, + (hash_get_key) tina_get_key,0,0); + } + return 0; +} + +static int tina_done_func() +{ + if (tina_init) + { + if (tina_open_tables.records) + { + return 1; + } + hash_free(&tina_open_tables); + pthread_mutex_destroy(&tina_mutex); + tina_init--; + } + return 0; +} + + +/* + Simple lock controls. +*/ +static TINA_SHARE *get_share(const char *table_name, TABLE *table) +{ + TINA_SHARE *share; + char *tmp_name; + uint length; + + pthread_mutex_lock(&tina_mutex); + length=(uint) strlen(table_name); + if (!(share=(TINA_SHARE*) hash_search(&tina_open_tables, + (byte*) table_name, + length))) + { + char data_file_name[FN_REFLEN]; + if (!my_multi_malloc(MYF(MY_WME | MY_ZEROFILL), + &share, sizeof(*share), + &tmp_name, length+1, + NullS)) + { + pthread_mutex_unlock(&tina_mutex); + return NULL; + } + + share->use_count= 0; + share->table_name_length= length; + share->table_name= tmp_name; + strmov(share->table_name, table_name); + fn_format(data_file_name, table_name, "", ".CSV", + MY_REPLACE_EXT|MY_UNPACK_FILENAME); + if (my_hash_insert(&tina_open_tables, (byte*) share)) + goto error; + thr_lock_init(&share->lock); + pthread_mutex_init(&share->mutex,MY_MUTEX_INIT_FAST); + + if ((share->data_file= my_open(data_file_name, O_RDWR|O_APPEND, + MYF(0))) == -1) + goto error2; + + /* + We only use share->data_file for writing, so we scan to + the end to append + */ + if (my_seek(share->data_file, 0, SEEK_END, MYF(0)) == MY_FILEPOS_ERROR) + goto error2; + + share->mapped_file= NULL; // We don't know the state as we just allocated it + if (get_mmap(share, 0) > 0) + goto error3; + } + share->use_count++; + pthread_mutex_unlock(&tina_mutex); + + return share; + +error3: + my_close(share->data_file,MYF(0)); +error2: + thr_lock_delete(&share->lock); + pthread_mutex_destroy(&share->mutex); +error: + pthread_mutex_unlock(&tina_mutex); + my_free((gptr) share, MYF(0)); + + return NULL; +} + + +/* + Free lock controls. +*/ +static int free_share(TINA_SHARE *share) +{ + DBUG_ENTER("ha_tina::free_share"); + pthread_mutex_lock(&tina_mutex); + int result_code= 0; + if (!--share->use_count){ + /* Drop the mapped file */ + if (share->mapped_file) + my_munmap(share->mapped_file, share->file_stat.st_size); + result_code= my_close(share->data_file,MYF(0)); + hash_delete(&tina_open_tables, (byte*) share); + thr_lock_delete(&share->lock); + pthread_mutex_destroy(&share->mutex); + my_free((gptr) share, MYF(0)); + } + pthread_mutex_unlock(&tina_mutex); + + DBUG_RETURN(result_code); +} + +int tina_end(ha_panic_function type) +{ + return tina_done_func(); +} + +/* + Finds the end of a line. + Currently only supports files written on a UNIX OS. +*/ +byte * find_eoln(byte *data, off_t begin, off_t end) +{ + for (off_t x= begin; x < end; x++) + if (data[x] == '\n') + return data + x; + + return 0; +} + + +static handler *tina_create_handler(TABLE_SHARE *table) +{ + return new ha_tina(table); +} + + +ha_tina::ha_tina(TABLE_SHARE *table_arg) + :handler(&tina_hton, table_arg), + /* + These definitions are found in handler.h + They are not probably completely right. + */ + current_position(0), next_position(0), chain_alloced(0), + chain_size(DEFAULT_CHAIN_LENGTH), records_is_known(0) +{ + /* Set our original buffers from pre-allocated memory */ + buffer.set(byte_buffer, IO_SIZE, system_charset_info); + chain= chain_buffer; +} + +/* + Encode a buffer into the quoted format. +*/ + +int ha_tina::encode_quote(byte *buf) +{ + char attribute_buffer[1024]; + String attribute(attribute_buffer, sizeof(attribute_buffer), &my_charset_bin); + + buffer.length(0); + for (Field **field=table->field ; *field ; field++) + { + const char *ptr; + const char *end_ptr; + + (*field)->val_str(&attribute,&attribute); + ptr= attribute.ptr(); + end_ptr= attribute.length() + ptr; + + buffer.append('"'); + + while (ptr < end_ptr) + { + if (*ptr == '"') + { + buffer.append('\\'); + buffer.append('"'); + *ptr++; + } + else if (*ptr == '\r') + { + buffer.append('\\'); + buffer.append('r'); + *ptr++; + } + else if (*ptr == '\\') + { + buffer.append('\\'); + buffer.append('\\'); + *ptr++; + } + else if (*ptr == '\n') + { + buffer.append('\\'); + buffer.append('n'); + *ptr++; + } + else + buffer.append(*ptr++); + } + buffer.append('"'); + buffer.append(','); + } + // Remove the comma, add a line feed + buffer.length(buffer.length() - 1); + buffer.append('\n'); + //buffer.replace(buffer.length(), 0, "\n", 1); + + 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", occured + 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)) + { + off_t location= chain_ptr - chain; + chain_size += DEFAULT_CHAIN_LENGTH; + if (chain_alloced) + { + /* Must cast since my_malloc unlike malloc doesn't have a void ptr */ + if ((chain= (tina_set *) my_realloc((gptr)chain, + chain_size, MYF(MY_WME))) == NULL) + return -1; + } + else + { + tina_set *ptr= (tina_set *) my_malloc(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(byte *buf) +{ + byte *mapped_ptr= (byte *)share->mapped_file + current_position; + byte *end_ptr; + DBUG_ENTER("ha_tina::find_current_row"); + + /* EOF should be counted as new line */ + if ((end_ptr= find_eoln(share->mapped_file, current_position, + share->file_stat.st_size)) == 0) + DBUG_RETURN(HA_ERR_END_OF_FILE); + + for (Field **field=table->field ; *field ; field++) + { + buffer.length(0); + mapped_ptr++; // Increment past the first quote + for(;mapped_ptr != end_ptr; mapped_ptr++) + { + // Need to convert line feeds! + if (*mapped_ptr == '"' && + (((mapped_ptr[1] == ',') && (mapped_ptr[2] == '"')) || + (mapped_ptr == end_ptr -1 ))) + { + mapped_ptr += 2; // Move past the , and the " + break; + } + if (*mapped_ptr == '\\' && mapped_ptr != (end_ptr - 1)) + { + mapped_ptr++; + if (*mapped_ptr == 'r') + buffer.append('\r'); + else if (*mapped_ptr == 'n' ) + buffer.append('\n'); + else if ((*mapped_ptr == '\\') || (*mapped_ptr == '"')) + buffer.append(*mapped_ptr); + else /* This could only happed with an externally created file */ + { + buffer.append('\\'); + buffer.append(*mapped_ptr); + } + } + else + buffer.append(*mapped_ptr); + } + (*field)->store(buffer.ptr(), buffer.length(), system_charset_info); + } + next_position= (end_ptr - share->mapped_file)+1; + /* Maybe use \N for null? */ + memset(buf, 0, table->s->null_bytes); /* We do not implement nulls! */ + + DBUG_RETURN(0); +} + +/* + 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", + NullS +}; + +const char **ha_tina::bas_ext() const +{ + return ha_tina_exts; +} + + +/* + 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 test_if_locked) +{ + DBUG_ENTER("ha_tina::open"); + + if (!(share= get_share(name, table))) + DBUG_RETURN(1); + thr_lock_data_init(&share->lock,&lock,NULL); + ref_length=sizeof(off_t); + + DBUG_RETURN(0); +} + + +/* + Close a database file. We remove ourselves from the shared strucutre. + If it is empty we destroy it and free the mapped file. +*/ +int ha_tina::close(void) +{ + DBUG_ENTER("ha_tina::close"); + DBUG_RETURN(free_share(share)); +} + +/* + 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(byte * buf) +{ + int size; + DBUG_ENTER("ha_tina::write_row"); + + statistic_increment(table->in_use->status_var.ha_write_count, &LOCK_status); + + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); + + size= encode_quote(buf); + + if (my_write(share->data_file, buffer.ptr(), size, MYF(MY_WME | MY_NABP))) + DBUG_RETURN(-1); + + /* + Ok, this is means that we will be doing potentially bad things + during a bulk insert on some OS'es. What we need is a cleanup + call for ::write_row that would let us fix up everything after the bulk + insert. The archive handler does this with an extra mutx call, which + might be a solution for this. + */ + if (get_mmap(share, 0) > 0) + DBUG_RETURN(-1); + records++; + DBUG_RETURN(0); +} + + +/* + This is called for an update. + Make sure you put in code to increment the auto increment, also + update any timestamp data. 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 byte * old_data, byte * new_data) +{ + int size; + DBUG_ENTER("ha_tina::update_row"); + + statistic_increment(table->in_use->status_var.ha_read_rnd_next_count, + &LOCK_status); + + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); + + size= encode_quote(new_data); + + if (chain_append()) + DBUG_RETURN(-1); + + if (my_write(share->data_file, buffer.ptr(), size, MYF(MY_WME | MY_NABP))) + DBUG_RETURN(-1); + DBUG_RETURN(0); +} + + +/* + 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 byte * buf) +{ + DBUG_ENTER("ha_tina::delete_row"); + statistic_increment(table->in_use->status_var.ha_delete_count, + &LOCK_status); + + if (chain_append()) + DBUG_RETURN(-1); + + --records; + + DBUG_RETURN(0); +} + +/* + Fill buf with value from key. Simply this is used for a single index read + with a key. +*/ +int ha_tina::index_read(byte * buf, const byte * key, + uint key_len __attribute__((unused)), + enum ha_rkey_function find_flag + __attribute__((unused))) +{ + DBUG_ENTER("ha_tina::index_read"); + DBUG_ASSERT(0); + DBUG_RETURN(HA_ADMIN_NOT_IMPLEMENTED); +} + +/* + Fill buf with value from key. Simply this is used for a single index read + with a key. + Whatever the current key is we will use it. This is what will be in "index". +*/ +int ha_tina::index_read_idx(byte * buf, uint index, const byte * key, + uint key_len __attribute__((unused)), + enum ha_rkey_function find_flag + __attribute__((unused))) +{ + DBUG_ENTER("ha_tina::index_read_idx"); + DBUG_ASSERT(0); + DBUG_RETURN(HA_ADMIN_NOT_IMPLEMENTED); +} + + +/* + Read the next position in the index. +*/ +int ha_tina::index_next(byte * buf) +{ + DBUG_ENTER("ha_tina::index_next"); + DBUG_ASSERT(0); + DBUG_RETURN(HA_ADMIN_NOT_IMPLEMENTED); +} + +/* + Read the previous position in the index. +*/ +int ha_tina::index_prev(byte * buf) +{ + DBUG_ENTER("ha_tina::index_prev"); + DBUG_ASSERT(0); + DBUG_RETURN(HA_ADMIN_NOT_IMPLEMENTED); +} + +/* + Read the first position in the index +*/ +int ha_tina::index_first(byte * buf) +{ + DBUG_ENTER("ha_tina::index_first"); + DBUG_ASSERT(0); + DBUG_RETURN(HA_ADMIN_NOT_IMPLEMENTED); +} + +/* + Read the last position in the index + With this we don't need to do a filesort() with index. + We just read the last row and call previous. +*/ +int ha_tina::index_last(byte * buf) +{ + DBUG_ENTER("ha_tina::index_last"); + DBUG_ASSERT(0); + DBUG_RETURN(HA_ADMIN_NOT_IMPLEMENTED); +} + +/* + 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"); + + current_position= next_position= 0; + records= 0; + records_is_known= 0; + chain_ptr= chain; +#ifdef HAVE_MADVISE + if (scan) + (void) madvise(share->mapped_file, share->file_stat.st_size, + MADV_SEQUENTIAL); +#endif + + 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(byte *buf) +{ + DBUG_ENTER("ha_tina::rnd_next"); + + statistic_increment(table->in_use->status_var.ha_read_rnd_next_count, + &LOCK_status); + + current_position= next_position; + if (!share->mapped_file) + DBUG_RETURN(HA_ERR_END_OF_FILE); + if (HA_ERR_END_OF_FILE == find_current_row(buf) ) + DBUG_RETURN(HA_ERR_END_OF_FILE); + + records++; + DBUG_RETURN(0); +} + +/* + 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 byte *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(byte * buf, byte *pos) +{ + DBUG_ENTER("ha_tina::rnd_pos"); + statistic_increment(table->in_use->status_var.ha_read_rnd_next_count, + &LOCK_status); + current_position= my_get_ptr(pos,ref_length); + DBUG_RETURN(find_current_row(buf)); +} + +/* + ::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 +*/ +void 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 && records < 2) + records= 2; + DBUG_VOID_RETURN; +} + +/* + 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"); + DBUG_RETURN(0); +} + +/* + This is no longer used. +*/ +int ha_tina::reset(void) +{ + DBUG_ENTER("ha_tina::reset"); + ha_tina::extra(HA_EXTRA_RESET); + DBUG_RETURN(0); +} + + +/* + 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() +{ + DBUG_ENTER("ha_tina::rnd_end"); + + records_is_known= 1; + + /* First position will be truncate position, second will be increment */ + if ((chain_ptr - chain) > 0) + { + tina_set *ptr; + off_t length; + + /* + Setting up writable map, this will contain all of the data after the + get_mmap call that we have added to the file. + */ + if (get_mmap(share, 1) > 0) + DBUG_RETURN(-1); + length= share->file_stat.st_size; + + /* + The sort handles updates/deletes with random orders. + It also sorts so that we move the final blocks to the + beginning so that we move the smallest amount of data possible. + */ + qsort(chain, (size_t)(chain_ptr - chain), sizeof(tina_set), + (qsort_cmp)sort_set); + for (ptr= chain; ptr < chain_ptr; ptr++) + { + memmove(share->mapped_file + ptr->begin, share->mapped_file + ptr->end, + length - (size_t)ptr->end); + length= length - (size_t)(ptr->end - ptr->begin); + } + + /* Truncate the file to the new size */ + if (my_chsize(share->data_file, length, 0, MYF(MY_WME))) + DBUG_RETURN(-1); + + if (my_munmap(share->mapped_file, length)) + DBUG_RETURN(-1); + + /* We set it to null so that get_mmap() won't try to unmap it */ + share->mapped_file= NULL; + if (get_mmap(share, 0) > 0) + DBUG_RETURN(-1); + } + + DBUG_RETURN(0); +} + + +/* + DELETE without WHERE calls this +*/ + +int ha_tina::delete_all_rows() +{ + DBUG_ENTER("ha_tina::delete_all_rows"); + + if (!records_is_known) + return (my_errno=HA_ERR_WRONG_COMMAND); + + int rc= my_chsize(share->data_file, 0, 0, MYF(MY_WME)); + + if (get_mmap(share, 0) > 0) + DBUG_RETURN(-1); + + records=0; + DBUG_RETURN(rc); +} + +/* + Always called by the start of a transaction (or by "lock tables"); +*/ +int ha_tina::external_lock(THD *thd, int lock_type) +{ + DBUG_ENTER("ha_tina::external_lock"); + DBUG_RETURN(0); // No external locking +} + +/* + 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"); + + if ((create_file= my_create(fn_format(name_buff, name, "", ".CSV", + MY_REPLACE_EXT|MY_UNPACK_FILENAME),0, + O_RDWR | O_TRUNC,MYF(MY_WME))) < 0) + DBUG_RETURN(-1); + + my_close(create_file,MYF(0)); + + DBUG_RETURN(0); +} + +mysql_declare_plugin +{ + MYSQL_STORAGE_ENGINE_PLUGIN, + &tina_hton, + tina_hton.name, + 0x00010000 /* 0.1.0 */, + "Brian Aker, MySQL AB", + "CSV Storage Engine", + tina_init_func, /* Plugin Init */ + tina_done_func /* Plugin Deinit */ +} +mysql_declare_plugin_end; + diff --git a/storage/csv/ha_tina.h b/storage/csv/ha_tina.h new file mode 100644 index 00000000000..c46750fb703 --- /dev/null +++ b/storage/csv/ha_tina.h @@ -0,0 +1,130 @@ +/* Copyright (C) 2003 MySQL AB + + 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; either version 2 of the License, or + (at your option) any later version. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <my_dir.h> + +#define DEFAULT_CHAIN_LENGTH 512 + +typedef struct st_tina_share { + char *table_name; + byte *mapped_file; /* mapped region of file */ + uint table_name_length,use_count; + MY_STAT file_stat; /* Stat information for the data file */ + File data_file; /* Current open data file */ + pthread_mutex_t mutex; + THR_LOCK lock; +} TINA_SHARE; + +typedef struct tina_set { + off_t begin; + off_t end; +}; + +class ha_tina: public handler +{ + THR_LOCK_DATA lock; /* MySQL lock */ + TINA_SHARE *share; /* Shared lock info */ + off_t current_position; /* Current position in the file during a file scan */ + off_t next_position; /* Next position in the file scan */ + byte byte_buffer[IO_SIZE]; + String buffer; + /* + The chain contains "holes" in the file, occured 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; + byte chain_alloced; + uint32 chain_size; + bool records_is_known; + +public: + ha_tina(TABLE_SHARE *table_arg); + ~ha_tina() + { + if (chain_alloced) + my_free((gptr)chain,0); + } + const char *table_type() const { return "CSV"; } + const char *index_type(uint inx) { return "NONE"; } + const char **bas_ext() const; + ulong table_flags() const + { + return (HA_REC_NOT_IN_SEQ | HA_NOT_EXACT_COUNT | + HA_NO_AUTO_INCREMENT ); + } + 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) (records+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 test_if_locked); + int close(void); + int write_row(byte * buf); + int update_row(const byte * old_data, byte * new_data); + int delete_row(const byte * buf); + int index_read(byte * buf, const byte * key, + uint key_len, enum ha_rkey_function find_flag); + int index_read_idx(byte * buf, uint idx, const byte * key, + uint key_len, enum ha_rkey_function find_flag); + int index_next(byte * buf); + int index_prev(byte * buf); + int index_first(byte * buf); + int index_last(byte * buf); + int rnd_init(bool scan=1); + int rnd_next(byte *buf); + int rnd_pos(byte * buf, byte *pos); + int rnd_end(); + void position(const byte *record); + void info(uint); + int extra(enum ha_extra_function operation); + int reset(void); + int external_lock(THD *thd, int lock_type); + int delete_all_rows(void); + int create(const char *name, TABLE *form, HA_CREATE_INFO *create_info); + + THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to, + enum thr_lock_type lock_type); + + /* The following methods were added just for TINA */ + int encode_quote(byte *buf); + int find_current_row(byte *buf); + int chain_append(); +}; + +int tina_end(ha_panic_function type); + diff --git a/storage/example/Makefile.am b/storage/example/Makefile.am new file mode 100644 index 00000000000..d5896946c5b --- /dev/null +++ b/storage/example/Makefile.am @@ -0,0 +1,39 @@ +# Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB +# +# 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; either version 2 of the License, or +# (at your option) any later version. +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#called from the top level Makefile + +MYSQLDATAdir = $(localstatedir) +MYSQLSHAREdir = $(pkgdatadir) +MYSQLBASEdir= $(prefix) +MYSQLLIBdir= $(pkglibdir) +INCLUDES = -I$(top_srcdir)/include \ + -I$(top_srcdir)/regex \ + -I$(top_srcdir)/sql \ + -I$(srcdir) +WRAPLIBS= + +pkglib_LTLIBRARIES = ha_example.la + +ha_example_la_LDFLAGS = -module +ha_example_la_SOURCES = ha_example.cc + +LDADD = + +DEFS = -DMYSQL_SERVER @DEFS@ + +# Don't update the files from bitkeeper +%::SCCS/s.% diff --git a/storage/example/ha_example.cc b/storage/example/ha_example.cc new file mode 100644 index 00000000000..74f25d1411d --- /dev/null +++ b/storage/example/ha_example.cc @@ -0,0 +1,740 @@ +/* Copyright (C) 2003 MySQL AB + + 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; either version 2 of the License, or + (at your option) any later version. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + ha_example is a stubbed storage engine. It does nothing at this point. It + will let you create/open/delete tables but that is all. You can enable it + in your buld by doing the following during your build process: + ./configure --with-example-storage-engine + + Once this is done mysql will let you create tables with: + CREATE TABLE A (...) ENGINE=EXAMPLE; + + The example is setup to use table locks. It implements an example "SHARE" + that is inserted into a hash by table name. You can use this to store + information of state that any example handler object will be able to see + if it is using the same table. + + Please read the object definition in ha_example.h before reading the rest + if this file. + + To get an idea of what occurs here is an example select that would do a + scan of an entire table: + ha_example::store_lock + ha_example::external_lock + ha_example::info + ha_example::rnd_init + ha_example::extra + ENUM HA_EXTRA_CACHE Cash record in HA_rrnd() + ha_example::rnd_next + ha_example::rnd_next + ha_example::rnd_next + ha_example::rnd_next + ha_example::rnd_next + ha_example::rnd_next + ha_example::rnd_next + ha_example::rnd_next + ha_example::rnd_next + ha_example::extra + ENUM HA_EXTRA_NO_CACHE End cacheing of records (def) + ha_example::external_lock + ha_example::extra + ENUM HA_EXTRA_RESET Reset database to after open + + In the above example has 9 row called before rnd_next signalled that it was + at the end of its data. In the above example the table was already opened + (or you would have seen a call to ha_example::open(). Calls to + ha_example::extra() are hints as to what will be occuring to the request. + + Happy coding! + -Brian +*/ + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#include "../mysql_priv.h" +#include <plugin.h> + +#include "ha_example.h" + +static handler* example_create_handler(TABLE_SHARE *table); +static int example_init_func(); + +handlerton example_hton= { + MYSQL_HANDLERTON_INTERFACE_VERSION, + "EXAMPLE", + SHOW_OPTION_YES, + "Example storage engine", + DB_TYPE_EXAMPLE_DB, + (bool (*)()) example_init_func, + 0, /* slot */ + 0, /* savepoint size. */ + NULL, /* close_connection */ + NULL, /* savepoint */ + NULL, /* rollback to savepoint */ + NULL, /* release savepoint */ + NULL, /* commit */ + NULL, /* rollback */ + NULL, /* prepare */ + NULL, /* recover */ + NULL, /* commit_by_xid */ + NULL, /* rollback_by_xid */ + NULL, /* create_cursor_read_view */ + NULL, /* set_cursor_read_view */ + NULL, /* close_cursor_read_view */ + example_create_handler, /* Create a new handler */ + NULL, /* Drop a database */ + NULL, /* Panic call */ + NULL, /* Release temporary latches */ + NULL, /* Update Statistics */ + NULL, /* Start Consistent Snapshot */ + NULL, /* Flush logs */ + NULL, /* Show status */ + NULL, /* Replication Report Sent Binlog */ + HTON_CAN_RECREATE +}; + +/* Variables for example share methods */ +static HASH example_open_tables; // Hash used to track open tables +pthread_mutex_t example_mutex; // This is the mutex we use to init the hash +static int example_init= 0; // Variable for checking the init state of hash + + +/* + Function we use in the creation of our hash to get key. +*/ +static byte* example_get_key(EXAMPLE_SHARE *share,uint *length, + my_bool not_used __attribute__((unused))) +{ + *length=share->table_name_length; + return (byte*) share->table_name; +} + + +static int example_init_func() +{ + if (!example_init) + { + example_init++; + VOID(pthread_mutex_init(&example_mutex,MY_MUTEX_INIT_FAST)); + (void) hash_init(&example_open_tables,system_charset_info,32,0,0, + (hash_get_key) example_get_key,0,0); + } + return 0; +} + +static int example_done_func() +{ + if (example_init) + { + if (example_open_tables.records) + { + return 1; + } + hash_free(&example_open_tables); + pthread_mutex_destroy(&example_mutex); + example_init--; + } + return 0; +} + + +/* + Example of simple lock controls. The "share" it creates is structure we will + pass to each example handler. Do you have to have one of these? Well, you have + pieces that are used for locking, and they are needed to function. +*/ +static EXAMPLE_SHARE *get_share(const char *table_name, TABLE *table) +{ + EXAMPLE_SHARE *share; + uint length; + char *tmp_name; + + pthread_mutex_lock(&example_mutex); + length=(uint) strlen(table_name); + + if (!(share=(EXAMPLE_SHARE*) hash_search(&example_open_tables, + (byte*) table_name, + length))) + { + if (!(share=(EXAMPLE_SHARE *) + my_multi_malloc(MYF(MY_WME | MY_ZEROFILL), + &share, sizeof(*share), + &tmp_name, length+1, + NullS))) + { + pthread_mutex_unlock(&example_mutex); + return NULL; + } + + share->use_count=0; + share->table_name_length=length; + share->table_name=tmp_name; + strmov(share->table_name,table_name); + if (my_hash_insert(&example_open_tables, (byte*) share)) + goto error; + thr_lock_init(&share->lock); + pthread_mutex_init(&share->mutex,MY_MUTEX_INIT_FAST); + } + share->use_count++; + pthread_mutex_unlock(&example_mutex); + + return share; + +error: + pthread_mutex_destroy(&share->mutex); + my_free((gptr) share, MYF(0)); + + return NULL; +} + + +/* + Free lock controls. We call this whenever we close a table. If the table had + the last reference to the share then we free memory associated with it. +*/ +static int free_share(EXAMPLE_SHARE *share) +{ + pthread_mutex_lock(&example_mutex); + if (!--share->use_count) + { + hash_delete(&example_open_tables, (byte*) share); + thr_lock_delete(&share->lock); + pthread_mutex_destroy(&share->mutex); + my_free((gptr) share, MYF(0)); + } + pthread_mutex_unlock(&example_mutex); + + return 0; +} + + +static handler* example_create_handler(TABLE_SHARE *table) +{ + return new ha_example(table); +} + + +ha_example::ha_example(TABLE_SHARE *table_arg) + :handler(&example_hton, table_arg) +{} + +/* + If frm_error() is called then we will use this to to find out what file extentions + exist for the storage engine. This is also used by the default rename_table and + delete_table method in handler.cc. +*/ +static const char *ha_example_exts[] = { + NullS +}; + +const char **ha_example::bas_ext() const +{ + return ha_example_exts; +} + + +/* + Used for opening tables. The name will be the name of the file. + A table is opened when it needs to be opened. For instance + when a request comes in for a select on the table (tables are not + open and closed for each request, they are cached). + + Called from handler.cc by handler::ha_open(). The server opens all tables by + calling ha_open() which then calls the handler specific open(). +*/ +int ha_example::open(const char *name, int mode, uint test_if_locked) +{ + DBUG_ENTER("ha_example::open"); + + if (!(share = get_share(name, table))) + DBUG_RETURN(1); + thr_lock_data_init(&share->lock,&lock,NULL); + + DBUG_RETURN(0); +} + + +/* + Closes a table. We call the free_share() function to free any resources + that we have allocated in the "shared" structure. + + Called from sql_base.cc, sql_select.cc, and table.cc. + In sql_select.cc it is only used to close up temporary tables or during + the process where a temporary table is converted over to being a + myisam table. + For sql_base.cc look at close_data_tables(). +*/ +int ha_example::close(void) +{ + DBUG_ENTER("ha_example::close"); + DBUG_RETURN(free_share(share)); +} + + +/* + write_row() inserts a row. No extra() hint is given currently if a bulk load + is happeneding. buf() is a byte array of data. You can use the field + information to extract the data from the native byte array type. + Example of this would be: + for (Field **field=table->field ; *field ; field++) + { + ... + } + + See ha_tina.cc for an example of extracting all of the data as strings. + ha_berekly.cc has an example of how to store it intact by "packing" it + for ha_berkeley's own native storage type. + + See the note for update_row() on auto_increments and timestamps. This + case also applied to write_row(). + + Called from item_sum.cc, item_sum.cc, sql_acl.cc, sql_insert.cc, + sql_insert.cc, sql_select.cc, sql_table.cc, sql_udf.cc, and sql_update.cc. +*/ +int ha_example::write_row(byte * buf) +{ + DBUG_ENTER("ha_example::write_row"); + DBUG_RETURN(HA_ERR_WRONG_COMMAND); +} + + +/* + Yes, update_row() does what you expect, it updates a row. old_data will have + the previous row record in it, while new_data will have the newest data in + it. + Keep in mind that the server can do updates based on ordering if an ORDER BY + clause was used. Consecutive ordering is not guarenteed. + Currently new_data will not have an updated auto_increament record, or + and updated timestamp field. You can do these for example by doing these: + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); + if (table->next_number_field && record == table->record[0]) + update_auto_increment(); + + Called from sql_select.cc, sql_acl.cc, sql_update.cc, and sql_insert.cc. +*/ +int ha_example::update_row(const byte * old_data, byte * new_data) +{ + + DBUG_ENTER("ha_example::update_row"); + DBUG_RETURN(HA_ERR_WRONG_COMMAND); +} + + +/* + This will delete a row. buf will contain a copy of the row to be deleted. + The server will call this right after the current row has been called (from + either a previous rnd_nexT() or index call). + If you keep a pointer to the last row or can access a primary key it will + make doing the deletion quite a bit easier. + Keep in mind that the server does no guarentee consecutive deletions. ORDER BY + clauses can be used. + + Called in sql_acl.cc and sql_udf.cc to manage internal table information. + Called in sql_delete.cc, sql_insert.cc, and sql_select.cc. In sql_select it is + used for removing duplicates while in insert it is used for REPLACE calls. +*/ +int ha_example::delete_row(const byte * buf) +{ + DBUG_ENTER("ha_example::delete_row"); + DBUG_RETURN(HA_ERR_WRONG_COMMAND); +} + + +/* + Positions an index cursor to the index specified in the handle. Fetches the + row if available. If the key value is null, begin at the first key of the + index. +*/ +int ha_example::index_read(byte * buf, const byte * key, + uint key_len __attribute__((unused)), + enum ha_rkey_function find_flag + __attribute__((unused))) +{ + DBUG_ENTER("ha_example::index_read"); + DBUG_RETURN(HA_ERR_WRONG_COMMAND); +} + + +/* + Positions an index cursor to the index specified in key. Fetches the + row if any. This is only used to read whole keys. +*/ +int ha_example::index_read_idx(byte * buf, uint index, const byte * key, + uint key_len __attribute__((unused)), + enum ha_rkey_function find_flag + __attribute__((unused))) +{ + DBUG_ENTER("ha_example::index_read_idx"); + DBUG_RETURN(HA_ERR_WRONG_COMMAND); +} + + +/* + Used to read forward through the index. +*/ +int ha_example::index_next(byte * buf) +{ + DBUG_ENTER("ha_example::index_next"); + DBUG_RETURN(HA_ERR_WRONG_COMMAND); +} + + +/* + Used to read backwards through the index. +*/ +int ha_example::index_prev(byte * buf) +{ + DBUG_ENTER("ha_example::index_prev"); + DBUG_RETURN(HA_ERR_WRONG_COMMAND); +} + + +/* + index_first() asks for the first key in the index. + + Called from opt_range.cc, opt_sum.cc, sql_handler.cc, + and sql_select.cc. +*/ +int ha_example::index_first(byte * buf) +{ + DBUG_ENTER("ha_example::index_first"); + DBUG_RETURN(HA_ERR_WRONG_COMMAND); +} + + +/* + index_last() asks for the last key in the index. + + Called from opt_range.cc, opt_sum.cc, sql_handler.cc, + and sql_select.cc. +*/ +int ha_example::index_last(byte * buf) +{ + DBUG_ENTER("ha_example::index_last"); + DBUG_RETURN(HA_ERR_WRONG_COMMAND); +} + + +/* + rnd_init() is called when the system wants the storage engine to do a table + scan. + See the example in the introduction at the top of this file to see when + rnd_init() is called. + + Called from filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc, + and sql_update.cc. +*/ +int ha_example::rnd_init(bool scan) +{ + DBUG_ENTER("ha_example::rnd_init"); + DBUG_RETURN(HA_ERR_WRONG_COMMAND); +} + +int ha_example::rnd_end() +{ + DBUG_ENTER("ha_example::rnd_end"); + DBUG_RETURN(0); +} + +/* + This is called for each row of the table scan. When you run out of records + you should return HA_ERR_END_OF_FILE. Fill buff up with the row information. + The Field structure for the table is the key to getting data into buf + in a manner that will allow the server to understand it. + + Called from filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc, + and sql_update.cc. +*/ +int ha_example::rnd_next(byte *buf) +{ + DBUG_ENTER("ha_example::rnd_next"); + DBUG_RETURN(HA_ERR_END_OF_FILE); +} + + +/* + position() is called after each call to rnd_next() if the data needs + to be ordered. You can do something like the following to store + the position: + my_store_ptr(ref, ref_length, current_position); + + The server uses ref to store data. ref_length in the above case is + the size needed to store current_position. ref is just a byte array + that the server will maintain. If you are using offsets to mark rows, then + current_position should be the offset. If it is a primary key like in + BDB, then it needs to be a primary key. + + Called from filesort.cc, sql_select.cc, sql_delete.cc and sql_update.cc. +*/ +void ha_example::position(const byte *record) +{ + DBUG_ENTER("ha_example::position"); + DBUG_VOID_RETURN; +} + + +/* + This is like rnd_next, but you are given a position to use + to determine the row. The position will be of the type that you stored in + ref. You can use ha_get_ptr(pos,ref_length) to retrieve whatever key + or position you saved when position() was called. + Called from filesort.cc records.cc sql_insert.cc sql_select.cc sql_update.cc. +*/ +int ha_example::rnd_pos(byte * buf, byte *pos) +{ + DBUG_ENTER("ha_example::rnd_pos"); + DBUG_RETURN(HA_ERR_WRONG_COMMAND); +} + + +/* + ::info() is used to return information to the optimizer. + see my_base.h for the complete description + + Currently this table handler doesn't implement most of the fields + really needed. SHOW also makes use of this data + Another note, you will probably want to have the following in your + code: + if (records < 2) + records = 2; + The reason is that the server will optimize for cases of only a single + record. If in a table scan you don't know the number of records + it will probably be better to set records to two so you can return + as many records as you need. + Along with records a few more variables you may wish to set are: + records + deleted + data_file_length + index_file_length + delete_length + check_time + Take a look at the public variables in handler.h for more information. + + Called in: + filesort.cc + ha_heap.cc + item_sum.cc + opt_sum.cc + sql_delete.cc + sql_delete.cc + sql_derived.cc + sql_select.cc + sql_select.cc + sql_select.cc + sql_select.cc + sql_select.cc + sql_show.cc + sql_show.cc + sql_show.cc + sql_show.cc + sql_table.cc + sql_union.cc + sql_update.cc + +*/ +void ha_example::info(uint flag) +{ + DBUG_ENTER("ha_example::info"); + DBUG_VOID_RETURN; +} + + +/* + extra() is called whenever the server wishes to send a hint to + the storage engine. The myisam engine implements the most hints. + ha_innodb.cc has the most exhaustive list of these hints. +*/ +int ha_example::extra(enum ha_extra_function operation) +{ + DBUG_ENTER("ha_example::extra"); + DBUG_RETURN(0); +} + + +/* + Deprecated and likely to be removed in the future. Storage engines normally + just make a call like: + ha_example::extra(HA_EXTRA_RESET); + to handle it. +*/ +int ha_example::reset(void) +{ + DBUG_ENTER("ha_example::reset"); + DBUG_RETURN(0); +} + + +/* + Used to delete all rows in a table. Both for cases of truncate and + for cases where the optimizer realizes that all rows will be + removed as a result of a SQL statement. + + Called from item_sum.cc by Item_func_group_concat::clear(), + Item_sum_count_distinct::clear(), and Item_func_group_concat::clear(). + Called from sql_delete.cc by mysql_delete(). + Called from sql_select.cc by JOIN::reinit(). + Called from sql_union.cc by st_select_lex_unit::exec(). +*/ +int ha_example::delete_all_rows() +{ + DBUG_ENTER("ha_example::delete_all_rows"); + DBUG_RETURN(HA_ERR_WRONG_COMMAND); +} + + +/* + First you should go read the section "locking functions for mysql" in + lock.cc to understand this. + This create a lock on the table. If you are implementing a storage engine + that can handle transacations look at ha_berkely.cc to see how you will + want to goo about doing this. Otherwise you should consider calling flock() + here. + + Called from lock.cc by lock_external() and unlock_external(). Also called + from sql_table.cc by copy_data_between_tables(). +*/ +int ha_example::external_lock(THD *thd, int lock_type) +{ + DBUG_ENTER("ha_example::external_lock"); + DBUG_RETURN(0); +} + + +/* + The idea with handler::store_lock() is the following: + + The statement decided which locks we should need for the table + for updates/deletes/inserts we get WRITE locks, for SELECT... we get + read locks. + + Before adding the lock into the table lock handler (see thr_lock.c) + mysqld calls store lock with the requested locks. Store lock can now + modify a write lock to a read lock (or some other lock), ignore the + lock (if we don't want to use MySQL table locks at all) or add locks + for many tables (like we do when we are using a MERGE handler). + + Berkeley DB for example changes all WRITE locks to TL_WRITE_ALLOW_WRITE + (which signals that we are doing WRITES, but we are still allowing other + reader's and writer's. + + When releasing locks, store_lock() are also called. In this case one + usually doesn't have to do anything. + + In some exceptional cases MySQL may send a request for a TL_IGNORE; + This means that we are requesting the same lock as last time and this + should also be ignored. (This may happen when someone does a flush + table when we have opened a part of the tables, in which case mysqld + closes and reopens the tables and tries to get the same locks at last + time). In the future we will probably try to remove this. + + Called from lock.cc by get_lock_data(). +*/ +THR_LOCK_DATA **ha_example::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; +} + +/* + Used to delete a table. By the time delete_table() has been called all + opened references to this table will have been closed (and your globally + shared references released. The variable name will just be the name of + the table. You will need to remove any files you have created at this point. + + If you do not implement this, the default delete_table() is called from + handler.cc and it will delete all files with the file extentions returned + by bas_ext(). + + Called from handler.cc by delete_table and ha_create_table(). Only used + during create if the table_flag HA_DROP_BEFORE_CREATE was specified for + the storage engine. +*/ +int ha_example::delete_table(const char *name) +{ + DBUG_ENTER("ha_example::delete_table"); + /* This is not implemented but we want someone to be able that it works. */ + DBUG_RETURN(0); +} + +/* + Renames a table from one name to another from alter table call. + + If you do not implement this, the default rename_table() is called from + handler.cc and it will delete all files with the file extentions returned + by bas_ext(). + + Called from sql_table.cc by mysql_rename_table(). +*/ +int ha_example::rename_table(const char * from, const char * to) +{ + DBUG_ENTER("ha_example::rename_table "); + DBUG_RETURN(HA_ERR_WRONG_COMMAND); +} + +/* + Given a starting key, and an ending key estimate the number of rows that + will exist between the two. end_key may be empty which in case determine + if start_key matches any rows. + + Called from opt_range.cc by check_quick_keys(). +*/ +ha_rows ha_example::records_in_range(uint inx, key_range *min_key, + key_range *max_key) +{ + DBUG_ENTER("ha_example::records_in_range"); + DBUG_RETURN(10); // low number to force index usage +} + + +/* + create() is called to create a database. The variable name will have the name + of the table. When create() is called you do not need to worry about opening + the table. Also, the FRM file will have already been created so adjusting + create_info will not do you any good. You can overwrite the frm file at this + point if you wish to change the table definition, but there are no methods + currently provided for doing that. + + Called from handle.cc by ha_create_table(). +*/ +int ha_example::create(const char *name, TABLE *table_arg, + HA_CREATE_INFO *create_info) +{ + DBUG_ENTER("ha_example::create"); + /* This is not implemented but we want someone to be able that it works. */ + DBUG_RETURN(0); +} + +mysql_declare_plugin +{ + MYSQL_STORAGE_ENGINE_PLUGIN, + &example_hton, + example_hton.name, + 0x01000000 /* 1.0.0 */, + "Brian Aker, MySQL AB", + "Example Storage Engine", + tina_init_func, /* Plugin Init */ + tina_done_func /* Plugin Deinit */ +} +mysql_declare_plugin_end; + diff --git a/storage/example/ha_example.h b/storage/example/ha_example.h new file mode 100644 index 00000000000..139a50a3281 --- /dev/null +++ b/storage/example/ha_example.h @@ -0,0 +1,155 @@ +/* Copyright (C) 2003 MySQL AB + + 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; either version 2 of the License, or + (at your option) any later version. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + Please read ha_exmple.cc before reading this file. + Please keep in mind that the example storage engine implements all methods + that are required to be implemented. handler.h has a full list of methods + that you can implement. +*/ + +#ifdef USE_PRAGMA_INTERFACE +#pragma interface /* gcc class implementation */ +#endif + +/* + EXAMPLE_SHARE is a structure that will be shared amoung all open handlers + The example implements the minimum of what you will probably need. +*/ +typedef struct st_example_share { + char *table_name; + uint table_name_length,use_count; + pthread_mutex_t mutex; + THR_LOCK lock; +} EXAMPLE_SHARE; + +/* + Class definition for the storage engine +*/ +class ha_example: public handler +{ + THR_LOCK_DATA lock; /* MySQL lock */ + EXAMPLE_SHARE *share; /* Shared lock info */ + +public: + ha_example(TABLE_SHARE *table_arg); + ~ha_example() + { + } + /* The name that will be used for display purposes */ + const char *table_type() const { return "EXAMPLE"; } + /* + The name of the index type that will be used for display + don't implement this method unless you really have indexes + */ + const char *index_type(uint inx) { return "HASH"; } + const char **bas_ext() const; + /* + This is a list of flags that says what the storage engine + implements. The current table flags are documented in + handler.h + */ + ulong table_flags() const + { + return 0; + } + /* + This is a bitmap of flags that says how the storage engine + implements indexes. The current index flags are documented in + handler.h. If you do not implement indexes, just return zero + here. + + part is the key part to check. First key part is 0 + If all_parts it's set, MySQL want to know the flags for the combined + index up to and including 'part'. + */ + ulong index_flags(uint inx, uint part, bool all_parts) const + { + return 0; + } + /* + unireg.cc will call the following to make sure that the storage engine can + handle the data it is about to send. + + Return *real* limits of your storage engine here. MySQL will do + min(your_limits, MySQL_limits) automatically + + There is no need to implement ..._key_... methods if you don't suport + indexes. + */ + uint max_supported_record_length() const { return HA_MAX_REC_LENGTH; } + uint max_supported_keys() const { return 0; } + uint max_supported_key_parts() const { return 0; } + uint max_supported_key_length() const { return 0; } + /* + Called in test_quick_select to determine if indexes should be used. + */ + virtual double scan_time() { return (double) (records+deleted) / 20.0+10; } + /* + The next method will never be called if you do not implement indexes. + */ + virtual double read_time(ha_rows rows) { return (double) rows / 20.0+1; } + + /* + Everything below are methods that we implment in ha_example.cc. + + Most of these methods are not obligatory, skip them and + MySQL will treat them as not implemented + */ + int open(const char *name, int mode, uint test_if_locked); // required + int close(void); // required + + int write_row(byte * buf); + int update_row(const byte * old_data, byte * new_data); + int delete_row(const byte * buf); + int index_read(byte * buf, const byte * key, + uint key_len, enum ha_rkey_function find_flag); + int index_read_idx(byte * buf, uint idx, const byte * key, + uint key_len, enum ha_rkey_function find_flag); + int index_next(byte * buf); + int index_prev(byte * buf); + int index_first(byte * buf); + int index_last(byte * buf); + /* + unlike index_init(), rnd_init() can be called two times + without rnd_end() in between (it only makes sense if scan=1). + then the second call should prepare for the new table scan + (e.g if rnd_init allocates the cursor, second call should + position it to the start of the table, no need to deallocate + and allocate it again + */ + int rnd_init(bool scan); //required + int rnd_end(); + int rnd_next(byte *buf); //required + int rnd_pos(byte * buf, byte *pos); //required + void position(const byte *record); //required + void info(uint); //required + + int extra(enum ha_extra_function operation); + int reset(void); + int external_lock(THD *thd, int lock_type); //required + int delete_all_rows(void); + ha_rows records_in_range(uint inx, key_range *min_key, + key_range *max_key); + int delete_table(const char *from); + int rename_table(const char * from, const char * to); + int create(const char *name, TABLE *form, + HA_CREATE_INFO *create_info); //required + + THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to, + enum thr_lock_type lock_type); //required +}; + |