├── lib
├── config
│ ├── config.go
│ ├── elastic.go
│ ├── redis.go
│ ├── email.go
│ ├── http.go
│ ├── s3.go
│ └── mysql.go
├── life
│ └── life.go
├── util
│ └── util.go
├── testdata
│ ├── schema.go
│ ├── parse.go
│ ├── default.go
│ └── datayml.go
├── divine
│ ├── main
│ │ └── main.go
│ └── divine.go
├── rediskey
│ └── rediskey.go
└── http
│ └── http.go
├── sqlboiler.toml
├── README.md
├── svc
├── app
│ ├── redis.go
│ ├── app.go
│ ├── index.go
│ ├── api.go
│ ├── problem.go
│ └── judge.go
├── redis
│ ├── script.go
│ ├── pipeline.go
│ ├── scan.go
│ ├── redis.go
│ ├── timer.go
│ └── x.go
├── email
│ └── email.go
├── main
│ ├── app
│ │ └── main.go
│ └── migrate
│ │ └── main.go
├── migrate
│ └── migrate.go
└── judge
│ └── judge.go
├── Dockerfile
├── .gitignore
├── .gitlab-ci.yml
├── models
├── boil_queries.go
├── mysql_suites_test.go
├── boil_queries_test.go
├── boil_table_names.go
├── mysql_upsert.go
├── boil_types.go
├── boil_main_test.go
├── mysql_main_test.go
├── boil_suites_test.go
├── file_test.go
├── user_test.go
├── article_test.go
└── contest_test.go
└── go.mod
/lib/config/config.go:
--------------------------------------------------------------------------------
1 | // config gets application configuration and creates service instances from environment variables
2 | package config
3 |
--------------------------------------------------------------------------------
/sqlboiler.toml:
--------------------------------------------------------------------------------
1 | [mysql]
2 | dbname = "syzoj"
3 | host = "172.21.0.11"
4 | port = "3306"
5 | user = "root"
6 | pass = "123456"
7 | sslmode = "false"
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | syzoj-ng
2 | ---
3 |
4 | ## 许可证
5 | 本软件以 [GNU Affero General Public License v3.0](https://choosealicense.com/licenses/agpl-3.0/) 发布,见 [LICENSE](LICENSE).
6 |
--------------------------------------------------------------------------------
/svc/app/redis.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | func (a *App) checkRedis(data interface{}, err error) {
4 | if err != nil {
5 | log.WithError(err).Warning("failure while executing redis command")
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.13
2 | WORKDIR /go/src/github.com/syzoj/syzoj-ng-go/
3 | COPY . .
4 | RUN go get -v github.com/syzoj/syzoj-ng-go/svc/main/app && go get -v github.com/syzoj/syzoj-ng-go/svc/main/migrate
5 |
6 | FROM ubuntu
7 | WORKDIR /app
8 | COPY --from=0 /go/bin/ .
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, build with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | *.swp
15 | vendor
16 |
--------------------------------------------------------------------------------
/lib/config/elastic.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/elastic/go-elasticsearch"
5 | "os"
6 | )
7 |
8 | // Creates an elasticsearch client from environment variables.
9 | func NewElastic(prefix string) (*elasticsearch.Client, error) {
10 | return elasticsearch.NewClient(elasticsearch.Config{
11 | Addresses: []string{os.Getenv(prefix + "ELASTICSEARCH")},
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/lib/life/life.go:
--------------------------------------------------------------------------------
1 | package life
2 |
3 | import (
4 | "context"
5 | "os"
6 | "os/signal"
7 | "syscall"
8 | )
9 |
10 | func SignalContext() context.Context {
11 | ch := make(chan os.Signal, 1)
12 | signal.Notify(ch, os.Interrupt)
13 | signal.Notify(ch, syscall.SIGTERM)
14 | ctx := context.Background()
15 | ctx, cancel := context.WithCancel(ctx)
16 | go func() {
17 | <-ch
18 | cancel()
19 | }()
20 | return ctx
21 | }
22 |
--------------------------------------------------------------------------------
/lib/config/redis.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "os"
5 | "time"
6 |
7 | "github.com/gomodule/redigo/redis"
8 | )
9 |
10 | // Creates a new *github.com/gomodule/redigo/redis.Pool instance from environment variables.
11 | // The environment variable is ${prefix}REDIS_ADDR, in host:port format.
12 | func NewRedis(prefix string) (*redis.Pool, error) {
13 | addr := os.Getenv(prefix + "REDIS_ADDR")
14 | pool := &redis.Pool{
15 | MaxIdle: 3,
16 | IdleTimeout: 240 * time.Second,
17 | Dial: func() (redis.Conn, error) { return redis.Dial("tcp", addr) },
18 | }
19 | return pool, nil
20 | }
21 |
--------------------------------------------------------------------------------
/lib/config/email.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/syzoj/syzoj-ng-go/svc/email"
8 | )
9 |
10 | func NewAliyunEmail(prefix string) (*email.EmailService, error) {
11 | accessKey := os.Getenv(prefix + "ACCESS_KEY_ID")
12 | secret := os.Getenv(prefix + "ACCESS_KEY_SECRET")
13 | accountName := os.Getenv(prefix + "ACCOUNT_NAME")
14 | if accessKey == "" || secret == "" || accountName == "" {
15 | return nil, fmt.Errorf("%sACCESS_KEY_ID or %sACCESS_KEY_SECRET or %sACCOUNT_NAME missing", prefix, prefix, prefix)
16 | }
17 | return email.DefaultEmailService(accessKey, secret, accountName), nil
18 | }
19 |
--------------------------------------------------------------------------------
/lib/config/http.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "net/url"
6 | "os"
7 |
8 | "github.com/sirupsen/logrus"
9 | )
10 |
11 | var log = logrus.StandardLogger()
12 |
13 | // Get listen port for http server from environment variable HTTP_LISTEN_ADDR.
14 | func GetHttpListenAddr() string {
15 | return os.Getenv("HTTP_LISTEN_ADDR")
16 | }
17 |
18 | // Get URL for http endpoint from environment variable HTTP_URL.
19 | func GetHttpURL(prefix string) (*url.URL, error) {
20 | u := os.Getenv(prefix + "HTTP_URL")
21 | if u == "" {
22 | return nil, fmt.Errorf("Environment variable %sHTTP_URL not found", prefix)
23 | }
24 | return url.Parse(u)
25 | }
26 |
--------------------------------------------------------------------------------
/lib/config/s3.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/minio/minio-go"
7 | )
8 |
9 | // Creates an Amazon S3 compatible client from environment variables.
10 | // The environment variables are ${prefix}ENDPOINT, ${prefix}ACCESS_KEY, ${prefix}SECRET_KEY.
11 | // The prefix defaults to "S3_" if not specified.
12 | func NewMinio(prefix string) (*minio.Client, error) {
13 | if prefix == "" {
14 | prefix = "S3_"
15 | }
16 | endpoint := os.Getenv(prefix + "ENDPOINT")
17 | accessKey := os.Getenv(prefix + "ACCESS_KEY")
18 | secretKey := os.Getenv(prefix + "SECRET_KEY")
19 | return minio.New(endpoint, accessKey, secretKey, false)
20 | }
21 |
--------------------------------------------------------------------------------
/lib/util/util.go:
--------------------------------------------------------------------------------
1 | // Utility library.
2 | package util
3 |
4 | import (
5 | "crypto/rand"
6 | "encoding/hex"
7 | "time"
8 | )
9 |
10 | // Creates a random hex string. n is the number of bytes.
11 | func RandomHex(n int) string {
12 | b := make([]byte, n)
13 | _, err := rand.Read(b)
14 | if err != nil {
15 | panic(err)
16 | }
17 | return hex.EncodeToString(b)
18 | }
19 |
20 | // Allocates a string.
21 | func String(s string) *string {
22 | return &s
23 | }
24 |
25 | // Allocates a time.Time.
26 | func Time(t time.Time) *time.Time {
27 | return &t
28 | }
29 |
30 | // Allocates a *int64.
31 | func Int64(v int64) *int64 {
32 | return &v
33 | }
34 |
35 | // Allocates a *int.
36 | func Int(v int) *int {
37 | return &v
38 | }
39 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | # This file is a template, and might need editing before it works on your project.
2 | docker-build-master:
3 | # Official docker image.
4 | image: docker:latest
5 | stage: build
6 | services:
7 | - docker:dind
8 | before_script:
9 | - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
10 | script:
11 | - docker build --pull -t "$CI_REGISTRY_IMAGE" .
12 | - docker push "$CI_REGISTRY_IMAGE"
13 | only:
14 | - master
15 |
16 | docker-build:
17 | # Official docker image.
18 | image: docker:latest
19 | stage: build
20 | services:
21 | - docker:dind
22 | before_script:
23 | - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
24 | script:
25 | - docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" .
26 | - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG"
27 | except:
28 | - master
29 |
30 |
--------------------------------------------------------------------------------
/models/boil_queries.go:
--------------------------------------------------------------------------------
1 | // Code generated by SQLBoiler 3.5.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT.
2 | // This file is meant to be re-generated in place and/or deleted at any time.
3 |
4 | package models
5 |
6 | import (
7 | "github.com/volatiletech/sqlboiler/drivers"
8 | "github.com/volatiletech/sqlboiler/queries"
9 | "github.com/volatiletech/sqlboiler/queries/qm"
10 | )
11 |
12 | var dialect = drivers.Dialect{
13 | LQ: 0x60,
14 | RQ: 0x60,
15 |
16 | UseIndexPlaceholders: false,
17 | UseLastInsertID: true,
18 | UseSchema: false,
19 | UseDefaultKeyword: false,
20 | UseAutoColumns: false,
21 | UseTopClause: false,
22 | UseOutputClause: false,
23 | UseCaseWhenExistsClause: false,
24 | }
25 |
26 | // NewQuery initializes a new Query using the passed in QueryMods
27 | func NewQuery(mods ...qm.QueryMod) *queries.Query {
28 | q := &queries.Query{}
29 | queries.SetDialect(q, &dialect)
30 | qm.Apply(q, mods...)
31 |
32 | return q
33 | }
34 |
--------------------------------------------------------------------------------
/lib/testdata/schema.go:
--------------------------------------------------------------------------------
1 | package testdata
2 |
3 | type TestdataInfo struct {
4 | Cases map[string]*Testcase `json:"cases"`
5 | SpecialJudges map[string]*SpecialJudge `json:"special_judges"`
6 | Languages map[string]*Language `json:"languages"`
7 | Subtasks []*Subtask `json:"subtasks"`
8 | }
9 |
10 | type Testcase struct {
11 | MemoryLimit int64 `json:"memory_limit,omitempty"`
12 | TimeLimit int64 `json:"time_limit,omitempty"`
13 | Input *File `json:"input,omitempty"`
14 | Output *File `json:"output,omitempty"`
15 | Answer string `json:"answer,omitempty"`
16 | SpecialJudge string `json:"special_judge,omitempty"`
17 | }
18 |
19 | type SpecialJudge struct {
20 | Language string `json:"language,omitempty"`
21 | File *File `json:"file,omitempty"`
22 | }
23 |
24 | type Language struct {
25 | }
26 |
27 | type Subtask struct {
28 | Cases []string `json:"cases"`
29 | Score float64 `json:"score"`
30 | }
31 |
32 | type File struct {
33 | Name string `json:"name"`
34 | Sha256Sum string `json:"sha256sum"`
35 | }
36 |
--------------------------------------------------------------------------------
/lib/testdata/parse.go:
--------------------------------------------------------------------------------
1 | package testdata
2 |
3 | import (
4 | "crypto/sha256"
5 | "encoding/hex"
6 | "io"
7 | "os"
8 | "path/filepath"
9 | )
10 |
11 | func ParseTestdata(path string) (*TestdataInfo, error) {
12 | info, err := os.Stat(filepath.Join(path, "data.yml"))
13 | if err == nil && !info.IsDir() {
14 | return ParseDataYml(path)
15 | }
16 | return ParseDefault(path)
17 | }
18 |
19 | func getFile(path string, name string) (*File, error) {
20 | f, err := os.Open(filepath.Join(path, name))
21 | if err != nil {
22 | return nil, err
23 | }
24 | defer f.Close()
25 | hasher := sha256.New()
26 | if _, err := io.Copy(hasher, f); err != nil {
27 | return nil, err
28 | }
29 | hash := hasher.Sum(nil)
30 | return &File{
31 | Name: name,
32 | Sha256Sum: hex.EncodeToString(hash),
33 | }, nil
34 | }
35 |
36 | type fileSet map[string]*File
37 |
38 | func getFileCached(path string, name string, fs fileSet) (*File, error) {
39 | if f, ok := fs[name]; ok {
40 | return f, nil
41 | }
42 | f, err := getFile(path, name)
43 | if err == nil {
44 | fs[name] = f
45 | return f, nil
46 | }
47 | return nil, err
48 | }
49 |
--------------------------------------------------------------------------------
/svc/redis/script.go:
--------------------------------------------------------------------------------
1 | package redis
2 |
3 | import (
4 | "context"
5 | "crypto/sha1"
6 | "encoding/hex"
7 |
8 | "github.com/gomodule/redigo/redis"
9 | )
10 |
11 | // Script wraps a Lua script to be used in Redis.
12 | type Script struct {
13 | Script []byte
14 | SHA1 [20]byte
15 | Hash string
16 | }
17 |
18 | func NewScript(sc []byte) *Script {
19 | sum := sha1.Sum(sc)
20 | return &Script{
21 | Script: sc,
22 | SHA1: sum,
23 | Hash: hex.EncodeToString(sum[:]),
24 | }
25 | }
26 |
27 | // Evaluates a Lua script.
28 | func (r *RedisService) EvalContext(ctx context.Context, sc *Script, keys []string, data []interface{}) (interface{}, error) {
29 | args := make([]interface{}, 2+len(keys)+len(data))
30 | args[0] = sc.Hash
31 | args[1] = len(keys)
32 | for i, key := range keys {
33 | args[i+2] = key
34 | }
35 | for i, dat := range data {
36 | args[i+2+len(keys)] = dat
37 | }
38 | val, err := r.DoContext(ctx, "EVALSHA", args...)
39 | if rerr, ok := err.(redis.Error); ok {
40 | if rerr[0:9] == "NOSCRIPT " {
41 | args[0] = sc.Script
42 | return r.DoContext(ctx, "EVAL", args...)
43 | }
44 | }
45 | return val, err
46 | }
47 |
--------------------------------------------------------------------------------
/models/mysql_suites_test.go:
--------------------------------------------------------------------------------
1 | // Code generated by SQLBoiler 3.5.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT.
2 | // This file is meant to be re-generated in place and/or deleted at any time.
3 |
4 | package models
5 |
6 | import "testing"
7 |
8 | func TestUpsert(t *testing.T) {
9 | t.Run("Articles", testArticlesUpsert)
10 |
11 | t.Run("ArticleComments", testArticleCommentsUpsert)
12 |
13 | t.Run("Contests", testContestsUpsert)
14 |
15 | t.Run("ContestPlayers", testContestPlayersUpsert)
16 |
17 | t.Run("ContestRanklists", testContestRanklistsUpsert)
18 |
19 | t.Run("Files", testFilesUpsert)
20 |
21 | t.Run("FormattedCodes", testFormattedCodesUpsert)
22 |
23 | t.Run("JudgeStates", testJudgeStatesUpsert)
24 |
25 | t.Run("Problems", testProblemsUpsert)
26 |
27 | t.Run("ProblemTags", testProblemTagsUpsert)
28 |
29 | t.Run("ProblemTagMaps", testProblemTagMapsUpsert)
30 |
31 | t.Run("RatingCalculations", testRatingCalculationsUpsert)
32 |
33 | t.Run("RatingHistories", testRatingHistoriesUpsert)
34 |
35 | t.Run("SubmissionStatistics", testSubmissionStatisticsUpsert)
36 |
37 | t.Run("Users", testUsersUpsert)
38 |
39 | t.Run("UserPrivileges", testUserPrivilegesUpsert)
40 | }
41 |
--------------------------------------------------------------------------------
/lib/divine/main/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 |
7 | "github.com/syzoj/syzoj-ng-go/lib/divine"
8 | )
9 |
10 | type items struct {
11 | Items []*divine.Item `json:"items"`
12 | }
13 |
14 | var config = `
15 | {
16 | "items": [
17 | {
18 | "title": "刷题",
19 | "detail": [
20 | "一遍过样例",
21 | null
22 | ]
23 | },
24 | {
25 | "title": "装弱",
26 | "detail": [
27 | "我好菜啊",
28 | "你太强了"
29 | ]
30 | },
31 | {
32 | "title": {
33 | "boy": "搞基",
34 | "girl": "搞姬"
35 | },
36 | "detail": [
37 | "爱上学习",
38 | "会被掰弯"
39 | ]
40 | },
41 | {
42 | "title": "直播写代码",
43 | "detail": [
44 | "月入百万",
45 | "CE, RE and T,身败名裂"
46 | ]
47 | },
48 | {
49 | "title": "学数论",
50 | "detail": [
51 | "思维敏捷",
52 | "咋看都不会"
53 | ]
54 | },
55 | {
56 | "title": "参加模拟赛",
57 | "detail": [
58 | "AK 虐场",
59 | "爆零"
60 | ]
61 | }
62 | ]
63 | }
64 | `
65 |
66 | func main() {
67 | var val items
68 | if err := json.Unmarshal([]byte(config), &val); err != nil {
69 | panic(err)
70 | }
71 | fmt.Printf("%#v\n", val.Items[0])
72 | }
73 |
--------------------------------------------------------------------------------
/svc/email/email.go:
--------------------------------------------------------------------------------
1 | package email
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
7 | "github.com/aliyun/alibaba-cloud-sdk-go/services/dm"
8 | )
9 |
10 | type EmailService struct {
11 | cli *dm.Client
12 | AliyunAccountName string
13 | FromAlias string
14 | }
15 |
16 | func DefaultEmailService(aliyunAccessKeyId string, aliyunAccessKeySecret string, aliyunAccountName string) *EmailService {
17 | cli, err := dm.NewClientWithAccessKey("cn-hangzhou", aliyunAccessKeyId, aliyunAccessKeySecret)
18 | if err != nil {
19 | panic(err)
20 | }
21 | return &EmailService{
22 | cli: cli,
23 | AliyunAccountName: aliyunAccountName,
24 | FromAlias: "syzoj-ng",
25 | }
26 | }
27 |
28 | func (s *EmailService) SendEmail(ctx context.Context, To string, Subject string, Body string) error {
29 | req := dm.CreateSingleSendMailRequest()
30 | req.Scheme = "https"
31 | req.AccountName = s.AliyunAccountName
32 | req.AddressType = requests.NewInteger(1)
33 | req.ReplyToAddress = requests.NewBoolean(false)
34 | req.ToAddress = To
35 | req.FromAlias = s.FromAlias
36 | req.Subject = Subject
37 | req.HtmlBody = Body
38 | _, err := s.cli.SingleSendMail(req)
39 | if err != nil {
40 | return err
41 | }
42 | return nil
43 | }
44 |
--------------------------------------------------------------------------------
/models/boil_queries_test.go:
--------------------------------------------------------------------------------
1 | // Code generated by SQLBoiler 3.5.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT.
2 | // This file is meant to be re-generated in place and/or deleted at any time.
3 |
4 | package models
5 |
6 | import (
7 | "bytes"
8 | "fmt"
9 | "io"
10 | "io/ioutil"
11 | "math/rand"
12 | "regexp"
13 |
14 | "github.com/volatiletech/sqlboiler/boil"
15 | )
16 |
17 | var dbNameRand *rand.Rand
18 |
19 | func MustTx(transactor boil.ContextTransactor, err error) boil.ContextTransactor {
20 | if err != nil {
21 | panic(fmt.Sprintf("Cannot create a transactor: %s", err))
22 | }
23 | return transactor
24 | }
25 |
26 | func newFKeyDestroyer(regex *regexp.Regexp, reader io.Reader) io.Reader {
27 | return &fKeyDestroyer{
28 | reader: reader,
29 | rgx: regex,
30 | }
31 | }
32 |
33 | type fKeyDestroyer struct {
34 | reader io.Reader
35 | buf *bytes.Buffer
36 | rgx *regexp.Regexp
37 | }
38 |
39 | func (f *fKeyDestroyer) Read(b []byte) (int, error) {
40 | if f.buf == nil {
41 | all, err := ioutil.ReadAll(f.reader)
42 | if err != nil {
43 | return 0, err
44 | }
45 |
46 | all = bytes.Replace(all, []byte{'\r', '\n'}, []byte{'\n'}, -1)
47 | all = f.rgx.ReplaceAll(all, []byte{})
48 | f.buf = bytes.NewBuffer(all)
49 | }
50 |
51 | return f.buf.Read(b)
52 | }
53 |
--------------------------------------------------------------------------------
/models/boil_table_names.go:
--------------------------------------------------------------------------------
1 | // Code generated by SQLBoiler 3.5.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT.
2 | // This file is meant to be re-generated in place and/or deleted at any time.
3 |
4 | package models
5 |
6 | var TableNames = struct {
7 | Article string
8 | ArticleComment string
9 | Contest string
10 | ContestPlayer string
11 | ContestRanklist string
12 | File string
13 | FormattedCode string
14 | JudgeState string
15 | Problem string
16 | ProblemTag string
17 | ProblemTagMap string
18 | RatingCalculation string
19 | RatingHistory string
20 | SubmissionStatistics string
21 | User string
22 | UserPrivilege string
23 | }{
24 | Article: "article",
25 | ArticleComment: "article_comment",
26 | Contest: "contest",
27 | ContestPlayer: "contest_player",
28 | ContestRanklist: "contest_ranklist",
29 | File: "file",
30 | FormattedCode: "formatted_code",
31 | JudgeState: "judge_state",
32 | Problem: "problem",
33 | ProblemTag: "problem_tag",
34 | ProblemTagMap: "problem_tag_map",
35 | RatingCalculation: "rating_calculation",
36 | RatingHistory: "rating_history",
37 | SubmissionStatistics: "submission_statistics",
38 | User: "user",
39 | UserPrivilege: "user_privilege",
40 | }
41 |
--------------------------------------------------------------------------------
/lib/config/mysql.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "database/sql"
5 | "os"
6 |
7 | "github.com/go-sql-driver/mysql"
8 | "github.com/jmoiron/sqlx"
9 | )
10 |
11 | // Creates a *sql.DB instance from environment variables. The environment variables are:
12 | // ${prefix}MYSQL_HOST, ${prefix}MYSQL_USER, ${prefix}MYSQL_PASSWORD, ${prefix}MYSQL_DATABASE.
13 | func NewMySQL(prefix string) (*sql.DB, error) {
14 | cfg := mysql.NewConfig()
15 | cfg.Net = "tcp"
16 | cfg.Addr = os.Getenv(prefix + "MYSQL_HOST")
17 | cfg.User = os.Getenv(prefix + "MYSQL_USER")
18 | cfg.Passwd = os.Getenv(prefix + "MYSQL_PASSWORD")
19 | cfg.DBName = os.Getenv(prefix + "MYSQL_DATABASE")
20 | cfg.ClientFoundRows = true // Default to return matched rows instead of changed rows in an UPDATE query since that makes more sense
21 | cfg.ParseTime = true
22 | return sql.Open("mysql", cfg.FormatDSN())
23 | }
24 |
25 | // Creates a *sqlx.DB instance from environment variables. The environment variables are:
26 | // ${prefix}MYSQL_HOST, ${prefix}MYSQL_USER, ${prefix}MYSQL_PASSWORD, ${prefix}MYSQL_DATABASE.
27 | func NewMySQLx(prefix string) (*sqlx.DB, error) {
28 | cfg := mysql.NewConfig()
29 | cfg.Net = "tcp"
30 | cfg.Addr = os.Getenv(prefix + "MYSQL_HOST")
31 | cfg.User = os.Getenv(prefix + "MYSQL_USER")
32 | cfg.Passwd = os.Getenv(prefix + "MYSQL_PASSWORD")
33 | cfg.DBName = os.Getenv(prefix + "MYSQL_DATABASE")
34 | cfg.ClientFoundRows = true // Default to return matched rows instead of changed rows in an UPDATE query since that makes more sense
35 | cfg.ParseTime = true
36 | return sqlx.Open("mysql", cfg.FormatDSN())
37 | }
38 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/syzoj/syzoj-ng-go
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/DATA-DOG/go-sqlmock v1.3.3 // indirect
7 | github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20191009062011-51fffd9a2436
8 | github.com/elastic/go-elasticsearch v0.0.0
9 | github.com/gin-contrib/sse v0.1.0
10 | github.com/gin-gonic/gin v1.4.0
11 | github.com/go-ini/ini v1.48.0 // indirect
12 | github.com/go-sql-driver/mysql v1.4.1
13 | github.com/gofrs/uuid v3.2.0+incompatible // indirect
14 | github.com/golang/protobuf v1.3.2 // indirect
15 | github.com/gomodule/redigo v2.0.0+incompatible
16 | github.com/gorilla/websocket v1.4.1
17 | github.com/jmoiron/sqlx v1.2.0
18 | github.com/kat-co/vala v0.0.0-20170210184112-42e1d8b61f12
19 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
20 | github.com/lib/pq v1.1.1 // indirect
21 | github.com/mattn/go-sqlite3 v1.10.0 // indirect
22 | github.com/microcosm-cc/bluemonday v1.0.2
23 | github.com/minio/minio-go v6.0.14+incompatible
24 | github.com/mitchellh/go-homedir v1.1.0 // indirect
25 | github.com/pkg/errors v0.8.1
26 | github.com/russross/blackfriday v2.0.0+incompatible
27 | github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
28 | github.com/sirupsen/logrus v1.4.2
29 | github.com/spf13/viper v1.4.0
30 | github.com/volatiletech/inflect v0.0.0-20170731032912-e7201282ae8d // indirect
31 | github.com/volatiletech/null v8.0.0+incompatible
32 | github.com/volatiletech/sqlboiler v3.5.0+incompatible
33 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect
34 | golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa // indirect
35 | google.golang.org/appengine v1.6.1 // indirect
36 | )
37 |
--------------------------------------------------------------------------------
/svc/main/app/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/sirupsen/logrus"
7 | "github.com/syzoj/syzoj-ng-go/lib/config"
8 | "github.com/syzoj/syzoj-ng-go/lib/life"
9 | "github.com/syzoj/syzoj-ng-go/svc/app"
10 | "github.com/syzoj/syzoj-ng-go/svc/judge"
11 | srvredis "github.com/syzoj/syzoj-ng-go/svc/redis"
12 | )
13 |
14 | var log = logrus.StandardLogger()
15 |
16 | func main() {
17 | listenAddr := config.GetHttpListenAddr()
18 |
19 | redis, err := config.NewRedis("")
20 | if err != nil {
21 | log.WithError(err).Error("failed to get redis config")
22 | return
23 | }
24 | r := srvredis.DefaultRedisService(redis)
25 |
26 | redisCache, err := config.NewRedis("CACHE_")
27 | if err != nil {
28 | log.WithError(err).Error("failed to get redis cache config")
29 | return
30 | }
31 | rc := srvredis.DefaultRedisService(redisCache)
32 |
33 | judgeToken := os.Getenv("JUDGE_TOKEN")
34 |
35 | db, err := config.NewMySQL("")
36 | if err != nil {
37 | log.WithError(err).Error("failed to get mysql config")
38 | return
39 | }
40 |
41 | minio, err := config.NewMinio("")
42 | if err != nil {
43 | log.WithError(err).Error("failed to get s3 config")
44 | return
45 | }
46 | _, _ = r, minio
47 |
48 | judgeService := judge.DefaultJudgeService(db, r)
49 |
50 | emailService, _ := config.NewAliyunEmail("EMAIL_")
51 | if emailService == nil {
52 | log.Warning("Email service missing")
53 | }
54 |
55 | a := app.DefaultApp(db, r, rc, listenAddr, judgeService)
56 | a.JudgeToken = judgeToken
57 | a.EmailService = emailService
58 | //Minio: minio,
59 | //TestdataBucket: "testdata",
60 | a.Run(life.SignalContext())
61 | }
62 |
--------------------------------------------------------------------------------
/svc/main/migrate/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "os"
6 |
7 | "github.com/sirupsen/logrus"
8 | "github.com/syzoj/syzoj-ng-go/lib/config"
9 | "github.com/syzoj/syzoj-ng-go/lib/life"
10 | "github.com/syzoj/syzoj-ng-go/svc/migrate"
11 | srvredis "github.com/syzoj/syzoj-ng-go/svc/redis"
12 | )
13 |
14 | var log = logrus.StandardLogger()
15 | var funcs = map[string]func(*migrate.MigrateService, context.Context) error{
16 | "all": (*migrate.MigrateService).All,
17 | "problem-tags": (*migrate.MigrateService).MigrateProblemTags,
18 | "problem-counter": (*migrate.MigrateService).MigrateProblemCounter,
19 | "user-submissions": (*migrate.MigrateService).MigrateUserSubmissions,
20 | "user-email": (*migrate.MigrateService).MigrateUserEmail,
21 | }
22 |
23 | func main() {
24 | var flist []func(*migrate.MigrateService, context.Context) error
25 | for i, arg := range os.Args {
26 | if i == 0 {
27 | continue
28 | }
29 | if f, ok := funcs[arg]; ok {
30 | flist = append(flist, f)
31 | } else {
32 | log.WithField("arg", arg).Error("unsupported argument")
33 | }
34 | }
35 | redis, err := config.NewRedis("")
36 | if err != nil {
37 | log.WithError(err).Error("failed to get redis config")
38 | return
39 | }
40 | r := srvredis.DefaultRedisService(redis)
41 | db, err := config.NewMySQL("")
42 | if err != nil {
43 | log.WithError(err).Error("failed to get mysql config")
44 | return
45 | }
46 | srv := migrate.DefaultMigrateService(db, r)
47 | ctx := life.SignalContext()
48 | for _, f := range flist {
49 | if err := f(srv, ctx); err != nil {
50 | log.Error(err)
51 | return
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/models/mysql_upsert.go:
--------------------------------------------------------------------------------
1 | // Code generated by SQLBoiler 3.5.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT.
2 | // This file is meant to be re-generated in place and/or deleted at any time.
3 |
4 | package models
5 |
6 | import (
7 | "fmt"
8 | "strings"
9 |
10 | "github.com/volatiletech/sqlboiler/drivers"
11 | "github.com/volatiletech/sqlboiler/strmangle"
12 | )
13 |
14 | // buildUpsertQueryMySQL builds a SQL statement string using the upsertData provided.
15 | func buildUpsertQueryMySQL(dia drivers.Dialect, tableName string, update, whitelist []string) string {
16 | whitelist = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, whitelist)
17 | tableName = strmangle.IdentQuote(dia.LQ, dia.RQ, tableName)
18 |
19 | buf := strmangle.GetBuffer()
20 | defer strmangle.PutBuffer(buf)
21 |
22 | var columns string
23 | if len(whitelist) != 0 {
24 | columns = strings.Join(whitelist, ",")
25 | }
26 |
27 | if len(update) == 0 {
28 | fmt.Fprintf(
29 | buf,
30 | "INSERT IGNORE INTO %s (%s) VALUES (%s)",
31 | tableName,
32 | columns,
33 | strmangle.Placeholders(dia.UseIndexPlaceholders, len(whitelist), 1, 1),
34 | )
35 | return buf.String()
36 | }
37 |
38 | fmt.Fprintf(
39 | buf,
40 | "INSERT INTO %s (%s) VALUES (%s) ON DUPLICATE KEY UPDATE ",
41 | tableName,
42 | columns,
43 | strmangle.Placeholders(dia.UseIndexPlaceholders, len(whitelist), 1, 1),
44 | )
45 |
46 | for i, v := range update {
47 | if i != 0 {
48 | buf.WriteByte(',')
49 | }
50 | quoted := strmangle.IdentQuote(dia.LQ, dia.RQ, v)
51 | buf.WriteString(quoted)
52 | buf.WriteString(" = VALUES(")
53 | buf.WriteString(quoted)
54 | buf.WriteByte(')')
55 | }
56 |
57 | return buf.String()
58 | }
59 |
--------------------------------------------------------------------------------
/lib/rediskey/rediskey.go:
--------------------------------------------------------------------------------
1 | package rediskey
2 |
3 | import (
4 | "strings"
5 | "time"
6 | )
7 |
8 | type RedisKey []string
9 |
10 | func (k RedisKey) Format(s ...string) string {
11 | if len(s) != len(k)-1 {
12 | panic("RedisKey: Format: wrong number of arguments")
13 | }
14 | b := &strings.Builder{}
15 | b.WriteString(k[0])
16 | for i, v := range s {
17 | for j := 0; j < len(v); j++ {
18 | if v[j] == ':' || v[j] == '{' || v[j] == '}' {
19 | panic("RedisKey: Format: key contains ':', '{', or '}'")
20 | }
21 | }
22 | b.WriteString(v)
23 | b.WriteString(k[i+1])
24 | }
25 | return b.String()
26 | }
27 |
28 | var (
29 | SESSION = RedisKey{"{session:", "}"}
30 |
31 | CORE_QUEUE = RedisKey{"{core:queue:", "}"}
32 | CORE_SUBMISSION_PROGRESS = RedisKey{"{core:submission:", "}:progress"}
33 | CORE_SUBMISSION_DATA = RedisKey{"{core:submission:", "}:data"}
34 | CORE_SUBMISSION_CALLBACK = RedisKey{"{core:submission:", "}:callback"}
35 | CORE_SUBMISSION_RESULT = RedisKey{"{core:submission:", "}:result"}
36 |
37 | MAIN_PROBLEM_SUBMITS = RedisKey{"{main:problem:", "}:submits"}
38 | MAIN_PROBLEM_ACCEPTS = RedisKey{"{main:problem:", "}:accepts"}
39 | MAIN_USER_LAST_SUBMISSION = RedisKey{"{main:user:", "}:last_submit"}
40 | MAIN_USER_LAST_ACCEPT = RedisKey{"{main:user:", "}:last_accept"}
41 | MAIN_JUDGE_DONE = "{main:judge_done}"
42 | MAIN_EMAIL_PASSWORD_RECOVERY_RATELIM = RedisKey{"{main:email:", "}:password_recovery_ratelim"}
43 | MAIN_EMAIL_PASSWORD_RECOVERY_TOKEN = RedisKey{"{main:email:", "}:password_recovery_token:", ""}
44 | )
45 |
46 | // Default expiry time for keys that are no longer active.
47 | const DEFAULT_EXPIRE = time.Second * 86400 * 7 // a week
48 |
--------------------------------------------------------------------------------
/lib/testdata/default.go:
--------------------------------------------------------------------------------
1 | package testdata
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "strings"
7 | )
8 |
9 | type defaultTestcase struct {
10 | Name string
11 | Input string
12 | Output string
13 | }
14 |
15 | func ParseDefault(path string) (*TestdataInfo, error) {
16 | files, err := ioutil.ReadDir(path)
17 | if err != nil {
18 | return nil, err
19 | }
20 | list := make(map[string]struct{})
21 | for _, file := range files {
22 | if !file.IsDir() {
23 | list[file.Name()] = struct{}{}
24 | }
25 | }
26 | var cases []defaultTestcase
27 | for name := range list {
28 | if strings.HasSuffix(name, ".in") {
29 | caseName := name[:len(name)-3]
30 | var outFile string
31 | outFile1 := caseName + ".out"
32 | if _, exists := list[outFile1]; exists {
33 | outFile = outFile1
34 | } else {
35 | outFile2 := caseName + ".ans"
36 | if _, exists := list[outFile2]; exists {
37 | outFile = outFile2
38 | }
39 | }
40 | if outFile != "" {
41 | cases = append(cases, defaultTestcase{
42 | Name: caseName,
43 | Input: caseName + ".in",
44 | Output: outFile,
45 | })
46 | }
47 | }
48 | }
49 | if len(cases) == 0 {
50 | return nil, fmt.Errorf("cannot parse test data")
51 | }
52 | fset := make(fileSet)
53 | info := &TestdataInfo{}
54 | info.Cases = make(map[string]*Testcase, len(cases))
55 | caseScore := 100. / float64(len(cases))
56 | for _, testcase := range cases {
57 | inpFile, err := getFileCached(path, testcase.Input, fset)
58 | if err != nil {
59 | return nil, err
60 | }
61 | outFile, err := getFileCached(path, testcase.Output, fset)
62 | if err != nil {
63 | return nil, err
64 | }
65 | info.Cases[testcase.Name] = &Testcase{
66 | Input: inpFile,
67 | Output: outFile,
68 | }
69 | info.Subtasks = append(info.Subtasks, &Subtask{
70 | Cases: []string{testcase.Name},
71 | Score: caseScore,
72 | })
73 | }
74 | return info, nil
75 | }
76 |
--------------------------------------------------------------------------------
/lib/http/http.go:
--------------------------------------------------------------------------------
1 | // http is a utility library for dealing with HTTP requests.
2 | package http
3 |
4 | import (
5 | "context"
6 | "time"
7 |
8 | "math/rand"
9 | )
10 |
11 | // M is a shortcut for map[string]interface{}.
12 | type M map[string]interface{}
13 |
14 | // RetryPolicy describes a retry strategy.
15 | type RetryPolicy interface {
16 | // Keeps calling a function until the operation succeeds or retry limit exceeds.
17 | // It should return nil if the operation succeeds at least once. Otherwise, the
18 | // last error should be returned.
19 | Execute(context.Context, func() error) error
20 | }
21 |
22 | // ExpRetry tries the operation with an exponential backoff.
23 | type ExpRetry struct {
24 | MaxRetry int // Defaults to 5
25 | Unit time.Duration // Defaults to 500*time.Millisecond
26 | Cap time.Duration // Defaults to 15*time.Second
27 | Exp float64 // Defaults to 2
28 | Jitter float64 // Randomly varies the backoff to distribute requests evenly. Defaults to 0.4, must be [0,1)
29 | }
30 |
31 | // The default retry mechanism.
32 | var DefaultRetry = &ExpRetry{}
33 |
34 | // Implements the RetryPolicy interface.
35 | func (r *ExpRetry) Execute(ctx context.Context, f func() error) (err error) {
36 | cnt := r.MaxRetry
37 | if cnt < 0 {
38 | panic("ExpRetry: negative MaxRetry")
39 | }
40 | if cnt == 0 {
41 | cnt = 5
42 | }
43 | d := r.Unit
44 | if d == 0 {
45 | d = 500 * time.Millisecond
46 | }
47 | jitter := r.Jitter
48 | if jitter < 0 || jitter > 1 {
49 | panic("ExpRetry: invalid jitter")
50 | }
51 | if jitter == 0 {
52 | jitter = 0.4
53 | }
54 | exp := r.Exp
55 | if exp < 0 {
56 | panic("ExpRetry: invalid exp")
57 | }
58 | if exp == 0 {
59 | exp = 2
60 | }
61 | for ; cnt > 0; cnt-- {
62 | if err = f(); err == nil {
63 | return nil
64 | }
65 | select {
66 | case <-time.After(time.Duration(float64(d) * (1 - rand.Float64()*jitter))):
67 | case <-ctx.Done():
68 | return ctx.Err()
69 | }
70 | d = time.Duration(float64(d) * exp)
71 | }
72 | return
73 | }
74 |
--------------------------------------------------------------------------------
/lib/divine/divine.go:
--------------------------------------------------------------------------------
1 | package divine
2 |
3 | import (
4 | "hash/crc32"
5 | "math/rand"
6 | "time"
7 | )
8 |
9 | type Divine struct {
10 | Fortune string `json:"fortune"`
11 | Good []*Item
12 | Bad []*Item
13 | }
14 |
15 | type Item struct {
16 | Title string `json:"title"`
17 | Detail string `json:"detail"`
18 | }
19 |
20 | type item struct {
21 | Title string
22 | Detail [2]string
23 | }
24 |
25 | var items = []*item{
26 | {Title: "刷题", Detail: [2]string{"一遍过样例", ""}},
27 | {Title: "装弱", Detail: [2]string{"我好菜啊", "你太强了"}},
28 | {Title: "搞x", Detail: [2]string{"爱上学习", "会被掰弯"}},
29 | {Title: "直播写代码", Detail: [2]string{"月入百万", "CE, RE and T, 身败名裂"}},
30 | {Title: "学数论", Detail: [2]string{"思维敏捷", "咋看都不会"}},
31 | {Title: "参加模拟赛", Detail: [2]string{"AK 虐场", "爆零"}},
32 | }
33 |
34 | func DoDivine(name string, sex int) *Divine {
35 | res := &Divine{Good: []*Item{}, Bad: []*Item{}}
36 | seed := crc32.ChecksumIEEE([]byte(name + time.Now().Format("20060102")))
37 | random := rand.New(rand.NewSource(int64(seed)))
38 |
39 | f := random.Float32()
40 | switch {
41 | case f <= 0.25:
42 | res.Fortune = "大吉"
43 | case f <= 0.5:
44 | res.Fortune = "大凶"
45 | case f <= 0.6:
46 | res.Fortune = "中平"
47 | case f <= 0.7:
48 | res.Fortune = "小吉"
49 | case f <= 0.8:
50 | res.Fortune = "小凶"
51 | case f <= 0.9:
52 | res.Fortune = "吉"
53 | default:
54 | res.Fortune = "凶"
55 | }
56 |
57 | pitems := make([]*item, len(items))
58 | copy(pitems, items)
59 | makeItem := func(typ int) *Item {
60 | for {
61 | id := random.Intn(len(pitems))
62 | item := pitems[id]
63 | if item == nil {
64 | continue
65 | }
66 | citem := &Item{Title: item.Title}
67 | if citem.Title == "搞x" {
68 | switch sex {
69 | case 0:
70 | citem.Title = "搞基"
71 | case 1:
72 | citem.Title = "搞姬"
73 | }
74 | }
75 | if item.Detail[typ] == "" {
76 | continue
77 | } else {
78 | citem.Detail = item.Detail[typ]
79 | }
80 | pitems[id] = nil
81 | return citem
82 | }
83 | }
84 | if res.Fortune != "大凶" {
85 | res.Good = []*Item{makeItem(0), makeItem(0)}
86 | }
87 | if res.Fortune != "大吉" {
88 | res.Bad = []*Item{makeItem(1), makeItem(1)}
89 | }
90 | return res
91 | }
92 |
--------------------------------------------------------------------------------
/models/boil_types.go:
--------------------------------------------------------------------------------
1 | // Code generated by SQLBoiler 3.5.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT.
2 | // This file is meant to be re-generated in place and/or deleted at any time.
3 |
4 | package models
5 |
6 | import (
7 | "strconv"
8 |
9 | "github.com/pkg/errors"
10 | "github.com/volatiletech/sqlboiler/boil"
11 | "github.com/volatiletech/sqlboiler/strmangle"
12 | )
13 |
14 | // M type is for providing columns and column values to UpdateAll.
15 | type M map[string]interface{}
16 |
17 | // ErrSyncFail occurs during insert when the record could not be retrieved in
18 | // order to populate default value information. This usually happens when LastInsertId
19 | // fails or there was a primary key configuration that was not resolvable.
20 | var ErrSyncFail = errors.New("models: failed to synchronize data after insert")
21 |
22 | type insertCache struct {
23 | query string
24 | retQuery string
25 | valueMapping []uint64
26 | retMapping []uint64
27 | }
28 |
29 | type updateCache struct {
30 | query string
31 | valueMapping []uint64
32 | }
33 |
34 | func makeCacheKey(cols boil.Columns, nzDefaults []string) string {
35 | buf := strmangle.GetBuffer()
36 |
37 | buf.WriteString(strconv.Itoa(cols.Kind))
38 | for _, w := range cols.Cols {
39 | buf.WriteString(w)
40 | }
41 |
42 | if len(nzDefaults) != 0 {
43 | buf.WriteByte('.')
44 | }
45 | for _, nz := range nzDefaults {
46 | buf.WriteString(nz)
47 | }
48 |
49 | str := buf.String()
50 | strmangle.PutBuffer(buf)
51 | return str
52 | }
53 |
54 | // Enum values for contest.type
55 | const (
56 | ContestTypeNoi = "noi"
57 | ContestTypeIoi = "ioi"
58 | ContestTypeAcm = "acm"
59 | )
60 |
61 | // Enum values for judge_state.status are not proper Go identifiers, cannot emit constants
62 | // Enum values for problem.type are not proper Go identifiers, cannot emit constants
63 | // Enum values for submission_statistics.type
64 | const (
65 | SubmissionStatisticsTypeFastest = "fastest"
66 | SubmissionStatisticsTypeSlowest = "slowest"
67 | SubmissionStatisticsTypeShortest = "shortest"
68 | SubmissionStatisticsTypeLongest = "longest"
69 | SubmissionStatisticsTypeMin = "min"
70 | SubmissionStatisticsTypeMax = "max"
71 | SubmissionStatisticsTypeEarliest = "earliest"
72 | )
73 |
--------------------------------------------------------------------------------
/svc/redis/pipeline.go:
--------------------------------------------------------------------------------
1 | package redis
2 |
3 | import (
4 | "context"
5 | "sync"
6 |
7 | "github.com/gomodule/redigo/redis"
8 | )
9 |
10 | // Pipeline wraps a Redis connection to provide pipelining utilities.
11 | // It supports multiple concurrent callers.
12 | type Pipeline struct {
13 | conn redis.Conn
14 | mu sync.Mutex
15 | ch chan interface{}
16 | }
17 |
18 | // Create a new pipeline. The pipeline must be closed after use.
19 | func (r *RedisService) NewPipeline(ctx context.Context) (*Pipeline, error) {
20 | conn, err := r.Pool.GetContext(ctx)
21 | if err != nil {
22 | return nil, err
23 | }
24 | p := &Pipeline{conn: conn, ch: make(chan interface{}, 1000)}
25 | go p.run()
26 | return p, nil
27 | }
28 |
29 | // Add a command to pipeline. Note that callback will not receive connection errors, only redis errors.
30 | // Callback can be nil, in which case it will be ignored.
31 | // The callback will be called in order in a dedicated goroutine.
32 | func (p *Pipeline) Do(callback func(interface{}, error), cmdName string, args ...interface{}) {
33 | p.mu.Lock()
34 | p.conn.Send(cmdName, args...)
35 | p.ch <- callback
36 | p.mu.Unlock()
37 | }
38 |
39 | // Flush the pipeline and wait until all previous commands have returned.
40 | func (p *Pipeline) Flush(ctx context.Context) error {
41 | p.mu.Lock()
42 | if err := p.conn.Flush(); err != nil {
43 | p.mu.Unlock()
44 | return err
45 | }
46 | p.mu.Unlock()
47 | ch := make(chan error, 1)
48 | p.ch <- ch
49 | select {
50 | case <-ctx.Done():
51 | return ctx.Err()
52 | case err := <-ch:
53 | return err
54 | }
55 | }
56 |
57 | func (p *Pipeline) run() {
58 | var perr error
59 | for f := range p.ch {
60 | switch obj := f.(type) {
61 | case chan error: // Flush command
62 | obj <- perr
63 | case func(interface{}, error):
64 | data, err := p.conn.Receive()
65 | if err != nil {
66 | if _, ok := err.(redis.Error); !ok {
67 | perr = err
68 | }
69 | }
70 | if perr == nil && obj != nil {
71 | obj(data, err)
72 | }
73 | }
74 | }
75 | }
76 |
77 | func (p *Pipeline) Close() error {
78 | close(p.ch)
79 | return p.conn.Close()
80 | }
81 |
82 | // A wrapper class to provide a callback that collects the results.
83 | type RedisResult struct {
84 | Result interface{}
85 | Err error
86 | }
87 |
88 | func (r *RedisResult) Save(a interface{}, e error) {
89 | r.Result = a
90 | r.Err = e
91 | }
92 |
--------------------------------------------------------------------------------
/svc/redis/scan.go:
--------------------------------------------------------------------------------
1 | package redis
2 |
3 | import (
4 | "context"
5 | "strings"
6 | "time"
7 |
8 | "github.com/gomodule/redigo/redis"
9 | )
10 |
11 | type node struct {
12 | child map[string]*node
13 | scan func(string)
14 | }
15 |
16 | // Handles a Redis prefix. The prefix must either be empty or end in a colon.
17 | func (s *RedisService) ScanPrefix(prefix string, handler func(string)) {
18 | cur := &s.root
19 | for {
20 | ind := strings.Index(prefix, ":")
21 | if ind == -1 {
22 | break
23 | }
24 | name := prefix[:ind]
25 | prefix = prefix[ind+1:]
26 | if cur.scan != nil {
27 | panic("Scanner: conflicting prefix")
28 | }
29 | if cur.child == nil {
30 | cur.child = make(map[string]*node)
31 | }
32 | nex := &node{}
33 | cur.child[name] = nex
34 | cur = nex
35 | }
36 | if prefix != "" {
37 | panic("ScanPrefix: invalid prefix")
38 | }
39 | if cur.child != nil || cur.scan != nil {
40 | panic("ScanPrefix: conflicting prefix")
41 | }
42 | cur.scan = handler
43 | }
44 |
45 | func (r *RedisService) RunScanner(ctx context.Context) error {
46 | if r.Ratio < 0 || r.Ratio > 1 {
47 | panic("redis: invalid ratio")
48 | }
49 | r.cursor = 0
50 | for {
51 | select {
52 | case <-ctx.Done():
53 | return ctx.Err()
54 | default:
55 | }
56 | startTime := time.Now()
57 | if err := r.loop(ctx); err != nil {
58 | r.ErrorHandler(err)
59 | }
60 | dur := time.Now().Sub(startTime)
61 | select {
62 | case <-ctx.Done():
63 | return ctx.Err()
64 | case <-time.After(time.Duration(float64(dur) / r.Ratio)):
65 | }
66 | }
67 | }
68 |
69 | func (r *RedisService) loop(ctx context.Context) error {
70 | res, err := redis.Values(r.DoContext(ctx, "SCAN", r.cursor, "COUNT", r.BatchSize))
71 | if err != nil {
72 | return err
73 | }
74 | r.cursor, err = redis.Int64(res[0], nil)
75 | if err != nil {
76 | return err
77 | }
78 | keys, err := redis.Strings(res[1], nil)
79 | if err != nil {
80 | return err
81 | }
82 | for _, key := range keys {
83 | cur := &r.root
84 | for cur.child != nil {
85 | ind := strings.Index(key, ":")
86 | name := key[:ind]
87 | if nex, ok := cur.child[name]; ok {
88 | cur = nex
89 | key = key[ind+1:]
90 | } else {
91 | cur = nil
92 | break
93 | }
94 | }
95 | if cur != nil && cur.scan != nil {
96 | cur.scan(key)
97 | }
98 | }
99 | return nil
100 | }
101 |
--------------------------------------------------------------------------------
/svc/redis/redis.go:
--------------------------------------------------------------------------------
1 | // redis wraps around github.com/gomodule/redigo/redis to provide convenience functions.
2 | package redis
3 |
4 | import (
5 | "context"
6 | "time"
7 |
8 | "github.com/gomodule/redigo/redis"
9 | "github.com/sirupsen/logrus"
10 | )
11 |
12 | var log = logrus.StandardLogger()
13 |
14 | // Redis service.
15 | type RedisService struct {
16 | *redis.Pool
17 |
18 | // For redis keyspace scanning
19 | BatchSize int
20 | Ratio float64
21 | ErrorHandler func(error)
22 |
23 | root node
24 | cursor int64
25 | }
26 |
27 | // Creates a Redis service with default settings.
28 | func DefaultRedisService(p *redis.Pool) *RedisService {
29 | return &RedisService{
30 | Pool: p,
31 | BatchSize: 10,
32 | Ratio: 0.01,
33 | ErrorHandler: func(err error) {
34 | log.Error("error while scanning Redis: %s", err)
35 | },
36 | }
37 | }
38 |
39 | // Do takes a connection from the pool and does the command.
40 | func (s *RedisService) Do(cmdName string, args ...interface{}) (interface{}, error) {
41 | conn := s.Get()
42 | defer conn.Close()
43 | return conn.Do(cmdName, args...)
44 | }
45 |
46 | // DoContext takes a connection from the pool and does the command.
47 | // Context deadline is respected.
48 | func (s *RedisService) DoContext(ctx context.Context, cmdName string, args ...interface{}) (interface{}, error) {
49 | conn, err := s.GetContext(ctx)
50 | if err != nil {
51 | return nil, err
52 | }
53 | defer conn.Close()
54 | conni := conn.(redis.ConnWithTimeout)
55 | deadline, ok := ctx.Deadline()
56 | var timeout time.Duration
57 | if ok {
58 | timeout = deadline.Sub(time.Now())
59 | if timeout < 0 {
60 | return nil, ctx.Err()
61 | }
62 | }
63 | if timeout > 0 {
64 | return conni.DoWithTimeout(timeout, cmdName, args...)
65 | } else {
66 | return conni.Do(cmdName, args...)
67 | }
68 | }
69 |
70 | // WithCache queries redis for cached content before calling func.
71 | func (s *RedisService) WithCache(ctx context.Context, key string, f func() ([]byte, time.Duration, error)) ([]byte, error) {
72 | val, err := redis.Bytes(s.DoContext(ctx, "GET", key))
73 | if err == redis.ErrNil {
74 | var d time.Duration
75 | val, d, err = f()
76 | if err != nil {
77 | return nil, err
78 | }
79 | _, err2 := s.DoContext(ctx, "SET", key, val, "PX", int64(d/time.Millisecond))
80 | if err2 != nil { // this is not critical error
81 | log.WithError(err2).Warning("failed to write redis cache")
82 | }
83 | }
84 | return val, err
85 | }
86 |
--------------------------------------------------------------------------------
/models/boil_main_test.go:
--------------------------------------------------------------------------------
1 | // Code generated by SQLBoiler 3.5.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT.
2 | // This file is meant to be re-generated in place and/or deleted at any time.
3 |
4 | package models
5 |
6 | import (
7 | "database/sql"
8 | "flag"
9 | "fmt"
10 | "math/rand"
11 | "os"
12 | "path/filepath"
13 | "strings"
14 | "testing"
15 | "time"
16 |
17 | "github.com/spf13/viper"
18 | "github.com/volatiletech/sqlboiler/boil"
19 | )
20 |
21 | var flagDebugMode = flag.Bool("test.sqldebug", false, "Turns on debug mode for SQL statements")
22 | var flagConfigFile = flag.String("test.config", "", "Overrides the default config")
23 |
24 | const outputDirDepth = 1
25 |
26 | var (
27 | dbMain tester
28 | )
29 |
30 | type tester interface {
31 | setup() error
32 | conn() (*sql.DB, error)
33 | teardown() error
34 | }
35 |
36 | func TestMain(m *testing.M) {
37 | if dbMain == nil {
38 | fmt.Println("no dbMain tester interface was ready")
39 | os.Exit(-1)
40 | }
41 |
42 | rand.Seed(time.Now().UnixNano())
43 |
44 | flag.Parse()
45 |
46 | var err error
47 |
48 | // Load configuration
49 | err = initViper()
50 | if err != nil {
51 | fmt.Println("unable to load config file")
52 | os.Exit(-2)
53 | }
54 |
55 | // Set DebugMode so we can see generated sql statements
56 | boil.DebugMode = *flagDebugMode
57 |
58 | if err = dbMain.setup(); err != nil {
59 | fmt.Println("Unable to execute setup:", err)
60 | os.Exit(-4)
61 | }
62 |
63 | conn, err := dbMain.conn()
64 | if err != nil {
65 | fmt.Println("failed to get connection:", err)
66 | }
67 |
68 | var code int
69 | boil.SetDB(conn)
70 | code = m.Run()
71 |
72 | if err = dbMain.teardown(); err != nil {
73 | fmt.Println("Unable to execute teardown:", err)
74 | os.Exit(-5)
75 | }
76 |
77 | os.Exit(code)
78 | }
79 |
80 | func initViper() error {
81 | if flagConfigFile != nil && *flagConfigFile != "" {
82 | viper.SetConfigFile(*flagConfigFile)
83 | if err := viper.ReadInConfig(); err != nil {
84 | return err
85 | }
86 | return nil
87 | }
88 |
89 | var err error
90 |
91 | viper.SetConfigName("sqlboiler")
92 |
93 | configHome := os.Getenv("XDG_CONFIG_HOME")
94 | homePath := os.Getenv("HOME")
95 | wd, err := os.Getwd()
96 | if err != nil {
97 | wd = strings.Repeat("../", outputDirDepth)
98 | } else {
99 | wd = wd + strings.Repeat("/..", outputDirDepth)
100 | }
101 |
102 | configPaths := []string{wd}
103 | if len(configHome) > 0 {
104 | configPaths = append(configPaths, filepath.Join(configHome, "sqlboiler"))
105 | } else {
106 | configPaths = append(configPaths, filepath.Join(homePath, ".config/sqlboiler"))
107 | }
108 |
109 | for _, p := range configPaths {
110 | viper.AddConfigPath(p)
111 | }
112 |
113 | // Ignore errors here, fall back to defaults and validation to provide errs
114 | _ = viper.ReadInConfig()
115 | viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
116 | viper.AutomaticEnv()
117 |
118 | return nil
119 | }
120 |
--------------------------------------------------------------------------------
/lib/testdata/datayml.go:
--------------------------------------------------------------------------------
1 | package testdata
2 |
3 | import (
4 | "io"
5 | "os"
6 | "path/filepath"
7 | "strings"
8 |
9 | "github.com/go-yaml/yaml"
10 | )
11 |
12 | type dataYml struct {
13 | InputFile string `yaml:"inputFile"`
14 | OutputFile string `yaml:"outputFile"`
15 | UserOutput string `yaml:"userOutput"`
16 | Subtasks []*dataYmlSubtask `yaml:"subtasks"`
17 | SpecialJudge *dataYmlSpecialJudge `yaml:"specialJudge"`
18 | }
19 |
20 | type dataYmlSubtask struct {
21 | Score float64 `yaml:"score"`
22 | Type string `yaml:"type"` // sum, min, mul
23 | Cases []string `yaml:"cases"`
24 | }
25 |
26 | type dataYmlSpecialJudge struct {
27 | Language string `yaml:"language"`
28 | FileName string `yaml:"fileName"`
29 | }
30 |
31 | func ParseDataYml(path string) (*TestdataInfo, error) {
32 | filePath := filepath.Join(path, "data.yml")
33 | file, err := os.Open(filePath)
34 | if err != nil {
35 | return nil, err
36 | }
37 | defer file.Close()
38 | fileReader := io.LimitReader(file, 100*1024)
39 | var data dataYml
40 | err = yaml.NewDecoder(fileReader).Decode(&data)
41 | if err != nil {
42 | return nil, err
43 | }
44 |
45 | fset := make(fileSet)
46 | info := &TestdataInfo{}
47 | info.SpecialJudges = make(map[string]*SpecialJudge)
48 | var spjName string
49 | if data.SpecialJudge != nil {
50 | spjName = "Spj"
51 | dataSpj := data.SpecialJudge
52 | spjFile, err := getFileCached(path, dataSpj.FileName, fset)
53 | if err != nil {
54 | return nil, err
55 | }
56 | info.SpecialJudges[spjName] = &SpecialJudge{
57 | Language: dataSpj.Language,
58 | File: spjFile,
59 | }
60 | }
61 | info.Cases = make(map[string]*Testcase)
62 | for _, dataSubtask := range data.Subtasks {
63 | for _, dataCase := range dataSubtask.Cases {
64 | if _, exists := info.Cases[dataCase]; !exists {
65 | inpFile, err := getFileCached(path, strings.ReplaceAll(data.InputFile, "#", dataCase), fset)
66 | if err != nil {
67 | return nil, err
68 | }
69 | outFile, err := getFileCached(path, strings.ReplaceAll(data.OutputFile, "#", dataCase), fset)
70 | if err != nil {
71 | return nil, err
72 | }
73 | info.Cases[dataCase] = &Testcase{
74 | Input: inpFile,
75 | Output: outFile,
76 | Answer: strings.ReplaceAll(data.UserOutput, "#", dataCase),
77 | SpecialJudge: spjName,
78 | }
79 | }
80 | }
81 | if dataSubtask.Type == "sum" && len(dataSubtask.Cases) != 0 {
82 | // Split into multiple subtasks
83 | score := dataSubtask.Score / float64(len(dataSubtask.Cases))
84 | for _, dataCase := range dataSubtask.Cases {
85 | infoSubtask := &Subtask{}
86 | infoSubtask.Cases = []string{dataCase}
87 | infoSubtask.Score = score
88 | info.Subtasks = append(info.Subtasks, infoSubtask)
89 | }
90 | } else {
91 | infoSubtask := &Subtask{}
92 | infoSubtask.Cases = dataSubtask.Cases
93 | infoSubtask.Score = dataSubtask.Score
94 | info.Subtasks = append(info.Subtasks, infoSubtask)
95 | }
96 | }
97 | return info, nil
98 | }
99 |
--------------------------------------------------------------------------------
/svc/app/app.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "context"
5 | "crypto/subtle"
6 | "database/sql"
7 | "encoding/json"
8 | "net/http"
9 | "sync"
10 |
11 | "github.com/gin-gonic/gin"
12 | "github.com/sirupsen/logrus"
13 | "github.com/syzoj/syzoj-ng-go/models"
14 | "github.com/syzoj/syzoj-ng-go/svc/email"
15 | "github.com/syzoj/syzoj-ng-go/svc/judge"
16 | svcredis "github.com/syzoj/syzoj-ng-go/svc/redis"
17 | "github.com/volatiletech/sqlboiler/queries/qm"
18 | )
19 |
20 | var log = logrus.StandardLogger()
21 |
22 | const GIN_USER_ID = "USER_ID"
23 |
24 | type App struct {
25 | Db *sql.DB
26 | ListenAddr string
27 | Redis *svcredis.RedisService // The persistent redis instance. No eviction policies allowed.
28 | RedisCache *svcredis.RedisService
29 | JudgeService *judge.JudgeService
30 | EmailService *email.EmailService // optional
31 | JudgeToken string
32 | }
33 |
34 | func DefaultApp(db *sql.DB, redis *svcredis.RedisService, redisCache *svcredis.RedisService, listenAddr string, judgeService *judge.JudgeService) *App {
35 | return &App{
36 | Db: db,
37 | ListenAddr: listenAddr,
38 | Redis: redis,
39 | RedisCache: redisCache,
40 | JudgeService: judgeService,
41 | }
42 | }
43 |
44 | func (a *App) Run(ctx context.Context) error {
45 | var wg sync.WaitGroup
46 | wg.Add(1)
47 | go func() {
48 | defer wg.Done()
49 | if err := a.ensureQueue(ctx, "default"); err != nil {
50 | log.WithError(err).Error("failed to create default queue")
51 | }
52 | }()
53 | router := gin.Default()
54 | router.Use(a.UserMiddleware)
55 | router.GET("/api/index", a.getApiIndex)
56 | router.POST("/api/login", a.postApiLogin)
57 | router.POST("/api/forget", a.postApiForget)
58 | router.GET("/api/problems", a.getApiProblems)
59 | router.GET("/api/problem/:problem_id", a.getApiProblem)
60 | router.GET("/api/submission-progress/:sid", a.getTaskProgress)
61 | router.GET("/api/header", a.getHeader)
62 | jg := router.Group("/judge")
63 | jg.Use(a.useCheckJudgeToken)
64 | jg.GET("/wait-for-task", a.getJudgeWaitForTask)
65 | server := &http.Server{Addr: a.ListenAddr, Handler: router}
66 | wg.Add(1)
67 | go func() {
68 | defer wg.Done()
69 | <-ctx.Done()
70 | server.Close()
71 | }()
72 | wg.Add(1)
73 | go func() {
74 | defer wg.Done()
75 | if err := server.ListenAndServe(); err != nil {
76 | log.WithError(err).Error("failed to listen and serve")
77 | }
78 | }()
79 | go func() {
80 | defer wg.Done()
81 | if err := a.handleJudgeDone(ctx); err != nil {
82 | log.WithError(err).Error("failed to handle judge done")
83 | }
84 | }()
85 | wg.Wait()
86 | return nil
87 | }
88 |
89 | func (a *App) UserMiddleware(c *gin.Context) {
90 | ctx := c.Request.Context()
91 | c.Set(GIN_USER_ID, 0)
92 | loginCookie, err := c.Cookie("login")
93 | if err != nil {
94 | return
95 | }
96 | var loginData []string
97 | if err := json.Unmarshal([]byte(loginCookie), &loginData); err != nil {
98 | return
99 | }
100 | if len(loginData) != 2 {
101 | return
102 | }
103 | user, err := models.Users(qm.Select("id", "password"), qm.Where("username=?", loginData[0])).One(ctx, a.Db)
104 | if err != nil {
105 | return
106 | }
107 | if !user.Password.Valid || subtle.ConstantTimeCompare([]byte(loginData[1]), []byte(user.Password.String)) != 1 {
108 | return
109 | }
110 | c.Set(GIN_USER_ID, user.ID)
111 | }
112 |
--------------------------------------------------------------------------------
/svc/app/index.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "strconv"
5 |
6 | "github.com/gin-gonic/gin"
7 | "github.com/syzoj/syzoj-ng-go/lib/divine"
8 | "github.com/syzoj/syzoj-ng-go/models"
9 | "github.com/volatiletech/null"
10 | "github.com/volatiletech/sqlboiler/queries/qm"
11 | )
12 |
13 | type IndexResponse struct {
14 | Divine *divine.Divine
15 | Ranklist []*IndexResponseUser `json:"ranklist"`
16 | Notices []*IndexResponseNotice `json:"notices"`
17 | Contests []*IndexResponseContest `json:"contests"`
18 | Problems []*IndexResponseProblem `json:"problems"`
19 | }
20 | type IndexResponseUser struct {
21 | ID int `json:"id"`
22 | Username null.String `json:"username"`
23 | Nameplate null.String `json:"nameplate"`
24 | Information null.String `json:"information"`
25 | }
26 | type IndexResponseContest struct {
27 | ID int `json:"id"`
28 | StartTime null.Int `json:"start_time"`
29 | EndTime null.Int `json:"end_time"`
30 | }
31 | type IndexResponseNotice struct {
32 | ID int `json:"id"`
33 | Title null.String `json:"title"`
34 | Date null.Int `json:"date"`
35 | }
36 | type IndexResponseProblem struct {
37 | ID int `json:"id"`
38 | Title null.String `json:"title"`
39 | PublicizeTime null.Time `json:"publicize_time"`
40 | }
41 |
42 | func (a *App) getApiIndex(c *gin.Context) {
43 | ctx := c.Request.Context()
44 | users, err := models.Users(qm.OrderBy("rating desc"), qm.Limit(20)).All(ctx, a.Db)
45 | if err != nil {
46 | c.AbortWithError(500, err)
47 | return
48 | }
49 | articles, err := models.Articles(qm.Where("is_notice = true"), qm.OrderBy("public_time desc")).All(ctx, a.Db)
50 | if err != nil {
51 | c.AbortWithError(500, err)
52 | return
53 | }
54 | contests, err := models.Contests(qm.Where("is_public = true"), qm.OrderBy("start_time desc"), qm.Limit(5)).All(ctx, a.Db)
55 | if err != nil {
56 | c.AbortWithError(500, err)
57 | return
58 | }
59 | problems, err := models.Problems(qm.Where("is_public = true"), qm.OrderBy("publicize_time desc"), qm.Limit(5)).All(ctx, a.Db)
60 | if err != nil {
61 | c.AbortWithError(500, err)
62 | return
63 | }
64 | resp := &IndexResponse{
65 | Ranklist: []*IndexResponseUser{},
66 | Notices: []*IndexResponseNotice{},
67 | Contests: []*IndexResponseContest{},
68 | Problems: []*IndexResponseProblem{},
69 | }
70 | for _, user := range users {
71 | resp.Ranklist = append(resp.Ranklist, &IndexResponseUser{
72 | ID: user.ID,
73 | Username: user.Username,
74 | Nameplate: user.Nameplate,
75 | Information: user.Information,
76 | })
77 | }
78 | for _, article := range articles {
79 | resp.Notices = append(resp.Notices, &IndexResponseNotice{
80 | ID: article.ID,
81 | Title: article.Title,
82 | Date: article.PublicTime,
83 | })
84 | }
85 | for _, contest := range contests {
86 | resp.Contests = append(resp.Contests, &IndexResponseContest{
87 | ID: contest.ID,
88 | StartTime: contest.StartTime,
89 | EndTime: contest.EndTime,
90 | })
91 | _ = contest
92 | }
93 | for _, problem := range problems {
94 | resp.Problems = append(resp.Problems, &IndexResponseProblem{
95 | ID: problem.ID,
96 | Title: problem.Title,
97 | PublicizeTime: problem.PublicizeTime,
98 | })
99 | }
100 | /* TODO: fortune */
101 | userId := c.GetInt(GIN_USER_ID)
102 | if userId != 0 {
103 | // TODO: supply sex
104 | resp.Divine = divine.DoDivine(strconv.Itoa(userId), 0)
105 | }
106 | c.JSON(200, resp)
107 | }
108 |
--------------------------------------------------------------------------------
/svc/redis/timer.go:
--------------------------------------------------------------------------------
1 | package redis
2 |
3 | import (
4 | "context"
5 | "sync"
6 | "time"
7 |
8 | "github.com/gomodule/redigo/redis"
9 | )
10 |
11 | // Redis timer is designed around a ZSET. The score is used as Unix time in milliseconds.
12 | // It polls a Redis ZSET to fetch latest messages. Instead of popping items out of ZSET,
13 | // it modifies the score to some time in the future so items can't be lost. The item
14 | // will be deleted automatically after the associated work is done.
15 | type RedisTimer struct {
16 | r *RedisService
17 | Key string
18 | Handler func(context.Context, string) error
19 | Timeout time.Duration
20 | Interval time.Duration // Maximum interval between polls
21 | BatchSize int
22 |
23 | tmr *time.Timer
24 | tmrmu sync.Mutex
25 | nextRun time.Time
26 | }
27 |
28 | func (r *RedisService) DefaultTimer(key string, handler func(context.Context, string) error) *RedisTimer {
29 | return &RedisTimer{
30 | r: r,
31 | Key: key,
32 | Handler: handler,
33 | Timeout: time.Second * 60,
34 | Interval: time.Second * 5,
35 | BatchSize: 10,
36 | tmr: time.NewTimer(0),
37 | }
38 | }
39 |
40 | func TimeToMillisecond(t time.Time) int64 {
41 | return t.Unix()*1000 + int64(t.Nanosecond())/1000000
42 | }
43 |
44 | func TimeFromMillisecond(t int64) time.Time {
45 | return time.Unix(t/1000, (t%1000)*1000000)
46 | }
47 |
48 | // Fetch a few elements, set their score to a new value, then find the lowest score.
49 | var timerFetchScript = NewScript([]byte(`
50 | local keys = redis.call("ZRANGEBYSCORE", KEYS[1], "-inf", ARGV[1])
51 | local args = {}
52 | for i,key in pairs(keys) do
53 | args[#args+1] = ARGV[2]
54 | args[#args+1] = key
55 | end
56 | if next(args) ~= nil then redis.call("ZADD", KEYS[1], unpack(args)) end
57 | local first = redis.call("ZRANGE", KEYS[1], 0, 0, "WITHSCORES")
58 | if next(first) == nil then first = false else first = first[2] end
59 | return { first, keys }
60 | `))
61 |
62 | func (t *RedisTimer) Run(ctx context.Context) error {
63 | t.reschedule(time.Now())
64 | for {
65 | select {
66 | case <-ctx.Done():
67 | return ctx.Err()
68 | case <-t.tmr.C:
69 | }
70 | now := time.Now()
71 | nex := now.Add(t.Timeout)
72 | res, err := redis.Values(t.r.EvalContext(ctx, timerFetchScript, []string{t.Key}, []interface{}{TimeToMillisecond(now), TimeToMillisecond(nex)}))
73 | if err != nil {
74 | log.WithField("key", t.Key).WithError(err).Errorf("failed to fetch redis timer")
75 | t.reschedule(time.Now().Add(t.Interval))
76 | continue
77 | }
78 | keys, err := redis.Strings(res[1], nil)
79 | if err != nil {
80 | panic(err)
81 | }
82 | for _, key := range keys {
83 | if err := t.Handler(ctx, key); err != nil {
84 | log.WithField("key", t.Key).WithError(err).Error("failed to handle redis timer")
85 | } else if err = t.Delete(ctx, key); err != nil {
86 | log.WithField("key", t.Key).WithError(err).Error("failed to drain redis timer")
87 | }
88 | select {
89 | case <-ctx.Done():
90 | break
91 | default:
92 | }
93 | }
94 | nexTime := time.Now().Add(t.Interval)
95 | firstTime, err := redis.Int64(res[0], nil)
96 | if err == nil {
97 | ft := TimeFromMillisecond(firstTime)
98 | if ft.Before(nexTime) {
99 | nexTime = ft
100 | }
101 | } else if err != redis.ErrNil {
102 | panic(err)
103 | }
104 | t.reschedule(nexTime)
105 | }
106 | }
107 |
108 | func (t *RedisTimer) reschedule(v time.Time) {
109 | t.tmrmu.Lock()
110 | defer t.tmrmu.Unlock()
111 | d := v.Sub(time.Now())
112 | if d < 0 {
113 | d = 0
114 | }
115 | t.tmr.Reset(d)
116 | t.nextRun = v
117 | }
118 |
119 | func (t *RedisTimer) schedule(v time.Time) {
120 | t.tmrmu.Lock()
121 | defer t.tmrmu.Unlock()
122 | if v.After(t.nextRun) {
123 | return
124 | }
125 | d := v.Sub(time.Now())
126 | if d < 0 {
127 | d = 0
128 | }
129 | t.tmr.Reset(d)
130 | t.nextRun = v
131 | }
132 |
133 | // Schedule key at the specified time. If key already exists, it is rescheduled.
134 | func (t *RedisTimer) Schedule(ctx context.Context, key string, at time.Time) error {
135 | t.schedule(at)
136 | _, err := t.r.DoContext(ctx, "ZADD", t.Key, TimeToMillisecond(at), key)
137 | return err
138 | }
139 |
140 | // Delete key.
141 | func (t *RedisTimer) Delete(ctx context.Context, key string) error {
142 | _, err := t.r.DoContext(ctx, "ZREM", t.Key, key)
143 | return err
144 | }
145 |
--------------------------------------------------------------------------------
/svc/app/api.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "bytes"
5 | "crypto/rand"
6 | "crypto/subtle"
7 | "database/sql"
8 | "encoding/base64"
9 | "encoding/json"
10 | "html/template"
11 | "net/url"
12 |
13 | "github.com/gin-gonic/gin"
14 | "github.com/gomodule/redigo/redis"
15 | "github.com/syzoj/syzoj-ng-go/lib/rediskey"
16 | "github.com/syzoj/syzoj-ng-go/models"
17 | "github.com/volatiletech/sqlboiler/queries/qm"
18 | )
19 |
20 | type HeaderResponse struct {
21 | User *models.User `json:"user"`
22 | }
23 |
24 | func (a *App) getHeader(c *gin.Context) {
25 | ctx := c.Request.Context()
26 | resp := &HeaderResponse{}
27 | userId := c.GetInt(GIN_USER_ID)
28 | if userId != 0 {
29 | user, err := models.Users(qm.Select("username", "is_admin"), qm.Where("id=?", userId)).One(ctx, a.Db)
30 | if err != nil {
31 | c.AbortWithError(500, err)
32 | return
33 | }
34 | resp.User = &models.User{
35 | Username: user.Username,
36 | IsAdmin: user.IsAdmin,
37 | }
38 | }
39 | c.JSON(200, resp)
40 | }
41 |
42 | type LoginRequest struct {
43 | UserName string `json:"username" form:"username"`
44 | Password string `json:"password" form:"password"`
45 | }
46 |
47 | func (a *App) postApiLogin(c *gin.Context) {
48 | ctx := c.Request.Context()
49 | var req LoginRequest
50 | if err := c.Bind(&req); err != nil {
51 | c.AbortWithError(400, err)
52 | return
53 | }
54 |
55 | user, err := models.Users(qm.Where("username=?", req.UserName)).One(ctx, a.Db)
56 | if err == sql.ErrNoRows {
57 | c.JSON(200, gin.H{"error_code": 1001})
58 | return
59 | } else if err != nil {
60 | c.AbortWithError(500, err)
61 | return
62 | }
63 | if !user.Password.Valid || user.Password.String == "" {
64 | c.JSON(200, gin.H{"error_code": 1003})
65 | return
66 | }
67 | if subtle.ConstantTimeCompare([]byte(user.Password.String), []byte(req.Password)) == 0 {
68 | c.JSON(200, gin.H{"error_code": 1002})
69 | return
70 | }
71 | data, _ := json.Marshal([]string{req.UserName, req.Password})
72 | c.SetCookie("login", string(data), 86400*31, "/", "", false, true)
73 | c.JSON(200, gin.H{"error_code": 1})
74 | }
75 |
76 | type ForgetRequest struct {
77 | Email string `json:"email" form:"email"`
78 | }
79 |
80 | type forgetData struct {
81 | Username string
82 | Url string
83 | }
84 |
85 | var forgetTitleTemplate = template.Must(template.New("forget").Parse("{{.Username}} 的密码重置邮件"))
86 | var forgetBodyTemplate = template.Must(template.New("forget").Parse(`
请点击该链接来重置密码:
{{.Url}}
链接有效期为 12h。如果您不是 {{.Username}},请忽略此邮件。
`))
87 |
88 | func (a *App) postApiForget(c *gin.Context) {
89 | if a.EmailService == nil {
90 | c.JSON(500, gin.H{"error": "Email service not configured"})
91 | return
92 | }
93 | ctx := c.Request.Context()
94 | var req ForgetRequest
95 | if err := c.Bind(&req); err != nil {
96 | c.AbortWithError(400, err)
97 | return
98 | }
99 | user, err := models.Users(qm.Where("email=?", req.Email)).One(ctx, a.Db)
100 | if err == sql.ErrNoRows {
101 | c.JSON(200, gin.H{"error_code": 1001})
102 | return
103 | } else if err != nil {
104 | c.AbortWithError(500, err)
105 | return
106 | }
107 |
108 | _, err = a.RedisCache.DoContext(ctx, "SET", rediskey.MAIN_EMAIL_PASSWORD_RECOVERY_RATELIM.Format(req.Email), "", "EX", 3600, "NX")
109 | if err == redis.ErrNil {
110 | c.JSON(200, gin.H{"error": "Only one email allowed per hour"})
111 | return
112 | } else if err != nil {
113 | c.AbortWithError(500, err)
114 | return
115 | }
116 |
117 | token := make([]byte, 32)
118 | if _, err := rand.Read(token); err != nil {
119 | c.AbortWithError(500, err)
120 | return
121 | }
122 | tokenString := base64.URLEncoding.EncodeToString(token)
123 | _, err = a.RedisCache.DoContext(ctx, "SET", rediskey.MAIN_EMAIL_PASSWORD_RECOVERY_TOKEN.Format(req.Email, tokenString), "", "EX", 43200)
124 | if err != nil {
125 | c.AbortWithError(500, err)
126 | return
127 | }
128 | forgetData := &forgetData{Username: user.Username.String, Url: "/forget_confirm?email=" + url.QueryEscape(req.Email) + "&token=" + tokenString}
129 | bufTitle := &bytes.Buffer{}
130 | if err := forgetTitleTemplate.Execute(bufTitle, forgetData); err != nil {
131 | c.AbortWithError(500, err)
132 | return
133 | }
134 | bufBody := &bytes.Buffer{}
135 | if err := forgetBodyTemplate.Execute(bufBody, forgetData); err != nil {
136 | c.AbortWithError(500, err)
137 | return
138 | }
139 |
140 | err = a.EmailService.SendEmail(ctx, req.Email, bufTitle.String(), bufBody.String())
141 | if err != nil {
142 | c.AbortWithError(500, err)
143 | return
144 | }
145 | c.JSON(200, gin.H{"error_code": 1})
146 | }
147 |
--------------------------------------------------------------------------------
/svc/migrate/migrate.go:
--------------------------------------------------------------------------------
1 | package migrate
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 | "encoding/json"
7 | "strconv"
8 |
9 | "github.com/sirupsen/logrus"
10 | "github.com/syzoj/syzoj-ng-go/lib/rediskey"
11 | "github.com/syzoj/syzoj-ng-go/models"
12 | "github.com/syzoj/syzoj-ng-go/svc/redis"
13 | "github.com/volatiletech/null"
14 | "github.com/volatiletech/sqlboiler/queries"
15 | "github.com/volatiletech/sqlboiler/queries/qm"
16 | )
17 |
18 | var log = logrus.StandardLogger()
19 |
20 | type MigrateService struct {
21 | Db *sql.DB
22 | Redis *redis.RedisService
23 | }
24 |
25 | func DefaultMigrateService(db *sql.DB, redis *redis.RedisService) *MigrateService {
26 | return &MigrateService{
27 | Db: db,
28 | Redis: redis,
29 | }
30 | }
31 |
32 | func (m *MigrateService) MigrateProblemTags(ctx context.Context) error {
33 | log.Info("Computing problem tags")
34 | m.Db.ExecContext(ctx, "ALTER TABLE `problem` ADD COLUMN `tags` JSON DEFAULT NULL")
35 | tagMaps, err := models.ProblemTagMaps().All(ctx, m.Db)
36 | if err != nil {
37 | return err
38 | }
39 | tags, err := models.ProblemTags().All(ctx, m.Db)
40 | if err != nil {
41 | return err
42 | }
43 | tagsMap := make(map[int]string)
44 | for _, tag := range tags {
45 | if tag.Name.Valid {
46 | tagsMap[tag.ID] = tag.Name.String
47 | }
48 | }
49 | problemTags := make(map[int][]string)
50 | for _, tagMap := range tagMaps {
51 | pid := tagMap.ProblemID
52 | tid := tagMap.TagID
53 | problemTags[pid] = append(problemTags[pid], tagsMap[tid])
54 | }
55 | for pid, tags := range problemTags {
56 | b, err := json.Marshal(tags)
57 | if err != nil {
58 | return err
59 | }
60 | if _, err := m.Db.ExecContext(ctx, "UPDATE `problem` SET `tags`=? WHERE `id`=?", b, pid); err != nil {
61 | return err
62 | }
63 | }
64 | return nil
65 | }
66 |
67 | func (m *MigrateService) MigrateProblemCounter(ctx context.Context) error {
68 | log.Info("Creating problem counters")
69 | problems, err := models.Problems().All(ctx, m.Db)
70 | if err != nil {
71 | return err
72 | }
73 | pipeline, err := m.Redis.NewPipeline(ctx)
74 | if err != nil {
75 | return err
76 | }
77 | for _, problem := range problems {
78 | if problem.AcNum.Valid {
79 | pipeline.Do(nil, "SET", rediskey.MAIN_PROBLEM_ACCEPTS.Format(strconv.Itoa(problem.ID)), problem.AcNum.Int)
80 | }
81 | if problem.SubmitNum.Valid {
82 | pipeline.Do(nil, "SET", rediskey.MAIN_PROBLEM_SUBMITS.Format(strconv.Itoa(problem.ID)), problem.SubmitNum.Int)
83 | }
84 | }
85 | return pipeline.Flush(ctx)
86 | }
87 |
88 | func (m *MigrateService) MigrateUserSubmissions(ctx context.Context) error {
89 | log.Info("Migrating user submissions")
90 | query, err := models.JudgeStates(qm.Select("status", "user_id", "problem_id"), qm.OrderBy("submit_time")).QueryContext(ctx, m.Db)
91 | if err != nil {
92 | return err
93 | }
94 | pipeline, err := m.Redis.NewPipeline(ctx)
95 | if err != nil {
96 | return err
97 | }
98 | for query.Next() {
99 | submission := &models.JudgeState{}
100 | if err := queries.Bind(query, submission); err != nil {
101 | log.WithError(err).Error("failed to bind model")
102 | continue
103 | }
104 | if !submission.UserID.Valid || !submission.ProblemID.Valid {
105 | continue
106 | }
107 | userId := submission.UserID.Int
108 | problemId := submission.ProblemID.Int
109 | if submission.Status == null.StringFrom("Accepted") {
110 | pipeline.Do(nil, "HSET", rediskey.MAIN_USER_LAST_ACCEPT.Format(strconv.Itoa(userId)), problemId, submission.ID)
111 | }
112 | pipeline.Do(nil, "HSET", rediskey.MAIN_USER_LAST_SUBMISSION.Format(strconv.Itoa(userId)), problemId, submission.ID)
113 | }
114 | if err := query.Err(); err != nil {
115 | return err
116 | }
117 | return pipeline.Flush(ctx)
118 | }
119 |
120 | func (m *MigrateService) MigrateUserEmail(ctx context.Context) error {
121 | log.Info("Adding user email verify status column")
122 | _, err := m.Db.ExecContext(ctx, "ALTER TABLE `user` ADD COLUMN `email_verified` TINYINT NOT NULL DEFAULT 0")
123 | rows, err := m.Db.QueryContext(ctx, "SELECT `email_verified` FROM `user` LIMIT 1")
124 | if err != nil {
125 | return err
126 | }
127 | rows.Close()
128 | return nil
129 | }
130 |
131 | func (m *MigrateService) All(ctx context.Context) error {
132 | if err := m.MigrateProblemTags(ctx); err != nil {
133 | return err
134 | }
135 | if err := m.MigrateProblemCounter(ctx); err != nil {
136 | return err
137 | }
138 | if err := m.MigrateUserSubmissions(ctx); err != nil {
139 | return err
140 | }
141 | if err := m.MigrateUserEmail(ctx); err != nil {
142 | return err
143 | }
144 | return nil
145 | }
146 |
--------------------------------------------------------------------------------
/svc/redis/x.go:
--------------------------------------------------------------------------------
1 | package redis
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/gomodule/redigo/redis"
7 | )
8 |
9 | type XREAD struct {
10 | Key string
11 | Msgs []*XREADMsg
12 | }
13 |
14 | type XREADMsg struct {
15 | ID string
16 | Data map[string]string
17 | }
18 |
19 | // Parse XREAD and XREADGROUP result.
20 | func ParseXREAD(data interface{}, err error) ([]*XREAD, error) {
21 | if err != nil {
22 | return nil, err
23 | }
24 | if data == nil {
25 | return nil, redis.ErrNil
26 | }
27 | vals := data.([]interface{})
28 | res := make([]*XREAD, len(vals))
29 | for i, val := range vals {
30 | valv := val.([]interface{})
31 | xread := &XREAD{}
32 | if xread.Key, err = redis.String(valv[0], nil); err != nil {
33 | return nil, err
34 | }
35 | msgs := valv[1].([]interface{})
36 | xread.Msgs = make([]*XREADMsg, len(msgs))
37 | for j, msg := range msgs {
38 | msgv := msg.([]interface{})
39 | xmsg := &XREADMsg{}
40 | if xmsg.ID, err = redis.String(msgv[0], nil); err != nil {
41 | return nil, err
42 | }
43 | kvs, err := redis.Values(msgv[1], nil)
44 | if err != nil {
45 | return nil, err
46 | }
47 | xmsg.Data = make(map[string]string, len(kvs)/2)
48 | for i := 0; i < len(kvs); i += 2 {
49 | k, err := redis.String(kvs[i], nil)
50 | if err != nil {
51 | return nil, err
52 | }
53 | v, err := redis.String(kvs[i+1], nil)
54 | if err != nil {
55 | return nil, err
56 | }
57 | xmsg.Data[k] = v
58 | }
59 | xread.Msgs[j] = xmsg
60 | }
61 | res[i] = xread
62 | }
63 | return res, nil
64 | }
65 |
66 | // Read up to entries from a Redis 5 stream until context is done.
67 | // If count==0, it reads as many entries as possible.
68 | func (r *RedisService) ReadStreamOnce(ctx context.Context, key string, lastId string, count int) ([]*XREADMsg, error) {
69 | for {
70 | select {
71 | case <-ctx.Done():
72 | return nil, ctx.Err()
73 | default:
74 | }
75 | var (
76 | data []*XREAD
77 | err error
78 | )
79 | if count == 0 {
80 | data, err = ParseXREAD(r.DoContext(ctx, "XREAD", "BLOCK", 60000, "STREAMS", key, lastId))
81 | } else {
82 | data, err = ParseXREAD(r.DoContext(ctx, "XREAD", "COUNT", count, "BLOCK", 60000, "STREAMS", key, lastId))
83 | }
84 | if err == redis.ErrNil {
85 | continue
86 | }
87 | return data[0].Msgs, err
88 | }
89 | }
90 |
91 | // Continuously reads from a single Redis 5 stream until context is done.
92 | // When the operation completes, an error is returned, and both channels will be closed.
93 | // Be sure to read from the returned channels or the stream will block.
94 | // Set lastId to "0" to read from beginning, or "$" to read new entries (racy).
95 | func (r *RedisService) ReadStream(ctx context.Context, key string, lastId string) (<-chan *XREADMsg, <-chan error) {
96 | msgCh := make(chan *XREADMsg)
97 | msgErr := make(chan error, 1)
98 | go func() {
99 | var err error
100 | defer func() {
101 | msgErr <- err
102 | close(msgCh)
103 | close(msgErr)
104 | }()
105 | for {
106 | var data []*XREADMsg
107 | data, err = r.ReadStreamOnce(ctx, key, lastId, 0)
108 | if err != nil {
109 | return
110 | }
111 | for _, msg := range data {
112 | lastId = msg.ID
113 | select {
114 | case <-ctx.Done():
115 | err = ctx.Err()
116 | return
117 | case msgCh <- msg:
118 | }
119 | }
120 | }
121 | }()
122 | return msgCh, msgErr
123 | }
124 |
125 | // Reads from a consumer group in a stream, using sema as the semaphore. It receives struct{}{} as "tokens" and reads a message for every token.
126 | func (r *RedisService) ReadStreamGroup(ctx context.Context, key string, group string, consumer string, sema <-chan struct{}) (<-chan *XREADMsg, <-chan error) {
127 | msgCh := make(chan *XREADMsg)
128 | errCh := make(chan error, 1)
129 | go func() {
130 | var count int
131 | var err error
132 | lastId := "0"
133 | defer func() {
134 | errCh <- err
135 | close(msgCh)
136 | close(errCh)
137 | }()
138 | for {
139 | if count == 0 {
140 | select {
141 | case <-ctx.Done():
142 | return
143 | case <-sema:
144 | count++
145 | }
146 | }
147 | var data []*XREAD
148 | data, err = ParseXREAD(r.DoContext(ctx, "XREADGROUP", "GROUP", group, consumer, "COUNT", 1, "BLOCK", 60000, "STREAMS", key, lastId))
149 | if err == redis.ErrNil {
150 | err = nil
151 | continue
152 | }
153 | if err != nil {
154 | return
155 | }
156 | if len(data) == 0 || len(data[0].Msgs) == 0 {
157 | lastId = ">"
158 | continue
159 | }
160 | for _, msg := range data[0].Msgs {
161 | if lastId != ">" {
162 | lastId = msg.ID
163 | }
164 | msgCh <- msg
165 | count--
166 | }
167 | }
168 | }()
169 | return msgCh, errCh
170 | }
171 |
--------------------------------------------------------------------------------
/svc/app/problem.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "bytes"
5 | "database/sql"
6 | "encoding/json"
7 | "strconv"
8 |
9 | "github.com/gin-gonic/gin"
10 | "github.com/gomodule/redigo/redis"
11 | "github.com/microcosm-cc/bluemonday"
12 | "github.com/syzoj/syzoj-ng-go/lib/rediskey"
13 | "github.com/syzoj/syzoj-ng-go/models"
14 | "github.com/volatiletech/null"
15 | "github.com/volatiletech/sqlboiler/queries/qm"
16 | "github.com/russross/blackfriday"
17 | )
18 |
19 | type GetProblemsResponse struct {
20 | Problems []*GetProblemsResponseProblem
21 | }
22 |
23 | type GetProblemsResponseProblem struct {
24 | Id string `json:"id"`
25 | Title null.String `json:"title"`
26 | Tags []string `json:"tags"`
27 | SubmitNum int64 `json:"submit_num"`
28 | AcceptNum int64 `json:"accept_num"`
29 | LastSubmit null.String `json:"last_submit"`
30 | LastAccept null.String `json:"last_accept"`
31 | }
32 |
33 | func (a *App) getApiProblems(c *gin.Context) {
34 | ctx := c.Request.Context()
35 | resp := &GetProblemsResponse{Problems: []*GetProblemsResponseProblem{}}
36 | problems, err := models.Problems(qm.Where("is_public=1"), qm.OrderBy("id ASC")).All(ctx, a.Db)
37 | if err != nil {
38 | c.AbortWithError(500, err)
39 | return
40 | }
41 | pipeline, err := a.Redis.NewPipeline(ctx)
42 | if err != nil {
43 | c.AbortWithError(500, err)
44 | return
45 | }
46 | defer pipeline.Close()
47 | userIdInt := c.GetInt(GIN_USER_ID)
48 | var userId string
49 | if userIdInt != 0 {
50 | userId = strconv.Itoa(userIdInt)
51 | }
52 | for _, problem := range problems {
53 | prob := &GetProblemsResponseProblem{Tags: []string{}}
54 | prob.Title = problem.Title
55 | if problem.Tags.Valid {
56 | json.Unmarshal([]byte(problem.Tags.String), &prob.Tags)
57 | }
58 | resp.Problems = append(resp.Problems, prob)
59 | probId := strconv.Itoa(problem.ID)
60 | prob.Id = probId
61 | pipeline.Do(func(data interface{}, err error) {
62 | num, err := redis.Int64(data, err)
63 | if err != nil {
64 | log.WithField("problem_id", probId).WithError(err).Error("failed to get problem submit count")
65 | return
66 | }
67 | prob.SubmitNum = num
68 | }, "GET", rediskey.MAIN_PROBLEM_SUBMITS.Format(probId))
69 | pipeline.Do(func(data interface{}, err error) {
70 | num, err := redis.Int64(data, err)
71 | if err != nil {
72 | log.WithField("problem_id", probId).WithError(err).Error("failed to get problem submit count")
73 | return
74 | }
75 | prob.AcceptNum = num
76 | }, "GET", rediskey.MAIN_PROBLEM_ACCEPTS.Format(probId))
77 | if userId != "" {
78 | pipeline.Do(func(data interface{}, err error) {
79 | sid, err := redis.String(data, err)
80 | if err == redis.ErrNil {
81 | return
82 | }
83 | if err != nil {
84 | log.WithField("user_id", userId).WithError(err).Error("failed to get user last submission")
85 | return
86 | }
87 | prob.LastSubmit = null.StringFrom(sid)
88 | }, "HGET", rediskey.MAIN_USER_LAST_SUBMISSION.Format(userId), probId)
89 | pipeline.Do(func(data interface{}, err error) {
90 | sid, err := redis.String(data, err)
91 | if err == redis.ErrNil {
92 | return
93 | }
94 | if err != nil {
95 | log.WithField("user_id", userId).WithError(err).Error("failed to get user last submission")
96 | return
97 | }
98 | prob.LastAccept = null.StringFrom(sid)
99 | }, "HGET", rediskey.MAIN_USER_LAST_ACCEPT.Format(userId), probId)
100 | }
101 | }
102 | if err := pipeline.Flush(ctx); err != nil {
103 | c.AbortWithError(500, err)
104 | return
105 | }
106 | c.JSON(200, resp)
107 | }
108 |
109 | type GetProblemResponse struct {
110 | Title string `json:"title"`
111 | Body string `json:"body"`
112 | }
113 |
114 | func (a *App) getApiProblem(c *gin.Context) {
115 | ctx := c.Request.Context()
116 | problemId, err := strconv.Atoi(c.Param("problem_id"))
117 | if err != nil {
118 | c.AbortWithError(400, err)
119 | return
120 | }
121 | problem, err := models.Problems(qm.Where("id=?", problemId)).One(ctx, a.Db)
122 | if err == sql.ErrNoRows {
123 | c.AbortWithError(404, err)
124 | return
125 | }
126 | if err != nil {
127 | c.AbortWithError(500, err)
128 | return
129 | }
130 |
131 | if problem.IsPublic.Int8 != 1 {
132 | c.JSON(200, gin.H{"error": "您没有权限进行此操作。"})
133 | return
134 | }
135 |
136 | body := &bytes.Buffer{}
137 | resp := &GetProblemResponse{}
138 | resp.Title = problem.Title.String
139 | if problem.Description.String != "" {
140 | body.WriteString("# 题目描述\n\n" + problem.Description.String)
141 | }
142 | if problem.InputFormat.String != "" {
143 | body.WriteString("# 输入格式\n\n" + problem.InputFormat.String)
144 | }
145 | if problem.OutputFormat.String != "" {
146 | body.WriteString("# 输出格式\n\n" + problem.OutputFormat.String)
147 | }
148 | if problem.Example.String != "" {
149 | body.WriteString("# 样例\n\n" + problem.Example.String)
150 | }
151 | if problem.LimitAndHint.String != "" {
152 | body.WriteString("# 数据范围与提示\n\n" + problem.LimitAndHint.String)
153 | }
154 | resp.Body = string(bluemonday.UGCPolicy().SanitizeBytes(blackfriday.Run(body.Bytes())))
155 | c.JSON(200, resp)
156 | }
157 |
--------------------------------------------------------------------------------
/svc/judge/judge.go:
--------------------------------------------------------------------------------
1 | // Judge provides the judging infrastructure.
2 | package judge
3 |
4 | import (
5 | "context"
6 | "database/sql"
7 | "time"
8 |
9 | "github.com/gomodule/redigo/redis"
10 | "github.com/sirupsen/logrus"
11 | "github.com/syzoj/syzoj-ng-go/lib/rediskey"
12 | svcredis "github.com/syzoj/syzoj-ng-go/svc/redis"
13 | "github.com/volatiletech/null"
14 | )
15 |
16 | var log = logrus.StandardLogger()
17 |
18 | type JudgeService struct {
19 | Db *sql.DB
20 | Redis *svcredis.RedisService
21 | }
22 |
23 | func DefaultJudgeService(db *sql.DB, r *svcredis.RedisService) *JudgeService {
24 | return &JudgeService{Db: db, Redis: r}
25 | }
26 |
27 | func (s *JudgeService) Run(ctx context.Context) error {
28 | _, err := s.Redis.DoContext(ctx, "XGROUP", "CREATE", rediskey.CORE_QUEUE.Format("default"), "judger", "$", "MKSTREAM")
29 | if err != nil {
30 | return err
31 | }
32 | return nil
33 | }
34 |
35 | // ConvertResult
36 | type Judge struct {
37 | TaskId null.String `json:"task_id,omitempty"`
38 | Type null.Int `json:"type,omitempty"`
39 | Progress *JudgeProgress `json:"progress,omitempty"`
40 | }
41 | type JudgeProgress struct {
42 | Compile *JudgeProgressCompile `json:"compile,omitempty"`
43 | Judge *JudgeProgressJudge `json:"judge,omitempty"`
44 | Error null.Int `json:"error,omitempty"`
45 | SystemMessage null.String `json:"systemMessage,omitempty"`
46 | Status null.Int `json;"status,omitempty"`
47 | Message null.String `json:"message,omitempty"`
48 | }
49 | type JudgeProgressCompile struct {
50 | Status null.Int `json:"status"`
51 | }
52 | type JudgeProgressJudge struct {
53 | Subtasks []*JudgeProgressJudgeSubtask `json:"subtasks,omitempty"`
54 | }
55 | type JudgeProgressJudgeSubtask struct {
56 | Type null.Int `json:"type,omitempty"`
57 | Cases []*JudgeProgressJudgeSubtaskCase `json:"cases,omitempty"`
58 | Score null.Float64 `json:"score,omitempty"`
59 | }
60 | type JudgeProgressJudgeSubtaskCase struct {
61 | Status null.Int `json:"status,omitempty"`
62 | Result *JudgeProgressCaseResult `json:"result,omitempty"`
63 | ErrorMessage null.String `json:"errorMessage,omitempty"`
64 | }
65 | type JudgeProgressCaseResult struct {
66 | Type null.Int `json:"type,omitempty"`
67 | Time null.Float64 `json:"time,omitempty"`
68 | Memory null.Float64 `json:"memory,omitempty"`
69 | Status null.Int `json:"status,omitempty"`
70 | Input *FileContent `json:"input,omitempty"`
71 | Output *FileContent `json:"output,omitempty"`
72 | Score null.Float64 `json:"score,omitempty"`
73 | ScoringRate null.Float64 `json:"scoringRate,omitempty"` // ???
74 | UserOutput null.String `json:"userOutput,omitempty'`
75 | UserError null.String `json:"userError,omitempty"`
76 | SpjMessage null.String `json:"spjMessage,omitempty"`
77 | SystemMessage null.String `json:"systemMessage,omitempty"`
78 | }
79 | type FileContent struct {
80 | Content string `json:"content"`
81 | Name string `json:"name"`
82 | }
83 | type SubmissionResult struct {
84 | Score null.Int `json:"score,omitempty"`
85 | Pending null.Bool `json:"pending,omitempty"`
86 | StatusString null.String `json:"statusString,omitempty"`
87 | Time null.Float64 `json:"time,omitempty"`
88 | Memory null.Float64 `json:"memory,omitempty"`
89 | Result null.String `json:"result,omitempty"`
90 | }
91 |
92 | var statusString = map[int]string{
93 | 1: "Accepted",
94 | 2: "Wrong Answer",
95 | 3: "Partially Correct",
96 | 4: "Memory Limit Exceeded",
97 | 5: "Time Limit Exceeded",
98 | 6: "Output Limit Exceeded",
99 | 7: "Runtime Error",
100 | 8: "File Error",
101 | 9: "Judgement Failed",
102 | 10: "Invalid Interaction",
103 | }
104 |
105 | // TODO: updateRelatedInfo
106 | type SubmissionResultSummary struct {
107 | Status string
108 | Time null.Float64
109 | Memory null.Float64
110 | Score null.Float64
111 | }
112 |
113 | func GetSummary(res *Judge) *SubmissionResultSummary {
114 | var (
115 | status string
116 | rtime null.Float64
117 | memory null.Float64
118 | score null.Float64
119 | )
120 | prog := res.Progress
121 | if prog.Compile != nil && prog.Compile.Status == null.IntFrom(3) {
122 | status = "Compile Error"
123 | } else if prog.Error.Valid {
124 | switch prog.Error.Int {
125 | case 0:
126 | status = "No Testdata"
127 | default:
128 | status = "System Error"
129 | }
130 | } else if prog.Judge != nil && prog.Judge.Subtasks != nil {
131 | rtime = null.Float64From(0)
132 | memory = null.Float64From(0)
133 | for _, subtask := range prog.Judge.Subtasks {
134 | if subtask == nil {
135 | continue
136 | }
137 | for _, c := range subtask.Cases {
138 | if c.Result != nil {
139 | rtime.Float64 += c.Result.Time.Float64
140 | if c.Result.Memory.Valid && memory.Float64 < c.Result.Memory.Float64 {
141 | memory.Float64 = c.Result.Memory.Float64
142 | }
143 | if status == "" && c.Result.Type != null.IntFrom(1) {
144 | status = statusString[c.Result.Type.Int]
145 | }
146 | }
147 | }
148 | score.Float64 += subtask.Score.Float64
149 | }
150 | if status == "" {
151 | status = statusString[1]
152 | }
153 | } else {
154 | status = "System Error"
155 | }
156 | return &SubmissionResultSummary{
157 | Status: status,
158 | Time: rtime,
159 | Memory: memory,
160 | Score: score,
161 | }
162 | }
163 |
164 | func (s *JudgeService) SaveTask(ctx context.Context, sid string, res *Judge) error {
165 |
166 | // Notify callbacks
167 | keys, err := redis.Values(s.Redis.DoContext(ctx, "SMEMBERS", rediskey.CORE_SUBMISSION_CALLBACK.Format(sid)))
168 | if err != nil {
169 | return err
170 | }
171 | pipe, err := s.Redis.NewPipeline(ctx)
172 | if err != nil {
173 | return err
174 | }
175 | defer pipe.Close()
176 | for _, key := range keys {
177 | s, err := redis.String(key, nil)
178 | if err != nil {
179 | return err
180 | }
181 | pipe.Do(nil, "XADD", s, "*", "sid", sid)
182 | }
183 | // Make submission expire
184 | pipe.Do(nil, "EXPIRE", rediskey.CORE_SUBMISSION_PROGRESS.Format(sid), int64(rediskey.DEFAULT_EXPIRE/time.Second))
185 | pipe.Do(nil, "EXPIRE", rediskey.CORE_SUBMISSION_DATA.Format(sid), int64(rediskey.DEFAULT_EXPIRE/time.Second))
186 | pipe.Do(nil, "EXPIRE", rediskey.CORE_SUBMISSION_RESULT.Format(sid), int64(rediskey.DEFAULT_EXPIRE/time.Second))
187 | pipe.Do(nil, "EXPIRE", rediskey.CORE_SUBMISSION_CALLBACK.Format(sid), int64(rediskey.DEFAULT_EXPIRE/time.Second))
188 | return pipe.Flush(ctx)
189 | }
190 |
--------------------------------------------------------------------------------
/models/mysql_main_test.go:
--------------------------------------------------------------------------------
1 | // Code generated by SQLBoiler 3.5.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT.
2 | // This file is meant to be re-generated in place and/or deleted at any time.
3 |
4 | package models
5 |
6 | import (
7 | "bytes"
8 | "database/sql"
9 | "fmt"
10 | "io"
11 | "io/ioutil"
12 | "os"
13 | "os/exec"
14 | "regexp"
15 | "strings"
16 |
17 | _ "github.com/go-sql-driver/mysql"
18 | "github.com/kat-co/vala"
19 | "github.com/pkg/errors"
20 | "github.com/spf13/viper"
21 | "github.com/volatiletech/sqlboiler/drivers/sqlboiler-mysql/driver"
22 | "github.com/volatiletech/sqlboiler/randomize"
23 | )
24 |
25 | var rgxMySQLkey = regexp.MustCompile(`(?m)((,\n)?\s+CONSTRAINT.*?FOREIGN KEY.*?\n)+`)
26 |
27 | type mysqlTester struct {
28 | dbConn *sql.DB
29 |
30 | dbName string
31 | host string
32 | user string
33 | pass string
34 | sslmode string
35 | port int
36 |
37 | optionFile string
38 |
39 | testDBName string
40 | skipSQLCmd bool
41 | }
42 |
43 | func init() {
44 | dbMain = &mysqlTester{}
45 | }
46 |
47 | func (m *mysqlTester) setup() error {
48 | var err error
49 |
50 | viper.SetDefault("mysql.sslmode", "true")
51 | viper.SetDefault("mysql.port", 3306)
52 |
53 | m.dbName = viper.GetString("mysql.dbname")
54 | m.host = viper.GetString("mysql.host")
55 | m.user = viper.GetString("mysql.user")
56 | m.pass = viper.GetString("mysql.pass")
57 | m.port = viper.GetInt("mysql.port")
58 | m.sslmode = viper.GetString("mysql.sslmode")
59 | m.testDBName = viper.GetString("mysql.testdbname")
60 | m.skipSQLCmd = viper.GetBool("mysql.skipsqlcmd")
61 |
62 | err = vala.BeginValidation().Validate(
63 | vala.StringNotEmpty(m.user, "mysql.user"),
64 | vala.StringNotEmpty(m.host, "mysql.host"),
65 | vala.Not(vala.Equals(m.port, 0, "mysql.port")),
66 | vala.StringNotEmpty(m.dbName, "mysql.dbname"),
67 | vala.StringNotEmpty(m.sslmode, "mysql.sslmode"),
68 | ).Check()
69 |
70 | if err != nil {
71 | return err
72 | }
73 |
74 | // Create a randomized db name.
75 | if len(m.testDBName) == 0 {
76 | m.testDBName = randomize.StableDBName(m.dbName)
77 | }
78 |
79 | if err = m.makeOptionFile(); err != nil {
80 | return errors.Wrap(err, "couldn't make option file")
81 | }
82 |
83 | if !m.skipSQLCmd {
84 | if err = m.dropTestDB(); err != nil {
85 | return err
86 | }
87 | if err = m.createTestDB(); err != nil {
88 | return err
89 | }
90 |
91 | dumpCmd := exec.Command("mysqldump", m.defaultsFile(), "--no-data", m.dbName)
92 | createCmd := exec.Command("mysql", m.defaultsFile(), "--database", m.testDBName)
93 |
94 | r, w := io.Pipe()
95 | dumpCmdStderr := &bytes.Buffer{}
96 | createCmdStderr := &bytes.Buffer{}
97 |
98 | dumpCmd.Stdout = w
99 | dumpCmd.Stderr = dumpCmdStderr
100 |
101 | createCmd.Stdin = newFKeyDestroyer(rgxMySQLkey, r)
102 | createCmd.Stderr = createCmdStderr
103 |
104 | if err = dumpCmd.Start(); err != nil {
105 | return errors.Wrap(err, "failed to start mysqldump command")
106 | }
107 | if err = createCmd.Start(); err != nil {
108 | return errors.Wrap(err, "failed to start mysql command")
109 | }
110 |
111 | if err = dumpCmd.Wait(); err != nil {
112 | fmt.Println(err)
113 | fmt.Println(dumpCmdStderr.String())
114 | return errors.Wrap(err, "failed to wait for mysqldump command")
115 | }
116 |
117 | _ = w.Close() // After dumpCmd is done, close the write end of the pipe
118 |
119 | if err = createCmd.Wait(); err != nil {
120 | fmt.Println(err)
121 | fmt.Println(createCmdStderr.String())
122 | return errors.Wrap(err, "failed to wait for mysql command")
123 | }
124 | }
125 |
126 | return nil
127 | }
128 |
129 | func (m *mysqlTester) sslMode(mode string) string {
130 | switch mode {
131 | case "true":
132 | return "REQUIRED"
133 | case "false":
134 | return "DISABLED"
135 | default:
136 | return "PREFERRED"
137 | }
138 | }
139 |
140 | func (m *mysqlTester) defaultsFile() string {
141 | return fmt.Sprintf("--defaults-file=%s", m.optionFile)
142 | }
143 |
144 | func (m *mysqlTester) makeOptionFile() error {
145 | tmp, err := ioutil.TempFile("", "optionfile")
146 | if err != nil {
147 | return errors.Wrap(err, "failed to create option file")
148 | }
149 |
150 | isTCP := false
151 | _, err = os.Stat(m.host)
152 | if os.IsNotExist(err) {
153 | isTCP = true
154 | } else if err != nil {
155 | return errors.Wrap(err, "could not stat m.host")
156 | }
157 |
158 | fmt.Fprintln(tmp, "[client]")
159 | fmt.Fprintf(tmp, "host=%s\n", m.host)
160 | fmt.Fprintf(tmp, "port=%d\n", m.port)
161 | fmt.Fprintf(tmp, "user=%s\n", m.user)
162 | if len(m.pass) != 0 {
163 | fmt.Fprintf(tmp, "password=%s\n", m.pass)
164 | }
165 | fmt.Fprintf(tmp, "ssl-mode=%s\n", m.sslMode(m.sslmode))
166 | if isTCP {
167 | fmt.Fprintln(tmp, "protocol=tcp")
168 | }
169 |
170 | fmt.Fprintln(tmp, "[mysqldump]")
171 | fmt.Fprintf(tmp, "host=%s\n", m.host)
172 | fmt.Fprintf(tmp, "port=%d\n", m.port)
173 | fmt.Fprintf(tmp, "user=%s\n", m.user)
174 | if len(m.pass) != 0 {
175 | fmt.Fprintf(tmp, "password=%s\n", m.pass)
176 | }
177 | fmt.Fprintf(tmp, "ssl-mode=%s\n", m.sslMode(m.sslmode))
178 | if isTCP {
179 | fmt.Fprintln(tmp, "protocol=tcp")
180 | }
181 |
182 | m.optionFile = tmp.Name()
183 |
184 | return tmp.Close()
185 | }
186 |
187 | func (m *mysqlTester) createTestDB() error {
188 | sql := fmt.Sprintf("create database %s;", m.testDBName)
189 | return m.runCmd(sql, "mysql")
190 | }
191 |
192 | func (m *mysqlTester) dropTestDB() error {
193 | sql := fmt.Sprintf("drop database if exists %s;", m.testDBName)
194 | return m.runCmd(sql, "mysql")
195 | }
196 |
197 | func (m *mysqlTester) teardown() error {
198 | if m.dbConn != nil {
199 | return m.dbConn.Close()
200 | }
201 |
202 | if !m.skipSQLCmd {
203 | if err := m.dropTestDB(); err != nil {
204 | return err
205 | }
206 | }
207 |
208 | return os.Remove(m.optionFile)
209 | }
210 |
211 | func (m *mysqlTester) runCmd(stdin, command string, args ...string) error {
212 | args = append([]string{m.defaultsFile()}, args...)
213 |
214 | cmd := exec.Command(command, args...)
215 | cmd.Stdin = strings.NewReader(stdin)
216 |
217 | stdout := &bytes.Buffer{}
218 | stderr := &bytes.Buffer{}
219 | cmd.Stdout = stdout
220 | cmd.Stderr = stderr
221 | if err := cmd.Run(); err != nil {
222 | fmt.Println("failed running:", command, args)
223 | fmt.Println(stdout.String())
224 | fmt.Println(stderr.String())
225 | return err
226 | }
227 |
228 | return nil
229 | }
230 |
231 | func (m *mysqlTester) conn() (*sql.DB, error) {
232 | if m.dbConn != nil {
233 | return m.dbConn, nil
234 | }
235 |
236 | var err error
237 | m.dbConn, err = sql.Open("mysql", driver.MySQLBuildQueryString(m.user, m.pass, m.testDBName, m.host, m.port, m.sslmode))
238 | if err != nil {
239 | return nil, err
240 | }
241 |
242 | return m.dbConn, nil
243 | }
244 |
--------------------------------------------------------------------------------
/svc/app/judge.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "regexp"
7 | "strconv"
8 | "sync"
9 | "time"
10 |
11 | "github.com/gin-contrib/sse"
12 | "github.com/gin-gonic/gin"
13 | "github.com/gomodule/redigo/redis"
14 | "github.com/gorilla/websocket"
15 | "github.com/syzoj/syzoj-ng-go/lib/rediskey"
16 | "github.com/syzoj/syzoj-ng-go/models"
17 | "github.com/syzoj/syzoj-ng-go/svc/judge"
18 | svcredis "github.com/syzoj/syzoj-ng-go/svc/redis"
19 | "github.com/volatiletech/null"
20 | "github.com/volatiletech/sqlboiler/boil"
21 | "github.com/volatiletech/sqlboiler/queries/qm"
22 | )
23 |
24 | // # Judge core
25 | // This part of code is designed to be stateless and all state is stored in Redis for persistence.
26 | // List of redis keys used:
27 | // 1. rediskey.CORE_QUEUE: A Redis 5 stream where each entry is a submission. Every entry has the following keys:
28 | // - sid: Submission id.
29 | // - data: additional related data in JSON format.
30 | // It has a consumer group called "judger". Judgers are identified by their username in HTTP basic auth. The username is used as the Redis consumer name.
31 | // Redis mechanisms should be used to handle failures.
32 | // 2. rediskey.CORE_SUBMISSION_PROGRESS: A Redis 5 stream containing submission progress. Every entry has the following keys:
33 | // - type: One of "reset", "progress", "done".
34 | // - data: JSON data if type is "progress".
35 | // ## Judger protocol
36 | // The judger endpoints use HTTP basic authentication. Username must match [0-9A-Za-z_]{3,16} and password is the judger token in App.JudgeToken.
37 | // The judger connects to /judger/wait-for-task and upgrades the connection to a WebSocket. Then it receives tasks, process the task, send progress and result, and receive another task and loop.
38 | // While processing a task, the judger sends messages with type="progress" to the broker. When the judger has done with the task, it sends a message with type="finish".
39 | // The broker saves the result to the database and emits a type="done" event.
40 |
41 | const GIN_JUDGER = "github.com/syzoj/syzoj-ng-go/svc/app.judger"
42 |
43 | func (a *App) ensureQueue(ctx context.Context, queueName string) error {
44 | _, err := a.Redis.DoContext(ctx, "XGROUP", "CREATE", rediskey.CORE_QUEUE.Format(queueName), "judger", "$", "MKSTREAM")
45 | if rerr, ok := err.(redis.Error); ok {
46 | if rerr[:10] == "BUSYGROUP " {
47 | return nil
48 | }
49 | }
50 | return err
51 | }
52 |
53 | var judgerRegexp = regexp.MustCompile("[0-9A-Za-z_]{3,16}")
54 |
55 | func (a *App) useCheckJudgeToken(c *gin.Context) {
56 | judger := c.Query("user")
57 | token := c.Query("token")
58 | if a.JudgeToken == "" || token != a.JudgeToken || !judgerRegexp.Match([]byte(judger)) {
59 | c.JSON(403, gin.H{"error": "Token doesn't match or invalid judger name (judger name must be [0-9A-Za-z_]{3,16})"})
60 | return
61 | }
62 | c.Set(GIN_JUDGER, judger)
63 | }
64 |
65 | var wsUpgrader = websocket.Upgrader{} // TODO: configure ReadBufferSize and WriteBufferSize (seems to default to 4096)
66 |
67 | type TaskResult struct {
68 | Id string `json:"id"`
69 | Sid string `json:"sid"`
70 | Type string `json:"type"`
71 | Data json.RawMessage `json:"data"`
72 | }
73 |
74 | // TODO: move logic to judge
75 | func (a *App) getJudgeWaitForTask(c *gin.Context) {
76 | ctx, cancel := context.WithCancel(c.Request.Context())
77 | defer cancel()
78 |
79 | conn, err := wsUpgrader.Upgrade(c.Writer, c.Request, nil)
80 | if err != nil {
81 | log.WithError(err).Error("failed to upgrade websocket")
82 | return
83 | }
84 | defer conn.Close()
85 | pipeline, err := a.Redis.NewPipeline(ctx)
86 | if err != nil {
87 | log.WithError(err).Error("failed to create redis pipeline")
88 | return
89 | }
90 | defer pipeline.Close()
91 |
92 | key := rediskey.CORE_QUEUE.Format("default")
93 | judger := c.GetString(GIN_JUDGER)
94 | sema := make(chan struct{}, 1) // queue capacity is 1
95 | sema <- struct{}{}
96 | var wg sync.WaitGroup
97 | wg.Add(2)
98 | // Fetch tasks
99 | go func() {
100 | defer wg.Done()
101 | msgCh, errCh := a.Redis.ReadStreamGroup(ctx, key, "judger", judger, sema)
102 | for {
103 | select {
104 | case <-ctx.Done():
105 | return
106 | case msg := <-msgCh:
107 | sid := msg.Data["sid"]
108 | data, err := redis.Bytes(a.Redis.DoContext(ctx, "GET", rediskey.CORE_SUBMISSION_DATA.Format(sid)))
109 | if err != nil {
110 | log.WithError(err).Error("failed to call GET")
111 | return
112 | }
113 | pipeline.Do(a.checkRedis, "XADD", rediskey.CORE_SUBMISSION_PROGRESS.Format(sid), "*", "type", "reset")
114 | if err := pipeline.Flush(ctx); err != nil {
115 | log.WithError(err).Error("failed to call XADD")
116 | return
117 | }
118 | if err := conn.WriteJSON(gin.H{
119 | "id": msg.ID,
120 | "sid": sid,
121 | "data": json.RawMessage(data),
122 | }); err != nil {
123 | log.WithError(err).Error("failed to send data")
124 | return
125 | }
126 | case err := <-errCh:
127 | if err != nil {
128 | log.WithError(err).Error("failed to read from Redis")
129 | }
130 | return
131 | }
132 | }
133 | }()
134 |
135 | // Receive results
136 | go func() {
137 | defer cancel()
138 | defer wg.Done()
139 | for {
140 | data := &TaskResult{}
141 | if err := conn.ReadJSON(data); err != nil {
142 | log.WithError(err).Error("failed to read data")
143 | return
144 | }
145 | skey := rediskey.CORE_SUBMISSION_PROGRESS.Format(data.Sid)
146 | switch data.Type {
147 | case "progress":
148 | pipeline.Do(a.checkRedis, "XADD", skey, "*", "type", "progress", "data", []byte(data.Data))
149 | case "finish":
150 | res := &judge.Judge{}
151 | if err := json.Unmarshal(data.Data, res); err != nil {
152 | log.WithField("sid", data.Sid).WithError(err).Error("failed to parse data")
153 | return
154 | }
155 | if res.Type == null.IntFrom(4) {
156 | // Save submission result
157 | sum := judge.GetSummary(res)
158 | subm, err := models.JudgeStates(qm.Where("task_id=?", data.Sid)).One(ctx, a.Db)
159 | if err != nil {
160 | log.WithError(err).Error("failed to query db")
161 | return
162 | }
163 | subm.Score = null.IntFrom(int(sum.Score.Float64))
164 | subm.Pending = null.Int8From(0)
165 | subm.Status = null.StringFrom(sum.Status)
166 | subm.TotalTime = null.IntFrom(int(sum.Time.Float64))
167 | subm.MaxMemory = null.IntFrom(int(sum.Memory.Float64))
168 | progBytes, err := json.Marshal(res.Progress)
169 | if err != nil {
170 | panic(err)
171 | }
172 | subm.Result = null.StringFrom(string(progBytes))
173 | if _, err := subm.Update(ctx, a.Db, boil.Blacklist()); err != nil {
174 | log.WithError(err).Error("failed to update db")
175 | return
176 | }
177 | pipeline.Do(a.checkRedis, "SET", rediskey.CORE_SUBMISSION_RESULT.Format(data.Sid), data.Data)
178 | if err := pipeline.Flush(ctx); err != nil {
179 | log.WithError(err).Error("failed to send redis")
180 | return
181 | }
182 | if err := a.JudgeService.SaveTask(ctx, data.Sid, res); err != nil {
183 | log.WithField("sid", data.Sid).WithError(err).Error("failed to save submission")
184 | return
185 | }
186 | pipeline.Do(a.checkRedis, "XADD", skey, "*", "type", "done")
187 | var res svcredis.RedisResult
188 | pipeline.Do(res.Save, "XACK", key, "judger", data.Id)
189 | if err := pipeline.Flush(ctx); err != nil {
190 | log.WithError(err).Error("failed to flush to Redis")
191 | return
192 | }
193 | n, err := redis.Int64(res.Result, res.Err)
194 | if err != nil {
195 | log.WithError(err).Error("failed to call XACK")
196 | return
197 | }
198 | for ; n > 0; n-- {
199 | select {
200 | case <-ctx.Done():
201 | return
202 | case sema <- struct{}{}:
203 | }
204 | }
205 | }
206 | }
207 | }
208 | }()
209 | wg.Wait()
210 | }
211 |
212 | func (a *App) getTaskProgress(c *gin.Context) {
213 | ctx, cancel := context.WithCancel(c.Request.Context())
214 | defer cancel()
215 | key := rediskey.CORE_SUBMISSION_PROGRESS.Format(c.Param("sid"))
216 | lastId := c.Request.Header.Get("Last-Event-Id")
217 | if lastId == "" {
218 | lastId = "0"
219 | }
220 | c.Header("X-Accel-Buffering", "no") // need this to make SSE work through nginx
221 | c.Render(200, sse.Event{Event: "start"})
222 | ping := time.NewTicker(time.Second * 30)
223 | defer ping.Stop()
224 | chMsg, chErr := a.Redis.ReadStream(ctx, key, lastId)
225 | for {
226 | select {
227 | case <-ctx.Done():
228 | return
229 | case err := <-chErr:
230 | c.AbortWithError(500, err)
231 | return
232 | case msg := <-chMsg:
233 | var ev sse.Event
234 | ev.Id = msg.ID
235 | ev.Event = msg.Data["type"]
236 | if d, ok := msg.Data["data"]; ok {
237 | ev.Data = json.RawMessage(d)
238 | }
239 | c.Render(-1, ev)
240 | c.Writer.Flush()
241 | case <-ping.C:
242 | c.Render(-1, sse.Event{Event: "ping"})
243 | c.Writer.Flush()
244 | }
245 | }
246 | }
247 |
248 | // Handle judge done events and update stats.
249 | func (a *App) handleJudgeDone(ctx context.Context) error {
250 | pipe, err := a.Redis.NewPipeline(ctx)
251 | if err != nil {
252 | return err
253 | }
254 | pipe.Do(nil, "XGROUP", "CREATE", rediskey.MAIN_JUDGE_DONE, "main", "$", "MKSTREAM")
255 | if err := pipe.Flush(ctx); err != nil {
256 | return err
257 | }
258 | sema := make(chan struct{}, 1)
259 | sema <- struct{}{}
260 | chMsg, chErr := a.Redis.ReadStreamGroup(ctx, rediskey.MAIN_JUDGE_DONE, "main", "main", sema)
261 | go func() {
262 | for {
263 | select {
264 | case <-ctx.Done():
265 | return
266 | case <-time.After(time.Second):
267 | pipe.Do(nil, "PING")
268 | if err := pipe.Flush(ctx); err != nil {
269 | log.WithError(err).Error("failed to flush to redis")
270 | return
271 | }
272 | }
273 | }
274 | }()
275 | for {
276 | select {
277 | case <-ctx.Done():
278 | return ctx.Err()
279 | case err := <-chErr:
280 | return err
281 | case msg := <-chMsg:
282 | sid := msg.Data["sid"]
283 | if sid == "" {
284 | log.WithField("id", msg.ID).Error("judge done stream: missing sid field")
285 | continue
286 | }
287 | subm, err := models.JudgeStates(qm.Where("task_id=?", sid)).One(ctx, a.Db)
288 | if err != nil {
289 | log.WithField("sid", sid).Error("judge done stream: failed to get submission")
290 | continue
291 | }
292 | if subm.ProblemID.Valid {
293 | pid := strconv.Itoa(subm.ProblemID.Int)
294 | pipe.Do(a.checkRedis, "INCR", rediskey.MAIN_PROBLEM_SUBMITS.Format(pid))
295 | if subm.Status == null.StringFrom("Accepted") {
296 | pipe.Do(a.checkRedis, "INCR", rediskey.MAIN_PROBLEM_ACCEPTS.Format(pid))
297 | }
298 | if subm.UserID.Valid {
299 | uid := strconv.Itoa(subm.UserID.Int)
300 | pipe.Do(a.checkRedis, "HSET", rediskey.MAIN_USER_LAST_SUBMISSION.Format(uid), pid, sid)
301 | if subm.Status == null.StringFrom("Accepted") {
302 | pipe.Do(a.checkRedis, "HSET", rediskey.MAIN_USER_LAST_ACCEPT.Format(uid), pid, sid)
303 | }
304 | }
305 | }
306 | pipe.Do(a.checkRedis, "XACK", rediskey.MAIN_JUDGE_DONE, "main", msg.ID)
307 | }
308 | }
309 | }
310 |
--------------------------------------------------------------------------------
/models/boil_suites_test.go:
--------------------------------------------------------------------------------
1 | // Code generated by SQLBoiler 3.5.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT.
2 | // This file is meant to be re-generated in place and/or deleted at any time.
3 |
4 | package models
5 |
6 | import "testing"
7 |
8 | // This test suite runs each operation test in parallel.
9 | // Example, if your database has 3 tables, the suite will run:
10 | // table1, table2 and table3 Delete in parallel
11 | // table1, table2 and table3 Insert in parallel, and so forth.
12 | // It does NOT run each operation group in parallel.
13 | // Separating the tests thusly grants avoidance of Postgres deadlocks.
14 | func TestParent(t *testing.T) {
15 | t.Run("Articles", testArticles)
16 | t.Run("ArticleComments", testArticleComments)
17 | t.Run("Contests", testContests)
18 | t.Run("ContestPlayers", testContestPlayers)
19 | t.Run("ContestRanklists", testContestRanklists)
20 | t.Run("Files", testFiles)
21 | t.Run("FormattedCodes", testFormattedCodes)
22 | t.Run("JudgeStates", testJudgeStates)
23 | t.Run("Problems", testProblems)
24 | t.Run("ProblemTags", testProblemTags)
25 | t.Run("ProblemTagMaps", testProblemTagMaps)
26 | t.Run("RatingCalculations", testRatingCalculations)
27 | t.Run("RatingHistories", testRatingHistories)
28 | t.Run("SubmissionStatistics", testSubmissionStatistics)
29 | t.Run("Users", testUsers)
30 | t.Run("UserPrivileges", testUserPrivileges)
31 | }
32 |
33 | func TestDelete(t *testing.T) {
34 | t.Run("Articles", testArticlesDelete)
35 | t.Run("ArticleComments", testArticleCommentsDelete)
36 | t.Run("Contests", testContestsDelete)
37 | t.Run("ContestPlayers", testContestPlayersDelete)
38 | t.Run("ContestRanklists", testContestRanklistsDelete)
39 | t.Run("Files", testFilesDelete)
40 | t.Run("FormattedCodes", testFormattedCodesDelete)
41 | t.Run("JudgeStates", testJudgeStatesDelete)
42 | t.Run("Problems", testProblemsDelete)
43 | t.Run("ProblemTags", testProblemTagsDelete)
44 | t.Run("ProblemTagMaps", testProblemTagMapsDelete)
45 | t.Run("RatingCalculations", testRatingCalculationsDelete)
46 | t.Run("RatingHistories", testRatingHistoriesDelete)
47 | t.Run("SubmissionStatistics", testSubmissionStatisticsDelete)
48 | t.Run("Users", testUsersDelete)
49 | t.Run("UserPrivileges", testUserPrivilegesDelete)
50 | }
51 |
52 | func TestQueryDeleteAll(t *testing.T) {
53 | t.Run("Articles", testArticlesQueryDeleteAll)
54 | t.Run("ArticleComments", testArticleCommentsQueryDeleteAll)
55 | t.Run("Contests", testContestsQueryDeleteAll)
56 | t.Run("ContestPlayers", testContestPlayersQueryDeleteAll)
57 | t.Run("ContestRanklists", testContestRanklistsQueryDeleteAll)
58 | t.Run("Files", testFilesQueryDeleteAll)
59 | t.Run("FormattedCodes", testFormattedCodesQueryDeleteAll)
60 | t.Run("JudgeStates", testJudgeStatesQueryDeleteAll)
61 | t.Run("Problems", testProblemsQueryDeleteAll)
62 | t.Run("ProblemTags", testProblemTagsQueryDeleteAll)
63 | t.Run("ProblemTagMaps", testProblemTagMapsQueryDeleteAll)
64 | t.Run("RatingCalculations", testRatingCalculationsQueryDeleteAll)
65 | t.Run("RatingHistories", testRatingHistoriesQueryDeleteAll)
66 | t.Run("SubmissionStatistics", testSubmissionStatisticsQueryDeleteAll)
67 | t.Run("Users", testUsersQueryDeleteAll)
68 | t.Run("UserPrivileges", testUserPrivilegesQueryDeleteAll)
69 | }
70 |
71 | func TestSliceDeleteAll(t *testing.T) {
72 | t.Run("Articles", testArticlesSliceDeleteAll)
73 | t.Run("ArticleComments", testArticleCommentsSliceDeleteAll)
74 | t.Run("Contests", testContestsSliceDeleteAll)
75 | t.Run("ContestPlayers", testContestPlayersSliceDeleteAll)
76 | t.Run("ContestRanklists", testContestRanklistsSliceDeleteAll)
77 | t.Run("Files", testFilesSliceDeleteAll)
78 | t.Run("FormattedCodes", testFormattedCodesSliceDeleteAll)
79 | t.Run("JudgeStates", testJudgeStatesSliceDeleteAll)
80 | t.Run("Problems", testProblemsSliceDeleteAll)
81 | t.Run("ProblemTags", testProblemTagsSliceDeleteAll)
82 | t.Run("ProblemTagMaps", testProblemTagMapsSliceDeleteAll)
83 | t.Run("RatingCalculations", testRatingCalculationsSliceDeleteAll)
84 | t.Run("RatingHistories", testRatingHistoriesSliceDeleteAll)
85 | t.Run("SubmissionStatistics", testSubmissionStatisticsSliceDeleteAll)
86 | t.Run("Users", testUsersSliceDeleteAll)
87 | t.Run("UserPrivileges", testUserPrivilegesSliceDeleteAll)
88 | }
89 |
90 | func TestExists(t *testing.T) {
91 | t.Run("Articles", testArticlesExists)
92 | t.Run("ArticleComments", testArticleCommentsExists)
93 | t.Run("Contests", testContestsExists)
94 | t.Run("ContestPlayers", testContestPlayersExists)
95 | t.Run("ContestRanklists", testContestRanklistsExists)
96 | t.Run("Files", testFilesExists)
97 | t.Run("FormattedCodes", testFormattedCodesExists)
98 | t.Run("JudgeStates", testJudgeStatesExists)
99 | t.Run("Problems", testProblemsExists)
100 | t.Run("ProblemTags", testProblemTagsExists)
101 | t.Run("ProblemTagMaps", testProblemTagMapsExists)
102 | t.Run("RatingCalculations", testRatingCalculationsExists)
103 | t.Run("RatingHistories", testRatingHistoriesExists)
104 | t.Run("SubmissionStatistics", testSubmissionStatisticsExists)
105 | t.Run("Users", testUsersExists)
106 | t.Run("UserPrivileges", testUserPrivilegesExists)
107 | }
108 |
109 | func TestFind(t *testing.T) {
110 | t.Run("Articles", testArticlesFind)
111 | t.Run("ArticleComments", testArticleCommentsFind)
112 | t.Run("Contests", testContestsFind)
113 | t.Run("ContestPlayers", testContestPlayersFind)
114 | t.Run("ContestRanklists", testContestRanklistsFind)
115 | t.Run("Files", testFilesFind)
116 | t.Run("FormattedCodes", testFormattedCodesFind)
117 | t.Run("JudgeStates", testJudgeStatesFind)
118 | t.Run("Problems", testProblemsFind)
119 | t.Run("ProblemTags", testProblemTagsFind)
120 | t.Run("ProblemTagMaps", testProblemTagMapsFind)
121 | t.Run("RatingCalculations", testRatingCalculationsFind)
122 | t.Run("RatingHistories", testRatingHistoriesFind)
123 | t.Run("SubmissionStatistics", testSubmissionStatisticsFind)
124 | t.Run("Users", testUsersFind)
125 | t.Run("UserPrivileges", testUserPrivilegesFind)
126 | }
127 |
128 | func TestBind(t *testing.T) {
129 | t.Run("Articles", testArticlesBind)
130 | t.Run("ArticleComments", testArticleCommentsBind)
131 | t.Run("Contests", testContestsBind)
132 | t.Run("ContestPlayers", testContestPlayersBind)
133 | t.Run("ContestRanklists", testContestRanklistsBind)
134 | t.Run("Files", testFilesBind)
135 | t.Run("FormattedCodes", testFormattedCodesBind)
136 | t.Run("JudgeStates", testJudgeStatesBind)
137 | t.Run("Problems", testProblemsBind)
138 | t.Run("ProblemTags", testProblemTagsBind)
139 | t.Run("ProblemTagMaps", testProblemTagMapsBind)
140 | t.Run("RatingCalculations", testRatingCalculationsBind)
141 | t.Run("RatingHistories", testRatingHistoriesBind)
142 | t.Run("SubmissionStatistics", testSubmissionStatisticsBind)
143 | t.Run("Users", testUsersBind)
144 | t.Run("UserPrivileges", testUserPrivilegesBind)
145 | }
146 |
147 | func TestOne(t *testing.T) {
148 | t.Run("Articles", testArticlesOne)
149 | t.Run("ArticleComments", testArticleCommentsOne)
150 | t.Run("Contests", testContestsOne)
151 | t.Run("ContestPlayers", testContestPlayersOne)
152 | t.Run("ContestRanklists", testContestRanklistsOne)
153 | t.Run("Files", testFilesOne)
154 | t.Run("FormattedCodes", testFormattedCodesOne)
155 | t.Run("JudgeStates", testJudgeStatesOne)
156 | t.Run("Problems", testProblemsOne)
157 | t.Run("ProblemTags", testProblemTagsOne)
158 | t.Run("ProblemTagMaps", testProblemTagMapsOne)
159 | t.Run("RatingCalculations", testRatingCalculationsOne)
160 | t.Run("RatingHistories", testRatingHistoriesOne)
161 | t.Run("SubmissionStatistics", testSubmissionStatisticsOne)
162 | t.Run("Users", testUsersOne)
163 | t.Run("UserPrivileges", testUserPrivilegesOne)
164 | }
165 |
166 | func TestAll(t *testing.T) {
167 | t.Run("Articles", testArticlesAll)
168 | t.Run("ArticleComments", testArticleCommentsAll)
169 | t.Run("Contests", testContestsAll)
170 | t.Run("ContestPlayers", testContestPlayersAll)
171 | t.Run("ContestRanklists", testContestRanklistsAll)
172 | t.Run("Files", testFilesAll)
173 | t.Run("FormattedCodes", testFormattedCodesAll)
174 | t.Run("JudgeStates", testJudgeStatesAll)
175 | t.Run("Problems", testProblemsAll)
176 | t.Run("ProblemTags", testProblemTagsAll)
177 | t.Run("ProblemTagMaps", testProblemTagMapsAll)
178 | t.Run("RatingCalculations", testRatingCalculationsAll)
179 | t.Run("RatingHistories", testRatingHistoriesAll)
180 | t.Run("SubmissionStatistics", testSubmissionStatisticsAll)
181 | t.Run("Users", testUsersAll)
182 | t.Run("UserPrivileges", testUserPrivilegesAll)
183 | }
184 |
185 | func TestCount(t *testing.T) {
186 | t.Run("Articles", testArticlesCount)
187 | t.Run("ArticleComments", testArticleCommentsCount)
188 | t.Run("Contests", testContestsCount)
189 | t.Run("ContestPlayers", testContestPlayersCount)
190 | t.Run("ContestRanklists", testContestRanklistsCount)
191 | t.Run("Files", testFilesCount)
192 | t.Run("FormattedCodes", testFormattedCodesCount)
193 | t.Run("JudgeStates", testJudgeStatesCount)
194 | t.Run("Problems", testProblemsCount)
195 | t.Run("ProblemTags", testProblemTagsCount)
196 | t.Run("ProblemTagMaps", testProblemTagMapsCount)
197 | t.Run("RatingCalculations", testRatingCalculationsCount)
198 | t.Run("RatingHistories", testRatingHistoriesCount)
199 | t.Run("SubmissionStatistics", testSubmissionStatisticsCount)
200 | t.Run("Users", testUsersCount)
201 | t.Run("UserPrivileges", testUserPrivilegesCount)
202 | }
203 |
204 | func TestHooks(t *testing.T) {
205 | t.Run("Articles", testArticlesHooks)
206 | t.Run("ArticleComments", testArticleCommentsHooks)
207 | t.Run("Contests", testContestsHooks)
208 | t.Run("ContestPlayers", testContestPlayersHooks)
209 | t.Run("ContestRanklists", testContestRanklistsHooks)
210 | t.Run("Files", testFilesHooks)
211 | t.Run("FormattedCodes", testFormattedCodesHooks)
212 | t.Run("JudgeStates", testJudgeStatesHooks)
213 | t.Run("Problems", testProblemsHooks)
214 | t.Run("ProblemTags", testProblemTagsHooks)
215 | t.Run("ProblemTagMaps", testProblemTagMapsHooks)
216 | t.Run("RatingCalculations", testRatingCalculationsHooks)
217 | t.Run("RatingHistories", testRatingHistoriesHooks)
218 | t.Run("SubmissionStatistics", testSubmissionStatisticsHooks)
219 | t.Run("Users", testUsersHooks)
220 | t.Run("UserPrivileges", testUserPrivilegesHooks)
221 | }
222 |
223 | func TestInsert(t *testing.T) {
224 | t.Run("Articles", testArticlesInsert)
225 | t.Run("Articles", testArticlesInsertWhitelist)
226 | t.Run("ArticleComments", testArticleCommentsInsert)
227 | t.Run("ArticleComments", testArticleCommentsInsertWhitelist)
228 | t.Run("Contests", testContestsInsert)
229 | t.Run("Contests", testContestsInsertWhitelist)
230 | t.Run("ContestPlayers", testContestPlayersInsert)
231 | t.Run("ContestPlayers", testContestPlayersInsertWhitelist)
232 | t.Run("ContestRanklists", testContestRanklistsInsert)
233 | t.Run("ContestRanklists", testContestRanklistsInsertWhitelist)
234 | t.Run("Files", testFilesInsert)
235 | t.Run("Files", testFilesInsertWhitelist)
236 | t.Run("FormattedCodes", testFormattedCodesInsert)
237 | t.Run("FormattedCodes", testFormattedCodesInsertWhitelist)
238 | t.Run("JudgeStates", testJudgeStatesInsert)
239 | t.Run("JudgeStates", testJudgeStatesInsertWhitelist)
240 | t.Run("Problems", testProblemsInsert)
241 | t.Run("Problems", testProblemsInsertWhitelist)
242 | t.Run("ProblemTags", testProblemTagsInsert)
243 | t.Run("ProblemTags", testProblemTagsInsertWhitelist)
244 | t.Run("ProblemTagMaps", testProblemTagMapsInsert)
245 | t.Run("ProblemTagMaps", testProblemTagMapsInsertWhitelist)
246 | t.Run("RatingCalculations", testRatingCalculationsInsert)
247 | t.Run("RatingCalculations", testRatingCalculationsInsertWhitelist)
248 | t.Run("RatingHistories", testRatingHistoriesInsert)
249 | t.Run("RatingHistories", testRatingHistoriesInsertWhitelist)
250 | t.Run("SubmissionStatistics", testSubmissionStatisticsInsert)
251 | t.Run("SubmissionStatistics", testSubmissionStatisticsInsertWhitelist)
252 | t.Run("Users", testUsersInsert)
253 | t.Run("Users", testUsersInsertWhitelist)
254 | t.Run("UserPrivileges", testUserPrivilegesInsert)
255 | t.Run("UserPrivileges", testUserPrivilegesInsertWhitelist)
256 | }
257 |
258 | // TestToOne tests cannot be run in parallel
259 | // or deadlocks can occur.
260 | func TestToOne(t *testing.T) {}
261 |
262 | // TestOneToOne tests cannot be run in parallel
263 | // or deadlocks can occur.
264 | func TestOneToOne(t *testing.T) {}
265 |
266 | // TestToMany tests cannot be run in parallel
267 | // or deadlocks can occur.
268 | func TestToMany(t *testing.T) {}
269 |
270 | // TestToOneSet tests cannot be run in parallel
271 | // or deadlocks can occur.
272 | func TestToOneSet(t *testing.T) {}
273 |
274 | // TestToOneRemove tests cannot be run in parallel
275 | // or deadlocks can occur.
276 | func TestToOneRemove(t *testing.T) {}
277 |
278 | // TestOneToOneSet tests cannot be run in parallel
279 | // or deadlocks can occur.
280 | func TestOneToOneSet(t *testing.T) {}
281 |
282 | // TestOneToOneRemove tests cannot be run in parallel
283 | // or deadlocks can occur.
284 | func TestOneToOneRemove(t *testing.T) {}
285 |
286 | // TestToManyAdd tests cannot be run in parallel
287 | // or deadlocks can occur.
288 | func TestToManyAdd(t *testing.T) {}
289 |
290 | // TestToManySet tests cannot be run in parallel
291 | // or deadlocks can occur.
292 | func TestToManySet(t *testing.T) {}
293 |
294 | // TestToManyRemove tests cannot be run in parallel
295 | // or deadlocks can occur.
296 | func TestToManyRemove(t *testing.T) {}
297 |
298 | func TestReload(t *testing.T) {
299 | t.Run("Articles", testArticlesReload)
300 | t.Run("ArticleComments", testArticleCommentsReload)
301 | t.Run("Contests", testContestsReload)
302 | t.Run("ContestPlayers", testContestPlayersReload)
303 | t.Run("ContestRanklists", testContestRanklistsReload)
304 | t.Run("Files", testFilesReload)
305 | t.Run("FormattedCodes", testFormattedCodesReload)
306 | t.Run("JudgeStates", testJudgeStatesReload)
307 | t.Run("Problems", testProblemsReload)
308 | t.Run("ProblemTags", testProblemTagsReload)
309 | t.Run("ProblemTagMaps", testProblemTagMapsReload)
310 | t.Run("RatingCalculations", testRatingCalculationsReload)
311 | t.Run("RatingHistories", testRatingHistoriesReload)
312 | t.Run("SubmissionStatistics", testSubmissionStatisticsReload)
313 | t.Run("Users", testUsersReload)
314 | t.Run("UserPrivileges", testUserPrivilegesReload)
315 | }
316 |
317 | func TestReloadAll(t *testing.T) {
318 | t.Run("Articles", testArticlesReloadAll)
319 | t.Run("ArticleComments", testArticleCommentsReloadAll)
320 | t.Run("Contests", testContestsReloadAll)
321 | t.Run("ContestPlayers", testContestPlayersReloadAll)
322 | t.Run("ContestRanklists", testContestRanklistsReloadAll)
323 | t.Run("Files", testFilesReloadAll)
324 | t.Run("FormattedCodes", testFormattedCodesReloadAll)
325 | t.Run("JudgeStates", testJudgeStatesReloadAll)
326 | t.Run("Problems", testProblemsReloadAll)
327 | t.Run("ProblemTags", testProblemTagsReloadAll)
328 | t.Run("ProblemTagMaps", testProblemTagMapsReloadAll)
329 | t.Run("RatingCalculations", testRatingCalculationsReloadAll)
330 | t.Run("RatingHistories", testRatingHistoriesReloadAll)
331 | t.Run("SubmissionStatistics", testSubmissionStatisticsReloadAll)
332 | t.Run("Users", testUsersReloadAll)
333 | t.Run("UserPrivileges", testUserPrivilegesReloadAll)
334 | }
335 |
336 | func TestSelect(t *testing.T) {
337 | t.Run("Articles", testArticlesSelect)
338 | t.Run("ArticleComments", testArticleCommentsSelect)
339 | t.Run("Contests", testContestsSelect)
340 | t.Run("ContestPlayers", testContestPlayersSelect)
341 | t.Run("ContestRanklists", testContestRanklistsSelect)
342 | t.Run("Files", testFilesSelect)
343 | t.Run("FormattedCodes", testFormattedCodesSelect)
344 | t.Run("JudgeStates", testJudgeStatesSelect)
345 | t.Run("Problems", testProblemsSelect)
346 | t.Run("ProblemTags", testProblemTagsSelect)
347 | t.Run("ProblemTagMaps", testProblemTagMapsSelect)
348 | t.Run("RatingCalculations", testRatingCalculationsSelect)
349 | t.Run("RatingHistories", testRatingHistoriesSelect)
350 | t.Run("SubmissionStatistics", testSubmissionStatisticsSelect)
351 | t.Run("Users", testUsersSelect)
352 | t.Run("UserPrivileges", testUserPrivilegesSelect)
353 | }
354 |
355 | func TestUpdate(t *testing.T) {
356 | t.Run("Articles", testArticlesUpdate)
357 | t.Run("ArticleComments", testArticleCommentsUpdate)
358 | t.Run("Contests", testContestsUpdate)
359 | t.Run("ContestPlayers", testContestPlayersUpdate)
360 | t.Run("ContestRanklists", testContestRanklistsUpdate)
361 | t.Run("Files", testFilesUpdate)
362 | t.Run("FormattedCodes", testFormattedCodesUpdate)
363 | t.Run("JudgeStates", testJudgeStatesUpdate)
364 | t.Run("Problems", testProblemsUpdate)
365 | t.Run("ProblemTags", testProblemTagsUpdate)
366 | t.Run("ProblemTagMaps", testProblemTagMapsUpdate)
367 | t.Run("RatingCalculations", testRatingCalculationsUpdate)
368 | t.Run("RatingHistories", testRatingHistoriesUpdate)
369 | t.Run("SubmissionStatistics", testSubmissionStatisticsUpdate)
370 | t.Run("Users", testUsersUpdate)
371 | t.Run("UserPrivileges", testUserPrivilegesUpdate)
372 | }
373 |
374 | func TestSliceUpdateAll(t *testing.T) {
375 | t.Run("Articles", testArticlesSliceUpdateAll)
376 | t.Run("ArticleComments", testArticleCommentsSliceUpdateAll)
377 | t.Run("Contests", testContestsSliceUpdateAll)
378 | t.Run("ContestPlayers", testContestPlayersSliceUpdateAll)
379 | t.Run("ContestRanklists", testContestRanklistsSliceUpdateAll)
380 | t.Run("Files", testFilesSliceUpdateAll)
381 | t.Run("FormattedCodes", testFormattedCodesSliceUpdateAll)
382 | t.Run("JudgeStates", testJudgeStatesSliceUpdateAll)
383 | t.Run("Problems", testProblemsSliceUpdateAll)
384 | t.Run("ProblemTags", testProblemTagsSliceUpdateAll)
385 | t.Run("ProblemTagMaps", testProblemTagMapsSliceUpdateAll)
386 | t.Run("RatingCalculations", testRatingCalculationsSliceUpdateAll)
387 | t.Run("RatingHistories", testRatingHistoriesSliceUpdateAll)
388 | t.Run("SubmissionStatistics", testSubmissionStatisticsSliceUpdateAll)
389 | t.Run("Users", testUsersSliceUpdateAll)
390 | t.Run("UserPrivileges", testUserPrivilegesSliceUpdateAll)
391 | }
392 |
--------------------------------------------------------------------------------
/models/file_test.go:
--------------------------------------------------------------------------------
1 | // Code generated by SQLBoiler 3.5.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT.
2 | // This file is meant to be re-generated in place and/or deleted at any time.
3 |
4 | package models
5 |
6 | import (
7 | "bytes"
8 | "context"
9 | "reflect"
10 | "testing"
11 |
12 | "github.com/volatiletech/sqlboiler/boil"
13 | "github.com/volatiletech/sqlboiler/queries"
14 | "github.com/volatiletech/sqlboiler/randomize"
15 | "github.com/volatiletech/sqlboiler/strmangle"
16 | )
17 |
18 | var (
19 | // Relationships sometimes use the reflection helper queries.Equal/queries.Assign
20 | // so force a package dependency in case they don't.
21 | _ = queries.Equal
22 | )
23 |
24 | func testFiles(t *testing.T) {
25 | t.Parallel()
26 |
27 | query := Files()
28 |
29 | if query.Query == nil {
30 | t.Error("expected a query, got nothing")
31 | }
32 | }
33 |
34 | func testFilesDelete(t *testing.T) {
35 | t.Parallel()
36 |
37 | seed := randomize.NewSeed()
38 | var err error
39 | o := &File{}
40 | if err = randomize.Struct(seed, o, fileDBTypes, true, fileColumnsWithDefault...); err != nil {
41 | t.Errorf("Unable to randomize File struct: %s", err)
42 | }
43 |
44 | ctx := context.Background()
45 | tx := MustTx(boil.BeginTx(ctx, nil))
46 | defer func() { _ = tx.Rollback() }()
47 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
48 | t.Error(err)
49 | }
50 |
51 | if rowsAff, err := o.Delete(ctx, tx); err != nil {
52 | t.Error(err)
53 | } else if rowsAff != 1 {
54 | t.Error("should only have deleted one row, but affected:", rowsAff)
55 | }
56 |
57 | count, err := Files().Count(ctx, tx)
58 | if err != nil {
59 | t.Error(err)
60 | }
61 |
62 | if count != 0 {
63 | t.Error("want zero records, got:", count)
64 | }
65 | }
66 |
67 | func testFilesQueryDeleteAll(t *testing.T) {
68 | t.Parallel()
69 |
70 | seed := randomize.NewSeed()
71 | var err error
72 | o := &File{}
73 | if err = randomize.Struct(seed, o, fileDBTypes, true, fileColumnsWithDefault...); err != nil {
74 | t.Errorf("Unable to randomize File struct: %s", err)
75 | }
76 |
77 | ctx := context.Background()
78 | tx := MustTx(boil.BeginTx(ctx, nil))
79 | defer func() { _ = tx.Rollback() }()
80 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
81 | t.Error(err)
82 | }
83 |
84 | if rowsAff, err := Files().DeleteAll(ctx, tx); err != nil {
85 | t.Error(err)
86 | } else if rowsAff != 1 {
87 | t.Error("should only have deleted one row, but affected:", rowsAff)
88 | }
89 |
90 | count, err := Files().Count(ctx, tx)
91 | if err != nil {
92 | t.Error(err)
93 | }
94 |
95 | if count != 0 {
96 | t.Error("want zero records, got:", count)
97 | }
98 | }
99 |
100 | func testFilesSliceDeleteAll(t *testing.T) {
101 | t.Parallel()
102 |
103 | seed := randomize.NewSeed()
104 | var err error
105 | o := &File{}
106 | if err = randomize.Struct(seed, o, fileDBTypes, true, fileColumnsWithDefault...); err != nil {
107 | t.Errorf("Unable to randomize File struct: %s", err)
108 | }
109 |
110 | ctx := context.Background()
111 | tx := MustTx(boil.BeginTx(ctx, nil))
112 | defer func() { _ = tx.Rollback() }()
113 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
114 | t.Error(err)
115 | }
116 |
117 | slice := FileSlice{o}
118 |
119 | if rowsAff, err := slice.DeleteAll(ctx, tx); err != nil {
120 | t.Error(err)
121 | } else if rowsAff != 1 {
122 | t.Error("should only have deleted one row, but affected:", rowsAff)
123 | }
124 |
125 | count, err := Files().Count(ctx, tx)
126 | if err != nil {
127 | t.Error(err)
128 | }
129 |
130 | if count != 0 {
131 | t.Error("want zero records, got:", count)
132 | }
133 | }
134 |
135 | func testFilesExists(t *testing.T) {
136 | t.Parallel()
137 |
138 | seed := randomize.NewSeed()
139 | var err error
140 | o := &File{}
141 | if err = randomize.Struct(seed, o, fileDBTypes, true, fileColumnsWithDefault...); err != nil {
142 | t.Errorf("Unable to randomize File struct: %s", err)
143 | }
144 |
145 | ctx := context.Background()
146 | tx := MustTx(boil.BeginTx(ctx, nil))
147 | defer func() { _ = tx.Rollback() }()
148 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
149 | t.Error(err)
150 | }
151 |
152 | e, err := FileExists(ctx, tx, o.ID)
153 | if err != nil {
154 | t.Errorf("Unable to check if File exists: %s", err)
155 | }
156 | if !e {
157 | t.Errorf("Expected FileExists to return true, but got false.")
158 | }
159 | }
160 |
161 | func testFilesFind(t *testing.T) {
162 | t.Parallel()
163 |
164 | seed := randomize.NewSeed()
165 | var err error
166 | o := &File{}
167 | if err = randomize.Struct(seed, o, fileDBTypes, true, fileColumnsWithDefault...); err != nil {
168 | t.Errorf("Unable to randomize File struct: %s", err)
169 | }
170 |
171 | ctx := context.Background()
172 | tx := MustTx(boil.BeginTx(ctx, nil))
173 | defer func() { _ = tx.Rollback() }()
174 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
175 | t.Error(err)
176 | }
177 |
178 | fileFound, err := FindFile(ctx, tx, o.ID)
179 | if err != nil {
180 | t.Error(err)
181 | }
182 |
183 | if fileFound == nil {
184 | t.Error("want a record, got nil")
185 | }
186 | }
187 |
188 | func testFilesBind(t *testing.T) {
189 | t.Parallel()
190 |
191 | seed := randomize.NewSeed()
192 | var err error
193 | o := &File{}
194 | if err = randomize.Struct(seed, o, fileDBTypes, true, fileColumnsWithDefault...); err != nil {
195 | t.Errorf("Unable to randomize File struct: %s", err)
196 | }
197 |
198 | ctx := context.Background()
199 | tx := MustTx(boil.BeginTx(ctx, nil))
200 | defer func() { _ = tx.Rollback() }()
201 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
202 | t.Error(err)
203 | }
204 |
205 | if err = Files().Bind(ctx, tx, o); err != nil {
206 | t.Error(err)
207 | }
208 | }
209 |
210 | func testFilesOne(t *testing.T) {
211 | t.Parallel()
212 |
213 | seed := randomize.NewSeed()
214 | var err error
215 | o := &File{}
216 | if err = randomize.Struct(seed, o, fileDBTypes, true, fileColumnsWithDefault...); err != nil {
217 | t.Errorf("Unable to randomize File struct: %s", err)
218 | }
219 |
220 | ctx := context.Background()
221 | tx := MustTx(boil.BeginTx(ctx, nil))
222 | defer func() { _ = tx.Rollback() }()
223 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
224 | t.Error(err)
225 | }
226 |
227 | if x, err := Files().One(ctx, tx); err != nil {
228 | t.Error(err)
229 | } else if x == nil {
230 | t.Error("expected to get a non nil record")
231 | }
232 | }
233 |
234 | func testFilesAll(t *testing.T) {
235 | t.Parallel()
236 |
237 | seed := randomize.NewSeed()
238 | var err error
239 | fileOne := &File{}
240 | fileTwo := &File{}
241 | if err = randomize.Struct(seed, fileOne, fileDBTypes, false, fileColumnsWithDefault...); err != nil {
242 | t.Errorf("Unable to randomize File struct: %s", err)
243 | }
244 | if err = randomize.Struct(seed, fileTwo, fileDBTypes, false, fileColumnsWithDefault...); err != nil {
245 | t.Errorf("Unable to randomize File struct: %s", err)
246 | }
247 |
248 | ctx := context.Background()
249 | tx := MustTx(boil.BeginTx(ctx, nil))
250 | defer func() { _ = tx.Rollback() }()
251 | if err = fileOne.Insert(ctx, tx, boil.Infer()); err != nil {
252 | t.Error(err)
253 | }
254 | if err = fileTwo.Insert(ctx, tx, boil.Infer()); err != nil {
255 | t.Error(err)
256 | }
257 |
258 | slice, err := Files().All(ctx, tx)
259 | if err != nil {
260 | t.Error(err)
261 | }
262 |
263 | if len(slice) != 2 {
264 | t.Error("want 2 records, got:", len(slice))
265 | }
266 | }
267 |
268 | func testFilesCount(t *testing.T) {
269 | t.Parallel()
270 |
271 | var err error
272 | seed := randomize.NewSeed()
273 | fileOne := &File{}
274 | fileTwo := &File{}
275 | if err = randomize.Struct(seed, fileOne, fileDBTypes, false, fileColumnsWithDefault...); err != nil {
276 | t.Errorf("Unable to randomize File struct: %s", err)
277 | }
278 | if err = randomize.Struct(seed, fileTwo, fileDBTypes, false, fileColumnsWithDefault...); err != nil {
279 | t.Errorf("Unable to randomize File struct: %s", err)
280 | }
281 |
282 | ctx := context.Background()
283 | tx := MustTx(boil.BeginTx(ctx, nil))
284 | defer func() { _ = tx.Rollback() }()
285 | if err = fileOne.Insert(ctx, tx, boil.Infer()); err != nil {
286 | t.Error(err)
287 | }
288 | if err = fileTwo.Insert(ctx, tx, boil.Infer()); err != nil {
289 | t.Error(err)
290 | }
291 |
292 | count, err := Files().Count(ctx, tx)
293 | if err != nil {
294 | t.Error(err)
295 | }
296 |
297 | if count != 2 {
298 | t.Error("want 2 records, got:", count)
299 | }
300 | }
301 |
302 | func fileBeforeInsertHook(ctx context.Context, e boil.ContextExecutor, o *File) error {
303 | *o = File{}
304 | return nil
305 | }
306 |
307 | func fileAfterInsertHook(ctx context.Context, e boil.ContextExecutor, o *File) error {
308 | *o = File{}
309 | return nil
310 | }
311 |
312 | func fileAfterSelectHook(ctx context.Context, e boil.ContextExecutor, o *File) error {
313 | *o = File{}
314 | return nil
315 | }
316 |
317 | func fileBeforeUpdateHook(ctx context.Context, e boil.ContextExecutor, o *File) error {
318 | *o = File{}
319 | return nil
320 | }
321 |
322 | func fileAfterUpdateHook(ctx context.Context, e boil.ContextExecutor, o *File) error {
323 | *o = File{}
324 | return nil
325 | }
326 |
327 | func fileBeforeDeleteHook(ctx context.Context, e boil.ContextExecutor, o *File) error {
328 | *o = File{}
329 | return nil
330 | }
331 |
332 | func fileAfterDeleteHook(ctx context.Context, e boil.ContextExecutor, o *File) error {
333 | *o = File{}
334 | return nil
335 | }
336 |
337 | func fileBeforeUpsertHook(ctx context.Context, e boil.ContextExecutor, o *File) error {
338 | *o = File{}
339 | return nil
340 | }
341 |
342 | func fileAfterUpsertHook(ctx context.Context, e boil.ContextExecutor, o *File) error {
343 | *o = File{}
344 | return nil
345 | }
346 |
347 | func testFilesHooks(t *testing.T) {
348 | t.Parallel()
349 |
350 | var err error
351 |
352 | ctx := context.Background()
353 | empty := &File{}
354 | o := &File{}
355 |
356 | seed := randomize.NewSeed()
357 | if err = randomize.Struct(seed, o, fileDBTypes, false); err != nil {
358 | t.Errorf("Unable to randomize File object: %s", err)
359 | }
360 |
361 | AddFileHook(boil.BeforeInsertHook, fileBeforeInsertHook)
362 | if err = o.doBeforeInsertHooks(ctx, nil); err != nil {
363 | t.Errorf("Unable to execute doBeforeInsertHooks: %s", err)
364 | }
365 | if !reflect.DeepEqual(o, empty) {
366 | t.Errorf("Expected BeforeInsertHook function to empty object, but got: %#v", o)
367 | }
368 | fileBeforeInsertHooks = []FileHook{}
369 |
370 | AddFileHook(boil.AfterInsertHook, fileAfterInsertHook)
371 | if err = o.doAfterInsertHooks(ctx, nil); err != nil {
372 | t.Errorf("Unable to execute doAfterInsertHooks: %s", err)
373 | }
374 | if !reflect.DeepEqual(o, empty) {
375 | t.Errorf("Expected AfterInsertHook function to empty object, but got: %#v", o)
376 | }
377 | fileAfterInsertHooks = []FileHook{}
378 |
379 | AddFileHook(boil.AfterSelectHook, fileAfterSelectHook)
380 | if err = o.doAfterSelectHooks(ctx, nil); err != nil {
381 | t.Errorf("Unable to execute doAfterSelectHooks: %s", err)
382 | }
383 | if !reflect.DeepEqual(o, empty) {
384 | t.Errorf("Expected AfterSelectHook function to empty object, but got: %#v", o)
385 | }
386 | fileAfterSelectHooks = []FileHook{}
387 |
388 | AddFileHook(boil.BeforeUpdateHook, fileBeforeUpdateHook)
389 | if err = o.doBeforeUpdateHooks(ctx, nil); err != nil {
390 | t.Errorf("Unable to execute doBeforeUpdateHooks: %s", err)
391 | }
392 | if !reflect.DeepEqual(o, empty) {
393 | t.Errorf("Expected BeforeUpdateHook function to empty object, but got: %#v", o)
394 | }
395 | fileBeforeUpdateHooks = []FileHook{}
396 |
397 | AddFileHook(boil.AfterUpdateHook, fileAfterUpdateHook)
398 | if err = o.doAfterUpdateHooks(ctx, nil); err != nil {
399 | t.Errorf("Unable to execute doAfterUpdateHooks: %s", err)
400 | }
401 | if !reflect.DeepEqual(o, empty) {
402 | t.Errorf("Expected AfterUpdateHook function to empty object, but got: %#v", o)
403 | }
404 | fileAfterUpdateHooks = []FileHook{}
405 |
406 | AddFileHook(boil.BeforeDeleteHook, fileBeforeDeleteHook)
407 | if err = o.doBeforeDeleteHooks(ctx, nil); err != nil {
408 | t.Errorf("Unable to execute doBeforeDeleteHooks: %s", err)
409 | }
410 | if !reflect.DeepEqual(o, empty) {
411 | t.Errorf("Expected BeforeDeleteHook function to empty object, but got: %#v", o)
412 | }
413 | fileBeforeDeleteHooks = []FileHook{}
414 |
415 | AddFileHook(boil.AfterDeleteHook, fileAfterDeleteHook)
416 | if err = o.doAfterDeleteHooks(ctx, nil); err != nil {
417 | t.Errorf("Unable to execute doAfterDeleteHooks: %s", err)
418 | }
419 | if !reflect.DeepEqual(o, empty) {
420 | t.Errorf("Expected AfterDeleteHook function to empty object, but got: %#v", o)
421 | }
422 | fileAfterDeleteHooks = []FileHook{}
423 |
424 | AddFileHook(boil.BeforeUpsertHook, fileBeforeUpsertHook)
425 | if err = o.doBeforeUpsertHooks(ctx, nil); err != nil {
426 | t.Errorf("Unable to execute doBeforeUpsertHooks: %s", err)
427 | }
428 | if !reflect.DeepEqual(o, empty) {
429 | t.Errorf("Expected BeforeUpsertHook function to empty object, but got: %#v", o)
430 | }
431 | fileBeforeUpsertHooks = []FileHook{}
432 |
433 | AddFileHook(boil.AfterUpsertHook, fileAfterUpsertHook)
434 | if err = o.doAfterUpsertHooks(ctx, nil); err != nil {
435 | t.Errorf("Unable to execute doAfterUpsertHooks: %s", err)
436 | }
437 | if !reflect.DeepEqual(o, empty) {
438 | t.Errorf("Expected AfterUpsertHook function to empty object, but got: %#v", o)
439 | }
440 | fileAfterUpsertHooks = []FileHook{}
441 | }
442 |
443 | func testFilesInsert(t *testing.T) {
444 | t.Parallel()
445 |
446 | seed := randomize.NewSeed()
447 | var err error
448 | o := &File{}
449 | if err = randomize.Struct(seed, o, fileDBTypes, true, fileColumnsWithDefault...); err != nil {
450 | t.Errorf("Unable to randomize File struct: %s", err)
451 | }
452 |
453 | ctx := context.Background()
454 | tx := MustTx(boil.BeginTx(ctx, nil))
455 | defer func() { _ = tx.Rollback() }()
456 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
457 | t.Error(err)
458 | }
459 |
460 | count, err := Files().Count(ctx, tx)
461 | if err != nil {
462 | t.Error(err)
463 | }
464 |
465 | if count != 1 {
466 | t.Error("want one record, got:", count)
467 | }
468 | }
469 |
470 | func testFilesInsertWhitelist(t *testing.T) {
471 | t.Parallel()
472 |
473 | seed := randomize.NewSeed()
474 | var err error
475 | o := &File{}
476 | if err = randomize.Struct(seed, o, fileDBTypes, true); err != nil {
477 | t.Errorf("Unable to randomize File struct: %s", err)
478 | }
479 |
480 | ctx := context.Background()
481 | tx := MustTx(boil.BeginTx(ctx, nil))
482 | defer func() { _ = tx.Rollback() }()
483 | if err = o.Insert(ctx, tx, boil.Whitelist(fileColumnsWithoutDefault...)); err != nil {
484 | t.Error(err)
485 | }
486 |
487 | count, err := Files().Count(ctx, tx)
488 | if err != nil {
489 | t.Error(err)
490 | }
491 |
492 | if count != 1 {
493 | t.Error("want one record, got:", count)
494 | }
495 | }
496 |
497 | func testFilesReload(t *testing.T) {
498 | t.Parallel()
499 |
500 | seed := randomize.NewSeed()
501 | var err error
502 | o := &File{}
503 | if err = randomize.Struct(seed, o, fileDBTypes, true, fileColumnsWithDefault...); err != nil {
504 | t.Errorf("Unable to randomize File struct: %s", err)
505 | }
506 |
507 | ctx := context.Background()
508 | tx := MustTx(boil.BeginTx(ctx, nil))
509 | defer func() { _ = tx.Rollback() }()
510 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
511 | t.Error(err)
512 | }
513 |
514 | if err = o.Reload(ctx, tx); err != nil {
515 | t.Error(err)
516 | }
517 | }
518 |
519 | func testFilesReloadAll(t *testing.T) {
520 | t.Parallel()
521 |
522 | seed := randomize.NewSeed()
523 | var err error
524 | o := &File{}
525 | if err = randomize.Struct(seed, o, fileDBTypes, true, fileColumnsWithDefault...); err != nil {
526 | t.Errorf("Unable to randomize File struct: %s", err)
527 | }
528 |
529 | ctx := context.Background()
530 | tx := MustTx(boil.BeginTx(ctx, nil))
531 | defer func() { _ = tx.Rollback() }()
532 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
533 | t.Error(err)
534 | }
535 |
536 | slice := FileSlice{o}
537 |
538 | if err = slice.ReloadAll(ctx, tx); err != nil {
539 | t.Error(err)
540 | }
541 | }
542 |
543 | func testFilesSelect(t *testing.T) {
544 | t.Parallel()
545 |
546 | seed := randomize.NewSeed()
547 | var err error
548 | o := &File{}
549 | if err = randomize.Struct(seed, o, fileDBTypes, true, fileColumnsWithDefault...); err != nil {
550 | t.Errorf("Unable to randomize File struct: %s", err)
551 | }
552 |
553 | ctx := context.Background()
554 | tx := MustTx(boil.BeginTx(ctx, nil))
555 | defer func() { _ = tx.Rollback() }()
556 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
557 | t.Error(err)
558 | }
559 |
560 | slice, err := Files().All(ctx, tx)
561 | if err != nil {
562 | t.Error(err)
563 | }
564 |
565 | if len(slice) != 1 {
566 | t.Error("want one record, got:", len(slice))
567 | }
568 | }
569 |
570 | var (
571 | fileDBTypes = map[string]string{`ID`: `int`, `Type`: `varchar`, `MD5`: `varchar`}
572 | _ = bytes.MinRead
573 | )
574 |
575 | func testFilesUpdate(t *testing.T) {
576 | t.Parallel()
577 |
578 | if 0 == len(filePrimaryKeyColumns) {
579 | t.Skip("Skipping table with no primary key columns")
580 | }
581 | if len(fileAllColumns) == len(filePrimaryKeyColumns) {
582 | t.Skip("Skipping table with only primary key columns")
583 | }
584 |
585 | seed := randomize.NewSeed()
586 | var err error
587 | o := &File{}
588 | if err = randomize.Struct(seed, o, fileDBTypes, true, fileColumnsWithDefault...); err != nil {
589 | t.Errorf("Unable to randomize File struct: %s", err)
590 | }
591 |
592 | ctx := context.Background()
593 | tx := MustTx(boil.BeginTx(ctx, nil))
594 | defer func() { _ = tx.Rollback() }()
595 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
596 | t.Error(err)
597 | }
598 |
599 | count, err := Files().Count(ctx, tx)
600 | if err != nil {
601 | t.Error(err)
602 | }
603 |
604 | if count != 1 {
605 | t.Error("want one record, got:", count)
606 | }
607 |
608 | if err = randomize.Struct(seed, o, fileDBTypes, true, filePrimaryKeyColumns...); err != nil {
609 | t.Errorf("Unable to randomize File struct: %s", err)
610 | }
611 |
612 | if rowsAff, err := o.Update(ctx, tx, boil.Infer()); err != nil {
613 | t.Error(err)
614 | } else if rowsAff != 1 {
615 | t.Error("should only affect one row but affected", rowsAff)
616 | }
617 | }
618 |
619 | func testFilesSliceUpdateAll(t *testing.T) {
620 | t.Parallel()
621 |
622 | if len(fileAllColumns) == len(filePrimaryKeyColumns) {
623 | t.Skip("Skipping table with only primary key columns")
624 | }
625 |
626 | seed := randomize.NewSeed()
627 | var err error
628 | o := &File{}
629 | if err = randomize.Struct(seed, o, fileDBTypes, true, fileColumnsWithDefault...); err != nil {
630 | t.Errorf("Unable to randomize File struct: %s", err)
631 | }
632 |
633 | ctx := context.Background()
634 | tx := MustTx(boil.BeginTx(ctx, nil))
635 | defer func() { _ = tx.Rollback() }()
636 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
637 | t.Error(err)
638 | }
639 |
640 | count, err := Files().Count(ctx, tx)
641 | if err != nil {
642 | t.Error(err)
643 | }
644 |
645 | if count != 1 {
646 | t.Error("want one record, got:", count)
647 | }
648 |
649 | if err = randomize.Struct(seed, o, fileDBTypes, true, filePrimaryKeyColumns...); err != nil {
650 | t.Errorf("Unable to randomize File struct: %s", err)
651 | }
652 |
653 | // Remove Primary keys and unique columns from what we plan to update
654 | var fields []string
655 | if strmangle.StringSliceMatch(fileAllColumns, filePrimaryKeyColumns) {
656 | fields = fileAllColumns
657 | } else {
658 | fields = strmangle.SetComplement(
659 | fileAllColumns,
660 | filePrimaryKeyColumns,
661 | )
662 | }
663 |
664 | value := reflect.Indirect(reflect.ValueOf(o))
665 | typ := reflect.TypeOf(o).Elem()
666 | n := typ.NumField()
667 |
668 | updateMap := M{}
669 | for _, col := range fields {
670 | for i := 0; i < n; i++ {
671 | f := typ.Field(i)
672 | if f.Tag.Get("boil") == col {
673 | updateMap[col] = value.Field(i).Interface()
674 | }
675 | }
676 | }
677 |
678 | slice := FileSlice{o}
679 | if rowsAff, err := slice.UpdateAll(ctx, tx, updateMap); err != nil {
680 | t.Error(err)
681 | } else if rowsAff != 1 {
682 | t.Error("wanted one record updated but got", rowsAff)
683 | }
684 | }
685 |
686 | func testFilesUpsert(t *testing.T) {
687 | t.Parallel()
688 |
689 | if len(fileAllColumns) == len(filePrimaryKeyColumns) {
690 | t.Skip("Skipping table with only primary key columns")
691 | }
692 | if len(mySQLFileUniqueColumns) == 0 {
693 | t.Skip("Skipping table with no unique columns to conflict on")
694 | }
695 |
696 | seed := randomize.NewSeed()
697 | var err error
698 | // Attempt the INSERT side of an UPSERT
699 | o := File{}
700 | if err = randomize.Struct(seed, &o, fileDBTypes, false); err != nil {
701 | t.Errorf("Unable to randomize File struct: %s", err)
702 | }
703 |
704 | ctx := context.Background()
705 | tx := MustTx(boil.BeginTx(ctx, nil))
706 | defer func() { _ = tx.Rollback() }()
707 | if err = o.Upsert(ctx, tx, boil.Infer(), boil.Infer()); err != nil {
708 | t.Errorf("Unable to upsert File: %s", err)
709 | }
710 |
711 | count, err := Files().Count(ctx, tx)
712 | if err != nil {
713 | t.Error(err)
714 | }
715 | if count != 1 {
716 | t.Error("want one record, got:", count)
717 | }
718 |
719 | // Attempt the UPDATE side of an UPSERT
720 | if err = randomize.Struct(seed, &o, fileDBTypes, false, filePrimaryKeyColumns...); err != nil {
721 | t.Errorf("Unable to randomize File struct: %s", err)
722 | }
723 |
724 | if err = o.Upsert(ctx, tx, boil.Infer(), boil.Infer()); err != nil {
725 | t.Errorf("Unable to upsert File: %s", err)
726 | }
727 |
728 | count, err = Files().Count(ctx, tx)
729 | if err != nil {
730 | t.Error(err)
731 | }
732 | if count != 1 {
733 | t.Error("want one record, got:", count)
734 | }
735 | }
736 |
--------------------------------------------------------------------------------
/models/user_test.go:
--------------------------------------------------------------------------------
1 | // Code generated by SQLBoiler 3.5.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT.
2 | // This file is meant to be re-generated in place and/or deleted at any time.
3 |
4 | package models
5 |
6 | import (
7 | "bytes"
8 | "context"
9 | "reflect"
10 | "testing"
11 |
12 | "github.com/volatiletech/sqlboiler/boil"
13 | "github.com/volatiletech/sqlboiler/queries"
14 | "github.com/volatiletech/sqlboiler/randomize"
15 | "github.com/volatiletech/sqlboiler/strmangle"
16 | )
17 |
18 | var (
19 | // Relationships sometimes use the reflection helper queries.Equal/queries.Assign
20 | // so force a package dependency in case they don't.
21 | _ = queries.Equal
22 | )
23 |
24 | func testUsers(t *testing.T) {
25 | t.Parallel()
26 |
27 | query := Users()
28 |
29 | if query.Query == nil {
30 | t.Error("expected a query, got nothing")
31 | }
32 | }
33 |
34 | func testUsersDelete(t *testing.T) {
35 | t.Parallel()
36 |
37 | seed := randomize.NewSeed()
38 | var err error
39 | o := &User{}
40 | if err = randomize.Struct(seed, o, userDBTypes, true, userColumnsWithDefault...); err != nil {
41 | t.Errorf("Unable to randomize User struct: %s", err)
42 | }
43 |
44 | ctx := context.Background()
45 | tx := MustTx(boil.BeginTx(ctx, nil))
46 | defer func() { _ = tx.Rollback() }()
47 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
48 | t.Error(err)
49 | }
50 |
51 | if rowsAff, err := o.Delete(ctx, tx); err != nil {
52 | t.Error(err)
53 | } else if rowsAff != 1 {
54 | t.Error("should only have deleted one row, but affected:", rowsAff)
55 | }
56 |
57 | count, err := Users().Count(ctx, tx)
58 | if err != nil {
59 | t.Error(err)
60 | }
61 |
62 | if count != 0 {
63 | t.Error("want zero records, got:", count)
64 | }
65 | }
66 |
67 | func testUsersQueryDeleteAll(t *testing.T) {
68 | t.Parallel()
69 |
70 | seed := randomize.NewSeed()
71 | var err error
72 | o := &User{}
73 | if err = randomize.Struct(seed, o, userDBTypes, true, userColumnsWithDefault...); err != nil {
74 | t.Errorf("Unable to randomize User struct: %s", err)
75 | }
76 |
77 | ctx := context.Background()
78 | tx := MustTx(boil.BeginTx(ctx, nil))
79 | defer func() { _ = tx.Rollback() }()
80 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
81 | t.Error(err)
82 | }
83 |
84 | if rowsAff, err := Users().DeleteAll(ctx, tx); err != nil {
85 | t.Error(err)
86 | } else if rowsAff != 1 {
87 | t.Error("should only have deleted one row, but affected:", rowsAff)
88 | }
89 |
90 | count, err := Users().Count(ctx, tx)
91 | if err != nil {
92 | t.Error(err)
93 | }
94 |
95 | if count != 0 {
96 | t.Error("want zero records, got:", count)
97 | }
98 | }
99 |
100 | func testUsersSliceDeleteAll(t *testing.T) {
101 | t.Parallel()
102 |
103 | seed := randomize.NewSeed()
104 | var err error
105 | o := &User{}
106 | if err = randomize.Struct(seed, o, userDBTypes, true, userColumnsWithDefault...); err != nil {
107 | t.Errorf("Unable to randomize User struct: %s", err)
108 | }
109 |
110 | ctx := context.Background()
111 | tx := MustTx(boil.BeginTx(ctx, nil))
112 | defer func() { _ = tx.Rollback() }()
113 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
114 | t.Error(err)
115 | }
116 |
117 | slice := UserSlice{o}
118 |
119 | if rowsAff, err := slice.DeleteAll(ctx, tx); err != nil {
120 | t.Error(err)
121 | } else if rowsAff != 1 {
122 | t.Error("should only have deleted one row, but affected:", rowsAff)
123 | }
124 |
125 | count, err := Users().Count(ctx, tx)
126 | if err != nil {
127 | t.Error(err)
128 | }
129 |
130 | if count != 0 {
131 | t.Error("want zero records, got:", count)
132 | }
133 | }
134 |
135 | func testUsersExists(t *testing.T) {
136 | t.Parallel()
137 |
138 | seed := randomize.NewSeed()
139 | var err error
140 | o := &User{}
141 | if err = randomize.Struct(seed, o, userDBTypes, true, userColumnsWithDefault...); err != nil {
142 | t.Errorf("Unable to randomize User struct: %s", err)
143 | }
144 |
145 | ctx := context.Background()
146 | tx := MustTx(boil.BeginTx(ctx, nil))
147 | defer func() { _ = tx.Rollback() }()
148 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
149 | t.Error(err)
150 | }
151 |
152 | e, err := UserExists(ctx, tx, o.ID)
153 | if err != nil {
154 | t.Errorf("Unable to check if User exists: %s", err)
155 | }
156 | if !e {
157 | t.Errorf("Expected UserExists to return true, but got false.")
158 | }
159 | }
160 |
161 | func testUsersFind(t *testing.T) {
162 | t.Parallel()
163 |
164 | seed := randomize.NewSeed()
165 | var err error
166 | o := &User{}
167 | if err = randomize.Struct(seed, o, userDBTypes, true, userColumnsWithDefault...); err != nil {
168 | t.Errorf("Unable to randomize User struct: %s", err)
169 | }
170 |
171 | ctx := context.Background()
172 | tx := MustTx(boil.BeginTx(ctx, nil))
173 | defer func() { _ = tx.Rollback() }()
174 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
175 | t.Error(err)
176 | }
177 |
178 | userFound, err := FindUser(ctx, tx, o.ID)
179 | if err != nil {
180 | t.Error(err)
181 | }
182 |
183 | if userFound == nil {
184 | t.Error("want a record, got nil")
185 | }
186 | }
187 |
188 | func testUsersBind(t *testing.T) {
189 | t.Parallel()
190 |
191 | seed := randomize.NewSeed()
192 | var err error
193 | o := &User{}
194 | if err = randomize.Struct(seed, o, userDBTypes, true, userColumnsWithDefault...); err != nil {
195 | t.Errorf("Unable to randomize User struct: %s", err)
196 | }
197 |
198 | ctx := context.Background()
199 | tx := MustTx(boil.BeginTx(ctx, nil))
200 | defer func() { _ = tx.Rollback() }()
201 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
202 | t.Error(err)
203 | }
204 |
205 | if err = Users().Bind(ctx, tx, o); err != nil {
206 | t.Error(err)
207 | }
208 | }
209 |
210 | func testUsersOne(t *testing.T) {
211 | t.Parallel()
212 |
213 | seed := randomize.NewSeed()
214 | var err error
215 | o := &User{}
216 | if err = randomize.Struct(seed, o, userDBTypes, true, userColumnsWithDefault...); err != nil {
217 | t.Errorf("Unable to randomize User struct: %s", err)
218 | }
219 |
220 | ctx := context.Background()
221 | tx := MustTx(boil.BeginTx(ctx, nil))
222 | defer func() { _ = tx.Rollback() }()
223 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
224 | t.Error(err)
225 | }
226 |
227 | if x, err := Users().One(ctx, tx); err != nil {
228 | t.Error(err)
229 | } else if x == nil {
230 | t.Error("expected to get a non nil record")
231 | }
232 | }
233 |
234 | func testUsersAll(t *testing.T) {
235 | t.Parallel()
236 |
237 | seed := randomize.NewSeed()
238 | var err error
239 | userOne := &User{}
240 | userTwo := &User{}
241 | if err = randomize.Struct(seed, userOne, userDBTypes, false, userColumnsWithDefault...); err != nil {
242 | t.Errorf("Unable to randomize User struct: %s", err)
243 | }
244 | if err = randomize.Struct(seed, userTwo, userDBTypes, false, userColumnsWithDefault...); err != nil {
245 | t.Errorf("Unable to randomize User struct: %s", err)
246 | }
247 |
248 | ctx := context.Background()
249 | tx := MustTx(boil.BeginTx(ctx, nil))
250 | defer func() { _ = tx.Rollback() }()
251 | if err = userOne.Insert(ctx, tx, boil.Infer()); err != nil {
252 | t.Error(err)
253 | }
254 | if err = userTwo.Insert(ctx, tx, boil.Infer()); err != nil {
255 | t.Error(err)
256 | }
257 |
258 | slice, err := Users().All(ctx, tx)
259 | if err != nil {
260 | t.Error(err)
261 | }
262 |
263 | if len(slice) != 2 {
264 | t.Error("want 2 records, got:", len(slice))
265 | }
266 | }
267 |
268 | func testUsersCount(t *testing.T) {
269 | t.Parallel()
270 |
271 | var err error
272 | seed := randomize.NewSeed()
273 | userOne := &User{}
274 | userTwo := &User{}
275 | if err = randomize.Struct(seed, userOne, userDBTypes, false, userColumnsWithDefault...); err != nil {
276 | t.Errorf("Unable to randomize User struct: %s", err)
277 | }
278 | if err = randomize.Struct(seed, userTwo, userDBTypes, false, userColumnsWithDefault...); err != nil {
279 | t.Errorf("Unable to randomize User struct: %s", err)
280 | }
281 |
282 | ctx := context.Background()
283 | tx := MustTx(boil.BeginTx(ctx, nil))
284 | defer func() { _ = tx.Rollback() }()
285 | if err = userOne.Insert(ctx, tx, boil.Infer()); err != nil {
286 | t.Error(err)
287 | }
288 | if err = userTwo.Insert(ctx, tx, boil.Infer()); err != nil {
289 | t.Error(err)
290 | }
291 |
292 | count, err := Users().Count(ctx, tx)
293 | if err != nil {
294 | t.Error(err)
295 | }
296 |
297 | if count != 2 {
298 | t.Error("want 2 records, got:", count)
299 | }
300 | }
301 |
302 | func userBeforeInsertHook(ctx context.Context, e boil.ContextExecutor, o *User) error {
303 | *o = User{}
304 | return nil
305 | }
306 |
307 | func userAfterInsertHook(ctx context.Context, e boil.ContextExecutor, o *User) error {
308 | *o = User{}
309 | return nil
310 | }
311 |
312 | func userAfterSelectHook(ctx context.Context, e boil.ContextExecutor, o *User) error {
313 | *o = User{}
314 | return nil
315 | }
316 |
317 | func userBeforeUpdateHook(ctx context.Context, e boil.ContextExecutor, o *User) error {
318 | *o = User{}
319 | return nil
320 | }
321 |
322 | func userAfterUpdateHook(ctx context.Context, e boil.ContextExecutor, o *User) error {
323 | *o = User{}
324 | return nil
325 | }
326 |
327 | func userBeforeDeleteHook(ctx context.Context, e boil.ContextExecutor, o *User) error {
328 | *o = User{}
329 | return nil
330 | }
331 |
332 | func userAfterDeleteHook(ctx context.Context, e boil.ContextExecutor, o *User) error {
333 | *o = User{}
334 | return nil
335 | }
336 |
337 | func userBeforeUpsertHook(ctx context.Context, e boil.ContextExecutor, o *User) error {
338 | *o = User{}
339 | return nil
340 | }
341 |
342 | func userAfterUpsertHook(ctx context.Context, e boil.ContextExecutor, o *User) error {
343 | *o = User{}
344 | return nil
345 | }
346 |
347 | func testUsersHooks(t *testing.T) {
348 | t.Parallel()
349 |
350 | var err error
351 |
352 | ctx := context.Background()
353 | empty := &User{}
354 | o := &User{}
355 |
356 | seed := randomize.NewSeed()
357 | if err = randomize.Struct(seed, o, userDBTypes, false); err != nil {
358 | t.Errorf("Unable to randomize User object: %s", err)
359 | }
360 |
361 | AddUserHook(boil.BeforeInsertHook, userBeforeInsertHook)
362 | if err = o.doBeforeInsertHooks(ctx, nil); err != nil {
363 | t.Errorf("Unable to execute doBeforeInsertHooks: %s", err)
364 | }
365 | if !reflect.DeepEqual(o, empty) {
366 | t.Errorf("Expected BeforeInsertHook function to empty object, but got: %#v", o)
367 | }
368 | userBeforeInsertHooks = []UserHook{}
369 |
370 | AddUserHook(boil.AfterInsertHook, userAfterInsertHook)
371 | if err = o.doAfterInsertHooks(ctx, nil); err != nil {
372 | t.Errorf("Unable to execute doAfterInsertHooks: %s", err)
373 | }
374 | if !reflect.DeepEqual(o, empty) {
375 | t.Errorf("Expected AfterInsertHook function to empty object, but got: %#v", o)
376 | }
377 | userAfterInsertHooks = []UserHook{}
378 |
379 | AddUserHook(boil.AfterSelectHook, userAfterSelectHook)
380 | if err = o.doAfterSelectHooks(ctx, nil); err != nil {
381 | t.Errorf("Unable to execute doAfterSelectHooks: %s", err)
382 | }
383 | if !reflect.DeepEqual(o, empty) {
384 | t.Errorf("Expected AfterSelectHook function to empty object, but got: %#v", o)
385 | }
386 | userAfterSelectHooks = []UserHook{}
387 |
388 | AddUserHook(boil.BeforeUpdateHook, userBeforeUpdateHook)
389 | if err = o.doBeforeUpdateHooks(ctx, nil); err != nil {
390 | t.Errorf("Unable to execute doBeforeUpdateHooks: %s", err)
391 | }
392 | if !reflect.DeepEqual(o, empty) {
393 | t.Errorf("Expected BeforeUpdateHook function to empty object, but got: %#v", o)
394 | }
395 | userBeforeUpdateHooks = []UserHook{}
396 |
397 | AddUserHook(boil.AfterUpdateHook, userAfterUpdateHook)
398 | if err = o.doAfterUpdateHooks(ctx, nil); err != nil {
399 | t.Errorf("Unable to execute doAfterUpdateHooks: %s", err)
400 | }
401 | if !reflect.DeepEqual(o, empty) {
402 | t.Errorf("Expected AfterUpdateHook function to empty object, but got: %#v", o)
403 | }
404 | userAfterUpdateHooks = []UserHook{}
405 |
406 | AddUserHook(boil.BeforeDeleteHook, userBeforeDeleteHook)
407 | if err = o.doBeforeDeleteHooks(ctx, nil); err != nil {
408 | t.Errorf("Unable to execute doBeforeDeleteHooks: %s", err)
409 | }
410 | if !reflect.DeepEqual(o, empty) {
411 | t.Errorf("Expected BeforeDeleteHook function to empty object, but got: %#v", o)
412 | }
413 | userBeforeDeleteHooks = []UserHook{}
414 |
415 | AddUserHook(boil.AfterDeleteHook, userAfterDeleteHook)
416 | if err = o.doAfterDeleteHooks(ctx, nil); err != nil {
417 | t.Errorf("Unable to execute doAfterDeleteHooks: %s", err)
418 | }
419 | if !reflect.DeepEqual(o, empty) {
420 | t.Errorf("Expected AfterDeleteHook function to empty object, but got: %#v", o)
421 | }
422 | userAfterDeleteHooks = []UserHook{}
423 |
424 | AddUserHook(boil.BeforeUpsertHook, userBeforeUpsertHook)
425 | if err = o.doBeforeUpsertHooks(ctx, nil); err != nil {
426 | t.Errorf("Unable to execute doBeforeUpsertHooks: %s", err)
427 | }
428 | if !reflect.DeepEqual(o, empty) {
429 | t.Errorf("Expected BeforeUpsertHook function to empty object, but got: %#v", o)
430 | }
431 | userBeforeUpsertHooks = []UserHook{}
432 |
433 | AddUserHook(boil.AfterUpsertHook, userAfterUpsertHook)
434 | if err = o.doAfterUpsertHooks(ctx, nil); err != nil {
435 | t.Errorf("Unable to execute doAfterUpsertHooks: %s", err)
436 | }
437 | if !reflect.DeepEqual(o, empty) {
438 | t.Errorf("Expected AfterUpsertHook function to empty object, but got: %#v", o)
439 | }
440 | userAfterUpsertHooks = []UserHook{}
441 | }
442 |
443 | func testUsersInsert(t *testing.T) {
444 | t.Parallel()
445 |
446 | seed := randomize.NewSeed()
447 | var err error
448 | o := &User{}
449 | if err = randomize.Struct(seed, o, userDBTypes, true, userColumnsWithDefault...); err != nil {
450 | t.Errorf("Unable to randomize User struct: %s", err)
451 | }
452 |
453 | ctx := context.Background()
454 | tx := MustTx(boil.BeginTx(ctx, nil))
455 | defer func() { _ = tx.Rollback() }()
456 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
457 | t.Error(err)
458 | }
459 |
460 | count, err := Users().Count(ctx, tx)
461 | if err != nil {
462 | t.Error(err)
463 | }
464 |
465 | if count != 1 {
466 | t.Error("want one record, got:", count)
467 | }
468 | }
469 |
470 | func testUsersInsertWhitelist(t *testing.T) {
471 | t.Parallel()
472 |
473 | seed := randomize.NewSeed()
474 | var err error
475 | o := &User{}
476 | if err = randomize.Struct(seed, o, userDBTypes, true); err != nil {
477 | t.Errorf("Unable to randomize User struct: %s", err)
478 | }
479 |
480 | ctx := context.Background()
481 | tx := MustTx(boil.BeginTx(ctx, nil))
482 | defer func() { _ = tx.Rollback() }()
483 | if err = o.Insert(ctx, tx, boil.Whitelist(userColumnsWithoutDefault...)); err != nil {
484 | t.Error(err)
485 | }
486 |
487 | count, err := Users().Count(ctx, tx)
488 | if err != nil {
489 | t.Error(err)
490 | }
491 |
492 | if count != 1 {
493 | t.Error("want one record, got:", count)
494 | }
495 | }
496 |
497 | func testUsersReload(t *testing.T) {
498 | t.Parallel()
499 |
500 | seed := randomize.NewSeed()
501 | var err error
502 | o := &User{}
503 | if err = randomize.Struct(seed, o, userDBTypes, true, userColumnsWithDefault...); err != nil {
504 | t.Errorf("Unable to randomize User struct: %s", err)
505 | }
506 |
507 | ctx := context.Background()
508 | tx := MustTx(boil.BeginTx(ctx, nil))
509 | defer func() { _ = tx.Rollback() }()
510 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
511 | t.Error(err)
512 | }
513 |
514 | if err = o.Reload(ctx, tx); err != nil {
515 | t.Error(err)
516 | }
517 | }
518 |
519 | func testUsersReloadAll(t *testing.T) {
520 | t.Parallel()
521 |
522 | seed := randomize.NewSeed()
523 | var err error
524 | o := &User{}
525 | if err = randomize.Struct(seed, o, userDBTypes, true, userColumnsWithDefault...); err != nil {
526 | t.Errorf("Unable to randomize User struct: %s", err)
527 | }
528 |
529 | ctx := context.Background()
530 | tx := MustTx(boil.BeginTx(ctx, nil))
531 | defer func() { _ = tx.Rollback() }()
532 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
533 | t.Error(err)
534 | }
535 |
536 | slice := UserSlice{o}
537 |
538 | if err = slice.ReloadAll(ctx, tx); err != nil {
539 | t.Error(err)
540 | }
541 | }
542 |
543 | func testUsersSelect(t *testing.T) {
544 | t.Parallel()
545 |
546 | seed := randomize.NewSeed()
547 | var err error
548 | o := &User{}
549 | if err = randomize.Struct(seed, o, userDBTypes, true, userColumnsWithDefault...); err != nil {
550 | t.Errorf("Unable to randomize User struct: %s", err)
551 | }
552 |
553 | ctx := context.Background()
554 | tx := MustTx(boil.BeginTx(ctx, nil))
555 | defer func() { _ = tx.Rollback() }()
556 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
557 | t.Error(err)
558 | }
559 |
560 | slice, err := Users().All(ctx, tx)
561 | if err != nil {
562 | t.Error(err)
563 | }
564 |
565 | if len(slice) != 1 {
566 | t.Error("want one record, got:", len(slice))
567 | }
568 | }
569 |
570 | var (
571 | userDBTypes = map[string]string{`ID`: `int`, `Username`: `varchar`, `Email`: `varchar`, `Password`: `varchar`, `Nickname`: `varchar`, `Nameplate`: `text`, `Information`: `text`, `AcNum`: `int`, `SubmitNum`: `int`, `IsAdmin`: `tinyint`, `IsShow`: `tinyint`, `PublicEmail`: `tinyint`, `PreferFormattedCode`: `tinyint`, `Sex`: `int`, `Rating`: `int`, `RegisterTime`: `int`}
572 | _ = bytes.MinRead
573 | )
574 |
575 | func testUsersUpdate(t *testing.T) {
576 | t.Parallel()
577 |
578 | if 0 == len(userPrimaryKeyColumns) {
579 | t.Skip("Skipping table with no primary key columns")
580 | }
581 | if len(userAllColumns) == len(userPrimaryKeyColumns) {
582 | t.Skip("Skipping table with only primary key columns")
583 | }
584 |
585 | seed := randomize.NewSeed()
586 | var err error
587 | o := &User{}
588 | if err = randomize.Struct(seed, o, userDBTypes, true, userColumnsWithDefault...); err != nil {
589 | t.Errorf("Unable to randomize User struct: %s", err)
590 | }
591 |
592 | ctx := context.Background()
593 | tx := MustTx(boil.BeginTx(ctx, nil))
594 | defer func() { _ = tx.Rollback() }()
595 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
596 | t.Error(err)
597 | }
598 |
599 | count, err := Users().Count(ctx, tx)
600 | if err != nil {
601 | t.Error(err)
602 | }
603 |
604 | if count != 1 {
605 | t.Error("want one record, got:", count)
606 | }
607 |
608 | if err = randomize.Struct(seed, o, userDBTypes, true, userPrimaryKeyColumns...); err != nil {
609 | t.Errorf("Unable to randomize User struct: %s", err)
610 | }
611 |
612 | if rowsAff, err := o.Update(ctx, tx, boil.Infer()); err != nil {
613 | t.Error(err)
614 | } else if rowsAff != 1 {
615 | t.Error("should only affect one row but affected", rowsAff)
616 | }
617 | }
618 |
619 | func testUsersSliceUpdateAll(t *testing.T) {
620 | t.Parallel()
621 |
622 | if len(userAllColumns) == len(userPrimaryKeyColumns) {
623 | t.Skip("Skipping table with only primary key columns")
624 | }
625 |
626 | seed := randomize.NewSeed()
627 | var err error
628 | o := &User{}
629 | if err = randomize.Struct(seed, o, userDBTypes, true, userColumnsWithDefault...); err != nil {
630 | t.Errorf("Unable to randomize User struct: %s", err)
631 | }
632 |
633 | ctx := context.Background()
634 | tx := MustTx(boil.BeginTx(ctx, nil))
635 | defer func() { _ = tx.Rollback() }()
636 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
637 | t.Error(err)
638 | }
639 |
640 | count, err := Users().Count(ctx, tx)
641 | if err != nil {
642 | t.Error(err)
643 | }
644 |
645 | if count != 1 {
646 | t.Error("want one record, got:", count)
647 | }
648 |
649 | if err = randomize.Struct(seed, o, userDBTypes, true, userPrimaryKeyColumns...); err != nil {
650 | t.Errorf("Unable to randomize User struct: %s", err)
651 | }
652 |
653 | // Remove Primary keys and unique columns from what we plan to update
654 | var fields []string
655 | if strmangle.StringSliceMatch(userAllColumns, userPrimaryKeyColumns) {
656 | fields = userAllColumns
657 | } else {
658 | fields = strmangle.SetComplement(
659 | userAllColumns,
660 | userPrimaryKeyColumns,
661 | )
662 | }
663 |
664 | value := reflect.Indirect(reflect.ValueOf(o))
665 | typ := reflect.TypeOf(o).Elem()
666 | n := typ.NumField()
667 |
668 | updateMap := M{}
669 | for _, col := range fields {
670 | for i := 0; i < n; i++ {
671 | f := typ.Field(i)
672 | if f.Tag.Get("boil") == col {
673 | updateMap[col] = value.Field(i).Interface()
674 | }
675 | }
676 | }
677 |
678 | slice := UserSlice{o}
679 | if rowsAff, err := slice.UpdateAll(ctx, tx, updateMap); err != nil {
680 | t.Error(err)
681 | } else if rowsAff != 1 {
682 | t.Error("wanted one record updated but got", rowsAff)
683 | }
684 | }
685 |
686 | func testUsersUpsert(t *testing.T) {
687 | t.Parallel()
688 |
689 | if len(userAllColumns) == len(userPrimaryKeyColumns) {
690 | t.Skip("Skipping table with only primary key columns")
691 | }
692 | if len(mySQLUserUniqueColumns) == 0 {
693 | t.Skip("Skipping table with no unique columns to conflict on")
694 | }
695 |
696 | seed := randomize.NewSeed()
697 | var err error
698 | // Attempt the INSERT side of an UPSERT
699 | o := User{}
700 | if err = randomize.Struct(seed, &o, userDBTypes, false); err != nil {
701 | t.Errorf("Unable to randomize User struct: %s", err)
702 | }
703 |
704 | ctx := context.Background()
705 | tx := MustTx(boil.BeginTx(ctx, nil))
706 | defer func() { _ = tx.Rollback() }()
707 | if err = o.Upsert(ctx, tx, boil.Infer(), boil.Infer()); err != nil {
708 | t.Errorf("Unable to upsert User: %s", err)
709 | }
710 |
711 | count, err := Users().Count(ctx, tx)
712 | if err != nil {
713 | t.Error(err)
714 | }
715 | if count != 1 {
716 | t.Error("want one record, got:", count)
717 | }
718 |
719 | // Attempt the UPDATE side of an UPSERT
720 | if err = randomize.Struct(seed, &o, userDBTypes, false, userPrimaryKeyColumns...); err != nil {
721 | t.Errorf("Unable to randomize User struct: %s", err)
722 | }
723 |
724 | if err = o.Upsert(ctx, tx, boil.Infer(), boil.Infer()); err != nil {
725 | t.Errorf("Unable to upsert User: %s", err)
726 | }
727 |
728 | count, err = Users().Count(ctx, tx)
729 | if err != nil {
730 | t.Error(err)
731 | }
732 | if count != 1 {
733 | t.Error("want one record, got:", count)
734 | }
735 | }
736 |
--------------------------------------------------------------------------------
/models/article_test.go:
--------------------------------------------------------------------------------
1 | // Code generated by SQLBoiler 3.5.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT.
2 | // This file is meant to be re-generated in place and/or deleted at any time.
3 |
4 | package models
5 |
6 | import (
7 | "bytes"
8 | "context"
9 | "reflect"
10 | "testing"
11 |
12 | "github.com/volatiletech/sqlboiler/boil"
13 | "github.com/volatiletech/sqlboiler/queries"
14 | "github.com/volatiletech/sqlboiler/randomize"
15 | "github.com/volatiletech/sqlboiler/strmangle"
16 | )
17 |
18 | var (
19 | // Relationships sometimes use the reflection helper queries.Equal/queries.Assign
20 | // so force a package dependency in case they don't.
21 | _ = queries.Equal
22 | )
23 |
24 | func testArticles(t *testing.T) {
25 | t.Parallel()
26 |
27 | query := Articles()
28 |
29 | if query.Query == nil {
30 | t.Error("expected a query, got nothing")
31 | }
32 | }
33 |
34 | func testArticlesDelete(t *testing.T) {
35 | t.Parallel()
36 |
37 | seed := randomize.NewSeed()
38 | var err error
39 | o := &Article{}
40 | if err = randomize.Struct(seed, o, articleDBTypes, true, articleColumnsWithDefault...); err != nil {
41 | t.Errorf("Unable to randomize Article struct: %s", err)
42 | }
43 |
44 | ctx := context.Background()
45 | tx := MustTx(boil.BeginTx(ctx, nil))
46 | defer func() { _ = tx.Rollback() }()
47 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
48 | t.Error(err)
49 | }
50 |
51 | if rowsAff, err := o.Delete(ctx, tx); err != nil {
52 | t.Error(err)
53 | } else if rowsAff != 1 {
54 | t.Error("should only have deleted one row, but affected:", rowsAff)
55 | }
56 |
57 | count, err := Articles().Count(ctx, tx)
58 | if err != nil {
59 | t.Error(err)
60 | }
61 |
62 | if count != 0 {
63 | t.Error("want zero records, got:", count)
64 | }
65 | }
66 |
67 | func testArticlesQueryDeleteAll(t *testing.T) {
68 | t.Parallel()
69 |
70 | seed := randomize.NewSeed()
71 | var err error
72 | o := &Article{}
73 | if err = randomize.Struct(seed, o, articleDBTypes, true, articleColumnsWithDefault...); err != nil {
74 | t.Errorf("Unable to randomize Article struct: %s", err)
75 | }
76 |
77 | ctx := context.Background()
78 | tx := MustTx(boil.BeginTx(ctx, nil))
79 | defer func() { _ = tx.Rollback() }()
80 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
81 | t.Error(err)
82 | }
83 |
84 | if rowsAff, err := Articles().DeleteAll(ctx, tx); err != nil {
85 | t.Error(err)
86 | } else if rowsAff != 1 {
87 | t.Error("should only have deleted one row, but affected:", rowsAff)
88 | }
89 |
90 | count, err := Articles().Count(ctx, tx)
91 | if err != nil {
92 | t.Error(err)
93 | }
94 |
95 | if count != 0 {
96 | t.Error("want zero records, got:", count)
97 | }
98 | }
99 |
100 | func testArticlesSliceDeleteAll(t *testing.T) {
101 | t.Parallel()
102 |
103 | seed := randomize.NewSeed()
104 | var err error
105 | o := &Article{}
106 | if err = randomize.Struct(seed, o, articleDBTypes, true, articleColumnsWithDefault...); err != nil {
107 | t.Errorf("Unable to randomize Article struct: %s", err)
108 | }
109 |
110 | ctx := context.Background()
111 | tx := MustTx(boil.BeginTx(ctx, nil))
112 | defer func() { _ = tx.Rollback() }()
113 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
114 | t.Error(err)
115 | }
116 |
117 | slice := ArticleSlice{o}
118 |
119 | if rowsAff, err := slice.DeleteAll(ctx, tx); err != nil {
120 | t.Error(err)
121 | } else if rowsAff != 1 {
122 | t.Error("should only have deleted one row, but affected:", rowsAff)
123 | }
124 |
125 | count, err := Articles().Count(ctx, tx)
126 | if err != nil {
127 | t.Error(err)
128 | }
129 |
130 | if count != 0 {
131 | t.Error("want zero records, got:", count)
132 | }
133 | }
134 |
135 | func testArticlesExists(t *testing.T) {
136 | t.Parallel()
137 |
138 | seed := randomize.NewSeed()
139 | var err error
140 | o := &Article{}
141 | if err = randomize.Struct(seed, o, articleDBTypes, true, articleColumnsWithDefault...); err != nil {
142 | t.Errorf("Unable to randomize Article struct: %s", err)
143 | }
144 |
145 | ctx := context.Background()
146 | tx := MustTx(boil.BeginTx(ctx, nil))
147 | defer func() { _ = tx.Rollback() }()
148 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
149 | t.Error(err)
150 | }
151 |
152 | e, err := ArticleExists(ctx, tx, o.ID)
153 | if err != nil {
154 | t.Errorf("Unable to check if Article exists: %s", err)
155 | }
156 | if !e {
157 | t.Errorf("Expected ArticleExists to return true, but got false.")
158 | }
159 | }
160 |
161 | func testArticlesFind(t *testing.T) {
162 | t.Parallel()
163 |
164 | seed := randomize.NewSeed()
165 | var err error
166 | o := &Article{}
167 | if err = randomize.Struct(seed, o, articleDBTypes, true, articleColumnsWithDefault...); err != nil {
168 | t.Errorf("Unable to randomize Article struct: %s", err)
169 | }
170 |
171 | ctx := context.Background()
172 | tx := MustTx(boil.BeginTx(ctx, nil))
173 | defer func() { _ = tx.Rollback() }()
174 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
175 | t.Error(err)
176 | }
177 |
178 | articleFound, err := FindArticle(ctx, tx, o.ID)
179 | if err != nil {
180 | t.Error(err)
181 | }
182 |
183 | if articleFound == nil {
184 | t.Error("want a record, got nil")
185 | }
186 | }
187 |
188 | func testArticlesBind(t *testing.T) {
189 | t.Parallel()
190 |
191 | seed := randomize.NewSeed()
192 | var err error
193 | o := &Article{}
194 | if err = randomize.Struct(seed, o, articleDBTypes, true, articleColumnsWithDefault...); err != nil {
195 | t.Errorf("Unable to randomize Article struct: %s", err)
196 | }
197 |
198 | ctx := context.Background()
199 | tx := MustTx(boil.BeginTx(ctx, nil))
200 | defer func() { _ = tx.Rollback() }()
201 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
202 | t.Error(err)
203 | }
204 |
205 | if err = Articles().Bind(ctx, tx, o); err != nil {
206 | t.Error(err)
207 | }
208 | }
209 |
210 | func testArticlesOne(t *testing.T) {
211 | t.Parallel()
212 |
213 | seed := randomize.NewSeed()
214 | var err error
215 | o := &Article{}
216 | if err = randomize.Struct(seed, o, articleDBTypes, true, articleColumnsWithDefault...); err != nil {
217 | t.Errorf("Unable to randomize Article struct: %s", err)
218 | }
219 |
220 | ctx := context.Background()
221 | tx := MustTx(boil.BeginTx(ctx, nil))
222 | defer func() { _ = tx.Rollback() }()
223 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
224 | t.Error(err)
225 | }
226 |
227 | if x, err := Articles().One(ctx, tx); err != nil {
228 | t.Error(err)
229 | } else if x == nil {
230 | t.Error("expected to get a non nil record")
231 | }
232 | }
233 |
234 | func testArticlesAll(t *testing.T) {
235 | t.Parallel()
236 |
237 | seed := randomize.NewSeed()
238 | var err error
239 | articleOne := &Article{}
240 | articleTwo := &Article{}
241 | if err = randomize.Struct(seed, articleOne, articleDBTypes, false, articleColumnsWithDefault...); err != nil {
242 | t.Errorf("Unable to randomize Article struct: %s", err)
243 | }
244 | if err = randomize.Struct(seed, articleTwo, articleDBTypes, false, articleColumnsWithDefault...); err != nil {
245 | t.Errorf("Unable to randomize Article struct: %s", err)
246 | }
247 |
248 | ctx := context.Background()
249 | tx := MustTx(boil.BeginTx(ctx, nil))
250 | defer func() { _ = tx.Rollback() }()
251 | if err = articleOne.Insert(ctx, tx, boil.Infer()); err != nil {
252 | t.Error(err)
253 | }
254 | if err = articleTwo.Insert(ctx, tx, boil.Infer()); err != nil {
255 | t.Error(err)
256 | }
257 |
258 | slice, err := Articles().All(ctx, tx)
259 | if err != nil {
260 | t.Error(err)
261 | }
262 |
263 | if len(slice) != 2 {
264 | t.Error("want 2 records, got:", len(slice))
265 | }
266 | }
267 |
268 | func testArticlesCount(t *testing.T) {
269 | t.Parallel()
270 |
271 | var err error
272 | seed := randomize.NewSeed()
273 | articleOne := &Article{}
274 | articleTwo := &Article{}
275 | if err = randomize.Struct(seed, articleOne, articleDBTypes, false, articleColumnsWithDefault...); err != nil {
276 | t.Errorf("Unable to randomize Article struct: %s", err)
277 | }
278 | if err = randomize.Struct(seed, articleTwo, articleDBTypes, false, articleColumnsWithDefault...); err != nil {
279 | t.Errorf("Unable to randomize Article struct: %s", err)
280 | }
281 |
282 | ctx := context.Background()
283 | tx := MustTx(boil.BeginTx(ctx, nil))
284 | defer func() { _ = tx.Rollback() }()
285 | if err = articleOne.Insert(ctx, tx, boil.Infer()); err != nil {
286 | t.Error(err)
287 | }
288 | if err = articleTwo.Insert(ctx, tx, boil.Infer()); err != nil {
289 | t.Error(err)
290 | }
291 |
292 | count, err := Articles().Count(ctx, tx)
293 | if err != nil {
294 | t.Error(err)
295 | }
296 |
297 | if count != 2 {
298 | t.Error("want 2 records, got:", count)
299 | }
300 | }
301 |
302 | func articleBeforeInsertHook(ctx context.Context, e boil.ContextExecutor, o *Article) error {
303 | *o = Article{}
304 | return nil
305 | }
306 |
307 | func articleAfterInsertHook(ctx context.Context, e boil.ContextExecutor, o *Article) error {
308 | *o = Article{}
309 | return nil
310 | }
311 |
312 | func articleAfterSelectHook(ctx context.Context, e boil.ContextExecutor, o *Article) error {
313 | *o = Article{}
314 | return nil
315 | }
316 |
317 | func articleBeforeUpdateHook(ctx context.Context, e boil.ContextExecutor, o *Article) error {
318 | *o = Article{}
319 | return nil
320 | }
321 |
322 | func articleAfterUpdateHook(ctx context.Context, e boil.ContextExecutor, o *Article) error {
323 | *o = Article{}
324 | return nil
325 | }
326 |
327 | func articleBeforeDeleteHook(ctx context.Context, e boil.ContextExecutor, o *Article) error {
328 | *o = Article{}
329 | return nil
330 | }
331 |
332 | func articleAfterDeleteHook(ctx context.Context, e boil.ContextExecutor, o *Article) error {
333 | *o = Article{}
334 | return nil
335 | }
336 |
337 | func articleBeforeUpsertHook(ctx context.Context, e boil.ContextExecutor, o *Article) error {
338 | *o = Article{}
339 | return nil
340 | }
341 |
342 | func articleAfterUpsertHook(ctx context.Context, e boil.ContextExecutor, o *Article) error {
343 | *o = Article{}
344 | return nil
345 | }
346 |
347 | func testArticlesHooks(t *testing.T) {
348 | t.Parallel()
349 |
350 | var err error
351 |
352 | ctx := context.Background()
353 | empty := &Article{}
354 | o := &Article{}
355 |
356 | seed := randomize.NewSeed()
357 | if err = randomize.Struct(seed, o, articleDBTypes, false); err != nil {
358 | t.Errorf("Unable to randomize Article object: %s", err)
359 | }
360 |
361 | AddArticleHook(boil.BeforeInsertHook, articleBeforeInsertHook)
362 | if err = o.doBeforeInsertHooks(ctx, nil); err != nil {
363 | t.Errorf("Unable to execute doBeforeInsertHooks: %s", err)
364 | }
365 | if !reflect.DeepEqual(o, empty) {
366 | t.Errorf("Expected BeforeInsertHook function to empty object, but got: %#v", o)
367 | }
368 | articleBeforeInsertHooks = []ArticleHook{}
369 |
370 | AddArticleHook(boil.AfterInsertHook, articleAfterInsertHook)
371 | if err = o.doAfterInsertHooks(ctx, nil); err != nil {
372 | t.Errorf("Unable to execute doAfterInsertHooks: %s", err)
373 | }
374 | if !reflect.DeepEqual(o, empty) {
375 | t.Errorf("Expected AfterInsertHook function to empty object, but got: %#v", o)
376 | }
377 | articleAfterInsertHooks = []ArticleHook{}
378 |
379 | AddArticleHook(boil.AfterSelectHook, articleAfterSelectHook)
380 | if err = o.doAfterSelectHooks(ctx, nil); err != nil {
381 | t.Errorf("Unable to execute doAfterSelectHooks: %s", err)
382 | }
383 | if !reflect.DeepEqual(o, empty) {
384 | t.Errorf("Expected AfterSelectHook function to empty object, but got: %#v", o)
385 | }
386 | articleAfterSelectHooks = []ArticleHook{}
387 |
388 | AddArticleHook(boil.BeforeUpdateHook, articleBeforeUpdateHook)
389 | if err = o.doBeforeUpdateHooks(ctx, nil); err != nil {
390 | t.Errorf("Unable to execute doBeforeUpdateHooks: %s", err)
391 | }
392 | if !reflect.DeepEqual(o, empty) {
393 | t.Errorf("Expected BeforeUpdateHook function to empty object, but got: %#v", o)
394 | }
395 | articleBeforeUpdateHooks = []ArticleHook{}
396 |
397 | AddArticleHook(boil.AfterUpdateHook, articleAfterUpdateHook)
398 | if err = o.doAfterUpdateHooks(ctx, nil); err != nil {
399 | t.Errorf("Unable to execute doAfterUpdateHooks: %s", err)
400 | }
401 | if !reflect.DeepEqual(o, empty) {
402 | t.Errorf("Expected AfterUpdateHook function to empty object, but got: %#v", o)
403 | }
404 | articleAfterUpdateHooks = []ArticleHook{}
405 |
406 | AddArticleHook(boil.BeforeDeleteHook, articleBeforeDeleteHook)
407 | if err = o.doBeforeDeleteHooks(ctx, nil); err != nil {
408 | t.Errorf("Unable to execute doBeforeDeleteHooks: %s", err)
409 | }
410 | if !reflect.DeepEqual(o, empty) {
411 | t.Errorf("Expected BeforeDeleteHook function to empty object, but got: %#v", o)
412 | }
413 | articleBeforeDeleteHooks = []ArticleHook{}
414 |
415 | AddArticleHook(boil.AfterDeleteHook, articleAfterDeleteHook)
416 | if err = o.doAfterDeleteHooks(ctx, nil); err != nil {
417 | t.Errorf("Unable to execute doAfterDeleteHooks: %s", err)
418 | }
419 | if !reflect.DeepEqual(o, empty) {
420 | t.Errorf("Expected AfterDeleteHook function to empty object, but got: %#v", o)
421 | }
422 | articleAfterDeleteHooks = []ArticleHook{}
423 |
424 | AddArticleHook(boil.BeforeUpsertHook, articleBeforeUpsertHook)
425 | if err = o.doBeforeUpsertHooks(ctx, nil); err != nil {
426 | t.Errorf("Unable to execute doBeforeUpsertHooks: %s", err)
427 | }
428 | if !reflect.DeepEqual(o, empty) {
429 | t.Errorf("Expected BeforeUpsertHook function to empty object, but got: %#v", o)
430 | }
431 | articleBeforeUpsertHooks = []ArticleHook{}
432 |
433 | AddArticleHook(boil.AfterUpsertHook, articleAfterUpsertHook)
434 | if err = o.doAfterUpsertHooks(ctx, nil); err != nil {
435 | t.Errorf("Unable to execute doAfterUpsertHooks: %s", err)
436 | }
437 | if !reflect.DeepEqual(o, empty) {
438 | t.Errorf("Expected AfterUpsertHook function to empty object, but got: %#v", o)
439 | }
440 | articleAfterUpsertHooks = []ArticleHook{}
441 | }
442 |
443 | func testArticlesInsert(t *testing.T) {
444 | t.Parallel()
445 |
446 | seed := randomize.NewSeed()
447 | var err error
448 | o := &Article{}
449 | if err = randomize.Struct(seed, o, articleDBTypes, true, articleColumnsWithDefault...); err != nil {
450 | t.Errorf("Unable to randomize Article struct: %s", err)
451 | }
452 |
453 | ctx := context.Background()
454 | tx := MustTx(boil.BeginTx(ctx, nil))
455 | defer func() { _ = tx.Rollback() }()
456 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
457 | t.Error(err)
458 | }
459 |
460 | count, err := Articles().Count(ctx, tx)
461 | if err != nil {
462 | t.Error(err)
463 | }
464 |
465 | if count != 1 {
466 | t.Error("want one record, got:", count)
467 | }
468 | }
469 |
470 | func testArticlesInsertWhitelist(t *testing.T) {
471 | t.Parallel()
472 |
473 | seed := randomize.NewSeed()
474 | var err error
475 | o := &Article{}
476 | if err = randomize.Struct(seed, o, articleDBTypes, true); err != nil {
477 | t.Errorf("Unable to randomize Article struct: %s", err)
478 | }
479 |
480 | ctx := context.Background()
481 | tx := MustTx(boil.BeginTx(ctx, nil))
482 | defer func() { _ = tx.Rollback() }()
483 | if err = o.Insert(ctx, tx, boil.Whitelist(articleColumnsWithoutDefault...)); err != nil {
484 | t.Error(err)
485 | }
486 |
487 | count, err := Articles().Count(ctx, tx)
488 | if err != nil {
489 | t.Error(err)
490 | }
491 |
492 | if count != 1 {
493 | t.Error("want one record, got:", count)
494 | }
495 | }
496 |
497 | func testArticlesReload(t *testing.T) {
498 | t.Parallel()
499 |
500 | seed := randomize.NewSeed()
501 | var err error
502 | o := &Article{}
503 | if err = randomize.Struct(seed, o, articleDBTypes, true, articleColumnsWithDefault...); err != nil {
504 | t.Errorf("Unable to randomize Article struct: %s", err)
505 | }
506 |
507 | ctx := context.Background()
508 | tx := MustTx(boil.BeginTx(ctx, nil))
509 | defer func() { _ = tx.Rollback() }()
510 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
511 | t.Error(err)
512 | }
513 |
514 | if err = o.Reload(ctx, tx); err != nil {
515 | t.Error(err)
516 | }
517 | }
518 |
519 | func testArticlesReloadAll(t *testing.T) {
520 | t.Parallel()
521 |
522 | seed := randomize.NewSeed()
523 | var err error
524 | o := &Article{}
525 | if err = randomize.Struct(seed, o, articleDBTypes, true, articleColumnsWithDefault...); err != nil {
526 | t.Errorf("Unable to randomize Article struct: %s", err)
527 | }
528 |
529 | ctx := context.Background()
530 | tx := MustTx(boil.BeginTx(ctx, nil))
531 | defer func() { _ = tx.Rollback() }()
532 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
533 | t.Error(err)
534 | }
535 |
536 | slice := ArticleSlice{o}
537 |
538 | if err = slice.ReloadAll(ctx, tx); err != nil {
539 | t.Error(err)
540 | }
541 | }
542 |
543 | func testArticlesSelect(t *testing.T) {
544 | t.Parallel()
545 |
546 | seed := randomize.NewSeed()
547 | var err error
548 | o := &Article{}
549 | if err = randomize.Struct(seed, o, articleDBTypes, true, articleColumnsWithDefault...); err != nil {
550 | t.Errorf("Unable to randomize Article struct: %s", err)
551 | }
552 |
553 | ctx := context.Background()
554 | tx := MustTx(boil.BeginTx(ctx, nil))
555 | defer func() { _ = tx.Rollback() }()
556 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
557 | t.Error(err)
558 | }
559 |
560 | slice, err := Articles().All(ctx, tx)
561 | if err != nil {
562 | t.Error(err)
563 | }
564 |
565 | if len(slice) != 1 {
566 | t.Error("want one record, got:", len(slice))
567 | }
568 | }
569 |
570 | var (
571 | articleDBTypes = map[string]string{`ID`: `int`, `Title`: `varchar`, `Content`: `mediumtext`, `UserID`: `int`, `ProblemID`: `int`, `PublicTime`: `int`, `UpdateTime`: `int`, `SortTime`: `int`, `CommentsNum`: `int`, `AllowComment`: `tinyint`, `IsNotice`: `tinyint`}
572 | _ = bytes.MinRead
573 | )
574 |
575 | func testArticlesUpdate(t *testing.T) {
576 | t.Parallel()
577 |
578 | if 0 == len(articlePrimaryKeyColumns) {
579 | t.Skip("Skipping table with no primary key columns")
580 | }
581 | if len(articleAllColumns) == len(articlePrimaryKeyColumns) {
582 | t.Skip("Skipping table with only primary key columns")
583 | }
584 |
585 | seed := randomize.NewSeed()
586 | var err error
587 | o := &Article{}
588 | if err = randomize.Struct(seed, o, articleDBTypes, true, articleColumnsWithDefault...); err != nil {
589 | t.Errorf("Unable to randomize Article struct: %s", err)
590 | }
591 |
592 | ctx := context.Background()
593 | tx := MustTx(boil.BeginTx(ctx, nil))
594 | defer func() { _ = tx.Rollback() }()
595 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
596 | t.Error(err)
597 | }
598 |
599 | count, err := Articles().Count(ctx, tx)
600 | if err != nil {
601 | t.Error(err)
602 | }
603 |
604 | if count != 1 {
605 | t.Error("want one record, got:", count)
606 | }
607 |
608 | if err = randomize.Struct(seed, o, articleDBTypes, true, articlePrimaryKeyColumns...); err != nil {
609 | t.Errorf("Unable to randomize Article struct: %s", err)
610 | }
611 |
612 | if rowsAff, err := o.Update(ctx, tx, boil.Infer()); err != nil {
613 | t.Error(err)
614 | } else if rowsAff != 1 {
615 | t.Error("should only affect one row but affected", rowsAff)
616 | }
617 | }
618 |
619 | func testArticlesSliceUpdateAll(t *testing.T) {
620 | t.Parallel()
621 |
622 | if len(articleAllColumns) == len(articlePrimaryKeyColumns) {
623 | t.Skip("Skipping table with only primary key columns")
624 | }
625 |
626 | seed := randomize.NewSeed()
627 | var err error
628 | o := &Article{}
629 | if err = randomize.Struct(seed, o, articleDBTypes, true, articleColumnsWithDefault...); err != nil {
630 | t.Errorf("Unable to randomize Article struct: %s", err)
631 | }
632 |
633 | ctx := context.Background()
634 | tx := MustTx(boil.BeginTx(ctx, nil))
635 | defer func() { _ = tx.Rollback() }()
636 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
637 | t.Error(err)
638 | }
639 |
640 | count, err := Articles().Count(ctx, tx)
641 | if err != nil {
642 | t.Error(err)
643 | }
644 |
645 | if count != 1 {
646 | t.Error("want one record, got:", count)
647 | }
648 |
649 | if err = randomize.Struct(seed, o, articleDBTypes, true, articlePrimaryKeyColumns...); err != nil {
650 | t.Errorf("Unable to randomize Article struct: %s", err)
651 | }
652 |
653 | // Remove Primary keys and unique columns from what we plan to update
654 | var fields []string
655 | if strmangle.StringSliceMatch(articleAllColumns, articlePrimaryKeyColumns) {
656 | fields = articleAllColumns
657 | } else {
658 | fields = strmangle.SetComplement(
659 | articleAllColumns,
660 | articlePrimaryKeyColumns,
661 | )
662 | }
663 |
664 | value := reflect.Indirect(reflect.ValueOf(o))
665 | typ := reflect.TypeOf(o).Elem()
666 | n := typ.NumField()
667 |
668 | updateMap := M{}
669 | for _, col := range fields {
670 | for i := 0; i < n; i++ {
671 | f := typ.Field(i)
672 | if f.Tag.Get("boil") == col {
673 | updateMap[col] = value.Field(i).Interface()
674 | }
675 | }
676 | }
677 |
678 | slice := ArticleSlice{o}
679 | if rowsAff, err := slice.UpdateAll(ctx, tx, updateMap); err != nil {
680 | t.Error(err)
681 | } else if rowsAff != 1 {
682 | t.Error("wanted one record updated but got", rowsAff)
683 | }
684 | }
685 |
686 | func testArticlesUpsert(t *testing.T) {
687 | t.Parallel()
688 |
689 | if len(articleAllColumns) == len(articlePrimaryKeyColumns) {
690 | t.Skip("Skipping table with only primary key columns")
691 | }
692 | if len(mySQLArticleUniqueColumns) == 0 {
693 | t.Skip("Skipping table with no unique columns to conflict on")
694 | }
695 |
696 | seed := randomize.NewSeed()
697 | var err error
698 | // Attempt the INSERT side of an UPSERT
699 | o := Article{}
700 | if err = randomize.Struct(seed, &o, articleDBTypes, false); err != nil {
701 | t.Errorf("Unable to randomize Article struct: %s", err)
702 | }
703 |
704 | ctx := context.Background()
705 | tx := MustTx(boil.BeginTx(ctx, nil))
706 | defer func() { _ = tx.Rollback() }()
707 | if err = o.Upsert(ctx, tx, boil.Infer(), boil.Infer()); err != nil {
708 | t.Errorf("Unable to upsert Article: %s", err)
709 | }
710 |
711 | count, err := Articles().Count(ctx, tx)
712 | if err != nil {
713 | t.Error(err)
714 | }
715 | if count != 1 {
716 | t.Error("want one record, got:", count)
717 | }
718 |
719 | // Attempt the UPDATE side of an UPSERT
720 | if err = randomize.Struct(seed, &o, articleDBTypes, false, articlePrimaryKeyColumns...); err != nil {
721 | t.Errorf("Unable to randomize Article struct: %s", err)
722 | }
723 |
724 | if err = o.Upsert(ctx, tx, boil.Infer(), boil.Infer()); err != nil {
725 | t.Errorf("Unable to upsert Article: %s", err)
726 | }
727 |
728 | count, err = Articles().Count(ctx, tx)
729 | if err != nil {
730 | t.Error(err)
731 | }
732 | if count != 1 {
733 | t.Error("want one record, got:", count)
734 | }
735 | }
736 |
--------------------------------------------------------------------------------
/models/contest_test.go:
--------------------------------------------------------------------------------
1 | // Code generated by SQLBoiler 3.5.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT.
2 | // This file is meant to be re-generated in place and/or deleted at any time.
3 |
4 | package models
5 |
6 | import (
7 | "bytes"
8 | "context"
9 | "reflect"
10 | "testing"
11 |
12 | "github.com/volatiletech/sqlboiler/boil"
13 | "github.com/volatiletech/sqlboiler/queries"
14 | "github.com/volatiletech/sqlboiler/randomize"
15 | "github.com/volatiletech/sqlboiler/strmangle"
16 | )
17 |
18 | var (
19 | // Relationships sometimes use the reflection helper queries.Equal/queries.Assign
20 | // so force a package dependency in case they don't.
21 | _ = queries.Equal
22 | )
23 |
24 | func testContests(t *testing.T) {
25 | t.Parallel()
26 |
27 | query := Contests()
28 |
29 | if query.Query == nil {
30 | t.Error("expected a query, got nothing")
31 | }
32 | }
33 |
34 | func testContestsDelete(t *testing.T) {
35 | t.Parallel()
36 |
37 | seed := randomize.NewSeed()
38 | var err error
39 | o := &Contest{}
40 | if err = randomize.Struct(seed, o, contestDBTypes, true, contestColumnsWithDefault...); err != nil {
41 | t.Errorf("Unable to randomize Contest struct: %s", err)
42 | }
43 |
44 | ctx := context.Background()
45 | tx := MustTx(boil.BeginTx(ctx, nil))
46 | defer func() { _ = tx.Rollback() }()
47 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
48 | t.Error(err)
49 | }
50 |
51 | if rowsAff, err := o.Delete(ctx, tx); err != nil {
52 | t.Error(err)
53 | } else if rowsAff != 1 {
54 | t.Error("should only have deleted one row, but affected:", rowsAff)
55 | }
56 |
57 | count, err := Contests().Count(ctx, tx)
58 | if err != nil {
59 | t.Error(err)
60 | }
61 |
62 | if count != 0 {
63 | t.Error("want zero records, got:", count)
64 | }
65 | }
66 |
67 | func testContestsQueryDeleteAll(t *testing.T) {
68 | t.Parallel()
69 |
70 | seed := randomize.NewSeed()
71 | var err error
72 | o := &Contest{}
73 | if err = randomize.Struct(seed, o, contestDBTypes, true, contestColumnsWithDefault...); err != nil {
74 | t.Errorf("Unable to randomize Contest struct: %s", err)
75 | }
76 |
77 | ctx := context.Background()
78 | tx := MustTx(boil.BeginTx(ctx, nil))
79 | defer func() { _ = tx.Rollback() }()
80 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
81 | t.Error(err)
82 | }
83 |
84 | if rowsAff, err := Contests().DeleteAll(ctx, tx); err != nil {
85 | t.Error(err)
86 | } else if rowsAff != 1 {
87 | t.Error("should only have deleted one row, but affected:", rowsAff)
88 | }
89 |
90 | count, err := Contests().Count(ctx, tx)
91 | if err != nil {
92 | t.Error(err)
93 | }
94 |
95 | if count != 0 {
96 | t.Error("want zero records, got:", count)
97 | }
98 | }
99 |
100 | func testContestsSliceDeleteAll(t *testing.T) {
101 | t.Parallel()
102 |
103 | seed := randomize.NewSeed()
104 | var err error
105 | o := &Contest{}
106 | if err = randomize.Struct(seed, o, contestDBTypes, true, contestColumnsWithDefault...); err != nil {
107 | t.Errorf("Unable to randomize Contest struct: %s", err)
108 | }
109 |
110 | ctx := context.Background()
111 | tx := MustTx(boil.BeginTx(ctx, nil))
112 | defer func() { _ = tx.Rollback() }()
113 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
114 | t.Error(err)
115 | }
116 |
117 | slice := ContestSlice{o}
118 |
119 | if rowsAff, err := slice.DeleteAll(ctx, tx); err != nil {
120 | t.Error(err)
121 | } else if rowsAff != 1 {
122 | t.Error("should only have deleted one row, but affected:", rowsAff)
123 | }
124 |
125 | count, err := Contests().Count(ctx, tx)
126 | if err != nil {
127 | t.Error(err)
128 | }
129 |
130 | if count != 0 {
131 | t.Error("want zero records, got:", count)
132 | }
133 | }
134 |
135 | func testContestsExists(t *testing.T) {
136 | t.Parallel()
137 |
138 | seed := randomize.NewSeed()
139 | var err error
140 | o := &Contest{}
141 | if err = randomize.Struct(seed, o, contestDBTypes, true, contestColumnsWithDefault...); err != nil {
142 | t.Errorf("Unable to randomize Contest struct: %s", err)
143 | }
144 |
145 | ctx := context.Background()
146 | tx := MustTx(boil.BeginTx(ctx, nil))
147 | defer func() { _ = tx.Rollback() }()
148 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
149 | t.Error(err)
150 | }
151 |
152 | e, err := ContestExists(ctx, tx, o.ID)
153 | if err != nil {
154 | t.Errorf("Unable to check if Contest exists: %s", err)
155 | }
156 | if !e {
157 | t.Errorf("Expected ContestExists to return true, but got false.")
158 | }
159 | }
160 |
161 | func testContestsFind(t *testing.T) {
162 | t.Parallel()
163 |
164 | seed := randomize.NewSeed()
165 | var err error
166 | o := &Contest{}
167 | if err = randomize.Struct(seed, o, contestDBTypes, true, contestColumnsWithDefault...); err != nil {
168 | t.Errorf("Unable to randomize Contest struct: %s", err)
169 | }
170 |
171 | ctx := context.Background()
172 | tx := MustTx(boil.BeginTx(ctx, nil))
173 | defer func() { _ = tx.Rollback() }()
174 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
175 | t.Error(err)
176 | }
177 |
178 | contestFound, err := FindContest(ctx, tx, o.ID)
179 | if err != nil {
180 | t.Error(err)
181 | }
182 |
183 | if contestFound == nil {
184 | t.Error("want a record, got nil")
185 | }
186 | }
187 |
188 | func testContestsBind(t *testing.T) {
189 | t.Parallel()
190 |
191 | seed := randomize.NewSeed()
192 | var err error
193 | o := &Contest{}
194 | if err = randomize.Struct(seed, o, contestDBTypes, true, contestColumnsWithDefault...); err != nil {
195 | t.Errorf("Unable to randomize Contest struct: %s", err)
196 | }
197 |
198 | ctx := context.Background()
199 | tx := MustTx(boil.BeginTx(ctx, nil))
200 | defer func() { _ = tx.Rollback() }()
201 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
202 | t.Error(err)
203 | }
204 |
205 | if err = Contests().Bind(ctx, tx, o); err != nil {
206 | t.Error(err)
207 | }
208 | }
209 |
210 | func testContestsOne(t *testing.T) {
211 | t.Parallel()
212 |
213 | seed := randomize.NewSeed()
214 | var err error
215 | o := &Contest{}
216 | if err = randomize.Struct(seed, o, contestDBTypes, true, contestColumnsWithDefault...); err != nil {
217 | t.Errorf("Unable to randomize Contest struct: %s", err)
218 | }
219 |
220 | ctx := context.Background()
221 | tx := MustTx(boil.BeginTx(ctx, nil))
222 | defer func() { _ = tx.Rollback() }()
223 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
224 | t.Error(err)
225 | }
226 |
227 | if x, err := Contests().One(ctx, tx); err != nil {
228 | t.Error(err)
229 | } else if x == nil {
230 | t.Error("expected to get a non nil record")
231 | }
232 | }
233 |
234 | func testContestsAll(t *testing.T) {
235 | t.Parallel()
236 |
237 | seed := randomize.NewSeed()
238 | var err error
239 | contestOne := &Contest{}
240 | contestTwo := &Contest{}
241 | if err = randomize.Struct(seed, contestOne, contestDBTypes, false, contestColumnsWithDefault...); err != nil {
242 | t.Errorf("Unable to randomize Contest struct: %s", err)
243 | }
244 | if err = randomize.Struct(seed, contestTwo, contestDBTypes, false, contestColumnsWithDefault...); err != nil {
245 | t.Errorf("Unable to randomize Contest struct: %s", err)
246 | }
247 |
248 | ctx := context.Background()
249 | tx := MustTx(boil.BeginTx(ctx, nil))
250 | defer func() { _ = tx.Rollback() }()
251 | if err = contestOne.Insert(ctx, tx, boil.Infer()); err != nil {
252 | t.Error(err)
253 | }
254 | if err = contestTwo.Insert(ctx, tx, boil.Infer()); err != nil {
255 | t.Error(err)
256 | }
257 |
258 | slice, err := Contests().All(ctx, tx)
259 | if err != nil {
260 | t.Error(err)
261 | }
262 |
263 | if len(slice) != 2 {
264 | t.Error("want 2 records, got:", len(slice))
265 | }
266 | }
267 |
268 | func testContestsCount(t *testing.T) {
269 | t.Parallel()
270 |
271 | var err error
272 | seed := randomize.NewSeed()
273 | contestOne := &Contest{}
274 | contestTwo := &Contest{}
275 | if err = randomize.Struct(seed, contestOne, contestDBTypes, false, contestColumnsWithDefault...); err != nil {
276 | t.Errorf("Unable to randomize Contest struct: %s", err)
277 | }
278 | if err = randomize.Struct(seed, contestTwo, contestDBTypes, false, contestColumnsWithDefault...); err != nil {
279 | t.Errorf("Unable to randomize Contest struct: %s", err)
280 | }
281 |
282 | ctx := context.Background()
283 | tx := MustTx(boil.BeginTx(ctx, nil))
284 | defer func() { _ = tx.Rollback() }()
285 | if err = contestOne.Insert(ctx, tx, boil.Infer()); err != nil {
286 | t.Error(err)
287 | }
288 | if err = contestTwo.Insert(ctx, tx, boil.Infer()); err != nil {
289 | t.Error(err)
290 | }
291 |
292 | count, err := Contests().Count(ctx, tx)
293 | if err != nil {
294 | t.Error(err)
295 | }
296 |
297 | if count != 2 {
298 | t.Error("want 2 records, got:", count)
299 | }
300 | }
301 |
302 | func contestBeforeInsertHook(ctx context.Context, e boil.ContextExecutor, o *Contest) error {
303 | *o = Contest{}
304 | return nil
305 | }
306 |
307 | func contestAfterInsertHook(ctx context.Context, e boil.ContextExecutor, o *Contest) error {
308 | *o = Contest{}
309 | return nil
310 | }
311 |
312 | func contestAfterSelectHook(ctx context.Context, e boil.ContextExecutor, o *Contest) error {
313 | *o = Contest{}
314 | return nil
315 | }
316 |
317 | func contestBeforeUpdateHook(ctx context.Context, e boil.ContextExecutor, o *Contest) error {
318 | *o = Contest{}
319 | return nil
320 | }
321 |
322 | func contestAfterUpdateHook(ctx context.Context, e boil.ContextExecutor, o *Contest) error {
323 | *o = Contest{}
324 | return nil
325 | }
326 |
327 | func contestBeforeDeleteHook(ctx context.Context, e boil.ContextExecutor, o *Contest) error {
328 | *o = Contest{}
329 | return nil
330 | }
331 |
332 | func contestAfterDeleteHook(ctx context.Context, e boil.ContextExecutor, o *Contest) error {
333 | *o = Contest{}
334 | return nil
335 | }
336 |
337 | func contestBeforeUpsertHook(ctx context.Context, e boil.ContextExecutor, o *Contest) error {
338 | *o = Contest{}
339 | return nil
340 | }
341 |
342 | func contestAfterUpsertHook(ctx context.Context, e boil.ContextExecutor, o *Contest) error {
343 | *o = Contest{}
344 | return nil
345 | }
346 |
347 | func testContestsHooks(t *testing.T) {
348 | t.Parallel()
349 |
350 | var err error
351 |
352 | ctx := context.Background()
353 | empty := &Contest{}
354 | o := &Contest{}
355 |
356 | seed := randomize.NewSeed()
357 | if err = randomize.Struct(seed, o, contestDBTypes, false); err != nil {
358 | t.Errorf("Unable to randomize Contest object: %s", err)
359 | }
360 |
361 | AddContestHook(boil.BeforeInsertHook, contestBeforeInsertHook)
362 | if err = o.doBeforeInsertHooks(ctx, nil); err != nil {
363 | t.Errorf("Unable to execute doBeforeInsertHooks: %s", err)
364 | }
365 | if !reflect.DeepEqual(o, empty) {
366 | t.Errorf("Expected BeforeInsertHook function to empty object, but got: %#v", o)
367 | }
368 | contestBeforeInsertHooks = []ContestHook{}
369 |
370 | AddContestHook(boil.AfterInsertHook, contestAfterInsertHook)
371 | if err = o.doAfterInsertHooks(ctx, nil); err != nil {
372 | t.Errorf("Unable to execute doAfterInsertHooks: %s", err)
373 | }
374 | if !reflect.DeepEqual(o, empty) {
375 | t.Errorf("Expected AfterInsertHook function to empty object, but got: %#v", o)
376 | }
377 | contestAfterInsertHooks = []ContestHook{}
378 |
379 | AddContestHook(boil.AfterSelectHook, contestAfterSelectHook)
380 | if err = o.doAfterSelectHooks(ctx, nil); err != nil {
381 | t.Errorf("Unable to execute doAfterSelectHooks: %s", err)
382 | }
383 | if !reflect.DeepEqual(o, empty) {
384 | t.Errorf("Expected AfterSelectHook function to empty object, but got: %#v", o)
385 | }
386 | contestAfterSelectHooks = []ContestHook{}
387 |
388 | AddContestHook(boil.BeforeUpdateHook, contestBeforeUpdateHook)
389 | if err = o.doBeforeUpdateHooks(ctx, nil); err != nil {
390 | t.Errorf("Unable to execute doBeforeUpdateHooks: %s", err)
391 | }
392 | if !reflect.DeepEqual(o, empty) {
393 | t.Errorf("Expected BeforeUpdateHook function to empty object, but got: %#v", o)
394 | }
395 | contestBeforeUpdateHooks = []ContestHook{}
396 |
397 | AddContestHook(boil.AfterUpdateHook, contestAfterUpdateHook)
398 | if err = o.doAfterUpdateHooks(ctx, nil); err != nil {
399 | t.Errorf("Unable to execute doAfterUpdateHooks: %s", err)
400 | }
401 | if !reflect.DeepEqual(o, empty) {
402 | t.Errorf("Expected AfterUpdateHook function to empty object, but got: %#v", o)
403 | }
404 | contestAfterUpdateHooks = []ContestHook{}
405 |
406 | AddContestHook(boil.BeforeDeleteHook, contestBeforeDeleteHook)
407 | if err = o.doBeforeDeleteHooks(ctx, nil); err != nil {
408 | t.Errorf("Unable to execute doBeforeDeleteHooks: %s", err)
409 | }
410 | if !reflect.DeepEqual(o, empty) {
411 | t.Errorf("Expected BeforeDeleteHook function to empty object, but got: %#v", o)
412 | }
413 | contestBeforeDeleteHooks = []ContestHook{}
414 |
415 | AddContestHook(boil.AfterDeleteHook, contestAfterDeleteHook)
416 | if err = o.doAfterDeleteHooks(ctx, nil); err != nil {
417 | t.Errorf("Unable to execute doAfterDeleteHooks: %s", err)
418 | }
419 | if !reflect.DeepEqual(o, empty) {
420 | t.Errorf("Expected AfterDeleteHook function to empty object, but got: %#v", o)
421 | }
422 | contestAfterDeleteHooks = []ContestHook{}
423 |
424 | AddContestHook(boil.BeforeUpsertHook, contestBeforeUpsertHook)
425 | if err = o.doBeforeUpsertHooks(ctx, nil); err != nil {
426 | t.Errorf("Unable to execute doBeforeUpsertHooks: %s", err)
427 | }
428 | if !reflect.DeepEqual(o, empty) {
429 | t.Errorf("Expected BeforeUpsertHook function to empty object, but got: %#v", o)
430 | }
431 | contestBeforeUpsertHooks = []ContestHook{}
432 |
433 | AddContestHook(boil.AfterUpsertHook, contestAfterUpsertHook)
434 | if err = o.doAfterUpsertHooks(ctx, nil); err != nil {
435 | t.Errorf("Unable to execute doAfterUpsertHooks: %s", err)
436 | }
437 | if !reflect.DeepEqual(o, empty) {
438 | t.Errorf("Expected AfterUpsertHook function to empty object, but got: %#v", o)
439 | }
440 | contestAfterUpsertHooks = []ContestHook{}
441 | }
442 |
443 | func testContestsInsert(t *testing.T) {
444 | t.Parallel()
445 |
446 | seed := randomize.NewSeed()
447 | var err error
448 | o := &Contest{}
449 | if err = randomize.Struct(seed, o, contestDBTypes, true, contestColumnsWithDefault...); err != nil {
450 | t.Errorf("Unable to randomize Contest struct: %s", err)
451 | }
452 |
453 | ctx := context.Background()
454 | tx := MustTx(boil.BeginTx(ctx, nil))
455 | defer func() { _ = tx.Rollback() }()
456 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
457 | t.Error(err)
458 | }
459 |
460 | count, err := Contests().Count(ctx, tx)
461 | if err != nil {
462 | t.Error(err)
463 | }
464 |
465 | if count != 1 {
466 | t.Error("want one record, got:", count)
467 | }
468 | }
469 |
470 | func testContestsInsertWhitelist(t *testing.T) {
471 | t.Parallel()
472 |
473 | seed := randomize.NewSeed()
474 | var err error
475 | o := &Contest{}
476 | if err = randomize.Struct(seed, o, contestDBTypes, true); err != nil {
477 | t.Errorf("Unable to randomize Contest struct: %s", err)
478 | }
479 |
480 | ctx := context.Background()
481 | tx := MustTx(boil.BeginTx(ctx, nil))
482 | defer func() { _ = tx.Rollback() }()
483 | if err = o.Insert(ctx, tx, boil.Whitelist(contestColumnsWithoutDefault...)); err != nil {
484 | t.Error(err)
485 | }
486 |
487 | count, err := Contests().Count(ctx, tx)
488 | if err != nil {
489 | t.Error(err)
490 | }
491 |
492 | if count != 1 {
493 | t.Error("want one record, got:", count)
494 | }
495 | }
496 |
497 | func testContestsReload(t *testing.T) {
498 | t.Parallel()
499 |
500 | seed := randomize.NewSeed()
501 | var err error
502 | o := &Contest{}
503 | if err = randomize.Struct(seed, o, contestDBTypes, true, contestColumnsWithDefault...); err != nil {
504 | t.Errorf("Unable to randomize Contest struct: %s", err)
505 | }
506 |
507 | ctx := context.Background()
508 | tx := MustTx(boil.BeginTx(ctx, nil))
509 | defer func() { _ = tx.Rollback() }()
510 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
511 | t.Error(err)
512 | }
513 |
514 | if err = o.Reload(ctx, tx); err != nil {
515 | t.Error(err)
516 | }
517 | }
518 |
519 | func testContestsReloadAll(t *testing.T) {
520 | t.Parallel()
521 |
522 | seed := randomize.NewSeed()
523 | var err error
524 | o := &Contest{}
525 | if err = randomize.Struct(seed, o, contestDBTypes, true, contestColumnsWithDefault...); err != nil {
526 | t.Errorf("Unable to randomize Contest struct: %s", err)
527 | }
528 |
529 | ctx := context.Background()
530 | tx := MustTx(boil.BeginTx(ctx, nil))
531 | defer func() { _ = tx.Rollback() }()
532 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
533 | t.Error(err)
534 | }
535 |
536 | slice := ContestSlice{o}
537 |
538 | if err = slice.ReloadAll(ctx, tx); err != nil {
539 | t.Error(err)
540 | }
541 | }
542 |
543 | func testContestsSelect(t *testing.T) {
544 | t.Parallel()
545 |
546 | seed := randomize.NewSeed()
547 | var err error
548 | o := &Contest{}
549 | if err = randomize.Struct(seed, o, contestDBTypes, true, contestColumnsWithDefault...); err != nil {
550 | t.Errorf("Unable to randomize Contest struct: %s", err)
551 | }
552 |
553 | ctx := context.Background()
554 | tx := MustTx(boil.BeginTx(ctx, nil))
555 | defer func() { _ = tx.Rollback() }()
556 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
557 | t.Error(err)
558 | }
559 |
560 | slice, err := Contests().All(ctx, tx)
561 | if err != nil {
562 | t.Error(err)
563 | }
564 |
565 | if len(slice) != 1 {
566 | t.Error("want one record, got:", len(slice))
567 | }
568 | }
569 |
570 | var (
571 | contestDBTypes = map[string]string{`ID`: `int`, `Title`: `varchar`, `Subtitle`: `text`, `StartTime`: `int`, `EndTime`: `int`, `HolderID`: `int`, `Type`: `enum('noi','ioi','acm')`, `Information`: `text`, `Problems`: `text`, `Admins`: `text`, `RanklistID`: `int`, `IsPublic`: `tinyint`, `HideStatistics`: `tinyint`}
572 | _ = bytes.MinRead
573 | )
574 |
575 | func testContestsUpdate(t *testing.T) {
576 | t.Parallel()
577 |
578 | if 0 == len(contestPrimaryKeyColumns) {
579 | t.Skip("Skipping table with no primary key columns")
580 | }
581 | if len(contestAllColumns) == len(contestPrimaryKeyColumns) {
582 | t.Skip("Skipping table with only primary key columns")
583 | }
584 |
585 | seed := randomize.NewSeed()
586 | var err error
587 | o := &Contest{}
588 | if err = randomize.Struct(seed, o, contestDBTypes, true, contestColumnsWithDefault...); err != nil {
589 | t.Errorf("Unable to randomize Contest struct: %s", err)
590 | }
591 |
592 | ctx := context.Background()
593 | tx := MustTx(boil.BeginTx(ctx, nil))
594 | defer func() { _ = tx.Rollback() }()
595 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
596 | t.Error(err)
597 | }
598 |
599 | count, err := Contests().Count(ctx, tx)
600 | if err != nil {
601 | t.Error(err)
602 | }
603 |
604 | if count != 1 {
605 | t.Error("want one record, got:", count)
606 | }
607 |
608 | if err = randomize.Struct(seed, o, contestDBTypes, true, contestPrimaryKeyColumns...); err != nil {
609 | t.Errorf("Unable to randomize Contest struct: %s", err)
610 | }
611 |
612 | if rowsAff, err := o.Update(ctx, tx, boil.Infer()); err != nil {
613 | t.Error(err)
614 | } else if rowsAff != 1 {
615 | t.Error("should only affect one row but affected", rowsAff)
616 | }
617 | }
618 |
619 | func testContestsSliceUpdateAll(t *testing.T) {
620 | t.Parallel()
621 |
622 | if len(contestAllColumns) == len(contestPrimaryKeyColumns) {
623 | t.Skip("Skipping table with only primary key columns")
624 | }
625 |
626 | seed := randomize.NewSeed()
627 | var err error
628 | o := &Contest{}
629 | if err = randomize.Struct(seed, o, contestDBTypes, true, contestColumnsWithDefault...); err != nil {
630 | t.Errorf("Unable to randomize Contest struct: %s", err)
631 | }
632 |
633 | ctx := context.Background()
634 | tx := MustTx(boil.BeginTx(ctx, nil))
635 | defer func() { _ = tx.Rollback() }()
636 | if err = o.Insert(ctx, tx, boil.Infer()); err != nil {
637 | t.Error(err)
638 | }
639 |
640 | count, err := Contests().Count(ctx, tx)
641 | if err != nil {
642 | t.Error(err)
643 | }
644 |
645 | if count != 1 {
646 | t.Error("want one record, got:", count)
647 | }
648 |
649 | if err = randomize.Struct(seed, o, contestDBTypes, true, contestPrimaryKeyColumns...); err != nil {
650 | t.Errorf("Unable to randomize Contest struct: %s", err)
651 | }
652 |
653 | // Remove Primary keys and unique columns from what we plan to update
654 | var fields []string
655 | if strmangle.StringSliceMatch(contestAllColumns, contestPrimaryKeyColumns) {
656 | fields = contestAllColumns
657 | } else {
658 | fields = strmangle.SetComplement(
659 | contestAllColumns,
660 | contestPrimaryKeyColumns,
661 | )
662 | }
663 |
664 | value := reflect.Indirect(reflect.ValueOf(o))
665 | typ := reflect.TypeOf(o).Elem()
666 | n := typ.NumField()
667 |
668 | updateMap := M{}
669 | for _, col := range fields {
670 | for i := 0; i < n; i++ {
671 | f := typ.Field(i)
672 | if f.Tag.Get("boil") == col {
673 | updateMap[col] = value.Field(i).Interface()
674 | }
675 | }
676 | }
677 |
678 | slice := ContestSlice{o}
679 | if rowsAff, err := slice.UpdateAll(ctx, tx, updateMap); err != nil {
680 | t.Error(err)
681 | } else if rowsAff != 1 {
682 | t.Error("wanted one record updated but got", rowsAff)
683 | }
684 | }
685 |
686 | func testContestsUpsert(t *testing.T) {
687 | t.Parallel()
688 |
689 | if len(contestAllColumns) == len(contestPrimaryKeyColumns) {
690 | t.Skip("Skipping table with only primary key columns")
691 | }
692 | if len(mySQLContestUniqueColumns) == 0 {
693 | t.Skip("Skipping table with no unique columns to conflict on")
694 | }
695 |
696 | seed := randomize.NewSeed()
697 | var err error
698 | // Attempt the INSERT side of an UPSERT
699 | o := Contest{}
700 | if err = randomize.Struct(seed, &o, contestDBTypes, false); err != nil {
701 | t.Errorf("Unable to randomize Contest struct: %s", err)
702 | }
703 |
704 | ctx := context.Background()
705 | tx := MustTx(boil.BeginTx(ctx, nil))
706 | defer func() { _ = tx.Rollback() }()
707 | if err = o.Upsert(ctx, tx, boil.Infer(), boil.Infer()); err != nil {
708 | t.Errorf("Unable to upsert Contest: %s", err)
709 | }
710 |
711 | count, err := Contests().Count(ctx, tx)
712 | if err != nil {
713 | t.Error(err)
714 | }
715 | if count != 1 {
716 | t.Error("want one record, got:", count)
717 | }
718 |
719 | // Attempt the UPDATE side of an UPSERT
720 | if err = randomize.Struct(seed, &o, contestDBTypes, false, contestPrimaryKeyColumns...); err != nil {
721 | t.Errorf("Unable to randomize Contest struct: %s", err)
722 | }
723 |
724 | if err = o.Upsert(ctx, tx, boil.Infer(), boil.Infer()); err != nil {
725 | t.Errorf("Unable to upsert Contest: %s", err)
726 | }
727 |
728 | count, err = Contests().Count(ctx, tx)
729 | if err != nil {
730 | t.Error(err)
731 | }
732 | if count != 1 {
733 | t.Error("want one record, got:", count)
734 | }
735 | }
736 |
--------------------------------------------------------------------------------