Merge pull request #10159 from dolthub/elian/7628c

Add `adapters.TableAdapter` to handle `dolt_status` and other table conversions for integrators (a.k.a. Doltgres)
This commit is contained in:
Elian
2025-12-10 11:57:31 -08:00
committed by GitHub
8 changed files with 187 additions and 60 deletions

View File

@@ -155,12 +155,13 @@ func GeneratedSystemTableNames() []string {
GetTableOfTablesWithViolationsName(),
GetCommitsTableName(),
GetCommitAncestorsTableName(),
GetStatusTableName(),
GetRemotesTableName(),
GetHelpTableName(),
GetBackupsTableName(),
GetStashesTableName(),
GetBranchActivityTableName(),
// [dtables.StatusTable] now uses [adapters.DoltTableAdapterRegistry] in its constructor for Doltgres.
StatusTableName,
}
}
@@ -367,11 +368,6 @@ var GetSchemaConflictsTableName = func() string {
return SchemaConflictsTableName
}
// GetStatusTableName returns the status system table name.
var GetStatusTableName = func() string {
return StatusTableName
}
// GetTagsTableName returns the tags table name
var GetTagsTableName = func() string {
return TagsTableName

View File

@@ -0,0 +1,85 @@
// 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 adapters
import (
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
)
// TableAdapter provides a hook for extensions to customize or wrap table implementations. For example, this allows
// libraries like Doltgres to intercept system table creation and apply type conversions, schema modifications, or other
// customizations without modifying the core Dolt implementation for their compatibility.
type TableAdapter interface {
// NewTable creates or wraps a system table. The function receives all necessary parameters to construct the table
// and can either build it from scratch or call the default Dolt constructor and wrap it.
NewTable(ctx *sql.Context, tableName string, dDb *doltdb.DoltDB, workingSet *doltdb.WorkingSet, rootsProvider env.RootsProvider[*sql.Context]) sql.Table
// TableName returns the preferred name for the adapter's table. This allows extensions to rename tables while
// preserving the underlying implementation. For example, Doltgres uses "status" while Dolt uses "dolt_status",
// enabling cleaner Postgres-style naming.
TableName() string
}
var DoltTableAdapterRegistry = newDoltTableAdapterRegistry()
// doltTableAdapterRegistry is a Dolt table name to TableAdapter map. Integrators populate this registry during package
// initialization, and it's intended to be read-only thereafter. The registry links with existing Dolt system tables to
// allow them to be resolved and evaluated to integrator's version and internal aliases (integrators' Dolt table name
// keys).
type doltTableAdapterRegistry struct {
Adapters map[string]TableAdapter
internalAliases map[string]string
}
// newDoltTableAdapterRegistry constructs Dolt table adapter registry with empty internal alias and adapter maps.
func newDoltTableAdapterRegistry() *doltTableAdapterRegistry {
return &doltTableAdapterRegistry{
Adapters: make(map[string]TableAdapter),
internalAliases: make(map[string]string),
}
}
// AddAdapter maps |doltTableName| to an |adapter| in the Dolt table adapter registry, with optional |internalAliases|.
func (as *doltTableAdapterRegistry) AddAdapter(doltTableName string, adapter TableAdapter, internalAliases ...string) {
for _, alias := range internalAliases {
as.internalAliases[alias] = doltTableName
}
as.Adapters[doltTableName] = adapter
}
// GetAdapter gets a Dolt TableAdapter mapped to |name|, which can be the dolt table name or internal alias.
func (as *doltTableAdapterRegistry) GetAdapter(name string) (TableAdapter, bool) {
adapter, ok := as.Adapters[name]
if !ok {
name = as.internalAliases[name]
adapter, ok = as.Adapters[name]
}
return adapter, ok
}
// NormalizeName normalizes |name| if it's an internal alias of the underlying Dolt table name. If no match is found,
// |name| is returned as-is.
func (as *doltTableAdapterRegistry) NormalizeName(name string) string {
doltTableName, ok := as.internalAliases[name]
if !ok {
return name
}
return doltTableName
}

View File

@@ -0,0 +1,64 @@
package adapters
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
)
type mockAdapter struct {
name string
}
func (m mockAdapter) NewTable(_ *sql.Context, _ string, _ *doltdb.DoltDB, _ *doltdb.WorkingSet, _ env.RootsProvider[*sql.Context]) sql.Table {
return nil
}
func (m mockAdapter) TableName() string {
return m.name
}
func TestDoltTableAdapterRegistry(t *testing.T) {
registry := newDoltTableAdapterRegistry()
statusAdapter := mockAdapter{name: "status"}
logAdapter := mockAdapter{name: "log"}
registry.AddAdapter(doltdb.StatusTableName, statusAdapter, "status")
registry.AddAdapter(doltdb.LogTableName, logAdapter, "log")
t.Run("GetAdapter", func(t *testing.T) {
adapter, ok := registry.GetAdapter("dolt_status")
require.True(t, ok)
require.Equal(t, "status", adapter.TableName())
adapter, ok = registry.GetAdapter("status")
require.True(t, ok)
require.Equal(t, "status", adapter.TableName())
_, ok = registry.GetAdapter("unknown_alias")
require.False(t, ok)
_, ok = registry.GetAdapter("dolt_unknown")
require.False(t, ok)
})
t.Run("NormalizeName", func(t *testing.T) {
normalized := registry.NormalizeName("status")
require.Equal(t, "dolt_status", normalized)
normalized = registry.NormalizeName("log")
require.Equal(t, "dolt_log", normalized)
normalized = registry.NormalizeName("dolt_status")
require.Equal(t, "dolt_status", normalized)
normalized = registry.NormalizeName("unknown_table")
require.Equal(t, "unknown_table", normalized)
})
}

View File

@@ -44,6 +44,7 @@ import (
"github.com/dolthub/dolt/go/libraries/doltcore/rebase"
"github.com/dolthub/dolt/go/libraries/doltcore/ref"
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/adapters"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dprocedures"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dtables"
@@ -621,7 +622,7 @@ func (db Database) getTableInsensitiveWithRoot(ctx *sql.Context, head *doltdb.Co
var dt sql.Table
found := false
tname := doltdb.TableName{Name: lwrName, Schema: db.schemaName}
switch lwrName {
switch adapters.DoltTableAdapterRegistry.NormalizeName(lwrName) {
case doltdb.GetLogTableName(), doltdb.LogTableName:
isDoltgresSystemTable, err := resolve.IsDoltgresSystemTable(ctx, tname, root)
if err != nil {
@@ -750,7 +751,7 @@ func (db Database) getTableInsensitiveWithRoot(ctx *sql.Context, head *doltdb.Co
if !resolve.UseSearchPath || isDoltgresSystemTable {
dt, found = dtables.NewCommitAncestorsTable(ctx, db.Name(), lwrName, db.ddb), true
}
case doltdb.GetStatusTableName(), doltdb.StatusTableName:
case doltdb.StatusTableName:
isDoltgresSystemTable, err := resolve.IsDoltgresSystemTable(ctx, tname, root)
if err != nil {
return nil, false, err

View File

@@ -79,16 +79,8 @@ func doltBackup(ctx *sql.Context, args ...string) (sql.RowIter, error) {
return nil, err
}
if sqlserver.RunningInServerMode() {
// TODO(elianddb): DoltgreSQL needs an auth handler for stored procedures, i.e. AuthType_CALL, but for now we use
// this. dolt_backup already requires admin privilege on GMS due to its potentially destructive nature.
privileges, counter := ctx.GetPrivilegeSet()
if counter == 0 || !privileges.Has(sql.PrivilegeType_Super) {
return nil, sql.ErrPrivilegeCheckFailed.New(ctx.Session.Client().User)
}
if apr.ContainsAny(cli.AwsParams...) {
return nil, fmt.Errorf("AWS parameters are unavailable when running in server mode")
}
if sqlserver.RunningInServerMode() && apr.ContainsAny(cli.AwsParams...) {
return nil, fmt.Errorf("AWS parameters are unavailable when running in server mode")
}
doltSess := dsess.DSessFromSess(ctx.Session)

View File

@@ -27,11 +27,6 @@ func doltPurgeDroppedDatabases(ctx *sql.Context, args ...string) (sql.RowIter, e
return nil, fmt.Errorf("dolt_purge_dropped_databases does not take any arguments")
}
// Only allow admins to purge dropped databases
if err := checkDoltPurgeDroppedDatabasesPrivs(ctx); err != nil {
return nil, err
}
doltSession := dsess.DSessFromSess(ctx.Session)
err := doltSession.Provider().PurgeDroppedDatabases(ctx)
if err != nil {
@@ -40,18 +35,3 @@ func doltPurgeDroppedDatabases(ctx *sql.Context, args ...string) (sql.RowIter, e
return rowToIter(int64(cmdSuccess)), nil
}
// checkDoltPurgeDroppedDatabasesPrivs returns an error if the user requesting to purge dropped databases
// does not have SUPER access. Since this is a permanent and destructive operation, we restrict it to admins,
// even though the SUPER privilege has been deprecated, since there isn't another appropriate global privilege.
func checkDoltPurgeDroppedDatabasesPrivs(ctx *sql.Context) error {
privs, counter := ctx.GetPrivilegeSet()
if counter == 0 {
return fmt.Errorf("unable to check user privileges for dolt_purge_dropped_databases procedure")
}
if privs.Has(sql.PrivilegeType_Super) == false {
return sql.ErrPrivilegeCheckFailed.New(ctx.Session.Client().User)
}
return nil
}

View File

@@ -25,6 +25,7 @@ import (
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/adapters"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/index"
)
@@ -61,20 +62,12 @@ func (st StatusTable) String() string {
return st.tableName
}
func getDoltStatusSchema(tableName string) sql.Schema {
return []*sql.Column{
{Name: "table_name", Type: types.Text, Source: tableName, PrimaryKey: true, Nullable: false},
{Name: "staged", Type: types.Boolean, Source: tableName, PrimaryKey: true, Nullable: false},
{Name: "status", Type: types.Text, Source: tableName, PrimaryKey: true, Nullable: false},
}
}
// GetDoltStatusSchema returns the schema of the dolt_status system table. This is used
// by Doltgres to update the dolt_status schema using Doltgres types.
var GetDoltStatusSchema = getDoltStatusSchema
func (st StatusTable) Schema() sql.Schema {
return GetDoltStatusSchema(st.tableName)
return []*sql.Column{
{Name: "table_name", Type: types.Text, Source: doltdb.StatusTableName, PrimaryKey: true, Nullable: false},
{Name: "staged", Type: types.Boolean, Source: doltdb.StatusTableName, PrimaryKey: true, Nullable: false},
{Name: "status", Type: types.Text, Source: doltdb.StatusTableName, PrimaryKey: true, Nullable: false},
}
}
func (st StatusTable) Collation() sql.CollationID {
@@ -89,8 +82,19 @@ func (st StatusTable) PartitionRows(context *sql.Context, _ sql.Partition) (sql.
return newStatusItr(context, &st)
}
// NewStatusTable creates a StatusTable
func NewStatusTable(_ *sql.Context, tableName string, ddb *doltdb.DoltDB, ws *doltdb.WorkingSet, rp env.RootsProvider[*sql.Context]) sql.Table {
// NewStatusTable creates a new StatusTable using either an integrators' [adapters.TableAdapter] or the
// NewStatusTableWithNoAdapter constructor (the default implementation provided by Dolt).
func NewStatusTable(ctx *sql.Context, tableName string, ddb *doltdb.DoltDB, ws *doltdb.WorkingSet, rp env.RootsProvider[*sql.Context]) sql.Table {
adapter, ok := adapters.DoltTableAdapterRegistry.GetAdapter(tableName)
if ok {
return adapter.NewTable(ctx, tableName, ddb, ws, rp)
}
return NewStatusTableWithNoAdapter(ctx, tableName, ddb, ws, rp)
}
// NewStatusTableWithNoAdapter returns a new StatusTable.
func NewStatusTableWithNoAdapter(_ *sql.Context, tableName string, ddb *doltdb.DoltDB, ws *doltdb.WorkingSet, rp env.RootsProvider[*sql.Context]) sql.Table {
return &StatusTable{
tableName: tableName,
ddb: ddb,

View File

@@ -19,6 +19,7 @@ import (
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/adapters"
)
// GetGeneratedSystemTables returns table names of all generated system tables.
@@ -26,15 +27,19 @@ func GetGeneratedSystemTables(ctx context.Context, root doltdb.RootValue) ([]dol
s := doltdb.NewTableNameSet(nil)
// Depending on whether the search path is used, the generated system tables will either be in the dolt namespace
// or the empty (default) namespace
if !UseSearchPath {
for _, t := range doltdb.GeneratedSystemTableNames() {
s.Add(doltdb.TableName{Name: t})
// or the empty (default) namespace.
for _, tableName := range doltdb.GeneratedSystemTableNames() {
adapter, ok := adapters.DoltTableAdapterRegistry.Adapters[tableName]
if ok {
tableName = adapter.TableName()
}
} else {
for _, t := range doltdb.GeneratedSystemTableNames() {
s.Add(doltdb.TableName{Name: t, Schema: doltdb.DoltNamespace})
tableUnique := doltdb.TableName{Name: tableName}
if UseSearchPath {
tableUnique.Schema = doltdb.DoltNamespace
}
s.Add(tableUnique)
}
schemas, err := root.GetDatabaseSchemas(ctx)