Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions server/hook/rename_table.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright 2026 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 hook

import (
"fmt"

"github.com/cockroachdb/errors"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"

"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"
pgtypes "github.com/dolthub/doltgresql/server/types"
)

// AfterTableRename handles updating various columns using the table type, alongside other validation that's unique
// to Doltgres.
func AfterTableRename(ctx *sql.Context, runner sql.StatementRunner, nodeInterface sql.Node) error {
n, ok := nodeInterface.(*plan.RenameTable)
if !ok {
return errors.Errorf("RENAME TABLE post-hook expected `*plan.RenameTable` but received `%T`", nodeInterface)
}

// Grab the table being altered (so we know the schema)
sqlTable, ok := n.TableExists(ctx, n.NewNames[0])
if !ok {
return errors.New("RENAME TABLE post-hook did not receive a new table name")
}
doltTable := core.SQLTableToDoltTable(sqlTable)
if doltTable == nil {
// If this table isn't a Dolt table then we don't have anything to do
return nil
}
_, root, err := core.GetRootFromContext(ctx)
if err != nil {
return err
}
tableName := doltTable.TableName()
tableName.Name = n.OldNames[0]
tableAsType := id.NewType(tableName.Schema, tableName.Name)
allTableNames, err := root.GetAllTableNames(ctx, false)
if err != nil {
return err
}

for _, otherTableName := range allTableNames {
if doltdb.IsSystemTable(otherTableName) {
// System tables don't use any table types
continue
}
otherTable, ok, err := root.GetTable(ctx, otherTableName)
if err != nil {
return err
}
if !ok {
return errors.Errorf("root returned table name `%s` but it could not be found?", otherTableName.String())
}
otherTableSch, err := otherTable.GetSchema(ctx)
if err != nil {
return err
}
for _, otherCol := range otherTableSch.GetAllCols().GetColumns() {
colType := otherCol.TypeInfo.ToSqlType()
dgtype, ok := colType.(*pgtypes.DoltgresType)
if !ok {
// If this isn't a Doltgres type, then it can't be a table type so we can ignore it
continue
}
if dgtype.ID != tableAsType {
// This column isn't our table type, so we can ignore it
continue
}
// The ALTER updates the type on the schema since it still has the old one
alterStr := fmt.Sprintf(`ALTER TABLE "%s"."%s" ALTER COLUMN "%s" TYPE "%s"."%s";`,
otherTableName.Schema, otherTableName.Name, otherCol.Name, tableName.Schema, n.NewNames[0])
// We run the statement as though it's interpreted since we're running new statements inside the original
_, err = sql.RunInterpreted(ctx, func(subCtx *sql.Context) ([]sql.Row, error) {
_, rowIter, _, err := runner.QueryWithBindings(subCtx, alterStr, nil, nil, nil)
if err != nil {
return nil, err
}
return sql.RowIterToRows(subCtx, rowIter)
})
if err != nil {
return err
}
}
}
return nil
}
106 changes: 106 additions & 0 deletions server/hook/table_modify_column.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2026 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 hook

import (
"github.com/cockroachdb/errors"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"

"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"
pgtypes "github.com/dolthub/doltgresql/server/types"
)

// beforeTableModifyColumnChange represents what properties of a column changed when a call is made to BeforeTableModifyColumn.
type beforeTableModifyColumnChange uint8

const (
beforeTableModifyColumnChange_None beforeTableModifyColumnChange = iota
beforeTableModifyColumnChange_Type
)

// BeforeTableModifyColumn handles validation that's unique to Doltgres.
func BeforeTableModifyColumn(ctx *sql.Context, runner sql.StatementRunner, nodeInterface sql.Node) (sql.Node, error) {
n, ok := nodeInterface.(*plan.ModifyColumn)
if !ok {
return nil, errors.Errorf("MODIFY COLUMN pre-hook expected `*plan.ModifyColumn` but received `%T`", nodeInterface)
}

// Figure out what was changed. We know it's not the name because we have a dedicated *RenameColumn node.
changed := beforeTableModifyColumnChange_None
newColumn := n.NewColumn()
for _, col := range n.TargetSchema() {
if col.Name == newColumn.Name {
if !col.Type.Equals(newColumn.Type) {
changed = beforeTableModifyColumnChange_Type
}
}
}
if changed == beforeTableModifyColumnChange_None {
return n, nil
}

// Grab the table being altered (so we know the schema)
doltTable := core.SQLNodeToDoltTable(n.Table)
if doltTable == nil {
// If this table isn't a Dolt table then we don't have anything to do
return n, nil
}
_, root, err := core.GetRootFromContext(ctx)
if err != nil {
return n, nil
}
tableName := doltTable.TableName()
tableAsType := id.NewType(tableName.Schema, tableName.Name)
allTableNames, err := root.GetAllTableNames(ctx, false)
if err != nil {
return nil, err
}

for _, otherTableName := range allTableNames {
if doltdb.IsSystemTable(otherTableName) {
// System tables don't use any table types
continue
}
otherTable, ok, err := root.GetTable(ctx, otherTableName)
if err != nil {
return nil, err
}
if !ok {
return nil, errors.Errorf("root returned table name `%s` but it could not be found?", otherTableName.String())
}
otherTableSch, err := otherTable.GetSchema(ctx)
if err != nil {
return nil, err
}
for _, otherCol := range otherTableSch.GetAllCols().GetColumns() {
colType := otherCol.TypeInfo.ToSqlType()
dgtype, ok := colType.(*pgtypes.DoltgresType)
if !ok {
// If this isn't a Doltgres type, then it can't be a table type so we can ignore it
continue
}
if dgtype.ID != tableAsType {
// This column isn't our table type, so we can ignore it
continue
}
return nil, errors.Errorf(`cannot alter table "%s" because column "%s.%s" uses its row type`,
tableName.Name, otherTableName.Name, otherCol.Name)
}
}
return n, nil
}
101 changes: 101 additions & 0 deletions server/hook/table_rename_column.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2026 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 hook

import (
"fmt"

"github.com/cockroachdb/errors"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"

"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"
pgtypes "github.com/dolthub/doltgresql/server/types"
)

// AfterTableRenameColumn handles updating various table columns, alongside other validation that's unique to Doltgres.
func AfterTableRenameColumn(ctx *sql.Context, runner sql.StatementRunner, nodeInterface sql.Node) error {
n, ok := nodeInterface.(*plan.RenameColumn)
if !ok {
return errors.Errorf("RENAME COLUMN post-hook expected `*plan.RenameColumn` but received `%T`", nodeInterface)
}
if n.ColumnName == n.NewColumnName {
return nil
}

// Grab the table being altered
doltTable := core.SQLNodeToDoltTable(n.Table)
if doltTable == nil {
// If this table isn't a Dolt table then we don't have anything to do
return nil
}
_, root, err := core.GetRootFromContext(ctx)
if err != nil {
return err
}
tableName := doltTable.TableName()
tableAsType := id.NewType(tableName.Schema, tableName.Name)
allTableNames, err := root.GetAllTableNames(ctx, false)
if err != nil {
return err
}

for _, otherTableName := range allTableNames {
if doltdb.IsSystemTable(otherTableName) {
// System tables don't use any table types
continue
}
otherTable, ok, err := root.GetTable(ctx, otherTableName)
if err != nil {
return err
}
if !ok {
return errors.Errorf("root returned table name `%s` but it could not be found?", otherTableName.String())
}
otherTableSch, err := otherTable.GetSchema(ctx)
if err != nil {
return err
}
for _, otherCol := range otherTableSch.GetAllCols().GetColumns() {
colType := otherCol.TypeInfo.ToSqlType()
dgtype, ok := colType.(*pgtypes.DoltgresType)
if !ok {
// If this isn't a Doltgres type, then it can't be a table type so we can ignore it
continue
}
if dgtype.ID != tableAsType {
// This column isn't our table type, so we can ignore it
continue
}
// The ALTER updates the type on the schema since it still has the old one
alterStr := fmt.Sprintf(`ALTER TABLE "%s"."%s" ALTER COLUMN "%s" TYPE "%s"."%s";`,
Comment thread
Hydrocharged marked this conversation as resolved.
otherTableName.Schema, otherTableName.Name, otherCol.Name, tableName.Schema, tableName.Name)
// We run the statement as though it were interpreted since we're running new statements inside the original
_, err = sql.RunInterpreted(ctx, func(subCtx *sql.Context) ([]sql.Row, error) {
_, rowIter, _, err := runner.QueryWithBindings(subCtx, alterStr, nil, nil, nil)
if err != nil {
return nil, err
}
return sql.RowIterToRows(subCtx, rowIter)
})
if err != nil {
return err
}
}
}
return nil
}
9 changes: 9 additions & 0 deletions servercfg/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,22 @@ func (*DoltgresConfig) Overrides() sql.EngineOverrides {
Parser: pgsql.NewPostgresParser(),
},
Hooks: sql.ExecutionHooks{
RenameTable: sql.RenameTable{
PostSQLExecution: hook.AfterTableRename,
},
DropTable: sql.DropTable{
PreSQLExecution: hook.BeforeTableDeletion,
},
TableAddColumn: sql.TableAddColumn{
PreSQLExecution: hook.BeforeTableAddColumn,
PostSQLExecution: hook.AfterTableAddColumn,
},
TableRenameColumn: sql.TableRenameColumn{
PostSQLExecution: hook.AfterTableRenameColumn,
},
TableModifyColumn: sql.TableModifyColumn{
PreSQLExecution: hook.BeforeTableModifyColumn,
},
TableDropColumn: sql.TableDropColumn{
PostSQLExecution: hook.AfterTableDropColumn,
},
Expand Down
Loading