📄 db_am.c
字号:
u_int32_t flags;{ DB_ENV *dbenv; DBC *pdbc, *sdbc; DBT skey, key, data; int build, ret, t_ret, txn_local; dbenv = dbp->dbenv; PANIC_CHECK(dbenv); txn_local = 0; pdbc = NULL; memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); memset(&skey, 0, sizeof(DBT)); if ((ret = __db_associatechk(dbp, sdbp, callback, flags)) != 0) return (ret); /* * Create a local transaction as necessary, check for consistent * transaction usage, and, if we have no transaction but do have * locking on, acquire a locker id for the handle lock acquisition. */ if (IS_AUTO_COMMIT(dbenv, txn, flags)) { if ((ret = __db_txn_auto(dbp, &txn)) != 0) return (ret); txn_local = 1; } else if (txn != NULL && !TXN_ON(dbenv)) return (__db_not_txn_env(dbenv)); /* * Check that if an open transaction is in progress, we're in it, * for other common transaction errors, and for concurrent associates. */ if ((ret = __db_check_txn(dbp, txn, DB_LOCK_INVALIDID, 0)) != 0) return (ret); sdbp->s_callback = callback; sdbp->s_primary = dbp; sdbp->stored_get = sdbp->get; sdbp->get = __db_secondary_get; sdbp->stored_close = sdbp->close; sdbp->close = __db_secondary_close; /* * Secondary cursors may have the primary's lock file ID, so we * need to make sure that no older cursors are lying around * when we make the transition. */ if (TAILQ_FIRST(&sdbp->active_queue) != NULL || TAILQ_FIRST(&sdbp->join_queue) != NULL) { __db_err(dbenv, "Databases may not become secondary indices while cursors are open"); ret = EINVAL; goto err; } while ((sdbc = TAILQ_FIRST(&sdbp->free_queue)) != NULL) if ((ret = __db_c_destroy(sdbc)) != 0) goto err; F_SET(sdbp, DB_AM_SECONDARY); /* * Check to see if the secondary is empty--and thus if we should * build it--before we link it in and risk making it show up in * other threads. */ build = 0; if (LF_ISSET(DB_CREATE)) { if ((ret = sdbp->cursor(sdbp, txn, &sdbc, 0)) != 0) goto err; memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); /* * We don't care about key or data; we're just doing * an existence check. */ F_SET(&key, DB_DBT_PARTIAL | DB_DBT_USERMEM); F_SET(&data, DB_DBT_PARTIAL | DB_DBT_USERMEM); if ((ret = sdbc->c_real_get(sdbc, &key, &data, (STD_LOCKING(sdbc) ? DB_RMW : 0) | DB_FIRST)) == DB_NOTFOUND) { build = 1; ret = 0; } /* * Secondary cursors have special refcounting close * methods. Be careful. */ if ((t_ret = __db_c_close(sdbc)) != 0) ret = t_ret; if (ret != 0) goto err; } /* * Add the secondary to the list on the primary. Do it here * so that we see any updates that occur while we're walking * the primary. */ MUTEX_THREAD_LOCK(dbenv, dbp->mutexp); /* See __db_s_next for an explanation of secondary refcounting. */ DB_ASSERT(sdbp->s_refcnt == 0); sdbp->s_refcnt = 1; LIST_INSERT_HEAD(&dbp->s_secondaries, sdbp, s_links); MUTEX_THREAD_UNLOCK(dbenv, dbp->mutexp); if (build) { /* * We loop through the primary, putting each item we * find into the new secondary. * * If we're using CDB, opening these two cursors puts us * in a bit of a locking tangle: CDB locks are done on the * primary, so that we stay deadlock-free, but that means * that updating the secondary while we have a read cursor * open on the primary will self-block. To get around this, * we force the primary cursor to use the same locker ID * as the secondary, so they won't conflict. This should * be harmless even if we're not using CDB. */ if ((ret = sdbp->cursor(sdbp, txn, &sdbc, CDB_LOCKING(sdbp->dbenv) ? DB_WRITECURSOR : 0)) != 0) goto err; if ((ret = __db_icursor(dbp, txn, dbp->type, PGNO_INVALID, 0, sdbc->locker, &pdbc)) != 0) goto err; /* Lock out other threads, now that we have a locker ID. */ dbp->associate_lid = sdbc->locker; memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); while ((ret = pdbc->c_get(pdbc, &key, &data, DB_NEXT)) == 0) { memset(&skey, 0, sizeof(DBT)); if ((ret = callback(sdbp, &key, &data, &skey)) != 0) { if (ret == DB_DONOTINDEX) continue; else goto err; } if ((ret = sdbc->c_put(sdbc, &skey, &key, DB_UPDATE_SECONDARY)) != 0) { FREE_IF_NEEDED(sdbp, &skey); goto err; } FREE_IF_NEEDED(sdbp, &skey); } if (ret == DB_NOTFOUND) ret = 0; if ((ret = sdbc->c_close(sdbc)) != 0) goto err; }err: if (pdbc != NULL && (t_ret = pdbc->c_close(pdbc)) != 0 && ret == 0) ret = t_ret; dbp->associate_lid = DB_LOCK_INVALIDID; if (txn_local) { if (ret == 0) ret = txn->commit(txn, 0); else if ((t_ret = txn->abort(txn)) != 0) ret = __db_panic(dbenv, t_ret); } return (ret);}/* * __db_pget -- * Return a primary key/data pair given a secondary key. * * PUBLIC: int __db_pget __P((DB *, DB_TXN *, DBT *, DBT *, DBT *, u_int32_t)); */int__db_pget(dbp, txn, skey, pkey, data, flags) DB *dbp; DB_TXN *txn; DBT *skey, *pkey, *data; u_int32_t flags;{ DBC *dbc; int ret, t_ret; PANIC_CHECK(dbp->dbenv); DB_ILLEGAL_BEFORE_OPEN(dbp, "DB->pget"); if ((ret = __db_pgetchk(dbp, skey, pkey, data, flags)) != 0) return (ret); if ((ret = dbp->cursor(dbp, txn, &dbc, 0)) != 0) return (ret); SET_RET_MEM(dbc, dbp); /* * The underlying cursor pget will fill in a default DBT for null * pkeys, and use the cursor's returned-key memory internally to * store any intermediate primary keys. However, we've just set * the returned-key memory to the DB handle's key memory, which * is unsafe to use if the DB handle is threaded. If the pkey * argument is NULL, use the DBC-owned returned-key memory * instead; it'll go away when we close the cursor before we * return, but in this case that's just fine, as we're not * returning the primary key. */ if (pkey == NULL) dbc->rkey = &dbc->my_rkey; DEBUG_LREAD(dbc, txn, "__db_pget", skey, NULL, flags); /* * The cursor is just a perfectly ordinary secondary database * cursor. Call its c_pget() method to do the dirty work. */ if (flags == 0 || flags == DB_RMW) flags |= DB_SET; ret = dbc->c_pget(dbc, skey, pkey, data, flags); if ((t_ret = __db_c_close(dbc)) != 0 && ret == 0) ret = t_ret; return (ret);}/* * __db_secondary_get -- * This wrapper function for DB->pget() is the DB->get() function * on a database which has been made into a secondary index. */static int__db_secondary_get(sdbp, txn, skey, data, flags) DB *sdbp; DB_TXN *txn; DBT *skey, *data; u_int32_t flags;{ DB_ASSERT(F_ISSET(sdbp, DB_AM_SECONDARY)); return (sdbp->pget(sdbp, txn, skey, NULL, data, flags));}/* * __db_secondary_close -- * Wrapper function for DB->close() which we use on secondaries to * manage refcounting and make sure we don't close them underneath * a primary that is updating. */static int__db_secondary_close(sdbp, flags) DB *sdbp; u_int32_t flags;{ DB *primary; int doclose; doclose = 0; primary = sdbp->s_primary; MUTEX_THREAD_LOCK(primary->dbenv, primary->mutexp); /* * Check the refcount--if it was at 1 when we were called, no * thread is currently updating this secondary through the primary, * so it's safe to close it for real. * * If it's not safe to do the close now, we do nothing; the * database will actually be closed when the refcount is decremented, * which can happen in either __db_s_next or __db_s_done. */ DB_ASSERT(sdbp->s_refcnt != 0); if (--sdbp->s_refcnt == 0) { LIST_REMOVE(sdbp, s_links); /* We don't want to call close while the mutex is held. */ doclose = 1; } MUTEX_THREAD_UNLOCK(primary->dbenv, primary->mutexp); /* * sdbp->close is this function; call the real one explicitly if * need be. */ return (doclose ? __db_close(sdbp, flags) : 0);}/* * __db_append_primary -- * Perform the secondary index updates necessary to put(DB_APPEND) * a record to a primary database. */static int__db_append_primary(dbc, key, data) DBC *dbc; DBT *key, *data;{ DB *dbp, *sdbp; DBC *sdbc, *pdbc; DBT oldpkey, pkey, pdata, skey; int cmp, ret, t_ret; dbp = dbc->dbp; sdbp = NULL; ret = 0; /* * Worrying about partial appends seems a little like worrying * about Linear A character encodings. But we support those * too if your application understands them. */ pdbc = NULL; if (F_ISSET(data, DB_DBT_PARTIAL) || F_ISSET(key, DB_DBT_PARTIAL)) { /* * The dbc we were passed is all set to pass things * back to the user; we can't safely do a call on it. * Dup the cursor, grab the real data item (we don't * care what the key is--we've been passed it directly), * and use that instead of the data DBT we were passed. * * Note that we can get away with this simple get because * an appended item is by definition new, and the * correctly-constructed full data item from this partial * put is on the page waiting for us. */ if ((ret = __db_c_idup(dbc, &pdbc, DB_POSITIONI)) != 0) return (ret); memset(&pkey, 0, sizeof(DBT)); memset(&pdata, 0, sizeof(DBT)); if ((ret = pdbc->c_get(pdbc, &pkey, &pdata, DB_CURRENT)) != 0) goto err; key = &pkey; data = &pdata; } /* * Loop through the secondary indices, putting a new item in * each that points to the appended item. * * This is much like the loop in "step 3" in __db_c_put, so * I'm not commenting heavily here; it was unclean to excerpt * just that section into a common function, but the basic * overview is the same here. */ for (sdbp = __db_s_first(dbp); sdbp != NULL && ret == 0; ret = __db_s_next(&sdbp)) { memset(&skey, 0, sizeof(DBT)); if ((ret = sdbp->s_callback(sdbp, key, data, &skey)) != 0) { if (ret == DB_DONOTINDEX) continue; else goto err; } if ((ret = __db_icursor(sdbp, dbc->txn, sdbp->type, PGNO_INVALID, 0, dbc->locker, &sdbc)) != 0) { FREE_IF_NEEDED(sdbp, &skey); goto err; } if (CDB_LOCKING(sdbp->dbenv)) { DB_ASSERT(sdbc->mylock.off == LOCK_INVALID); F_SET(sdbc, DBC_WRITER); } /* * Since we know we have a new primary key, it can't be a * duplicate duplicate in the secondary. It can be a * duplicate in a secondary that doesn't support duplicates, * however, so we need to be careful to avoid an overwrite * (which would corrupt our index). */ if (!F_ISSET(sdbp, DB_AM_DUP)) { memset(&oldpkey, 0, sizeof(DBT)); F_SET(&oldpkey, DB_DBT_MALLOC); ret = sdbc->c_real_get(sdbc, &skey, &oldpkey, DB_SET | (STD_LOCKING(dbc) ? DB_RMW : 0)); if (ret == 0) { cmp = __bam_defcmp(sdbp, &oldpkey, key); /* * XXX * This needs to use the right free function * as soon as this is possible. */ __os_ufree(sdbp->dbenv, oldpkey.data); if (cmp != 0) { __db_err(sdbp->dbenv, "%s%s", "Append results in a non-unique secondary key in", " an index not configured to support duplicates"); ret = EINVAL; goto err1; } } else if (ret != DB_NOTFOUND && ret != DB_KEYEMPTY) goto err1; } ret = sdbc->c_put(sdbc, &skey, key, DB_UPDATE_SECONDARY);err1: FREE_IF_NEEDED(sdbp, &skey); if ((t_ret = sdbc->c_close(sdbc)) != 0 && ret == 0) ret = t_ret; if (ret != 0) goto err; }err: if (pdbc != NULL && (t_ret = pdbc->c_close(pdbc)) != 0 && ret == 0) ret = t_ret; if (sdbp != NULL && (t_ret = __db_s_done(sdbp)) != 0 && ret == 0) ret = t_ret; return (ret);}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -