1 /*-
2  * Copyright (c) 2001, 2020 Oracle and/or its affiliates.  All rights reserved.
3  *
4  * See the file LICENSE for license information.
5  *
6  * $Id$
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/heap.h"
16 #include "dbinc/qam.h"
17 #include "dbinc/lock.h"
18 #include "dbinc/partition.h"
19 #include "dbinc/txn.h"
20 
21 static int __db_cursor_check_func
22     __P((DBC *, DBC *, u_int32_t *, db_pgno_t, u_int32_t, void *));
23 static int __db_cursor_check __P((DB *));
24 
25 /*
26  * __db_truncate_pp
27  *	DB->truncate pre/post processing.
28  *
29  *	countp can be NULL.
30 
31  * PUBLIC: int __db_truncate_pp __P((DB *, DB_TXN *, u_int32_t *, u_int32_t));
32  */
33 int
__db_truncate_pp(dbp,txn,countp,flags)34 __db_truncate_pp(dbp, txn, countp, flags)
35 	DB *dbp;
36 	DB_TXN *txn;
37 	u_int32_t *countp, flags;
38 {
39 	DB_THREAD_INFO *ip;
40 	ENV *env;
41 	int handle_check, ret, t_ret, txn_local;
42 #ifdef HAVE_SLICES
43 	u_int32_t slice_txn_flags;
44 #endif
45 
46 	env = dbp->env;
47 	handle_check = txn_local = 0;
48 #ifdef HAVE_SLICES
49 	slice_txn_flags = flags;
50 #endif
51 
52 	DB_ILLEGAL_BEFORE_OPEN(dbp, "DB->truncate");
53 
54 	STRIP_AUTO_COMMIT(flags);
55 
56 	/* Check for invalid flags. */
57 	if (F_ISSET(dbp, DB_AM_SECONDARY)) {
58 		__db_errx(env, DB_STR("0685",
59 		    "DB->truncate forbidden on secondary indices"));
60 		return (EINVAL);
61 	}
62 	if ((ret = __db_fchk(env, "DB->truncate", flags, 0)) != 0)
63 		return (ret);
64 
65 	ENV_ENTER(env, ip);
66 	XA_CHECK_TXN(ip, txn);
67 
68 	/*
69 	 * Make sure there are no active cursors on this db.  Since we drop
70 	 * pages we cannot really adjust cursors.
71 	 */
72 	if ((ret = __db_cursor_check(dbp)) != 0) {
73 		__db_errx(env, DB_STR("0686",
74 		    "DB->truncate not permitted with active cursors"));
75 		goto err;
76 	}
77 
78 #ifdef CONFIG_TEST
79 	if (IS_REP_MASTER(env))
80 		DB_TEST_WAIT(env, env->test_check);
81 #endif
82 	/* Check for replication block. */
83 	handle_check = IS_ENV_REPLICATED(env);
84 	if (handle_check &&
85 	    (ret = __db_rep_enter(dbp, 1, 0, IS_REAL_TXN(txn))) != 0) {
86 		handle_check = 0;
87 		goto err;
88 	}
89 
90 	/*
91 	 * Check for changes to a read-only database.  This must be after the
92 	 * replication block so that we cannot race master/client state changes.
93 	 */
94 	if (DB_IS_READONLY(dbp)) {
95 		ret = __db_rdonly(env, "DB->truncate");
96 		goto err;
97 	}
98 
99 	/*
100 	 * Create local transaction as necessary, check for consistent
101 	 * transaction usage.
102 	 */
103 	if (IS_DB_AUTO_COMMIT(dbp, txn)) {
104 		if ((ret = __txn_begin(env, ip, NULL, &txn, 0)) != 0)
105 			goto err;
106 		txn_local = 1;
107 	}
108 
109 	/* Check for consistent transaction usage. */
110 	if ((ret = __db_check_txn(dbp, txn, DB_LOCK_INVALIDID, 0)) != 0)
111 		goto err;
112 
113 #ifdef HAVE_SLICES
114 	/*
115 	 * If sliced, just truncate the slices. The 'else' explicitly avoids
116 	 * truncating the container of a sliced db; it might contain metadata
117 	 * such as the location of the slices, etc.
118 	 */
119 	if (FLD_ISSET(dbp->open_flags, DB_SLICED))
120 		ret = __db_slice_truncate(dbp, txn, countp, slice_txn_flags);
121 	else
122 #endif
123 		/*lint -e{539} Did not expect positive indentation. */
124 		ret = __db_truncate(dbp, ip, txn, countp);
125 
126 err:	if (txn_local &&
127 	    (t_ret = __db_txn_auto_resolve(env, txn, 0, ret)) && ret == 0)
128 		ret = t_ret;
129 
130 	/* Release replication block. */
131 	if (handle_check && (t_ret = __env_db_rep_exit(env)) != 0 && ret == 0)
132 		ret = t_ret;
133 
134 	ENV_LEAVE(env, ip);
135 	return (ret);
136 }
137 
138 /*
139  * __db_truncate
140  *	DB->truncate.
141  *
142  * PUBLIC: int __db_truncate __P((DB *, DB_THREAD_INFO *, DB_TXN *,
143  * PUBLIC:     u_int32_t *));
144  */
145 int
__db_truncate(dbp,ip,txn,countp)146 __db_truncate(dbp, ip, txn, countp)
147 	DB *dbp;
148 	DB_THREAD_INFO *ip;
149 	DB_TXN *txn;
150 	u_int32_t *countp;
151 {
152 	DB *sdbp;
153 	DBC *dbc;
154 	ENV *env;
155 	u_int32_t scount;
156 	int ret, t_ret;
157 
158 	env = dbp->env;
159 	dbc = NULL;
160 	ret = 0;
161 
162 	/*
163 	 * Run through all secondaries and truncate them first.  The count
164 	 * returned is the count of the primary only.  QUEUE uses normal
165 	 * processing to truncate so it will update the secondaries normally.
166 	 */
167 	if (dbp->type != DB_QUEUE && DB_IS_PRIMARY(dbp)) {
168 		if ((ret = __db_s_first(dbp, &sdbp)) != 0)
169 			return (ret);
170 		for (; sdbp != NULL && ret == 0; ret = __db_s_next(&sdbp, txn))
171 			if ((ret = __db_truncate(sdbp, ip, txn, &scount)) != 0)
172 				break;
173 		if (sdbp != NULL)
174 			(void)__db_s_done(sdbp, txn);
175 		if (ret != 0)
176 			return (ret);
177 	}
178 
179 	DB_TEST_RECOVERY(dbp, DB_TEST_PREDESTROY, ret, NULL);
180 
181 	/* Acquire a cursor. */
182 	if ((ret = __db_cursor(dbp, ip, txn, &dbc, 0)) != 0)
183 		return (ret);
184 
185 	DEBUG_LWRITE(dbc, txn, "DB->truncate", NULL, NULL, 0);
186 #ifdef HAVE_PARTITION
187 	if (DB_IS_PARTITIONED(dbp))
188 		ret = __part_truncate(dbc, countp);
189 	else
190 #endif
191 	switch (dbp->type) {
192 	case DB_BTREE:
193 	case DB_RECNO:
194 		ret = __bam_truncate(dbc, countp);
195 		break;
196 	case DB_HASH:
197 		ret = __ham_truncate(dbc, countp);
198 		break;
199 	case DB_HEAP:
200 		ret = __heap_truncate(dbc, countp);
201 		break;
202 	case DB_QUEUE:
203 		ret = __qam_truncate(dbc, countp);
204 		break;
205 	case DB_UNKNOWN:
206 	default:
207 		ret = __db_unknown_type(env, "DB->truncate", dbp->type);
208 		break;
209 	}
210 
211 	/* Discard the cursor. */
212 	if (dbc != NULL && (t_ret = __dbc_close(dbc)) != 0 && ret == 0)
213 		ret = t_ret;
214 
215 	/* Delete all blob/external files if that feature is supported. */
216 	if (ret == 0 && dbp->blob_threshold)
217 		ret = __blob_del_all(dbp, txn, 1);
218 
219 	DB_TEST_RECOVERY(dbp, DB_TEST_POSTDESTROY, ret, NULL);
220 
221 DB_TEST_RECOVERY_LABEL
222 
223 	return (ret);
224 }
225 
226 static int
__db_cursor_check_func(dbc,my_dbc,foundp,pgno,indx,args)227 __db_cursor_check_func(dbc, my_dbc, foundp, pgno, indx, args)
228 	DBC *dbc, *my_dbc;
229 	u_int32_t *foundp;
230 	db_pgno_t pgno;
231 	u_int32_t indx;
232 	void *args;
233 {
234 	COMPQUIET(my_dbc, NULL);
235 	COMPQUIET(args, NULL);
236 	COMPQUIET(pgno, 0);
237 	COMPQUIET(indx, 0);
238 	if (IS_INITIALIZED(dbc)) {
239 		*foundp = 1;
240 		return (EEXIST);
241 	}
242 	return (0);
243 }
244 /*
245  * __db_cursor_check --
246  *	See if there are any active cursors on this db.
247  */
248 static int
__db_cursor_check(dbp)249 __db_cursor_check(dbp)
250 	DB *dbp;
251 {
252 	int ret;
253 	u_int32_t found;
254 
255 	ret = __db_walk_cursors(dbp, NULL,
256 	    __db_cursor_check_func, &found, 0, 0, NULL);
257 	return (ret == EEXIST ? EINVAL : ret);
258 }
259