@@ -22,6 +22,7 @@ import (
2222 sqle "github.com/dolthub/go-mysql-server"
2323 "github.com/dolthub/go-mysql-server/sql"
2424 "github.com/dolthub/go-mysql-server/sql/analyzer"
25+ "github.com/dolthub/go-mysql-server/sql/plan"
2526 "github.com/dolthub/go-mysql-server/sql/types"
2627 "github.com/dolthub/vitess/go/vt/sqlparser"
2728)
@@ -33,13 +34,37 @@ func NewEngine(provider *catalog.DatabaseProvider) (*sqle.Engine, *DuckBuilder)
3334 parser := & mysqlParser {Parser : sql .NewMysqlParser ()}
3435 overrides := sql.EngineOverrides {
3536 Builder : sql.BuilderOverrides {Parser : parser },
37+ Hooks : sql.ExecutionHooks {
38+ CreateTable : sql.CreateTable {
39+ PreSQLExecution : prepareMySQLCreateTableStorage ,
40+ },
41+ },
3642 }
3743 engine := sqle .New (analyzer .NewBuilder (provider ).AddOverrides (overrides ).Build (), nil )
3844 builder := NewDuckBuilder (engine .Analyzer .ExecBuilder , provider )
3945 engine .Analyzer .ExecBuilder .PriorityBuilder = builder
4046 return engine , builder
4147}
4248
49+ // prepareMySQLCreateTableStorage bridges the planner's table-option map to
50+ // the catalog's request-scoped storage selector. The catalog consumes the
51+ // selector while creating the table and persists it in the managed comment;
52+ // no credentials, endpoints, or object paths are accepted from SQL.
53+ func prepareMySQLCreateTableStorage (ctx * sql.Context , _ sql.StatementRunner , node sql.Node ) (sql.Node , error ) {
54+ create , ok := node .(* plan.CreateTable )
55+ if ! ok {
56+ return node , nil
57+ }
58+ selection , err := catalog .ResolveMySQLTableStorage (create .TableOpts )
59+ if err != nil {
60+ return nil , err
61+ }
62+ if err := catalog .SetTableStorageSelection (ctx , selection ); err != nil {
63+ return nil , err
64+ }
65+ return create , nil
66+ }
67+
4368// registerMySQLCompatibilitySystemVariables keeps MyDuck's advertised SQL
4469// compatibility level stable across GMS upgrades. Clients such as MySQL Shell
4570// branch on @@version and otherwise probe newer variables MyDuck does not
@@ -67,13 +92,21 @@ type mysqlParser struct {
6792func (p * mysqlParser ) ParseSimple (query string ) (sqlparser.Statement , error ) {
6893 compat := rewriteMySQLCompatibility (query )
6994 stmt , err := p .Parser .ParseSimple (compat .query )
70- return normalizeMySQLStatement (stmt , compat .replacements ), err
95+ stmt = normalizeMySQLStatement (stmt , compat .replacements )
96+ if err == nil {
97+ err = validateMySQLTableStorageStatement (stmt )
98+ }
99+ return stmt , err
71100}
72101
73102func (p * mysqlParser ) Parse (ctx * sql.Context , query string , multi bool ) (sqlparser.Statement , string , string , error ) {
74103 compat := rewriteMySQLCompatibility (query )
75104 stmt , parsed , remainder , err := p .Parser .Parse (ctx , compat .query , multi )
76- return normalizeMySQLStatement (stmt , compat .replacements ), compat .restoreParsedQuery (parsed ), remainder , err
105+ stmt = normalizeMySQLStatement (stmt , compat .replacements )
106+ if err == nil {
107+ err = validateMySQLTableStorageStatement (stmt )
108+ }
109+ return stmt , compat .restoreParsedQuery (parsed ), remainder , err
77110}
78111
79112func (p * mysqlParser ) ParseWithOptions (
@@ -85,7 +118,11 @@ func (p *mysqlParser) ParseWithOptions(
85118) (sqlparser.Statement , string , string , error ) {
86119 compat := rewriteMySQLCompatibility (query )
87120 stmt , parsed , remainder , err := p .Parser .ParseWithOptions (ctx , compat .query , delimiter , multi , options )
88- return normalizeMySQLStatement (stmt , compat .replacements ), compat .restoreParsedQuery (parsed ), remainder , err
121+ stmt = normalizeMySQLStatement (stmt , compat .replacements )
122+ if err == nil {
123+ err = validateMySQLTableStorageStatement (stmt )
124+ }
125+ return stmt , compat .restoreParsedQuery (parsed ), remainder , err
89126}
90127
91128func (p * mysqlParser ) ParseOneWithOptions (
@@ -95,7 +132,33 @@ func (p *mysqlParser) ParseOneWithOptions(
95132) (sqlparser.Statement , int , error ) {
96133 compat := rewriteMySQLCompatibility (query )
97134 stmt , index , err := p .Parser .ParseOneWithOptions (ctx , compat .query , options )
98- return normalizeMySQLStatement (stmt , compat .replacements ), compat .originalOffset (index ), err
135+ stmt = normalizeMySQLStatement (stmt , compat .replacements )
136+ if err == nil {
137+ err = validateMySQLTableStorageStatement (stmt )
138+ }
139+ return stmt , compat .originalOffset (index ), err
140+ }
141+
142+ // validateMySQLTableStorageStatement runs before the planner turns table
143+ // options into a map. That preserves duplicate ENGINE/myduck_storage
144+ // declarations, which would otherwise be silently overwritten by the map.
145+ func validateMySQLTableStorageStatement (stmt sqlparser.Statement ) error {
146+ ddl , ok := stmt .(* sqlparser.DDL )
147+ if ! ok || ddl .TableSpec == nil || len (ddl .TableSpec .TableOpts ) == 0 {
148+ return nil
149+ }
150+ options := make ([]catalog.TableStorageOption , 0 , len (ddl .TableSpec .TableOpts ))
151+ for _ , option := range ddl .TableSpec .TableOpts {
152+ if option == nil {
153+ continue
154+ }
155+ options = append (options , catalog.TableStorageOption {
156+ Name : option .Name ,
157+ Value : option .Value ,
158+ })
159+ }
160+ _ , err := catalog .NormalizeTableStorageOptions (options )
161+ return err
99162}
100163
101164type mysqlOptionReplacement struct {
0 commit comments