add dolt_tags system table and dolt_tag() procedure (#3774)

This commit is contained in:
jennifersp
2022-07-08 17:55:01 -07:00
committed by GitHub
parent 6c78330128
commit 00d520ddc8
16 changed files with 441 additions and 17 deletions
+12 -3
View File
@@ -78,7 +78,7 @@ func ParseAuthor(authorStr string) (string, string, error) {
const (
AllowEmptyFlag = "allow-empty"
DateParam = "date"
CommitMessageArg = "message"
MessageArg = "message"
AuthorParam = "author"
ForceFlag = "force"
DryRunFlag = "dry-run"
@@ -115,7 +115,7 @@ If there were uncommitted working set changes present when the merge started, {{
// Creates the argparser shared dolt commit cli and DOLT_COMMIT.
func CreateCommitArgParser() *argparser.ArgParser {
ap := argparser.NewArgParser()
ap.SupportsString(CommitMessageArg, "m", "msg", "Use the given {{.LessThan}}msg{{.GreaterThan}} as the commit message.")
ap.SupportsString(MessageArg, "m", "msg", "Use the given {{.LessThan}}msg{{.GreaterThan}} as the commit message.")
ap.SupportsFlag(AllowEmptyFlag, "", "Allow recording a commit that has the exact same data as its sole parent. This is usually a mistake, so it is disabled by default. This option bypasses that safety.")
ap.SupportsString(DateParam, "", "date", "Specify the date used in the commit. If not specified the current system time is used.")
ap.SupportsFlag(ForceFlag, "f", "Ignores any foreign key warnings and proceeds with the commit.")
@@ -128,7 +128,7 @@ func CreateMergeArgParser() *argparser.ArgParser {
ap := argparser.NewArgParser()
ap.SupportsFlag(NoFFParam, "", "Create a merge commit even when the merge resolves as a fast-forward.")
ap.SupportsFlag(SquashParam, "", "Merges changes to the working set without updating the commit history")
ap.SupportsString(CommitMessageArg, "m", "msg", "Use the given {{.LessThan}}msg{{.GreaterThan}} as the commit message.")
ap.SupportsString(MessageArg, "m", "msg", "Use the given {{.LessThan}}msg{{.GreaterThan}} as the commit message.")
ap.SupportsFlag(AbortParam, "", mergeAbortDetails)
return ap
}
@@ -208,6 +208,15 @@ func CreateBranchArgParser() *argparser.ArgParser {
return ap
}
func CreateTagArgParser() *argparser.ArgParser {
ap := argparser.NewArgParser()
ap.ArgListHelp = append(ap.ArgListHelp, [2]string{"ref", "A commit ref that the tag should point at."})
ap.SupportsString(MessageArg, "m", "msg", "Use the given {{.LessThan}}msg{{.GreaterThan}} as the tag message.")
ap.SupportsFlag(VerboseFlag, "v", "list tags along with their metadata.")
ap.SupportsFlag(DeleteFlag, "d", "Delete a tag.")
return ap
}
func CreateBackupArgParser() *argparser.ArgParser {
ap := argparser.NewArgParser()
ap.ArgListHelp = append(ap.ArgListHelp, [2]string{"region", "cloud provider region associated with this backup."})
+1 -1
View File
@@ -114,7 +114,7 @@ func (cmd CommitCmd) Exec(ctx context.Context, commandStr string, args []string,
return handleCommitErr(ctx, dEnv, err, usage)
}
msg, msgOk := apr.GetValue(cli.CommitMessageArg)
msg, msgOk := apr.GetValue(cli.MessageArg)
if !msgOk {
msg, err = getCommitMessageFromEditor(ctx, dEnv)
if err != nil {
+4 -4
View File
@@ -208,12 +208,12 @@ func logCommits(ctx context.Context, dEnv *env.DoltEnv, cs *doltdb.CommitSpec, o
return 1
}
for _, t := range tags {
refName := t.Ref.String()
tagName := t.Tag.GetDoltRef().String()
if opts.decoration != "full" {
refName = t.Ref.GetPath() // trim out "refs/tags/"
tagName = t.Tag.Name // trim out "refs/tags/"
}
refName = fmt.Sprintf("\033[33;1mtag: %s\033[0m", refName) // tags names are bright yellow (33;1m)
cHashToRefs[t.Hash] = append(cHashToRefs[t.Hash], refName)
tagName = fmt.Sprintf("\033[33;1mtag: %s\033[0m", tagName) // tags names are bright yellow (33;1m)
cHashToRefs[t.Hash] = append(cHashToRefs[t.Hash], tagName)
}
h, err := commit.HashOf()
+1 -1
View File
@@ -243,7 +243,7 @@ func getUnmergedTableCount(ctx context.Context, root *doltdb.RootValue) (int, er
}
func getCommitMessage(ctx context.Context, apr *argparser.ArgParseResults, dEnv *env.DoltEnv, spec *merge.MergeSpec) (string, errhand.VerboseError) {
if m, ok := apr.GetValue(cli.CommitMessageArg); ok {
if m, ok := apr.GetValue(cli.MessageArg); ok {
return m, nil
}
+3 -3
View File
@@ -705,11 +705,11 @@ func (ddb *DoltDB) GetTags(ctx context.Context) ([]ref.DoltRef, error) {
}
type TagWithHash struct {
Ref ref.DoltRef
Tag *Tag
Hash hash.Hash
}
// GetTagsWithHashes returns a list of objects containing TagRefs with their associated Commit's hash
// GetTagsWithHashes returns a list of objects containing Tags with their associated Commit's hashes
func (ddb *DoltDB) GetTagsWithHashes(ctx context.Context) ([]TagWithHash, error) {
var refs []TagWithHash
err := ddb.VisitRefsOfType(ctx, tagsRefFilter, func(r ref.DoltRef, _ hash.Hash) error {
@@ -722,7 +722,7 @@ func (ddb *DoltDB) GetTagsWithHashes(ctx context.Context) ([]TagWithHash, error)
if err != nil {
return err
}
refs = append(refs, TagWithHash{r, h})
refs = append(refs, TagWithHash{tag, h})
}
return nil
})
@@ -242,6 +242,9 @@ const (
// StatusTableName is the status system table name.
StatusTableName = "dolt_status"
// TagsTableName is the tags table name
TagsTableName = "dolt_tags"
)
const (
+2
View File
@@ -414,6 +414,8 @@ func (db Database) getTableInsensitive(ctx *sql.Context, head *doltdb.Commit, ro
map[string]env.BranchConfig{},
map[string]env.Remote{})
dt, found = dtables.NewStatusTable(ctx, db.name, db.ddb, adapter, db.drw), true
case doltdb.TagsTableName:
dt, found = dtables.NewTagsTable(ctx, db.ddb), true
}
if found {
return dt, found, nil
@@ -83,7 +83,7 @@ func DoDoltCommit(ctx *sql.Context, args []string) (string, error) {
email = dSess.Email()
}
msg, msgOk := apr.GetValue(cli.CommitMessageArg)
msg, msgOk := apr.GetValue(cli.MessageArg)
if !msgOk {
return "", fmt.Errorf("Must provide commit message.")
}
@@ -345,7 +345,7 @@ func createMergeSpec(ctx *sql.Context, sess *dsess.DoltSession, dbName string, a
dbData, ok := sess.GetDbData(ctx, dbName)
msg, ok := apr.GetValue(cli.CommitMessageArg)
msg, ok := apr.GetValue(cli.MessageArg)
if !ok {
// TODO probably change, but we can't open editor so it'll have to be automated
msg = "automatic SQL merge"
@@ -0,0 +1,98 @@
// Copyright 2022 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dprocedures
import (
"fmt"
"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/libraries/doltcore/env/actions"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
"github.com/dolthub/go-mysql-server/sql"
)
// doltTag is the stored procedure version of the CLI `dolt tag` command
func doltTag(ctx *sql.Context, args ...string) (sql.RowIter, error) {
res, err := doDoltTag(ctx, args)
if err != nil {
return nil, err
}
return rowToIter(res), nil
}
// doDoltTag is used as sql dolt_tag command for only creating or deleting tags, not listing.
// To read/select tags, dolt_tags system table is used.
func doDoltTag(ctx *sql.Context, args []string) (int, error) {
dbName := ctx.GetCurrentDatabase()
if len(dbName) == 0 {
return 1, fmt.Errorf("Empty database name.")
}
dSess := dsess.DSessFromSess(ctx.Session)
dbData, ok := dSess.GetDbData(ctx, dbName)
if !ok {
return 1, fmt.Errorf("Could not load database %s", dbName)
}
apr, err := cli.CreateTagArgParser().Parse(args)
if err != nil {
return 1, err
}
// list tags
if len(apr.Args) == 0 || apr.Contains(cli.VerboseFlag) {
return 1, fmt.Errorf("error: invalid argument, use 'dolt_tags' system table to list tags")
}
// delete tag
if apr.Contains(cli.DeleteFlag) {
if apr.Contains(cli.MessageArg) {
return 1, fmt.Errorf("delete and tag message options are incompatible")
}
err = actions.DeleteTagsOnDB(ctx, dbData.Ddb, apr.Args...)
if err != nil {
return 1, err
}
return 0, nil
}
// create tag
if len(apr.Args) > 2 {
return 1, fmt.Errorf("create tag takes at most two args")
}
name := dSess.Username()
email := dSess.Email()
msg, _ := apr.GetValue(cli.MessageArg)
props := actions.TagProps{
TaggerName: name,
TaggerEmail: email,
Description: msg,
}
tagName := apr.Arg(0)
startPoint := "head"
if len(apr.Args) > 1 {
startPoint = apr.Arg(1)
}
headRef := dbData.Rsr.CWBHeadRef()
err = actions.CreateTagOnDB(ctx, dbData.Ddb, tagName, startPoint, props, headRef)
if err != nil {
return 1, err
}
return 0, nil
}
@@ -29,6 +29,7 @@ var DoltProcedures = []sql.ExternalStoredProcedureDetails{
{Name: "dolt_push", Schema: int64Schema("success"), Function: doltPush},
{Name: "dolt_reset", Schema: int64Schema("status"), Function: doltReset},
{Name: "dolt_revert", Schema: int64Schema("status"), Function: doltRevert},
{Name: "dolt_tag", Schema: int64Schema("status"), Function: doltTag},
{Name: "dolt_verify_constraints", Schema: int64Schema("violations"), Function: doltVerifyConstraints},
{Name: "dadd", Schema: int64Schema("status"), Function: doltAdd},
{Name: "dbranch", Schema: int64Schema("status"), Function: doltBranch},
@@ -41,6 +42,7 @@ var DoltProcedures = []sql.ExternalStoredProcedureDetails{
{Name: "dpush", Schema: int64Schema("success"), Function: doltPush},
{Name: "dreset", Schema: int64Schema("status"), Function: doltReset},
{Name: "drevert", Schema: int64Schema("status"), Function: doltRevert},
{Name: "dtag", Schema: int64Schema("status"), Function: doltTag},
{Name: "dverify_constraints", Schema: int64Schema("violations"), Function: doltVerifyConstraints},
}
@@ -0,0 +1,106 @@
// Copyright 2022 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dtables
import (
"io"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/index"
)
var _ sql.Table = (*TagsTable)(nil)
// TagsTable is a sql.Table implementation that implements a system table which shows the dolt tags
type TagsTable struct {
ddb *doltdb.DoltDB
}
// NewTagsTable creates a TagsTable
func NewTagsTable(_ *sql.Context, ddb *doltdb.DoltDB) sql.Table {
return &TagsTable{ddb: ddb}
}
// Name is a sql.Table interface function which returns the name of the table which is defined by the constant
// TagsTableName
func (dt *TagsTable) Name() string {
return doltdb.TagsTableName
}
// String is a sql.Table interface function which returns the name of the table which is defined by the constant
// TagsTableName
func (dt *TagsTable) String() string {
return doltdb.TagsTableName
}
// Schema is a sql.Table interface function that gets the sql.Schema of the tags system table.
func (dt *TagsTable) Schema() sql.Schema {
return []*sql.Column{
{Name: "tag_name", Type: sql.Text, Source: doltdb.TagsTableName, PrimaryKey: true},
{Name: "tag_hash", Type: sql.Text, Source: doltdb.TagsTableName, PrimaryKey: true},
{Name: "tagger", Type: sql.Text, Source: doltdb.TagsTableName, PrimaryKey: false},
{Name: "email", Type: sql.Text, Source: doltdb.TagsTableName, PrimaryKey: false},
{Name: "date", Type: sql.Datetime, Source: doltdb.TagsTableName, PrimaryKey: false},
{Name: "message", Type: sql.Text, Source: doltdb.TagsTableName, PrimaryKey: false},
}
}
// Partitions is a sql.Table interface function that returns a partition of the data. Currently, the data is unpartitioned.
func (dt *TagsTable) Partitions(*sql.Context) (sql.PartitionIter, error) {
return index.SinglePartitionIterFromNomsMap(nil), nil
}
// PartitionRows is a sql.Table interface function that gets a row iterator for a partition
func (dt *TagsTable) PartitionRows(ctx *sql.Context, _ sql.Partition) (sql.RowIter, error) {
return NewTagsItr(ctx, dt.ddb)
}
// TagsItr is a sql.RowItr implementation which iterates over each commit as if it's a row in the table.
type TagsItr struct {
tagsWithHash []doltdb.TagWithHash
idx int
}
// NewTagsItr creates a TagsItr from the current environment.
func NewTagsItr(ctx *sql.Context, ddb *doltdb.DoltDB) (*TagsItr, error) {
tagsWithHash, err := ddb.GetTagsWithHashes(ctx)
if err != nil {
return nil, err
}
return &TagsItr{tagsWithHash, 0}, nil
}
// Next retrieves the next row. It will return io.EOF if it's the last row.
// After retrieving the last row, Close will be automatically closed.
func (itr *TagsItr) Next(ctx *sql.Context) (sql.Row, error) {
if itr.idx >= len(itr.tagsWithHash) {
return nil, io.EOF
}
defer func() {
itr.idx++
}()
twh := itr.tagsWithHash[itr.idx]
return sql.NewRow(twh.Tag.Name, twh.Hash.String(), twh.Tag.Meta.Name, twh.Tag.Meta.Email, twh.Tag.Meta.Time(), twh.Tag.Meta.Description), nil
}
// Close closes the iterator.
func (itr *TagsItr) Close(*sql.Context) error {
return nil
}
@@ -796,6 +796,12 @@ func TestDoltBranch(t *testing.T) {
}
}
func TestDoltTag(t *testing.T) {
for _, script := range DoltTagTestScripts {
enginetest.TestScript(t, newDoltHarness(t), script)
}
}
// TestSingleTransactionScript is a convenience method for debugging a single transaction test. Unskip and set to the
// desired test.
func TestSingleTransactionScript(t *testing.T) {
@@ -4656,3 +4656,102 @@ var DoltVerifyConstraintsTestScripts = []queries.ScriptTest{
},
},
}
var DoltTagTestScripts = []queries.ScriptTest{
{
Name: "dolt-tag: SQL create tags",
SetUpScript: []string{
"CREATE TABLE test(pk int primary key);",
"INSERT INTO test VALUES (0),(1),(2);",
"CALL DOLT_COMMIT('-am','created table test')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "CALL DOLT_TAG('v1', 'HEAD')",
Expected: []sql.Row{{0}},
},
{
Query: "SELECT tag_name, IF(CHAR_LENGTH(tag_hash) < 0, NULL, 'not null'), tagger, email, IF(date IS NULL, NULL, 'not null'), message from dolt_tags",
Expected: []sql.Row{{"v1", "not null", "billy bob", "bigbillieb@fake.horse", "not null", ""}},
},
{
Query: "CALL DOLT_TAG('v2', '-m', 'create tag v2')",
Expected: []sql.Row{{0}},
},
{
Query: "SELECT tag_name, message from dolt_tags",
Expected: []sql.Row{{"v1", ""}, {"v2", "create tag v2"}},
},
},
},
{
Name: "dolt-tag: SQL delete tags",
SetUpScript: []string{
"CREATE TABLE test(pk int primary key);",
"INSERT INTO test VALUES (0),(1),(2);",
"CALL DOLT_COMMIT('-am','created table test')",
"CALL DOLT_TAG('v1', '-m', 'create tag v1')",
"CALL DOLT_TAG('v2', '-m', 'create tag v2')",
"CALL DOLT_TAG('v3', '-m', 'create tag v3')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT tag_name, message from dolt_tags",
Expected: []sql.Row{{"v1", "create tag v1"}, {"v2", "create tag v2"}, {"v3", "create tag v3"}},
},
{
Query: "CALL DOLT_TAG('-d','v1')",
Expected: []sql.Row{{0}},
},
{
Query: "SELECT tag_name, message from dolt_tags",
Expected: []sql.Row{{"v2", "create tag v2"}, {"v3", "create tag v3"}},
},
{
Query: "CALL DOLT_TAG('-d','v2','v3')",
Expected: []sql.Row{{0}},
},
{
Query: "SELECT tag_name, message from dolt_tags",
Expected: []sql.Row{},
},
},
},
{
Name: "dolt-tag: SQL use a tag as a ref for merge",
SetUpScript: []string{
"CREATE TABLE test(pk int primary key);",
"INSERT INTO test VALUES (0),(1),(2);",
"CALL DOLT_COMMIT('-am','created table test')",
"DELETE FROM test WHERE pk = 0",
"INSERT INTO test VALUES (3)",
"CALL DOLT_COMMIT('-am','made changes')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "CALL DOLT_TAG('v1','HEAD')",
Expected: []sql.Row{{0}},
},
{
Query: "CALL DOLT_CHECKOUT('-b','other','HEAD^')",
Expected: []sql.Row{{0}},
},
{
Query: "INSERT INTO test VALUES (8), (9)",
Expected: []sql.Row{{sql.OkResult{RowsAffected: 2}}},
},
{
Query: "CALL DOLT_COMMIT('-am','made changes in other')",
SkipResultsCheck: true,
},
{
Query: "CALL DOLT_MERGE('v1')",
Expected: []sql.Row{{0, 0}},
},
{
Query: "SELECT * FROM test",
Expected: []sql.Row{{1}, {2}, {3}, {8}, {9}},
},
},
},
}
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/env bats
load $BATS_TEST_DIRNAME/helper/common.bash
setup() {
setup_common
dolt sql <<SQL
CREATE TABLE test (
pk int primary key
);
INSERT INTO test VALUES (0),(1),(2);
SQL
dolt add .
dolt commit -m "created table test"
dolt sql <<SQL
DELETE FROM test WHERE pk = 0;
INSERT INTO test VALUES (3);
SQL
dolt add .
dolt commit -m "made changes"
}
teardown() {
assert_feature_version
teardown_common
}
@test "sql-tags: DOLT_TAG works with a explicit ref" {
run dolt sql -q "CALL DOLT_TAG('v1', 'HEAD^')"
[ $status -eq 0 ]
run dolt tag
[ $status -eq 0 ]
[[ "$output" =~ "v1" ]] || false
}
@test "sql-tags: DOLT_TAG works with implicit head ref" {
run dolt sql -q "CALL DOLT_TAG('v1')"
[ $status -eq 0 ]
run dolt tag
[ $status -eq 0 ]
[[ "$output" =~ "v1" ]] || false
}
@test "sql-tags: create tag v1.2.3" {
skip "Noms doesn't support '.' in dataset names"
run dolt sql -q "CALL DOLT_TAG('v1.2.3')"
[ $status -eq 0 ]
}
@test "sql-tags: delete a tag" {
dolt sql -q "CALL DOLT_TAG('v1')"
dolt sql -q "CALL DOLT_TAG('-d','v1')"
run dolt tag
[ $status -eq 0 ]
[ "$output" = "" ]
}
@test "sql-tags: use a tag as ref for diff" {
dolt sql -q "CALL DOLT_TAG('v1', 'HEAD^')"
run dolt diff v1
[ $status -eq 0 ]
[[ "$output" =~ "- | 0" ]]
[[ "$output" =~ "+ | 3" ]]
}
@test "sql-tags: use a tag as a ref for merge" {
dolt sql -q "CALL DOLT_TAG('v1', 'HEAD')"
dolt checkout -b other HEAD^
dolt sql -q "insert into test values (8),(9)"
dolt add -A && dolt commit -m 'made changes'
run dolt merge v1
[ $status -eq 0 ]
run dolt sql -q "select * from test"
[ $status -eq 0 ]
[[ "$output" =~ "1" ]]
[[ "$output" =~ "2" ]]
[[ "$output" =~ "3" ]]
[[ "$output" =~ "8" ]]
[[ "$output" =~ "9" ]]
}
+22 -3
View File
@@ -138,7 +138,7 @@ teardown() {
@test "system-tables: query dolt_remotes system table" {
skip_nbf_dolt_1 "dolt remote not supported"
run dolt sql -q "select count(*) from dolt_remotes" -r csv
[ $status -eq 0 ]
[[ "$output" =~ 0 ]] || false
@@ -163,7 +163,7 @@ teardown() {
@test "system-tables: check unsupported dolt_remote behavior" {
skip_nbf_dolt_1 "dolt remote not supported"
run dolt sql -q "insert into dolt_remotes (name, url) values ('origin1', 'file://remote')"
[ $status -ne 0 ]
[[ "$output" =~ "cannot insert remote in an SQL session" ]] || false
@@ -483,7 +483,7 @@ teardown() {
remotesrv_pid=$!
cd dolt-repo-$$
mkdir "dolt-repo-clones"
# Create a remote with a test branch
dolt remote add test-remote http://localhost:50051/test-org/test-repo
run dolt push test-remote main
@@ -545,3 +545,22 @@ SQL
[[ "$output" =~ "1,1" ]] || false
[[ "$output" =~ "2,2" ]] || false
}
@test "system-tables: query dolt_tags" {
dolt sql -q "CREATE TABLE test(pk int primary key, val int)"
dolt sql -q "INSERT INTO test VALUES (1,1)"
dolt commit -am "cm1"
dolt tag v1 head -m "tag v1 from main"
dolt checkout -b branch1
dolt sql -q "INSERT INTO test VALUES (2,2)"
dolt commit -am "cm2"
dolt tag v2 branch1~ -m "tag v2 from branch1"
dolt tag v3 branch1 -m "tag v3 from branch1"
run dolt sql -q "SELECT * FROM dolt_tags" -r csv
[ "$status" -eq 0 ]
[[ "$output" =~ "tag v1 from main" ]] || false
[[ "$output" =~ "tag v2 from branch1" ]] || false
[[ "$output" =~ "tag v3 from branch1" ]] || false
}