diff --git a/internal/compiler/output_columns.go b/internal/compiler/output_columns.go index d8795a6c08..16a96c40a8 100644 --- a/internal/compiler/output_columns.go +++ b/internal/compiler/output_columns.go @@ -72,7 +72,7 @@ func outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, error) { continue } - if err := findColumnForRef(ref, tables); err != nil { + if err := findColumnForRef(ref, tables, n); err != nil { return nil, err } } @@ -485,7 +485,7 @@ func outputColumnRefs(res *ast.ResTarget, tables []*Table, node *ast.ColumnRef) return cols, nil } -func findColumnForRef(ref *ast.ColumnRef, tables []*Table) error { +func findColumnForRef(ref *ast.ColumnRef, tables []*Table, selectStatement *ast.SelectStmt) error { parts := stringSlice(ref.Fields) var alias, name string if len(parts) == 1 { @@ -500,9 +500,28 @@ func findColumnForRef(ref *ast.ColumnRef, tables []*Table) error { if alias != "" && t.Rel.Name != alias { continue } + + // Find matching column + var foundColumn bool for _, c := range t.Columns { if c.Name == name { found++ + foundColumn = true + } + } + + if foundColumn { + continue + } + + // Find matching alias + for _, c := range selectStatement.TargetList.Items { + resTarget, ok := c.(*ast.ResTarget) + if !ok { + continue + } + if resTarget.Name != nil && *resTarget.Name == name { + found++ } } } diff --git a/internal/endtoend/testdata/valid_group_by_reference/mysql/go/db.go b/internal/endtoend/testdata/valid_group_by_reference/mysql/go/db.go new file mode 100644 index 0000000000..6a99519302 --- /dev/null +++ b/internal/endtoend/testdata/valid_group_by_reference/mysql/go/db.go @@ -0,0 +1,29 @@ +// Code generated by sqlc. DO NOT EDIT. + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/valid_group_by_reference/mysql/go/models.go b/internal/endtoend/testdata/valid_group_by_reference/mysql/go/models.go new file mode 100644 index 0000000000..edf14744a9 --- /dev/null +++ b/internal/endtoend/testdata/valid_group_by_reference/mysql/go/models.go @@ -0,0 +1,34 @@ +// Code generated by sqlc. DO NOT EDIT. + +package querytest + +import ( + "database/sql" + "time" +) + +type Author struct { + ID int64 + Name string + Bio sql.NullString +} + +type WeatherMetric struct { + Time time.Time + TimezoneShift sql.NullInt32 + CityName sql.NullString + TempC interface{} + FeelsLikeC interface{} + TempMinC interface{} + TempMaxC interface{} + PressureHpa interface{} + HumidityPercent interface{} + WindSpeedMs interface{} + WindDeg sql.NullInt32 + Rain1hMm interface{} + Rain3hMm interface{} + Snow1hMm interface{} + Snow3hMm interface{} + CloudsPercent sql.NullInt32 + WeatherTypeID sql.NullInt32 +} diff --git a/internal/endtoend/testdata/valid_group_by_reference/mysql/go/query.sql.go b/internal/endtoend/testdata/valid_group_by_reference/mysql/go/query.sql.go new file mode 100644 index 0000000000..4e5c8aeba1 --- /dev/null +++ b/internal/endtoend/testdata/valid_group_by_reference/mysql/go/query.sql.go @@ -0,0 +1,110 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" +) + +const listAuthors = `-- name: ListAuthors :many +SELECT id, name as full_name, bio +FROM authors +GROUP BY full_name +` + +type ListAuthorsRow struct { + ID int64 + FullName string + Bio sql.NullString +} + +func (q *Queries) ListAuthors(ctx context.Context) ([]ListAuthorsRow, error) { + rows, err := q.db.QueryContext(ctx, listAuthors) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListAuthorsRow + for rows.Next() { + var i ListAuthorsRow + if err := rows.Scan(&i.ID, &i.FullName, &i.Bio); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listAuthorsIdenticalAlias = `-- name: ListAuthorsIdenticalAlias :many +SELECT id, name as name, bio +FROM authors +GROUP BY name +` + +func (q *Queries) ListAuthorsIdenticalAlias(ctx context.Context) ([]Author, error) { + rows, err := q.db.QueryContext(ctx, listAuthorsIdenticalAlias) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Author + for rows.Next() { + var i Author + if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listMetrics = `-- name: ListMetrics :many +SELECT time_bucket('15 days', time) AS bucket, city_name, AVG(temp_c) +FROM weather_metrics +WHERE DATE_SUB(NOW(), INTERVAL 6 MONTH) +GROUP BY bucket, city_name +ORDER BY bucket DESC +` + +type ListMetricsRow struct { + Bucket interface{} + CityName sql.NullString + Avg interface{} +} + +func (q *Queries) ListMetrics(ctx context.Context) ([]ListMetricsRow, error) { + rows, err := q.db.QueryContext(ctx, listMetrics) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListMetricsRow + for rows.Next() { + var i ListMetricsRow + if err := rows.Scan(&i.Bucket, &i.CityName, &i.Avg); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/internal/endtoend/testdata/valid_group_by_reference/mysql/query.sql b/internal/endtoend/testdata/valid_group_by_reference/mysql/query.sql new file mode 100644 index 0000000000..38a93bd2b8 --- /dev/null +++ b/internal/endtoend/testdata/valid_group_by_reference/mysql/query.sql @@ -0,0 +1,47 @@ +CREATE TABLE authors ( + id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + name text NOT NULL, + bio text, + UNIQUE(name) +); + +-- name: ListAuthors :many +SELECT id, name as full_name, bio +FROM authors +GROUP BY full_name; + +-- name: ListAuthorsIdenticalAlias :many +SELECT id, name as name, bio +FROM authors +GROUP BY name; + + +-- https://github.com/kyleconroy/sqlc/issues/1315 + +CREATE TABLE IF NOT EXISTS weather_metrics +( + time TIMESTAMP NOT NULL, + timezone_shift INT NULL, + city_name TEXT NULL, + temp_c FLOAT NULL, + feels_like_c FLOAT NULL, + temp_min_c FLOAT NULL, + temp_max_c FLOAT NULL, + pressure_hpa FLOAT NULL, + humidity_percent FLOAT NULL, + wind_speed_ms FLOAT NULL, + wind_deg INT NULL, + rain_1h_mm FLOAT NULL, + rain_3h_mm FLOAT NULL, + snow_1h_mm FLOAT NULL, + snow_3h_mm FLOAT NULL, + clouds_percent INT NULL, + weather_type_id INT NULL +); + +-- name: ListMetrics :many +SELECT time_bucket('15 days', time) AS bucket, city_name, AVG(temp_c) +FROM weather_metrics +WHERE DATE_SUB(NOW(), INTERVAL 6 MONTH) +GROUP BY bucket, city_name +ORDER BY bucket DESC; diff --git a/internal/endtoend/testdata/valid_group_by_reference/mysql/sqlc.json b/internal/endtoend/testdata/valid_group_by_reference/mysql/sqlc.json new file mode 100644 index 0000000000..534b7e24e9 --- /dev/null +++ b/internal/endtoend/testdata/valid_group_by_reference/mysql/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "mysql", + "name": "querytest", + "schema": "query.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/valid_group_by_reference/postgresql/go/db.go b/internal/endtoend/testdata/valid_group_by_reference/postgresql/go/db.go new file mode 100644 index 0000000000..6a99519302 --- /dev/null +++ b/internal/endtoend/testdata/valid_group_by_reference/postgresql/go/db.go @@ -0,0 +1,29 @@ +// Code generated by sqlc. DO NOT EDIT. + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/valid_group_by_reference/postgresql/go/models.go b/internal/endtoend/testdata/valid_group_by_reference/postgresql/go/models.go new file mode 100644 index 0000000000..09af05bedf --- /dev/null +++ b/internal/endtoend/testdata/valid_group_by_reference/postgresql/go/models.go @@ -0,0 +1,34 @@ +// Code generated by sqlc. DO NOT EDIT. + +package querytest + +import ( + "database/sql" + "time" +) + +type Author struct { + ID int64 + Name string + Bio sql.NullString +} + +type WeatherMetric struct { + Time time.Time + TimezoneShift sql.NullInt32 + CityName sql.NullString + TempC sql.NullFloat64 + FeelsLikeC sql.NullFloat64 + TempMinC sql.NullFloat64 + TempMaxC sql.NullFloat64 + PressureHpa sql.NullFloat64 + HumidityPercent sql.NullFloat64 + WindSpeedMs sql.NullFloat64 + WindDeg sql.NullInt32 + Rain1hMm sql.NullFloat64 + Rain3hMm sql.NullFloat64 + Snow1hMm sql.NullFloat64 + Snow3hMm sql.NullFloat64 + CloudsPercent sql.NullInt32 + WeatherTypeID sql.NullInt32 +} diff --git a/internal/endtoend/testdata/valid_group_by_reference/postgresql/go/query.sql.go b/internal/endtoend/testdata/valid_group_by_reference/postgresql/go/query.sql.go new file mode 100644 index 0000000000..b4adccc07e --- /dev/null +++ b/internal/endtoend/testdata/valid_group_by_reference/postgresql/go/query.sql.go @@ -0,0 +1,104 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" +) + +const listAuthors = `-- name: ListAuthors :many +SELECT id, name as name, bio +FROM authors +GROUP BY name +` + +func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) { + rows, err := q.db.QueryContext(ctx, listAuthors) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Author + for rows.Next() { + var i Author + if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listAuthorsIdenticalAlias = `-- name: ListAuthorsIdenticalAlias :many +SELECT id, name as name, bio +FROM authors +GROUP BY name +` + +func (q *Queries) ListAuthorsIdenticalAlias(ctx context.Context) ([]Author, error) { + rows, err := q.db.QueryContext(ctx, listAuthorsIdenticalAlias) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Author + for rows.Next() { + var i Author + if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listMetrics = `-- name: ListMetrics :many +SELECT time_bucket('15 days', time) AS bucket, city_name, AVG(temp_c) +FROM weather_metrics +WHERE time > NOW() - (6 * INTERVAL '1 month') +GROUP BY bucket, city_name +ORDER BY bucket DESC +` + +type ListMetricsRow struct { + Bucket interface{} + CityName sql.NullString + Avg string +} + +func (q *Queries) ListMetrics(ctx context.Context) ([]ListMetricsRow, error) { + rows, err := q.db.QueryContext(ctx, listMetrics) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListMetricsRow + for rows.Next() { + var i ListMetricsRow + if err := rows.Scan(&i.Bucket, &i.CityName, &i.Avg); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/internal/endtoend/testdata/valid_group_by_reference/postgresql/query.sql b/internal/endtoend/testdata/valid_group_by_reference/postgresql/query.sql new file mode 100644 index 0000000000..f646a796f3 --- /dev/null +++ b/internal/endtoend/testdata/valid_group_by_reference/postgresql/query.sql @@ -0,0 +1,46 @@ +CREATE TABLE authors ( + id BIGSERIAL PRIMARY KEY, + name text NOT NULL, + bio text +); + +-- name: ListAuthors :many +SELECT id, name as name, bio +FROM authors +GROUP BY name; + +-- name: ListAuthorsIdenticalAlias :many +SELECT id, name as name, bio +FROM authors +GROUP BY name; + + +-- https://github.com/kyleconroy/sqlc/issues/1315 + +CREATE TABLE IF NOT EXISTS weather_metrics +( + time TIMESTAMP WITHOUT TIME ZONE NOT NULL, + timezone_shift INT NULL, + city_name TEXT NULL, + temp_c DOUBLE PRECISION NULL, + feels_like_c DOUBLE PRECISION NULL, + temp_min_c DOUBLE PRECISION NULL, + temp_max_c DOUBLE PRECISION NULL, + pressure_hpa DOUBLE PRECISION NULL, + humidity_percent DOUBLE PRECISION NULL, + wind_speed_ms DOUBLE PRECISION NULL, + wind_deg INT NULL, + rain_1h_mm DOUBLE PRECISION NULL, + rain_3h_mm DOUBLE PRECISION NULL, + snow_1h_mm DOUBLE PRECISION NULL, + snow_3h_mm DOUBLE PRECISION NULL, + clouds_percent INT NULL, + weather_type_id INT NULL +); + +-- name: ListMetrics :many +SELECT time_bucket('15 days', time) AS bucket, city_name, AVG(temp_c) +FROM weather_metrics +WHERE time > NOW() - (6 * INTERVAL '1 month') +GROUP BY bucket, city_name +ORDER BY bucket DESC; diff --git a/internal/endtoend/testdata/valid_group_by_reference/postgresql/sqlc.json b/internal/endtoend/testdata/valid_group_by_reference/postgresql/sqlc.json new file mode 100644 index 0000000000..af57681f66 --- /dev/null +++ b/internal/endtoend/testdata/valid_group_by_reference/postgresql/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "postgresql", + "name": "querytest", + "schema": "query.sql", + "queries": "query.sql" + } + ] +}