nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: ariga/atlas
Pull request #2707 (2024-04-21 18:52:34)

sql/internal/specutil: handle heredoc with crlf


Patch:
diff --git a/sql/internal/specutil/convert.go b/sql/internal/specutil/convert.go
index a38f2313..50341028 100644
--- a/sql/internal/specutil/convert.go
+++ b/sql/internal/specutil/convert.go
@@ -680,10 +680,10 @@ func FromView(v *schema.View, colFn ViewColumnSpecFunc, idxFn IndexSpecFunc) (*s
 		}
 		spec.Indexes = append(spec.Indexes, i)
 	}
-	as := v.Def
+	as := normalizeCRLF(v.Def)
 	// In case the view definition is multi-line,
 	// format it as indented heredoc with two spaces.
-	if lines := strings.Split(v.Def, "\n"); len(lines) > 1 {
+	if lines := strings.Split(as, "\n"); len(lines) > 1 {
 		as = fmt.Sprintf("<<-SQL\n  %s\n  SQL", strings.Join(lines, "\n  "))
 	}
 	embed := &schemahcl.Resource{
@@ -708,6 +708,15 @@ func FromView(v *schema.View, colFn ViewColumnSpecFunc, idxFn IndexSpecFunc) (*s
 	return spec, nil
 }
 
+// normalizeCRLF for heredoc strings that inspected and printed in the HCL as-is to
+// avoid having mixed endings in the printed file - Unix-like (default) and Windows-like.
+func normalizeCRLF(s string) string {
+	if strings.Contains(s, "\r\n") {
+		return strings.ReplaceAll(s, "\r\n", "\n")
+	}
+	return s
+}
+
 // dependsOn returns the depends_on attribute for the given objects.
 func dependsOn(realm *schema.Realm, objects []schema.Object) (*schemahcl.Attr, bool) {
 	var (


Test patch:
diff --git a/sql/internal/specutil/convert_test.go b/sql/internal/specutil/convert_test.go
index 8448c8f7..6b00d20d 100644
--- a/sql/internal/specutil/convert_test.go
+++ b/sql/internal/specutil/convert_test.go
@@ -145,3 +145,17 @@ func TestDefault(t *testing.T) {
 		require.True(t, tt.v.Equals(v).True())
 	}
 }
+
+func TestFromView(t *testing.T) {
+	spec, err := FromView(&schema.View{
+		Name:   "view",
+		Def:    "SELECT * FROM users\r\n WHERE c NOT LIKE \"\\r\\n\"",
+		Schema: schema.New("public").SetRealm(schema.NewRealm()),
+	}, nil, nil)
+	require.NoError(t, err)
+	as, ok := spec.DefaultExtension.Attr("as")
+	require.True(t, ok)
+	s, err := as.String()
+	require.NoError(t, err)
+	require.Equal(t, "<<-SQL\n  SELECT * FROM users\n   WHERE c NOT LIKE \"\\r\\n\"\n  SQL", s)
+}
Repository: ariga/atlas
Pull request #2714 (2024-04-24 05:07:27)

sql/migrate: support io.Writer/io.Reader to compress dir


Patch:
diff --git a/sql/migrate/dir.go b/sql/migrate/dir.go
index 7ad9ba70..75a605b3 100644
--- a/sql/migrate/dir.go
+++ b/sql/migrate/dir.go
@@ -840,38 +840,51 @@ func (m *memFile) Sys() interface{}           { return nil }
 // ArchiveDir returns a tar archive of the given directory.
 func ArchiveDir(dir Dir) ([]byte, error) {
 	var buf bytes.Buffer
-	tw := tar.NewWriter(&buf)
+	if err := ArchiveDirTo(&buf, dir); err != nil {
+		return nil, err
+	}
+	return buf.Bytes(), nil
+}
+
+// ArchiveDirTo writes a tar archive of the given directory to the given writer.
+func ArchiveDirTo(w io.Writer, dir Dir) error {
+	tw := tar.NewWriter(w)
 	defer tw.Close()
 	sumF, err := dir.Open(HashFileName)
 	if err != nil && !errors.Is(err, fs.ErrNotExist) {
-		return nil, err
+		return err
 	}
 	if sumF != nil {
 		sumB, err := io.ReadAll(sumF)
 		if err != nil {
-			return nil, err
+			return err
 		}
 		if err := append2Tar(tw, HashFileName, sumB); err != nil {
-			return nil, err
+			return err
 		}
 	}
 	files, err := dir.Files()
 	if err != nil {
-		return nil, err
+		return err
 	}
 	for _, f := range files {
 		if err := append2Tar(tw, f.Name(), f.Bytes()); err != nil {
-			return nil, err
+			return err
 		}
 	}
-	return buf.Bytes(), nil
+	return nil
 }
 
 // UnarchiveDir extracts the tar archive into the given directory.
 func UnarchiveDir(arc []byte) (Dir, error) {
+	return UnarchiveDirFrom(bytes.NewReader(arc))
+}
+
+// UnarchiveDirFrom extracts the tar archive into the given directory.
+func UnarchiveDirFrom(r io.Reader) (Dir, error) {
 	var (
 		md = &MemDir{}
-		tr = tar.NewReader(bytes.NewReader(arc))
+		tr = tar.NewReader(r)
 	)
 	for {
 		h, err := tr.Next()


Test patch:
diff --git a/sql/migrate/dir_test.go b/sql/migrate/dir_test.go
index d9914a52..f9e47bbf 100644
--- a/sql/migrate/dir_test.go
+++ b/sql/migrate/dir_test.go
@@ -7,6 +7,7 @@ package migrate_test
 import (
 	"archive/tar"
 	"bytes"
+	"compress/gzip"
 	_ "embed"
 	"io"
 	"os"
@@ -512,6 +513,22 @@ func TestDirTar(t *testing.T) {
 	require.Len(t, files, 1)
 	require.Equal(t, "1.sql", files[0].Name())
 	require.Equal(t, "create table t(c int);", string(files[0].Bytes()))
+
+	// Compress the dir.
+	var buf bytes.Buffer
+	w := gzip.NewWriter(&buf)
+	require.NoError(t, migrate.ArchiveDirTo(w, d))
+	require.NoError(t, w.Close()) // flush and Close.
+
+	// Decompress.
+	rr, err := gzip.NewReader(&buf)
+	require.NoError(t, err)
+	dir, err = migrate.UnarchiveDirFrom(rr)
+	require.NoError(t, err)
+	files, err = dir.Files()
+	require.Len(t, files, 1)
+	require.Equal(t, "1.sql", files[0].Name())
+	require.Equal(t, "create table t(c int);", string(files[0].Bytes()))
 }
 
 func fileNames(r io.Reader) ([]string, error) {
Repository: ariga/atlas
Pull request #2727 (2024-04-27 18:55:47)

sql/migrate: fix comment scanning


Patch:
diff --git a/sql/migrate/lex.go b/sql/migrate/lex.go
index 02521a3d..775aea59 100644
--- a/sql/migrate/lex.go
+++ b/sql/migrate/lex.go
@@ -214,9 +214,11 @@ Scan:
 		// expressions until we make the lexer driver-aware.
 		case depth == 0 && r == '#':
 			s.comment("#", "\n")
-		case r == '-' && s.next() == '-':
+		case r == '-' && s.pick() == '-':
+			s.next()
 			s.comment("--", "\n")
-		case r == '/' && s.next() == '*':
+		case r == '/' && s.pick() == '*':
+			s.next()
 			s.comment("/*", "*/")
 		case s.delim == delimiter && s.MatchBeginAtomic && reBeginAtomic.MatchString(s.input[s.pos-1:]):
 			if err := s.skipBeginAtomic(); err == nil {
@@ -248,9 +250,9 @@ func (s *Scanner) next() rune {
 }
 
 func (s *Scanner) pick() rune {
-	p, w := s.pos, s.width
+	p, w, t := s.pos, s.width, s.total
 	r := s.next()
-	s.pos, s.width = p, w
+	s.pos, s.width, s.total = p, w, t
 	return r
 }
 


Test patch:
diff --git a/sql/migrate/testdata/lex/17_paren.sql b/sql/migrate/testdata/lex/17_paren.sql
new file mode 100644
index 00000000..37064d63
--- /dev/null
+++ b/sql/migrate/testdata/lex/17_paren.sql
@@ -0,0 +1,6 @@
+ALTER TABLE ONLY t ADD CONSTRAINT name1 EXCLUDE USING gist (id WITH =, cid WITH -|-);
+ALTER TABLE ONLY t
+    ADD CONSTRAINT name2 EXCLUDE USING gist (id WITH =, cid WITH -|-);
+ALTER TABLE ONLY t
+    ADD CONSTRAINT name3 EXCLUDE USING gist (id WITH =, cid WITH -|-),
+    ADD CONSTRAINT name4 EXCLUDE USING gist (id WITH =, cid WITH -|-);
diff --git a/sql/migrate/testdata/lex/17_paren.sql.golden b/sql/migrate/testdata/lex/17_paren.sql.golden
new file mode 100644
index 00000000..0d8add5d
--- /dev/null
+++ b/sql/migrate/testdata/lex/17_paren.sql.golden
@@ -0,0 +1,8 @@
+ALTER TABLE ONLY t ADD CONSTRAINT name1 EXCLUDE USING gist (id WITH =, cid WITH -|-);
+-- end --
+ALTER TABLE ONLY t
+    ADD CONSTRAINT name2 EXCLUDE USING gist (id WITH =, cid WITH -|-);
+-- end --
+ALTER TABLE ONLY t
+    ADD CONSTRAINT name3 EXCLUDE USING gist (id WITH =, cid WITH -|-),
+    ADD CONSTRAINT name4 EXCLUDE USING gist (id WITH =, cid WITH -|-);
\ No newline at end of file
Repository: ariga/atlas
Pull request #2751 (2024-05-06 09:41:31)

sql/postgres: ignore auto created public comment


Patch:
diff --git a/sql/postgres/diff.go b/sql/postgres/diff.go
index 69a7b7e9..6e7d59eb 100644
--- a/sql/postgres/diff.go
+++ b/sql/postgres/diff.go
@@ -27,17 +27,28 @@ var DefaultDiff schema.Differ = &sqlx.Diff{DiffDriver: &diff{&conn{ExecQuerier:
 type diff struct{ *conn }
 
 // SchemaAttrDiff returns a changeset for migrating schema attributes from one state to the other.
-func (*diff) SchemaAttrDiff(from, to *schema.Schema) []schema.Change {
-	var changes []schema.Change
-	if change := sqlx.CommentDiff(skipDefaultComment(from), skipDefaultComment(to)); change != nil {
+func (d *diff) SchemaAttrDiff(from, to *schema.Schema) []schema.Change {
+	var (
+		changes    []schema.Change
+		fromA, toA []schema.Attr
+	)
+	// In schema scope, users might compare a "public" schema with a "non-public" schema.
+	// However, since the public standard comment is auto created and not set by the users
+	// this is unintentional in most cases, and this change will be rejected by the plan stage.
+	if d.schema != "" {
+		fromA, toA = skipDefaultComment(from, from.Name), skipDefaultComment(to, to.Name)
+	} else {
+		fromA, toA = skipDefaultComment(from, "public"), skipDefaultComment(to, "public")
+	}
+	if change := sqlx.CommentDiff(fromA, toA); change != nil {
 		changes = append(changes, change)
 	}
 	return changes
 }
 
-func skipDefaultComment(s *schema.Schema) []schema.Attr {
+func skipDefaultComment(s *schema.Schema, public string) []schema.Attr {
 	attrs := s.Attrs
-	if c := (schema.Comment{}); sqlx.Has(attrs, &c) && c.Text == "standard public schema" && (s.Name == "" || s.Name == "public") {
+	if c := (schema.Comment{}); sqlx.Has(attrs, &c) && c.Text == "standard public schema" && (s.Name == "" || s.Name == public) {
 		attrs = schema.RemoveAttr[*schema.Comment](attrs)
 	}
 	return attrs


Test patch:
diff --git a/sql/postgres/diff_test.go b/sql/postgres/diff_test.go
index e8763f84..fe74c7ca 100644
--- a/sql/postgres/diff_test.go
+++ b/sql/postgres/diff_test.go
@@ -587,6 +587,38 @@ func TestDiff_SchemaDiff(t *testing.T) {
 				},
 			}},
 		}, changes)
+
+		// In schema scope, auto created
+		// public comment should be ignored.
+		drv.(*Driver).schema = "public"
+		from.Name, to.Name = "other", "other"
+		from.SetComment("standard public schema")
+		to.SetComment("standard public schema")
+		changes, err = drv.SchemaDiff(from, to)
+		require.NoError(t, err)
+		require.Empty(t, changes)
+
+		to.SetComment("")
+		changes, err = drv.SchemaDiff(from, to)
+		require.NoError(t, err)
+		require.Empty(t, changes)
+
+		from.Name, to.Name = "public", "public"
+		changes, err = drv.SchemaDiff(from, to)
+		require.NoError(t, err)
+		require.Empty(t, changes)
+
+		from.SetComment("custom public schema")
+		changes, err = drv.SchemaDiff(from, to)
+		require.NoError(t, err)
+		require.EqualValues(t, []schema.Change{
+			&schema.ModifySchema{S: to, Changes: schema.Changes{
+				&schema.ModifyAttr{
+					From: &schema.Comment{Text: "custom public schema"},
+					To:   &schema.Comment{Text: ""},
+				},
+			}},
+		}, changes)
 	})
 }
Repository: ariga/atlas
Pull request #2780 (2024-05-15 07:02:52)

sql/postgres: add schema qualifier for COMMENT ON INDEX command if needed


Patch:
diff --git a/sql/postgres/migrate.go b/sql/postgres/migrate.go
index e5f87de6..5fa5a5e8 100644
--- a/sql/postgres/migrate.go
+++ b/sql/postgres/migrate.go
@@ -860,7 +860,7 @@ func (s *state) columnComment(src schema.Change, t *schema.Table, c *schema.Colu
 }
 
 func (s *state) indexComment(src schema.Change, t *schema.Table, idx *schema.Index, to, from string) *migrate.Change {
-	b := s.Build("COMMENT ON INDEX").Ident(idx.Name).P("IS")
+	b := s.Build("COMMENT ON INDEX").SchemaResource(t.Schema, idx.Name).P("IS")
 	return &migrate.Change{
 		Cmd:     b.Clone().P(quote(to)).String(),
 		Source:  src,


Test patch:
diff --git a/sql/postgres/migrate_test.go b/sql/postgres/migrate_test.go
index 686dac8a..7ba9db38 100644
--- a/sql/postgres/migrate_test.go
+++ b/sql/postgres/migrate_test.go
@@ -433,6 +433,38 @@ func TestPlanChanges(t *testing.T) {
 				},
 			},
 		},
+		// Qualified DROP TABLE plan.
+		{
+			changes: []schema.Change{
+				&schema.DropTable{
+					T: func() *schema.Table {
+						id := schema.NewIntColumn("id", "int")
+						return schema.NewTable("posts").
+							SetSchema(schema.New("private")).
+							SetComment("a8m's posts").
+							AddColumns(id).
+							AddIndexes(
+								schema.NewIndex("idx").AddColumns(id).SetComment("a8m's index"),
+							)
+					}(),
+				},
+			},
+			wantPlan: &migrate.Plan{
+				Reversible:    true,
+				Transactional: true,
+				Changes: []*migrate.Change{
+					{
+						Cmd: `DROP TABLE "private"."posts"`,
+						Reverse: []string{
+							`CREATE TABLE "private"."posts" ("id" integer NOT NULL)`,
+							`CREATE INDEX "idx" ON "private"."posts" ("id")`,
+							`COMMENT ON TABLE "private"."posts" IS 'a8m''s posts'`,
+							`COMMENT ON INDEX "private"."idx" IS 'a8m''s index'`,
+						},
+					},
+				},
+			},
+		},
 		// Drop a primary key.
 		{
 			changes: []schema.Change{
Repository: ariga/atlas
Pull request #2784 (2024-05-15 15:59:09)

sql/sqlite: replace internal type with `schema.UUID` type


Patch:
diff --git a/sql/sqlite/convert.go b/sql/sqlite/convert.go
index 2885e4b4..f5c18604 100644
--- a/sql/sqlite/convert.go
+++ b/sql/sqlite/convert.go
@@ -39,7 +39,7 @@ func FormatType(t schema.Type) (string, error) {
 		f = strings.ToLower(t.T)
 	case *schema.SpatialType:
 		f = strings.ToLower(t.T)
-	case *UUIDType:
+	case *schema.UUIDType:
 		f = strings.ToLower(t.T)
 	case *schema.UnsupportedType:
 		return "", fmt.Errorf("sqlite: unsupported type: %q", t.T)
@@ -100,7 +100,7 @@ func ParseType(c string) (schema.Type, error) {
 	case "date", "datetime", "time", "timestamp":
 		return &schema.TimeType{T: t}, nil
 	case "uuid":
-		return &UUIDType{T: t}, nil
+		return &schema.UUIDType{T: t}, nil
 	default:
 		return &schema.UnsupportedType{T: t}, nil
 	}
diff --git a/sql/sqlite/inspect.go b/sql/sqlite/inspect.go
index f8c94406..4677d32f 100644
--- a/sql/sqlite/inspect.go
+++ b/sql/sqlite/inspect.go
@@ -512,10 +512,9 @@ type (
 	}
 
 	// A UUIDType defines a UUID type.
-	UUIDType struct {
-		schema.Type
-		T string
-	}
+	//
+	// Deprecated: Use schema.UUIDType instead.
+	UUIDType = schema.UUIDType
 )
 
 func columnParts(t string) []string {


Test patch:
diff --git a/sql/sqlite/sqlspec_test.go b/sql/sqlite/sqlspec_test.go
index 2d365647..4b214552 100644
--- a/sql/sqlite/sqlspec_test.go
+++ b/sql/sqlite/sqlspec_test.go
@@ -456,7 +456,7 @@ func TestTypes(t *testing.T) {
 		},
 		{
 			typeExpr: "uuid",
-			expected: &UUIDType{T: "uuid"},
+			expected: &schema.UUIDType{T: "uuid"},
 		},
 	} {
 		t.Run(tt.typeExpr, func(t *testing.T) {
Repository: ariga/atlas
Pull request #2794 (2024-05-21 10:35:54)

sql/internal/sqlx: add WrapIndentErr helper to builder


Patch:
diff --git a/sql/internal/sqlx/sqlx.go b/sql/internal/sqlx/sqlx.go
index 5e312890..ac16a606 100644
--- a/sql/internal/sqlx/sqlx.go
+++ b/sql/internal/sqlx/sqlx.go
@@ -497,6 +497,18 @@ func (b *Builder) WrapIndent(f func(b *Builder)) *Builder {
 	})
 }
 
+// WrapIndentErr is like WrapErr but with extra level of indentation.
+func (b *Builder) WrapIndentErr(f func(b *Builder) error) error {
+	var err error
+	b.Wrap(func(b *Builder) {
+		b.IndentIn()
+		err = f(b)
+		b.IndentOut()
+		b.NL()
+	})
+	return err
+}
+
 // Clone returns a duplicate of the builder.
 func (b *Builder) Clone() *Builder {
 	return &Builder{


Test patch:
diff --git a/sql/internal/sqlx/sqlx_test.go b/sql/internal/sqlx/sqlx_test.go
index d22478a5..c7fac7fc 100644
--- a/sql/internal/sqlx/sqlx_test.go
+++ b/sql/internal/sqlx/sqlx_test.go
@@ -63,6 +63,7 @@ func TestBuilder(t *testing.T) {
 
 	// WrapErr.
 	require.EqualError(t, b.WrapErr(func(*Builder) error { return errors.New("oops") }), "oops")
+	require.EqualError(t, b.WrapIndentErr(func(*Builder) error { return errors.New("oops") }), "oops")
 }
 
 func TestBuilder_Qualifier(t *testing.T) {
Repository: ariga/atlas
Pull request #2813 (2024-05-29 20:33:50)

sql/postgres: fix column identifier on COMMAND commands
Fixed https://github.com/ariga/atlas/issues/2812


Patch:
diff --git a/sql/postgres/migrate.go b/sql/postgres/migrate.go
index 1948958e..fe8b5a65 100644
--- a/sql/postgres/migrate.go
+++ b/sql/postgres/migrate.go
@@ -851,9 +851,8 @@ func (s *state) tableComment(src schema.Change, t *schema.Table, to, from string
 }
 
 func (s *state) columnComment(src schema.Change, t *schema.Table, c *schema.Column, to, from string) *migrate.Change {
-	b := s.Build("COMMENT ON COLUMN").Table(t)
-	b.WriteByte('.')
-	b.Ident(c.Name).P("IS")
+	b := s.Build("COMMENT ON COLUMN").TableResource(t, c)
+	b.P("IS")
 	return &migrate.Change{
 		Cmd:     b.Clone().P(quote(to)).String(),
 		Source:  src,


Test patch:
diff --git a/sql/postgres/migrate_test.go b/sql/postgres/migrate_test.go
index 7ba9db38..1f5d9df9 100644
--- a/sql/postgres/migrate_test.go
+++ b/sql/postgres/migrate_test.go
@@ -856,12 +856,12 @@ func TestPlanChanges(t *testing.T) {
 						Reverse: `DROP INDEX "nulls_not_distinct"`,
 					},
 					{
-						Cmd:     `COMMENT ON COLUMN "users" ."name" IS 'foo'`,
-						Reverse: `COMMENT ON COLUMN "users" ."name" IS ''`,
+						Cmd:     `COMMENT ON COLUMN "users"."name" IS 'foo'`,
+						Reverse: `COMMENT ON COLUMN "users"."name" IS ''`,
 					},
 					{
-						Cmd:     `COMMENT ON COLUMN "users" ."last" IS 'bar'`,
-						Reverse: `COMMENT ON COLUMN "users" ."last" IS ''`,
+						Cmd:     `COMMENT ON COLUMN "users"."last" IS 'bar'`,
+						Reverse: `COMMENT ON COLUMN "users"."last" IS ''`,
 					},
 					{
 						Cmd:     `COMMENT ON INDEX "id_key" IS 'comment'`,
@@ -903,8 +903,8 @@ func TestPlanChanges(t *testing.T) {
 						Reverse: `ALTER TABLE "users" ALTER COLUMN "id" SET GENERATED BY DEFAULT SET START WITH 1 SET INCREMENT BY 1 RESTART, ADD COLUMN "name" character varying NOT NULL`,
 					},
 					{
-						Cmd:     `COMMENT ON COLUMN "users" ."id" IS ''`,
-						Reverse: `COMMENT ON COLUMN "users" ."id" IS 'comment'`,
+						Cmd:     `COMMENT ON COLUMN "users"."id" IS ''`,
+						Reverse: `COMMENT ON COLUMN "users"."id" IS 'comment'`,
 					},
 				},
 			},
@@ -1293,7 +1293,7 @@ func TestPlanChanges(t *testing.T) {
 				Transactional: true,
 				Changes: []*migrate.Change{
 					{Cmd: `ALTER TYPE "public"."state" ADD VALUE 'unknown'`},
-					{Cmd: `COMMENT ON COLUMN "public"."users" ."state" IS ''`, Reverse: `COMMENT ON COLUMN "public"."users" ."state" IS 'foo'`},
+					{Cmd: `COMMENT ON COLUMN "public"."users"."state" IS ''`, Reverse: `COMMENT ON COLUMN "public"."users"."state" IS 'foo'`},
 				},
 			},
 		},
@@ -1323,7 +1323,7 @@ func TestPlanChanges(t *testing.T) {
 				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
-					{Cmd: `COMMENT ON COLUMN "users" ."state" IS 'foo'`, Reverse: `COMMENT ON COLUMN "users" ."state" IS 'bar'`},
+					{Cmd: `COMMENT ON COLUMN "users"."state" IS 'foo'`, Reverse: `COMMENT ON COLUMN "users"."state" IS 'bar'`},
 				},
 			},
 		},
Repository: ariga/atlas
Pull request #2855 (2024-06-11 12:45:55)

schemahcl: add support for regexpescape hcl function


Patch:
diff --git a/schemahcl/stdlib.go b/schemahcl/stdlib.go
index cfccd017..10f3f5b7 100644
--- a/schemahcl/stdlib.go
+++ b/schemahcl/stdlib.go
@@ -10,6 +10,7 @@ import (
 	"net/url"
 	"os"
 	"path/filepath"
+	"regexp"
 	"strconv"
 	"strings"
 
@@ -141,6 +142,7 @@ func stdFuncs() map[string]function.Function {
 		"range":           stdlib.RangeFunc,
 		"regex":           stdlib.RegexFunc,
 		"regexall":        stdlib.RegexAllFunc,
+		"regexpescape":    regexpEscape,
 		"regexreplace":    stdlib.RegexReplaceFunc,
 		"reverse":         stdlib.ReverseListFunc,
 		"setintersection": stdlib.SetIntersectionFunc,
@@ -442,6 +444,20 @@ var (
 			return cty.False, nil
 		},
 	})
+
+	regexpEscape = function.New(&function.Spec{
+		Description: `Return a string that escapes all regular expression metacharacters in the provided text.`,
+		Params: []function.Parameter{
+			{
+				Name: "str",
+				Type: cty.String,
+			},
+		},
+		Type: function.StaticReturnType(cty.String),
+		Impl: func(args []cty.Value, _ cty.Type) (ret cty.Value, err error) {
+			return cty.StringVal(regexp.QuoteMeta(args[0].AsString())), nil
+		},
+	})
 )
 
 // MakeFileFunc returns a function that reads a file


Test patch:
diff --git a/schemahcl/stdlib_test.go b/schemahcl/stdlib_test.go
index 2386efa1..ff74d76d 100644
--- a/schemahcl/stdlib_test.go
+++ b/schemahcl/stdlib_test.go
@@ -156,6 +156,15 @@ func TestEndsWithFunc(t *testing.T) {
 	require.Equal(t, cty.True, got)
 }
 
+func TestRegexpEscapeFunc(t *testing.T) {
+	got, err := regexpEscape.Call([]cty.Value{cty.StringVal("a|b|c")})
+	require.NoError(t, err)
+	require.Equal(t, "a\\|b\\|c", got.AsString())
+	got, err = regexpEscape.Call([]cty.Value{cty.StringVal("abc")})
+	require.NoError(t, err)
+	require.Equal(t, "abc", got.AsString())
+}
+
 func TestMakeFileFunc(t *testing.T) {
 	fn := MakeFileFunc("testdata")
 	_, err := fn.Call([]cty.Value{cty.StringVal("foo")})
@@ -168,6 +177,36 @@ func TestMakeFileFunc(t *testing.T) {
 	require.Equal(t, "person \"rotemtam\" {\n  hobby = var.hobby\n}", v.AsString())
 }
 
+func Example_RegexpEscapeFunc() {
+	for _, f := range []string{
+		`v  = regexpescape("a|b|c")`,
+		`v  = regexpescape("abc")`,
+		`v = regexpescape(<<TAB
+id | name
+---+-----
+1  | foo
+TAB
+)`,
+	} {
+		var d struct {
+			V string `spec:"v"`
+		}
+		if err := New().EvalBytes([]byte(f), &d, nil); err != nil {
+			fmt.Println("failed to evaluate:", err)
+			return
+		}
+		fmt.Printf("%s\n\n", d.V)
+	}
+	// Output:
+	// a\|b\|c
+	//
+	// abc
+	//
+	// id \| name
+	// ---\+-----
+	// 1  \| foo
+}
+
 func Example_PrintFunc() {
 	for _, f := range []string{
 		`v  = print("a")`,
Repository: ariga/atlas
Pull request #2881 (2024-06-19 13:42:11)

schemahcl: fix blockdef varibale schema


Patch:
diff --git a/schemahcl/context.go b/schemahcl/context.go
index 612af9ec..5790ea9f 100644
--- a/schemahcl/context.go
+++ b/schemahcl/context.go
@@ -443,11 +443,14 @@ func (t *blockDef) addChild(blk *hclsyntax.Block, depth int) {
 	if t.children == nil {
 		t.children = make(map[string]*blockDef)
 	}
-	c := &blockDef{
-		name:   blk.Type,
-		parent: t,
+	c, ok := t.children[blk.Type]
+	if !ok {
+		c = &blockDef{
+			name:   blk.Type,
+			parent: t,
+		}
+		t.children[blk.Type] = c
 	}
-	t.children[blk.Type] = c
 	// Limit depth of children to two as we do not have any
 	// case for more than this atm. e.g., table.column.
 	if depth < 2 {


Test patch:
diff --git a/schemahcl/schemahcl_test.go b/schemahcl/schemahcl_test.go
index da49632a..f0c11629 100644
--- a/schemahcl/schemahcl_test.go
+++ b/schemahcl/schemahcl_test.go
@@ -1104,3 +1104,46 @@ baz = 1
 	require.Equal(t, 4, rs[1].Children[0].Range().Start.Line)
 	require.Equal(t, 5, rs[1].Children[0].Attrs[0].Range().Start.Line)
 }
+
+func TestExtendedBlockDef(t *testing.T) {
+	var (
+		doc struct {
+			DefaultExtension
+		}
+		b = []byte(`
+schema "public" {}
+table "users" {}
+materialized "users_view2" {
+  schema = schema.public
+  as = "SELECT * FROM script_matview_inspect.users"
+  column "id" {
+    null = false
+  }
+  column "a" {
+    null = false
+  }
+  column "b" {
+    null = false
+  }
+  primary_key {
+    columns = [column.id]
+  }
+  populate = true
+}
+materialized "users_view" {
+  schema = schema.public
+  to = table.users
+  as = "SELECT * FROM script_matview_inspect.users"
+  index "i" {
+    on {
+      expr = "a"
+    }
+  }
+  primary_key {
+    using = index.i # Not a real syntax.
+  }
+}
+`)
+	)
+	require.NoError(t, New().EvalBytes(b, &doc, nil))
+}