github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cmd/docgen/diagrams.go (about)

     1  // Copyright 2017 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package main
    12  
    13  import (
    14  	"bytes"
    15  	"fmt"
    16  	"io"
    17  	"io/ioutil"
    18  	"log"
    19  	"os"
    20  	"path/filepath"
    21  	"regexp"
    22  	"sort"
    23  	"strings"
    24  	"sync"
    25  
    26  	"github.com/cockroachdb/cockroach/pkg/cmd/docgen/extract"
    27  	"github.com/cockroachdb/cockroach/pkg/util/envutil"
    28  	"github.com/cockroachdb/errors"
    29  	"github.com/spf13/cobra"
    30  )
    31  
    32  func init() {
    33  	const topStmt = "stmt_block"
    34  
    35  	// Global vars.
    36  	var (
    37  		filter      string
    38  		invertMatch bool
    39  	)
    40  
    41  	write := func(name string, data []byte) {
    42  		if err := os.MkdirAll(filepath.Dir(name), 0755); err != nil {
    43  			log.Fatal(err)
    44  		}
    45  		if err := ioutil.WriteFile(name, data, 0644); err != nil {
    46  			log.Fatal(err)
    47  		}
    48  	}
    49  
    50  	// BNF vars.
    51  	var (
    52  		addr string
    53  	)
    54  
    55  	cmdBNF := &cobra.Command{
    56  		Use:   "bnf [dir]",
    57  		Short: "Generate EBNF from sql.y.",
    58  		Args:  cobra.ExactArgs(1),
    59  		Run: func(cmd *cobra.Command, args []string) {
    60  			bnfDir := args[0]
    61  			bnf, err := runBNF(addr)
    62  			if err != nil {
    63  				log.Fatal(err)
    64  			}
    65  			br := func() io.Reader {
    66  				return bytes.NewReader(bnf)
    67  			}
    68  
    69  			filterRE := regexp.MustCompile(filter)
    70  
    71  			if filterRE.MatchString(topStmt) != invertMatch {
    72  				name := topStmt
    73  				if !quiet {
    74  					fmt.Println("processing", name)
    75  				}
    76  				g, err := runParse(br(), nil, name, true, true, nil, nil)
    77  				if err != nil {
    78  					log.Fatalf("%s: %+v", name, err)
    79  				}
    80  				write(filepath.Join(bnfDir, name+".bnf"), g)
    81  			}
    82  
    83  			for _, s := range specs {
    84  				if filterRE.MatchString(s.name) == invertMatch {
    85  					continue
    86  				}
    87  				if !quiet {
    88  					fmt.Println("processing", s.name)
    89  				}
    90  				if s.stmt == "" {
    91  					s.stmt = s.name
    92  				}
    93  				g, err := runParse(br(), s.inline, s.stmt, false, s.nosplit, s.match, s.exclude)
    94  				if err != nil {
    95  					log.Fatalf("%s: %+v", s.name, err)
    96  				}
    97  				if !quiet {
    98  					fmt.Printf("raw data:\n%s\n", string(g))
    99  				}
   100  				replacements := make([]string, 0, len(s.replace))
   101  				for from := range s.replace {
   102  					replacements = append(replacements, from)
   103  				}
   104  				sort.Strings(replacements)
   105  				for _, from := range replacements {
   106  					if !quiet {
   107  						fmt.Printf("replacing: %q -> %q\n", from, s.replace[from])
   108  					}
   109  					g = bytes.Replace(g, []byte(from), []byte(s.replace[from]), -1)
   110  				}
   111  				replacements = replacements[:0]
   112  				for from := range s.regreplace {
   113  					replacements = append(replacements, from)
   114  				}
   115  				sort.Strings(replacements)
   116  				for _, from := range replacements {
   117  					if !quiet {
   118  						fmt.Printf("replacing re: %q -> %q\n", from, s.replace[from])
   119  					}
   120  					re := regexp.MustCompile(from)
   121  					g = re.ReplaceAll(g, []byte(s.regreplace[from]))
   122  				}
   123  				if !quiet {
   124  					fmt.Printf("result:\n%s\n", string(g))
   125  				}
   126  				write(filepath.Join(bnfDir, s.name+".bnf"), g)
   127  			}
   128  		},
   129  	}
   130  
   131  	cmdBNF.Flags().StringVar(&addr, "addr", "./pkg/sql/parser/sql.y", "Location of sql.y file. Can also specify an http address.")
   132  
   133  	// SVG vars.
   134  	var (
   135  		maxWorkers  int
   136  		railroadJar string
   137  	)
   138  
   139  	cmdSVG := &cobra.Command{
   140  		Use:   "svg [bnf dir] [svg dir]",
   141  		Short: "Generate SVG diagrams from SQL grammar",
   142  		Long:  `With no arguments, generates SQL diagrams for all statements.`,
   143  		Args:  cobra.ExactArgs(2),
   144  		Run: func(cmd *cobra.Command, args []string) {
   145  			bnfDir := args[0]
   146  			svgDir := args[1]
   147  			if railroadJar != "" {
   148  				_, err := os.Stat(railroadJar)
   149  				if err != nil {
   150  					if envutil.EnvOrDefaultBool("COCKROACH_REQUIRE_RAILROAD", false) {
   151  						log.Fatalf("%s not found\n", railroadJar)
   152  					} else {
   153  						log.Printf("%s not found, falling back to slower web service (employees can find Railroad.jar on Google Drive).", railroadJar)
   154  						railroadJar = ""
   155  					}
   156  				}
   157  			}
   158  
   159  			filterRE := regexp.MustCompile(filter)
   160  			stripRE := regexp.MustCompile("\n(\n| )+")
   161  
   162  			matches, err := filepath.Glob(filepath.Join(bnfDir, "*.bnf"))
   163  			if err != nil {
   164  				log.Fatal(err)
   165  			}
   166  
   167  			specMap := make(map[string]stmtSpec)
   168  			for _, s := range specs {
   169  				specMap[s.name] = s
   170  			}
   171  			if len(specs) != len(specMap) {
   172  				log.Fatal("duplicate spec name")
   173  			}
   174  
   175  			var wg sync.WaitGroup
   176  			sem := make(chan struct{}, maxWorkers) // max number of concurrent workers
   177  			for _, m := range matches {
   178  				name := strings.TrimSuffix(filepath.Base(m), ".bnf")
   179  				if filterRE.MatchString(name) == invertMatch {
   180  					continue
   181  				}
   182  				wg.Add(1)
   183  				sem <- struct{}{}
   184  				go func(m, name string) {
   185  					defer wg.Done()
   186  					defer func() { <-sem }()
   187  
   188  					if !quiet {
   189  						fmt.Printf("generating svg of %s (%s)\n", name, m)
   190  					}
   191  
   192  					f, err := os.Open(m)
   193  					if err != nil {
   194  						log.Fatal(err)
   195  					}
   196  					defer f.Close()
   197  
   198  					rr, err := runRR(f, railroadJar)
   199  					if err != nil {
   200  						log.Fatalf("%s: %s\n", m, err)
   201  					}
   202  
   203  					var body string
   204  					if strings.HasSuffix(m, topStmt+".bnf") {
   205  						body, err = extract.InnerTag(bytes.NewReader(rr), "body")
   206  						body = strings.SplitN(body, "<hr/>", 2)[0]
   207  						body += `<p>generated by <a href="http://www.bottlecaps.de/rr/ui" data-proofer-ignore>Railroad Diagram Generator</a></p>`
   208  						body = fmt.Sprintf("<div>%s</div>", body)
   209  						if err != nil {
   210  							log.Fatal(err)
   211  						}
   212  					} else {
   213  						s, ok := specMap[name]
   214  						if !ok {
   215  							log.Fatalf("unfound spec: %s", name)
   216  						}
   217  						body, err = extract.Tag(bytes.NewReader(rr), "svg")
   218  						if err != nil {
   219  							log.Fatal(err)
   220  						}
   221  						body = strings.Replace(body, `<a xlink:href="#`, `<a xlink:href="sql-grammar.html#`, -1)
   222  						for _, u := range s.unlink {
   223  							s := fmt.Sprintf(`<a xlink:href="sql-grammar.html#%s" xlink:title="%s">((?s).*?)</a>`, u, u)
   224  							link := regexp.MustCompile(s)
   225  							body = link.ReplaceAllString(body, "$1")
   226  						}
   227  						for from, to := range s.relink {
   228  							replaceFrom := fmt.Sprintf(`<a xlink:href="sql-grammar.html#%s" xlink:title="%s">`, from, from)
   229  							replaceTo := fmt.Sprintf(`<a xlink:href="sql-grammar.html#%s" xlink:title="%s">`, to, to)
   230  							body = strings.Replace(body, replaceFrom, replaceTo, -1)
   231  						}
   232  						// Wrap the output in a <div> so that the Markdown parser
   233  						// doesn't attempt to parse the inside of the contained
   234  						// <svg> as Markdown.
   235  						body = fmt.Sprintf(`<div>%s</div>`, body)
   236  						// Remove blank lines and strip spaces.
   237  						body = stripRE.ReplaceAllString(body, "\n") + "\n"
   238  					}
   239  					name = strings.Replace(name, "_stmt", "", 1)
   240  					write(filepath.Join(svgDir, name+".html"), []byte(body))
   241  				}(m, name)
   242  			}
   243  			wg.Wait()
   244  		},
   245  	}
   246  
   247  	cmdSVG.Flags().IntVar(&maxWorkers, "max-workers", 1, "maximum number of concurrent workers")
   248  	cmdSVG.Flags().StringVar(&railroadJar, "railroad", "", "Location of Railroad.jar; empty to use website")
   249  
   250  	diagramCmd := &cobra.Command{
   251  		Use:   "grammar",
   252  		Short: "Generate diagrams.",
   253  	}
   254  
   255  	diagramCmd.PersistentFlags().StringVar(&filter, "filter", ".*", "Filter statement names (regular expression)")
   256  	diagramCmd.PersistentFlags().BoolVar(&invertMatch, "invert-match", false, "Generate everything that doesn't match the filter")
   257  
   258  	diagramCmd.AddCommand(cmdBNF, cmdSVG)
   259  	cmds = append(cmds, diagramCmd)
   260  }
   261  
   262  // stmtSpec is needed for each top-level bnf file to process.
   263  // See the wiki page for more details about what these controls do.
   264  // https://github.com/cockroachdb/docs/wiki/SQL-Grammar-Railroad-Diagram-Changes#structure
   265  type stmtSpec struct {
   266  	name           string
   267  	stmt           string // if unspecified, uses name
   268  	inline         []string
   269  	replace        map[string]string
   270  	regreplace     map[string]string
   271  	match, exclude []*regexp.Regexp
   272  	unlink         []string
   273  	relink         map[string]string
   274  	nosplit        bool
   275  }
   276  
   277  func runBNF(addr string) ([]byte, error) {
   278  	return extract.GenerateBNF(addr)
   279  }
   280  
   281  func runParse(
   282  	r io.Reader,
   283  	inline []string,
   284  	topStmt string,
   285  	descend, nosplit bool,
   286  	match, exclude []*regexp.Regexp,
   287  ) ([]byte, error) {
   288  	g, err := extract.ParseGrammar(r)
   289  	if err != nil {
   290  		return nil, errors.Wrap(err, "parse grammar")
   291  	}
   292  	if err := g.Inline(inline...); err != nil {
   293  		return nil, errors.Wrap(err, "inline")
   294  	}
   295  	b, err := g.ExtractProduction(topStmt, descend, nosplit, match, exclude)
   296  	b = bytes.Replace(b, []byte("'IDENT'"), []byte("'identifier'"), -1)
   297  	b = bytes.Replace(b, []byte("_LA"), []byte(""), -1)
   298  	return b, err
   299  }
   300  
   301  func runRR(r io.Reader, railroadJar string) ([]byte, error) {
   302  	b, err := ioutil.ReadAll(r)
   303  	if err != nil {
   304  		return nil, err
   305  	}
   306  	var html []byte
   307  	if railroadJar == "" {
   308  		html, err = extract.GenerateRRNet(b)
   309  	} else {
   310  		html, err = extract.GenerateRRJar(railroadJar, b)
   311  	}
   312  	if err != nil {
   313  		return nil, err
   314  	}
   315  	s, err := extract.XHTMLtoHTML(bytes.NewReader(html))
   316  	return []byte(s), err
   317  }
   318  
   319  var specs = []stmtSpec{
   320  	{
   321  		name:   "add_column",
   322  		stmt:   "alter_onetable_stmt",
   323  		inline: []string{"alter_table_cmds", "alter_table_cmd", "column_def", "col_qual_list"},
   324  		regreplace: map[string]string{
   325  			` \( \( col_qualification \) \)\* .*`: `( ( col_qualification ) )*`,
   326  		},
   327  		match: []*regexp.Regexp{regexp.MustCompile("'ADD' ('COLUMN')? ?('IF' 'NOT' 'EXISTS')? ?column_name")},
   328  		replace: map[string]string{
   329  			"relation_expr": "table_name",
   330  		},
   331  		unlink: []string{"table_name"},
   332  	},
   333  	{
   334  		name:    "add_constraint",
   335  		stmt:    "alter_onetable_stmt",
   336  		replace: map[string]string{"relation_expr": "table_name", "alter_table_cmds": "'ADD' 'CONSTRAINT' constraint_name constraint_elem opt_validate_behavior"},
   337  		unlink:  []string{"table_name"},
   338  	},
   339  	{
   340  		name:   "alter_column",
   341  		stmt:   "alter_onetable_stmt",
   342  		inline: []string{"alter_table_cmds", "alter_table_cmd", "opt_column", "alter_column_default", "opt_set_data", "opt_collate", "opt_alter_column_using"},
   343  		regreplace: map[string]string{
   344  			regList: "",
   345  		},
   346  		match: []*regexp.Regexp{regexp.MustCompile("relation_expr 'ALTER' ")},
   347  		replace: map[string]string{
   348  			"relation_expr": "table_name",
   349  		},
   350  		unlink: []string{"table_name"},
   351  	},
   352  	{
   353  		name:   "alter_role_stmt",
   354  		inline: []string{"role_or_group_or_user", "opt_role_options"},
   355  		replace: map[string]string{
   356  			"string_or_placeholder":             "name",
   357  			"opt_role_options":                  "OPTIONS",
   358  			"string_or_placeholder  'PASSWORD'": "name 'PASSWORD'",
   359  			"'PASSWORD' string_or_placeholder":  "'PASSWORD' password"},
   360  		unlink: []string{"name", "password"},
   361  	},
   362  	{
   363  		name:    "alter_sequence_options_stmt",
   364  		inline:  []string{"sequence_option_list", "sequence_option_elem"},
   365  		replace: map[string]string{"relation_expr": "sequence_name", "signed_iconst64": "integer"},
   366  		unlink:  []string{"integer", "sequence_name"},
   367  		nosplit: true,
   368  	},
   369  	{
   370  		name:   "alter_table",
   371  		stmt:   "alter_onetable_stmt",
   372  		inline: []string{"alter_table_cmds", "alter_table_cmd", "column_def", "opt_drop_behavior", "alter_column_default", "opt_column", "opt_set_data", "table_constraint", "opt_collate", "opt_alter_column_using"},
   373  		replace: map[string]string{
   374  			"'VALIDATE' 'CONSTRAINT' name": "",
   375  			"opt_validate_behavior":        "",
   376  			"relation_expr":                "table_name"},
   377  		unlink:  []string{"table_name"},
   378  		nosplit: true,
   379  	},
   380  	{
   381  		name:   "alter_type",
   382  		stmt:   "alter_onetable_stmt",
   383  		inline: []string{"alter_table_cmds", "alter_table_cmd", "opt_column", "opt_set_data"},
   384  		match:  []*regexp.Regexp{regexp.MustCompile(`'ALTER' ('COLUMN')? column_name ('SET' 'DATA')? 'TYPE'`)},
   385  		regreplace: map[string]string{
   386  			regList:                   "",
   387  			" opt_collate":            "",
   388  			" opt_alter_column_using": "",
   389  		},
   390  		replace: map[string]string{
   391  			"relation_expr": "table_name",
   392  		},
   393  		unlink: []string{"table_name", "column_name"},
   394  	},
   395  	{
   396  		name:    "alter_view",
   397  		stmt:    "alter_rename_view_stmt",
   398  		inline:  []string{"opt_transaction"},
   399  		replace: map[string]string{"relation_expr": "view_name", "qualified_name": "name"}, unlink: []string{"view_name", "name"},
   400  	},
   401  	{
   402  		name:    "alter_zone_database_stmt",
   403  		inline:  []string{"set_zone_config", "var_set_list"},
   404  		replace: map[string]string{"var_name": "variable", "var_value": "value"},
   405  		unlink:  []string{"variable", "value"},
   406  	},
   407  	{
   408  		name:    "alter_zone_index_stmt",
   409  		inline:  []string{"table_index_name", "set_zone_config", "var_set_list"},
   410  		replace: map[string]string{"var_name": "variable", "var_value": "value", "standalone_index_name": "index_name"},
   411  		unlink:  []string{"variable", "value"},
   412  	},
   413  	{
   414  		name:    "alter_zone_range_stmt",
   415  		inline:  []string{"set_zone_config", "var_set_list"},
   416  		replace: map[string]string{"zone_name": "range_name", "var_name": "variable", "var_value": "value"},
   417  		unlink:  []string{"range_name", "variable", "value"},
   418  	},
   419  	{
   420  		name:    "alter_zone_table_stmt",
   421  		inline:  []string{"set_zone_config", "var_set_list"},
   422  		replace: map[string]string{"var_name": "variable", "var_value": "value"},
   423  		unlink:  []string{"variable", "value"},
   424  	},
   425  	{
   426  		name:    "alter_zone_partition_stmt",
   427  		inline:  []string{"table_index_name", "set_zone_config", "var_set_list"},
   428  		replace: map[string]string{"var_name": "variable", "var_value": "value", "standalone_index_name": "index_name"},
   429  		unlink:  []string{"variable", "value"},
   430  	},
   431  	{
   432  		name:   "backup",
   433  		stmt:   "backup_stmt",
   434  		inline: []string{"table_pattern_list", "name_list", "opt_as_of_clause", "opt_incremental", "opt_with_options"},
   435  		match:  []*regexp.Regexp{regexp.MustCompile("'BACKUP'")},
   436  		replace: map[string]string{
   437  			"non_reserved_word_or_sconst":                     "destination",
   438  			"'AS' 'OF' 'SYSTEM' 'TIME' a_expr":                "'AS OF SYSTEM TIME' timestamp",
   439  			"'INCREMENTAL' 'FROM' string_or_placeholder_list": "'INCREMENTAL FROM' full_backup_location ( | ',' incremental_backup_location ( ',' incremental_backup_location )* )",
   440  			"'WITH' 'OPTIONS' '(' kv_option_list ')'":         "",
   441  			"targets": "( ( 'TABLE' | ) table_pattern ( ( ',' table_pattern ) )* | 'DATABASE' database_name ( ( ',' database_name ) )* )",
   442  		},
   443  		unlink: []string{"destination", "timestamp", "full_backup_location", "incremental_backup_location"},
   444  	},
   445  	{
   446  		name: "begin_transaction",
   447  		stmt: "begin_stmt",
   448  		inline: []string{
   449  			"opt_transaction",
   450  			"begin_transaction",
   451  			"transaction_mode",
   452  			"transaction_user_priority",
   453  			"user_priority",
   454  			"iso_level",
   455  			"transaction_mode_list",
   456  			"opt_comma",
   457  			"transaction_read_mode",
   458  			"as_of_clause",
   459  		},
   460  		exclude: []*regexp.Regexp{
   461  			regexp.MustCompile("'START'"),
   462  		},
   463  	},
   464  	{
   465  		name: "check_column_level",
   466  		stmt: "stmt_block",
   467  		replace: map[string]string{"	stmt": "	'CREATE' 'TABLE' table_name '(' column_name column_type 'CHECK' '(' check_expr ')' ( column_constraints | ) ( ',' ( column_def ( ',' column_def )* ) | ) ( table_constraints | ) ')' ')'"},
   468  		unlink: []string{"table_name", "column_name", "column_type", "check_expr", "column_constraints", "table_constraints"},
   469  	},
   470  	{
   471  		name: "check_table_level",
   472  		stmt: "stmt_block",
   473  		replace: map[string]string{"	stmt": "	'CREATE' 'TABLE' table_name '(' ( column_def ( ',' column_def )* ) ( 'CONSTRAINT' constraint_name | ) 'CHECK' '(' check_expr ')' ( table_constraints | ) ')'"},
   474  		unlink: []string{"table_name", "check_expr", "table_constraints"},
   475  	},
   476  	{
   477  		name:   "column_def",
   478  		stmt:   "column_def",
   479  		inline: []string{"col_qual_list"},
   480  	},
   481  	{
   482  		name:   "col_qualification",
   483  		stmt:   "col_qualification",
   484  		inline: []string{"col_qualification_elem"},
   485  	},
   486  	{
   487  		name:    "comment",
   488  		stmt:    "comment_stmt",
   489  		replace: map[string]string{"column_path": "column_name"},
   490  		unlink:  []string{"column_path"},
   491  	},
   492  	{
   493  		name:   "commit_transaction",
   494  		stmt:   "commit_stmt",
   495  		inline: []string{"opt_transaction"},
   496  		match:  []*regexp.Regexp{regexp.MustCompile("'COMMIT'|'END'")},
   497  	},
   498  	{
   499  		name:    "cancel_job",
   500  		stmt:    "cancel_jobs_stmt",
   501  		replace: map[string]string{"a_expr": "job_id"},
   502  		unlink:  []string{"job_id"},
   503  	},
   504  	{
   505  		name:   "create_as_col_qual_list",
   506  		inline: []string{"create_as_col_qualification", "create_as_col_qualification_elem"},
   507  	},
   508  	{
   509  		name:   "create_as_constraint_def",
   510  		inline: []string{"create_as_constraint_elem"},
   511  	},
   512  	{name: "cancel_query", stmt: "cancel_queries_stmt", replace: map[string]string{"a_expr": "query_id"}, unlink: []string{"query_id"}},
   513  	{name: "cancel_session", stmt: "cancel_sessions_stmt", replace: map[string]string{"a_expr": "session_id"}, unlink: []string{"session_id"}},
   514  	{name: "create_database_stmt", inline: []string{"opt_encoding_clause"}, replace: map[string]string{"'SCONST'": "encoding"}, unlink: []string{"name", "encoding"}},
   515  	{
   516  		name:   "create_changefeed_stmt",
   517  		inline: []string{"changefeed_targets", "single_table_pattern_list", "opt_changefeed_sink", "opt_with_options", "kv_option_list", "kv_option"},
   518  		replace: map[string]string{
   519  			"table_option":                 "table_name",
   520  			"'INTO' string_or_placeholder": "'INTO' sink",
   521  			"name":                         "option",
   522  			"'SCONST'":                     "option",
   523  			"'=' string_or_placeholder":    "'=' value"},
   524  		exclude: []*regexp.Regexp{
   525  			regexp.MustCompile("'OPTIONS'")},
   526  		unlink: []string{"table_name", "sink", "option", "value"},
   527  	},
   528  	{
   529  		name:   "create_index_stmt",
   530  		inline: []string{"opt_unique", "opt_storing", "storing", "index_params", "index_elem", "opt_asc_desc", "opt_using_gin_btree"},
   531  		replace: map[string]string{
   532  			"a_expr":          "column_name",
   533  			"opt_nulls_order": "",
   534  		},
   535  		regreplace: map[string]string{
   536  			".* 'CREATE' .* 'INVERTED' 'INDEX' .*": "",
   537  		},
   538  		nosplit: true,
   539  	},
   540  	{
   541  		name:   "create_index_interleaved_stmt",
   542  		stmt:   "create_index_stmt",
   543  		match:  []*regexp.Regexp{regexp.MustCompile("'INTERLEAVE'")},
   544  		inline: []string{"opt_unique", "opt_storing", "opt_interleave"},
   545  		replace: map[string]string{
   546  			"a_expr":                               "column_name",
   547  			" opt_index_name":                      "",
   548  			" opt_partition_by":                    "",
   549  			" opt_using_gin_btree":                 "",
   550  			"'ON' table_name '(' index_params ')'": "'...'",
   551  			"storing '(' name_list ')'":            "'STORING' '(' stored_columns ')'",
   552  			"table_name '(' name_list":             "parent_table '(' interleave_prefix",
   553  		},
   554  		exclude: []*regexp.Regexp{
   555  			regexp.MustCompile("'CREATE' 'INVERTED'"),
   556  			regexp.MustCompile("'EXISTS'"),
   557  		},
   558  		unlink: []string{"stored_columns", "parent_table", "interleave_prefix"},
   559  	},
   560  	{
   561  		name:   "create_inverted_index_stmt",
   562  		stmt:   "create_index_stmt",
   563  		match:  []*regexp.Regexp{regexp.MustCompile("'CREATE' 'INVERTED'")},
   564  		inline: []string{"opt_storing", "storing", "opt_unique", "opt_name", "index_params", "index_elem", "opt_asc_desc"},
   565  	},
   566  	{
   567  		name:    "create_sequence_stmt",
   568  		inline:  []string{"opt_sequence_option_list", "sequence_option_list", "sequence_option_elem"},
   569  		replace: map[string]string{"signed_iconst64": "integer", "any_name": "sequence_name"},
   570  		unlink:  []string{"integer", "sequence_name"},
   571  		nosplit: true,
   572  	},
   573  	{
   574  		name:    "create_stats_stmt",
   575  		replace: map[string]string{"name_list": "column_name"},
   576  		unlink:  []string{"statistics_name", "column_name"},
   577  	},
   578  	{
   579  		name:   "create_table_as_stmt",
   580  		inline: []string{"create_as_opt_col_list", "create_as_table_defs"},
   581  	},
   582  	{
   583  		name:   "create_table_stmt",
   584  		inline: []string{"opt_table_elem_list", "table_elem_list", "table_elem"},
   585  	},
   586  	{
   587  		name:   "create_view_stmt",
   588  		inline: []string{"opt_column_list"},
   589  	},
   590  	{
   591  		name:   "create_role_stmt",
   592  		inline: []string{"role_or_group_or_user", "opt_role_options"},
   593  		replace: map[string]string{
   594  			"string_or_placeholder":             "name",
   595  			"opt_role_options":                  "OPTIONS",
   596  			"string_or_placeholder  'PASSWORD'": "name 'PASSWORD'",
   597  			"'PASSWORD' string_or_placeholder":  "'PASSWORD' password"},
   598  	},
   599  	{
   600  		name: "default_value_column_level",
   601  		stmt: "stmt_block",
   602  		replace: map[string]string{
   603  			"	stmt": "	'CREATE' 'TABLE' table_name '(' column_name column_type 'DEFAULT' default_value ( column_constraints | ) ( ',' ( column_def ( ',' column_def )* ) | ) ( table_constraints | ) ')' ')'",
   604  		},
   605  		unlink: []string{"table_name", "column_name", "column_type", "default_value", "table_constraints"},
   606  	},
   607  	{
   608  		name:   "delete_stmt",
   609  		inline: []string{"opt_with_clause", "with_clause", "cte_list", "table_expr_opt_alias_idx", "table_name_opt_idx", "opt_where_clause", "where_clause", "returning_clause", "opt_sort_clause", "opt_limit_clause"},
   610  		replace: map[string]string{
   611  			"relation_expr": "table_name",
   612  		},
   613  		unlink:  []string{"count"},
   614  		nosplit: true,
   615  	},
   616  	{
   617  		name:   "with_clause",
   618  		inline: []string{"cte_list", "common_table_expr", "name_list", "opt_column_list"},
   619  		replace: map[string]string{
   620  			"preparable_stmt ')' ) ) )* )": "preparable_stmt ')' ) ) )* ) ( insert_stmt | update_stmt | delete_stmt | upsert_stmt | select_stmt )",
   621  		},
   622  		nosplit: true,
   623  	},
   624  	{
   625  		name:   "drop_column",
   626  		stmt:   "alter_onetable_stmt",
   627  		inline: []string{"alter_table_cmds", "alter_table_cmd", "opt_column", "opt_drop_behavior"},
   628  		match:  []*regexp.Regexp{regexp.MustCompile("relation_expr 'DROP' 'COLUMN'")},
   629  		regreplace: map[string]string{
   630  			regList: "",
   631  		},
   632  		replace: map[string]string{
   633  			"relation_expr": "table_name",
   634  			"column_name":   "name",
   635  		},
   636  		unlink: []string{"table_name", "name"},
   637  	},
   638  	{
   639  		name:   "drop_constraint",
   640  		stmt:   "alter_onetable_stmt",
   641  		inline: []string{"alter_table_cmds", "alter_table_cmd", "opt_drop_behavior"},
   642  		match:  []*regexp.Regexp{regexp.MustCompile("relation_expr 'DROP' 'CONSTRAINT'")},
   643  		regreplace: map[string]string{
   644  			regList: "",
   645  		},
   646  		replace: map[string]string{
   647  			"relation_expr":   "table_name",
   648  			"constraint_name": "name",
   649  		},
   650  		unlink: []string{"table_name"},
   651  	},
   652  	{
   653  		name:   "drop_database",
   654  		stmt:   "drop_database_stmt",
   655  		inline: []string{"opt_drop_behavior"},
   656  		match:  []*regexp.Regexp{regexp.MustCompile("'DROP' 'DATABASE'")},
   657  	},
   658  	{
   659  		name:   "drop_index",
   660  		stmt:   "drop_index_stmt",
   661  		match:  []*regexp.Regexp{regexp.MustCompile("'DROP' 'INDEX'")},
   662  		inline: []string{"opt_drop_behavior", "table_index_name_list", "table_index_name"},
   663  		regreplace: map[string]string{
   664  			regList: "",
   665  		},
   666  		replace: map[string]string{"standalone_index_name": "index_name"},
   667  	},
   668  	{
   669  		name:    "drop_role_stmt",
   670  		inline:  []string{"role_or_group_or_user"},
   671  		replace: map[string]string{"string_or_placeholder_list": "name"},
   672  	},
   673  	{
   674  		name:   "drop_sequence_stmt",
   675  		inline: []string{"table_name_list", "opt_drop_behavior"},
   676  		unlink: []string{"sequence_name"},
   677  	},
   678  	{
   679  		name:   "drop_stmt",
   680  		inline: []string{"table_name_list", "drop_ddl_stmt"},
   681  	},
   682  	{
   683  		name:   "drop_table",
   684  		stmt:   "drop_table_stmt",
   685  		inline: []string{"opt_drop_behavior", "table_name_list"},
   686  		match:  []*regexp.Regexp{regexp.MustCompile("'DROP' 'TABLE'")},
   687  	},
   688  	{
   689  		name:   "drop_view",
   690  		stmt:   "drop_view_stmt",
   691  		inline: []string{"opt_drop_behavior", "table_name_list"},
   692  		match:  []*regexp.Regexp{regexp.MustCompile("'DROP' 'VIEW'")},
   693  	},
   694  	{
   695  		name:   "experimental_audit",
   696  		stmt:   "alter_onetable_stmt",
   697  		inline: []string{"audit_mode", "alter_table_cmd", "alter_table_cmds"},
   698  		match:  []*regexp.Regexp{regexp.MustCompile(`relation_expr 'EXPERIMENTAL_AUDIT'`)},
   699  		replace: map[string]string{
   700  			"relation_expr": "table_name",
   701  		},
   702  		regreplace: map[string]string{
   703  			`'READ' 'WRITE' .*`: `'READ' 'WRITE'`,
   704  			`'OFF' .*`:          `'OFF'`,
   705  		},
   706  	},
   707  	{
   708  		name:    "alter_table_partition_by",
   709  		stmt:    "alter_onetable_stmt",
   710  		inline:  []string{"alter_table_cmds", "alter_table_cmd", "partition_by"},
   711  		replace: map[string]string{"relation_expr": "table_name"},
   712  		regreplace: map[string]string{
   713  			`'NOTHING' .*`:        `'NOTHING'`,
   714  			`_partitions '\)' .*`: `_partitions ')'`,
   715  		},
   716  		match: []*regexp.Regexp{regexp.MustCompile("relation_expr 'PARTITION")},
   717  	},
   718  	{
   719  		name:    "alter_index_partition_by",
   720  		stmt:    "alter_oneindex_stmt",
   721  		inline:  []string{"alter_index_cmds", "alter_index_cmd", "partition_by", "table_index_name"},
   722  		replace: map[string]string{"standalone_index_name": "index_name"},
   723  	},
   724  	{
   725  		name:    "create_table_partition_by",
   726  		stmt:    "create_table_stmt",
   727  		inline:  []string{"opt_partition_by", "partition_by"},
   728  		replace: map[string]string{"opt_table_elem_list": "table_definition", "opt_interleave": ""},
   729  		match:   []*regexp.Regexp{regexp.MustCompile("PARTITION")},
   730  		unlink:  []string{"table_definition"},
   731  	},
   732  	{
   733  		name:   "explain_stmt",
   734  		inline: []string{"explain_option_list"},
   735  		replace: map[string]string{
   736  			"explain_option_name": "( 'VERBOSE' | 'TYPES' | 'OPT' | 'DISTSQL' | 'VEC' )",
   737  		},
   738  		exclude: []*regexp.Regexp{
   739  			regexp.MustCompile("'ANALYZE'"),
   740  			regexp.MustCompile("'ANALYSE'"),
   741  		},
   742  	},
   743  	{
   744  		name:  "explain_analyze_stmt",
   745  		stmt:  "explain_stmt",
   746  		match: []*regexp.Regexp{regexp.MustCompile("'ANALY[SZ]E'")},
   747  		replace: map[string]string{
   748  			"explain_option_list": "'DISTSQL'",
   749  		},
   750  		unlink: []string{"'DISTSQL'"},
   751  	},
   752  	{
   753  		name: "export_stmt",
   754  		replace: map[string]string{
   755  			"import_data_format":    "CSV",
   756  			"string_or_placeholder": "file_location",
   757  			"select_stmt":           "(| 'select_stmt' | 'TABLE' 'table_name')",
   758  		},
   759  		unlink: []string{"CSV", "file_location"},
   760  	},
   761  	{
   762  		name:   "family_def",
   763  		inline: []string{"name_list"},
   764  	},
   765  	{
   766  		name:   "grant_privileges",
   767  		stmt:   "grant_stmt",
   768  		inline: []string{"privileges", "privilege_list", "privilege", "table_pattern_list", "name_list"},
   769  		replace: map[string]string{
   770  			"( name | 'CREATE' | 'GRANT' | 'SELECT' )": "( 'CREATE' | 'GRANT' | 'SELECT' | 'DROP' | 'INSERT' | 'DELETE' | 'UPDATE' )",
   771  			"table_pattern":                     "table_name",
   772  			"'TO' ( ( name ) ( ( ',' name ) )*": "'TO' ( ( user_name ) ( ( ',' user_name ) )*",
   773  			"| 'GRANT' ( ( ( 'CREATE' | 'GRANT' | 'SELECT' | 'DROP' | 'INSERT' | 'DELETE' | 'UPDATE' ) ) ( ( ',' ( 'CREATE' | 'GRANT' | 'SELECT' | 'DROP' | 'INSERT' | 'DELETE' | 'UPDATE' ) ) )* ) 'TO' ( ( user_name ) ( ( ',' user_name ) )* )": "",
   774  			"'WITH' 'ADMIN' 'OPTION'": "",
   775  			"targets":                 "( ( 'TABLE' | ) table_pattern ( ( ',' table_pattern ) )* | 'DATABASE' database_name ( ( ',' database_name ) )* )",
   776  		},
   777  		unlink:  []string{"table_name", "database_name", "user_name"},
   778  		nosplit: true,
   779  	},
   780  	{
   781  		name: "grant_roles",
   782  		stmt: "grant_stmt",
   783  		replace: map[string]string{
   784  			"'GRANT' privileges 'ON' targets 'TO' name_list":                "",
   785  			"'GRANT' privilege_list 'TO' name_list 'WITH' 'ADMIN' 'OPTION'": "'GRANT' ( role_name ) ( ( ',' role_name ) )* 'TO' ( user_name ) ( ( ',' user_name ) )* 'WITH' 'ADMIN' 'OPTION'",
   786  			"| 'GRANT' privilege_list 'TO' name_list":                       "'GRANT' ( role_name ) ( ( ',' role_name ) )* 'TO' ( user_name ) ( ( ',' user_name ) )*",
   787  		},
   788  		unlink: []string{"role_name", "user_name"},
   789  	},
   790  	{
   791  		name: "foreign_key_column_level",
   792  		stmt: "stmt_block",
   793  		replace: map[string]string{"	stmt": "	'CREATE' 'TABLE' table_name '(' column_name column_type 'REFERENCES' parent_table ( '(' ref_column_name ')' | ) ( column_constraints | ) ( ',' ( column_def ( ',' column_def )* ) | ) ( table_constraints | ) ')' ')'"},
   794  		unlink: []string{"table_name", "column_name", "column_type", "parent_table", "table_constraints"},
   795  	},
   796  	{
   797  		name: "foreign_key_table_level",
   798  		stmt: "stmt_block",
   799  		replace: map[string]string{"	stmt": "	'CREATE' 'TABLE' table_name '(' ( column_def ( ',' column_def )* ) ( 'CONSTRAINT' constraint_name | ) 'FOREIGN KEY' '(' ( fk_column_name ( ',' fk_column_name )* ) ')' 'REFERENCES' parent_table ( '(' ( ref_column_name ( ',' ref_column_name )* ) ')' | ) ( table_constraints | ) ')'"},
   800  		unlink: []string{"table_name", "column_name", "parent_table", "table_constraints"},
   801  	},
   802  	{
   803  		name:   "index_def",
   804  		inline: []string{"opt_storing", "storing", "index_params", "opt_name"},
   805  	},
   806  	{
   807  		name:   "import_csv",
   808  		stmt:   "import_stmt",
   809  		inline: []string{"string_or_placeholder_list", "opt_with_options"},
   810  		exclude: []*regexp.Regexp{
   811  			regexp.MustCompile("'IMPORT' import_format"),
   812  			regexp.MustCompile("'FROM' import_format"),
   813  			regexp.MustCompile("'WITH' 'OPTIONS'"),
   814  		},
   815  		replace: map[string]string{
   816  			"string_or_placeholder": "file_location",
   817  			"import_format":         "'CSV'",
   818  		},
   819  		unlink: []string{"file_location"},
   820  	},
   821  	{
   822  		name:   "import_into",
   823  		stmt:   "import_stmt",
   824  		match:  []*regexp.Regexp{regexp.MustCompile("INTO")},
   825  		inline: []string{"insert_column_list", "string_or_placeholder_list", "opt_with_options", "kv_option_list"},
   826  		replace: map[string]string{
   827  			"table_option":          "table_name",
   828  			"insert_column_item":    "column_name",
   829  			"import_format":         "'CSV'",
   830  			"string_or_placeholder": "file_location",
   831  			"kv_option":             "option '=' value"},
   832  		unlink: []string{"table_name", "column_name", "file_location", "option", "value"},
   833  		exclude: []*regexp.Regexp{
   834  			regexp.MustCompile("'WITH' 'OPTIONS'"),
   835  		},
   836  	},
   837  	{
   838  		name:   "import_dump",
   839  		stmt:   "import_stmt",
   840  		inline: []string{"string_or_placeholder_list", "opt_with_options"},
   841  		exclude: []*regexp.Regexp{
   842  			regexp.MustCompile("CREATE' 'USING'"),
   843  			regexp.MustCompile("table_elem_list"),
   844  			regexp.MustCompile("'WITH' 'OPTIONS'"),
   845  		},
   846  		replace: map[string]string{
   847  			"string_or_placeholder": "file_location",
   848  		},
   849  		unlink: []string{"file_location"},
   850  	},
   851  	{
   852  		name:    "insert_stmt",
   853  		inline:  []string{"insert_target", "insert_rest", "returning_clause", "insert_column_list", "insert_column_item", "target_list", "opt_with_clause", "with_clause", "cte_list"},
   854  		nosplit: true,
   855  	},
   856  	{
   857  		name: "on_conflict",
   858  		inline: []string{"opt_conf_expr", "name_list", "set_clause_list", "insert_column_list",
   859  			"insert_column_item", "set_clause", "single_set_clause", "multiple_set_clause", "in_expr", "expr_list",
   860  			"expr_tuple1_ambiguous", "tuple1_ambiguous_values"},
   861  		replace: map[string]string{
   862  			"select_with_parens": "'(' select_stmt ')'",
   863  			"opt_where_clause":   "",
   864  		},
   865  		nosplit: true,
   866  	},
   867  	{name: "iso_level"},
   868  	{
   869  		name:    "interleave",
   870  		stmt:    "create_table_stmt",
   871  		inline:  []string{"opt_interleave"},
   872  		replace: map[string]string{"opt_table_elem_list": "table_definition"},
   873  		unlink:  []string{"table_definition"},
   874  	},
   875  	{
   876  		name: "not_null_column_level",
   877  		stmt: "stmt_block",
   878  		replace: map[string]string{"	stmt": "	'CREATE' 'TABLE' table_name '(' column_name column_type 'NOT NULL' ( column_constraints | ) ( ',' ( column_def ( ',' column_def )* ) | ) ( table_constraints | ) ')' ')'"},
   879  		unlink: []string{"table_name", "column_name", "column_type", "table_constraints"},
   880  	},
   881  	{
   882  		name: "opt_interleave",
   883  	},
   884  	{
   885  		name:    "pause_job",
   886  		stmt:    "pause_stmt",
   887  		replace: map[string]string{"a_expr": "job_id"},
   888  		unlink:  []string{"job_id"},
   889  	},
   890  	{
   891  		name: "primary_key_column_level",
   892  		stmt: "stmt_block",
   893  		replace: map[string]string{"	stmt": "	'CREATE' 'TABLE' table_name '(' column_name column_type 'PRIMARY KEY' ( column_constraints | ) ( ',' ( column_def ( ',' column_def )* ) | ) ( table_constraints | ) ')' ')'"},
   894  		unlink: []string{"table_name", "column_name", "column_type", "table_constraints"},
   895  	},
   896  	{
   897  		name: "primary_key_table_level",
   898  		stmt: "stmt_block",
   899  		replace: map[string]string{"	stmt": "	'CREATE' 'TABLE' table_name '(' ( column_def ( ',' column_def )* ) ( 'CONSTRAINT' name | ) 'PRIMARY KEY' '(' ( column_name ( ',' column_name )* ) ')' ( table_constraints | ) ')'"},
   900  		unlink: []string{"table_name", "column_name", "table_constraints"},
   901  	},
   902  	{
   903  		name:   "release_savepoint",
   904  		stmt:   "release_stmt",
   905  		inline: []string{"savepoint_name"},
   906  	},
   907  	{
   908  		name:    "rename_column",
   909  		stmt:    "alter_rename_table_stmt",
   910  		inline:  []string{"opt_column"},
   911  		match:   []*regexp.Regexp{regexp.MustCompile("'ALTER' 'TABLE' .* 'RENAME' ('COLUMN'|name)")},
   912  		replace: map[string]string{"relation_expr": "table_name", "name 'TO'": "current_name 'TO'"},
   913  		unlink:  []string{"table_name", "current_name"},
   914  	},
   915  	{
   916  		name:    "rename_constraint",
   917  		stmt:    "alter_onetable_stmt",
   918  		replace: map[string]string{"relation_expr": "table_name", "alter_table_cmds": "'RENAME' 'CONSTRAINT' current_name 'TO' name"},
   919  		unlink:  []string{"table_name", "current_name"},
   920  	},
   921  	{
   922  		name:  "rename_database",
   923  		stmt:  "alter_rename_database_stmt",
   924  		match: []*regexp.Regexp{regexp.MustCompile("'ALTER' 'DATABASE'")}},
   925  	{
   926  		name:    "rename_index",
   927  		stmt:    "alter_rename_index_stmt",
   928  		match:   []*regexp.Regexp{regexp.MustCompile("'ALTER' 'INDEX'")},
   929  		inline:  []string{"table_index_name"},
   930  		replace: map[string]string{"standalone_index_name": "index_name"},
   931  	},
   932  	{
   933  		name:    "rename_sequence",
   934  		stmt:    "alter_rename_sequence_stmt",
   935  		match:   []*regexp.Regexp{regexp.MustCompile("'ALTER' 'SEQUENCE' .* 'RENAME' 'TO'")},
   936  		replace: map[string]string{"relation_expr": "current_name", "sequence_name": "new_name"},
   937  		unlink:  []string{"current_name"},
   938  		relink:  map[string]string{"new_name": "name"}},
   939  	{
   940  		name:    "rename_table",
   941  		stmt:    "alter_rename_table_stmt",
   942  		match:   []*regexp.Regexp{regexp.MustCompile("'ALTER' 'TABLE' .* 'RENAME' 'TO'")},
   943  		replace: map[string]string{"relation_expr": "current_name", "qualified_name": "new_name"},
   944  		unlink:  []string{"current_name"}, relink: map[string]string{"new_name": "name"}},
   945  	{
   946  		name:   "restore",
   947  		stmt:   "restore_stmt",
   948  		inline: []string{"as_of_clause", "opt_with_options"},
   949  		replace: map[string]string{
   950  			"a_expr":                     "timestamp",
   951  			"string_or_placeholder_list": "full_backup_location ( | incremental_backup_location ( ',' incremental_backup_location )*)",
   952  			"'WITH' 'OPTIONS' '(' kv_option_list ')'": "",
   953  			"targets": "( ( 'TABLE' | ) table_pattern ( ( ',' table_pattern ) )* | 'DATABASE' database_name ( ( ',' database_name ) )* )",
   954  		},
   955  		unlink: []string{"timestamp", "full_backup_location", "incremental_backup_location"},
   956  	},
   957  	{
   958  		name:    "resume_job",
   959  		stmt:    "resume_stmt",
   960  		replace: map[string]string{"a_expr": "job_id"},
   961  		unlink:  []string{"job_id"},
   962  	},
   963  	{
   964  		name:   "revoke_privileges",
   965  		stmt:   "revoke_stmt",
   966  		inline: []string{"privileges", "privilege_list", "privilege", "name_list"},
   967  		replace: map[string]string{
   968  			"( name | 'CREATE' | 'GRANT' | 'SELECT' )": "( 'CREATE' | 'GRANT' | 'SELECT' | 'DROP' | 'INSERT' | 'DELETE' | 'UPDATE' )",
   969  			"targets":                             "( ( 'TABLE' | ) table_pattern ( ( ',' table_pattern ) )* | 'DATABASE' database_name ( ( ',' database_name ) )* )",
   970  			"'FROM' ( ( name ) ( ( ',' name ) )*": "'FROM' ( ( user_name ) ( ( ',' user_name ) )*",
   971  			"| 'REVOKE' ( ( ( 'CREATE' | 'GRANT' | 'SELECT' | 'DROP' | 'INSERT' | 'DELETE' | 'UPDATE' ) ) ( ( ',' ( 'CREATE' | 'GRANT' | 'SELECT' | 'DROP' | 'INSERT' | 'DELETE' | 'UPDATE' ) ) )* ) 'FROM' ( ( user_name ) ( ( ',' user_name ) )* )":  "",
   972  			"| 'REVOKE'  ( ( ( 'CREATE' | 'GRANT' | 'SELECT' | 'DROP' | 'INSERT' | 'DELETE' | 'UPDATE' ) ) ( ( ',' ( 'CREATE' | 'GRANT' | 'SELECT' | 'DROP' | 'INSERT' | 'DELETE' | 'UPDATE' ) ) )* ) 'FROM' ( ( user_name ) ( ( ',' user_name ) )* )": "",
   973  			"'ADMIN' 'OPTION' 'FOR'": "",
   974  		},
   975  		unlink:  []string{"table_name", "database_name", "user_name"},
   976  		nosplit: true,
   977  	},
   978  	{
   979  		name: "revoke_roles",
   980  		stmt: "revoke_stmt",
   981  		replace: map[string]string{
   982  			"'REVOKE' privileges 'ON' targets 'FROM' name_list":               "",
   983  			"'REVOKE' 'ADMIN' 'OPTION' 'FOR' privilege_list 'FROM' name_list": "'REVOKE' 'ADMIN' 'OPTION' 'FOR' ( role_name ) ( ( ',' role_name ) )* 'FROM' ( user_name ) ( ( ',' user_name ) )*",
   984  			"| 'REVOKE' privilege_list 'FROM' name_list":                      "'REVOKE' ( role_name ) ( ( ',' role_name ) )* 'FROM' ( user_name ) ( ( ',' user_name ) )*",
   985  		},
   986  		unlink: []string{"role_name", "user_name"},
   987  	},
   988  	{
   989  		name:    "rollback_transaction",
   990  		stmt:    "rollback_stmt",
   991  		inline:  []string{"opt_transaction"},
   992  		match:   []*regexp.Regexp{regexp.MustCompile("'ROLLBACK'")},
   993  		replace: map[string]string{"'TRANSACTION'": "", "'TO'": "'TO' 'SAVEPOINT'"},
   994  	},
   995  	{
   996  		name:   "limit_clause",
   997  		inline: []string{"row_or_rows", "first_or_next"},
   998  		replace: map[string]string{
   999  			"select_limit_value":           "count",
  1000  			"opt_select_fetch_first_value": "count",
  1001  		},
  1002  	},
  1003  	{
  1004  		name:   "offset_clause",
  1005  		inline: []string{"row_or_rows"},
  1006  	},
  1007  	{name: "savepoint_stmt", inline: []string{"savepoint_name"}},
  1008  	{
  1009  		name: "select_stmt",
  1010  		inline: []string{
  1011  			"with_clause",
  1012  			"cte_list",
  1013  			"select_no_parens",
  1014  			"opt_sort_clause",
  1015  			"select_limit",
  1016  		},
  1017  		replace: map[string]string{
  1018  			"( simple_select |":    "(",
  1019  			"| select_with_parens": "",
  1020  			"select_clause sort_clause | select_clause ( sort_clause |  ) ( limit_clause offset_clause | offset_clause limit_clause | limit_clause | offset_clause )":                                                                                                                                           "select_clause ( sort_clause | ) ( limit_clause | ) ( offset_clause | )",
  1021  			"| ( 'WITH' ( ( common_table_expr ) ( ( ',' common_table_expr ) )* ) ) select_clause sort_clause | ( 'WITH' ( ( common_table_expr ) ( ( ',' common_table_expr ) )* ) ) select_clause ( sort_clause |  ) ( limit_clause offset_clause | offset_clause limit_clause | limit_clause | offset_clause )": "( sort_clause | ) ( limit_clause | ) ( offset_clause | )",
  1022  		},
  1023  		unlink:  []string{"index_name"},
  1024  		nosplit: true,
  1025  	},
  1026  	{
  1027  		name:   "select_clause",
  1028  		inline: []string{"simple_select"},
  1029  		replace: map[string]string{
  1030  			"| select_with_parens": "| '(' ( simple_select_clause | values_clause | table_clause | set_operation ) ')'",
  1031  		},
  1032  		nosplit: true,
  1033  	},
  1034  	{name: "table_clause"},
  1035  	{
  1036  		name:    "set_operation",
  1037  		inline:  []string{"all_or_distinct"},
  1038  		nosplit: true,
  1039  	},
  1040  	{
  1041  		name:   "values_clause",
  1042  		inline: []string{"expr_list"},
  1043  	},
  1044  	{
  1045  		name:    "simple_select_clause",
  1046  		inline:  []string{"opt_all_clause", "distinct_clause", "distinct_on_clause", "opt_as_of_clause", "as_of_clause", "expr_list", "target_list", "from_clause", "opt_where_clause", "where_clause", "group_clause", "having_clause", "window_clause", "from_list"},
  1047  		unlink:  []string{"index_name"},
  1048  		nosplit: true,
  1049  	},
  1050  	{
  1051  		name:    "joined_table",
  1052  		inline:  []string{"join_qual", "name_list", "join_type", "join_outer"},
  1053  		nosplit: true,
  1054  	},
  1055  	{
  1056  		name:   "table_ref",
  1057  		inline: []string{"opt_ordinality", "opt_alias_clause", "opt_expr_list", "opt_column_list", "name_list", "alias_clause"},
  1058  		replace: map[string]string{
  1059  			"select_with_parens": "'(' select_stmt ')'",
  1060  			"opt_index_flags":    "( '@' index_name | )",
  1061  			"relation_expr":      "table_name",
  1062  			"func_table":         "func_application",
  1063  			//			"| func_name '(' ( expr_list |  ) ')' ( 'WITH' 'ORDINALITY' |  ) ( ( 'AS' table_alias_name ( '(' ( ( name ) ( ( ',' name ) )* ) ')' |  ) | table_alias_name ( '(' ( ( name ) ( ( ',' name ) )* ) ')' |  ) ) |  )": "",
  1064  			"| special_function ( 'WITH' 'ORDINALITY' |  ) ( ( 'AS' table_alias_name ( '(' ( ( name ) ( ( ',' name ) )* ) ')' |  ) | table_alias_name ( '(' ( ( name ) ( ( ',' name ) )* ) ')' |  ) ) |  )": "",
  1065  			"| '(' joined_table ')' ( 'WITH' 'ORDINALITY' |  ) ( 'AS' table_alias_name ( '(' ( ( name ) ( ( ',' name ) )* ) ')' |  ) | table_alias_name ( '(' ( ( name ) ( ( ',' name ) )* ) ')' |  ) )":    "| '(' joined_table ')' ( 'WITH' 'ORDINALITY' |  ) ( ( 'AS' table_alias_name ( '(' ( ( name ) ( ( ',' name ) )* ) ')' |  ) | table_alias_name ( '(' ( ( name ) ( ( ',' name ) )* ) ')' |  ) ) |  )",
  1066  		},
  1067  		unlink:  []string{"index_name"},
  1068  		nosplit: true,
  1069  	},
  1070  	{
  1071  		name:   "set_var",
  1072  		stmt:   "preparable_set_stmt",
  1073  		inline: []string{"set_session_stmt", "set_rest_more", "generic_set", "var_list", "to_or_eq"},
  1074  		exclude: []*regexp.Regexp{
  1075  			regexp.MustCompile(`'SET' . 'TRANSACTION'`),
  1076  			regexp.MustCompile(`'SET' 'TRANSACTION'`),
  1077  			regexp.MustCompile(`'SET' 'SESSION' var_name`),
  1078  			regexp.MustCompile(`'SET' 'SESSION' 'TRANSACTION'`),
  1079  			regexp.MustCompile(`'SET' 'SESSION' 'CHARACTERISTICS'`),
  1080  			regexp.MustCompile("'SET' 'CLUSTER'"),
  1081  		},
  1082  		replace: map[string]string{
  1083  			"'=' 'DEFAULT'":  "'=' 'DEFAULT' | 'SET' 'TIME' 'ZONE' ( var_value | 'DEFAULT' | 'LOCAL' )",
  1084  			"'SET' var_name": "'SET' ( 'SESSION' | ) var_name",
  1085  		},
  1086  	},
  1087  	{
  1088  		name:   "set_cluster_setting",
  1089  		stmt:   "preparable_set_stmt",
  1090  		inline: []string{"set_csetting_stmt", "generic_set", "var_list", "to_or_eq"},
  1091  		match: []*regexp.Regexp{
  1092  			regexp.MustCompile("'SET' 'CLUSTER'"),
  1093  		},
  1094  	},
  1095  	{
  1096  		name: "set_transaction",
  1097  		stmt: "nonpreparable_set_stmt",
  1098  		inline: []string{
  1099  			"set_transaction_stmt",
  1100  			"transaction_mode",
  1101  			"transaction_mode_list",
  1102  			"transaction_read_mode",
  1103  			"transaction_user_priority",
  1104  			"user_priority",
  1105  			"as_of_clause",
  1106  			"opt_comma",
  1107  		},
  1108  		match: []*regexp.Regexp{regexp.MustCompile("'SET' 'TRANSACTION'")},
  1109  	},
  1110  	{
  1111  		name: "show_var",
  1112  		stmt: "show_stmt",
  1113  		exclude: []*regexp.Regexp{
  1114  			regexp.MustCompile("'SHOW' 'ALL' 'CLUSTER'"),
  1115  			regexp.MustCompile("'SHOW' 'IN"),       // INDEX, INDEXES
  1116  			regexp.MustCompile("'SHOW' '[B-HJ-Z]"), // Keep ALL and IDENT.
  1117  		},
  1118  		replace: map[string]string{"identifier": "var_name"},
  1119  	},
  1120  	{
  1121  		name: "show_cluster_setting",
  1122  		stmt: "show_csettings_stmt",
  1123  	},
  1124  	{
  1125  		name:   "show_columns_stmt",
  1126  		inline: []string{"with_comment"},
  1127  	},
  1128  	{
  1129  		name:    "show_constraints",
  1130  		stmt:    "show_stmt",
  1131  		match:   []*regexp.Regexp{regexp.MustCompile("'SHOW' 'CONSTRAINTS'")},
  1132  		replace: map[string]string{"var_name": "table_name"},
  1133  		unlink:  []string{"table_name"},
  1134  	},
  1135  	{
  1136  		name:    "show_create_stmt",
  1137  		replace: map[string]string{"table_name": "object_name"},
  1138  		unlink:  []string{"object_name"},
  1139  	},
  1140  	{
  1141  		name:   "show_databases_stmt",
  1142  		inline: []string{"with_comment"},
  1143  	},
  1144  	{
  1145  		name:    "show_backup",
  1146  		stmt:    "show_backup_stmt",
  1147  		match:   []*regexp.Regexp{regexp.MustCompile("'SHOW' 'BACKUP'")},
  1148  		replace: map[string]string{"string_or_placeholder": "location"},
  1149  		unlink:  []string{"location"},
  1150  	},
  1151  	{
  1152  		name:    "show_jobs",
  1153  		stmt:    "show_jobs_stmt",
  1154  		replace: map[string]string{"a_expr": "job_id"},
  1155  		unlink:  []string{"job_id"},
  1156  	},
  1157  	{
  1158  		name:   "show_grants_stmt",
  1159  		inline: []string{"name_list", "opt_on_targets_roles", "for_grantee_clause", "name_list"},
  1160  		replace: map[string]string{
  1161  			"targets_roles":                "( 'ROLE' | 'ROLE' name ( ',' name ) )* | ( 'TABLE' | ) table_pattern ( ( ',' table_pattern ) )* | 'DATABASE' database_name ( ( ',' database_name ) )* )",
  1162  			"'FOR' name ( ( ',' name ) )*": "'FOR' user_name ( ( ',' user_name ) )*",
  1163  		},
  1164  		unlink: []string{"role_name", "table_name", "database_name", "user_name"},
  1165  	},
  1166  	{
  1167  		name: "show_indexes",
  1168  		stmt: "show_indexes_stmt",
  1169  	},
  1170  	{
  1171  		name:    "show_index",
  1172  		stmt:    "show_stmt",
  1173  		match:   []*regexp.Regexp{regexp.MustCompile("'SHOW' 'INDEX'")},
  1174  		replace: map[string]string{"var_name": "table_name"},
  1175  		unlink:  []string{"table_name"},
  1176  	},
  1177  	{
  1178  		name:  "show_keys",
  1179  		stmt:  "show_stmt",
  1180  		match: []*regexp.Regexp{regexp.MustCompile("'SHOW' 'KEYS'")},
  1181  	},
  1182  	{
  1183  		name:    "show_locality",
  1184  		stmt:    "show_roles_stmt",
  1185  		replace: map[string]string{"'ROLES'": "'LOCALITY'"},
  1186  	},
  1187  	{
  1188  		name: "show_partitions_stmt",
  1189  	},
  1190  	{
  1191  		name:   "show_queries",
  1192  		stmt:   "show_queries_stmt",
  1193  		inline: []string{"opt_cluster"},
  1194  	},
  1195  	{
  1196  		name: "show_roles_stmt",
  1197  	},
  1198  	{
  1199  		name: "show_users_stmt",
  1200  	},
  1201  	{
  1202  		name: "show_ranges_stmt",
  1203  		stmt: "show_ranges_stmt",
  1204  	},
  1205  	{
  1206  		name:    "show_range_for_row_stmt",
  1207  		stmt:    "show_range_for_row_stmt",
  1208  		inline:  []string{"expr_list"},
  1209  		replace: map[string]string{"a_expr": "row_vals"},
  1210  		unlink:  []string{"row_vals"},
  1211  	},
  1212  	{
  1213  		name: "show_schemas",
  1214  		stmt: "show_schemas_stmt",
  1215  	},
  1216  	{
  1217  		name: "show_sequences",
  1218  		stmt: "show_sequences_stmt",
  1219  	},
  1220  	{
  1221  		name:   "show_sessions",
  1222  		stmt:   "show_sessions_stmt",
  1223  		inline: []string{"opt_cluster"},
  1224  	},
  1225  	{
  1226  		name: "show_stats",
  1227  		stmt: "show_stats_stmt",
  1228  	},
  1229  	{
  1230  		name:    "show_tables",
  1231  		stmt:    "show_tables_stmt",
  1232  		inline:  []string{"with_comment"},
  1233  		replace: map[string]string{"'FROM' name": "'FROM' database_name", "'.' name": "'.' schema_name"},
  1234  		unlink:  []string{"schema.name"},
  1235  	},
  1236  	{
  1237  		name:    "show_trace",
  1238  		stmt:    "show_trace_stmt",
  1239  		inline:  []string{"opt_compact"},
  1240  		exclude: []*regexp.Regexp{regexp.MustCompile("'SHOW' 'EXPERIMENTAL_REPLICA'")},
  1241  	},
  1242  	{
  1243  		name:  "show_transaction",
  1244  		stmt:  "show_stmt",
  1245  		match: []*regexp.Regexp{regexp.MustCompile("'SHOW' 'TRANSACTION'")},
  1246  	},
  1247  	{
  1248  		name:  "show_savepoint_status",
  1249  		stmt:  "show_savepoint_stmt",
  1250  		match: []*regexp.Regexp{regexp.MustCompile("'SHOW' 'SAVEPOINT' 'STATUS'")},
  1251  	},
  1252  	{
  1253  		name:   "show_zone_stmt",
  1254  		inline: []string{"opt_partition", "table_index_name", "partition"},
  1255  	},
  1256  	{
  1257  		name:   "sort_clause",
  1258  		inline: []string{"sortby_list", "sortby", "opt_asc_desc", "opt_nulls_order"},
  1259  	},
  1260  	{
  1261  		name:    "split_index_at",
  1262  		stmt:    "alter_split_index_stmt",
  1263  		inline:  []string{"table_index_name"},
  1264  		replace: map[string]string{"standalone_index_name": "index_name"},
  1265  	},
  1266  	{
  1267  		name:   "split_table_at",
  1268  		stmt:   "alter_split_stmt",
  1269  		unlink: []string{"table_name"},
  1270  	},
  1271  	{
  1272  		name:   "table_constraint",
  1273  		inline: []string{"constraint_elem", "opt_storing", "storing"},
  1274  	},
  1275  	{
  1276  		name:    "truncate_stmt",
  1277  		inline:  []string{"opt_table", "relation_expr_list", "opt_drop_behavior"},
  1278  		replace: map[string]string{"relation_expr": "table_name"},
  1279  		unlink:  []string{"table_name"},
  1280  	},
  1281  	{
  1282  		name: "unique_column_level",
  1283  		stmt: "stmt_block",
  1284  		replace: map[string]string{"	stmt": "	'CREATE' 'TABLE' table_name '(' column_name column_type 'UNIQUE' ( column_constraints | ) ( ',' ( column_def ( ',' column_def )* ) | ) ( table_constraints | ) ')' ')'"},
  1285  		unlink: []string{"table_name", "column_name", "column_type", "table_constraints"},
  1286  	},
  1287  	{
  1288  		name: "unique_table_level",
  1289  		stmt: "stmt_block",
  1290  		replace: map[string]string{"	stmt": "	'CREATE' 'TABLE' table_name '(' ( column_def ( ',' column_def )* ) ( 'CONSTRAINT' name | ) 'UNIQUE' '(' ( column_name ( ',' column_name )* ) ')' ( table_constraints | ) ')'"},
  1291  		unlink: []string{"table_name", "check_expr", "table_constraints"},
  1292  	},
  1293  	{
  1294  		name:    "unsplit_index_at",
  1295  		stmt:    "alter_unsplit_index_stmt",
  1296  		inline:  []string{"table_index_name"},
  1297  		replace: map[string]string{"standalone_index_name": "index_name"},
  1298  	},
  1299  	{
  1300  		name:   "unsplit_table_at",
  1301  		stmt:   "alter_unsplit_stmt",
  1302  		unlink: []string{"table_name"},
  1303  	},
  1304  	{
  1305  		name: "update_stmt",
  1306  		inline: []string{
  1307  			"opt_with_clause",
  1308  			"with_clause",
  1309  			"cte_list",
  1310  			"table_expr_opt_alias_idx",
  1311  			"table_name_opt_idx",
  1312  			"set_clause_list",
  1313  			"set_clause",
  1314  			"single_set_clause",
  1315  			"multiple_set_clause",
  1316  			"in_expr",
  1317  			"expr_list",
  1318  			"expr_tuple1_ambiguous",
  1319  			"tuple1_ambiguous_values",
  1320  			"opt_where_clause",
  1321  			"where_clause",
  1322  			"opt_sort_clause",
  1323  			"returning_clause",
  1324  			"insert_column_list",
  1325  			"insert_column_item",
  1326  			"opt_limit_clause",
  1327  		},
  1328  		replace: map[string]string{
  1329  			"relation_expr":      "table_name",
  1330  			"select_with_parens": "'(' select_stmt ')'",
  1331  		},
  1332  		relink: map[string]string{
  1333  			"table_name":       "relation_expr",
  1334  			"column_name_list": "insert_column_list",
  1335  		},
  1336  		nosplit: true,
  1337  	},
  1338  	{
  1339  		name:    "upsert_stmt",
  1340  		stmt:    "upsert_stmt",
  1341  		inline:  []string{"insert_target", "insert_rest", "returning_clause", "opt_with_clause", "with_clause", "cte_list", "insert_column_list", "insert_column_item"},
  1342  		unlink:  []string{"select_stmt"},
  1343  		nosplit: true,
  1344  	},
  1345  	{
  1346  		name:    "validate_constraint",
  1347  		stmt:    "alter_onetable_stmt",
  1348  		replace: map[string]string{"alter_table_cmds": "'VALIDATE' 'CONSTRAINT' constraint_name", "relation_expr": "table_name"},
  1349  		unlink:  []string{"constraint_name", "table_name"},
  1350  	},
  1351  	{
  1352  		name:   "window_definition",
  1353  		inline: []string{"window_specification"},
  1354  	},
  1355  	{
  1356  		name:   "opt_frame_clause",
  1357  		inline: []string{"frame_extent"},
  1358  	},
  1359  }
  1360  
  1361  // regList is a common regex used when removing loops from alter and drop
  1362  // statements.
  1363  const regList = ` \( \( ',' .* \) \)\*`