~drizzle-trunk/drizzle/development

« back to all changes in this revision

Viewing changes to storage/archive/ha_archive.cc

  • Committer: Monty Taylor
  • Date: 2008-09-16 00:00:48 UTC
  • mto: This revision was merged to the branch mainline in revision 391.
  • Revision ID: monty@inaugust.com-20080916000048-3rvrv3gv9l0ad3gs
Fixed copyright headers in drizzled/

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
/* Copyright (C) 2003 MySQL AB
2
 
   Copyright (C) 2010 Brian Aker
3
2
 
4
3
  This program is free software; you can redistribute it and/or modify
5
4
  it under the terms of the GNU General Public License as published by
12
11
 
13
12
  You should have received a copy of the GNU General Public License
14
13
  along with this program; if not, write to the Free Software
15
 
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA */
16
 
 
17
 
 
18
 
#include "config.h"
19
 
 
20
 
#include "plugin/archive/archive_engine.h"
21
 
#include <memory>
22
 
#include <boost/scoped_ptr.hpp>
23
 
 
24
 
using namespace std;
25
 
using namespace drizzled;
26
 
 
 
14
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
 
15
 
 
16
#ifdef USE_PRAGMA_IMPLEMENTATION
 
17
#pragma implementation        // gcc: Class implementation
 
18
#endif
 
19
 
 
20
#include <drizzled/common_includes.h>
 
21
#include <storage/myisam/myisam.h>
 
22
 
 
23
#include "ha_archive.h"
27
24
 
28
25
/*
29
 
  First, if you want to understand storage engines you should look at
30
 
  ha_example.cc and ha_example.h.
 
26
  First, if you want to understand storage engines you should look at 
 
27
  ha_example.cc and ha_example.h. 
31
28
 
32
29
  This example was written as a test case for a customer who needed
33
30
  a storage engine without indexes that could compress data very well.
34
31
  So, welcome to a completely compressed storage engine. This storage
35
 
  engine only does inserts. No replace, deletes, or updates. All reads are
 
32
  engine only does inserts. No replace, deletes, or updates. All reads are 
36
33
  complete table scans. Compression is done through a combination of packing
37
34
  and making use of the zlib library
38
 
 
 
35
  
39
36
  We keep a file pointer open for each instance of ha_archive for each read
40
37
  but for writes we keep one open file handle just for that. We flush it
41
38
  only if we have a read occur. azip handles compressing lots of records
45
42
  the same time since we would want to flush).
46
43
 
47
44
  A "meta" file is kept alongside the data file. This file serves two purpose.
48
 
  The first purpose is to track the number of rows in the table. The second
49
 
  purpose is to determine if the table was closed properly or not. When the
50
 
  meta file is first opened it is marked as dirty. It is opened when the table
51
 
  itself is opened for writing. When the table is closed the new count for rows
52
 
  is written to the meta file and the file is marked as clean. If the meta file
53
 
  is opened and it is marked as dirty, it is assumed that a crash occured. At
 
45
  The first purpose is to track the number of rows in the table. The second 
 
46
  purpose is to determine if the table was closed properly or not. When the 
 
47
  meta file is first opened it is marked as dirty. It is opened when the table 
 
48
  itself is opened for writing. When the table is closed the new count for rows 
 
49
  is written to the meta file and the file is marked as clean. If the meta file 
 
50
  is opened and it is marked as dirty, it is assumed that a crash occured. At 
54
51
  this point an error occurs and the user is told to rebuild the file.
55
52
  A rebuild scans the rows and rewrites the meta file. If corruption is found
56
53
  in the data file then the meta file is not repaired.
57
54
 
58
55
  At some point a recovery method for such a drastic case needs to be divised.
59
56
 
60
 
  Locks are row level, and you will get a consistant read.
 
57
  Locks are row level, and you will get a consistant read. 
61
58
 
62
59
  For performance as far as table scans go it is quite fast. I don't have
63
60
  good numbers but locally it has out performed both Innodb and MyISAM. For
64
61
  Innodb the question will be if the table can be fit into the buffer
65
62
  pool. For MyISAM its a question of how much the file system caches the
66
63
  MyISAM file. With enough free memory MyISAM is faster. Its only when the OS
67
 
  doesn't have enough memory to cache entire table that archive turns out
68
 
  to be any faster.
 
64
  doesn't have enough memory to cache entire table that archive turns out 
 
65
  to be any faster. 
69
66
 
70
67
  Examples between MyISAM (packed) and Archive.
71
68
 
93
90
    -Brian
94
91
*/
95
92
 
96
 
/* When the engine starts up set the first version */
97
 
static uint64_t global_version= 1;
98
 
 
99
 
// We use this to find out the state of the archive aio option.
100
 
extern bool archive_aio_state(void);
 
93
/* Variables for archive share methods */
 
94
pthread_mutex_t archive_mutex;
 
95
static HASH archive_open_tables;
 
96
static unsigned int global_version;
 
97
 
 
98
/* The file extension */
 
99
#define ARZ ".ARZ"               // The data file
 
100
#define ARN ".ARN"               // Files used during an optimize call
 
101
#define ARM ".ARM"               // Meta file (deprecated)
 
102
 
 
103
/*
 
104
  uchar + uchar
 
105
*/
 
106
#define DATA_BUFFER_SIZE 2       // Size of the data used in the data file
 
107
#define ARCHIVE_CHECK_HEADER 254 // The number we use to determine corruption
 
108
 
 
109
/* Static declarations for handerton */
 
110
static handler *archive_create_handler(handlerton *hton, 
 
111
                                       TABLE_SHARE *table, 
 
112
                                       MEM_ROOT *mem_root);
 
113
int archive_discover(handlerton *hton, THD* thd, const char *db, 
 
114
                     const char *name,
 
115
                     uchar **frmblob, 
 
116
                     size_t *frmlen);
 
117
 
 
118
static bool archive_use_aio= false;
101
119
 
102
120
/*
103
121
  Number of rows that will force a bulk insert.
109
127
*/
110
128
#define ARCHIVE_ROW_HEADER_SIZE 4
111
129
 
112
 
ArchiveShare *ArchiveEngine::findOpenTable(const string table_name)
113
 
{
114
 
  ArchiveMap::iterator find_iter=
115
 
    archive_open_tables.find(table_name);
116
 
 
117
 
  if (find_iter != archive_open_tables.end())
118
 
    return (*find_iter).second;
119
 
  else
120
 
    return NULL;
121
 
}
122
 
 
123
 
void ArchiveEngine::addOpenTable(const string &table_name, ArchiveShare *share)
124
 
{
125
 
  archive_open_tables[table_name]= share;
126
 
}
127
 
 
128
 
void ArchiveEngine::deleteOpenTable(const string &table_name)
129
 
{
130
 
  archive_open_tables.erase(table_name);
131
 
}
132
 
 
133
 
 
134
 
int ArchiveEngine::doDropTable(Session&, const identifier::Table &identifier)
135
 
{
136
 
  string new_path(identifier.getPath());
137
 
 
138
 
  new_path+= ARZ;
139
 
 
140
 
  int error= unlink(new_path.c_str());
141
 
 
142
 
  if (error != 0)
143
 
  {
144
 
    error= errno= errno;
145
 
  }
146
 
 
147
 
  return error;
148
 
}
149
 
 
150
 
int ArchiveEngine::doGetTableDefinition(Session&,
151
 
                                        const identifier::Table &identifier,
152
 
                                        drizzled::message::Table &table_proto)
153
 
{
154
 
  struct stat stat_info;
155
 
  int error= ENOENT;
156
 
  string proto_path;
157
 
 
158
 
  proto_path.reserve(FN_REFLEN);
159
 
  proto_path.assign(identifier.getPath());
160
 
 
161
 
  proto_path.append(ARZ);
162
 
 
163
 
  if (stat(proto_path.c_str(),&stat_info))
164
 
    return errno;
165
 
  else
166
 
    error= EEXIST;
167
 
 
168
 
  {
169
 
    boost::scoped_ptr<azio_stream> proto_stream(new azio_stream);
170
 
    char* proto_string;
171
 
    if (azopen(proto_stream.get(), proto_path.c_str(), O_RDONLY, AZ_METHOD_BLOCK) == 0)
172
 
      return HA_ERR_CRASHED_ON_USAGE;
173
 
 
174
 
    proto_string= (char*)malloc(sizeof(char) * proto_stream->frm_length);
175
 
    if (proto_string == NULL)
176
 
    {
177
 
      azclose(proto_stream.get());
178
 
      return ENOMEM;
179
 
    }
180
 
 
181
 
    azread_frm(proto_stream.get(), proto_string);
182
 
 
183
 
    if (table_proto.ParseFromArray(proto_string, proto_stream->frm_length) == false)
184
 
      error= HA_ERR_CRASHED_ON_USAGE;
185
 
 
186
 
    azclose(proto_stream.get());
187
 
    free(proto_string);
188
 
  }
189
 
 
190
 
  /* We set the name from what we've asked for as in RENAME TABLE for ARCHIVE
191
 
     we do not rewrite the table proto (as it's wedged in the file header)
192
 
  */
193
 
  table_proto.set_schema(identifier.getSchemaName());
194
 
  table_proto.set_name(identifier.getTableName());
195
 
 
196
 
  return error;
197
 
}
198
 
 
199
 
 
200
 
ha_archive::ha_archive(drizzled::plugin::StorageEngine &engine_arg,
201
 
                       Table &table_arg)
202
 
  :Cursor(engine_arg, table_arg), delayed_insert(0), bulk_insert(0)
 
130
static handler *archive_create_handler(handlerton *hton,
 
131
                                       TABLE_SHARE *table, 
 
132
                                       MEM_ROOT *mem_root)
 
133
{
 
134
  return new (mem_root) ha_archive(hton, table);
 
135
}
 
136
 
 
137
/*
 
138
  Used for hash table that tracks open tables.
 
139
*/
 
140
static uchar* archive_get_key(ARCHIVE_SHARE *share, size_t *length,
 
141
                             bool not_used __attribute__((unused)))
 
142
{
 
143
  *length=share->table_name_length;
 
144
  return (uchar*) share->table_name;
 
145
}
 
146
 
 
147
 
 
148
/*
 
149
  Initialize the archive handler.
 
150
 
 
151
  SYNOPSIS
 
152
    archive_db_init()
 
153
    void *
 
154
 
 
155
  RETURN
 
156
    false       OK
 
157
    true        Error
 
158
*/
 
159
 
 
160
int archive_db_init(void *p)
 
161
{
 
162
  handlerton *archive_hton;
 
163
 
 
164
  archive_hton= (handlerton *)p;
 
165
  archive_hton->state= SHOW_OPTION_YES;
 
166
  archive_hton->db_type= DB_TYPE_ARCHIVE_DB;
 
167
  archive_hton->create= archive_create_handler;
 
168
  archive_hton->flags= HTON_NO_FLAGS;
 
169
  archive_hton->discover= archive_discover;
 
170
 
 
171
  /* When the engine starts up set the first version */
 
172
  global_version= 1;
 
173
 
 
174
  if (pthread_mutex_init(&archive_mutex, MY_MUTEX_INIT_FAST))
 
175
    goto error;
 
176
  if (hash_init(&archive_open_tables, system_charset_info, 32, 0, 0,
 
177
                (hash_get_key) archive_get_key, 0, 0))
 
178
  {
 
179
    VOID(pthread_mutex_destroy(&archive_mutex));
 
180
  }
 
181
  else
 
182
  {
 
183
    return(false);
 
184
  }
 
185
error:
 
186
  return(true);
 
187
}
 
188
 
 
189
/*
 
190
  Release the archive handler.
 
191
 
 
192
  SYNOPSIS
 
193
    archive_db_done()
 
194
    void
 
195
 
 
196
  RETURN
 
197
    false       OK
 
198
*/
 
199
 
 
200
int archive_db_done(void *p __attribute__((unused)))
 
201
{
 
202
  hash_free(&archive_open_tables);
 
203
  VOID(pthread_mutex_destroy(&archive_mutex));
 
204
 
 
205
  return 0;
 
206
}
 
207
 
 
208
 
 
209
ha_archive::ha_archive(handlerton *hton, TABLE_SHARE *table_arg)
 
210
  :handler(hton, table_arg), delayed_insert(0), bulk_insert(0)
203
211
{
204
212
  /* Set our original buffer from pre-allocated memory */
205
213
  buffer.set((char *)byte_buffer, IO_SIZE, system_charset_info);
206
214
 
207
215
  /* The size of the offset value we will use for position() */
208
 
  ref_length= sizeof(internal::my_off_t);
 
216
  ref_length= sizeof(my_off_t);
209
217
  archive_reader_open= false;
210
218
}
211
219
 
 
220
int archive_discover(handlerton *hton __attribute__((unused)),
 
221
                     THD* thd __attribute__((unused)),
 
222
                     const char *db,
 
223
                     const char *name,
 
224
                     uchar **frmblob,
 
225
                     size_t *frmlen)
 
226
{
 
227
  azio_stream frm_stream;
 
228
  char az_file[FN_REFLEN];
 
229
  char *frm_ptr;
 
230
  struct stat file_stat; 
 
231
 
 
232
  fn_format(az_file, name, db, ARZ, MY_REPLACE_EXT | MY_UNPACK_FILENAME);
 
233
 
 
234
  if (stat(az_file, &file_stat))
 
235
    goto err;
 
236
 
 
237
  if (!(azopen(&frm_stream, az_file, O_RDONLY|O_BINARY, AZ_METHOD_BLOCK)))
 
238
  {
 
239
    if (errno == EROFS || errno == EACCES)
 
240
      return(my_errno= errno);
 
241
    return(HA_ERR_CRASHED_ON_USAGE);
 
242
  }
 
243
 
 
244
  if (frm_stream.frm_length == 0)
 
245
    goto err;
 
246
 
 
247
  frm_ptr= (char *)my_malloc(sizeof(char) * frm_stream.frm_length, MYF(0));
 
248
  azread_frm(&frm_stream, frm_ptr);
 
249
  azclose(&frm_stream);
 
250
 
 
251
  *frmlen= frm_stream.frm_length;
 
252
  *frmblob= (uchar*) frm_ptr;
 
253
 
 
254
  return(0);
 
255
err:
 
256
  my_errno= 0;
 
257
  return(1);
 
258
}
 
259
 
212
260
/*
213
261
  This method reads the header of a datafile and returns whether or not it was successful.
214
262
*/
223
271
  return(1);
224
272
}
225
273
 
226
 
ArchiveShare::ArchiveShare():
227
 
  use_count(0), archive_write_open(false), dirty(false), crashed(false),
228
 
  mean_rec_length(0), version(0), rows_recorded(0), version_rows(0)
229
 
{
230
 
  assert(1);
231
 
}
232
 
 
233
 
ArchiveShare::ArchiveShare(const char *name):
234
 
  use_count(0), archive_write_open(false), dirty(false), crashed(false),
235
 
  mean_rec_length(0), version(0), rows_recorded(0), version_rows(0)
236
 
{
237
 
  memset(&archive_write, 0, sizeof(azio_stream));     /* Archive file we are working with */
238
 
  table_name.append(name);
239
 
  data_file_name.assign(table_name);
240
 
  data_file_name.append(ARZ);
241
 
  /*
242
 
    We will use this lock for rows.
243
 
  */
244
 
  pthread_mutex_init(&_mutex,MY_MUTEX_INIT_FAST);
245
 
}
246
 
 
247
 
ArchiveShare::~ArchiveShare()
248
 
{
249
 
  _lock.deinit();
250
 
  pthread_mutex_destroy(&_mutex);
251
 
  /*
252
 
    We need to make sure we don't reset the crashed state.
253
 
    If we open a crashed file, wee need to close it as crashed unless
254
 
    it has been repaired.
255
 
    Since we will close the data down after this, we go on and count
256
 
    the flush on close;
257
 
  */
258
 
  if (archive_write_open == true)
259
 
    (void)azclose(&archive_write);
260
 
}
261
 
 
262
 
bool ArchiveShare::prime(uint64_t *auto_increment)
263
 
{
264
 
  boost::scoped_ptr<azio_stream> archive_tmp(new azio_stream);
265
 
 
266
 
  /*
267
 
    We read the meta file, but do not mark it dirty. Since we are not
268
 
    doing a write we won't mark it dirty (and we won't open it for
269
 
    anything but reading... open it for write and we will generate null
270
 
    compression writes).
271
 
  */
272
 
  if (!(azopen(archive_tmp.get(), data_file_name.c_str(), O_RDONLY,
273
 
               AZ_METHOD_BLOCK)))
274
 
    return false;
275
 
 
276
 
  *auto_increment= archive_tmp->auto_increment + 1;
277
 
  rows_recorded= (ha_rows)archive_tmp->rows;
278
 
  crashed= archive_tmp->dirty;
279
 
  if (version < global_version)
280
 
  {
281
 
    version_rows= rows_recorded;
282
 
    version= global_version;
283
 
  }
284
 
  azclose(archive_tmp.get());
285
 
 
286
 
  return true;
287
 
}
288
 
 
289
274
 
290
275
/*
291
 
  We create the shared memory space that we will use for the open table.
 
276
  We create the shared memory space that we will use for the open table. 
292
277
  No matter what we try to get or create a share. This is so that a repair
293
 
  table operation can occur.
 
278
  table operation can occur. 
294
279
 
295
280
  See ha_example.cc for a longer description.
296
281
*/
297
 
ArchiveShare *ha_archive::get_share(const char *table_name, int *rc)
 
282
ARCHIVE_SHARE *ha_archive::get_share(const char *table_name, int *rc)
298
283
{
299
 
  ArchiveEngine *a_engine= static_cast<ArchiveEngine *>(getEngine());
300
 
 
301
 
  pthread_mutex_lock(&a_engine->mutex());
302
 
 
303
 
  share= a_engine->findOpenTable(table_name);
304
 
 
305
 
  if (!share)
 
284
  uint length;
 
285
 
 
286
  pthread_mutex_lock(&archive_mutex);
 
287
  length=(uint) strlen(table_name);
 
288
 
 
289
  if (!(share=(ARCHIVE_SHARE*) hash_search(&archive_open_tables,
 
290
                                           (uchar*) table_name,
 
291
                                           length)))
306
292
  {
307
 
    share= new ArchiveShare(table_name);
 
293
    char *tmp_name;
 
294
    azio_stream archive_tmp;
308
295
 
309
 
    if (share == NULL)
 
296
    if (!my_multi_malloc(MYF(MY_WME | MY_ZEROFILL),
 
297
                          &share, sizeof(*share),
 
298
                          &tmp_name, length+1,
 
299
                          NullS)) 
310
300
    {
311
 
      pthread_mutex_unlock(&a_engine->mutex());
 
301
      pthread_mutex_unlock(&archive_mutex);
312
302
      *rc= HA_ERR_OUT_OF_MEM;
313
303
      return(NULL);
314
304
    }
315
305
 
316
 
    if (share->prime(&stats.auto_increment_value) == false)
 
306
    share->use_count= 0;
 
307
    share->table_name_length= length;
 
308
    share->table_name= tmp_name;
 
309
    share->crashed= false;
 
310
    share->archive_write_open= false;
 
311
    fn_format(share->data_file_name, table_name, "",
 
312
              ARZ, MY_REPLACE_EXT | MY_UNPACK_FILENAME);
 
313
    stpcpy(share->table_name, table_name);
 
314
    /*
 
315
      We will use this lock for rows.
 
316
    */
 
317
    VOID(pthread_mutex_init(&share->mutex,MY_MUTEX_INIT_FAST));
 
318
    
 
319
    /*
 
320
      We read the meta file, but do not mark it dirty. Since we are not
 
321
      doing a write we won't mark it dirty (and we won't open it for
 
322
      anything but reading... open it for write and we will generate null
 
323
      compression writes).
 
324
    */
 
325
    if (!(azopen(&archive_tmp, share->data_file_name, O_RDONLY|O_BINARY,
 
326
                 AZ_METHOD_BLOCK)))
317
327
    {
318
 
      pthread_mutex_unlock(&a_engine->mutex());
 
328
      VOID(pthread_mutex_destroy(&share->mutex));
 
329
      free(share);
 
330
      pthread_mutex_unlock(&archive_mutex);
319
331
      *rc= HA_ERR_CRASHED_ON_REPAIR;
320
 
      delete share;
321
 
 
322
 
      return NULL;
323
 
    }
324
 
 
325
 
    a_engine->addOpenTable(share->table_name, share);
326
 
    thr_lock_init(&share->_lock);
 
332
      return(NULL);
 
333
    }
 
334
    stats.auto_increment_value= archive_tmp.auto_increment + 1;
 
335
    share->rows_recorded= (ha_rows)archive_tmp.rows;
 
336
    share->crashed= archive_tmp.dirty;
 
337
    if (share->version < global_version)
 
338
    {
 
339
      share->version_rows= share->rows_recorded;
 
340
      share->version= global_version;
 
341
    }
 
342
    azclose(&archive_tmp);
 
343
 
 
344
    VOID(my_hash_insert(&archive_open_tables, (uchar*) share));
 
345
    thr_lock_init(&share->lock);
327
346
  }
328
347
  share->use_count++;
329
 
 
330
348
  if (share->crashed)
331
349
    *rc= HA_ERR_CRASHED_ON_USAGE;
332
 
  pthread_mutex_unlock(&a_engine->mutex());
 
350
  pthread_mutex_unlock(&archive_mutex);
333
351
 
334
352
  return(share);
335
353
}
336
354
 
337
355
 
338
 
/*
 
356
/* 
339
357
  Free the share.
340
358
  See ha_example.cc for a description.
341
359
*/
342
360
int ha_archive::free_share()
343
361
{
344
 
  ArchiveEngine *a_engine= static_cast<ArchiveEngine *>(getEngine());
 
362
  int rc= 0;
345
363
 
346
 
  pthread_mutex_lock(&a_engine->mutex());
 
364
  pthread_mutex_lock(&archive_mutex);
347
365
  if (!--share->use_count)
348
366
  {
349
 
    a_engine->deleteOpenTable(share->table_name);
350
 
    delete share;
 
367
    hash_delete(&archive_open_tables, (uchar*) share);
 
368
    thr_lock_delete(&share->lock);
 
369
    VOID(pthread_mutex_destroy(&share->mutex));
 
370
    /* 
 
371
      We need to make sure we don't reset the crashed state.
 
372
      If we open a crashed file, wee need to close it as crashed unless
 
373
      it has been repaired.
 
374
      Since we will close the data down after this, we go on and count
 
375
      the flush on close;
 
376
    */
 
377
    if (share->archive_write_open == true)
 
378
    {
 
379
      if (azclose(&(share->archive_write)))
 
380
        rc= 1;
 
381
    }
 
382
    my_free((uchar*) share, MYF(0));
351
383
  }
352
 
  pthread_mutex_unlock(&a_engine->mutex());
 
384
  pthread_mutex_unlock(&archive_mutex);
353
385
 
354
 
  return 0;
 
386
  return(rc);
355
387
}
356
388
 
357
389
int ha_archive::init_archive_writer()
358
390
{
359
 
  /*
 
391
  /* 
360
392
    It is expensive to open and close the data files and since you can't have
361
393
    a gzip file that can be both read and written we keep a writer open
362
394
    that is shared amoung all open tables.
363
395
  */
364
 
  if (!(azopen(&(share->archive_write), share->data_file_name.c_str(),
365
 
               O_RDWR, AZ_METHOD_BLOCK)))
 
396
  if (!(azopen(&(share->archive_write), share->data_file_name, 
 
397
               O_RDWR|O_BINARY, AZ_METHOD_BLOCK)))
366
398
  {
367
399
    share->crashed= true;
368
400
    return(1);
373
405
}
374
406
 
375
407
 
376
 
/*
377
 
  No locks are required because it is associated with just one Cursor instance
 
408
/* 
 
409
  No locks are required because it is associated with just one handler instance
378
410
*/
379
411
int ha_archive::init_archive_reader()
380
412
{
381
 
  /*
 
413
  /* 
382
414
    It is expensive to open and close the data files and since you can't have
383
415
    a gzip file that can be both read and written we keep a writer open
384
416
    that is shared amoung all open tables.
387
419
  {
388
420
    az_method method;
389
421
 
390
 
    if (archive_aio_state())
 
422
    switch (archive_use_aio)
391
423
    {
 
424
    case false:
 
425
      method= AZ_METHOD_BLOCK;
 
426
      break;
 
427
    case true:
392
428
      method= AZ_METHOD_AIO;
393
 
    }
394
 
    else
395
 
    {
 
429
      break;
 
430
    default:
396
431
      method= AZ_METHOD_BLOCK;
397
432
    }
398
 
    if (!(azopen(&archive, share->data_file_name.c_str(), O_RDONLY,
 
433
    if (!(azopen(&archive, share->data_file_name, O_RDONLY|O_BINARY, 
399
434
                 method)))
400
435
    {
401
436
      share->crashed= true;
407
442
  return(0);
408
443
}
409
444
 
 
445
 
410
446
/*
 
447
  We just implement one additional file extension.
 
448
*/
 
449
static const char *ha_archive_exts[] = {
 
450
  ARZ,
 
451
  NullS
 
452
};
 
453
 
 
454
const char **ha_archive::bas_ext() const
 
455
{
 
456
  return ha_archive_exts;
 
457
}
 
458
 
 
459
 
 
460
/* 
411
461
  When opening a file we:
412
462
  Create/get our shared structure.
413
463
  Init out lock.
414
464
  We open the file we will read from.
415
465
*/
416
 
int ha_archive::doOpen(const identifier::Table &identifier, int , uint32_t )
 
466
int ha_archive::open(const char *name,
 
467
                     int mode __attribute__((unused)),
 
468
                     uint open_options)
417
469
{
418
470
  int rc= 0;
419
 
  share= get_share(identifier.getPath().c_str(), &rc);
420
 
 
421
 
  /** 
422
 
    We either fix it ourselves, or we just take it offline 
423
 
 
424
 
    @todo Create some documentation in the recovery tools shipped with the engine.
425
 
  */
426
 
  if (rc == HA_ERR_CRASHED_ON_USAGE)
 
471
  share= get_share(name, &rc);
 
472
 
 
473
  if (rc == HA_ERR_CRASHED_ON_USAGE && !(open_options & HA_OPEN_FOR_REPAIR))
427
474
  {
 
475
    /* purecov: begin inspected */
428
476
    free_share();
429
 
    rc= repair();
430
 
 
431
 
    return 0;
 
477
    return(rc);
 
478
    /* purecov: end */    
432
479
  }
433
480
  else if (rc == HA_ERR_OUT_OF_MEM)
434
481
  {
437
484
 
438
485
  assert(share);
439
486
 
440
 
  record_buffer.resize(getTable()->getShare()->getRecordLength() + ARCHIVE_ROW_HEADER_SIZE);
441
 
 
442
 
  lock.init(&share->_lock);
443
 
 
444
 
  return(rc);
445
 
}
446
 
 
447
 
// Should never be called
448
 
int ha_archive::open(const char *, int, uint32_t)
449
 
{
450
 
  assert(0);
451
 
  return -1;
 
487
  record_buffer= create_record_buffer(table->s->reclength + 
 
488
                                      ARCHIVE_ROW_HEADER_SIZE);
 
489
 
 
490
  if (!record_buffer)
 
491
  {
 
492
    free_share();
 
493
    return(HA_ERR_OUT_OF_MEM);
 
494
  }
 
495
 
 
496
  thr_lock_data_init(&share->lock, &lock, NULL);
 
497
 
 
498
  if (rc == HA_ERR_CRASHED_ON_USAGE && open_options & HA_OPEN_FOR_REPAIR)
 
499
  {
 
500
    return(0);
 
501
  }
 
502
  else
 
503
    return(rc);
452
504
}
453
505
 
454
506
 
457
509
 
458
510
  SYNOPSIS
459
511
    close();
460
 
 
 
512
  
461
513
  IMPLEMENTATION:
462
514
 
463
515
  We first close this storage engines file handle to the archive and
473
525
{
474
526
  int rc= 0;
475
527
 
476
 
  record_buffer.clear();
 
528
  destroy_record_buffer(record_buffer);
477
529
 
478
530
  /* First close stream */
479
531
  if (archive_reader_open == true)
489
541
 
490
542
 
491
543
/*
492
 
  We create our data file here. The format is pretty simple.
 
544
  We create our data file here. The format is pretty simple. 
493
545
  You can read about the format of the data file above.
494
 
  Unlike other storage engines we do not "pack" our data. Since we
495
 
  are about to do a general compression, packing would just be a waste of
496
 
  CPU time. If the table has blobs they are written after the row in the order
 
546
  Unlike other storage engines we do not "pack" our data. Since we 
 
547
  are about to do a general compression, packing would just be a waste of 
 
548
  CPU time. If the table has blobs they are written after the row in the order 
497
549
  of creation.
498
550
*/
499
551
 
500
 
int ArchiveEngine::doCreateTable(Session &,
501
 
                                 Table& table_arg,
502
 
                                 const drizzled::identifier::Table &identifier,
503
 
                                 drizzled::message::Table& proto)
 
552
int ha_archive::create(const char *name, Table *table_arg,
 
553
                       HA_CREATE_INFO *create_info)
504
554
{
505
 
  int error= 0;
506
 
  boost::scoped_ptr<azio_stream> create_stream(new azio_stream);
507
 
  uint64_t auto_increment_value;
508
 
  string serialized_proto;
509
 
 
510
 
  auto_increment_value= proto.options().auto_increment_value();
511
 
 
512
 
  for (uint32_t key= 0; key < table_arg.sizeKeys(); key++)
 
555
  char name_buff[FN_REFLEN];
 
556
  char linkname[FN_REFLEN];
 
557
  int error;
 
558
  azio_stream create_stream;            /* Archive file we are working with */
 
559
  File frm_file;                   /* File handler for readers */
 
560
  struct stat file_stat;
 
561
  uchar *frm_ptr;
 
562
 
 
563
  stats.auto_increment_value= create_info->auto_increment_value;
 
564
 
 
565
  for (uint key= 0; key < table_arg->sizeKeys(); key++)
513
566
  {
514
 
    KeyInfo *pos= &table_arg.key_info[key];
515
 
    KeyPartInfo *key_part=     pos->key_part;
516
 
    KeyPartInfo *key_part_end= key_part + pos->key_parts;
 
567
    KEY *pos= table_arg->key_info+key;
 
568
    KEY_PART_INFO *key_part=     pos->key_part;
 
569
    KEY_PART_INFO *key_part_end= key_part + pos->key_parts;
517
570
 
518
571
    for (; key_part != key_part_end; key_part++)
519
572
    {
521
574
 
522
575
      if (!(field->flags & AUTO_INCREMENT_FLAG))
523
576
      {
524
 
        return -1;
 
577
        error= -1;
 
578
        goto error;
525
579
      }
526
580
    }
527
581
  }
528
582
 
529
 
  std::string named_file= identifier.getPath();
530
 
  named_file.append(ARZ);
531
 
 
532
 
  errno= 0;
533
 
  if (azopen(create_stream.get(), named_file.c_str(), O_CREAT|O_RDWR,
534
 
             AZ_METHOD_BLOCK) == 0)
535
 
  {
536
 
    error= errno;
537
 
    unlink(named_file.c_str());
538
 
 
539
 
    return(error ? error : -1);
540
 
  }
541
 
 
542
 
  try {
543
 
    proto.SerializeToString(&serialized_proto);
544
 
  }
545
 
  catch (...)
546
 
  {
547
 
    unlink(named_file.c_str());
548
 
 
549
 
    return(error ? error : -1);
550
 
  }
551
 
 
552
 
  if (azwrite_frm(create_stream.get(), serialized_proto.c_str(),
553
 
                  serialized_proto.length()))
554
 
  {
555
 
    unlink(named_file.c_str());
556
 
 
557
 
    return(error ? error : -1);
558
 
  }
559
 
 
560
 
  if (proto.options().has_comment())
561
 
  {
562
 
    int write_length;
563
 
 
564
 
    write_length= azwrite_comment(create_stream.get(),
565
 
                                  proto.options().comment().c_str(),
566
 
                                  proto.options().comment().length());
567
 
 
568
 
    if (write_length < 0)
569
 
    {
570
 
      error= errno;
571
 
      unlink(named_file.c_str());
572
 
 
573
 
      return(error ? error : -1);
574
 
    }
 
583
  /* 
 
584
    We reuse name_buff since it is available.
 
585
  */
 
586
  if (create_info->data_file_name && create_info->data_file_name[0] != '#')
 
587
  {
 
588
    fn_format(name_buff, create_info->data_file_name, "", ARZ,
 
589
              MY_REPLACE_EXT | MY_UNPACK_FILENAME);
 
590
    fn_format(linkname, name, "", ARZ,
 
591
              MY_REPLACE_EXT | MY_UNPACK_FILENAME);
 
592
  }
 
593
  else
 
594
  {
 
595
    fn_format(name_buff, name, "", ARZ,
 
596
              MY_REPLACE_EXT | MY_UNPACK_FILENAME);
 
597
    linkname[0]= 0;
575
598
  }
576
599
 
577
600
  /*
578
 
    Yes you need to do this, because the starting value
579
 
    for the autoincrement may not be zero.
 
601
    There is a chance that the file was "discovered". In this case
 
602
    just use whatever file is there.
580
603
  */
581
 
  create_stream->auto_increment= auto_increment_value ?
582
 
    auto_increment_value - 1 : 0;
583
 
 
584
 
  if (azclose(create_stream.get()))
 
604
  if (!stat(name_buff, &file_stat))
585
605
  {
586
 
    error= errno;
587
 
    unlink(named_file.c_str());
588
 
 
589
 
    return(error ? error : -1);
 
606
    my_errno= 0;
 
607
    if (!(azopen(&create_stream, name_buff, O_CREAT|O_RDWR|O_BINARY,
 
608
                 AZ_METHOD_BLOCK)))
 
609
    {
 
610
      error= errno;
 
611
      goto error2;
 
612
    }
 
613
 
 
614
    if (linkname[0])
 
615
      my_symlink(name_buff, linkname, MYF(0));
 
616
    fn_format(name_buff, name, "", ".frm",
 
617
              MY_REPLACE_EXT | MY_UNPACK_FILENAME);
 
618
 
 
619
    /*
 
620
      Here is where we open up the frm and pass it to archive to store 
 
621
    */
 
622
    if ((frm_file= my_open(name_buff, O_RDONLY, MYF(0))) > 0)
 
623
    {
 
624
      if (fstat(frm_file, &file_stat))
 
625
      {
 
626
        frm_ptr= (uchar *)my_malloc(sizeof(uchar) * file_stat.st_size, MYF(0));
 
627
        if (frm_ptr)
 
628
        {
 
629
          my_read(frm_file, frm_ptr, file_stat.st_size, MYF(0));
 
630
          azwrite_frm(&create_stream, (char *)frm_ptr, file_stat.st_size);
 
631
          my_free((uchar*)frm_ptr, MYF(0));
 
632
        }
 
633
      }
 
634
      my_close(frm_file, MYF(0));
 
635
    }
 
636
 
 
637
    if (create_info->comment.str)
 
638
      azwrite_comment(&create_stream, create_info->comment.str, 
 
639
                      (unsigned int)create_info->comment.length);
 
640
 
 
641
    /* 
 
642
      Yes you need to do this, because the starting value 
 
643
      for the autoincrement may not be zero.
 
644
    */
 
645
    create_stream.auto_increment= stats.auto_increment_value ?
 
646
                                    stats.auto_increment_value - 1 : 0;
 
647
    if (azclose(&create_stream))
 
648
    {
 
649
      error= errno;
 
650
      goto error2;
 
651
    }
590
652
  }
 
653
  else
 
654
    my_errno= 0;
591
655
 
592
656
  return(0);
 
657
 
 
658
error2:
 
659
  delete_table(name);
 
660
error:
 
661
  /* Return error number, if we got one */
 
662
  return(error ? error : -1);
593
663
}
594
664
 
595
665
/*
596
666
  This is where the actual row is written out.
597
667
*/
598
 
int ha_archive::real_write_row(unsigned char *buf, azio_stream *writer)
 
668
int ha_archive::real_write_row(uchar *buf, azio_stream *writer)
599
669
{
600
 
  off_t written;
 
670
  my_off_t written;
601
671
  unsigned int r_pack_length;
602
672
 
603
673
  /* We pack the row for writing */
604
674
  r_pack_length= pack_row(buf);
605
675
 
606
 
  written= azwrite_row(writer, &record_buffer[0], r_pack_length);
 
676
  written= azwrite_row(writer, record_buffer->buffer, r_pack_length);
607
677
  if (written != r_pack_length)
608
678
  {
609
679
    return(-1);
616
686
}
617
687
 
618
688
 
619
 
/*
 
689
/* 
620
690
  Calculate max length needed for row. This includes
621
691
  the bytes required for the length in the header.
622
692
*/
623
693
 
624
 
uint32_t ha_archive::max_row_length(const unsigned char *)
 
694
uint32_t ha_archive::max_row_length(const uchar *buf __attribute__((unused)))
625
695
{
626
 
  uint32_t length= (uint32_t)(getTable()->getRecordLength() + getTable()->sizeFields()*2);
 
696
  uint32_t length= (uint32_t)(table->getRecordLength() + table->sizeFields()*2);
627
697
  length+= ARCHIVE_ROW_HEADER_SIZE;
628
698
 
629
 
  uint32_t *ptr, *end;
630
 
  for (ptr= getTable()->getBlobField(), end=ptr + getTable()->sizeBlobFields();
 
699
  uint *ptr, *end;
 
700
  for (ptr= table->getBlobField(), end=ptr + table->sizeBlobFields();
631
701
       ptr != end ;
632
702
       ptr++)
633
703
  {
634
 
      length += 2 + ((Field_blob*)getTable()->getField(*ptr))->get_length();
 
704
      length += 2 + ((Field_blob*)table->field[*ptr])->get_length();
635
705
  }
636
706
 
637
707
  return length;
638
708
}
639
709
 
640
710
 
641
 
unsigned int ha_archive::pack_row(unsigned char *record)
 
711
unsigned int ha_archive::pack_row(uchar *record)
642
712
{
643
 
  unsigned char *ptr;
 
713
  uchar *ptr;
644
714
 
645
715
  if (fix_rec_buff(max_row_length(record)))
646
 
    return(HA_ERR_OUT_OF_MEM);
 
716
    return(HA_ERR_OUT_OF_MEM); /* purecov: inspected */
647
717
 
648
718
  /* Copy null bits */
649
 
  memcpy(&record_buffer[0], record, getTable()->getShare()->null_bytes);
650
 
  ptr= &record_buffer[0] + getTable()->getShare()->null_bytes;
 
719
  memcpy(record_buffer->buffer, record, table->s->null_bytes);
 
720
  ptr= record_buffer->buffer + table->s->null_bytes;
651
721
 
652
 
  for (Field **field=getTable()->getFields() ; *field ; field++)
 
722
  for (Field **field=table->field ; *field ; field++)
653
723
  {
654
724
    if (!((*field)->is_null()))
655
725
      ptr= (*field)->pack(ptr, record + (*field)->offset(record));
656
726
  }
657
727
 
658
 
  return((unsigned int) (ptr - &record_buffer[0]));
 
728
  return((unsigned int) (ptr - record_buffer->buffer));
659
729
}
660
730
 
661
731
 
662
 
/*
 
732
/* 
663
733
  Look at ha_archive::open() for an explanation of the row format.
664
734
  Here we just write out the row.
665
735
 
666
736
  Wondering about start_bulk_insert()? We don't implement it for
667
737
  archive since it optimizes for lots of writes. The only save
668
 
  for implementing start_bulk_insert() is that we could skip
 
738
  for implementing start_bulk_insert() is that we could skip 
669
739
  setting dirty to true each time.
670
740
*/
671
 
int ha_archive::doInsertRecord(unsigned char *buf)
 
741
int ha_archive::write_row(uchar *buf)
672
742
{
673
743
  int rc;
674
 
  unsigned char *read_buf= NULL;
 
744
  uchar *read_buf= NULL;
675
745
  uint64_t temp_auto;
676
 
  unsigned char *record=  getTable()->getInsertRecord();
 
746
  uchar *record=  table->record[0];
677
747
 
678
748
  if (share->crashed)
679
749
    return(HA_ERR_CRASHED_ON_USAGE);
680
750
 
681
 
  pthread_mutex_lock(&share->mutex());
 
751
  ha_statistic_increment(&SSV::ha_write_count);
 
752
  if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT)
 
753
    table->timestamp_field->set_time();
 
754
  pthread_mutex_lock(&share->mutex);
682
755
 
683
756
  if (share->archive_write_open == false)
684
757
    if (init_archive_writer())
685
758
      return(HA_ERR_CRASHED_ON_USAGE);
686
759
 
687
760
 
688
 
  if (getTable()->next_number_field && record == getTable()->getInsertRecord())
 
761
  if (table->next_number_field && record == table->record[0])
689
762
  {
 
763
    KEY *mkey= &table->s->key_info[0]; // We only support one key right now
690
764
    update_auto_increment();
691
 
    temp_auto= getTable()->next_number_field->val_int();
 
765
    temp_auto= table->next_number_field->val_int();
692
766
 
693
767
    /*
694
768
      We don't support decremening auto_increment. They make the performance
695
769
      just cry.
696
770
    */
697
 
    if (temp_auto <= share->archive_write.auto_increment &&
698
 
        getTable()->getShare()->getKeyInfo(0).flags & HA_NOSAME)
 
771
    if (temp_auto <= share->archive_write.auto_increment && 
 
772
        mkey->flags & HA_NOSAME)
699
773
    {
700
774
      rc= HA_ERR_FOUND_DUPP_KEY;
701
775
      goto error;
702
776
    }
 
777
#ifdef DEAD_CODE
 
778
    /*
 
779
      Bad news, this will cause a search for the unique value which is very 
 
780
      expensive since we will have to do a table scan which will lock up 
 
781
      all other writers during this period. This could perhaps be optimized 
 
782
      in the future.
 
783
    */
 
784
    {
 
785
      /* 
 
786
        First we create a buffer that we can use for reading rows, and can pass
 
787
        to get_row().
 
788
      */
 
789
      if (!(read_buf= (uchar*) my_malloc(table->s->reclength, MYF(MY_WME))))
 
790
      {
 
791
        rc= HA_ERR_OUT_OF_MEM;
 
792
        goto error;
 
793
      }
 
794
       /* 
 
795
         All of the buffer must be written out or we won't see all of the
 
796
         data 
 
797
       */
 
798
      azflush(&(share->archive_write), Z_SYNC_FLUSH);
 
799
      /*
 
800
        Set the position of the local read thread to the beginning postion.
 
801
      */
 
802
      if (read_data_header(&archive))
 
803
      {
 
804
        rc= HA_ERR_CRASHED_ON_USAGE;
 
805
        goto error;
 
806
      }
 
807
 
 
808
      Field *mfield= table->next_number_field;
 
809
 
 
810
      while (!(get_row(&archive, read_buf)))
 
811
      {
 
812
        if (!memcmp(read_buf + mfield->offset(record),
 
813
                    table->next_number_field->ptr,
 
814
                    mfield->max_display_length()))
 
815
        {
 
816
          rc= HA_ERR_FOUND_DUPP_KEY;
 
817
          goto error;
 
818
        }
 
819
      }
 
820
    }
 
821
#endif
703
822
    else
704
823
    {
705
824
      if (temp_auto > share->archive_write.auto_increment)
715
834
  share->rows_recorded++;
716
835
  rc= real_write_row(buf,  &(share->archive_write));
717
836
error:
718
 
  pthread_mutex_unlock(&share->mutex());
 
837
  pthread_mutex_unlock(&share->mutex);
719
838
  if (read_buf)
720
 
    free((unsigned char*) read_buf);
 
839
    my_free((uchar*) read_buf, MYF(0));
721
840
 
722
841
  return(rc);
723
842
}
724
843
 
725
844
 
726
 
void ha_archive::get_auto_increment(uint64_t, uint64_t, uint64_t,
727
 
                                    uint64_t *first_value, uint64_t *nb_reserved_values)
 
845
void ha_archive::get_auto_increment(uint64_t offset __attribute__((unused)),
 
846
                                    uint64_t increment __attribute__((unused)),
 
847
                                    uint64_t nb_desired_values __attribute__((unused)),
 
848
                                    uint64_t *first_value __attribute__((unused)),
 
849
                                    uint64_t *nb_reserved_values __attribute__((unused)))
728
850
{
729
851
  *nb_reserved_values= UINT64_MAX;
730
852
  *first_value= share->archive_write.auto_increment + 1;
731
853
}
732
854
 
733
 
/* Initialized at each key walk (called multiple times unlike doStartTableScan()) */
734
 
int ha_archive::doStartIndexScan(uint32_t keynr, bool)
 
855
/* Initialized at each key walk (called multiple times unlike rnd_init()) */
 
856
int ha_archive::index_init(uint keynr, bool sorted __attribute__((unused)))
735
857
{
736
858
  active_index= keynr;
737
859
  return(0);
742
864
  No indexes, so if we get a request for an index search since we tell
743
865
  the optimizer that we have unique indexes, we scan
744
866
*/
745
 
int ha_archive::index_read(unsigned char *buf, const unsigned char *key,
746
 
                             uint32_t key_len, enum ha_rkey_function)
 
867
int ha_archive::index_read(uchar *buf, const uchar *key,
 
868
                             uint key_len, enum ha_rkey_function find_flag)
 
869
{
 
870
  int rc;
 
871
  rc= index_read_idx(buf, active_index, key, key_len, find_flag);
 
872
  return(rc);
 
873
}
 
874
 
 
875
 
 
876
int ha_archive::index_read_idx(uchar *buf, uint index, const uchar *key,
 
877
                               uint key_len,
 
878
                               enum ha_rkey_function find_flag __attribute__((unused)))
747
879
{
748
880
  int rc;
749
881
  bool found= 0;
750
 
  current_k_offset= getTable()->getShare()->getKeyInfo(0).key_part->offset;
 
882
  KEY *mkey= &table->s->key_info[index];
 
883
  current_k_offset= mkey->key_part->offset;
751
884
  current_key= key;
752
885
  current_key_len= key_len;
753
886
 
754
 
  rc= doStartTableScan(true);
 
887
  rc= rnd_init(true);
755
888
 
756
889
  if (rc)
757
890
    goto error;
773
906
}
774
907
 
775
908
 
776
 
int ha_archive::index_next(unsigned char * buf)
777
 
{
 
909
int ha_archive::index_next(uchar * buf) 
 
910
778
911
  bool found= 0;
779
912
 
780
913
  while (!(get_row(&archive, buf)))
786
919
    }
787
920
  }
788
921
 
789
 
  return(found ? 0 : HA_ERR_END_OF_FILE);
 
922
  return(found ? 0 : HA_ERR_END_OF_FILE); 
790
923
}
791
924
 
792
925
/*
795
928
  we assume the position will be set.
796
929
*/
797
930
 
798
 
int ha_archive::doStartTableScan(bool scan)
 
931
int ha_archive::rnd_init(bool scan)
799
932
{
800
933
  if (share->crashed)
801
934
      return(HA_ERR_CRASHED_ON_USAGE);
814
947
 
815
948
 
816
949
/*
817
 
  This is the method that is used to read a row. It assumes that the row is
 
950
  This is the method that is used to read a row. It assumes that the row is 
818
951
  positioned where you want it.
819
952
*/
820
 
int ha_archive::get_row(azio_stream *file_to_read, unsigned char *buf)
 
953
int ha_archive::get_row(azio_stream *file_to_read, uchar *buf)
821
954
{
822
955
  int rc;
823
956
 
832
965
/* Reallocate buffer if needed */
833
966
bool ha_archive::fix_rec_buff(unsigned int length)
834
967
{
835
 
  record_buffer.resize(length);
836
 
 
837
 
  return false;
 
968
  assert(record_buffer->buffer);
 
969
 
 
970
  if (length > record_buffer->length)
 
971
  {
 
972
    uchar *newptr;
 
973
    if (!(newptr=(uchar*) my_realloc((uchar*) record_buffer->buffer, 
 
974
                                    length,
 
975
                                    MYF(MY_ALLOW_ZERO_PTR))))
 
976
      return(1);
 
977
    record_buffer->buffer= newptr;
 
978
    record_buffer->length= length;
 
979
  }
 
980
 
 
981
  assert(length <= record_buffer->length);
 
982
 
 
983
  return(0);
838
984
}
839
985
 
840
 
int ha_archive::unpack_row(azio_stream *file_to_read, unsigned char *record)
 
986
int ha_archive::unpack_row(azio_stream *file_to_read, uchar *record)
841
987
{
842
988
  unsigned int read;
843
989
  int error;
844
 
  const unsigned char *ptr;
 
990
  const uchar *ptr;
845
991
 
846
992
  read= azread_row(file_to_read, &error);
847
 
  ptr= (const unsigned char *)file_to_read->row_ptr;
 
993
  ptr= (const uchar *)file_to_read->row_ptr;
848
994
 
849
995
  if (error || read == 0)
850
996
  {
852
998
  }
853
999
 
854
1000
  /* Copy null bits */
855
 
  memcpy(record, ptr, getTable()->getNullBytes());
856
 
  ptr+= getTable()->getNullBytes();
857
 
  for (Field **field= getTable()->getFields() ; *field ; field++)
 
1001
  memcpy(record, ptr, table->getNullBytes());
 
1002
  ptr+= table->getNullBytes();
 
1003
  for (Field **field=table->field ; *field ; field++)
858
1004
  {
859
1005
    if (!((*field)->is_null()))
860
1006
    {
861
 
      ptr= (*field)->unpack(record + (*field)->offset(getTable()->getInsertRecord()), ptr);
 
1007
      ptr= (*field)->unpack(record + (*field)->offset(table->record[0]), ptr);
862
1008
    }
863
1009
  }
864
1010
  return(0);
865
1011
}
866
1012
 
867
1013
 
868
 
int ha_archive::get_row_version3(azio_stream *file_to_read, unsigned char *buf)
 
1014
int ha_archive::get_row_version3(azio_stream *file_to_read, uchar *buf)
869
1015
{
870
1016
  int returnable= unpack_row(file_to_read, buf);
871
1017
 
873
1019
}
874
1020
 
875
1021
 
876
 
/*
 
1022
/* 
877
1023
  Called during ORDER BY. Its position is either from being called sequentially
878
1024
  or by having had ha_archive::rnd_pos() called before it is called.
879
1025
*/
880
1026
 
881
 
int ha_archive::rnd_next(unsigned char *buf)
 
1027
int ha_archive::rnd_next(uchar *buf)
882
1028
{
883
1029
  int rc;
884
1030
 
889
1035
    return(HA_ERR_END_OF_FILE);
890
1036
  scan_rows--;
891
1037
 
892
 
  ha_statistic_increment(&system_status_var::ha_read_rnd_next_count);
 
1038
  ha_statistic_increment(&SSV::ha_read_rnd_next_count);
893
1039
  current_position= aztell(&archive);
894
1040
  rc= get_row(&archive, buf);
895
1041
 
896
 
  getTable()->status=rc ? STATUS_NOT_FOUND: 0;
 
1042
  table->status=rc ? STATUS_NOT_FOUND: 0;
897
1043
 
898
1044
  return(rc);
899
1045
}
900
1046
 
901
1047
 
902
1048
/*
903
 
  Thanks to the table bool is_ordered this will be called after
 
1049
  Thanks to the table flag HA_REC_NOT_IN_SEQ this will be called after
904
1050
  each call to ha_archive::rnd_next() if an ordering of the rows is
905
1051
  needed.
906
1052
*/
907
1053
 
908
 
void ha_archive::position(const unsigned char *)
 
1054
void ha_archive::position(const uchar *record __attribute__((unused)))
909
1055
{
910
 
  internal::my_store_ptr(ref, ref_length, current_position);
 
1056
  my_store_ptr(ref, ref_length, current_position);
911
1057
  return;
912
1058
}
913
1059
 
919
1065
  correctly ordered row.
920
1066
*/
921
1067
 
922
 
int ha_archive::rnd_pos(unsigned char * buf, unsigned char *pos)
 
1068
int ha_archive::rnd_pos(uchar * buf, uchar *pos)
923
1069
{
924
 
  ha_statistic_increment(&system_status_var::ha_read_rnd_next_count);
925
 
  current_position= (internal::my_off_t)internal::my_get_ptr(pos, ref_length);
 
1070
  ha_statistic_increment(&SSV::ha_read_rnd_next_count);
 
1071
  current_position= (my_off_t)my_get_ptr(pos, ref_length);
926
1072
  if (azseek(&archive, (size_t)current_position, SEEK_SET) == (size_t)(-1L))
927
1073
    return(HA_ERR_CRASHED_ON_USAGE);
928
1074
  return(get_row(&archive, buf));
929
1075
}
930
1076
 
931
1077
/*
932
 
  This method repairs the meta file. It does this by walking the datafile and
 
1078
  This method repairs the meta file. It does this by walking the datafile and 
933
1079
  rewriting the meta file. Currently it does this by calling optimize with
934
1080
  the extended flag.
935
1081
*/
936
 
int ha_archive::repair()
 
1082
int ha_archive::repair(THD* thd, HA_CHECK_OPT* check_opt)
937
1083
{
938
 
  int rc= optimize();
 
1084
  check_opt->flags= T_EXTEND;
 
1085
  int rc= optimize(thd, check_opt);
939
1086
 
940
1087
  if (rc)
941
1088
    return(HA_ERR_CRASHED_ON_REPAIR);
946
1093
 
947
1094
/*
948
1095
  The table can become fragmented if data was inserted, read, and then
949
 
  inserted again. What we do is open up the file and recompress it completely.
 
1096
  inserted again. What we do is open up the file and recompress it completely. 
950
1097
*/
951
 
int ha_archive::optimize()
 
1098
int ha_archive::optimize(THD* thd __attribute__((unused)),
 
1099
                         HA_CHECK_OPT* check_opt __attribute__((unused)))
952
1100
{
953
1101
  int rc= 0;
954
 
  boost::scoped_ptr<azio_stream> writer(new azio_stream);
 
1102
  azio_stream writer;
 
1103
  char writer_filename[FN_REFLEN];
955
1104
 
956
1105
  init_archive_reader();
957
1106
 
962
1111
    share->archive_write_open= false;
963
1112
  }
964
1113
 
965
 
  char* proto_string;
966
 
  proto_string= (char*)malloc(sizeof(char) * archive.frm_length);
967
 
  if (proto_string == NULL)
968
 
  {
969
 
    return ENOMEM;
970
 
  }
971
 
  azread_frm(&archive, proto_string);
972
 
 
973
1114
  /* Lets create a file to contain the new data */
974
 
  std::string writer_filename= share->table_name;
975
 
  writer_filename.append(ARN);
976
 
 
977
 
  if (!(azopen(writer.get(), writer_filename.c_str(), O_CREAT|O_RDWR, AZ_METHOD_BLOCK)))
978
 
  {
979
 
    free(proto_string);
980
 
    return(HA_ERR_CRASHED_ON_USAGE);
981
 
  }
982
 
 
983
 
  azwrite_frm(writer.get(), proto_string, archive.frm_length);
984
 
 
985
 
  /*
986
 
    An extended rebuild is a lot more effort. We open up each row and re-record it.
987
 
    Any dead rows are removed (aka rows that may have been partially recorded).
 
1115
  fn_format(writer_filename, share->table_name, "", ARN, 
 
1116
            MY_REPLACE_EXT | MY_UNPACK_FILENAME);
 
1117
 
 
1118
  if (!(azopen(&writer, writer_filename, O_CREAT|O_RDWR|O_BINARY, AZ_METHOD_BLOCK)))
 
1119
    return(HA_ERR_CRASHED_ON_USAGE); 
 
1120
 
 
1121
  /* 
 
1122
    An extended rebuild is a lot more effort. We open up each row and re-record it. 
 
1123
    Any dead rows are removed (aka rows that may have been partially recorded). 
988
1124
 
989
1125
    As of Archive format 3, this is the only type that is performed, before this
990
1126
    version it was just done on T_EXTEND
992
1128
  if (1)
993
1129
  {
994
1130
    /*
995
 
      Now we will rewind the archive file so that we are positioned at the
 
1131
      Now we will rewind the archive file so that we are positioned at the 
996
1132
      start of the file.
997
1133
    */
998
1134
    azflush(&archive, Z_SYNC_FLUSH);
999
1135
    rc= read_data_header(&archive);
1000
1136
 
1001
 
    /*
 
1137
    /* 
1002
1138
      On success of writing out the new header, we now fetch each row and
1003
 
      insert it into the new archive file.
 
1139
      insert it into the new archive file. 
1004
1140
    */
1005
1141
    if (!rc)
1006
1142
    {
 
1143
      uint64_t x;
1007
1144
      uint64_t rows_restored;
1008
1145
      share->rows_recorded= 0;
1009
1146
      stats.auto_increment_value= 1;
1011
1148
 
1012
1149
      rows_restored= archive.rows;
1013
1150
 
1014
 
      for (uint64_t x= 0; x < rows_restored ; x++)
 
1151
      for (x= 0; x < rows_restored ; x++)
1015
1152
      {
1016
 
        rc= get_row(&archive, getTable()->getInsertRecord());
 
1153
        rc= get_row(&archive, table->record[0]);
1017
1154
 
1018
1155
        if (rc != 0)
1019
1156
          break;
1020
1157
 
1021
 
        real_write_row(getTable()->getInsertRecord(), writer.get());
 
1158
        real_write_row(table->record[0], &writer);
1022
1159
        /*
1023
1160
          Long term it should be possible to optimize this so that
1024
1161
          it is not called on each row.
1025
1162
        */
1026
 
        if (getTable()->found_next_number_field)
 
1163
        if (table->found_next_number_field)
1027
1164
        {
1028
 
          Field *field= getTable()->found_next_number_field;
1029
 
 
1030
 
          /* Since we will need to use field to translate, we need to flip its read bit */
1031
 
          field->setReadSet();
1032
 
 
 
1165
          Field *field= table->found_next_number_field;
1033
1166
          uint64_t auto_value=
1034
 
            (uint64_t) field->val_int_internal(getTable()->getInsertRecord() +
1035
 
                                               field->offset(getTable()->getInsertRecord()));
 
1167
            (uint64_t) field->val_int(table->record[0] +
 
1168
                                       field->offset(table->record[0]));
1036
1169
          if (share->archive_write.auto_increment < auto_value)
1037
1170
            stats.auto_increment_value=
1038
1171
              (share->archive_write.auto_increment= auto_value) + 1;
1039
1172
        }
1040
1173
      }
1041
 
      share->rows_recorded= (ha_rows)writer->rows;
 
1174
      share->rows_recorded= (ha_rows)writer.rows;
1042
1175
    }
1043
1176
 
1044
1177
    if (rc && rc != HA_ERR_END_OF_FILE)
1045
1178
    {
1046
1179
      goto error;
1047
1180
    }
1048
 
  }
 
1181
  } 
1049
1182
 
1050
 
  azclose(writer.get());
 
1183
  azclose(&writer);
1051
1184
  share->dirty= false;
1052
 
 
 
1185
  
1053
1186
  azclose(&archive);
1054
1187
 
1055
1188
  // make the file we just wrote be our data file
1056
 
  rc = internal::my_rename(writer_filename.c_str(), share->data_file_name.c_str(), MYF(0));
1057
 
 
1058
 
  free(proto_string);
 
1189
  rc = my_rename(writer_filename,share->data_file_name,MYF(0));
 
1190
 
 
1191
 
1059
1192
  return(rc);
1060
1193
error:
1061
 
  free(proto_string);
1062
 
  azclose(writer.get());
 
1194
  azclose(&writer);
1063
1195
 
1064
 
  return(rc);
 
1196
  return(rc); 
1065
1197
}
1066
1198
 
1067
 
/*
 
1199
/* 
1068
1200
  Below is an example of how to setup row level locking.
1069
1201
*/
1070
 
THR_LOCK_DATA **ha_archive::store_lock(Session *session,
 
1202
THR_LOCK_DATA **ha_archive::store_lock(THD *thd,
1071
1203
                                       THR_LOCK_DATA **to,
1072
1204
                                       enum thr_lock_type lock_type)
1073
1205
{
1074
 
  delayed_insert= false;
 
1206
  if (lock_type == TL_WRITE_DELAYED)
 
1207
    delayed_insert= true;
 
1208
  else
 
1209
    delayed_insert= false;
1075
1210
 
1076
 
  if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK)
 
1211
  if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK) 
1077
1212
  {
1078
 
    /*
 
1213
    /* 
1079
1214
      Here is where we get into the guts of a row level lock.
1080
 
      If TL_UNLOCK is set
 
1215
      If TL_UNLOCK is set 
1081
1216
      If we are not doing a LOCK Table or DISCARD/IMPORT
1082
 
      TABLESPACE, then allow multiple writers
 
1217
      TABLESPACE, then allow multiple writers 
1083
1218
    */
1084
1219
 
1085
1220
    if ((lock_type >= TL_WRITE_CONCURRENT_INSERT &&
1086
 
         lock_type <= TL_WRITE)
1087
 
        && ! session->doing_tablespace_operation())
 
1221
         lock_type <= TL_WRITE) && !thd_in_lock_tables(thd)
 
1222
        && !thd_tablespace_op(thd))
1088
1223
      lock_type = TL_WRITE_ALLOW_WRITE;
1089
1224
 
1090
 
    /*
 
1225
    /* 
1091
1226
      In queries of type INSERT INTO t1 SELECT ... FROM t2 ...
1092
1227
      MySQL would use the lock TL_READ_NO_INSERT on t2, and that
1093
1228
      would conflict with TL_WRITE_ALLOW_WRITE, blocking all inserts
1094
1229
      to t2. Convert the lock to a normal read lock to allow
1095
 
      concurrent inserts to t2.
 
1230
      concurrent inserts to t2. 
1096
1231
    */
1097
1232
 
1098
 
    if (lock_type == TL_READ_NO_INSERT)
 
1233
    if (lock_type == TL_READ_NO_INSERT && !thd_in_lock_tables(thd)) 
1099
1234
      lock_type = TL_READ;
1100
1235
 
1101
1236
    lock.type=lock_type;
1106
1241
  return to;
1107
1242
}
1108
1243
 
 
1244
void ha_archive::update_create_info(HA_CREATE_INFO *create_info)
 
1245
{
 
1246
  ha_archive::info(HA_STATUS_AUTO);
 
1247
  if (!(create_info->used_fields & HA_CREATE_USED_AUTO))
 
1248
  {
 
1249
    create_info->auto_increment_value= stats.auto_increment_value;
 
1250
  }
 
1251
 
 
1252
  if (!(my_readlink(share->real_path, share->data_file_name, MYF(0))))
 
1253
    create_info->data_file_name= share->real_path;
 
1254
 
 
1255
  return;
 
1256
}
 
1257
 
 
1258
 
1109
1259
/*
1110
1260
  Hints for optimizer, see ha_tina for more information
1111
1261
*/
1112
 
int ha_archive::info(uint32_t flag)
 
1262
int ha_archive::info(uint flag)
1113
1263
{
1114
 
  /*
 
1264
  /* 
1115
1265
    If dirty, we lock, and then reset/flush the data.
1116
1266
    I found that just calling azflush() doesn't always work.
1117
1267
  */
1118
 
  pthread_mutex_lock(&share->mutex());
 
1268
  pthread_mutex_lock(&share->mutex);
1119
1269
  if (share->dirty == true)
1120
1270
  {
1121
1271
    azflush(&(share->archive_write), Z_SYNC_FLUSH);
1129
1279
 
1130
1280
  }
1131
1281
 
1132
 
  /*
 
1282
  /* 
1133
1283
    This should be an accurate number now, though bulk and delayed inserts can
1134
1284
    cause the number to be inaccurate.
1135
1285
  */
1136
1286
  stats.records= share->rows_recorded;
1137
 
  pthread_mutex_unlock(&share->mutex());
 
1287
  pthread_mutex_unlock(&share->mutex);
1138
1288
 
1139
1289
  scan_rows= stats.records;
1140
1290
  stats.deleted= 0;
1144
1294
  {
1145
1295
    struct stat file_stat;  // Stat information for the data file
1146
1296
 
1147
 
    stat(share->data_file_name.c_str(), &file_stat);
 
1297
    VOID(stat(share->data_file_name, &file_stat));
1148
1298
 
1149
 
    stats.mean_rec_length= getTable()->getRecordLength()+ buffer.alloced_length();
 
1299
    stats.mean_rec_length= table->getRecordLength()+ buffer.alloced_length();
1150
1300
    stats.data_file_length= file_stat.st_size;
1151
1301
    stats.create_time= file_stat.st_ctime;
1152
1302
    stats.update_time= file_stat.st_mtime;
1158
1308
  if (flag & HA_STATUS_AUTO)
1159
1309
  {
1160
1310
    init_archive_reader();
1161
 
    pthread_mutex_lock(&share->mutex());
 
1311
    pthread_mutex_lock(&share->mutex);
1162
1312
    azflush(&archive, Z_SYNC_FLUSH);
1163
 
    pthread_mutex_unlock(&share->mutex());
 
1313
    pthread_mutex_unlock(&share->mutex);
1164
1314
    stats.auto_increment_value= archive.auto_increment + 1;
1165
1315
  }
1166
1316
 
1170
1320
 
1171
1321
/*
1172
1322
  This method tells us that a bulk insert operation is about to occur. We set
1173
 
  a flag which will keep doInsertRecord from saying that its data is dirty. This in
 
1323
  a flag which will keep write_row from saying that its data is dirty. This in
1174
1324
  turn will keep selects from causing a sync to occur.
1175
1325
  Basically, yet another optimizations to keep compression working well.
1176
1326
*/
1182
1332
}
1183
1333
 
1184
1334
 
1185
 
/*
 
1335
/* 
1186
1336
  Other side of start_bulk_insert, is end_bulk_insert. Here we turn off the bulk insert
1187
1337
  flag, and set the share dirty so that the next select will call sync for us.
1188
1338
*/
1195
1345
 
1196
1346
/*
1197
1347
  We cancel a truncate command. The only way to delete an archive table is to drop it.
1198
 
  This is done for security reasons. In a later version we will enable this by
 
1348
  This is done for security reasons. In a later version we will enable this by 
1199
1349
  allowing the user to select a different row format.
1200
1350
*/
1201
1351
int ha_archive::delete_all_rows()
1204
1354
}
1205
1355
 
1206
1356
/*
 
1357
  We just return state if asked.
 
1358
*/
 
1359
bool ha_archive::is_crashed() const 
 
1360
{
 
1361
  return(share->crashed); 
 
1362
}
 
1363
 
 
1364
/*
1207
1365
  Simple scan of the tables to make sure everything is ok.
1208
1366
*/
1209
1367
 
1210
 
int ha_archive::check(Session* session)
 
1368
int ha_archive::check(THD* thd,
 
1369
                      HA_CHECK_OPT* check_opt __attribute__((unused)))
1211
1370
{
1212
1371
  int rc= 0;
1213
1372
  const char *old_proc_info;
 
1373
  uint64_t x;
1214
1374
 
1215
 
  old_proc_info= session->get_proc_info();
1216
 
  session->set_proc_info("Checking table");
 
1375
  old_proc_info= thd_proc_info(thd, "Checking table");
1217
1376
  /* Flush any waiting data */
1218
 
  pthread_mutex_lock(&share->mutex());
 
1377
  pthread_mutex_lock(&share->mutex);
1219
1378
  azflush(&(share->archive_write), Z_SYNC_FLUSH);
1220
 
  pthread_mutex_unlock(&share->mutex());
 
1379
  pthread_mutex_unlock(&share->mutex);
1221
1380
 
1222
1381
  /*
1223
 
    Now we will rewind the archive file so that we are positioned at the
 
1382
    Now we will rewind the archive file so that we are positioned at the 
1224
1383
    start of the file.
1225
1384
  */
1226
1385
  init_archive_reader();
1227
1386
  azflush(&archive, Z_SYNC_FLUSH);
1228
1387
  read_data_header(&archive);
1229
 
  for (uint64_t x= 0; x < share->archive_write.rows; x++)
 
1388
  for (x= 0; x < share->archive_write.rows; x++)
1230
1389
  {
1231
 
    rc= get_row(&archive, getTable()->getInsertRecord());
 
1390
    rc= get_row(&archive, table->record[0]);
1232
1391
 
1233
1392
    if (rc != 0)
1234
1393
      break;
1235
1394
  }
1236
1395
 
1237
 
  session->set_proc_info(old_proc_info);
 
1396
  thd_proc_info(thd, old_proc_info);
1238
1397
 
1239
 
  if ((rc && rc != HA_ERR_END_OF_FILE))
 
1398
  if ((rc && rc != HA_ERR_END_OF_FILE))  
1240
1399
  {
1241
1400
    share->crashed= false;
1242
1401
    return(HA_ADMIN_CORRUPT);
1247
1406
  }
1248
1407
}
1249
1408
 
1250
 
int ArchiveEngine::doRenameTable(Session&, const identifier::Table &from, const identifier::Table &to)
1251
 
{
1252
 
  int error= 0;
1253
 
 
1254
 
  for (const char **ext= bas_ext(); *ext ; ext++)
1255
 
  {
1256
 
    if (rename_file_ext(from.getPath().c_str(), to.getPath().c_str(), *ext))
1257
 
    {
1258
 
      if ((error=errno) != ENOENT)
1259
 
        break;
1260
 
      error= 0;
1261
 
    }
1262
 
  }
1263
 
 
1264
 
  return error;
1265
 
}
1266
 
 
1267
 
bool ArchiveEngine::doDoesTableExist(Session&,
1268
 
                                     const identifier::Table &identifier)
1269
 
{
1270
 
  string proto_path(identifier.getPath());
1271
 
  proto_path.append(ARZ);
1272
 
 
1273
 
  if (access(proto_path.c_str(), F_OK))
1274
 
  {
1275
 
    return false;
1276
 
  }
1277
 
 
1278
 
  return true;
1279
 
}
1280
 
 
1281
 
void ArchiveEngine::doGetTableIdentifiers(drizzled::CachedDirectory &directory,
1282
 
                                          const drizzled::identifier::Schema &schema_identifier,
1283
 
                                          drizzled::identifier::Table::vector &set_of_identifiers)
1284
 
{
1285
 
  drizzled::CachedDirectory::Entries entries= directory.getEntries();
1286
 
 
1287
 
  for (drizzled::CachedDirectory::Entries::iterator entry_iter= entries.begin(); 
1288
 
       entry_iter != entries.end(); ++entry_iter)
1289
 
  {
1290
 
    drizzled::CachedDirectory::Entry *entry= *entry_iter;
1291
 
    const string *filename= &entry->filename;
1292
 
 
1293
 
    assert(filename->size());
1294
 
 
1295
 
    const char *ext= strchr(filename->c_str(), '.');
1296
 
 
1297
 
    if (ext == NULL || my_strcasecmp(system_charset_info, ext, ARZ) ||
1298
 
        (filename->compare(0, strlen(TMP_FILE_PREFIX), TMP_FILE_PREFIX) == 0))
1299
 
    {  }
1300
 
    else
1301
 
    {
1302
 
      char uname[NAME_LEN + 1];
1303
 
      uint32_t file_name_len;
1304
 
 
1305
 
      file_name_len= identifier::Table::filename_to_tablename(filename->c_str(), uname, sizeof(uname));
1306
 
      // TODO: Remove need for memory copy here
1307
 
      uname[file_name_len - sizeof(ARZ) + 1]= '\0'; // Subtract ending, place NULL 
1308
 
 
1309
 
      set_of_identifiers.push_back(identifier::Table(schema_identifier, uname));
1310
 
    }
1311
 
  }
1312
 
}
 
1409
/*
 
1410
  Check and repair the table if needed.
 
1411
*/
 
1412
bool ha_archive::check_and_repair(THD *thd) 
 
1413
{
 
1414
  HA_CHECK_OPT check_opt;
 
1415
 
 
1416
  check_opt.init();
 
1417
 
 
1418
  return(repair(thd, &check_opt));
 
1419
}
 
1420
 
 
1421
archive_record_buffer *ha_archive::create_record_buffer(unsigned int length) 
 
1422
{
 
1423
  archive_record_buffer *r;
 
1424
  if (!(r= 
 
1425
        (archive_record_buffer*) my_malloc(sizeof(archive_record_buffer),
 
1426
                                           MYF(MY_WME))))
 
1427
  {
 
1428
    return(NULL); /* purecov: inspected */
 
1429
  }
 
1430
  r->length= (int)length;
 
1431
 
 
1432
  if (!(r->buffer= (uchar*) my_malloc(r->length,
 
1433
                                    MYF(MY_WME))))
 
1434
  {
 
1435
    my_free((char*) r, MYF(MY_ALLOW_ZERO_PTR));
 
1436
    return(NULL); /* purecov: inspected */
 
1437
  }
 
1438
 
 
1439
  return(r);
 
1440
}
 
1441
 
 
1442
void ha_archive::destroy_record_buffer(archive_record_buffer *r) 
 
1443
{
 
1444
  my_free((char*) r->buffer, MYF(MY_ALLOW_ZERO_PTR));
 
1445
  my_free((char*) r, MYF(MY_ALLOW_ZERO_PTR));
 
1446
  return;
 
1447
}
 
1448
 
 
1449
static DRIZZLE_SYSVAR_BOOL(aio, archive_use_aio,
 
1450
  PLUGIN_VAR_NOCMDOPT,
 
1451
  "Whether or not to use asynchronous IO.",
 
1452
  NULL, NULL, true);
 
1453
 
 
1454
static struct st_mysql_sys_var* archive_system_variables[]= {
 
1455
  DRIZZLE_SYSVAR(aio),
 
1456
  NULL
 
1457
};
 
1458
 
 
1459
mysql_declare_plugin(archive)
 
1460
{
 
1461
  DRIZZLE_STORAGE_ENGINE_PLUGIN,
 
1462
  "ARCHIVE",
 
1463
  "3.5",
 
1464
  "Brian Aker, MySQL AB",
 
1465
  "Archive storage engine",
 
1466
  PLUGIN_LICENSE_GPL,
 
1467
  archive_db_init, /* Plugin Init */
 
1468
  archive_db_done, /* Plugin Deinit */
 
1469
  NULL,                       /* status variables                */
 
1470
  archive_system_variables,   /* system variables                */
 
1471
  NULL                        /* config options                  */
 
1472
}
 
1473
mysql_declare_plugin_end;
 
1474