1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2005,2008 Oracle.  All rights reserved.
5 *
6 * $Id: DbRecord.c,v 1.16 2008/01/08 20:58:23 bostic Exp $
7 */
8
9#include "csv.h"
10#include "csv_local.h"
11#include "csv_extern.h"
12
13static int	DbRecord_field(DbRecord *, u_int, void *, datatype);
14static int	DbRecord_search_field(DbField *, char *, OPERATOR);
15static int	DbRecord_search_recno(char *, OPERATOR);
16
17/*
18 * DbRecord_print --
19 *	Display a DbRecord structure.
20 */
21void
22DbRecord_print(DbRecord *recordp, FILE *fp)
23{
24	DbField *f;
25	void *faddr;
26
27	if (fp == NULL)
28		fp = stdout;
29
30	fprintf(fp, "Record: %lu:\n", (u_long)recordp->recno);
31	for (f = fieldlist; f->name != NULL; ++f) {
32		faddr = (u_int8_t *)recordp + f->offset;
33		fprintf(fp, "\t%s: ", f->name);
34		switch (f->type) {
35		case NOTSET:
36			/* NOTREACHED */
37			abort();
38			break;
39		case DOUBLE:
40			fprintf(fp, "%f\n", *(double *)faddr);
41			break;
42		case STRING:
43			fprintf(fp, "%s\n", *(char **)faddr);
44			break;
45		case UNSIGNED_LONG:
46			fprintf(fp, "%lu\n", *(u_long *)faddr);
47			break;
48		}
49	}
50}
51
52/*
53 * DbRecord_read --
54 *	Read a specific record from the database.
55 */
56int
57DbRecord_read(u_long recno_ulong, DbRecord *recordp)
58{
59	DBT key, data;
60	u_int32_t recno;
61	int ret;
62
63	/*
64	 * XXX
65	 * This code assumes a record number (typed as u_int32_t) is the same
66	 * size as an unsigned long, and there's no reason to believe that.
67	 */
68	recno = recno_ulong;
69
70	/*
71	 * Retrieve the requested record from the primary database.  Have
72	 * Berkeley DB allocate memory for us, keeps the DB handle thread
73	 * safe.
74	 *
75	 * We have the Berkeley DB library allocate memory for the record,
76	 * which we own and must eventually free.  The reason is so we can
77	 * have the string fields in the structure point into the actual
78	 * record, rather than allocating structure local memory to hold them
79	 * and copying them out of the record.
80	 */
81	memset(&key, 0, sizeof(key));
82	memset(&data, 0, sizeof(data));
83	key.data = &recno;
84	key.size = sizeof(recno);
85	data.flags = DB_DBT_MALLOC;
86	if ((ret = db->get(db, NULL, &key, &data, 0)) != 0)
87		return (ret);
88
89	if ((ret = DbRecord_init(&key, &data, recordp)) != 0)
90		return (ret);
91
92	return (0);
93}
94
95/*
96 * DbRecord_discard --
97 *	Discard a DbRecord structure.
98 */
99int
100DbRecord_discard(DbRecord *recordp)
101{
102	/* Free the allocated memory. */
103	free(recordp->raw);
104	recordp->raw = NULL;
105
106	return (0);
107}
108
109/*
110 * DbRecord_init --
111 *	Fill in a DbRecord from the database key/data pair.
112 */
113int
114DbRecord_init(const DBT *key, const DBT *data, DbRecord *recordp)
115{
116	DbField *f;
117	u_int32_t skip;
118	void *faddr;
119
120	/* Initialize the structure (get the pre-set index values). */
121	*recordp = DbRecord_base;
122
123	/* Fill in the ID and version. */
124	memcpy(&recordp->recno, key->data, sizeof(u_int32_t));
125	memcpy(&recordp->version,
126	    (u_int32_t *)data->data + 1, sizeof(u_int32_t));
127
128	/* Set up the record references. */
129	recordp->raw = data->data;
130	recordp->offset = (u_int32_t *)data->data + 1;
131	skip = (recordp->field_count + 2) * sizeof(u_int32_t);
132	recordp->record = (u_int8_t *)data->data + skip;
133	recordp->record_len = data->size - skip;
134
135	for (f = fieldlist; f->name != NULL; ++f) {
136		faddr = (u_int8_t *)recordp + f->offset;
137		if (DbRecord_field(
138		    recordp, f->fieldno, faddr, f->type) != 0)
139			return (1);
140	}
141	return (0);
142}
143
144/*
145 * DbRecord_field --
146 *	Fill in an individual field of the DbRecord.
147 */
148static int
149DbRecord_field(
150    DbRecord *recordp, u_int field, void *addr, datatype type)
151{
152	size_t len;
153	char number_buf[20];
154
155	/*
156	 * The offset table is 0-based, the field numbers are 1-based.
157	 * Correct.
158	 */
159	--field;
160
161	switch (type) {
162	case NOTSET:
163		/* NOTREACHED */
164		abort();
165		break;
166	case STRING:
167		*((u_char **)addr) = recordp->record + recordp->offset[field];
168		recordp->record[recordp->offset[field] +
169		    OFFSET_LEN(recordp->offset, field)] = '\0';
170		break;
171	case DOUBLE:
172	case UNSIGNED_LONG:
173		/* This shouldn't be possible -- 2^32 is only 10 digits. */
174		len = OFFSET_LEN(recordp->offset, field);
175		if (len > sizeof(number_buf) - 1) {
176			dbenv->errx(dbenv,
177    "record %lu field %lu: numeric field is %lu bytes and too large to copy",
178			    recordp->recno, field, (u_long)len);
179			return (1);
180		}
181		memcpy(number_buf,
182		    recordp->record + recordp->offset[field], len);
183		number_buf[len] = '\0';
184
185		if (type == DOUBLE) {
186			if (len == 0)
187				*(double *)addr = 0;
188			else if (strtod_err(number_buf, (double *)addr) != 0)
189				goto fmt_err;
190		} else
191			if (len == 0)
192				*(u_long *)addr = 0;
193			else if (strtoul_err(number_buf, (u_long *)addr) != 0) {
194fmt_err:			dbenv->errx(dbenv,
195				    "record %lu: numeric field %u error: %s",
196				    recordp->recno, field, number_buf);
197				return (1);
198			}
199		break;
200	}
201	return (0);
202}
203
204/*
205 * DbRecord_search_field_name --
206 *	Search, looking for a record by field name.
207 */
208int
209DbRecord_search_field_name(char *field, char *value, OPERATOR op)
210{
211	DbField *f;
212
213	for (f = fieldlist; f->name != NULL; ++f)
214		if (strcasecmp(field, f->name) == 0)
215			return (DbRecord_search_field(f, value, op));
216
217	/* Record numbers aren't handled as fields. */
218	if (strcasecmp(field, "id") == 0)
219		return (DbRecord_search_recno(value, op));
220
221	dbenv->errx(dbenv, "unknown field name: %s", field);
222	return (1);
223}
224
225/*
226 * DbRecord_search_field_number --
227 *	Search, looking for a record by field number.
228 */
229int
230DbRecord_search_field_number(u_int32_t fieldno, char *value, OPERATOR op)
231{
232	DbField *f;
233
234	for (f = fieldlist; f->name != NULL; ++f)
235		if (fieldno == f->fieldno)
236			return (DbRecord_search_field(f, value, op));
237
238	dbenv->errx(dbenv, "field number %lu not configured", (u_long)fieldno);
239	return (1);
240}
241
242/*
243 * DbRecord_search_recno --
244 *	Search, looking for a record by record number.
245 */
246static int
247DbRecord_search_recno(char *value, OPERATOR op)
248{
249	DBC *dbc;
250	DbRecord record;
251	DBT key, data;
252	u_int32_t recno;
253	u_long recno_ulong;
254	int ret;
255
256	/*
257	 * XXX
258	 * This code assumes a record number (typed as u_int32_t) is the same
259	 * size as an unsigned long, and there's no reason to believe that.
260	 */
261	if (strtoul_err(value, &recno_ulong) != 0)
262		return (1);
263
264	memset(&key, 0, sizeof(key));
265	memset(&data, 0, sizeof(data));
266	key.data = &recno;
267	key.size = sizeof(recno);
268
269	if ((ret = db->cursor(db, NULL, &dbc, 0)) != 0)
270		return (ret);
271
272	/*
273	 * Retrieve the first record that interests us.  The range depends on
274	 * the operator:
275	 *
276	 *	~		error
277	 *	!=		beginning to end
278	 *	<		beginning to first match
279	 *	<=		beginning to last match
280	 *	=		first match to last match
281	 *	>		record after last match to end
282	 *	>=		first match to end
283	 */
284	if (op == LT || op == LTEQ || op == NEQ || op == WC || op == NWC)
285		recno = 1;
286	else if (op == WC || op == NWC) {
287		dbenv->errx(dbenv,
288		    "wildcard operator only supported for string fields");
289		return (1);
290	} else {
291		recno = recno_ulong;
292		if (op == GT)
293			++recno;
294	}
295	if ((ret = dbc->c_get(dbc, &key, &data, DB_SET)) != 0)
296		goto err;
297
298	for (;;) {
299		if ((ret = DbRecord_init(&key, &data, &record)) != 0)
300			break;
301		if (field_cmp_ulong(&record.recno, &recno_ulong, op))
302			DbRecord_print(&record, NULL);
303		else
304			if (op == LT || op == LTEQ || op == EQ)
305				break;
306		if ((ret = dbc->c_get(dbc, &key, &data, DB_NEXT)) != 0)
307			break;
308	}
309
310err:	return (ret == DB_NOTFOUND ? 0 : ret);
311}
312
313/*
314 * DbRecord_search_field --
315 *	Search, looking for a record by field.
316 */
317static int
318DbRecord_search_field(DbField *f, char *value, OPERATOR op)
319{
320#ifdef HAVE_WILDCARD_SUPPORT
321	regex_t preq;
322#endif
323	DBC *dbc;
324	DbRecord record;
325	DBT key, data, pkey;
326	double value_double;
327	u_long value_ulong;
328	u_int32_t cursor_flags;
329	int ret, t_ret;
330	int (*cmp)(void *, void *, OPERATOR);
331	void *faddr, *valuep;
332
333	dbc = NULL;
334	memset(&key, 0, sizeof(key));
335	memset(&pkey, 0, sizeof(pkey));
336	memset(&data, 0, sizeof(data));
337
338	/*
339	 * Initialize the comparison function, crack the value.  Wild cards
340	 * are always strings, otherwise we follow the field type.
341	 */
342	if (op == WC || op == NWC) {
343#ifdef HAVE_WILDCARD_SUPPORT
344		if (f->type != STRING) {
345			dbenv->errx(dbenv,
346		    "wildcard operator only supported for string fields");
347			return (1);
348		}
349		if (regcomp(&preq, value, 0) != 0) {
350			dbenv->errx(dbenv, "regcomp of pattern failed");
351			return (1);
352		}
353		valuep = &preq;
354		cmp = field_cmp_re;
355#else
356		dbenv->errx(dbenv,
357		    "wildcard operators not supported in this build");
358		return (1);
359#endif
360	} else
361		switch (f->type) {
362		case DOUBLE:
363			if (strtod_err(value, &value_double) != 0)
364				return (1);
365			cmp = field_cmp_double;
366			valuep = &value_double;
367			key.size = sizeof(double);
368			break;
369		case STRING:
370			valuep = value;
371			cmp = field_cmp_string;
372			key.size = strlen(value);
373			break;
374		case UNSIGNED_LONG:
375			if (strtoul_err(value, &value_ulong) != 0)
376				return (1);
377			cmp = field_cmp_ulong;
378			valuep = &value_ulong;
379			key.size = sizeof(u_long);
380			break;
381		default:
382		case NOTSET:
383			abort();
384			/* NOTREACHED */
385		}
386
387	/*
388	 * Retrieve the first record that interests us.  The range depends on
389	 * the operator:
390	 *
391	 *	~		beginning to end
392	 *	!=		beginning to end
393	 *	<		beginning to first match
394	 *	<=		beginning to last match
395	 *	=		first match to last match
396	 *	>		record after last match to end
397	 *	>=		first match to end
398	 *
399	 * If we have a secondary, set a cursor in the secondary, else set the
400	 * cursor to the beginning of the primary.
401	 *
402	 * XXX
403	 * If the wildcard string has a leading non-magic character we should
404	 * be able to do a range search instead of a full-database search.
405	 *
406	 * Step through records to the first non-match or to the end of the
407	 * database, depending on the operation.  If the comparison function
408	 * returns success for a key/data pair, print the pair.
409	 */
410	if (f->secondary == NULL || op == NEQ || op == WC || op == NWC) {
411		if ((ret = db->cursor(db, NULL, &dbc, 0)) != 0)
412			goto err;
413		while ((ret = dbc->c_get(dbc, &key, &data, DB_NEXT)) == 0) {
414			if ((ret = DbRecord_init(&key, &data, &record)) != 0)
415				break;
416			faddr = (u_int8_t *)&record + f->offset;
417			if (cmp(faddr, valuep, op))
418				DbRecord_print(&record, NULL);
419			else
420				if (op == EQ || op == LT || op == LTEQ)
421					break;
422		}
423	} else {
424		if ((ret =
425		    f->secondary->cursor(f->secondary, NULL, &dbc, 0)) != 0)
426			goto err;
427		key.data = valuep;
428		cursor_flags = op == LT || op == LTEQ ? DB_FIRST : DB_SET_RANGE;
429		if ((ret =
430		    dbc->c_pget(dbc, &key, &pkey, &data, cursor_flags)) != 0)
431			goto done;
432		if (op == GT) {
433			while ((ret = dbc->c_pget(
434			    dbc, &key, &pkey, &data, DB_NEXT)) == 0) {
435				if ((ret =
436				    DbRecord_init(&pkey, &data, &record)) != 0)
437					break;
438				faddr = (u_int8_t *)&record + f->offset;
439				if (cmp(faddr, valuep, op) != 0)
440					break;
441			}
442			if (ret != 0)
443				goto done;
444		}
445		do {
446			if ((ret = DbRecord_init(&pkey, &data, &record)) != 0)
447				break;
448			faddr = (u_int8_t *)&record + f->offset;
449			if (cmp(faddr, valuep, op))
450				DbRecord_print(&record, NULL);
451			else
452				if (op == EQ || op == LT || op == LTEQ)
453					break;
454		} while ((ret =
455		    dbc->c_pget(dbc, &key, &pkey, &data, DB_NEXT)) == 0);
456	}
457
458done:	if (ret == DB_NOTFOUND)
459		ret = 0;
460
461err:	if (dbc != NULL && (t_ret = dbc->c_close(dbc)) != 0 && ret == 0)
462		ret = t_ret;
463
464#ifdef HAVE_WILDCARD_SUPPORT
465	if (op == WC || op == NWC)
466		regfree(&preq);
467#endif
468
469	return (ret);
470}
471