diff --git a/go.mod b/go.mod index 092b47d..196ff2b 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/ClickHouse/clickhouse-go/v2 v2.40.3 github.com/google/go-cmp v0.7.0 github.com/hashicorp/hcl/v2 v2.24.0 - github.com/orian/clickhouse-sql-parser v0.0.0-20260813141301-768a69c3d95a + github.com/orian/clickhouse-sql-parser v1.0.1 github.com/pmezard/go-difflib v1.0.0 github.com/rs/zerolog v1.34.0 github.com/stretchr/testify v1.11.1 diff --git a/go.sum b/go.sum index 9392602..0b19611 100644 --- a/go.sum +++ b/go.sum @@ -54,8 +54,8 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/orian/clickhouse-sql-parser v0.0.0-20260813141301-768a69c3d95a h1:pEpE55SB1MrdR7O8Vow1zDmUeAJxKQ1PeRVNyXS5L4g= -github.com/orian/clickhouse-sql-parser v0.0.0-20260813141301-768a69c3d95a/go.mod h1:22D/qoWV/Ipbn5Gcub+0ttta1wgzSDaJMprQRZWghoE= +github.com/orian/clickhouse-sql-parser v1.0.1 h1:jxNed69Vax3V3xTwBLz0nAiNPHL2qtlLThpoK1bdOIQ= +github.com/orian/clickhouse-sql-parser v1.0.1/go.mod h1:22D/qoWV/Ipbn5Gcub+0ttta1wgzSDaJMprQRZWghoE= github.com/paulmach/orb v0.12.0 h1:z+zOwjmG3MyEEqzv92UN49Lg1JFYx0L9GpGKNVDKk1s= github.com/paulmach/orb v0.12.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= diff --git a/internal/loader/hcl/introspect.go b/internal/loader/hcl/introspect.go index eade02b..4702658 100644 --- a/internal/loader/hcl/introspect.go +++ b/internal/loader/hcl/introspect.go @@ -225,13 +225,7 @@ func upsertView(db *DatabaseSpec, v ViewSpec) { // Parser panics become errors so introspection can use its normal -allow-raw // fallback instead of terminating the process. func parseCreateStatement(createSQL string) (chparser.Expr, error) { - stmts, err := safeParseStmts(createSQL) - if err != nil { - normalized := stripDefaultTimeSeriesTargetShorthand(createSQL) - if normalized != createSQL { - stmts, err = safeParseStmts(normalized) - } - } + stmts, err := safeParseStmts(stripDefaultTimeSeriesTargetShorthand(createSQL)) if err != nil { return nil, fmt.Errorf("parser: %w", err) } @@ -253,9 +247,9 @@ var defaultTimeSeriesTargetShorthandRE = regexp.MustCompile( // table. The schema model already represents these exact auto-generated // targets as nil, so retaining the suffix would create a permanent diff. // -// The SQL parser supports the documented INNER COLUMNS / INNER ENGINE form, -// but not this newer server spelling (orian/clickhouse-sql-parser#24). The -// matcher is suffix-anchored and requires every default clause, so custom +// Normalize before parsing even when the parser supports this shorthand: +// these defaults must still map to nil targets in the schema (issue #246). +// The matcher is suffix-anchored and requires every default clause, so custom // target definitions are never discarded. func stripDefaultTimeSeriesTargetShorthand(createSQL string) string { if !strings.Contains(createSQL, "ENGINE = TimeSeries") { diff --git a/internal/loader/hcl/timeseries_tags_escape_test.go b/internal/loader/hcl/timeseries_tags_escape_test.go index f8d89cc..88d19f8 100644 --- a/internal/loader/hcl/timeseries_tags_escape_test.go +++ b/internal/loader/hcl/timeseries_tags_escape_test.go @@ -1,6 +1,7 @@ package hcl import ( + "strings" "testing" "github.com/stretchr/testify/assert" @@ -40,6 +41,23 @@ ORDER BY metric_family_name` engine, ok := db.Tables[0].Engine.Decoded.(EngineTimeSeries) require.True(t, ok) assert.Equal(t, map[string]string{"foo'bar": "foo_bar"}, engine.TagsToColumns) + assert.Nil(t, engine.Samples) + assert.Nil(t, engine.Tags) + assert.Nil(t, engine.Metrics) + + // SHOW CREATE's default targets must not introduce drift from the same + // table declared without explicit targets, regardless of parser support. + bareSQL := strings.Split(sql, " DATA\n")[0] + bare := &DatabaseSpec{Name: "default"} + require.NoError(t, processIntrospectRows(bare, "default", &fakeRows{rows: []fakeRow{{name: "m", sql: bareSQL}}})) + changes := Diff(&Schema{Databases: []DatabaseSpec{*bare}}, &Schema{Databases: []DatabaseSpec{*db}}) + assert.Empty(t, changes.Databases) + + // A custom target must not be silently normalized away. Engine-only + // custom targets are still unsupported by the schema converter. + customSQL := strings.Replace(sql, "ORDER BY (id, timestamp)", "ORDER BY (timestamp, id)", 1) + _, err := buildTableFromCreateSQL(customSQL) + require.ErrorContains(t, err, "samples target: clause has neither external table nor inner columns") } func TestSQLGen_TimeSeriesTagsToColumns_HCLFirstEscaping(t *testing.T) {