Golang BoltDB 删除键似乎不起作用

huangapple go评论97阅读模式
英文:

Golang BoltDB Delete Key Seemingly Not Working

问题

CentOS 7,Github boltdb/bolt版本1.3.1,go版本go1.17.7 linux/amd64

这个问题可能是对BoltDB工作原理的误解,或者可能是我有一个bug,或者可能是有一个问题。我之前使用过BoltDB,并且取得了非常好的结果。尽管如此,我没有明确寻找这个问题。我看到的情况是,我尝试从一个bucket中删除一个键,该键及其值在活动的db.Update中被删除,但在该db.Update结束后仍然存在。寻找可能发生的任何解释。看起来这个功能不可能被破坏。

我正在使用BoltDB bucket来存储与电子邮件地址关联的临时令牌,用于创建新帐户。希望能够及时清理旧数据(过期的令牌,被滥用的令牌等)。非常标准的东西。临时令牌的结构如下(键是临时令牌,一个10位数字的随机字符字符串):

(临时令牌是Bucket键)

type tempTokenStruct struct {
EmailAddress string json:"emailaddress" // 要更改的电子邮件地址
TokenExpiryTime int64 json:"tokenexpirytime" // 令牌的过期时间(Epoch时间)
}

用户在Web表单中输入电子邮件地址并点击“提交”。这将调用创建临时令牌表的REST服务,例如:

"BpLnfgDsc2" => foo@bar.com, 1645650084

服务会发送一个包含临时令牌的URL的电子邮件,该链接将用户带到一个表单,允许他们再次输入他们的电子邮件地址(以进行验证)和新密码(两次)。点击提交后,将从Web处理程序中调用以下代码:

func checkTokenValid(emailAddress string, tempToken string) error {
var tempTokenData tempTokenStruct
var tempTokenBytes []byte

tempTokenBytes = []byte(tempToken)

db, err := bolt.Open(USER_DB_NAME, 0644, nil)

if err != nil {
    return err
}

defer db.Close()

err = db.Update(func(tx *bolt.Tx) error {
    tempTokenBucket := tx.Bucket([]byte("temptokens"))

    // 该bucket尚未创建,因此没有存储的令牌
    if tempTokenBucket == nil {
        return errors.New("Not Authorized (1): Please request a new password new/change email from the login page.")
    }

    // 桶中没有匹配的令牌,因此这是一个无效的请求
    tempTokenJSON := tempTokenBucket.Get(tempTokenBytes)

    //[我在这里放了一个printf:A]

    if tempTokenJSON == nil {
        return errors.New("Not Authorized (2): Please request a new password new/change email from the login page.")
    }

    jsonConvertErr := json.Unmarshal(tempTokenJSON, &tempTokenData)

    if jsonConvertErr != nil {
        tempTokenBucket.Delete(tempTokenBytes)
        return errors.New("Not Authorized (3): Please request a new password new/change email from the login page.")
    }

    // 检查时间是否过期,如果是,则删除键并指示错误
    if tempTokenData.TokenExpiryTime < time.Now().Unix() {
        tempTokenBucket.Delete(tempTokenBytes)

        //[我在这里放了一个printf:B]

        return errors.New("Not Authorized (4): Please request a new password new/change email from the login page.")
    }

    // 检查电子邮件地址是否匹配
    if emailAddress != tempTokenData.EmailAddress {
        tempTokenBucket.Delete(tempTokenBytes)
        return errors.New("Not Authorized (5): Please request a new password new/change email from the login page.")
    }

    tempTokenBucket.Delete(tempTokenBytes)
    return nil
})

// 这是测试代码,用于查看键是否被删除
db.Update(func(tx *bolt.Tx) error {
    tempTokenBucket := tx.Bucket([]byte("temptokens"))
    tempTokenJSON := tempTokenBucket.Get(tempTokenBytes)

    // [我在这里放了一个printf:C]

    return nil
})

return err

}

我正在测试一个超时的令牌(4),所以当它遇到超时的令牌时,它希望从bucket中删除这个无效的令牌。

在A位置,它打印:
第一个Get调用的令牌BpLnfgDsc2是{"emailaddress":"foo@bar.com","tokenexpirytime":1645650084}

在B位置,我放了一段代码,它执行了一个.Get,它打印出(看起来已被删除):
在DB关闭之前(4)删除之后,令牌BpLnfgDsc2为空

在C位置,它打印出(看起来又回来了):
在DB关闭之后,令牌BpLnfgDsc2是{"emailaddress":"foo@bar.com","tokenexpirytime":1645650084}

没有返回任何错误。我重复了很多次,到处放置fmt.Printf来查看发生了什么。结果是一样的,键似乎没有被删除。在这段时间之后,我使用'vi -b'命令查看DB文件,键和值仍然存在。在它静置之后运行,它仍然看到键值存在。我很困惑,希望能得到任何指导。

更新:Put/Get/Delete/Get的基本bolt功能按照这个测试代码的要求工作(应该是显而易见的):

package main

import "fmt"
import "encoding/json"
import "github.com/boltdb/bolt"

type tempTokenStruct struct {
EmailAddress string json:"emailaddress" // 要更改的电子邮件地址(临时令牌是DB键)
TokenExpiryTime int64 json:"tokenexpirytime" // 令牌的过期时间(Epoch时间)
}

func main() {
var tempToken tempTokenStruct

tempToken.EmailAddress = "foo@bar.com"
tempToken.TokenExpiryTime = 1234567890

tempTokenDataJSON, jsonMarshalError := json.Marshal(tempToken)

if jsonMarshalError != nil {
    fmt.Printf("JSON Marshal Error: %s\n", jsonMarshalError.Error())
    return
}

tempTokenKey := []byte("foo")

db, err := bolt.Open("test.db", 0644, nil)

if err != nil {
    fmt.Printf("Error opening Database\n")
    return
}

defer db.Close()

// 将一个键放入表中
err = db.Update(func(tx *bolt.Tx) error {
    tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte("temptokens"))

    if err != nil {
        return err
    }

    dbPutError := tempTokenBucket.Put(tempTokenKey, []byte(tempTokenDataJSON))

    return dbPutError
})

if err != nil {
    fmt.Printf("Error putting key value pair into table: %s\n", err.Error())
}

// 在放入后检查键/值是否存在
err = db.Update(func(tx *bolt.Tx) error {
    tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte("temptokens"))

    if err != nil {
        return err
    }

    valueGet := tempTokenBucket.Get(tempTokenKey)

    fmt.Printf("Value for Token: \"%s\" is \"%s\" just after putting it in there\n", tempTokenKey, valueGet)

    return nil
})

// 从表中删除该键
err = db.Update(func(tx *bolt.Tx) error {
    tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte("temptokens"))

    if err != nil {
        return err
    }

    dbDeleteError := tempTokenBucket.Delete(tempTokenKey)

    return dbDeleteError
})

if err != nil {
    fmt.Printf("Error Deleting key from bucket: %s\n", err.Error())
}

// 在删除后检查键/值是否存在
err = db.Update(func(tx *bolt.Tx) error {
    tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte("temptokens"))

    if err != nil {
        return err
    }

    valueGet := tempTokenBucket.Get(tempTokenKey)

    fmt.Printf("Value for Token: \"%s\" is \"%s\" after the delete\n", tempTokenKey, valueGet)

    return nil
})

if err != nil {
    fmt.Printf("Error getting key from table: %s\n", err.Error())
}

}

输出结果为:

Value for Token: "foo" is "{"emailaddress":"foo@bar.com","tokenexpirytime":1234567890}" just after putting it in there

Value for Token: "foo" is "" after the delete

所以,不确定为什么其他代码不起作用。几乎就像删除使用了不同的键一样,但是键在其他代码中是相同的。

英文:

CentOS 7, Github boltdb/bolt version 1.3.1, go version go1.17.7 linux/amd64

This issue may go to a misunderstanding of how BoltDB works, or maybe I have a bug, or maybe there is an issue. I've used BoltDB before, and have had very good results. Though, I didn't explicly look for this issue. What I'm seeing is that I try to delete a key from a bucket, and the key and its value are deleted in the active db.Update, but it's still there after that db.Update is ended. Looking for any explanation of what might be going on. Seems like this functionality couldn't possibly be broken.

I am using a BoltDB bucket for storing a temporary token associated with an email address for creating a new account. Want to be tidy and clean up old data right away (expired tokens, misused tokens, etc). Pretty standard stuff. The structure for the temporary token is (the key is the temporary token, a 10 digit random character string):

(Temporary Token is the Bucket key)

type tempTokenStruct struct {

EmailAddress        string   `json:&quot;emailaddress&quot;`        // Email Address to be changed 

TokenExpiryTime     int64    `json:&quot;tokenexpirytime&quot;`     // Expiry Time for token in Epoch time

}

The user enters an email address in a web form and hits 'submit'. That creates a call to the REST service that creates an entry in the temporary token table, like:

"BpLnfgDsc2" => foo@bar.com, 1645650084

The service emails a URL that has the temporary token embedded, and that link takes the user to a form that allows them to put in their email address (again to verify) and new password (twice). Hitting Submit then results in the following code being called from within a web handler:

func checkTokenValid(emailAddress string, tempToken string) error {
var tempTokenData tempTokenStruct
var tempTokenBytes []byte

tempTokenBytes = []byte(tempToken)

db, err := bolt.Open(USER_DB_NAME, 0644, nil)

if err != nil {
    return err
}

defer db.Close()

err = db.Update(func(tx *bolt.Tx) error {
    tempTokenBucket := tx.Bucket([]byte(&quot;temptokens&quot;))

    // The bucket hasn&#39;t been created, so there are no stored tokens
    if tempTokenBucket == nil {
        return errors.New(&quot;Not Authorized (1): Please request a new password new/change email from the login page.&quot;)
    }

    // There is no matching token stored in the bucket, so this is an invalid request
    tempTokenJSON := tempTokenBucket.Get(tempTokenBytes)

    //[I&#39;ve put a printf here: A]

    if tempTokenJSON == nil {
        return errors.New(&quot;Not Authorized (2): Please request a new password new/change email from the login page.&quot;)
    }

    jsonConvertErr := json.Unmarshal(tempTokenJSON, &amp;tempTokenData)

    if jsonConvertErr != nil {
        tempTokenBucket.Delete(tempTokenBytes)
        return errors.New(&quot;Not Authorized (3): Please request a new password new/change email from the login page.&quot;)
    }

    // Check to see if the time is expired, if so, remove the key and indicate error
    if tempTokenData.TokenExpiryTime &lt; time.Now().Unix() {
        tempTokenBucket.Delete(tempTokenBytes)

        //[I&#39;ve put a printf here: B]

        return errors.New(&quot;Not Authorized (4): Please request a new password new/change email from the login page.&quot;)
    }

    // Check to see if the email addresses match
    if emailAddress != tempTokenData.EmailAddress {
        tempTokenBucket.Delete(tempTokenBytes)
        return errors.New(&quot;Not Authorized (5): Please request a new password new/change email from the login page.&quot;)
    }

    tempTokenBucket.Delete(tempTokenBytes)
    return nil
})

// This is test code to see if the key was in fact deleted
db.Update(func(tx *bolt.Tx) error {
    tempTokenBucket := tx.Bucket([]byte(&quot;temptokens&quot;))
    tempTokenJSON := tempTokenBucket.Get(tempTokenBytes)

    // [I&#39;ve put a printf here: C]

    return nil
})

return err
}

I'm testing with a timed-out token (4), so the idea is that when it encounters that timed out token, it wants to delete this now invalid token from the bucket.

At the A location, it prints:
First Get call token BpLnfgDsc2 is {"emailaddress":"foo@bar.com","tokenexpirytime":1645650084}

At the B location I put code in that does a .Get, it prints out (looks to be deleted):
Before the DB Close (4), after deleting, token BpLnfgDsc2 is

At the C location, it prints (looks to be back):
After the DB Close, token BpLnfgDsc2 is {"emailaddress":"foo@bar.com","tokenexpirytime":1645650084}

There are no errors returned for anything. I've repeated this many times, putting fmt.Printfs everywhere to see what's going on. The results are the same, the key doesn't seem to be getting deleted. After this sits, I 'vi -b' the DB file, and the key, value is still there. Running after it sits, it still sees the key value there. I'm confused, and any pointers will be appreciated.

Update: The basic bolt functionality of Put/Get/Delete/Get works as per this test code (should be obvious):

package main

import &quot;fmt&quot;
import &quot;encoding/json&quot;
import &quot;github.com/boltdb/bolt&quot;

type tempTokenStruct struct {
EmailAddress        string   `json:&quot;emailaddress&quot;`        // Email Address to be changed (Temporary Token is the DB key)
TokenExpiryTime     int64    `json:&quot;tokenexpirytime&quot;`     // Expiry Time for token in Epoch time
}


func main() {
    var tempToken tempTokenStruct

    tempToken.EmailAddress = &quot;foo@bar.com&quot;
    tempToken.TokenExpiryTime = 1234567890

    tempTokenDataJSON, jsonMarshalError := json.Marshal(tempToken)

    if jsonMarshalError != nil {
            fmt.Printf(&quot;JSON Marshal Error: %s\n&quot;, jsonMarshalError.Error())
            return
    }

    tempTokenKey := []byte(&quot;foo&quot;)

db, err := bolt.Open(&quot;test.db&quot;, 0644, nil)

if err != nil {
            fmt.Printf(&quot;Error opening Database\n&quot;)
    return
}

defer db.Close()

// Put a key in the table
err = db.Update(func(tx *bolt.Tx) error {
    tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte(&quot;temptokens&quot;))

    if err != nil {
        return err
    }

    dbPutError := tempTokenBucket.Put(tempTokenKey, []byte(tempTokenDataJSON))

    return dbPutError
})

    if err != nil {
            fmt.Printf(&quot;Error putting key value pair into table: %s\n&quot;, err.Error())
    }

    // Check if the key/value is there after putting it in
err = db.Update(func(tx *bolt.Tx) error {
    tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte(&quot;temptokens&quot;))

    if err != nil {
        return err
    }

    valueGet := tempTokenBucket.Get(tempTokenKey)

            fmt.Printf(&quot;Value for Token: \&quot;%s\&quot; is \&quot;%s\&quot; just after putting it in there\n&quot;, tempTokenKey, valueGet)

    return nil
})

    // Delete that key from the table
err = db.Update(func(tx *bolt.Tx) error {
    tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte(&quot;temptokens&quot;))

    if err != nil {
        return err
    }

    dbDeleteError := tempTokenBucket.Delete(tempTokenKey)

    return dbDeleteError
})

    if err != nil {
            fmt.Printf(&quot;Error Deleting key from bucket: %s\n&quot;, err.Error())
    }

    // Check if the key/value is there after deleting it
err = db.Update(func(tx *bolt.Tx) error {
    tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte(&quot;temptokens&quot;))

    if err != nil {
        return err
    }

    valueGet := tempTokenBucket.Get(tempTokenKey)

            fmt.Printf(&quot;Value for Token: \&quot;%s\&quot; is \&quot;%s\&quot; after the delete\n&quot;, tempTokenKey, valueGet)

    return nil
})

    if err != nil {
            fmt.Printf(&quot;Error getting key from table: %s\n&quot;, err.Error())
    }
}

Prints out:

Value for Token: "foo" is "{"emailaddress":"foo@bar.com","tokenexpirytime":1234567890}" just after putting it in there

Value for Token: "foo" is "" after the delete

So, not sure why the other code doesn't work. Almost as if the delete is using a different key, but the key is the same across the other code.

答案1

得分: 0

我相信这里的困惑在于db.Update的行为与非nil返回值有关。根据文档的说明:

>在闭包内部,您对数据库有一个一致的视图。您通过在末尾返回nil来提交事务。您还可以通过返回错误来随时回滚事务。

您使用以下代码返回了一个错误:

return errors.New("Not Authorized (4): Please request a new password new/change email from the login page.")

这意味着在db.Update(中的所有操作都会被回滚。您可以通过在简单示例中进行小改动(return fmt.Errorf("RETURNING ERROR HERE"))来复制这个问题:

package main

import "fmt"
import "encoding/json"
import "github.com/boltdb/bolt"

type tempTokenStruct struct {
	EmailAddress    string `json:"emailaddress"`    // Email Address to be changed (Temporary Token is the DB key)
	TokenExpiryTime int64  `json:"tokenexpirytime"` // Expiry Time for token in Epoch time
}

func main() {
	var tempToken tempTokenStruct
	tempToken.EmailAddress = "foo@bar.com"
	tempToken.TokenExpiryTime = 1234567890

	tempTokenDataJSON, jsonMarshalError := json.Marshal(tempToken)
	if jsonMarshalError != nil {
		fmt.Printf("JSON Marshal Error: %s\n", jsonMarshalError.Error())
		return
	}
	tempTokenKey := []byte("foo")
	db, err := bolt.Open("test.db", 0644, nil)
	if err != nil {
		fmt.Printf("Error opening Database\n")
		return
	}
	defer db.Close()

	// Put a key in the table
	err = db.Update(func(tx *bolt.Tx) error {
		tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte("temptokens"))

		if err != nil {
			return err
		}

		dbPutError := tempTokenBucket.Put(tempTokenKey, []byte(tempTokenDataJSON))

		return dbPutError
	})

	if err != nil {
		fmt.Printf("Error putting key value pair into table: %s\n", err.Error())
	}

	// Check if the key/value is there after putting it in
	err = db.Update(func(tx *bolt.Tx) error {
		tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte("temptokens"))

		if err != nil {
			return err
		}

		valueGet := tempTokenBucket.Get(tempTokenKey)

		fmt.Printf("Value for Token: \"%s\" is \"%s\" just after putting it in there\n", tempTokenKey, valueGet)

		return nil
	})

	// Delete that key from the table
	err = db.Update(func(tx *bolt.Tx) error {
		tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte("temptokens"))

		if err != nil {
			return err
		}
		tempTokenBucket.Delete(tempTokenKey)
		return fmt.Errorf("RETURNING ERROR HERE")  // CHANGED HERE
	})

	if err != nil {
		fmt.Printf("Error Deleting key from bucket: %s\n", err.Error())
	}

	// Check if the key/value is there after deleting it
	err = db.Update(func(tx *bolt.Tx) error {
		tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte("temptokens"))
		if err != nil {
			return err
		}
		valueGet := tempTokenBucket.Get(tempTokenKey)
		fmt.Printf("Value for Token: \"%s\" is \"%s\" after the delete\n", tempTokenKey, valueGet)
		return nil
	})

	if err != nil {
		fmt.Printf("Error getting key from table: %s\n", err.Error())
	}
}

现在的输出是:

Value for Token: "foo" is "{"emailaddress":"foo@bar.com","tokenexpirytime":1234567890}" just after putting it in there
Error Deleting key from bucket: RETURNING ERROR HERE
Value for Token: "foo" is "{"emailaddress":"foo@bar.com","tokenexpirytime":1234567890}" after the delete

这似乎与您在主代码中看到的情况相符。修复方法相对简单-如果要提交更改,请不要返回错误。

英文:

I believe that the behaviour of db.Update with a non-nil return value is the confusion here. As per the docs

>Inside the closure, you have a consistent view of the database. You commit the transaction by returning nil at the end. You can also rollback the transaction at any point by returning an error.

You are returning an error with:

return errors.New(&quot;Not Authorized (4): Please request a new password new/change email from the login page.&quot;)

This means that all operations within that db.Update( are rolled back. This can be replicated in your simple example with a small change (return fmt.Errorf(&quot;RETURNING ERROR HERE&quot;)):

package main

import &quot;fmt&quot;
import &quot;encoding/json&quot;
import &quot;github.com/boltdb/bolt&quot;

type tempTokenStruct struct {
	EmailAddress    string `json:&quot;emailaddress&quot;`    // Email Address to be changed (Temporary Token is the DB key)
	TokenExpiryTime int64  `json:&quot;tokenexpirytime&quot;` // Expiry Time for token in Epoch time
}

func main() {
	var tempToken tempTokenStruct
	tempToken.EmailAddress = &quot;foo@bar.com&quot;
	tempToken.TokenExpiryTime = 1234567890

	tempTokenDataJSON, jsonMarshalError := json.Marshal(tempToken)
	if jsonMarshalError != nil {
		fmt.Printf(&quot;JSON Marshal Error: %s\n&quot;, jsonMarshalError.Error())
		return
	}
	tempTokenKey := []byte(&quot;foo&quot;)
	db, err := bolt.Open(&quot;test.db&quot;, 0644, nil)
	if err != nil {
		fmt.Printf(&quot;Error opening Database\n&quot;)
		return
	}
	defer db.Close()

	// Put a key in the table
	err = db.Update(func(tx *bolt.Tx) error {
		tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte(&quot;temptokens&quot;))

		if err != nil {
			return err
		}

		dbPutError := tempTokenBucket.Put(tempTokenKey, []byte(tempTokenDataJSON))

		return dbPutError
	})

	if err != nil {
		fmt.Printf(&quot;Error putting key value pair into table: %s\n&quot;, err.Error())
	}

	// Check if the key/value is there after putting it in
	err = db.Update(func(tx *bolt.Tx) error {
		tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte(&quot;temptokens&quot;))

		if err != nil {
			return err
		}

		valueGet := tempTokenBucket.Get(tempTokenKey)

		fmt.Printf(&quot;Value for Token: \&quot;%s\&quot; is \&quot;%s\&quot; just after putting it in there\n&quot;, tempTokenKey, valueGet)

		return nil
	})

	// Delete that key from the table
	err = db.Update(func(tx *bolt.Tx) error {
		tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte(&quot;temptokens&quot;))

		if err != nil {
			return err
		}
		tempTokenBucket.Delete(tempTokenKey)
		return fmt.Errorf(&quot;RETURNING ERROR HERE&quot;)  // CHANGED HERE
	})

	if err != nil {
		fmt.Printf(&quot;Error Deleting key from bucket: %s\n&quot;, err.Error())
	}

	// Check if the key/value is there after deleting it
	err = db.Update(func(tx *bolt.Tx) error {
		tempTokenBucket, err := tx.CreateBucketIfNotExists([]byte(&quot;temptokens&quot;))
		if err != nil {
			return err
		}
		valueGet := tempTokenBucket.Get(tempTokenKey)
		fmt.Printf(&quot;Value for Token: \&quot;%s\&quot; is \&quot;%s\&quot; after the delete\n&quot;, tempTokenKey, valueGet)
		return nil
	})

	if err != nil {
		fmt.Printf(&quot;Error getting key from table: %s\n&quot;, err.Error())
	}
}

The output is now:

Value for Token: &quot;foo&quot; is &quot;{&quot;emailaddress&quot;:&quot;foo@bar.com&quot;,&quot;tokenexpirytime&quot;:1234567890}&quot; just after putting it in there
Error Deleting key from bucket: RETURNING ERROR HERE
Value for Token: &quot;foo&quot; is &quot;{&quot;emailaddress&quot;:&quot;foo@bar.com&quot;,&quot;tokenexpirytime&quot;:1234567890}&quot; after the delete

This appears to match what you are seeing in your main code. The fix is relatively simple - don't return an error if you want changes to be committed.

huangapple
  • 本文由 发表于 2022年2月24日 10:32:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/71246379.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定