Repository: ariga/atlas
Pull request #777 (2022-05-07 09:35:51)

all: minor bug fixes before release


Patch:
diff --git a/cmd/atlas/main.go b/cmd/atlas/main.go
index 24643320..aa1636ba 100644
--- a/cmd/atlas/main.go
+++ b/cmd/atlas/main.go
@@ -20,13 +20,10 @@ func main() {
 	ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
 	atlascmd.Root.SetOut(os.Stdout)
 	err := atlascmd.Root.ExecuteContext(ctx)
-	// Print error from command
 	if err != nil {
 		atlascmd.Root.PrintErrln("Error:", err)
 	}
-	// Check for update.
 	atlascmd.CheckForUpdate()
-	// Exit code according to command success.
 	if err != nil {
 		os.Exit(1)
 	}
diff --git a/cmd/atlascmd/atlascmd.go b/cmd/atlascmd/atlascmd.go
index 6be07309..f45b84c3 100644
--- a/cmd/atlascmd/atlascmd.go
+++ b/cmd/atlascmd/atlascmd.go
@@ -38,8 +38,8 @@ var (
 		},
 	}
 
-	// envCmd represents the subcommand 'atlas env'.
-	envCmd = &cobra.Command{
+	// Env represents the subcommand 'atlas env'.
+	Env = &cobra.Command{
 		Use:   "env",
 		Short: "Print atlas environment variables.",
 		Long: `'atlas env' prints atlas environment information.
@@ -61,13 +61,13 @@ List of supported environment parameters:
 	}
 )
 
-// CheckForUpdate exposes internal update logic to CLI
+// CheckForUpdate exposes internal update logic to CLI.
 func CheckForUpdate() {
-	update.CheckForUpdate(version, Root.PrintErrln)
+	update.Check(version, Root.PrintErrln)
 }
 
 func init() {
-	Root.AddCommand(envCmd)
+	Root.AddCommand(Env)
 	Root.AddCommand(schemaCmd)
 	Root.AddCommand(versionCmd)
 }
diff --git a/cmd/atlascmd/update/update.go b/cmd/atlascmd/update/update.go
index da467db8..89da4546 100644
--- a/cmd/atlascmd/update/update.go
+++ b/cmd/atlascmd/update/update.go
@@ -37,12 +37,12 @@ const (
 	AtlasNoUpdateNotifier = "ATLAS_NO_UPDATE_NOTIFIER"
 )
 
-// CheckForUpdate implements a notification to the user when a later release is available
+// Check implements a notification to the user when a later release is available
 // 1. Check release file ~/.atlas/release.json for latest known release and poll time
 // 2. If last poll was more than 24h, poll GitHub public API https://docs.github.com/en/rest/reference/releases#get-the-latest-release
 // 3. Store the latest release metadata
 // 4. If current build Version, that is not development, is lower than the latest release, notify user
-func CheckForUpdate(version string, logF func(i ...interface{})) {
+func Check(version string, logF func(i ...interface{})) {
 	if !enabled(version) {
 		return
 	}
diff --git a/doc/md/ddl/sql.mdx b/doc/md/ddl/sql.mdx
index c4e8e861..91eb387e 100644
--- a/doc/md/ddl/sql.mdx
+++ b/doc/md/ddl/sql.mdx
@@ -157,7 +157,7 @@ table "metrics" {
       column = column.x
     }
     by {
-      expr = "f(y)"
+      expr = "floor(y)"
     }
   }
 }
diff --git a/sql/internal/sqlx/sqlx.go b/sql/internal/sqlx/sqlx.go
index 2b3824c6..a11e906b 100644
--- a/sql/internal/sqlx/sqlx.go
+++ b/sql/internal/sqlx/sqlx.go
@@ -386,7 +386,7 @@ func DefaultValue(c *schema.Column) (string, bool) {
 // Used by the different drivers to turn strings valid expressions.
 func MayWrap(s string) string {
 	n := len(s) - 1
-	if len(s) < 2 || s[0] != '(' || s[n] != ')' || !balanced(s) {
+	if len(s) < 2 || s[0] != '(' || s[n] != ')' || !balanced(s[1:n]) {
 		return "(" + s + ")"
 	}
 	return s
@@ -396,17 +396,17 @@ func balanced(expr string) bool {
 	return ExprLastIndex(expr) == len(expr)-1
 }
 
-// ExprLastIndex scans the expression string (wrapped with parens)
-// and returns its last. e.g. "(a+1), c int ...".
+// ExprLastIndex scans the first expression in the given string until
+// its end and returns its last index.
 func ExprLastIndex(expr string) int {
-	var r, l int
+	var l, r int
 	for i := 0; i < len(expr); i++ {
 	Top:
 		switch expr[i] {
 		case '(':
-			r++
-		case ')':
 			l++
+		case ')':
+			r++
 		// String or identifier.
 		case '\'', '"', '`':
 			for j := i + 1; j < len(expr); j++ {
@@ -421,9 +421,11 @@ func ExprLastIndex(expr string) int {
 			// Unexpected EOS.
 			return -1
 		}
-		// Balanced parens.
-		if r == l {
+		// Balanced parens and we reached EOS or a terminator.
+		if l == r && (i == len(expr)-1 || expr[i+1] == ',') {
 			return i
+		} else if r > l {
+			return -1
 		}
 	}
 	return -1


Test patch:
diff --git a/internal/integration/testdata/postgres/table-partition.txt b/internal/integration/testdata/postgres/table-partition.txt
index 6150e570..d1b219ab 100644
--- a/internal/integration/testdata/postgres/table-partition.txt
+++ b/internal/integration/testdata/postgres/table-partition.txt
@@ -16,6 +16,14 @@ apply drop.hcl
 
 apply 3.hcl
 cmpshow measurement 3.sql
+cmphcl 3.inspect.hcl
+
+# Drop all tables.
+apply drop.hcl
+
+apply 4.hcl
+cmpshow metrics 4.sql
+cmphcl 4.inspect.hcl
 
 -- 1.hcl --
 schema "$db" {}
@@ -165,5 +173,118 @@ Partitioned table "script_table_partition.measurement"
 Partition key: RANGE (logdate)
 Number of partitions: 0
 
+-- 3.inspect.hcl --
+table "measurement" {
+  schema = schema.script_table_partition
+  column "city_id" {
+    null = false
+    type = integer
+  }
+  column "logdate" {
+    null = false
+    type = date
+  }
+  column "peaktemp" {
+    null = true
+    type = integer
+  }
+  column "unitsales" {
+    null = true
+    type = integer
+  }
+  partition {
+    type    = RANGE
+    columns = [column.logdate]
+  }
+}
+schema "script_table_partition" {
+}
+
+-- 4.hcl --
+schema "$db" {}
+
+table "metrics" {
+  schema = schema.$db
+  column "x" {
+    null = false
+    type = integer
+  }
+  column "y" {
+    null = false
+    type = integer
+  }
+  partition {
+    type = RANGE
+    by {
+        column = column.x
+    }
+    by {
+        expr = "floor(x)"
+    }
+    by {
+        expr = "y * 2"
+    }
+    by {
+        expr = "floor(y)"
+    }
+  }
+}
+
+-- postgres10/4.sql --
+      Table "script_table_partition.metrics"
+ Column |  Type   | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ x      | integer |           | not null |
+ y      | integer |           | not null |
+Partition key: RANGE (x, floor((x)::double precision), ((y * 2)), floor((y)::double precision))
+
+-- postgres11/4.sql --
+      Table "script_table_partition.metrics"
+ Column |  Type   | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ x      | integer |           | not null |
+ y      | integer |           | not null |
+Partition key: RANGE (x, floor((x)::double precision), ((y * 2)), floor((y)::double precision))
+Number of partitions: 0
+
+-- 4.sql --
+Partitioned table "script_table_partition.metrics"
+ Column |  Type   | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ x      | integer |           | not null |
+ y      | integer |           | not null |
+Partition key: RANGE (x, floor((x)::double precision), ((y * 2)), floor((y)::double precision))
+Number of partitions: 0
+
+-- 4.inspect.hcl --
+table "metrics" {
+  schema = schema.script_table_partition
+  column "x" {
+    null = false
+    type = integer
+  }
+  column "y" {
+    null = false
+    type = integer
+  }
+  partition {
+    type = RANGE
+    by {
+      column = column.x
+    }
+    by {
+      expr = "floor((x)::double precision)"
+    }
+    by {
+      expr = "(y * 2)"
+    }
+    by {
+      expr = "floor((y)::double precision)"
+    }
+  }
+}
+schema "script_table_partition" {
+}
+
 -- drop.hcl --
 schema "$db" {}
\ No newline at end of file
diff --git a/sql/internal/sqlx/sqlx_test.go b/sql/internal/sqlx/sqlx_test.go
index b839d112..fe27f04f 100644
--- a/sql/internal/sqlx/sqlx_test.go
+++ b/sql/internal/sqlx/sqlx_test.go
@@ -80,9 +80,9 @@ func TestMayWrap(t *testing.T) {
 		{"b || 'x'", true},
 		{"a+1", true},
 		{"substr(x, 2)", true},
-		{"(json_extract(x, '$.a'), json_extract(x, '$.b'))", false},
-		{"(substr(a, 2) COLLATE NOCASE, b)", false},
-		{"(a,b+random())", false},
+		{"(json_extract(x, '$.a'))", false},
+		{"(substr(a, 2) COLLATE NOCASE)", false},
+		{"(b+random())", false},
 	}
 	for i, tt := range tests {
 		t.Run(strconv.Itoa(i), func(t *testing.T) {
@@ -95,3 +95,30 @@ func TestMayWrap(t *testing.T) {
 		})
 	}
 }
+
+func TestExprLastIndex(t *testing.T) {
+	tests := []struct {
+		input   string
+		wantIdx int
+	}{
+		{"", -1},
+		{"()", 1},
+		{"'('", 2},
+		{"('(')", 4},
+		{"('text')", 7},
+		{"floor(x), y", 7},
+		{"f(floor(x), y)", 13},
+		{"f(floor(x), y, (z))", 18},
+		{"f(x, (x*2)), y, (z)", 10},
+		{"(a || ' ' || b)", 14},
+		{"(a || ', ' || b)", 15},
+		{"a || ', ' || b, x", 13},
+		{"(a || ', ' || b), x", 15},
+	}
+	for i, tt := range tests {
+		t.Run(strconv.Itoa(i), func(t *testing.T) {
+			idx := ExprLastIndex(tt.input)
+			require.Equal(t, tt.wantIdx, idx)
+		})
+	}
+}