📄 db_cam.c
字号:
PANIC_CHECK(dbp->dbenv); /* Check for invalid flags. */ if ((ret = __db_cputchk(dbp, key, data, flags, IS_INITIALIZED(dbc_arg))) != 0) return (ret); /* Check for consistent transaction usage. */ if ((ret = __db_check_txn(dbp, dbc_arg->txn, dbc_arg->locker, 0)) != 0) return (ret); /* * Putting to secondary indices is forbidden; when we need * to internally update one, we'll call this with a private * synonym for DB_KEYLAST, DB_UPDATE_SECONDARY, which does * the right thing but won't return an error from cputchk(). */ if (flags == DB_UPDATE_SECONDARY) flags = DB_KEYLAST; DEBUG_LWRITE(dbc_arg, dbc_arg->txn, "db_c_put", flags == DB_KEYFIRST || flags == DB_KEYLAST || flags == DB_NODUPDATA ? key : NULL, data, flags); CDB_LOCKING_INIT(dbp, dbc_arg); /* * Check to see if we are a primary and have secondary indices. * If we are not, we save ourselves a good bit of trouble and * just skip to the "normal" put. */ if (LIST_FIRST(&dbp->s_secondaries) == NULL) goto skip_s_update; /* * We have at least one secondary which we may need to update. * * There is a rather vile locking issue here. Secondary gets * will always involve acquiring a read lock in the secondary, * then acquiring a read lock in the primary. Ideally, we * would likewise perform puts by updating all the secondaries * first, then doing the actual put in the primary, to avoid * deadlock (since having multiple threads doing secondary * gets and puts simultaneously is probably a common case). * * However, if this put is a put-overwrite--and we have no way to * tell in advance whether it will be--we may need to delete * an outdated secondary key. In order to find that old * secondary key, we need to get the record we're overwriting, * before we overwrite it. * * (XXX: It would be nice to avoid this extra get, and have the * underlying put routines somehow pass us the old record * since they need to traverse the tree anyway. I'm saving * this optimization for later, as it's a lot of work, and it * would be hard to fit into this locking paradigm anyway.) * * The simple thing to do would be to go get the old record before * we do anything else. Unfortunately, though, doing so would * violate our "secondary, then primary" lock acquisition * ordering--even in the common case where no old primary record * exists, we'll still acquire and keep a lock on the page where * we're about to do the primary insert. * * To get around this, we do the following gyrations, which * hopefully solve this problem in the common case: * * 1) If this is a c_put(DB_CURRENT), go ahead and get the * old record. We already hold the lock on this page in * the primary, so no harm done, and we'll need the primary * key (which we weren't passed in this case) to do any * secondary puts anyway. * * 2) If we're doing a partial put, we need to perform the * get on the primary key right away, since we don't have * the whole datum that the secondary key is based on. * We may also need to pad out the record if the primary * has a fixed record length. * * 3) Loop through the secondary indices, putting into each a * new secondary key that corresponds to the new record. * * 4) If we haven't done so in (1) or (2), get the old primary * key/data pair. If one does not exist--the common case--we're * done with secondary indices, and can go straight on to the * primary put. * * 5) If we do have an old primary key/data pair, however, we need * to loop through all the secondaries a second time and delete * the old secondary in each. */ memset(&pkey, 0, sizeof(DBT)); memset(&olddata, 0, sizeof(DBT)); have_oldrec = nodel = 0; /* * Primary indices can't have duplicates, so only DB_CURRENT, * DB_KEYFIRST, and DB_KEYLAST make any sense. Other flags * should have been caught by the checking routine, but * add a sprinkling of paranoia. */ DB_ASSERT(flags == DB_CURRENT || flags == DB_KEYFIRST || flags == DB_KEYLAST); /* * We'll want to use DB_RMW in a few places, but it's only legal * when locking is on. */ rmw = STD_LOCKING(dbc_arg) ? DB_RMW : 0; if (flags == DB_CURRENT) { /* Step 1. */ /* * This is safe to do on the cursor we already have; * error or no, it won't move. * * We use DB_RMW for all of these gets because we'll be * writing soon enough in the "normal" put code. In * transactional databases we'll hold those write locks * even if we close the cursor we're reading with. */ ret = dbc_arg->c_get(dbc_arg, &pkey, &olddata, rmw | DB_CURRENT); if (ret == DB_KEYEMPTY) { nodel = 1; /* * We know we don't need a delete * in the secondary. */ have_oldrec = 1; /* We've looked for the old record. */ } else if (ret != 0) goto err; else have_oldrec = 1; } else { /* So we can just use &pkey everywhere instead of key. */ pkey.data = key->data; pkey.size = key->size; } /* * Check for partial puts (step 2). */ if (F_ISSET(data, DB_DBT_PARTIAL)) { if (!have_oldrec && !nodel) { /* * We're going to have to search the tree for the * specified key. Dup a cursor (so we have the same * locking info) and do a c_get. */ if ((ret = __db_c_idup(dbc_arg, &pdbc, 0)) != 0) goto err; /* We should have gotten DB_CURRENT in step 1. */ DB_ASSERT(flags != DB_CURRENT); ret = pdbc->c_get(pdbc, &pkey, &olddata, rmw | DB_SET); if (ret == DB_KEYEMPTY || ret == DB_NOTFOUND) { nodel = 1; ret = 0; } if ((t_ret = pdbc->c_close(pdbc)) != 0) ret = t_ret; if (ret != 0) goto err; have_oldrec = 1; } /* * Now build the new datum from olddata and the partial * data we were given. */ if ((ret = __db_buildpartial(dbp, &olddata, data, &newdata)) != 0) goto err; ispartial = 1; } else ispartial = 0; /* * Handle fixed-length records. If the primary database has * fixed-length records, we need to pad out the datum before * we pass it into the callback function; we always index the * "real" record. */ if ((dbp->type == DB_RECNO && F_ISSET(dbp, DB_AM_FIXEDLEN)) || (dbp->type == DB_QUEUE)) { if (dbp->type == DB_QUEUE) { re_len = ((QUEUE *)dbp->q_internal)->re_len; re_pad = ((QUEUE *)dbp->q_internal)->re_pad; } else { re_len = ((BTREE *)dbp->bt_internal)->re_len; re_pad = ((BTREE *)dbp->bt_internal)->re_pad; } size = ispartial ? newdata.size : data->size; if (size > re_len) { __db_err(dbp->dbenv, "Length improper for fixed length record %lu", (u_long)size); ret = EINVAL; goto err; } else if (size < re_len) { /* * If we're not doing a partial put, copy * data->data into newdata.data, then pad out * newdata.data. * * If we're doing a partial put, the data * we want are already in newdata.data; we * just need to pad. * * Either way, realloc is safe. */ if ((ret = __os_realloc(dbp->dbenv, re_len, &newdata.data)) != 0) goto err; if (!ispartial) memcpy(newdata.data, data->data, size); memset((u_int8_t *)newdata.data + size, re_pad, re_len - size); newdata.size = re_len; ispartial = 1; } } /* * Loop through the secondaries. (Step 3.) * * Note that __db_s_first and __db_s_next will take care of * thread-locking and refcounting issues. */ for (sdbp = __db_s_first(dbp); sdbp != NULL && ret == 0; ret = __db_s_next(&sdbp)) { /* * Call the callback for this secondary, to get the * appropriate secondary key. */ memset(&skey, 0, sizeof(DBT)); if ((ret = sdbp->s_callback(sdbp, &pkey, ispartial ? &newdata : data, &skey)) != 0) { if (ret == DB_DONOTINDEX) /* * The callback returned a null value--don't * put this key in the secondary. Just * move on to the next one--we'll handle * any necessary deletes in step 5. */ continue; else goto err; } /* * Save the DBT we just got back from the callback function * off; we want to pass its value into c_get functions * that may stomp on a buffer the callback function * allocated. */ memset(&save_skey, 0, sizeof(DBT)); /* Paranoia. */ save_skey = skey; /* * Open a cursor in this secondary. * * Use the same locker ID as our primary cursor, so that * we're guaranteed that the locks don't conflict (e.g. in CDB * or if we're subdatabases that share and want to lock a * metadata page). */ if ((ret = __db_icursor(sdbp, dbc_arg->txn, sdbp->type, PGNO_INVALID, 0, dbc_arg->locker, &sdbc)) != 0) goto err; /* * If we're in CDB, updates will fail since the new cursor * isn't a writer. However, we hold the WRITE lock in the * primary and will for as long as our new cursor lasts, * and the primary and secondary share a lock file ID, * so it's safe to consider this a WRITER. The close * routine won't try to put anything because we don't * really have a lock. */ if (CDB_LOCKING(sdbp->dbenv)) { DB_ASSERT(sdbc->mylock.off == LOCK_INVALID); F_SET(sdbc, DBC_WRITER); } /* * There are three cases here-- * 1) The secondary supports sorted duplicates. * If we attempt to put a secondary/primary pair * that already exists, that's a duplicate duplicate, * and c_put will return DB_KEYEXIST (see __db_duperr). * This will leave us with exactly one copy of the * secondary/primary pair, and this is just right--we'll * avoid deleting it later, as the old and new secondaries * will match (since the old secondary is the dup dup * that's already there). * 2) The secondary supports duplicates, but they're not * sorted. We need to avoid putting a duplicate * duplicate, because the matching old and new secondaries * will prevent us from deleting anything and we'll * wind up with two secondary records that point to the * same primary key. Do a c_get(DB_GET_BOTH); if * that returns 0, skip the put. * 3) The secondary doesn't support duplicates at all. * In this case, secondary keys must be unique; if * another primary key already exists for this * secondary key, we have to either overwrite it or * not put this one, and in either case we've * corrupted the secondary index. Do a c_get(DB_SET). * If the secondary/primary pair already exists, do * nothing; if the secondary exists with a different * primary, return an error; and if the secondary * does not exist, put it. */ if (!F_ISSET(sdbp, DB_AM_DUP)) { /* Case 3. */ memset(&oldpkey, 0, sizeof(DBT)); F_SET(&oldpkey, DB_DBT_MALLOC); ret = sdbc->c_real_get(sdbc, &skey, &oldpkey, rmw | DB_SET); if (ret == 0) { cmp = __bam_defcmp(sdbp, &oldpkey, &pkey); __os_ufree(sdbp->dbenv, oldpkey.data); if (cmp != 0) { __db_err(sdbp->dbenv, "%s%s", "Put results in a non-unique secondary key in an ", "index not configured to support duplicates"); ret = EINVAL; goto skipput; } } else if (ret != DB_NOTFOUND && ret != DB_KEYEMPTY) goto skipput; } else if (!F_ISSET(sdbp, DB_AM_DUPSORT)) /* Case 2. */ if ((ret = sdbc->c_real_get(sdbc, &skey, &pkey, rmw | DB_GET_BOTH)) == 0) goto skipput; ret = sdbc->c_put(sdbc, &skey, &pkey, DB_UPDATE_SECONDARY); /* * We don't know yet whether this was a put-overwrite that * in fact changed nothing. If it was, we may get DB_KEYEXIST. * This is not an error. */ if (ret == DB_KEYEXIST) ret = 0;skipput: FREE_IF_NEEDED(sdbp, &save_skey) if ((t_ret = sdbc->c_close(sdbc)) != 0) ret = t_ret; if (ret != 0) goto err; } if (ret != 0) goto err; /* If still necessary, go get the old primary key/data. (Step 4.) */ if (!have_oldrec) { /* See the comments in step 2. This is real familiar. */ if ((ret = __db_c_idup(dbc_arg, &pdbc, 0)) != 0) goto err; DB_ASSERT(flags != DB_CURRENT); pkey.data = key->data; pkey.size = key->size; ret = pdbc->c_get(pdbc, &pkey, &olddata, rmw | DB_SET); if (ret == DB_KEYEMPTY || ret == DB_NOTFOUND) { nodel = 1; ret = 0; } if ((t_ret = pdbc->c_close(pdbc)) != 0) ret = t_ret; if (ret != 0) goto err; have_oldrec = 1; } /* * If we don't follow this goto, we do in fact have an old record * we may need to go delete. (Step 5). */ if (nodel) goto skip_s_update; for (sdbp = __db_s_first(dbp); sdbp != NULL && ret == 0; ret = __db_s_next(&sdbp)) { /* * Call the callback for this secondary to get the * old secondary key. */ memset(&oldskey, 0, sizeof(DBT)); if ((ret = sdbp->s_callback(sdbp, &pkey, &olddata, &oldskey)) != 0) { if (ret == DB_DONOTINDEX) /* * The callback returned a null value--there's * nothing to delete. Go on to the next * secondary. */ continue; else goto err; } if ((ret = sdbp->s_callback(sdbp, &pkey, ispartial ? &newdata : data, &skey)) != 0 && ret != DB_DONOTINDEX) goto err; /* * If there is no new secondary key, or if the old secondary * key is different from the new secondary key, then * we need to delete the old one. * * Note that bt_compare is (and must be) set no matter * what access method we're in. */ sdbc = NULL; if (ret == DB_DONOTINDEX || ((BTREE *)sdbp->bt_internal)->bt_compare(sdbp, &oldskey, &skey) != 0) { if ((ret = __db_icursor(sdbp, dbc_arg->txn, sdbp->type, PGNO_INVALID, 0, dbc_arg->locker, &sdbc)) != 0) goto err; if (CDB_LOCKING(sdbp->dbenv)) { DB_ASSERT(sdbc->mylock.off == LOCK_INVALID); F_SET(sdbc, DBC_WRITER); } /* * Don't let c_get(DB_GET_BOTH) stomp on * any secondary key value that the callback * function may have allocated. Use a temp * DBT instead. */ memset(&temp, 0, sizeof(DBT)); temp.data = oldskey.data; temp.size = oldskey.size; if ((ret = sdbc->c_real_get(sdbc, &temp, &pkey, rmw | DB_GET_BOTH)) == 0) ret = sdbc->c_del(sdbc, DB_UPDATE_SECONDARY); } FREE_IF_NEEDED(sdbp, &skey); FREE_IF_NEEDED(sdbp, &oldskey); if (sdbc != NULL && (t_ret = sdbc->c_close(sdbc)) != 0) ret = t_ret; if (ret != 0) goto err; } /* Secondary index updates are now done. On to the "real" stuff. */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -