Import BSDDB 4.7.25 (as of svn r89086)
This commit is contained in:
40
examples_c/README
Normal file
40
examples_c/README
Normal file
@@ -0,0 +1,40 @@
|
||||
# $Id: README,v 12.1 2006/07/07 23:36:14 alanb Exp $
|
||||
|
||||
getting_started/
|
||||
Examples from the Getting Started Guide
|
||||
|
||||
bench_001.c Micro-benchmark for the bulk fetch interface.
|
||||
|
||||
ex_access.c Using just the DB access methods.
|
||||
|
||||
ex_apprec/ Application-specific recovery.
|
||||
|
||||
ex_btrec.c Using the BTREE access method with record numbers.
|
||||
|
||||
ex_dbclient.c Using DB from an RPC client.
|
||||
|
||||
ex_env.c Setting up the DB environment.
|
||||
|
||||
ex_lock.c Locking.
|
||||
|
||||
ex_mpool.c Shared memory buffer pools.
|
||||
|
||||
ex_rep/ Replication. This creates a toy stock quote server
|
||||
with DB's single-master, multiple-client replication,
|
||||
with communication over TCP. See ex_rep/README.
|
||||
|
||||
ex_sequence.c Sequences.
|
||||
|
||||
ex_thread.c Threaded application with multiple readers and writers.
|
||||
|
||||
ex_tpcb.c TPC/B.
|
||||
Ex_tpcb sets up a framework in which to run a TPC/B test.
|
||||
Database initialization (the -i flag) and running the
|
||||
benchmark (-n flag) must take place separately (i.e.,
|
||||
first create the database, then run 1 or more copies of
|
||||
the benchmark). Furthermore, when running more than one
|
||||
TPCB process, it is necessary to run the deadlock detector
|
||||
(db_deadlock), since it is possible for concurrent tpcb
|
||||
processes to deadlock. For performance measurement, it
|
||||
will also be beneficial to run the db_checkpoint process
|
||||
as well.
|
||||
431
examples_c/bench_001.c
Normal file
431
examples_c/bench_001.c
Normal file
@@ -0,0 +1,431 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2001,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: bench_001.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
/*
|
||||
* bench_001 - time bulk fetch interface.
|
||||
* Without -R builds a btree acording to the arguments.
|
||||
* With -R runs and times bulk fetches. If -d is specified
|
||||
* during reads the DB_MULTIPLE interface is used
|
||||
* otherwise the DB_MULTIPLE_KEY interface is used.
|
||||
*
|
||||
* ARGUMENTS:
|
||||
* -c cachesize [1000 * pagesize]
|
||||
* -d number of duplicates [none]
|
||||
* -E don't use environment
|
||||
* -I Just initialize the environment
|
||||
* -i number of read iterations [1000000]
|
||||
* -l length of data item [20]
|
||||
* -n number of keys [1000000]
|
||||
* -p pagesize [65536]
|
||||
* -R perform read test.
|
||||
* -T incorporate transactions.
|
||||
*
|
||||
* COMPILE:
|
||||
* cc -I /usr/local/BerkeleyDB/include \
|
||||
* -o bench_001 -O2 bench_001.c /usr/local/BerkeleyDB/lib/libdb.so
|
||||
*/
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
extern int getopt(int, char * const *, const char *);
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <db.h>
|
||||
|
||||
#define DATABASE "bench_001.db"
|
||||
|
||||
int compare_int(DB *, const DBT *, const DBT *);
|
||||
DB_ENV *db_init(char *, char *, u_int, int);
|
||||
int fill(DB_ENV *, DB *, int, u_int, int, int);
|
||||
int get(DB *, int, u_int, int, int, int, int *);
|
||||
int main(int, char *[]);
|
||||
void usage(void);
|
||||
|
||||
const char
|
||||
*progname = "bench_001"; /* Program name. */
|
||||
/*
|
||||
* db_init --
|
||||
* Initialize the environment.
|
||||
*/
|
||||
DB_ENV *
|
||||
db_init(home, prefix, cachesize, txn)
|
||||
char *home, *prefix;
|
||||
u_int cachesize;
|
||||
int txn;
|
||||
{
|
||||
DB_ENV *dbenv;
|
||||
u_int32_t flags;
|
||||
int ret;
|
||||
|
||||
if ((ret = db_env_create(&dbenv, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "db_env_create");
|
||||
return (NULL);
|
||||
}
|
||||
dbenv->set_errfile(dbenv, stderr);
|
||||
dbenv->set_errpfx(dbenv, prefix);
|
||||
(void)dbenv->set_cachesize(dbenv, 0,
|
||||
cachesize == 0 ? 50 * 1024 * 1024 : (u_int32_t)cachesize, 0);
|
||||
|
||||
flags = DB_CREATE | DB_INIT_MPOOL;
|
||||
if (txn)
|
||||
flags |= DB_INIT_TXN | DB_INIT_LOCK;
|
||||
if ((ret = dbenv->open(dbenv, home, flags, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB_ENV->open: %s", home);
|
||||
(void)dbenv->close(dbenv, 0);
|
||||
return (NULL);
|
||||
}
|
||||
return (dbenv);
|
||||
}
|
||||
|
||||
/*
|
||||
* get -- loop getting batches of records.
|
||||
*
|
||||
*/
|
||||
int
|
||||
get(dbp, txn, datalen, num, dups, iter, countp)
|
||||
DB *dbp;
|
||||
u_int datalen;
|
||||
int txn, num, dups, iter, *countp;
|
||||
{
|
||||
DBC *dbcp;
|
||||
DBT key, data;
|
||||
DB_ENV *dbenv;
|
||||
DB_TXN *txnp;
|
||||
u_int32_t flags, len, klen;
|
||||
int count, i, j, ret;
|
||||
void *pointer, *dp, *kp;
|
||||
|
||||
dbenv = dbp->dbenv;
|
||||
|
||||
klen = 0; /* Lint. */
|
||||
klen = klen;
|
||||
|
||||
memset(&key, 0, sizeof(key));
|
||||
key.data = &j;
|
||||
key.size = sizeof(j);
|
||||
memset(&data, 0, sizeof(data));
|
||||
data.flags = DB_DBT_USERMEM;
|
||||
data.data = malloc(datalen*1024*1024);
|
||||
data.ulen = data.size = datalen*1024*1024;
|
||||
|
||||
count = 0;
|
||||
flags = DB_SET;
|
||||
if (!dups)
|
||||
flags |= DB_MULTIPLE_KEY;
|
||||
else
|
||||
flags |= DB_MULTIPLE;
|
||||
for (i = 0; i < iter; i++) {
|
||||
txnp = NULL;
|
||||
if (txn)
|
||||
if ((ret =
|
||||
dbenv->txn_begin(dbenv, NULL, &txnp, 0)) != 0)
|
||||
goto err;
|
||||
if ((ret = dbp->cursor(dbp, txnp, &dbcp, 0)) != 0)
|
||||
goto err;
|
||||
|
||||
j = random() % num;
|
||||
if ((ret = dbcp->get(dbcp, &key, &data, flags)) != 0)
|
||||
goto err;
|
||||
DB_MULTIPLE_INIT(pointer, &data);
|
||||
if (dups)
|
||||
while (pointer != NULL) {
|
||||
DB_MULTIPLE_NEXT(pointer, &data, dp, len);
|
||||
if (dp != NULL)
|
||||
count++;
|
||||
}
|
||||
else
|
||||
while (pointer != NULL) {
|
||||
DB_MULTIPLE_KEY_NEXT(pointer,
|
||||
&data, kp, klen, dp, len);
|
||||
if (kp != NULL)
|
||||
count++;
|
||||
}
|
||||
if ((ret = dbcp->close(dbcp)) != 0)
|
||||
goto err;
|
||||
if (txn)
|
||||
if ((ret = txnp->commit(txnp, 0)) != 0)
|
||||
goto err;
|
||||
}
|
||||
|
||||
*countp = count;
|
||||
return (0);
|
||||
|
||||
err: dbp->err(dbp, ret, "get");
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* fill - fill a db
|
||||
* Since we open/created the db with transactions (potentially),
|
||||
* we need to populate it with transactions. We'll bundle the puts
|
||||
* 10 to a transaction.
|
||||
*/
|
||||
#define PUTS_PER_TXN 10
|
||||
int
|
||||
fill(dbenv, dbp, txn, datalen, num, dups)
|
||||
DB_ENV *dbenv;
|
||||
DB *dbp;
|
||||
u_int datalen;
|
||||
int txn, num, dups;
|
||||
{
|
||||
DBT key, data;
|
||||
DB_TXN *txnp;
|
||||
struct data {
|
||||
int id;
|
||||
char str[1];
|
||||
} *data_val;
|
||||
int count, i, ret;
|
||||
|
||||
/*
|
||||
* Insert records into the database, where the key is the user
|
||||
* input and the data is the user input in reverse order.
|
||||
*/
|
||||
txnp = NULL;
|
||||
ret = 0;
|
||||
count = 0;
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
memset(&data, 0, sizeof(DBT));
|
||||
key.data = &i;
|
||||
key.size = sizeof(i);
|
||||
data.data = data_val = malloc(datalen);
|
||||
memcpy(data_val->str, "0123456789012345678901234567890123456789",
|
||||
datalen - sizeof(data_val->id));
|
||||
data.size = datalen;
|
||||
data.flags = DB_DBT_USERMEM;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
if (txn != 0 && i % PUTS_PER_TXN == 0) {
|
||||
if (txnp != NULL) {
|
||||
ret = txnp->commit(txnp, 0);
|
||||
txnp = NULL;
|
||||
if (ret != 0)
|
||||
goto err;
|
||||
}
|
||||
if ((ret =
|
||||
dbenv->txn_begin(dbenv, NULL, &txnp, 0)) != 0)
|
||||
goto err;
|
||||
}
|
||||
data_val->id = 0;
|
||||
do {
|
||||
switch (ret = dbp->put(dbp, txnp, &key, &data, 0)) {
|
||||
case 0:
|
||||
count++;
|
||||
break;
|
||||
default:
|
||||
dbp->err(dbp, ret, "DB->put");
|
||||
goto err;
|
||||
}
|
||||
} while (++data_val->id < dups);
|
||||
}
|
||||
if (txnp != NULL)
|
||||
ret = txnp->commit(txnp, 0);
|
||||
|
||||
printf("%d\n", count);
|
||||
return (ret);
|
||||
|
||||
err: if (txnp != NULL)
|
||||
(void)txnp->abort(txnp);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
int
|
||||
main(argc, argv)
|
||||
int argc;
|
||||
char *argv[];
|
||||
{
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
DB *dbp;
|
||||
DB_ENV *dbenv;
|
||||
DB_TXN *txnp;
|
||||
struct timeval start_time, end_time;
|
||||
double secs;
|
||||
u_int cache, datalen, pagesize;
|
||||
int ch, count, dups, env, init, iter, num;
|
||||
int ret, rflag, txn;
|
||||
|
||||
txnp = NULL;
|
||||
datalen = 20;
|
||||
iter = num = 1000000;
|
||||
env = 1;
|
||||
dups = init = rflag = txn = 0;
|
||||
|
||||
pagesize = 65536;
|
||||
cache = 1000 * pagesize;
|
||||
|
||||
while ((ch = getopt(argc, argv, "c:d:EIi:l:n:p:RT")) != EOF)
|
||||
switch (ch) {
|
||||
case 'c':
|
||||
cache = (u_int)atoi(optarg);
|
||||
break;
|
||||
case 'd':
|
||||
dups = atoi(optarg);
|
||||
break;
|
||||
case 'E':
|
||||
env = 0;
|
||||
break;
|
||||
case 'I':
|
||||
init = 1;
|
||||
break;
|
||||
case 'i':
|
||||
iter = atoi(optarg);
|
||||
break;
|
||||
case 'l':
|
||||
datalen = (u_int)atoi(optarg);
|
||||
break;
|
||||
case 'n':
|
||||
num = atoi(optarg);
|
||||
break;
|
||||
case 'p':
|
||||
pagesize = (u_int)atoi(optarg);
|
||||
break;
|
||||
case 'R':
|
||||
rflag = 1;
|
||||
break;
|
||||
case 'T':
|
||||
txn = 1;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
usage();
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
/* Remove the previous database. */
|
||||
if (!rflag) {
|
||||
if (env)
|
||||
(void)system("rm -rf BENCH_001; mkdir BENCH_001");
|
||||
else
|
||||
(void)unlink(DATABASE);
|
||||
}
|
||||
|
||||
dbenv = NULL;
|
||||
if (env == 1 &&
|
||||
(dbenv = db_init("BENCH_001", "bench_001", cache, txn)) == NULL)
|
||||
return (-1);
|
||||
if (init)
|
||||
exit(0);
|
||||
/* Create and initialize database object, open the database. */
|
||||
if ((ret = db_create(&dbp, dbenv, 0)) != 0) {
|
||||
fprintf(stderr,
|
||||
"%s: db_create: %s\n", progname, db_strerror(ret));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
dbp->set_errfile(dbp, stderr);
|
||||
dbp->set_errpfx(dbp, progname);
|
||||
if ((ret = dbp->set_bt_compare(dbp, compare_int)) != 0) {
|
||||
dbp->err(dbp, ret, "set_bt_compare");
|
||||
goto err;
|
||||
}
|
||||
if ((ret = dbp->set_pagesize(dbp, pagesize)) != 0) {
|
||||
dbp->err(dbp, ret, "set_pagesize");
|
||||
goto err;
|
||||
}
|
||||
if (dups && (ret = dbp->set_flags(dbp, DB_DUP)) != 0) {
|
||||
dbp->err(dbp, ret, "set_flags");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (env == 0 && (ret = dbp->set_cachesize(dbp, 0, cache, 0)) != 0) {
|
||||
dbp->err(dbp, ret, "set_cachesize");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if ((ret = dbp->set_flags(dbp, DB_DUP)) != 0) {
|
||||
dbp->err(dbp, ret, "set_flags");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (txn != 0)
|
||||
if ((ret = dbenv->txn_begin(dbenv, NULL, &txnp, 0)) != 0)
|
||||
goto err;
|
||||
|
||||
if ((ret = dbp->open(
|
||||
dbp, txnp, DATABASE, NULL, DB_BTREE, DB_CREATE, 0664)) != 0) {
|
||||
dbp->err(dbp, ret, "%s: open", DATABASE);
|
||||
if (txnp != NULL)
|
||||
(void)txnp->abort(txnp);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (txnp != NULL)
|
||||
ret = txnp->commit(txnp, 0);
|
||||
txnp = NULL;
|
||||
if (ret != 0)
|
||||
goto err;
|
||||
|
||||
if (rflag) {
|
||||
/* If no environment, fill the cache. */
|
||||
if (!env && (ret =
|
||||
get(dbp, txn, datalen, num, dups, iter, &count)) != 0)
|
||||
goto err;
|
||||
|
||||
/* Time the get loop. */
|
||||
(void)gettimeofday(&start_time, NULL);
|
||||
if ((ret =
|
||||
get(dbp, txn, datalen, num, dups, iter, &count)) != 0)
|
||||
goto err;
|
||||
(void)gettimeofday(&end_time, NULL);
|
||||
secs =
|
||||
(((double)end_time.tv_sec * 1000000 + end_time.tv_usec) -
|
||||
((double)start_time.tv_sec * 1000000 + start_time.tv_usec))
|
||||
/ 1000000;
|
||||
printf("%d records read using %d batches in %.2f seconds: ",
|
||||
count, iter, secs);
|
||||
printf("%.0f records/second\n", (double)count / secs);
|
||||
|
||||
} else if ((ret = fill(dbenv, dbp, txn, datalen, num, dups)) != 0)
|
||||
goto err;
|
||||
|
||||
/* Close everything down. */
|
||||
if ((ret = dbp->close(dbp, rflag ? DB_NOSYNC : 0)) != 0) {
|
||||
fprintf(stderr,
|
||||
"%s: DB->close: %s\n", progname, db_strerror(ret));
|
||||
return (1);
|
||||
}
|
||||
return (ret);
|
||||
|
||||
err: (void)dbp->close(dbp, 0);
|
||||
return (1);
|
||||
}
|
||||
|
||||
int
|
||||
compare_int(dbp, a, b)
|
||||
DB *dbp;
|
||||
const DBT *a, *b;
|
||||
{
|
||||
int ai, bi;
|
||||
|
||||
dbp = dbp; /* Lint. */
|
||||
|
||||
/*
|
||||
* Returns:
|
||||
* < 0 if a < b
|
||||
* = 0 if a = b
|
||||
* > 0 if a > b
|
||||
*/
|
||||
memcpy(&ai, a->data, sizeof(int));
|
||||
memcpy(&bi, b->data, sizeof(int));
|
||||
return (ai - bi);
|
||||
}
|
||||
|
||||
void
|
||||
usage()
|
||||
{
|
||||
(void)fprintf(stderr, "usage: %s %s\n\t%s\n",
|
||||
progname, "[-EIRT] [-c cachesize] [-d dups]",
|
||||
"[-i iterations] [-l datalen] [-n keys] [-p pagesize]");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
470
examples_c/csv/DbRecord.c
Normal file
470
examples_c/csv/DbRecord.c
Normal file
@@ -0,0 +1,470 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2005,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: DbRecord.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include "csv.h"
|
||||
#include "csv_local.h"
|
||||
#include "csv_extern.h"
|
||||
|
||||
static int DbRecord_field(DbRecord *, u_int, void *, datatype);
|
||||
static int DbRecord_search_field(DbField *, char *, OPERATOR);
|
||||
static int DbRecord_search_recno(char *, OPERATOR);
|
||||
|
||||
/*
|
||||
* DbRecord_print --
|
||||
* Display a DbRecord structure.
|
||||
*/
|
||||
void
|
||||
DbRecord_print(DbRecord *recordp, FILE *fp)
|
||||
{
|
||||
DbField *f;
|
||||
void *faddr;
|
||||
|
||||
if (fp == NULL)
|
||||
fp = stdout;
|
||||
|
||||
fprintf(fp, "Record: %lu:\n", (u_long)recordp->recno);
|
||||
for (f = fieldlist; f->name != NULL; ++f) {
|
||||
faddr = (u_int8_t *)recordp + f->offset;
|
||||
fprintf(fp, "\t%s: ", f->name);
|
||||
switch (f->type) {
|
||||
case NOTSET:
|
||||
/* NOTREACHED */
|
||||
abort();
|
||||
break;
|
||||
case DOUBLE:
|
||||
fprintf(fp, "%f\n", *(double *)faddr);
|
||||
break;
|
||||
case STRING:
|
||||
fprintf(fp, "%s\n", *(char **)faddr);
|
||||
break;
|
||||
case UNSIGNED_LONG:
|
||||
fprintf(fp, "%lu\n", *(u_long *)faddr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* DbRecord_read --
|
||||
* Read a specific record from the database.
|
||||
*/
|
||||
int
|
||||
DbRecord_read(u_long recno_ulong, DbRecord *recordp)
|
||||
{
|
||||
DBT key, data;
|
||||
u_int32_t recno;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* XXX
|
||||
* This code assumes a record number (typed as u_int32_t) is the same
|
||||
* size as an unsigned long, and there's no reason to believe that.
|
||||
*/
|
||||
recno = recno_ulong;
|
||||
|
||||
/*
|
||||
* Retrieve the requested record from the primary database. Have
|
||||
* Berkeley DB allocate memory for us, keeps the DB handle thread
|
||||
* safe.
|
||||
*
|
||||
* We have the Berkeley DB library allocate memory for the record,
|
||||
* which we own and must eventually free. The reason is so we can
|
||||
* have the string fields in the structure point into the actual
|
||||
* record, rather than allocating structure local memory to hold them
|
||||
* and copying them out of the record.
|
||||
*/
|
||||
memset(&key, 0, sizeof(key));
|
||||
memset(&data, 0, sizeof(data));
|
||||
key.data = &recno;
|
||||
key.size = sizeof(recno);
|
||||
data.flags = DB_DBT_MALLOC;
|
||||
if ((ret = db->get(db, NULL, &key, &data, 0)) != 0)
|
||||
return (ret);
|
||||
|
||||
if ((ret = DbRecord_init(&key, &data, recordp)) != 0)
|
||||
return (ret);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* DbRecord_discard --
|
||||
* Discard a DbRecord structure.
|
||||
*/
|
||||
int
|
||||
DbRecord_discard(DbRecord *recordp)
|
||||
{
|
||||
/* Free the allocated memory. */
|
||||
free(recordp->raw);
|
||||
recordp->raw = NULL;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* DbRecord_init --
|
||||
* Fill in a DbRecord from the database key/data pair.
|
||||
*/
|
||||
int
|
||||
DbRecord_init(const DBT *key, const DBT *data, DbRecord *recordp)
|
||||
{
|
||||
DbField *f;
|
||||
u_int32_t skip;
|
||||
void *faddr;
|
||||
|
||||
/* Initialize the structure (get the pre-set index values). */
|
||||
*recordp = DbRecord_base;
|
||||
|
||||
/* Fill in the ID and version. */
|
||||
memcpy(&recordp->recno, key->data, sizeof(u_int32_t));
|
||||
memcpy(&recordp->version,
|
||||
(u_int32_t *)data->data + 1, sizeof(u_int32_t));
|
||||
|
||||
/* Set up the record references. */
|
||||
recordp->raw = data->data;
|
||||
recordp->offset = (u_int32_t *)data->data + 1;
|
||||
skip = (recordp->field_count + 2) * sizeof(u_int32_t);
|
||||
recordp->record = (u_int8_t *)data->data + skip;
|
||||
recordp->record_len = data->size - skip;
|
||||
|
||||
for (f = fieldlist; f->name != NULL; ++f) {
|
||||
faddr = (u_int8_t *)recordp + f->offset;
|
||||
if (DbRecord_field(
|
||||
recordp, f->fieldno, faddr, f->type) != 0)
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* DbRecord_field --
|
||||
* Fill in an individual field of the DbRecord.
|
||||
*/
|
||||
static int
|
||||
DbRecord_field(
|
||||
DbRecord *recordp, u_int field, void *addr, datatype type)
|
||||
{
|
||||
size_t len;
|
||||
char number_buf[20];
|
||||
|
||||
/*
|
||||
* The offset table is 0-based, the field numbers are 1-based.
|
||||
* Correct.
|
||||
*/
|
||||
--field;
|
||||
|
||||
switch (type) {
|
||||
case NOTSET:
|
||||
/* NOTREACHED */
|
||||
abort();
|
||||
break;
|
||||
case STRING:
|
||||
*((u_char **)addr) = recordp->record + recordp->offset[field];
|
||||
recordp->record[recordp->offset[field] +
|
||||
OFFSET_LEN(recordp->offset, field)] = '\0';
|
||||
break;
|
||||
case DOUBLE:
|
||||
case UNSIGNED_LONG:
|
||||
/* This shouldn't be possible -- 2^32 is only 10 digits. */
|
||||
len = OFFSET_LEN(recordp->offset, field);
|
||||
if (len > sizeof(number_buf) - 1) {
|
||||
dbenv->errx(dbenv,
|
||||
"record %lu field %lu: numeric field is %lu bytes and too large to copy",
|
||||
recordp->recno, field, (u_long)len);
|
||||
return (1);
|
||||
}
|
||||
memcpy(number_buf,
|
||||
recordp->record + recordp->offset[field], len);
|
||||
number_buf[len] = '\0';
|
||||
|
||||
if (type == DOUBLE) {
|
||||
if (len == 0)
|
||||
*(double *)addr = 0;
|
||||
else if (strtod_err(number_buf, (double *)addr) != 0)
|
||||
goto fmt_err;
|
||||
} else
|
||||
if (len == 0)
|
||||
*(u_long *)addr = 0;
|
||||
else if (strtoul_err(number_buf, (u_long *)addr) != 0) {
|
||||
fmt_err: dbenv->errx(dbenv,
|
||||
"record %lu: numeric field %u error: %s",
|
||||
recordp->recno, field, number_buf);
|
||||
return (1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* DbRecord_search_field_name --
|
||||
* Search, looking for a record by field name.
|
||||
*/
|
||||
int
|
||||
DbRecord_search_field_name(char *field, char *value, OPERATOR op)
|
||||
{
|
||||
DbField *f;
|
||||
|
||||
for (f = fieldlist; f->name != NULL; ++f)
|
||||
if (strcasecmp(field, f->name) == 0)
|
||||
return (DbRecord_search_field(f, value, op));
|
||||
|
||||
/* Record numbers aren't handled as fields. */
|
||||
if (strcasecmp(field, "id") == 0)
|
||||
return (DbRecord_search_recno(value, op));
|
||||
|
||||
dbenv->errx(dbenv, "unknown field name: %s", field);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* DbRecord_search_field_number --
|
||||
* Search, looking for a record by field number.
|
||||
*/
|
||||
int
|
||||
DbRecord_search_field_number(u_int32_t fieldno, char *value, OPERATOR op)
|
||||
{
|
||||
DbField *f;
|
||||
|
||||
for (f = fieldlist; f->name != NULL; ++f)
|
||||
if (fieldno == f->fieldno)
|
||||
return (DbRecord_search_field(f, value, op));
|
||||
|
||||
dbenv->errx(dbenv, "field number %lu not configured", (u_long)fieldno);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* DbRecord_search_recno --
|
||||
* Search, looking for a record by record number.
|
||||
*/
|
||||
static int
|
||||
DbRecord_search_recno(char *value, OPERATOR op)
|
||||
{
|
||||
DBC *dbc;
|
||||
DbRecord record;
|
||||
DBT key, data;
|
||||
u_int32_t recno;
|
||||
u_long recno_ulong;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* XXX
|
||||
* This code assumes a record number (typed as u_int32_t) is the same
|
||||
* size as an unsigned long, and there's no reason to believe that.
|
||||
*/
|
||||
if (strtoul_err(value, &recno_ulong) != 0)
|
||||
return (1);
|
||||
|
||||
memset(&key, 0, sizeof(key));
|
||||
memset(&data, 0, sizeof(data));
|
||||
key.data = &recno;
|
||||
key.size = sizeof(recno);
|
||||
|
||||
if ((ret = db->cursor(db, NULL, &dbc, 0)) != 0)
|
||||
return (ret);
|
||||
|
||||
/*
|
||||
* Retrieve the first record that interests us. The range depends on
|
||||
* the operator:
|
||||
*
|
||||
* ~ error
|
||||
* != beginning to end
|
||||
* < beginning to first match
|
||||
* <= beginning to last match
|
||||
* = first match to last match
|
||||
* > record after last match to end
|
||||
* >= first match to end
|
||||
*/
|
||||
if (op == LT || op == LTEQ || op == NEQ || op == WC || op == NWC)
|
||||
recno = 1;
|
||||
else if (op == WC || op == NWC) {
|
||||
dbenv->errx(dbenv,
|
||||
"wildcard operator only supported for string fields");
|
||||
return (1);
|
||||
} else {
|
||||
recno = recno_ulong;
|
||||
if (op == GT)
|
||||
++recno;
|
||||
}
|
||||
if ((ret = dbc->c_get(dbc, &key, &data, DB_SET)) != 0)
|
||||
goto err;
|
||||
|
||||
for (;;) {
|
||||
if ((ret = DbRecord_init(&key, &data, &record)) != 0)
|
||||
break;
|
||||
if (field_cmp_ulong(&record.recno, &recno_ulong, op))
|
||||
DbRecord_print(&record, NULL);
|
||||
else
|
||||
if (op == LT || op == LTEQ || op == EQ)
|
||||
break;
|
||||
if ((ret = dbc->c_get(dbc, &key, &data, DB_NEXT)) != 0)
|
||||
break;
|
||||
}
|
||||
|
||||
err: return (ret == DB_NOTFOUND ? 0 : ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* DbRecord_search_field --
|
||||
* Search, looking for a record by field.
|
||||
*/
|
||||
static int
|
||||
DbRecord_search_field(DbField *f, char *value, OPERATOR op)
|
||||
{
|
||||
#ifdef HAVE_WILDCARD_SUPPORT
|
||||
regex_t preq;
|
||||
#endif
|
||||
DBC *dbc;
|
||||
DbRecord record;
|
||||
DBT key, data, pkey;
|
||||
double value_double;
|
||||
u_long value_ulong;
|
||||
u_int32_t cursor_flags;
|
||||
int ret, t_ret;
|
||||
int (*cmp)(void *, void *, OPERATOR);
|
||||
void *faddr, *valuep;
|
||||
|
||||
dbc = NULL;
|
||||
memset(&key, 0, sizeof(key));
|
||||
memset(&pkey, 0, sizeof(pkey));
|
||||
memset(&data, 0, sizeof(data));
|
||||
|
||||
/*
|
||||
* Initialize the comparison function, crack the value. Wild cards
|
||||
* are always strings, otherwise we follow the field type.
|
||||
*/
|
||||
if (op == WC || op == NWC) {
|
||||
#ifdef HAVE_WILDCARD_SUPPORT
|
||||
if (f->type != STRING) {
|
||||
dbenv->errx(dbenv,
|
||||
"wildcard operator only supported for string fields");
|
||||
return (1);
|
||||
}
|
||||
if (regcomp(&preq, value, 0) != 0) {
|
||||
dbenv->errx(dbenv, "regcomp of pattern failed");
|
||||
return (1);
|
||||
}
|
||||
valuep = &preq;
|
||||
cmp = field_cmp_re;
|
||||
#else
|
||||
dbenv->errx(dbenv,
|
||||
"wildcard operators not supported in this build");
|
||||
return (1);
|
||||
#endif
|
||||
} else
|
||||
switch (f->type) {
|
||||
case DOUBLE:
|
||||
if (strtod_err(value, &value_double) != 0)
|
||||
return (1);
|
||||
cmp = field_cmp_double;
|
||||
valuep = &value_double;
|
||||
key.size = sizeof(double);
|
||||
break;
|
||||
case STRING:
|
||||
valuep = value;
|
||||
cmp = field_cmp_string;
|
||||
key.size = strlen(value);
|
||||
break;
|
||||
case UNSIGNED_LONG:
|
||||
if (strtoul_err(value, &value_ulong) != 0)
|
||||
return (1);
|
||||
cmp = field_cmp_ulong;
|
||||
valuep = &value_ulong;
|
||||
key.size = sizeof(u_long);
|
||||
break;
|
||||
default:
|
||||
case NOTSET:
|
||||
abort();
|
||||
/* NOTREACHED */
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve the first record that interests us. The range depends on
|
||||
* the operator:
|
||||
*
|
||||
* ~ beginning to end
|
||||
* != beginning to end
|
||||
* < beginning to first match
|
||||
* <= beginning to last match
|
||||
* = first match to last match
|
||||
* > record after last match to end
|
||||
* >= first match to end
|
||||
*
|
||||
* If we have a secondary, set a cursor in the secondary, else set the
|
||||
* cursor to the beginning of the primary.
|
||||
*
|
||||
* XXX
|
||||
* If the wildcard string has a leading non-magic character we should
|
||||
* be able to do a range search instead of a full-database search.
|
||||
*
|
||||
* Step through records to the first non-match or to the end of the
|
||||
* database, depending on the operation. If the comparison function
|
||||
* returns success for a key/data pair, print the pair.
|
||||
*/
|
||||
if (f->secondary == NULL || op == NEQ || op == WC || op == NWC) {
|
||||
if ((ret = db->cursor(db, NULL, &dbc, 0)) != 0)
|
||||
goto err;
|
||||
while ((ret = dbc->c_get(dbc, &key, &data, DB_NEXT)) == 0) {
|
||||
if ((ret = DbRecord_init(&key, &data, &record)) != 0)
|
||||
break;
|
||||
faddr = (u_int8_t *)&record + f->offset;
|
||||
if (cmp(faddr, valuep, op))
|
||||
DbRecord_print(&record, NULL);
|
||||
else
|
||||
if (op == EQ || op == LT || op == LTEQ)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if ((ret =
|
||||
f->secondary->cursor(f->secondary, NULL, &dbc, 0)) != 0)
|
||||
goto err;
|
||||
key.data = valuep;
|
||||
cursor_flags = op == LT || op == LTEQ ? DB_FIRST : DB_SET_RANGE;
|
||||
if ((ret =
|
||||
dbc->c_pget(dbc, &key, &pkey, &data, cursor_flags)) != 0)
|
||||
goto done;
|
||||
if (op == GT) {
|
||||
while ((ret = dbc->c_pget(
|
||||
dbc, &key, &pkey, &data, DB_NEXT)) == 0) {
|
||||
if ((ret =
|
||||
DbRecord_init(&pkey, &data, &record)) != 0)
|
||||
break;
|
||||
faddr = (u_int8_t *)&record + f->offset;
|
||||
if (cmp(faddr, valuep, op) != 0)
|
||||
break;
|
||||
}
|
||||
if (ret != 0)
|
||||
goto done;
|
||||
}
|
||||
do {
|
||||
if ((ret = DbRecord_init(&pkey, &data, &record)) != 0)
|
||||
break;
|
||||
faddr = (u_int8_t *)&record + f->offset;
|
||||
if (cmp(faddr, valuep, op))
|
||||
DbRecord_print(&record, NULL);
|
||||
else
|
||||
if (op == EQ || op == LT || op == LTEQ)
|
||||
break;
|
||||
} while ((ret =
|
||||
dbc->c_pget(dbc, &key, &pkey, &data, DB_NEXT)) == 0);
|
||||
}
|
||||
|
||||
done: if (ret == DB_NOTFOUND)
|
||||
ret = 0;
|
||||
|
||||
err: if (dbc != NULL && (t_ret = dbc->c_close(dbc)) != 0 && ret == 0)
|
||||
ret = t_ret;
|
||||
|
||||
#ifdef HAVE_WILDCARD_SUPPORT
|
||||
if (op == WC || op == NWC)
|
||||
regfree(&preq);
|
||||
#endif
|
||||
|
||||
return (ret);
|
||||
}
|
||||
75
examples_c/csv/Makefile
Normal file
75
examples_c/csv/Makefile
Normal file
@@ -0,0 +1,75 @@
|
||||
# $Id: Makefile,v 1.14 2006/10/27 00:56:44 bostic Exp $
|
||||
|
||||
# Berkeley DB installation.
|
||||
DB_INCLUDE=../../build_unix
|
||||
LIBS= -L../../build_unix -L../../build_unix/.libs/ -ldb
|
||||
|
||||
INC= -I. -I$(DB_INCLUDE)
|
||||
CFLAGS= $(INC) -g -W -Wall -Wpointer-arith -Wmissing-prototypes
|
||||
|
||||
PROGS= csv_code csv_load csv_query
|
||||
SRCS= DbRecord.c code.c csv_local.c db.c load.c load_main.c query.c \
|
||||
query_main.c util.c
|
||||
|
||||
all: csv_load csv_query
|
||||
|
||||
csv_code: code.o
|
||||
$(CC) -o $@ $? $(LIBS)
|
||||
|
||||
LOAD_OBJS=DbRecord.o csv_local.o db.o load.o load_main.o util.o
|
||||
csv_load: $(LOAD_OBJS)
|
||||
$(CC) -o $@ $(LOAD_OBJS) $(LIBS)
|
||||
|
||||
QUERY_OBJS=DbRecord.o csv_local.o db.o query.o query_main.o util.o
|
||||
csv_query: $(QUERY_OBJS)
|
||||
$(CC) -o $@ $(QUERY_OBJS) $(LIBS)
|
||||
|
||||
clean distclean realclean:
|
||||
rm -rf $(PROGS) TESTDIR eBay tags *.o *.core csv_local.[ch]
|
||||
|
||||
tags:
|
||||
rm -f tags
|
||||
ctags $(SRCS) code.c
|
||||
|
||||
DbRecord.o csv_local.o db.o load.o load_main.o query.o: csv_local.h csv.h
|
||||
query_main.o util.o: csv_local.h csv.h
|
||||
|
||||
csv_local.c csv_local.h: csv_code
|
||||
./csv_code -c csv_local.c -h csv_local.h -f sample.desc
|
||||
|
||||
lint_code:
|
||||
flexelint +fll \
|
||||
"-e801" \
|
||||
"-e818" \
|
||||
"-esym(534,fprintf)" \
|
||||
"-esym(534,memcpy)" \
|
||||
"-esym(534,memmove)" \
|
||||
"-esym(534,memset)" \
|
||||
"-esym(534,printf)" \
|
||||
"-wlib(1)" \
|
||||
-i$(DB_INCLUDE) "-i/usr/include" \
|
||||
code.c
|
||||
lint_load:
|
||||
flexelint +fll \
|
||||
"-e801" \
|
||||
"-e818" \
|
||||
"-esym(534,fprintf)" \
|
||||
"-esym(534,memcpy)" \
|
||||
"-esym(534,memmove)" \
|
||||
"-esym(534,memset)" \
|
||||
"-esym(534,printf)" \
|
||||
"-wlib(1)" \
|
||||
-i$(DB_INCLUDE) "-i/usr/include" \
|
||||
DbRecord.c csv_local.c db.c load.c load_main.c util.c
|
||||
lint_query:
|
||||
flexelint +fll \
|
||||
"-e801" \
|
||||
"-e818" \
|
||||
"-esym(534,fprintf)" \
|
||||
"-esym(534,memcpy)" \
|
||||
"-esym(534,memmove)" \
|
||||
"-esym(534,memset)" \
|
||||
"-esym(534,printf)" \
|
||||
"-wlib(1)" \
|
||||
-i$(DB_INCLUDE) "-i/usr/include" \
|
||||
DbRecord.c csv_local.c db.c query.c query_main.c util.c
|
||||
408
examples_c/csv/README
Normal file
408
examples_c/csv/README
Normal file
@@ -0,0 +1,408 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2005,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: README,v 1.22 2008/01/08 20:58:23 bostic Exp $
|
||||
*/
|
||||
|
||||
The "comma-separated value" (csv) directory is a suite of three programs:
|
||||
|
||||
csv_code: write "helper" code on which to build applications,
|
||||
csv_load: import csv files into a Berkeley DB database,
|
||||
csv_query: query databases created by csv_load.
|
||||
|
||||
The goal is to allow programmers to easily build applications for using
|
||||
csv databases.
|
||||
|
||||
You can build the three programs, and run a sample application in this
|
||||
directory.
|
||||
|
||||
First, there's the sample.csv file:
|
||||
|
||||
Adams,Bob,01/02/03,green,apple,37
|
||||
Carter,Denise Ann,04/05/06,blue,banana,38
|
||||
Eidel,Frank,07/08/09,red,cherry,38
|
||||
Grabel,Harriet,10/11/12,purple,date,40
|
||||
Indals,Jason,01/03/05,pink,orange,32
|
||||
Kilt,Laura,07/09/11,yellow,grape,38
|
||||
Moreno,Nancy,02/04/06,black,strawberry,38
|
||||
Octon,Patrick,08/10/12,magenta,kiwi,15
|
||||
|
||||
The fields are:
|
||||
Last name,
|
||||
First name,
|
||||
Birthdate,
|
||||
Favorite color,
|
||||
Favorite fruit,
|
||||
Age
|
||||
|
||||
Second, there's a "description" of that csv file in sample.desc:
|
||||
|
||||
version 1 {
|
||||
LastName string
|
||||
FirstName string
|
||||
BirthDate
|
||||
Color string index
|
||||
Fruit string index
|
||||
Age unsigned_long index
|
||||
}
|
||||
|
||||
The DESCRIPTION file maps one-to-one to the fields in the csv file, and
|
||||
provides a data type for any field the application wants to use. (If
|
||||
the application doesn't care about a field, don't specify a data type
|
||||
and the csv code will ignore it.) The string "index" specifies there
|
||||
should be a secondary index based on the field.
|
||||
|
||||
The "field" names in the DESCRIPTION file don't have to be the same as
|
||||
the ones in the csv file (and, as they may not have embedded spaces,
|
||||
probably won't be).
|
||||
|
||||
To build in the sample directory, on POSIX-like systems, type "make".
|
||||
This first builds the program csv_code, which it then run, with the file
|
||||
DESCRIPTION as an input. Running csv_code creates two additional files:
|
||||
csv_local.c and csv_local.h. Those two files are then used as part of
|
||||
the build process for two more programs: csv_load and csv_query.
|
||||
|
||||
You can load now load the csv file into a Berkeley DB database with the
|
||||
following command:
|
||||
|
||||
% ./csv_load -h TESTDIR < sample.csv
|
||||
|
||||
The csv_load command will create a directory and four databases:
|
||||
|
||||
primary primary database
|
||||
Age secondary index on Age field
|
||||
Color secondary index on Color field
|
||||
Fruit secondary index on Fruit field
|
||||
|
||||
You can then query the database:
|
||||
|
||||
% ./csv_query -h TESTDIR
|
||||
Query: id=2
|
||||
Record: 2:
|
||||
LastName: Carter
|
||||
FirstName: Denise
|
||||
Color: blue
|
||||
Fruit: banana
|
||||
Age: 38
|
||||
Query: color==green
|
||||
Record: 1:
|
||||
LastName: Adams
|
||||
FirstName: Bob
|
||||
Color: green
|
||||
Fruit: apple
|
||||
Age: 37
|
||||
|
||||
and so on.
|
||||
|
||||
The csv_code process also creates source code modules that support
|
||||
building your own applications based on this database. First, there
|
||||
is the local csv_local.h include file:
|
||||
|
||||
/*
|
||||
* DO NOT EDIT: automatically built by csv_code.
|
||||
*
|
||||
* Record structure.
|
||||
*/
|
||||
typedef struct __DbRecord {
|
||||
u_int32_t recno; /* Record number */
|
||||
|
||||
/*
|
||||
* Management fields
|
||||
*/
|
||||
void *raw; /* Memory returned by DB */
|
||||
char *record; /* Raw record */
|
||||
size_t record_len; /* Raw record length */
|
||||
|
||||
u_int32_t field_count; /* Field count */
|
||||
u_int32_t version; /* Record version */
|
||||
|
||||
u_int32_t *offset; /* Offset table */
|
||||
|
||||
/*
|
||||
* Indexed fields
|
||||
*/
|
||||
#define CSV_INDX_LASTNAME 1
|
||||
char *LastName;
|
||||
|
||||
#define CSV_INDX_FIRSTNAME 2
|
||||
char *FirstName;
|
||||
|
||||
#define CSV_INDX_COLOR 4
|
||||
char *Color;
|
||||
|
||||
#define CSV_INDX_FRUIT 5
|
||||
char *Fruit;
|
||||
|
||||
#define CSV_INDX_AGE 6
|
||||
u_long Age;
|
||||
} DbRecord;
|
||||
|
||||
This defines the DbRecord structure that is the primary object for this
|
||||
csv file. As you can see, the intersting fields in the csv file have
|
||||
mappings in this structure.
|
||||
|
||||
Also, there are routines in the Dbrecord.c file your application can use
|
||||
to handle DbRecord structures. When you retrieve a record from the
|
||||
database the DbRecord structure will be filled in based on that record.
|
||||
|
||||
Here are the helper routines:
|
||||
|
||||
int
|
||||
DbRecord_print(DbRecord *recordp, FILE *fp)
|
||||
Display the contents of a DbRecord structure to the specified
|
||||
output stream.
|
||||
|
||||
int
|
||||
DbRecord_init(const DBT *key, DBT *data, DbRecord *recordp)
|
||||
Fill in a DbRecord from a returned database key/data pair.
|
||||
|
||||
int
|
||||
DbRecord_read(u_long key, DbRecord *recordp)
|
||||
Read the specified record (DbRecord_init will be called
|
||||
to fill in the DbRecord).
|
||||
|
||||
int
|
||||
DbRecord_discard(DbRecord *recordp)
|
||||
Discard the DbRecord structure (must be called after the
|
||||
DbRecord_read function), when the application no longer
|
||||
needs the returned DbRecord.
|
||||
|
||||
int
|
||||
DbRecord_search_field_name(char *field, char *value, OPERATOR op)
|
||||
Display the DbRecords where the field (named by field) has
|
||||
the specified relationship to the value. For example:
|
||||
|
||||
DbRecord_search_field_name("Age", "35", GT)
|
||||
|
||||
would search for records with a "Age" field greater than
|
||||
35.
|
||||
|
||||
int
|
||||
DbRecord_search_field_number(
|
||||
u_int32_t fieldno, char *value, OPERATOR op)
|
||||
Display the DbRecords where the field (named by field)
|
||||
has the specified relationship to the value. The field
|
||||
number used as an argument comes from the csv_local.h
|
||||
file, for example, CSV_INDX_AGE is the field index for
|
||||
the "Age" field in this csv file. For example:
|
||||
|
||||
DbRecord_search_field_number(CSV_INDX_AGE, 35, GT)
|
||||
|
||||
would search for records with a "Age" field greater than
|
||||
35.
|
||||
|
||||
Currently, the csv code only supports three types of data:
|
||||
strings, unsigned longs and doubles. Others can easily be
|
||||
added.
|
||||
|
||||
The usage of the csv_code program is as follows:
|
||||
|
||||
usage: csv_code [-v] [-c source-file] [-f input] [-h header-file]
|
||||
-c output C source code file
|
||||
-h output C header file
|
||||
-f input file
|
||||
-v verbose (defaults to off)
|
||||
|
||||
-c A file to which to write the C language code. By default,
|
||||
the file "csv_local.c" is used.
|
||||
|
||||
-f A file to read for a description of the fields in the
|
||||
csv file. By default, csv_code reads from stdin.
|
||||
|
||||
-h A file to which to write the C language header structures.
|
||||
By default, the file "csv_local.h" is used.
|
||||
|
||||
-v The -v verbose flag outputs potentially useful debugging
|
||||
information.
|
||||
|
||||
There are two applications built on top of the code produced by
|
||||
csv_code, csv_load and csv_query.
|
||||
|
||||
The usage of the csv_load program is as follows:
|
||||
|
||||
usage: csv_load [-v] [-F format] [-f csv-file] [-h home] [-V version]
|
||||
-F format (currently supports "excel")
|
||||
-f input file
|
||||
-h database environment home directory
|
||||
-v verbose (defaults to off)
|
||||
|
||||
-F See "Input format" below.
|
||||
|
||||
-f If an input file is specified using the -f flag, the file
|
||||
is read and the records in the file are stored into the
|
||||
database. By default, csv_load reads from stdin.
|
||||
|
||||
-h If a database environment home directory is specified
|
||||
using the -h flag, that directory is used as the
|
||||
Berkeley DB directory. The default for -h is the
|
||||
current working directory or the value of the DB_HOME
|
||||
environment variable.
|
||||
|
||||
-V Specify a version number for the input (the default is 1).
|
||||
|
||||
-v The -v verbose flag outputs potentially useful debugging
|
||||
information. It can be specified twice for additional
|
||||
information.
|
||||
|
||||
The usage of csv_query program is as follows:
|
||||
|
||||
usage: csv_query [-v] [-c cmd] [-h home]
|
||||
|
||||
-c A command to run, otherwise csv_query will enter
|
||||
interactive mode and prompt for user input.
|
||||
|
||||
-h If a database environment home directory is specified
|
||||
using the -h flag, that directory is used as the
|
||||
Berkeley DB directory. The default for -h is the
|
||||
current working directory or the value of the DB_HOME
|
||||
environment variable.
|
||||
|
||||
-v The -v verbose flag outputs potentially useful debugging
|
||||
information. It can be specified twice for additional
|
||||
information.
|
||||
|
||||
The query program currently supports the following commands:
|
||||
|
||||
? Display help screen
|
||||
exit Exit program
|
||||
fields Display list of field names
|
||||
help Display help screen
|
||||
quit Exit program
|
||||
version Display database format version
|
||||
field[op]value Display fields by value (=, !=, <, <=, >, >=, ~, !~)
|
||||
|
||||
The "field[op]value" command allows you to specify a field and a
|
||||
relationship to a value. For example, you could run the query:
|
||||
|
||||
csv_query -c "price < 5"
|
||||
|
||||
to list all of the records with a "price" field less than "5".
|
||||
|
||||
Field names and all string comparisons are case-insensitive.
|
||||
|
||||
The operators ~ and !~ do match/no-match based on the IEEE Std 1003.2
|
||||
(POSIX.2) Basic Regular Expression standard.
|
||||
|
||||
As a special case, every database has the field "Id", which matches the
|
||||
record number of the primary key.
|
||||
|
||||
Input format:
|
||||
The input to the csv_load utility is a text file, containing
|
||||
lines of comma-separated fields.
|
||||
|
||||
Blank lines are ignored. All non-blank lines must be comma-separated
|
||||
lists of fields.
|
||||
|
||||
By default:
|
||||
<nul> (\000) bytes and unprintable characters are stripped,
|
||||
input lines are <nl> (\012) separated,
|
||||
commas cannot be escaped.
|
||||
|
||||
If "-F excel" is specified:
|
||||
<nul> (\000) bytes and unprintable characters are stripped,
|
||||
input lines are <cr> (\015) separated,
|
||||
<nl> bytes (\012) characters are stripped from the input,
|
||||
commas surrounded by double-quote character (") are not
|
||||
treated as field separators.
|
||||
|
||||
Storage format:
|
||||
Records in the primary database are stored with a 32-bit unsigned
|
||||
record number as the key.
|
||||
|
||||
Key/Data pair 0 is of the format:
|
||||
[version] 32-bit unsigned int
|
||||
[field count] 32-bit unsigned int
|
||||
[raw record] byte array
|
||||
|
||||
For example:
|
||||
[1]
|
||||
[5]
|
||||
[field1,field2,field3,field4,field5]
|
||||
|
||||
All other Key/Data pairs are of the format:
|
||||
[version] 32-bit unsigned int
|
||||
[offset to field 1] 32-bit unsigned int
|
||||
[offset to field 2] 32-bit unsigned int
|
||||
[offset to field 3] 32-bit unsigned int
|
||||
... 32-bit unsigned int
|
||||
[offset to field N] 32-bit unsigned int
|
||||
[offset past field N] 32-bit unsigned int
|
||||
[raw record] byte array
|
||||
|
||||
For example:
|
||||
[1]
|
||||
[0]
|
||||
[2]
|
||||
[5]
|
||||
[9]
|
||||
[14]
|
||||
[19]
|
||||
[a,ab,abc,abcd,abcde]
|
||||
012345678901234567890 << byte offsets
|
||||
0 1 2
|
||||
|
||||
So, field 3 of the data can be directly accessed by using
|
||||
the "offset to field 3", and the length of the field is
|
||||
the "((offset to field 4) - (offset to field 3)) - 1".
|
||||
|
||||
Limits:
|
||||
The csv program stores the primary key in a 32-bit unsigned
|
||||
value, limiting the number of records in the database. New
|
||||
records are inserted after the last existing record, that is,
|
||||
new records are not inserted into gaps left by any deleted
|
||||
records. This will limit the total number of records stored in
|
||||
any database.
|
||||
|
||||
Versioning:
|
||||
Versioning is when a database supports multiple versions of the
|
||||
records. This is likely to be necessary when dealing with large
|
||||
applications and databases, as record fields change over time.
|
||||
|
||||
The csv application suite does not currently support versions,
|
||||
although all of the necessary hooks are there.
|
||||
|
||||
The way versioning will work is as follows:
|
||||
|
||||
The XXX.desc file needs to support multiple version layouts.
|
||||
|
||||
The generated C language structure defined should be a superset
|
||||
of all of the interesting fields from all of the version
|
||||
layouts, regardless of which versions of the csv records those
|
||||
fields exist in.
|
||||
|
||||
When the csv layer is asked for a record, the record's version
|
||||
will provide a lookup into a separate database of field lists.
|
||||
That is, there will be another database which has key/data pairs
|
||||
where the key is a version number, and the data is the field
|
||||
list. At that point, it's relatively easy to map the fields
|
||||
to the structure as is currently done, except that some of the
|
||||
fields may not be filled in.
|
||||
|
||||
To determine if a field is filled in, in the structure, the
|
||||
application has to have an out-of-band value to put in that
|
||||
field during DbRecord initialization. If that's a problem, the
|
||||
alternative would be to add an additional field for each listed
|
||||
field -- if the additional field is set to 1, the listed field
|
||||
has been filled in, otherwise it hasn't. The csv code will
|
||||
support the notion of required fields, so in most cases the
|
||||
application won't need to check before simply using the field,
|
||||
it's only if a field isn't required and may be filled in that
|
||||
the check will be necessary.
|
||||
|
||||
TODO:
|
||||
Csv databases are not portable between machines of different
|
||||
byte orders. To make them portable, all of the 32-bit unsigned
|
||||
int fields currently written into the database should be
|
||||
converted to a standard byte order. This would include the
|
||||
version number and field count in the column-map record, and the
|
||||
version and field offsets in the other records.
|
||||
|
||||
Add Extended RE string matches.
|
||||
|
||||
Add APIs to replace the reading of a schema file, allow users to
|
||||
fill in a DbRecord structure and do a put on it. (Hard problem:
|
||||
how to flag fields that aren't filled in.)
|
||||
|
||||
Add a second sample file, and write the actual versioning code.
|
||||
405
examples_c/csv/code.c
Normal file
405
examples_c/csv/code.c
Normal file
@@ -0,0 +1,405 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2005,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: code.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include "csv.h"
|
||||
|
||||
typedef struct {
|
||||
char *name; /* Field name */
|
||||
char *upper; /* Field name in upper-case */
|
||||
datatype type; /* Data type */
|
||||
int indx; /* Index */
|
||||
} FIELD;
|
||||
|
||||
int code_source(void);
|
||||
int code_header(void);
|
||||
int desc_dump(void);
|
||||
int desc_load(void);
|
||||
char *type_to_string(datatype);
|
||||
int usage(void);
|
||||
|
||||
/*
|
||||
* Globals
|
||||
*/
|
||||
FILE *cfp; /* C source file */
|
||||
FILE *hfp; /* C source file */
|
||||
char *progname; /* Program name */
|
||||
int verbose; /* Verbose flag */
|
||||
|
||||
u_int field_cnt; /* Count of fields */
|
||||
FIELD *fields; /* Field list */
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
int ch;
|
||||
char *cfile, *hfile;
|
||||
|
||||
/* Initialize globals. */
|
||||
if ((progname = strrchr(argv[0], '/')) == NULL)
|
||||
progname = argv[0];
|
||||
else
|
||||
++progname;
|
||||
|
||||
/* Initialize arguments. */
|
||||
cfile = "csv_local.c"; /* Default header/source files */
|
||||
hfile = "csv_local.h";
|
||||
|
||||
/* Process arguments. */
|
||||
while ((ch = getopt(argc, argv, "c:f:h:v")) != EOF)
|
||||
switch (ch) {
|
||||
case 'c':
|
||||
cfile = optarg;
|
||||
break;
|
||||
case 'f':
|
||||
if (freopen(optarg, "r", stdin) == NULL) {
|
||||
fprintf(stderr,
|
||||
"%s: %s\n", optarg, strerror(errno));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case 'h':
|
||||
hfile = optarg;
|
||||
break;
|
||||
case 'v':
|
||||
++verbose;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
return (usage());
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (*argv != NULL)
|
||||
return (usage());
|
||||
|
||||
/* Load records from the input file. */
|
||||
if (desc_load())
|
||||
return (EXIT_FAILURE);
|
||||
|
||||
/* Dump records for debugging. */
|
||||
if (verbose && desc_dump())
|
||||
return (EXIT_FAILURE);
|
||||
|
||||
/* Open output files. */
|
||||
if ((cfp = fopen(cfile, "w")) == NULL) {
|
||||
fprintf(stderr,
|
||||
"%s: %s: %s\n", progname, cfile, strerror(errno));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
if ((hfp = fopen(hfile, "w")) == NULL) {
|
||||
fprintf(stderr,
|
||||
"%s: %s: %s\n", progname, hfile, strerror(errno));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Build the source and header files. */
|
||||
if (code_header())
|
||||
return (EXIT_FAILURE);
|
||||
if (code_source())
|
||||
return (EXIT_FAILURE);
|
||||
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/*
|
||||
* desc_load --
|
||||
* Load a description file.
|
||||
*/
|
||||
int
|
||||
desc_load()
|
||||
{
|
||||
u_int field_alloc;
|
||||
int version;
|
||||
char *p, *t, save_ch, buf[256];
|
||||
|
||||
field_alloc = version = 0;
|
||||
while (fgets(buf, sizeof(buf), stdin) != NULL) {
|
||||
if ((p = strchr(buf, '\n')) == NULL) {
|
||||
fprintf(stderr, "%s: input line too long\n", progname);
|
||||
return (1);
|
||||
}
|
||||
*p = '\0';
|
||||
|
||||
/* Skip leading whitespace. */
|
||||
for (p = buf; isspace(*p); ++p)
|
||||
;
|
||||
|
||||
/* Skip empty lines or lines beginning with '#'. */
|
||||
if (*p == '\0' || *p == '#')
|
||||
continue;
|
||||
|
||||
/* Get a version. */
|
||||
if (!version) {
|
||||
if (strncasecmp(
|
||||
p, "version", sizeof("version") - 1) == 0) {
|
||||
version = 1;
|
||||
continue;
|
||||
}
|
||||
fprintf(stderr,
|
||||
"%s: expected \"version\" line\n", progname);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Skip block close -- not currently useful, but when this
|
||||
* code supports versioned descriptions, it will matter.
|
||||
*/
|
||||
if (*p == '}') {
|
||||
version = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Allocate a new field structure as necessary. */
|
||||
if (field_cnt == field_alloc &&
|
||||
(fields = realloc(fields, field_alloc += 100)) == NULL) {
|
||||
fprintf(stderr, "%s: %s\n", progname, strerror(errno));
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Find the end of the field name. */
|
||||
for (t = p; *t != '\0' && !isspace(*t); ++t)
|
||||
;
|
||||
save_ch = *t;
|
||||
*t = '\0';
|
||||
if ((fields[field_cnt].name = strdup(p)) == NULL ||
|
||||
(fields[field_cnt].upper = strdup(p)) == NULL) {
|
||||
fprintf(stderr, "%s: %s\n", progname, strerror(errno));
|
||||
return (1);
|
||||
}
|
||||
*t = save_ch;
|
||||
p = t;
|
||||
|
||||
fields[field_cnt].indx = 0;
|
||||
fields[field_cnt].type = NOTSET;
|
||||
for (;;) {
|
||||
/* Skip to the next field, if any. */
|
||||
for (; *p != '\0' && isspace(*p); ++p)
|
||||
;
|
||||
if (*p == '\0')
|
||||
break;
|
||||
|
||||
/* Find the end of the field. */
|
||||
for (t = p; *t != '\0' && !isspace(*t); ++t)
|
||||
;
|
||||
save_ch = *t;
|
||||
*t = '\0';
|
||||
if (strcasecmp(p, "double") == 0)
|
||||
fields[field_cnt].type = DOUBLE;
|
||||
else if (strcasecmp(p, "index") == 0)
|
||||
fields[field_cnt].indx = 1;
|
||||
else if (strcasecmp(p, "string") == 0)
|
||||
fields[field_cnt].type = STRING;
|
||||
else if (strcasecmp(p, "unsigned_long") == 0)
|
||||
fields[field_cnt].type = UNSIGNED_LONG;
|
||||
else {
|
||||
fprintf(stderr,
|
||||
"%s: unknown keyword: %s\n", progname, p);
|
||||
return (1);
|
||||
}
|
||||
*t = save_ch;
|
||||
p = t;
|
||||
}
|
||||
|
||||
/* Create a copy of the field name that's upper-case. */
|
||||
for (p = fields[field_cnt].upper; *p != '\0'; ++p)
|
||||
if (islower(*p))
|
||||
*p = (char)toupper(*p);
|
||||
++field_cnt;
|
||||
}
|
||||
if (ferror(stdin)) {
|
||||
fprintf(stderr, "%s: stdin: %s\n", progname, strerror(errno));
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* desc_dump --
|
||||
* Dump a set of FIELD structures.
|
||||
*/
|
||||
int
|
||||
desc_dump()
|
||||
{
|
||||
FIELD *f;
|
||||
u_int i;
|
||||
|
||||
for (f = fields, i = 0; i < field_cnt; ++i, ++f) {
|
||||
fprintf(stderr, "field {%s}: (", f->name);
|
||||
switch (f->type) {
|
||||
case NOTSET:
|
||||
fprintf(stderr, "ignored");
|
||||
break;
|
||||
case DOUBLE:
|
||||
fprintf(stderr, "double");
|
||||
break;
|
||||
case STRING:
|
||||
fprintf(stderr, "string");
|
||||
break;
|
||||
case UNSIGNED_LONG:
|
||||
fprintf(stderr, "unsigned_long");
|
||||
break;
|
||||
}
|
||||
if (f->indx)
|
||||
fprintf(stderr, ", indexed");
|
||||
fprintf(stderr, ")\n");
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* code_header --
|
||||
* Print out the C #include file.
|
||||
*/
|
||||
int
|
||||
code_header()
|
||||
{
|
||||
FIELD *f;
|
||||
u_int i;
|
||||
|
||||
fprintf(hfp, "/*\n");
|
||||
fprintf(hfp, " * DO NOT EDIT: automatically built by %s.\n", progname);
|
||||
fprintf(hfp, " *\n");
|
||||
fprintf(hfp, " * Record structure.\n");
|
||||
fprintf(hfp, " */\n");
|
||||
fprintf(hfp, "typedef struct __DbRecord {\n");
|
||||
fprintf(hfp, "\tu_int32_t\t recno;\t\t/* Record number */\n");
|
||||
fprintf(hfp, "\n");
|
||||
fprintf(hfp, "\t/*\n");
|
||||
fprintf(hfp, "\t * Management fields\n");
|
||||
fprintf(hfp, "\t */\n");
|
||||
fprintf(hfp, "\tvoid\t\t*raw;\t\t/* Memory returned by DB */\n");
|
||||
fprintf(hfp, "\tu_char\t\t*record;\t/* Raw record */\n");
|
||||
fprintf(hfp, "\tsize_t\t\t record_len;\t/* Raw record length */\n\n");
|
||||
fprintf(hfp, "\tu_int32_t\t field_count;\t/* Field count */\n");
|
||||
fprintf(hfp, "\tu_int32_t\t version;\t/* Record version */\n\n");
|
||||
fprintf(hfp, "\tu_int32_t\t*offset;\t/* Offset table */\n");
|
||||
fprintf(hfp, "\n");
|
||||
|
||||
fprintf(hfp, "\t/*\n");
|
||||
fprintf(hfp, "\t * Indexed fields\n");
|
||||
fprintf(hfp, "\t */\n");
|
||||
for (f = fields, i = 0; i < field_cnt; ++i, ++f) {
|
||||
if (f->type == NOTSET)
|
||||
continue;
|
||||
if (i != 0)
|
||||
fprintf(hfp, "\n");
|
||||
fprintf(hfp, "#define CSV_INDX_%s\t%d\n", f->upper, i + 1);
|
||||
switch (f->type) {
|
||||
case NOTSET:
|
||||
/* NOTREACHED */
|
||||
abort();
|
||||
break;
|
||||
case DOUBLE:
|
||||
fprintf(hfp, "\tdouble\t\t %s;\n", f->name);
|
||||
break;
|
||||
case STRING:
|
||||
fprintf(hfp, "\tchar\t\t*%s;\n", f->name);
|
||||
break;
|
||||
case UNSIGNED_LONG:
|
||||
fprintf(hfp, "\tu_long\t\t %s;\n", f->name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
fprintf(hfp, "} DbRecord;\n");
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* code_source --
|
||||
* Print out the C structure initialization.
|
||||
*/
|
||||
int
|
||||
code_source()
|
||||
{
|
||||
FIELD *f;
|
||||
u_int i;
|
||||
|
||||
fprintf(cfp, "/*\n");
|
||||
fprintf(cfp,
|
||||
" * DO NOT EDIT: automatically built by %s.\n", progname);
|
||||
fprintf(cfp, " *\n");
|
||||
fprintf(cfp, " * Initialized record structure.\n");
|
||||
fprintf(cfp, " */\n");
|
||||
fprintf(cfp, "\n");
|
||||
fprintf(cfp, "#include \"csv.h\"\n");
|
||||
fprintf(cfp, "#include \"csv_local.h\"\n");
|
||||
fprintf(cfp, "\n");
|
||||
fprintf(cfp, "DbRecord DbRecord_base = {\n");
|
||||
fprintf(cfp, "\t0,\t\t/* Record number */\n");
|
||||
fprintf(cfp, "\tNULL,\t\t/* Memory returned by DB */\n");
|
||||
fprintf(cfp, "\tNULL,\t\t/* Raw record */\n");
|
||||
fprintf(cfp, "\t0,\t\t/* Raw record length */\n");
|
||||
fprintf(cfp, "\t%d,\t\t/* Field count */\n", field_cnt);
|
||||
fprintf(cfp, "\t0,\t\t/* Record version */\n");
|
||||
fprintf(cfp, "\tNULL,\t\t/* Offset table */\n");
|
||||
fprintf(cfp, "\n");
|
||||
for (f = fields, i = 0; i < field_cnt; ++i, ++f) {
|
||||
if (f->type == NOTSET)
|
||||
continue;
|
||||
switch (f->type) {
|
||||
case NOTSET:
|
||||
abort();
|
||||
/* NOTREACHED */
|
||||
break;
|
||||
case DOUBLE:
|
||||
case UNSIGNED_LONG:
|
||||
fprintf(cfp, "\t0,\t\t/* %s */\n", f->name);
|
||||
break;
|
||||
case STRING:
|
||||
fprintf(cfp, "\tNULL,\t\t/* %s */\n", f->name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
fprintf(cfp, "};\n");
|
||||
|
||||
fprintf(cfp, "\n");
|
||||
fprintf(cfp, "DbField fieldlist[] = {\n");
|
||||
for (f = fields, i = 0; i < field_cnt; ++i, ++f) {
|
||||
if (f->type == NOTSET)
|
||||
continue;
|
||||
fprintf(cfp, "\t{ \"%s\",", f->name);
|
||||
fprintf(cfp, " CSV_INDX_%s,", f->upper);
|
||||
fprintf(cfp, "\n\t %s,", type_to_string(f->type));
|
||||
fprintf(cfp, " %d,", f->indx ? 1 : 0);
|
||||
fprintf(cfp, " NULL,");
|
||||
fprintf(cfp, " FIELD_OFFSET(%s)},\n", f->name);
|
||||
}
|
||||
fprintf(cfp, "\t{NULL, 0, STRING, 0, NULL, 0}\n};\n");
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
char *
|
||||
type_to_string(type)
|
||||
datatype type;
|
||||
{
|
||||
switch (type) {
|
||||
case NOTSET:
|
||||
return ("NOTSET");
|
||||
case DOUBLE:
|
||||
return ("DOUBLE");
|
||||
case STRING:
|
||||
return ("STRING");
|
||||
case UNSIGNED_LONG:
|
||||
return ("UNSIGNED_LONG");
|
||||
}
|
||||
|
||||
abort();
|
||||
/* NOTREACHED */
|
||||
}
|
||||
|
||||
int
|
||||
usage()
|
||||
{
|
||||
(void)fprintf(stderr,
|
||||
"usage: %s [-v] [-c source-file] [-f input] [-h header-file]\n",
|
||||
progname);
|
||||
exit(1);
|
||||
}
|
||||
101
examples_c/csv/csv.h
Normal file
101
examples_c/csv/csv.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2005,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: csv.h 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
|
||||
#include <direct.h>
|
||||
#include <db.h>
|
||||
|
||||
extern int getopt(int, char * const *, const char *);
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
#else
|
||||
#define HAVE_WILDCARD_SUPPORT 1
|
||||
|
||||
#include <regex.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "db.h"
|
||||
|
||||
/*
|
||||
* MAP_VERSION
|
||||
* This code has hooks for versioning, but does not directly support it.
|
||||
* See the README file for details.
|
||||
*/
|
||||
#define MAP_VERSION 1
|
||||
|
||||
/*
|
||||
* Supported formats.
|
||||
*
|
||||
* FORMAT_NL: <nl> separated
|
||||
* FORMAT_EXCEL: Excel dumped flat text.
|
||||
*/
|
||||
typedef enum { FORMAT_EXCEL, FORMAT_NL } input_fmt;
|
||||
|
||||
/*
|
||||
* OFFSET_LEN
|
||||
* The length of any item can be calculated from the two offset fields.
|
||||
* OFFSET_OOB
|
||||
* An offset that's illegal, used to detect unavailable fields.
|
||||
*/
|
||||
#define OFFSET_LEN(offset, indx) \
|
||||
(((offset)[(indx) + 1] - (offset)[(indx)]) - 1)
|
||||
|
||||
#define OFFSET_OOB 0
|
||||
|
||||
/*
|
||||
* Field comparison operators.
|
||||
*/
|
||||
typedef enum { EQ=1, NEQ, GT, GTEQ, LT, LTEQ, WC, NWC } OPERATOR;
|
||||
|
||||
/*
|
||||
* Supported data types.
|
||||
*/
|
||||
typedef enum { NOTSET=1, DOUBLE, STRING, UNSIGNED_LONG } datatype;
|
||||
|
||||
/*
|
||||
* C structure that describes the csv fields.
|
||||
*/
|
||||
typedef struct {
|
||||
char *name; /* Field name */
|
||||
u_int32_t fieldno; /* Field index */
|
||||
datatype type; /* Data type */
|
||||
|
||||
int indx; /* Indexed */
|
||||
DB *secondary; /* Secondary index handle */
|
||||
|
||||
#define FIELD_OFFSET(field) ((size_t)(&(((DbRecord *)0)->field)))
|
||||
size_t offset; /* DbRecord field offset */
|
||||
} DbField;
|
||||
|
||||
/*
|
||||
* Globals
|
||||
*/
|
||||
extern DB *db; /* Primary database */
|
||||
extern DbField fieldlist[]; /* Field list */
|
||||
extern DB_ENV *dbenv; /* Database environment */
|
||||
extern char *progname; /* Program name */
|
||||
extern int verbose; /* Program verbosity */
|
||||
#ifdef _WIN32
|
||||
#undef strcasecmp
|
||||
#define strcasecmp _stricmp
|
||||
#undef strncasecmp
|
||||
#define strncasecmp _strnicmp
|
||||
#define mkdir(d, perm) _mkdir(d)
|
||||
#endif
|
||||
37
examples_c/csv/csv_extern.h
Normal file
37
examples_c/csv/csv_extern.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2005,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: csv_extern.h 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
extern DbRecord DbRecord_base; /* Initialized structure. */
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
extern int DbRecord_discard(DbRecord *);
|
||||
extern int DbRecord_init(const DBT *, const DBT *, DbRecord *);
|
||||
extern void DbRecord_print(DbRecord *, FILE *);
|
||||
extern int DbRecord_read(u_long, DbRecord *);
|
||||
extern int DbRecord_search_field_name(char *, char *, OPERATOR);
|
||||
extern int DbRecord_search_field_number(u_int, char *, OPERATOR);
|
||||
extern int compare_double(DB *, const DBT *, const DBT *);
|
||||
extern int compare_string(DB *, const DBT *, const DBT *);
|
||||
extern int compare_ulong(DB *, const DBT *, const DBT *);
|
||||
extern int csv_env_close(void);
|
||||
extern int csv_env_open(const char *, int);
|
||||
extern int csv_secondary_close(void);
|
||||
extern int csv_secondary_open(void);
|
||||
extern int entry_print(void *, size_t, u_int32_t);
|
||||
extern int field_cmp_double(void *, void *, OPERATOR);
|
||||
extern int field_cmp_re(void *, void *, OPERATOR);
|
||||
extern int field_cmp_string(void *, void *, OPERATOR);
|
||||
extern int field_cmp_ulong(void *, void *, OPERATOR);
|
||||
extern int input_load(input_fmt, u_long);
|
||||
extern int query(char *, int *);
|
||||
extern int query_interactive(void);
|
||||
extern int secondary_callback(DB *, const DBT *, const DBT *, DBT *);
|
||||
extern int strtod_err(char *, double *);
|
||||
extern int strtoul_err(char *, u_long *);
|
||||
244
examples_c/csv/db.c
Normal file
244
examples_c/csv/db.c
Normal file
@@ -0,0 +1,244 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2005,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: db.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include "csv.h"
|
||||
#include "csv_local.h"
|
||||
#include "csv_extern.h"
|
||||
|
||||
static int compare_uint32(DB *, const DBT *, const DBT *);
|
||||
|
||||
/*
|
||||
* csv_env_init --
|
||||
* Initialize the database environment.
|
||||
*/
|
||||
int
|
||||
csv_env_open(const char *home, int is_rdonly)
|
||||
{
|
||||
int ret;
|
||||
|
||||
dbenv = NULL;
|
||||
db = NULL;
|
||||
|
||||
/* Create a database environment handle. */
|
||||
if ((ret = db_env_create(&dbenv, 0)) != 0) {
|
||||
fprintf(stderr,
|
||||
"%s: db_env_create: %s\n", progname, db_strerror(ret));
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Configure Berkeley DB error reporting to stderr, with our program
|
||||
* name as the prefix.
|
||||
*/
|
||||
dbenv->set_errfile(dbenv, stderr);
|
||||
dbenv->set_errpfx(dbenv, progname);
|
||||
|
||||
/*
|
||||
* The default Berkeley DB cache size is fairly small; configure a
|
||||
* 1MB cache for now. This value will require tuning in the future.
|
||||
*/
|
||||
if ((ret = dbenv->set_cachesize(dbenv, 0, 1048576, 1)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB_ENV->set_cachesize");
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* We may be working with an existing environment -- try and join it.
|
||||
* If that fails, create a new database environment; for now, we only
|
||||
* need a cache, no logging, locking, or transactions.
|
||||
*/
|
||||
if ((ret = dbenv->open(dbenv, home,
|
||||
DB_JOINENV | DB_USE_ENVIRON, 0)) != 0 &&
|
||||
(ret = dbenv->open(dbenv, home,
|
||||
DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_USE_ENVIRON, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB_ENV->open");
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Create the primary database handle. */
|
||||
if ((ret = db_create(&db, dbenv, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "db_create");
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Records may be relatively large -- use a large page size.
|
||||
*/
|
||||
if ((ret = db->set_pagesize(db, 32 * 1024)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB->set_pagesize");
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* The primary database uses an integer as its key; on little-endian
|
||||
* machines, integers sort badly using the default Berkeley DB sort
|
||||
* function (which is lexicographic). Specify a comparison function
|
||||
* for the database.
|
||||
*/
|
||||
if ((ret = db->set_bt_compare(db, compare_uint32)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB->set_bt_compare");
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Open the primary database. */
|
||||
if ((ret = db->open(db, NULL,
|
||||
"primary", NULL, DB_BTREE, is_rdonly ? 0 : DB_CREATE, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB->open: primary");
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Open the secondaries. */
|
||||
if ((ret = csv_secondary_open()) != 0)
|
||||
return (1);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* csv_env_close --
|
||||
* Discard the database environment.
|
||||
*/
|
||||
int
|
||||
csv_env_close()
|
||||
{
|
||||
int ret, t_ret;
|
||||
|
||||
ret = 0;
|
||||
|
||||
/* Close the secondaries. */
|
||||
ret = csv_secondary_close();
|
||||
|
||||
/* Close the primary handle. */
|
||||
if (db != NULL && (t_ret = db->close(db, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB->close");
|
||||
if (ret == 0)
|
||||
ret = t_ret;
|
||||
}
|
||||
if ((t_ret = dbenv->close(dbenv, 0)) != 0) {
|
||||
fprintf(stderr,
|
||||
"%s: DB_ENV->close: %s\n", progname, db_strerror(ret));
|
||||
if (ret == 0)
|
||||
ret = t_ret;
|
||||
}
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* csv_secondary_open --
|
||||
* Open any secondary indices.
|
||||
*/
|
||||
int
|
||||
csv_secondary_open()
|
||||
{
|
||||
DB *sdb;
|
||||
DbField *f;
|
||||
int ret, (*fcmp)(DB *, const DBT *, const DBT *);
|
||||
|
||||
/*
|
||||
* Create secondary database handles.
|
||||
*/
|
||||
for (f = fieldlist; f->name != NULL; ++f) {
|
||||
if (f->indx == 0)
|
||||
continue;
|
||||
|
||||
if ((ret = db_create(&sdb, dbenv, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "db_create");
|
||||
return (1);
|
||||
}
|
||||
sdb->app_private = f;
|
||||
|
||||
/* Keys are small, use a relatively small page size. */
|
||||
if ((ret = sdb->set_pagesize(sdb, 8 * 1024)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB->set_pagesize");
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sort the database based on the underlying type. Skip
|
||||
* strings, Berkeley DB defaults to lexicographic sort.
|
||||
*/
|
||||
switch (f->type) {
|
||||
case DOUBLE:
|
||||
fcmp = compare_double;
|
||||
break;
|
||||
case UNSIGNED_LONG:
|
||||
fcmp = compare_ulong;
|
||||
break;
|
||||
case NOTSET:
|
||||
case STRING:
|
||||
default:
|
||||
fcmp = NULL;
|
||||
break;
|
||||
}
|
||||
if (fcmp != NULL &&
|
||||
(ret = sdb->set_bt_compare(sdb, fcmp)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB->set_bt_compare");
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Always configure secondaries for sorted duplicates. */
|
||||
if ((ret = sdb->set_flags(sdb, DB_DUPSORT)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB->set_flags");
|
||||
return (1);
|
||||
}
|
||||
if ((ret = sdb->set_dup_compare(sdb, compare_ulong)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB->set_dup_compare");
|
||||
return (1);
|
||||
}
|
||||
|
||||
if ((ret = sdb->open(
|
||||
sdb, NULL, f->name, NULL, DB_BTREE, DB_CREATE, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB->open: %s", f->name);
|
||||
return (1);
|
||||
}
|
||||
if ((ret = sdb->associate(
|
||||
db, NULL, sdb, secondary_callback, DB_CREATE)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB->set_associate");
|
||||
return (1);
|
||||
}
|
||||
f->secondary = sdb;
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* csv_secondary_close --
|
||||
* Close any secondary indices.
|
||||
*/
|
||||
int
|
||||
csv_secondary_close()
|
||||
{
|
||||
DbField *f;
|
||||
int ret, t_ret;
|
||||
|
||||
ret = 0;
|
||||
for (f = fieldlist; f->name != NULL; ++f)
|
||||
if (f->secondary != NULL && (t_ret =
|
||||
f->secondary->close(f->secondary, 0)) != 0 && ret == 0)
|
||||
ret = t_ret;
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* compare_uint32 --
|
||||
* Compare two keys.
|
||||
*/
|
||||
static int
|
||||
compare_uint32(DB *db_arg, const DBT *a_arg, const DBT *b_arg)
|
||||
{
|
||||
u_int32_t a, b;
|
||||
|
||||
db_arg = db_arg; /* Quiet compiler. */
|
||||
|
||||
memcpy(&a, a_arg->data, sizeof(u_int32_t));
|
||||
memcpy(&b, b_arg->data, sizeof(u_int32_t));
|
||||
return (a > b ? 1 : ((a < b) ? -1 : 0));
|
||||
}
|
||||
346
examples_c/csv/load.c
Normal file
346
examples_c/csv/load.c
Normal file
@@ -0,0 +1,346 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2005,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: load.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include "csv.h"
|
||||
#include "csv_local.h"
|
||||
#include "csv_extern.h"
|
||||
|
||||
typedef enum { GL_OK, GL_EOF, GL_FAIL } getline_status;
|
||||
|
||||
static int input_field_count(const char *, size_t, u_int32_t *);
|
||||
static getline_status
|
||||
input_getline(char **, size_t *, size_t *);
|
||||
static int input_put_alloc(u_int32_t **, size_t *, size_t, u_int32_t);
|
||||
static int input_set_offset(u_int32_t *, char *, size_t, u_int32_t);
|
||||
|
||||
static input_fmt ifmt; /* Input format. */
|
||||
static u_long record_count = 0; /* Input record count for errors. */
|
||||
static u_long version; /* Version we're loading. */
|
||||
|
||||
/*
|
||||
* input_load --
|
||||
* Read the input file and load new records into the database.
|
||||
*/
|
||||
int
|
||||
input_load(input_fmt ifmt_arg, u_long version_arg)
|
||||
{
|
||||
getline_status gtl_status;
|
||||
DBT key, data;
|
||||
DBC *cursor;
|
||||
u_int32_t field_count, primary_key, *put_line;
|
||||
size_t input_len, len, put_len;
|
||||
int is_first, ret;
|
||||
char *input_line;
|
||||
|
||||
field_count = 0; /* Shut the compiler up. */
|
||||
|
||||
/* ifmt and version are global to this file. */
|
||||
ifmt = ifmt_arg;
|
||||
version = version_arg;
|
||||
|
||||
/*
|
||||
* The primary key for the database is a unique number. Find out the
|
||||
* last unique number allocated in this database by opening a cursor
|
||||
* and fetching the last record.
|
||||
*/
|
||||
if ((ret = db->cursor(db, NULL, &cursor, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB->cursor");
|
||||
return (1);
|
||||
}
|
||||
memset(&key, 0, sizeof(key));
|
||||
memset(&data, 0, sizeof(data));
|
||||
if ((ret = cursor->c_get(cursor, &key, &data, DB_LAST)) != 0)
|
||||
if (ret == DB_NOTFOUND)
|
||||
primary_key = 0;
|
||||
else {
|
||||
dbenv->err(dbenv, ret, "DB->cursor: DB_LAST");
|
||||
return (1);
|
||||
}
|
||||
else
|
||||
memcpy(&primary_key, key.data, sizeof(primary_key));
|
||||
if ((ret = cursor->c_close(cursor)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DBC->close");
|
||||
return (1);
|
||||
}
|
||||
if (verbose)
|
||||
dbenv->errx(dbenv,
|
||||
"maximum existing record in the database is %lu",
|
||||
(u_long)primary_key);
|
||||
|
||||
key.data = &primary_key;
|
||||
key.size = sizeof(primary_key);
|
||||
input_line = NULL;
|
||||
put_line = NULL;
|
||||
input_len = put_len = 0;
|
||||
|
||||
/*
|
||||
* See the README file for a description of the file input format.
|
||||
*/
|
||||
for (is_first = 1; (gtl_status =
|
||||
input_getline(&input_line, &input_len, &len)) == GL_OK;) {
|
||||
++record_count;
|
||||
if (verbose > 1)
|
||||
dbenv->errx(dbenv, "reading %lu", (u_long)record_count);
|
||||
|
||||
/* The first non-blank line of the input is a column map. */
|
||||
if (is_first) {
|
||||
is_first = 0;
|
||||
|
||||
/* Count the fields we're expecting in the input. */
|
||||
if (input_field_count(
|
||||
input_line, len, &field_count) != 0)
|
||||
return (1);
|
||||
|
||||
}
|
||||
|
||||
/* Allocate room for the table of offsets. */
|
||||
if (input_put_alloc(
|
||||
&put_line, &put_len, len, field_count) != 0)
|
||||
return (1);
|
||||
|
||||
/*
|
||||
* Build the offset table and create the record we're
|
||||
* going to store.
|
||||
*/
|
||||
if (input_set_offset(put_line,
|
||||
input_line, len, field_count) != 0)
|
||||
return (1);
|
||||
|
||||
++primary_key;
|
||||
|
||||
memcpy(put_line + (field_count + 2), input_line, len);
|
||||
data.data = put_line;
|
||||
data.size = (field_count + 2) * sizeof(u_int32_t) + len;
|
||||
|
||||
if (verbose > 1)
|
||||
(void)entry_print(
|
||||
data.data, data.size, field_count);
|
||||
|
||||
/* Load the key/data pair into the database. */
|
||||
if ((ret = db->put(db, NULL, &key, &data, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret,
|
||||
"DB->put: %lu", (u_long)primary_key);
|
||||
return (1);
|
||||
}
|
||||
}
|
||||
|
||||
if (gtl_status != GL_EOF)
|
||||
return (1);
|
||||
|
||||
if (verbose)
|
||||
dbenv->errx(dbenv,
|
||||
"%lu records read from the input file into the database",
|
||||
record_count);
|
||||
|
||||
/*
|
||||
* This program isn't transactional, limit the window for corruption.
|
||||
*/
|
||||
if ((ret = db->sync(db, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB->sync");
|
||||
return (1);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* input_getline --
|
||||
* Read in a line of input into a buffer.
|
||||
*/
|
||||
static getline_status
|
||||
input_getline(char **input_linep, size_t *input_lenp, size_t *lenp)
|
||||
{
|
||||
size_t input_len, len;
|
||||
int ch;
|
||||
char *input_line, *p, *endp;
|
||||
|
||||
input_line = *input_linep;
|
||||
input_len = *input_lenp;
|
||||
|
||||
p = input_line;
|
||||
endp = input_line + input_len;
|
||||
|
||||
for (len = 0; (ch = getchar()) != EOF;) {
|
||||
if (ch == '\0') /* Strip <nul> (\000) bytes. */
|
||||
continue;
|
||||
switch (ifmt) {
|
||||
case FORMAT_NL:
|
||||
if (ch == '\n')
|
||||
goto end;
|
||||
break;
|
||||
case FORMAT_EXCEL:
|
||||
/* Strip <nl> (\012) bytes. */
|
||||
if (ch == '\n')
|
||||
continue;
|
||||
/*
|
||||
* <cr> (\015) bytes terminate lines.
|
||||
* Skip blank lines.
|
||||
*/
|
||||
if (ch == '\015') {
|
||||
if (len == 0)
|
||||
continue;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if (input_line == endp) {
|
||||
input_len += 256;
|
||||
input_len *= 2;
|
||||
if ((input_line =
|
||||
realloc(input_line, input_len)) == NULL) {
|
||||
dbenv->err(dbenv, errno,
|
||||
"unable to allocate %lu bytes for record",
|
||||
(u_long)input_len);
|
||||
return (GL_FAIL);
|
||||
}
|
||||
p = input_line;
|
||||
endp = p + input_len;
|
||||
}
|
||||
|
||||
if (isprint(ch)) { /* Strip unprintables. */
|
||||
*p++ = (char)ch;
|
||||
++len;
|
||||
}
|
||||
}
|
||||
|
||||
end: if (len == 0)
|
||||
return (GL_EOF);
|
||||
|
||||
*lenp = len;
|
||||
*input_linep = input_line;
|
||||
*input_lenp = input_len;
|
||||
|
||||
return (GL_OK);
|
||||
}
|
||||
|
||||
/*
|
||||
* input_field_count --
|
||||
* Count the fields in the line.
|
||||
*/
|
||||
static int
|
||||
input_field_count(const char *line, size_t len, u_int32_t *field_countp)
|
||||
{
|
||||
u_int32_t field_count;
|
||||
int quoted;
|
||||
|
||||
field_count = 1;
|
||||
|
||||
/*
|
||||
* There are N-1 separators for N fields, that is, "a,b,c" is three
|
||||
* fields, with two comma separators.
|
||||
*/
|
||||
switch (ifmt) {
|
||||
case FORMAT_EXCEL:
|
||||
quoted = 0;
|
||||
for (field_count = 1; len > 0; ++line, --len)
|
||||
if (*line == '"')
|
||||
quoted = !quoted;
|
||||
else if (*line == ',' && !quoted)
|
||||
++field_count;
|
||||
break;
|
||||
case FORMAT_NL:
|
||||
for (field_count = 1; len > 0; ++line, --len)
|
||||
if (*line == ',')
|
||||
++field_count;
|
||||
break;
|
||||
}
|
||||
*field_countp = field_count;
|
||||
|
||||
if (verbose)
|
||||
dbenv->errx(dbenv,
|
||||
"input file made up of %lu fields", (u_int)field_count);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* input_put_alloc --
|
||||
* Allocate room for the offset table plus the input.
|
||||
*/
|
||||
static int
|
||||
input_put_alloc(u_int32_t **put_linep,
|
||||
size_t *put_lenp, size_t len, u_int32_t field_count)
|
||||
{
|
||||
size_t total;
|
||||
|
||||
total = (field_count + 2) * sizeof(u_int32_t) + len;
|
||||
if (total > *put_lenp &&
|
||||
(*put_linep = realloc(*put_linep, *put_lenp += total)) == NULL) {
|
||||
dbenv->err(dbenv, errno,
|
||||
"unable to allocate %lu bytes for record",
|
||||
(u_long)*put_lenp);
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* input_set_offset --
|
||||
* Build an offset table and record combination.
|
||||
*/
|
||||
static int
|
||||
input_set_offset(u_int32_t *put_line,
|
||||
char *input_line, size_t len, u_int32_t field_count)
|
||||
{
|
||||
u_int32_t *op;
|
||||
int quoted;
|
||||
char *p, *endp;
|
||||
|
||||
op = put_line;
|
||||
|
||||
/* The first field is the version number. */
|
||||
*op++ = version;
|
||||
|
||||
/*
|
||||
* Walk the input line, looking for comma separators. It's an error
|
||||
* to have too many or too few fields.
|
||||
*/
|
||||
*op++ = 0;
|
||||
quoted = 0;
|
||||
for (p = input_line, endp = input_line + len;; ++p) {
|
||||
if (ifmt == FORMAT_EXCEL && p < endp) {
|
||||
if (*p == '"')
|
||||
quoted = !quoted;
|
||||
if (quoted)
|
||||
continue;
|
||||
}
|
||||
if (*p == ',' || p == endp) {
|
||||
if (field_count == 0) {
|
||||
dbenv->errx(dbenv,
|
||||
"record %lu: too many fields in the record",
|
||||
record_count);
|
||||
return (1);
|
||||
}
|
||||
--field_count;
|
||||
|
||||
*op++ = (u_int32_t)(p - input_line) + 1;
|
||||
|
||||
if (verbose > 1)
|
||||
dbenv->errx(dbenv,
|
||||
"offset %lu: {%.*s}", op[-1],
|
||||
OFFSET_LEN(op, -2), input_line + op[-2]);
|
||||
|
||||
/*
|
||||
* Don't insert a new field if the input lines ends
|
||||
* in a comma.
|
||||
*/
|
||||
if (p == endp || p + 1 == endp)
|
||||
break;
|
||||
}
|
||||
}
|
||||
*op++ = (u_int32_t)(p - input_line);
|
||||
|
||||
if (field_count != 0) {
|
||||
dbenv->errx(dbenv,
|
||||
"record %lu: not enough fields in the record",
|
||||
record_count);
|
||||
return (1);
|
||||
}
|
||||
memcpy(op, input_line, len);
|
||||
|
||||
return (0);
|
||||
}
|
||||
117
examples_c/csv/load_main.c
Normal file
117
examples_c/csv/load_main.c
Normal file
@@ -0,0 +1,117 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2005,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: load_main.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include "csv.h"
|
||||
#include "csv_local.h"
|
||||
#include "csv_extern.h"
|
||||
|
||||
static int usage(void);
|
||||
|
||||
/*
|
||||
* Globals
|
||||
*/
|
||||
DB_ENV *dbenv; /* Database environment */
|
||||
DB *db; /* Primary database */
|
||||
DB **secondary; /* Secondaries */
|
||||
int verbose; /* Program verbosity */
|
||||
char *progname; /* Program name */
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
input_fmt ifmt;
|
||||
u_long version;
|
||||
int ch, ret, t_ret;
|
||||
char *home;
|
||||
|
||||
/* Initialize globals. */
|
||||
dbenv = NULL;
|
||||
db = NULL;
|
||||
if ((progname = strrchr(argv[0], '/')) == NULL)
|
||||
progname = argv[0];
|
||||
else
|
||||
++progname;
|
||||
verbose = 0;
|
||||
|
||||
/* Initialize arguments. */
|
||||
home = NULL;
|
||||
ifmt = FORMAT_NL;
|
||||
version = 1;
|
||||
|
||||
/* Process arguments. */
|
||||
while ((ch = getopt(argc, argv, "F:f:h:V:v")) != EOF)
|
||||
switch (ch) {
|
||||
case 'f':
|
||||
if (freopen(optarg, "r", stdin) == NULL) {
|
||||
fprintf(stderr,
|
||||
"%s: %s\n", optarg, db_strerror(errno));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case 'F':
|
||||
if (strcasecmp(optarg, "excel") == 0) {
|
||||
ifmt = FORMAT_EXCEL;
|
||||
break;
|
||||
}
|
||||
return (usage());
|
||||
case 'h':
|
||||
home = optarg;
|
||||
break;
|
||||
case 'V':
|
||||
if (strtoul_err(optarg, &version))
|
||||
return (EXIT_FAILURE);
|
||||
break;
|
||||
case 'v':
|
||||
++verbose;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
return (usage());
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (*argv != NULL)
|
||||
return (usage());
|
||||
|
||||
/*
|
||||
* The home directory may not exist -- try and create it. We don't
|
||||
* bother to distinguish between failure to create it and it already
|
||||
* existing, as the database environment open will fail if we aren't
|
||||
* successful.
|
||||
*/
|
||||
if (home == NULL)
|
||||
home = getenv("DB_HOME");
|
||||
if (home != NULL)
|
||||
(void)mkdir(home, S_IRWXU);
|
||||
|
||||
/* Create or join the database environment. */
|
||||
if (csv_env_open(home, 0) != 0)
|
||||
return (EXIT_FAILURE);
|
||||
|
||||
/* Load records into the database. */
|
||||
ret = input_load(ifmt, version);
|
||||
|
||||
/* Close the database environment. */
|
||||
if ((t_ret = csv_env_close()) != 0 && ret == 0)
|
||||
ret = t_ret;
|
||||
|
||||
return (ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/*
|
||||
* usage --
|
||||
* Program usage message.
|
||||
*/
|
||||
static int
|
||||
usage(void)
|
||||
{
|
||||
(void)fprintf(stderr,
|
||||
"usage: %s [-v] [-F excel] [-f csv-file] [-h home]\n", progname);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
241
examples_c/csv/query.c
Normal file
241
examples_c/csv/query.c
Normal file
@@ -0,0 +1,241 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2005,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: query.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include "csv.h"
|
||||
#include "csv_local.h"
|
||||
#include "csv_extern.h"
|
||||
|
||||
static int query_by_field(char *);
|
||||
static int query_fieldlist(char *);
|
||||
static int query_help(char *);
|
||||
static int query_usage(void);
|
||||
|
||||
typedef struct _cmdtab {
|
||||
char *cmd; /* Command name */
|
||||
int (*f)(char *); /* Underlying function. */
|
||||
char *help; /* Help message. */
|
||||
} CMDTAB;
|
||||
|
||||
static CMDTAB cmdtab[] = {
|
||||
{ "?",
|
||||
query_help,
|
||||
"?\t\tDisplay help screen" },
|
||||
{ "exit",
|
||||
NULL,
|
||||
"exit\t\tExit program" },
|
||||
{ "fields",
|
||||
query_fieldlist,
|
||||
"fields\t\tDisplay list of field names" },
|
||||
{ "help",
|
||||
query_help,
|
||||
"help\t\tDisplay help screen" },
|
||||
{ "quit",
|
||||
NULL,
|
||||
"quit\t\tExit program" },
|
||||
{ NULL,
|
||||
query_by_field,
|
||||
"field[op]value\tDisplay fields by value (=, !=, <, <=, >, >=, ~, !~)" },
|
||||
{ NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
/*
|
||||
* query_interactive --
|
||||
* Allow the user to interactively query the database.
|
||||
*/
|
||||
int
|
||||
query_interactive()
|
||||
{
|
||||
int done;
|
||||
char *p, input[256];
|
||||
|
||||
for (;;) {
|
||||
printf("Query: ");
|
||||
(void)fflush(stdout);
|
||||
if (fgets(input, sizeof(input), stdin) == NULL) {
|
||||
printf("\n");
|
||||
if (ferror(stdin)) {
|
||||
dbenv->err(dbenv, errno,
|
||||
"error occurred reading from stdin");
|
||||
return (1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ((p = strchr(input, '\n')) == NULL) {
|
||||
dbenv->errx(dbenv, "input buffer too small");
|
||||
return (1);
|
||||
}
|
||||
*p = '\0';
|
||||
if (query(input, &done) != 0)
|
||||
return (1);
|
||||
if (done != 0)
|
||||
break;
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* query --
|
||||
* Process a query.
|
||||
*/
|
||||
int
|
||||
query(char *cmd, int *donep)
|
||||
{
|
||||
CMDTAB *p;
|
||||
|
||||
if (donep != NULL)
|
||||
*donep = 0;
|
||||
|
||||
for (p = cmdtab; p->cmd != NULL; ++p)
|
||||
if (p->cmd != NULL &&
|
||||
strncasecmp(cmd, p->cmd, strlen(p->cmd)) == 0)
|
||||
break;
|
||||
|
||||
if (p->cmd == NULL)
|
||||
return (query_by_field(cmd));
|
||||
|
||||
if (p->f == NULL) {
|
||||
if (donep != NULL)
|
||||
*donep = 1;
|
||||
return (0);
|
||||
}
|
||||
|
||||
return (p->f(cmd));
|
||||
}
|
||||
|
||||
/*
|
||||
* query_by_field --
|
||||
* Query the primary database by field.
|
||||
*/
|
||||
static int
|
||||
query_by_field(char *input)
|
||||
{
|
||||
OPERATOR operator;
|
||||
size_t len;
|
||||
char *field, *op, *value;
|
||||
|
||||
/*
|
||||
* We expect to see "field [op] value" -- figure it out.
|
||||
*
|
||||
* Skip leading whitespace.
|
||||
*/
|
||||
while (isspace(*input))
|
||||
++input;
|
||||
|
||||
/*
|
||||
* Find an operator, and it better not start the string.
|
||||
*/
|
||||
if ((len = strcspn(field = input, "<>!=~")) == 0)
|
||||
return (query_usage());
|
||||
op = field + len;
|
||||
|
||||
/* Figure out the operator, and find the start of the value. */
|
||||
switch (op[0]) {
|
||||
case '~':
|
||||
operator = WC;
|
||||
value = op + 1;
|
||||
break;
|
||||
case '!':
|
||||
if (op[1] == '=') {
|
||||
operator = NEQ;
|
||||
value = op + 2;
|
||||
break;
|
||||
}
|
||||
if (op[1] == '~') {
|
||||
operator = NWC;
|
||||
value = op + 2;
|
||||
break;
|
||||
}
|
||||
return (query_usage());
|
||||
case '<':
|
||||
if (op[1] == '=') {
|
||||
operator = LTEQ;
|
||||
value = op + 2;
|
||||
} else {
|
||||
operator = LT;
|
||||
value = op + 1;
|
||||
}
|
||||
break;
|
||||
case '=':
|
||||
operator = EQ;
|
||||
if (op[1] == '=')
|
||||
value = op + 2;
|
||||
else
|
||||
value = op + 1;
|
||||
break;
|
||||
case '>':
|
||||
if (op[1] == '=') {
|
||||
operator = GTEQ;
|
||||
value = op + 2;
|
||||
} else {
|
||||
operator = GT;
|
||||
value = op + 1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return (query_usage());
|
||||
}
|
||||
|
||||
/* Terminate the field name, and there better be a field name. */
|
||||
while (--op > input && isspace(*op))
|
||||
;
|
||||
if (op == input)
|
||||
return (query_usage());
|
||||
op[1] = '\0';
|
||||
|
||||
/* Make sure there is a value field. */
|
||||
while (isspace(*value))
|
||||
++value;
|
||||
if (*value == '\0')
|
||||
return (query_usage());
|
||||
|
||||
return (DbRecord_search_field_name(field, value, operator));
|
||||
}
|
||||
|
||||
/*
|
||||
* query_fieldlist --
|
||||
* Display list of field names.
|
||||
*/
|
||||
static int
|
||||
query_fieldlist(char *input)
|
||||
{
|
||||
DbField *f;
|
||||
|
||||
input = input; /* Quiet compiler. */
|
||||
|
||||
for (f = fieldlist; f->name != NULL; ++f)
|
||||
printf("field %3d: %s\n", f->fieldno, f->name);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* query_help --
|
||||
* Query command list.
|
||||
*/
|
||||
static int
|
||||
query_help(char *input)
|
||||
{
|
||||
CMDTAB *p;
|
||||
|
||||
input = input; /* Quiet compiler. */
|
||||
|
||||
printf("Query commands:\n");
|
||||
for (p = cmdtab; p->help != NULL; ++p)
|
||||
printf("\t%s\n", p->help);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* query_usage --
|
||||
* Query usage message.
|
||||
*/
|
||||
static int
|
||||
query_usage(void)
|
||||
{
|
||||
fprintf(stderr, "%s: query syntax error\n", progname);
|
||||
return (query_help(NULL));
|
||||
}
|
||||
99
examples_c/csv/query_main.c
Normal file
99
examples_c/csv/query_main.c
Normal file
@@ -0,0 +1,99 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2005,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: query_main.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include "csv.h"
|
||||
#include "csv_local.h"
|
||||
#include "csv_extern.h"
|
||||
|
||||
static int usage(void);
|
||||
|
||||
/*
|
||||
* Globals
|
||||
*/
|
||||
DB_ENV *dbenv; /* Database environment */
|
||||
DB *db; /* Primary database */
|
||||
int verbose; /* Program verbosity */
|
||||
char *progname; /* Program name */
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
int ch, done, ret, t_ret;
|
||||
char **clist, **clp, *home;
|
||||
|
||||
/* Initialize globals. */
|
||||
dbenv = NULL;
|
||||
db = NULL;
|
||||
if ((progname = strrchr(argv[0], '/')) == NULL)
|
||||
progname = argv[0];
|
||||
else
|
||||
++progname;
|
||||
verbose = 0;
|
||||
|
||||
/* Initialize arguments. */
|
||||
home = NULL;
|
||||
ret = 0;
|
||||
|
||||
/* Allocate enough room for command-list arguments. */
|
||||
if ((clp = clist =
|
||||
(char **)calloc((size_t)argc + 1, sizeof(char *))) == NULL) {
|
||||
fprintf(stderr, "%s: %s\n", progname, strerror(ENOMEM));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Process arguments. */
|
||||
while ((ch = getopt(argc, argv, "c:h:v")) != EOF)
|
||||
switch (ch) {
|
||||
case 'c':
|
||||
*clp++ = optarg;
|
||||
break;
|
||||
case 'h':
|
||||
home = optarg;
|
||||
break;
|
||||
case 'v':
|
||||
++verbose;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
return (usage());
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (*argv != NULL)
|
||||
return (usage());
|
||||
|
||||
/* Create or join the database environment. */
|
||||
if (csv_env_open(home, 1) != 0)
|
||||
return (EXIT_FAILURE);
|
||||
|
||||
/* Handle the queries. */
|
||||
if (clp == clist)
|
||||
ret = query_interactive();
|
||||
else
|
||||
for (clp = clist, done = 0; *clp != NULL && !done; ++clp)
|
||||
if ((ret = query(*clp, &done)) != 0)
|
||||
break;
|
||||
|
||||
/* Close the database environment. */
|
||||
if ((t_ret = csv_env_close()) != 0 && ret == 0)
|
||||
ret = t_ret;
|
||||
|
||||
return (ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/*
|
||||
* usage --
|
||||
* Program usage message.
|
||||
*/
|
||||
static int
|
||||
usage(void)
|
||||
{
|
||||
(void)fprintf(stderr, "usage: %s [-v] [-c cmd] [-h home]\n", progname);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
8
examples_c/csv/sample.csv
Normal file
8
examples_c/csv/sample.csv
Normal file
@@ -0,0 +1,8 @@
|
||||
Adams,Bob,01/02/03,green,apple,37
|
||||
Carter,Denise Ann,04/05/06,blue,banana,38
|
||||
Eidel,Frank,07/08/09,red,cherry,38
|
||||
Grabel,Harriet,10/11/12,purple,date,40
|
||||
Indals,Jason,01/03/05,pink,orange,32
|
||||
Kilt,Laura,07/09/11,yellow,grape,38
|
||||
Moreno,Nancy,02/04/06,black,strawberry,38
|
||||
Octon,Patrick,08/10/12,magenta,kiwi,15
|
||||
|
10
examples_c/csv/sample.desc
Normal file
10
examples_c/csv/sample.desc
Normal file
@@ -0,0 +1,10 @@
|
||||
# $Id: sample.desc,v 1.4 2007/10/16 19:33:14 bostic Exp $
|
||||
|
||||
version 1 {
|
||||
LastName string
|
||||
FirstName string
|
||||
BirthDate
|
||||
Color string index
|
||||
Fruit string index
|
||||
Age unsigned_long index
|
||||
}
|
||||
309
examples_c/csv/util.c
Normal file
309
examples_c/csv/util.c
Normal file
@@ -0,0 +1,309 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2005,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: util.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include "csv.h"
|
||||
#include "csv_local.h"
|
||||
#include "csv_extern.h"
|
||||
|
||||
/*
|
||||
* entry_print --
|
||||
* Display the primary database's data item.
|
||||
*/
|
||||
int
|
||||
entry_print(void *data, size_t len, u_int32_t field_count)
|
||||
{
|
||||
u_int32_t a, *offset;
|
||||
u_int i;
|
||||
char *raw;
|
||||
|
||||
memcpy(&a, data, sizeof(u_int32_t));
|
||||
printf("\tversion: %lu\n", (u_long)a);
|
||||
|
||||
offset = (u_int32_t *)data + 1;
|
||||
if (field_count == 0) {
|
||||
memcpy(&a, offset++, sizeof(u_int32_t));
|
||||
printf("\tcolumn map: %lu fields: {%.*s}\n", (u_long)a,
|
||||
(int)(len - 2 * sizeof(u_int32_t)),
|
||||
(u_int8_t *)data + 2 * sizeof(u_int32_t));
|
||||
} else {
|
||||
raw = (char *)(offset + (field_count + 1));
|
||||
for (i = 0; i < field_count; ++i) {
|
||||
memcpy(&a, &offset[i], sizeof(u_int32_t));
|
||||
len = OFFSET_LEN(offset, i);
|
||||
printf("\toffset %4lu: len %4lu: {%.*s}\n",
|
||||
(u_long)offset[i],
|
||||
(u_long)len, (int)len, raw + a);
|
||||
}
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* strtod_err --
|
||||
* strtod(3) with error checking.
|
||||
*/
|
||||
int
|
||||
strtod_err(char *input, double *valp)
|
||||
{
|
||||
double val;
|
||||
char *end;
|
||||
|
||||
/*
|
||||
* strtoul requires setting errno to detect errors.
|
||||
*/
|
||||
errno = 0;
|
||||
val = strtod(input, &end);
|
||||
if (errno == ERANGE) {
|
||||
dbenv->err(dbenv, ERANGE, "%s", input);
|
||||
return (1);
|
||||
}
|
||||
if (input[0] == '\0' ||
|
||||
(end[0] != '\0' && end[0] != '\n' && !isspace(end[0]))) {
|
||||
dbenv->errx(dbenv,
|
||||
"%s: invalid floating point argument", input);
|
||||
return (1);
|
||||
}
|
||||
|
||||
*valp = val;
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* strtoul_err --
|
||||
* strtoul(3) with error checking.
|
||||
*/
|
||||
int
|
||||
strtoul_err(char *input, u_long *valp)
|
||||
{
|
||||
u_long val;
|
||||
char *end;
|
||||
|
||||
/*
|
||||
* strtoul requires setting errno to detect errors.
|
||||
*/
|
||||
errno = 0;
|
||||
val = strtoul(input, &end, 10);
|
||||
if (errno == ERANGE) {
|
||||
dbenv->err(dbenv, ERANGE, "%s", input);
|
||||
return (1);
|
||||
}
|
||||
if (input[0] == '\0' ||
|
||||
(end[0] != '\0' && end[0] != '\n' && !isspace(end[0]))) {
|
||||
dbenv->errx(dbenv, "%s: invalid unsigned long argument", input);
|
||||
return (1);
|
||||
}
|
||||
|
||||
*valp = val;
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
secondary_callback(DB *db_arg, const DBT *key, const DBT *data, DBT *result)
|
||||
{
|
||||
DbField *f;
|
||||
DbRecord record;
|
||||
void *faddr, *addr;
|
||||
|
||||
/* Populate the field. */
|
||||
if (DbRecord_init(key, data, &record) != 0)
|
||||
return (-1);
|
||||
|
||||
f = db_arg->app_private;
|
||||
faddr = (u_int8_t *)&record + f->offset;
|
||||
|
||||
/*
|
||||
* If necessary, copy the field into separate memory.
|
||||
* Set up the result DBT.
|
||||
*/
|
||||
switch (f->type) {
|
||||
case STRING:
|
||||
result->data = *(char **)faddr;
|
||||
result->size = strlen(*(char **)faddr) + 1;
|
||||
break;
|
||||
case DOUBLE:
|
||||
if ((addr = malloc(sizeof(double))) == NULL)
|
||||
return (-1);
|
||||
result->data = addr;
|
||||
result->size = sizeof(double);
|
||||
result->flags = DB_DBT_APPMALLOC;
|
||||
memcpy(addr, faddr, sizeof(double));
|
||||
break;
|
||||
case UNSIGNED_LONG:
|
||||
if ((addr = malloc(sizeof(u_long))) == NULL)
|
||||
return (-1);
|
||||
result->data = addr;
|
||||
result->size = sizeof(u_long);
|
||||
result->flags = DB_DBT_APPMALLOC;
|
||||
memcpy(addr, faddr, sizeof(u_long));
|
||||
break;
|
||||
default:
|
||||
case NOTSET:
|
||||
abort();
|
||||
/* NOTREACHED */
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* compare_double --
|
||||
* Compare two keys.
|
||||
*/
|
||||
int
|
||||
compare_double(DB *db_arg, const DBT *a_arg, const DBT *b_arg)
|
||||
{
|
||||
double a, b;
|
||||
|
||||
db_arg = db_arg; /* Quiet compiler. */
|
||||
|
||||
memcpy(&a, a_arg->data, sizeof(double));
|
||||
memcpy(&b, b_arg->data, sizeof(double));
|
||||
return (a > b ? 1 : ((a < b) ? -1 : 0));
|
||||
}
|
||||
|
||||
/*
|
||||
* compare_ulong --
|
||||
* Compare two keys.
|
||||
*/
|
||||
int
|
||||
compare_ulong(DB *db_arg, const DBT *a_arg, const DBT *b_arg)
|
||||
{
|
||||
u_long a, b;
|
||||
|
||||
db_arg = db_arg; /* Quiet compiler. */
|
||||
|
||||
memcpy(&a, a_arg->data, sizeof(u_long));
|
||||
memcpy(&b, b_arg->data, sizeof(u_long));
|
||||
return (a > b ? 1 : ((a < b) ? -1 : 0));
|
||||
}
|
||||
|
||||
/*
|
||||
* field_cmp_double --
|
||||
* Compare two double.
|
||||
*/
|
||||
int
|
||||
field_cmp_double(void *a, void *b, OPERATOR op)
|
||||
{
|
||||
switch (op) {
|
||||
case GT:
|
||||
return (*(double *)a > *(double *)b);
|
||||
case GTEQ:
|
||||
return (*(double *)a >= *(double *)b);
|
||||
case LT:
|
||||
return (*(double *)a < *(double *)b);
|
||||
case LTEQ:
|
||||
return (*(double *)a <= *(double *)b);
|
||||
case NEQ:
|
||||
return (*(double *)a != *(double *)b);
|
||||
case EQ:
|
||||
return (*(double *)a == *(double *)b);
|
||||
case WC:
|
||||
case NWC:
|
||||
break;
|
||||
}
|
||||
|
||||
abort();
|
||||
/* NOTREACHED */
|
||||
}
|
||||
|
||||
/*
|
||||
* field_cmp_re --
|
||||
* Compare against regular expression.
|
||||
*/
|
||||
int
|
||||
field_cmp_re(void *a, void *b, OPERATOR op)
|
||||
{
|
||||
op = op; /* Quiet compiler. */
|
||||
|
||||
switch (op) {
|
||||
#ifdef HAVE_WILDCARD_SUPPORT
|
||||
case WC:
|
||||
return (regexec(b, *(char **)a, 0, NULL, 0) == 0);
|
||||
case NWC:
|
||||
return (regexec(b, *(char **)a, 0, NULL, 0) != 0);
|
||||
#else
|
||||
case WC:
|
||||
case NWC:
|
||||
a = a;
|
||||
b = b; /* Quiet compiler. */
|
||||
/* FALLTHROUGH */
|
||||
#endif
|
||||
case GT:
|
||||
case GTEQ:
|
||||
case LT:
|
||||
case LTEQ:
|
||||
case NEQ:
|
||||
case EQ:
|
||||
break;
|
||||
}
|
||||
|
||||
abort();
|
||||
/* NOTREACHED */
|
||||
}
|
||||
|
||||
/*
|
||||
* field_cmp_string --
|
||||
* Compare two strings.
|
||||
*/
|
||||
int
|
||||
field_cmp_string(void *a, void *b, OPERATOR op)
|
||||
{
|
||||
int v;
|
||||
|
||||
v = strcasecmp(*(char **)a, b);
|
||||
switch (op) {
|
||||
case GT:
|
||||
return (v > 0 ? 1 : 0);
|
||||
case GTEQ:
|
||||
return (v >= 0 ? 1 : 0);
|
||||
case LT:
|
||||
return (v < 0 ? 1 : 0);
|
||||
case LTEQ:
|
||||
return (v <= 0 ? 1 : 0);
|
||||
case NEQ:
|
||||
return (v ? 1 : 0);
|
||||
case EQ:
|
||||
return (v ? 0 : 1);
|
||||
case WC:
|
||||
case NWC:
|
||||
break;
|
||||
}
|
||||
|
||||
abort();
|
||||
/* NOTREACHED */
|
||||
}
|
||||
|
||||
/*
|
||||
* field_cmp_ulong --
|
||||
* Compare two ulongs.
|
||||
*/
|
||||
int
|
||||
field_cmp_ulong(void *a, void *b, OPERATOR op)
|
||||
{
|
||||
switch (op) {
|
||||
case GT:
|
||||
return (*(u_long *)a > *(u_long *)b);
|
||||
case GTEQ:
|
||||
return (*(u_long *)a >= *(u_long *)b);
|
||||
case LT:
|
||||
return (*(u_long *)a < *(u_long *)b);
|
||||
case LTEQ:
|
||||
return (*(u_long *)a <= *(u_long *)b);
|
||||
case NEQ:
|
||||
return (*(u_long *)a != *(u_long *)b);
|
||||
case EQ:
|
||||
return (*(u_long *)a == *(u_long *)b);
|
||||
case WC:
|
||||
case NWC:
|
||||
break;
|
||||
}
|
||||
|
||||
abort();
|
||||
/* NOTREACHED */
|
||||
}
|
||||
161
examples_c/ex_access.c
Normal file
161
examples_c/ex_access.c
Normal file
@@ -0,0 +1,161 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 1997,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: ex_access.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
extern int getopt(int, char * const *, const char *);
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <db.h>
|
||||
|
||||
#define DATABASE "access.db"
|
||||
int main __P((int, char *[]));
|
||||
int usage __P((void));
|
||||
|
||||
int
|
||||
main(argc, argv)
|
||||
int argc;
|
||||
char *argv[];
|
||||
{
|
||||
extern int optind;
|
||||
DB *dbp;
|
||||
DBC *dbcp;
|
||||
DBT key, data;
|
||||
size_t len;
|
||||
int ch, ret, rflag;
|
||||
char *database, *p, *t, buf[1024], rbuf[1024];
|
||||
const char *progname = "ex_access"; /* Program name. */
|
||||
|
||||
rflag = 0;
|
||||
while ((ch = getopt(argc, argv, "r")) != EOF)
|
||||
switch (ch) {
|
||||
case 'r':
|
||||
rflag = 1;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
return (usage());
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
/* Accept optional database name. */
|
||||
database = *argv == NULL ? DATABASE : argv[0];
|
||||
|
||||
/* Optionally discard the database. */
|
||||
if (rflag)
|
||||
(void)remove(database);
|
||||
|
||||
/* Create and initialize database object, open the database. */
|
||||
if ((ret = db_create(&dbp, NULL, 0)) != 0) {
|
||||
fprintf(stderr,
|
||||
"%s: db_create: %s\n", progname, db_strerror(ret));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
dbp->set_errfile(dbp, stderr);
|
||||
dbp->set_errpfx(dbp, progname);
|
||||
if ((ret = dbp->set_pagesize(dbp, 1024)) != 0) {
|
||||
dbp->err(dbp, ret, "set_pagesize");
|
||||
goto err1;
|
||||
}
|
||||
if ((ret = dbp->set_cachesize(dbp, 0, 32 * 1024, 0)) != 0) {
|
||||
dbp->err(dbp, ret, "set_cachesize");
|
||||
goto err1;
|
||||
}
|
||||
if ((ret = dbp->open(dbp,
|
||||
NULL, database, NULL, DB_BTREE, DB_CREATE, 0664)) != 0) {
|
||||
dbp->err(dbp, ret, "%s: open", database);
|
||||
goto err1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert records into the database, where the key is the user
|
||||
* input and the data is the user input in reverse order.
|
||||
*/
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
memset(&data, 0, sizeof(DBT));
|
||||
for (;;) {
|
||||
printf("input> ");
|
||||
fflush(stdout);
|
||||
if (fgets(buf, sizeof(buf), stdin) == NULL)
|
||||
break;
|
||||
if (strcmp(buf, "exit\n") == 0 || strcmp(buf, "quit\n") == 0)
|
||||
break;
|
||||
if ((len = strlen(buf)) <= 1)
|
||||
continue;
|
||||
for (t = rbuf, p = buf + (len - 2); p >= buf;)
|
||||
*t++ = *p--;
|
||||
*t++ = '\0';
|
||||
|
||||
key.data = buf;
|
||||
data.data = rbuf;
|
||||
data.size = key.size = (u_int32_t)len - 1;
|
||||
|
||||
switch (ret =
|
||||
dbp->put(dbp, NULL, &key, &data, DB_NOOVERWRITE)) {
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
dbp->err(dbp, ret, "DB->put");
|
||||
if (ret != DB_KEYEXIST)
|
||||
goto err1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
/* Acquire a cursor for the database. */
|
||||
if ((ret = dbp->cursor(dbp, NULL, &dbcp, 0)) != 0) {
|
||||
dbp->err(dbp, ret, "DB->cursor");
|
||||
goto err1;
|
||||
}
|
||||
|
||||
/* Initialize the key/data pair so the flags aren't set. */
|
||||
memset(&key, 0, sizeof(key));
|
||||
memset(&data, 0, sizeof(data));
|
||||
|
||||
/* Walk through the database and print out the key/data pairs. */
|
||||
while ((ret = dbcp->get(dbcp, &key, &data, DB_NEXT)) == 0)
|
||||
printf("%.*s : %.*s\n",
|
||||
(int)key.size, (char *)key.data,
|
||||
(int)data.size, (char *)data.data);
|
||||
if (ret != DB_NOTFOUND) {
|
||||
dbp->err(dbp, ret, "DBcursor->get");
|
||||
goto err2;
|
||||
}
|
||||
|
||||
/* Close everything down. */
|
||||
if ((ret = dbcp->close(dbcp)) != 0) {
|
||||
dbp->err(dbp, ret, "DBcursor->close");
|
||||
goto err1;
|
||||
}
|
||||
if ((ret = dbp->close(dbp, 0)) != 0) {
|
||||
fprintf(stderr,
|
||||
"%s: DB->close: %s\n", progname, db_strerror(ret));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
return (EXIT_SUCCESS);
|
||||
|
||||
err2: (void)dbcp->close(dbcp);
|
||||
err1: (void)dbp->close(dbp, 0);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int
|
||||
usage()
|
||||
{
|
||||
(void)fprintf(stderr, "usage: ex_access [-r] [database]\n");
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
10
examples_c/ex_apprec/auto_rebuild
Normal file
10
examples_c/ex_apprec/auto_rebuild
Normal file
@@ -0,0 +1,10 @@
|
||||
# Script to rebuild automatically generated files for ex_apprec.
|
||||
|
||||
E=../examples_c/ex_apprec
|
||||
|
||||
cd ../../dist
|
||||
awk -f gen_rec.awk \
|
||||
-v source_file=$E/ex_apprec_auto.c \
|
||||
-v header_file=$E/ex_apprec_auto.h \
|
||||
-v print_file=$E/ex_apprec_autop.c \
|
||||
-v template_file=$E/ex_apprec_template < $E/ex_apprec.src
|
||||
277
examples_c/ex_apprec/ex_apprec.c
Normal file
277
examples_c/ex_apprec/ex_apprec.c
Normal file
@@ -0,0 +1,277 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 1996,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: ex_apprec.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "db_config.h"
|
||||
#include "db.h"
|
||||
#include "db_int.h"
|
||||
#include "dbinc/db_swap.h"
|
||||
|
||||
#include "ex_apprec.h"
|
||||
|
||||
int apprec_dispatch __P((DB_ENV *, DBT *, DB_LSN *, db_recops));
|
||||
int open_env __P((const char *, FILE *, const char *, DB_ENV **));
|
||||
int verify_absence __P((DB_ENV *, const char *));
|
||||
int verify_presence __P((DB_ENV *, const char *));
|
||||
|
||||
int
|
||||
main(argc, argv)
|
||||
int argc;
|
||||
char *argv[];
|
||||
{
|
||||
extern char *optarg;
|
||||
DB_ENV *dbenv;
|
||||
DB_LSN lsn;
|
||||
DB_TXN *txn;
|
||||
DBT dirnamedbt;
|
||||
int ret;
|
||||
const char *home;
|
||||
char ch, dirname[256];
|
||||
const char *progname = "ex_apprec"; /* Program name. */
|
||||
|
||||
/* Default home. */
|
||||
home = "TESTDIR";
|
||||
|
||||
while ((ch = getopt(argc, argv, "h:")) != EOF)
|
||||
switch (ch) {
|
||||
case 'h':
|
||||
home = optarg;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "usage: %s [-h home]", progname);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf("Set up environment.\n");
|
||||
if ((ret = open_env(home, stderr, progname, &dbenv)) != 0)
|
||||
return (EXIT_FAILURE);
|
||||
|
||||
printf("Create a directory in a transaction.\n");
|
||||
/*
|
||||
* This application's convention is to log the full directory name,
|
||||
* including trailing nul.
|
||||
*/
|
||||
memset(&dirnamedbt, 0, sizeof(dirnamedbt));
|
||||
sprintf(dirname, "%s/MYDIRECTORY", home);
|
||||
dirnamedbt.data = dirname;
|
||||
dirnamedbt.size = strlen(dirname) + 1;
|
||||
|
||||
if ((ret = dbenv->txn_begin(dbenv, NULL, &txn, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "txn_begin");
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Remember, always log actions before you execute them!
|
||||
* Since this log record is describing a file system operation and
|
||||
* we have no control over when file system operations go to disk,
|
||||
* we need to flush the log record immediately to ensure that the
|
||||
* log record is on disk before the operation it describes. The
|
||||
* flush would not be necessary were we doing an operation into the
|
||||
* BDB mpool and using LSNs that mpool knew about.
|
||||
*/
|
||||
memset(&lsn, 0, sizeof(lsn));
|
||||
if ((ret =
|
||||
ex_apprec_mkdir_log(dbenv,
|
||||
txn, &lsn, DB_FLUSH, &dirnamedbt)) != 0) {
|
||||
dbenv->err(dbenv, ret, "mkdir_log");
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
if (mkdir(dirname, 0755) != 0) {
|
||||
dbenv->err(dbenv, errno, "mkdir");
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf("Verify the directory's presence: ");
|
||||
verify_presence(dbenv, dirname);
|
||||
printf("check.\n");
|
||||
|
||||
/* Now abort the transaction and verify that the directory goes away. */
|
||||
printf("Abort the transaction.\n");
|
||||
if ((ret = txn->abort(txn)) != 0) {
|
||||
dbenv->err(dbenv, ret, "txn_abort");
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf("Verify the directory's absence: ");
|
||||
verify_absence(dbenv, dirname);
|
||||
printf("check.\n");
|
||||
|
||||
/* Now do the same thing over again, only with a commit this time. */
|
||||
printf("Create a directory in a transaction.\n");
|
||||
memset(&dirnamedbt, 0, sizeof(dirnamedbt));
|
||||
sprintf(dirname, "%s/MYDIRECTORY", home);
|
||||
dirnamedbt.data = dirname;
|
||||
dirnamedbt.size = strlen(dirname) + 1;
|
||||
if ((ret = dbenv->txn_begin(dbenv, NULL, &txn, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "txn_begin");
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
memset(&lsn, 0, sizeof(lsn));
|
||||
if ((ret =
|
||||
ex_apprec_mkdir_log(dbenv, txn, &lsn, 0, &dirnamedbt)) != 0) {
|
||||
dbenv->err(dbenv, ret, "mkdir_log");
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
if (mkdir(dirname, 0755) != 0) {
|
||||
dbenv->err(dbenv, errno, "mkdir");
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf("Verify the directory's presence: ");
|
||||
verify_presence(dbenv, dirname);
|
||||
printf("check.\n");
|
||||
|
||||
/* Now abort the transaction and verify that the directory goes away. */
|
||||
printf("Commit the transaction.\n");
|
||||
if ((ret = txn->commit(txn, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "txn_commit");
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf("Verify the directory's presence: ");
|
||||
verify_presence(dbenv, dirname);
|
||||
printf("check.\n");
|
||||
|
||||
printf("Now remove the directory, then run recovery.\n");
|
||||
if ((ret = dbenv->close(dbenv, 0)) != 0) {
|
||||
fprintf(stderr, "DB_ENV->close: %s\n", db_strerror(ret));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
if (rmdir(dirname) != 0) {
|
||||
fprintf(stderr,
|
||||
"%s: rmdir failed with error %s", progname,
|
||||
strerror(errno));
|
||||
}
|
||||
verify_absence(dbenv, dirname);
|
||||
|
||||
/* Opening with DB_RECOVER runs recovery. */
|
||||
if ((ret = open_env(home, stderr, progname, &dbenv)) != 0)
|
||||
return (EXIT_FAILURE);
|
||||
|
||||
printf("Verify the directory's presence: ");
|
||||
verify_presence(dbenv, dirname);
|
||||
printf("check.\n");
|
||||
|
||||
/* Close the handle. */
|
||||
if ((ret = dbenv->close(dbenv, 0)) != 0) {
|
||||
fprintf(stderr, "DB_ENV->close: %s\n", db_strerror(ret));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
int
|
||||
open_env(home, errfp, progname, dbenvp)
|
||||
const char *home, *progname;
|
||||
FILE *errfp;
|
||||
DB_ENV **dbenvp;
|
||||
{
|
||||
DB_ENV *dbenv;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Create an environment object and initialize it for error
|
||||
* reporting.
|
||||
*/
|
||||
if ((ret = db_env_create(&dbenv, 0)) != 0) {
|
||||
fprintf(errfp, "%s: %s\n", progname, db_strerror(ret));
|
||||
return (ret);
|
||||
}
|
||||
dbenv->set_errfile(dbenv, errfp);
|
||||
dbenv->set_errpfx(dbenv, progname);
|
||||
|
||||
/* Set up our custom recovery dispatch function. */
|
||||
if ((ret = dbenv->set_app_dispatch(dbenv, apprec_dispatch)) != 0) {
|
||||
dbenv->err(dbenv, ret, "set_app_dispatch");
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* Open the environment with full transactional support, running
|
||||
* recovery.
|
||||
*/
|
||||
if ((ret =
|
||||
dbenv->open(dbenv, home, DB_CREATE | DB_RECOVER | DB_INIT_LOCK |
|
||||
DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "environment open: %s", home);
|
||||
dbenv->close(dbenv, 0);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
*dbenvp = dbenv;
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sample application dispatch function to handle user-specified log record
|
||||
* types.
|
||||
*/
|
||||
int
|
||||
apprec_dispatch(dbenv, dbt, lsn, op)
|
||||
DB_ENV *dbenv;
|
||||
DBT *dbt;
|
||||
DB_LSN *lsn;
|
||||
db_recops op;
|
||||
{
|
||||
u_int32_t rectype;
|
||||
|
||||
/* Pull the record type out of the log record. */
|
||||
LOGCOPY_32(dbenv->env, &rectype, dbt->data);
|
||||
|
||||
switch (rectype) {
|
||||
case DB_ex_apprec_mkdir:
|
||||
return (ex_apprec_mkdir_recover(dbenv, dbt, lsn, op));
|
||||
default:
|
||||
/*
|
||||
* We've hit an unexpected, allegedly user-defined record
|
||||
* type.
|
||||
*/
|
||||
dbenv->errx(dbenv, "Unexpected log record type encountered");
|
||||
return (EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
verify_absence(dbenv, dirname)
|
||||
DB_ENV *dbenv;
|
||||
const char *dirname;
|
||||
{
|
||||
|
||||
if (access(dirname, F_OK) == 0) {
|
||||
dbenv->errx(dbenv, "Error--directory present!");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
verify_presence(dbenv, dirname)
|
||||
DB_ENV *dbenv;
|
||||
const char *dirname;
|
||||
{
|
||||
|
||||
if (access(dirname, F_OK) != 0) {
|
||||
dbenv->errx(dbenv, "Error--directory not present!");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
24
examples_c/ex_apprec/ex_apprec.h
Normal file
24
examples_c/ex_apprec/ex_apprec.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2002,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: ex_apprec.h 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#ifndef _EX_APPREC_H_
|
||||
#define _EX_APPREC_H_
|
||||
|
||||
#include "ex_apprec_auto.h"
|
||||
|
||||
int ex_apprec_mkdir_log
|
||||
__P((DB_ENV *, DB_TXN *, DB_LSN *, u_int32_t, const DBT *));
|
||||
int ex_apprec_mkdir_print
|
||||
__P((DB_ENV *, DBT *, DB_LSN *, db_recops));
|
||||
int ex_apprec_mkdir_read
|
||||
__P((DB_ENV *, void *, ex_apprec_mkdir_args **));
|
||||
int ex_apprec_mkdir_recover
|
||||
__P((DB_ENV *, DBT *, DB_LSN *, db_recops));
|
||||
int ex_apprec_init_print __P((DB_ENV *, DB_DISTAB *));
|
||||
|
||||
#endif /* !_EX_APPREC_H_ */
|
||||
33
examples_c/ex_apprec/ex_apprec.src
Normal file
33
examples_c/ex_apprec/ex_apprec.src
Normal file
@@ -0,0 +1,33 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2002,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: ex_apprec.src,v 12.8 2008/01/08 20:58:24 bostic Exp $
|
||||
*/
|
||||
|
||||
PREFIX ex_apprec
|
||||
INCLUDE #include "ex_apprec.h"
|
||||
|
||||
/*
|
||||
* This is the source file used to generate the application-specific recovery
|
||||
* functions used by the ex_apprec example. It should be turned into usable
|
||||
* source code (including a template for the recovery function itself) by
|
||||
* invoking changing to the dist directory of the DB distribution and
|
||||
* running the gen_rec.awk script there as follows:
|
||||
*
|
||||
* awk -f ./gen_rec.awk \
|
||||
* -v source_file=../examples_c/ex_apprec/ex_apprec_auto.c \
|
||||
* -v header_file=../examples_c/ex_apprec/ex_apprec_auto.h \
|
||||
* -v template_file=../examples_c/ex_apprec/ex_apprec_template \
|
||||
* < ../examples_c/ex_apprec/ex_apprec.src
|
||||
*/
|
||||
|
||||
/*
|
||||
* mkdir: used to create a directory
|
||||
*
|
||||
* dirname: relative or absolute pathname of the directory to be created
|
||||
*/
|
||||
BEGIN mkdir 42 10000
|
||||
DBT dirname DBT s
|
||||
END
|
||||
140
examples_c/ex_apprec/ex_apprec_auto.c
Normal file
140
examples_c/ex_apprec/ex_apprec_auto.c
Normal file
@@ -0,0 +1,140 @@
|
||||
/* Do not edit: automatically built by gen_rec.awk. */
|
||||
|
||||
#include "db_config.h"
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "db.h"
|
||||
#include "db_int.h"
|
||||
#include "dbinc/db_swap.h"
|
||||
#include "ex_apprec.h"
|
||||
/*
|
||||
* PUBLIC: int ex_apprec_mkdir_read __P((DB_ENV *, void *,
|
||||
* PUBLIC: ex_apprec_mkdir_args **));
|
||||
*/
|
||||
int
|
||||
ex_apprec_mkdir_read(dbenv, recbuf, argpp)
|
||||
DB_ENV *dbenv;
|
||||
void *recbuf;
|
||||
ex_apprec_mkdir_args **argpp;
|
||||
{
|
||||
ex_apprec_mkdir_args *argp;
|
||||
u_int8_t *bp;
|
||||
ENV *env;
|
||||
|
||||
env = dbenv->env;
|
||||
|
||||
if ((argp = malloc(sizeof(ex_apprec_mkdir_args) + sizeof(DB_TXN))) == NULL)
|
||||
return (ENOMEM);
|
||||
bp = recbuf;
|
||||
argp->txnp = (DB_TXN *)&argp[1];
|
||||
memset(argp->txnp, 0, sizeof(DB_TXN));
|
||||
|
||||
LOGCOPY_32(env, &argp->type, bp);
|
||||
bp += sizeof(argp->type);
|
||||
|
||||
LOGCOPY_32(env, &argp->txnp->txnid, bp);
|
||||
bp += sizeof(argp->txnp->txnid);
|
||||
|
||||
LOGCOPY_TOLSN(env, &argp->prev_lsn, bp);
|
||||
bp += sizeof(DB_LSN);
|
||||
|
||||
memset(&argp->dirname, 0, sizeof(argp->dirname));
|
||||
LOGCOPY_32(env,&argp->dirname.size, bp);
|
||||
bp += sizeof(u_int32_t);
|
||||
argp->dirname.data = bp;
|
||||
bp += argp->dirname.size;
|
||||
|
||||
*argpp = argp;
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* PUBLIC: int ex_apprec_mkdir_log __P((DB_ENV *, DB_TXN *, DB_LSN *,
|
||||
* PUBLIC: u_int32_t, const DBT *));
|
||||
*/
|
||||
int
|
||||
ex_apprec_mkdir_log(dbenv, txnp, ret_lsnp, flags,
|
||||
dirname)
|
||||
DB_ENV *dbenv;
|
||||
DB_TXN *txnp;
|
||||
DB_LSN *ret_lsnp;
|
||||
u_int32_t flags;
|
||||
const DBT *dirname;
|
||||
{
|
||||
DBT logrec;
|
||||
DB_LSN *lsnp, null_lsn, *rlsnp;
|
||||
ENV *env;
|
||||
u_int32_t zero, rectype, txn_num;
|
||||
u_int npad;
|
||||
u_int8_t *bp;
|
||||
int ret;
|
||||
|
||||
env = dbenv->env;
|
||||
rlsnp = ret_lsnp;
|
||||
rectype = DB_ex_apprec_mkdir;
|
||||
npad = 0;
|
||||
ret = 0;
|
||||
|
||||
if (txnp == NULL) {
|
||||
txn_num = 0;
|
||||
lsnp = &null_lsn;
|
||||
null_lsn.file = null_lsn.offset = 0;
|
||||
} else {
|
||||
/*
|
||||
* We need to assign begin_lsn while holding region mutex.
|
||||
* That assignment is done inside the DbEnv->log_put call,
|
||||
* so pass in the appropriate memory location to be filled
|
||||
* in by the log_put code.
|
||||
*/
|
||||
DB_SET_TXN_LSNP(txnp, &rlsnp, &lsnp);
|
||||
txn_num = txnp->txnid;
|
||||
}
|
||||
|
||||
logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
|
||||
+ sizeof(u_int32_t) + (dirname == NULL ? 0 : dirname->size);
|
||||
if ((logrec.data = malloc(logrec.size)) == NULL)
|
||||
return (ENOMEM);
|
||||
bp = logrec.data;
|
||||
|
||||
if (npad > 0)
|
||||
memset((u_int8_t *)logrec.data + logrec.size - npad, 0, npad);
|
||||
|
||||
bp = logrec.data;
|
||||
|
||||
LOGCOPY_32(env, bp, &rectype);
|
||||
bp += sizeof(rectype);
|
||||
|
||||
LOGCOPY_32(env, bp, &txn_num);
|
||||
bp += sizeof(txn_num);
|
||||
|
||||
LOGCOPY_FROMLSN(env, bp, lsnp);
|
||||
bp += sizeof(DB_LSN);
|
||||
|
||||
if (dirname == NULL) {
|
||||
zero = 0;
|
||||
LOGCOPY_32(env, bp, &zero);
|
||||
bp += sizeof(u_int32_t);
|
||||
} else {
|
||||
LOGCOPY_32(env, bp, &dirname->size);
|
||||
bp += sizeof(dirname->size);
|
||||
memcpy(bp, dirname->data, dirname->size);
|
||||
bp += dirname->size;
|
||||
}
|
||||
|
||||
if ((ret = dbenv->log_put(dbenv, rlsnp, (DBT *)&logrec,
|
||||
flags | DB_LOG_NOCOPY)) == 0 && txnp != NULL) {
|
||||
*lsnp = *rlsnp;
|
||||
if (rlsnp != ret_lsnp)
|
||||
*ret_lsnp = *rlsnp;
|
||||
}
|
||||
#ifdef LOG_DIAGNOSTIC
|
||||
if (ret != 0)
|
||||
(void)ex_apprec_mkdir_print(dbenv,
|
||||
(DBT *)&logrec, ret_lsnp, DB_TXN_PRINT);
|
||||
#endif
|
||||
|
||||
free(logrec.data);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
13
examples_c/ex_apprec/ex_apprec_auto.h
Normal file
13
examples_c/ex_apprec/ex_apprec_auto.h
Normal file
@@ -0,0 +1,13 @@
|
||||
/* Do not edit: automatically built by gen_rec.awk. */
|
||||
|
||||
#ifndef ex_apprec_AUTO_H
|
||||
#define ex_apprec_AUTO_H
|
||||
#define DB_ex_apprec_mkdir 10000
|
||||
typedef struct _ex_apprec_mkdir_args {
|
||||
u_int32_t type;
|
||||
DB_TXN *txnp;
|
||||
DB_LSN prev_lsn;
|
||||
DBT dirname;
|
||||
} ex_apprec_mkdir_args;
|
||||
|
||||
#endif
|
||||
65
examples_c/ex_apprec/ex_apprec_autop.c
Normal file
65
examples_c/ex_apprec/ex_apprec_autop.c
Normal file
@@ -0,0 +1,65 @@
|
||||
/* Do not edit: automatically built by gen_rec.awk. */
|
||||
|
||||
#include "db_config.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "db.h"
|
||||
#include "ex_apprec.h"
|
||||
/*
|
||||
* PUBLIC: int ex_apprec_mkdir_print __P((DB_ENV *, DBT *, DB_LSN *,
|
||||
* PUBLIC: db_recops));
|
||||
*/
|
||||
int
|
||||
ex_apprec_mkdir_print(dbenv, dbtp, lsnp, notused2)
|
||||
DB_ENV *dbenv;
|
||||
DBT *dbtp;
|
||||
DB_LSN *lsnp;
|
||||
db_recops notused2;
|
||||
{
|
||||
ex_apprec_mkdir_args *argp;
|
||||
int ex_apprec_mkdir_read __P((DB_ENV *, void *, ex_apprec_mkdir_args **));
|
||||
u_int32_t i;
|
||||
int ch;
|
||||
int ret;
|
||||
|
||||
notused2 = DB_TXN_PRINT;
|
||||
|
||||
if ((ret = ex_apprec_mkdir_read(dbenv, dbtp->data, &argp)) != 0)
|
||||
return (ret);
|
||||
(void)printf(
|
||||
"[%lu][%lu]ex_apprec_mkdir%s: rec: %lu txnp %lx prevlsn [%lu][%lu]\n",
|
||||
(u_long)lsnp->file, (u_long)lsnp->offset,
|
||||
(argp->type & DB_debug_FLAG) ? "_debug" : "",
|
||||
(u_long)argp->type,
|
||||
(u_long)argp->txnp->txnid,
|
||||
(u_long)argp->prev_lsn.file, (u_long)argp->prev_lsn.offset);
|
||||
(void)printf("\tdirname: ");
|
||||
for (i = 0; i < argp->dirname.size; i++) {
|
||||
ch = ((u_int8_t *)argp->dirname.data)[i];
|
||||
printf(isprint(ch) || ch == 0x0a ? "%c" : "%#x ", ch);
|
||||
}
|
||||
(void)printf("\n");
|
||||
(void)printf("\n");
|
||||
free(argp);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* PUBLIC: int ex_apprec_init_print __P((DB_ENV *, DB_DISTAB *));
|
||||
*/
|
||||
int
|
||||
ex_apprec_init_print(dbenv, dtabp)
|
||||
DB_ENV *dbenv;
|
||||
DB_DISTAB *dtabp;
|
||||
{
|
||||
int __db_add_recovery __P((DB_ENV *, DB_DISTAB *,
|
||||
int (*)(DB_ENV *, DBT *, DB_LSN *, db_recops), u_int32_t));
|
||||
int ret;
|
||||
|
||||
if ((ret = __db_add_recovery(dbenv, dtabp,
|
||||
ex_apprec_mkdir_print, DB_ex_apprec_mkdir)) != 0)
|
||||
return (ret);
|
||||
return (0);
|
||||
}
|
||||
108
examples_c/ex_apprec/ex_apprec_rec.c
Normal file
108
examples_c/ex_apprec/ex_apprec_rec.c
Normal file
@@ -0,0 +1,108 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 1996,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: ex_apprec_rec.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is based on the template file ex_apprec_template. Note that
|
||||
* because ex_apprec_mkdir, like most application-specific recovery functions,
|
||||
* does not make use of DB-private structures, it has actually been simplified
|
||||
* significantly.
|
||||
*/
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <db.h>
|
||||
|
||||
#include "ex_apprec.h"
|
||||
|
||||
/*
|
||||
* ex_apprec_mkdir_recover --
|
||||
* Recovery function for mkdir.
|
||||
*
|
||||
* PUBLIC: int ex_apprec_mkdir_recover
|
||||
* PUBLIC: __P((DB_ENV *, DBT *, DB_LSN *, db_recops));
|
||||
*/
|
||||
int
|
||||
ex_apprec_mkdir_recover(dbenv, dbtp, lsnp, op)
|
||||
DB_ENV *dbenv;
|
||||
DBT *dbtp;
|
||||
DB_LSN *lsnp;
|
||||
db_recops op;
|
||||
{
|
||||
ex_apprec_mkdir_args *argp;
|
||||
int ret;
|
||||
|
||||
argp = NULL;
|
||||
|
||||
#ifdef DEBUG_RECOVER
|
||||
ex_apprec_mkdir_print(dbenv, dbtp, lsnp, op);
|
||||
#endif
|
||||
if ((ret = ex_apprec_mkdir_read(dbenv, dbtp->data, &argp)) != 0)
|
||||
goto out;
|
||||
|
||||
switch (op) {
|
||||
case DB_TXN_ABORT:
|
||||
case DB_TXN_BACKWARD_ROLL:
|
||||
/*
|
||||
* If we're aborting, we need to remove the directory if it
|
||||
* exists. We log the trailing zero in pathnames, so we can
|
||||
* simply pass the data part of the DBT into rmdir as a string.
|
||||
* (Note that we don't have any alignment guarantees, but for
|
||||
* a char * this doesn't matter.)
|
||||
*
|
||||
* Ignore all errors other than ENOENT; DB may attempt to undo
|
||||
* or redo operations without knowing whether they have already
|
||||
* been done or undone, so we should never assume in a recovery
|
||||
* function that the task definitely needs doing or undoing.
|
||||
*/
|
||||
ret = rmdir(argp->dirname.data);
|
||||
if (ret != 0 && errno != ENOENT)
|
||||
dbenv->err(dbenv, ret, "Error in abort of mkdir");
|
||||
else
|
||||
ret = 0;
|
||||
break;
|
||||
case DB_TXN_FORWARD_ROLL:
|
||||
/*
|
||||
* The forward direction is just the opposite; here, we ignore
|
||||
* EEXIST, because the directory may already exist.
|
||||
*/
|
||||
ret = mkdir(argp->dirname.data, 0755);
|
||||
if (ret != 0 && errno != EEXIST)
|
||||
dbenv->err(dbenv,
|
||||
ret, "Error in roll-forward of mkdir");
|
||||
else
|
||||
ret = 0;
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* We might want to handle DB_TXN_PRINT or DB_TXN_APPLY here,
|
||||
* too, but we don't try to print the log records and aren't
|
||||
* using replication, so there's no need to in this example.
|
||||
*/
|
||||
dbenv->errx(dbenv, "Unexpected operation type\n");
|
||||
return (EINVAL);
|
||||
}
|
||||
|
||||
/*
|
||||
* The recovery function is responsible for returning the LSN of the
|
||||
* previous log record in this transaction, so that transaction aborts
|
||||
* can follow the chain backwards.
|
||||
*
|
||||
* (If we'd wanted the LSN of this record earlier, we could have
|
||||
* read it from lsnp, as well--but because we weren't working with
|
||||
* pages or other objects that store their LSN and base recovery
|
||||
* decisions on it, we didn't need to.)
|
||||
*/
|
||||
*lsnp = argp->prev_lsn;
|
||||
|
||||
out: if (argp != NULL)
|
||||
free(argp);
|
||||
return (ret);
|
||||
}
|
||||
70
examples_c/ex_apprec/ex_apprec_template
Normal file
70
examples_c/ex_apprec/ex_apprec_template
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "db.h"
|
||||
|
||||
/*
|
||||
* ex_apprec_mkdir_recover --
|
||||
* Recovery function for mkdir.
|
||||
*
|
||||
* PUBLIC: int ex_apprec_mkdir_recover
|
||||
* PUBLIC: __P((dbenv *, DBT *, DB_LSN *, db_recops));
|
||||
*/
|
||||
int
|
||||
ex_apprec_mkdir_recover(dbenv, dbtp, lsnp, op)
|
||||
dbenv *dbenv;
|
||||
DBT *dbtp;
|
||||
DB_LSN *lsnp;
|
||||
db_recops op;
|
||||
{
|
||||
ex_apprec_mkdir_args *argp;
|
||||
int cmp_n, cmp_p, modified, ret;
|
||||
|
||||
#ifdef DEBUG_RECOVER
|
||||
(void)ex_apprec_mkdir_print(dbenv, dbtp, lsnp, op);
|
||||
#endif
|
||||
argp = NULL;
|
||||
if ((ret = ex_apprec_mkdir_read(dbenv, dbtp->data, &argp)) != 0)
|
||||
goto out;
|
||||
|
||||
modified = 0;
|
||||
cmp_n = 0;
|
||||
cmp_p = 0;
|
||||
|
||||
/*
|
||||
* The function now needs to calculate cmp_n and cmp_p based
|
||||
* on whatever is in argp (usually an LSN representing the state
|
||||
* of an object BEFORE the operation described in this record was
|
||||
* applied) and whatever other information the function needs,
|
||||
* e.g., the LSN of the object as it exists now.
|
||||
*
|
||||
* cmp_p should be set to 0 if the current state of the object
|
||||
* is believed to be same as the state of the object BEFORE the
|
||||
* described operation was applied. For example, if you had an
|
||||
* LSN in the log record (argp->prevlsn) and a current LSN of the
|
||||
* object (curlsn), you might want to do:
|
||||
*
|
||||
* cmp_p = log_compare(curlsn, argp->prevlsn);
|
||||
*
|
||||
* Similarly, cmp_n should be set to 0 if the current state
|
||||
* of the object reflects the object AFTER this operation has
|
||||
* been applied. Thus, if you can figure out an object's current
|
||||
* LSN, yo might set cmp_n as:
|
||||
*
|
||||
* cmp_n = log_compare(lsnp, curlsn);
|
||||
*/
|
||||
if (cmp_p == 0 && DB_REDO(op)) {
|
||||
/* Need to redo update described. */
|
||||
modified = 1;
|
||||
} else if (cmp_n == 0 && !DB_REDO(op)) {
|
||||
/* Need to undo update described. */
|
||||
modified = 1;
|
||||
}
|
||||
|
||||
/* Allow for following LSN pointers through a transaction. */
|
||||
*lsnp = argp->prev_lsn;
|
||||
ret = 0;
|
||||
|
||||
out: if (argp != NULL)
|
||||
free(argp);
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
||||
202
examples_c/ex_btrec.c
Normal file
202
examples_c/ex_btrec.c
Normal file
@@ -0,0 +1,202 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 1997,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: ex_btrec.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <db.h>
|
||||
|
||||
#define DATABASE "access.db"
|
||||
#define WORDLIST "../test/wordlist"
|
||||
int main __P((void));
|
||||
|
||||
int ex_btrec __P((void));
|
||||
void show __P((const char *, DBT *, DBT *));
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
return (ex_btrec() == 1 ? EXIT_FAILURE : EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
int
|
||||
ex_btrec()
|
||||
{
|
||||
DB *dbp;
|
||||
DBC *dbcp;
|
||||
DBT key, data;
|
||||
DB_BTREE_STAT *statp;
|
||||
FILE *fp;
|
||||
db_recno_t recno;
|
||||
size_t len;
|
||||
int cnt, ret;
|
||||
char *p, *t, buf[1024], rbuf[1024];
|
||||
const char *progname = "ex_btrec"; /* Program name. */
|
||||
|
||||
/* Open the word database. */
|
||||
if ((fp = fopen(WORDLIST, "r")) == NULL) {
|
||||
fprintf(stderr, "%s: open %s: %s\n",
|
||||
progname, WORDLIST, db_strerror(errno));
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Remove the previous database. */
|
||||
(void)remove(DATABASE);
|
||||
|
||||
/* Create and initialize database object, open the database. */
|
||||
if ((ret = db_create(&dbp, NULL, 0)) != 0) {
|
||||
fprintf(stderr,
|
||||
"%s: db_create: %s\n", progname, db_strerror(ret));
|
||||
return (1);
|
||||
}
|
||||
dbp->set_errfile(dbp, stderr);
|
||||
dbp->set_errpfx(dbp, progname); /* 1K page sizes. */
|
||||
if ((ret = dbp->set_pagesize(dbp, 1024)) != 0) {
|
||||
dbp->err(dbp, ret, "set_pagesize");
|
||||
return (1);
|
||||
} /* Record numbers. */
|
||||
if ((ret = dbp->set_flags(dbp, DB_RECNUM)) != 0) {
|
||||
dbp->err(dbp, ret, "set_flags: DB_RECNUM");
|
||||
return (1);
|
||||
}
|
||||
if ((ret = dbp->open(dbp,
|
||||
NULL, DATABASE, NULL, DB_BTREE, DB_CREATE, 0664)) != 0) {
|
||||
dbp->err(dbp, ret, "open: %s", DATABASE);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert records into the database, where the key is the word
|
||||
* preceded by its record number, and the data is the same, but
|
||||
* in reverse order.
|
||||
*/
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
memset(&data, 0, sizeof(DBT));
|
||||
for (cnt = 1; cnt <= 1000; ++cnt) {
|
||||
(void)sprintf(buf, "%04d_", cnt);
|
||||
if (fgets(buf + 4, sizeof(buf) - 4, fp) == NULL)
|
||||
break;
|
||||
len = strlen(buf);
|
||||
for (t = rbuf, p = buf + (len - 2); p >= buf;)
|
||||
*t++ = *p--;
|
||||
*t++ = '\0';
|
||||
|
||||
key.data = buf;
|
||||
data.data = rbuf;
|
||||
data.size = key.size = (u_int32_t)len - 1;
|
||||
|
||||
if ((ret =
|
||||
dbp->put(dbp, NULL, &key, &data, DB_NOOVERWRITE)) != 0) {
|
||||
dbp->err(dbp, ret, "DB->put");
|
||||
if (ret != DB_KEYEXIST)
|
||||
goto err1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Close the word database. */
|
||||
(void)fclose(fp);
|
||||
|
||||
/* Print out the number of records in the database. */
|
||||
if ((ret = dbp->stat(dbp, NULL, &statp, 0)) != 0) {
|
||||
dbp->err(dbp, ret, "DB->stat");
|
||||
goto err1;
|
||||
}
|
||||
printf("%s: database contains %lu records\n",
|
||||
progname, (u_long)statp->bt_ndata);
|
||||
free(statp);
|
||||
|
||||
/* Acquire a cursor for the database. */
|
||||
if ((ret = dbp->cursor(dbp, NULL, &dbcp, 0)) != 0) {
|
||||
dbp->err(dbp, ret, "DB->cursor");
|
||||
goto err1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prompt the user for a record number, then retrieve and display
|
||||
* that record.
|
||||
*/
|
||||
for (;;) {
|
||||
/* Get a record number. */
|
||||
printf("recno #> ");
|
||||
fflush(stdout);
|
||||
if (fgets(buf, sizeof(buf), stdin) == NULL)
|
||||
break;
|
||||
recno = atoi(buf);
|
||||
|
||||
/*
|
||||
* Reset the key each time, the dbp->get() routine returns
|
||||
* the key and data pair, not just the key!
|
||||
*/
|
||||
key.data = &recno;
|
||||
key.size = sizeof(recno);
|
||||
if ((ret = dbcp->get(dbcp, &key, &data, DB_SET_RECNO)) != 0)
|
||||
goto get_err;
|
||||
|
||||
/* Display the key and data. */
|
||||
show("k/d\t", &key, &data);
|
||||
|
||||
/* Move the cursor a record forward. */
|
||||
if ((ret = dbcp->get(dbcp, &key, &data, DB_NEXT)) != 0)
|
||||
goto get_err;
|
||||
|
||||
/* Display the key and data. */
|
||||
show("next\t", &key, &data);
|
||||
|
||||
/*
|
||||
* Retrieve the record number for the following record into
|
||||
* local memory.
|
||||
*/
|
||||
data.data = &recno;
|
||||
data.size = sizeof(recno);
|
||||
data.ulen = sizeof(recno);
|
||||
data.flags |= DB_DBT_USERMEM;
|
||||
if ((ret = dbcp->get(dbcp, &key, &data, DB_GET_RECNO)) != 0) {
|
||||
get_err: dbp->err(dbp, ret, "DBcursor->get");
|
||||
if (ret != DB_NOTFOUND && ret != DB_KEYEMPTY)
|
||||
goto err2;
|
||||
} else
|
||||
printf("retrieved recno: %lu\n", (u_long)recno);
|
||||
|
||||
/* Reset the data DBT. */
|
||||
memset(&data, 0, sizeof(data));
|
||||
}
|
||||
|
||||
if ((ret = dbcp->close(dbcp)) != 0) {
|
||||
dbp->err(dbp, ret, "DBcursor->close");
|
||||
goto err1;
|
||||
}
|
||||
if ((ret = dbp->close(dbp, 0)) != 0) {
|
||||
fprintf(stderr,
|
||||
"%s: DB->close: %s\n", progname, db_strerror(ret));
|
||||
return (1);
|
||||
}
|
||||
|
||||
return (0);
|
||||
|
||||
err2: (void)dbcp->close(dbcp);
|
||||
err1: (void)dbp->close(dbp, 0);
|
||||
return (ret);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* show --
|
||||
* Display a key/data pair.
|
||||
*/
|
||||
void
|
||||
show(msg, key, data)
|
||||
const char *msg;
|
||||
DBT *key, *data;
|
||||
{
|
||||
printf("%s%.*s : %.*s\n", msg,
|
||||
(int)key->size, (char *)key->data,
|
||||
(int)data->size, (char *)data->data);
|
||||
}
|
||||
226
examples_c/ex_dbclient.c
Normal file
226
examples_c/ex_dbclient.c
Normal file
@@ -0,0 +1,226 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 1996,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: ex_dbclient.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <db.h>
|
||||
|
||||
#define DATABASE_HOME "database"
|
||||
|
||||
#define DATABASE "access.db"
|
||||
|
||||
int db_clientrun __P((DB_ENV *, const char *));
|
||||
int ex_dbclient __P((const char *));
|
||||
int ex_dbclient_run __P((const char *, FILE *, const char *, const char *));
|
||||
int main __P((int, char *[]));
|
||||
|
||||
/*
|
||||
* An example of a program creating/configuring a Berkeley DB environment.
|
||||
*/
|
||||
int
|
||||
main(argc, argv)
|
||||
int argc;
|
||||
char *argv[];
|
||||
{
|
||||
const char *home;
|
||||
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "Usage: %s hostname\n", argv[0]);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/*
|
||||
* All of the shared database files live in DATABASE_HOME, but
|
||||
* data files will live in CONFIG_DATA_DIR.
|
||||
*/
|
||||
home = DATABASE_HOME;
|
||||
return (ex_dbclient_run(home,
|
||||
stderr, argv[1], argv[0]) == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int
|
||||
ex_dbclient(host)
|
||||
const char *host;
|
||||
{
|
||||
const char *home;
|
||||
const char *progname = "ex_dbclient"; /* Program name. */
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* All of the shared database files live in DATABASE_HOME, but
|
||||
* data files will live in CONFIG_DATA_DIR.
|
||||
*/
|
||||
home = DATABASE_HOME;
|
||||
|
||||
if ((ret = ex_dbclient_run(home, stderr, host, progname)) != 0)
|
||||
return (ret);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
ex_dbclient_run(home, errfp, host, progname)
|
||||
const char *home, *host, *progname;
|
||||
FILE *errfp;
|
||||
{
|
||||
DB_ENV *dbenv;
|
||||
int ret, retry;
|
||||
|
||||
/*
|
||||
* Create an environment object and initialize it for error
|
||||
* reporting.
|
||||
*/
|
||||
if ((ret = db_env_create(&dbenv, DB_RPCCLIENT)) != 0) {
|
||||
fprintf(errfp, "%s: %s\n", progname, db_strerror(ret));
|
||||
return (1);
|
||||
}
|
||||
retry = 0;
|
||||
loop:
|
||||
while (retry < 5) {
|
||||
/*
|
||||
* Set the server host we are talking to.
|
||||
*/
|
||||
if ((ret = dbenv->set_rpc_server(dbenv, NULL, host, 10000,
|
||||
10000, 0)) != 0) {
|
||||
fprintf(stderr, "Try %d: DB_ENV->set_rpc_server: %s\n",
|
||||
retry, db_strerror(ret));
|
||||
retry++;
|
||||
sleep(15);
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
if (retry >= 5) {
|
||||
fprintf(stderr,
|
||||
"DB_ENV->set_rpc_server: %s\n", db_strerror(ret));
|
||||
dbenv->close(dbenv, 0);
|
||||
return (1);
|
||||
}
|
||||
/*
|
||||
* We want to specify the shared memory buffer pool cachesize,
|
||||
* but everything else is the default.
|
||||
*/
|
||||
if ((ret = dbenv->set_cachesize(dbenv, 0, 64 * 1024, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "set_cachesize");
|
||||
dbenv->close(dbenv, 0);
|
||||
return (1);
|
||||
}
|
||||
/*
|
||||
* We have multiple processes reading/writing these files, so
|
||||
* we need concurrency control and a shared buffer pool, but
|
||||
* not logging or transactions.
|
||||
*/
|
||||
if ((ret = dbenv->open(dbenv, home,
|
||||
DB_CREATE | DB_INIT_LOCK | DB_INIT_MPOOL, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "environment open: %s", home);
|
||||
dbenv->close(dbenv, 0);
|
||||
if (ret == DB_NOSERVER)
|
||||
goto loop;
|
||||
return (1);
|
||||
}
|
||||
|
||||
ret = db_clientrun(dbenv, progname);
|
||||
printf("db_clientrun returned %d\n", ret);
|
||||
if (ret == DB_NOSERVER)
|
||||
goto loop;
|
||||
|
||||
/* Close the handle. */
|
||||
if ((ret = dbenv->close(dbenv, 0)) != 0) {
|
||||
fprintf(stderr, "DB_ENV->close: %s\n", db_strerror(ret));
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
db_clientrun(dbenv, progname)
|
||||
DB_ENV *dbenv;
|
||||
const char *progname;
|
||||
{
|
||||
DB *dbp;
|
||||
DBT key, data;
|
||||
u_int32_t len;
|
||||
int ret;
|
||||
char *p, *t, buf[1024], rbuf[1024];
|
||||
|
||||
/* Remove the previous database. */
|
||||
|
||||
/* Create and initialize database object, open the database. */
|
||||
if ((ret = db_create(&dbp, dbenv, 0)) != 0) {
|
||||
fprintf(stderr,
|
||||
"%s: db_create: %s\n", progname, db_strerror(ret));
|
||||
return (ret);
|
||||
}
|
||||
if ((ret = dbp->set_pagesize(dbp, 1024)) != 0) {
|
||||
dbp->err(dbp, ret, "set_pagesize");
|
||||
goto err1;
|
||||
}
|
||||
if ((ret = dbp->open(dbp,
|
||||
NULL, DATABASE, NULL, DB_BTREE, DB_CREATE, 0664)) != 0) {
|
||||
dbp->err(dbp, ret, "%s: open", DATABASE);
|
||||
goto err1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert records into the database, where the key is the user
|
||||
* input and the data is the user input in reverse order.
|
||||
*/
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
memset(&data, 0, sizeof(DBT));
|
||||
for (;;) {
|
||||
printf("input> ");
|
||||
fflush(stdout);
|
||||
if (fgets(buf, sizeof(buf), stdin) == NULL)
|
||||
break;
|
||||
if ((len = strlen(buf)) <= 1)
|
||||
continue;
|
||||
for (t = rbuf, p = buf + (len - 2); p >= buf;)
|
||||
*t++ = *p--;
|
||||
*t++ = '\0';
|
||||
|
||||
key.data = buf;
|
||||
data.data = rbuf;
|
||||
data.size = key.size = len - 1;
|
||||
|
||||
switch (ret =
|
||||
dbp->put(dbp, NULL, &key, &data, DB_NOOVERWRITE)) {
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
dbp->err(dbp, ret, "DB->put");
|
||||
if (ret != DB_KEYEXIST)
|
||||
goto err1;
|
||||
break;
|
||||
}
|
||||
memset(&data, 0, sizeof(DBT));
|
||||
switch (ret = dbp->get(dbp, NULL, &key, &data, 0)) {
|
||||
case 0:
|
||||
printf("%.*s : %.*s\n",
|
||||
(int)key.size, (char *)key.data,
|
||||
(int)data.size, (char *)data.data);
|
||||
break;
|
||||
default:
|
||||
dbp->err(dbp, ret, "DB->get");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((ret = dbp->close(dbp, 0)) != 0) {
|
||||
fprintf(stderr,
|
||||
"%s: DB->close: %s\n", progname, db_strerror(ret));
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
|
||||
err1: (void)dbp->close(dbp, 0);
|
||||
return (ret);
|
||||
}
|
||||
134
examples_c/ex_env.c
Normal file
134
examples_c/ex_env.c
Normal file
@@ -0,0 +1,134 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 1996,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: ex_env.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <db.h>
|
||||
|
||||
#ifdef macintosh
|
||||
#define DATABASE_HOME ":database"
|
||||
#define CONFIG_DATA_DIR ":database"
|
||||
#else
|
||||
#ifdef DB_WIN32
|
||||
#define DATABASE_HOME "\\tmp\\database"
|
||||
#define CONFIG_DATA_DIR "\\database\\files"
|
||||
#else
|
||||
#define DATABASE_HOME "/tmp/database"
|
||||
#define CONFIG_DATA_DIR "/database/files"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
int db_setup __P((const char *, const char *, FILE *, const char *));
|
||||
int db_teardown __P((const char *, const char *, FILE *, const char *));
|
||||
int main __P((void));
|
||||
|
||||
/*
|
||||
* An example of a program creating/configuring a Berkeley DB environment.
|
||||
*/
|
||||
int
|
||||
main()
|
||||
{
|
||||
const char *data_dir, *home;
|
||||
const char *progname = "ex_env"; /* Program name. */
|
||||
|
||||
/*
|
||||
* All of the shared database files live in DATABASE_HOME, but
|
||||
* data files will live in CONFIG_DATA_DIR.
|
||||
*/
|
||||
home = DATABASE_HOME;
|
||||
data_dir = CONFIG_DATA_DIR;
|
||||
|
||||
printf("Setup env\n");
|
||||
if (db_setup(home, data_dir, stderr, progname) != 0)
|
||||
return (EXIT_FAILURE);
|
||||
|
||||
printf("Teardown env\n");
|
||||
if (db_teardown(home, data_dir, stderr, progname) != 0)
|
||||
return (EXIT_FAILURE);
|
||||
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
int
|
||||
db_setup(home, data_dir, errfp, progname)
|
||||
const char *home, *data_dir, *progname;
|
||||
FILE *errfp;
|
||||
{
|
||||
DB_ENV *dbenv;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Create an environment object and initialize it for error
|
||||
* reporting.
|
||||
*/
|
||||
if ((ret = db_env_create(&dbenv, 0)) != 0) {
|
||||
fprintf(errfp, "%s: %s\n", progname, db_strerror(ret));
|
||||
return (1);
|
||||
}
|
||||
dbenv->set_errfile(dbenv, errfp);
|
||||
dbenv->set_errpfx(dbenv, progname);
|
||||
|
||||
/*
|
||||
* We want to specify the shared memory buffer pool cachesize,
|
||||
* but everything else is the default.
|
||||
*/
|
||||
if ((ret = dbenv->set_cachesize(dbenv, 0, 64 * 1024, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "set_cachesize");
|
||||
dbenv->close(dbenv, 0);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Databases are in a subdirectory. */
|
||||
(void)dbenv->set_data_dir(dbenv, data_dir);
|
||||
|
||||
/* Open the environment with full transactional support. */
|
||||
if ((ret = dbenv->open(dbenv, home,
|
||||
DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN,
|
||||
0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "environment open: %s", home);
|
||||
dbenv->close(dbenv, 0);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Do something interesting... */
|
||||
|
||||
/* Close the handle. */
|
||||
if ((ret = dbenv->close(dbenv, 0)) != 0) {
|
||||
fprintf(stderr, "DB_ENV->close: %s\n", db_strerror(ret));
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
db_teardown(home, data_dir, errfp, progname)
|
||||
const char *home, *data_dir, *progname;
|
||||
FILE *errfp;
|
||||
{
|
||||
DB_ENV *dbenv;
|
||||
int ret;
|
||||
|
||||
/* Remove the shared database regions. */
|
||||
if ((ret = db_env_create(&dbenv, 0)) != 0) {
|
||||
fprintf(errfp, "%s: %s\n", progname, db_strerror(ret));
|
||||
return (1);
|
||||
}
|
||||
dbenv->set_errfile(dbenv, errfp);
|
||||
dbenv->set_errpfx(dbenv, progname);
|
||||
|
||||
(void)dbenv->set_data_dir(dbenv, data_dir);
|
||||
if ((ret = dbenv->remove(dbenv, home, 0)) != 0) {
|
||||
fprintf(stderr, "DB_ENV->remove: %s\n", db_strerror(ret));
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
239
examples_c/ex_lock.c
Normal file
239
examples_c/ex_lock.c
Normal file
@@ -0,0 +1,239 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 1997,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: ex_lock.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
extern int getopt(int, char * const *, const char *);
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <db.h>
|
||||
|
||||
int db_init __P((const char *, u_int32_t, int));
|
||||
int main __P((int, char *[]));
|
||||
int usage __P((void));
|
||||
|
||||
DB_ENV *dbenv;
|
||||
const char
|
||||
*progname = "ex_lock"; /* Program name. */
|
||||
|
||||
int
|
||||
main(argc, argv)
|
||||
int argc;
|
||||
char *argv[];
|
||||
{
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
DBT lock_dbt;
|
||||
DB_LOCK lock;
|
||||
DB_LOCK *locks;
|
||||
db_lockmode_t lock_type;
|
||||
long held;
|
||||
size_t len;
|
||||
u_int32_t locker, maxlocks;
|
||||
int ch, do_unlink, did_get, i, lockid, lockcount, ret;
|
||||
const char *home;
|
||||
char opbuf[16], objbuf[1024], lockbuf[16];
|
||||
|
||||
home = "TESTDIR";
|
||||
maxlocks = 0;
|
||||
do_unlink = 0;
|
||||
while ((ch = getopt(argc, argv, "h:m:u")) != EOF)
|
||||
switch (ch) {
|
||||
case 'h':
|
||||
home = optarg;
|
||||
break;
|
||||
case 'm':
|
||||
if ((i = atoi(optarg)) <= 0)
|
||||
return (usage());
|
||||
maxlocks = (u_int32_t)i; /* XXX: possible overflow. */
|
||||
break;
|
||||
case 'u':
|
||||
do_unlink = 1;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
return (usage());
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (argc != 0)
|
||||
return (usage());
|
||||
|
||||
/* Initialize the database environment. */
|
||||
if ((ret = db_init(home, maxlocks, do_unlink)) != 0)
|
||||
return (ret);
|
||||
|
||||
locks = 0;
|
||||
lockcount = 0;
|
||||
|
||||
/*
|
||||
* Accept lock requests.
|
||||
*/
|
||||
if ((ret = dbenv->lock_id(dbenv, &locker)) != 0) {
|
||||
dbenv->err(dbenv, ret, "unable to get locker id");
|
||||
(void)dbenv->close(dbenv, 0);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
lockid = -1;
|
||||
|
||||
memset(&lock_dbt, 0, sizeof(lock_dbt));
|
||||
for (held = 0, did_get = 0;;) {
|
||||
printf("Operation get/release [get]> ");
|
||||
fflush(stdout);
|
||||
if (fgets(opbuf, sizeof(opbuf), stdin) == NULL)
|
||||
break;
|
||||
if ((len = strlen(opbuf)) <= 1 || strcmp(opbuf, "get\n") == 0) {
|
||||
/* Acquire a lock. */
|
||||
printf("input object (text string) to lock> ");
|
||||
fflush(stdout);
|
||||
if (fgets(objbuf, sizeof(objbuf), stdin) == NULL)
|
||||
break;
|
||||
if ((len = strlen(objbuf)) <= 1)
|
||||
continue;
|
||||
|
||||
do {
|
||||
printf("lock type read/write [read]> ");
|
||||
fflush(stdout);
|
||||
if (fgets(lockbuf,
|
||||
sizeof(lockbuf), stdin) == NULL)
|
||||
break;
|
||||
len = strlen(lockbuf);
|
||||
} while (len > 1 &&
|
||||
strcmp(lockbuf, "read\n") != 0 &&
|
||||
strcmp(lockbuf, "write\n") != 0);
|
||||
if (len == 1 || strcmp(lockbuf, "read\n") == 0)
|
||||
lock_type = DB_LOCK_READ;
|
||||
else
|
||||
lock_type = DB_LOCK_WRITE;
|
||||
|
||||
lock_dbt.data = objbuf;
|
||||
lock_dbt.size = (u_int32_t)strlen(objbuf);
|
||||
ret = dbenv->lock_get(dbenv, locker,
|
||||
DB_LOCK_NOWAIT, &lock_dbt, lock_type, &lock);
|
||||
if (ret == 0) {
|
||||
did_get = 1;
|
||||
lockid = lockcount++;
|
||||
if (locks == NULL)
|
||||
locks =
|
||||
(DB_LOCK *)malloc(sizeof(DB_LOCK));
|
||||
else
|
||||
locks = (DB_LOCK *)realloc(locks,
|
||||
lockcount * sizeof(DB_LOCK));
|
||||
locks[lockid] = lock;
|
||||
}
|
||||
} else {
|
||||
/* Release a lock. */
|
||||
do {
|
||||
printf("input lock to release> ");
|
||||
fflush(stdout);
|
||||
if (fgets(objbuf,
|
||||
sizeof(objbuf), stdin) == NULL)
|
||||
break;
|
||||
} while ((len = strlen(objbuf)) <= 1);
|
||||
lockid = strtol(objbuf, NULL, 16);
|
||||
if (lockid < 0 || lockid >= lockcount) {
|
||||
printf("Lock #%d out of range\n", lockid);
|
||||
continue;
|
||||
}
|
||||
lock = locks[lockid];
|
||||
ret = dbenv->lock_put(dbenv, &lock);
|
||||
did_get = 0;
|
||||
}
|
||||
switch (ret) {
|
||||
case 0:
|
||||
printf("Lock #%d %s\n", lockid,
|
||||
did_get ? "granted" : "released");
|
||||
held += did_get ? 1 : -1;
|
||||
break;
|
||||
case DB_LOCK_NOTGRANTED:
|
||||
dbenv->err(dbenv, ret, NULL);
|
||||
break;
|
||||
case DB_LOCK_DEADLOCK:
|
||||
dbenv->err(dbenv, ret,
|
||||
"lock_%s", did_get ? "get" : "put");
|
||||
break;
|
||||
default:
|
||||
dbenv->err(dbenv, ret,
|
||||
"lock_%s", did_get ? "get" : "put");
|
||||
(void)dbenv->close(dbenv, 0);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\nClosing lock region %ld locks held\n", held);
|
||||
|
||||
if (locks != NULL)
|
||||
free(locks);
|
||||
|
||||
if ((ret = dbenv->close(dbenv, 0)) != 0) {
|
||||
fprintf(stderr,
|
||||
"%s: dbenv->close: %s\n", progname, db_strerror(ret));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/*
|
||||
* db_init --
|
||||
* Initialize the environment.
|
||||
*/
|
||||
int
|
||||
db_init(home, maxlocks, do_unlink)
|
||||
const char *home;
|
||||
u_int32_t maxlocks;
|
||||
int do_unlink;
|
||||
{
|
||||
int ret;
|
||||
|
||||
if ((ret = db_env_create(&dbenv, 0)) != 0) {
|
||||
fprintf(stderr, "%s: db_env_create: %s\n",
|
||||
progname, db_strerror(ret));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (do_unlink) {
|
||||
if ((ret = dbenv->remove(dbenv, home, DB_FORCE)) != 0) {
|
||||
fprintf(stderr, "%s: dbenv->remove: %s\n",
|
||||
progname, db_strerror(ret));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
if ((ret = db_env_create(&dbenv, 0)) != 0) {
|
||||
fprintf(stderr, "%s: db_env_create: %s\n",
|
||||
progname, db_strerror(ret));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
dbenv->set_errfile(dbenv, stderr);
|
||||
dbenv->set_errpfx(dbenv, progname);
|
||||
if (maxlocks != 0)
|
||||
dbenv->set_lk_max_locks(dbenv, maxlocks);
|
||||
|
||||
if ((ret =
|
||||
dbenv->open(dbenv, home, DB_CREATE | DB_INIT_LOCK, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, NULL);
|
||||
(void)dbenv->close(dbenv, 0);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
usage()
|
||||
{
|
||||
(void)fprintf(stderr,
|
||||
"usage: %s [-u] [-h home] [-m maxlocks]\n", progname);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
258
examples_c/ex_mpool.c
Normal file
258
examples_c/ex_mpool.c
Normal file
@@ -0,0 +1,258 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 1997,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: ex_mpool.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
extern int getopt(int, char * const *, const char *);
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <db.h>
|
||||
|
||||
int init __P((const char *, int, int, const char *));
|
||||
int run __P((int, int, int, int, const char *));
|
||||
int run_mpool __P((int, int, int, int, const char *));
|
||||
int main __P((int, char *[]));
|
||||
int usage __P((const char *));
|
||||
#define MPOOL "mpool" /* File. */
|
||||
|
||||
int
|
||||
main(argc, argv)
|
||||
int argc;
|
||||
char *argv[];
|
||||
{
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
int cachesize, ch, hits, npages, pagesize;
|
||||
char *progname;
|
||||
|
||||
cachesize = 20 * 1024;
|
||||
hits = 1000;
|
||||
npages = 50;
|
||||
pagesize = 1024;
|
||||
progname = argv[0];
|
||||
while ((ch = getopt(argc, argv, "c:h:n:p:")) != EOF)
|
||||
switch (ch) {
|
||||
case 'c':
|
||||
if ((cachesize = atoi(optarg)) < 20 * 1024)
|
||||
return (usage(progname));
|
||||
break;
|
||||
case 'h':
|
||||
if ((hits = atoi(optarg)) <= 0)
|
||||
return (usage(progname));
|
||||
break;
|
||||
case 'n':
|
||||
if ((npages = atoi(optarg)) <= 0)
|
||||
return (usage(progname));
|
||||
break;
|
||||
case 'p':
|
||||
if ((pagesize = atoi(optarg)) <= 0)
|
||||
return (usage(progname));
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
return (usage(progname));
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
return (run_mpool(pagesize, cachesize,
|
||||
hits, npages, progname) == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int
|
||||
usage(progname)
|
||||
const char *progname;
|
||||
{
|
||||
(void)fprintf(stderr,
|
||||
"usage: %s [-c cachesize] [-h hits] [-n npages] [-p pagesize]\n",
|
||||
progname);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int
|
||||
run_mpool(pagesize, cachesize, hits, npages, progname)
|
||||
int pagesize, cachesize, hits, npages;
|
||||
const char *progname;
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Initialize the file. */
|
||||
if ((ret = init(MPOOL, pagesize, npages, progname)) != 0)
|
||||
return (ret);
|
||||
|
||||
/* Get the pages. */
|
||||
if ((ret = run(hits, cachesize, pagesize, npages, progname)) != 0)
|
||||
return (ret);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* init --
|
||||
* Create a backing file.
|
||||
*/
|
||||
int
|
||||
init(file, pagesize, npages, progname)
|
||||
const char *file, *progname;
|
||||
int pagesize, npages;
|
||||
{
|
||||
FILE *fp;
|
||||
int cnt;
|
||||
char *p;
|
||||
|
||||
/*
|
||||
* Create a file with the right number of pages, and store a page
|
||||
* number on each page.
|
||||
*/
|
||||
(void)remove(file);
|
||||
if ((fp = fopen(file, "wb")) == NULL) {
|
||||
fprintf(stderr,
|
||||
"%s: %s: %s\n", progname, file, strerror(errno));
|
||||
return (1);
|
||||
}
|
||||
if ((p = (char *)malloc(pagesize)) == NULL) {
|
||||
fprintf(stderr, "%s: %s\n", progname, strerror(ENOMEM));
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* The pages are numbered from 0, not 1.
|
||||
*
|
||||
* Write the index of the page at the beginning of the page in order
|
||||
* to verify the retrieved page (see run()).
|
||||
*/
|
||||
for (cnt = 0; cnt < npages; ++cnt) {
|
||||
*(db_pgno_t *)p = cnt;
|
||||
if (fwrite(p, pagesize, 1, fp) != 1) {
|
||||
fprintf(stderr,
|
||||
"%s: %s: %s\n", progname, file, strerror(errno));
|
||||
return (1);
|
||||
}
|
||||
}
|
||||
|
||||
(void)fclose(fp);
|
||||
free(p);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* run --
|
||||
* Get a set of pages.
|
||||
*/
|
||||
int
|
||||
run(hits, cachesize, pagesize, npages, progname)
|
||||
int hits, cachesize, pagesize, npages;
|
||||
const char *progname;
|
||||
{
|
||||
DB_ENV *dbenv;
|
||||
DB_MPOOLFILE *mfp;
|
||||
db_pgno_t pageno;
|
||||
int cnt, ret;
|
||||
void *p;
|
||||
|
||||
dbenv = NULL;
|
||||
mfp = NULL;
|
||||
|
||||
printf("%s: cachesize: %d; pagesize: %d; N pages: %d\n",
|
||||
progname, cachesize, pagesize, npages);
|
||||
|
||||
/*
|
||||
* Open a memory pool, specify a cachesize, output error messages
|
||||
* to stderr.
|
||||
*/
|
||||
if ((ret = db_env_create(&dbenv, 0)) != 0) {
|
||||
fprintf(stderr,
|
||||
"%s: db_env_create: %s\n", progname, db_strerror(ret));
|
||||
return (1);
|
||||
}
|
||||
dbenv->set_errfile(dbenv, stderr);
|
||||
dbenv->set_errpfx(dbenv, progname);
|
||||
#ifdef HAVE_VXWORKS
|
||||
if ((ret = dbenv->set_shm_key(dbenv, VXSHM_KEY)) != 0) {
|
||||
dbenv->err(dbenv, ret, "set_shm_key");
|
||||
return (1);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Set the cachesize. */
|
||||
if ((ret = dbenv->set_cachesize(dbenv, 0, cachesize, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "set_cachesize");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Open the environment. */
|
||||
if ((ret = dbenv->open(
|
||||
dbenv, NULL, DB_CREATE | DB_INIT_MPOOL, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB_ENV->open");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Open the file in the environment. */
|
||||
if ((ret = dbenv->memp_fcreate(dbenv, &mfp, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB_ENV->memp_fcreate: %s", MPOOL);
|
||||
goto err;
|
||||
}
|
||||
if ((ret = mfp->open(mfp, MPOOL, 0, 0, pagesize)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB_MPOOLFILE->open: %s", MPOOL);
|
||||
goto err;
|
||||
}
|
||||
|
||||
printf("retrieve %d random pages... ", hits);
|
||||
|
||||
srand((u_int)time(NULL));
|
||||
for (cnt = 0; cnt < hits; ++cnt) {
|
||||
pageno = rand() % npages;
|
||||
if ((ret = mfp->get(mfp, &pageno, NULL, 0, &p)) != 0) {
|
||||
dbenv->err(dbenv, ret,
|
||||
"unable to retrieve page %lu", (u_long)pageno);
|
||||
goto err;
|
||||
}
|
||||
/* Verify the page's number that was written in init(). */
|
||||
if (*(db_pgno_t *)p != pageno) {
|
||||
dbenv->errx(dbenv,
|
||||
"wrong page retrieved (%lu != %d)",
|
||||
(u_long)pageno, *(int *)p);
|
||||
goto err;
|
||||
}
|
||||
if ((ret = mfp->put(mfp, p, DB_PRIORITY_UNCHANGED, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret,
|
||||
"unable to return page %lu", (u_long)pageno);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
printf("successful.\n");
|
||||
|
||||
/* Close the file. */
|
||||
if ((ret = mfp->close(mfp, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB_MPOOLFILE->close");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Close the pool. */
|
||||
if ((ret = dbenv->close(dbenv, 0)) != 0) {
|
||||
fprintf(stderr,
|
||||
"%s: db_env_create: %s\n", progname, db_strerror(ret));
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
|
||||
err: if (mfp != NULL)
|
||||
(void)mfp->close(mfp, 0);
|
||||
if (dbenv != NULL)
|
||||
(void)dbenv->close(dbenv, 0);
|
||||
return (1);
|
||||
}
|
||||
19
examples_c/ex_rep/README
Normal file
19
examples_c/ex_rep/README
Normal file
@@ -0,0 +1,19 @@
|
||||
# $Id: README,v 1.1 2006/07/07 23:36:13 alanb Exp $
|
||||
|
||||
This is the parent directory for the replication example programs.
|
||||
|
||||
The example is a toy stock quote server. There are two versions of
|
||||
the program: one version uses Berkeley DB's Replication Manager
|
||||
support, and the other uses the base replication API.
|
||||
|
||||
common/ Contains code to implement the basic functions of the
|
||||
application, to demonstrate that these are largely
|
||||
independent of which replication API is used.
|
||||
|
||||
mgr/ Contains the small amount of code necessary to
|
||||
configure the application to use Replication Manager.
|
||||
|
||||
base/ Contains the sample communications infrastructure, and
|
||||
other replication support code, to demonstrate some of
|
||||
the kinds of things that are necessary when using the
|
||||
base replication API.
|
||||
304
examples_c/ex_rep/base/rep_base.c
Normal file
304
examples_c/ex_rep/base/rep_base.c
Normal file
@@ -0,0 +1,304 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2001,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: rep_base.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <db.h>
|
||||
|
||||
#include "rep_base.h"
|
||||
|
||||
/*
|
||||
* Process globals (we could put these in the machtab I suppose).
|
||||
*/
|
||||
int master_eid;
|
||||
char *myaddr;
|
||||
unsigned short myport;
|
||||
|
||||
static void event_callback __P((DB_ENV *, u_int32_t, void *));
|
||||
|
||||
int
|
||||
main(argc, argv)
|
||||
int argc;
|
||||
char *argv[];
|
||||
{
|
||||
extern char *optarg;
|
||||
DB_ENV *dbenv;
|
||||
DBT local;
|
||||
enum { MASTER, CLIENT, UNKNOWN } whoami;
|
||||
all_args aa;
|
||||
connect_args ca;
|
||||
machtab_t *machtab;
|
||||
thread_t all_thr, conn_thr;
|
||||
void *astatus, *cstatus;
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
#else
|
||||
struct sigaction sigact;
|
||||
#endif
|
||||
repsite_t site, *sitep, self, *selfp;
|
||||
int maxsites, nsites, ret, priority, totalsites;
|
||||
char *c, ch;
|
||||
const char *home, *progname;
|
||||
APP_DATA my_app_data;
|
||||
|
||||
master_eid = DB_EID_INVALID;
|
||||
|
||||
my_app_data.elected = 0;
|
||||
my_app_data.shared_data.is_master = 0; /* assume start out as client */
|
||||
dbenv = NULL;
|
||||
whoami = UNKNOWN;
|
||||
machtab = NULL;
|
||||
selfp = sitep = NULL;
|
||||
maxsites = nsites = ret = totalsites = 0;
|
||||
priority = 100;
|
||||
home = "TESTDIR";
|
||||
progname = "ex_rep_base";
|
||||
|
||||
if ((ret = create_env(progname, &dbenv)) != 0)
|
||||
goto err;
|
||||
dbenv->app_private = &my_app_data;
|
||||
(void)dbenv->set_event_notify(dbenv, event_callback);
|
||||
|
||||
while ((ch = getopt(argc, argv, "Ch:Mm:n:o:p:v")) != EOF)
|
||||
switch (ch) {
|
||||
case 'M':
|
||||
whoami = MASTER;
|
||||
master_eid = SELF_EID;
|
||||
break;
|
||||
case 'C':
|
||||
whoami = CLIENT;
|
||||
break;
|
||||
case 'h':
|
||||
home = optarg;
|
||||
break;
|
||||
case 'm':
|
||||
if ((myaddr = strdup(optarg)) == NULL) {
|
||||
fprintf(stderr,
|
||||
"System error %s\n", strerror(errno));
|
||||
goto err;
|
||||
}
|
||||
self.host = optarg;
|
||||
self.host = strtok(self.host, ":");
|
||||
if ((c = strtok(NULL, ":")) == NULL) {
|
||||
fprintf(stderr, "Bad host specification.\n");
|
||||
goto err;
|
||||
}
|
||||
myport = self.port = (unsigned short)atoi(c);
|
||||
selfp = &self;
|
||||
break;
|
||||
case 'n':
|
||||
totalsites = atoi(optarg);
|
||||
break;
|
||||
case 'o':
|
||||
site.host = optarg;
|
||||
site.host = strtok(site.host, ":");
|
||||
if ((c = strtok(NULL, ":")) == NULL) {
|
||||
fprintf(stderr, "Bad host specification.\n");
|
||||
goto err;
|
||||
}
|
||||
site.port = atoi(c);
|
||||
if (sitep == NULL || nsites >= maxsites) {
|
||||
maxsites = maxsites == 0 ? 10 : 2 * maxsites;
|
||||
if ((sitep = realloc(sitep,
|
||||
maxsites * sizeof(repsite_t))) == NULL) {
|
||||
fprintf(stderr, "System error %s\n",
|
||||
strerror(errno));
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
sitep[nsites++] = site;
|
||||
break;
|
||||
case 'p':
|
||||
priority = atoi(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
if ((ret = dbenv->set_verbose(dbenv,
|
||||
DB_VERB_REPLICATION, 1)) != 0)
|
||||
goto err;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
usage(progname);
|
||||
}
|
||||
|
||||
/* Error check command line. */
|
||||
if (whoami == UNKNOWN) {
|
||||
fprintf(stderr, "Must specify -M or -C.\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (selfp == NULL)
|
||||
usage(progname);
|
||||
|
||||
if (home == NULL)
|
||||
usage(progname);
|
||||
|
||||
dbenv->rep_set_priority(dbenv, priority);
|
||||
|
||||
#ifdef _WIN32
|
||||
/* Initialize the Windows sockets DLL. */
|
||||
if ((ret = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0) {
|
||||
fprintf(stderr,
|
||||
"Unable to initialize Windows sockets: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
#else
|
||||
/*
|
||||
* Turn off SIGPIPE so that we don't kill processes when they
|
||||
* happen to lose a connection at the wrong time.
|
||||
*/
|
||||
memset(&sigact, 0, sizeof(sigact));
|
||||
sigact.sa_handler = SIG_IGN;
|
||||
if ((ret = sigaction(SIGPIPE, &sigact, NULL)) != 0) {
|
||||
fprintf(stderr,
|
||||
"Unable to turn off SIGPIPE: %s\n", strerror(ret));
|
||||
goto err;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* We are hardcoding priorities here that all clients have the
|
||||
* same priority except for a designated master who gets a higher
|
||||
* priority.
|
||||
*/
|
||||
if ((ret =
|
||||
machtab_init(&machtab, totalsites)) != 0)
|
||||
goto err;
|
||||
my_app_data.comm_infrastructure = machtab;
|
||||
|
||||
if ((ret = env_init(dbenv, home)) != 0)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* Now sets up comm infrastructure. There are two phases. First,
|
||||
* we open our port for listening for incoming connections. Then
|
||||
* we attempt to connect to every host we know about.
|
||||
*/
|
||||
|
||||
(void)dbenv->rep_set_transport(dbenv, SELF_EID, quote_send);
|
||||
|
||||
ca.dbenv = dbenv;
|
||||
ca.home = home;
|
||||
ca.progname = progname;
|
||||
ca.machtab = machtab;
|
||||
ca.port = selfp->port;
|
||||
if ((ret = thread_create(&conn_thr, NULL, connect_thread, &ca)) != 0) {
|
||||
dbenv->errx(dbenv, "can't create connect thread");
|
||||
goto err;
|
||||
}
|
||||
|
||||
aa.dbenv = dbenv;
|
||||
aa.progname = progname;
|
||||
aa.home = home;
|
||||
aa.machtab = machtab;
|
||||
aa.sites = sitep;
|
||||
aa.nsites = nsites;
|
||||
if ((ret = thread_create(&all_thr, NULL, connect_all, &aa)) != 0) {
|
||||
dbenv->errx(dbenv, "can't create connect-all thread");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* We have now got the entire communication infrastructure set up.
|
||||
* It's time to declare ourselves to be a client or master.
|
||||
*/
|
||||
if (whoami == MASTER) {
|
||||
if ((ret = dbenv->rep_start(dbenv, NULL, DB_REP_MASTER)) != 0) {
|
||||
dbenv->err(dbenv, ret, "dbenv->rep_start failed");
|
||||
goto err;
|
||||
}
|
||||
} else {
|
||||
memset(&local, 0, sizeof(local));
|
||||
local.data = myaddr;
|
||||
local.size = (u_int32_t)strlen(myaddr) + 1;
|
||||
if ((ret =
|
||||
dbenv->rep_start(dbenv, &local, DB_REP_CLIENT)) != 0) {
|
||||
dbenv->err(dbenv, ret, "dbenv->rep_start failed");
|
||||
goto err;
|
||||
}
|
||||
/* Sleep to give ourselves time to find a master. */
|
||||
sleep(5);
|
||||
}
|
||||
if ((ret = doloop(dbenv, &my_app_data.shared_data)) != 0) {
|
||||
dbenv->err(dbenv, ret, "Main loop failed");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Wait on the connection threads. */
|
||||
if (thread_join(all_thr, &astatus) || thread_join(conn_thr, &cstatus)) {
|
||||
ret = -1;
|
||||
goto err;
|
||||
}
|
||||
if ((uintptr_t)astatus != EXIT_SUCCESS ||
|
||||
(uintptr_t)cstatus != EXIT_SUCCESS) {
|
||||
ret = -1;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* We have used the DB_TXN_NOSYNC environment flag for improved
|
||||
* performance without the usual sacrifice of transactional durability,
|
||||
* as discussed in the "Transactional guarantees" page of the Reference
|
||||
* Guide: if one replication site crashes, we can expect the data to
|
||||
* exist at another site. However, in case we shut down all sites
|
||||
* gracefully, we push out the end of the log here so that the most
|
||||
* recent transactions don't mysteriously disappear.
|
||||
*/
|
||||
if ((ret = dbenv->log_flush(dbenv, NULL)) != 0)
|
||||
dbenv->err(dbenv, ret, "log_flush");
|
||||
|
||||
err: if (machtab != NULL)
|
||||
free(machtab);
|
||||
if (dbenv != NULL)
|
||||
(void)dbenv->close(dbenv, 0);
|
||||
#ifdef _WIN32
|
||||
/* Shut down the Windows sockets DLL. */
|
||||
(void)WSACleanup();
|
||||
#endif
|
||||
return (ret);
|
||||
}
|
||||
|
||||
static void
|
||||
event_callback(dbenv, which, info)
|
||||
DB_ENV *dbenv;
|
||||
u_int32_t which;
|
||||
void *info;
|
||||
{
|
||||
APP_DATA *app = dbenv->app_private;
|
||||
SHARED_DATA *shared = &app->shared_data;
|
||||
|
||||
switch (which) {
|
||||
case DB_EVENT_REP_CLIENT:
|
||||
shared->is_master = 0;
|
||||
break;
|
||||
|
||||
case DB_EVENT_REP_ELECTED:
|
||||
app->elected = 1;
|
||||
master_eid = SELF_EID;
|
||||
break;
|
||||
|
||||
case DB_EVENT_REP_MASTER:
|
||||
shared->is_master = 1;
|
||||
break;
|
||||
|
||||
case DB_EVENT_REP_NEWMASTER:
|
||||
master_eid = *(int*)info;
|
||||
break;
|
||||
|
||||
case DB_EVENT_REP_STARTUPDONE:
|
||||
/* I don't care about this, for now. */
|
||||
break;
|
||||
|
||||
default:
|
||||
dbenv->errx(dbenv, "ignoring event %d", which);
|
||||
}
|
||||
}
|
||||
141
examples_c/ex_rep/base/rep_base.h
Normal file
141
examples_c/ex_rep/base/rep_base.h
Normal file
@@ -0,0 +1,141 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2001,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: rep_base.h 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#ifndef _EX_REPQUOTE_H_
|
||||
#define _EX_REPQUOTE_H_
|
||||
|
||||
#include "../common/rep_common.h"
|
||||
|
||||
#define SELF_EID 1
|
||||
|
||||
typedef struct {
|
||||
char *host; /* Host name. */
|
||||
u_int32_t port; /* Port on which to connect to this site. */
|
||||
} repsite_t;
|
||||
|
||||
/* Globals */
|
||||
typedef struct {
|
||||
SHARED_DATA shared_data;
|
||||
int elected;
|
||||
void *comm_infrastructure;
|
||||
} APP_DATA;
|
||||
|
||||
extern int master_eid;
|
||||
extern char *myaddr;
|
||||
extern unsigned short myport;
|
||||
|
||||
struct __member; typedef struct __member member_t;
|
||||
struct __machtab; typedef struct __machtab machtab_t;
|
||||
|
||||
/* Arguments for the connect_all thread. */
|
||||
typedef struct {
|
||||
DB_ENV *dbenv;
|
||||
const char *progname;
|
||||
const char *home;
|
||||
machtab_t *machtab;
|
||||
repsite_t *sites;
|
||||
int nsites;
|
||||
} all_args;
|
||||
|
||||
/* Arguments for the connect_loop thread. */
|
||||
typedef struct {
|
||||
DB_ENV *dbenv;
|
||||
const char * home;
|
||||
const char * progname;
|
||||
machtab_t *machtab;
|
||||
int port;
|
||||
} connect_args;
|
||||
|
||||
#define CACHESIZE (10 * 1024 * 1024)
|
||||
#define DATABASE "quote.db"
|
||||
#define MAX_THREADS 25
|
||||
#define SLEEPTIME 3
|
||||
|
||||
#ifndef COMPQUIET
|
||||
#define COMPQUIET(x,y) x = (y)
|
||||
#endif
|
||||
|
||||
/* Portability macros for basic threading and networking */
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
|
||||
extern int getopt(int, char * const *, const char *);
|
||||
|
||||
typedef HANDLE thread_t;
|
||||
#define thread_create(thrp, attr, func, arg) \
|
||||
(((*(thrp) = CreateThread(NULL, 0, \
|
||||
(LPTHREAD_START_ROUTINE)(func), (arg), 0, NULL)) == NULL) ? -1 : 0)
|
||||
#define thread_join(thr, statusp) \
|
||||
((WaitForSingleObject((thr), INFINITE) == WAIT_OBJECT_0) && \
|
||||
GetExitCodeThread((thr), (LPDWORD)(statusp)) ? 0 : -1)
|
||||
|
||||
typedef HANDLE mutex_t;
|
||||
#define mutex_init(m, attr) \
|
||||
(((*(m) = CreateMutex(NULL, FALSE, NULL)) != NULL) ? 0 : -1)
|
||||
#define mutex_lock(m) \
|
||||
((WaitForSingleObject(*(m), INFINITE) == WAIT_OBJECT_0) ? 0 : -1)
|
||||
#define mutex_unlock(m) (ReleaseMutex(*(m)) ? 0 : -1)
|
||||
#define sleep(s) Sleep(1000 * (s))
|
||||
|
||||
typedef int socklen_t;
|
||||
typedef SOCKET socket_t;
|
||||
#define SOCKET_CREATION_FAILURE INVALID_SOCKET
|
||||
#define readsocket(s, buf, sz) recv((s), (buf), (int)(sz), 0)
|
||||
#define writesocket(s, buf, sz) send((s), (const char *)(buf), (int)(sz), 0)
|
||||
#define net_errno WSAGetLastError()
|
||||
|
||||
#else /* !_WIN32 */
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/wait.h>
|
||||
#include <netdb.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef pthread_t thread_t;
|
||||
#define thread_create(thrp, attr, func, arg) \
|
||||
pthread_create((thrp), (attr), (func), (arg))
|
||||
#define thread_join(thr, statusp) pthread_join((thr), (statusp))
|
||||
|
||||
typedef pthread_mutex_t mutex_t;
|
||||
#define mutex_init(m, attr) pthread_mutex_init((m), (attr))
|
||||
#define mutex_lock(m) pthread_mutex_lock(m)
|
||||
#define mutex_unlock(m) pthread_mutex_unlock(m)
|
||||
|
||||
typedef int socket_t;
|
||||
#define SOCKET_CREATION_FAILURE -1
|
||||
#define closesocket(fd) close(fd)
|
||||
#define net_errno errno
|
||||
#define readsocket(s, buf, sz) read((s), (buf), (sz))
|
||||
#define writesocket(s, buf, sz) write((s), (buf), (sz))
|
||||
|
||||
#endif
|
||||
|
||||
void *connect_all __P((void *args));
|
||||
void *connect_thread __P((void *args));
|
||||
int doclient __P((DB_ENV *, const char *, machtab_t *));
|
||||
int domaster __P((DB_ENV *, const char *));
|
||||
socket_t get_accepted_socket __P((const char *, int));
|
||||
socket_t get_connected_socket
|
||||
__P((machtab_t *, const char *, const char *, int, int *, int *));
|
||||
int get_next_message __P((socket_t, DBT *, DBT *));
|
||||
socket_t listen_socket_init __P((const char *, int));
|
||||
socket_t listen_socket_accept
|
||||
__P((machtab_t *, const char *, socket_t, int *));
|
||||
int machtab_getinfo __P((machtab_t *, int, u_int32_t *, int *));
|
||||
int machtab_init __P((machtab_t **, int));
|
||||
void machtab_parm __P((machtab_t *, int *, u_int32_t *));
|
||||
int machtab_rem __P((machtab_t *, int, int));
|
||||
int quote_send __P((DB_ENV *, const DBT *, const DBT *, const DB_LSN *,
|
||||
int, u_int32_t));
|
||||
|
||||
#endif /* !_EX_REPQUOTE_H_ */
|
||||
467
examples_c/ex_rep/base/rep_msg.c
Normal file
467
examples_c/ex_rep/base/rep_msg.c
Normal file
@@ -0,0 +1,467 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2001,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: rep_msg.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <db.h>
|
||||
|
||||
#include "rep_base.h"
|
||||
|
||||
static int connect_site __P((DB_ENV *, machtab_t *,
|
||||
const char *, repsite_t *, int *, thread_t *));
|
||||
static void *elect_thread __P((void *));
|
||||
static void *hm_loop __P((void *));
|
||||
|
||||
typedef struct {
|
||||
DB_ENV *dbenv;
|
||||
machtab_t *machtab;
|
||||
} elect_args;
|
||||
|
||||
typedef struct {
|
||||
DB_ENV *dbenv;
|
||||
const char *progname;
|
||||
const char *home;
|
||||
socket_t fd;
|
||||
u_int32_t eid;
|
||||
machtab_t *tab;
|
||||
} hm_loop_args;
|
||||
|
||||
/*
|
||||
* This is a generic message handling loop that is used both by the
|
||||
* master to accept messages from a client as well as by clients
|
||||
* to communicate with other clients.
|
||||
*/
|
||||
static void *
|
||||
hm_loop(args)
|
||||
void *args;
|
||||
{
|
||||
DB_ENV *dbenv;
|
||||
DB_LSN permlsn;
|
||||
DBT rec, control;
|
||||
APP_DATA *app;
|
||||
const char *c, *home, *progname;
|
||||
elect_args *ea;
|
||||
hm_loop_args *ha;
|
||||
machtab_t *tab;
|
||||
thread_t elect_thr, *site_thrs, *tmp, tid;
|
||||
repsite_t self;
|
||||
u_int32_t timeout;
|
||||
int eid, n, nsites, nsites_allocd;
|
||||
int already_open, r, ret, t_ret;
|
||||
socket_t fd;
|
||||
void *status;
|
||||
|
||||
ea = NULL;
|
||||
site_thrs = NULL;
|
||||
nsites_allocd = 0;
|
||||
nsites = 0;
|
||||
|
||||
ha = (hm_loop_args *)args;
|
||||
dbenv = ha->dbenv;
|
||||
fd = ha->fd;
|
||||
home = ha->home;
|
||||
eid = ha->eid;
|
||||
progname = ha->progname;
|
||||
tab = ha->tab;
|
||||
free(ha);
|
||||
app = dbenv->app_private;
|
||||
|
||||
memset(&rec, 0, sizeof(DBT));
|
||||
memset(&control, 0, sizeof(DBT));
|
||||
|
||||
for (ret = 0; ret == 0;) {
|
||||
if ((ret = get_next_message(fd, &rec, &control)) != 0) {
|
||||
/*
|
||||
* Close this connection; if it's the master call
|
||||
* for an election.
|
||||
*/
|
||||
closesocket(fd);
|
||||
if ((ret = machtab_rem(tab, eid, 1)) != 0)
|
||||
break;
|
||||
|
||||
/*
|
||||
* If I'm the master, I just lost a client and this
|
||||
* thread is done.
|
||||
*/
|
||||
if (master_eid == SELF_EID)
|
||||
break;
|
||||
|
||||
/*
|
||||
* If I was talking with the master and the master
|
||||
* went away, I need to call an election; else I'm
|
||||
* done.
|
||||
*/
|
||||
if (master_eid != eid)
|
||||
break;
|
||||
|
||||
master_eid = DB_EID_INVALID;
|
||||
machtab_parm(tab, &n, &timeout);
|
||||
(void)dbenv->rep_set_timeout(dbenv,
|
||||
DB_REP_ELECTION_TIMEOUT, timeout);
|
||||
if ((ret = dbenv->rep_elect(dbenv,
|
||||
n, (n/2+1), 0)) != 0)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Regardless of the results, the site I was talking
|
||||
* to is gone, so I have nothing to do but exit.
|
||||
*/
|
||||
if (app->elected) {
|
||||
app->elected = 0;
|
||||
ret = dbenv->rep_start(dbenv,
|
||||
NULL, DB_REP_MASTER);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch (r = dbenv->rep_process_message(dbenv,
|
||||
&control, &rec, eid, &permlsn)) {
|
||||
case DB_REP_NEWSITE:
|
||||
/*
|
||||
* Check if we got sent connect information and if we
|
||||
* did, if this is me or if we already have a
|
||||
* connection to this new site. If we don't,
|
||||
* establish a new one.
|
||||
*/
|
||||
|
||||
/* No connect info. */
|
||||
if (rec.size == 0)
|
||||
break;
|
||||
|
||||
/* It's me, do nothing. */
|
||||
if (strncmp(myaddr, rec.data, rec.size) == 0)
|
||||
break;
|
||||
|
||||
self.host = (char *)rec.data;
|
||||
self.host = strtok(self.host, ":");
|
||||
if ((c = strtok(NULL, ":")) == NULL) {
|
||||
dbenv->errx(dbenv, "Bad host specification");
|
||||
goto out;
|
||||
}
|
||||
self.port = atoi(c);
|
||||
|
||||
/*
|
||||
* We try to connect to the new site. If we can't,
|
||||
* we treat it as an error since we know that the site
|
||||
* should be up if we got a message from it (even
|
||||
* indirectly).
|
||||
*/
|
||||
if (nsites == nsites_allocd) {
|
||||
/* Need to allocate more space. */
|
||||
if ((tmp = realloc(
|
||||
site_thrs, (10 + nsites) *
|
||||
sizeof(thread_t))) == NULL) {
|
||||
ret = errno;
|
||||
goto out;
|
||||
}
|
||||
site_thrs = tmp;
|
||||
nsites_allocd += 10;
|
||||
}
|
||||
if ((ret = connect_site(dbenv, tab, progname,
|
||||
&self, &already_open, &tid)) != 0)
|
||||
goto out;
|
||||
if (!already_open)
|
||||
memcpy(&site_thrs
|
||||
[nsites++], &tid, sizeof(thread_t));
|
||||
break;
|
||||
case DB_REP_HOLDELECTION:
|
||||
if (master_eid == SELF_EID)
|
||||
break;
|
||||
/* Make sure that previous election has finished. */
|
||||
if (ea != NULL) {
|
||||
if (thread_join(elect_thr, &status) != 0) {
|
||||
dbenv->errx(dbenv,
|
||||
"thread join failure");
|
||||
goto out;
|
||||
}
|
||||
ea = NULL;
|
||||
}
|
||||
if ((ea = calloc(sizeof(elect_args), 1)) == NULL) {
|
||||
dbenv->errx(dbenv, "can't allocate memory");
|
||||
ret = errno;
|
||||
goto out;
|
||||
}
|
||||
ea->dbenv = dbenv;
|
||||
ea->machtab = tab;
|
||||
if ((ret = thread_create(&elect_thr,
|
||||
NULL, elect_thread, (void *)ea)) != 0) {
|
||||
dbenv->errx(dbenv,
|
||||
"can't create election thread");
|
||||
}
|
||||
break;
|
||||
case DB_REP_ISPERM:
|
||||
break;
|
||||
case 0:
|
||||
if (app->elected) {
|
||||
app->elected = 0;
|
||||
if ((ret = dbenv->rep_start(dbenv,
|
||||
NULL, DB_REP_MASTER)) != 0) {
|
||||
dbenv->err(dbenv, ret,
|
||||
"can't start as master");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dbenv->err(dbenv, r, "DB_ENV->rep_process_message");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
out: if ((t_ret = machtab_rem(tab, eid, 1)) != 0 && ret == 0)
|
||||
ret = t_ret;
|
||||
|
||||
/* Don't close the environment before any children exit. */
|
||||
if (ea != NULL && thread_join(elect_thr, &status) != 0)
|
||||
dbenv->errx(dbenv, "can't join election thread");
|
||||
|
||||
if (site_thrs != NULL)
|
||||
while (--nsites >= 0)
|
||||
if (thread_join(site_thrs[nsites], &status) != 0)
|
||||
dbenv->errx(dbenv, "can't join site thread");
|
||||
|
||||
return ((void *)(uintptr_t)ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a generic thread that spawns a thread to listen for connections
|
||||
* on a socket and then spawns off child threads to handle each new
|
||||
* connection.
|
||||
*/
|
||||
void *
|
||||
connect_thread(args)
|
||||
void *args;
|
||||
{
|
||||
DB_ENV *dbenv;
|
||||
const char *home, *progname;
|
||||
hm_loop_args *ha;
|
||||
connect_args *cargs;
|
||||
machtab_t *machtab;
|
||||
thread_t hm_thrs[MAX_THREADS];
|
||||
void *status;
|
||||
int i, eid, port, ret;
|
||||
socket_t fd, ns;
|
||||
|
||||
ha = NULL;
|
||||
cargs = (connect_args *)args;
|
||||
dbenv = cargs->dbenv;
|
||||
home = cargs->home;
|
||||
progname = cargs->progname;
|
||||
machtab = cargs->machtab;
|
||||
port = cargs->port;
|
||||
|
||||
/*
|
||||
* Loop forever, accepting connections from new machines,
|
||||
* and forking off a thread to handle each.
|
||||
*/
|
||||
if ((fd = listen_socket_init(progname, port)) < 0) {
|
||||
ret = errno;
|
||||
goto err;
|
||||
}
|
||||
|
||||
for (i = 0; i < MAX_THREADS; i++) {
|
||||
if ((ns = listen_socket_accept(machtab,
|
||||
progname, fd, &eid)) == SOCKET_CREATION_FAILURE) {
|
||||
ret = errno;
|
||||
goto err;
|
||||
}
|
||||
if ((ha = calloc(sizeof(hm_loop_args), 1)) == NULL) {
|
||||
dbenv->errx(dbenv, "can't allocate memory");
|
||||
ret = errno;
|
||||
goto err;
|
||||
}
|
||||
ha->progname = progname;
|
||||
ha->home = home;
|
||||
ha->fd = ns;
|
||||
ha->eid = eid;
|
||||
ha->tab = machtab;
|
||||
ha->dbenv = dbenv;
|
||||
if ((ret = thread_create(&hm_thrs[i++], NULL,
|
||||
hm_loop, (void *)ha)) != 0) {
|
||||
dbenv->errx(dbenv, "can't create thread for site");
|
||||
goto err;
|
||||
}
|
||||
ha = NULL;
|
||||
}
|
||||
|
||||
/* If we fell out, we ended up with too many threads. */
|
||||
dbenv->errx(dbenv, "Too many threads");
|
||||
ret = ENOMEM;
|
||||
|
||||
/* Do not return until all threads have exited. */
|
||||
while (--i >= 0)
|
||||
if (thread_join(hm_thrs[i], &status) != 0)
|
||||
dbenv->errx(dbenv, "can't join site thread");
|
||||
|
||||
err: return (ret == 0 ? (void *)EXIT_SUCCESS : (void *)EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Open a connection to everyone that we've been told about. If we
|
||||
* cannot open some connections, keep trying.
|
||||
*/
|
||||
void *
|
||||
connect_all(args)
|
||||
void *args;
|
||||
{
|
||||
DB_ENV *dbenv;
|
||||
all_args *aa;
|
||||
const char *home, *progname;
|
||||
hm_loop_args *ha;
|
||||
int failed, i, nsites, open, ret, *success;
|
||||
machtab_t *machtab;
|
||||
thread_t *hm_thr;
|
||||
repsite_t *sites;
|
||||
|
||||
ha = NULL;
|
||||
aa = (all_args *)args;
|
||||
dbenv = aa->dbenv;
|
||||
progname = aa->progname;
|
||||
home = aa->home;
|
||||
machtab = aa->machtab;
|
||||
nsites = aa->nsites;
|
||||
sites = aa->sites;
|
||||
|
||||
ret = 0;
|
||||
hm_thr = NULL;
|
||||
success = NULL;
|
||||
|
||||
/* Some implementations of calloc are sad about allocating 0 things. */
|
||||
if ((success = calloc(nsites > 0 ? nsites : 1, sizeof(int))) == NULL) {
|
||||
dbenv->err(dbenv, errno, "connect_all");
|
||||
ret = 1;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (nsites > 0 && (hm_thr = calloc(nsites, sizeof(int))) == NULL) {
|
||||
dbenv->err(dbenv, errno, "connect_all");
|
||||
ret = 1;
|
||||
goto err;
|
||||
}
|
||||
|
||||
for (failed = nsites; failed > 0;) {
|
||||
for (i = 0; i < nsites; i++) {
|
||||
if (success[i])
|
||||
continue;
|
||||
|
||||
ret = connect_site(dbenv, machtab,
|
||||
progname, &sites[i], &open, &hm_thr[i]);
|
||||
|
||||
/*
|
||||
* If we couldn't make the connection, this isn't
|
||||
* fatal to the loop, but we have nothing further
|
||||
* to do on this machine at the moment.
|
||||
*/
|
||||
if (ret == DB_REP_UNAVAIL)
|
||||
continue;
|
||||
|
||||
if (ret != 0)
|
||||
goto err;
|
||||
|
||||
failed--;
|
||||
success[i] = 1;
|
||||
|
||||
/* If the connection is already open, we're done. */
|
||||
if (ret == 0 && open == 1)
|
||||
continue;
|
||||
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
err: if (success != NULL)
|
||||
free(success);
|
||||
if (hm_thr != NULL)
|
||||
free(hm_thr);
|
||||
return (ret ? (void *)EXIT_FAILURE : (void *)EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
static int
|
||||
connect_site(dbenv, machtab, progname, site, is_open, hm_thrp)
|
||||
DB_ENV *dbenv;
|
||||
machtab_t *machtab;
|
||||
const char *progname;
|
||||
repsite_t *site;
|
||||
int *is_open;
|
||||
thread_t *hm_thrp;
|
||||
{
|
||||
int eid, ret;
|
||||
socket_t s;
|
||||
hm_loop_args *ha;
|
||||
|
||||
if ((s = get_connected_socket(machtab, progname,
|
||||
site->host, site->port, is_open, &eid)) < 0)
|
||||
return (DB_REP_UNAVAIL);
|
||||
|
||||
if (*is_open)
|
||||
return (0);
|
||||
|
||||
if ((ha = calloc(sizeof(hm_loop_args), 1)) == NULL) {
|
||||
dbenv->errx(dbenv, "can't allocate memory");
|
||||
ret = errno;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ha->progname = progname;
|
||||
ha->fd = s;
|
||||
ha->eid = eid;
|
||||
ha->tab = machtab;
|
||||
ha->dbenv = dbenv;
|
||||
|
||||
if ((ret = thread_create(hm_thrp, NULL,
|
||||
hm_loop, (void *)ha)) != 0) {
|
||||
dbenv->errx(dbenv, "can't create thread for connected site");
|
||||
goto err1;
|
||||
}
|
||||
|
||||
return (0);
|
||||
|
||||
err1: free(ha);
|
||||
err:
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to spawn off a new thread in which to hold an election in
|
||||
* case we are the only thread listening on for messages.
|
||||
*/
|
||||
static void *
|
||||
elect_thread(args)
|
||||
void *args;
|
||||
{
|
||||
DB_ENV *dbenv;
|
||||
elect_args *eargs;
|
||||
machtab_t *machtab;
|
||||
u_int32_t timeout;
|
||||
int n, ret;
|
||||
APP_DATA *app;
|
||||
|
||||
eargs = (elect_args *)args;
|
||||
dbenv = eargs->dbenv;
|
||||
machtab = eargs->machtab;
|
||||
free(eargs);
|
||||
app = dbenv->app_private;
|
||||
|
||||
machtab_parm(machtab, &n, &timeout);
|
||||
(void)dbenv->rep_set_timeout(dbenv, DB_REP_ELECTION_TIMEOUT, timeout);
|
||||
while ((ret = dbenv->rep_elect(dbenv, n, (n/2+1), 0)) != 0)
|
||||
sleep(2);
|
||||
|
||||
if (app->elected) {
|
||||
app->elected = 0;
|
||||
if ((ret = dbenv->rep_start(dbenv, NULL, DB_REP_MASTER)) != 0)
|
||||
dbenv->err(dbenv, ret,
|
||||
"can't start as master in election thread");
|
||||
}
|
||||
|
||||
return (NULL);
|
||||
}
|
||||
746
examples_c/ex_rep/base/rep_net.c
Normal file
746
examples_c/ex_rep/base/rep_net.c
Normal file
@@ -0,0 +1,746 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2001,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: rep_net.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <db.h>
|
||||
#include "rep_base.h"
|
||||
#ifndef _SYS_QUEUE_H
|
||||
/*
|
||||
* Some *BSD Unix variants include the Queue macros in their libraries and
|
||||
* these might already have been included. In that case, it would be bad
|
||||
* to include them again.
|
||||
*/
|
||||
#include <dbinc/queue.h> /* !!!: for the LIST_XXX macros. */
|
||||
#endif
|
||||
|
||||
int machtab_add __P((machtab_t *, socket_t, u_int32_t, int, int *));
|
||||
#ifdef DIAGNOSTIC
|
||||
void machtab_print __P((machtab_t *));
|
||||
#endif
|
||||
ssize_t readn __P((socket_t, void *, size_t));
|
||||
|
||||
/*
|
||||
* This file defines the communication infrastructure for the ex_repquote
|
||||
* sample application.
|
||||
*
|
||||
* This application uses TCP/IP for its communication. In an N-site
|
||||
* replication group, this means that there are N * N communication
|
||||
* channels so that every site can communicate with every other site
|
||||
* (this allows elections to be held when the master fails). We do
|
||||
* not require that anyone know about all sites when the application
|
||||
* starts up. In order to communicate, the application should know
|
||||
* about someone, else it has no idea how to ever get in the game.
|
||||
*
|
||||
* Communication is handled via a number of different threads. These
|
||||
* thread functions are implemented in rep_util.c In this file, we
|
||||
* define the data structures that maintain the state that describes
|
||||
* the comm infrastructure, the functions that manipulates this state
|
||||
* and the routines used to actually send and receive data over the
|
||||
* sockets.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The communication infrastructure is represented by a machine table,
|
||||
* machtab_t, which is essentially a mutex-protected linked list of members
|
||||
* of the group. The machtab also contains the parameters that are needed
|
||||
* to call for an election. We hardwire values for these parameters in the
|
||||
* init function, but these could be set via some configuration setup in a
|
||||
* real application. We reserve the machine-id 1 to refer to ourselves and
|
||||
* make the machine-id 0 be invalid.
|
||||
*/
|
||||
|
||||
#define MACHID_INVALID 0
|
||||
#define MACHID_SELF 1
|
||||
|
||||
struct __machtab {
|
||||
LIST_HEAD(__machlist, __member) machlist;
|
||||
int nextid;
|
||||
mutex_t mtmutex;
|
||||
u_int32_t timeout_time;
|
||||
int current;
|
||||
int max;
|
||||
int nsites;
|
||||
};
|
||||
|
||||
/* Data structure that describes each entry in the machtab. */
|
||||
struct __member {
|
||||
u_int32_t hostaddr; /* Host IP address. */
|
||||
int port; /* Port number. */
|
||||
int eid; /* Application-specific machine id. */
|
||||
socket_t fd; /* File descriptor for the socket. */
|
||||
LIST_ENTRY(__member) links;
|
||||
/* For linked list of all members we know of. */
|
||||
};
|
||||
|
||||
static int quote_send_broadcast __P((machtab_t *,
|
||||
const DBT *, const DBT *, u_int32_t));
|
||||
static int quote_send_one __P((const DBT *, const DBT *, socket_t, u_int32_t));
|
||||
|
||||
/*
|
||||
* machtab_init --
|
||||
* Initialize the machine ID table.
|
||||
* XXX Right now we treat the number of sites as the maximum
|
||||
* number we've ever had on the list at one time. We probably
|
||||
* want to make that smarter.
|
||||
*/
|
||||
int
|
||||
machtab_init(machtabp, nsites)
|
||||
machtab_t **machtabp;
|
||||
int nsites;
|
||||
{
|
||||
int ret;
|
||||
machtab_t *machtab;
|
||||
|
||||
if ((machtab = malloc(sizeof(machtab_t))) == NULL) {
|
||||
fprintf(stderr, "can't allocate memory\n");
|
||||
return (ENOMEM);
|
||||
}
|
||||
|
||||
LIST_INIT(&machtab->machlist);
|
||||
|
||||
/* Reserve eid's 0 and 1. */
|
||||
machtab->nextid = 2;
|
||||
machtab->timeout_time = 2 * 1000000; /* 2 seconds. */
|
||||
machtab->current = machtab->max = 0;
|
||||
machtab->nsites = nsites;
|
||||
|
||||
ret = mutex_init(&machtab->mtmutex, NULL);
|
||||
*machtabp = machtab;
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* machtab_add --
|
||||
* Add a file descriptor to the table of machines, returning
|
||||
* a new machine ID.
|
||||
*/
|
||||
int
|
||||
machtab_add(machtab, fd, hostaddr, port, idp)
|
||||
machtab_t *machtab;
|
||||
socket_t fd;
|
||||
u_int32_t hostaddr;
|
||||
int port, *idp;
|
||||
{
|
||||
int ret;
|
||||
member_t *m, *member;
|
||||
|
||||
ret = 0;
|
||||
if ((member = malloc(sizeof(member_t))) == NULL) {
|
||||
fprintf(stderr, "can't allocate memory\n");
|
||||
return (ENOMEM);
|
||||
}
|
||||
|
||||
member->fd = fd;
|
||||
member->hostaddr = hostaddr;
|
||||
member->port = port;
|
||||
|
||||
if ((ret = mutex_lock(&machtab->mtmutex)) != 0) {
|
||||
fprintf(stderr, "can't lock mutex");
|
||||
return (ret);
|
||||
}
|
||||
|
||||
for (m = LIST_FIRST(&machtab->machlist);
|
||||
m != NULL; m = LIST_NEXT(m, links))
|
||||
if (m->hostaddr == hostaddr && m->port == port)
|
||||
break;
|
||||
|
||||
if (m == NULL) {
|
||||
member->eid = machtab->nextid++;
|
||||
LIST_INSERT_HEAD(&machtab->machlist, member, links);
|
||||
} else
|
||||
member->eid = m->eid;
|
||||
|
||||
if ((ret = mutex_unlock(&machtab->mtmutex)) != 0) {
|
||||
fprintf(stderr, "can't unlock mutex\n");
|
||||
return (ret);
|
||||
}
|
||||
|
||||
if (idp != NULL)
|
||||
*idp = member->eid;
|
||||
|
||||
if (m == NULL) {
|
||||
if (++machtab->current > machtab->max)
|
||||
machtab->max = machtab->current;
|
||||
} else {
|
||||
free(member);
|
||||
ret = EEXIST;
|
||||
}
|
||||
#ifdef DIAGNOSTIC
|
||||
printf("Exiting machtab_add\n");
|
||||
machtab_print(machtab);
|
||||
#endif
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* machtab_getinfo --
|
||||
* Return host and port information for a particular machine id.
|
||||
*/
|
||||
int
|
||||
machtab_getinfo(machtab, eid, hostp, portp)
|
||||
machtab_t *machtab;
|
||||
int eid;
|
||||
u_int32_t *hostp;
|
||||
int *portp;
|
||||
{
|
||||
int ret;
|
||||
member_t *member;
|
||||
|
||||
if ((ret = mutex_lock(&machtab->mtmutex)) != 0) {
|
||||
fprintf(stderr, "can't lock mutex\n");
|
||||
return (ret);
|
||||
}
|
||||
|
||||
for (member = LIST_FIRST(&machtab->machlist);
|
||||
member != NULL;
|
||||
member = LIST_NEXT(member, links))
|
||||
if (member->eid == eid) {
|
||||
*hostp = member->hostaddr;
|
||||
*portp = member->port;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((ret = mutex_unlock(&machtab->mtmutex)) != 0) {
|
||||
fprintf(stderr, "can't unlock mutex\n");
|
||||
return (ret);
|
||||
}
|
||||
|
||||
return (member != NULL ? 0 : EINVAL);
|
||||
}
|
||||
|
||||
/*
|
||||
* machtab_rem --
|
||||
* Remove a mapping from the table of machines. Lock indicates
|
||||
* whether we need to lock the machtab or not (0 indicates we do not
|
||||
* need to lock; non-zero indicates that we do need to lock).
|
||||
*/
|
||||
int
|
||||
machtab_rem(machtab, eid, lock)
|
||||
machtab_t *machtab;
|
||||
int eid;
|
||||
int lock;
|
||||
{
|
||||
int found, ret;
|
||||
member_t *member;
|
||||
|
||||
ret = 0;
|
||||
if (lock && (ret = mutex_lock(&machtab->mtmutex)) != 0) {
|
||||
fprintf(stderr, "can't lock mutex\n");
|
||||
return (ret);
|
||||
}
|
||||
|
||||
for (found = 0, member = LIST_FIRST(&machtab->machlist);
|
||||
member != NULL;
|
||||
member = LIST_NEXT(member, links))
|
||||
if (member->eid == eid) {
|
||||
found = 1;
|
||||
LIST_REMOVE(member, links);
|
||||
(void)closesocket(member->fd);
|
||||
free(member);
|
||||
machtab->current--;
|
||||
break;
|
||||
}
|
||||
|
||||
if (LIST_FIRST(&machtab->machlist) == NULL)
|
||||
machtab->nextid = 2;
|
||||
|
||||
if (lock && (ret = mutex_unlock(&machtab->mtmutex)) != 0)
|
||||
fprintf(stderr, "can't unlock mutex\n");
|
||||
|
||||
#ifdef DIAGNOSTIC
|
||||
printf("Exiting machtab_rem\n");
|
||||
machtab_print(machtab);
|
||||
#endif
|
||||
return (ret);
|
||||
}
|
||||
|
||||
void
|
||||
machtab_parm(machtab, nump, timeoutp)
|
||||
machtab_t *machtab;
|
||||
int *nump;
|
||||
u_int32_t *timeoutp;
|
||||
{
|
||||
if (machtab->nsites == 0)
|
||||
*nump = machtab->max;
|
||||
else
|
||||
*nump = machtab->nsites;
|
||||
*timeoutp = machtab->timeout_time;
|
||||
}
|
||||
|
||||
#ifdef DIAGNOSTIC
|
||||
void
|
||||
machtab_print(machtab)
|
||||
machtab_t *machtab;
|
||||
{
|
||||
member_t *m;
|
||||
|
||||
if (mutex_lock(&machtab->mtmutex) != 0) {
|
||||
fprintf(stderr, "can't lock mutex\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
for (m = LIST_FIRST(&machtab->machlist);
|
||||
m != NULL; m = LIST_NEXT(m, links)) {
|
||||
|
||||
printf("IP: %lx Port: %6d EID: %2d FD: %3d\n",
|
||||
(long)m->hostaddr, m->port, m->eid, m->fd);
|
||||
}
|
||||
|
||||
if (mutex_unlock(&machtab->mtmutex) != 0) {
|
||||
fprintf(stderr, "can't unlock mutex\n");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
* listen_socket_init --
|
||||
* Initialize a socket for listening on the specified port. Returns
|
||||
* a file descriptor for the socket, ready for an accept() call
|
||||
* in a thread that we're happy to let block.
|
||||
*/
|
||||
socket_t
|
||||
listen_socket_init(progname, port)
|
||||
const char *progname;
|
||||
int port;
|
||||
{
|
||||
socket_t s;
|
||||
int sockopt;
|
||||
struct sockaddr_in si;
|
||||
|
||||
COMPQUIET(progname, NULL);
|
||||
|
||||
if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
|
||||
perror("can't create listen socket");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
memset(&si, 0, sizeof(si));
|
||||
si.sin_family = AF_INET;
|
||||
si.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
si.sin_port = htons((unsigned short)port);
|
||||
|
||||
/*
|
||||
* When using this example for testing, it's common to kill and restart
|
||||
* regularly. On some systems, this causes bind to fail with "address
|
||||
* in use" errors unless this option is set.
|
||||
*/
|
||||
sockopt = 1;
|
||||
setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
|
||||
(const char *)&sockopt, sizeof(sockopt));
|
||||
|
||||
if (bind(s, (struct sockaddr *)&si, sizeof(si)) != 0) {
|
||||
perror("can't bind listen socket");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (listen(s, 5) != 0) {
|
||||
perror("can't establish listen queue");
|
||||
goto err;
|
||||
}
|
||||
|
||||
return (s);
|
||||
|
||||
err: closesocket(s);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/*
|
||||
* listen_socket_accept --
|
||||
* Accept a connection on a socket. This is essentially just a wrapper
|
||||
* for accept(3).
|
||||
*/
|
||||
socket_t
|
||||
listen_socket_accept(machtab, progname, s, eidp)
|
||||
machtab_t *machtab;
|
||||
const char *progname;
|
||||
socket_t s;
|
||||
int *eidp;
|
||||
{
|
||||
struct sockaddr_in si;
|
||||
socklen_t si_len;
|
||||
int host, ret;
|
||||
socket_t ns;
|
||||
u_int16_t port;
|
||||
|
||||
COMPQUIET(progname, NULL);
|
||||
|
||||
accept_wait:
|
||||
memset(&si, 0, sizeof(si));
|
||||
si_len = sizeof(si);
|
||||
ns = accept(s, (struct sockaddr *)&si, &si_len);
|
||||
if (ns == SOCKET_CREATION_FAILURE) {
|
||||
fprintf(stderr, "can't accept incoming connection\n");
|
||||
return ns;
|
||||
}
|
||||
host = ntohl(si.sin_addr.s_addr);
|
||||
|
||||
/*
|
||||
* Sites send their listening port when connections are first
|
||||
* established, as it will be different from the outgoing port
|
||||
* for this connection.
|
||||
*/
|
||||
if (readn(ns, &port, 2) != 2)
|
||||
goto err;
|
||||
port = ntohs(port);
|
||||
|
||||
ret = machtab_add(machtab, ns, host, port, eidp);
|
||||
if (ret == EEXIST) {
|
||||
closesocket(ns);
|
||||
goto accept_wait;
|
||||
} else if (ret != 0)
|
||||
goto err;
|
||||
printf("Connected to host %x port %d, eid = %d\n", host, port, *eidp);
|
||||
return (ns);
|
||||
|
||||
err: closesocket(ns);
|
||||
return SOCKET_CREATION_FAILURE;
|
||||
}
|
||||
|
||||
/*
|
||||
* get_connected_socket --
|
||||
* Connect to the specified port of the specified remote machine,
|
||||
* and return a file descriptor when we have accepted a connection on it.
|
||||
* Add this connection to the machtab. If we already have a connection
|
||||
* open to this machine, then don't create another one, return the eid
|
||||
* of the connection (in *eidp) and set is_open to 1. Return 0.
|
||||
*/
|
||||
socket_t
|
||||
get_connected_socket(machtab, progname, remotehost, port, is_open, eidp)
|
||||
machtab_t *machtab;
|
||||
const char *progname, *remotehost;
|
||||
int port, *is_open, *eidp;
|
||||
{
|
||||
int ret;
|
||||
socket_t s;
|
||||
struct hostent *hp;
|
||||
struct sockaddr_in si;
|
||||
u_int32_t addr;
|
||||
u_int16_t nport;
|
||||
|
||||
*is_open = 0;
|
||||
|
||||
if ((hp = gethostbyname(remotehost)) == NULL) {
|
||||
fprintf(stderr, "%s: host not found: %s\n", progname,
|
||||
strerror(net_errno));
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
|
||||
perror("can't create outgoing socket");
|
||||
return (-1);
|
||||
}
|
||||
memset(&si, 0, sizeof(si));
|
||||
memcpy((char *)&si.sin_addr, hp->h_addr, hp->h_length);
|
||||
addr = ntohl(si.sin_addr.s_addr);
|
||||
ret = machtab_add(machtab, s, addr, port, eidp);
|
||||
if (ret == EEXIST) {
|
||||
*is_open = 1;
|
||||
closesocket(s);
|
||||
return (0);
|
||||
} else if (ret != 0) {
|
||||
closesocket(s);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
si.sin_family = AF_INET;
|
||||
si.sin_port = htons((unsigned short)port);
|
||||
if (connect(s, (struct sockaddr *)&si, sizeof(si)) < 0) {
|
||||
fprintf(stderr, "%s: connection failed: %s\n",
|
||||
progname, strerror(net_errno));
|
||||
(void)machtab_rem(machtab, *eidp, 1);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/*
|
||||
* The first thing we send on the socket is our (listening) port
|
||||
* so the site we are connecting to can register us correctly in
|
||||
* its machtab.
|
||||
*/
|
||||
nport = htons(myport);
|
||||
writesocket(s, &nport, 2);
|
||||
|
||||
return (s);
|
||||
}
|
||||
|
||||
/*
|
||||
* get_next_message --
|
||||
* Read a single message from the specified file descriptor, and
|
||||
* return it in the format used by rep functions (two DBTs and a type).
|
||||
*
|
||||
* This function is called in a loop by both clients and masters, and
|
||||
* the resulting DBTs are manually dispatched to DB_ENV->rep_process_message().
|
||||
*/
|
||||
int
|
||||
get_next_message(fd, rec, control)
|
||||
socket_t fd;
|
||||
DBT *rec, *control;
|
||||
{
|
||||
size_t nr;
|
||||
u_int32_t rsize, csize;
|
||||
u_int8_t *recbuf, *controlbuf;
|
||||
|
||||
/*
|
||||
* The protocol we use on the wire is dead simple:
|
||||
*
|
||||
* 4 bytes - rec->size
|
||||
* (# read above) - rec->data
|
||||
* 4 bytes - control->size
|
||||
* (# read above) - control->data
|
||||
*/
|
||||
|
||||
/* Read rec->size. */
|
||||
nr = readn(fd, &rsize, 4);
|
||||
if (nr != 4)
|
||||
return (1);
|
||||
|
||||
/* Read the record itself. */
|
||||
if (rsize > 0) {
|
||||
if (rec->size < rsize)
|
||||
rec->data = realloc(rec->data, rsize);
|
||||
recbuf = rec->data;
|
||||
nr = readn(fd, recbuf, rsize);
|
||||
} else {
|
||||
if (rec->data != NULL)
|
||||
free(rec->data);
|
||||
rec->data = NULL;
|
||||
}
|
||||
rec->size = rsize;
|
||||
|
||||
/* Read control->size. */
|
||||
nr = readn(fd, &csize, 4);
|
||||
if (nr != 4)
|
||||
return (1);
|
||||
|
||||
/* Read the control struct itself. */
|
||||
if (csize > 0) {
|
||||
controlbuf = control->data;
|
||||
if (control->size < csize)
|
||||
controlbuf = realloc(controlbuf, csize);
|
||||
nr = readn(fd, controlbuf, csize);
|
||||
if (nr != csize)
|
||||
return (1);
|
||||
} else {
|
||||
if (control->data != NULL)
|
||||
free(control->data);
|
||||
controlbuf = NULL;
|
||||
}
|
||||
control->data = controlbuf;
|
||||
control->size = csize;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* readn --
|
||||
* Read a full n characters from a file descriptor, unless we get an error
|
||||
* or EOF.
|
||||
*/
|
||||
ssize_t
|
||||
readn(fd, vptr, n)
|
||||
socket_t fd;
|
||||
void *vptr;
|
||||
size_t n;
|
||||
{
|
||||
size_t nleft;
|
||||
ssize_t nread;
|
||||
char *ptr;
|
||||
|
||||
ptr = vptr;
|
||||
nleft = n;
|
||||
while (nleft > 0) {
|
||||
if ((nread = readsocket(fd, ptr, nleft)) < 0) {
|
||||
/*
|
||||
* Call read() again on interrupted system call;
|
||||
* on other errors, bail.
|
||||
*/
|
||||
if (net_errno == EINTR)
|
||||
nread = 0;
|
||||
else {
|
||||
perror("can't read from socket");
|
||||
return (-1);
|
||||
}
|
||||
} else if (nread == 0)
|
||||
break; /* EOF */
|
||||
|
||||
nleft -= nread;
|
||||
ptr += nread;
|
||||
}
|
||||
|
||||
return (n - nleft);
|
||||
}
|
||||
|
||||
/*
|
||||
* quote_send --
|
||||
* The f_send function for DB_ENV->set_rep_transport.
|
||||
*/
|
||||
int
|
||||
quote_send(dbenv, control, rec, lsnp, eid, flags)
|
||||
DB_ENV *dbenv;
|
||||
const DBT *control, *rec;
|
||||
const DB_LSN *lsnp;
|
||||
int eid;
|
||||
u_int32_t flags;
|
||||
{
|
||||
int n, ret, t_ret;
|
||||
socket_t fd;
|
||||
machtab_t *machtab;
|
||||
member_t *m;
|
||||
|
||||
COMPQUIET(lsnp, NULL);
|
||||
machtab =
|
||||
(machtab_t *)((APP_DATA*)dbenv->app_private)->comm_infrastructure;
|
||||
|
||||
if (eid == DB_EID_BROADCAST) {
|
||||
/*
|
||||
* Right now, we do not require successful transmission.
|
||||
* I'd like to move this requiring at least one successful
|
||||
* transmission on PERMANENT requests.
|
||||
*/
|
||||
n = quote_send_broadcast(machtab, rec, control, flags);
|
||||
if (n < 0 /*|| (n == 0 && LF_ISSET(DB_REP_PERMANENT))*/)
|
||||
return (DB_REP_UNAVAIL);
|
||||
return (0);
|
||||
}
|
||||
|
||||
if ((ret = mutex_lock(&machtab->mtmutex)) != 0) {
|
||||
dbenv->errx(dbenv, "can't lock mutex");
|
||||
return (ret);
|
||||
}
|
||||
|
||||
fd = 0;
|
||||
for (m = LIST_FIRST(&machtab->machlist); m != NULL;
|
||||
m = LIST_NEXT(m, links)) {
|
||||
if (m->eid == eid) {
|
||||
fd = m->fd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fd == 0) {
|
||||
dbenv->err(dbenv, DB_REP_UNAVAIL,
|
||||
"quote_send: cannot find machine ID %d", eid);
|
||||
return (DB_REP_UNAVAIL);
|
||||
}
|
||||
|
||||
if ((ret = quote_send_one(rec, control, fd, flags)) != 0)
|
||||
fprintf(stderr, "socket write error in send() function\n");
|
||||
|
||||
if ((t_ret = mutex_unlock(&machtab->mtmutex)) != 0) {
|
||||
dbenv->errx(dbenv, "can't unlock mutex");
|
||||
if (ret == 0)
|
||||
ret = t_ret;
|
||||
}
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* quote_send_broadcast --
|
||||
* Send a message to everybody.
|
||||
* Returns the number of sites to which this message was successfully
|
||||
* communicated. A -1 indicates a fatal error.
|
||||
*/
|
||||
static int
|
||||
quote_send_broadcast(machtab, rec, control, flags)
|
||||
machtab_t *machtab;
|
||||
const DBT *rec, *control;
|
||||
u_int32_t flags;
|
||||
{
|
||||
int ret, sent;
|
||||
member_t *m, *next;
|
||||
|
||||
if ((ret = mutex_lock(&machtab->mtmutex)) != 0) {
|
||||
fprintf(stderr, "can't lock mutex\n");
|
||||
return (ret);
|
||||
}
|
||||
|
||||
sent = 0;
|
||||
for (m = LIST_FIRST(&machtab->machlist); m != NULL; m = next) {
|
||||
next = LIST_NEXT(m, links);
|
||||
if ((ret = quote_send_one(rec, control, m->fd, flags)) != 0) {
|
||||
fprintf(stderr, "socket write error in broadcast\n");
|
||||
(void)machtab_rem(machtab, m->eid, 0);
|
||||
} else
|
||||
sent++;
|
||||
}
|
||||
|
||||
if (mutex_unlock(&machtab->mtmutex) != 0) {
|
||||
fprintf(stderr, "can't unlock mutex\n");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
return (sent);
|
||||
}
|
||||
|
||||
/*
|
||||
* quote_send_one --
|
||||
* Send a message to a single machine, given that machine's file
|
||||
* descriptor.
|
||||
*
|
||||
* !!!
|
||||
* Note that the machtab mutex should be held through this call.
|
||||
* It doubles as a synchronizer to make sure that two threads don't
|
||||
* intersperse writes that are part of two single messages.
|
||||
*/
|
||||
static int
|
||||
quote_send_one(rec, control, fd, flags)
|
||||
const DBT *rec, *control;
|
||||
socket_t fd;
|
||||
u_int32_t flags;
|
||||
|
||||
{
|
||||
int retry;
|
||||
ssize_t bytes_left, nw;
|
||||
u_int8_t *wp;
|
||||
|
||||
COMPQUIET(flags, 0);
|
||||
|
||||
/*
|
||||
* The protocol is simply: write rec->size, write rec->data,
|
||||
* write control->size, write control->data.
|
||||
*/
|
||||
nw = writesocket(fd, (const char *)&rec->size, 4);
|
||||
if (nw != 4)
|
||||
return (DB_REP_UNAVAIL);
|
||||
|
||||
if (rec->size > 0) {
|
||||
nw = writesocket(fd, rec->data, rec->size);
|
||||
if (nw < 0)
|
||||
return (DB_REP_UNAVAIL);
|
||||
if (nw != (ssize_t)rec->size) {
|
||||
/* Try a couple of times to finish the write. */
|
||||
wp = (u_int8_t *)rec->data + nw;
|
||||
bytes_left = rec->size - nw;
|
||||
for (retry = 0; bytes_left > 0 && retry < 3; retry++) {
|
||||
nw = writesocket(fd, wp, bytes_left);
|
||||
if (nw < 0)
|
||||
return (DB_REP_UNAVAIL);
|
||||
bytes_left -= nw;
|
||||
wp += nw;
|
||||
}
|
||||
if (bytes_left > 0)
|
||||
return (DB_REP_UNAVAIL);
|
||||
}
|
||||
}
|
||||
|
||||
nw = writesocket(fd, (const char *)&control->size, 4);
|
||||
if (nw != 4)
|
||||
return (DB_REP_UNAVAIL);
|
||||
if (control->size > 0) {
|
||||
nw = writesocket(fd, control->data, control->size);
|
||||
if (nw != (ssize_t)control->size)
|
||||
return (DB_REP_UNAVAIL);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
266
examples_c/ex_rep/common/rep_common.c
Normal file
266
examples_c/ex_rep/common/rep_common.c
Normal file
@@ -0,0 +1,266 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2006,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: rep_common.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <db.h>
|
||||
|
||||
#include "rep_common.h"
|
||||
|
||||
#define CACHESIZE (10 * 1024 * 1024)
|
||||
#define DATABASE "quote.db"
|
||||
#define SLEEPTIME 3
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#define sleep(s) Sleep(1000 * (s))
|
||||
#endif
|
||||
|
||||
static int print_stocks __P((DB *));
|
||||
|
||||
static int
|
||||
print_stocks(dbp)
|
||||
DB *dbp;
|
||||
{
|
||||
DBC *dbc;
|
||||
DBT key, data;
|
||||
#define MAXKEYSIZE 10
|
||||
#define MAXDATASIZE 20
|
||||
char keybuf[MAXKEYSIZE + 1], databuf[MAXDATASIZE + 1];
|
||||
int ret, t_ret;
|
||||
u_int32_t keysize, datasize;
|
||||
|
||||
if ((ret = dbp->cursor(dbp, NULL, &dbc, 0)) != 0) {
|
||||
dbp->err(dbp, ret, "can't open cursor");
|
||||
return (ret);
|
||||
}
|
||||
|
||||
memset(&key, 0, sizeof(key));
|
||||
memset(&data, 0, sizeof(data));
|
||||
|
||||
printf("\tSymbol\tPrice\n");
|
||||
printf("\t======\t=====\n");
|
||||
|
||||
for (ret = dbc->get(dbc, &key, &data, DB_FIRST);
|
||||
ret == 0;
|
||||
ret = dbc->get(dbc, &key, &data, DB_NEXT)) {
|
||||
keysize = key.size > MAXKEYSIZE ? MAXKEYSIZE : key.size;
|
||||
memcpy(keybuf, key.data, keysize);
|
||||
keybuf[keysize] = '\0';
|
||||
|
||||
datasize = data.size >= MAXDATASIZE ? MAXDATASIZE : data.size;
|
||||
memcpy(databuf, data.data, datasize);
|
||||
databuf[datasize] = '\0';
|
||||
|
||||
printf("\t%s\t%s\n", keybuf, databuf);
|
||||
}
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
|
||||
if ((t_ret = dbc->close(dbc)) != 0 && ret == 0)
|
||||
ret = t_ret;
|
||||
|
||||
switch (ret) {
|
||||
case 0:
|
||||
case DB_NOTFOUND:
|
||||
case DB_LOCK_DEADLOCK:
|
||||
return (0);
|
||||
default:
|
||||
return (ret);
|
||||
}
|
||||
}
|
||||
|
||||
#define BUFSIZE 1024
|
||||
|
||||
int
|
||||
doloop(dbenv, shared_data)
|
||||
DB_ENV *dbenv;
|
||||
SHARED_DATA *shared_data;
|
||||
{
|
||||
DB *dbp;
|
||||
DBT key, data;
|
||||
char buf[BUFSIZE], *first, *price;
|
||||
u_int32_t flags;
|
||||
int ret;
|
||||
|
||||
dbp = NULL;
|
||||
ret = 0;
|
||||
memset(&key, 0, sizeof(key));
|
||||
memset(&data, 0, sizeof(data));
|
||||
|
||||
for (;;) {
|
||||
printf("QUOTESERVER%s> ",
|
||||
shared_data->is_master ? "" : " (read-only)");
|
||||
fflush(stdout);
|
||||
|
||||
if (fgets(buf, sizeof(buf), stdin) == NULL)
|
||||
break;
|
||||
|
||||
#define DELIM " \t\n"
|
||||
if ((first = strtok(&buf[0], DELIM)) == NULL) {
|
||||
/* Blank input line. */
|
||||
price = NULL;
|
||||
} else if ((price = strtok(NULL, DELIM)) == NULL) {
|
||||
/* Just one input token. */
|
||||
if (strncmp(buf, "exit", 4) == 0 ||
|
||||
strncmp(buf, "quit", 4) == 0)
|
||||
break;
|
||||
dbenv->errx(dbenv, "Format: TICKER VALUE");
|
||||
continue;
|
||||
} else {
|
||||
/* Normal two-token input line. */
|
||||
if (first != NULL && !shared_data->is_master) {
|
||||
dbenv->errx(dbenv, "Can't update at client");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (dbp == NULL) {
|
||||
if ((ret = db_create(&dbp, dbenv, 0)) != 0)
|
||||
return (ret);
|
||||
|
||||
/* Set page size small so page allocation is cheap. */
|
||||
if ((ret = dbp->set_pagesize(dbp, 512)) != 0)
|
||||
goto err;
|
||||
|
||||
flags = DB_AUTO_COMMIT;
|
||||
if (shared_data->is_master)
|
||||
flags |= DB_CREATE;
|
||||
if ((ret = dbp->open(dbp,
|
||||
NULL, DATABASE, NULL, DB_BTREE, flags, 0)) != 0) {
|
||||
if (ret == ENOENT) {
|
||||
printf(
|
||||
"No stock database yet available.\n");
|
||||
if ((ret = dbp->close(dbp, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret,
|
||||
"DB->close");
|
||||
goto err;
|
||||
}
|
||||
dbp = NULL;
|
||||
continue;
|
||||
}
|
||||
if (ret == DB_REP_HANDLE_DEAD ||
|
||||
ret == DB_LOCK_DEADLOCK) {
|
||||
dbenv->err(dbenv, ret,
|
||||
"please retry the operation");
|
||||
dbp->close(dbp, DB_NOSYNC);
|
||||
dbp = NULL;
|
||||
continue;
|
||||
}
|
||||
dbenv->err(dbenv, ret, "DB->open");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (first == NULL)
|
||||
switch ((ret = print_stocks(dbp))) {
|
||||
case 0:
|
||||
break;
|
||||
case DB_REP_HANDLE_DEAD:
|
||||
(void)dbp->close(dbp, DB_NOSYNC);
|
||||
dbp = NULL;
|
||||
break;
|
||||
default:
|
||||
dbp->err(dbp, ret, "Error traversing data");
|
||||
goto err;
|
||||
}
|
||||
else {
|
||||
key.data = first;
|
||||
key.size = (u_int32_t)strlen(first);
|
||||
|
||||
data.data = price;
|
||||
data.size = (u_int32_t)strlen(price);
|
||||
|
||||
if ((ret = dbp->put(dbp,
|
||||
NULL, &key, &data, DB_AUTO_COMMIT)) != 0) {
|
||||
dbp->err(dbp, ret, "DB->put");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err: if (dbp != NULL)
|
||||
(void)dbp->close(dbp, DB_NOSYNC);
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
||||
int
|
||||
create_env(progname, dbenvp)
|
||||
const char *progname;
|
||||
DB_ENV **dbenvp;
|
||||
{
|
||||
DB_ENV *dbenv;
|
||||
int ret;
|
||||
|
||||
if ((ret = db_env_create(&dbenv, 0)) != 0) {
|
||||
fprintf(stderr, "can't create env handle: %s\n",
|
||||
db_strerror(ret));
|
||||
return (ret);
|
||||
}
|
||||
|
||||
dbenv->set_errfile(dbenv, stderr);
|
||||
dbenv->set_errpfx(dbenv, progname);
|
||||
|
||||
*dbenvp = dbenv;
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Open and configure an environment. */
|
||||
int
|
||||
env_init(dbenv, home)
|
||||
DB_ENV *dbenv;
|
||||
const char *home;
|
||||
{
|
||||
u_int32_t flags;
|
||||
int ret;
|
||||
|
||||
(void)dbenv->set_cachesize(dbenv, 0, CACHESIZE, 0);
|
||||
(void)dbenv->set_flags(dbenv, DB_TXN_NOSYNC, 1);
|
||||
|
||||
flags = DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL |
|
||||
DB_INIT_REP | DB_INIT_TXN | DB_RECOVER | DB_THREAD;
|
||||
if ((ret = dbenv->open(dbenv, home, flags, 0)) != 0)
|
||||
dbenv->err(dbenv, ret, "can't open environment");
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* In this application, we specify all communication via the command line. In
|
||||
* a real application, we would expect that information about the other sites
|
||||
* in the system would be maintained in some sort of configuration file. The
|
||||
* critical part of this interface is that we assume at startup that we can
|
||||
* find out
|
||||
* 1) what host/port we wish to listen on for connections,
|
||||
* 2) a (possibly empty) list of other sites we should attempt to connect
|
||||
* to; and
|
||||
* 3) what our Berkeley DB home environment is.
|
||||
*
|
||||
* These pieces of information are expressed by the following flags.
|
||||
* -m host:port (required; m stands for me)
|
||||
* -o host:port (optional; o stands for other; any number of these may be
|
||||
* specified)
|
||||
* -h home directory
|
||||
* -n nsites (optional; number of sites in replication group; defaults to 0
|
||||
* in which case we try to dynamically compute the number of sites in
|
||||
* the replication group)
|
||||
* -p priority (optional: defaults to 100)
|
||||
* -C or -M start up as client or master (optional)
|
||||
*/
|
||||
void
|
||||
usage(progname)
|
||||
const char *progname;
|
||||
{
|
||||
fprintf(stderr, "usage: %s ", progname);
|
||||
fprintf(stderr, "[-CM][-h home][-o host:port][-m host:port]%s",
|
||||
"[-n nsites][-p priority]\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
17
examples_c/ex_rep/common/rep_common.h
Normal file
17
examples_c/ex_rep/common/rep_common.h
Normal file
@@ -0,0 +1,17 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2006,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: rep_common.h 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
/* Data shared by both repmgr and base versions of this program. */
|
||||
typedef struct {
|
||||
int is_master;
|
||||
} SHARED_DATA;
|
||||
|
||||
int create_env __P((const char *progname, DB_ENV **));
|
||||
int doloop __P((DB_ENV *, SHARED_DATA *));
|
||||
int env_init __P((DB_ENV *, const char *));
|
||||
void usage __P((const char *));
|
||||
201
examples_c/ex_rep/mgr/rep_mgr.c
Normal file
201
examples_c/ex_rep/mgr/rep_mgr.c
Normal file
@@ -0,0 +1,201 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2001,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: rep_mgr.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <db.h>
|
||||
|
||||
#include "../common/rep_common.h"
|
||||
|
||||
typedef struct {
|
||||
SHARED_DATA shared_data;
|
||||
} APP_DATA;
|
||||
|
||||
const char *progname = "ex_rep";
|
||||
|
||||
#ifdef _WIN32
|
||||
extern int getopt(int, char * const *, const char *);
|
||||
#endif
|
||||
|
||||
static void event_callback __P((DB_ENV *, u_int32_t, void *));
|
||||
|
||||
int
|
||||
main(argc, argv)
|
||||
int argc;
|
||||
char *argv[];
|
||||
{
|
||||
extern char *optarg;
|
||||
DB_ENV *dbenv;
|
||||
const char *home;
|
||||
char ch, *host, *portstr;
|
||||
int ret, totalsites, t_ret, got_listen_address, friend;
|
||||
u_int16_t port;
|
||||
APP_DATA my_app_data;
|
||||
u_int32_t start_policy;
|
||||
int priority;
|
||||
|
||||
my_app_data.shared_data.is_master = 0; /* assume start out as client */
|
||||
dbenv = NULL;
|
||||
ret = got_listen_address = 0;
|
||||
home = "TESTDIR";
|
||||
|
||||
if ((ret = create_env(progname, &dbenv)) != 0)
|
||||
goto err;
|
||||
dbenv->app_private = &my_app_data;
|
||||
(void)dbenv->set_event_notify(dbenv, event_callback);
|
||||
|
||||
start_policy = DB_REP_ELECTION; /* default */
|
||||
priority = 100; /* default */
|
||||
|
||||
while ((ch = getopt(argc, argv, "Cf:h:Mm:n:o:p:v")) != EOF) {
|
||||
friend = 0;
|
||||
switch (ch) {
|
||||
case 'C':
|
||||
start_policy = DB_REP_CLIENT;
|
||||
break;
|
||||
case 'h':
|
||||
home = optarg;
|
||||
break;
|
||||
case 'M':
|
||||
start_policy = DB_REP_MASTER;
|
||||
break;
|
||||
case 'm':
|
||||
host = strtok(optarg, ":");
|
||||
if ((portstr = strtok(NULL, ":")) == NULL) {
|
||||
fprintf(stderr, "Bad host specification.\n");
|
||||
goto err;
|
||||
}
|
||||
port = (unsigned short)atoi(portstr);
|
||||
if ((ret = dbenv->repmgr_set_local_site(dbenv,
|
||||
host, port, 0)) != 0) {
|
||||
fprintf(stderr,
|
||||
"Could not set listen address (%d).\n",
|
||||
ret);
|
||||
goto err;
|
||||
}
|
||||
got_listen_address = 1;
|
||||
break;
|
||||
case 'n':
|
||||
totalsites = atoi(optarg);
|
||||
if ((ret =
|
||||
dbenv->rep_set_nsites(dbenv, totalsites)) != 0)
|
||||
dbenv->err(dbenv, ret, "set_nsites");
|
||||
break;
|
||||
case 'f':
|
||||
friend = 1; /* FALLTHROUGH */
|
||||
case 'o':
|
||||
host = strtok(optarg, ":");
|
||||
if ((portstr = strtok(NULL, ":")) == NULL) {
|
||||
fprintf(stderr, "Bad host specification.\n");
|
||||
goto err;
|
||||
}
|
||||
port = (unsigned short)atoi(portstr);
|
||||
if ((ret = dbenv->repmgr_add_remote_site(dbenv, host,
|
||||
port, NULL, friend ? DB_REPMGR_PEER : 0)) != 0) {
|
||||
dbenv->err(dbenv, ret,
|
||||
"Could not add site %s:%d", host,
|
||||
(int)port);
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
case 'p':
|
||||
priority = atoi(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
if ((ret = dbenv->set_verbose(dbenv,
|
||||
DB_VERB_REPLICATION, 1)) != 0)
|
||||
goto err;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
usage(progname);
|
||||
}
|
||||
}
|
||||
|
||||
/* Error check command line. */
|
||||
if ((!got_listen_address) || home == NULL)
|
||||
usage(progname);
|
||||
|
||||
dbenv->rep_set_priority(dbenv, priority);
|
||||
|
||||
if ((ret = env_init(dbenv, home)) != 0)
|
||||
goto err;
|
||||
|
||||
if ((ret = dbenv->repmgr_start(dbenv, 3, start_policy)) != 0)
|
||||
goto err;
|
||||
|
||||
if ((ret = doloop(dbenv, &my_app_data.shared_data)) != 0) {
|
||||
dbenv->err(dbenv, ret, "Client failed");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* We have used the DB_TXN_NOSYNC environment flag for improved
|
||||
* performance without the usual sacrifice of transactional durability,
|
||||
* as discussed in the "Transactional guarantees" page of the Reference
|
||||
* Guide: if one replication site crashes, we can expect the data to
|
||||
* exist at another site. However, in case we shut down all sites
|
||||
* gracefully, we push out the end of the log here so that the most
|
||||
* recent transactions don't mysteriously disappear.
|
||||
*/
|
||||
if ((ret = dbenv->log_flush(dbenv, NULL)) != 0) {
|
||||
dbenv->err(dbenv, ret, "log_flush");
|
||||
goto err;
|
||||
}
|
||||
|
||||
err:
|
||||
if (dbenv != NULL &&
|
||||
(t_ret = dbenv->close(dbenv, 0)) != 0) {
|
||||
fprintf(stderr, "failure closing env: %s (%d)\n",
|
||||
db_strerror(t_ret), t_ret);
|
||||
if (ret == 0)
|
||||
ret = t_ret;
|
||||
}
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
||||
static void
|
||||
event_callback(dbenv, which, info)
|
||||
DB_ENV *dbenv;
|
||||
u_int32_t which;
|
||||
void *info;
|
||||
{
|
||||
APP_DATA *app = dbenv->app_private;
|
||||
SHARED_DATA *shared = &app->shared_data;
|
||||
|
||||
info = NULL; /* Currently unused. */
|
||||
|
||||
switch (which) {
|
||||
case DB_EVENT_REP_CLIENT:
|
||||
shared->is_master = 0;
|
||||
break;
|
||||
|
||||
case DB_EVENT_REP_MASTER:
|
||||
shared->is_master = 1;
|
||||
break;
|
||||
|
||||
case DB_EVENT_REP_PERM_FAILED:
|
||||
printf("insufficient acks\n");
|
||||
break;
|
||||
|
||||
case DB_EVENT_REP_STARTUPDONE: /* FALLTHROUGH */
|
||||
case DB_EVENT_REP_NEWMASTER:
|
||||
/* I don't care about these, for now. */
|
||||
break;
|
||||
|
||||
default:
|
||||
dbenv->errx(dbenv, "ignoring event %d", which);
|
||||
}
|
||||
}
|
||||
133
examples_c/ex_sequence.c
Normal file
133
examples_c/ex_sequence.c
Normal file
@@ -0,0 +1,133 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 1997,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: ex_sequence.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
extern int getopt(int, char * const *, const char *);
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <db.h>
|
||||
|
||||
#define DATABASE "sequence.db"
|
||||
#define SEQUENCE "my_sequence"
|
||||
int main __P((int, char *[]));
|
||||
int usage __P((void));
|
||||
|
||||
int
|
||||
main(argc, argv)
|
||||
int argc;
|
||||
char *argv[];
|
||||
{
|
||||
extern int optind;
|
||||
DB *dbp;
|
||||
DB_SEQUENCE *seq;
|
||||
DBT key;
|
||||
int i, ret, rflag;
|
||||
db_seq_t seqnum;
|
||||
char ch;
|
||||
const char *database, *progname = "ex_sequence";
|
||||
|
||||
dbp = NULL;
|
||||
seq = NULL;
|
||||
|
||||
rflag = 0;
|
||||
while ((ch = getopt(argc, argv, "r")) != EOF)
|
||||
switch (ch) {
|
||||
case 'r':
|
||||
rflag = 1;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
return (usage());
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
/* Accept optional database name. */
|
||||
database = *argv == NULL ? DATABASE : argv[0];
|
||||
|
||||
/* Optionally discard the database. */
|
||||
if (rflag)
|
||||
(void)remove(database);
|
||||
|
||||
/* Create and initialize database object, open the database. */
|
||||
if ((ret = db_create(&dbp, NULL, 0)) != 0) {
|
||||
fprintf(stderr,
|
||||
"%s: db_create: %s\n", progname, db_strerror(ret));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
dbp->set_errfile(dbp, stderr);
|
||||
dbp->set_errpfx(dbp, progname);
|
||||
if ((ret = dbp->open(dbp,
|
||||
NULL, database, NULL, DB_BTREE, DB_CREATE, 0664)) != 0) {
|
||||
dbp->err(dbp, ret, "%s: open", database);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if ((ret = db_sequence_create(&seq, dbp, 0)) != 0) {
|
||||
dbp->err(dbp, ret, "db_sequence_create");
|
||||
goto err;
|
||||
}
|
||||
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
key.data = SEQUENCE;
|
||||
key.size = (u_int32_t)strlen(SEQUENCE);
|
||||
|
||||
if ((ret = seq->open(seq, NULL, &key, DB_CREATE)) != 0) {
|
||||
dbp->err(dbp, ret, "%s: DB_SEQUENCE->open", SEQUENCE);
|
||||
goto err;
|
||||
}
|
||||
|
||||
for (i = 0; i < 10; i++) {
|
||||
if ((ret = seq->get(seq, NULL, 1, &seqnum, 0)) != 0) {
|
||||
dbp->err(dbp, ret, "DB_SEQUENCE->get");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* There's no portable way to print 64-bit numbers. */
|
||||
#ifdef _WIN32
|
||||
printf("Got sequence number %I64d\n", (int64_t)seqnum);
|
||||
#else
|
||||
printf(
|
||||
"Got sequence number %llu\n", (unsigned long long)seqnum);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Close everything down. */
|
||||
if ((ret = seq->close(seq, 0)) != 0) {
|
||||
seq = NULL;
|
||||
dbp->err(dbp, ret, "DB_SEQUENCE->close");
|
||||
goto err;
|
||||
}
|
||||
if ((ret = dbp->close(dbp, 0)) != 0) {
|
||||
fprintf(stderr,
|
||||
"%s: DB->close: %s\n", progname, db_strerror(ret));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
return (EXIT_SUCCESS);
|
||||
|
||||
err: if (seq != NULL)
|
||||
(void)seq->close(seq, 0);
|
||||
if (dbp != NULL)
|
||||
(void)dbp->close(dbp, 0);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int
|
||||
usage()
|
||||
{
|
||||
(void)fprintf(stderr, "usage: ex_sequence [-r] [database]\n");
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
626
examples_c/ex_thread.c
Normal file
626
examples_c/ex_thread.c
Normal file
@@ -0,0 +1,626 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 1997,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: ex_thread.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
extern int getopt(int, char * const *, const char *);
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <db.h>
|
||||
|
||||
/*
|
||||
* NB: This application is written using POSIX 1003.1b-1993 pthreads
|
||||
* interfaces, which may not be portable to your system.
|
||||
*/
|
||||
extern int sched_yield __P((void)); /* Pthread yield function. */
|
||||
|
||||
int db_init __P((const char *));
|
||||
void *deadlock __P((void *));
|
||||
void fatal __P((const char *, int, int));
|
||||
void onint __P((int));
|
||||
int main __P((int, char *[]));
|
||||
int reader __P((int));
|
||||
void stats __P((void));
|
||||
void *trickle __P((void *));
|
||||
void *tstart __P((void *));
|
||||
int usage __P((void));
|
||||
void word __P((void));
|
||||
int writer __P((int));
|
||||
|
||||
int quit; /* Interrupt handling flag. */
|
||||
|
||||
struct _statistics {
|
||||
int aborted; /* Write. */
|
||||
int aborts; /* Read/write. */
|
||||
int adds; /* Write. */
|
||||
int deletes; /* Write. */
|
||||
int txns; /* Write. */
|
||||
int found; /* Read. */
|
||||
int notfound; /* Read. */
|
||||
} *perf;
|
||||
|
||||
const char
|
||||
*progname = "ex_thread"; /* Program name. */
|
||||
|
||||
#define DATABASE "access.db" /* Database name. */
|
||||
#define WORDLIST "../test/wordlist" /* Dictionary. */
|
||||
|
||||
/*
|
||||
* We can seriously increase the number of collisions and transaction
|
||||
* aborts by yielding the scheduler after every DB call. Specify the
|
||||
* -p option to do this.
|
||||
*/
|
||||
int punish; /* -p */
|
||||
int nlist; /* -n */
|
||||
int nreaders; /* -r */
|
||||
int verbose; /* -v */
|
||||
int nwriters; /* -w */
|
||||
|
||||
DB *dbp; /* Database handle. */
|
||||
DB_ENV *dbenv; /* Database environment. */
|
||||
int nthreads; /* Total threads. */
|
||||
char **list; /* Word list. */
|
||||
|
||||
/*
|
||||
* ex_thread --
|
||||
* Run a simple threaded application of some numbers of readers and
|
||||
* writers competing for a set of words.
|
||||
*
|
||||
* Example UNIX shell script to run this program:
|
||||
* % rm -rf TESTDIR
|
||||
* % mkdir TESTDIR
|
||||
* % ex_thread -h TESTDIR
|
||||
*/
|
||||
int
|
||||
main(argc, argv)
|
||||
int argc;
|
||||
char *argv[];
|
||||
{
|
||||
extern char *optarg;
|
||||
extern int errno, optind;
|
||||
DB_TXN *txnp;
|
||||
pthread_t *tids;
|
||||
int ch, i, ret;
|
||||
const char *home;
|
||||
void *retp;
|
||||
|
||||
txnp = NULL;
|
||||
nlist = 1000;
|
||||
nreaders = nwriters = 4;
|
||||
home = "TESTDIR";
|
||||
while ((ch = getopt(argc, argv, "h:pn:r:vw:")) != EOF)
|
||||
switch (ch) {
|
||||
case 'h':
|
||||
home = optarg;
|
||||
break;
|
||||
case 'p':
|
||||
punish = 1;
|
||||
break;
|
||||
case 'n':
|
||||
nlist = atoi(optarg);
|
||||
break;
|
||||
case 'r':
|
||||
nreaders = atoi(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
verbose = 1;
|
||||
break;
|
||||
case 'w':
|
||||
nwriters = atoi(optarg);
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
return (usage());
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
/* Initialize the random number generator. */
|
||||
srand(getpid() | time(NULL));
|
||||
|
||||
/* Register the signal handler. */
|
||||
(void)signal(SIGINT, onint);
|
||||
|
||||
/* Build the key list. */
|
||||
word();
|
||||
|
||||
/* Remove the previous database. */
|
||||
(void)remove(DATABASE);
|
||||
|
||||
/* Initialize the database environment. */
|
||||
if ((ret = db_init(home)) != 0)
|
||||
return (ret);
|
||||
|
||||
/* Initialize the database. */
|
||||
if ((ret = db_create(&dbp, dbenv, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "db_create");
|
||||
(void)dbenv->close(dbenv, 0);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
if ((ret = dbp->set_pagesize(dbp, 1024)) != 0) {
|
||||
dbp->err(dbp, ret, "set_pagesize");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if ((ret = dbenv->txn_begin(dbenv, NULL, &txnp, 0)) != 0)
|
||||
fatal("txn_begin", ret, 1);
|
||||
if ((ret = dbp->open(dbp, txnp,
|
||||
DATABASE, NULL, DB_BTREE, DB_CREATE | DB_THREAD, 0664)) != 0) {
|
||||
dbp->err(dbp, ret, "%s: open", DATABASE);
|
||||
goto err;
|
||||
} else {
|
||||
ret = txnp->commit(txnp, 0);
|
||||
txnp = NULL;
|
||||
if (ret != 0)
|
||||
goto err;
|
||||
}
|
||||
|
||||
nthreads = nreaders + nwriters + 2;
|
||||
printf("Running: readers %d, writers %d\n", nreaders, nwriters);
|
||||
fflush(stdout);
|
||||
|
||||
/* Create statistics structures, offset by 1. */
|
||||
if ((perf = calloc(nreaders + nwriters + 1, sizeof(*perf))) == NULL)
|
||||
fatal(NULL, errno, 1);
|
||||
|
||||
/* Create thread ID structures. */
|
||||
if ((tids = malloc(nthreads * sizeof(pthread_t))) == NULL)
|
||||
fatal(NULL, errno, 1);
|
||||
|
||||
/* Create reader/writer threads. */
|
||||
for (i = 0; i < nreaders + nwriters; ++i)
|
||||
if ((ret = pthread_create(
|
||||
&tids[i], NULL, tstart, (void *)(uintptr_t)i)) != 0)
|
||||
fatal("pthread_create", ret > 0 ? ret : errno, 1);
|
||||
|
||||
/* Create buffer pool trickle thread. */
|
||||
if (pthread_create(&tids[i], NULL, trickle, &i))
|
||||
fatal("pthread_create", errno, 1);
|
||||
++i;
|
||||
|
||||
/* Create deadlock detector thread. */
|
||||
if (pthread_create(&tids[i], NULL, deadlock, &i))
|
||||
fatal("pthread_create", errno, 1);
|
||||
|
||||
/* Wait for the threads. */
|
||||
for (i = 0; i < nthreads; ++i)
|
||||
(void)pthread_join(tids[i], &retp);
|
||||
|
||||
printf("Exiting\n");
|
||||
stats();
|
||||
|
||||
err: if (txnp != NULL)
|
||||
(void)txnp->abort(txnp);
|
||||
(void)dbp->close(dbp, 0);
|
||||
(void)dbenv->close(dbenv, 0);
|
||||
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
int
|
||||
reader(id)
|
||||
int id;
|
||||
{
|
||||
DBT key, data;
|
||||
int n, ret;
|
||||
char buf[64];
|
||||
|
||||
/*
|
||||
* DBT's must use local memory or malloc'd memory if the DB handle
|
||||
* is accessed in a threaded fashion.
|
||||
*/
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
memset(&data, 0, sizeof(DBT));
|
||||
data.flags = DB_DBT_MALLOC;
|
||||
|
||||
/*
|
||||
* Read-only threads do not require transaction protection, unless
|
||||
* there's a need for repeatable reads.
|
||||
*/
|
||||
while (!quit) {
|
||||
/* Pick a key at random, and look it up. */
|
||||
n = rand() % nlist;
|
||||
key.data = list[n];
|
||||
key.size = strlen(key.data);
|
||||
|
||||
if (verbose) {
|
||||
sprintf(buf, "reader: %d: list entry %d\n", id, n);
|
||||
write(STDOUT_FILENO, buf, strlen(buf));
|
||||
}
|
||||
|
||||
switch (ret = dbp->get(dbp, NULL, &key, &data, 0)) {
|
||||
case DB_LOCK_DEADLOCK: /* Deadlock. */
|
||||
++perf[id].aborts;
|
||||
break;
|
||||
case 0: /* Success. */
|
||||
++perf[id].found;
|
||||
free(data.data);
|
||||
break;
|
||||
case DB_NOTFOUND: /* Not found. */
|
||||
++perf[id].notfound;
|
||||
break;
|
||||
default:
|
||||
sprintf(buf,
|
||||
"reader %d: dbp->get: %s", id, (char *)key.data);
|
||||
fatal(buf, ret, 0);
|
||||
}
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
writer(id)
|
||||
int id;
|
||||
{
|
||||
DBT key, data;
|
||||
DB_TXN *tid;
|
||||
time_t now, then;
|
||||
int n, ret;
|
||||
char buf[256], dbuf[10000];
|
||||
|
||||
time(&now);
|
||||
then = now;
|
||||
|
||||
/*
|
||||
* DBT's must use local memory or malloc'd memory if the DB handle
|
||||
* is accessed in a threaded fashion.
|
||||
*/
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
memset(&data, 0, sizeof(DBT));
|
||||
data.data = dbuf;
|
||||
data.ulen = sizeof(dbuf);
|
||||
data.flags = DB_DBT_USERMEM;
|
||||
|
||||
while (!quit) {
|
||||
/* Pick a random key. */
|
||||
n = rand() % nlist;
|
||||
key.data = list[n];
|
||||
key.size = strlen(key.data);
|
||||
|
||||
if (verbose) {
|
||||
sprintf(buf, "writer: %d: list entry %d\n", id, n);
|
||||
write(STDOUT_FILENO, buf, strlen(buf));
|
||||
}
|
||||
|
||||
/* Abort and retry. */
|
||||
if (0) {
|
||||
retry: if ((ret = tid->abort(tid)) != 0)
|
||||
fatal("DB_TXN->abort", ret, 1);
|
||||
++perf[id].aborts;
|
||||
++perf[id].aborted;
|
||||
}
|
||||
|
||||
/* Thread #1 prints out the stats every 20 seconds. */
|
||||
if (id == 1) {
|
||||
time(&now);
|
||||
if (now - then >= 20) {
|
||||
stats();
|
||||
then = now;
|
||||
}
|
||||
}
|
||||
|
||||
/* Begin the transaction. */
|
||||
if ((ret = dbenv->txn_begin(dbenv, NULL, &tid, 0)) != 0)
|
||||
fatal("txn_begin", ret, 1);
|
||||
|
||||
/*
|
||||
* Get the key. If it doesn't exist, add it. If it does
|
||||
* exist, delete it.
|
||||
*/
|
||||
switch (ret = dbp->get(dbp, tid, &key, &data, 0)) {
|
||||
case DB_LOCK_DEADLOCK:
|
||||
goto retry;
|
||||
case 0:
|
||||
goto delete;
|
||||
case DB_NOTFOUND:
|
||||
goto add;
|
||||
}
|
||||
|
||||
sprintf(buf, "writer: %d: dbp->get", id);
|
||||
fatal(buf, ret, 1);
|
||||
/* NOTREACHED */
|
||||
|
||||
delete: /* Delete the key. */
|
||||
switch (ret = dbp->del(dbp, tid, &key, 0)) {
|
||||
case DB_LOCK_DEADLOCK:
|
||||
goto retry;
|
||||
case 0:
|
||||
++perf[id].deletes;
|
||||
goto commit;
|
||||
}
|
||||
|
||||
sprintf(buf, "writer: %d: dbp->del", id);
|
||||
fatal(buf, ret, 1);
|
||||
/* NOTREACHED */
|
||||
|
||||
add: /* Add the key. 1 data item in 30 is an overflow item. */
|
||||
data.size = 20 + rand() % 128;
|
||||
if (rand() % 30 == 0)
|
||||
data.size += 8192;
|
||||
|
||||
switch (ret = dbp->put(dbp, tid, &key, &data, 0)) {
|
||||
case DB_LOCK_DEADLOCK:
|
||||
goto retry;
|
||||
case 0:
|
||||
++perf[id].adds;
|
||||
goto commit;
|
||||
default:
|
||||
sprintf(buf, "writer: %d: dbp->put", id);
|
||||
fatal(buf, ret, 1);
|
||||
}
|
||||
|
||||
commit: /* The transaction finished, commit it. */
|
||||
if ((ret = tid->commit(tid, 0)) != 0)
|
||||
fatal("DB_TXN->commit", ret, 1);
|
||||
|
||||
/*
|
||||
* Every time the thread completes 20 transactions, show
|
||||
* our progress.
|
||||
*/
|
||||
if (++perf[id].txns % 20 == 0) {
|
||||
sprintf(buf,
|
||||
"writer: %2d: adds: %4d: deletes: %4d: aborts: %4d: txns: %4d\n",
|
||||
id, perf[id].adds, perf[id].deletes,
|
||||
perf[id].aborts, perf[id].txns);
|
||||
write(STDOUT_FILENO, buf, strlen(buf));
|
||||
}
|
||||
|
||||
/*
|
||||
* If this thread was aborted more than 5 times before
|
||||
* the transaction finished, complain.
|
||||
*/
|
||||
if (perf[id].aborted > 5) {
|
||||
sprintf(buf,
|
||||
"writer: %2d: adds: %4d: deletes: %4d: aborts: %4d: txns: %4d: ABORTED: %2d\n",
|
||||
id, perf[id].adds, perf[id].deletes,
|
||||
perf[id].aborts, perf[id].txns, perf[id].aborted);
|
||||
write(STDOUT_FILENO, buf, strlen(buf));
|
||||
}
|
||||
perf[id].aborted = 0;
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* stats --
|
||||
* Display reader/writer thread statistics. To display the statistics
|
||||
* for the mpool trickle or deadlock threads, use db_stat(1).
|
||||
*/
|
||||
void
|
||||
stats()
|
||||
{
|
||||
int id;
|
||||
char *p, buf[8192];
|
||||
|
||||
p = buf + sprintf(buf, "-------------\n");
|
||||
for (id = 0; id < nreaders + nwriters;)
|
||||
if (id++ < nwriters)
|
||||
p += sprintf(p,
|
||||
"writer: %2d: adds: %4d: deletes: %4d: aborts: %4d: txns: %4d\n",
|
||||
id, perf[id].adds,
|
||||
perf[id].deletes, perf[id].aborts, perf[id].txns);
|
||||
else
|
||||
p += sprintf(p,
|
||||
"reader: %2d: found: %5d: notfound: %5d: aborts: %4d\n",
|
||||
id, perf[id].found,
|
||||
perf[id].notfound, perf[id].aborts);
|
||||
p += sprintf(p, "-------------\n");
|
||||
|
||||
write(STDOUT_FILENO, buf, p - buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* db_init --
|
||||
* Initialize the environment.
|
||||
*/
|
||||
int
|
||||
db_init(home)
|
||||
const char *home;
|
||||
{
|
||||
int ret;
|
||||
|
||||
if ((ret = db_env_create(&dbenv, 0)) != 0) {
|
||||
fprintf(stderr,
|
||||
"%s: db_env_create: %s\n", progname, db_strerror(ret));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
if (punish)
|
||||
(void)dbenv->set_flags(dbenv, DB_YIELDCPU, 1);
|
||||
|
||||
dbenv->set_errfile(dbenv, stderr);
|
||||
dbenv->set_errpfx(dbenv, progname);
|
||||
(void)dbenv->set_cachesize(dbenv, 0, 100 * 1024, 0);
|
||||
(void)dbenv->set_lg_max(dbenv, 200000);
|
||||
|
||||
if ((ret = dbenv->open(dbenv, home,
|
||||
DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG |
|
||||
DB_INIT_MPOOL | DB_INIT_TXN | DB_THREAD, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, NULL);
|
||||
(void)dbenv->close(dbenv, 0);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* tstart --
|
||||
* Thread start function for readers and writers.
|
||||
*/
|
||||
void *
|
||||
tstart(arg)
|
||||
void *arg;
|
||||
{
|
||||
pthread_t tid;
|
||||
u_int id;
|
||||
|
||||
id = (uintptr_t)arg + 1;
|
||||
|
||||
tid = pthread_self();
|
||||
|
||||
if (id <= (u_int)nwriters) {
|
||||
printf("write thread %d starting: tid: %lu\n", id, (u_long)tid);
|
||||
fflush(stdout);
|
||||
writer(id);
|
||||
} else {
|
||||
printf("read thread %d starting: tid: %lu\n", id, (u_long)tid);
|
||||
fflush(stdout);
|
||||
reader(id);
|
||||
}
|
||||
|
||||
/* NOTREACHED */
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* deadlock --
|
||||
* Thread start function for DB_ENV->lock_detect.
|
||||
*/
|
||||
void *
|
||||
deadlock(arg)
|
||||
void *arg;
|
||||
{
|
||||
struct timeval t;
|
||||
pthread_t tid;
|
||||
|
||||
arg = arg; /* XXX: shut the compiler up. */
|
||||
tid = pthread_self();
|
||||
|
||||
printf("deadlock thread starting: tid: %lu\n", (u_long)tid);
|
||||
fflush(stdout);
|
||||
|
||||
t.tv_sec = 0;
|
||||
t.tv_usec = 100000;
|
||||
while (!quit) {
|
||||
(void)dbenv->lock_detect(dbenv, 0, DB_LOCK_YOUNGEST, NULL);
|
||||
|
||||
/* Check every 100ms. */
|
||||
(void)select(0, NULL, NULL, NULL, &t);
|
||||
}
|
||||
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* trickle --
|
||||
* Thread start function for memp_trickle.
|
||||
*/
|
||||
void *
|
||||
trickle(arg)
|
||||
void *arg;
|
||||
{
|
||||
pthread_t tid;
|
||||
int wrote;
|
||||
char buf[64];
|
||||
|
||||
arg = arg; /* XXX: shut the compiler up. */
|
||||
tid = pthread_self();
|
||||
|
||||
printf("trickle thread starting: tid: %lu\n", (u_long)tid);
|
||||
fflush(stdout);
|
||||
|
||||
while (!quit) {
|
||||
(void)dbenv->memp_trickle(dbenv, 10, &wrote);
|
||||
if (verbose) {
|
||||
sprintf(buf, "trickle: wrote %d\n", wrote);
|
||||
write(STDOUT_FILENO, buf, strlen(buf));
|
||||
}
|
||||
if (wrote == 0) {
|
||||
sleep(1);
|
||||
sched_yield();
|
||||
}
|
||||
}
|
||||
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* word --
|
||||
* Build the dictionary word list.
|
||||
*/
|
||||
void
|
||||
word()
|
||||
{
|
||||
FILE *fp;
|
||||
int cnt;
|
||||
char buf[256];
|
||||
|
||||
if ((fp = fopen(WORDLIST, "r")) == NULL)
|
||||
fatal(WORDLIST, errno, 1);
|
||||
|
||||
if ((list = malloc(nlist * sizeof(char *))) == NULL)
|
||||
fatal(NULL, errno, 1);
|
||||
|
||||
for (cnt = 0; cnt < nlist; ++cnt) {
|
||||
if (fgets(buf, sizeof(buf), fp) == NULL)
|
||||
break;
|
||||
if ((list[cnt] = strdup(buf)) == NULL)
|
||||
fatal(NULL, errno, 1);
|
||||
}
|
||||
nlist = cnt; /* In case nlist was larger than possible. */
|
||||
}
|
||||
|
||||
/*
|
||||
* fatal --
|
||||
* Report a fatal error and quit.
|
||||
*/
|
||||
void
|
||||
fatal(msg, err, syserr)
|
||||
const char *msg;
|
||||
int err, syserr;
|
||||
{
|
||||
fprintf(stderr, "%s: ", progname);
|
||||
if (msg != NULL) {
|
||||
fprintf(stderr, "%s", msg);
|
||||
if (syserr)
|
||||
fprintf(stderr, ": ");
|
||||
}
|
||||
if (syserr)
|
||||
fprintf(stderr, "%s", strerror(err));
|
||||
fprintf(stderr, "\n");
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
/* NOTREACHED */
|
||||
}
|
||||
|
||||
/*
|
||||
* usage --
|
||||
* Usage message.
|
||||
*/
|
||||
int
|
||||
usage()
|
||||
{
|
||||
(void)fprintf(stderr,
|
||||
"usage: %s [-pv] [-h home] [-n words] [-r readers] [-w writers]\n",
|
||||
progname);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/*
|
||||
* onint --
|
||||
* Interrupt signal handler.
|
||||
*/
|
||||
void
|
||||
onint(signo)
|
||||
int signo;
|
||||
{
|
||||
signo = 0; /* Quiet compiler. */
|
||||
quit = 1;
|
||||
}
|
||||
717
examples_c/ex_tpcb.c
Normal file
717
examples_c/ex_tpcb.c
Normal file
@@ -0,0 +1,717 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 1997,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: ex_tpcb.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#define NS_PER_MS 1000000 /* Nanoseconds in a millisecond */
|
||||
#define NS_PER_US 1000 /* Nanoseconds in a microsecond */
|
||||
#ifdef _WIN32
|
||||
#include <sys/timeb.h>
|
||||
extern int getopt(int, char * const *, const char *);
|
||||
/* Implement a basic high res timer with a POSIX interface for Windows. */
|
||||
struct timeval {
|
||||
time_t tv_sec;
|
||||
long tv_usec;
|
||||
};
|
||||
int gettimeofday(struct timeval *tv, struct timezone *tz)
|
||||
{
|
||||
struct _timeb now;
|
||||
_ftime(&now);
|
||||
tv->tv_sec = now.time;
|
||||
tv->tv_usec = now.millitm * NS_PER_US;
|
||||
return (0);
|
||||
}
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
#include <db.h>
|
||||
|
||||
typedef enum { ACCOUNT, BRANCH, TELLER } FTYPE;
|
||||
|
||||
DB_ENV *db_init __P((const char *, const char *, int, u_int32_t));
|
||||
int hpopulate __P((DB *, int, int, int, int));
|
||||
int populate __P((DB *, u_int32_t, u_int32_t, int, const char *));
|
||||
u_int32_t random_id __P((FTYPE, int, int, int));
|
||||
u_int32_t random_int __P((u_int32_t, u_int32_t));
|
||||
int tp_populate __P((DB_ENV *, int, int, int, int, int));
|
||||
int tp_run __P((DB_ENV *, int, int, int, int, int));
|
||||
int tp_txn __P((DB_ENV *, DB *, DB *, DB *, DB *, int, int, int, int));
|
||||
|
||||
int invarg __P((const char *, int, const char *));
|
||||
int main __P((int, char *[]));
|
||||
int usage __P((const char *));
|
||||
|
||||
/*
|
||||
* This program implements a basic TPC/B driver program. To create the
|
||||
* TPC/B database, run with the -i (init) flag. The number of records
|
||||
* with which to populate the account, history, branch, and teller tables
|
||||
* is specified by the a, s, b, and t flags respectively. To run a TPC/B
|
||||
* test, use the n flag to indicate a number of transactions to run (note
|
||||
* that you can run many of these processes in parallel to simulate a
|
||||
* multiuser test run).
|
||||
*/
|
||||
#define TELLERS_PER_BRANCH 10
|
||||
#define ACCOUNTS_PER_TELLER 10000
|
||||
#define HISTORY_PER_BRANCH 2592000
|
||||
|
||||
/*
|
||||
* The default configuration that adheres to TPCB scaling rules requires
|
||||
* nearly 3 GB of space. To avoid requiring that much space for testing,
|
||||
* we set the parameters much lower. If you want to run a valid 10 TPS
|
||||
* configuration, define VALID_SCALING.
|
||||
*/
|
||||
#ifdef VALID_SCALING
|
||||
#define ACCOUNTS 1000000
|
||||
#define BRANCHES 10
|
||||
#define TELLERS 100
|
||||
#define HISTORY 25920000
|
||||
#endif
|
||||
|
||||
#ifdef TINY
|
||||
#define ACCOUNTS 1000
|
||||
#define BRANCHES 10
|
||||
#define TELLERS 100
|
||||
#define HISTORY 10000
|
||||
#endif
|
||||
|
||||
#ifdef VERY_TINY
|
||||
#define ACCOUNTS 500
|
||||
#define BRANCHES 10
|
||||
#define TELLERS 50
|
||||
#define HISTORY 5000
|
||||
#endif
|
||||
|
||||
#if !defined(VALID_SCALING) && !defined(TINY) && !defined(VERY_TINY)
|
||||
#define ACCOUNTS 100000
|
||||
#define BRANCHES 10
|
||||
#define TELLERS 100
|
||||
#define HISTORY 259200
|
||||
#endif
|
||||
|
||||
#define HISTORY_LEN 100
|
||||
#define RECLEN 100
|
||||
#define BEGID 1000000
|
||||
|
||||
typedef struct _defrec {
|
||||
u_int32_t id;
|
||||
u_int32_t balance;
|
||||
u_int8_t pad[RECLEN - sizeof(u_int32_t) - sizeof(u_int32_t)];
|
||||
} defrec;
|
||||
|
||||
typedef struct _histrec {
|
||||
u_int32_t aid;
|
||||
u_int32_t bid;
|
||||
u_int32_t tid;
|
||||
u_int32_t amount;
|
||||
u_int8_t pad[RECLEN - 4 * sizeof(u_int32_t)];
|
||||
} histrec;
|
||||
|
||||
char *progname = "ex_tpcb"; /* Program name. */
|
||||
|
||||
int
|
||||
main(argc, argv)
|
||||
int argc;
|
||||
char *argv[];
|
||||
{
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
DB_ENV *dbenv;
|
||||
int accounts, branches, seed, tellers, history;
|
||||
int ch, iflag, mpool, ntxns, ret, txn_no_sync, verbose;
|
||||
const char *home;
|
||||
|
||||
home = "TESTDIR";
|
||||
accounts = branches = history = tellers = 0;
|
||||
iflag = mpool = ntxns = txn_no_sync = verbose = 0;
|
||||
seed = (int)time(NULL);
|
||||
|
||||
while ((ch = getopt(argc, argv, "a:b:c:fh:in:S:s:t:v")) != EOF)
|
||||
switch (ch) {
|
||||
case 'a': /* Number of account records */
|
||||
if ((accounts = atoi(optarg)) <= 0)
|
||||
return (invarg(progname, ch, optarg));
|
||||
break;
|
||||
case 'b': /* Number of branch records */
|
||||
if ((branches = atoi(optarg)) <= 0)
|
||||
return (invarg(progname, ch, optarg));
|
||||
break;
|
||||
case 'c': /* Cachesize in bytes */
|
||||
if ((mpool = atoi(optarg)) <= 0)
|
||||
return (invarg(progname, ch, optarg));
|
||||
break;
|
||||
case 'f': /* Fast mode: no txn sync. */
|
||||
txn_no_sync = 1;
|
||||
break;
|
||||
case 'h': /* DB home. */
|
||||
home = optarg;
|
||||
break;
|
||||
case 'i': /* Initialize the test. */
|
||||
iflag = 1;
|
||||
break;
|
||||
case 'n': /* Number of transactions */
|
||||
if ((ntxns = atoi(optarg)) <= 0)
|
||||
return (invarg(progname, ch, optarg));
|
||||
break;
|
||||
case 'S': /* Random number seed. */
|
||||
if ((seed = atoi(optarg)) <= 0)
|
||||
return (invarg(progname, ch, optarg));
|
||||
break;
|
||||
case 's': /* Number of history records */
|
||||
if ((history = atoi(optarg)) <= 0)
|
||||
return (invarg(progname, ch, optarg));
|
||||
break;
|
||||
case 't': /* Number of teller records */
|
||||
if ((tellers = atoi(optarg)) <= 0)
|
||||
return (invarg(progname, ch, optarg));
|
||||
break;
|
||||
case 'v': /* Verbose option. */
|
||||
verbose = 1;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
return (usage(progname));
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
srand((u_int)seed);
|
||||
|
||||
/* Initialize the database environment. */
|
||||
if ((dbenv = db_init(home,
|
||||
progname, mpool, txn_no_sync ? DB_TXN_NOSYNC : 0)) == NULL)
|
||||
return (EXIT_FAILURE);
|
||||
|
||||
accounts = accounts == 0 ? ACCOUNTS : accounts;
|
||||
branches = branches == 0 ? BRANCHES : branches;
|
||||
tellers = tellers == 0 ? TELLERS : tellers;
|
||||
history = history == 0 ? HISTORY : history;
|
||||
|
||||
if (verbose)
|
||||
printf("%ld Accounts, %ld Branches, %ld Tellers, %ld History\n",
|
||||
(long)accounts, (long)branches,
|
||||
(long)tellers, (long)history);
|
||||
|
||||
if (iflag) {
|
||||
if (ntxns != 0)
|
||||
return (usage(progname));
|
||||
tp_populate(dbenv,
|
||||
accounts, branches, history, tellers, verbose);
|
||||
} else {
|
||||
if (ntxns == 0)
|
||||
return (usage(progname));
|
||||
tp_run(dbenv, ntxns, accounts, branches, tellers, verbose);
|
||||
}
|
||||
|
||||
if ((ret = dbenv->close(dbenv, 0)) != 0) {
|
||||
fprintf(stderr, "%s: dbenv->close failed: %s\n",
|
||||
progname, db_strerror(ret));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
int
|
||||
invarg(progname, arg, str)
|
||||
const char *progname;
|
||||
int arg;
|
||||
const char *str;
|
||||
{
|
||||
(void)fprintf(stderr,
|
||||
"%s: invalid argument for -%c: %s\n", progname, arg, str);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int
|
||||
usage(progname)
|
||||
const char *progname;
|
||||
{
|
||||
const char *a1, *a2;
|
||||
|
||||
a1 = "[-fv] [-a accounts] [-b branches]\n";
|
||||
a2 = "\t[-c cache_size] [-h home] [-S seed] [-s history] [-t tellers]";
|
||||
(void)fprintf(stderr, "usage: %s -i %s %s\n", progname, a1, a2);
|
||||
(void)fprintf(stderr,
|
||||
" %s -n transactions %s %s\n", progname, a1, a2);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/*
|
||||
* db_init --
|
||||
* Initialize the environment.
|
||||
*/
|
||||
DB_ENV *
|
||||
db_init(home, prefix, cachesize, flags)
|
||||
const char *home, *prefix;
|
||||
int cachesize;
|
||||
u_int32_t flags;
|
||||
{
|
||||
DB_ENV *dbenv;
|
||||
u_int32_t local_flags;
|
||||
int ret;
|
||||
|
||||
if ((ret = db_env_create(&dbenv, 0)) != 0) {
|
||||
fprintf(stderr,
|
||||
"%s: db_env_create: %s\n", progname, db_strerror(ret));
|
||||
return (NULL);
|
||||
}
|
||||
dbenv->set_errfile(dbenv, stderr);
|
||||
dbenv->set_errpfx(dbenv, prefix);
|
||||
(void)dbenv->set_cachesize(dbenv, 0,
|
||||
cachesize == 0 ? 4 * 1024 * 1024 : (u_int32_t)cachesize, 0);
|
||||
|
||||
if (flags & (DB_TXN_NOSYNC))
|
||||
(void)dbenv->set_flags(dbenv, DB_TXN_NOSYNC, 1);
|
||||
flags &= ~(DB_TXN_NOSYNC);
|
||||
|
||||
local_flags = flags | DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG |
|
||||
DB_INIT_MPOOL | DB_INIT_TXN;
|
||||
if ((ret = dbenv->open(dbenv, home, local_flags, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB_ENV->open: %s", home);
|
||||
(void)dbenv->close(dbenv, 0);
|
||||
return (NULL);
|
||||
}
|
||||
return (dbenv);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the database to the specified number of accounts, branches,
|
||||
* history records, and tellers.
|
||||
*/
|
||||
int
|
||||
tp_populate(env, accounts, branches, history, tellers, verbose)
|
||||
DB_ENV *env;
|
||||
int accounts, branches, history, tellers, verbose;
|
||||
{
|
||||
DB *dbp;
|
||||
u_int32_t balance, idnum, oflags;
|
||||
u_int32_t end_anum, end_bnum, end_tnum;
|
||||
u_int32_t start_anum, start_bnum, start_tnum;
|
||||
int ret;
|
||||
|
||||
idnum = BEGID;
|
||||
balance = 500000;
|
||||
oflags = DB_CREATE;
|
||||
|
||||
if ((ret = db_create(&dbp, env, 0)) != 0) {
|
||||
env->err(env, ret, "db_create");
|
||||
return (1);
|
||||
}
|
||||
(void)dbp->set_h_nelem(dbp, (u_int32_t)accounts);
|
||||
|
||||
if ((ret = dbp->open(dbp, NULL, "account", NULL,
|
||||
DB_HASH, oflags, 0644)) != 0) {
|
||||
env->err(env, ret, "DB->open: account");
|
||||
return (1);
|
||||
}
|
||||
|
||||
start_anum = idnum;
|
||||
populate(dbp, idnum, balance, accounts, "account");
|
||||
idnum += accounts;
|
||||
end_anum = idnum - 1;
|
||||
if ((ret = dbp->close(dbp, 0)) != 0) {
|
||||
env->err(env, ret, "DB->close: account");
|
||||
return (1);
|
||||
}
|
||||
if (verbose)
|
||||
printf("Populated accounts: %ld - %ld\n",
|
||||
(long)start_anum, (long)end_anum);
|
||||
|
||||
/*
|
||||
* Since the number of branches is very small, we want to use very
|
||||
* small pages and only 1 key per page, i.e., key-locking instead
|
||||
* of page locking.
|
||||
*/
|
||||
if ((ret = db_create(&dbp, env, 0)) != 0) {
|
||||
env->err(env, ret, "db_create");
|
||||
return (1);
|
||||
}
|
||||
(void)dbp->set_h_ffactor(dbp, 1);
|
||||
(void)dbp->set_h_nelem(dbp, (u_int32_t)branches);
|
||||
(void)dbp->set_pagesize(dbp, 512);
|
||||
if ((ret = dbp->open(dbp, NULL, "branch", NULL,
|
||||
DB_HASH, oflags, 0644)) != 0) {
|
||||
env->err(env, ret, "DB->open: branch");
|
||||
return (1);
|
||||
}
|
||||
start_bnum = idnum;
|
||||
populate(dbp, idnum, balance, branches, "branch");
|
||||
idnum += branches;
|
||||
end_bnum = idnum - 1;
|
||||
if ((ret = dbp->close(dbp, 0)) != 0) {
|
||||
env->err(env, ret, "DB->close: branch");
|
||||
return (1);
|
||||
}
|
||||
if (verbose)
|
||||
printf("Populated branches: %ld - %ld\n",
|
||||
(long)start_bnum, (long)end_bnum);
|
||||
|
||||
/*
|
||||
* In the case of tellers, we also want small pages, but we'll let
|
||||
* the fill factor dynamically adjust itself.
|
||||
*/
|
||||
if ((ret = db_create(&dbp, env, 0)) != 0) {
|
||||
env->err(env, ret, "db_create");
|
||||
return (1);
|
||||
}
|
||||
(void)dbp->set_h_ffactor(dbp, 0);
|
||||
(void)dbp->set_h_nelem(dbp, (u_int32_t)tellers);
|
||||
(void)dbp->set_pagesize(dbp, 512);
|
||||
if ((ret = dbp->open(dbp, NULL, "teller", NULL,
|
||||
DB_HASH, oflags, 0644)) != 0) {
|
||||
env->err(env, ret, "DB->open: teller");
|
||||
return (1);
|
||||
}
|
||||
|
||||
start_tnum = idnum;
|
||||
populate(dbp, idnum, balance, tellers, "teller");
|
||||
idnum += tellers;
|
||||
end_tnum = idnum - 1;
|
||||
if ((ret = dbp->close(dbp, 0)) != 0) {
|
||||
env->err(env, ret, "DB->close: teller");
|
||||
return (1);
|
||||
}
|
||||
if (verbose)
|
||||
printf("Populated tellers: %ld - %ld\n",
|
||||
(long)start_tnum, (long)end_tnum);
|
||||
|
||||
if ((ret = db_create(&dbp, env, 0)) != 0) {
|
||||
env->err(env, ret, "db_create");
|
||||
return (1);
|
||||
}
|
||||
(void)dbp->set_re_len(dbp, HISTORY_LEN);
|
||||
if ((ret = dbp->open(dbp, NULL, "history", NULL,
|
||||
DB_RECNO, oflags, 0644)) != 0) {
|
||||
env->err(env, ret, "DB->open: history");
|
||||
return (1);
|
||||
}
|
||||
|
||||
hpopulate(dbp, history, accounts, branches, tellers);
|
||||
if ((ret = dbp->close(dbp, 0)) != 0) {
|
||||
env->err(env, ret, "DB->close: history");
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
populate(dbp, start_id, balance, nrecs, msg)
|
||||
DB *dbp;
|
||||
u_int32_t start_id, balance;
|
||||
int nrecs;
|
||||
const char *msg;
|
||||
{
|
||||
DBT kdbt, ddbt;
|
||||
defrec drec;
|
||||
int i, ret;
|
||||
|
||||
kdbt.flags = 0;
|
||||
kdbt.data = &drec.id;
|
||||
kdbt.size = sizeof(u_int32_t);
|
||||
ddbt.flags = 0;
|
||||
ddbt.data = &drec;
|
||||
ddbt.size = sizeof(drec);
|
||||
memset(&drec.pad[0], 1, sizeof(drec.pad));
|
||||
|
||||
for (i = 0; i < nrecs; i++) {
|
||||
drec.id = start_id + (u_int32_t)i;
|
||||
drec.balance = balance;
|
||||
if ((ret =
|
||||
(dbp->put)(dbp, NULL, &kdbt, &ddbt, DB_NOOVERWRITE)) != 0) {
|
||||
dbp->err(dbp,
|
||||
ret, "Failure initializing %s file\n", msg);
|
||||
return (1);
|
||||
}
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
hpopulate(dbp, history, accounts, branches, tellers)
|
||||
DB *dbp;
|
||||
int history, accounts, branches, tellers;
|
||||
{
|
||||
DBT kdbt, ddbt;
|
||||
histrec hrec;
|
||||
db_recno_t key;
|
||||
int i, ret;
|
||||
|
||||
memset(&kdbt, 0, sizeof(kdbt));
|
||||
memset(&ddbt, 0, sizeof(ddbt));
|
||||
ddbt.data = &hrec;
|
||||
ddbt.size = sizeof(hrec);
|
||||
kdbt.data = &key;
|
||||
kdbt.size = sizeof(key);
|
||||
memset(&hrec.pad[0], 1, sizeof(hrec.pad));
|
||||
hrec.amount = 10;
|
||||
|
||||
for (i = 1; i <= history; i++) {
|
||||
hrec.aid = random_id(ACCOUNT, accounts, branches, tellers);
|
||||
hrec.bid = random_id(BRANCH, accounts, branches, tellers);
|
||||
hrec.tid = random_id(TELLER, accounts, branches, tellers);
|
||||
if ((ret = dbp->put(dbp, NULL, &kdbt, &ddbt, DB_APPEND)) != 0) {
|
||||
dbp->err(dbp, ret, "dbp->put");
|
||||
return (1);
|
||||
}
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
u_int32_t
|
||||
random_int(lo, hi)
|
||||
u_int32_t lo, hi;
|
||||
{
|
||||
u_int32_t ret;
|
||||
int t;
|
||||
|
||||
#ifndef RAND_MAX
|
||||
#define RAND_MAX 0x7fffffff
|
||||
#endif
|
||||
t = rand();
|
||||
ret = (u_int32_t)(((double)t / ((double)(RAND_MAX) + 1)) *
|
||||
(hi - lo + 1));
|
||||
ret += lo;
|
||||
return (ret);
|
||||
}
|
||||
|
||||
u_int32_t
|
||||
random_id(type, accounts, branches, tellers)
|
||||
FTYPE type;
|
||||
int accounts, branches, tellers;
|
||||
{
|
||||
u_int32_t min, max, num;
|
||||
|
||||
max = min = BEGID;
|
||||
num = accounts;
|
||||
switch (type) {
|
||||
case TELLER:
|
||||
min += branches;
|
||||
num = tellers;
|
||||
/* FALLTHROUGH */
|
||||
case BRANCH:
|
||||
if (type == BRANCH)
|
||||
num = branches;
|
||||
min += accounts;
|
||||
/* FALLTHROUGH */
|
||||
case ACCOUNT:
|
||||
max = min + num - 1;
|
||||
}
|
||||
return (random_int(min, max));
|
||||
}
|
||||
|
||||
int
|
||||
tp_run(dbenv, n, accounts, branches, tellers, verbose)
|
||||
DB_ENV *dbenv;
|
||||
int n, accounts, branches, tellers, verbose;
|
||||
{
|
||||
DB *adb, *bdb, *hdb, *tdb;
|
||||
int failed, ret, txns;
|
||||
struct timeval start_tv, end_tv;
|
||||
double start_time, end_time;
|
||||
|
||||
adb = bdb = hdb = tdb = NULL;
|
||||
|
||||
/*
|
||||
* Open the database files.
|
||||
*/
|
||||
if ((ret = db_create(&adb, dbenv, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "db_create");
|
||||
goto err;
|
||||
}
|
||||
if ((ret = adb->open(adb, NULL, "account", NULL, DB_UNKNOWN,
|
||||
DB_AUTO_COMMIT, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB->open: account");
|
||||
goto err;
|
||||
}
|
||||
if ((ret = db_create(&bdb, dbenv, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "db_create");
|
||||
goto err;
|
||||
}
|
||||
if ((ret = bdb->open(bdb, NULL, "branch", NULL, DB_UNKNOWN,
|
||||
DB_AUTO_COMMIT, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB->open: branch");
|
||||
goto err;
|
||||
}
|
||||
if ((ret = db_create(&hdb, dbenv, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "db_create");
|
||||
goto err;
|
||||
}
|
||||
if ((ret = hdb->open(hdb, NULL, "history", NULL, DB_UNKNOWN,
|
||||
DB_AUTO_COMMIT, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB->open: history");
|
||||
goto err;
|
||||
}
|
||||
if ((ret = db_create(&tdb, dbenv, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "db_create");
|
||||
goto err;
|
||||
}
|
||||
if ((ret = tdb->open(tdb, NULL, "teller", NULL, DB_UNKNOWN,
|
||||
DB_AUTO_COMMIT, 0)) != 0) {
|
||||
dbenv->err(dbenv, ret, "DB->open: teller");
|
||||
goto err;
|
||||
}
|
||||
|
||||
(void)gettimeofday(&start_tv, NULL);
|
||||
|
||||
for (txns = n, failed = 0; n-- > 0;)
|
||||
if ((ret = tp_txn(dbenv, adb, bdb, tdb, hdb,
|
||||
accounts, branches, tellers, verbose)) != 0)
|
||||
++failed;
|
||||
|
||||
(void)gettimeofday(&end_tv, NULL);
|
||||
|
||||
start_time = start_tv.tv_sec + ((start_tv.tv_usec + 0.0)/NS_PER_MS);
|
||||
end_time = end_tv.tv_sec + ((end_tv.tv_usec + 0.0)/NS_PER_MS);
|
||||
if (end_time == start_time)
|
||||
end_time += 1/NS_PER_MS;
|
||||
|
||||
printf("%s: %d txns: %d failed, %.3f sec, %.2f TPS\n", progname,
|
||||
txns, failed, (end_time - start_time),
|
||||
(txns - failed) / (double)(end_time - start_time));
|
||||
|
||||
err: if (adb != NULL)
|
||||
(void)adb->close(adb, 0);
|
||||
if (bdb != NULL)
|
||||
(void)bdb->close(bdb, 0);
|
||||
if (tdb != NULL)
|
||||
(void)tdb->close(tdb, 0);
|
||||
if (hdb != NULL)
|
||||
(void)hdb->close(hdb, 0);
|
||||
return (ret == 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* XXX Figure out the appropriate way to pick out IDs.
|
||||
*/
|
||||
int
|
||||
tp_txn(dbenv, adb, bdb, tdb, hdb, accounts, branches, tellers, verbose)
|
||||
DB_ENV *dbenv;
|
||||
DB *adb, *bdb, *tdb, *hdb;
|
||||
int accounts, branches, tellers, verbose;
|
||||
{
|
||||
DBC *acurs, *bcurs, *tcurs;
|
||||
DBT d_dbt, d_histdbt, k_dbt, k_histdbt;
|
||||
DB_TXN *t;
|
||||
db_recno_t key;
|
||||
defrec rec;
|
||||
histrec hrec;
|
||||
int account, branch, teller, ret;
|
||||
|
||||
t = NULL;
|
||||
acurs = bcurs = tcurs = NULL;
|
||||
|
||||
/*
|
||||
* !!!
|
||||
* This is sample code -- we could move a lot of this into the driver
|
||||
* to make it faster.
|
||||
*/
|
||||
account = random_id(ACCOUNT, accounts, branches, tellers);
|
||||
branch = random_id(BRANCH, accounts, branches, tellers);
|
||||
teller = random_id(TELLER, accounts, branches, tellers);
|
||||
|
||||
memset(&d_histdbt, 0, sizeof(d_histdbt));
|
||||
|
||||
memset(&k_histdbt, 0, sizeof(k_histdbt));
|
||||
k_histdbt.data = &key;
|
||||
k_histdbt.size = sizeof(key);
|
||||
|
||||
memset(&k_dbt, 0, sizeof(k_dbt));
|
||||
k_dbt.size = sizeof(int);
|
||||
|
||||
memset(&d_dbt, 0, sizeof(d_dbt));
|
||||
d_dbt.flags = DB_DBT_USERMEM;
|
||||
d_dbt.data = &rec;
|
||||
d_dbt.ulen = sizeof(rec);
|
||||
|
||||
hrec.aid = account;
|
||||
hrec.bid = branch;
|
||||
hrec.tid = teller;
|
||||
hrec.amount = 10;
|
||||
/* Request 0 bytes since we're just positioning. */
|
||||
d_histdbt.flags = DB_DBT_PARTIAL;
|
||||
|
||||
/*
|
||||
* START PER-TRANSACTION TIMING.
|
||||
*
|
||||
* Technically, TPCB requires a limit on response time, you only get
|
||||
* to count transactions that complete within 2 seconds. That's not
|
||||
* an issue for this sample application -- regardless, here's where
|
||||
* the transaction begins.
|
||||
*/
|
||||
if (dbenv->txn_begin(dbenv, NULL, &t, 0) != 0)
|
||||
goto err;
|
||||
|
||||
if (adb->cursor(adb, t, &acurs, 0) != 0 ||
|
||||
bdb->cursor(bdb, t, &bcurs, 0) != 0 ||
|
||||
tdb->cursor(tdb, t, &tcurs, 0) != 0)
|
||||
goto err;
|
||||
|
||||
/* Account record */
|
||||
k_dbt.data = &account;
|
||||
if (acurs->get(acurs, &k_dbt, &d_dbt, DB_SET) != 0)
|
||||
goto err;
|
||||
rec.balance += 10;
|
||||
if (acurs->put(acurs, &k_dbt, &d_dbt, DB_CURRENT) != 0)
|
||||
goto err;
|
||||
|
||||
/* Branch record */
|
||||
k_dbt.data = &branch;
|
||||
if (bcurs->get(bcurs, &k_dbt, &d_dbt, DB_SET) != 0)
|
||||
goto err;
|
||||
rec.balance += 10;
|
||||
if (bcurs->put(bcurs, &k_dbt, &d_dbt, DB_CURRENT) != 0)
|
||||
goto err;
|
||||
|
||||
/* Teller record */
|
||||
k_dbt.data = &teller;
|
||||
if (tcurs->get(tcurs, &k_dbt, &d_dbt, DB_SET) != 0)
|
||||
goto err;
|
||||
rec.balance += 10;
|
||||
if (tcurs->put(tcurs, &k_dbt, &d_dbt, DB_CURRENT) != 0)
|
||||
goto err;
|
||||
|
||||
/* History record */
|
||||
d_histdbt.flags = 0;
|
||||
d_histdbt.data = &hrec;
|
||||
d_histdbt.ulen = sizeof(hrec);
|
||||
if (hdb->put(hdb, t, &k_histdbt, &d_histdbt, DB_APPEND) != 0)
|
||||
goto err;
|
||||
|
||||
if (acurs->close(acurs) != 0 || bcurs->close(bcurs) != 0 ||
|
||||
tcurs->close(tcurs) != 0)
|
||||
goto err;
|
||||
|
||||
ret = t->commit(t, 0);
|
||||
t = NULL;
|
||||
if (ret != 0)
|
||||
goto err;
|
||||
/* END PER-TRANSACTION TIMING. */
|
||||
|
||||
return (0);
|
||||
|
||||
err: if (acurs != NULL)
|
||||
(void)acurs->close(acurs);
|
||||
if (bcurs != NULL)
|
||||
(void)bcurs->close(bcurs);
|
||||
if (tcurs != NULL)
|
||||
(void)tcurs->close(tcurs);
|
||||
if (t != NULL)
|
||||
(void)t->abort(t);
|
||||
|
||||
if (verbose)
|
||||
printf("Transaction A=%ld B=%ld T=%ld failed\n",
|
||||
(long)account, (long)branch, (long)teller);
|
||||
return (-1);
|
||||
}
|
||||
274
examples_c/getting_started/example_database_load.c
Normal file
274
examples_c/getting_started/example_database_load.c
Normal file
@@ -0,0 +1,274 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2004,2008 Oracle. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "gettingstarted_common.h"
|
||||
|
||||
/* Forward declarations */
|
||||
int usage(void);
|
||||
int load_vendors_database(STOCK_DBS, char *);
|
||||
size_t pack_string(char *, char *, size_t);
|
||||
int load_inventory_database(STOCK_DBS, char *);
|
||||
|
||||
int
|
||||
usage()
|
||||
{
|
||||
fprintf(stderr, "example_database_load [-b <path to data files>]");
|
||||
fprintf(stderr, " [-h <database_home_directory>]\n");
|
||||
|
||||
fprintf(stderr, "\tNote: Any path specified must end with your");
|
||||
fprintf(stderr, " system's path delimiter (/ or \\)\n");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Loads the contents of vendors.txt and inventory.txt into
|
||||
* Berkeley DB databases. Also causes the itemname secondary
|
||||
* database to be created and loaded.
|
||||
*/
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
STOCK_DBS my_stock;
|
||||
int ch, ret;
|
||||
size_t size;
|
||||
char *basename, *inventory_file, *vendor_file;
|
||||
|
||||
/* Initialize the STOCK_DBS struct */
|
||||
initialize_stockdbs(&my_stock);
|
||||
|
||||
/* Initialize the base path. */
|
||||
basename = "./";
|
||||
|
||||
/* Parse the command line arguments */
|
||||
while ((ch = getopt(argc, argv, "b:h:")) != EOF)
|
||||
switch (ch) {
|
||||
case 'h':
|
||||
if (optarg[strlen(optarg)-1] != '/' &&
|
||||
optarg[strlen(optarg)-1] != '\\')
|
||||
return (usage());
|
||||
my_stock.db_home_dir = optarg;
|
||||
break;
|
||||
case 'b':
|
||||
if (basename[strlen(basename)-1] != '/' &&
|
||||
basename[strlen(basename)-1] != '\\')
|
||||
return (usage());
|
||||
basename = optarg;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
return (usage());
|
||||
}
|
||||
|
||||
/* Identify the files that will hold our databases */
|
||||
set_db_filenames(&my_stock);
|
||||
|
||||
/* Find our input files */
|
||||
size = strlen(basename) + strlen(INVENTORY_FILE) + 1;
|
||||
inventory_file = malloc(size);
|
||||
snprintf(inventory_file, size, "%s%s", basename, INVENTORY_FILE);
|
||||
|
||||
size = strlen(basename) + strlen(VENDORS_FILE) + 1;
|
||||
vendor_file = malloc(size);
|
||||
snprintf(vendor_file, size, "%s%s", basename, VENDORS_FILE);
|
||||
|
||||
/* Open all databases */
|
||||
ret = databases_setup(&my_stock, "example_database_load", stderr);
|
||||
if (ret) {
|
||||
fprintf(stderr, "Error opening databases\n");
|
||||
databases_close(&my_stock);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
ret = load_vendors_database(my_stock, vendor_file);
|
||||
if (ret) {
|
||||
fprintf(stderr, "Error loading vendors database.\n");
|
||||
databases_close(&my_stock);
|
||||
return (ret);
|
||||
}
|
||||
ret = load_inventory_database(my_stock, inventory_file);
|
||||
if (ret) {
|
||||
fprintf(stderr, "Error loading inventory database.\n");
|
||||
databases_close(&my_stock);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/* close our environment and databases */
|
||||
databases_close(&my_stock);
|
||||
|
||||
printf("Done loading databases.\n");
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* Loads the contents of the vendors.txt file into
|
||||
* a database.
|
||||
*/
|
||||
int
|
||||
load_vendors_database(STOCK_DBS my_stock, char *vendor_file)
|
||||
{
|
||||
DBT key, data;
|
||||
char buf[MAXLINE];
|
||||
FILE *ifp;
|
||||
VENDOR my_vendor;
|
||||
|
||||
/* Load the vendors database */
|
||||
ifp = fopen(vendor_file, "r");
|
||||
if (ifp == NULL) {
|
||||
fprintf(stderr, "Error opening file '%s'\n", vendor_file);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
while (fgets(buf, MAXLINE, ifp) != NULL) {
|
||||
/* zero out the structure */
|
||||
memset(&my_vendor, 0, sizeof(VENDOR));
|
||||
/* Zero out the DBTs */
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
memset(&data, 0, sizeof(DBT));
|
||||
|
||||
/*
|
||||
* Scan the line into the structure.
|
||||
* Convenient, but not particularly safe.
|
||||
* In a real program, there would be a lot more
|
||||
* defensive code here.
|
||||
*/
|
||||
sscanf(buf,
|
||||
"%20[^#]#%20[^#]#%20[^#]#%3[^#]#%6[^#]#%13[^#]#%20[^#]#%20[^\n]",
|
||||
my_vendor.name, my_vendor.street,
|
||||
my_vendor.city, my_vendor.state,
|
||||
my_vendor.zipcode, my_vendor.phone_number,
|
||||
my_vendor.sales_rep, my_vendor.sales_rep_phone);
|
||||
|
||||
/* Now that we have our structure we can load it into the database. */
|
||||
|
||||
/* Set up the database record's key */
|
||||
key.data = my_vendor.name;
|
||||
key.size = (u_int32_t)strlen(my_vendor.name) + 1;
|
||||
|
||||
/* Set up the database record's data */
|
||||
data.data = &my_vendor;
|
||||
data.size = sizeof(VENDOR);
|
||||
|
||||
/*
|
||||
* Note that given the way we built our struct, there's extra
|
||||
* bytes in it. Essentially we're using fixed-width fields with
|
||||
* the unused portion of some fields padded with zeros. This
|
||||
* is the easiest thing to do, but it does result in a bloated
|
||||
* database. Look at load_inventory_data() for an example of how
|
||||
* to avoid this.
|
||||
*/
|
||||
|
||||
/* Put the data into the database */
|
||||
my_stock.vendor_dbp->put(my_stock.vendor_dbp, 0, &key, &data, 0);
|
||||
} /* end vendors database while loop */
|
||||
|
||||
fclose(ifp);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Simple little convenience function that takes a buffer, a string,
|
||||
* and an offset and copies that string into the buffer at the
|
||||
* appropriate location. Used to ensure that all our strings
|
||||
* are contained in a single contiguous chunk of memory.
|
||||
*/
|
||||
size_t
|
||||
pack_string(char *buffer, char *string, size_t start_pos)
|
||||
{
|
||||
size_t string_size;
|
||||
|
||||
string_size = strlen(string) + 1;
|
||||
memcpy(buffer+start_pos, string, string_size);
|
||||
|
||||
return (start_pos + string_size);
|
||||
}
|
||||
|
||||
/*
|
||||
* Loads the contents of the inventory.txt file into
|
||||
* a database. Note that because the itemname
|
||||
* secondary database is associated to the inventorydb
|
||||
* (see env_setup() in gettingstarted_common.c), the
|
||||
* itemname index is automatically created when this
|
||||
* database is loaded.
|
||||
*/
|
||||
int
|
||||
load_inventory_database(STOCK_DBS my_stock, char *inventory_file)
|
||||
{
|
||||
DBT key, data;
|
||||
char buf[MAXLINE];
|
||||
char databuf[MAXDATABUF];
|
||||
size_t bufLen, dataLen;
|
||||
FILE *ifp;
|
||||
|
||||
/*
|
||||
* Rather than lining everything up nicely in a struct, we're being
|
||||
* deliberately a bit sloppy here. This function illustrates how to
|
||||
* store mixed data that might be obtained from various locations
|
||||
* in your application.
|
||||
*/
|
||||
float price;
|
||||
int quantity;
|
||||
char category[MAXFIELD], name[MAXFIELD];
|
||||
char vendor[MAXFIELD], sku[MAXFIELD];
|
||||
|
||||
/* Load the inventory database */
|
||||
ifp = fopen(inventory_file, "r");
|
||||
if (ifp == NULL) {
|
||||
fprintf(stderr, "Error opening file '%s'\n", inventory_file);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
while (fgets(buf, MAXLINE, ifp) != NULL) {
|
||||
/*
|
||||
* Scan the line into the appropriate buffers and variables.
|
||||
* Convenient, but not particularly safe. In a real
|
||||
* program, there would be a lot more defensive code here.
|
||||
*/
|
||||
sscanf(buf,
|
||||
"%20[^#]#%20[^#]#%f#%i#%20[^#]#%20[^\n]",
|
||||
name, sku, &price, &quantity, category, vendor);
|
||||
|
||||
/*
|
||||
* Now pack it into a single contiguous memory location for
|
||||
* storage.
|
||||
*/
|
||||
memset(databuf, 0, MAXDATABUF);
|
||||
bufLen = 0;
|
||||
dataLen = 0;
|
||||
|
||||
dataLen = sizeof(float);
|
||||
memcpy(databuf, &price, dataLen);
|
||||
bufLen += dataLen;
|
||||
|
||||
dataLen = sizeof(int);
|
||||
memcpy(databuf + bufLen, &quantity, dataLen);
|
||||
bufLen += dataLen;
|
||||
|
||||
bufLen = pack_string(databuf, name, bufLen);
|
||||
bufLen = pack_string(databuf, sku, bufLen);
|
||||
bufLen = pack_string(databuf, category, bufLen);
|
||||
bufLen = pack_string(databuf, vendor, bufLen);
|
||||
|
||||
/* Zero out the DBTs */
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
memset(&data, 0, sizeof(DBT));
|
||||
|
||||
/* The key is the item's SKU */
|
||||
key.data = sku;
|
||||
key.size = (u_int32_t)strlen(sku) + 1;
|
||||
|
||||
/* The data is the information that we packed into databuf. */
|
||||
data.data = databuf;
|
||||
data.size = (u_int32_t)bufLen;
|
||||
|
||||
/* Put the data into the database */
|
||||
my_stock.vendor_dbp->put(my_stock.inventory_dbp, 0, &key, &data, 0);
|
||||
} /* end vendors database while loop */
|
||||
|
||||
/* Cleanup */
|
||||
fclose(ifp);
|
||||
|
||||
return (0);
|
||||
}
|
||||
275
examples_c/getting_started/example_database_read.c
Normal file
275
examples_c/getting_started/example_database_read.c
Normal file
@@ -0,0 +1,275 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2004,2008 Oracle. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "gettingstarted_common.h"
|
||||
|
||||
/* Forward declarations */
|
||||
int usage(void);
|
||||
char *show_inventory_item(void *);
|
||||
int show_all_records(STOCK_DBS *);
|
||||
int show_records(STOCK_DBS *, char *);
|
||||
int show_vendor_record(char *, DB *);
|
||||
|
||||
int
|
||||
usage()
|
||||
{
|
||||
fprintf(stderr, "example_database_read [-i <item name>]");
|
||||
fprintf(stderr, " [-h <database home>]\n");
|
||||
|
||||
fprintf(stderr,
|
||||
"\tNote: Any path specified to the -h parameter must end\n");
|
||||
fprintf(stderr, " with your system's path delimiter (/ or \\)\n");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Searches for a inventory item based on that item's name. The search is
|
||||
* performed using the item name secondary database. Displays all
|
||||
* inventory items that use the specified name, as well as the vendor
|
||||
* associated with that inventory item.
|
||||
*
|
||||
* If no item name is provided, then all inventory items are displayed.
|
||||
*/
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
STOCK_DBS my_stock;
|
||||
int ch, ret;
|
||||
char *itemname;
|
||||
|
||||
/* Initialize the STOCK_DBS struct */
|
||||
initialize_stockdbs(&my_stock);
|
||||
|
||||
/* Parse the command line arguments */
|
||||
itemname = NULL;
|
||||
while ((ch = getopt(argc, argv, "h:i:?")) != EOF)
|
||||
switch (ch) {
|
||||
case 'h':
|
||||
if (optarg[strlen(optarg)-1] != '/' &&
|
||||
optarg[strlen(optarg)-1] != '\\')
|
||||
return (usage());
|
||||
my_stock.db_home_dir = optarg;
|
||||
break;
|
||||
case 'i':
|
||||
itemname = optarg;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
return (usage());
|
||||
}
|
||||
|
||||
/* Identify the files that hold our databases */
|
||||
set_db_filenames(&my_stock);
|
||||
|
||||
/* Open all databases */
|
||||
ret = databases_setup(&my_stock, "example_database_read", stderr);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Error opening databases\n");
|
||||
databases_close(&my_stock);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
if (itemname == NULL)
|
||||
ret = show_all_records(&my_stock);
|
||||
else
|
||||
ret = show_records(&my_stock, itemname);
|
||||
|
||||
/* close our databases */
|
||||
databases_close(&my_stock);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
int show_all_records(STOCK_DBS *my_stock)
|
||||
{
|
||||
DBC *inventory_cursorp;
|
||||
DBT key, data;
|
||||
char *the_vendor;
|
||||
int exit_value, ret;
|
||||
|
||||
/* Initialize our DBTs. */
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
memset(&data, 0, sizeof(DBT));
|
||||
|
||||
/* Get a cursor to the inventory db */
|
||||
my_stock->inventory_dbp->cursor(my_stock->inventory_dbp, NULL,
|
||||
&inventory_cursorp, 0);
|
||||
|
||||
/*
|
||||
* Iterate over the inventory database, from the first record
|
||||
* to the last, displaying each in turn.
|
||||
*/
|
||||
exit_value = 0;
|
||||
while ((ret =
|
||||
inventory_cursorp->get(inventory_cursorp, &key, &data, DB_NEXT)) == 0)
|
||||
{
|
||||
the_vendor = show_inventory_item(data.data);
|
||||
ret = show_vendor_record(the_vendor, my_stock->vendor_dbp);
|
||||
if (ret) {
|
||||
exit_value = ret;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Close the cursor */
|
||||
inventory_cursorp->close(inventory_cursorp);
|
||||
return (exit_value);
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for an inventory item given its name (using the inventory item
|
||||
* secondary database) and display that record and any duplicates that may
|
||||
* exist.
|
||||
*/
|
||||
int
|
||||
show_records(STOCK_DBS *my_stock, char *itemname)
|
||||
{
|
||||
DBC *itemname_cursorp;
|
||||
DBT key, data;
|
||||
char *the_vendor;
|
||||
int ret, exit_value;
|
||||
|
||||
/* Initialize our DBTs. */
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
memset(&data, 0, sizeof(DBT));
|
||||
|
||||
/* Get a cursor to the itemname db */
|
||||
my_stock->itemname_sdbp->cursor(my_stock->itemname_sdbp, NULL,
|
||||
&itemname_cursorp, 0);
|
||||
|
||||
/*
|
||||
* Get the search key. This is the name on the inventory
|
||||
* record that we want to examine.
|
||||
*/
|
||||
key.data = itemname;
|
||||
key.size = (u_int32_t)strlen(itemname) + 1;
|
||||
|
||||
/*
|
||||
* Position our cursor to the first record in the secondary
|
||||
* database that has the appropriate key.
|
||||
*/
|
||||
exit_value = 0;
|
||||
ret = itemname_cursorp->get(itemname_cursorp, &key, &data, DB_SET);
|
||||
if (!ret) {
|
||||
do {
|
||||
/*
|
||||
* Show the inventory record and the vendor responsible
|
||||
* for this inventory item.
|
||||
*/
|
||||
the_vendor = show_inventory_item(data.data);
|
||||
ret = show_vendor_record(the_vendor, my_stock->vendor_dbp);
|
||||
if (ret) {
|
||||
exit_value = ret;
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* Our secondary allows duplicates, so we need to loop over
|
||||
* the next duplicate records and show them all. This is done
|
||||
* because an inventory item's name is not a unique value.
|
||||
*/
|
||||
} while (itemname_cursorp->get(itemname_cursorp, &key, &data,
|
||||
DB_NEXT_DUP) == 0);
|
||||
} else {
|
||||
printf("No records found for '%s'\n", itemname);
|
||||
}
|
||||
|
||||
/* Close the cursor */
|
||||
itemname_cursorp->close(itemname_cursorp);
|
||||
|
||||
return (exit_value);
|
||||
}
|
||||
|
||||
/*
|
||||
* Shows an inventory item. How we retrieve the inventory
|
||||
* item values from the provided buffer is strictly dependent
|
||||
* on the order that those items were originally stored in the
|
||||
* DBT. See load_inventory_database in example_database_load
|
||||
* for how this was done.
|
||||
*/
|
||||
char *
|
||||
show_inventory_item(void *vBuf)
|
||||
{
|
||||
float price;
|
||||
int quantity;
|
||||
size_t buf_pos;
|
||||
char *category, *name, *sku, *vendor_name;
|
||||
char *buf = (char *)vBuf;
|
||||
|
||||
price = *((float *)buf);
|
||||
buf_pos = sizeof(float);
|
||||
|
||||
quantity = *((int *)(buf + buf_pos));
|
||||
buf_pos += sizeof(int);
|
||||
|
||||
name = buf + buf_pos;
|
||||
buf_pos += strlen(name) + 1;
|
||||
|
||||
sku = buf + buf_pos;
|
||||
buf_pos += strlen(sku) + 1;
|
||||
|
||||
category = buf + buf_pos;
|
||||
buf_pos += strlen(category) + 1;
|
||||
|
||||
vendor_name = buf + buf_pos;
|
||||
|
||||
printf("name: %s\n", name);
|
||||
printf("\tSKU: %s\n", sku);
|
||||
printf("\tCategory: %s\n", category);
|
||||
printf("\tPrice: %.2f\n", price);
|
||||
printf("\tQuantity: %i\n", quantity);
|
||||
printf("\tVendor:\n");
|
||||
|
||||
return (vendor_name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Shows a vendor record. Each vendor record is an instance of
|
||||
* a vendor structure. See load_vendor_database() in
|
||||
* example_database_load for how this structure was originally
|
||||
* put into the database.
|
||||
*/
|
||||
int
|
||||
show_vendor_record(char *vendor_name, DB *vendor_dbp)
|
||||
{
|
||||
DBT key, data;
|
||||
VENDOR my_vendor;
|
||||
int ret;
|
||||
|
||||
/* Zero our DBTs */
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
memset(&data, 0, sizeof(DBT));
|
||||
|
||||
/* Set the search key to the vendor's name */
|
||||
key.data = vendor_name;
|
||||
key.size = (u_int32_t)strlen(vendor_name) + 1;
|
||||
|
||||
/*
|
||||
* Make sure we use the memory we set aside for the VENDOR
|
||||
* structure rather than the memory that DB allocates.
|
||||
* Some systems may require structures to be aligned in memory
|
||||
* in a specific way, and DB may not get it right.
|
||||
*/
|
||||
|
||||
data.data = &my_vendor;
|
||||
data.ulen = sizeof(VENDOR);
|
||||
data.flags = DB_DBT_USERMEM;
|
||||
|
||||
/* Get the record */
|
||||
ret = vendor_dbp->get(vendor_dbp, NULL, &key, &data, 0);
|
||||
if (ret != 0) {
|
||||
vendor_dbp->err(vendor_dbp, ret, "Error searching for vendor: '%s'",
|
||||
vendor_name);
|
||||
return (ret);
|
||||
} else {
|
||||
printf("\t\t%s\n", my_vendor.name);
|
||||
printf("\t\t%s\n", my_vendor.street);
|
||||
printf("\t\t%s, %s\n", my_vendor.city, my_vendor.state);
|
||||
printf("\t\t%s\n\n", my_vendor.zipcode);
|
||||
printf("\t\t%s\n\n", my_vendor.phone_number);
|
||||
printf("\t\tContact: %s\n", my_vendor.sales_rep);
|
||||
printf("\t\t%s\n", my_vendor.sales_rep_phone);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
239
examples_c/getting_started/gettingstarted_common.c
Normal file
239
examples_c/getting_started/gettingstarted_common.c
Normal file
@@ -0,0 +1,239 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2004,2008 Oracle. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "gettingstarted_common.h"
|
||||
|
||||
int get_item_name(DB *, const DBT *, const DBT *, DBT *);
|
||||
|
||||
/*
|
||||
* Used to extract an inventory item's name from an
|
||||
* inventory database record. This function is used to create
|
||||
* keys for secondary database records.
|
||||
*/
|
||||
int
|
||||
get_item_name(DB *dbp, const DBT *pkey, const DBT *pdata, DBT *skey)
|
||||
{
|
||||
u_int offset;
|
||||
|
||||
dbp = NULL; /* Not needed, unused. */
|
||||
pkey = NULL;
|
||||
|
||||
/*
|
||||
* First, obtain the buffer location where we placed the
|
||||
* item's name. In this example, the item's name is located
|
||||
* in the primary data. It is the first string in the
|
||||
* buffer after the price (a float) and the quantity (an int).
|
||||
*
|
||||
* See load_inventory_database() in example_database_load.c
|
||||
* for how we packed the inventory information into the
|
||||
* data DBT.
|
||||
*/
|
||||
offset = sizeof(float) + sizeof(int);
|
||||
|
||||
/* Check to make sure there's data */
|
||||
if (pdata->size < offset)
|
||||
return (-1); /* Returning non-zero means that the
|
||||
* secondary record is not created/updated.
|
||||
*/
|
||||
|
||||
/* Now set the secondary key's data to be the item name */
|
||||
memset(skey, 0, sizeof(DBT));
|
||||
skey->data = (u_int8_t *)pdata->data + offset;
|
||||
skey->size = (u_int32_t)strlen(skey->data) + 1;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Opens a database */
|
||||
int
|
||||
open_database(DB **dbpp, const char *file_name,
|
||||
const char *program_name, FILE *error_file_pointer,
|
||||
int is_secondary)
|
||||
{
|
||||
DB *dbp; /* For convenience */
|
||||
u_int32_t open_flags;
|
||||
int ret;
|
||||
|
||||
/* Initialize the DB handle */
|
||||
ret = db_create(&dbp, NULL, 0);
|
||||
if (ret != 0) {
|
||||
fprintf(error_file_pointer, "%s: %s\n", program_name,
|
||||
db_strerror(ret));
|
||||
return (ret);
|
||||
}
|
||||
/* Point to the memory malloc'd by db_create() */
|
||||
*dbpp = dbp;
|
||||
|
||||
/* Set up error handling for this database */
|
||||
dbp->set_errfile(dbp, error_file_pointer);
|
||||
dbp->set_errpfx(dbp, program_name);
|
||||
|
||||
/*
|
||||
* If this is a secondary database, then we want to allow
|
||||
* sorted duplicates.
|
||||
*/
|
||||
if (is_secondary) {
|
||||
ret = dbp->set_flags(dbp, DB_DUPSORT);
|
||||
if (ret != 0) {
|
||||
dbp->err(dbp, ret, "Attempt to set DUPSORT flags failed.",
|
||||
file_name);
|
||||
return (ret);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the open flags */
|
||||
open_flags = DB_CREATE; /* Allow database creation */
|
||||
|
||||
/* Now open the database */
|
||||
ret = dbp->open(dbp, /* Pointer to the database */
|
||||
NULL, /* Txn pointer */
|
||||
file_name, /* File name */
|
||||
NULL, /* Logical db name */
|
||||
DB_BTREE, /* Database type (using btree) */
|
||||
open_flags, /* Open flags */
|
||||
0); /* File mode. Using defaults */
|
||||
if (ret != 0) {
|
||||
dbp->err(dbp, ret, "Database '%s' open failed.", file_name);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* opens all databases */
|
||||
int
|
||||
databases_setup(STOCK_DBS *my_stock, const char *program_name,
|
||||
FILE *error_file_pointer)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Open the vendor database */
|
||||
ret = open_database(&(my_stock->vendor_dbp),
|
||||
my_stock->vendor_db_name,
|
||||
program_name, error_file_pointer,
|
||||
PRIMARY_DB);
|
||||
if (ret != 0)
|
||||
/*
|
||||
* Error reporting is handled in open_database() so just return
|
||||
* the return code.
|
||||
*/
|
||||
return (ret);
|
||||
|
||||
/* Open the inventory database */
|
||||
ret = open_database(&(my_stock->inventory_dbp),
|
||||
my_stock->inventory_db_name,
|
||||
program_name, error_file_pointer,
|
||||
PRIMARY_DB);
|
||||
if (ret != 0)
|
||||
/*
|
||||
* Error reporting is handled in open_database() so just return
|
||||
* the return code.
|
||||
*/
|
||||
return (ret);
|
||||
|
||||
/*
|
||||
* Open the itemname secondary database. This is used to
|
||||
* index the product names found in the inventory
|
||||
* database.
|
||||
*/
|
||||
ret = open_database(&(my_stock->itemname_sdbp),
|
||||
my_stock->itemname_db_name,
|
||||
program_name, error_file_pointer,
|
||||
SECONDARY_DB);
|
||||
if (ret != 0)
|
||||
/*
|
||||
* Error reporting is handled in open_database() so just return
|
||||
* the return code.
|
||||
*/
|
||||
return (0);
|
||||
|
||||
/*
|
||||
* Associate the itemname db with its primary db
|
||||
* (inventory db).
|
||||
*/
|
||||
my_stock->inventory_dbp->associate(
|
||||
my_stock->inventory_dbp, /* Primary db */
|
||||
NULL, /* txn id */
|
||||
my_stock->itemname_sdbp, /* Secondary db */
|
||||
get_item_name, /* Secondary key creator */
|
||||
0); /* Flags */
|
||||
|
||||
printf("databases opened successfully\n");
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Initializes the STOCK_DBS struct.*/
|
||||
void
|
||||
initialize_stockdbs(STOCK_DBS *my_stock)
|
||||
{
|
||||
my_stock->db_home_dir = DEFAULT_HOMEDIR;
|
||||
my_stock->inventory_dbp = NULL;
|
||||
my_stock->vendor_dbp = NULL;
|
||||
my_stock->itemname_sdbp = NULL;
|
||||
my_stock->vendor_db_name = NULL;
|
||||
my_stock->inventory_db_name = NULL;
|
||||
my_stock->itemname_db_name = NULL;
|
||||
}
|
||||
|
||||
/* Identify all the files that will hold our databases. */
|
||||
void
|
||||
set_db_filenames(STOCK_DBS *my_stock)
|
||||
{
|
||||
size_t size;
|
||||
|
||||
/* Create the Inventory DB file name */
|
||||
size = strlen(my_stock->db_home_dir) + strlen(INVENTORYDB) + 1;
|
||||
my_stock->inventory_db_name = malloc(size);
|
||||
snprintf(my_stock->inventory_db_name, size, "%s%s",
|
||||
my_stock->db_home_dir, INVENTORYDB);
|
||||
|
||||
/* Create the Vendor DB file name */
|
||||
size = strlen(my_stock->db_home_dir) + strlen(VENDORDB) + 1;
|
||||
my_stock->vendor_db_name = malloc(size);
|
||||
snprintf(my_stock->vendor_db_name, size, "%s%s",
|
||||
my_stock->db_home_dir, VENDORDB);
|
||||
|
||||
/* Create the itemname DB file name */
|
||||
size = strlen(my_stock->db_home_dir) + strlen(ITEMNAMEDB) + 1;
|
||||
my_stock->itemname_db_name = malloc(size);
|
||||
snprintf(my_stock->itemname_db_name, size, "%s%s",
|
||||
my_stock->db_home_dir, ITEMNAMEDB);
|
||||
|
||||
}
|
||||
|
||||
/* Closes all the databases and secondary databases. */
|
||||
int
|
||||
databases_close(STOCK_DBS *my_stock)
|
||||
{
|
||||
int ret;
|
||||
/*
|
||||
* Note that closing a database automatically flushes its cached data
|
||||
* to disk, so no sync is required here.
|
||||
*/
|
||||
if (my_stock->itemname_sdbp != NULL) {
|
||||
ret = my_stock->itemname_sdbp->close(my_stock->itemname_sdbp, 0);
|
||||
if (ret != 0)
|
||||
fprintf(stderr, "Itemname database close failed: %s\n",
|
||||
db_strerror(ret));
|
||||
}
|
||||
|
||||
if (my_stock->inventory_dbp != NULL) {
|
||||
ret = my_stock->inventory_dbp->close(my_stock->inventory_dbp, 0);
|
||||
if (ret != 0)
|
||||
fprintf(stderr, "Inventory database close failed: %s\n",
|
||||
db_strerror(ret));
|
||||
}
|
||||
|
||||
if (my_stock->vendor_dbp != NULL) {
|
||||
ret = my_stock->vendor_dbp->close(my_stock->vendor_dbp, 0);
|
||||
if (ret != 0)
|
||||
fprintf(stderr, "Vendor database close failed: %s\n",
|
||||
db_strerror(ret));
|
||||
}
|
||||
|
||||
printf("databases closed.\n");
|
||||
return (0);
|
||||
}
|
||||
59
examples_c/getting_started/gettingstarted_common.h
Normal file
59
examples_c/getting_started/gettingstarted_common.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 2004,2008 Oracle. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <db.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
extern int getopt(int, char * const *, const char *);
|
||||
extern char *optarg;
|
||||
#define snprintf _snprintf
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#define DEFAULT_HOMEDIR "./"
|
||||
#define INVENTORY_FILE "inventory.txt"
|
||||
#define VENDORS_FILE "vendors.txt"
|
||||
#define INVENTORYDB "inventoryDB.db"
|
||||
#define ITEMNAMEDB "itemnameDB.db"
|
||||
#define MAXDATABUF 1024
|
||||
#define MAXFIELD 20
|
||||
#define MAXLINE 150
|
||||
#define PRIMARY_DB 0
|
||||
#define SECONDARY_DB 1
|
||||
#define VENDORDB "vendorDB.db"
|
||||
|
||||
typedef struct stock_dbs {
|
||||
DB *inventory_dbp; /* Database containing inventory information */
|
||||
DB *vendor_dbp; /* Database containing vendor information */
|
||||
DB *itemname_sdbp; /* Index based on the item name index */
|
||||
|
||||
char *db_home_dir; /* Directory containing the database files */
|
||||
char *itemname_db_name; /* Itemname secondary database */
|
||||
char *inventory_db_name; /* Name of the inventory database */
|
||||
char *vendor_db_name; /* Name of the vendor database */
|
||||
} STOCK_DBS;
|
||||
|
||||
typedef struct vendor {
|
||||
char name[MAXFIELD]; /* Vendor name */
|
||||
char street[MAXFIELD]; /* Street name and number */
|
||||
char city[MAXFIELD]; /* City */
|
||||
char state[3]; /* Two-digit US state code */
|
||||
char zipcode[6]; /* US zipcode */
|
||||
char phone_number[13]; /* Vendor phone number */
|
||||
char sales_rep[MAXFIELD]; /* Name of sales representative */
|
||||
char sales_rep_phone[MAXFIELD]; /* Sales rep's phone number */
|
||||
} VENDOR;
|
||||
|
||||
/* Function prototypes */
|
||||
int databases_close(STOCK_DBS *);
|
||||
int databases_setup(STOCK_DBS *, const char *, FILE *);
|
||||
void initialize_stockdbs(STOCK_DBS *);
|
||||
int open_database(DB **, const char *, const char *, FILE *, int);
|
||||
void set_db_filenames(STOCK_DBS *my_stock);
|
||||
800
examples_c/getting_started/inventory.txt
Normal file
800
examples_c/getting_started/inventory.txt
Normal file
@@ -0,0 +1,800 @@
|
||||
Oranges#OranfruiRu6Ghr#0.71#451#fruits#TriCounty Produce
|
||||
Oranges#OranfruiXRPFn1#0.73#263#fruits#Simply Fresh
|
||||
Oranges#OranfruiLEuzQj#0.69#261#fruits#Off the Vine
|
||||
Apples#ApplfruiZls4Du#1.20#472#fruits#TriCounty Produce
|
||||
Apples#Applfrui8fewZe#1.21#402#fruits#Simply Fresh
|
||||
Apples#ApplfruiXoT6xG#1.20#728#fruits#Off the Vine
|
||||
Bananas#BanafruipIlluX#0.50#207#fruits#TriCounty Produce
|
||||
Bananas#BanafruiEQhWuj#0.50#518#fruits#Simply Fresh
|
||||
Bananas#BanafruimpRgPO#0.50#741#fruits#Off the Vine
|
||||
Almonds#AlmofruiPPCLz8#0.55#600#fruits#TriCounty Produce
|
||||
Almonds#AlmofruidMyKmp#0.54#745#fruits#Simply Fresh
|
||||
Almonds#Almofrui7K0xzH#0.53#405#fruits#Off the Vine
|
||||
Allspice#AllsfruibJGK4R#0.94#669#fruits#TriCounty Produce
|
||||
Allspice#Allsfruilfvoeg#0.94#244#fruits#Simply Fresh
|
||||
Allspice#Allsfruio12BOS#0.95#739#fruits#Off the Vine
|
||||
Apricot#AprifruijphEpM#0.89#560#fruits#TriCounty Produce
|
||||
Apricot#AprifruiU1zIDn#0.91#980#fruits#Simply Fresh
|
||||
Apricot#AprifruichcwYS#0.95#668#fruits#Off the Vine
|
||||
Avocado#AvocfruiwYYomu#0.99#379#fruits#TriCounty Produce
|
||||
Avocado#AvocfruiT6IwWE#1.02#711#fruits#Simply Fresh
|
||||
Avocado#AvocfruisbK1h5#0.97#856#fruits#Off the Vine
|
||||
Bael Fruit#BaelfruilAU7Hj#0.41#833#fruits#TriCounty Produce
|
||||
Bael Fruit#BaelfruiX2KvqV#0.40#770#fruits#Simply Fresh
|
||||
Bael Fruit#Baelfruidjne4e#0.39#778#fruits#Off the Vine
|
||||
Betel Nut#BetefruiQYdHqQ#0.34#926#fruits#TriCounty Produce
|
||||
Betel Nut#Betefrui32BKAz#0.37#523#fruits#Simply Fresh
|
||||
Betel Nut#BetefruisaWzY4#0.34#510#fruits#Off the Vine
|
||||
Black Walnut#BlacfruiXxIuMU#0.57#923#fruits#TriCounty Produce
|
||||
Black Walnut#BlacfruiZXgY9t#0.59#312#fruits#Simply Fresh
|
||||
Black Walnut#BlacfruikWO0vz#0.60#877#fruits#Off the Vine
|
||||
Blueberry#BluefruiCbxb4t#1.02#276#fruits#TriCounty Produce
|
||||
Blueberry#BluefruiBuCfgO#1.03#522#fruits#Simply Fresh
|
||||
Blueberry#Bluefruixz8MkE#1.01#278#fruits#Off the Vine
|
||||
Boysenberry#BoysfruizxyMuz#1.05#239#fruits#TriCounty Produce
|
||||
Boysenberry#Boysfrui3hTRQu#1.09#628#fruits#Simply Fresh
|
||||
Boysenberry#BoysfruinpLvr3#1.02#349#fruits#Off the Vine
|
||||
Breadnut#Breafrui0kDPs6#0.31#558#fruits#TriCounty Produce
|
||||
Breadnut#Breafrui44s3og#0.32#879#fruits#Simply Fresh
|
||||
Breadnut#BreafruiwyLKhJ#0.30#407#fruits#Off the Vine
|
||||
Cactus#Cactfruiyo2ddH#0.56#601#fruits#TriCounty Produce
|
||||
Cactus#CactfruixTOLv5#0.54#477#fruits#Simply Fresh
|
||||
Cactus#Cactfrui4ioUav#0.55#896#fruits#Off the Vine
|
||||
California Wild Grape#CalifruiZsWAa6#0.78#693#fruits#TriCounty Produce
|
||||
California Wild Grape#Califruid84xyt#0.83#293#fruits#Simply Fresh
|
||||
California Wild Grape#CalifruiLSJFoJ#0.81#543#fruits#Off the Vine
|
||||
Cashew#CashfruihaOFVP#0.37#221#fruits#TriCounty Produce
|
||||
Cashew#Cashfruizzcw1E#0.38#825#fruits#Simply Fresh
|
||||
Cashew#CashfruiqtMe2Q#0.38#515#fruits#Off the Vine
|
||||
Chico Sapote#ChicfruiY534SX#0.47#216#fruits#TriCounty Produce
|
||||
Chico Sapote#ChicfruiSqL3Lc#0.45#476#fruits#Simply Fresh
|
||||
Chico Sapote#ChicfruiurzIp4#0.47#200#fruits#Off the Vine
|
||||
Chinese Jello#ChinfruiyRg75u#0.64#772#fruits#TriCounty Produce
|
||||
Chinese Jello#ChinfruiuIUj0X#0.65#624#fruits#Simply Fresh
|
||||
Chinese Jello#ChinfruiwXbRrL#0.67#719#fruits#Off the Vine
|
||||
Common Guava#Commfruib6znSI#0.80#483#fruits#TriCounty Produce
|
||||
Common Guava#Commfrui6eUivL#0.81#688#fruits#Simply Fresh
|
||||
Common Guava#CommfruibWKnz3#0.84#581#fruits#Off the Vine
|
||||
Crabapple#CrabfruioY2L63#0.94#582#fruits#TriCounty Produce
|
||||
Crabapple#Crabfruijxcxyt#0.94#278#fruits#Simply Fresh
|
||||
Crabapple#CrabfruibvWd8K#0.95#213#fruits#Off the Vine
|
||||
Cranberry#CranfruiJxmKr5#0.83#923#fruits#TriCounty Produce
|
||||
Cranberry#CranfruiPlklAF#0.84#434#fruits#Simply Fresh
|
||||
Cranberry#Cranfrui3G5XL9#0.84#880#fruits#Off the Vine
|
||||
Damson Plum#DamsfruibMRMwe#0.98#782#fruits#TriCounty Produce
|
||||
Damson Plum#DamsfruiV6wFLk#1.03#400#fruits#Simply Fresh
|
||||
Damson Plum#DamsfruiLhqFrQ#0.98#489#fruits#Off the Vine
|
||||
Date Palm#DatefruigS31GU#1.14#315#fruits#TriCounty Produce
|
||||
Date Palm#DatefruipKPaJK#1.09#588#fruits#Simply Fresh
|
||||
Date Palm#Datefrui5fTyNS#1.14#539#fruits#Off the Vine
|
||||
Dragon's Eye#DragfruirGJ3aI#0.28#315#fruits#TriCounty Produce
|
||||
Dragon's Eye#DragfruiBotxqt#0.27#705#fruits#Simply Fresh
|
||||
Dragon's Eye#DragfruiPsSnV9#0.29#482#fruits#Off the Vine
|
||||
East Indian Wine Palm#EastfruiNXFJuG#0.43#992#fruits#TriCounty Produce
|
||||
East Indian Wine Palm#Eastfruiq06fRr#0.40#990#fruits#Simply Fresh
|
||||
East Indian Wine Palm#Eastfrui4QUwl2#0.43#351#fruits#Off the Vine
|
||||
English Walnut#EnglfruiBMtHtW#1.04#787#fruits#TriCounty Produce
|
||||
English Walnut#EnglfruiHmVzxV#1.03#779#fruits#Simply Fresh
|
||||
English Walnut#Englfrui18Tc9n#1.06#339#fruits#Off the Vine
|
||||
False Mangosteen#FalsfruibkmYqH#0.66#971#fruits#TriCounty Produce
|
||||
False Mangosteen#FalsfruipBsbcX#0.68#250#fruits#Simply Fresh
|
||||
False Mangosteen#FalsfruiPrFfhe#0.70#386#fruits#Off the Vine
|
||||
Fried Egg Tree#FriefruiihHUdc#0.29#649#fruits#TriCounty Produce
|
||||
Fried Egg Tree#FriefruimdD1rf#0.28#527#fruits#Simply Fresh
|
||||
Fried Egg Tree#FriefruivyAzYq#0.29#332#fruits#Off the Vine
|
||||
Genipap#GenifruiDtKusQ#0.62#986#fruits#TriCounty Produce
|
||||
Genipap#GenifruiXq32eP#0.61#326#fruits#Simply Fresh
|
||||
Genipap#Genifruiphwwyq#0.61#794#fruits#Off the Vine
|
||||
Ginger#GingfruiQLbRZI#0.28#841#fruits#TriCounty Produce
|
||||
Ginger#GingfruiS8kK4p#0.29#432#fruits#Simply Fresh
|
||||
Ginger#GingfruioL3Y4S#0.27#928#fruits#Off the Vine
|
||||
Grapefruit#Grapfruih86Zxh#1.07#473#fruits#TriCounty Produce
|
||||
Grapefruit#GrapfruiwL1v0N#1.08#878#fruits#Simply Fresh
|
||||
Grapefruit#GrapfruihmJzWm#1.02#466#fruits#Off the Vine
|
||||
Hackberry#HackfruiQjomN7#0.22#938#fruits#TriCounty Produce
|
||||
Hackberry#HackfruiWS0eKp#0.20#780#fruits#Simply Fresh
|
||||
Hackberry#Hackfrui0MIv6J#0.21#345#fruits#Off the Vine
|
||||
Honey Locust#HonefruiebXGRc#1.08#298#fruits#TriCounty Produce
|
||||
Honey Locust#HonefruiPSqILB#1.00#427#fruits#Simply Fresh
|
||||
Honey Locust#Honefrui6UXtvW#1.03#422#fruits#Off the Vine
|
||||
Japanese Plum#JapafruihTmoYR#0.40#658#fruits#TriCounty Produce
|
||||
Japanese Plum#JapafruifGqz0l#0.40#700#fruits#Simply Fresh
|
||||
Japanese Plum#JapafruiufWkLx#0.39#790#fruits#Off the Vine
|
||||
Jojoba#JojofruisE0wTh#0.97#553#fruits#TriCounty Produce
|
||||
Jojoba#JojofruiwiYLp2#1.02#969#fruits#Simply Fresh
|
||||
Jojoba#JojofruigMD1ej#0.96#899#fruits#Off the Vine
|
||||
Jostaberry#JostfruiglsEGV#0.50#300#fruits#TriCounty Produce
|
||||
Jostaberry#JostfruiV3oo1h#0.52#423#fruits#Simply Fresh
|
||||
Jostaberry#JostfruiUBerur#0.53#562#fruits#Off the Vine
|
||||
Kangaroo Apple#KangfruiEQknz8#0.60#661#fruits#TriCounty Produce
|
||||
Kangaroo Apple#KangfruiNabdFq#0.60#377#fruits#Simply Fresh
|
||||
Kangaroo Apple#Kangfrui7hky1i#0.60#326#fruits#Off the Vine
|
||||
Ken's Red#Ken'fruinPUSIm#0.21#337#fruits#TriCounty Produce
|
||||
Ken's Red#Ken'fruiAoZlpl#0.21#902#fruits#Simply Fresh
|
||||
Ken's Red#Ken'frui5rmbd4#0.22#972#fruits#Off the Vine
|
||||
Ketembilla#Ketefrui3yAKxQ#0.31#303#fruits#TriCounty Produce
|
||||
Ketembilla#KetefruiROn6F5#0.34#283#fruits#Simply Fresh
|
||||
Ketembilla#Ketefrui16Rsts#0.33#887#fruits#Off the Vine
|
||||
King Orange#KingfruisOFzWk#0.74#429#fruits#TriCounty Produce
|
||||
King Orange#KingfruiBmzRJT#0.74#500#fruits#Simply Fresh
|
||||
King Orange#KingfruiGsrgRX#0.78#994#fruits#Off the Vine
|
||||
Kola Nut#KolafruiBbtAuw#0.58#991#fruits#TriCounty Produce
|
||||
Kola Nut#KolafruirbnLVS#0.62#733#fruits#Simply Fresh
|
||||
Kola Nut#Kolafrui1ItXJx#0.58#273#fruits#Off the Vine
|
||||
Kuko#Kukofrui6YH5Ds#0.41#647#fruits#TriCounty Produce
|
||||
Kuko#Kukofrui7WZaZK#0.39#241#fruits#Simply Fresh
|
||||
Kuko#Kukofruig9MQFT#0.40#204#fruits#Off the Vine
|
||||
Kumquat#KumqfruiT6WKQL#0.73#388#fruits#TriCounty Produce
|
||||
Kumquat#KumqfruidLiFLU#0.70#393#fruits#Simply Fresh
|
||||
Kumquat#KumqfruiL6zhQX#0.71#994#fruits#Off the Vine
|
||||
Kwai Muk#KwaifruiQK1zOE#1.10#249#fruits#TriCounty Produce
|
||||
Kwai Muk#KwaifruifbCRlT#1.14#657#fruits#Simply Fresh
|
||||
Kwai Muk#Kwaifruipe7T2m#1.09#617#fruits#Off the Vine
|
||||
Lanzone#LanzfruijsPf1v#0.34#835#fruits#TriCounty Produce
|
||||
Lanzone#LanzfruibU3QoL#0.34#404#fruits#Simply Fresh
|
||||
Lanzone#LanzfruiYgHwv6#0.34#237#fruits#Off the Vine
|
||||
Lemon#Lemofrui4Tgsg2#0.46#843#fruits#TriCounty Produce
|
||||
Lemon#LemofruivK6qvj#0.43#207#fruits#Simply Fresh
|
||||
Lemon#LemofruiXSXqJ0#0.44#910#fruits#Off the Vine
|
||||
Lemon Grass#LemofruiVFgVh5#0.40#575#fruits#TriCounty Produce
|
||||
Lemon Grass#LemofruiWIelvi#0.41#386#fruits#Simply Fresh
|
||||
Lemon Grass#LemofruiGVAow0#0.39#918#fruits#Off the Vine
|
||||
Lilly-pilly#LillfruiEQnW1m#1.21#974#fruits#TriCounty Produce
|
||||
Lilly-pilly#LillfruiMqVuR5#1.23#303#fruits#Simply Fresh
|
||||
Lilly-pilly#LillfruiVGH9p4#1.17#512#fruits#Off the Vine
|
||||
Ling Nut#LingfruiGtOf8X#0.85#540#fruits#TriCounty Produce
|
||||
Ling Nut#LingfruiuP0Jf9#0.83#200#fruits#Simply Fresh
|
||||
Ling Nut#LingfruiuO5qf5#0.81#319#fruits#Off the Vine
|
||||
Lipote#LipofruisxD2Qc#0.85#249#fruits#TriCounty Produce
|
||||
Lipote#LipofruiHNdIqL#0.85#579#fruits#Simply Fresh
|
||||
Lipote#LipofruiSQ2pKK#0.83#472#fruits#Off the Vine
|
||||
Litchee#Litcfrui1R6Ydz#0.99#806#fruits#TriCounty Produce
|
||||
Litchee#LitcfruiwtDM79#1.01#219#fruits#Simply Fresh
|
||||
Litchee#LitcfruilpPZbC#1.05#419#fruits#Off the Vine
|
||||
Longan#LongfruiEI0lWF#1.02#573#fruits#TriCounty Produce
|
||||
Longan#LongfruiPQxxSF#1.04#227#fruits#Simply Fresh
|
||||
Longan#LongfruisdI812#0.99#993#fruits#Off the Vine
|
||||
Love-in-a-mist#LovefruiKYPW70#0.69#388#fruits#TriCounty Produce
|
||||
Love-in-a-mist#LovefruiHrgjDa#0.67#478#fruits#Simply Fresh
|
||||
Love-in-a-mist#LovefruipSOWVz#0.71#748#fruits#Off the Vine
|
||||
Lychee#LychfruiicVLnY#0.38#276#fruits#TriCounty Produce
|
||||
Lychee#LychfruiGY6yJr#0.38#602#fruits#Simply Fresh
|
||||
Lychee#LychfruiTzDCq2#0.40#572#fruits#Off the Vine
|
||||
Mabolo#MabofruiSY8RQS#0.97#263#fruits#TriCounty Produce
|
||||
Mabolo#MabofruiOWWk0n#0.98#729#fruits#Simply Fresh
|
||||
Mabolo#MabofruixQLOTF#0.98#771#fruits#Off the Vine
|
||||
Macadamia Nut#MacafruiZppJPw#1.22#888#fruits#TriCounty Produce
|
||||
Macadamia Nut#MacafruiI7XFMV#1.24#484#fruits#Simply Fresh
|
||||
Macadamia Nut#Macafrui4x8bxV#1.20#536#fruits#Off the Vine
|
||||
Madagascar Plum#MadafruiVj5fDf#1.14#596#fruits#TriCounty Produce
|
||||
Madagascar Plum#MadafruivJhAFI#1.15#807#fruits#Simply Fresh
|
||||
Madagascar Plum#Madafrui7MTe1x#1.17#355#fruits#Off the Vine
|
||||
Magnolia Vine#MagnfruiigN4Y1#1.17#321#fruits#TriCounty Produce
|
||||
Magnolia Vine#MagnfruicKtiHd#1.15#353#fruits#Simply Fresh
|
||||
Magnolia Vine#MagnfruiLPDSCp#1.23#324#fruits#Off the Vine
|
||||
Mamey#Mamefrui5rjLF6#0.36#683#fruits#TriCounty Produce
|
||||
Mamey#MamefruiM6ndnR#0.38#404#fruits#Simply Fresh
|
||||
Mamey#Mamefruiq9KntD#0.36#527#fruits#Off the Vine
|
||||
Mandarin Orange#MandfruiRKpmKL#0.42#352#fruits#TriCounty Produce
|
||||
Mandarin Orange#Mandfrui1V0KLG#0.42#548#fruits#Simply Fresh
|
||||
Mandarin Orange#Mandfruig2o9Fg#0.41#686#fruits#Off the Vine
|
||||
Marany Nut#MarafruiqkrwoJ#1.14#273#fruits#TriCounty Produce
|
||||
Marany Nut#MarafruiCGKpke#1.12#482#fruits#Simply Fresh
|
||||
Marany Nut#MarafruiB1YE5x#1.09#412#fruits#Off the Vine
|
||||
Marula#MarufruiXF4biH#0.22#403#fruits#TriCounty Produce
|
||||
Marula#MarufruidZiVKZ#0.23#317#fruits#Simply Fresh
|
||||
Marula#MarufruiIS8BEp#0.21#454#fruits#Off the Vine
|
||||
Mayhaw#MayhfruiCSrm7k#0.24#220#fruits#TriCounty Produce
|
||||
Mayhaw#MayhfruiNRDzWs#0.25#710#fruits#Simply Fresh
|
||||
Mayhaw#MayhfruiIUCyEg#0.24#818#fruits#Off the Vine
|
||||
Meiwa Kumquat#MeiwfruiYhv3AY#0.21#997#fruits#TriCounty Produce
|
||||
Meiwa Kumquat#MeiwfruiyzQFNR#0.22#347#fruits#Simply Fresh
|
||||
Meiwa Kumquat#Meiwfruict4OUp#0.21#923#fruits#Off the Vine
|
||||
Mexican Barberry#Mexifrui2P2dXi#0.28#914#fruits#TriCounty Produce
|
||||
Mexican Barberry#MexifruiywUTMI#0.29#782#fruits#Simply Fresh
|
||||
Mexican Barberry#MexifruijPHu5X#0.29#367#fruits#Off the Vine
|
||||
Meyer Lemon#Meyefruin9901J#0.38#824#fruits#TriCounty Produce
|
||||
Meyer Lemon#MeyefruiNeQpjO#0.37#617#fruits#Simply Fresh
|
||||
Meyer Lemon#MeyefruiYEVznZ#0.37#741#fruits#Off the Vine
|
||||
Mississippi Honeyberry#Missfruipb5iW3#0.95#595#fruits#TriCounty Produce
|
||||
Mississippi Honeyberry#MissfruiINiDbB#0.96#551#fruits#Simply Fresh
|
||||
Mississippi Honeyberry#MissfruiNUQ82a#0.93#396#fruits#Off the Vine
|
||||
Monkey Pot#MonkfruiXlTW4j#0.90#896#fruits#TriCounty Produce
|
||||
Monkey Pot#Monkfrui1p7a4h#0.88#344#fruits#Simply Fresh
|
||||
Monkey Pot#Monkfrui4eKggb#0.92#917#fruits#Off the Vine
|
||||
Monos Plum#Monofrui0Mv9aV#1.11#842#fruits#TriCounty Produce
|
||||
Monos Plum#Monofrui6iTGQY#1.14#570#fruits#Simply Fresh
|
||||
Monos Plum#MonofruiNu2uGH#1.13#978#fruits#Off the Vine
|
||||
Moosewood#MoosfruiMXEGex#0.86#969#fruits#TriCounty Produce
|
||||
Moosewood#Moosfrui8805mB#0.86#963#fruits#Simply Fresh
|
||||
Moosewood#MoosfruiOsnDFL#0.88#594#fruits#Off the Vine
|
||||
Natal Orange#NatafruitB8Kh2#0.42#332#fruits#TriCounty Produce
|
||||
Natal Orange#NatafruiOhqRrd#0.42#982#fruits#Simply Fresh
|
||||
Natal Orange#NatafruiRObMf6#0.41#268#fruits#Off the Vine
|
||||
Nectarine#NectfruilNfeD8#0.36#601#fruits#TriCounty Produce
|
||||
Nectarine#NectfruiQfjt6b#0.35#818#fruits#Simply Fresh
|
||||
Nectarine#Nectfrui5U7U96#0.37#930#fruits#Off the Vine
|
||||
Neem Tree#NeemfruiCruEMF#0.24#222#fruits#TriCounty Produce
|
||||
Neem Tree#NeemfruiGv0pv5#0.24#645#fruits#Simply Fresh
|
||||
Neem Tree#NeemfruiUFPVfk#0.25#601#fruits#Off the Vine
|
||||
New Zealand Spinach#New fruihDIgec#0.87#428#fruits#TriCounty Produce
|
||||
New Zealand Spinach#New fruiaoR9TP#0.87#630#fruits#Simply Fresh
|
||||
New Zealand Spinach#New fruiy8LBul#0.94#570#fruits#Off the Vine
|
||||
Olosapo#OlosfruiGXvaMm#0.76#388#fruits#TriCounty Produce
|
||||
Olosapo#OlosfruiESlpB3#0.76#560#fruits#Simply Fresh
|
||||
Olosapo#OlosfruiFNEkER#0.76#962#fruits#Off the Vine
|
||||
Oregon Grape#OregfruiWxhzrf#1.14#892#fruits#TriCounty Produce
|
||||
Oregon Grape#OregfruiMgjHUn#1.20#959#fruits#Simply Fresh
|
||||
Oregon Grape#OregfruiC5UCxX#1.17#419#fruits#Off the Vine
|
||||
Otaheite Apple#OtahfruilT0iFj#0.21#579#fruits#TriCounty Produce
|
||||
Otaheite Apple#Otahfrui92PyMY#0.22#857#fruits#Simply Fresh
|
||||
Otaheite Apple#OtahfruiLGD1EH#0.20#807#fruits#Off the Vine
|
||||
Oyster Plant#OystfruimGxOsj#0.77#835#fruits#TriCounty Produce
|
||||
Oyster Plant#Oystfrui1kudBX#0.81#989#fruits#Simply Fresh
|
||||
Oyster Plant#OystfruiaX3uO2#0.80#505#fruits#Off the Vine
|
||||
Panama Berry#PanafruiZG0Vp4#1.19#288#fruits#TriCounty Produce
|
||||
Panama Berry#PanafruiobvXPE#1.21#541#fruits#Simply Fresh
|
||||
Panama Berry#PanafruipaW8F3#1.16#471#fruits#Off the Vine
|
||||
Peach Tomato#PeacfruiQpovYH#1.20#475#fruits#TriCounty Produce
|
||||
Peach Tomato#PeacfruixYXLTN#1.18#655#fruits#Simply Fresh
|
||||
Peach Tomato#PeacfruiILDYAp#1.23#876#fruits#Off the Vine
|
||||
Peanut#Peanfruiy8M7pt#0.69#275#fruits#TriCounty Produce
|
||||
Peanut#PeanfruiEimbED#0.65#307#fruits#Simply Fresh
|
||||
Peanut#Peanfruic452Vc#0.68#937#fruits#Off the Vine
|
||||
Peanut Butter Fruit#PeanfruixEDt9Y#0.27#628#fruits#TriCounty Produce
|
||||
Peanut Butter Fruit#PeanfruiST0T0R#0.27#910#fruits#Simply Fresh
|
||||
Peanut Butter Fruit#Peanfrui7jeRN2#0.27#938#fruits#Off the Vine
|
||||
Pear#PearfruiB5YmSJ#0.20#945#fruits#TriCounty Produce
|
||||
Pear#PearfruiA93XZx#0.21#333#fruits#Simply Fresh
|
||||
Pear#PearfruioNKiIf#0.21#715#fruits#Off the Vine
|
||||
Pecan#PecafruiiTIv1Z#0.26#471#fruits#TriCounty Produce
|
||||
Pecan#PecafruiMGkqla#0.26#889#fruits#Simply Fresh
|
||||
Pecan#Pecafrui1szYz2#0.25#929#fruits#Off the Vine
|
||||
Purple Passion Fruit#Purpfrui4mMGkD#1.04#914#fruits#TriCounty Produce
|
||||
Purple Passion Fruit#Purpfrui5XOW3K#1.06#423#fruits#Simply Fresh
|
||||
Purple Passion Fruit#PurpfruifDTAgW#1.05#549#fruits#Off the Vine
|
||||
Red Mulberry#Red fruiVLOXIW#1.24#270#fruits#TriCounty Produce
|
||||
Red Mulberry#Red fruiXNXt4a#1.21#836#fruits#Simply Fresh
|
||||
Red Mulberry#Red fruiUseWLG#1.21#795#fruits#Off the Vine
|
||||
Red Princess#Red fruigJLR4V#0.23#829#fruits#TriCounty Produce
|
||||
Red Princess#Red fruinVKps5#0.23#558#fruits#Simply Fresh
|
||||
Red Princess#Red frui0jl9mg#0.24#252#fruits#Off the Vine
|
||||
Striped Screw Pine#StrifruiUKzjoU#0.60#226#fruits#TriCounty Produce
|
||||
Striped Screw Pine#StrifruivWLDzH#0.64#685#fruits#Simply Fresh
|
||||
Striped Screw Pine#StrifruiiF7CGH#0.60#983#fruits#Off the Vine
|
||||
Tapioca#Tapifruib4LCqt#0.40#955#fruits#TriCounty Produce
|
||||
Tapioca#TapifruiwgQLj9#0.41#889#fruits#Simply Fresh
|
||||
Tapioca#TapifruiZ6Igg3#0.41#655#fruits#Off the Vine
|
||||
Tavola#Tavofrui0k9XOt#1.16#938#fruits#TriCounty Produce
|
||||
Tavola#Tavofrui8DuRxL#1.08#979#fruits#Simply Fresh
|
||||
Tavola#TavofruiNZEuJZ#1.16#215#fruits#Off the Vine
|
||||
Tea#TeafruiL0357s#1.11#516#fruits#TriCounty Produce
|
||||
Tea#TeafruiD5soTf#1.13#970#fruits#Simply Fresh
|
||||
Tea#TeafruiOWq4oO#1.19#357#fruits#Off the Vine
|
||||
Ugli Fruit#UglifruipKNCpf#0.24#501#fruits#TriCounty Produce
|
||||
Ugli Fruit#UglifruifbDrzc#0.24#642#fruits#Simply Fresh
|
||||
Ugli Fruit#Uglifruiwx8or4#0.24#280#fruits#Off the Vine
|
||||
Vegetable Brain#VegefruieXLBoc#0.73#355#fruits#TriCounty Produce
|
||||
Vegetable Brain#Vegefruik5FSdl#0.71#498#fruits#Simply Fresh
|
||||
Vegetable Brain#VegefruiKBfzN0#0.72#453#fruits#Off the Vine
|
||||
White Walnut#Whitfruit3oVHL#0.30#501#fruits#TriCounty Produce
|
||||
White Walnut#WhitfruiHygydw#0.30#913#fruits#Simply Fresh
|
||||
White Walnut#WhitfruieNtplo#0.30#401#fruits#Off the Vine
|
||||
Wood Apple#WoodfruijVPRqA#0.68#501#fruits#TriCounty Produce
|
||||
Wood Apple#Woodfrui4Zk69T#0.68#616#fruits#Simply Fresh
|
||||
Wood Apple#WoodfruiuSLHZK#0.70#474#fruits#Off the Vine
|
||||
Yellow Horn#Yellfrui5igjjf#1.18#729#fruits#TriCounty Produce
|
||||
Yellow Horn#Yellfrui0DiPqa#1.13#517#fruits#Simply Fresh
|
||||
Yellow Horn#Yellfrui0ljvqC#1.14#853#fruits#Off the Vine
|
||||
Yellow Sapote#YellfruilGmCfq#0.93#204#fruits#TriCounty Produce
|
||||
Yellow Sapote#Yellfrui4J2mke#0.88#269#fruits#Simply Fresh
|
||||
Yellow Sapote#Yellfrui6PuXaL#0.86#575#fruits#Off the Vine
|
||||
Ylang-ylang#Ylanfrui3rmByO#0.76#429#fruits#TriCounty Produce
|
||||
Ylang-ylang#YlanfruiA80Nkq#0.76#886#fruits#Simply Fresh
|
||||
Ylang-ylang#YlanfruinUEm5d#0.72#747#fruits#Off the Vine
|
||||
Zapote Blanco#ZapofruisZ5sMA#0.67#428#fruits#TriCounty Produce
|
||||
Zapote Blanco#ZapofruilKxl7N#0.65#924#fruits#Simply Fresh
|
||||
Zapote Blanco#ZapofruiAe6Eu1#0.68#255#fruits#Off the Vine
|
||||
Zulu Nut#Zulufrui469K4k#0.71#445#fruits#TriCounty Produce
|
||||
Zulu Nut#ZulufruiWbz6vU#0.71#653#fruits#Simply Fresh
|
||||
Zulu Nut#Zulufrui0LJnWK#0.71#858#fruits#Off the Vine
|
||||
Artichoke#ArtivegeIuqmS4#0.71#282#vegetables#The Pantry
|
||||
Artichoke#Artivegebljjnf#0.69#66#vegetables#TriCounty Produce
|
||||
Artichoke#ArtivegeTa2lcF#0.70#618#vegetables#Off the Vine
|
||||
Asparagus#AspavegezC0cDl#0.23#70#vegetables#The Pantry
|
||||
Asparagus#AspavegeM1q5Kt#0.24#546#vegetables#TriCounty Produce
|
||||
Asparagus#AspavegeXWbCb8#0.24#117#vegetables#Off the Vine
|
||||
Basil#Basivegev08fzf#0.31#213#vegetables#The Pantry
|
||||
Basil#BasivegeF3Uha7#0.29#651#vegetables#TriCounty Produce
|
||||
Basil#BasivegeqR8SHC#0.31#606#vegetables#Off the Vine
|
||||
Bean#BeanvegegCFUOp#0.27#794#vegetables#The Pantry
|
||||
Bean#BeanvegeqMSEVq#0.27#468#vegetables#TriCounty Produce
|
||||
Bean#Beanvege4IGUwX#0.27#463#vegetables#Off the Vine
|
||||
Beet#BeetvegedEv4Ic#0.35#120#vegetables#The Pantry
|
||||
Beet#Beetvegegi1bz1#0.35#540#vegetables#TriCounty Produce
|
||||
Beet#BeetvegemztZcN#0.36#386#vegetables#Off the Vine
|
||||
Blackeyed Pea#Blacvege3TPldr#0.86#133#vegetables#The Pantry
|
||||
Blackeyed Pea#Blacvege3Zqnep#0.88#67#vegetables#TriCounty Produce
|
||||
Blackeyed Pea#Blacvege3khffZ#0.90#790#vegetables#Off the Vine
|
||||
Cabbage#CabbvegeY0c4Fw#0.82#726#vegetables#The Pantry
|
||||
Cabbage#CabbvegeoaK7Co#0.85#439#vegetables#TriCounty Produce
|
||||
Cabbage#CabbvegeVvO646#0.82#490#vegetables#Off the Vine
|
||||
Carrot#CarrvegeEbI0sw#0.45#717#vegetables#The Pantry
|
||||
Carrot#CarrvegeEZndWL#0.49#284#vegetables#TriCounty Produce
|
||||
Carrot#CarrvegewUkHao#0.47#122#vegetables#Off the Vine
|
||||
Cauliflower#Caulvege1CPeNG#0.68#756#vegetables#The Pantry
|
||||
Cauliflower#CaulvegedrPqib#0.66#269#vegetables#TriCounty Produce
|
||||
Cauliflower#CaulvegeT6cka8#0.65#728#vegetables#Off the Vine
|
||||
Chayote#ChayvegePRReGE#0.14#233#vegetables#The Pantry
|
||||
Chayote#Chayvegep058f7#0.14#88#vegetables#TriCounty Produce
|
||||
Chayote#ChayvegeoxO40S#0.14#611#vegetables#Off the Vine
|
||||
Corn#CornvegeukXkv6#0.72#632#vegetables#The Pantry
|
||||
Corn#CornvegePnPREC#0.72#609#vegetables#TriCounty Produce
|
||||
Corn#CornvegeO0GwoQ#0.70#664#vegetables#Off the Vine
|
||||
Cucumber#CucuvegeEqQeA7#0.94#499#vegetables#The Pantry
|
||||
Cucumber#CucuvegewmKbJ1#0.94#738#vegetables#TriCounty Produce
|
||||
Cucumber#CucuvegeUW6JaA#0.94#565#vegetables#Off the Vine
|
||||
Cantaloupe#CantvegeIHs9vJ#0.66#411#vegetables#The Pantry
|
||||
Cantaloupe#CantvegeEaDdST#0.66#638#vegetables#TriCounty Produce
|
||||
Cantaloupe#CantvegewWQEa0#0.64#682#vegetables#Off the Vine
|
||||
Carraway#CarrvegewuL4Ma#0.32#740#vegetables#The Pantry
|
||||
Carraway#CarrvegeyiWfBj#0.32#265#vegetables#TriCounty Produce
|
||||
Carraway#CarrvegeMjb1i9#0.31#732#vegetables#Off the Vine
|
||||
Celeriac#CelevegeoTBicd#0.74#350#vegetables#The Pantry
|
||||
Celeriac#CelevegeCNABoZ#0.70#261#vegetables#TriCounty Produce
|
||||
Celeriac#Celevege9LUeww#0.70#298#vegetables#Off the Vine
|
||||
Celery#Celevegej40ZCc#0.59#740#vegetables#The Pantry
|
||||
Celery#CelevegerYlVRy#0.58#734#vegetables#TriCounty Produce
|
||||
Celery#Celevege67eimC#0.58#619#vegetables#Off the Vine
|
||||
Chervil#ChervegeuH4Dge#0.09#502#vegetables#The Pantry
|
||||
Chervil#Chervegea1OyKO#0.09#299#vegetables#TriCounty Produce
|
||||
Chervil#Chervegeq56gMO#0.09#474#vegetables#Off the Vine
|
||||
Chicory#Chicvege79qoQ8#0.09#709#vegetables#The Pantry
|
||||
Chicory#ChicvegeTSVBQq#0.10#477#vegetables#TriCounty Produce
|
||||
Chicory#Chicvege6qpcyi#0.10#282#vegetables#Off the Vine
|
||||
Chinese Cabbage#ChinvegeFNsSRn#0.78#408#vegetables#The Pantry
|
||||
Chinese Cabbage#Chinvege2ldNr3#0.80#799#vegetables#TriCounty Produce
|
||||
Chinese Cabbage#ChinvegeK3R2Td#0.80#180#vegetables#Off the Vine
|
||||
Chinese Beans#ChinvegebxbyPy#0.45#654#vegetables#The Pantry
|
||||
Chinese Beans#ChinvegewKGwgx#0.45#206#vegetables#TriCounty Produce
|
||||
Chinese Beans#ChinvegevVjzC0#0.47#643#vegetables#Off the Vine
|
||||
Chines Kale#ChinvegeCfdkss#0.70#239#vegetables#The Pantry
|
||||
Chines Kale#Chinvege6V6Dne#0.65#548#vegetables#TriCounty Produce
|
||||
Chines Kale#ChinvegeB7vE3x#0.66#380#vegetables#Off the Vine
|
||||
Chinese Radish#ChinvegeXcM4eq#0.22#190#vegetables#The Pantry
|
||||
Chinese Radish#ChinvegeTdUBqN#0.22#257#vegetables#TriCounty Produce
|
||||
Chinese Radish#ChinvegeMXMms8#0.22#402#vegetables#Off the Vine
|
||||
Chinese Mustard#ChinvegeRDdpdl#0.33#149#vegetables#The Pantry
|
||||
Chinese Mustard#ChinvegeABDhNd#0.32#320#vegetables#TriCounty Produce
|
||||
Chinese Mustard#Chinvege8NPwa2#0.34#389#vegetables#Off the Vine
|
||||
Cilantro#CilavegeQXBEsW#0.60#674#vegetables#The Pantry
|
||||
Cilantro#CilavegeRgjkUG#0.60#355#vegetables#TriCounty Produce
|
||||
Cilantro#CilavegelT2msu#0.59#464#vegetables#Off the Vine
|
||||
Collard#CollvegesTGGNw#0.32#745#vegetables#The Pantry
|
||||
Collard#CollvegeAwdor5#0.32#124#vegetables#TriCounty Produce
|
||||
Collard#CollvegeQe900L#0.30#796#vegetables#Off the Vine
|
||||
Coriander#CorivegeXxp4xY#0.26#560#vegetables#The Pantry
|
||||
Coriander#Corivege9xBAT0#0.27#321#vegetables#TriCounty Produce
|
||||
Coriander#CorivegeCfNjBx#0.27#709#vegetables#Off the Vine
|
||||
Dandelion#DandvegeJNcnbr#0.11#285#vegetables#The Pantry
|
||||
Dandelion#DandvegeGwBkHZ#0.11#733#vegetables#TriCounty Produce
|
||||
Dandelion#DandvegeZfwVqn#0.11#57#vegetables#Off the Vine
|
||||
Daikon Radish#DaikvegeHHsd7M#0.61#743#vegetables#The Pantry
|
||||
Daikon Radish#DaikvegeIu17yC#0.62#459#vegetables#TriCounty Produce
|
||||
Daikon Radish#DaikvegePzFjqf#0.63#296#vegetables#Off the Vine
|
||||
Eggplant#EggpvegeKJtydN#0.55#200#vegetables#The Pantry
|
||||
Eggplant#EggpvegeQMKrNs#0.53#208#vegetables#TriCounty Produce
|
||||
Eggplant#EggpvegeN0WnSo#0.51#761#vegetables#Off the Vine
|
||||
English Pea#Englvegea1ytIn#0.40#457#vegetables#The Pantry
|
||||
English Pea#EnglvegerU9Vty#0.37#263#vegetables#TriCounty Produce
|
||||
English Pea#EnglvegeCmkd3y#0.39#430#vegetables#Off the Vine
|
||||
Fennel#Fennvegebz2UM7#0.76#545#vegetables#The Pantry
|
||||
Fennel#FennvegeQzjtZ3#0.78#795#vegetables#TriCounty Produce
|
||||
Fennel#FennvegeXSrW61#0.75#79#vegetables#Off the Vine
|
||||
Garlic#GarlvegesR2yel#0.76#478#vegetables#The Pantry
|
||||
Garlic#GarlvegeEQvt8W#0.77#349#vegetables#TriCounty Produce
|
||||
Garlic#GarlvegedljBdK#0.80#708#vegetables#Off the Vine
|
||||
Ginger#GingvegeMNiTc2#0.88#563#vegetables#The Pantry
|
||||
Ginger#Gingvegeq366Sn#0.89#738#vegetables#TriCounty Produce
|
||||
Ginger#GingvegeznyyVj#0.89#598#vegetables#Off the Vine
|
||||
Horseradish#HorsvegemSwISt#0.12#622#vegetables#The Pantry
|
||||
Horseradish#HorsvegetCOS0x#0.11#279#vegetables#TriCounty Produce
|
||||
Horseradish#Horsvegew6XXaS#0.12#478#vegetables#Off the Vine
|
||||
Japanese Eggplant#JapavegeTdKDCL#0.57#539#vegetables#The Pantry
|
||||
Japanese Eggplant#JapavegevsJfGa#0.58#782#vegetables#TriCounty Produce
|
||||
Japanese Eggplant#JapavegeCIrIxd#0.57#777#vegetables#Off the Vine
|
||||
Jerusalem Artichoke#Jeruvege928cr0#0.13#231#vegetables#The Pantry
|
||||
Jerusalem Artichoke#JeruvegeC2v086#0.14#123#vegetables#TriCounty Produce
|
||||
Jerusalem Artichoke#JeruvegeehCYzi#0.14#196#vegetables#Off the Vine
|
||||
Jicama#JicavegeRWYj9n#0.75#79#vegetables#The Pantry
|
||||
Jicama#JicavegeGk5LKH#0.71#292#vegetables#TriCounty Produce
|
||||
Jicama#JicavegeUjpaX1#0.70#308#vegetables#Off the Vine
|
||||
Kale#Kalevegext6RNT#0.55#765#vegetables#The Pantry
|
||||
Kale#KalevegeFsp17B#0.53#107#vegetables#TriCounty Produce
|
||||
Kale#KalevegeAffBTS#0.57#573#vegetables#Off the Vine
|
||||
Kiwifruit#KiwivegeloZBKJ#0.60#769#vegetables#The Pantry
|
||||
Kiwifruit#KiwivegenCQAHw#0.59#307#vegetables#TriCounty Produce
|
||||
Kiwifruit#Kiwivege0Gi3P2#0.59#235#vegetables#Off the Vine
|
||||
Kohlrabi#KohlvegeJFKZDl#0.26#406#vegetables#The Pantry
|
||||
Kohlrabi#Kohlvege32UTAj#0.28#613#vegetables#TriCounty Produce
|
||||
Kohlrabi#KohlvegejNQC1M#0.28#326#vegetables#Off the Vine
|
||||
Leek#Leekvege5iaFtg#0.70#580#vegetables#The Pantry
|
||||
Leek#Leekvegei9Wxbz#0.68#188#vegetables#TriCounty Produce
|
||||
Leek#LeekvegewY4mAc#0.70#473#vegetables#Off the Vine
|
||||
Lettuce#LettvegesK9wDR#0.55#716#vegetables#The Pantry
|
||||
Lettuce#LettvegeWzMyCM#0.57#83#vegetables#TriCounty Produce
|
||||
Lettuce#LettvegeHgfGG8#0.56#268#vegetables#Off the Vine
|
||||
Melons#Melovege6t93WF#0.11#252#vegetables#The Pantry
|
||||
Melons#Melovegeq9kz7T#0.12#558#vegetables#TriCounty Produce
|
||||
Melons#Melovege9kLTXN#0.12#382#vegetables#Off the Vine
|
||||
Mushroom#MushvegeSq53h8#0.59#365#vegetables#The Pantry
|
||||
Mushroom#Mushvegedq6lYP#0.59#444#vegetables#TriCounty Produce
|
||||
Mushroom#Mushvege8o27D2#0.55#467#vegetables#Off the Vine
|
||||
Okra#OkravegeTszQSL#0.55#62#vegetables#The Pantry
|
||||
Okra#OkravegeJBWmfh#0.58#165#vegetables#TriCounty Produce
|
||||
Okra#OkravegeD6tF9n#0.55#77#vegetables#Off the Vine
|
||||
Onion#OniovegejwimQo#0.80#186#vegetables#The Pantry
|
||||
Onion#OniovegeUOwwks#0.80#417#vegetables#TriCounty Produce
|
||||
Onion#OniovegezcRDrc#0.80#435#vegetables#Off the Vine
|
||||
Oregano#OregvegetlU7Ez#0.71#119#vegetables#The Pantry
|
||||
Oregano#Oregvege9h9ZKy#0.70#173#vegetables#TriCounty Produce
|
||||
Oregano#OregvegebXr0PJ#0.70#773#vegetables#Off the Vine
|
||||
Parsley#ParsvegeXFEjjN#0.83#502#vegetables#The Pantry
|
||||
Parsley#ParsvegejAg5C4#0.80#454#vegetables#TriCounty Produce
|
||||
Parsley#ParsvegehAtH2H#0.84#523#vegetables#Off the Vine
|
||||
Parsnip#Parsvegee9Lp6D#0.46#626#vegetables#The Pantry
|
||||
Parsnip#ParsvegeSxXHSA#0.47#411#vegetables#TriCounty Produce
|
||||
Parsnip#Parsvegea0stPf#0.44#403#vegetables#Off the Vine
|
||||
Pea#Peavegecq4SxR#0.18#342#vegetables#The Pantry
|
||||
Pea#Peavege46Gdp9#0.18#255#vegetables#TriCounty Produce
|
||||
Pea#Peavegeov1gc5#0.18#251#vegetables#Off the Vine
|
||||
Pepper#PeppvegeUcBYRp#0.33#52#vegetables#The Pantry
|
||||
Pepper#PeppvegeB60btP#0.35#107#vegetables#TriCounty Produce
|
||||
Pepper#PeppvegeG4tP3e#0.34#481#vegetables#Off the Vine
|
||||
Pigeon Pea#Pigevegec5bAtm#0.94#391#vegetables#The Pantry
|
||||
Pigeon Pea#Pigevegeb93eLi#0.91#447#vegetables#TriCounty Produce
|
||||
Pigeon Pea#PigevegejEBDRa#0.89#259#vegetables#Off the Vine
|
||||
Irish Potato#IrisvegeJNQqby#0.72#355#vegetables#The Pantry
|
||||
Irish Potato#Irisvegewq1PLd#0.72#601#vegetables#TriCounty Produce
|
||||
Irish Potato#IrisvegeAfFLdO#0.68#740#vegetables#Off the Vine
|
||||
Pumpkin#PumpvegeiYsPR8#0.25#776#vegetables#The Pantry
|
||||
Pumpkin#PumpvegelqP1Kh#0.25#189#vegetables#TriCounty Produce
|
||||
Pumpkin#Pumpvegeb3nQU5#0.26#207#vegetables#Off the Vine
|
||||
Radish#RadivegeNwwSBJ#0.16#613#vegetables#The Pantry
|
||||
Radish#Radivege0tIBnL#0.16#779#vegetables#TriCounty Produce
|
||||
Radish#RadivegeNLqJCf#0.16#731#vegetables#Off the Vine
|
||||
Rhubarb#RhubvegeREfOti#0.12#301#vegetables#The Pantry
|
||||
Rhubarb#Rhubvege4Jc3b7#0.12#557#vegetables#TriCounty Produce
|
||||
Rhubarb#RhubvegeaXqF7H#0.12#378#vegetables#Off the Vine
|
||||
Rosemary#Rosevege16QStc#0.73#380#vegetables#The Pantry
|
||||
Rosemary#RosevegeNf6Oem#0.75#622#vegetables#TriCounty Produce
|
||||
Rosemary#RosevegeFgsOyN#0.74#631#vegetables#Off the Vine
|
||||
Rutabaga#RutavegecUYfQ3#0.55#676#vegetables#The Pantry
|
||||
Rutabaga#RutavegejOG5DF#0.55#273#vegetables#TriCounty Produce
|
||||
Rutabaga#RutavegewEVjzV#0.53#452#vegetables#Off the Vine
|
||||
Salsify#SalsvegeViS9HF#0.11#537#vegetables#The Pantry
|
||||
Salsify#Salsvegemd3HAL#0.11#753#vegetables#TriCounty Produce
|
||||
Salsify#SalsvegeuRCnmq#0.10#787#vegetables#Off the Vine
|
||||
Savory#Savovegee4DRWl#0.21#456#vegetables#The Pantry
|
||||
Savory#SavovegerZ90Xm#0.21#642#vegetables#TriCounty Produce
|
||||
Savory#Savovegeje7yy7#0.22#328#vegetables#Off the Vine
|
||||
Sesame#Sesavege4NAWZE#0.84#54#vegetables#The Pantry
|
||||
Sesame#SesavegeMTc9IN#0.84#458#vegetables#TriCounty Produce
|
||||
Sesame#SesavegegOwAjo#0.83#125#vegetables#Off the Vine
|
||||
Shallots#ShalvegeUO2pDO#0.26#599#vegetables#The Pantry
|
||||
Shallots#ShalvegeY1sekb#0.27#647#vegetables#TriCounty Produce
|
||||
Shallots#ShalvegeSDC8VY#0.27#369#vegetables#Off the Vine
|
||||
Sugar Snap Peas#SugavegepUZDTl#0.47#308#vegetables#The Pantry
|
||||
Sugar Snap Peas#Sugavege1XyzNH#0.48#205#vegetables#TriCounty Produce
|
||||
Sugar Snap Peas#SugavegeJuaG7f#0.46#348#vegetables#Off the Vine
|
||||
Soybean#SoybvegeqxSVRL#0.70#639#vegetables#The Pantry
|
||||
Soybean#SoybvegezEMjOG#0.68#423#vegetables#TriCounty Produce
|
||||
Soybean#SoybvegebanSFq#0.67#268#vegetables#Off the Vine
|
||||
Spaghetti Squash#SpagvegeMNO1yC#0.12#753#vegetables#The Pantry
|
||||
Spaghetti Squash#SpagvegeilpUaD#0.13#604#vegetables#TriCounty Produce
|
||||
Spaghetti Squash#SpagvegeAOoZNX#0.13#431#vegetables#Off the Vine
|
||||
Spinach#SpinvegeegXXou#0.10#742#vegetables#The Pantry
|
||||
Spinach#SpinvegeVcqXL6#0.11#708#vegetables#TriCounty Produce
|
||||
Spinach#SpinvegetZ26DN#0.11#625#vegetables#Off the Vine
|
||||
Sweet Potato#SweevegepNDQWb#0.94#720#vegetables#The Pantry
|
||||
Sweet Potato#Sweevegepnw7Tm#0.90#377#vegetables#TriCounty Produce
|
||||
Sweet Potato#Sweevegeyk0C82#0.89#242#vegetables#Off the Vine
|
||||
Swiss Chard#SwisvegeksalTA#0.54#545#vegetables#The Pantry
|
||||
Swiss Chard#SwisvegeKm2Kze#0.54#472#vegetables#TriCounty Produce
|
||||
Swiss Chard#SwisvegehteuMk#0.56#142#vegetables#Off the Vine
|
||||
Taro#Tarovege3fpGV6#0.87#155#vegetables#The Pantry
|
||||
Taro#TarovegerZkmof#0.86#371#vegetables#TriCounty Produce
|
||||
Taro#TarovegeXKPuzc#0.89#443#vegetables#Off the Vine
|
||||
Tarragon#TarrvegeCzVC6U#0.18#491#vegetables#The Pantry
|
||||
Tarragon#TarrvegesIkEfS#0.17#65#vegetables#TriCounty Produce
|
||||
Tarragon#TarrvegerZsKFP#0.18#180#vegetables#Off the Vine
|
||||
Thyme#Thymvege8Rv72c#0.41#442#vegetables#The Pantry
|
||||
Thyme#ThymvegeJoUdQS#0.42#237#vegetables#TriCounty Produce
|
||||
Thyme#ThymvegeRck5uO#0.43#491#vegetables#Off the Vine
|
||||
Tomato#Tomavegey0NHGK#0.31#60#vegetables#The Pantry
|
||||
Tomato#TomavegeKAjRUn#0.30#630#vegetables#TriCounty Produce
|
||||
Tomato#TomavegePZOHlH#0.30#70#vegetables#Off the Vine
|
||||
Turnip#TurnvegeRVQiV5#0.44#580#vegetables#The Pantry
|
||||
Turnip#TurnvegeVjIX9D#0.45#743#vegetables#TriCounty Produce
|
||||
Turnip#TurnvegelFhvuJ#0.44#219#vegetables#Off the Vine
|
||||
Watercress#WatevegelwzPLQ#0.54#230#vegetables#The Pantry
|
||||
Watercress#Watevege8oeDCT#0.54#774#vegetables#TriCounty Produce
|
||||
Watercress#Watevegexr8L1t#0.55#185#vegetables#Off the Vine
|
||||
Watermelon#WatevegeL83MRH#0.19#698#vegetables#The Pantry
|
||||
Watermelon#WatevegeR2S4Dq#0.21#488#vegetables#TriCounty Produce
|
||||
Watermelon#WatevegepFPXQu#0.21#439#vegetables#Off the Vine
|
||||
Kamote#KamovegegdON75#0.13#218#vegetables#The Pantry
|
||||
Kamote#KamovegevupDBf#0.13#98#vegetables#TriCounty Produce
|
||||
Kamote#KamovegeSQX7IA#0.14#703#vegetables#Off the Vine
|
||||
Alogbati#AlogvegeB1WaJU#0.41#775#vegetables#The Pantry
|
||||
Alogbati#AlogvegeVr5cPP#0.40#789#vegetables#TriCounty Produce
|
||||
Alogbati#AlogvegeyTUQzy#0.40#416#vegetables#Off the Vine
|
||||
Ampalaya#AmpavegemR9fSd#0.85#107#vegetables#The Pantry
|
||||
Ampalaya#AmpavegeJDu9Im#0.90#676#vegetables#TriCounty Produce
|
||||
Ampalaya#AmpavegepL8GH5#0.86#728#vegetables#Off the Vine
|
||||
Dahon ng sili#Dahovege6X9grk#0.11#369#vegetables#The Pantry
|
||||
Dahon ng sili#DahovegeiHZjQT#0.11#141#vegetables#TriCounty Produce
|
||||
Dahon ng sili#DahovegeoCDAH8#0.12#517#vegetables#Off the Vine
|
||||
Gabi#GabivegeVm4Xk3#0.44#396#vegetables#The Pantry
|
||||
Gabi#Gabivegeu6woqK#0.42#722#vegetables#TriCounty Produce
|
||||
Gabi#GabivegezcA7q1#0.42#394#vegetables#Off the Vine
|
||||
Kabute#Kabuvege6Tqrif#0.16#123#vegetables#The Pantry
|
||||
Kabute#KabuvegeA3uYdG#0.15#183#vegetables#TriCounty Produce
|
||||
Kabute#KabuvegeXW6ZiI#0.16#624#vegetables#Off the Vine
|
||||
Kamoteng Kahoy#KamovegeAdW37X#0.42#782#vegetables#The Pantry
|
||||
Kamoteng Kahoy#KamovegetFlqpC#0.42#515#vegetables#TriCounty Produce
|
||||
Kamoteng Kahoy#KamovegeMvxoLn#0.40#166#vegetables#Off the Vine
|
||||
Kangkong#KangvegeSFTvEz#0.35#759#vegetables#The Pantry
|
||||
Kangkong#KangvegeRLR6gL#0.34#695#vegetables#TriCounty Produce
|
||||
Kangkong#Kangvege9BFo14#0.35#783#vegetables#Off the Vine
|
||||
Labanos#Labavege3qrWJL#0.94#514#vegetables#The Pantry
|
||||
Labanos#LabavegekgVWDH#0.89#210#vegetables#TriCounty Produce
|
||||
Labanos#LabavegeiVPgMx#0.89#207#vegetables#Off the Vine
|
||||
Labong#LabovegeX3O8yz#0.85#722#vegetables#The Pantry
|
||||
Labong#LabovegeI1wSEs#0.87#472#vegetables#TriCounty Produce
|
||||
Labong#LabovegeOPiQht#0.85#740#vegetables#Off the Vine
|
||||
Malunggay#MaluvegeHkwAFm#0.30#252#vegetables#The Pantry
|
||||
Malunggay#Maluvegez6TiSY#0.30#245#vegetables#TriCounty Produce
|
||||
Malunggay#MaluvegewzY37D#0.31#405#vegetables#Off the Vine
|
||||
Munggo#MungvegeqeuwGw#0.25#362#vegetables#The Pantry
|
||||
Munggo#MungvegeNhqWvL#0.26#360#vegetables#TriCounty Produce
|
||||
Munggo#MungvegeGxNxQC#0.25#555#vegetables#Off the Vine
|
||||
Pechay#PechvegezDeHFZ#0.36#401#vegetables#The Pantry
|
||||
Pechay#Pechvegehi4Fcx#0.35#723#vegetables#TriCounty Produce
|
||||
Pechay#Pechvege8Pq8Eo#0.36#141#vegetables#Off the Vine
|
||||
Sigarilyas#SigavegeMJrtlV#0.88#335#vegetables#The Pantry
|
||||
Sigarilyas#SigavegeLhsoOB#0.87#768#vegetables#TriCounty Produce
|
||||
Sigarilyas#SigavegeS6RJcA#0.93#356#vegetables#Off the Vine
|
||||
Sitaw#Sitavege0hMi9z#0.65#153#vegetables#The Pantry
|
||||
Sitaw#Sitavegeez1g6N#0.67#561#vegetables#TriCounty Produce
|
||||
Sitaw#Sitavege0BCNeF#0.66#674#vegetables#Off the Vine
|
||||
Talong#TalovegevZjVK6#0.10#530#vegetables#The Pantry
|
||||
Talong#TalovegexX4MRw#0.09#305#vegetables#TriCounty Produce
|
||||
Talong#TalovegeO3U2ze#0.10#126#vegetables#Off the Vine
|
||||
Toge#TogevegeYelJUw#0.54#449#vegetables#The Pantry
|
||||
Toge#Togevegeilr1xK#0.54#274#vegetables#TriCounty Produce
|
||||
Toge#Togevegesvjnyn#0.51#316#vegetables#Off the Vine
|
||||
Ube#UbevegeoPnxvb#0.56#397#vegetables#The Pantry
|
||||
Ube#Ubevege2CNyve#0.55#450#vegetables#TriCounty Produce
|
||||
Ube#UbevegeC43sVj#0.55#263#vegetables#Off the Vine
|
||||
Upo#UpovegecOGRqC#0.22#404#vegetables#The Pantry
|
||||
Upo#Upovegekjl2wl#0.22#541#vegetables#TriCounty Produce
|
||||
Upo#UpovegemTTTwI#0.23#459#vegetables#Off the Vine
|
||||
Edamame#EdamvegeVYtk8z#0.79#296#vegetables#The Pantry
|
||||
Edamame#Edamvege608vXi#0.78#700#vegetables#TriCounty Produce
|
||||
Edamame#Edamvege1jiqGY#0.75#115#vegetables#Off the Vine
|
||||
Hairy melon#HairvegeFYFHIw#0.71#789#vegetables#The Pantry
|
||||
Hairy melon#HairvegeS7AAqI#0.72#302#vegetables#TriCounty Produce
|
||||
Hairy melon#HairvegeO6WJHL#0.72#444#vegetables#Off the Vine
|
||||
Burdock#BurdvegeyLstLV#0.56#761#vegetables#The Pantry
|
||||
Burdock#BurdvegeZsqAjT#0.56#582#vegetables#TriCounty Produce
|
||||
Burdock#BurdvegeycF7mo#0.55#566#vegetables#Off the Vine
|
||||
Snake gourd#SnakvegesfHGvt#0.92#626#vegetables#The Pantry
|
||||
Snake gourd#SnakvegedlNiBk#0.92#669#vegetables#TriCounty Produce
|
||||
Snake gourd#Snakvegec5n1UM#0.92#143#vegetables#Off the Vine
|
||||
Wasabi#Wasavege5P5pZp#0.67#751#vegetables#The Pantry
|
||||
Wasabi#Wasavege6EEE9r#0.68#559#vegetables#TriCounty Produce
|
||||
Wasabi#Wasavege1ve7TY#0.65#61#vegetables#Off the Vine
|
||||
Yam#YamvegeRN9ONH#0.57#438#vegetables#The Pantry
|
||||
Yam#YamvegeWjdzeA#0.56#564#vegetables#TriCounty Produce
|
||||
Yam#YamvegeI1AnyI#0.56#456#vegetables#Off the Vine
|
||||
Apple Fritters#AppldessDj96hw#6.12#16#desserts#Mom's Kitchen
|
||||
Apple Fritters#AppldessrN1kvM#6.06#7#desserts#The Baking Pan
|
||||
Banana Split#Banadess7tpjkJ#10.86#10#desserts#Mom's Kitchen
|
||||
Banana Split#Banadessfif758#11.07#14#desserts#The Baking Pan
|
||||
Blueberry Boy Bait#BluedesseX2LVU#3.72#16#desserts#Mom's Kitchen
|
||||
Blueberry Boy Bait#Bluedess9zLhaH#3.93#9#desserts#The Baking Pan
|
||||
Candied Cranberries#CanddessjW92p3#1.77#9#desserts#Mom's Kitchen
|
||||
Candied Cranberries#CanddesskhtVoQ#1.72#0#desserts#The Baking Pan
|
||||
Daiquiri Souffle#DaiqdessebnYcy#9.54#15#desserts#Mom's Kitchen
|
||||
Daiquiri Souffle#DaiqdessfM1DnX#9.72#6#desserts#The Baking Pan
|
||||
Bananas Flambe#BanadesscczumD#6.94#12#desserts#Mom's Kitchen
|
||||
Bananas Flambe#Banadess8qNfxd#7.07#16#desserts#The Baking Pan
|
||||
Pie, Apple#Pie,desshcSHhT#7.88#11#desserts#Mom's Kitchen
|
||||
Pie, Apple#Pie,dessTbiwDp#7.88#15#desserts#The Baking Pan
|
||||
Pie, Pumpkin#Pie,desswhPBPB#6.00#20#desserts#Mom's Kitchen
|
||||
Pie, Pumpkin#Pie,dessDg3NWl#6.24#19#desserts#The Baking Pan
|
||||
Pie, Blueberry#Pie,dessw9VdgD#2.14#3#desserts#Mom's Kitchen
|
||||
Pie, Blueberry#Pie,dessiSjZKD#2.12#1#desserts#The Baking Pan
|
||||
Pie, Pecan#Pie,dess2NqhNR#12.70#20#desserts#Mom's Kitchen
|
||||
Pie, Pecan#Pie,dessB1LfcE#12.33#12#desserts#The Baking Pan
|
||||
Pie, Cranberry Apple#Pie,dess1mL7IS#10.16#7#desserts#Mom's Kitchen
|
||||
Pie, Cranberry Apple#Pie,dessmDhkUA#10.16#11#desserts#The Baking Pan
|
||||
Pie, Banana Cream#Pie,dessH80DuG#7.35#6#desserts#Mom's Kitchen
|
||||
Pie, Banana Cream#Pie,dessf1YvFb#7.08#11#desserts#The Baking Pan
|
||||
Pie, Key Lime#Pie,desshtli5N#4.85#2#desserts#Mom's Kitchen
|
||||
Pie, Key Lime#Pie,dessMwQkKm#5.13#1#desserts#The Baking Pan
|
||||
Pie, Lemon Meringue#Pie,dess9naVkX#3.74#7#desserts#Mom's Kitchen
|
||||
Pie, Lemon Meringue#Pie,dessKYcNML#3.67#5#desserts#The Baking Pan
|
||||
Pie, Caramel#Pie,dessSUuiIU#2.27#9#desserts#Mom's Kitchen
|
||||
Pie, Caramel#Pie,dessvo8uHh#2.33#4#desserts#The Baking Pan
|
||||
Pie, Raspberry#Pie,dessUHhMlS#2.36#0#desserts#Mom's Kitchen
|
||||
Pie, Raspberry#Pie,dessJflbf5#2.36#2#desserts#The Baking Pan
|
||||
Ice Cream, Chocolate#Ice desseXuyxx#1.44#9#desserts#Mom's Kitchen
|
||||
Ice Cream, Chocolate#Ice dessASBohf#1.41#13#desserts#The Baking Pan
|
||||
Ice Cream, Vanilla#Ice dessYnzbbt#11.92#19#desserts#Mom's Kitchen
|
||||
Ice Cream, Vanilla#Ice dessUBBKp8#11.58#10#desserts#The Baking Pan
|
||||
Ice Cream, Strawberry#Ice dessfTwKhD#1.90#14#desserts#Mom's Kitchen
|
||||
Ice Cream, Strawberry#Ice dessaO9Fxf#1.99#6#desserts#The Baking Pan
|
||||
Ice Cream, Rocky Road#Ice dessyIri3P#13.10#20#desserts#Mom's Kitchen
|
||||
Ice Cream, Rocky Road#Ice dessZuLr8F#13.48#13#desserts#The Baking Pan
|
||||
Ice Cream, Mint Chocolate Chip#Ice dessV1IGG7#5.75#4#desserts#Mom's Kitchen
|
||||
Ice Cream, Mint Chocolate Chip#Ice dessX1gEQ4#5.64#1#desserts#The Baking Pan
|
||||
Ice Cream Sundae#Ice dessbhlAXt#5.62#11#desserts#Mom's Kitchen
|
||||
Ice Cream Sundae#Ice dessByapxl#5.72#16#desserts#The Baking Pan
|
||||
Cobbler, Peach#CobbdessYUGeOB#10.14#20#desserts#Mom's Kitchen
|
||||
Cobbler, Peach#CobbdessXfEtUK#10.43#16#desserts#The Baking Pan
|
||||
Cobbler, Berry-Pecan#Cobbdessx3htak#5.36#12#desserts#Mom's Kitchen
|
||||
Cobbler, Berry-Pecan#Cobbdesse4FUVI#5.41#8#desserts#The Baking Pan
|
||||
Cobbler, Blueberry#CobbdessbiI0oF#3.78#11#desserts#Mom's Kitchen
|
||||
Cobbler, Blueberry#CobbdessMXxbBN#3.57#2#desserts#The Baking Pan
|
||||
Cobbler, Cherry#CobbdessNSa8QW#12.58#0#desserts#Mom's Kitchen
|
||||
Cobbler, Cherry#CobbdessA1dADa#12.10#10#desserts#The Baking Pan
|
||||
Cobbler, Huckleberry#Cobbdess3t6O8d#3.99#18#desserts#Mom's Kitchen
|
||||
Cobbler, Huckleberry#CobbdessGI9euK#3.88#0#desserts#The Baking Pan
|
||||
Cobbler, Rhubarb#Cobbdess22X40Z#9.54#0#desserts#Mom's Kitchen
|
||||
Cobbler, Rhubarb#CobbdessPfnCT0#9.27#18#desserts#The Baking Pan
|
||||
Cobbler, Strawberry#CobbdessI78188#12.43#0#desserts#Mom's Kitchen
|
||||
Cobbler, Strawberry#CobbdessH3LdgQ#12.20#3#desserts#The Baking Pan
|
||||
Cobbler, Zucchini#Cobbdess5rK4dP#11.24#3#desserts#Mom's Kitchen
|
||||
Cobbler, Zucchini#Cobbdess4Ez8kS#10.51#10#desserts#The Baking Pan
|
||||
Brownies#BrowdessmogdTl#7.62#9#desserts#Mom's Kitchen
|
||||
Brownies#Browdess84Qc1z#7.55#9#desserts#The Baking Pan
|
||||
Fudge Bar#Fudgdess8iXSyf#11.72#6#desserts#Mom's Kitchen
|
||||
Fudge Bar#FudgdessakU1Id#12.29#5#desserts#The Baking Pan
|
||||
Cookies, Oatmeal#Cookdessnq9Oya#2.84#15#desserts#Mom's Kitchen
|
||||
Cookies, Oatmeal#CookdessBhgp7p#2.68#10#desserts#The Baking Pan
|
||||
Cookies, Chocolate Chip#CookdessRVszsZ#12.73#17#desserts#Mom's Kitchen
|
||||
Cookies, Chocolate Chip#CookdessSOoHmT#12.26#19#desserts#The Baking Pan
|
||||
Cookies, Peanut Butter#Cookdess2UcMI2#7.82#5#desserts#Mom's Kitchen
|
||||
Cookies, Peanut Butter#Cookdess1cILme#7.46#20#desserts#The Baking Pan
|
||||
Mousse, Chocolate#MousdessDpN4sQ#6.25#20#desserts#Mom's Kitchen
|
||||
Mousse, Chocolate#Mousdess8FyFT8#5.96#1#desserts#The Baking Pan
|
||||
Mousse, Blueberry Maple#MousdessacwrkO#7.28#7#desserts#Mom's Kitchen
|
||||
Mousse, Blueberry Maple#MousdessbiCMFg#7.21#12#desserts#The Baking Pan
|
||||
Mousse, Chocolate Banana#MousdessIeW4qz#5.13#2#desserts#Mom's Kitchen
|
||||
Mousse, Chocolate Banana#Mousdess1De9oL#5.08#19#desserts#The Baking Pan
|
||||
Mousse, Cherry#Mousdesss1bF8H#13.05#20#desserts#Mom's Kitchen
|
||||
Mousse, Cherry#Mousdess0ujevx#12.43#1#desserts#The Baking Pan
|
||||
Mousse, Eggnog#MousdessZ38hXj#9.07#10#desserts#Mom's Kitchen
|
||||
Mousse, Eggnog#Mousdesshs05ST#8.81#8#desserts#The Baking Pan
|
||||
Mousse, Strawberry#MousdessHCDlBK#5.58#3#desserts#Mom's Kitchen
|
||||
Mousse, Strawberry#MousdessSZ4PyW#5.36#6#desserts#The Baking Pan
|
||||
Sherbet, Cantaloupe#Sherdess3DCxUg#3.11#9#desserts#Mom's Kitchen
|
||||
Sherbet, Cantaloupe#Sherdesscp2VIz#2.99#7#desserts#The Baking Pan
|
||||
Sherbet, Lemon Milk#Sherdess1JVFOS#7.57#9#desserts#Mom's Kitchen
|
||||
Sherbet, Lemon Milk#SherdessC865vu#7.57#0#desserts#The Baking Pan
|
||||
Sherbet, Orange Crush#Sherdess8W8Mb9#4.32#18#desserts#Mom's Kitchen
|
||||
Sherbet, Orange Crush#SherdessxmVJBF#4.16#10#desserts#The Baking Pan
|
||||
Sherbet, Blueberry#SherdessFAgxqp#3.46#9#desserts#Mom's Kitchen
|
||||
Sherbet, Blueberry#SherdessMPL87u#3.60#6#desserts#The Baking Pan
|
||||
Sherbet, Raspberry#Sherdesse86ugA#6.08#1#desserts#Mom's Kitchen
|
||||
Sherbet, Raspberry#Sherdesslc1etR#5.85#12#desserts#The Baking Pan
|
||||
Sherbet, Strawberry#SherdessFwv09m#4.63#17#desserts#Mom's Kitchen
|
||||
Sherbet, Strawberry#SherdessKB0H7q#4.81#20#desserts#The Baking Pan
|
||||
Tart, Apple#TartdessrsTyXA#3.35#18#desserts#Mom's Kitchen
|
||||
Tart, Apple#Tartdessp7pyiy#3.13#11#desserts#The Baking Pan
|
||||
Tart, Almond#TartdessC7FARL#6.62#10#desserts#Mom's Kitchen
|
||||
Tart, Almond#Tartdess1V1A1c#6.68#13#desserts#The Baking Pan
|
||||
Tart, Blueberry#TartdesssQZRXX#10.28#10#desserts#Mom's Kitchen
|
||||
Tart, Blueberry#TartdessUSJSuc#10.28#9#desserts#The Baking Pan
|
||||
Tart, Chocolate-Pear#Tartdess2pdOE4#5.67#17#desserts#Mom's Kitchen
|
||||
Tart, Chocolate-Pear#TartdessL3aEDd#5.51#9#desserts#The Baking Pan
|
||||
Tart, Lemon Fudge#Tartdess9DhZUT#3.88#3#desserts#Mom's Kitchen
|
||||
Tart, Lemon Fudge#TartdesshzLOWt#3.96#13#desserts#The Baking Pan
|
||||
Tart, Pecan#TartdessvSbXzd#11.80#3#desserts#Mom's Kitchen
|
||||
Tart, Pecan#Tartdess6YXJec#11.04#13#desserts#The Baking Pan
|
||||
Tart, Pineapple#TartdesseMfJFe#9.01#18#desserts#Mom's Kitchen
|
||||
Tart, Pineapple#TartdessA2Wftr#8.44#13#desserts#The Baking Pan
|
||||
Tart, Pear#Tartdess4a1BUc#10.09#2#desserts#Mom's Kitchen
|
||||
Tart, Pear#TartdessNw8YPG#10.68#5#desserts#The Baking Pan
|
||||
Tart, Raspberry#TartdessAVnpP6#6.18#7#desserts#Mom's Kitchen
|
||||
Tart, Raspberry#TartdessfVxZFf#5.95#9#desserts#The Baking Pan
|
||||
Tart, Strawberry#Tartdess4IUcZW#4.75#8#desserts#Mom's Kitchen
|
||||
Tart, Strawberry#Tartdess2BeEDb#4.61#17#desserts#The Baking Pan
|
||||
Tart, Raspberry#TartdesshyBd24#1.85#5#desserts#Mom's Kitchen
|
||||
Tart, Raspberry#Tartdess5fqxgy#1.94#20#desserts#The Baking Pan
|
||||
Trifle, Berry#TrifdessmEkbU2#12.48#19#desserts#Mom's Kitchen
|
||||
Trifle, Berry#TrifdessAV9Ix8#12.60#18#desserts#The Baking Pan
|
||||
Trifle, American#TrifdesscsdSCd#4.70#17#desserts#Mom's Kitchen
|
||||
Trifle, American#TrifdessTArskm#4.35#11#desserts#The Baking Pan
|
||||
Trifle, English#TrifdessX87q8T#8.20#9#desserts#Mom's Kitchen
|
||||
Trifle, English#Trifdess52l955#8.12#11#desserts#The Baking Pan
|
||||
Trifle, Orange#TrifdesslUwxwe#9.74#15#desserts#Mom's Kitchen
|
||||
Trifle, Orange#TrifdessFrfCHP#10.22#1#desserts#The Baking Pan
|
||||
Trifle, Pumpkin#TrifdessJKFN96#4.72#7#desserts#Mom's Kitchen
|
||||
Trifle, Pumpkin#TrifdessMNw4EV#4.95#16#desserts#The Baking Pan
|
||||
Trifle, Scottish#TrifdessFa0JdK#13.63#0#desserts#Mom's Kitchen
|
||||
Trifle, Scottish#TrifdessAAUQCN#14.03#6#desserts#The Baking Pan
|
||||
Trifle, Sherry#TrifdesscuttJg#4.42#5#desserts#Mom's Kitchen
|
||||
Trifle, Sherry#TrifdesspRGpfP#4.21#19#desserts#The Baking Pan
|
||||
Trifle, Strawberry#TrifdessAd5TpV#3.58#11#desserts#Mom's Kitchen
|
||||
Trifle, Strawberry#Trifdess1rtW0A#3.58#3#desserts#The Baking Pan
|
||||
Trifle, Scotch Whiskey#Trifdess2zJsGi#5.44#5#desserts#Mom's Kitchen
|
||||
Trifle, Scotch Whiskey#TrifdessL8nuI6#5.18#5#desserts#The Baking Pan
|
||||
Cheesecake, Amaretto#CheedessOJBqfD#11.89#5#desserts#Mom's Kitchen
|
||||
Cheesecake, Amaretto#CheedessVnDf14#11.89#9#desserts#The Baking Pan
|
||||
Cheesecake, Apple#Cheedessuks1YK#11.22#15#desserts#Mom's Kitchen
|
||||
Cheesecake, Apple#CheedessMYKaKK#11.01#14#desserts#The Baking Pan
|
||||
Cheesecake, Apricot#CheedessKUxTYY#12.34#16#desserts#Mom's Kitchen
|
||||
Cheesecake, Apricot#CheedessMvB1pr#11.88#18#desserts#The Baking Pan
|
||||
Cheesecake, Australian#CheedessQ9WAIn#2.70#9#desserts#Mom's Kitchen
|
||||
Cheesecake, Australian#CheedessE6Jyjc#2.53#14#desserts#The Baking Pan
|
||||
Cheesecake, Arkansas#CheedessTbqzmw#6.98#9#desserts#Mom's Kitchen
|
||||
Cheesecake, Arkansas#CheedesstWJZfC#6.66#5#desserts#The Baking Pan
|
||||
Cheesecake, Blueberry#Cheedessyo51KL#8.07#11#desserts#Mom's Kitchen
|
||||
Cheesecake, Blueberry#Cheedess4Hz7P4#8.62#5#desserts#The Baking Pan
|
||||
Cheesecake, Cherry#CheedessEahRkC#4.40#14#desserts#Mom's Kitchen
|
||||
Cheesecake, Cherry#Cheedess3Nx4jZ#4.65#3#desserts#The Baking Pan
|
||||
Cheesecake, Cran-Raspberry#CheedessrJsr9i#13.47#20#desserts#Mom's Kitchen
|
||||
Cheesecake, Cran-Raspberry#CheedesshcuXCy#14.00#6#desserts#The Baking Pan
|
||||
Cheesecake, German Chocolate#CheedesswayvJL#12.03#16#desserts#Mom's Kitchen
|
||||
Cheesecake, German Chocolate#CheedessebTAeB#11.58#0#desserts#The Baking Pan
|
||||
Cheesecake, Turtle#CheedessLqgeIA#12.19#6#desserts#Mom's Kitchen
|
||||
Cheesecake, Turtle#CheedessvyNohA#12.07#19#desserts#The Baking Pan
|
||||
Brownies, Apple#BrowdessIDW1Cc#5.44#12#desserts#Mom's Kitchen
|
||||
Brownies, Apple#BrowdessyRMrAH#5.14#12#desserts#The Baking Pan
|
||||
Brownies, Fudge#BrowdessmIHIFJ#5.19#8#desserts#Mom's Kitchen
|
||||
Brownies, Fudge#BrowdessqewJ38#5.10#17#desserts#The Baking Pan
|
||||
Brownies, Almond Macaroon#BrowdessniK7QI#10.57#3#desserts#Mom's Kitchen
|
||||
Brownies, Almond Macaroon#BrowdessgkXURH#10.36#17#desserts#The Baking Pan
|
||||
Brownies, Butterscotch#BrowdesslpA06E#7.16#13#desserts#Mom's Kitchen
|
||||
Brownies, Butterscotch#BrowdessK5hofE#7.30#6#desserts#The Baking Pan
|
||||
Brownies, Caramel#BrowdessVGfoA8#3.07#3#desserts#Mom's Kitchen
|
||||
Brownies, Caramel#Browdess5jvVMM#3.13#11#desserts#The Baking Pan
|
||||
Brownies, Cherry#Browdessyoa66A#3.39#17#desserts#Mom's Kitchen
|
||||
Brownies, Cherry#BrowdessIg2JuF#3.39#11#desserts#The Baking Pan
|
||||
Brownies, Chocolate Chip#Browdessb9dc59#6.18#10#desserts#Mom's Kitchen
|
||||
Brownies, Chocolate Chip#BrowdessvW4nOx#6.43#14#desserts#The Baking Pan
|
||||
Brownies, Coconut#BrowdessWPHrVR#3.06#15#desserts#Mom's Kitchen
|
||||
Brownies, Coconut#BrowdessGVBlML#2.86#11#desserts#The Baking Pan
|
||||
Brownies, Cream Cheese#Browdess1OyRay#12.74#4#desserts#Mom's Kitchen
|
||||
Brownies, Cream Cheese#Browdess2fRsNv#12.61#19#desserts#The Baking Pan
|
||||
Brownies, Fudge Mint#Browdessl7DP7k#11.45#14#desserts#Mom's Kitchen
|
||||
Brownies, Fudge Mint#Browdessv70VKQ#11.34#16#desserts#The Baking Pan
|
||||
Brownies, Mint Chip#BrowdessDDMvF7#1.81#15#desserts#Mom's Kitchen
|
||||
Brownies, Mint Chip#Browdess0j9PBD#1.84#9#desserts#The Baking Pan
|
||||
Cake, Angel Food#CakedessEaqGaE#11.18#3#desserts#Mom's Kitchen
|
||||
Cake, Angel Food#CakedessJyAyFe#11.18#14#desserts#The Baking Pan
|
||||
Cake, Chocolate#CakedessKLXFbn#10.11#7#desserts#Mom's Kitchen
|
||||
Cake, Chocolate#CakedessfNP5Hg#9.91#14#desserts#The Baking Pan
|
||||
Cake, Carrot#CakedessUTgMoV#4.20#13#desserts#Mom's Kitchen
|
||||
Cake, Carrot#CakedessQdkaYg#4.00#3#desserts#The Baking Pan
|
||||
Cake, Lemon Blueberry#CakedessepkeEW#11.73#16#desserts#Mom's Kitchen
|
||||
Cake, Lemon Blueberry#CakedessHTKyQs#12.42#16#desserts#The Baking Pan
|
||||
Cake Triple Fudge#CakedessiZ75lR#7.92#7#desserts#Mom's Kitchen
|
||||
Cake Triple Fudge#CakedessWRrSXP#8.00#15#desserts#The Baking Pan
|
||||
Cake, Walnut#CakedessveYVXZ#10.83#17#desserts#Mom's Kitchen
|
||||
Cake, Walnut#Cakedesse22rT5#11.04#7#desserts#The Baking Pan
|
||||
Cake, French Apple#CakedessjA2Kxv#1.95#0#desserts#Mom's Kitchen
|
||||
Cake, French Apple#CakedessNBHCk0#1.86#20#desserts#The Baking Pan
|
||||
Cake, Fig#CakedessOncX4y#6.82#3#desserts#Mom's Kitchen
|
||||
Cake, Fig#CakedessTJtffn#7.08#10#desserts#The Baking Pan
|
||||
Cake, Maple#CakedessnoGPRF#3.04#11#desserts#Mom's Kitchen
|
||||
Cake, Maple#CakedessfVattM#3.22#4#desserts#The Baking Pan
|
||||
Cake, Devil's Food#CakedessiXcDCt#4.73#7#desserts#Mom's Kitchen
|
||||
Cake, Devil's Food#CakedessnBZk45#4.82#6#desserts#The Baking Pan
|
||||
Cake, Double-Lemon#CakedesskeS0Vd#3.46#9#desserts#Mom's Kitchen
|
||||
Cake, Double-Lemon#Cakedess50vx53#3.60#6#desserts#The Baking Pan
|
||||
Sorbet, Blackberry#SorbdessQoa0CE#9.88#15#desserts#Mom's Kitchen
|
||||
Sorbet, Blackberry#SorbdessqoOYzv#9.78#9#desserts#The Baking Pan
|
||||
6
examples_c/getting_started/vendors.txt
Normal file
6
examples_c/getting_started/vendors.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
TriCounty Produce#309 S. Main Street#Middle Town#MN#55432#763 555 5761#Mort Dufresne#763 555 5765
|
||||
Simply Fresh#15612 Bogart Lane#Harrigan#WI#53704#420 333 3912#Cheryl Swedberg#420 333 3952
|
||||
Off the Vine#133 American Ct.#Centennial#IA#52002#563 121 3800#Bob King#563 121 3800 x54
|
||||
The Pantry#1206 N. Creek Way#Middle Town#MN#55432#763 555 3391#Sully Beckstrom#763 555 3391
|
||||
Mom's Kitchen#53 Yerman Ct.#Middle Town#MN#55432#763 554 9200#Maggie Kultgen#763 554 9200 x12
|
||||
The Baking Pan#1415 53rd Ave.#Dutchin#MN#56304#320 442 2277#Mike Roan#320 442 6879
|
||||
461
examples_c/txn_guide/txn_guide.c
Normal file
461
examples_c/txn_guide/txn_guide.c
Normal file
@@ -0,0 +1,461 @@
|
||||
/* File: txn_guide.c */
|
||||
|
||||
/* We assume an ANSI-compatible compiler */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <db.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#define PATHD '\\'
|
||||
extern int getopt(int, char * const *, const char *);
|
||||
extern char *optarg;
|
||||
|
||||
typedef HANDLE thread_t;
|
||||
#define thread_create(thrp, attr, func, arg) \
|
||||
(((*(thrp) = CreateThread(NULL, 0, \
|
||||
(LPTHREAD_START_ROUTINE)(func), (arg), 0, NULL)) == NULL) ? -1 : 0)
|
||||
#define thread_join(thr, statusp) \
|
||||
((WaitForSingleObject((thr), INFINITE) == WAIT_OBJECT_0) && \
|
||||
GetExitCodeThread((thr), (LPDWORD)(statusp)) ? 0 : -1)
|
||||
|
||||
typedef HANDLE mutex_t;
|
||||
#define mutex_init(m, attr) \
|
||||
(((*(m) = CreateMutex(NULL, FALSE, NULL)) != NULL) ? 0 : -1)
|
||||
#define mutex_lock(m) \
|
||||
((WaitForSingleObject(*(m), INFINITE) == WAIT_OBJECT_0) ? 0 : -1)
|
||||
#define mutex_unlock(m) (ReleaseMutex(*(m)) ? 0 : -1)
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#define PATHD '/'
|
||||
|
||||
typedef pthread_t thread_t;
|
||||
#define thread_create(thrp, attr, func, arg) \
|
||||
pthread_create((thrp), (attr), (func), (arg))
|
||||
#define thread_join(thr, statusp) pthread_join((thr), (statusp))
|
||||
|
||||
typedef pthread_mutex_t mutex_t;
|
||||
#define mutex_init(m, attr) pthread_mutex_init((m), (attr))
|
||||
#define mutex_lock(m) pthread_mutex_lock(m)
|
||||
#define mutex_unlock(m) pthread_mutex_unlock(m)
|
||||
#endif
|
||||
|
||||
/* Run 5 writers threads at a time. */
|
||||
#define NUMWRITERS 5
|
||||
|
||||
/*
|
||||
* Printing of a thread_t is implementation-specific, so we
|
||||
* create our own thread IDs for reporting purposes.
|
||||
*/
|
||||
int global_thread_num;
|
||||
mutex_t thread_num_lock;
|
||||
|
||||
/* Forward declarations */
|
||||
int count_records(DB *, DB_TXN *);
|
||||
int open_db(DB **, const char *, const char *, DB_ENV *, u_int32_t);
|
||||
int usage(void);
|
||||
void *writer_thread(void *);
|
||||
|
||||
/* Usage function */
|
||||
int
|
||||
usage()
|
||||
{
|
||||
fprintf(stderr, " [-h <database_home_directory>]\n");
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
/* Initialize our handles */
|
||||
DB *dbp = NULL;
|
||||
DB_ENV *envp = NULL;
|
||||
|
||||
thread_t writer_threads[NUMWRITERS];
|
||||
int ch, i, ret, ret_t;
|
||||
u_int32_t env_flags;
|
||||
char *db_home_dir;
|
||||
/* Application name */
|
||||
const char *prog_name = "txn_guide";
|
||||
/* Database file name */
|
||||
const char *file_name = "mydb.db";
|
||||
|
||||
/* Parse the command line arguments */
|
||||
#ifdef _WIN32
|
||||
db_home_dir = ".\\";
|
||||
#else
|
||||
db_home_dir = "./";
|
||||
#endif
|
||||
while ((ch = getopt(argc, argv, "h:")) != EOF)
|
||||
switch (ch) {
|
||||
case 'h':
|
||||
db_home_dir = optarg;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
return (usage());
|
||||
}
|
||||
|
||||
/* Create the environment */
|
||||
ret = db_env_create(&envp, 0);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Error creating environment handle: %s\n",
|
||||
db_strerror(ret));
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Indicate that we want db to perform lock detection internally.
|
||||
* Also indicate that the transaction with the fewest number of
|
||||
* write locks will receive the deadlock notification in
|
||||
* the event of a deadlock.
|
||||
*/
|
||||
ret = envp->set_lk_detect(envp, DB_LOCK_MINWRITE);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Error setting lock detect: %s\n",
|
||||
db_strerror(ret));
|
||||
goto err;
|
||||
}
|
||||
|
||||
env_flags =
|
||||
DB_CREATE | /* Create the environment if it does not exist */
|
||||
DB_RECOVER | /* Run normal recovery. */
|
||||
DB_INIT_LOCK | /* Initialize the locking subsystem */
|
||||
DB_INIT_LOG | /* Initialize the logging subsystem */
|
||||
DB_INIT_TXN | /* Initialize the transactional subsystem. This
|
||||
* also turns on logging. */
|
||||
DB_INIT_MPOOL | /* Initialize the memory pool (in-memory cache) */
|
||||
DB_THREAD; /* Cause the environment to be free-threaded */
|
||||
|
||||
/* Now actually open the environment */
|
||||
ret = envp->open(envp, db_home_dir, env_flags, 0);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Error opening environment: %s\n",
|
||||
db_strerror(ret));
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we had utility threads (for running checkpoints or
|
||||
* deadlock detection, for example) we would spawn those
|
||||
* here. However, for a simple example such as this,
|
||||
* that is not required.
|
||||
*/
|
||||
|
||||
/* Open the database */
|
||||
ret = open_db(&dbp, prog_name, file_name,
|
||||
envp, DB_DUPSORT);
|
||||
if (ret != 0)
|
||||
goto err;
|
||||
|
||||
/* Initialize a mutex. Used to help provide thread ids. */
|
||||
(void)mutex_init(&thread_num_lock, NULL);
|
||||
|
||||
/* Start the writer threads. */
|
||||
for (i = 0; i < NUMWRITERS; i++)
|
||||
(void)thread_create(
|
||||
&writer_threads[i], NULL, writer_thread, (void *)dbp);
|
||||
|
||||
/* Join the writers */
|
||||
for (i = 0; i < NUMWRITERS; i++)
|
||||
(void)thread_join(writer_threads[i], NULL);
|
||||
|
||||
err:
|
||||
/* Close our database handle, if it was opened. */
|
||||
if (dbp != NULL) {
|
||||
ret_t = dbp->close(dbp, 0);
|
||||
if (ret_t != 0) {
|
||||
fprintf(stderr, "%s database close failed: %s\n",
|
||||
file_name, db_strerror(ret_t));
|
||||
ret = ret_t;
|
||||
}
|
||||
}
|
||||
|
||||
/* Close our environment, if it was opened. */
|
||||
if (envp != NULL) {
|
||||
ret_t = envp->close(envp, 0);
|
||||
if (ret_t != 0) {
|
||||
fprintf(stderr, "environment close failed: %s\n",
|
||||
db_strerror(ret_t));
|
||||
ret = ret_t;
|
||||
}
|
||||
}
|
||||
|
||||
/* Final status message and return. */
|
||||
printf("I'm all done.\n");
|
||||
return (ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/*
|
||||
* A function that performs a series of writes to a
|
||||
* Berkeley DB database. The information written
|
||||
* to the database is largely nonsensical, but the
|
||||
* mechanism of transactional commit/abort and
|
||||
* deadlock detection is illustrated here.
|
||||
*/
|
||||
void *
|
||||
writer_thread(void *args)
|
||||
{
|
||||
static char *key_strings[] = {
|
||||
"key 1", "key 2", "key 3", "key 4", "key 5",
|
||||
"key 6", "key 7", "key 8", "key 9", "key 10"
|
||||
};
|
||||
DB *dbp;
|
||||
DB_ENV *envp;
|
||||
DBT key, value;
|
||||
DB_TXN *txn;
|
||||
int i, j, payload, ret, thread_num;
|
||||
int retry_count, max_retries = 20; /* Max retry on a deadlock */
|
||||
|
||||
dbp = (DB *)args;
|
||||
envp = dbp->get_env(dbp);
|
||||
|
||||
/* Get the thread number */
|
||||
(void)mutex_lock(&thread_num_lock);
|
||||
global_thread_num++;
|
||||
thread_num = global_thread_num;
|
||||
(void)mutex_unlock(&thread_num_lock);
|
||||
|
||||
/* Initialize the random number generator */
|
||||
srand(thread_num);
|
||||
|
||||
/* Write 50 times and then quit */
|
||||
for (i = 0; i < 50; i++) {
|
||||
retry_count = 0; /* Used for deadlock retries */
|
||||
|
||||
/*
|
||||
* Some think it is bad form to loop with a goto statement, but
|
||||
* we do it anyway because it is the simplest and clearest way
|
||||
* to achieve our abort/retry operation.
|
||||
*/
|
||||
retry:
|
||||
/* Begin our transaction. We group multiple writes in
|
||||
* this thread under a single transaction so as to
|
||||
* (1) show that you can atomically perform multiple writes
|
||||
* at a time, and (2) to increase the chances of a
|
||||
* deadlock occurring so that we can observe our
|
||||
* deadlock detection at work.
|
||||
*
|
||||
* Normally we would want to avoid the potential for deadlocks,
|
||||
* so for this workload the correct thing would be to perform our
|
||||
* puts with autocommit. But that would excessively simplify our
|
||||
* example, so we do the "wrong" thing here instead.
|
||||
*/
|
||||
ret = envp->txn_begin(envp, NULL, &txn, 0);
|
||||
if (ret != 0) {
|
||||
envp->err(envp, ret, "txn_begin failed");
|
||||
return ((void *)EXIT_FAILURE);
|
||||
}
|
||||
for (j = 0; j < 10; j++) {
|
||||
/* Set up our key and values DBTs */
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
key.data = key_strings[j];
|
||||
key.size = (u_int32_t)strlen(key_strings[j]) + 1;
|
||||
|
||||
memset(&value, 0, sizeof(DBT));
|
||||
payload = rand() + i;
|
||||
value.data = &payload;
|
||||
value.size = sizeof(int);
|
||||
|
||||
/* Perform the database put. */
|
||||
switch (ret = dbp->put(dbp, txn, &key, &value, 0)) {
|
||||
case 0:
|
||||
break;
|
||||
/*
|
||||
* Our database is configured for sorted duplicates,
|
||||
* so there is a potential for a KEYEXIST error return.
|
||||
* If we get one, simply ignore it and continue on.
|
||||
*
|
||||
* Note that you will see KEYEXIST errors only after you
|
||||
* have run this program at least once.
|
||||
*/
|
||||
case DB_KEYEXIST:
|
||||
printf("Got keyexists.\n");
|
||||
break;
|
||||
/*
|
||||
* Here's where we perform deadlock detection. If
|
||||
* DB_LOCK_DEADLOCK is returned by the put operation,
|
||||
* then this thread has been chosen to break a deadlock.
|
||||
* It must abort its operation, and optionally retry the
|
||||
* put.
|
||||
*/
|
||||
case DB_LOCK_DEADLOCK:
|
||||
/*
|
||||
* First thing that we MUST do is abort the
|
||||
* transaction.
|
||||
*/
|
||||
(void)txn->abort(txn);
|
||||
/*
|
||||
* Now we decide if we want to retry the operation.
|
||||
* If we have retried less than max_retries,
|
||||
* increment the retry count and goto retry.
|
||||
*/
|
||||
if (retry_count < max_retries) {
|
||||
printf("Writer %i: Got DB_LOCK_DEADLOCK.\n",
|
||||
thread_num);
|
||||
printf("Writer %i: Retrying write operation.\n",
|
||||
thread_num);
|
||||
retry_count++;
|
||||
goto retry;
|
||||
}
|
||||
/*
|
||||
* Otherwise, just give up.
|
||||
*/
|
||||
printf("Writer %i: ", thread_num);
|
||||
printf("Got DB_LOCK_DEADLOCK and out of retries.\n");
|
||||
printf("Writer %i: Giving up.\n", thread_num);
|
||||
return ((void *)EXIT_FAILURE);
|
||||
/*
|
||||
* If a generic error occurs, we simply abort the
|
||||
* transaction and exit the thread completely.
|
||||
*/
|
||||
default:
|
||||
envp->err(envp, ret, "db put failed");
|
||||
ret = txn->abort(txn);
|
||||
if (ret != 0)
|
||||
envp->err(envp, ret,
|
||||
"txn abort failed");
|
||||
return ((void *)EXIT_FAILURE);
|
||||
} /** End case statement **/
|
||||
|
||||
} /** End for loop **/
|
||||
|
||||
/*
|
||||
* print the number of records found in the database.
|
||||
* See count_records() for usage information.
|
||||
*/
|
||||
printf("Thread %i. Record count: %i\n", thread_num,
|
||||
count_records(dbp, NULL));
|
||||
|
||||
/*
|
||||
* If all goes well, we can commit the transaction and
|
||||
* exit the thread.
|
||||
*/
|
||||
ret = txn->commit(txn, 0);
|
||||
if (ret != 0) {
|
||||
envp->err(envp, ret, "txn commit failed");
|
||||
return ((void *)EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
return ((void *)EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/*
|
||||
* This simply counts the number of records contained in the
|
||||
* database and returns the result. You can use this function
|
||||
* in three ways:
|
||||
*
|
||||
* First call it with an active txn handle.
|
||||
* Secondly, configure the cursor for uncommitted reads (this
|
||||
* is what the example currently does).
|
||||
* Third, call count_records AFTER the writer has committed
|
||||
* its transaction.
|
||||
*
|
||||
* If you do none of these things, the writer thread will
|
||||
* self-deadlock.
|
||||
*
|
||||
* Note that this function exists only for illustrative purposes.
|
||||
* A more straight-forward way to count the number of records in
|
||||
* a database is to use DB->stat() or DB->stat_print().
|
||||
*/
|
||||
|
||||
int
|
||||
count_records(DB *dbp, DB_TXN *txn)
|
||||
{
|
||||
|
||||
DBT key, value;
|
||||
DBC *cursorp;
|
||||
int count, ret;
|
||||
|
||||
cursorp = NULL;
|
||||
count = 0;
|
||||
|
||||
/* Get the cursor */
|
||||
ret = dbp->cursor(dbp, txn, &cursorp,
|
||||
DB_READ_UNCOMMITTED);
|
||||
if (ret != 0) {
|
||||
dbp->err(dbp, ret,
|
||||
"count_records: cursor open failed.");
|
||||
goto cursor_err;
|
||||
}
|
||||
|
||||
/* Get the key DBT used for the database read */
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
memset(&value, 0, sizeof(DBT));
|
||||
do {
|
||||
ret = cursorp->get(cursorp, &key, &value, DB_NEXT);
|
||||
switch (ret) {
|
||||
case 0:
|
||||
count++;
|
||||
break;
|
||||
case DB_NOTFOUND:
|
||||
break;
|
||||
default:
|
||||
dbp->err(dbp, ret,
|
||||
"Count records unspecified error");
|
||||
goto cursor_err;
|
||||
}
|
||||
} while (ret == 0);
|
||||
|
||||
cursor_err:
|
||||
if (cursorp != NULL) {
|
||||
ret = cursorp->close(cursorp);
|
||||
if (ret != 0) {
|
||||
dbp->err(dbp, ret,
|
||||
"count_records: cursor close failed.");
|
||||
}
|
||||
}
|
||||
|
||||
return (count);
|
||||
}
|
||||
|
||||
/* Open a Berkeley DB database */
|
||||
int
|
||||
open_db(DB **dbpp, const char *progname, const char *file_name,
|
||||
DB_ENV *envp, u_int32_t extra_flags)
|
||||
{
|
||||
int ret;
|
||||
u_int32_t open_flags;
|
||||
DB *dbp;
|
||||
|
||||
/* Initialize the DB handle */
|
||||
ret = db_create(&dbp, envp, 0);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "%s: %s\n", progname,
|
||||
db_strerror(ret));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Point to the memory malloc'd by db_create() */
|
||||
*dbpp = dbp;
|
||||
|
||||
if (extra_flags != 0) {
|
||||
ret = dbp->set_flags(dbp, extra_flags);
|
||||
if (ret != 0) {
|
||||
dbp->err(dbp, ret,
|
||||
"open_db: Attempt to set extra flags failed.");
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
/* Now open the database */
|
||||
open_flags = DB_CREATE | /* Allow database creation */
|
||||
DB_READ_UNCOMMITTED | /* Allow dirty reads */
|
||||
DB_AUTO_COMMIT; /* Allow autocommit */
|
||||
|
||||
ret = dbp->open(dbp, /* Pointer to the database */
|
||||
NULL, /* Txn pointer */
|
||||
file_name, /* File name */
|
||||
NULL, /* Logical db name */
|
||||
DB_BTREE, /* Database type (using btree) */
|
||||
open_flags, /* Open flags */
|
||||
0); /* File mode. Using defaults */
|
||||
if (ret != 0) {
|
||||
dbp->err(dbp, ret, "Database '%s' open failed",
|
||||
file_name);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
451
examples_c/txn_guide/txn_guide_inmemory.c
Normal file
451
examples_c/txn_guide/txn_guide_inmemory.c
Normal file
@@ -0,0 +1,451 @@
|
||||
/* File: txn_guide_inmemory.c */
|
||||
|
||||
/* We assume an ANSI-compatible compiler */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <db.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#define PATHD '\\'
|
||||
extern int getopt(int, char * const *, const char *);
|
||||
extern char *optarg;
|
||||
|
||||
typedef HANDLE thread_t;
|
||||
#define thread_create(thrp, attr, func, arg) \
|
||||
(((*(thrp) = CreateThread(NULL, 0, \
|
||||
(LPTHREAD_START_ROUTINE)(func), (arg), 0, NULL)) == NULL) ? -1 : 0)
|
||||
#define thread_join(thr, statusp) \
|
||||
((WaitForSingleObject((thr), INFINITE) == WAIT_OBJECT_0) && \
|
||||
GetExitCodeThread((thr), (LPDWORD)(statusp)) ? 0 : -1)
|
||||
|
||||
typedef HANDLE mutex_t;
|
||||
#define mutex_init(m, attr) \
|
||||
(((*(m) = CreateMutex(NULL, FALSE, NULL)) != NULL) ? 0 : -1)
|
||||
#define mutex_lock(m) \
|
||||
((WaitForSingleObject(*(m), INFINITE) == WAIT_OBJECT_0) ? 0 : -1)
|
||||
#define mutex_unlock(m) (ReleaseMutex(*(m)) ? 0 : -1)
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#define PATHD '/'
|
||||
|
||||
typedef pthread_t thread_t;
|
||||
#define thread_create(thrp, attr, func, arg) \
|
||||
pthread_create((thrp), (attr), (func), (arg))
|
||||
#define thread_join(thr, statusp) pthread_join((thr), (statusp))
|
||||
|
||||
typedef pthread_mutex_t mutex_t;
|
||||
#define mutex_init(m, attr) pthread_mutex_init((m), (attr))
|
||||
#define mutex_lock(m) pthread_mutex_lock(m)
|
||||
#define mutex_unlock(m) pthread_mutex_unlock(m)
|
||||
#endif
|
||||
|
||||
/* Run 5 writers threads at a time. */
|
||||
#define NUMWRITERS 5
|
||||
|
||||
/*
|
||||
* Printing of a thread_t is implementation-specific, so we
|
||||
* create our own thread IDs for reporting purposes.
|
||||
*/
|
||||
int global_thread_num;
|
||||
mutex_t thread_num_lock;
|
||||
|
||||
/* Forward declarations */
|
||||
int count_records(DB *, DB_TXN *);
|
||||
int open_db(DB **, const char *, const char *, DB_ENV *, u_int32_t);
|
||||
void *writer_thread(void *);
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
/* Initialize our handles */
|
||||
DB *dbp = NULL;
|
||||
DB_ENV *envp = NULL;
|
||||
|
||||
thread_t writer_threads[NUMWRITERS];
|
||||
int i, ret, ret_t;
|
||||
u_int32_t env_flags;
|
||||
|
||||
/* Application name */
|
||||
const char *prog_name = "txn_guide_inmemory";
|
||||
|
||||
/* Create the environment */
|
||||
ret = db_env_create(&envp, 0);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Error creating environment handle: %s\n",
|
||||
db_strerror(ret));
|
||||
goto err;
|
||||
}
|
||||
|
||||
env_flags =
|
||||
DB_CREATE | /* Create the environment if it does not exist */
|
||||
DB_INIT_LOCK | /* Initialize the locking subsystem */
|
||||
DB_INIT_LOG | /* Initialize the logging subsystem */
|
||||
DB_INIT_TXN | /* Initialize the transactional subsystem. This
|
||||
* also turns on logging. */
|
||||
DB_INIT_MPOOL | /* Initialize the memory pool (in-memory cache) */
|
||||
DB_PRIVATE | /* Region files are backed by heap memory. */
|
||||
DB_THREAD; /* Cause the environment to be free-threaded */
|
||||
|
||||
/* Specify in-memory logging */
|
||||
ret = envp->log_set_config(envp, DB_LOG_IN_MEMORY, 1);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Error setting log subsystem to in-memory: %s\n",
|
||||
db_strerror(ret));
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Specify the size of the in-memory log buffer.
|
||||
*/
|
||||
ret = envp->set_lg_bsize(envp, 10 * 1024 * 1024);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Error increasing the log buffer size: %s\n",
|
||||
db_strerror(ret));
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Specify the size of the in-memory cache.
|
||||
*/
|
||||
ret = envp->set_cachesize(envp, 0, 10 * 1024 * 1024, 1);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Error increasing the cache size: %s\n",
|
||||
db_strerror(ret));
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Indicate that we want db to perform lock detection internally.
|
||||
* Also indicate that the transaction with the fewest number of
|
||||
* write locks will receive the deadlock notification in
|
||||
* the event of a deadlock.
|
||||
*/
|
||||
ret = envp->set_lk_detect(envp, DB_LOCK_MINWRITE);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Error setting lock detect: %s\n",
|
||||
db_strerror(ret));
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Now actually open the environment */
|
||||
ret = envp->open(envp, NULL, env_flags, 0);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Error opening environment: %s\n",
|
||||
db_strerror(ret));
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we had utility threads (for running checkpoints or
|
||||
* deadlock detection, for example) we would spawn those
|
||||
* here. However, for a simple example such as this,
|
||||
* that is not required.
|
||||
*/
|
||||
|
||||
/* Open the database */
|
||||
ret = open_db(&dbp, prog_name, NULL,
|
||||
envp, DB_DUPSORT);
|
||||
if (ret != 0)
|
||||
goto err;
|
||||
|
||||
/* Initialize a mutex. Used to help provide thread ids. */
|
||||
(void)mutex_init(&thread_num_lock, NULL);
|
||||
|
||||
/* Start the writer threads. */
|
||||
for (i = 0; i < NUMWRITERS; i++)
|
||||
(void)thread_create(
|
||||
&writer_threads[i], NULL, writer_thread, (void *)dbp);
|
||||
|
||||
/* Join the writers */
|
||||
for (i = 0; i < NUMWRITERS; i++)
|
||||
(void)thread_join(writer_threads[i], NULL);
|
||||
|
||||
err:
|
||||
/* Close our database handle, if it was opened. */
|
||||
if (dbp != NULL) {
|
||||
ret_t = dbp->close(dbp, 0);
|
||||
if (ret_t != 0) {
|
||||
fprintf(stderr, "%s database close failed.\n",
|
||||
db_strerror(ret_t));
|
||||
ret = ret_t;
|
||||
}
|
||||
}
|
||||
|
||||
/* Close our environment, if it was opened. */
|
||||
if (envp != NULL) {
|
||||
ret_t = envp->close(envp, 0);
|
||||
if (ret_t != 0) {
|
||||
fprintf(stderr, "environment close failed: %s\n",
|
||||
db_strerror(ret_t));
|
||||
ret = ret_t;
|
||||
}
|
||||
}
|
||||
|
||||
/* Final status message and return. */
|
||||
printf("I'm all done.\n");
|
||||
return (ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/*
|
||||
* A function that performs a series of writes to a
|
||||
* Berkeley DB database. The information written
|
||||
* to the database is largely nonsensical, but the
|
||||
* mechanism of transactional commit/abort and
|
||||
* deadlock detection is illustrated here.
|
||||
*/
|
||||
void *
|
||||
writer_thread(void *args)
|
||||
{
|
||||
static char *key_strings[] = {
|
||||
"key 1", "key 2", "key 3", "key 4", "key 5",
|
||||
"key 6", "key 7", "key 8", "key 9", "key 10"
|
||||
};
|
||||
DB *dbp;
|
||||
DB_ENV *envp;
|
||||
DBT key, value;
|
||||
DB_TXN *txn;
|
||||
int i, j, payload, ret, thread_num;
|
||||
int retry_count, max_retries = 20; /* Max retry on a deadlock */
|
||||
|
||||
dbp = (DB *)args;
|
||||
envp = dbp->get_env(dbp);
|
||||
|
||||
/* Get the thread number */
|
||||
(void)mutex_lock(&thread_num_lock);
|
||||
global_thread_num++;
|
||||
thread_num = global_thread_num;
|
||||
(void)mutex_unlock(&thread_num_lock);
|
||||
|
||||
/* Initialize the random number generator */
|
||||
srand(thread_num);
|
||||
|
||||
/* Write 50 times and then quit */
|
||||
for (i = 0; i < 50; i++) {
|
||||
retry_count = 0; /* Used for deadlock retries */
|
||||
|
||||
/*
|
||||
* Some think it is bad form to loop with a goto statement, but
|
||||
* we do it anyway because it is the simplest and clearest way
|
||||
* to achieve our abort/retry operation.
|
||||
*/
|
||||
retry:
|
||||
/* Begin our transaction. We group multiple writes in
|
||||
* this thread under a single transaction so as to
|
||||
* (1) show that you can atomically perform multiple writes
|
||||
* at a time, and (2) to increase the chances of a
|
||||
* deadlock occurring so that we can observe our
|
||||
* deadlock detection at work.
|
||||
*
|
||||
* Normally we would want to avoid the potential for deadlocks,
|
||||
* so for this workload the correct thing would be to perform our
|
||||
* puts with autocommit. But that would excessively simplify our
|
||||
* example, so we do the "wrong" thing here instead.
|
||||
*/
|
||||
ret = envp->txn_begin(envp, NULL, &txn, 0);
|
||||
if (ret != 0) {
|
||||
envp->err(envp, ret, "txn_begin failed");
|
||||
return ((void *)EXIT_FAILURE);
|
||||
}
|
||||
for (j = 0; j < 10; j++) {
|
||||
/* Set up our key and values DBTs */
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
key.data = key_strings[j];
|
||||
key.size = (u_int32_t)strlen(key_strings[j]) + 1;
|
||||
|
||||
memset(&value, 0, sizeof(DBT));
|
||||
payload = rand() + i;
|
||||
value.data = &payload;
|
||||
value.size = sizeof(int);
|
||||
|
||||
/* Perform the database put. */
|
||||
switch (ret = dbp->put(dbp, txn, &key, &value, 0)) {
|
||||
case 0:
|
||||
break;
|
||||
|
||||
/*
|
||||
* Here's where we perform deadlock detection. If
|
||||
* DB_LOCK_DEADLOCK is returned by the put operation,
|
||||
* then this thread has been chosen to break a deadlock.
|
||||
* It must abort its operation, and optionally retry the
|
||||
* put.
|
||||
*/
|
||||
case DB_LOCK_DEADLOCK:
|
||||
/*
|
||||
* First thing that we MUST do is abort the
|
||||
* transaction.
|
||||
*/
|
||||
(void)txn->abort(txn);
|
||||
/*
|
||||
* Now we decide if we want to retry the operation.
|
||||
* If we have retried less than max_retries,
|
||||
* increment the retry count and goto retry.
|
||||
*/
|
||||
if (retry_count < max_retries) {
|
||||
printf("Writer %i: Got DB_LOCK_DEADLOCK.\n",
|
||||
thread_num);
|
||||
printf("Writer %i: Retrying write operation.\n",
|
||||
thread_num);
|
||||
retry_count++;
|
||||
goto retry;
|
||||
}
|
||||
/*
|
||||
* Otherwise, just give up.
|
||||
*/
|
||||
printf("Writer %i: ", thread_num);
|
||||
printf("Got DB_LOCK_DEADLOCK and out of retries.\n");
|
||||
printf("Writer %i: Giving up.\n", thread_num);
|
||||
return ((void *)EXIT_FAILURE);
|
||||
/*
|
||||
* If a generic error occurs, we simply abort the
|
||||
* transaction and exit the thread completely.
|
||||
*/
|
||||
default:
|
||||
envp->err(envp, ret, "db put failed");
|
||||
ret = txn->abort(txn);
|
||||
if (ret != 0)
|
||||
envp->err(envp, ret, "txn abort failed");
|
||||
return ((void *)EXIT_FAILURE);
|
||||
} /** End case statement **/
|
||||
|
||||
} /** End for loop **/
|
||||
|
||||
/*
|
||||
* print the number of records found in the database.
|
||||
* See count_records() for usage information.
|
||||
*/
|
||||
printf("Thread %i. Record count: %i\n", thread_num,
|
||||
count_records(dbp, txn));
|
||||
|
||||
/*
|
||||
* If all goes well, we can commit the transaction and
|
||||
* exit the thread.
|
||||
*/
|
||||
ret = txn->commit(txn, 0);
|
||||
if (ret != 0) {
|
||||
envp->err(envp, ret, "txn commit failed");
|
||||
return ((void *)EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
return ((void *)EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/*
|
||||
* This simply counts the number of records contained in the
|
||||
* database and returns the result. You can use this function
|
||||
* in three ways:
|
||||
*
|
||||
* First call it with an active txn handle (this is what the
|
||||
* example currently does).
|
||||
*
|
||||
* Secondly, configure the cursor for uncommitted reads.
|
||||
*
|
||||
* Third, call count_records AFTER the writer has committed
|
||||
* its transaction.
|
||||
*
|
||||
* If you do none of these things, the writer thread will
|
||||
* self-deadlock.
|
||||
*
|
||||
* Note that this function exists only for illustrative purposes.
|
||||
* A more straight-forward way to count the number of records in
|
||||
* a database is to use DB->stat() or DB->stat_print().
|
||||
*/
|
||||
|
||||
int
|
||||
count_records(DB *dbp, DB_TXN *txn)
|
||||
{
|
||||
DBT key, value;
|
||||
DBC *cursorp;
|
||||
int count, ret;
|
||||
|
||||
cursorp = NULL;
|
||||
count = 0;
|
||||
|
||||
/* Get the cursor */
|
||||
ret = dbp->cursor(dbp, txn, &cursorp, 0);
|
||||
if (ret != 0) {
|
||||
dbp->err(dbp, ret,
|
||||
"count_records: cursor open failed.");
|
||||
goto cursor_err;
|
||||
}
|
||||
|
||||
/* Get the key DBT used for the database read */
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
memset(&value, 0, sizeof(DBT));
|
||||
do {
|
||||
ret = cursorp->get(cursorp, &key, &value, DB_NEXT);
|
||||
switch (ret) {
|
||||
case 0:
|
||||
count++;
|
||||
break;
|
||||
case DB_NOTFOUND:
|
||||
break;
|
||||
default:
|
||||
dbp->err(dbp, ret,
|
||||
"Count records unspecified error");
|
||||
goto cursor_err;
|
||||
}
|
||||
} while (ret == 0);
|
||||
|
||||
cursor_err:
|
||||
if (cursorp != NULL) {
|
||||
ret = cursorp->close(cursorp);
|
||||
if (ret != 0) {
|
||||
dbp->err(dbp, ret,
|
||||
"count_records: cursor close failed.");
|
||||
}
|
||||
}
|
||||
|
||||
return (count);
|
||||
}
|
||||
|
||||
/* Open a Berkeley DB database */
|
||||
int
|
||||
open_db(DB **dbpp, const char *progname, const char *file_name,
|
||||
DB_ENV *envp, u_int32_t extra_flags)
|
||||
{
|
||||
int ret;
|
||||
u_int32_t open_flags;
|
||||
DB *dbp;
|
||||
|
||||
/* Initialize the DB handle */
|
||||
ret = db_create(&dbp, envp, 0);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "%s: %s\n", progname,
|
||||
db_strerror(ret));
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Point to the memory malloc'd by db_create() */
|
||||
*dbpp = dbp;
|
||||
|
||||
if (extra_flags != 0) {
|
||||
ret = dbp->set_flags(dbp, extra_flags);
|
||||
if (ret != 0) {
|
||||
dbp->err(dbp, ret,
|
||||
"open_db: Attempt to set extra flags failed.");
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
/* Now open the database */
|
||||
open_flags = DB_CREATE | /* Allow database creation */
|
||||
DB_THREAD |
|
||||
DB_AUTO_COMMIT; /* Allow autocommit */
|
||||
|
||||
ret = dbp->open(dbp, /* Pointer to the database */
|
||||
NULL, /* Txn pointer */
|
||||
file_name, /* File name */
|
||||
NULL, /* Logical db name */
|
||||
DB_BTREE, /* Database type (using btree) */
|
||||
open_flags, /* Open flags */
|
||||
0); /* File mode. Using defaults */
|
||||
|
||||
if (ret != 0) {
|
||||
dbp->err(dbp, ret, "Database open failed");
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
Reference in New Issue
Block a user