Skip to content

Commit b80aedc

Browse files
authored
Merge pull request #2289 from dolthub/daylon/alter-table-with-table-types
Table types are handled by all relevant ALTER TABLE statements
2 parents 1eded64 + de17e98 commit b80aedc

File tree

5 files changed

+617
-0
lines changed

5 files changed

+617
-0
lines changed

server/hook/rename_table.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright 2026 Dolthub, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package hook
16+
17+
import (
18+
"fmt"
19+
20+
"github.com/cockroachdb/errors"
21+
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
22+
"github.com/dolthub/go-mysql-server/sql"
23+
"github.com/dolthub/go-mysql-server/sql/plan"
24+
25+
"github.com/dolthub/doltgresql/core"
26+
"github.com/dolthub/doltgresql/core/id"
27+
pgtypes "github.com/dolthub/doltgresql/server/types"
28+
)
29+
30+
// AfterTableRename handles updating various columns using the table type, alongside other validation that's unique
31+
// to Doltgres.
32+
func AfterTableRename(ctx *sql.Context, runner sql.StatementRunner, nodeInterface sql.Node) error {
33+
n, ok := nodeInterface.(*plan.RenameTable)
34+
if !ok {
35+
return errors.Errorf("RENAME TABLE post-hook expected `*plan.RenameTable` but received `%T`", nodeInterface)
36+
}
37+
38+
// Grab the table being altered (so we know the schema)
39+
sqlTable, ok := n.TableExists(ctx, n.NewNames[0])
40+
if !ok {
41+
// Views do not manifest as tables, so we'll return here if this isn't a table
42+
return nil
43+
}
44+
doltTable := core.SQLTableToDoltTable(sqlTable)
45+
if doltTable == nil {
46+
// If this table isn't a Dolt table then we don't have anything to do
47+
return nil
48+
}
49+
_, root, err := core.GetRootFromContext(ctx)
50+
if err != nil {
51+
return err
52+
}
53+
tableName := doltTable.TableName()
54+
tableName.Name = n.OldNames[0]
55+
tableAsType := id.NewType(tableName.Schema, tableName.Name)
56+
allTableNames, err := root.GetAllTableNames(ctx, false)
57+
if err != nil {
58+
return err
59+
}
60+
61+
for _, otherTableName := range allTableNames {
62+
if doltdb.IsSystemTable(otherTableName) {
63+
// System tables don't use any table types
64+
continue
65+
}
66+
otherTable, ok, err := root.GetTable(ctx, otherTableName)
67+
if err != nil {
68+
return err
69+
}
70+
if !ok {
71+
return errors.Errorf("root returned table name `%s` but it could not be found?", otherTableName.String())
72+
}
73+
otherTableSch, err := otherTable.GetSchema(ctx)
74+
if err != nil {
75+
return err
76+
}
77+
for _, otherCol := range otherTableSch.GetAllCols().GetColumns() {
78+
colType := otherCol.TypeInfo.ToSqlType()
79+
dgtype, ok := colType.(*pgtypes.DoltgresType)
80+
if !ok {
81+
// If this isn't a Doltgres type, then it can't be a table type so we can ignore it
82+
continue
83+
}
84+
if dgtype.ID != tableAsType {
85+
// This column isn't our table type, so we can ignore it
86+
continue
87+
}
88+
// The ALTER updates the type on the schema since it still has the old one
89+
alterStr := fmt.Sprintf(`ALTER TABLE "%s"."%s" ALTER COLUMN "%s" TYPE "%s"."%s";`,
90+
otherTableName.Schema, otherTableName.Name, otherCol.Name, tableName.Schema, n.NewNames[0])
91+
// We run the statement as though it's interpreted since we're running new statements inside the original
92+
_, err = sql.RunInterpreted(ctx, func(subCtx *sql.Context) ([]sql.Row, error) {
93+
_, rowIter, _, err := runner.QueryWithBindings(subCtx, alterStr, nil, nil, nil)
94+
if err != nil {
95+
return nil, err
96+
}
97+
return sql.RowIterToRows(subCtx, rowIter)
98+
})
99+
if err != nil {
100+
return err
101+
}
102+
}
103+
}
104+
return nil
105+
}

server/hook/table_modify_column.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright 2026 Dolthub, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package hook
16+
17+
import (
18+
"github.com/cockroachdb/errors"
19+
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
20+
"github.com/dolthub/go-mysql-server/sql"
21+
"github.com/dolthub/go-mysql-server/sql/plan"
22+
23+
"github.com/dolthub/doltgresql/core"
24+
"github.com/dolthub/doltgresql/core/id"
25+
pgtypes "github.com/dolthub/doltgresql/server/types"
26+
)
27+
28+
// beforeTableModifyColumnChange represents what properties of a column changed when a call is made to BeforeTableModifyColumn.
29+
type beforeTableModifyColumnChange uint8
30+
31+
const (
32+
beforeTableModifyColumnChange_None beforeTableModifyColumnChange = iota
33+
beforeTableModifyColumnChange_Type
34+
)
35+
36+
// BeforeTableModifyColumn handles validation that's unique to Doltgres.
37+
func BeforeTableModifyColumn(ctx *sql.Context, runner sql.StatementRunner, nodeInterface sql.Node) (sql.Node, error) {
38+
n, ok := nodeInterface.(*plan.ModifyColumn)
39+
if !ok {
40+
return nil, errors.Errorf("MODIFY COLUMN pre-hook expected `*plan.ModifyColumn` but received `%T`", nodeInterface)
41+
}
42+
43+
// Figure out what was changed. We know it's not the name because we have a dedicated *RenameColumn node.
44+
changed := beforeTableModifyColumnChange_None
45+
newColumn := n.NewColumn()
46+
for _, col := range n.TargetSchema() {
47+
if col.Name == newColumn.Name {
48+
if !col.Type.Equals(newColumn.Type) {
49+
changed = beforeTableModifyColumnChange_Type
50+
}
51+
}
52+
}
53+
if changed == beforeTableModifyColumnChange_None {
54+
return n, nil
55+
}
56+
57+
// Grab the table being altered (so we know the schema)
58+
doltTable := core.SQLNodeToDoltTable(n.Table)
59+
if doltTable == nil {
60+
// If this table isn't a Dolt table then we don't have anything to do
61+
return n, nil
62+
}
63+
_, root, err := core.GetRootFromContext(ctx)
64+
if err != nil {
65+
return n, nil
66+
}
67+
tableName := doltTable.TableName()
68+
tableAsType := id.NewType(tableName.Schema, tableName.Name)
69+
allTableNames, err := root.GetAllTableNames(ctx, false)
70+
if err != nil {
71+
return nil, err
72+
}
73+
74+
for _, otherTableName := range allTableNames {
75+
if doltdb.IsSystemTable(otherTableName) {
76+
// System tables don't use any table types
77+
continue
78+
}
79+
otherTable, ok, err := root.GetTable(ctx, otherTableName)
80+
if err != nil {
81+
return nil, err
82+
}
83+
if !ok {
84+
return nil, errors.Errorf("root returned table name `%s` but it could not be found?", otherTableName.String())
85+
}
86+
otherTableSch, err := otherTable.GetSchema(ctx)
87+
if err != nil {
88+
return nil, err
89+
}
90+
for _, otherCol := range otherTableSch.GetAllCols().GetColumns() {
91+
colType := otherCol.TypeInfo.ToSqlType()
92+
dgtype, ok := colType.(*pgtypes.DoltgresType)
93+
if !ok {
94+
// If this isn't a Doltgres type, then it can't be a table type so we can ignore it
95+
continue
96+
}
97+
if dgtype.ID != tableAsType {
98+
// This column isn't our table type, so we can ignore it
99+
continue
100+
}
101+
return nil, errors.Errorf(`cannot alter table "%s" because column "%s.%s" uses its row type`,
102+
tableName.Name, otherTableName.Name, otherCol.Name)
103+
}
104+
}
105+
return n, nil
106+
}

server/hook/table_rename_column.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright 2026 Dolthub, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package hook
16+
17+
import (
18+
"fmt"
19+
20+
"github.com/cockroachdb/errors"
21+
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
22+
"github.com/dolthub/go-mysql-server/sql"
23+
"github.com/dolthub/go-mysql-server/sql/plan"
24+
25+
"github.com/dolthub/doltgresql/core"
26+
"github.com/dolthub/doltgresql/core/id"
27+
pgtypes "github.com/dolthub/doltgresql/server/types"
28+
)
29+
30+
// AfterTableRenameColumn handles updating various table columns, alongside other validation that's unique to Doltgres.
31+
func AfterTableRenameColumn(ctx *sql.Context, runner sql.StatementRunner, nodeInterface sql.Node) error {
32+
n, ok := nodeInterface.(*plan.RenameColumn)
33+
if !ok {
34+
return errors.Errorf("RENAME COLUMN post-hook expected `*plan.RenameColumn` but received `%T`", nodeInterface)
35+
}
36+
if n.ColumnName == n.NewColumnName {
37+
return nil
38+
}
39+
40+
// Grab the table being altered
41+
doltTable := core.SQLNodeToDoltTable(n.Table)
42+
if doltTable == nil {
43+
// If this table isn't a Dolt table then we don't have anything to do
44+
return nil
45+
}
46+
_, root, err := core.GetRootFromContext(ctx)
47+
if err != nil {
48+
return err
49+
}
50+
tableName := doltTable.TableName()
51+
tableAsType := id.NewType(tableName.Schema, tableName.Name)
52+
allTableNames, err := root.GetAllTableNames(ctx, false)
53+
if err != nil {
54+
return err
55+
}
56+
57+
for _, otherTableName := range allTableNames {
58+
if doltdb.IsSystemTable(otherTableName) {
59+
// System tables don't use any table types
60+
continue
61+
}
62+
otherTable, ok, err := root.GetTable(ctx, otherTableName)
63+
if err != nil {
64+
return err
65+
}
66+
if !ok {
67+
return errors.Errorf("root returned table name `%s` but it could not be found?", otherTableName.String())
68+
}
69+
otherTableSch, err := otherTable.GetSchema(ctx)
70+
if err != nil {
71+
return err
72+
}
73+
for _, otherCol := range otherTableSch.GetAllCols().GetColumns() {
74+
colType := otherCol.TypeInfo.ToSqlType()
75+
dgtype, ok := colType.(*pgtypes.DoltgresType)
76+
if !ok {
77+
// If this isn't a Doltgres type, then it can't be a table type so we can ignore it
78+
continue
79+
}
80+
if dgtype.ID != tableAsType {
81+
// This column isn't our table type, so we can ignore it
82+
continue
83+
}
84+
// The ALTER updates the type on the schema since it still has the old one
85+
alterStr := fmt.Sprintf(`ALTER TABLE "%s"."%s" ALTER COLUMN "%s" TYPE "%s"."%s";`,
86+
otherTableName.Schema, otherTableName.Name, otherCol.Name, tableName.Schema, tableName.Name)
87+
// We run the statement as though it were interpreted since we're running new statements inside the original
88+
_, err = sql.RunInterpreted(ctx, func(subCtx *sql.Context) ([]sql.Row, error) {
89+
_, rowIter, _, err := runner.QueryWithBindings(subCtx, alterStr, nil, nil, nil)
90+
if err != nil {
91+
return nil, err
92+
}
93+
return sql.RowIterToRows(subCtx, rowIter)
94+
})
95+
if err != nil {
96+
return err
97+
}
98+
}
99+
}
100+
return nil
101+
}

servercfg/config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,22 @@ func (*DoltgresConfig) Overrides() sql.EngineOverrides {
4747
Parser: pgsql.NewPostgresParser(),
4848
},
4949
Hooks: sql.ExecutionHooks{
50+
RenameTable: sql.RenameTable{
51+
PostSQLExecution: hook.AfterTableRename,
52+
},
5053
DropTable: sql.DropTable{
5154
PreSQLExecution: hook.BeforeTableDeletion,
5255
},
5356
TableAddColumn: sql.TableAddColumn{
5457
PreSQLExecution: hook.BeforeTableAddColumn,
5558
PostSQLExecution: hook.AfterTableAddColumn,
5659
},
60+
TableRenameColumn: sql.TableRenameColumn{
61+
PostSQLExecution: hook.AfterTableRenameColumn,
62+
},
63+
TableModifyColumn: sql.TableModifyColumn{
64+
PreSQLExecution: hook.BeforeTableModifyColumn,
65+
},
5766
TableDropColumn: sql.TableDropColumn{
5867
PostSQLExecution: hook.AfterTableDropColumn,
5968
},

0 commit comments

Comments
 (0)