Repository: ariga/atlas
Pull request #820 (2022-05-23 15:34:53)

Revert "Revert "sql/cockroach: make postgres inspect compatible with crdb""
Reverts ariga/atlas#819


Patch:
diff --git a/sql/postgres/driver.go b/sql/postgres/driver.go
index 93b68670..3cfa2864 100644
--- a/sql/postgres/driver.go
+++ b/sql/postgres/driver.go
@@ -36,6 +36,7 @@ type (
 		collate string
 		ctype   string
 		version string
+		crdb    bool
 	}
 )
 
@@ -65,8 +66,14 @@ func OpenCRDB(db schema.ExecQuerier) (migrate.Driver, error) {
 	if err != nil {
 		return nil, err
 	}
-	drv.Differ = &sqlx.Diff{DiffDriver: &crdbDiff{diff{drv.conn}}}
-	return drv, nil
+	c := drv.conn
+	c.crdb = true
+	return &Driver{
+		conn:        c,
+		Differ:      &sqlx.Diff{DiffDriver: &crdbDiff{diff{c}}},
+		Inspector:   &inspect{c},
+		PlanApplier: &planApply{c},
+	}, nil
 }
 
 // Open opens a new PostgreSQL driver.
@@ -199,23 +206,25 @@ const (
 	TypeSmallInt = "smallint"
 	TypeInteger  = "integer"
 	TypeBigInt   = "bigint"
-	TypeInt      = "int"  // integer.
-	TypeInt2     = "int2" // smallint.
-	TypeInt4     = "int4" // integer.
-	TypeInt8     = "int8" // bigint.
+	TypeInt      = "int"   // integer.
+	TypeInt2     = "int2"  // smallint.
+	TypeInt4     = "int4"  // integer.
+	TypeInt8     = "int8"  // bigint.
+	TypeInt64    = "int64" // cockroach bigint.
 
 	TypeCIDR     = "cidr"
 	TypeInet     = "inet"
 	TypeMACAddr  = "macaddr"
 	TypeMACAddr8 = "macaddr8"
 
-	TypeCircle  = "circle"
-	TypeLine    = "line"
-	TypeLseg    = "lseg"
-	TypeBox     = "box"
-	TypePath    = "path"
-	TypePolygon = "polygon"
-	TypePoint   = "point"
+	TypeCircle   = "circle"
+	TypeLine     = "line"
+	TypeLseg     = "lseg"
+	TypeBox      = "box"
+	TypePath     = "path"
+	TypePolygon  = "polygon"
+	TypePoint    = "point"
+	TypeGeometry = "geometry" // cockroach geometry.
 
 	TypeDate          = "date"
 	TypeTime          = "time"   // time without time zone
diff --git a/sql/postgres/inspect.go b/sql/postgres/inspect.go
index e88eccc5..7fb539b0 100644
--- a/sql/postgres/inspect.go
+++ b/sql/postgres/inspect.go
@@ -8,6 +8,7 @@ import (
 	"context"
 	"database/sql"
 	"fmt"
+	"regexp"
 	"strconv"
 	"strings"
 
@@ -224,7 +225,7 @@ func (i *inspect) addColumn(s *schema.Schema, rows *sql.Rows) error {
 func columnType(c *columnDesc) schema.Type {
 	var typ schema.Type
 	switch t := c.typ; strings.ToLower(t) {
-	case TypeBigInt, TypeInt8, TypeInt, TypeInteger, TypeInt4, TypeSmallInt, TypeInt2:
+	case TypeBigInt, TypeInt8, TypeInt, TypeInteger, TypeInt4, TypeSmallInt, TypeInt2, TypeInt64:
 		typ = &schema.IntegerType{T: t}
 	case TypeBit, TypeBitVar:
 		typ = &BitType{T: t, Len: c.size}
@@ -238,7 +239,7 @@ func columnType(c *columnDesc) schema.Type {
 		typ = &schema.StringType{T: t, Size: int(c.size)}
 	case TypeCIDR, TypeInet, TypeMACAddr, TypeMACAddr8:
 		typ = &NetworkType{T: t}
-	case TypeCircle, TypeLine, TypeLseg, TypeBox, TypePath, TypePolygon, TypePoint:
+	case TypeCircle, TypeLine, TypeLseg, TypeBox, TypePath, TypePolygon, TypePoint, TypeGeometry:
 		typ = &schema.SpatialType{T: t}
 	case TypeDate:
 		typ = &schema.TimeType{T: t}
@@ -331,8 +332,23 @@ func (i *inspect) enumValues(ctx context.Context, s *schema.Schema) error {
 	return nil
 }
 
+func (i *inspect) crdbIndexes(ctx context.Context, s *schema.Schema) error {
+	rows, err := i.querySchema(ctx, crdbIndexesQuery, s)
+	if err != nil {
+		return fmt.Errorf("postgres: querying schema %q indexes: %w", s.Name, err)
+	}
+	defer rows.Close()
+	if err := i.crdbAddIndexes(s, rows); err != nil {
+		return err
+	}
+	return rows.Err()
+}
+
 // indexes queries and appends the indexes of the given table.
 func (i *inspect) indexes(ctx context.Context, s *schema.Schema) error {
+	if i.conn.crdb {
+		return i.crdbIndexes(ctx, s)
+	}
 	rows, err := i.querySchema(ctx, indexesQuery, s)
 	if err != nil {
 		return fmt.Errorf("postgres: querying schema %q indexes: %w", s.Name, err)
@@ -344,6 +360,77 @@ func (i *inspect) indexes(ctx context.Context, s *schema.Schema) error {
 	return rows.Err()
 }
 
+var reIndexType = regexp.MustCompile("(?i)USING (BTREE|GIN|GIST)")
+
+// indexType extracts index type information from index create statement. see: https://www.cockroachlabs.com/docs/stable/create-index.html
+func indexType(createStmt string) *IndexType {
+	t := reIndexType.FindStringSubmatch(createStmt)
+	return &IndexType{T: t[1]}
+}
+
+func (i *inspect) crdbAddIndexes(s *schema.Schema, rows *sql.Rows) error {
+	// Unlike Postgres, Cockroach may have duplicate index names.
+	names := make(map[string]*schema.Index)
+	for rows.Next() {
+		var (
+			uniq, primary                        bool
+			table, name, createStmt              string
+			column, contype, pred, expr, comment sql.NullString
+		)
+		if err := rows.Scan(&table, &name, &column, &primary, &uniq, &contype, &createStmt, &pred, &expr, &comment); err != nil {
+			return fmt.Errorf("cockroach: scanning indexes for schema %q: %w", s.Name, err)
+		}
+		t, ok := s.Table(table)
+		if !ok {
+			return fmt.Errorf("table %q was not found in schema", table)
+		}
+		uniqueName := fmt.Sprintf("%s.%s", table, name)
+		idx, ok := names[uniqueName]
+		if !ok {
+			idx = &schema.Index{
+				Name:   name,
+				Unique: uniq,
+				Table:  t,
+				Attrs: []schema.Attr{
+					indexType(createStmt),
+				},
+			}
+			if sqlx.ValidString(comment) {
+				idx.Attrs = append(idx.Attrs, &schema.Comment{Text: comment.String})
+			}
+			if sqlx.ValidString(contype) {
+				idx.Attrs = append(idx.Attrs, &ConType{T: contype.String})
+			}
+			if sqlx.ValidString(pred) {
+				idx.Attrs = append(idx.Attrs, &IndexPredicate{P: pred.String})
+			}
+			names[uniqueName] = idx
+			if primary {
+				t.PrimaryKey = idx
+			} else {
+				t.Indexes = append(t.Indexes, idx)
+			}
+		}
+		part := &schema.IndexPart{SeqNo: len(idx.Parts) + 1, Desc: strings.Contains(createStmt, "DESC")}
+		switch {
+		case sqlx.ValidString(column):
+			part.C, ok = t.Column(column.String)
+			if !ok {
+				return fmt.Errorf("cockroach: column %q was not found for index %q", column.String, idx.Name)
+			}
+			part.C.Indexes = append(part.C.Indexes, idx)
+		case sqlx.ValidString(expr):
+			part.X = &schema.RawExpr{
+				X: expr.String,
+			}
+		default:
+			return fmt.Errorf("cockroach: invalid part for index %q", idx.Name)
+		}
+		idx.Parts = append(idx.Parts, part)
+	}
+	return nil
+}
+
 // addIndexes scans the rows and adds the indexes to the table.
 func (i *inspect) addIndexes(s *schema.Schema, rows *sql.Rows) error {
 	names := make(map[string]*schema.Index)
@@ -832,14 +919,15 @@ const (
 SELECT
 	t1.table_schema,
 	t1.table_name,
-	pg_catalog.obj_description(t2.oid, 'pg_class') AS comment,
-	t3.partattrs AS partition_attrs,
-	t3.partstrat AS partition_strategy,
-	pg_get_expr(t3.partexprs, t3.partrelid) AS partition_exprs
+	pg_catalog.obj_description(t3.oid, 'pg_class') AS comment,
+	t4.partattrs AS partition_attrs,
+	t4.partstrat AS partition_strategy,
+	pg_get_expr(t4.partexprs, t4.partrelid) AS partition_exprs
 FROM
 	INFORMATION_SCHEMA.TABLES AS t1
-	JOIN pg_catalog.pg_class AS t2 ON t2.oid = to_regclass(quote_ident(t1.table_schema) || '.' || quote_ident(t1.table_name))::oid
-	LEFT JOIN pg_catalog.pg_partitioned_table AS t3 ON t3.partrelid = t2.oid
+	JOIN pg_catalog.pg_namespace AS t2 ON t2.nspname = t1.table_schema
+	JOIN pg_catalog.pg_class AS t3 ON t3.relnamespace = t2.oid AND t3.relname = t1.table_name
+	LEFT JOIN pg_catalog.pg_partitioned_table AS t4 ON t4.partrelid = t3.oid
 WHERE
 	t1.table_type = 'BASE TABLE'
 	AND t1.table_schema IN (%s)
@@ -885,19 +973,55 @@ SELECT
 	t1.identity_increment,
 	t1.identity_generation,
 	t1.generation_expression,
-	col_description(to_regclass(quote_ident("table_schema") || '.' || quote_ident("table_name"))::oid, "ordinal_position") AS comment,
-	t2.typtype,
-	t2.oid
+	col_description(t3.oid, "ordinal_position") AS comment,
+	t4.typtype,
+	t4.oid
 FROM
 	"information_schema"."columns" AS t1
-	LEFT JOIN pg_catalog.pg_type AS t2
-	ON t1.udt_name = t2.typname
+	JOIN pg_catalog.pg_namespace AS t2 ON t2.nspname = t1.table_schema
+	JOIN pg_catalog.pg_class AS t3 ON t3.relnamespace = t2.oid AND t3.relname = t1.table_name
+	LEFT JOIN pg_catalog.pg_type AS t4
+	ON t1.udt_name = t4.typname
 WHERE
 	table_schema = $1 AND table_name IN (%s)
 ORDER BY
 	t1.table_name, t1.ordinal_position
 `
 
+	crdbIndexesQuery = `
+SELECT
+	t.relname AS table_name,
+	i.relname AS index_name,
+	a.attname AS column_name,
+	idx.indisprimary AS primary,
+	idx.indisunique AS unique,
+	c.contype AS constraint_type,
+	pgi.indexdef create_stmt,
+	pg_get_expr(idx.indpred, idx.indrelid) AS predicate,
+	pg_get_indexdef(idx.indexrelid, idx.ord, false) AS expression,
+	pg_catalog.obj_description(i.oid, 'pg_class') AS comment
+	FROM
+	(
+		select
+			*,
+			generate_series(1,array_length(i.indkey,1)) as ord,
+			unnest(i.indkey) AS key
+		from pg_index i
+	) idx
+	JOIN pg_class i ON i.oid = idx.indexrelid
+	JOIN pg_class t ON t.oid = idx.indrelid
+	JOIN pg_namespace n ON n.oid = t.relnamespace
+	LEFT JOIN pg_constraint c ON idx.indexrelid = c.conindid
+	LEFT JOIN pg_indexes pgi ON pgi.tablename = t.relname AND indexname = i.relname
+	LEFT JOIN pg_attribute a ON (a.attrelid, a.attnum) = (idx.indrelid, idx.key)
+WHERE
+	n.nspname = $1
+	AND t.relname IN (%s)
+	AND COALESCE(c.contype, '') <> 'f'
+ORDER BY
+	table_name, index_name, idx.ord
+`
+
 	// Query to list table indexes.
 	indexesQuery = `
 SELECT
@@ -972,7 +1096,7 @@ ORDER BY
 SELECT
 	rel.relname AS table_name,
 	t1.conname AS constraint_name,
-	pg_get_expr(t1.conbin, to_regclass(quote_ident(nsp.nspname) || '.' || quote_ident(rel.relname))::oid) as expression,
+	pg_get_expr(t1.conbin, t1.conrelid) as expression,
 	t2.attname as column_name,
 	t1.conkey as column_indexes,
 	t1.connoinherit as no_inherit


Test patch:
diff --git a/internal/integration/mysql_test.go b/internal/integration/mysql_test.go
index 7e14f632..a65c58fc 100644
--- a/internal/integration/mysql_test.go
+++ b/internal/integration/mysql_test.go
@@ -387,6 +387,26 @@ func TestMySQL_ColumnBool(t *testing.T) {
 	})
 }
 
+func TestMySQL_ColumnCheck(t *testing.T) {
+	myRun(t, func(t *myTest) {
+		// Checks are not supported in all versions.
+		if t.version == "mysql56" || t.version == "mysql57" {
+			t.Skip()
+		}
+		usersT := &schema.Table{
+			Name:  "users",
+			Attrs: []schema.Attr{schema.NewCheck().SetName("users_c_check").SetExpr("c > 5")},
+			Columns: []*schema.Column{
+				{Name: "id", Type: &schema.ColumnType{Type: &schema.IntegerType{T: "int"}}},
+				{Name: "c", Type: &schema.ColumnType{Type: &schema.IntegerType{T: "int"}}},
+			},
+		}
+		t.dropTables(usersT.Name)
+		t.migrate(&schema.AddTable{T: usersT})
+		ensureNoChange(t, usersT)
+	})
+}
+
 func TestMySQL_ForeignKey(t *testing.T) {
 	t.Run("ChangeAction", func(t *testing.T) {
 		myRun(t, func(t *myTest) {
diff --git a/internal/integration/postgres_test.go b/internal/integration/postgres_test.go
index 1fa62434..c9143a00 100644
--- a/internal/integration/postgres_test.go
+++ b/internal/integration/postgres_test.go
@@ -156,6 +156,22 @@ func TestPostgres_AddIndexedColumns(t *testing.T) {
 	})
 }
 
+func TestPostgres_ColumnCheck(t *testing.T) {
+	pgRun(t, func(t *pgTest) {
+		usersT := &schema.Table{
+			Name:  "users",
+			Attrs: []schema.Attr{schema.NewCheck().SetName("users_c_check").SetExpr("c > 5")},
+			Columns: []*schema.Column{
+				{Name: "id", Type: &schema.ColumnType{Type: &schema.IntegerType{T: "int"}}},
+				{Name: "c", Type: &schema.ColumnType{Type: &schema.IntegerType{T: "int"}}},
+			},
+		}
+		t.dropTables(usersT.Name)
+		t.migrate(&schema.AddTable{T: usersT})
+		ensureNoChange(t, usersT)
+	})
+}
+
 func TestPostgres_AddColumns(t *testing.T) {
 	pgRun(t, func(t *pgTest) {
 		usersT := t.users()
diff --git a/internal/integration/sqlite_test.go b/internal/integration/sqlite_test.go
index d84cd247..8740dfbb 100644
--- a/internal/integration/sqlite_test.go
+++ b/internal/integration/sqlite_test.go
@@ -71,6 +71,22 @@ func TestSQLite_Ent(t *testing.T) {
 	})
 }
 
+func TestSQLite_ColumnCheck(t *testing.T) {
+	liteRun(t, func(t *liteTest) {
+		usersT := &schema.Table{
+			Name:  "users",
+			Attrs: []schema.Attr{schema.NewCheck().SetName("users_c_check").SetExpr("c > 5")},
+			Columns: []*schema.Column{
+				{Name: "id", Type: &schema.ColumnType{Type: &schema.IntegerType{T: "int"}}},
+				{Name: "c", Type: &schema.ColumnType{Type: &schema.IntegerType{T: "int"}}},
+			},
+		}
+		t.dropTables(usersT.Name)
+		t.migrate(&schema.AddTable{T: usersT})
+		ensureNoChange(t, usersT)
+	})
+}
+
 func TestSQLite_AddIndexedColumns(t *testing.T) {
 	liteRun(t, func(t *liteTest) {
 		usersT := &schema.Table{
diff --git a/sql/postgres/inspect_test.go b/sql/postgres/inspect_test.go
index 0b04e68a..7e7bc600 100644
--- a/sql/postgres/inspect_test.go
+++ b/sql/postgres/inspect_test.go
@@ -10,6 +10,7 @@ import (
 	"testing"
 
 	"ariga.io/atlas/sql/internal/sqltest"
+	"ariga.io/atlas/sql/migrate"
 	"ariga.io/atlas/sql/schema"
 
 	"github.com/DATA-DOG/go-sqlmock"
@@ -18,16 +19,18 @@ import (
 
 // Single table queries used by the different tests.
 var (
-	queryFKs     = sqltest.Escape(fmt.Sprintf(fksQuery, "$2"))
-	queryTables  = sqltest.Escape(fmt.Sprintf(tablesQuery, "$1"))
-	queryChecks  = sqltest.Escape(fmt.Sprintf(checksQuery, "$2"))
-	queryColumns = sqltest.Escape(fmt.Sprintf(columnsQuery, "$2"))
-	queryIndexes = sqltest.Escape(fmt.Sprintf(indexesQuery, "$2"))
+	queryFKs         = sqltest.Escape(fmt.Sprintf(fksQuery, "$2"))
+	queryTables      = sqltest.Escape(fmt.Sprintf(tablesQuery, "$1"))
+	queryChecks      = sqltest.Escape(fmt.Sprintf(checksQuery, "$2"))
+	queryColumns     = sqltest.Escape(fmt.Sprintf(columnsQuery, "$2"))
+	queryIndexes     = sqltest.Escape(fmt.Sprintf(indexesQuery, "$2"))
+	queryCrdbIndexes = sqltest.Escape(fmt.Sprintf(crdbIndexesQuery, "$2"))
 )
 
 func TestDriver_InspectTable(t *testing.T) {
 	tests := []struct {
 		name   string
+		crdb   bool
 		before func(mock)
 		expect func(*require.Assertions, *schema.Table, error)
 	}{
@@ -182,6 +185,61 @@ users           | idx6            | brin        | c1          | f       | t
 				require.EqualValues(pk, t.PrimaryKey)
 			},
 		},
+		{
+			name: "crdb table indexes",
+			crdb: true,
+			before: func(m mock) {
+				m.tableExists("public", "users", true)
+				m.ExpectQuery(queryColumns).
+					WithArgs("public", "users").
+					WillReturnRows(sqltest.Rows(`
+table_name  | column_name | data_type | is_nullable |              column_default               | character_maximum_length | numeric_precision | datetime_precision | numeric_scale | character_set_name | collation_name | udt_name | is_identity | identity_start | identity_increment |       identity_generation        | generation_expression | comment | typtype | oid 
+------------+-------------+-----------+-------------+-------------------------------------------+--------------------------+-------------------+--------------------+---------------+--------------------+----------------+----------+-------------+----------------+--------------------+----------------------------------+-----------------------+---------+---------+-----
+users       | a           | bigint    | NO          |                                           |                          |                64 |                    |             0 |                    |                | int8     | NO          |                |                    |                                  |                       |         | b       | 20 
+users       | b           | bigint    | NO          |                                           |                          |                64 |                    |             0 |                    |                | int8     | NO          |                |                    |                                  |                       |         | b       | 20 
+users       | c           | bigint    | NO          |                                           |                          |                64 |                    |             0 |                    |                | int8     | NO          |                |                    |                                  |                       |         | b       | 20 
+users       | d           | bigint    | NO          |                                           |                          |                64 |                    |             0 |                    |                | int8     | NO          |                |                    |                                  |                       |         | b       | 20 
+`))
+				m.ExpectQuery(queryCrdbIndexes).
+					WithArgs("public", "users").
+					WillReturnRows(sqltest.Rows(`
+table_name  | index_name | column_name | primary | unique | constraint_type |                                   create_stmt                                   | predicate | expression | comment 
+------------+------------+-------------+---------+--------+-----------------+---------------------------------------------------------------------------------+-----------+------------+---------
+users       | idx1       | a           | false   | false  |                 | CREATE INDEX idx1 ON defaultdb.public.serial USING btree (a ASC)                |           | a          |  
+users       | idx2       | b           | false   | true   | u               | CREATE UNIQUE INDEX idx2 ON defaultdb.public.serial USING btree (b ASC)         |           | b          |  
+users       | idx3       | c           | false   | false  |                 | CREATE INDEX idx3 ON defaultdb.public.serial USING btree (c DESC)               |           | c          | boring 
+users       | idx4       | d           | false   | false  |                 | CREATE INDEX idx5 ON defaultdb.public.serial USING btree (d ASC) WHERE (d < 10) | d < 10    | d          |  
+users       | idx5       | a           | false   | false  |                 | CREATE INDEX idx5 ON defaultdb.public.serial USING btree (a ASC, b ASC, c ASC)  |           | a          |  
+users       | idx5       | b           | false   | false  |                 | CREATE INDEX idx5 ON defaultdb.public.serial USING btree (a ASC, b ASC, c ASC)  |           | b          |  
+users       | idx5       | c           | false   | false  |                 | CREATE INDEX idx5 ON defaultdb.public.serial USING btree (a ASC, b ASC, c ASC)  |           | c          |  
+`))
+				m.noFKs()
+				m.noChecks()
+			},
+			expect: func(require *require.Assertions, t *schema.Table, err error) {
+				require.NoError(err)
+				require.Equal("users", t.Name)
+				columns := []*schema.Column{
+					{Name: "a", Type: &schema.ColumnType{Raw: "bigint", Type: &schema.IntegerType{T: "bigint"}}},
+					{Name: "b", Type: &schema.ColumnType{Raw: "bigint", Type: &schema.IntegerType{T: "bigint"}}},
+					{Name: "c", Type: &schema.ColumnType{Raw: "bigint", Type: &schema.IntegerType{T: "bigint"}}},
+					{Name: "d", Type: &schema.ColumnType{Raw: "bigint", Type: &schema.IntegerType{T: "bigint"}}},
+				}
+				indexes := []*schema.Index{
+					{Name: "idx1", Table: t, Attrs: []schema.Attr{&IndexType{T: "btree"}}, Parts: []*schema.IndexPart{{SeqNo: 1, C: columns[0]}}},
+					{Name: "idx2", Unique: true, Table: t, Attrs: []schema.Attr{&IndexType{T: "btree"}, &ConType{T: "u"}}, Parts: []*schema.IndexPart{{SeqNo: 1, C: columns[1]}}},
+					{Name: "idx3", Table: t, Attrs: []schema.Attr{&IndexType{T: "btree"}, &schema.Comment{Text: "boring"}}, Parts: []*schema.IndexPart{{SeqNo: 1, C: columns[2], Desc: true}}},
+					{Name: "idx4", Table: t, Attrs: []schema.Attr{&IndexType{T: "btree"}, &IndexPredicate{P: `d < 10`}}, Parts: []*schema.IndexPart{{SeqNo: 1, C: columns[3]}}},
+					{Name: "idx5", Table: t, Attrs: []schema.Attr{&IndexType{T: "btree"}}, Parts: []*schema.IndexPart{{SeqNo: 1, C: columns[0]}, {SeqNo: 2, C: columns[1]}, {SeqNo: 3, C: columns[2]}}},
+				}
+				columns[0].Indexes = []*schema.Index{indexes[0], indexes[4]}
+				columns[1].Indexes = []*schema.Index{indexes[1], indexes[4]}
+				columns[2].Indexes = []*schema.Index{indexes[2], indexes[4]}
+				columns[3].Indexes = []*schema.Index{indexes[3]}
+				require.EqualValues(columns, t.Columns)
+				require.EqualValues(indexes, t.Indexes)
+			},
+		},
 		{
 			name: "fks",
 			before: func(m mock) {
@@ -285,7 +343,12 @@ users        | users_check1       | (((c2 + c1) + c3) > 10) | c3          | {2,1
 			require.NoError(t, err)
 			mk := mock{m}
 			mk.version("130000")
-			drv, err := Open(db)
+			var drv migrate.Driver
+			if tt.crdb {
+				drv, err = OpenCRDB(db)
+			} else {
+				drv, err = Open(db)
+			}
 			require.NoError(t, err)
 			mk.ExpectQuery(sqltest.Escape(fmt.Sprintf(schemasQueryArgs, "= $1"))).
 				WithArgs("public").