mirror of
https://github.com/dolthub/dolt.git
synced 2026-01-06 08:50:04 -06:00
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:
@@ -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
|
||||
|
||||
85
go/libraries/doltcore/sqle/adapters/table.go
Normal file
85
go/libraries/doltcore/sqle/adapters/table.go
Normal 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
|
||||
}
|
||||
64
go/libraries/doltcore/sqle/adapters/table_test.go
Normal file
64
go/libraries/doltcore/sqle/adapters/table_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user