diff options
Diffstat (limited to 'src/btree/bt_compress.c')
-rw-r--r-- | src/btree/bt_compress.c | 3173 |
1 files changed, 3173 insertions, 0 deletions
diff --git a/src/btree/bt_compress.c b/src/btree/bt_compress.c new file mode 100644 index 00000000..3f293461 --- /dev/null +++ b/src/btree/bt_compress.c @@ -0,0 +1,3173 @@ +/*- + * See the file LICENSE for redistribution information. + * + * Copyright (c) 1996, 2012 Oracle and/or its affiliates. All rights reserved. + */ + +#include "db_config.h" + +#include "db_int.h" +#include "dbinc/db_page.h" +#include "dbinc/btree.h" + +#ifdef HAVE_COMPRESSION + +static int __bam_compress_marshal_data __P((DB *, const DBT *, DBT *)); +static int __bam_compress_set_dbt __P((DB *, DBT *, const void *, u_int32_t)); +static int __bam_compress_check_sort_multiple_key __P((DB *, DBT *)); +static int __bam_compress_check_sort_multiple __P((DB *, DBT *, DBT *)); +static int __bam_compress_check_sort_multiple_keyonly __P((DB *, DBT *)); +static int __bamc_compress_del_and_get_next __P((DBC *, DBT *, DBT *)); +static int __bamc_compress_get_bothc __P((DBC *, DBT *, u_int32_t)); +static int __bamc_compress_get_multiple_key __P((DBC *, DBT *, u_int32_t)); +static int __bamc_compress_get_multiple __P((DBC *, DBT *, DBT *,u_int32_t)); +static int __bamc_compress_get_next __P((DBC *, u_int32_t)); +static int __bamc_compress_get_next_dup __P((DBC *, DBT *, u_int32_t)); +static int __bamc_compress_get_next_nodup __P((DBC *, u_int32_t)); +static int __bamc_compress_get_prev __P((DBC *, u_int32_t)); +static int __bamc_compress_get_prev_dup __P((DBC *, u_int32_t)); +static int __bamc_compress_get_prev_nodup __P((DBC *, u_int32_t)); +static int __bamc_compress_get_set __P((DBC *, + DBT *, DBT *, u_int32_t, u_int32_t)); +static int __bamc_compress_ibulk_del __P((DBC *, DBT *, u_int32_t)); +static int __bamc_compress_idel __P((DBC *, u_int32_t)); +static int __bamc_compress_iget __P((DBC *, DBT *, DBT *, u_int32_t)); +static int __bamc_compress_iput __P((DBC *, DBT *, DBT *, u_int32_t)); +static int __bamc_compress_relocate __P((DBC *)); +static void __bamc_compress_reset __P((DBC *)); +static int __bamc_compress_seek __P((DBC *, + const DBT *, const DBT *, u_int32_t)); +static int __bamc_compress_store __P((DBC *, + DBT *, DBT*, DBT **, DBT **, DBT *, DBT *)); +static int __bamc_next_decompress __P((DBC *)); +static int __bamc_start_decompress __P((DBC *)); + +/* + * Call __dbc_iget(), resizing DBTs if DB_BUFFER_SMALL is returned. + * We're always using a transient cursor when this macro is used, so + * we have to replace the OP with DB_CURRENT when we retry. + */ +#define CMP_IGET_RETRY(ret, dbc, dbt1, dbt2, flags) do { \ + DB_ASSERT((dbc)->env, F_ISSET((dbt1), DB_DBT_USERMEM)); \ + DB_ASSERT((dbc)->env, F_ISSET((dbt2), DB_DBT_USERMEM)); \ + if (((ret) =__dbc_iget((dbc), \ + (dbt1), (dbt2), (flags))) == DB_BUFFER_SMALL) { \ + if ((CMP_RESIZE_DBT((ret), (dbc)->env, (dbt1))) != 0) \ + break; \ + if ((CMP_RESIZE_DBT((ret), (dbc)->env, (dbt2))) != 0) \ + break; \ + (ret) = __dbc_iget((dbc), (dbt1), (dbt2), \ + ((flags) & ~DB_OPFLAGS_MASK) | DB_CURRENT); \ + } \ +} while (0) + +#define CMP_INIT_DBT(dbt) do { \ + (dbt)->data = NULL; \ + (dbt)->size = 0; \ + (dbt)->ulen = 0; \ + (dbt)->doff = 0; \ + (dbt)->dlen = 0; \ + (dbt)->flags = DB_DBT_USERMEM; \ + (dbt)->app_data = NULL; \ +} while (0) + +#define CMP_FREE_DBT(env, dbt) do { \ + DB_ASSERT((env), F_ISSET((dbt), DB_DBT_USERMEM)); \ + __os_free((env), (dbt)->data); \ +} while (0) + +#define CMP_RESIZE_DBT(ret, env, dbt) \ + (((dbt)->size > (dbt)->ulen) ? \ + ((((ret) = __os_realloc((env), (dbt)->size, &(dbt)->data)) \ + != 0) ? (ret) : (((dbt)->ulen = (dbt)->size), 0)) : 0) + +static int +__bam_compress_set_dbt(dbp, dbt, data, size) + DB *dbp; + DBT *dbt; + const void *data; + u_int32_t size; +{ + int ret; + + ret = 0; + DB_ASSERT(dbp->env, F_ISSET(dbt, DB_DBT_USERMEM)); + + dbt->size = size; + if (CMP_RESIZE_DBT(ret, dbp->env, dbt) != 0) + return (ret); + + memcpy(dbt->data, data, size); + return (0); +} + +/******************************************************************************/ + +/* + * Very simple key/data stream to give __bamc_compress_merge_insert() + * a source of data to work on. + */ +struct __bam_compress_stream; +typedef struct __bam_compress_stream BTREE_COMPRESS_STREAM; +struct __bam_compress_stream +{ + int (*next)(BTREE_COMPRESS_STREAM *, DBT *, DBT *); + + void *kptr, *dptr; + DBT *key, *data; +}; + +/* + * These function prototypes can not go at the beginning because they rely on + * on BTREE_COMPRESS_STREAM defined above. + * The prototypes are required to avoid the Microsoft C++ compiler generating + * warnings about mismatching parameter lists. + */ +static int __bam_cs_next_done __P((BTREE_COMPRESS_STREAM *, DBT *, DBT *)); +static int __bam_cs_single_next __P((BTREE_COMPRESS_STREAM *, DBT *, DBT *)); +static void __bam_cs_create_single + __P((BTREE_COMPRESS_STREAM *, DBT *, DBT *)); +static int __bam_cs_single_keyonly_next + __P((BTREE_COMPRESS_STREAM *, DBT *, DBT *)); +static void __bam_cs_create_single_keyonly + __P((BTREE_COMPRESS_STREAM *, DBT *)); +static int __bam_cs_multiple_key_next + __P((BTREE_COMPRESS_STREAM *, DBT *, DBT *)); +static void __bam_cs_create_multiple_key __P((BTREE_COMPRESS_STREAM *, DBT *)); +static int __bam_cs_multiple_next __P((BTREE_COMPRESS_STREAM *, DBT *, DBT *)); +static void __bam_cs_create_multiple + __P((BTREE_COMPRESS_STREAM *, DBT *, DBT *)); +static int __bam_cs_multiple_keyonly_next + __P((BTREE_COMPRESS_STREAM *, DBT *, DBT *)); +static void __bam_cs_create_multiple_keyonly + __P((BTREE_COMPRESS_STREAM *, DBT *)); +static int __bamc_compress_merge_insert + __P((DBC *, BTREE_COMPRESS_STREAM *, u_int32_t *, u_int32_t)); +static int __bamc_compress_merge_delete + __P((DBC *, BTREE_COMPRESS_STREAM *, u_int32_t *)); +static int __bamc_compress_merge_delete_dups + __P((DBC *, BTREE_COMPRESS_STREAM *, u_int32_t *)); + +/* BTREE_COMPRESS_STREAM->next() for when the data has finished. */ +static int +__bam_cs_next_done(stream, key, data) + BTREE_COMPRESS_STREAM *stream; + DBT *key, *data; +{ + COMPQUIET(stream, NULL); + COMPQUIET(key, NULL); + COMPQUIET(data, NULL); + return (0); +} + +/* BTREE_COMPRESS_STREAM->next() for a single key/data pair. */ +static int +__bam_cs_single_next(stream, key, data) + BTREE_COMPRESS_STREAM *stream; + DBT *key, *data; +{ + key->data = stream->key->data; + key->size = stream->key->size; + data->data = stream->data->data; + data->size = stream->data->size; + stream->next = __bam_cs_next_done; + return (1); +} + +/* Create a BTREE_COMPRESS_STREAM for a single key/data pair */ +static void +__bam_cs_create_single(stream, key, data) + BTREE_COMPRESS_STREAM *stream; + DBT *key, *data; +{ + stream->next = __bam_cs_single_next; + stream->key = key; + stream->data = data; +} + +/* BTREE_COMPRESS_STREAM->next() for a single key. */ +static int +__bam_cs_single_keyonly_next(stream, key, data) + BTREE_COMPRESS_STREAM *stream; + DBT *key, *data; +{ + key->data = stream->key->data; + key->size = stream->key->size; + if (data != NULL) { + data->data = NULL; + data->size = 0; + } + stream->next = __bam_cs_next_done; + return (1); +} + +/* Create a BTREE_COMPRESS_STREAM for a single key/data pair */ +static void +__bam_cs_create_single_keyonly(stream, key) + BTREE_COMPRESS_STREAM *stream; + DBT *key; +{ + stream->next = __bam_cs_single_keyonly_next; + stream->key = key; +} + +/* + * BTREE_COMPRESS_STREAM->next() for a single buffer in the DB_MULTIPLE_KEY + * format. + */ +static int +__bam_cs_multiple_key_next(stream, key, data) + BTREE_COMPRESS_STREAM *stream; + DBT *key, *data; +{ + DB_MULTIPLE_KEY_NEXT(stream->kptr, stream->key, key->data, key->size, + data->data, data->size); + if (key->data == NULL) { + stream->next = __bam_cs_next_done; + return (0); + } + return (1); +} + +/* + * Create a BTREE_COMPRESS_STREAM for a single buffer in the DB_MULTIPLE_KEY + * format. + */ +static void +__bam_cs_create_multiple_key(stream, multiple) + BTREE_COMPRESS_STREAM *stream; + DBT *multiple; +{ + stream->next = __bam_cs_multiple_key_next; + stream->key = multiple; + DB_MULTIPLE_INIT(stream->kptr, stream->key); +} + +/* BTREE_COMPRESS_STREAM->next() for two buffers in the DB_MULTIPLE format. */ +static int +__bam_cs_multiple_next(stream, key, data) + BTREE_COMPRESS_STREAM *stream; + DBT *key, *data; +{ + DB_MULTIPLE_NEXT(stream->kptr, stream->key, key->data, key->size); + DB_MULTIPLE_NEXT(stream->dptr, stream->data, data->data, data->size); + if (key->data == NULL || data->data == NULL) { + stream->next = __bam_cs_next_done; + return (0); + } + return (1); +} + +/* Create a BTREE_COMPRESS_STREAM for two buffers in the DB_MULTIPLE format. */ +static void +__bam_cs_create_multiple(stream, key, data) + BTREE_COMPRESS_STREAM *stream; + DBT *key, *data; +{ + stream->next = __bam_cs_multiple_next; + stream->key = key; + stream->data = data; + DB_MULTIPLE_INIT(stream->kptr, stream->key); + DB_MULTIPLE_INIT(stream->dptr, stream->data); +} + +/* + * BTREE_COMPRESS_STREAM->next() for a single buffer in the DB_MULTIPLE + * format. + */ +static int +__bam_cs_multiple_keyonly_next(stream, key, data) + BTREE_COMPRESS_STREAM *stream; + DBT *key, *data; +{ + DB_MULTIPLE_NEXT(stream->kptr, stream->key, key->data, key->size); + if (key->data == NULL) { + stream->next = __bam_cs_next_done; + return (0); + } + if (data != NULL) { + data->data = NULL; + data->size = 0; + } + return (1); +} + +/* + * Create a BTREE_COMPRESS_STREAM for a single buffer in the DB_MULTIPLE + * format. + */ +static void +__bam_cs_create_multiple_keyonly(stream, key) + BTREE_COMPRESS_STREAM *stream; + DBT *key; +{ + stream->next = __bam_cs_multiple_keyonly_next; + stream->key = key; + DB_MULTIPLE_INIT(stream->kptr, stream->key); +} + +/******************************************************************************/ + +/* + * Marshal data in initial data format into destbuf, resizing destbuf if + * necessary. + */ +static int +__bam_compress_marshal_data(dbp, data, destbuf) + DB *dbp; + const DBT *data; + DBT *destbuf; +{ + int ret; + u_int8_t *ptr; + + ret = 0; + DB_ASSERT(dbp->env, F_ISSET(destbuf, DB_DBT_USERMEM)); + + destbuf->size = __db_compress_count_int(data->size); + destbuf->size += data->size; + if (CMP_RESIZE_DBT(ret, dbp->env, destbuf) != 0) + return (ret); + + ptr = (u_int8_t*)destbuf->data; + ptr += __db_compress_int(ptr, data->size); + memcpy(ptr, data->data, data->size); + + return (0); +} + +/* + * Unmarshal initial data from source into data - does not copy, points + * into source. + */ +#define CMP_UNMARSHAL_DATA(src, dest) do { \ + (dest)->data = ((u_int8_t*)(src)->data) + \ + __db_decompress_int32((u_int8_t*)(src)->data, \ + &(dest)->size); \ +} while (0) + +/******************************************************************************/ + +/* + * __bam_compress_dupcmp -- + * Duplicate comparison function for compressed BTrees. + * + * PUBLIC: int __bam_compress_dupcmp __P((DB *, const DBT *, const DBT *)); + */ +int +__bam_compress_dupcmp(db, a, b) + DB *db; + const DBT *a; + const DBT *b; +{ + DBT dcmp_a, dcmp_b; + + /* Decompress the initial data in a */ + CMP_UNMARSHAL_DATA(a, &dcmp_a); + dcmp_a.ulen = 0; + dcmp_a.doff = 0; + dcmp_a.dlen = 0; + dcmp_a.flags = 0; + dcmp_a.app_data = 0; + + /* Decompress the initial data in b */ + CMP_UNMARSHAL_DATA(b, &dcmp_b); + dcmp_b.ulen = 0; + dcmp_b.doff = 0; + dcmp_b.dlen = 0; + dcmp_b.flags = 0; + dcmp_b.app_data = 0; + + /* Call the user's duplicate compare function */ + return ((BTREE *)db->bt_internal)-> + compress_dup_compare(db, &dcmp_a, &dcmp_b); +} + +/* + * __bam_defcompress -- + * Default compression routine. + * + * PUBLIC: int __bam_defcompress __P((DB *, const DBT *, const DBT *, + * PUBLIC: const DBT *, const DBT *, DBT *)); + */ +int +__bam_defcompress(dbp, prevKey, prevData, key, data, dest) + DB *dbp; + const DBT *prevKey, *prevData, *key, *data; + DBT *dest; +{ + u_int8_t *ptr; + const u_int8_t *k, *p; + size_t len, prefix, suffix; + + COMPQUIET(dbp, NULL); + + k = (const u_int8_t*)key->data; + p = (const u_int8_t*)prevKey->data; + len = key->size > prevKey->size ? prevKey->size : key->size; + for (; len-- && *k == *p; ++k, ++p) + continue; + + prefix = (size_t)(k - (u_int8_t*)key->data); + suffix = key->size - prefix; + + if (prefix == prevKey->size && suffix == 0) { + /* It's a duplicate - do prefix compression on the value */ + k = (const u_int8_t*)data->data; + p = (const u_int8_t*)prevData->data; + len = data->size > prevData->size ? prevData->size : data->size; + for (; len-- && *k == *p; ++k, ++p) + continue; + + prefix = (size_t)(k - (u_int8_t*)data->data); + suffix = data->size - prefix; + + /* Check that we have enough space in dest */ + dest->size = (u_int32_t)(1 + __db_compress_count_int(prefix) + + __db_compress_count_int(suffix) + suffix); + if (dest->size > dest->ulen) + return (DB_BUFFER_SMALL); + + /* Magic identifying byte */ + ptr = (u_int8_t*)dest->data; + *ptr = CMP_INT_SPARE_VAL; + ++ptr; + + /* prefix length */ + ptr += __db_compress_int(ptr, prefix); + + /* suffix length */ + ptr += __db_compress_int(ptr, suffix); + + /* suffix */ + memcpy(ptr, k, suffix); + + return (0); + } + + /* Check that we have enough space in dest */ + dest->size = (u_int32_t)(__db_compress_count_int(prefix) + + __db_compress_count_int(suffix) + + __db_compress_count_int(data->size) + suffix + data->size); + if (dest->size > dest->ulen) + return (DB_BUFFER_SMALL); + + /* prefix length */ + ptr = (u_int8_t*)dest->data; + ptr += __db_compress_int(ptr, prefix); + + /* suffix length */ + ptr += __db_compress_int(ptr, suffix); + + /* data length */ + ptr += __db_compress_int(ptr, data->size); + + /* suffix */ + memcpy(ptr, k, suffix); + ptr += suffix; + + /* data */ + memcpy(ptr, data->data, data->size); + + return (0); +} + +/* + * __bam_defdecompress -- + * Default decompression routine. + * + * PUBLIC: int __bam_defdecompress __P((DB *, const DBT *, const DBT *, DBT *, + * PUBLIC: DBT *, DBT *)); + */ +int +__bam_defdecompress(dbp, prevKey, prevData, compressed, destKey, destData) + DB *dbp; + const DBT *prevKey, *prevData; + DBT *compressed, *destKey, *destData; +{ + u_int8_t *s, *d; + u_int32_t prefix, suffix, size; + + COMPQUIET(dbp, NULL); + + /* + * Check for the magic identifying byte, that tells us that this is a + * compressed duplicate value. + */ + s = (u_int8_t*)compressed->data; + if (*s == CMP_INT_SPARE_VAL) { + ++s; + size = 1; + + /* Unmarshal prefix and suffix */ + size += __db_decompress_count_int(s); + if (size > compressed->size) + return (EINVAL); + s += __db_decompress_int32(s, &prefix); + + size += __db_decompress_count_int(s); + if (size > compressed->size) + return (EINVAL); + s += __db_decompress_int32(s, &suffix); + + /* Check destination lengths */ + destKey->size = prevKey->size; + destData->size = prefix + suffix; + if (destKey->size > destKey->ulen || + destData->size > destData->ulen) + return (DB_BUFFER_SMALL); + + /* Write the key */ + memcpy(destKey->data, prevKey->data, destKey->size); + + /* Write the prefix */ + if (prefix > prevData->size) + return (EINVAL); + d = (u_int8_t*)destData->data; + memcpy(d, prevData->data, prefix); + d += prefix; + + /* Write the suffix */ + size += suffix; + if (size > compressed->size) + return (EINVAL); + memcpy(d, s, suffix); + s += suffix; + + /* Return bytes read */ + compressed->size = (u_int32_t)(s - (u_int8_t*)compressed->data); + return (0); + } + + /* Unmarshal prefix, suffix and data length */ + size = __db_decompress_count_int(s); + if (size > compressed->size) + return (EINVAL); + s += __db_decompress_int32(s, &prefix); + + size += __db_decompress_count_int(s); + if (size > compressed->size) + return (EINVAL); + s += __db_decompress_int32(s, &suffix); + + size += __db_decompress_count_int(s); + if (size > compressed->size) + return (EINVAL); + s += __db_decompress_int32(s, &destData->size); + + /* Check destination lengths */ + destKey->size = prefix + suffix; + if (destKey->size > destKey->ulen || destData->size > destData->ulen) + return (DB_BUFFER_SMALL); + + /* Write the prefix */ + if (prefix > prevKey->size) + return (EINVAL); + d = (u_int8_t*)destKey->data; + memcpy(d, prevKey->data, prefix); + d += prefix; + + /* Write the suffix */ + size += suffix; + if (size > compressed->size) + return (EINVAL); + memcpy(d, s, suffix); + s += suffix; + + /* Write the data */ + size += destData->size; + if (size > compressed->size) + return (EINVAL); + memcpy(destData->data, s, destData->size); + s += destData->size; + + /* Return bytes read */ + compressed->size = (u_int32_t)(s - (u_int8_t*)compressed->data); + return (0); +} + +/******************************************************************************/ + +/* + * Set dbc up to start decompressing the compressed key/data pair, dbc->key1 + * and dbc->compressed. + */ +static int +__bamc_start_decompress(dbc) + DBC *dbc; +{ + BTREE_CURSOR *cp; + int ret; + u_int32_t datasize; + + cp = (BTREE_CURSOR *)dbc->internal; + + cp->prevKey = NULL; + cp->prevData = NULL; + cp->currentKey = &cp->key1; + cp->currentData = &cp->data1; + cp->compcursor = (u_int8_t*)cp->compressed.data; + cp->compend = cp->compcursor + cp->compressed.size; + cp->prevcursor = NULL; + cp->prev2cursor = NULL; + + /* Unmarshal the first data */ + cp->compcursor += __db_decompress_int32(cp->compcursor, &datasize); + ret = __bam_compress_set_dbt(dbc->dbp, + cp->currentData, cp->compcursor, datasize); + + if (ret == 0) + cp->compcursor += datasize; + return (ret); +} + +/* Decompress the next key/data pair from dbc->compressed. */ +static int +__bamc_next_decompress(dbc) + DBC *dbc; +{ + DBT compressed; + int ret; + BTREE_CURSOR *cp; + DB *db; + + ret = 0; + cp = (BTREE_CURSOR *)dbc->internal; + db = dbc->dbp; + + if (cp->compcursor >= cp->compend) + return (DB_NOTFOUND); + + cp->prevKey = cp->currentKey; + cp->prevData = cp->currentData; + cp->prev2cursor = cp->prevcursor; + cp->prevcursor = cp->compcursor; + + if (cp->currentKey == &cp->key1) { + cp->currentKey = &cp->key2; + cp->currentData = &cp->data2; + } else { + cp->currentKey = &cp->key1; + cp->currentData = &cp->data1; + } + + compressed.flags = DB_DBT_USERMEM; + compressed.data = (void*)cp->compcursor; + compressed.ulen = compressed.size = + (u_int32_t)(cp->compend - cp->compcursor); + compressed.app_data = NULL; + + while ((ret = ((BTREE *)db->bt_internal)->bt_decompress(db, + cp->prevKey, cp->prevData, &compressed, + cp->currentKey, cp->currentData)) == DB_BUFFER_SMALL) { + if (CMP_RESIZE_DBT(ret, dbc->env, cp->currentKey) != 0) + break; + if (CMP_RESIZE_DBT(ret, dbc->env, cp->currentData) != 0) + break; + } + + if (ret == 0) + cp->compcursor += compressed.size; + return (ret); +} + +/* + * Store key and data into destkey and destbuf, using the compression + * callback given. + */ +static int +__bamc_compress_store(dbc, key, data, prevKey, prevData, destkey, destbuf) + DBC *dbc; + DBT *key, *data; + DBT **prevKey, **prevData; + DBT *destkey, *destbuf; +{ + int ret; + DBT dest; + + if (*prevKey == 0) { + if ((ret = __bam_compress_set_dbt(dbc->dbp, + destkey, key->data, key->size)) != 0) + return (ret); + + /* Marshal data - resize if it won't fit */ + ret = __bam_compress_marshal_data(dbc->dbp, data, destbuf); + + } else if (((BTREE_CURSOR *)dbc->internal)->ovflsize > destbuf->size) { + /* + * Don't write more than cp->ovflsize bytes to the destination + * buffer - destbuf must be at least cp->ovflsize in size. + */ + dest.flags = DB_DBT_USERMEM; + dest.data = (u_int8_t*)destbuf->data + destbuf->size; + dest.ulen = + ((BTREE_CURSOR *)dbc->internal)->ovflsize - destbuf->size; + dest.size = 0; + dest.app_data = NULL; + + ret = ((BTREE *)dbc->dbp->bt_internal)->bt_compress( + dbc->dbp, *prevKey, *prevData, key, data, &dest); + + if (ret == 0) + destbuf->size += dest.size; + } else + ret = DB_BUFFER_SMALL; + + if (ret == 0) { + *prevKey = key; + *prevData = data; + } + + return (ret); +} + +/* + * Move dbc->dbc to the correct position to start linear searching for + * seek_key/seek_data - the biggest key smaller than or equal to + * seek_key/seek_data. + */ +static int +__bamc_compress_seek(dbc, seek_key, seek_data, flags) + DBC *dbc; + const DBT *seek_key; + const DBT *seek_data; + u_int32_t flags; +{ + int ret; + u_int32_t method; + DB *dbp; + BTREE_CURSOR *cp; + + dbp = dbc->dbp; + cp = (BTREE_CURSOR *)dbc->internal; + + if ((ret = __bam_compress_set_dbt( + dbp, &cp->key1, seek_key->data, seek_key->size)) != 0) + return (ret); + + /* + * We allow seek_data to be 0 for __bamc_compress_get_set() with + * DB_SET + */ + if (F_ISSET(dbp, DB_AM_DUPSORT) && seek_data != NULL) { + if ((ret = __bam_compress_marshal_data( + dbp, seek_data, &cp->compressed)) != 0) + return (ret); + + method = DB_GET_BOTH_LTE; + } else + method = DB_SET_LTE; + + CMP_IGET_RETRY(ret, dbc, &cp->key1, &cp->compressed, method | flags); + + if (ret == 0 && + F_ISSET(dbp, DB_AM_DUPSORT) && seek_data == NULL && + __db_compare_both(dbp, seek_key, 0, &cp->key1, 0) == 0) { + /* + * Some entries for seek_key might be in the previous chunk, + * so we need to start searching there. + */ + CMP_IGET_RETRY(ret, + dbc, &cp->key1, &cp->compressed, DB_PREV | flags); + if (ret == DB_NOTFOUND) { + /* No previous, we must need the first entry */ + CMP_IGET_RETRY(ret, + dbc, &cp->key1, &cp->compressed, DB_FIRST | flags); + } + } + + return (ret); +} + +/* Reset the cursor to an uninitialized state */ +static void +__bamc_compress_reset(dbc) + DBC *dbc; +{ + BTREE_CURSOR *cp; + + cp = (BTREE_CURSOR *)dbc->internal; + + cp->prevKey = 0; + cp->prevData = 0; + cp->currentKey = 0; + cp->currentData = 0; + cp->compcursor = 0; + cp->compend = 0; + cp->prevcursor = 0; + cp->prev2cursor = 0; + + F_CLR(cp, C_COMPRESS_DELETED|C_COMPRESS_MODIFIED); +} + +/* + * Duplicate the cursor and delete the current entry, move the original cursor + * on and then close the cursor we used to delete. We do that to make sure that + * the close method runs __bamc_physdel(), and actually gets rid of the deleted + * entry! + */ +static int +__bamc_compress_del_and_get_next(dbc, nextk, nextc) + DBC *dbc; + DBT *nextk, *nextc; +{ + int ret, ret_n; + DBC *dbc_n; + + if ((ret = __dbc_dup(dbc, &dbc_n, DB_POSITION | DB_SHALLOW_DUP)) != 0) + return (ret); + F_SET(dbc_n, DBC_TRANSIENT); + + if ((ret = __dbc_idel(dbc_n, 0)) != 0) + goto err; + + /* Read the next position */ + CMP_IGET_RETRY(ret, dbc, nextk, nextc, DB_NEXT); + + err: + if ((ret_n = __dbc_close(dbc_n)) != 0 && ret == 0) + ret = ret_n; + + /* No need to relocate this cursor */ + F_CLR((BTREE_CURSOR *)dbc->internal, C_COMPRESS_MODIFIED); + + return (ret); +} + +/* + * Duplicate the cursor, re-locate the position that this cursor pointed to + * using the duplicate (it may have been deleted), and then swap + * the cursors. We do that to make sure that the close method runs + * __bamc_physdel(), and gets rid of the entry that may have been deleted. + */ +static int +__bamc_compress_relocate(dbc) + DBC *dbc; +{ + int ret, t_ret; + BTREE_CURSOR *cp, *cp_n; + DBC *dbc_n; + + cp = (BTREE_CURSOR *)dbc->internal; + + if ((ret = __dbc_dup(dbc, &dbc_n, 0)) != 0) + return (ret); + F_SET(dbc_n, DBC_TRANSIENT); + + cp_n = (BTREE_CURSOR *)dbc_n->internal; + + if (F_ISSET(cp, C_COMPRESS_DELETED)) { + /* Find the position after the deleted entry again */ + ret = __bamc_compress_get_set( + dbc_n, &cp->del_key, &cp->del_data, 0, 0); + if (ret == DB_NOTFOUND) { + __bamc_compress_reset(dbc_n); + ret = 0; + } else if (ret != 0) + goto err; + + F_SET(cp_n, C_COMPRESS_DELETED); + + } else if (cp->currentKey != NULL) { + /* Find the current entry again */ + ret = __bamc_compress_get_set( + dbc_n, cp->currentKey, cp->currentData, + F_ISSET(dbc->dbp, DB_AM_DUPSORT) ? DB_GET_BOTH : DB_SET, 0); + + if (ret == DB_NOTFOUND) { + /* The current entry has been deleted */ + if ((ret = __bam_compress_set_dbt(dbc_n->dbp, + &cp_n->del_key, + cp->currentKey->data, cp->currentKey->size)) != 0) + return (ret); + if ((ret = __bam_compress_set_dbt(dbc_n->dbp, + &cp_n->del_data, cp->currentData->data, + cp->currentData->size)) != 0) + return (ret); + F_SET(cp_n, C_COMPRESS_DELETED); + ret = 0; + } else if (ret != 0) + goto err; + } + + err: + /* Cleanup and cursor resolution. This also clears the + C_COMPRESS_MODIFIED flag. */ + if ((t_ret = __dbc_cleanup(dbc, dbc_n, ret)) != 0 && ret == 0) + ret = t_ret; + + return (ret); +} + +/******************************************************************************/ + +#define CMP_STORE(key, data) do { \ + while ((ret = __bamc_compress_store(dbc, (key), (data), \ + &prevDestKey, &prevDestData, &destkey, &destbuf)) \ + == DB_BUFFER_SMALL) { \ + if ((ret = __dbc_iput(dbc, \ + &destkey, &destbuf, DB_KEYLAST)) != 0) \ + goto end; \ + prevDestKey = NULL; \ + prevDestData = NULL; \ + destbuf.size = 0; \ + } \ +} while (0) + +/* Merge the sorted key/data pairs from stream into the compressed database. */ +static int +__bamc_compress_merge_insert(dbc, stream, countp, flags) + DBC *dbc; + BTREE_COMPRESS_STREAM *stream; + u_int32_t *countp; + u_int32_t flags; +{ + DBT ikey1, ikey2, idata1, idata2, nextk, nextc, nextd, destkey, destbuf; + DBT *ikey, *idata, *prevIkey, *prevIdata, *prevDestKey, *prevDestData; + int ret, bulk_ret, cmp, nextExists, moreCompressed, iSmallEnough; + int moreStream; + u_int32_t chunk_count; + ENV *env; + BTREE_CURSOR *cp; + DB *dbp; + + env = dbc->env; + cp = (BTREE_CURSOR *)dbc->internal; + dbp = dbc->dbp; + bulk_ret = 0; + + memset(&ikey1, 0, sizeof(DBT)); + memset(&ikey2, 0, sizeof(DBT)); + memset(&idata1, 0, sizeof(DBT)); + memset(&idata2, 0, sizeof(DBT)); + + CMP_INIT_DBT(&nextk); + CMP_INIT_DBT(&nextc); + memset(&nextd, 0, sizeof(DBT)); + + CMP_INIT_DBT(&destkey); + CMP_INIT_DBT(&destbuf); + if ((ret = __os_malloc(env, cp->ovflsize, &destbuf.data)) != 0) + goto end; + destbuf.ulen = cp->ovflsize; + + if (countp != NULL) + *countp = 0; + chunk_count = 0; + + /* Get the first input key and data */ + ret = 0; + prevIkey = NULL; + prevIdata = NULL; + ikey = &ikey1; + idata = &idata1; + if (stream->next(stream, ikey, idata) == 0) + goto end; + + prevDestKey = NULL; + prevDestData = NULL; + + moreStream = 1; + while (moreStream != 0) { + nextExists = 1; + moreCompressed = 1; + + /* Seek the ikey/idata position */ + ret = __bamc_compress_seek(dbc, ikey, idata, 0); + if (ret == 0) { + /* + * Delete the key - we might overwrite it below + * but it's safer to just always delete it, and it + * doesn't seem significantly slower to do so. + */ + ret = __bamc_compress_del_and_get_next(dbc, &nextk, + &nextc); + if (ret == DB_NOTFOUND) { + ret = 0; + nextExists = 0; + } else if (ret == 0) { + CMP_UNMARSHAL_DATA(&nextc, &nextd); + } else + goto end; + ret = __bamc_start_decompress(dbc); + } else if (ret == DB_NOTFOUND) { + moreCompressed = 0; + + /* Read the next position */ + CMP_IGET_RETRY(ret, dbc, &nextk, &nextc, DB_FIRST); + if (ret == DB_NOTFOUND) { + ret = 0; + nextExists = 0; + } else if (ret == 0) { + CMP_UNMARSHAL_DATA(&nextc, &nextd); + } + } + + if (ret != 0) + goto end; + + /* !nextExists || ikey/idata < nextk/nextd */ + iSmallEnough = 1; + + while (moreCompressed != 0 || iSmallEnough != 0) { + if (moreCompressed == 0) + cmp = 1; + else if (iSmallEnough == 0) + cmp = -1; + else + cmp = __db_compare_both(dbp, cp->currentKey, + cp->currentData, ikey, idata); + + if (cmp < 0) { +store_current: CMP_STORE(cp->currentKey, cp->currentData); + if (ret != 0) + goto end; + } else { + switch (flags) { + case DB_KEYLAST: + case DB_KEYFIRST: + case DB_NODUPDATA: + if (cmp == 0 && bulk_ret == 0 && + F_ISSET(dbp, DB_AM_DUPSORT)) { + bulk_ret = __db_duperr(dbp, + flags); + + /* + * Continue until we store + * the current chunk, + * but don't insert any + * more entries. + */ + moreStream = 0; + iSmallEnough = 0; + + goto store_current; + } + break; + default: + break; + } + + CMP_STORE(ikey, idata); + if (ret != 0) + goto end; + ++chunk_count; + + /* + * prevDestKey/prevDestData now point to + * the same DBTs as ikey/idata. We don't + * want to overwrite them, so swap them + * to point to the other DBTs. + */ + if (ikey == &ikey1) { + ikey = &ikey2; + idata = &idata2; + prevIkey = &ikey1; + prevIdata = &idata1; + } else { + ikey = &ikey1; + idata = &idata1; + prevIkey = &ikey2; + prevIdata = &idata2; + } + + do { + /* Get the next input key and data */ + if (stream->next( + stream, ikey, idata) == 0) { + moreStream = 0; + iSmallEnough = 0; + break; + } + +#ifdef DIAGNOSTIC + /* Check that the stream is sorted */ + DB_ASSERT(env, __db_compare_both(dbp, + ikey, idata, prevIkey, + prevIdata) >= 0); +#endif + + /* Check for duplicates in the stream */ + } while (__db_compare_both(dbp, ikey, idata, + prevIkey, prevIdata) == 0); + + /* + * Check that !nextExists || + * ikey/idata < nextk/nextd + */ + if (moreStream != 0 && nextExists != 0 && + __db_compare_both(dbp, ikey, + idata, &nextk, &nextd) >= 0) + iSmallEnough = 0; + } + + if (cmp <= 0) { + ret = __bamc_next_decompress(dbc); + if (ret == DB_NOTFOUND) { + moreCompressed = 0; + ret = 0; + } else if (ret != 0) + goto end; + + } + } + + if (prevDestKey != NULL) { + if ((ret = __dbc_iput( + dbc, &destkey, &destbuf, DB_KEYLAST)) != 0) + goto end; + + if (countp != NULL) + *countp += chunk_count; + chunk_count = 0; + + prevDestKey = NULL; + prevDestData = NULL; + destbuf.size = 0; + } + } + + end: + CMP_FREE_DBT(env, &destkey); + CMP_FREE_DBT(env, &destbuf); + CMP_FREE_DBT(env, &nextk); + CMP_FREE_DBT(env, &nextc); + + return (ret != 0 ? ret : bulk_ret); +} + +/******************************************************************************/ + +/* Remove the sorted key/data pairs in stream from the compressed database. */ +static int +__bamc_compress_merge_delete(dbc, stream, countp) + DBC *dbc; + BTREE_COMPRESS_STREAM *stream; + u_int32_t *countp; +{ + DBT ikey, idata, nextk, nextc, nextd, destkey, destbuf, pdestkey; + DBT pdestdata; +#ifdef DIAGNOSTIC + DBT pikey, pidata; +#endif + DBT *prevDestKey, *prevDestData; + int ret, bulk_ret, cmp, moreCompressed, moreStream, nextExists; + int iSmallEnough; + u_int32_t chunk_count; + ENV *env; + BTREE_CURSOR *cp; + DB *dbp; + + env = dbc->env; + cp = (BTREE_CURSOR *)dbc->internal; + dbp = dbc->dbp; + bulk_ret = 0; + + memset(&ikey, 0, sizeof(DBT)); + memset(&idata, 0, sizeof(DBT)); + + CMP_INIT_DBT(&nextk); + CMP_INIT_DBT(&nextc); + memset(&nextd, 0, sizeof(DBT)); + + CMP_INIT_DBT(&pdestkey); + CMP_INIT_DBT(&pdestdata); + + CMP_INIT_DBT(&destkey); + CMP_INIT_DBT(&destbuf); + if ((ret = __os_malloc(env, cp->ovflsize, &destbuf.data)) != 0) + goto end; + destbuf.ulen = cp->ovflsize; + + if (countp != NULL) + *countp = 0; + chunk_count = 0; + + /* Get the first input key and data */ + ret = 0; + if (stream->next(stream, &ikey, &idata) == 0) + goto end; + + prevDestKey = NULL; + prevDestData = NULL; + + moreStream = 1; + while (moreStream != 0) { + nextExists = 1; + moreCompressed = 1; + + /* Seek the ikey/idata position */ + if ((ret = __bamc_compress_seek(dbc, &ikey, &idata, 0)) != 0) + goto end; + + /* + * Delete the key - we might overwrite it below but it's safer + * to just always delete it, and it doesn't seem significantly + * slower to do so. + */ + ret = __bamc_compress_del_and_get_next(dbc, &nextk, &nextc); + if (ret == DB_NOTFOUND) { + ret = 0; + nextExists = 0; + } else if (ret == 0) { + CMP_UNMARSHAL_DATA(&nextc, &nextd); + } else + goto end; + + if ((ret = __bamc_start_decompress(dbc)) != 0) + goto end; + + /* !nextExists || ikey/idata < nextk/nextd */ + iSmallEnough = 1; + + while (moreCompressed != 0 || iSmallEnough != 0) { + if (moreCompressed == 0) + cmp = 1; + else if (iSmallEnough == 0) + cmp = -1; + else + cmp = __db_compare_both(dbp, cp->currentKey, + cp->currentData, &ikey, &idata); + + if (cmp < 0) { + CMP_STORE(cp->currentKey, cp->currentData); + if (ret != 0) + goto end; + + if ((ret = __bam_compress_set_dbt(dbp, + &pdestkey, cp->currentKey->data, + cp->currentKey->size)) != 0) + goto end; + if ((ret = __bam_compress_set_dbt(dbp, + &pdestdata, cp->currentData->data, + cp->currentData->size)) != 0) + goto end; + prevDestKey = &pdestkey; + prevDestData = &pdestdata; + } else { + if (cmp != 0) { + /* + * Continue until we store the current + * chunk, but don't delete any more + * entries. + */ + bulk_ret = DB_NOTFOUND; + moreStream = 0; + iSmallEnough = 0; + } else + ++chunk_count; + +#ifdef DIAGNOSTIC + pikey = ikey; + pidata = idata; +#endif + + /* Get the next input key and data */ + if (stream->next(stream, &ikey, &idata) == 0) { + moreStream = 0; + iSmallEnough = 0; + } + +#ifdef DIAGNOSTIC + /* Check that the stream is sorted */ + DB_ASSERT(env, moreStream == 0 || + __db_compare_both(dbp, &ikey, &idata, + &pikey, &pidata) >= 0); +#endif + + /* + * Check that !nextExists || + * ikey/idata < nextk/nextd + */ + if (moreStream != 0 && nextExists != 0 && + __db_compare_both(dbp, &ikey, + &idata, &nextk, &nextd) >= 0) + iSmallEnough = 0; + } + + if (cmp <= 0) { + ret = __bamc_next_decompress(dbc); + if (ret == DB_NOTFOUND) { + moreCompressed = 0; + ret = 0; + } else if (ret != 0) + goto end; + } + } + + if (prevDestKey != NULL) { + if ((ret = __dbc_iput( + dbc, &destkey, &destbuf, DB_KEYLAST)) != 0) + goto end; + + if (countp) + *countp += chunk_count; + chunk_count = 0; + + prevDestKey = NULL; + prevDestData = NULL; + destbuf.size = 0; + } + } + + end: + CMP_FREE_DBT(env, &destkey); + CMP_FREE_DBT(env, &destbuf); + CMP_FREE_DBT(env, &pdestkey); + CMP_FREE_DBT(env, &pdestdata); + CMP_FREE_DBT(env, &nextk); + CMP_FREE_DBT(env, &nextc); + + return (ret != 0 ? ret : bulk_ret); +} + +/* + * Remove the sorted keys in stream along with all duplicate values from + * the compressed database. + */ +static int +__bamc_compress_merge_delete_dups(dbc, stream, countp) + DBC *dbc; + BTREE_COMPRESS_STREAM *stream; + u_int32_t *countp; +{ + DBC *dbc_n; + DBT ikey, nextk, noread, destkey, destbuf, pdestkey, pdestdata; +#ifdef DIAGNOSTIC + DBT pikey; +#endif + DBT *prevDestKey, *prevDestData; + int ret, ret_n, bulk_ret, cmp, moreCompressed, moreStream, nextExists; + int iSmallEnough, ifound; + u_int32_t chunk_count; + ENV *env; + BTREE_CURSOR *cp; + DB *dbp; + + env = dbc->env; + cp = (BTREE_CURSOR *)dbc->internal; + dbp = dbc->dbp; + bulk_ret = 0; + + memset(&ikey, 0, sizeof(DBT)); + + CMP_INIT_DBT(&nextk); + + memset(&noread, 0, sizeof(DBT)); + noread.flags = DB_DBT_PARTIAL | DB_DBT_USERMEM; + + CMP_INIT_DBT(&pdestkey); + CMP_INIT_DBT(&pdestdata); + + CMP_INIT_DBT(&destkey); + CMP_INIT_DBT(&destbuf); + if ((ret = __os_malloc(env, cp->ovflsize, &destbuf.data)) != 0) + goto end; + destbuf.ulen = cp->ovflsize; + + if (countp != NULL) + *countp = 0; + chunk_count = 0; + + /* Get the first input key and data */ + ret = 0; + if (stream->next(stream, &ikey, NULL) == 0) + goto end; + ifound = 0; + + prevDestKey = NULL; + prevDestData = NULL; + + moreStream = 1; + iSmallEnough = 0; + nextExists = 0; + while (moreStream != 0) { + if (iSmallEnough != 0) { + if (nextExists == 0) { + /* + * We've finished deleting the last key + * in the database + */ + if (ifound == 0) { + bulk_ret = DB_NOTFOUND; + } else + ++chunk_count; + break; + } + + /* Move to the next chunk */ + CMP_IGET_RETRY( + ret, dbc, &cp->key1, &cp->compressed, DB_CURRENT); + if (ret == DB_NOTFOUND) { + ret = 0; + break; + } else if (ret != 0) + goto end; + } else + /* Seek the ikey position */ + if ((ret = + __bamc_compress_seek(dbc, &ikey, NULL, 0)) != 0) + goto end; + + nextExists = 1; + moreCompressed = 1; + + /* + * Delete the key - we might overwrite it below but it's + * safer to just always delete it, and it doesn't seem + * significantly slower to do so. + */ + ret = __bamc_compress_del_and_get_next(dbc, &nextk, &noread); + if (ret == DB_NOTFOUND) { + ret = 0; + nextExists = 0; + } else if (ret != 0) + goto end; + + if ((ret = __bamc_start_decompress(dbc)) != 0) + goto end; + + /* !nextExists || ikey <= nextk */ + iSmallEnough = 1; + + while (moreCompressed != 0) { + if (moreCompressed == 0) + cmp = 1; + else if (iSmallEnough == 0) + cmp = -1; + else + cmp = __db_compare_both( + dbp, cp->currentKey, NULL, &ikey, NULL); + + if (cmp < 0) { + if ((ret = __bamc_compress_store(dbc, + cp->currentKey, cp->currentData, + &prevDestKey, + &prevDestData, &destkey, &destbuf)) != 0) + goto end; + + if ((ret = __bam_compress_set_dbt(dbp, + &pdestkey, cp->currentKey->data, + cp->currentKey->size)) != 0) + goto end; + if ((ret = __bam_compress_set_dbt(dbp, + &pdestdata, cp->currentData->data, + cp->currentData->size)) != 0) + goto end; + prevDestKey = &pdestkey; + prevDestData = &pdestdata; + } else if (cmp > 0) { + if (ifound == 0) { + /* + * Continue until we store the + * current chunk, but don't delete + * any more entries. + */ + bulk_ret = DB_NOTFOUND; + moreStream = 0; + iSmallEnough = 0; + } else + ++chunk_count; + +#ifdef DIAGNOSTIC + pikey = ikey; +#endif + + /* Get the next input key */ + if (stream->next(stream, &ikey, NULL) == 0) { + moreStream = 0; + iSmallEnough = 0; + } + ifound = 0; + +#ifdef DIAGNOSTIC + /* Check that the stream is sorted */ + DB_ASSERT(env, moreStream == 0 || + __db_compare_both(dbp, &ikey, NULL, + &pikey, NULL) >= 0); +#endif + + /* Check that !nextExists || ikey <= nextk */ + if (moreStream != 0 && nextExists != 0 && + __db_compare_both(dbp, + &ikey, NULL, &nextk, NULL) > 0) + iSmallEnough = 0; + } else /* cmp == 0 */ + ifound = 1; + + if (cmp <= 0) { + ret = __bamc_next_decompress(dbc); + if (ret == DB_NOTFOUND) { + moreCompressed = 0; + ret = 0; + } else if (ret != 0) + goto end; + } + } + + if (prevDestKey != NULL) { + /* + * Do the DBC->put() with a duplicate cursor, so that + * the main cursor's position isn't changed - we might + * need it to be the same in order to use DB_CURRENT + * above. + */ + if ((ret = __dbc_dup(dbc, &dbc_n, 0)) != 0) + goto end; + F_SET(dbc_n, DBC_TRANSIENT); + + ret = __dbc_iput(dbc_n, &destkey, &destbuf, DB_KEYLAST); + + if ((ret_n = __dbc_close(dbc_n)) != 0 && ret == 0) + ret = ret_n; + + if (ret != 0) + goto end; + + if (countp) + *countp += chunk_count; + chunk_count = 0; + + prevDestKey = NULL; + prevDestData = NULL; + destbuf.size = 0; + } + } + + end: + CMP_FREE_DBT(env, &destkey); + CMP_FREE_DBT(env, &destbuf); + CMP_FREE_DBT(env, &pdestkey); + CMP_FREE_DBT(env, &pdestdata); + CMP_FREE_DBT(env, &nextk); + + return (ret != 0 ? ret : bulk_ret); +} + +/******************************************************************************/ + +/* Implements DB_PREV and DB_LAST for __bamc_compress_get() */ +static int +__bamc_compress_get_prev(dbc, flags) + DBC *dbc; + u_int32_t flags; +{ + int ret; + u_int32_t tofind; + BTREE_CURSOR *cp; + + ret = 0; + cp = (BTREE_CURSOR *)dbc->internal; + + F_CLR(cp, C_COMPRESS_DELETED); + + if (cp->prevKey != NULL) { + /* Return the stored previous key */ + cp->currentKey = cp->prevKey; + cp->currentData = cp->prevData; + cp->compcursor = cp->prevcursor; + cp->prevKey = 0; + cp->prevData = 0; + cp->prevcursor = cp->prev2cursor; + cp->prev2cursor = 0; + } else { + if (cp->currentKey == NULL) { + /* No current key, so fetch the last key */ + flags |= DB_LAST; + tofind = (u_int32_t)-1; + } else if (cp->prevcursor == 0) { + /* + * The current key is at the beginning of the + * compressed block, so get the last key from the + * previous block + */ + flags |= DB_PREV; + tofind = (u_int32_t)-1; + } else { + /* + * We have to search for the previous key in the + * current block + */ + flags |= DB_CURRENT; + tofind = (u_int32_t) + (cp->prevcursor - (u_int8_t*)cp->compressed.data); + } + + CMP_IGET_RETRY(ret, dbc, &cp->key1, &cp->compressed, flags); + if (ret != 0) + return (ret); + + /* Decompress until we reach tofind */ + ret = __bamc_start_decompress(dbc); + while (ret == 0 && tofind > (u_int32_t) + (cp->compcursor - (u_int8_t*)cp->compressed.data)) { + ret = __bamc_next_decompress(dbc); + } + + if (ret == DB_NOTFOUND) + ret = 0; + } + + return (ret); +} + +/* Implements DB_PREV_DUP for __bamc_compress_get() */ +static int +__bamc_compress_get_prev_dup(dbc, flags) + DBC *dbc; + u_int32_t flags; +{ + int ret; + BTREE_CURSOR *cp; + DB *dbp; + BTREE *t; + + ret = 0; + cp = (BTREE_CURSOR *)dbc->internal; + dbp = dbc->dbp; + t = (BTREE *)dbp->bt_internal; + + if (cp->currentKey == 0) + return (EINVAL); + + /* If this is a deleted entry, del_key is already set, otherwise we + have to set it now */ + if (!F_ISSET(cp, C_COMPRESS_DELETED)) { + if ((ret = __bam_compress_set_dbt(dbp, &cp->del_key, + cp->currentKey->data, cp->currentKey->size)) != 0) + return (ret); + } + + if ((ret = __bamc_compress_get_prev(dbc, flags)) != 0) + return (ret); + + if (t->bt_compare(dbp, cp->currentKey, &cp->del_key) != 0) + return (DB_NOTFOUND); + + return (0); +} + +/* Implements DB_PREV_NODUP for __bamc_compress_get() */ +static int +__bamc_compress_get_prev_nodup(dbc, flags) + DBC *dbc; + u_int32_t flags; +{ + int ret; + BTREE_CURSOR *cp; + DB *dbp; + BTREE *t; + + cp = (BTREE_CURSOR *)dbc->internal; + dbp = dbc->dbp; + t = (BTREE *)dbp->bt_internal; + + if (cp->currentKey == 0) + return (__bamc_compress_get_prev(dbc, flags)); + + /* + * If this is a deleted entry, del_key is already set, otherwise we + * have to set it now. + */ + if (!F_ISSET(cp, C_COMPRESS_DELETED)) + if ((ret = __bam_compress_set_dbt(dbp, &cp->del_key, + cp->currentKey->data, cp->currentKey->size)) != 0) + return (ret); + + /* + * Linear search for the next non-duplicate key - this is + * especially inefficient for DB_PREV_NODUP, since we have to + * decompress from the beginning of the chunk to find previous + * key/data pairs. Instead we could check for key equality as we + * decompress. + */ + do + if ((ret = __bamc_compress_get_prev(dbc, flags)) != 0) + return (ret); + while (t->bt_compare(dbp, cp->currentKey, &cp->del_key) == 0); + + return (0); +} + +/* Implements DB_NEXT and DB_FIRST for __bamc_compress_get() */ +static int +__bamc_compress_get_next(dbc, flags) + DBC *dbc; + u_int32_t flags; +{ + int ret; + BTREE_CURSOR *cp; + + cp = (BTREE_CURSOR *)dbc->internal; + + if (F_ISSET(cp, C_COMPRESS_DELETED)) { + if (cp->currentKey == 0) + return (DB_NOTFOUND); + F_CLR(cp, C_COMPRESS_DELETED); + return (0); + } else if (cp->currentKey) { + ret = __bamc_next_decompress(dbc); + if (ret != DB_NOTFOUND) + return (ret); + + flags |= DB_NEXT; + } else + flags |= DB_FIRST; + + CMP_IGET_RETRY(ret, dbc, &cp->key1, &cp->compressed, flags); + if (ret == DB_NOTFOUND) { + /* + * Reset the cursor, so that + * __bamc_compress_get_multiple_key will end up pointing + * to the right place + */ + __bamc_compress_reset(dbc); + return (DB_NOTFOUND); + } else if (ret != 0) + return (ret); + + ret = __bamc_start_decompress(dbc); + + return (ret); +} + +/* Implements DB_NEXT_DUP for __bamc_compress_get() */ +static int +__bamc_compress_get_next_dup(dbc, key, flags) + DBC *dbc; + DBT *key; + u_int32_t flags; +{ + int ret; + BTREE_CURSOR *cp; + DB *dbp; + BTREE *t; + + cp = (BTREE_CURSOR *)dbc->internal; + dbp = dbc->dbp; + t = (BTREE *)dbp->bt_internal; + + if (F_ISSET(cp, C_COMPRESS_DELETED)) { + /* + * Check that the next entry has the same key as the + * deleted entry. + */ + if (cp->currentKey == 0) + return (DB_NOTFOUND); + F_CLR(cp, C_COMPRESS_DELETED); + return (t->bt_compare(dbp, + cp->currentKey, &cp->del_key) == 0 ? 0 : DB_NOTFOUND); + } else if (cp->currentKey == 0) + return (EINVAL); + + /* Check that the next entry has the same key as the previous entry */ + ret = __bamc_next_decompress(dbc); + if (ret == 0 && t->bt_compare(dbp, cp->currentKey, cp->prevKey) != 0) + return (DB_NOTFOUND); + if (ret != DB_NOTFOUND) + return (ret); + + if (key == NULL) { + /* Copy the current key to del_key */ + if ((ret = __bam_compress_set_dbt(dbp, &cp->del_key, + cp->currentKey->data, cp->currentKey->size)) != 0) + return (ret); + key = &cp->del_key; + } + + /* Fetch the next chunk */ + CMP_IGET_RETRY(ret, dbc, &cp->key1, &cp->compressed, DB_NEXT | flags); + if (ret == DB_NOTFOUND) { + /* + * Reset the cursor, so that __bamc_compress_get_multiple + * will end up pointing to the right place + */ + __bamc_compress_reset(dbc); + return (DB_NOTFOUND); + } else if (ret != 0) + return (ret); + + if ((ret = __bamc_start_decompress(dbc)) != 0) + return (ret); + + /* Check the keys are the same */ + if (t->bt_compare(dbp, cp->currentKey, key) != 0) + return (DB_NOTFOUND); + + return (0); +} + +/* Implements DB_NEXT_NODUP for __bamc_compress_get() */ +static int +__bamc_compress_get_next_nodup(dbc, flags) + DBC *dbc; + u_int32_t flags; +{ + int ret; + BTREE_CURSOR *cp; + DB *dbp; + BTREE *t; + + cp = (BTREE_CURSOR *)dbc->internal; + dbp = dbc->dbp; + t = (BTREE *)dbp->bt_internal; + + if (cp->currentKey == 0) + return (__bamc_compress_get_next(dbc, flags)); + + /* + * If this is a deleted entry, del_key is already set, otherwise + * we have to set it now + */ + if (!F_ISSET(cp, C_COMPRESS_DELETED)) + if ((ret = __bam_compress_set_dbt(dbp, &cp->del_key, + cp->currentKey->data, cp->currentKey->size)) != 0) + return (ret); + + /* Linear search for the next non-duplicate key */ + do + if ((ret = __bamc_compress_get_next(dbc, flags)) != 0) + return (ret); + while (t->bt_compare(dbp, cp->currentKey, &cp->del_key) == 0); + + return (ret); +} + +/* + * Implements DB_SET, DB_SET_RANGE, DB_GET_BOTH, and DB_GET_BOTH_RANGE + * for __bamc_compress_get() + */ +static int +__bamc_compress_get_set(dbc, key, data, method, flags) + DBC *dbc; + DBT *key; + DBT *data; + u_int32_t method; + u_int32_t flags; +{ + int ret, cmp; + BTREE_CURSOR *cp; + DB *dbp; + + cp = (BTREE_CURSOR *)dbc->internal; + dbp = dbc->dbp; + + if (method == DB_SET || method == DB_SET_RANGE) + data = NULL; + + F_CLR(cp, C_COMPRESS_DELETED); + + ret = __bamc_compress_seek(dbc, key, data, flags); + if (ret == DB_NOTFOUND) + CMP_IGET_RETRY(ret, dbc, + &cp->key1, &cp->compressed, DB_FIRST | flags); + if (ret != 0) + return (ret); + + /* Decompress and perform a linear search for the key */ + cmp = 0; + ret = __bamc_start_decompress(dbc); + while (ret == 0 && (cmp = __db_compare_both(dbp, + cp->currentKey, cp->currentData, key, data)) < 0) { + ret = __bamc_next_decompress(dbc); + if (ret == DB_NOTFOUND) { + CMP_IGET_RETRY(ret, dbc, + &cp->key1, &cp->compressed, DB_NEXT | flags); + if (ret == 0) + ret = __bamc_start_decompress(dbc); + } + } + + switch (method) { + case DB_SET: + case DB_GET_BOTH_RANGE: + /* + * We need to exactly match the key, and if cmp != 0 we + * might not have - so check again here. + */ + if (ret == 0 && + __db_compare_both(dbp, cp->currentKey, 0, key, 0) != 0) { + /* We didn't find the key */ + ret = DB_NOTFOUND; + } + break; + case DB_GET_BOTH: + if (ret == 0 && (cmp != 0 || (!F_ISSET(dbp, DB_AM_DUPSORT) && + __bam_defcmp(dbp, cp->currentData, data) != 0))) { + /* We didn't find the key/data pair */ + ret = DB_NOTFOUND; + } + break; + default: + DB_ASSERT(dbp->env, method == 0 || method == DB_SET_RANGE); + } + + return (ret); +} + +/* Implements DB_GET_BOTHC for __bamc_compress_get() */ +static int +__bamc_compress_get_bothc(dbc, data, flags) + DBC *dbc; + DBT *data; + u_int32_t flags; +{ + int ret, cmp; + BTREE_CURSOR *cp; + DB *dbp; + + cp = (BTREE_CURSOR *)dbc->internal; + dbp = dbc->dbp; + + /* Check that the data we are looking for comes after the current + position */ + if (__db_compare_both(dbp, cp->currentKey, + cp->currentData, cp->currentKey, data) >= 0) + return (DB_NOTFOUND); + + cmp = 0; + /* Perform a linear search for the data in the current chunk */ + while ((ret = __bamc_next_decompress(dbc)) == 0 && + (cmp = __db_compare_both( + dbp, cp->currentKey, cp->currentData, cp->prevKey, data)) < 0) + continue; + + if (ret == 0) + return (cmp == 0 ? 0 : DB_NOTFOUND); + if (ret != DB_NOTFOUND) + return (ret); + + /* Copy the current key to del_key */ + if ((ret = __bam_compress_set_dbt(dbp, &cp->del_key, + cp->currentKey->data, cp->currentKey->size)) != 0) + return (ret); + + /* Search for the data using DB_GET_BOTH */ + return __bamc_compress_get_set( + dbc, &cp->del_key, data, DB_GET_BOTH, flags); +} + +/* Implements DB_MULTIPLE_KEY for __bamc_compress_get() */ +static int +__bamc_compress_get_multiple_key(dbc, data, flags) + DBC *dbc; + DBT *data; + u_int32_t flags; +{ + int ret; + u_int8_t *writekey, *writedata; + void *mptr; + BTREE_CURSOR *cp; + + ret = 0; + cp = (BTREE_CURSOR *)dbc->internal; + + DB_MULTIPLE_WRITE_INIT(mptr, data); + DB_MULTIPLE_KEY_RESERVE_NEXT(mptr, data, writekey, cp->currentKey->size, + writedata, cp->currentData->size); + if (writekey == NULL) { + data->size = cp->currentKey->size + cp->currentData->size + + 4 * sizeof(u_int32_t); + return DB_BUFFER_SMALL; + } + DB_ASSERT(dbc->dbp->env, writedata != NULL); + + memcpy(writekey, cp->currentKey->data, cp->currentKey->size); + memcpy(writedata, cp->currentData->data, cp->currentData->size); + + while ((ret = __bamc_compress_get_next(dbc, flags)) == 0) { + DB_MULTIPLE_KEY_RESERVE_NEXT(mptr, data, writekey, + cp->currentKey->size, writedata, cp->currentData->size); + if (writekey == NULL) + break; + DB_ASSERT(dbc->dbp->env, writedata != NULL); + + /* + * We could choose to optimize this by just storing one + * copy of a key for each set of duplicate data. + */ + memcpy(writekey, cp->currentKey->data, cp->currentKey->size); + memcpy(writedata, cp->currentData->data, cp->currentData->size); + } + + if (ret == DB_NOTFOUND) + ret = 0; + + if (ret == 0) + /* + * Rewind to the previous key/data, since we can't fit + * this one in the buffer + */ + ret = __bamc_compress_get_prev(dbc, flags); + + return (ret); +} + +/* Implements DB_MULTIPLE for __bamc_compress_get() */ +static int +__bamc_compress_get_multiple(dbc, key, data, flags) + DBC *dbc; + DBT *key, *data; + u_int32_t flags; +{ + int ret; + u_int8_t *writedata; + void *mptr; + BTREE_CURSOR *cp; + + ret = 0; + cp = (BTREE_CURSOR *)dbc->internal; + + data->size = 0; + + DB_MULTIPLE_WRITE_INIT(mptr, data); + DB_MULTIPLE_RESERVE_NEXT(mptr, data, writedata, cp->currentData->size); + data->size += cp->currentData->size + 2 * sizeof(u_int32_t); + if (writedata == NULL) + return DB_BUFFER_SMALL; + + memcpy(writedata, cp->currentData->data, cp->currentData->size); + + while ((ret = __bamc_compress_get_next_dup(dbc, key, flags)) == 0) { + DB_MULTIPLE_RESERVE_NEXT( + mptr, data, writedata, cp->currentData->size); + data->size += cp->currentData->size + 2 * sizeof(u_int32_t); + if (writedata == NULL) { + /* DBC_FROM_DB_GET indicates we need to fit all the + * duplicates into the buffer or return DB_BUFFER_SMALL. + * [#17039] + */ + if (F_ISSET(dbc, DBC_FROM_DB_GET)) + return DB_BUFFER_SMALL; + break; + } + + memcpy(writedata, cp->currentData->data, cp->currentData->size); + } + + if (ret == DB_NOTFOUND) + ret = 0; + + if (ret == 0) + /* + * Rewind to the previous key/data, as that's now our current + * entry. + */ + ret = __bamc_compress_get_prev(dbc, flags); + + return (ret); +} + +/* + * __bamc_compress_iget -- + * Get using a compressed cursor. (internal) + */ +static int +__bamc_compress_iget(dbc, key, data, flags) + DBC *dbc; + DBT *key, *data; + u_int32_t flags; +{ + int ret; + u_int32_t multiple, method; + BTREE_CURSOR *cp; + DB *dbp; + + cp = (BTREE_CURSOR *)dbc->internal; + dbp = dbc->dbp; + ret = 0; + + multiple = flags & (DB_MULTIPLE|DB_MULTIPLE_KEY); + method = flags & DB_OPFLAGS_MASK; + flags = flags & ~(DB_OPFLAGS_MASK|DB_MULTIPLE|DB_MULTIPLE_KEY); + + switch (method) { + case DB_CURRENT: + if (F_ISSET(cp, C_COMPRESS_DELETED)) + ret = DB_KEYEMPTY; + else if (cp->currentKey == NULL) + ret = EINVAL; + break; + case DB_FIRST: + __bamc_compress_reset(dbc); + ret = __bamc_compress_get_next(dbc, flags); + break; + case DB_NEXT: + ret = __bamc_compress_get_next(dbc, flags); + break; + case DB_NEXT_DUP: + ret = __bamc_compress_get_next_dup(dbc, 0, flags); + break; + case DB_NEXT_NODUP: + ret = __bamc_compress_get_next_nodup(dbc, flags); + break; + case DB_LAST: + __bamc_compress_reset(dbc); + ret = __bamc_compress_get_prev(dbc, flags); + break; + case DB_PREV: + ret = __bamc_compress_get_prev(dbc, flags); + break; + case DB_PREV_DUP: + ret = __bamc_compress_get_prev_dup(dbc, flags); + break; + case DB_PREV_NODUP: + ret = __bamc_compress_get_prev_nodup(dbc, flags); + break; + case DB_SET: + if (((BTREE *) + dbc->dbp->bt_internal)->bt_compare == __bam_defcmp) + F_SET(key, DB_DBT_ISSET); + /* FALL THROUGH */ + case DB_SET_RANGE: + ret = __bamc_compress_get_set(dbc, key, 0, method, flags); + break; + case DB_GET_BOTH: + if (!F_ISSET(dbc->dbp, DB_AM_DUPSORT) || ((BTREE *)dbc->dbp-> + bt_internal)->compress_dup_compare == __bam_defcmp) + F_SET(data, DB_DBT_ISSET); + /* FALL THROUGH */ + case DB_GET_BOTH_RANGE: + if (((BTREE *) + dbc->dbp->bt_internal)->bt_compare == __bam_defcmp) + F_SET(key, DB_DBT_ISSET); + ret = __bamc_compress_get_set(dbc, key, data, method, flags); + break; + case DB_GET_BOTHC: + ret = __bamc_compress_get_bothc(dbc, data, flags); + break; + default: + ret = __db_unknown_flag(dbp->env, "__bamc_compress_iget", + method); + break; + } + + if (ret != 0) + goto err; + + switch (multiple) { + case 0: + if (!F_ISSET(key, DB_DBT_ISSET)) + ret = __db_retcopy(dbc->env, key, + cp->currentKey->data, cp->currentKey->size, + &dbc->rkey->data, &dbc->rkey->ulen); + if (!F_ISSET(data, DB_DBT_ISSET) && ret == 0) + ret = __db_retcopy(dbc->env, data, + cp->currentData->data, cp->currentData->size, + &dbc->rdata->data, &dbc->rdata->ulen); + break; + case DB_MULTIPLE: + if (!F_ISSET(key, DB_DBT_ISSET)) + ret = __db_retcopy(dbc->env, key, + cp->currentKey->data, cp->currentKey->size, + &dbc->rkey->data, &dbc->rkey->ulen); + if (ret == 0) + ret = + __bamc_compress_get_multiple(dbc, key, data, flags); + break; + case DB_MULTIPLE_KEY: + ret = __bamc_compress_get_multiple_key(dbc, data, flags); + break; + default: + ret = __db_unknown_flag(dbp->env, "__bamc_compress_iget", + multiple); + break; + } + + err: + F_CLR(key, DB_DBT_ISSET); + F_CLR(data, DB_DBT_ISSET); + + return (ret); +} + +/* + * __bamc_compress_get -- + * Get using a compressed cursor. + * + * PUBLIC: int __bamc_compress_get __P((DBC *, DBT *, DBT *, u_int32_t)); + */ +int +__bamc_compress_get(dbc, key, data, flags) + DBC *dbc; + DBT *key, *data; + u_int32_t flags; +{ + DBC *dbc_n; + int ret, t_ret; + u_int32_t tmp_flags; + + switch (flags & DB_OPFLAGS_MASK) { + case DB_CURRENT: + case DB_GET_BOTHC: + case DB_NEXT: + case DB_NEXT_DUP: + case DB_NEXT_NODUP: + case DB_PREV: + case DB_PREV_DUP: + case DB_PREV_NODUP: + if (F_ISSET((BTREE_CURSOR *)dbc->internal, + C_COMPRESS_MODIFIED) && + (ret = __bamc_compress_relocate(dbc)) != 0) + return (ret); + tmp_flags = DB_POSITION; + break; + default: + F_CLR((BTREE_CURSOR *)dbc->internal, C_COMPRESS_MODIFIED); + tmp_flags = 0; + break; + } + + if (F_ISSET(dbc, DBC_TRANSIENT)) + dbc_n = dbc; + else { + if ((ret = __dbc_dup(dbc, &dbc_n, tmp_flags)) != 0) + goto err; + + /* + * We don't care about preserving the cursor's position on + * error. + */ + F_SET(dbc_n, DBC_TRANSIENT); + + COPY_RET_MEM(dbc, dbc_n); + } + + if ((ret = __bamc_compress_iget(dbc_n, key, data, flags)) != 0) + goto err; + +err: + /* Cleanup and cursor resolution. */ + if ((t_ret = __dbc_cleanup(dbc, dbc_n, ret)) != 0 && + (ret == 0 || ret == DB_BUFFER_SMALL)) + ret = t_ret; + return (ret); +} + +/* + * __bamc_compress_iput -- + * Put using a compressed cursor (internal) + */ +static int +__bamc_compress_iput(dbc, key, data, flags) + DBC *dbc; + DBT *key, *data; + u_int32_t flags; +{ + int ret; + u_int32_t multi; + DBT kcpy, pdata, empty; + BTREE_COMPRESS_STREAM stream; + BTREE_CURSOR *cp; + DB *dbp; + ENV *env; + + cp = (BTREE_CURSOR *)dbc->internal; + dbp = dbc->dbp; + env = dbc->env; + + memset(&pdata, 0, sizeof(DBT)); + memset(&empty, 0, sizeof(DBT)); + + multi = LF_ISSET(DB_MULTIPLE|DB_MULTIPLE_KEY); + LF_CLR(DB_MULTIPLE|DB_MULTIPLE_KEY); + if (flags == 0) + flags = DB_KEYLAST; + + switch (flags) { + case DB_CURRENT: + if (cp->currentKey == 0 || F_ISSET(cp, C_COMPRESS_DELETED)) { + ret = DB_NOTFOUND; + goto end; + } + + if (F_ISSET(data, DB_DBT_PARTIAL)) { + if ((ret = __db_buildpartial( + dbp, cp->currentData, data, &pdata)) != 0) + goto end; + data = &pdata; + } + + if (F_ISSET(dbp, DB_AM_DUPSORT) && + ((BTREE *)dbp->bt_internal)->compress_dup_compare( + dbp, cp->currentData, data) != 0) { + __db_errx(env, DB_STR("1032", + "Existing data sorts differently from put data")); + ret = EINVAL; + goto end; + } + CMP_INIT_DBT(&kcpy); + if ((ret = __bam_compress_set_dbt(dbp, + &kcpy, cp->currentKey->data, cp->currentKey->size)) != 0) + goto end; + + __bam_cs_create_single(&stream, &kcpy, data); + ret = __bamc_compress_merge_insert(dbc, &stream, NULL, flags); + + if (ret == 0) + /* Position the cursor on the entry written */ + ret = __bamc_compress_get_set( + dbc, &kcpy, data, DB_GET_BOTH_RANGE, 0); + + CMP_FREE_DBT(env, &kcpy); + break; + case DB_KEYFIRST: + case DB_KEYLAST: + case DB_NODUPDATA: + case DB_OVERWRITE_DUP: + switch (multi) { + case 0: + if (F_ISSET(data, DB_DBT_PARTIAL)) { + if ((ret = __bamc_compress_get_set(dbc, key, + data, DB_SET, 0)) != 0 && + ret != DB_NOTFOUND) + goto end; + if ((ret = __db_buildpartial(dbp, + ret == DB_NOTFOUND ? &empty : + cp->currentData, data, &pdata)) != 0) + goto end; + data = &pdata; + } + + __bam_cs_create_single(&stream, key, data); + ret = __bamc_compress_merge_insert( + dbc, &stream, NULL, flags); + + if (ret == 0) + /* Position the cursor on the entry written */ + ret = __bamc_compress_get_set( + dbc, key, data, DB_GET_BOTH_RANGE, 0); + break; + case DB_MULTIPLE: + if ((ret = __bam_compress_check_sort_multiple(dbp, + key, data)) != 0) + goto end; + __bam_cs_create_multiple(&stream, key, data); + ret = __bamc_compress_merge_insert( + dbc, &stream, &key->doff, flags); + break; + case DB_MULTIPLE_KEY: + if ((ret = __bam_compress_check_sort_multiple_key(dbp, + key)) != 0) + goto end; + __bam_cs_create_multiple_key(&stream, key); + ret = __bamc_compress_merge_insert( + dbc, &stream, &key->doff, flags); + break; + default: + return (__db_unknown_flag( + dbp->env, "__bamc_compress_iput", multi)); + } + break; + case DB_NOOVERWRITE: + /* Check key doesn't already exist */ + ret = __bamc_compress_get_set(dbc, key, 0, DB_SET, 0); + if (ret != DB_NOTFOUND) { + if (ret == 0) + ret = DB_KEYEXIST; + goto end; + } + + if (F_ISSET(data, DB_DBT_PARTIAL)) { + if ((ret = __db_buildpartial( + dbp, &empty, data, &pdata)) != 0) + goto end; + data = &pdata; + } + + __bam_cs_create_single(&stream, key, data); + ret = __bamc_compress_merge_insert(dbc, &stream, NULL, flags); + + if (ret == 0) + /* Position the cursor on the entry written */ + ret = __bamc_compress_get_set( + dbc, key, data, DB_GET_BOTH_RANGE, 0); + break; + default: + return (__db_unknown_flag( + dbp->env, "__bamc_compress_iput", flags)); + } + + end: + if (pdata.data != NULL) + __os_free(env, pdata.data); + return (ret); +} + +/* + * __bamc_compress_put -- + * Put using a compressed cursor. + * + * PUBLIC: int __bamc_compress_put __P((DBC *, DBT *, DBT *, u_int32_t)); + */ +int +__bamc_compress_put(dbc, key, data, flags) + DBC *dbc; + DBT *key, *data; + u_int32_t flags; +{ + DBC *dbc_n; + int ret, t_ret; + + if (F_ISSET((BTREE_CURSOR *)dbc->internal, C_COMPRESS_MODIFIED)) { + if ((flags & DB_OPFLAGS_MASK) == DB_CURRENT && + (ret = __bamc_compress_relocate(dbc)) != 0) + return (ret); + F_CLR((BTREE_CURSOR *)dbc->internal, C_COMPRESS_MODIFIED); + } + + if (F_ISSET(dbc, DBC_TRANSIENT)) + dbc_n = dbc; + else { + if ((ret = __dbc_dup(dbc, &dbc_n, + (flags & DB_OPFLAGS_MASK) == DB_CURRENT ? + DB_POSITION : 0)) != 0) + goto err; + + /* + * We don't care about preserving the cursor's position on + * error. + */ + F_SET(dbc_n, DBC_TRANSIENT); + } + + if ((ret = __bamc_compress_iput(dbc_n, key, data, flags)) != 0) + goto err; + +err: + /* Cleanup and cursor resolution. */ + if ((t_ret = __dbc_cleanup(dbc, dbc_n, ret)) != 0 && + (ret == 0 || ret == DB_BUFFER_SMALL)) + ret = t_ret; + return (ret); +} + +/* + * __bamc_compress_idel -- + * Del using a compressed cursor. (internal) + */ +static int +__bamc_compress_idel(dbc, flags) + DBC *dbc; + u_int32_t flags; +{ + int ret; + BTREE_COMPRESS_STREAM stream; + DB *dbp; + BTREE_CURSOR *cp; + + COMPQUIET(flags, 0); + + dbp = dbc->dbp; + cp = (BTREE_CURSOR *)dbc->internal; + + if (F_ISSET(cp, C_COMPRESS_DELETED)) + return DB_KEYEMPTY; + if (cp->currentKey == 0) + return DB_NOTFOUND; + + if ((ret = __bam_compress_set_dbt(dbp, &cp->del_key, + cp->currentKey->data, cp->currentKey->size)) != 0) + goto err; + if ((ret = __bam_compress_set_dbt(dbp, &cp->del_data, + cp->currentData->data, cp->currentData->size)) != 0) + goto err; + + __bam_cs_create_single(&stream, &cp->del_key, &cp->del_data); + if ((ret = __bamc_compress_merge_delete(dbc, &stream, NULL)) != 0) + goto err; + + /* Position the cursor on the entry after the key/data deleted */ + ret = __bamc_compress_get_set(dbc, &cp->del_key, &cp->del_data, 0, 0); + if (ret == DB_NOTFOUND) { + __bamc_compress_reset(dbc); + ret = 0; + } else if (ret != 0) + goto err; + + /* Mark current as being deleted */ + F_SET(cp, C_COMPRESS_DELETED); + + err: + return (ret); +} + +/* + * __bamc_compress_del -- + * Del using a compressed cursor. + * + * PUBLIC: int __bamc_compress_del __P((DBC *, u_int32_t)); + */ +int +__bamc_compress_del(dbc, flags) + DBC *dbc; + u_int32_t flags; +{ + int ret, t_ret; + DBC *dbc_n; + + if (F_ISSET((BTREE_CURSOR *)dbc->internal, C_COMPRESS_MODIFIED) && + (ret = __bamc_compress_relocate(dbc)) != 0) + return (ret); + + if (F_ISSET(dbc, DBC_TRANSIENT)) + dbc_n = dbc; + else { + if ((ret = __dbc_dup(dbc, &dbc_n, DB_POSITION)) != 0) + goto err; + + /* + * We don't care about preserving the cursor's position on + * error. + */ + F_SET(dbc_n, DBC_TRANSIENT); + + COPY_RET_MEM(dbc, dbc_n); + } + + if ((ret = __bamc_compress_idel(dbc_n, flags)) != 0) + goto err; + +err: + /* Cleanup and cursor resolution. */ + if ((t_ret = __dbc_cleanup(dbc, dbc_n, ret)) != 0 && + (ret == 0 || ret == DB_BUFFER_SMALL)) + ret = t_ret; + return (ret); +} + +/* + * __bamc_compress_ibulk_del -- + * Bulk del using a compressed cursor. (internal) + */ +static int +__bamc_compress_ibulk_del(dbc, key, flags) + DBC *dbc; + DBT *key; + u_int32_t flags; +{ + int ret; + BTREE_COMPRESS_STREAM stream; + + switch (flags) { + case 0: + __bam_cs_create_single_keyonly(&stream, key); + return (__bamc_compress_merge_delete_dups(dbc, &stream, NULL)); + case DB_MULTIPLE: + if ((ret = __bam_compress_check_sort_multiple_keyonly( + dbc->dbp, key)) != 0) + return (ret); + __bam_cs_create_multiple_keyonly(&stream, key); + return (__bamc_compress_merge_delete_dups( + dbc, &stream, &key->doff)); + case DB_MULTIPLE_KEY: + if ((ret = __bam_compress_check_sort_multiple_key( + dbc->dbp, key)) != 0) + return (ret); + __bam_cs_create_multiple_key(&stream, key); + return (__bamc_compress_merge_delete(dbc, &stream, &key->doff)); + default: + break; + } + + return (__db_unknown_flag( + dbc->env, "__bamc_compress_ibulk_del", flags)); +} + +/* + * __bamc_compress_bulk_del -- + * Bulk del using a compressed cursor. + * + * PUBLIC: int __bamc_compress_bulk_del __P((DBC *, DBT *, u_int32_t)); + */ +int +__bamc_compress_bulk_del(dbc, key, flags) + DBC *dbc; + DBT *key; + u_int32_t flags; +{ + int ret, t_ret; + DBC *dbc_n; + + F_CLR((BTREE_CURSOR *)dbc->internal, C_COMPRESS_MODIFIED); + + if (F_ISSET(dbc, DBC_TRANSIENT)) + dbc_n = dbc; + else { + if ((ret = __dbc_dup(dbc, &dbc_n, 0)) != 0) + goto err; + + /* + * We don't care about preserving the cursor's position on + * error. + */ + F_SET(dbc_n, DBC_TRANSIENT); + } + + if ((ret = __bamc_compress_ibulk_del(dbc_n, key, flags)) != 0) + goto err; + +err: + /* Cleanup and cursor resolution. */ + if ((t_ret = __dbc_cleanup(dbc, dbc_n, ret)) != 0 && + (ret == 0 || ret == DB_BUFFER_SMALL)) + ret = t_ret; + return (ret); +} + +/* + * __bamc_compress_count -- + * Count using a compressed cursor. + * + * PUBLIC: int __bamc_compress_count __P((DBC *, db_recno_t *)); + */ +int +__bamc_compress_count(dbc, countp) + DBC *dbc; + db_recno_t *countp; +{ + int ret, t_ret; + db_recno_t count; + DBT *key; + DBC *dbc_n; + BTREE_CURSOR *cp; + + cp = (BTREE_CURSOR *)dbc->internal; + + /* + * If the current entry is deleted use del_key, otherwise use + * currentKey. + */ + if (F_ISSET(cp, C_COMPRESS_DELETED)) + key = &cp->del_key; + else + key = cp->currentKey; + + /* Duplicate the cursor */ + if ((ret = __dbc_dup(dbc, &dbc_n, 0)) != 0) + return (ret); + + /* We don't care about preserving the cursor's position on error */ + F_SET(dbc_n, DBC_TRANSIENT); + + /* Find the first duplicate */ + if ((ret = __bamc_compress_get_set(dbc_n, key, 0, DB_SET, 0)) != 0) + goto err; + count = 1; + + /* Count subsequent duplicates */ + while ((ret = __bamc_compress_get_next_dup(dbc_n, key, 0)) == 0) + ++count; + + if (ret == DB_NOTFOUND) + ret = 0; + else if (ret != 0) + goto err; + + *countp = count; + + err: + if ((t_ret = __dbc_close(dbc_n)) != 0 && ret == 0) + ret = t_ret; + + return (ret); +} + +/* + * __bamc_compress_cmp -- + * Compare which compressed value is pointed to. + * + * PUBLIC: int __bamc_compress_cmp __P((DBC *, DBC *, int *)); + */ +int +__bamc_compress_cmp(dbc, other_dbc, result) + DBC *dbc, *other_dbc; + int *result; +{ + DB *dbp; + BTREE_CURSOR *cp, *ocp; + + /* + * At this point, we already know that the cursors point to the same + * DB. + */ + + dbp = dbc->dbp; + cp = (BTREE_CURSOR *)dbc->internal; + ocp = (BTREE_CURSOR *)other_dbc->internal; + + if (F_ISSET(cp, C_COMPRESS_DELETED)) + if (F_ISSET(ocp, C_COMPRESS_DELETED)) + *result = __db_compare_both( + dbp, &cp->del_key, &cp->del_data, + &ocp->del_key, &ocp->del_data) == 0 ? 0 : 1; + else { + if (ocp->currentKey == 0) + goto err; + + *result = __db_compare_both( + dbp, &cp->del_key, &cp->del_data, + ocp->currentKey, ocp->currentData) == 0 ? 0 : 1; + } + else { + if (cp->currentKey == 0) + goto err; + + if (F_ISSET(ocp, C_COMPRESS_DELETED)) + *result = __db_compare_both( + dbp, cp->currentKey, cp->currentData, + &ocp->del_key, &ocp->del_data) == 0 ? 0 : 1; + else { + if (ocp->currentKey == 0) + goto err; + + *result = __db_compare_both( + dbp, cp->currentKey, cp->currentData, + ocp->currentKey, ocp->currentData) == 0 ? 0 : 1; + } + } + return (0); + + err: + __db_errx(dbc->env, DB_STR("1033", + "Both cursors must be initialized before calling DBC->cmp.")); + return (EINVAL); +} + +/* + * __bamc_compress_dup -- + * Duplicate the compression specific part of a btree cursor. + * + * PUBLIC: int __bamc_compress_dup __P((DBC *, DBC *, u_int32_t)); + */ +int +__bamc_compress_dup(orig_dbc, new_dbc, flags) + DBC *orig_dbc, *new_dbc; + u_int32_t flags; +{ + int ret; + DB *dbp; + BTREE_CURSOR *orig, *new; + + dbp = new_dbc->dbp; + + orig = (BTREE_CURSOR *)orig_dbc->internal; + new = (BTREE_CURSOR *)new_dbc->internal; + + if (orig->currentKey != NULL && !LF_ISSET(DB_SHALLOW_DUP)) { + new->currentKey = &new->key1; + new->currentData = &new->data1; + + if ((ret = __bam_compress_set_dbt(dbp, new->currentKey, + orig->currentKey->data, orig->currentKey->size)) != 0) + return (ret); + if ((ret = __bam_compress_set_dbt(dbp, new->currentData, + orig->currentData->data, orig->currentData->size)) != 0) + return (ret); + + if (orig->prevKey) { + new->prevKey = &new->key2; + new->prevData = &new->data2; + + if ((ret = __bam_compress_set_dbt(dbp, new->prevKey, + orig->prevKey->data, orig->prevKey->size)) != 0) + return (ret); + if ((ret = __bam_compress_set_dbt(dbp, new->prevData, + orig->prevData->data, orig->prevData->size)) != 0) + return (ret); + } + + if ((ret = __bam_compress_set_dbt(dbp, &new->compressed, + orig->compressed.data, orig->compressed.size)) != 0) + return (ret); + + new->compcursor = (u_int8_t*)new->compressed.data + + (orig->compcursor - (u_int8_t*)orig->compressed.data); + new->compend = (u_int8_t*)new->compressed.data + + (orig->compend - (u_int8_t*)orig->compressed.data); + new->prevcursor = orig->prevcursor == NULL ? NULL : + (u_int8_t*)new->compressed.data + (orig->prevcursor - + (u_int8_t*)orig->compressed.data); + new->prev2cursor = orig->prev2cursor == NULL ? NULL : + (u_int8_t*)new->compressed.data + (orig->prev2cursor - + (u_int8_t*)orig->compressed.data); + + if (F_ISSET(orig, C_COMPRESS_DELETED)) { + if ((ret = __bam_compress_set_dbt(dbp, &new->del_key, + orig->del_key.data, orig->del_key.size)) != 0) + return (ret); + if ((ret = __bam_compress_set_dbt(dbp, &new->del_data, + orig->del_data.data, orig->del_data.size)) != 0) + return (ret); + } + } + + return (0); +} + +/* + * __bam_compress_salvage -- + * Salvage the compressed data from the key/data pair + * + * PUBLIC: int __bam_compress_salvage __P((DB *, VRFY_DBINFO *, + * PUBLIC: void *, int (*)(void *, const void *), DBT *, DBT *)); + */ +int +__bam_compress_salvage(dbp, vdp, handle, callback, key, data) + DB *dbp; + VRFY_DBINFO *vdp; + void *handle; + int (*callback) __P((void *, const void *)); + DBT *key, *data; +{ + DBT key1, key2, data1, data2, compressed; + DBT *currentKey, *currentData, *prevKey, *prevData; + ENV *env; + int ret, t_ret; + u_int8_t *compcursor, *compend; + u_int32_t datasize, size; + + env = dbp->env; + + memset(&key1, 0, sizeof(DBT)); + memset(&key2, 0, sizeof(DBT)); + memset(&data1, 0, sizeof(DBT)); + memset(&data2, 0, sizeof(DBT)); + memset(&compressed, 0, sizeof(DBT)); + + key1.flags = DB_DBT_USERMEM; + key2.flags = DB_DBT_USERMEM; + data1.flags = DB_DBT_USERMEM; + data2.flags = DB_DBT_USERMEM; + compressed.flags = DB_DBT_USERMEM; + + prevKey = NULL; + prevData = NULL; + currentKey = key; + currentData = &data2; + compcursor = (u_int8_t*)data->data; + compend = compcursor + data->size; + + if (data->size == 0) { + ret = DB_VERIFY_FATAL; + goto unknown_data; + } + + /* Unmarshal the first data */ + size = __db_decompress_count_int(compcursor); + if (size == 0xFF || compcursor + size > compend) { + ret = DB_VERIFY_FATAL; + goto unknown_data; + } + compcursor += __db_decompress_int32(compcursor, &datasize); + + if (compcursor + datasize > compend) { + ret = DB_VERIFY_FATAL; + goto unknown_data; + } + if ((ret = __bam_compress_set_dbt( + dbp, currentData, compcursor, datasize)) != 0) + goto err; + compcursor += datasize; + + /* Output first data (first key has already been output by our caller */ + if ((ret = __db_vrfy_prdbt( + currentData, 0, " ", handle, callback, 0, 0, vdp)) != 0) + goto err; + + while (compcursor < compend) { + prevKey = currentKey; + prevData = currentData; + + if (currentKey == &key1) { + currentKey = &key2; + currentData = &data2; + } else { + currentKey = &key1; + currentData = &data1; + } + + compressed.data = (void*)compcursor; + compressed.ulen = compressed.size = + (u_int32_t)(compend - compcursor); + + /* Decompress the next key/data pair */ + while ((ret = ((BTREE *)dbp->bt_internal)->bt_decompress( + dbp, prevKey, prevData, + &compressed, currentKey, currentData)) == DB_BUFFER_SMALL) { + if (CMP_RESIZE_DBT(ret, env, currentKey) != 0) + break; + if (CMP_RESIZE_DBT(ret, env, currentData) != 0) + break; + } + + if (ret == EINVAL) { + ret = DB_VERIFY_FATAL; + goto err; + } + if (ret != 0) + goto err; + + compcursor += compressed.size; + + if (compcursor > compend) { + ret = DB_VERIFY_FATAL; + goto err; + } + + /* Output the next key/data pair */ + if ((ret = __db_vrfy_prdbt( + currentKey, 0, " ", handle, callback, 0, 0, vdp)) != 0) + goto err; + if ((ret = __db_vrfy_prdbt( + currentData, 0, " ", handle, callback, 0, 0, vdp)) != 0) + goto err; + } + + if (0) { + unknown_data: + /* + * Make sure we output a data value for the key that's + * already been output + */ + DB_INIT_DBT( + compressed, "UNKNOWN_DATA", sizeof("UNKNOWN_DATA") - 1); + if ((t_ret = __db_vrfy_prdbt( + &compressed, 0, " ", handle, callback, 0, 0, vdp)) != 0) + ret = t_ret; + } + + err: + __os_free(env, key1.data); + __os_free(env, key2.data); + __os_free(env, data1.data); + __os_free(env, data2.data); + return (ret); +} + +/* + * __bam_compress_count -- + * Calculate key and entry counts for the compressed BTree + * + * PUBLIC: int __bam_compress_count __P((DBC *, u_int32_t *, u_int32_t *)); + */ +int +__bam_compress_count(dbc, nkeysp, ndatap) + DBC *dbc; + u_int32_t *nkeysp, *ndatap; +{ + int ret, t_ret; + u_int32_t nkeys, ndata; + DB *dbp; + BTREE *t; + DBC *dbc_n; + BTREE_CURSOR *cp_n; + + dbp = dbc->dbp; + t = (BTREE *)dbp->bt_internal; + + /* Duplicate the cursor */ + if ((ret = __dbc_dup(dbc, &dbc_n, 0)) != 0) + return (ret); + + /* We don't care about preserving the cursor's position on error */ + F_SET(dbc_n, DBC_TRANSIENT); + + cp_n = (BTREE_CURSOR *)dbc_n->internal; + + nkeys = 0; + ndata = 0; + + CMP_IGET_RETRY(ret, dbc_n, &cp_n->key1, &cp_n->compressed, DB_FIRST); + if (ret != 0) + goto err; + + if ((ret = __bamc_start_decompress(dbc_n)) != 0) + goto err; + nkeys += 1; + + for (;;) { + ndata += 1; + + ret = __bamc_next_decompress(dbc_n); + if (ret == DB_NOTFOUND) { + if (cp_n->currentKey == &cp_n->key1) { + /* + * Make sure that the previous key isn't + * overwritten when we fetch the next chunk. + */ + if ((ret = __bam_compress_set_dbt(dbp, + &cp_n->key2, cp_n->key1.data, + cp_n->key1.size)) != 0) + goto err; + } + + CMP_IGET_RETRY(ret, dbc_n, &cp_n->key1, + &cp_n->compressed, DB_NEXT); + if (ret != 0) + goto err; + + ret = __bamc_start_decompress(dbc_n); + + cp_n->prevKey = &cp_n->key2; + } + + if (ret != 0) + goto err; + + if (t->bt_compare(dbp, cp_n->currentKey, cp_n->prevKey) != 0) + nkeys += 1; + } + +err: + if (ret == DB_NOTFOUND) + ret = 0; + + if ((t_ret = __dbc_close(dbc_n)) != 0 && ret == 0) + ret = t_ret; + + if (ret == 0) { + if (nkeysp != NULL) + *nkeysp = nkeys; + if (ndatap != NULL) + *ndatap = ndata; + } + + return (ret); +} + +/* + * Check if the key/data pairs in the bulk buffer are sorted. + */ +static int +__bam_compress_check_sort_multiple_key(dbp, key) + DB *dbp; + DBT *key; +{ +#ifdef DIAGNOSTIC + void *kptr; + DBT key1, data1, key2, data2; + + memset(&key1, 0, sizeof(DBT)); + memset(&data1, 0, sizeof(DBT)); + memset(&key2, 0, sizeof(DBT)); + memset(&data2, 0, sizeof(DBT)); + + DB_MULTIPLE_INIT(kptr, key); + DB_MULTIPLE_KEY_NEXT(kptr, key, + key2.data, key2.size, data2.data, data2.size); + /* No key/data pair in the bulk buffer */ + if (kptr == NULL) + return (0); + + for (;;) { + DB_MULTIPLE_KEY_NEXT(kptr, key, + key1.data, key1.size, data1.data, data1.size); + if (kptr == NULL) + break; + if (__db_compare_both(dbp, &key1, &data1, &key2, &data2) < 0) { + __db_errx(dbp->env, DB_STR("1170", + "The key/data pairs in the buffer are not sorted.")); + return (EINVAL); + } + key2.data = key1.data; + key2.size = key1.size; + data2.data = data1.data; + data2.size = data1.size; + } +#else + COMPQUIET(dbp, NULL); + COMPQUIET(key, NULL); +#endif + return (0); +} + +/* + * Check if the key/data pairs in the bulk buffer are sorted. + */ +static int +__bam_compress_check_sort_multiple(dbp, key, data) + DB *dbp; + DBT *key, *data; +{ +#ifdef DIAGNOSTIC + void *kptr, *dptr; + DBT key1, data1, key2, data2; + + memset(&key1, 0, sizeof(DBT)); + memset(&data1, 0, sizeof(DBT)); + memset(&key2, 0, sizeof(DBT)); + memset(&data2, 0, sizeof(DBT)); + + DB_MULTIPLE_INIT(kptr, key); + DB_MULTIPLE_INIT(dptr, data); + DB_MULTIPLE_NEXT(kptr, key, key2.data, key2.size); + DB_MULTIPLE_NEXT(dptr, data, data2.data, data2.size); + /* No key/data pair in the bulk buffer */ + if (kptr == NULL || dptr == NULL) + return (0); + + for (;;) { + DB_MULTIPLE_NEXT(kptr, key, key1.data, key1.size); + DB_MULTIPLE_NEXT(dptr, data, data1.data, data1.size); + if (kptr == NULL || dptr == NULL) + break; + if (__db_compare_both(dbp, &key1, &data1, &key2, &data2) < 0) { + __db_errx(dbp->env, DB_STR("1171", + "The key/data pairs in the buffer are not sorted.")); + return (EINVAL); + } + key2.data = key1.data; + key2.size = key1.size; + data2.data = data1.data; + data2.size = data1.size; + } +#else + COMPQUIET(dbp, NULL); + COMPQUIET(key, NULL); + COMPQUIET(data, NULL); +#endif + return (0); +} + +/* + * Check if the keys in the bulk buffer are sorted. + */ +static int +__bam_compress_check_sort_multiple_keyonly(dbp, key) + DB *dbp; + DBT *key; +{ +#ifdef DIAGNOSTIC + void *kptr; + DBT key1, key2; + + memset(&key1, 0, sizeof(DBT)); + memset(&key2, 0, sizeof(DBT)); + + DB_MULTIPLE_INIT(kptr, key); + DB_MULTIPLE_NEXT(kptr, key, key2.data, key2.size); + /* No DBT item in the bulk buffer */ + if (kptr == NULL) + return (0); + + for (;;) { + DB_MULTIPLE_NEXT(kptr, key, key1.data, key1.size); + if (kptr == NULL) + break; + if (__db_compare_both(dbp, &key1, NULL, &key2, NULL) < 0) { + __db_errx(dbp->env, DB_STR("1172", + "The DBT items in the buffer are not sorted")); + return (EINVAL); + } + key2.data = key1.data; + key2.size = key1.size; + } +#else + COMPQUIET(dbp, NULL); + COMPQUIET(key, NULL); +#endif + return (0); +} + +#endif |