Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
shiyuhang0 committed Sep 28, 2023
0 parents commit 9149ab0
Show file tree
Hide file tree
Showing 11 changed files with 377 additions and 0 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: tests

on:
pull_request:
branches: [ master ]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: tidbcloud/wait-for-tidbcloud-branch@v0
id: wait-for-branch
with:
token: ${{ secrets.GITHUB_TOKEN }}
public-key: ${{ secrets.TIDB_CLOUD_PUBLIC_KEY }}
private-key: ${{ secrets.TIDB_CLOUD_PRIVATE_KEY }}
timeout-seconds: 600

- name: Set up Go 1.19
uses: actions/setup-go@v2
with:
go-version: 1.19

- name: go mod pakcage cache
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ubuntu-latest-go-1.19-${{ hashFiles('go.mod') }}

- name: Tests
run: |
GORM_ENABLE_CACHE=true GORM_DIALECT=tidb GORM_DSN="${{ steps.wait-for-branch.outputs.username }}:${{ steps.wait-for-branch.outputs.password }}@tcp(${{ steps.wait-for-branch.outputs.host }}:${{ steps.wait-for-branch.outputs.port }})/test?parseTime=true&tls=tidb" ./test.sh
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
gorm
go.sum
*.db
.idea
21 changes: 21 additions & 0 deletions License
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2013-NOW Jinzhu <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# branching-gorm-example

> **Warning:** This repo is only for testing. Please don't use it in production.
This repo is a gorm example of CI/CD workflow powered by the TiDB Serverless branching. From this repo, you can learn:

- How to connect to TiDB Serverless in gorm.
- How to use branching GitHub integration.

## About this repo

This repo is based on [gorm playground](https://github.com/go-gorm/playground), with some changes:

- A tidb dialect is added to the repo to test the TiDB Cloud.
- The [gormigrate](https://github.com/go-gormigrate/gormigrate) is used in `RunMigrations` to help migration.
- Delete some useless files like GitHub actions, docker-compose, etc.

## Connect to TiDB Serverless in gorm

> Make sure you have installed the go environment.
1. clone the code

```
git clone [email protected]:shiyuhang0/branching-gorm-example.git
cd branching-gorm-example
```

2. Fill in the following environment variable. You can find the information in the TiDB Serverless console.

```
export GORM_ENABLE_CACHE=true
export GORM_DIALECT=tidb
export GORM_DSN="${{ username }}:${{ password }}@tcp(${{ host }}:${{ port }})/test?parseTime=true&tls=tidb"
```

3. Connect to the TiDB Serverless (migration will be executed automatically)

```
./test.sh
```

## Use branching GitHub integration

This repo has been connected to a TiDB Serverless using the [Branching GitHub integration](https://docs.pingcap.com/tidbcloud/branch-github-integration). This brings database branches to your GitHub workflows, and a TiDB Cloud App will automatically manage database branches for you in the pull request.

**CI workflow**

The repo has a [Test GitHub Action](./.github/workflows/tests.yml) to run the test on the created TiDB Serverless branch. This action uses the [wait-for-tidbcloud-branch](https://github.com/tidbcloud/wait-for-tidbcloud-branch) to get branch connection information and pass it by environment variables. We can do it because the repo accepts the `GORM_DSN` environment variable as connection information. See the [code](https://github.com/shiyuhang0/branching-gorm-example/blob/9639f553418456fd1ebb1d933923fba131c98b6b/db.go#L52) for more details.

Check the [pull request](https://github.com/shiyuhang0/branching-gorm-example/pulls) to see how we use the CI workflow!

**CD workflow**

The CD workflow works well with native frameworks.

Take DDL as an example, you can use the [gormigrate](https://github.com/go-gormigrate/gormigrate) to manage your database migrations. Any DDL changes can be applied to the production cluster when the PR is merged. Don't worry about the influence of production business, the TiDB Serverless cluster supports online DDL without blocking your business.

127 changes: 127 additions & 0 deletions db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package main

import (
"crypto/tls"
"errors"
"github.com/go-gormigrate/gormigrate/v2"
mysql2 "github.com/go-sql-driver/mysql"
"log"
"os"
"path/filepath"
"regexp"

"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/driver/sqlserver"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)

var DB *gorm.DB

func init() {
var err error
if DB, err = OpenTestConnection(); err != nil {
log.Printf("failed to connect database, got error %v\n", err)
os.Exit(1)
} else {
sqlDB, err := DB.DB()
if err == nil {
err = sqlDB.Ping()
}

if err != nil {
log.Printf("failed to connect database, got error %v\n", err)
}

RunMigrations()

if DB.Dialector.Name() == "sqlite" {
DB.Exec("PRAGMA foreign_keys = ON")
}

DB.Logger = DB.Logger.LogMode(logger.Info)
}
}

func OpenTestConnection() (db *gorm.DB, err error) {
dbDSN := os.Getenv("GORM_DSN")
switch os.Getenv("GORM_DIALECT") {
case "tidb":
log.Println("testing tidb...")
if dbDSN == "" {
dbDSN = "gorm:gorm@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True&loc=Local"
}

re := regexp.MustCompile(`(?m)tcp\((\S+):\d+\)`)
match := re.FindStringSubmatch(dbDSN)

if len(match) <= 1 {
return nil, errors.New("invaild dsn")
}

mysql2.RegisterTLSConfig("tidb", &tls.Config{
MinVersion: tls.VersionTLS12,
ServerName: match[1],
})
db, err = gorm.Open(mysql.Open(dbDSN), &gorm.Config{})
case "mysql":
log.Println("testing mysql...")
if dbDSN == "" {
dbDSN = "gorm:gorm@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True&loc=Local"
}
db, err = gorm.Open(mysql.Open(dbDSN), &gorm.Config{})
case "postgres":
log.Println("testing postgres...")
if dbDSN == "" {
dbDSN = "user=gorm password=gorm host=localhost dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai"
}
db, err = gorm.Open(postgres.Open(dbDSN), &gorm.Config{})
case "sqlserver":
// CREATE LOGIN gorm WITH PASSWORD = 'LoremIpsum86';
// CREATE DATABASE gorm;
// USE gorm;
// CREATE USER gorm FROM LOGIN gorm;
// sp_changedbowner 'gorm';
log.Println("testing sqlserver...")
if dbDSN == "" {
dbDSN = "sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm"
}
db, err = gorm.Open(sqlserver.Open(dbDSN), &gorm.Config{})
default:
log.Println("testing sqlite3...")
db, err = gorm.Open(sqlite.Open(filepath.Join(os.TempDir(), "gorm.db")), &gorm.Config{})
}

if debug := os.Getenv("DEBUG"); debug == "true" {
db.Logger = db.Logger.LogMode(logger.Info)
} else if debug == "false" {
db.Logger = db.Logger.LogMode(logger.Silent)
}

return
}

func RunMigrations() {
var err error
allModels := []interface{}{&User{}, &Account{}, &Pet{}, &Company{}, &Toy{}, &Language{}}
m := gormigrate.New(DB, gormigrate.DefaultOptions, []*gormigrate.Migration{{
// create tables
ID: "202307101821",
Migrate: func(tx *gorm.DB) error {
if err = DB.AutoMigrate(allModels...); err != nil {
log.Printf("Failed to auto migrate, but got error %v\n", err)
return err
}
return nil
},
}})

if err = m.Migrate(); err != nil {
log.Fatalf("Migration failed: %v", err)
os.Exit(1)
}
log.Println("Migration did run successfully")

}
18 changes: 18 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module gorm.io/playground

go 1.16

require (
github.com/go-gormigrate/gormigrate/v2 v2.1.0
github.com/go-sql-driver/mysql v1.7.1
github.com/jackc/pgx/v5 v5.4.3 // indirect
github.com/microsoft/go-mssqldb v1.6.0 // indirect
golang.org/x/crypto v0.13.0 // indirect
gorm.io/driver/mysql v1.5.1
gorm.io/driver/postgres v1.5.2
gorm.io/driver/sqlite v1.5.3
gorm.io/driver/sqlserver v1.5.1
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55
)

replace gorm.io/gorm => ./gorm
7 changes: 7 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "fmt"

func main() {
fmt.Println("vim-go")
}
20 changes: 20 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import (
"testing"
)

// GORM_REPO: https://github.com/go-gorm/gorm.git
// GORM_BRANCH: master
// TEST_DRIVERS: sqlite, mysql, postgres, sqlserver, tidb

func TestGORM(t *testing.T) {
user := User{Name: "jinzhu"}

DB.Create(&user)

var result User
if err := DB.First(&result, user.ID).Error; err != nil {
t.Errorf("Failed, got error: %v", err)
}
}
60 changes: 60 additions & 0 deletions models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package main

import (
"database/sql"
"time"

"gorm.io/gorm"
)

// User has one `Account` (has one), many `Pets` (has many) and `Toys` (has many - polymorphic)
// He works in a Company (belongs to), he has a Manager (belongs to - single-table), and also managed a Team (has many - single-table)
// He speaks many languages (many to many) and has many friends (many to many - single-table)
// His pet also has one Toy (has one - polymorphic)
type User struct {
gorm.Model
Name string
Age uint
Birthday *time.Time
Account Account
Pets []*Pet
Toys []Toy `gorm:"polymorphic:Owner"`
CompanyID *int
Company Company
ManagerID *uint
Manager *User
Team []User `gorm:"foreignkey:ManagerID"`
Languages []Language `gorm:"many2many:UserSpeak"`
Friends []*User `gorm:"many2many:user_friends"`
Active bool
}

type Account struct {
gorm.Model
UserID sql.NullInt64
Number string
}

type Pet struct {
gorm.Model
UserID *uint
Name string
Toy Toy `gorm:"polymorphic:Owner;"`
}

type Toy struct {
gorm.Model
Name string
OwnerID string
OwnerType string
}

type Company struct {
ID int
Name string
}

type Language struct {
Code string `gorm:"primarykey"`
Name string
}
25 changes: 25 additions & 0 deletions test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/bash -e

dialects=("sqlite" "mysql" "postgres" "sqlserver" "tidb")

if [ "$GORM_ENABLE_CACHE" = "" ]
then
rm -rf gorm
fi

[ -d gorm ] || (echo "git clone --depth 1 -b $(cat main_test.go | grep GORM_BRANCH | awk '{print $3}') $(cat main_test.go | grep GORM_REPO | awk '{print $3}')"; git clone --depth 1 -b $(cat main_test.go | grep GORM_BRANCH | awk '{print $3}') $(cat main_test.go | grep GORM_REPO | awk '{print $3}'))

go get -u -t ./...

for dialect in "${dialects[@]}" ; do
if [ "$GORM_DIALECT" = "" ] || [ "$GORM_DIALECT" = "${dialect}" ]
then
if [[ $(grep TEST_DRIVER main_test.go) =~ "${dialect}" ]]
then
echo "testing ${dialect}..."
GORM_DIALECT=${dialect} go test -race -count=1 -v ./...
else
echo "skip ${dialect}..."
fi
fi
done
4 changes: 4 additions & 0 deletions tidbcloud.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
github:
branch:
allowList:
- "ci_example"

0 comments on commit 9149ab0

Please sign in to comment.