1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2001,2008 Oracle.  All rights reserved.
5 *
6 * $Id: db_truncate.c,v 12.30 2008/03/18 17:13:16 mbrey Exp $
7 */
8
9#include "db_config.h"
10
11#include "db_int.h"
12#include "dbinc/db_page.h"
13#include "dbinc/btree.h"
14#include "dbinc/hash.h"
15#include "dbinc/qam.h"
16#include "dbinc/lock.h"
17#include "dbinc/log.h"
18#include "dbinc/txn.h"
19
20static int __db_cursor_check __P((DB *));
21
22/*
23 * __db_truncate_pp
24 *	DB->truncate pre/post processing.
25 *
26 * PUBLIC: int __db_truncate_pp __P((DB *, DB_TXN *, u_int32_t *, u_int32_t));
27 */
28int
29__db_truncate_pp(dbp, txn, countp, flags)
30	DB *dbp;
31	DB_TXN *txn;
32	u_int32_t *countp, flags;
33{
34	DB_THREAD_INFO *ip;
35	ENV *env;
36	int handle_check, ret, t_ret, txn_local;
37
38	env = dbp->env;
39	handle_check = txn_local = 0;
40
41	STRIP_AUTO_COMMIT(flags);
42
43	/* Check for invalid flags. */
44	if (F_ISSET(dbp, DB_AM_SECONDARY)) {
45		__db_errx(env, "DB->truncate forbidden on secondary indices");
46		return (EINVAL);
47	}
48	if ((ret = __db_fchk(env, "DB->truncate", flags, 0)) != 0)
49		return (ret);
50
51	ENV_ENTER(env, ip);
52
53	/*
54	 * Make sure there are no active cursors on this db.  Since we drop
55	 * pages we cannot really adjust cursors.
56	 */
57	if ((ret = __db_cursor_check(dbp)) != 0) {
58		__db_errx(env,
59		     "DB->truncate not permitted with active cursors");
60		goto err;
61	}
62
63#ifdef CONFIG_TEST
64	if (IS_REP_MASTER(env))
65		DB_TEST_WAIT(env, env->test_check);
66#endif
67	/* Check for replication block. */
68	handle_check = IS_ENV_REPLICATED(env);
69	if (handle_check &&
70	    (ret = __db_rep_enter(dbp, 1, 0, txn != NULL)) != 0) {
71		handle_check = 0;
72		goto err;
73	}
74
75	/*
76	 * Check for changes to a read-only database.  This must be after the
77	 * replication block so that we cannot race master/client state changes.
78	 */
79	if (DB_IS_READONLY(dbp)) {
80		ret = __db_rdonly(env, "DB->truncate");
81		goto err;
82	}
83
84	/*
85	 * Create local transaction as necessary, check for consistent
86	 * transaction usage.
87	 */
88	if (IS_DB_AUTO_COMMIT(dbp, txn)) {
89		if ((ret = __txn_begin(env, ip, NULL, &txn, 0)) != 0)
90			goto err;
91		txn_local = 1;
92	}
93
94	/* Check for consistent transaction usage. */
95	if ((ret = __db_check_txn(dbp, txn, DB_LOCK_INVALIDID, 0)) != 0)
96		goto err;
97
98	ret = __db_truncate(dbp, ip, txn, countp);
99
100err:	if (txn_local &&
101	    (t_ret = __db_txn_auto_resolve(env, txn, 0, ret)) && ret == 0)
102		ret = t_ret;
103
104	/* Release replication block. */
105	if (handle_check && (t_ret = __env_db_rep_exit(env)) != 0 && ret == 0)
106		ret = t_ret;
107
108	ENV_LEAVE(env, ip);
109	return (ret);
110}
111
112/*
113 * __db_truncate
114 *	DB->truncate.
115 *
116 * PUBLIC: int __db_truncate __P((DB *, DB_THREAD_INFO *, DB_TXN *,
117 * PUBLIC:     u_int32_t *));
118 */
119int
120__db_truncate(dbp, ip, txn, countp)
121	DB *dbp;
122	DB_THREAD_INFO *ip;
123	DB_TXN *txn;
124	u_int32_t *countp;
125{
126	DB *sdbp;
127	DBC *dbc;
128	ENV *env;
129	u_int32_t scount;
130	int ret, t_ret;
131
132	env = dbp->env;
133	dbc = NULL;
134	ret = 0;
135
136	/*
137	 * Run through all secondaries and truncate them first.  The count
138	 * returned is the count of the primary only.  QUEUE uses normal
139	 * processing to truncate so it will update the secondaries normally.
140	 */
141	if (dbp->type != DB_QUEUE && LIST_FIRST(&dbp->s_secondaries) != NULL) {
142		if ((ret = __db_s_first(dbp, &sdbp)) != 0)
143			return (ret);
144		for (; sdbp != NULL && ret == 0; ret = __db_s_next(&sdbp, txn))
145			if ((ret = __db_truncate(sdbp, ip, txn, &scount)) != 0)
146				break;
147		if (sdbp != NULL)
148			(void)__db_s_done(sdbp, txn);
149		if (ret != 0)
150			return (ret);
151	}
152
153	DB_TEST_RECOVERY(dbp, DB_TEST_PREDESTROY, ret, NULL);
154
155	/* Acquire a cursor. */
156	if ((ret = __db_cursor(dbp, ip, txn, &dbc, 0)) != 0)
157		return (ret);
158
159	DEBUG_LWRITE(dbc, txn, "DB->truncate", NULL, NULL, 0);
160
161	switch (dbp->type) {
162	case DB_BTREE:
163	case DB_RECNO:
164		ret = __bam_truncate(dbc, countp);
165		break;
166	case DB_HASH:
167		ret = __ham_truncate(dbc, countp);
168		break;
169	case DB_QUEUE:
170		ret = __qam_truncate(dbc, countp);
171		break;
172	case DB_UNKNOWN:
173	default:
174		ret = __db_unknown_type(env, "DB->truncate", dbp->type);
175		break;
176	}
177
178	/* Discard the cursor. */
179	if (dbc != NULL && (t_ret = __dbc_close(dbc)) != 0 && ret == 0)
180		ret = t_ret;
181
182	DB_TEST_RECOVERY(dbp, DB_TEST_POSTDESTROY, ret, NULL);
183
184DB_TEST_RECOVERY_LABEL
185
186	return (ret);
187}
188
189/*
190 * __db_cursor_check --
191 *	See if there are any active cursors on this db.
192 */
193static int
194__db_cursor_check(dbp)
195	DB *dbp;
196{
197	DB *ldbp;
198	DBC *dbc;
199	ENV *env;
200	int found;
201
202	env = dbp->env;
203
204	MUTEX_LOCK(env, env->mtx_dblist);
205	FIND_FIRST_DB_MATCH(env, dbp, ldbp);
206	for (found = 0;
207	    !found && ldbp != NULL && ldbp->adj_fileid == dbp->adj_fileid;
208	    ldbp = TAILQ_NEXT(ldbp, dblistlinks)) {
209		MUTEX_LOCK(env, dbp->mutex);
210		TAILQ_FOREACH(dbc, &ldbp->active_queue, links)
211			if (IS_INITIALIZED(dbc)) {
212				found = 1;
213				break;
214			}
215		MUTEX_UNLOCK(env, dbp->mutex);
216	}
217	MUTEX_UNLOCK(env, env->mtx_dblist);
218
219	return (found ? EINVAL : 0);
220}
221