From 4808f7c8e81172a0a9f6a3efce62860d954c922e Mon Sep 17 00:00:00 2001 From: Daylon Wilkins Date: Tue, 25 Mar 2025 06:11:07 -0700 Subject: [PATCH 1/2] Root Objects --- go/go.work.sum | 8 +- go/libraries/doltcore/diff/table_deltas.go | 174 +++++++++++------- go/libraries/doltcore/doltdb/root_object.go | 30 +++ go/libraries/doltcore/doltdb/root_val.go | 99 ++++++++-- go/libraries/doltcore/env/actions/reset.go | 2 +- go/libraries/doltcore/env/actions/staged.go | 1 + go/libraries/doltcore/env/actions/table.go | 13 +- go/libraries/doltcore/merge/merge.go | 14 +- go/libraries/doltcore/merge/merge_rows.go | 164 +++++++++++++---- go/libraries/doltcore/merge/merge_schema.go | 4 + .../doltcore/sqle/dprocedures/dolt_add.go | 2 +- .../sqle/dprocedures/dolt_checkout.go | 2 + .../dolt_diff_summary_table_function.go | 2 +- .../doltcore/sqle/resolve/resolve_tables.go | 59 ++++-- 14 files changed, 428 insertions(+), 146 deletions(-) create mode 100644 go/libraries/doltcore/doltdb/root_object.go diff --git a/go/go.work.sum b/go/go.work.sum index 7a6b5fc0f32..a80b18bb390 100644 --- a/go/go.work.sum +++ b/go/go.work.sum @@ -447,7 +447,6 @@ github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pS github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v45 v45.2.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -526,6 +525,7 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -692,8 +692,6 @@ go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= @@ -707,8 +705,6 @@ golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4 golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -716,8 +712,6 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2 h1:IRJeR9r1pYWsHKTRe/IInb7lYvbBVIqOgsX/u0mbOWY= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= diff --git a/go/libraries/doltcore/diff/table_deltas.go b/go/libraries/doltcore/diff/table_deltas.go index 1cf02b3f806..d26ed675be3 100644 --- a/go/libraries/doltcore/diff/table_deltas.go +++ b/go/libraries/doltcore/diff/table_deltas.go @@ -25,6 +25,7 @@ import ( "github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable" "github.com/dolthub/dolt/go/libraries/doltcore/schema" "github.com/dolthub/dolt/go/libraries/utils/set" + "github.com/dolthub/dolt/go/store/hash" "github.com/dolthub/dolt/go/store/prolly/tree" "github.com/dolthub/dolt/go/store/types" ) @@ -54,6 +55,8 @@ type TableDelta struct { ToName doltdb.TableName FromTable *doltdb.Table ToTable *doltdb.Table + FromRootObject doltdb.RootObject + ToRootObject doltdb.RootObject FromNodeStore tree.NodeStore ToNodeStore tree.NodeStore FromVRW types.ValueReadWriter @@ -145,9 +148,23 @@ func GetTableDeltas(ctx context.Context, fromRoot, toRoot doltdb.RootValue) (del if err != nil { return nil, err } + err = fromRoot.IterRootObjects(ctx, func(name doltdb.TableName, tbl doltdb.RootObject) (stop bool, err error) { + fromDeltas = append(fromDeltas, TableDelta{ + FromName: name, + FromRootObject: tbl, + FromSch: schema.EmptySchema, + FromVRW: fromVRW, + FromNodeStore: fromNS, + ToVRW: toVRW, + ToNodeStore: toNS, + }) + return + }) + if err != nil { + return nil, err + } toDeltas := make([]TableDelta, 0) - err = toRoot.IterTables(ctx, func(name doltdb.TableName, tbl *doltdb.Table, sch schema.Schema) (stop bool, err error) { c, err := toRoot.GetForeignKeyCollection(ctx) if err != nil { @@ -176,6 +193,21 @@ func GetTableDeltas(ctx context.Context, fromRoot, toRoot doltdb.RootValue) (del if err != nil { return nil, err } + err = toRoot.IterRootObjects(ctx, func(name doltdb.TableName, tbl doltdb.RootObject) (stop bool, err error) { + toDeltas = append(toDeltas, TableDelta{ + ToName: name, + ToRootObject: tbl, + ToSch: schema.EmptySchema, + FromVRW: fromVRW, + FromNodeStore: fromNS, + ToVRW: toVRW, + ToNodeStore: toNS, + }) + return + }) + if err != nil { + return nil, err + } deltas = matchTableDeltas(fromDeltas, toDeltas) deltas, err = filterUnmodifiedTableDeltas(deltas) @@ -235,7 +267,7 @@ func getFkParentSchs(ctx context.Context, root doltdb.RootValue, fks ...doltdb.F func filterUnmodifiedTableDeltas(deltas []TableDelta) ([]TableDelta, error) { var filtered []TableDelta for _, d := range deltas { - if d.ToTable == nil || d.FromTable == nil { + if d.IsAdd() || d.IsDrop() { // Table was added or dropped filtered = append(filtered, d) continue @@ -276,6 +308,8 @@ func matchTableDeltas(fromDeltas, toDeltas []TableDelta) (deltas []TableDelta) { ToName: t.ToName, FromTable: f.FromTable, ToTable: t.ToTable, + FromRootObject: f.FromRootObject, + ToRootObject: t.ToRootObject, FromSch: f.FromSch, ToSch: t.ToSch, FromFks: f.FromFks, @@ -339,12 +373,12 @@ func schemasOverlap(from, to schema.Schema) bool { // IsAdd returns true if the table was added between the fromRoot and toRoot. func (td TableDelta) IsAdd() bool { - return td.FromTable == nil && td.ToTable != nil + return (td.FromTable == nil && td.ToTable != nil) || (td.FromRootObject == nil && td.ToRootObject != nil) } // IsDrop returns true if the table was dropped between the fromRoot and toRoot. func (td TableDelta) IsDrop() bool { - return td.FromTable != nil && td.ToTable == nil + return (td.FromTable != nil && td.ToTable == nil) || (td.FromRootObject != nil && td.ToRootObject == nil) } // IsRename return true if the table was renamed between the fromRoot and toRoot. @@ -362,22 +396,39 @@ func (td TableDelta) HasHashChanged() (bool, error) { return true, nil } - toHash, err := td.ToTable.HashOf() - if err != nil { - return false, err - } + var toHash hash.Hash + var fromHash hash.Hash + var err error - fromHash, err := td.FromTable.HashOf() - if err != nil { - return false, err + if td.FromTable != nil || td.ToTable != nil { + toHash, err = td.ToTable.HashOf() + if err != nil { + return false, err + } + fromHash, err = td.FromTable.HashOf() + if err != nil { + return false, err + } + } else { + ctx := context.Background() + toHash, err = td.ToRootObject.HashOf(ctx) + if err != nil { + return false, err + } + fromHash, err = td.FromRootObject.HashOf(ctx) + if err != nil { + return false, err + } } - return !toHash.Equal(fromHash), nil } // HasSchemaChanged returns true if the table schema has changed between the // fromRoot and toRoot. func (td TableDelta) HasSchemaChanged(ctx context.Context) (bool, error) { + if td.FromRootObject != nil || td.ToRootObject != nil { + return false, nil + } // Database collation change is a schema change if td.FromTable == nil && td.ToTable == nil { return true, nil @@ -419,71 +470,56 @@ func (td TableDelta) HasSchemaChanged(ctx context.Context) (bool, error) { } func (td TableDelta) HasChangesIgnoringColumnTags(ctx context.Context) (bool, error) { - - if td.FromTable == nil && td.ToTable == nil { + if td.FromTable == nil && td.ToTable == nil && td.FromRootObject == nil && td.ToRootObject == nil { return true, nil } - if td.IsAdd() || td.IsDrop() { return true, nil } - if td.IsRename() { return true, nil } - if td.HasFKChanges() { return true, nil } - fromRowDataHash, err := td.FromTable.GetRowDataHash(ctx) - if err != nil { - return false, err - } - - toRowDataHash, err := td.ToTable.GetRowDataHash(ctx) - if err != nil { - return false, err - } - - // Any change to the table data counts as a change - if !fromRowDataHash.Equal(toRowDataHash) { - return true, nil - } - - fromTableHash, err := td.FromTable.HashOf() - if err != nil { - return false, err - } - - toTableHash, err := td.FromTable.HashOf() - if err != nil { - return false, err - } - - // If the data hashes have changed, the table has obviously changed. - if !fromTableHash.Equal(toTableHash) { - return true, nil - } - - fromSchemaHash, err := td.FromTable.GetSchemaHash(ctx) - if err != nil { - return false, err - } - - toSchemaHash, err := td.ToTable.GetSchemaHash(ctx) - if err != nil { - return false, err - } + // There are specific checks that we make for regular tables + if td.FromTable != nil || td.ToTable != nil { + // Any change to the table data counts as a change + fromRowDataHash, err := td.FromTable.GetRowDataHash(ctx) + if err != nil { + return false, err + } + toRowDataHash, err := td.ToTable.GetRowDataHash(ctx) + if err != nil { + return false, err + } + if !fromRowDataHash.Equal(toRowDataHash) { + return true, nil + } - // If neither data nor schema hashes have changed, the table is obviously the same. - if fromSchemaHash.Equal(toSchemaHash) { - return false, nil + // If neither data nor schema hashes have changed, the table is the same + fromSchemaHash, err := td.FromTable.GetSchemaHash(ctx) + if err != nil { + return false, err + } + toSchemaHash, err := td.ToTable.GetSchemaHash(ctx) + if err != nil { + return false, err + } + if fromSchemaHash.Equal(toSchemaHash) { + return false, nil + } + } else { + // If we're not dealing with regular tables, then we must be dealing with root objects. + // In that case, it's a straight-forward check to see if the hashes changed. + if ok, err := td.HasHashChanged(); err != nil || ok { + return ok, err + } } // The schema hash has changed but the data has remained the same. We must inspect the schema to determine // whether the change is observable or if only column tags have changed. - fromSchema, toSchema, err := td.GetSchemas(ctx) if err != nil { return false, err @@ -495,19 +531,21 @@ func (td TableDelta) HasChangesIgnoringColumnTags(ctx context.Context) (bool, er func (td TableDelta) HasDataChanged(ctx context.Context) (bool, error) { // Database collation change is not a data change - if td.FromTable == nil && td.ToTable == nil { + if td.FromTable == nil && td.ToTable == nil && td.FromRootObject == nil && td.ToRootObject == nil { return false, nil } + if td.FromRootObject != nil || td.ToRootObject != nil { + return td.HasHashChanged() + } + if td.IsAdd() { isEmpty, err := isTableDataEmpty(ctx, td.ToTable) if err != nil { return false, err } - return !isEmpty, nil } - if td.IsDrop() { isEmpty, err := isTableDataEmpty(ctx, td.FromTable) if err != nil { @@ -520,12 +558,10 @@ func (td TableDelta) HasDataChanged(ctx context.Context) (bool, error) { if err != nil { return false, err } - toRowDataHash, err := td.ToTable.GetRowDataHash(ctx) if err != nil { return false, err } - return !fromRowDataHash.Equal(toRowDataHash), nil } @@ -596,6 +632,9 @@ func (td TableDelta) GetSchemas(ctx context.Context) (from, to schema.Schema, er // Format returns the format of the tables in this delta. func (td TableDelta) Format() *types.NomsBinFormat { + if td.FromRootObject != nil || td.ToRootObject != nil { + return types.Format_DOLT + } if td.FromTable != nil { return td.FromTable.Format() } @@ -693,6 +732,11 @@ func (td TableDelta) GetSummary(ctx context.Context) (*TableDeltaSummary, error) // GetRowData returns the table's row data at the fromRoot and toRoot, or an empty map if the table did not exist. func (td TableDelta) GetRowData(ctx context.Context) (from, to durable.Index, err error) { + if td.FromRootObject != nil || td.ToRootObject != nil { + // TODO: determine if this should error + return nil, nil, nil + } + if td.FromTable == nil && td.ToTable == nil { return nil, nil, fmt.Errorf("both from and to tables are missing from table delta") } diff --git a/go/libraries/doltcore/doltdb/root_object.go b/go/libraries/doltcore/doltdb/root_object.go new file mode 100644 index 00000000000..d126b4bd5bc --- /dev/null +++ b/go/libraries/doltcore/doltdb/root_object.go @@ -0,0 +1,30 @@ +// Copyright 2025 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 doltdb + +import ( + "context" + + "github.com/dolthub/dolt/go/store/hash" +) + +// RootObject is an object that is located on the root, rather than inside another object (table, schema, etc.). This is +// primarily used by Doltgres to store root-level objects (sequences, functions, etc.). +type RootObject interface { + // HashOf returns the hash of the underlying struct. + HashOf(ctx context.Context) (hash.Hash, error) + // Name returns the name of the custom table. + Name() TableName +} diff --git a/go/libraries/doltcore/doltdb/root_val.go b/go/libraries/doltcore/doltdb/root_val.go index 2f4ff1c3510..2ec53938d94 100644 --- a/go/libraries/doltcore/doltdb/root_val.go +++ b/go/libraries/doltcore/doltdb/root_val.go @@ -64,8 +64,12 @@ type RootValue interface { // all tables is also included. This method is very expensive for large root values, so |transitive| should only be used // when debugging tests. DebugString(ctx context.Context, transitive bool) string + // FilterRootObjectNames returns a slice containing only the names that are root objects, removing all tables. + FilterRootObjectNames(ctx context.Context, names []TableName) (rootObjNames []TableName, err error) // GetCollation returns the database collation. GetCollation(ctx context.Context) (schema.Collation, error) + // GetRootObject will retrieve a root object by its case-sensitive name. + GetRootObject(ctx context.Context, objName TableName) (RootObject, bool, error) // GetDatabaseSchemas returns all schemas. These differ from a table's schema. GetDatabaseSchemas(ctx context.Context) ([]schema.DatabaseSchema, error) // GetFeatureVersion returns the feature version of this root, if one is written @@ -79,31 +83,34 @@ type RootValue interface { GetTableHash(ctx context.Context, tName TableName) (hash.Hash, bool, error) // GetTableSchemaHash returns the hash of the given table's schema. GetTableSchemaHash(ctx context.Context, tName TableName) (hash.Hash, error) - // GetTableNames retrieves the lists of all tables for a RootValue + // GetTableNames retrieves the lists of all tables and root objects for a RootValue. GetTableNames(ctx context.Context, schemaName string) ([]string, error) - // HandlePostMerge handles merging for any root elements that are not handled by the standard merge workflow. This - // is called at the end of the standard merge workflow. The calling root is the merged root, so it is valid to - // return it if no further merge work needs to be done. This is primarily used by Doltgres. - HandlePostMerge(ctx context.Context, ourRoot, theirRoot, ancRoot RootValue) (RootValue, error) - // HasTable returns whether the root has a table with the given case-sensitive name. + // HasTable returns whether the root has a table with the given case-sensitive name. This will also return true if a + // root object matches the table name, as they occupy the same namespace. HasTable(ctx context.Context, tName TableName) (bool, error) + // IterRootObjects calls the callback function on each RootObject in this RootValue. + IterRootObjects(ctx context.Context, cb func(name TableName, rootObj RootObject) (stop bool, err error)) error // IterTables calls the callback function cb on each table in this RootValue. IterTables(ctx context.Context, cb func(name TableName, table *Table, sch schema.Schema) (stop bool, err error)) error // NodeStore returns this root's NodeStore. NodeStore() tree.NodeStore // NomsValue returns this root's storage as a noms value. NomsValue() types.Value + // PutRootObject inserts a root object by name into the root. If a root object already exists with that name, + // then it will be replaced. + PutRootObject(ctx context.Context, tName TableName, rootObj RootObject) (RootValue, error) // PutForeignKeyCollection returns a new root with the given foreign key collection. PutForeignKeyCollection(ctx context.Context, fkc *ForeignKeyCollection) (RootValue, error) // PutTable inserts a table by name into the map of tables. If a table already exists with that name it will be replaced PutTable(ctx context.Context, tName TableName, table *Table) (RootValue, error) - // RemoveTables removes the given case-sensitive tables from the root, and returns a new root. + // RemoveTables removes the given case-sensitive tables from the root, and returns a new root. This may also be used + // to remove root objects. RemoveTables(ctx context.Context, skipFKHandling bool, allowDroppingFKReferenced bool, tables ...TableName) (RootValue, error) // RenameTable renames a table by changing its string key in the RootValue's table map. In order to preserve - // column tag information, use this method instead of a table drop + add. + // column tag information, use this method instead of a table drop + add. This can also rename root objects. RenameTable(ctx context.Context, oldName, newName TableName) (RootValue, error) // ResolveTableName resolves a case-insensitive name to the exact name as stored in Dolt. Returns false if no matching - // name was found. + // name was found. This searches both tables and root objects. ResolveTableName(ctx context.Context, tName TableName) (string, bool, error) // SetCollation sets the given database collation and returns a new root. SetCollation(ctx context.Context, collation schema.Collation) (RootValue, error) @@ -279,6 +286,11 @@ func (root *rootValue) SetFeatureVersion(v FeatureVersion) (RootValue, error) { return root.withStorage(newStorage), nil } +// FindRootObjectNames is only used by Doltgres. +func (root *rootValue) FilterRootObjectNames(ctx context.Context, names []TableName) ([]TableName, error) { + return nil, nil +} + func (root *rootValue) GetCollation(ctx context.Context) (schema.Collation, error) { return root.st.GetCollation(ctx) } @@ -291,8 +303,9 @@ func (root *rootValue) SetCollation(ctx context.Context, collation schema.Collat return root.withStorage(newStorage), nil } -func (root *rootValue) HandlePostMerge(ctx context.Context, ourRoot, theirRoot, ancRoot RootValue) (RootValue, error) { - return root, nil +// GetRootObject is only used by Doltgres. +func (root *rootValue) GetRootObject(ctx context.Context, tName TableName) (RootObject, bool, error) { + return nil, false, nil } func (root *rootValue) GetTableSchemaHash(ctx context.Context, tName TableName) (hash.Hash, error) { @@ -634,14 +647,31 @@ func TablesWithDataConflicts(ctx context.Context, root RootValue) ([]TableName, return nil, err } + var rootObjNamesMap map[TableName]struct{} conflicted := make([]TableName, 0, len(names)) for _, name := range names { - tbl, _, err := root.GetTable(ctx, name) + tbl, ok, err := root.GetTable(ctx, name) if err != nil { return nil, err } + if !ok { + if rootObjNamesMap == nil { + rootObjNames, err := root.FilterRootObjectNames(ctx, names) + if err != nil { + return nil, err + } + rootObjNamesMap = make(map[TableName]struct{}) + for _, rootObjName := range rootObjNames { + rootObjNamesMap[rootObjName] = struct{}{} + } + } + if _, ok = rootObjNamesMap[name]; ok { + continue + } + return nil, fmt.Errorf("root returned table `%s` but it could not be found", name.String()) + } - ok, err := tbl.HasConflicts(ctx) + ok, err = tbl.HasConflicts(ctx) if err != nil { return nil, err } @@ -660,12 +690,29 @@ func TablesWithConstraintViolations(ctx context.Context, root RootValue) ([]Tabl return nil, err } + var rootObjNamesMap map[TableName]struct{} violating := make([]TableName, 0, len(names)) for _, name := range names { - tbl, _, err := root.GetTable(ctx, name) + tbl, ok, err := root.GetTable(ctx, name) if err != nil { return nil, err } + if !ok { + if rootObjNamesMap == nil { + rootObjNames, err := root.FilterRootObjectNames(ctx, names) + if err != nil { + return nil, err + } + rootObjNamesMap = make(map[TableName]struct{}) + for _, rootObjName := range rootObjNames { + rootObjNamesMap[rootObjName] = struct{}{} + } + } + if _, ok = rootObjNamesMap[name]; ok { + continue + } + return nil, fmt.Errorf("root returned table `%s` but it could not be found", name.String()) + } n, err := tbl.NumConstraintViolations(ctx) if err != nil { @@ -699,6 +746,11 @@ func HasConstraintViolations(ctx context.Context, root RootValue) (bool, error) return len(tbls) > 0, nil } +// IterRootObjects is only used by Doltgres. +func (root *rootValue) IterRootObjects(ctx context.Context, cb func(name TableName, table RootObject) (stop bool, err error)) error { + return nil +} + // IterTables calls the callback function cb on each table in this RootValue. func (root *rootValue) IterTables(ctx context.Context, cb func(name TableName, table *Table, sch schema.Schema) (stop bool, err error)) error { schemaNames, err := schemaNames(ctx, root) @@ -874,6 +926,11 @@ func (root *rootValue) PutTable(ctx context.Context, tName TableName, table *Tab return root.putTable(ctx, tName, tableRef, schHash) } +// PutRootObject is only used by Doltgres. +func (root *rootValue) PutRootObject(ctx context.Context, tName TableName, table RootObject) (RootValue, error) { + return nil, errors.New("dolt does not implement root object") +} + func RefFromNomsTable(ctx context.Context, table *Table) (types.Ref, error) { return durable.RefFromNomsTable(ctx, table.table) } @@ -1112,12 +1169,26 @@ func ValidateForeignKeysOnSchemas(ctx context.Context, root RootValue) (RootValu return nil, err } allTablesSet := make(map[TableName]schema.Schema) + var rootObjNamesMap map[TableName]struct{} for _, tableName := range allTablesSlice { tbl, ok, err := root.GetTable(ctx, tableName) if err != nil { return nil, err } if !ok { + if rootObjNamesMap == nil { + rootObjNames, err := root.FilterRootObjectNames(ctx, allTablesSlice) + if err != nil { + return nil, err + } + rootObjNamesMap = make(map[TableName]struct{}) + for _, rootObjName := range rootObjNames { + rootObjNamesMap[rootObjName] = struct{}{} + } + } + if _, ok = rootObjNamesMap[tableName]; ok { + continue + } return nil, fmt.Errorf("found table `%s` in staging but could not load for foreign key check", tableName) } tblSch, err := tbl.GetSchema(ctx) diff --git a/go/libraries/doltcore/env/actions/reset.go b/go/libraries/doltcore/env/actions/reset.go index 18fbf3c96f9..fb61b923d70 100644 --- a/go/libraries/doltcore/env/actions/reset.go +++ b/go/libraries/doltcore/env/actions/reset.go @@ -285,7 +285,7 @@ func CleanUntracked(ctx *sql.Context, roots doltdb.Roots, tables []string, dryru for i := range tables { name := tables[i] - resolvedName, _, tblExists, err := resolve.Table(ctx, roots.Working, name) + resolvedName, tblExists, err := resolve.TableName(ctx, roots.Working, name) if err != nil { return doltdb.Roots{}, err } diff --git a/go/libraries/doltcore/env/actions/staged.go b/go/libraries/doltcore/env/actions/staged.go index a753728f05e..9a7b748aec9 100644 --- a/go/libraries/doltcore/env/actions/staged.go +++ b/go/libraries/doltcore/env/actions/staged.go @@ -165,6 +165,7 @@ func clearEmptyConflicts(ctx context.Context, tbls []doltdb.TableName, working d return nil, err } if !ok { + // TODO: check custom tables continue } diff --git a/go/libraries/doltcore/env/actions/table.go b/go/libraries/doltcore/env/actions/table.go index d28e1901610..b6cf30794ac 100644 --- a/go/libraries/doltcore/env/actions/table.go +++ b/go/libraries/doltcore/env/actions/table.go @@ -84,9 +84,16 @@ func MoveTablesBetweenRoots(ctx context.Context, tbls []doltdb.TableName, src, d } } - dest, err = dest.PutTable(ctx, td.ToName, td.ToTable) - if err != nil { - return nil, err + if td.ToRootObject != nil { + dest, err = dest.PutRootObject(ctx, td.ToName, td.ToRootObject) + if err != nil { + return nil, err + } + } else { + dest, err = dest.PutTable(ctx, td.ToName, td.ToTable) + if err != nil { + return nil, err + } } stagedFKs.RemoveKeys(td.FromFks...) diff --git a/go/libraries/doltcore/merge/merge.go b/go/libraries/doltcore/merge/merge.go index 7d4fe6afaeb..1596a07a48e 100644 --- a/go/libraries/doltcore/merge/merge.go +++ b/go/libraries/doltcore/merge/merge.go @@ -311,6 +311,15 @@ func MergeRoots( return nil, err } continue + } else if mergedTable.rootObj != nil { + tblToStats[tblName] = stats + if stats.Operation != TableUnmodified { + mergedRoot, err = mergedRoot.PutRootObject(ctx, tblName, mergedTable.rootObj) + if err != nil { + return nil, err + } + } + continue } mergedRootHasTable, err := mergedRoot.HasTable(ctx, tblName) @@ -354,11 +363,6 @@ func MergeRoots( return nil, err } - mergedRoot, err = mergedRoot.HandlePostMerge(ctx, ourRoot, theirRoot, ancRoot) - if err != nil { - return nil, err - } - h, err := merger.rightSrc.HashOf() if err != nil { return nil, err diff --git a/go/libraries/doltcore/merge/merge_rows.go b/go/libraries/doltcore/merge/merge_rows.go index 8f80757486c..246922411ae 100644 --- a/go/libraries/doltcore/merge/merge_rows.go +++ b/go/libraries/doltcore/merge/merge_rows.go @@ -16,6 +16,7 @@ package merge import ( "context" + "errors" "github.com/dolthub/go-mysql-server/sql" @@ -61,6 +62,10 @@ type TableMerger struct { rightTbl *doltdb.Table ancTbl *doltdb.Table + leftRootObj doltdb.RootObject + rightRootObj doltdb.RootObject + ancRootObj doltdb.RootObject + leftSch schema.Schema rightSch schema.Schema ancSch schema.Schema @@ -78,21 +83,33 @@ type TableMerger struct { recordViolations bool } -func (tm TableMerger) tableHashes() (left, right, anc hash.Hash, err error) { +func (tm TableMerger) tableHashes(ctx context.Context) (left, right, anc hash.Hash, err error) { if tm.leftTbl != nil { if left, err = tm.leftTbl.HashOf(); err != nil { return } + } else if tm.leftRootObj != nil { + if left, err = tm.leftRootObj.HashOf(ctx); err != nil { + return + } } if tm.rightTbl != nil { if right, err = tm.rightTbl.HashOf(); err != nil { return } + } else if tm.rightRootObj != nil { + if right, err = tm.rightRootObj.HashOf(ctx); err != nil { + return + } } if tm.ancTbl != nil { if anc, err = tm.ancTbl.HashOf(); err != nil { return } + } else if tm.ancRootObj != nil { + if anc, err = tm.ancRootObj.HashOf(ctx); err != nil { + return + } } return } @@ -127,8 +144,10 @@ func NewMerger( }, nil } -type MergedTable struct { - table *doltdb.Table +// MergedResult returns either the merged table or merged root object. Both fields will never be set simultaneously. +type MergedResult struct { + table *doltdb.Table // If non-nil, represents a merged table (and not a merged root object). + rootObj doltdb.RootObject // If non-nil, represents a merged root object (and not a merged table). conflict SchemaConflict } @@ -151,16 +170,16 @@ func (rm *RootMerger) MergeTable( tblName doltdb.TableName, opts editor.Options, mergeOpts MergeOpts, -) (*MergedTable, *MergeStats, error) { +) (*MergedResult, *MergeStats, error) { tm, err := rm.makeTableMerger(ctx, tblName, mergeOpts) if err != nil { return nil, nil, err } // short-circuit here if we can - finished, stats, err := rm.maybeShortCircuit(ctx, tm, mergeOpts) + finished, finishedRootObj, stats, err := rm.maybeShortCircuit(ctx, tm, mergeOpts) if finished != nil || stats != nil || err != nil { - return &MergedTable{table: finished}, stats, err + return &MergedResult{table: finished, rootObj: finishedRootObj}, stats, err } // Calculate a merge of the schemas, but don't apply it yet @@ -173,7 +192,7 @@ func (rm *RootMerger) MergeTable( return nil, nil, schConflicts } // handle schema conflicts above - mt := &MergedTable{ + mt := &MergedResult{ table: tm.leftTbl, conflict: schConflicts, } @@ -185,15 +204,33 @@ func (rm *RootMerger) MergeTable( } var tbl *doltdb.Table - if types.IsFormat_DOLT(tm.vrw.Format()) { - tbl, stats, err = mergeProllyTable(ctx, tm, mergeSch, mergeInfo, diffInfo) + var rootObj doltdb.RootObject + involvesRootObjects := tm.leftRootObj != nil || tm.rightRootObj != nil || tm.ancRootObj != nil + if !involvesRootObjects { + if types.IsFormat_DOLT(tm.vrw.Format()) { + tbl, stats, err = mergeProllyTable(ctx, tm, mergeSch, mergeInfo, diffInfo) + } else { + tbl, stats, err = mergeNomsTable(ctx, tm, mergeSch, rm.vrw, opts) + } + if err != nil { + return nil, nil, err + } } else { - tbl, stats, err = mergeNomsTable(ctx, tm, mergeSch, rm.vrw, opts) - } - if err != nil { - return nil, nil, err + rootObj, stats, err = MergeRootObjects(ctx, MergeRootObject{ + Name: tm.name, + OurRootObj: tm.leftRootObj, + TheirRootObj: tm.rightRootObj, + AncestorRootObj: tm.ancRootObj, + RightSrc: tm.rightSrc, + AncestorSrc: tm.ancestorSrc, + VRW: tm.vrw, + NS: tm.ns, + }) + if err != nil { + return nil, nil, err + } } - return &MergedTable{table: tbl}, stats, nil + return &MergedResult{table: tbl, rootObj: rootObj}, stats, nil } func (rm *RootMerger) makeTableMerger(ctx context.Context, tblName doltdb.TableName, mergeOpts MergeOpts) (*TableMerger, error) { @@ -224,6 +261,11 @@ func (rm *RootMerger) makeTableMerger(ctx context.Context, tblName doltdb.TableN if tm.leftSch, err = tm.leftTbl.GetSchema(ctx); err != nil { return nil, err } + } else { + tm.leftRootObj, _, err = rm.left.GetRootObject(ctx, tblName) + if err != nil { + return nil, err + } } tm.rightTbl, rightSideTableExists, err = rm.right.GetTable(ctx, tblName) @@ -234,13 +276,18 @@ func (rm *RootMerger) makeTableMerger(ctx context.Context, tblName doltdb.TableN if tm.rightSch, err = tm.rightTbl.GetSchema(ctx); err != nil { return nil, err } + } else { + tm.rightRootObj, _, err = rm.right.GetRootObject(ctx, tblName) + if err != nil { + return nil, err + } } // If we need to re-verify all constraints, then we need to stub out tables // that don't exist, so that the diff logic can compare an empty table to // the table containing the real data. This is required by dolt_verify_constraints() // so that we can run the merge logic on all rows in all tables. - if mergeOpts.ReverifyAllConstraints { + if mergeOpts.ReverifyAllConstraints && !tm.HasRootObject() { if !leftSideTableExists && rightSideTableExists { // if left side doesn't have the table... stub it out with an empty table from the right side... tm.leftSch = tm.rightSch @@ -273,56 +320,67 @@ func (rm *RootMerger) makeTableMerger(ctx context.Context, tblName doltdb.TableN if err != nil { return nil, err } + } else { + tm.ancRootObj, _, err = rm.anc.GetRootObject(ctx, tblName) + if err != nil { + return nil, err + } } + // TODO: need to determine what to do if we have a mix of both tables and root objects (we'll error for now) + if tm.HasTable() && tm.HasRootObject() { + return nil, errors.New("Attempting to merge fundamentally different objects, which has not yet been implemented\n"+ + "Please contact us and share how you ran into this error to better help our development efforts.") + } return &tm, nil } -func (rm *RootMerger) maybeShortCircuit(ctx context.Context, tm *TableMerger, opts MergeOpts) (*doltdb.Table, *MergeStats, error) { +func (rm *RootMerger) maybeShortCircuit(ctx context.Context, tm *TableMerger, opts MergeOpts) (*doltdb.Table, doltdb.RootObject, *MergeStats, error) { // If we need to re-verify all constraints as part of this merge, then we can't short // circuit considering any tables, so return immediately if opts.ReverifyAllConstraints { - return nil, nil, nil + return nil, nil, nil, nil } - rootHash, mergeHash, ancHash, err := tm.tableHashes() + rootHash, mergeHash, ancHash, err := tm.tableHashes(ctx) if err != nil { - return nil, nil, err + return nil, nil, nil, err } - leftExists := tm.leftTbl != nil - rightExists := tm.rightTbl != nil - ancExists := tm.ancTbl != nil + leftExists := tm.leftTbl != nil || tm.leftRootObj != nil + rightExists := tm.rightTbl != nil || tm.rightRootObj != nil + ancExists := tm.ancTbl != nil || tm.ancRootObj != nil + areRootObjs := tm.leftRootObj != nil || tm.rightRootObj != nil || tm.ancRootObj != nil // Nothing changed if leftExists && rightExists && ancExists && rootHash == mergeHash && rootHash == ancHash { - return tm.leftTbl, &MergeStats{Operation: TableUnmodified}, nil + return tm.leftTbl, tm.leftRootObj, &MergeStats{Operation: TableUnmodified}, nil } // Both made identical changes // For keyless tables, this counts as a conflict if leftExists && rightExists && rootHash == mergeHash && !schema.IsKeyless(tm.leftSch) { - return tm.leftTbl, &MergeStats{Operation: TableUnmodified}, nil + return tm.leftTbl, tm.leftRootObj, &MergeStats{Operation: TableUnmodified}, nil } // One or both added this table if !ancExists { if rightExists && leftExists { if !schema.SchemasAreEqual(tm.leftSch, tm.rightSch) { - return nil, nil, ErrSameTblAddedTwice.New(tm.name) + return nil, nil, nil, ErrSameTblAddedTwice.New(tm.name) } } else if leftExists { // fast-forward - return tm.leftTbl, &MergeStats{Operation: TableUnmodified}, nil + return tm.leftTbl, tm.leftRootObj, &MergeStats{Operation: TableUnmodified}, nil } else { // fast-forward - return tm.rightTbl, &MergeStats{Operation: TableAdded}, nil + return tm.rightTbl, tm.rightRootObj, &MergeStats{Operation: TableAdded}, nil } } // Deleted in both, fast-forward if ancExists && !leftExists && !rightExists { - return nil, &MergeStats{Operation: TableRemoved}, nil + return nil, nil, &MergeStats{Operation: TableRemoved}, nil } // Deleted in root or in merge, either a conflict (if any changes in other root) or else a fast-forward @@ -339,38 +397,68 @@ func (rm *RootMerger) maybeShortCircuit(ctx context.Context, tm *TableMerger, op if childHash != ancHash { schemasEqual, err := doltdb.SchemaHashesEqual(ctx, childTable, tm.ancTbl) if err != nil { - return nil, nil, err + return nil, nil, nil, err } - if schemasEqual { - return nil, nil, ErrTableDeletedAndModified + if schemasEqual || areRootObjs { + return nil, nil, nil, ErrTableDeletedAndModified } else { - return nil, nil, ErrTableDeletedAndSchemaModified + return nil, nil, nil, ErrTableDeletedAndSchemaModified } } // fast-forward - return nil, &MergeStats{Operation: TableRemoved}, nil + return nil, nil, &MergeStats{Operation: TableRemoved}, nil } // Changes only in root, table unmodified if mergeHash == ancHash { - return tm.leftTbl, &MergeStats{Operation: TableUnmodified}, nil + return tm.leftTbl, tm.leftRootObj, &MergeStats{Operation: TableUnmodified}, nil } // Changes only in merge root, fast-forward // TODO : no fast-forward when cherry-picking for now if !opts.IsCherryPick && rootHash == ancHash { ms := MergeStats{Operation: TableModified} - if rootHash != mergeHash { + if rootHash != mergeHash && !areRootObjs { ms, err = calcTableMergeStats(ctx, tm.leftTbl, tm.rightTbl) if err != nil { - return nil, nil, err + return nil, nil, nil, err } } - return tm.rightTbl, &ms, nil + return tm.rightTbl, tm.rightRootObj, &ms, nil } // no short-circuit - return nil, nil, nil + return nil, nil, nil, nil +} + +// HasTable returns whether any of the table fields have been set. +func (tm TableMerger) HasTable() bool { + return tm.leftTbl != nil || tm.rightTbl != nil || tm.ancTbl != nil +} + +// HasRootObject returns whether any of the root object fields have been set. + func (tm TableMerger) HasRootObject() bool { + return tm.leftRootObj != nil || tm.rightRootObj != nil || tm.ancRootObj != nil + } + +// MergeRootObject contains all the information needed for MergeRootObjects to perform a merge. +type MergeRootObject struct { + Name doltdb.TableName + OurRootObj doltdb.RootObject + TheirRootObj doltdb.RootObject + AncestorRootObj doltdb.RootObject + RightSrc doltdb.Rootish + AncestorSrc doltdb.Rootish + VRW types.ValueReadWriter + NS tree.NodeStore +} + +// MergeRootObjects handles merging root objects, which is primarily used by Doltgres. This is implemented as a function +// pointer due to import cycles, as the `doltdb` package is referenced from within this `merge` package. To change this +// to a proper interface would mean that several items would need to be moved into `doltdb`, creating a sort of +// dual-location for the implementation to reside. Keeping this as a pointer makes it much simpler. +var MergeRootObjects = func(ctx context.Context, mro MergeRootObject) (doltdb.RootObject, *MergeStats, error) { + return nil, nil, errors.New("Dolt does not operate on root objects") } func setConflicts(ctx context.Context, cons durable.ConflictIndex, tbl, mergeTbl, ancTbl, tableToUpdate *doltdb.Table) (*doltdb.Table, error) { diff --git a/go/libraries/doltcore/merge/merge_schema.go b/go/libraries/doltcore/merge/merge_schema.go index 10ee11c20e6..bce43ebfe27 100644 --- a/go/libraries/doltcore/merge/merge_schema.go +++ b/go/libraries/doltcore/merge/merge_schema.go @@ -171,6 +171,10 @@ func SchemaMerge( ourSch, theirSch, ancSch schema.Schema, tblName doltdb.TableName, ) (sch schema.Schema, sc SchemaConflict, mergeInfo MergeInfo, diffInfo tree.ThreeWayDiffInfo, err error) { + // If the schemas are nil, then we have nothing to do here + if ourSch == nil && theirSch == nil && ancSch == nil { + return nil, sc, mergeInfo, diffInfo, nil + } // (sch - ancSch) ∪ (mergeSch - ancSch) ∪ (sch ∩ mergeSch) sc = SchemaConflict{ TableName: tblName, diff --git a/go/libraries/doltcore/sqle/dprocedures/dolt_add.go b/go/libraries/doltcore/sqle/dprocedures/dolt_add.go index eb73133734e..5afe9b14376 100644 --- a/go/libraries/doltcore/sqle/dprocedures/dolt_add.go +++ b/go/libraries/doltcore/sqle/dprocedures/dolt_add.go @@ -101,7 +101,7 @@ func doDoltAdd(ctx *sql.Context, args []string) (int, error) { var missingTables []string tableNames = make([]doltdb.TableName, len(unqualifiedTableNames)) for i, name := range unqualifiedTableNames { - tblName, _, ok, err := resolve.TableWithSearchPath(ctx, roots.Working, name) + tblName, ok, err := resolve.TableNameWithSearchPath(ctx, roots.Working, name) if err != nil { return 1, err } diff --git a/go/libraries/doltcore/sqle/dprocedures/dolt_checkout.go b/go/libraries/doltcore/sqle/dprocedures/dolt_checkout.go index 364b3f35798..fa42c4190cd 100644 --- a/go/libraries/doltcore/sqle/dprocedures/dolt_checkout.go +++ b/go/libraries/doltcore/sqle/dprocedures/dolt_checkout.go @@ -103,6 +103,8 @@ func doDoltCheckout(ctx *sql.Context, args []string) (statusCode int, successMes updateHead := apr.Contains(cli.MoveFlag) var rsc doltdb.ReplicationStatusController + // If we're switching branches, then we need to clear any Doltgres session objects since they're temporary + dSess.DoltgresSessObj = nil // Checking out new branch. if branchOrTrack { diff --git a/go/libraries/doltcore/sqle/dtablefunctions/dolt_diff_summary_table_function.go b/go/libraries/doltcore/sqle/dtablefunctions/dolt_diff_summary_table_function.go index 0ab8b37df08..11198ef893d 100644 --- a/go/libraries/doltcore/sqle/dtablefunctions/dolt_diff_summary_table_function.go +++ b/go/libraries/doltcore/sqle/dtablefunctions/dolt_diff_summary_table_function.go @@ -316,7 +316,7 @@ func (ds *DiffSummaryTableFunction) RowIter(ctx *sql.Context, row sql.Row) (sql. } func getSummaryForDelta(ctx *sql.Context, delta diff.TableDelta, sqledb dsess.SqlDatabase, fromDetails, toDetails *refDetails, shouldErrorOnPKChange bool) (*diff.TableDeltaSummary, error) { - if delta.FromTable == nil && delta.ToTable == nil { + if delta.FromTable == nil && delta.ToTable == nil && delta.FromRootObject == nil && delta.ToRootObject == nil { if !strings.HasPrefix(delta.FromName.Name, diff.DBPrefix) && !strings.HasPrefix(delta.ToName.Name, diff.DBPrefix) { return nil, nil } diff --git a/go/libraries/doltcore/sqle/resolve/resolve_tables.go b/go/libraries/doltcore/sqle/resolve/resolve_tables.go index f6e2c41a46c..de58a378c8d 100755 --- a/go/libraries/doltcore/sqle/resolve/resolve_tables.go +++ b/go/libraries/doltcore/sqle/resolve/resolve_tables.go @@ -37,6 +37,24 @@ func Table( return tName, tbl, tblExists, err } +// TableName returns the schema-qualified name of the table given in the root provided, along with whether it exists. +func TableName( + ctx *sql.Context, + root doltdb.RootValue, + tableName string, +) (doltdb.TableName, bool, error) { + if UseSearchPath { + return TableNameWithSearchPath(ctx, root, tableName) + } + + tName := doltdb.TableName{Schema: doltdb.DefaultSchemaName, Name: tableName} + resolvedName, ok, err := root.ResolveTableName(ctx, tName) + if err != nil || !ok { + return doltdb.TableName{}, false, err + } + return doltdb.TableName{Schema: doltdb.DefaultSchemaName, Name: resolvedName}, true, nil +} + // TablesOnSearchPath returns all the tables in the root value given that are in a schema in the search path func TablesOnSearchPath(ctx *sql.Context, root doltdb.RootValue) ([]doltdb.TableName, error) { schemasToSearch, err := SearchPath(ctx) @@ -56,39 +74,58 @@ func TablesOnSearchPath(ctx *sql.Context, root doltdb.RootValue) ([]doltdb.Table return tableNames, nil } -// TableWithSearchPath resolves a table name to a table in the root value, searching through the schemas in the -func TableWithSearchPath( +// TableNameWithSearchPath resolves a table name in the root value, searching through the schemas in the search path. +func TableNameWithSearchPath( ctx *sql.Context, root doltdb.RootValue, tableName string, -) (doltdb.TableName, *doltdb.Table, bool, error) { +) (doltdb.TableName, bool, error) { schemasToSearch, err := SearchPath(ctx) if err != nil { - return doltdb.TableName{}, nil, false, err + return doltdb.TableName{}, false, err } for _, schemaName := range schemasToSearch { tablesInSchema, err := root.GetTableNames(ctx, schemaName) if err != nil { - return doltdb.TableName{}, nil, false, err + return doltdb.TableName{}, false, err } - correctedTableName, ok := sql.GetTableNameInsensitive(tableName, tablesInSchema) if !ok { continue } candidate := doltdb.TableName{Name: correctedTableName, Schema: schemaName} - tbl, ok, err := root.GetTable(ctx, candidate) + ok, err = root.HasTable(ctx, candidate) if err != nil { - return doltdb.TableName{}, nil, false, err + return doltdb.TableName{}, false, err } else if !ok { // Should be impossible - return doltdb.TableName{}, nil, false, nil + return doltdb.TableName{}, false, nil } - return candidate, tbl, true, nil + return candidate, true, nil } - return doltdb.TableName{}, nil, false, nil + return doltdb.TableName{}, false, nil +} + +// TableWithSearchPath resolves a table name to a table in the root value, searching through the schemas in the search path. +func TableWithSearchPath( + ctx *sql.Context, + root doltdb.RootValue, + tableName string, +) (doltdb.TableName, *doltdb.Table, bool, error) { + correctedName, ok, err := TableNameWithSearchPath(ctx, root, tableName) + if err != nil || !ok { + return doltdb.TableName{}, nil, false, err + } + tbl, ok, err := root.GetTable(ctx, correctedName) + if err != nil { + return doltdb.TableName{}, nil, false, err + } else if !ok { + // Should be impossible + return doltdb.TableName{}, nil, false, nil + } + return correctedName, tbl, true, nil } From db61ca6d82fdacc0c437b31a9419b734875520a4 Mon Sep 17 00:00:00 2001 From: Hydrocharged Date: Wed, 26 Mar 2025 14:31:40 +0000 Subject: [PATCH 2/2] [ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/update.sh --- go/libraries/doltcore/merge/merge_rows.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/go/libraries/doltcore/merge/merge_rows.go b/go/libraries/doltcore/merge/merge_rows.go index 246922411ae..9fa93590c65 100644 --- a/go/libraries/doltcore/merge/merge_rows.go +++ b/go/libraries/doltcore/merge/merge_rows.go @@ -146,7 +146,7 @@ func NewMerger( // MergedResult returns either the merged table or merged root object. Both fields will never be set simultaneously. type MergedResult struct { - table *doltdb.Table // If non-nil, represents a merged table (and not a merged root object). + table *doltdb.Table // If non-nil, represents a merged table (and not a merged root object). rootObj doltdb.RootObject // If non-nil, represents a merged root object (and not a merged table). conflict SchemaConflict } @@ -329,7 +329,7 @@ func (rm *RootMerger) makeTableMerger(ctx context.Context, tblName doltdb.TableN // TODO: need to determine what to do if we have a mix of both tables and root objects (we'll error for now) if tm.HasTable() && tm.HasRootObject() { - return nil, errors.New("Attempting to merge fundamentally different objects, which has not yet been implemented\n"+ + return nil, errors.New("Attempting to merge fundamentally different objects, which has not yet been implemented\n" + "Please contact us and share how you ran into this error to better help our development efforts.") } return &tm, nil @@ -437,9 +437,9 @@ func (tm TableMerger) HasTable() bool { } // HasRootObject returns whether any of the root object fields have been set. - func (tm TableMerger) HasRootObject() bool { - return tm.leftRootObj != nil || tm.rightRootObj != nil || tm.ancRootObj != nil - } +func (tm TableMerger) HasRootObject() bool { + return tm.leftRootObj != nil || tm.rightRootObj != nil || tm.ancRootObj != nil +} // MergeRootObject contains all the information needed for MergeRootObjects to perform a merge. type MergeRootObject struct {