Below is the list of changes that have just been committed into a local
5.1 repository of svoj. When svoj does a push these changes will
be propagated to the main repository and, within 24 hours after the
push, to the public repository.
For information on how to access the public repository
see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html
ChangeSet@stripped, 2007-03-31 17:29:40+05:00, svoj@april.(none) +6 -0
Merge mysql.com:/home/svoj/devel/bk/mysql-5.0-engines
into mysql.com:/home/svoj/devel/mysql/BUG26138/mysql-5.1-engines
MERGE: 1.1810.2374.103
mysql-test/r/archive.result@stripped, 2007-03-31 17:29:38+05:00, svoj@april.(none) +10 -9
Manually merged.
MERGE: 1.13.1.4
mysql-test/t/archive.test@stripped, 2007-03-31 17:29:38+05:00, svoj@april.(none) +3 -3
Manually merged.
MERGE: 1.14.1.4
sql/handler.h@stripped, 2007-03-31 17:26:05+05:00, svoj@april.(none) +0 -0
Auto merged
MERGE: 1.138.2.45
sql/sql_table.cc@stripped, 2007-03-31 17:26:05+05:00, svoj@april.(none) +0 -0
Auto merged
MERGE: 1.239.1.97
storage/archive/ha_archive.cc@stripped, 2007-03-31 17:27:33+05:00, svoj@april.(none) +0 -0
SCCS merged
MERGE: 1.60.15.2
storage/archive/ha_archive.cc@stripped, 2007-03-31 17:26:05+05:00, svoj@april.(none) +0 -0
Merge rename: sql/ha_archive.cc -> storage/archive/ha_archive.cc
storage/example/ha_example.cc@stripped, 2007-03-31 17:29:38+05:00, svoj@april.(none) +6 -6
Manually merged.
MERGE: 1.19.3.2
storage/example/ha_example.cc@stripped, 2007-03-31 17:26:05+05:00, svoj@april.(none) +0 -0
Merge rename: sql/examples/ha_example.cc -> storage/example/ha_example.cc
# This is a BitKeeper patch. What follows are the unified diffs for the
# set of deltas contained in the patch. The rest of the patch, the part
# that BitKeeper cares about, is below these diffs.
# User: svoj
# Host: april.(none)
# Root: /home/svoj/devel/mysql/BUG26138/mysql-5.1-engines/RESYNC
--- 1.255/sql/handler.h 2007-03-31 17:29:49 +05:00
+++ 1.256/sql/handler.h 2007-03-31 17:29:49 +05:00
@@ -1472,6 +1472,17 @@ public:
virtual void free_foreign_key_create_info(char* str) {}
/* The following can be called without an open handler */
virtual const char *table_type() const =0;
+ /*
+ If frm_error() is called then we will use this 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.
+
+ For engines that have two file name extentions (separate meta/index file
+ and data file), the order of elements is relevant. First element of engine
+ file name extentions array should be meta/index file extention. Second
+ element - data file extention. This order is assumed by
+ prepare_for_repair() when REPAIR TABLE ... USE_FRM is issued.
+ */
virtual const char **bas_ext() const =0;
virtual int get_default_no_partitions(HA_CREATE_INFO *info) { return 1;}
--- 1.399/sql/sql_table.cc 2007-03-31 17:29:49 +05:00
+++ 1.400/sql/sql_table.cc 2007-03-31 17:29:49 +05:00
@@ -3938,7 +3938,9 @@ static int prepare_for_repair(THD *thd,
/*
Check if this is a table type that stores index and data separately,
- like ISAM or MyISAM
+ like ISAM or MyISAM. We assume fixed order of engine file name
+ extentions array. First element of engine file name extentions array
+ is meta/index file extention. Second element - data file extention.
*/
ext= table->file->bas_ext();
if (!ext[0] || !ext[1])
--- 1.29/mysql-test/r/archive.result 2007-03-31 17:29:49 +05:00
+++ 1.30/mysql-test/r/archive.result 2007-03-31 17:29:49 +05:00
@@ -12666,3 +12666,13 @@ t6 CREATE TABLE `t6` (
KEY `a` (`a`)
) ENGINE=ARCHIVE DEFAULT CHARSET=latin1
DROP TABLE t1, t2, t4, t5, t6;
+drop table t1, t2, t4;
+create table t1 (i int) engine=archive;
+insert into t1 values (1);
+repair table t1 use_frm;
+Table Op Msg_type Msg_text
+test.t1 repair status OK
+select * from t1;
+i
+1
+drop table t1;
--- 1.30/mysql-test/t/archive.test 2007-03-31 17:29:49 +05:00
+++ 1.31/mysql-test/t/archive.test 2007-03-31 17:29:49 +05:00
@@ -1540,3 +1540,13 @@ SHOW CREATE TABLE t6;
--disable_warnings
DROP TABLE t1, t2, t4, t5, t6;
--enable_warnings
+
+#
+# BUG#26138 - REPAIR TABLE with option USE_FRM erases all records in ARCHIVE
+# table
+#
+create table t1 (i int) engine=archive;
+insert into t1 values (1);
+repair table t1 use_frm;
+select * from t1;
+drop table t1;
--- 1.60.15.1/sql/ha_archive.cc 2007-03-31 17:29:49 +05:00
+++ 1.136/storage/archive/ha_archive.cc 2007-03-31 17:29:49 +05:00
@@ -18,25 +18,27 @@
#endif
#include "mysql_priv.h"
+#include <myisam.h>
-#if defined(HAVE_ARCHIVE_DB)
#include "ha_archive.h"
#include <my_dir.h>
+#include <mysql/plugin.h>
+
/*
First, if you want to understand storage engines you should look at
ha_example.cc and ha_example.h.
+
This example was written as a test case for a customer who needed
a storage engine without indexes that could compress data very well.
So, welcome to a completely compressed storage engine. This storage
engine only does inserts. No replace, deletes, or updates. All reads are
- complete table scans. Compression is done through gzip (bzip compresses
- better, but only marginally, if someone asks I could add support for
- it too, but beaware that it costs a lot more in CPU time then gzip).
+ complete table scans. Compression is done through a combination of packing
+ and making use of the zlib library
We keep a file pointer open for each instance of ha_archive for each read
but for writes we keep one open file handle just for that. We flush it
- only if we have a read occur. gzip handles compressing lots of records
+ only if we have a read occur. azip handles compressing lots of records
at once much better then doing lots of little records between writes.
It is possible to not lock on writes but this would then mean we couldn't
handle bulk inserts as well (that is if someone was trying to read at
@@ -63,8 +65,7 @@
pool. For MyISAM its a question of how much the file system caches the
MyISAM file. With enough free memory MyISAM is faster. Its only when the OS
doesn't have enough memory to cache entire table that archive turns out
- to be any faster. For writes it is always a bit slower then MyISAM. It has no
- internal limits though for row length.
+ to be any faster.
Examples between MyISAM (packed) and Archive.
@@ -79,95 +80,59 @@
TODO:
- Add bzip optional support.
Allow users to set compression level.
- Add truncate table command.
+ Allow adjustable block size.
Implement versioning, should be easy.
Allow for errors, find a way to mark bad rows.
- Talk to the gzip guys, come up with a writable format so that updates are doable
- without switching to a block method.
Add optional feature so that rows can be flushed at interval (which will cause less
compression but may speed up ordered searches).
Checkpoint the meta file to allow for faster rebuilds.
- Dirty open (right now the meta file is repaired if a crash occured).
Option to allow for dirty reads, this would lower the sync calls, which would make
inserts a lot faster, but would mean highly arbitrary reads.
-Brian
*/
-/*
- Notes on file formats.
- The Meta file is layed out as:
- check - Just an int of 254 to make sure that the the file we are opening was
- never corrupted.
- version - The current version of the file format.
- rows - This is an unsigned long long which is the number of rows in the data
- file.
- check point - Reserved for future use
- dirty - Status of the file, whether or not its values are the latest. This
- flag is what causes a repair to occur
-
- The data file:
- check - Just an int of 254 to make sure that the the file we are opening was
- never corrupted.
- version - The current version of the file format.
- data - The data is stored in a "row +blobs" format.
-*/
-/* If the archive storage engine has been inited */
-static bool archive_inited= FALSE;
/* Variables for archive share methods */
pthread_mutex_t archive_mutex;
static HASH archive_open_tables;
-static z_off_t max_zfile_size;
-static int zoffset_size;
/* The file extension */
#define ARZ ".ARZ" // The data file
#define ARN ".ARN" // Files used during an optimize call
-#define ARM ".ARM" // Meta file
-/*
- uchar + uchar + ulonglong + ulonglong + uchar
-*/
-#define META_BUFFER_SIZE 19 // Size of the data used in the meta file
+#define ARM ".ARM" // Meta file (deprecated)
+
/*
uchar + uchar
*/
#define DATA_BUFFER_SIZE 2 // Size of the data used in the data file
#define ARCHIVE_CHECK_HEADER 254 // The number we use to determine corruption
-/*
+/* Static declarations for handerton */
+static handler *archive_create_handler(handlerton *hton,
+ TABLE_SHARE *table,
+ MEM_ROOT *mem_root);
+int archive_discover(handlerton *hton, THD* thd, const char *db,
+ const char *name,
+ const void** frmblob,
+ uint* frmlen);
+
+/*
Number of rows that will force a bulk insert.
*/
#define ARCHIVE_MIN_ROWS_TO_USE_BULK_INSERT 2
+/*
+ Size of header used for row
+*/
+#define ARCHIVE_ROW_HEADER_SIZE 4
-
-/* dummy handlerton - only to have something to return from archive_db_init */
-handlerton archive_hton = {
- "ARCHIVE",
- SHOW_OPTION_YES,
- "Archive storage engine",
- DB_TYPE_ARCHIVE_DB,
- archive_db_init,
- 0, /* slot */
- 0, /* savepoint size. */
- NULL, /* close_connection */
- NULL, /* savepoint */
- NULL, /* rollback to savepoint */
- NULL, /* releas 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 */
- HTON_NO_FLAGS
-};
-
+static handler *archive_create_handler(handlerton *hton,
+ TABLE_SHARE *table,
+ MEM_ROOT *mem_root)
+{
+ return new (mem_root) ha_archive(hton, table);
+}
/*
Used for hash table that tracks open tables.
@@ -185,16 +150,25 @@ static byte* archive_get_key(ARCHIVE_SHA
SYNOPSIS
archive_db_init()
- void
+ void *
RETURN
FALSE OK
TRUE Error
*/
-bool archive_db_init()
+int archive_db_init(void *p)
{
DBUG_ENTER("archive_db_init");
+ handlerton *archive_hton;
+
+ archive_hton= (handlerton *)p;
+ archive_hton->state= SHOW_OPTION_YES;
+ archive_hton->db_type= DB_TYPE_ARCHIVE_DB;
+ archive_hton->create= archive_create_handler;
+ archive_hton->flags= HTON_NO_FLAGS;
+ archive_hton->discover= archive_discover;
+
if (pthread_mutex_init(&archive_mutex, MY_MUTEX_INIT_FAST))
goto error;
if (hash_init(&archive_open_tables, system_charset_info, 32, 0, 0,
@@ -204,23 +178,9 @@ bool archive_db_init()
}
else
{
- zoffset_size= 2 << ((zlibCompileFlags() >> 6) & 3);
- switch (sizeof(z_off_t)) {
- case 2:
- max_zfile_size= INT_MAX16;
- break;
- case 8:
- max_zfile_size= (z_off_t) LONGLONG_MAX;
- break;
- case 4:
- default:
- max_zfile_size= INT_MAX32;
- }
- archive_inited= TRUE;
DBUG_RETURN(FALSE);
}
error:
- have_archive_db= SHOW_OPTION_DISABLED; // If we couldn't use handler
DBUG_RETURN(TRUE);
}
@@ -228,145 +188,113 @@ error:
Release the archive handler.
SYNOPSIS
- archive_db_end()
+ archive_db_done()
void
RETURN
FALSE OK
*/
-bool archive_db_end()
+int archive_db_done(void *p)
{
- if (archive_inited)
- {
- hash_free(&archive_open_tables);
- VOID(pthread_mutex_destroy(&archive_mutex));
- }
- archive_inited= 0;
- return FALSE;
+ hash_free(&archive_open_tables);
+ VOID(pthread_mutex_destroy(&archive_mutex));
+
+ return 0;
}
-ha_archive::ha_archive(TABLE *table_arg)
- :handler(&archive_hton, table_arg), delayed_insert(0), bulk_insert(0)
+
+ha_archive::ha_archive(handlerton *hton, TABLE_SHARE *table_arg)
+ :handler(hton, table_arg), delayed_insert(0), bulk_insert(0)
{
/* Set our original buffer from pre-allocated memory */
buffer.set((char *)byte_buffer, IO_SIZE, system_charset_info);
/* The size of the offset value we will use for position() */
- ref_length = zoffset_size;
- DBUG_ASSERT(ref_length <= sizeof(z_off_t));
+ ref_length= sizeof(my_off_t);
+ archive_reader_open= FALSE;
}
-/*
- This method reads the header of a datafile and returns whether or not it was successful.
-*/
-int ha_archive::read_data_header(gzFile file_to_read)
+int archive_discover(handlerton *hton, THD* thd, const char *db,
+ const char *name,
+ const void** frmblob,
+ uint* frmlen)
{
- uchar data_buffer[DATA_BUFFER_SIZE];
- DBUG_ENTER("ha_archive::read_data_header");
+ DBUG_ENTER("archive_discover");
+ DBUG_PRINT("archive_discover", ("db: %s, name: %s", db, name));
+ azio_stream frm_stream;
+ char az_file[FN_REFLEN];
+ char *frm_ptr;
+ MY_STAT file_stat;
- if (gzrewind(file_to_read) == -1)
- DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
+ fn_format(az_file, name, db, ARZ, MY_REPLACE_EXT | MY_UNPACK_FILENAME);
- if (gzread(file_to_read, data_buffer, DATA_BUFFER_SIZE) != DATA_BUFFER_SIZE)
- DBUG_RETURN(errno ? errno : -1);
-
- DBUG_PRINT("ha_archive::read_data_header", ("Check %u", data_buffer[0]));
- DBUG_PRINT("ha_archive::read_data_header", ("Version %u", data_buffer[1]));
-
- if ((data_buffer[0] != (uchar)ARCHIVE_CHECK_HEADER) &&
- (data_buffer[1] != (uchar)ARCHIVE_VERSION))
- DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
+ if (!(my_stat(az_file, &file_stat, MYF(0))))
+ goto err;
- DBUG_RETURN(0);
-}
+ if (!(azopen(&frm_stream, az_file, O_RDONLY|O_BINARY)))
+ {
+ if (errno == EROFS || errno == EACCES)
+ DBUG_RETURN(my_errno= errno);
+ DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
+ }
-/*
- This method writes out the header of a datafile and returns whether or not it was successful.
-*/
-int ha_archive::write_data_header(gzFile file_to_write)
-{
- uchar data_buffer[DATA_BUFFER_SIZE];
- DBUG_ENTER("ha_archive::write_data_header");
+ if (frm_stream.frm_length == 0)
+ goto err;
- data_buffer[0]= (uchar)ARCHIVE_CHECK_HEADER;
- data_buffer[1]= (uchar)ARCHIVE_VERSION;
+ frm_ptr= (char *)my_malloc(sizeof(char) * frm_stream.frm_length, MYF(0));
+ azread_frm(&frm_stream, frm_ptr);
+ azclose(&frm_stream);
- if (gzwrite(file_to_write, &data_buffer, DATA_BUFFER_SIZE) !=
- DATA_BUFFER_SIZE)
- goto error;
- DBUG_PRINT("ha_archive::write_data_header", ("Check %u", (uint)data_buffer[0]));
- DBUG_PRINT("ha_archive::write_data_header", ("Version %u", (uint)data_buffer[1]));
+ *frmlen= frm_stream.frm_length;
+ *frmblob= frm_ptr;
DBUG_RETURN(0);
-error:
- DBUG_RETURN(errno);
+err:
+ my_errno= 0;
+ DBUG_RETURN(1);
}
/*
- This method reads the header of a meta file and returns whether or not it was successful.
- *rows will contain the current number of rows in the data file upon success.
+ This method reads the header of a datafile and returns whether or not it was successful.
*/
-int ha_archive::read_meta_file(File meta_file, ha_rows *rows)
+int ha_archive::read_data_header(azio_stream *file_to_read)
{
- uchar meta_buffer[META_BUFFER_SIZE];
- ulonglong check_point;
-
- DBUG_ENTER("ha_archive::read_meta_file");
-
- VOID(my_seek(meta_file, 0, MY_SEEK_SET, MYF(0)));
- if (my_read(meta_file, (byte*)meta_buffer, META_BUFFER_SIZE, 0) != META_BUFFER_SIZE)
- DBUG_RETURN(-1);
-
- /*
- Parse out the meta data, we ignore version at the moment
- */
- *rows= (ha_rows)uint8korr(meta_buffer + 2);
- check_point= uint8korr(meta_buffer + 10);
-
- DBUG_PRINT("ha_archive::read_meta_file", ("Check %d", (uint)meta_buffer[0]));
- DBUG_PRINT("ha_archive::read_meta_file", ("Version %d", (uint)meta_buffer[1]));
- DBUG_PRINT("ha_archive::read_meta_file", ("Rows %lu", (ulong) *rows));
- DBUG_PRINT("ha_archive::read_meta_file", ("Checkpoint %lu", (ulong) check_point));
- DBUG_PRINT("ha_archive::read_meta_file", ("Dirty %d", (int)meta_buffer[18]));
+ int error;
+ unsigned long ret;
+ uchar data_buffer[DATA_BUFFER_SIZE];
+ DBUG_ENTER("ha_archive::read_data_header");
- if ((meta_buffer[0] != (uchar)ARCHIVE_CHECK_HEADER) ||
- ((bool)meta_buffer[18] == TRUE))
+ if (azrewind(file_to_read) == -1)
DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
- my_sync(meta_file, MYF(MY_WME));
+ if (file_to_read->version >= 3)
+ DBUG_RETURN(0);
+ /* Everything below this is just legacy to version 2< */
- DBUG_RETURN(0);
-}
+ DBUG_PRINT("ha_archive", ("Reading legacy data header"));
-/*
- This method writes out the header of a meta file and returns whether or not it was successful.
- By setting dirty you say whether or not the file represents the actual state of the data file.
- Upon ::open() we set to dirty, and upon ::close() we set to clean.
-*/
-int ha_archive::write_meta_file(File meta_file, ha_rows rows, bool dirty)
-{
- uchar meta_buffer[META_BUFFER_SIZE];
- ulonglong check_point= 0; //Reserved for the future
-
- DBUG_ENTER("ha_archive::write_meta_file");
-
- meta_buffer[0]= (uchar)ARCHIVE_CHECK_HEADER;
- meta_buffer[1]= (uchar)ARCHIVE_VERSION;
- int8store(meta_buffer + 2, (ulonglong)rows);
- int8store(meta_buffer + 10, check_point);
- *(meta_buffer + 18)= (uchar)dirty;
- DBUG_PRINT("ha_archive::write_meta_file", ("Check %d", (uint)ARCHIVE_CHECK_HEADER));
- DBUG_PRINT("ha_archive::write_meta_file", ("Version %d", (uint)ARCHIVE_VERSION));
- DBUG_PRINT("ha_archive::write_meta_file", ("Rows %lu", (ulong)rows));
- DBUG_PRINT("ha_archive::write_meta_file", ("Checkpoint %lu", (ulong) check_point));
- DBUG_PRINT("ha_archive::write_meta_file", ("Dirty %d", (uint)dirty));
+ ret= azread(file_to_read, data_buffer, DATA_BUFFER_SIZE, &error);
- VOID(my_seek(meta_file, 0, MY_SEEK_SET, MYF(0)));
- if (my_write(meta_file, (byte *)meta_buffer, META_BUFFER_SIZE, 0) != META_BUFFER_SIZE)
- DBUG_RETURN(-1);
+ if (ret != DATA_BUFFER_SIZE)
+ {
+ DBUG_PRINT("ha_archive", ("Reading, expected %d got %lu",
+ DATA_BUFFER_SIZE, ret));
+ DBUG_RETURN(1);
+ }
+
+ if (error)
+ {
+ DBUG_PRINT("ha_archive", ("Compression error (%d)", error));
+ DBUG_RETURN(1);
+ }
- my_sync(meta_file, MYF(MY_WME));
+ DBUG_PRINT("ha_archive", ("Check %u", data_buffer[0]));
+ DBUG_PRINT("ha_archive", ("Version %u", data_buffer[1]));
+
+ if ((data_buffer[0] != (uchar)ARCHIVE_CHECK_HEADER) &&
+ (data_buffer[1] != (uchar)ARCHIVE_VERSION))
+ DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
DBUG_RETURN(0);
}
@@ -381,9 +309,7 @@ int ha_archive::write_meta_file(File met
*/
ARCHIVE_SHARE *ha_archive::get_share(const char *table_name, int *rc)
{
- char meta_file_name[FN_REFLEN];
uint length;
- char *tmp_name;
DBUG_ENTER("ha_archive::get_share");
pthread_mutex_lock(&archive_mutex);
@@ -393,6 +319,9 @@ ARCHIVE_SHARE *ha_archive::get_share(con
(byte*) table_name,
length)))
{
+ char *tmp_name;
+ azio_stream archive_tmp;
+
if (!my_multi_malloc(MYF(MY_WME | MY_ZEROFILL),
&share, sizeof(*share),
&tmp_name, length+1,
@@ -408,31 +337,36 @@ ARCHIVE_SHARE *ha_archive::get_share(con
share->table_name= tmp_name;
share->crashed= FALSE;
share->archive_write_open= FALSE;
- fn_format(share->data_file_name,table_name,"",ARZ,
- MY_REPLACE_EXT|MY_UNPACK_FILENAME);
- fn_format(meta_file_name,table_name,"",ARM,
- MY_REPLACE_EXT|MY_UNPACK_FILENAME);
- strmov(share->table_name,table_name);
+ fn_format(share->data_file_name, table_name, "",
+ ARZ, MY_REPLACE_EXT | MY_UNPACK_FILENAME);
+ strmov(share->table_name, table_name);
+ DBUG_PRINT("ha_archive", ("Data File %s",
+ share->data_file_name));
/*
We will use this lock for rows.
*/
VOID(pthread_mutex_init(&share->mutex,MY_MUTEX_INIT_FAST));
- if ((share->meta_file= my_open(meta_file_name, O_RDWR, MYF(0))) == -1)
- share->crashed= TRUE;
/*
- After we read, we set the file to dirty. When we close, we will do the
- opposite. If the meta file will not open we assume it is crashed and
- leave it up to the user to fix.
+ We read the meta file, but do not mark it dirty. Since we are not
+ doing a write we won't mark it dirty (and we won't open it for
+ anything but reading... open it for write and we will generate null
+ compression writes).
*/
- if (read_meta_file(share->meta_file, &share->rows_recorded))
- share->crashed= TRUE;
+ if (!(azopen(&archive_tmp, share->data_file_name, O_RDONLY|O_BINARY)))
+ {
+ DBUG_RETURN(NULL);
+ }
+ stats.auto_increment_value= archive_tmp.auto_increment;
+ share->rows_recorded= (ha_rows)archive_tmp.rows;
+ share->crashed= archive_tmp.dirty;
+ azclose(&archive_tmp);
VOID(my_hash_insert(&archive_open_tables, (byte*) share));
thr_lock_init(&share->lock);
}
share->use_count++;
- DBUG_PRINT("info", ("archive table %.*s has %d open handles now",
+ DBUG_PRINT("ha_archive", ("archive table %.*s has %d open handles now",
share->table_name_length, share->table_name,
share->use_count));
if (share->crashed)
@@ -451,9 +385,10 @@ int ha_archive::free_share()
{
int rc= 0;
DBUG_ENTER("ha_archive::free_share");
- DBUG_PRINT("info", ("archive table %.*s has %d open handles on entrance",
- share->table_name_length, share->table_name,
- share->use_count));
+ DBUG_PRINT("ha_archive",
+ ("archive table %.*s has %d open handles on entrance",
+ share->table_name_length, share->table_name,
+ share->use_count));
pthread_mutex_lock(&archive_mutex);
if (!--share->use_count)
@@ -461,15 +396,18 @@ int ha_archive::free_share()
hash_delete(&archive_open_tables, (byte*) share);
thr_lock_delete(&share->lock);
VOID(pthread_mutex_destroy(&share->mutex));
- if (share->crashed)
- (void)write_meta_file(share->meta_file, share->rows_recorded, TRUE);
- else
- (void)write_meta_file(share->meta_file, share->rows_recorded, FALSE);
+ /*
+ We need to make sure we don't reset the crashed state.
+ If we open a crashed file, wee need to close it as crashed unless
+ it has been repaired.
+ Since we will close the data down after this, we go on and count
+ the flush on close;
+ */
if (share->archive_write_open)
- if (gzclose(share->archive_write) == Z_ERRNO)
+ {
+ if (azclose(&(share->archive_write)))
rc= 1;
- if (my_close(share->meta_file, MYF(0)))
- rc= 1;
+ }
my_free((gptr) share, MYF(0));
}
pthread_mutex_unlock(&archive_mutex);
@@ -480,21 +418,43 @@ int ha_archive::free_share()
int ha_archive::init_archive_writer()
{
DBUG_ENTER("ha_archive::init_archive_writer");
- (void)write_meta_file(share->meta_file, share->rows_recorded, TRUE);
-
/*
It is expensive to open and close the data files and since you can't have
a gzip file that can be both read and written we keep a writer open
that is shared amoung all open tables.
*/
- if ((share->archive_write= gzopen(share->data_file_name, "ab")) == NULL)
+ if (!(azopen(&(share->archive_write), share->data_file_name,
+ O_RDWR|O_BINARY)))
{
+ DBUG_PRINT("ha_archive", ("Could not open archive write file"));
share->crashed= TRUE;
DBUG_RETURN(1);
}
share->archive_write_open= TRUE;
- info(HA_STATUS_TIME);
- share->approx_file_size= (ulong) data_file_length;
+
+ DBUG_RETURN(0);
+}
+
+
+int ha_archive::init_archive_reader()
+{
+ DBUG_ENTER("ha_archive::init_archive_reader");
+ /*
+ It is expensive to open and close the data files and since you can't have
+ a gzip file that can be both read and written we keep a writer open
+ that is shared amoung all open tables.
+ */
+ if (!archive_reader_open)
+ {
+ if (!(azopen(&archive, share->data_file_name, O_RDONLY|O_BINARY)))
+ {
+ DBUG_PRINT("ha_archive", ("Could not open archive read file"));
+ share->crashed= TRUE;
+ DBUG_RETURN(1);
+ }
+ archive_reader_open= TRUE;
+ }
+
DBUG_RETURN(0);
}
@@ -503,7 +463,6 @@ int ha_archive::init_archive_writer()
We just implement one additional file extension.
*/
static const char *ha_archive_exts[] = {
- ARM,
ARZ,
NullS
};
@@ -525,7 +484,7 @@ int ha_archive::open(const char *name, i
int rc= 0;
DBUG_ENTER("ha_archive::open");
- DBUG_PRINT("info", ("archive table was opened for crash %s",
+ DBUG_PRINT("ha_archive", ("archive table was opened for crash: %s",
(open_options & HA_OPEN_FOR_REPAIR) ? "yes" : "no"));
share= get_share(name, &rc);
@@ -541,16 +500,20 @@ int ha_archive::open(const char *name, i
DBUG_RETURN(rc);
}
- thr_lock_data_init(&share->lock,&lock,NULL);
+ DBUG_ASSERT(share);
- if ((archive= gzopen(share->data_file_name, "rb")) == NULL)
+ record_buffer= create_record_buffer(table->s->reclength +
+ ARCHIVE_ROW_HEADER_SIZE);
+
+ if (!record_buffer)
{
- if (errno == EROFS || errno == EACCES)
- DBUG_RETURN(my_errno= errno);
- DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
+ free_share();
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
}
- DBUG_PRINT("info", ("archive table was crashed %s",
+ thr_lock_data_init(&share->lock, &lock, NULL);
+
+ DBUG_PRINT("ha_archive", ("archive table was crashed %s",
rc == HA_ERR_CRASHED_ON_USAGE ? "yes" : "no"));
if (rc == HA_ERR_CRASHED_ON_USAGE && open_options & HA_OPEN_FOR_REPAIR)
{
@@ -583,9 +546,14 @@ int ha_archive::close(void)
int rc= 0;
DBUG_ENTER("ha_archive::close");
+ destroy_record_buffer(record_buffer);
+
/* First close stream */
- if (gzclose(archive) == Z_ERRNO)
- rc= 1;
+ if (archive_reader_open)
+ {
+ if (azclose(&archive))
+ rc= 1;
+ }
/* then also close share */
rc|= free_share();
@@ -605,57 +573,120 @@ int ha_archive::close(void)
int ha_archive::create(const char *name, TABLE *table_arg,
HA_CREATE_INFO *create_info)
{
- File create_file; // We use to create the datafile and the metafile
char name_buff[FN_REFLEN];
+ char linkname[FN_REFLEN];
int error;
+ azio_stream create_stream; /* Archive file we are working with */
+ File frm_file; /* File handler for readers */
+ MY_STAT file_stat; // Stat information for the data file
+ byte *frm_ptr;
+
DBUG_ENTER("ha_archive::create");
- if ((create_file= my_create(fn_format(name_buff,name,"",ARM,
- MY_REPLACE_EXT|MY_UNPACK_FILENAME),0,
- O_RDWR | O_TRUNC,MYF(MY_WME))) < 0)
+ stats.auto_increment_value= (create_info->auto_increment_value ?
+ create_info->auto_increment_value -1 :
+ (ulonglong) 0);
+
+ for (uint key= 0; key < table_arg->s->keys; key++)
{
- error= my_errno;
- goto error;
+ KEY *pos= table_arg->key_info+key;
+ KEY_PART_INFO *key_part= pos->key_part;
+ KEY_PART_INFO *key_part_end= key_part + pos->key_parts;
+
+ for (; key_part != key_part_end; key_part++)
+ {
+ Field *field= key_part->field;
+
+ if (!(field->flags & AUTO_INCREMENT_FLAG))
+ {
+ error= -1;
+ DBUG_PRINT("ha_archive", ("Index error in creating archive table"));
+ goto error;
+ }
+ }
}
- write_meta_file(create_file, 0, FALSE);
- my_close(create_file,MYF(0));
/*
We reuse name_buff since it is available.
*/
- if ((create_file= my_create(fn_format(name_buff,name,"",ARZ,
- MY_REPLACE_EXT|MY_UNPACK_FILENAME),0,
- O_RDWR | O_TRUNC,MYF(MY_WME))) < 0)
+ if (create_info->data_file_name && create_info->data_file_name[0] != '#')
{
- error= my_errno;
- goto error;
- }
- if ((archive= gzdopen(dup(create_file), "wb")) == NULL)
- {
- error= errno;
- goto error2;
+ DBUG_PRINT("ha_archive", ("archive will create stream file %s",
+ create_info->data_file_name));
+
+ fn_format(name_buff, create_info->data_file_name, "", ARZ,
+ MY_REPLACE_EXT | MY_UNPACK_FILENAME);
+ fn_format(linkname, name, "", ARZ,
+ MY_REPLACE_EXT | MY_UNPACK_FILENAME);
}
- if (write_data_header(archive))
+ else
{
- error= errno;
- goto error3;
+ fn_format(name_buff, name, "", ARZ,
+ MY_REPLACE_EXT | MY_UNPACK_FILENAME);
+ linkname[0]= 0;
}
- if (gzclose(archive))
+ /*
+ There is a chance that the file was "discovered". In this case
+ just use whatever file is there.
+ */
+ if (!(my_stat(name_buff, &file_stat, MYF(0))))
{
- error= errno;
- goto error2;
+ my_errno= 0;
+ if (!(azopen(&create_stream, name_buff, O_CREAT|O_RDWR|O_BINARY)))
+ {
+ error= errno;
+ goto error2;
+ }
+
+ if (linkname[0])
+ my_symlink(name_buff, linkname, MYF(0));
+ fn_format(name_buff, name, "", ".frm",
+ MY_REPLACE_EXT | MY_UNPACK_FILENAME);
+
+ /*
+ Here is where we open up the frm and pass it to archive to store
+ */
+ if ((frm_file= my_open(name_buff, O_RDONLY, MYF(0))) > 0)
+ {
+ if (!my_fstat(frm_file, &file_stat, MYF(MY_WME)))
+ {
+ frm_ptr= (byte *)my_malloc(sizeof(byte) * file_stat.st_size , MYF(0));
+ if (frm_ptr)
+ {
+ my_read(frm_file, frm_ptr, file_stat.st_size, MYF(0));
+ azwrite_frm(&create_stream, (char *)frm_ptr, file_stat.st_size);
+ my_free((gptr)frm_ptr, MYF(0));
+ }
+ }
+ my_close(frm_file, MYF(0));
+ }
+
+ if (create_info->comment.str)
+ azwrite_comment(&create_stream, create_info->comment.str,
+ create_info->comment.length);
+
+ /*
+ Yes you need to do this, because the starting value
+ for the autoincrement may not be zero.
+ */
+ create_stream.auto_increment= stats.auto_increment_value;
+ if (azclose(&create_stream))
+ {
+ error= errno;
+ goto error2;
+ }
}
+ else
+ my_errno= 0;
+
+ DBUG_PRINT("ha_archive", ("Creating File %s", name_buff));
+ DBUG_PRINT("ha_archive", ("Creating Link %s", linkname));
- my_close(create_file, MYF(0));
DBUG_RETURN(0);
-error3:
- /* We already have an error, so ignore results of gzclose. */
- (void)gzclose(archive);
error2:
- my_close(create_file, MYF(0));
delete_table(name);
error:
/* Return error number, if we got one */
@@ -665,51 +696,82 @@ error:
/*
This is where the actual row is written out.
*/
-int ha_archive::real_write_row(byte *buf, gzFile writer)
+int ha_archive::real_write_row(byte *buf, azio_stream *writer)
{
- z_off_t written, total_row_length;
- uint *ptr, *end;
+ my_off_t written;
+ unsigned int r_pack_length;
DBUG_ENTER("ha_archive::real_write_row");
- total_row_length= table->s->reclength;
- for (ptr= table->s->blob_field, end= ptr + table->s->blob_fields;
- ptr != end; ptr++)
- total_row_length+= ((Field_blob*) table->field[*ptr])->get_length();
- if (share->approx_file_size > max_zfile_size - total_row_length)
- {
- info(HA_STATUS_TIME);
- share->approx_file_size= (ulong) data_file_length;
- if (share->approx_file_size > max_zfile_size - total_row_length)
- DBUG_RETURN(HA_ERR_RECORD_FILE_FULL);
- }
- share->approx_file_size+= total_row_length;
- written= gzwrite(writer, buf, table->s->reclength);
- DBUG_PRINT("ha_archive::real_write_row", ("Wrote %d bytes expected %lu", (int) written,
- table->s->reclength));
+
+ /* We pack the row for writing */
+ r_pack_length= pack_row(buf);
+
+ written= azwrite(writer, record_buffer->buffer, r_pack_length);
+ if (written != r_pack_length)
+ {
+ DBUG_PRINT("ha_archive", ("Wrote %d bytes expected %d",
+ (uint32) written,
+ (uint32)r_pack_length));
+ DBUG_RETURN(-1);
+ }
+
if (!delayed_insert || !bulk_insert)
share->dirty= TRUE;
- if (written != (z_off_t)table->s->reclength)
- DBUG_RETURN(errno ? errno : -1);
- /*
- We should probably mark the table as damagaged if the record is written
- but the blob fails.
- */
- for (ptr= table->s->blob_field, end= ptr + table->s->blob_fields ;
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Calculate max length needed for row. This includes
+ the bytes required for the length in the header.
+*/
+
+uint32 ha_archive::max_row_length(const byte *buf)
+{
+ uint32 length= (uint32)(table->s->reclength + table->s->fields*2);
+ length+= ARCHIVE_ROW_HEADER_SIZE;
+
+ uint *ptr, *end;
+ for (ptr= table->s->blob_field, end=ptr + table->s->blob_fields ;
ptr != end ;
ptr++)
{
- char *data_ptr;
- uint32 size= ((Field_blob*) table->field[*ptr])->get_length();
+ length += 2 + ((Field_blob*)table->field[*ptr])->get_length();
+ }
- if (size)
- {
- ((Field_blob*) table->field[*ptr])->get_ptr(&data_ptr);
- written= gzwrite(writer, data_ptr, (unsigned)size);
- if (written != (z_off_t)size)
- DBUG_RETURN(errno ? errno : -1);
- }
+ return length;
+}
+
+
+unsigned int ha_archive::pack_row(byte *record)
+{
+ byte *ptr;
+
+ DBUG_ENTER("ha_archive::pack_row");
+
+
+ if (fix_rec_buff(max_row_length(record)))
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM); /* purecov: inspected */
+
+ /* Copy null bits */
+ memcpy(record_buffer->buffer+ARCHIVE_ROW_HEADER_SIZE,
+ record, table->s->null_bytes);
+ ptr= record_buffer->buffer + table->s->null_bytes + ARCHIVE_ROW_HEADER_SIZE;
+
+ for (Field **field=table->field ; *field ; field++)
+ {
+ if (!((*field)->is_null()))
+ ptr=(byte*) (*field)->pack((char*) ptr,
+ (char*) record + (*field)->offset(record));
}
- DBUG_RETURN(0);
+
+ int4store(record_buffer->buffer, (int)(ptr - record_buffer->buffer -
+ ARCHIVE_ROW_HEADER_SIZE));
+ DBUG_PRINT("ha_archive",("Pack row length %u", (unsigned int)
+ (ptr - record_buffer->buffer -
+ ARCHIVE_ROW_HEADER_SIZE)));
+
+ DBUG_RETURN((unsigned int) (ptr - record_buffer->buffer));
}
@@ -725,50 +787,192 @@ int ha_archive::real_write_row(byte *buf
int ha_archive::write_row(byte *buf)
{
int rc;
+ byte *read_buf= NULL;
+ ulonglong temp_auto;
+ byte *record= table->record[0];
DBUG_ENTER("ha_archive::write_row");
if (share->crashed)
+ DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
+
+ if (!share->archive_write_open)
+ if (init_archive_writer())
DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
- statistic_increment(table->in_use->status_var.ha_write_count, &LOCK_status);
+ ha_statistic_increment(&SSV::ha_write_count);
if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT)
table->timestamp_field->set_time();
pthread_mutex_lock(&share->mutex);
- if (!share->archive_write_open)
- if (init_archive_writer())
- DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
- /*
- Varchar structures are constant in size but are not cleaned up request
- to request. The following sets all unused space to null to improve
- compression.
- */
- for (Field **field=table->field ; *field ; field++)
+ if (table->next_number_field && record == table->record[0])
{
- DBUG_PRINT("archive",("Pack is %d\n", (*field)->pack_length()));
- DBUG_PRINT("archive",("MyPack is %d\n", (*field)->data_length((char*) buf + (*field)->offset())));
- if ((*field)->real_type() == MYSQL_TYPE_VARCHAR)
+ KEY *mkey= &table->s->key_info[0]; // We only support one key right now
+ update_auto_increment();
+ temp_auto= table->next_number_field->val_int();
+
+ /*
+ We don't support decremening auto_increment. They make the performance
+ just cry.
+ */
+ if (temp_auto <= share->archive_write.auto_increment &&
+ mkey->flags & HA_NOSAME)
{
-#ifndef DBUG_OFF
- uint actual_length= (*field)->data_length((char*) buf + (*field)->offset());
- uint offset= (*field)->offset() + actual_length +
- (actual_length > 255 ? 2 : 1);
- DBUG_PRINT("archive",("Offset is %d -> %d\n", actual_length, offset));
-#endif
- /*
- if ((*field)->pack_length() + (*field)->offset() != offset)
- bzero(buf + offset, (size_t)((*field)->pack_length() + (actual_length > 255 ? 2 : 1) - (*field)->data_length));
+ rc= HA_ERR_FOUND_DUPP_KEY;
+ goto error;
+ }
+#ifdef DEAD_CODE
+ /*
+ Bad news, this will cause a search for the unique value which is very
+ expensive since we will have to do a table scan which will lock up
+ all other writers during this period. This could perhaps be optimized
+ in the future.
*/
+ {
+ /*
+ First we create a buffer that we can use for reading rows, and can pass
+ to get_row().
+ */
+ if (!(read_buf= (byte*) my_malloc(table->s->reclength, MYF(MY_WME))))
+ {
+ rc= HA_ERR_OUT_OF_MEM;
+ goto error;
+ }
+ /*
+ All of the buffer must be written out or we won't see all of the
+ data
+ */
+ azflush(&(share->archive_write), Z_SYNC_FLUSH);
+ /*
+ Set the position of the local read thread to the beginning postion.
+ */
+ if (read_data_header(&archive))
+ {
+ rc= HA_ERR_CRASHED_ON_USAGE;
+ goto error;
+ }
+
+ Field *mfield= table->next_number_field;
+
+ while (!(get_row(&archive, read_buf)))
+ {
+ if (!memcmp(read_buf + mfield->offset(record),
+ table->next_number_field->ptr,
+ mfield->max_display_length()))
+ {
+ rc= HA_ERR_FOUND_DUPP_KEY;
+ goto error;
+ }
+ }
+ }
+#endif
+ else
+ {
+ if (temp_auto > share->archive_write.auto_increment)
+ stats.auto_increment_value= share->archive_write.auto_increment=
+ temp_auto;
}
}
+ /*
+ Notice that the global auto_increment has been increased.
+ In case of a failed row write, we will never try to reuse the value.
+ */
share->rows_recorded++;
- rc= real_write_row(buf, share->archive_write);
+ rc= real_write_row(buf, &(share->archive_write));
+error:
pthread_mutex_unlock(&share->mutex);
+ if (read_buf)
+ my_free((gptr) read_buf, MYF(0));
+
+ DBUG_RETURN(rc);
+}
+
+
+void ha_archive::get_auto_increment(ulonglong offset, ulonglong increment,
+ ulonglong nb_desired_values,
+ ulonglong *first_value,
+ ulonglong *nb_reserved_values)
+{
+ *nb_reserved_values= 1;
+ *first_value= share->archive_write.auto_increment + 1;
+}
+/* Initialized at each key walk (called multiple times unlike rnd_init()) */
+int ha_archive::index_init(uint keynr, bool sorted)
+{
+ DBUG_ENTER("ha_archive::index_init");
+ active_index= keynr;
+ DBUG_RETURN(0);
+}
+
+
+/*
+ No indexes, so if we get a request for an index search since we tell
+ the optimizer that we have unique indexes, we scan
+*/
+int ha_archive::index_read(byte *buf, const byte *key,
+ uint key_len, enum ha_rkey_function find_flag)
+{
+ int rc;
+ DBUG_ENTER("ha_archive::index_read");
+ rc= index_read_idx(buf, active_index, key, key_len, find_flag);
DBUG_RETURN(rc);
}
+
+int ha_archive::index_read_idx(byte *buf, uint index, const byte *key,
+ uint key_len, enum ha_rkey_function find_flag)
+{
+ int rc;
+ bool found= 0;
+ KEY *mkey= &table->s->key_info[index];
+ current_k_offset= mkey->key_part->offset;
+ current_key= key;
+ current_key_len= key_len;
+
+
+ DBUG_ENTER("ha_archive::index_read_idx");
+
+ rc= rnd_init(TRUE);
+
+ if (rc)
+ goto error;
+
+ while (!(get_row(&archive, buf)))
+ {
+ if (!memcmp(current_key, buf + current_k_offset, current_key_len))
+ {
+ found= 1;
+ break;
+ }
+ }
+
+ if (found)
+ DBUG_RETURN(0);
+
+error:
+ DBUG_RETURN(rc ? rc : HA_ERR_END_OF_FILE);
+}
+
+
+int ha_archive::index_next(byte * buf)
+{
+ bool found= 0;
+
+ DBUG_ENTER("ha_archive::index_next");
+
+ while (!(get_row(&archive, buf)))
+ {
+ if (!memcmp(current_key, buf+current_k_offset, current_key_len))
+ {
+ found= 1;
+ break;
+ }
+ }
+
+ DBUG_RETURN(found ? 0 : HA_ERR_END_OF_FILE);
+}
+
/*
All calls that need to scan the table start with this method. If we are told
that it is a table scan we rewind the file to the beginning, otherwise
@@ -782,30 +986,33 @@ int ha_archive::rnd_init(bool scan)
if (share->crashed)
DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
+ init_archive_reader();
+
/* We rewind the file so that we can read from the beginning if scan */
if (scan)
{
- scan_rows= share->rows_recorded;
- DBUG_PRINT("info", ("archive will retrieve %lu rows", (ulong) scan_rows));
- records= 0;
+ DBUG_PRINT("info", ("archive will retrieve %llu rows",
+ (unsigned long long) scan_rows));
+ stats.records= 0;
/*
If dirty, we lock, and then reset/flush the data.
- I found that just calling gzflush() doesn't always work.
+ I found that just calling azflush() doesn't always work.
*/
+ pthread_mutex_lock(&share->mutex);
+ scan_rows= share->rows_recorded;
if (share->dirty == TRUE)
{
- pthread_mutex_lock(&share->mutex);
if (share->dirty == TRUE)
{
- DBUG_PRINT("info", ("archive flushing out rows for scan"));
- gzflush(share->archive_write, Z_SYNC_FLUSH);
+ DBUG_PRINT("ha_archive", ("archive flushing out rows for scan"));
+ azflush(&(share->archive_write), Z_SYNC_FLUSH);
share->dirty= FALSE;
}
- pthread_mutex_unlock(&share->mutex);
}
+ pthread_mutex_unlock(&share->mutex);
- if (read_data_header(archive))
+ if (read_data_header(&archive))
DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
}
@@ -817,25 +1024,133 @@ int ha_archive::rnd_init(bool scan)
This is the method that is used to read a row. It assumes that the row is
positioned where you want it.
*/
-int ha_archive::get_row(gzFile file_to_read, byte *buf)
+int ha_archive::get_row(azio_stream *file_to_read, byte *buf)
{
- int read; // Bytes read, gzread() returns int
- uint *ptr, *end;
- char *last;
- size_t total_blob_length= 0;
+ int rc;
DBUG_ENTER("ha_archive::get_row");
+ DBUG_PRINT("ha_archive", ("Picking version for get_row() %d -> %d",
+ (uchar)file_to_read->version,
+ ARCHIVE_VERSION));
+ if (file_to_read->version == ARCHIVE_VERSION)
+ rc= get_row_version3(file_to_read, buf);
+ else
+ rc= get_row_version2(file_to_read, buf);
+
+ DBUG_PRINT("ha_archive", ("Return %d\n", rc));
+
+ DBUG_RETURN(rc);
+}
+
+/* Reallocate buffer if needed */
+bool ha_archive::fix_rec_buff(unsigned int length)
+{
+ DBUG_ENTER("ha_archive::fix_rec_buff");
+ DBUG_PRINT("ha_archive", ("Fixing %u for %u",
+ length, record_buffer->length));
+ DBUG_ASSERT(record_buffer->buffer);
+
+ if (length > record_buffer->length)
+ {
+ byte *newptr;
+ if (!(newptr=(byte*) my_realloc((gptr) record_buffer->buffer,
+ length,
+ MYF(MY_ALLOW_ZERO_PTR))))
+ DBUG_RETURN(1);
+ record_buffer->buffer= newptr;
+ record_buffer->length= length;
+ }
- read= gzread(file_to_read, buf, table->s->reclength);
- DBUG_PRINT("ha_archive::get_row", ("Read %d bytes expected %lu", (int) read,
- table->s->reclength));
+ DBUG_ASSERT(length <= record_buffer->length);
+
+ DBUG_RETURN(0);
+}
- if (read == Z_STREAM_ERROR)
+int ha_archive::unpack_row(azio_stream *file_to_read, byte *record)
+{
+ DBUG_ENTER("ha_archive::unpack_row");
+
+ unsigned int read;
+ int error;
+ byte size_buffer[ARCHIVE_ROW_HEADER_SIZE];
+ unsigned int row_len;
+
+ /* First we grab the length stored */
+ read= azread(file_to_read, (byte *)size_buffer, ARCHIVE_ROW_HEADER_SIZE, &error);
+
+ if (error == Z_STREAM_ERROR || (read && read < ARCHIVE_ROW_HEADER_SIZE))
DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
/* If we read nothing we are at the end of the file */
+ if (read == 0 || read != ARCHIVE_ROW_HEADER_SIZE)
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+
+ row_len= uint4korr(size_buffer);
+ DBUG_PRINT("ha_archive",("Unpack row length %u -> %u", row_len,
+ (unsigned int)table->s->reclength));
+ fix_rec_buff(row_len);
+ DBUG_ASSERT(row_len <= record_buffer->length);
+
+ read= azread(file_to_read, record_buffer->buffer, row_len, &error);
+
+ DBUG_ASSERT(row_len == read);
+
+ if (read != row_len || error)
+ {
+ DBUG_RETURN(-1);
+ }
+
+ /* Copy null bits */
+ const char *ptr= (const char*) record_buffer->buffer;
+ memcpy(record, ptr, table->s->null_bytes);
+ ptr+= table->s->null_bytes;
+ for (Field **field=table->field ; *field ; field++)
+ if (!((*field)->is_null()))
+ {
+ ptr= (*field)->unpack((char *)record +
+ (*field)->offset(table->record[0]), ptr);
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+int ha_archive::get_row_version3(azio_stream *file_to_read, byte *buf)
+{
+ DBUG_ENTER("ha_archive::get_row_version3");
+
+ int returnable= unpack_row(file_to_read, buf);
+
+ DBUG_RETURN(returnable);
+}
+
+
+int ha_archive::get_row_version2(azio_stream *file_to_read, byte *buf)
+{
+ unsigned int read;
+ int error;
+ uint *ptr, *end;
+ char *last;
+ size_t total_blob_length= 0;
+ MY_BITMAP *read_set= table->read_set;
+ DBUG_ENTER("ha_archive::get_row_version2");
+
+ read= azread(file_to_read, (voidp)buf, table->s->reclength, &error);
+
+ /* If we read nothing we are at the end of the file */
if (read == 0)
DBUG_RETURN(HA_ERR_END_OF_FILE);
+ if (read != table->s->reclength)
+ {
+ DBUG_PRINT("ha_archive::get_row_version2", ("Read %u bytes expected %u",
+ read,
+ (unsigned int)table->s->reclength));
+ DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
+ }
+
+ if (error == Z_STREAM_ERROR || error == Z_DATA_ERROR )
+ DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
+
/*
If the record is the wrong size, the file is probably damaged, unless
we are dealing with a delayed insert or a bulk insert.
@@ -847,7 +1162,11 @@ int ha_archive::get_row(gzFile file_to_r
for (ptr= table->s->blob_field, end=ptr + table->s->blob_fields ;
ptr != end ;
ptr++)
- total_blob_length += ((Field_blob*) table->field[*ptr])->get_length();
+ {
+ if (bitmap_is_set(read_set,
+ (((Field_blob*) table->field[*ptr])->field_index)))
+ total_blob_length += ((Field_blob*) table->field[*ptr])->get_length();
+ }
/* Adjust our row buffer if we need be */
buffer.alloc(total_blob_length);
@@ -861,11 +1180,23 @@ int ha_archive::get_row(gzFile file_to_r
size_t size= ((Field_blob*) table->field[*ptr])->get_length();
if (size)
{
- read= gzread(file_to_read, last, size);
- if ((size_t) read != size)
- DBUG_RETURN(HA_ERR_END_OF_FILE);
- ((Field_blob*) table->field[*ptr])->set_ptr(size, last);
- last += size;
+ if (bitmap_is_set(read_set,
+ ((Field_blob*) table->field[*ptr])->field_index))
+ {
+ read= azread(file_to_read, last, size, &error);
+
+ if (error)
+ DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
+
+ if ((size_t) read != size)
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ ((Field_blob*) table->field[*ptr])->set_ptr(size, last);
+ last += size;
+ }
+ else
+ {
+ (void)azseek(file_to_read, size, SEEK_CUR);
+ }
}
}
DBUG_RETURN(0);
@@ -889,14 +1220,13 @@ int ha_archive::rnd_next(byte *buf)
DBUG_RETURN(HA_ERR_END_OF_FILE);
scan_rows--;
- statistic_increment(table->in_use->status_var.ha_read_rnd_next_count,
- &LOCK_status);
- current_position= gztell(archive);
- rc= get_row(archive, buf);
+ ha_statistic_increment(&SSV::ha_read_rnd_next_count);
+ current_position= aztell(&archive);
+ rc= get_row(&archive, buf);
if (rc != HA_ERR_END_OF_FILE)
- records++;
+ stats.records++;
DBUG_RETURN(rc);
}
@@ -926,12 +1256,11 @@ void ha_archive::position(const byte *re
int ha_archive::rnd_pos(byte * buf, byte *pos)
{
DBUG_ENTER("ha_archive::rnd_pos");
- statistic_increment(table->in_use->status_var.ha_read_rnd_next_count,
- &LOCK_status);
- current_position= (z_off_t)my_get_ptr(pos, ref_length);
- (void)gzseek(archive, current_position, SEEK_SET);
+ ha_statistic_increment(&SSV::ha_read_rnd_next_count);
+ current_position= (my_off_t)my_get_ptr(pos, ref_length);
+ (void)azseek(&archive, current_position, SEEK_SET);
- DBUG_RETURN(get_row(archive, buf));
+ DBUG_RETURN(get_row(&archive, buf));
}
/*
@@ -959,55 +1288,42 @@ int ha_archive::repair(THD* thd, HA_CHEC
int ha_archive::optimize(THD* thd, HA_CHECK_OPT* check_opt)
{
DBUG_ENTER("ha_archive::optimize");
- int rc;
- gzFile writer;
+ int rc= 0;
+ azio_stream writer;
char writer_filename[FN_REFLEN];
- /* Open up the writer if we haven't yet */
- if (!share->archive_write_open)
- init_archive_writer();
+ init_archive_reader();
- /* Flush any waiting data */
- gzflush(share->archive_write, Z_SYNC_FLUSH);
+ // now we close both our writer and our reader for the rename
+ if (share->archive_write_open)
+ {
+ azclose(&(share->archive_write));
+ share->archive_write_open= FALSE;
+ }
/* Lets create a file to contain the new data */
fn_format(writer_filename, share->table_name, "", ARN,
- MY_REPLACE_EXT|MY_UNPACK_FILENAME);
+ MY_REPLACE_EXT | MY_UNPACK_FILENAME);
- if ((writer= gzopen(writer_filename, "wb")) == NULL)
+ if (!(azopen(&writer, writer_filename, O_CREAT|O_RDWR|O_BINARY)))
DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE);
/*
An extended rebuild is a lot more effort. We open up each row and re-record it.
Any dead rows are removed (aka rows that may have been partially recorded).
- */
- if (check_opt->flags == T_EXTEND)
+ As of Archive format 3, this is the only type that is performed, before this
+ version it was just done on T_EXTEND
+ */
+ if (1)
{
- byte *buf;
-
- /*
- First we create a buffer that we can use for reading rows, and can pass
- to get_row().
- */
- if (!(buf= (byte*) my_malloc(table->s->reclength, MYF(MY_WME))))
- {
- rc= HA_ERR_OUT_OF_MEM;
- goto error;
- }
+ DBUG_PRINT("ha_archive", ("archive extended rebuild"));
/*
Now we will rewind the archive file so that we are positioned at the
start of the file.
*/
- rc= read_data_header(archive);
-
- /*
- Assuming now error from rewinding the archive file, we now write out the
- new header for out data file.
- */
- if (!rc)
- rc= write_data_header(writer);
+ rc= read_data_header(&archive);
/*
On success of writing out the new header, we now fetch each row and
@@ -1016,58 +1332,55 @@ int ha_archive::optimize(THD* thd, HA_CH
if (!rc)
{
share->rows_recorded= 0;
- while (!(rc= get_row(archive, buf)))
+ stats.auto_increment_value= share->archive_write.auto_increment= 0;
+ my_bitmap_map *org_bitmap= dbug_tmp_use_all_columns(table, table->read_set);
+
+ while (!(rc= get_row(&archive, table->record[0])))
{
- real_write_row(buf, writer);
- share->rows_recorded++;
+ real_write_row(table->record[0], &writer);
+ /*
+ Long term it should be possible to optimize this so that
+ it is not called on each row.
+ */
+ if (table->found_next_number_field)
+ {
+ Field *field= table->found_next_number_field;
+ ulonglong auto_value=
+ (ulonglong) field->val_int((char*)(table->record[0] +
+ field->offset(table->record[0])));
+ if (share->archive_write.auto_increment < auto_value)
+ stats.auto_increment_value= share->archive_write.auto_increment=
+ auto_value;
+ }
}
+
+ dbug_tmp_restore_column_map(table->read_set, org_bitmap);
+ share->rows_recorded= (ha_rows)writer.rows;
}
- DBUG_PRINT("info", ("recovered %lu archive rows",
- (ulong) share->rows_recorded));
- my_free((char*)buf, MYF(0));
+ DBUG_PRINT("info", ("recovered %llu archive rows",
+ (unsigned long long)share->rows_recorded));
+
+ DBUG_PRINT("ha_archive", ("recovered %llu archive rows",
+ (unsigned long long)share->rows_recorded));
+
if (rc && rc != HA_ERR_END_OF_FILE)
goto error;
}
- else
- {
- /*
- The quick method is to just read the data raw, and then compress it directly.
- */
- int read; // Bytes read, gzread() returns int
- char block[IO_SIZE];
- if (gzrewind(archive) == -1)
- {
- rc= HA_ERR_CRASHED_ON_USAGE;
- goto error;
- }
-
- while ((read= gzread(archive, block, IO_SIZE)))
- gzwrite(writer, block, read);
- }
- gzflush(writer, Z_SYNC_FLUSH);
+ azclose(&writer);
share->dirty= FALSE;
- gzclose(share->archive_write);
- share->archive_write= writer;
-
- my_rename(writer_filename,share->data_file_name,MYF(0));
-
- /*
- Now we need to reopen our read descriptor since it has changed.
- */
- gzclose(archive);
- if ((archive= gzopen(share->data_file_name, "rb")) == NULL)
- {
- rc= HA_ERR_CRASHED_ON_USAGE;
- goto error;
- }
+
+ azclose(&archive);
+ // make the file we just wrote be our data file
+ rc = my_rename(writer_filename,share->data_file_name,MYF(0));
- DBUG_RETURN(0);
+ DBUG_RETURN(rc);
error:
- gzclose(writer);
+ DBUG_PRINT("ha_archive", ("Failed to recover, error was %d", rc));
+ azclose(&writer);
DBUG_RETURN(rc);
}
@@ -1094,8 +1407,8 @@ THR_LOCK_DATA **ha_archive::store_lock(T
*/
if ((lock_type >= TL_WRITE_CONCURRENT_INSERT &&
- lock_type <= TL_WRITE) && !thd->in_lock_tables
- && !thd->tablespace_op)
+ lock_type <= TL_WRITE) && !thd_in_lock_tables(thd)
+ && !thd_tablespace_op(thd))
lock_type = TL_WRITE_ALLOW_WRITE;
/*
@@ -1106,7 +1419,7 @@ THR_LOCK_DATA **ha_archive::store_lock(T
concurrent inserts to t2.
*/
- if (lock_type == TL_READ_NO_INSERT && !thd->in_lock_tables)
+ if (lock_type == TL_READ_NO_INSERT && !thd_in_lock_tables(thd))
lock_type = TL_READ;
lock.type=lock_type;
@@ -1117,6 +1430,31 @@ THR_LOCK_DATA **ha_archive::store_lock(T
return to;
}
+void ha_archive::update_create_info(HA_CREATE_INFO *create_info)
+{
+ DBUG_ENTER("ha_archive::update_create_info");
+
+ ha_archive::info(HA_STATUS_AUTO);
+ if (create_info->used_fields & HA_CREATE_USED_AUTO)
+ {
+ /*
+ Internally Archive keeps track of last used, not next used.
+ To make the output look like MyISAM we add 1 here.
+
+ This is not completely compatible with MYISAM though, since
+ MyISAM will record on "SHOW CREATE TABLE" the last position,
+ where we will report the original position the table was
+ created with.
+ */
+ create_info->auto_increment_value= stats.auto_increment_value + 1;
+ }
+
+ if (!(my_readlink(share->real_path, share->data_file_name, MYF(0))))
+ create_info->data_file_name= share->real_path;
+
+ DBUG_VOID_RETURN;
+}
+
/*
Hints for optimizer, see ha_tina for more information
@@ -1128,8 +1466,8 @@ int ha_archive::info(uint flag)
This should be an accurate number now, though bulk and delayed inserts can
cause the number to be inaccurate.
*/
- records= share->rows_recorded;
- deleted= 0;
+ stats.records= share->rows_recorded;
+ stats.deleted= 0;
/* Costs quite a bit more to get all information */
if (flag & HA_STATUS_TIME)
{
@@ -1137,14 +1475,21 @@ int ha_archive::info(uint flag)
VOID(my_stat(share->data_file_name, &file_stat, MYF(MY_WME)));
- mean_rec_length= table->s->reclength + buffer.alloced_length();
- data_file_length= file_stat.st_size;
- create_time= file_stat.st_ctime;
- update_time= file_stat.st_mtime;
- max_data_file_length= share->rows_recorded * mean_rec_length;
+ stats.mean_rec_length= table->s->reclength + buffer.alloced_length();
+ stats.data_file_length= file_stat.st_size;
+ stats.create_time= file_stat.st_ctime;
+ stats.update_time= file_stat.st_mtime;
+ stats.max_data_file_length= share->rows_recorded * stats.mean_rec_length;
+ }
+ stats.delete_length= 0;
+ stats.index_file_length=0;
+
+ if (flag & HA_STATUS_AUTO)
+ {
+ init_archive_reader();
+ azflush(&archive, Z_SYNC_FLUSH);
+ stats.auto_increment_value= archive.auto_increment;
}
- delete_length= 0;
- index_file_length=0;
DBUG_RETURN(0);
}
@@ -1205,13 +1550,13 @@ int ha_archive::check(THD* thd, HA_CHECK
{
int rc= 0;
byte *buf;
- const char *old_proc_info=thd->proc_info;
+ const char *old_proc_info;
ha_rows count= share->rows_recorded;
DBUG_ENTER("ha_archive::check");
- thd->proc_info= "Checking table";
+ old_proc_info= thd_proc_info(thd, "Checking table");
/* Flush any waiting data */
- gzflush(share->archive_write, Z_SYNC_FLUSH);
+ azflush(&(share->archive_write), Z_SYNC_FLUSH);
/*
First we create a buffer that we can use for reading rows, and can pass
@@ -1224,16 +1569,18 @@ int ha_archive::check(THD* thd, HA_CHECK
Now we will rewind the archive file so that we are positioned at the
start of the file.
*/
+ init_archive_reader();
+
if (!rc)
- read_data_header(archive);
+ read_data_header(&archive);
if (!rc)
- while (!(rc= get_row(archive, buf)))
+ while (!(rc= get_row(&archive, buf)))
count--;
my_free((char*)buf, MYF(0));
- thd->proc_info= old_proc_info;
+ thd_proc_info(thd, old_proc_info);
if ((rc && rc != HA_ERR_END_OF_FILE) || count)
{
@@ -1258,4 +1605,54 @@ bool ha_archive::check_and_repair(THD *t
DBUG_RETURN(repair(thd, &check_opt));
}
-#endif /* HAVE_ARCHIVE_DB */
+
+archive_record_buffer *ha_archive::create_record_buffer(unsigned int length)
+{
+ DBUG_ENTER("ha_archive::create_record_buffer");
+ archive_record_buffer *r;
+ if (!(r=
+ (archive_record_buffer*) my_malloc(sizeof(archive_record_buffer),
+ MYF(MY_WME))))
+ {
+ DBUG_RETURN(NULL); /* purecov: inspected */
+ }
+ r->length= (int)length;
+
+ if (!(r->buffer= (byte*) my_malloc(r->length,
+ MYF(MY_WME))))
+ {
+ my_free((char*) r, MYF(MY_ALLOW_ZERO_PTR));
+ DBUG_RETURN(NULL); /* purecov: inspected */
+ }
+
+ DBUG_RETURN(r);
+}
+
+void ha_archive::destroy_record_buffer(archive_record_buffer *r)
+{
+ DBUG_ENTER("ha_archive::destroy_record_buffer");
+ my_free((char*) r->buffer, MYF(MY_ALLOW_ZERO_PTR));
+ my_free((char*) r, MYF(MY_ALLOW_ZERO_PTR));
+ DBUG_VOID_RETURN;
+}
+
+struct st_mysql_storage_engine archive_storage_engine=
+{ MYSQL_HANDLERTON_INTERFACE_VERSION };
+
+mysql_declare_plugin(archive)
+{
+ MYSQL_STORAGE_ENGINE_PLUGIN,
+ &archive_storage_engine,
+ "ARCHIVE",
+ "Brian Aker, MySQL AB",
+ "Archive storage engine",
+ PLUGIN_LICENSE_GPL,
+ archive_db_init, /* Plugin Init */
+ archive_db_done, /* Plugin Deinit */
+ 0x0300 /* 3.0 */,
+ NULL, /* status variables */
+ NULL, /* system variables */
+ NULL /* config options */
+}
+mysql_declare_plugin_end;
+
--- 1.19.3.1/sql/examples/ha_example.cc 2007-03-31 17:29:49 +05:00
+++ 1.55/storage/example/ha_example.cc 2007-03-31 17:29:49 +05:00
@@ -13,31 +13,50 @@
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.
+/**
+ @file ha_example.cc
+
+ @brief
+ The ha_example engine is a stubbed storage engine for example purposes only;
+ it does nothing at this point. Its purpose is to provide a source
+ code illustration of how to begin writing new storage engines; see also
+ /storage/example/ha_example.h.
+
+ @details
+ ha_example will let you create/open/delete tables, but
+ nothing further (for example, indexes are not supported nor can data
+ be stored in the table). Use this example as a template for
+ implementing the same functionality in your own storage engine. You
+ can enable the example storage engine in your build by doing the
+ following during your build process:<br> ./configure
+ --with-example-storage-engine
+
+ Once this is done, MySQL will let you create tables with:<br>
+ CREATE TABLE <table name> (...) ENGINE=EXAMPLE;
+
+ The example storage engine is set up 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 when it is using that
+ table.
Please read the object definition in ha_example.h before reading the rest
- if this file.
+ of this file.
+
+ @note
+ When you create an EXAMPLE table, the MySQL Server creates a table .frm
+ (format) file in the database directory, using the table name as the file
+ name as is customary with MySQL. No other files are created. To get an idea
+ of what occurs, here is an example select that would do a scan of an entire
+ table:
- To get an idea of what occurs here is an example select that would do a
- scan of an entire table:
+ @code
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()
+ ENUM HA_EXTRA_CACHE Cache record in HA_rrnd()
ha_example::rnd_next
ha_example::rnd_next
ha_example::rnd_next
@@ -48,17 +67,19 @@
ha_example::rnd_next
ha_example::rnd_next
ha_example::extra
- ENUM HA_EXTRA_NO_CACHE End cacheing of records (def)
+ ENUM HA_EXTRA_NO_CACHE End caching of records (def)
ha_example::external_lock
ha_example::extra
- ENUM HA_EXTRA_RESET Reset database to after open
+ ENUM HA_EXTRA_RESET Reset database to after open
+ @endcode
- 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
+ Here you see that the example storage engine has 9 rows called before
+ rnd_next signals that it has reached the end of its data. Also note that
+ the table in question was already opened; had it not been open, a call to
+ ha_example::open() would also have been necessary. Calls to
ha_example::extra() are hints as to what will be occuring to the request.
- Happy coding!
+ Happy coding!<br>
-Brian
*/
@@ -66,45 +87,33 @@
#pragma implementation // gcc: Class implementation
#endif
-#include "../mysql_priv.h"
-
-#ifdef HAVE_EXAMPLE_DB
+#define MYSQL_SERVER 1
+#include "mysql_priv.h"
#include "ha_example.h"
+#include <mysql/plugin.h>
+static handler *example_create_handler(handlerton *hton,
+ TABLE_SHARE *table,
+ MEM_ROOT *mem_root);
-handlerton example_hton= {
- "EXAMPLE",
- SHOW_OPTION_YES,
- "Example storage engine",
- DB_TYPE_EXAMPLE_DB,
- NULL, /* We do need to write one! */
- 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 */
- HTON_CAN_RECREATE
-};
+handlerton *example_hton;
/* 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
+/*
+ Hash used to track the number of open tables; variable for example share
+ methods
+*/
+static HASH example_open_tables;
+
+/* The mutex used to init the hash; variable for example share methods */
+pthread_mutex_t example_mutex;
-/*
+/**
+ @brief
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)))
{
@@ -113,36 +122,52 @@ static byte* example_get_key(EXAMPLE_SHA
}
-/*
- 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 int example_init_func(void *p)
+{
+ DBUG_ENTER("example_init_func");
+
+ example_hton= (handlerton *)p;
+ 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);
+
+ example_hton->state= SHOW_OPTION_YES;
+ example_hton->db_type= DB_TYPE_EXAMPLE_DB;
+ example_hton->create= example_create_handler;
+ example_hton->flags= HTON_CAN_RECREATE;
+
+ DBUG_RETURN(0);
+}
+
+
+static int example_done_func(void *p)
+{
+ int error= 0;
+ DBUG_ENTER("example_done_func");
+
+ if (example_open_tables.records)
+ error= 1;
+ hash_free(&example_open_tables);
+ pthread_mutex_destroy(&example_mutex);
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief
+ Example of simple lock controls. The "share" it creates is a
+ 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;
- /*
- So why does this exist? There is no way currently to init a storage engine.
- Innodb and BDB both have modifications to the server to allow them to
- do this. Since you will not want to do this, this is probably the next
- best method.
- */
- if (!example_init)
- {
- /* Hijack a mutex for init'ing the storage engine */
- pthread_mutex_lock(&LOCK_mysql_create_db);
- 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);
- }
- pthread_mutex_unlock(&LOCK_mysql_create_db);
- }
pthread_mutex_lock(&example_mutex);
length=(uint) strlen(table_name);
@@ -176,17 +201,18 @@ static EXAMPLE_SHARE *get_share(const ch
error:
pthread_mutex_destroy(&share->mutex);
- pthread_mutex_unlock(&example_mutex);
my_free((gptr) share, MYF(0));
return NULL;
}
-/*
+/**
+ @brief
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.
+ 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);
@@ -202,22 +228,36 @@ static int free_share(EXAMPLE_SHARE *sha
return 0;
}
+static handler* example_create_handler(handlerton *hton,
+ TABLE_SHARE *table,
+ MEM_ROOT *mem_root)
+{
+ return new (mem_root) ha_example(hton, table);
+}
-ha_example::ha_example(TABLE *table_arg)
- :handler(&example_hton, table_arg)
+ha_example::ha_example(handlerton *hton, TABLE_SHARE *table_arg)
+ :handler(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.
+
+/**
+ @brief
+ If frm_error() is called then we will use this to determine
+ the file extensions that exist for the storage engine. This is also
+ used by the default rename_table and delete_table method in
+ handler.cc.
For engines that have two file name extentions (separate meta/index file
and data file), the order of elements is relevant. First element of engine
file name extentions array should be meta/index file extention. Second
element - data file extention. This order is assumed by
prepare_for_repair() when REPAIR TABLE ... USE_FRM is issued.
+
+ @see
+ rename_table method in handler.cc and
+ delete_table method in handler.cc
*/
+
static const char *ha_example_exts[] = {
NullS
};
@@ -228,15 +268,22 @@ const char **ha_example::bas_ext() const
}
-/*
+/**
+ @brief
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).
+
+ @details
+ A table is opened when it needs to be opened; e.g. 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().
+
+ @see
+ handler::ha_open() in handler.cc
*/
+
int ha_example::open(const char *name, int mode, uint test_if_locked)
{
DBUG_ENTER("ha_example::open");
@@ -249,16 +296,22 @@ int ha_example::open(const char *name, i
}
-/*
+/**
+ @brief
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.
+ @details
+ 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().
+
+ @see
+ sql_base.cc, sql_select.cc and table.cc
*/
+
int ha_example::close(void)
{
DBUG_ENTER("ha_example::close");
@@ -266,26 +319,36 @@ int ha_example::close(void)
}
-/*
+/**
+ @brief
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
+ is happening. buf() is a byte array of data. You can use the field
information to extract the data from the native byte array type.
+
+ @details
Example of this would be:
+ @code
for (Field **field=table->field ; *field ; field++)
{
...
}
+ @endcode
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().
+ case also applies 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.
+
+ @see
+ 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");
@@ -293,20 +356,27 @@ int ha_example::write_row(byte * buf)
}
-/*
+/**
+ @brief
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.
+ 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.
+ clause was used. Consecutive ordering is not guaranteed.
+
+ @details
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:
+ and updated timestamp field. You can do these for example by doing:
+ @code
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();
+ @endcode
Called from sql_select.cc, sql_acl.cc, sql_update.cc, and sql_insert.cc.
+
+ @see
+ 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)
{
@@ -316,19 +386,26 @@ int ha_example::update_row(const byte *
}
-/*
+/**
+ @brief
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).
+
+ @details
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.
+ make doing the deletion quite a bit easier. Keep in mind that the server does
+ not guarantee 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.
+
+ @see
+ sql_acl.cc, sql_udf.cc, sql_delete.cc, sql_insert.cc and sql_select.cc
*/
+
int ha_example::delete_row(const byte * buf)
{
DBUG_ENTER("ha_example::delete_row");
@@ -336,13 +413,15 @@ int ha_example::delete_row(const byte *
}
-/*
+/**
+ @brief
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)),
+ key_part_map keypart_map __attribute__((unused)),
enum ha_rkey_function find_flag
__attribute__((unused)))
{
@@ -351,23 +430,11 @@ int ha_example::index_read(byte * buf, c
}
-/*
- 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);
-}
-
-
-/*
+/**
+ @brief
Used to read forward through the index.
*/
+
int ha_example::index_next(byte * buf)
{
DBUG_ENTER("ha_example::index_next");
@@ -375,9 +442,11 @@ int ha_example::index_next(byte * buf)
}
-/*
+/**
+ @brief
Used to read backwards through the index.
*/
+
int ha_example::index_prev(byte * buf)
{
DBUG_ENTER("ha_example::index_prev");
@@ -385,11 +454,15 @@ int ha_example::index_prev(byte * buf)
}
-/*
+/**
+ @brief
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.
+ @details
+ Called from opt_range.cc, opt_sum.cc, sql_handler.cc, and sql_select.cc.
+
+ @see
+ opt_range.cc, opt_sum.cc, sql_handler.cc and sql_select.cc
*/
int ha_example::index_first(byte * buf)
{
@@ -398,11 +471,15 @@ int ha_example::index_first(byte * buf)
}
-/*
+/**
+ @brief
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.
+ @details
+ Called from opt_range.cc, opt_sum.cc, sql_handler.cc, and sql_select.cc.
+
+ @see
+ opt_range.cc, opt_sum.cc, sql_handler.cc and sql_select.cc
*/
int ha_example::index_last(byte * buf)
{
@@ -411,14 +488,18 @@ int ha_example::index_last(byte * buf)
}
-/*
+/**
+ @brief
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
+ scan. See the example in the introduction at the top of this file to see when
rnd_init() is called.
+ @details
Called from filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc,
and sql_update.cc.
+
+ @see
+ filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc and sql_update.cc
*/
int ha_example::rnd_init(bool scan)
{
@@ -432,14 +513,20 @@ int ha_example::rnd_end()
DBUG_RETURN(0);
}
-/*
+
+/**
+ @brief
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.
+ @details
Called from filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc,
and sql_update.cc.
+
+ @see
+ filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc and sql_update.cc
*/
int ha_example::rnd_next(byte *buf)
{
@@ -448,19 +535,26 @@ int ha_example::rnd_next(byte *buf)
}
-/*
+/**
+ @brief
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:
+ @code
my_store_ptr(ref, ref_length, current_position);
+ @endcode
+ @details
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.
+ Called from filesort.cc, sql_select.cc, sql_delete.cc, and sql_update.cc.
+
+ @see
+ filesort.cc, sql_select.cc, sql_delete.cc and sql_update.cc
*/
void ha_example::position(const byte *record)
{
@@ -469,12 +563,18 @@ void ha_example::position(const byte *re
}
-/*
+/**
+ @brief
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.
+
+ @details
+ Called from filesort.cc, records.cc, sql_insert.cc, sql_select.cc, and sql_update.cc.
+
+ @see
+ filesort.cc, records.cc, sql_insert.cc, sql_select.cc and sql_update.cc
*/
int ha_example::rnd_pos(byte * buf, byte *pos)
{
@@ -483,21 +583,25 @@ int ha_example::rnd_pos(byte * buf, byte
}
-/*
- ::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:
+/**
+ @brief
+ ::info() is used to return information to the optimizer. See my_base.h for
+ the complete description.
+
+ @details
+ Currently this table handler doesn't implement most of the fields really needed.
+ SHOW also makes use of this data.
+
+ You will probably want to have the following in your code:
+ @code
if (records < 2)
records = 2;
+ @endcode
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:
+ 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
@@ -506,27 +610,16 @@ int ha_example::rnd_pos(byte * buf, byte
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
-
+ 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, and sql_update.cc.
+
+ @see
+ 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 and sql_update.cc
*/
int ha_example::info(uint flag)
{
@@ -535,10 +628,14 @@ int ha_example::info(uint flag)
}
-/*
+/**
+ @brief
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.
+
+ @see
+ ha_innodb.cc
*/
int ha_example::extra(enum ha_extra_function operation)
{
@@ -547,29 +644,24 @@ int ha_example::extra(enum ha_extra_func
}
-/*
- 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.
+/**
+ @brief
+ Used to delete all rows in a table, including cases of truncate and cases where
+ the optimizer realizes that all rows will be removed as a result of an SQL statement.
+ @details
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().
+
+ @see
+ Item_func_group_concat::clear(), Item_sum_count_distinct::clear() and
+ Item_func_group_concat::clear() in item_sum.cc;
+ mysql_delete() in sql_delete.cc;
+ JOIN::reinit() in sql_select.cc and
+ st_select_lex_unit::exec() in sql_union.cc.
*/
int ha_example::delete_all_rows()
{
@@ -578,16 +670,22 @@ int ha_example::delete_all_rows()
}
-/*
- First you should go read the section "locking functions for mysql" in
- lock.cc to understand this.
+/**
+ @brief
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.
+ want to go about doing this. Otherwise you should consider calling flock()
+ here. Hint: Read the section "locking functions for mysql" in lock.cc to understand
+ this.
+ @details
Called from lock.cc by lock_external() and unlock_external(). Also called
from sql_table.cc by copy_data_between_tables().
+
+ @see
+ lock.cc by lock_external() and unlock_external() in lock.cc;
+ the section "locking functions for mysql" in lock.cc;
+ copy_data_between_tables() in sql_table.cc.
*/
int ha_example::external_lock(THD *thd, int lock_type)
{
@@ -596,24 +694,24 @@ int ha_example::external_lock(THD *thd,
}
-/*
- 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
+/**
+ @brief
+ The idea with handler::store_lock() is: The statement decides which locks
+ should be needed for the table. For updates/deletes/inserts we get WRITE
+ locks, for SELECT... we get read locks.
+
+ @details
+ 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
+ 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.
+ Berkeley DB, for example, changes all WRITE locks to TL_WRITE_ALLOW_WRITE
+ (which signals that we are doing WRITES, but are still allowing other
+ readers and writers).
- When releasing locks, store_lock() are also called. In this case one
+ When releasing locks, store_lock() is 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;
@@ -621,9 +719,12 @@ int ha_example::external_lock(THD *thd,
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.
+ time). In the future we will probably try to remove this.
Called from lock.cc by get_lock_data().
+
+ @see
+ get_lock_data() in lock.cc
*/
THR_LOCK_DATA **ha_example::store_lock(THD *thd,
THR_LOCK_DATA **to,
@@ -635,19 +736,25 @@ THR_LOCK_DATA **ha_example::store_lock(T
return to;
}
-/*
+
+/**
+ @brief
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
+ 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.
+ @details
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
+ handler.cc and it will delete all files with the file extensions returned
by bas_ext().
- Called from handler.cc by delete_table and ha_create_table(). Only used
+ 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.
+
+ @see
+ delete_table and ha_create_table() in handler.cc
*/
int ha_example::delete_table(const char *name)
{
@@ -656,14 +763,20 @@ int ha_example::delete_table(const char
DBUG_RETURN(0);
}
-/*
- Renames a table from one name to another from alter table call.
+/**
+ @brief
+ Renames a table from one name to another via an alter table call.
+
+ @details
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
+ handler.cc and it will delete all files with the file extensions returned
by bas_ext().
Called from sql_table.cc by mysql_rename_table().
+
+ @see
+ mysql_rename_table() in sql_table.cc
*/
int ha_example::rename_table(const char * from, const char * to)
{
@@ -671,12 +784,19 @@ int ha_example::rename_table(const char
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.
+
+/**
+ @brief
+ Given a starting key and an ending key, estimate the number of rows that
+ will exist between the two keys.
+
+ @details
+ end_key may be empty, in which case determine if start_key matches any rows.
Called from opt_range.cc by check_quick_keys().
+
+ @see
+ check_quick_keys() in opt_range.cc
*/
ha_rows ha_example::records_in_range(uint inx, key_range *min_key,
key_range *max_key)
@@ -686,21 +806,53 @@ ha_rows ha_example::records_in_range(uin
}
-/*
+/**
+ @brief
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.
+ of the table.
+
+ @details
+ 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 is not necessary. 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
+ so.
Called from handle.cc by ha_create_table().
+
+ @see
+ ha_create_table() in handle.cc
*/
+
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. */
+ /*
+ This is not implemented but we want someone to be able to see that it
+ works.
+ */
DBUG_RETURN(0);
}
-#endif /* HAVE_EXAMPLE_DB */
+
+
+struct st_mysql_storage_engine example_storage_engine=
+{ MYSQL_HANDLERTON_INTERFACE_VERSION };
+
+mysql_declare_plugin(example)
+{
+ MYSQL_STORAGE_ENGINE_PLUGIN,
+ &example_storage_engine,
+ "EXAMPLE",
+ "Brian Aker, MySQL AB",
+ "Example storage engine",
+ PLUGIN_LICENSE_GPL,
+ example_init_func, /* Plugin Init */
+ example_done_func, /* Plugin Deinit */
+ 0x0001 /* 0.1 */,
+ NULL, /* status variables */
+ NULL, /* system variables */
+ NULL /* config options */
+}
+mysql_declare_plugin_end;
| Thread |
|---|
| • bk commit into 5.1 tree (svoj:1.2539) | Sergey Vojtovich | 31 Mar |