├── .gitignore ├── README.md ├── postgresql-intro ├── README.md ├── app │ └── demo.go ├── cmd │ ├── classic │ │ └── main.go │ ├── gorm │ │ └── main.go │ └── pgx │ │ └── main.go ├── go.mod ├── go.sum └── website │ ├── repository.go │ ├── repository_postgresql_classic.go │ ├── repository_postgresql_gorm.go │ ├── repository_postgresql_pgx.go │ └── website.go └── sqlite-intro ├── README.md ├── go.mod ├── go.sum ├── main.go └── website ├── sqlite_repository.go └── website.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | *.db.DS_Store 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # samples -------------------------------------------------------------------------------- /postgresql-intro/README.md: -------------------------------------------------------------------------------- 1 | # Introduction to PostgreSQL in Go 2 | 3 | A sample project that demonstrates how to use [PostgreSQL](https://www.postgresql.org/) database in Go using the [pgx](https://github.com/jackc/pgx) driver, [pgx](https://github.com/jackc/pgx) client and [GORM](https://gorm.io/) ORM. 4 | 5 | See more at https://gosamples.dev/postgresql-intro -------------------------------------------------------------------------------- /postgresql-intro/app/demo.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "postgresql-intro/website" 9 | ) 10 | 11 | func RunRepositoryDemo(ctx context.Context, websiteRepository website.Repository) { 12 | fmt.Println("1. MIGRATE REPOSITORY") 13 | 14 | if err := websiteRepository.Migrate(ctx); err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | fmt.Println("2. CREATE RECORDS OF REPOSITORY") 19 | gosamples := website.Website{ 20 | Name: "GOSAMPLES", 21 | URL: "https://gosamples.dev", 22 | Rank: 2, 23 | } 24 | golang := website.Website{ 25 | Name: "Golang official website", 26 | URL: "https://golang.org", 27 | Rank: 1, 28 | } 29 | 30 | createdGosamples, err := websiteRepository.Create(ctx, gosamples) 31 | if errors.Is(err, website.ErrDuplicate) { 32 | fmt.Printf("record: %+v already exists\n", gosamples) 33 | } else if err != nil { 34 | log.Fatal(err) 35 | } 36 | createdGolang, err := websiteRepository.Create(ctx, golang) 37 | if errors.Is(err, website.ErrDuplicate) { 38 | log.Printf("record: %+v already exists\n", golang) 39 | } else if err != nil { 40 | log.Fatal(err) 41 | } 42 | 43 | fmt.Printf("%+v\n%+v\n", createdGosamples, createdGolang) 44 | 45 | fmt.Println("3. GET RECORD BY NAME") 46 | gotGosamples, err := websiteRepository.GetByName(ctx, "GOSAMPLES") 47 | if errors.Is(err, website.ErrNotExist) { 48 | log.Println("record: GOSAMPLES does not exist in the repository") 49 | } else if err != nil { 50 | log.Fatal(err) 51 | } 52 | 53 | fmt.Printf("%+v\n", gotGosamples) 54 | 55 | fmt.Println("4. UPDATE RECORD") 56 | createdGosamples.Rank = 1 57 | if _, err := websiteRepository.Update(ctx, createdGosamples.ID, *createdGosamples); err != nil { 58 | if errors.Is(err, website.ErrDuplicate) { 59 | fmt.Printf("record: %+v already exists\n", createdGosamples) 60 | } else if errors.Is(err, website.ErrUpdateFailed) { 61 | fmt.Printf("update of record: %+v failed", createdGolang) 62 | } else { 63 | log.Fatal(err) 64 | } 65 | } 66 | 67 | fmt.Println("5. GET ALL") 68 | all, err := websiteRepository.All(ctx) 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | 73 | for _, website := range all { 74 | fmt.Printf("%+v\n", website) 75 | } 76 | 77 | fmt.Println("6. DELETE RECORD") 78 | if err := websiteRepository.Delete(ctx, createdGolang.ID); err != nil { 79 | if errors.Is(err, website.ErrDeleteFailed) { 80 | fmt.Printf("delete of record: %d failed", createdGolang.ID) 81 | } else { 82 | log.Fatal(err) 83 | } 84 | } 85 | 86 | fmt.Println("7. GET ALL") 87 | all, err = websiteRepository.All(ctx) 88 | if err != nil { 89 | log.Fatal(err) 90 | } 91 | for _, website := range all { 92 | fmt.Printf("%+v\n", website) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /postgresql-intro/cmd/classic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "log" 7 | "postgresql-intro/app" 8 | "postgresql-intro/website" 9 | "time" 10 | 11 | _ "github.com/jackc/pgx/v4/stdlib" 12 | ) 13 | 14 | func main() { 15 | db, err := sql.Open("pgx", "postgres://postgres:mysecretpassword@localhost:5432/website") 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | defer db.Close() 20 | 21 | websiteRepository := website.NewPostgreSQLClassicRepository(db) 22 | 23 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 24 | defer cancel() 25 | 26 | app.RunRepositoryDemo(ctx, websiteRepository) 27 | } 28 | -------------------------------------------------------------------------------- /postgresql-intro/cmd/gorm/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "postgresql-intro/app" 7 | "postgresql-intro/website" 8 | "time" 9 | 10 | "gorm.io/driver/postgres" 11 | "gorm.io/gorm" 12 | ) 13 | 14 | func main() { 15 | gormDB, err := gorm.Open(postgres.Open("postgres://postgres:mysecretpassword@localhost:5432/website")) 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | 20 | websiteRepository := website.NewPostgreSQLGORMRepository(gormDB) 21 | 22 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 23 | defer cancel() 24 | 25 | app.RunRepositoryDemo(ctx, websiteRepository) 26 | } 27 | -------------------------------------------------------------------------------- /postgresql-intro/cmd/pgx/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "postgresql-intro/app" 7 | "postgresql-intro/website" 8 | "time" 9 | 10 | "github.com/jackc/pgx/v4/pgxpool" 11 | ) 12 | 13 | func main() { 14 | dbpool, err := pgxpool.Connect(context.Background(), "postgres://postgres:mysecretpassword@localhost:5432/website") 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | defer dbpool.Close() 19 | 20 | websiteRepository := website.NewPostgreSQLPGXRepository(dbpool) 21 | 22 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 23 | defer cancel() 24 | 25 | app.RunRepositoryDemo(ctx, websiteRepository) 26 | } 27 | -------------------------------------------------------------------------------- /postgresql-intro/go.mod: -------------------------------------------------------------------------------- 1 | module postgresql-intro 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect 7 | github.com/jackc/pgconn v1.12.1 // indirect 8 | github.com/jackc/pgio v1.0.0 // indirect 9 | github.com/jackc/pgpassfile v1.0.0 // indirect 10 | github.com/jackc/pgproto3/v2 v2.3.0 // indirect 11 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect 12 | github.com/jackc/pgtype v1.11.0 // indirect 13 | github.com/jackc/pgx/v4 v4.16.1 // indirect 14 | github.com/jackc/puddle v1.2.1 // indirect 15 | github.com/jinzhu/inflection v1.0.0 // indirect 16 | github.com/jinzhu/now v1.1.4 // indirect 17 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect 18 | golang.org/x/text v0.3.7 // indirect 19 | gorm.io/driver/postgres v1.3.8 // indirect 20 | gorm.io/gorm v1.23.7 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /postgresql-intro/go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 3 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 4 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 5 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 6 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 10 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 11 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 12 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 13 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 14 | github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= 15 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= 16 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 17 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= 18 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 19 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= 20 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= 21 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= 22 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= 23 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= 24 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= 25 | github.com/jackc/pgconn v1.12.1 h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8= 26 | github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono= 27 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= 28 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= 29 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= 30 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= 31 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= 32 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 33 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 34 | github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= 35 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= 36 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= 37 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= 38 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 39 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 40 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 41 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 42 | github.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y= 43 | github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 44 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= 45 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= 46 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= 47 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= 48 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= 49 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= 50 | github.com/jackc/pgtype v1.11.0 h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs= 51 | github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= 52 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= 53 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= 54 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= 55 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= 56 | github.com/jackc/pgx/v4 v4.16.1 h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y= 57 | github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ= 58 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 59 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 60 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 61 | github.com/jackc/puddle v1.2.1 h1:gI8os0wpRXFd4FiAY2dWiqRK037tjj3t7rKFeO4X5iw= 62 | github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 63 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 64 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 65 | github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas= 66 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 67 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 68 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 69 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 70 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 71 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 72 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 73 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 74 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 75 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 76 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 77 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 78 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 79 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 80 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 81 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 82 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 83 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 84 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 85 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 86 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 87 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= 88 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= 89 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 90 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= 91 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 92 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 93 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 94 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 95 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 96 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 97 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 98 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 99 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 100 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 101 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 102 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= 103 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 104 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 105 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 106 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 107 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 108 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 109 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 110 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 111 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 112 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 113 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 114 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 115 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 116 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 117 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 118 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 119 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 120 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 121 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 122 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= 123 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 124 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= 125 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 126 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 127 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 128 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 129 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 130 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 131 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 132 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 133 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 134 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 135 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 136 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 137 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 138 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 139 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 140 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 141 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 142 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 143 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 144 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 145 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 146 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 147 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 148 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 149 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 150 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 151 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 152 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 153 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 154 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 155 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 156 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 157 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 158 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 159 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 160 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 161 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 162 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 163 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 164 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 165 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 166 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 167 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 168 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 169 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 170 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 171 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 172 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= 173 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 174 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 175 | gorm.io/driver/postgres v1.3.8 h1:8bEphSAB69t3odsCR4NDzt581iZEWQuRM27Cg6KgfPY= 176 | gorm.io/driver/postgres v1.3.8/go.mod h1:qB98Aj6AhRO/oyu/jmZsi/YM9g6UzVCjMxO/6frFvcA= 177 | gorm.io/gorm v1.23.6/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 178 | gorm.io/gorm v1.23.7 h1:ww+9Mu5WwHKDSOQZFC4ipu/sgpKMr9EtrJ0uwBqNtB0= 179 | gorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 180 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 181 | -------------------------------------------------------------------------------- /postgresql-intro/website/repository.go: -------------------------------------------------------------------------------- 1 | package website 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | ) 7 | 8 | var ( 9 | ErrDuplicate = errors.New("record already exists") 10 | ErrNotExist = errors.New("row does not exist") 11 | ErrUpdateFailed = errors.New("update failed") 12 | ErrDeleteFailed = errors.New("delete failed") 13 | ) 14 | 15 | type Repository interface { 16 | Migrate(ctx context.Context) error 17 | Create(ctx context.Context, website Website) (*Website, error) 18 | All(ctx context.Context) ([]Website, error) 19 | GetByName(ctx context.Context, name string) (*Website, error) 20 | Update(ctx context.Context, id int64, updated Website) (*Website, error) 21 | Delete(ctx context.Context, id int64) error 22 | } 23 | -------------------------------------------------------------------------------- /postgresql-intro/website/repository_postgresql_classic.go: -------------------------------------------------------------------------------- 1 | package website 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "errors" 7 | 8 | "github.com/jackc/pgconn" 9 | ) 10 | 11 | type PostgreSQLClassicRepository struct { 12 | db *sql.DB 13 | } 14 | 15 | func NewPostgreSQLClassicRepository(db *sql.DB) *PostgreSQLClassicRepository { 16 | return &PostgreSQLClassicRepository{ 17 | db: db, 18 | } 19 | } 20 | 21 | func (r *PostgreSQLClassicRepository) Migrate(ctx context.Context) error { 22 | query := ` 23 | CREATE TABLE IF NOT EXISTS websites( 24 | id SERIAL PRIMARY KEY, 25 | name TEXT NOT NULL UNIQUE, 26 | url TEXT NOT NULL, 27 | rank INT NOT NULL 28 | ); 29 | ` 30 | 31 | _, err := r.db.ExecContext(ctx, query) 32 | return err 33 | } 34 | 35 | func (r *PostgreSQLClassicRepository) Create(ctx context.Context, website Website) (*Website, error) { 36 | var id int64 37 | err := r.db.QueryRowContext(ctx, "INSERT INTO websites(name, url, rank) values($1, $2, $3) RETURNING id", website.Name, website.URL, website.Rank).Scan(&id) 38 | if err != nil { 39 | var pgxError *pgconn.PgError 40 | if errors.As(err, &pgxError) { 41 | if pgxError.Code == "23505" { 42 | return nil, ErrDuplicate 43 | } 44 | } 45 | return nil, err 46 | } 47 | website.ID = id 48 | 49 | return &website, nil 50 | } 51 | 52 | func (r *PostgreSQLClassicRepository) All(ctx context.Context) ([]Website, error) { 53 | rows, err := r.db.QueryContext(ctx, "SELECT * FROM websites") 54 | if err != nil { 55 | return nil, err 56 | } 57 | defer rows.Close() 58 | 59 | var all []Website 60 | for rows.Next() { 61 | var website Website 62 | if err := rows.Scan(&website.ID, &website.Name, &website.URL, &website.Rank); err != nil { 63 | return nil, err 64 | } 65 | all = append(all, website) 66 | } 67 | return all, nil 68 | } 69 | 70 | func (r *PostgreSQLClassicRepository) GetByName(ctx context.Context, name string) (*Website, error) { 71 | row := r.db.QueryRowContext(ctx, "SELECT * FROM websites WHERE name = $1", name) 72 | 73 | var website Website 74 | if err := row.Scan(&website.ID, &website.Name, &website.URL, &website.Rank); err != nil { 75 | if errors.Is(err, sql.ErrNoRows) { 76 | return nil, ErrNotExist 77 | } 78 | return nil, err 79 | } 80 | return &website, nil 81 | } 82 | 83 | func (r *PostgreSQLClassicRepository) Update(ctx context.Context, id int64, updated Website) (*Website, error) { 84 | res, err := r.db.ExecContext(ctx, "UPDATE websites SET name = $1, url = $2, rank = $3 WHERE id = $4", updated.Name, updated.URL, updated.Rank, id) 85 | if err != nil { 86 | var pgxError *pgconn.PgError 87 | if errors.As(err, &pgxError) { 88 | if pgxError.Code == "23505" { 89 | return nil, ErrDuplicate 90 | } 91 | } 92 | return nil, err 93 | } 94 | 95 | rowsAffected, err := res.RowsAffected() 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | if rowsAffected == 0 { 101 | return nil, ErrUpdateFailed 102 | } 103 | 104 | return &updated, nil 105 | } 106 | 107 | func (r *PostgreSQLClassicRepository) Delete(ctx context.Context, id int64) error { 108 | res, err := r.db.ExecContext(ctx, "DELETE FROM websites WHERE id = $1", id) 109 | if err != nil { 110 | return err 111 | } 112 | 113 | rowsAffected, err := res.RowsAffected() 114 | if err != nil { 115 | return err 116 | } 117 | 118 | if rowsAffected == 0 { 119 | return ErrDeleteFailed 120 | } 121 | 122 | return err 123 | } 124 | -------------------------------------------------------------------------------- /postgresql-intro/website/repository_postgresql_gorm.go: -------------------------------------------------------------------------------- 1 | package website 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/jackc/pgconn" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | type gormWebsite struct { 12 | ID int64 `gorm:"primary_key"` 13 | Name string `gorm:"uniqueIndex;not null"` 14 | URL string `gorm:"not null"` 15 | Rank int64 `gorm:"not null"` 16 | } 17 | 18 | func (gormWebsite) TableName() string { 19 | return "websites" 20 | } 21 | 22 | type PostgreSQLGORMRepository struct { 23 | db *gorm.DB 24 | } 25 | 26 | func NewPostgreSQLGORMRepository(db *gorm.DB) *PostgreSQLGORMRepository { 27 | return &PostgreSQLGORMRepository{ 28 | db: db, 29 | } 30 | } 31 | 32 | func (r *PostgreSQLGORMRepository) Migrate(ctx context.Context) error { 33 | m := &gormWebsite{} 34 | return r.db.WithContext(ctx).AutoMigrate(&m) 35 | } 36 | 37 | func (r *PostgreSQLGORMRepository) Create(ctx context.Context, website Website) (*Website, error) { 38 | gormWebsite := gormWebsite{ 39 | Name: website.Name, 40 | URL: website.URL, 41 | Rank: website.Rank, 42 | } 43 | 44 | if err := r.db.WithContext(ctx).Create(&gormWebsite).Error; err != nil { 45 | var pgxError *pgconn.PgError 46 | if errors.As(err, &pgxError) { 47 | if pgxError.Code == "23505" { 48 | return nil, ErrDuplicate 49 | } 50 | } 51 | return nil, err 52 | } 53 | 54 | result := Website(gormWebsite) 55 | 56 | return &result, nil 57 | } 58 | 59 | func (r *PostgreSQLGORMRepository) All(ctx context.Context) ([]Website, error) { 60 | var gormWebsites []gormWebsite 61 | if err := r.db.WithContext(ctx).Find(&gormWebsites).Error; err != nil { 62 | return nil, err 63 | } 64 | 65 | var result []Website 66 | for _, gw := range gormWebsites { 67 | result = append(result, Website(gw)) 68 | } 69 | return result, nil 70 | } 71 | 72 | func (r *PostgreSQLGORMRepository) GetByName(ctx context.Context, name string) (*Website, error) { 73 | var gormWebsite gormWebsite 74 | if err := r.db.WithContext(ctx).Where("name = ?", name).Find(&gormWebsite).Error; err != nil { 75 | if errors.Is(err, gorm.ErrRecordNotFound) { 76 | return nil, ErrNotExist 77 | } 78 | return nil, err 79 | } 80 | website := Website(gormWebsite) 81 | return &website, nil 82 | } 83 | 84 | func (r *PostgreSQLGORMRepository) Update(ctx context.Context, id int64, updated Website) (*Website, error) { 85 | gormWebsite := Website(updated) 86 | updateRes := r.db.WithContext(ctx).Where("id = ?", id).Save(&gormWebsite) 87 | if err := updateRes.Error; err != nil { 88 | var pgxError *pgconn.PgError 89 | if errors.As(err, &pgxError) { 90 | if pgxError.Code == "23505" { 91 | return nil, ErrDuplicate 92 | } 93 | } 94 | return nil, err 95 | } 96 | 97 | rowsAffected := updateRes.RowsAffected 98 | if rowsAffected == 0 { 99 | return nil, ErrUpdateFailed 100 | } 101 | 102 | return &updated, nil 103 | } 104 | 105 | func (r *PostgreSQLGORMRepository) Delete(ctx context.Context, id int64) error { 106 | deleteRes := r.db.WithContext(ctx).Delete(&gormWebsite{}, id) 107 | if err := deleteRes.Error; err != nil { 108 | return err 109 | } 110 | 111 | if deleteRes.RowsAffected == 0 { 112 | return ErrDeleteFailed 113 | } 114 | 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /postgresql-intro/website/repository_postgresql_pgx.go: -------------------------------------------------------------------------------- 1 | package website 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "errors" 7 | 8 | "github.com/jackc/pgconn" 9 | "github.com/jackc/pgx/v4/pgxpool" 10 | ) 11 | 12 | type PostgreSQLPGXRepository struct { 13 | db *pgxpool.Pool 14 | } 15 | 16 | func NewPostgreSQLPGXRepository(db *pgxpool.Pool) *PostgreSQLPGXRepository { 17 | return &PostgreSQLPGXRepository{ 18 | db: db, 19 | } 20 | } 21 | 22 | func (r *PostgreSQLPGXRepository) Migrate(ctx context.Context) error { 23 | query := ` 24 | CREATE TABLE IF NOT EXISTS websites( 25 | id SERIAL PRIMARY KEY, 26 | name TEXT NOT NULL UNIQUE, 27 | url TEXT NOT NULL, 28 | rank INT NOT NULL 29 | ); 30 | ` 31 | 32 | _, err := r.db.Exec(ctx, query) 33 | return err 34 | } 35 | 36 | func (r *PostgreSQLPGXRepository) Create(ctx context.Context, website Website) (*Website, error) { 37 | var id int64 38 | err := r.db.QueryRow(ctx, "INSERT INTO websites(name, url, rank) values($1, $2, $3) RETURNING id", website.Name, website.URL, website.Rank).Scan(&id) 39 | if err != nil { 40 | var pgxError *pgconn.PgError 41 | if errors.As(err, &pgxError) { 42 | if pgxError.Code == "23505" { 43 | return nil, ErrDuplicate 44 | } 45 | } 46 | return nil, err 47 | } 48 | website.ID = id 49 | 50 | return &website, nil 51 | } 52 | 53 | func (r *PostgreSQLPGXRepository) All(ctx context.Context) ([]Website, error) { 54 | rows, err := r.db.Query(ctx, "SELECT * FROM websites") 55 | if err != nil { 56 | return nil, err 57 | } 58 | defer rows.Close() 59 | 60 | var all []Website 61 | for rows.Next() { 62 | var website Website 63 | if err := rows.Scan(&website.ID, &website.Name, &website.URL, &website.Rank); err != nil { 64 | return nil, err 65 | } 66 | all = append(all, website) 67 | } 68 | return all, nil 69 | } 70 | 71 | func (r *PostgreSQLPGXRepository) GetByName(ctx context.Context, name string) (*Website, error) { 72 | row := r.db.QueryRow(ctx, "SELECT * FROM websites WHERE name = $1", name) 73 | 74 | var website Website 75 | if err := row.Scan(&website.ID, &website.Name, &website.URL, &website.Rank); err != nil { 76 | if errors.Is(err, sql.ErrNoRows) { 77 | return nil, ErrNotExist 78 | } 79 | return nil, err 80 | } 81 | return &website, nil 82 | } 83 | 84 | func (r *PostgreSQLPGXRepository) Update(ctx context.Context, id int64, updated Website) (*Website, error) { 85 | res, err := r.db.Exec(ctx, "UPDATE websites SET name = $1, url = $2, rank = $3 WHERE id = $4", updated.Name, updated.URL, updated.Rank, id) 86 | if err != nil { 87 | var pgxError *pgconn.PgError 88 | if errors.As(err, &pgxError) { 89 | if pgxError.Code == "23505" { 90 | return nil, ErrDuplicate 91 | } 92 | } 93 | return nil, err 94 | } 95 | 96 | rowsAffected := res.RowsAffected() 97 | if rowsAffected == 0 { 98 | return nil, ErrUpdateFailed 99 | } 100 | 101 | return &updated, nil 102 | } 103 | 104 | func (r *PostgreSQLPGXRepository) Delete(ctx context.Context, id int64) error { 105 | res, err := r.db.Exec(ctx, "DELETE FROM websites WHERE id = $1", id) 106 | if err != nil { 107 | return err 108 | } 109 | 110 | rowsAffected := res.RowsAffected() 111 | if rowsAffected == 0 { 112 | return ErrDeleteFailed 113 | } 114 | 115 | return err 116 | } 117 | -------------------------------------------------------------------------------- /postgresql-intro/website/website.go: -------------------------------------------------------------------------------- 1 | package website 2 | 3 | type Website struct { 4 | ID int64 5 | Name string 6 | URL string 7 | Rank int64 8 | } 9 | -------------------------------------------------------------------------------- /sqlite-intro/README.md: -------------------------------------------------------------------------------- 1 | # Introduction to SQLite in Go 2 | 3 | A sample project that demonstrates how to use [SQLite](www.sqlite.org) database in Go using [go-sqlite3](https://github.com/mattn/go-sqlite3) driver. 4 | 5 | See more at https://gosamples.dev/sqlite-intro -------------------------------------------------------------------------------- /sqlite-intro/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gosamples-dev/samples/sqlite-intro 2 | 3 | go 1.17 4 | 5 | require github.com/mattn/go-sqlite3 v1.14.9 6 | -------------------------------------------------------------------------------- /sqlite-intro/go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA= 2 | github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 3 | -------------------------------------------------------------------------------- /sqlite-intro/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/gosamples-dev/samples/sqlite-intro/website" 10 | _ "github.com/mattn/go-sqlite3" 11 | ) 12 | 13 | const fileName = "sqlite.db" 14 | 15 | func main() { 16 | os.Remove(fileName) 17 | 18 | db, err := sql.Open("sqlite3", fileName) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | 23 | websiteRepository := website.NewSQLiteRepository(db) 24 | 25 | if err := websiteRepository.Migrate(); err != nil { 26 | log.Fatal(err) 27 | } 28 | 29 | gosamples := website.Website{ 30 | Name: "GOSAMPLES", 31 | URL: "https://gosamples.dev", 32 | Rank: 2, 33 | } 34 | golang := website.Website{ 35 | Name: "Golang official website", 36 | URL: "https://golang.org", 37 | Rank: 1, 38 | } 39 | 40 | createdGosamples, err := websiteRepository.Create(gosamples) 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | createdGolang, err := websiteRepository.Create(golang) 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | 49 | gotGosamples, err := websiteRepository.GetByName("GOSAMPLES") 50 | if err != nil { 51 | log.Fatal(err) 52 | } 53 | 54 | fmt.Printf("get by name: %+v\n", gotGosamples) 55 | 56 | createdGosamples.Rank = 1 57 | if _, err := websiteRepository.Update(createdGosamples.ID, *createdGosamples); err != nil { 58 | log.Fatal(err) 59 | } 60 | 61 | all, err := websiteRepository.All() 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | 66 | fmt.Printf("\nAll websites:\n") 67 | for _, website := range all { 68 | fmt.Printf("website: %+v\n", website) 69 | } 70 | 71 | if err := websiteRepository.Delete(createdGolang.ID); err != nil { 72 | log.Fatal(err) 73 | } 74 | 75 | all, err = websiteRepository.All() 76 | if err != nil { 77 | log.Fatal(err) 78 | } 79 | fmt.Printf("\nAll websites:\n") 80 | for _, website := range all { 81 | fmt.Printf("website: %+v\n", website) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /sqlite-intro/website/sqlite_repository.go: -------------------------------------------------------------------------------- 1 | package website 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | 7 | "github.com/mattn/go-sqlite3" 8 | ) 9 | 10 | var ( 11 | ErrDuplicate = errors.New("record already exists") 12 | ErrNotExists = errors.New("row not exists") 13 | ErrUpdateFailed = errors.New("update failed") 14 | ErrDeleteFailed = errors.New("delete failed") 15 | ) 16 | 17 | type SQLiteRepository struct { 18 | db *sql.DB 19 | } 20 | 21 | func NewSQLiteRepository(db *sql.DB) *SQLiteRepository { 22 | return &SQLiteRepository{ 23 | db: db, 24 | } 25 | } 26 | 27 | func (r *SQLiteRepository) Migrate() error { 28 | query := ` 29 | CREATE TABLE IF NOT EXISTS websites( 30 | id INTEGER PRIMARY KEY AUTOINCREMENT, 31 | name TEXT NOT NULL UNIQUE, 32 | url TEXT NOT NULL, 33 | rank INTEGER NOT NULL 34 | ); 35 | ` 36 | 37 | _, err := r.db.Exec(query) 38 | return err 39 | } 40 | 41 | func (r *SQLiteRepository) Create(website Website) (*Website, error) { 42 | res, err := r.db.Exec("INSERT INTO websites(name, url, rank) values(?,?,?)", website.Name, website.URL, website.Rank) 43 | if err != nil { 44 | var sqliteErr sqlite3.Error 45 | if errors.As(err, &sqliteErr) { 46 | if errors.Is(sqliteErr.ExtendedCode, sqlite3.ErrConstraintUnique) { 47 | return nil, ErrDuplicate 48 | } 49 | } 50 | return nil, err 51 | } 52 | 53 | id, err := res.LastInsertId() 54 | if err != nil { 55 | return nil, err 56 | } 57 | website.ID = id 58 | 59 | return &website, nil 60 | } 61 | 62 | func (r *SQLiteRepository) All() ([]Website, error) { 63 | rows, err := r.db.Query("SELECT * FROM websites") 64 | if err != nil { 65 | return nil, err 66 | } 67 | defer rows.Close() 68 | 69 | var all []Website 70 | for rows.Next() { 71 | var website Website 72 | if err := rows.Scan(&website.ID, &website.Name, &website.URL, &website.Rank); err != nil { 73 | return nil, err 74 | } 75 | all = append(all, website) 76 | } 77 | return all, nil 78 | } 79 | 80 | func (r *SQLiteRepository) GetByName(name string) (*Website, error) { 81 | row := r.db.QueryRow("SELECT * FROM websites WHERE name = ?", name) 82 | 83 | var website Website 84 | if err := row.Scan(&website.ID, &website.Name, &website.URL, &website.Rank); err != nil { 85 | if errors.Is(err, sql.ErrNoRows) { 86 | return nil, ErrNotExists 87 | } 88 | return nil, err 89 | } 90 | return &website, nil 91 | } 92 | 93 | func (r *SQLiteRepository) Update(id int64, updated Website) (*Website, error) { 94 | if id == 0 { 95 | return nil, errors.New("invalid updated ID") 96 | } 97 | res, err := r.db.Exec("UPDATE websites SET name = ?, url = ?, rank = ? WHERE id = ?", updated.Name, updated.URL, updated.Rank, id) 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | rowsAffected, err := res.RowsAffected() 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | if rowsAffected == 0 { 108 | return nil, ErrUpdateFailed 109 | } 110 | 111 | return &updated, nil 112 | } 113 | 114 | func (r *SQLiteRepository) Delete(id int64) error { 115 | res, err := r.db.Exec("DELETE FROM websites WHERE id = ?", id) 116 | if err != nil { 117 | return err 118 | } 119 | 120 | rowsAffected, err := res.RowsAffected() 121 | if err != nil { 122 | return err 123 | } 124 | 125 | if rowsAffected == 0 { 126 | return ErrDeleteFailed 127 | } 128 | 129 | return err 130 | } 131 | -------------------------------------------------------------------------------- /sqlite-intro/website/website.go: -------------------------------------------------------------------------------- 1 | package website 2 | 3 | type Website struct { 4 | ID int64 5 | Name string 6 | URL string 7 | Rank int64 8 | } 9 | --------------------------------------------------------------------------------