From 7c9b194b61e1220d8bc14069f8179a8a22fe5860 Mon Sep 17 00:00:00 2001 From: Timothy Studd Date: Wed, 16 Mar 2022 05:31:13 -0700 Subject: [PATCH 1/2] fix(compiler): Fix left join nullability with table aliases --- internal/compiler/output_columns.go | 17 +- .../testdata/join_left/mysql/go/models.go | 12 + .../testdata/join_left/mysql/go/query.sql.go | 276 ++++++++++++++++++ .../testdata/join_left/mysql/query.sql | 49 ++++ .../join_left/postgresql/go/models.go | 12 + .../join_left/postgresql/go/query.sql.go | 276 ++++++++++++++++++ .../testdata/join_left/postgresql/query.sql | 51 +++- 7 files changed, 684 insertions(+), 9 deletions(-) diff --git a/internal/compiler/output_columns.go b/internal/compiler/output_columns.go index e66cb7a006..65a2d5853c 100644 --- a/internal/compiler/output_columns.go +++ b/internal/compiler/output_columns.go @@ -182,14 +182,15 @@ func outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, error) { cname = *res.Name } cols = append(cols, &Column{ - Name: cname, - Type: c.Type, - Scope: scope, - Table: c.Table, - DataType: c.DataType, - NotNull: c.NotNull, - IsArray: c.IsArray, - Length: c.Length, + Name: cname, + Type: c.Type, + Scope: scope, + Table: c.Table, + TableAlias: t.Rel.Name, + DataType: c.DataType, + NotNull: c.NotNull, + IsArray: c.IsArray, + Length: c.Length, }) } } diff --git a/internal/endtoend/testdata/join_left/mysql/go/models.go b/internal/endtoend/testdata/join_left/mysql/go/models.go index 8a39dfa8ef..195afa4c3f 100644 --- a/internal/endtoend/testdata/join_left/mysql/go/models.go +++ b/internal/endtoend/testdata/join_left/mysql/go/models.go @@ -6,6 +6,12 @@ import ( "database/sql" ) +type Author struct { + ID int32 + Name string + ParentID sql.NullInt32 +} + type City struct { CityID int32 MayorID int32 @@ -16,6 +22,12 @@ type Mayor struct { FullName string } +type SuperAuthor struct { + SuperID int32 + SuperName string + SuperParentID sql.NullInt32 +} + type User struct { UserID int32 CityID sql.NullInt32 diff --git a/internal/endtoend/testdata/join_left/mysql/go/query.sql.go b/internal/endtoend/testdata/join_left/mysql/go/query.sql.go index 269aa0f4c7..a63063f662 100644 --- a/internal/endtoend/testdata/join_left/mysql/go/query.sql.go +++ b/internal/endtoend/testdata/join_left/mysql/go/query.sql.go @@ -8,6 +8,282 @@ import ( "database/sql" ) +const allAuthors = `-- name: AllAuthors :many +SELECT a.id, a.name, a.parent_id, p.id, p.name, p.parent_id +FROM authors a + LEFT JOIN authors p + ON a.parent_id = p.id +` + +type AllAuthorsRow struct { + ID int32 + Name string + ParentID sql.NullInt32 + ID_2 sql.NullInt32 + Name_2 sql.NullString + ParentID_2 sql.NullInt32 +} + +func (q *Queries) AllAuthors(ctx context.Context) ([]AllAuthorsRow, error) { + rows, err := q.db.QueryContext(ctx, allAuthors) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAuthorsRow + for rows.Next() { + var i AllAuthorsRow + if err := rows.Scan( + &i.ID, + &i.Name, + &i.ParentID, + &i.ID_2, + &i.Name_2, + &i.ParentID_2, + ); 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 allAuthorsAliases = `-- name: AllAuthorsAliases :many +SELECT a.id, a.name, a.parent_id, p.id, p.name, p.parent_id +FROM authors a + LEFT JOIN authors p + ON a.parent_id = p.id +` + +type AllAuthorsAliasesRow struct { + ID int32 + Name string + ParentID sql.NullInt32 + ID_2 sql.NullInt32 + Name_2 sql.NullString + ParentID_2 sql.NullInt32 +} + +func (q *Queries) AllAuthorsAliases(ctx context.Context) ([]AllAuthorsAliasesRow, error) { + rows, err := q.db.QueryContext(ctx, allAuthorsAliases) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAuthorsAliasesRow + for rows.Next() { + var i AllAuthorsAliasesRow + if err := rows.Scan( + &i.ID, + &i.Name, + &i.ParentID, + &i.ID_2, + &i.Name_2, + &i.ParentID_2, + ); 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 allAuthorsAliases2 = `-- name: AllAuthorsAliases2 :many +SELECT a.id, a.name, a.parent_id, p.id, p.name, p.parent_id +FROM authors a + LEFT JOIN authors p + ON a.parent_id = p.id +` + +type AllAuthorsAliases2Row struct { + ID int32 + Name string + ParentID sql.NullInt32 + ID_2 sql.NullInt32 + Name_2 sql.NullString + ParentID_2 sql.NullInt32 +} + +func (q *Queries) AllAuthorsAliases2(ctx context.Context) ([]AllAuthorsAliases2Row, error) { + rows, err := q.db.QueryContext(ctx, allAuthorsAliases2) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAuthorsAliases2Row + for rows.Next() { + var i AllAuthorsAliases2Row + if err := rows.Scan( + &i.ID, + &i.Name, + &i.ParentID, + &i.ID_2, + &i.Name_2, + &i.ParentID_2, + ); 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 allSuperAuthors = `-- name: AllSuperAuthors :many +SELECT id, name, parent_id, super_id, super_name, super_parent_id +FROM authors + LEFT JOIN super_authors + ON authors.parent_id = super_authors.super_id +` + +type AllSuperAuthorsRow struct { + ID int32 + Name string + ParentID sql.NullInt32 + SuperID sql.NullInt32 + SuperName sql.NullString + SuperParentID sql.NullInt32 +} + +func (q *Queries) AllSuperAuthors(ctx context.Context) ([]AllSuperAuthorsRow, error) { + rows, err := q.db.QueryContext(ctx, allSuperAuthors) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllSuperAuthorsRow + for rows.Next() { + var i AllSuperAuthorsRow + if err := rows.Scan( + &i.ID, + &i.Name, + &i.ParentID, + &i.SuperID, + &i.SuperName, + &i.SuperParentID, + ); 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 allSuperAuthorsAliases = `-- name: AllSuperAuthorsAliases :many +SELECT id, name, parent_id, super_id, super_name, super_parent_id +FROM authors a + LEFT JOIN super_authors sa + ON a.parent_id = sa.super_id +` + +type AllSuperAuthorsAliasesRow struct { + ID int32 + Name string + ParentID sql.NullInt32 + SuperID sql.NullInt32 + SuperName sql.NullString + SuperParentID sql.NullInt32 +} + +func (q *Queries) AllSuperAuthorsAliases(ctx context.Context) ([]AllSuperAuthorsAliasesRow, error) { + rows, err := q.db.QueryContext(ctx, allSuperAuthorsAliases) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllSuperAuthorsAliasesRow + for rows.Next() { + var i AllSuperAuthorsAliasesRow + if err := rows.Scan( + &i.ID, + &i.Name, + &i.ParentID, + &i.SuperID, + &i.SuperName, + &i.SuperParentID, + ); 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 allSuperAuthorsAliases2 = `-- name: AllSuperAuthorsAliases2 :many +SELECT a.id, a.name, a.parent_id, sa.super_id, sa.super_name, sa.super_parent_id +FROM authors a + LEFT JOIN super_authors sa + ON a.parent_id = sa.super_id +` + +type AllSuperAuthorsAliases2Row struct { + ID int32 + Name string + ParentID sql.NullInt32 + SuperID sql.NullInt32 + SuperName sql.NullString + SuperParentID sql.NullInt32 +} + +func (q *Queries) AllSuperAuthorsAliases2(ctx context.Context) ([]AllSuperAuthorsAliases2Row, error) { + rows, err := q.db.QueryContext(ctx, allSuperAuthorsAliases2) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllSuperAuthorsAliases2Row + for rows.Next() { + var i AllSuperAuthorsAliases2Row + if err := rows.Scan( + &i.ID, + &i.Name, + &i.ParentID, + &i.SuperID, + &i.SuperName, + &i.SuperParentID, + ); 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 getMayors = `-- name: GetMayors :many SELECT user_id, diff --git a/internal/endtoend/testdata/join_left/mysql/query.sql b/internal/endtoend/testdata/join_left/mysql/query.sql index 1d3b828949..2bf1fc36c8 100644 --- a/internal/endtoend/testdata/join_left/mysql/query.sql +++ b/internal/endtoend/testdata/join_left/mysql/query.sql @@ -28,3 +28,52 @@ SELECT FROM users LEFT JOIN cities USING (city_id) LEFT JOIN mayors USING (mayor_id); + +-- https://github.com/kyleconroy/sqlc/issues/1334 +CREATE TABLE authors ( + id INT PRIMARY KEY, + name TEXT NOT NULL, + parent_id INT -- nullable +); + +CREATE TABLE super_authors ( + super_id INT PRIMARY KEY, + super_name TEXT NOT NULL, + super_parent_id INT -- nullable +); + +-- name: AllAuthors :many +SELECT * +FROM authors a + LEFT JOIN authors p + ON a.parent_id = p.id; + +-- name: AllAuthorsAliases :many +SELECT * +FROM authors a + LEFT JOIN authors p + ON a.parent_id = p.id; + +-- name: AllAuthorsAliases2 :many +SELECT a.*, p.* +FROM authors a + LEFT JOIN authors p + ON a.parent_id = p.id; + +-- name: AllSuperAuthors :many +SELECT * +FROM authors + LEFT JOIN super_authors + ON authors.parent_id = super_authors.super_id; + +-- name: AllSuperAuthorsAliases :many +SELECT * +FROM authors a + LEFT JOIN super_authors sa + ON a.parent_id = sa.super_id; + +-- name: AllSuperAuthorsAliases2 :many +SELECT a.*, sa.* +FROM authors a + LEFT JOIN super_authors sa + ON a.parent_id = sa.super_id; diff --git a/internal/endtoend/testdata/join_left/postgresql/go/models.go b/internal/endtoend/testdata/join_left/postgresql/go/models.go index 8a39dfa8ef..195afa4c3f 100644 --- a/internal/endtoend/testdata/join_left/postgresql/go/models.go +++ b/internal/endtoend/testdata/join_left/postgresql/go/models.go @@ -6,6 +6,12 @@ import ( "database/sql" ) +type Author struct { + ID int32 + Name string + ParentID sql.NullInt32 +} + type City struct { CityID int32 MayorID int32 @@ -16,6 +22,12 @@ type Mayor struct { FullName string } +type SuperAuthor struct { + SuperID int32 + SuperName string + SuperParentID sql.NullInt32 +} + type User struct { UserID int32 CityID sql.NullInt32 diff --git a/internal/endtoend/testdata/join_left/postgresql/go/query.sql.go b/internal/endtoend/testdata/join_left/postgresql/go/query.sql.go index c89a48750c..6a1db622f1 100644 --- a/internal/endtoend/testdata/join_left/postgresql/go/query.sql.go +++ b/internal/endtoend/testdata/join_left/postgresql/go/query.sql.go @@ -8,6 +8,282 @@ import ( "database/sql" ) +const allAuthors = `-- name: AllAuthors :many +SELECT a.id, a.name, a.parent_id, p.id, p.name, p.parent_id +FROM authors a + LEFT JOIN authors p + ON a.parent_id = p.id +` + +type AllAuthorsRow struct { + ID int32 + Name string + ParentID sql.NullInt32 + ID_2 sql.NullInt32 + Name_2 sql.NullString + ParentID_2 sql.NullInt32 +} + +func (q *Queries) AllAuthors(ctx context.Context) ([]AllAuthorsRow, error) { + rows, err := q.db.QueryContext(ctx, allAuthors) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAuthorsRow + for rows.Next() { + var i AllAuthorsRow + if err := rows.Scan( + &i.ID, + &i.Name, + &i.ParentID, + &i.ID_2, + &i.Name_2, + &i.ParentID_2, + ); 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 allAuthorsAliases = `-- name: AllAuthorsAliases :many +SELECT a.id, a.name, a.parent_id, p.id, p.name, p.parent_id +FROM authors a + LEFT JOIN authors p + ON a.parent_id = p.id +` + +type AllAuthorsAliasesRow struct { + ID int32 + Name string + ParentID sql.NullInt32 + ID_2 sql.NullInt32 + Name_2 sql.NullString + ParentID_2 sql.NullInt32 +} + +func (q *Queries) AllAuthorsAliases(ctx context.Context) ([]AllAuthorsAliasesRow, error) { + rows, err := q.db.QueryContext(ctx, allAuthorsAliases) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAuthorsAliasesRow + for rows.Next() { + var i AllAuthorsAliasesRow + if err := rows.Scan( + &i.ID, + &i.Name, + &i.ParentID, + &i.ID_2, + &i.Name_2, + &i.ParentID_2, + ); 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 allAuthorsAliases2 = `-- name: AllAuthorsAliases2 :many +SELECT a.id, a.name, a.parent_id, p.id, p.name, p.parent_id +FROM authors a + LEFT JOIN authors p + ON a.parent_id = p.id +` + +type AllAuthorsAliases2Row struct { + ID int32 + Name string + ParentID sql.NullInt32 + ID_2 sql.NullInt32 + Name_2 sql.NullString + ParentID_2 sql.NullInt32 +} + +func (q *Queries) AllAuthorsAliases2(ctx context.Context) ([]AllAuthorsAliases2Row, error) { + rows, err := q.db.QueryContext(ctx, allAuthorsAliases2) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAuthorsAliases2Row + for rows.Next() { + var i AllAuthorsAliases2Row + if err := rows.Scan( + &i.ID, + &i.Name, + &i.ParentID, + &i.ID_2, + &i.Name_2, + &i.ParentID_2, + ); 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 allSuperAuthors = `-- name: AllSuperAuthors :many +SELECT id, name, parent_id, super_id, super_name, super_parent_id +FROM authors + LEFT JOIN super_authors + ON authors.parent_id = super_authors.super_id +` + +type AllSuperAuthorsRow struct { + ID int32 + Name string + ParentID sql.NullInt32 + SuperID sql.NullInt32 + SuperName sql.NullString + SuperParentID sql.NullInt32 +} + +func (q *Queries) AllSuperAuthors(ctx context.Context) ([]AllSuperAuthorsRow, error) { + rows, err := q.db.QueryContext(ctx, allSuperAuthors) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllSuperAuthorsRow + for rows.Next() { + var i AllSuperAuthorsRow + if err := rows.Scan( + &i.ID, + &i.Name, + &i.ParentID, + &i.SuperID, + &i.SuperName, + &i.SuperParentID, + ); 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 allSuperAuthorsAliases = `-- name: AllSuperAuthorsAliases :many +SELECT id, name, parent_id, super_id, super_name, super_parent_id +FROM authors a + LEFT JOIN super_authors sa + ON a.parent_id = sa.super_id +` + +type AllSuperAuthorsAliasesRow struct { + ID int32 + Name string + ParentID sql.NullInt32 + SuperID sql.NullInt32 + SuperName sql.NullString + SuperParentID sql.NullInt32 +} + +func (q *Queries) AllSuperAuthorsAliases(ctx context.Context) ([]AllSuperAuthorsAliasesRow, error) { + rows, err := q.db.QueryContext(ctx, allSuperAuthorsAliases) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllSuperAuthorsAliasesRow + for rows.Next() { + var i AllSuperAuthorsAliasesRow + if err := rows.Scan( + &i.ID, + &i.Name, + &i.ParentID, + &i.SuperID, + &i.SuperName, + &i.SuperParentID, + ); 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 allSuperAuthorsAliases2 = `-- name: AllSuperAuthorsAliases2 :many +SELECT a.id, a.name, a.parent_id, sa.super_id, sa.super_name, sa.super_parent_id +FROM authors a + LEFT JOIN super_authors sa + ON a.parent_id = sa.super_id +` + +type AllSuperAuthorsAliases2Row struct { + ID int32 + Name string + ParentID sql.NullInt32 + SuperID sql.NullInt32 + SuperName sql.NullString + SuperParentID sql.NullInt32 +} + +func (q *Queries) AllSuperAuthorsAliases2(ctx context.Context) ([]AllSuperAuthorsAliases2Row, error) { + rows, err := q.db.QueryContext(ctx, allSuperAuthorsAliases2) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllSuperAuthorsAliases2Row + for rows.Next() { + var i AllSuperAuthorsAliases2Row + if err := rows.Scan( + &i.ID, + &i.Name, + &i.ParentID, + &i.SuperID, + &i.SuperName, + &i.SuperParentID, + ); 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 getMayors = `-- name: GetMayors :many SELECT user_id, diff --git a/internal/endtoend/testdata/join_left/postgresql/query.sql b/internal/endtoend/testdata/join_left/postgresql/query.sql index 421f078884..3c7292741b 100644 --- a/internal/endtoend/testdata/join_left/postgresql/query.sql +++ b/internal/endtoend/testdata/join_left/postgresql/query.sql @@ -26,4 +26,53 @@ SELECT mayors.full_name FROM users LEFT JOIN cities USING (city_id) -LEFT JOIN mayors USING (mayor_id); \ No newline at end of file +LEFT JOIN mayors USING (mayor_id); + +-- https://github.com/kyleconroy/sqlc/issues/1334 +CREATE TABLE authors ( + id INT PRIMARY KEY, + name TEXT NOT NULL, + parent_id INT -- nullable +); + +CREATE TABLE super_authors ( + super_id INT PRIMARY KEY, + super_name TEXT NOT NULL, + super_parent_id INT -- nullable +); + +-- name: AllAuthors :many +SELECT * +FROM authors a + LEFT JOIN authors p + ON a.parent_id = p.id; + +-- name: AllAuthorsAliases :many +SELECT * +FROM authors a + LEFT JOIN authors p + ON a.parent_id = p.id; + +-- name: AllAuthorsAliases2 :many +SELECT a.*, p.* +FROM authors a + LEFT JOIN authors p + ON a.parent_id = p.id; + +-- name: AllSuperAuthors :many +SELECT * +FROM authors + LEFT JOIN super_authors + ON authors.parent_id = super_authors.super_id; + +-- name: AllSuperAuthorsAliases :many +SELECT * +FROM authors a + LEFT JOIN super_authors sa + ON a.parent_id = sa.super_id; + +-- name: AllSuperAuthorsAliases2 :many +SELECT a.*, sa.* +FROM authors a + LEFT JOIN super_authors sa + ON a.parent_id = sa.super_id; From 83eb9d07443f0e5b433521a4d3f1a12d235631f6 Mon Sep 17 00:00:00 2001 From: Timothy Studd Date: Wed, 16 Mar 2022 06:56:54 -0700 Subject: [PATCH 2/2] Add other test from #1334 --- .../testdata/join_left/mysql/go/models.go | 24 ++++++ .../testdata/join_left/mysql/go/query.sql.go | 70 +++++++++++++++++ .../testdata/join_left/mysql/query.sql | 37 +++++++++ .../join_left/postgresql/go/models.go | 26 +++++++ .../join_left/postgresql/go/query.sql.go | 78 +++++++++++++++++++ .../testdata/join_left/postgresql/query.sql | 35 +++++++++ 6 files changed, 270 insertions(+) diff --git a/internal/endtoend/testdata/join_left/mysql/go/models.go b/internal/endtoend/testdata/join_left/mysql/go/models.go index 195afa4c3f..b7a4befcd4 100644 --- a/internal/endtoend/testdata/join_left/mysql/go/models.go +++ b/internal/endtoend/testdata/join_left/mysql/go/models.go @@ -4,6 +4,7 @@ package querytest import ( "database/sql" + "time" ) type Author struct { @@ -22,6 +23,16 @@ type Mayor struct { FullName string } +type Medium struct { + MediaID int32 + MediaCreatedAt time.Time + MediaHash string + MediaDirectory string + MediaAuthorID int32 + MediaWidth int32 + MediaHeight int32 +} + type SuperAuthor struct { SuperID int32 SuperName string @@ -32,3 +43,16 @@ type User struct { UserID int32 CityID sql.NullInt32 } + +type Users2 struct { + UserID int32 + UserNickname string + UserEmail string + UserDisplayName string + UserPassword sql.NullString + UserGoogleID sql.NullString + UserAppleID sql.NullString + UserBio string + UserCreatedAt time.Time + UserAvatarID sql.NullInt32 +} diff --git a/internal/endtoend/testdata/join_left/mysql/go/query.sql.go b/internal/endtoend/testdata/join_left/mysql/go/query.sql.go index a63063f662..83483a5995 100644 --- a/internal/endtoend/testdata/join_left/mysql/go/query.sql.go +++ b/internal/endtoend/testdata/join_left/mysql/go/query.sql.go @@ -6,6 +6,7 @@ package querytest import ( "context" "database/sql" + "time" ) const allAuthors = `-- name: AllAuthors :many @@ -359,3 +360,72 @@ func (q *Queries) GetMayorsOptional(ctx context.Context) ([]GetMayorsOptionalRow } return items, nil } + +const getSuggestedUsersByID = `-- name: GetSuggestedUsersByID :many +SELECT DISTINCT u.user_id, u.user_nickname, u.user_email, u.user_display_name, u.user_password, u.user_google_id, u.user_apple_id, u.user_bio, u.user_created_at, u.user_avatar_id, m.media_id, m.media_created_at, m.media_hash, m.media_directory, m.media_author_id, m.media_width, m.media_height +FROM users_2 u + LEFT JOIN media m + ON u.user_avatar_id = m.media_id +WHERE u.user_id != @user_id +` + +type GetSuggestedUsersByIDRow struct { + UserID int32 + UserNickname string + UserEmail string + UserDisplayName string + UserPassword sql.NullString + UserGoogleID sql.NullString + UserAppleID sql.NullString + UserBio string + UserCreatedAt time.Time + UserAvatarID sql.NullInt32 + MediaID sql.NullInt32 + MediaCreatedAt sql.NullTime + MediaHash sql.NullString + MediaDirectory sql.NullString + MediaAuthorID sql.NullInt32 + MediaWidth sql.NullInt32 + MediaHeight sql.NullInt32 +} + +func (q *Queries) GetSuggestedUsersByID(ctx context.Context) ([]GetSuggestedUsersByIDRow, error) { + rows, err := q.db.QueryContext(ctx, getSuggestedUsersByID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetSuggestedUsersByIDRow + for rows.Next() { + var i GetSuggestedUsersByIDRow + if err := rows.Scan( + &i.UserID, + &i.UserNickname, + &i.UserEmail, + &i.UserDisplayName, + &i.UserPassword, + &i.UserGoogleID, + &i.UserAppleID, + &i.UserBio, + &i.UserCreatedAt, + &i.UserAvatarID, + &i.MediaID, + &i.MediaCreatedAt, + &i.MediaHash, + &i.MediaDirectory, + &i.MediaAuthorID, + &i.MediaWidth, + &i.MediaHeight, + ); 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/join_left/mysql/query.sql b/internal/endtoend/testdata/join_left/mysql/query.sql index 2bf1fc36c8..4991e59793 100644 --- a/internal/endtoend/testdata/join_left/mysql/query.sql +++ b/internal/endtoend/testdata/join_left/mysql/query.sql @@ -77,3 +77,40 @@ SELECT a.*, sa.* FROM authors a LEFT JOIN super_authors sa ON a.parent_id = sa.super_id; + +-- https://github.com/kyleconroy/sqlc/issues/1334 +CREATE TABLE users_2 ( + user_id INT PRIMARY KEY, + user_nickname VARCHAR(30) UNIQUE NOT NULL, + user_email TEXT UNIQUE NOT NULL, + user_display_name TEXT NOT NULL, + user_password TEXT NULL, + user_google_id TEXT UNIQUE NULL, + user_apple_id TEXT UNIQUE NULL, + user_bio VARCHAR(160) NOT NULL DEFAULT '', + user_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + user_avatar_id INT UNIQUE NULL +); + +CREATE TABLE media ( + media_id INT PRIMARY KEY, + media_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + media_hash TEXT NOT NULL, + media_directory TEXT NOT NULL, + media_author_id INT NOT NULL, + media_width INT NOT NULL, + media_height INT NOT NULL +); + +ALTER TABLE users_2 +ADD FOREIGN KEY (user_avatar_id) +REFERENCES media(media_id) +ON DELETE SET DEFAULT +ON UPDATE CASCADE; + +-- name: GetSuggestedUsersByID :many +SELECT DISTINCT u.*, m.* +FROM users_2 u + LEFT JOIN media m + ON u.user_avatar_id = m.media_id +WHERE u.user_id != @user_id; diff --git a/internal/endtoend/testdata/join_left/postgresql/go/models.go b/internal/endtoend/testdata/join_left/postgresql/go/models.go index 195afa4c3f..95a232c632 100644 --- a/internal/endtoend/testdata/join_left/postgresql/go/models.go +++ b/internal/endtoend/testdata/join_left/postgresql/go/models.go @@ -4,6 +4,9 @@ package querytest import ( "database/sql" + "time" + + "github.com/google/uuid" ) type Author struct { @@ -22,6 +25,16 @@ type Mayor struct { FullName string } +type Medium struct { + MediaID uuid.UUID + MediaCreatedAt time.Time + MediaHash string + MediaDirectory string + MediaAuthorID uuid.UUID + MediaWidth int32 + MediaHeight int32 +} + type SuperAuthor struct { SuperID int32 SuperName string @@ -32,3 +45,16 @@ type User struct { UserID int32 CityID sql.NullInt32 } + +type Users2 struct { + UserID uuid.UUID + UserNickname string + UserEmail string + UserDisplayName string + UserPassword sql.NullString + UserGoogleID sql.NullString + UserAppleID sql.NullString + UserBio string + UserCreatedAt time.Time + UserAvatarID uuid.NullUUID +} diff --git a/internal/endtoend/testdata/join_left/postgresql/go/query.sql.go b/internal/endtoend/testdata/join_left/postgresql/go/query.sql.go index 6a1db622f1..f59a55bf35 100644 --- a/internal/endtoend/testdata/join_left/postgresql/go/query.sql.go +++ b/internal/endtoend/testdata/join_left/postgresql/go/query.sql.go @@ -6,6 +6,9 @@ package querytest import ( "context" "database/sql" + "time" + + "github.com/google/uuid" ) const allAuthors = `-- name: AllAuthors :many @@ -357,3 +360,78 @@ func (q *Queries) GetMayorsOptional(ctx context.Context) ([]GetMayorsOptionalRow } return items, nil } + +const getSuggestedUsersByID = `-- name: GetSuggestedUsersByID :many +SELECT DISTINCT u.user_id, u.user_nickname, u.user_email, u.user_display_name, u.user_password, u.user_google_id, u.user_apple_id, u.user_bio, u.user_created_at, u.user_avatar_id, m.media_id, m.media_created_at, m.media_hash, m.media_directory, m.media_author_id, m.media_width, m.media_height +FROM users_2 u + LEFT JOIN media m + ON u.user_avatar_id = m.media_id +WHERE u.user_id != $1 +LIMIT $2 +` + +type GetSuggestedUsersByIDParams struct { + UserID uuid.UUID + UserImit int32 +} + +type GetSuggestedUsersByIDRow struct { + UserID uuid.UUID + UserNickname string + UserEmail string + UserDisplayName string + UserPassword sql.NullString + UserGoogleID sql.NullString + UserAppleID sql.NullString + UserBio string + UserCreatedAt time.Time + UserAvatarID uuid.NullUUID + MediaID uuid.NullUUID + MediaCreatedAt sql.NullTime + MediaHash sql.NullString + MediaDirectory sql.NullString + MediaAuthorID uuid.NullUUID + MediaWidth sql.NullInt32 + MediaHeight sql.NullInt32 +} + +func (q *Queries) GetSuggestedUsersByID(ctx context.Context, arg GetSuggestedUsersByIDParams) ([]GetSuggestedUsersByIDRow, error) { + rows, err := q.db.QueryContext(ctx, getSuggestedUsersByID, arg.UserID, arg.UserImit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetSuggestedUsersByIDRow + for rows.Next() { + var i GetSuggestedUsersByIDRow + if err := rows.Scan( + &i.UserID, + &i.UserNickname, + &i.UserEmail, + &i.UserDisplayName, + &i.UserPassword, + &i.UserGoogleID, + &i.UserAppleID, + &i.UserBio, + &i.UserCreatedAt, + &i.UserAvatarID, + &i.MediaID, + &i.MediaCreatedAt, + &i.MediaHash, + &i.MediaDirectory, + &i.MediaAuthorID, + &i.MediaWidth, + &i.MediaHeight, + ); 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/join_left/postgresql/query.sql b/internal/endtoend/testdata/join_left/postgresql/query.sql index 3c7292741b..816f154aff 100644 --- a/internal/endtoend/testdata/join_left/postgresql/query.sql +++ b/internal/endtoend/testdata/join_left/postgresql/query.sql @@ -76,3 +76,38 @@ SELECT a.*, sa.* FROM authors a LEFT JOIN super_authors sa ON a.parent_id = sa.super_id; + +-- https://github.com/kyleconroy/sqlc/issues/1334 +CREATE TABLE "users_2" ( + "user_id" uuid PRIMARY KEY, + "user_nickname" VARCHAR(30) UNIQUE NOT NULL, + "user_email" TEXT UNIQUE NOT NULL, + "user_display_name" TEXT NOT NULL, + "user_password" TEXT NULL, + "user_google_id" TEXT UNIQUE NULL, + "user_apple_id" TEXT UNIQUE NULL, + "user_bio" VARCHAR(160) NOT NULL DEFAULT '', + "user_created_at" TIMESTAMP NOT NULL DEFAULT (NOW()), + "user_avatar_id" uuid UNIQUE NULL +); + +CREATE TABLE "media" ( + "media_id" uuid PRIMARY KEY, + "media_created_at" TIMESTAMP NOT NULL DEFAULT (NOW()), + "media_hash" TEXT NOT NULL, + "media_directory" TEXT NOT NULL, + "media_author_id" uuid NOT NULL, + "media_width" INT NOT NULL, + "media_height" INT NOT NULL +); + +ALTER TABLE "users_2" + ADD FOREIGN KEY ("user_avatar_id") REFERENCES "media" ("media_id") ON DELETE SET DEFAULT ON UPDATE CASCADE; + +-- name: GetSuggestedUsersByID :many +SELECT DISTINCT u.*, m.* +FROM users_2 u + LEFT JOIN media m + ON u.user_avatar_id = m.media_id +WHERE u.user_id != @user_id +LIMIT @user_imit;