├── .gitattributes ├── .github ├── data │ └── results.md.proto ├── dependabot.yml ├── run_benchmarks_actions.sh └── workflows │ ├── build.yaml │ ├── codeql.yaml │ ├── golangci-lint.yaml │ ├── results.yaml │ └── security.yaml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── bench ├── beego.go ├── bun.go ├── dbr.go ├── ent.go ├── ent │ ├── client.go │ ├── ent.go │ ├── enttest │ │ └── enttest.go │ ├── generate.go │ ├── hook │ │ └── hook.go │ ├── migrate │ │ ├── migrate.go │ │ └── schema.go │ ├── model.go │ ├── model │ │ ├── model.go │ │ └── where.go │ ├── model_create.go │ ├── model_delete.go │ ├── model_query.go │ ├── model_update.go │ ├── mutation.go │ ├── predicate │ │ └── predicate.go │ ├── runtime.go │ ├── runtime │ │ └── runtime.go │ ├── schema │ │ └── model.go │ └── tx.go ├── gen.go ├── gen │ ├── main.go │ ├── models │ │ └── models.go │ └── query │ │ ├── gen.go │ │ └── models.gen.go ├── godb.go ├── gorm.go ├── gorm_prep.go ├── gorp.go ├── jet.go ├── jet │ ├── gen.go │ ├── test │ │ └── public │ │ │ ├── model │ │ │ └── models.go │ │ │ └── table │ │ │ ├── models.go │ │ │ └── table_use_schema.go │ └── tool.go ├── models.go ├── pg.go ├── pgx.go ├── pgx_pool.go ├── pop.go ├── raw.go ├── reform.go ├── reform │ ├── reform_models.go │ └── reform_models_reform.go ├── rel.go ├── sq.go ├── sq │ └── db │ │ └── models.go ├── sqlboiler.go ├── sqlboiler │ ├── boil_queries.go │ ├── boil_table_names.go │ ├── boil_types.go │ ├── models.go │ └── psql_upsert.go ├── sqlc.go ├── sqlc │ ├── README.md │ ├── db │ │ ├── copyfrom.go │ │ ├── db.go │ │ ├── models.go │ │ └── queries.sql.go │ ├── queries.sql │ ├── schema.sql │ └── sqlc.yaml ├── sqlx.go ├── upper.go ├── xorm.go └── zorm.go ├── docker-compose.yml ├── go.mod ├── go.sum ├── helper ├── constants.go ├── error.go ├── misc.go ├── sql.go └── suite.go ├── main.go ├── results.md └── run_benchmarks.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | ## GITATTRIBUTES FOR WEB PROJECTS 2 | # 3 | # These settings are for any web project. 4 | # 5 | # Details per file setting: 6 | # text These files should be normalized (i.e. convert CRLF to LF). 7 | # binary These files are binary and should be left untouched. 8 | # 9 | # Note that binary is a macro for -text -diff. 10 | ###################################################################### 11 | 12 | # Auto detect 13 | ## Handle line endings automatically for files detected as 14 | ## text and leave all files detected as binary untouched. 15 | ## This will handle all files NOT defined below. 16 | * text=auto 17 | 18 | # Source code 19 | *.bash text eol=lf 20 | *.bat text eol=crlf 21 | *.cmd text eol=crlf 22 | *.coffee text 23 | *.css text 24 | *.htm text diff=html 25 | *.html text diff=html 26 | *.inc text 27 | *.ini text 28 | *.js text 29 | *.json text 30 | *.jsx text 31 | *.less text 32 | *.ls text 33 | *.map text -diff 34 | *.od text 35 | *.onlydata text 36 | *.php text diff=php 37 | *.pl text 38 | *.ps1 text eol=crlf 39 | *.py text diff=python 40 | *.rb text diff=ruby 41 | *.sass text 42 | *.scm text 43 | *.scss text diff=css 44 | *.sh text eol=lf 45 | *.sql text 46 | *.styl text 47 | *.tag text 48 | *.ts text 49 | *.tsx text 50 | *.xml text 51 | *.xhtml text diff=html 52 | 53 | # Docker 54 | Dockerfile text 55 | 56 | # Documentation 57 | *.ipynb text 58 | *.markdown text 59 | *.md text 60 | *.mdwn text 61 | *.mdown text 62 | *.mkd text 63 | *.mkdn text 64 | *.mdtxt text 65 | *.mdtext text 66 | *.txt text 67 | AUTHORS text 68 | CHANGELOG text 69 | CHANGES text 70 | CONTRIBUTING text 71 | COPYING text 72 | copyright text 73 | *COPYRIGHT* text 74 | INSTALL text 75 | license text 76 | LICENSE text 77 | NEWS text 78 | readme text 79 | *README* text 80 | TODO text 81 | 82 | # Templates 83 | *.dot text 84 | *.ejs text 85 | *.haml text 86 | *.handlebars text 87 | *.hbs text 88 | *.hbt text 89 | *.jade text 90 | *.latte text 91 | *.mustache text 92 | *.njk text 93 | *.phtml text 94 | *.tmpl text 95 | *.tpl text 96 | *.twig text 97 | *.vue text 98 | 99 | # Configs 100 | *.cnf text 101 | *.conf text 102 | *.config text 103 | .editorconfig text 104 | .env text 105 | .gitattributes text 106 | .gitconfig text 107 | .htaccess text 108 | *.lock text -diff 109 | package-lock.json text -diff 110 | *.toml text 111 | *.yaml text 112 | *.yml text 113 | browserslist text 114 | Makefile text 115 | makefile text 116 | 117 | # Heroku 118 | Procfile text 119 | 120 | # Graphics 121 | *.ai binary 122 | *.bmp binary 123 | *.eps binary 124 | *.gif binary 125 | *.gifv binary 126 | *.ico binary 127 | *.jng binary 128 | *.jp2 binary 129 | *.jpg binary 130 | *.jpeg binary 131 | *.jpx binary 132 | *.jxr binary 133 | *.pdf binary 134 | *.png binary 135 | *.psb binary 136 | *.psd binary 137 | # SVG treated as an asset (binary) by default. 138 | *.svg text 139 | # If you want to treat it as binary, 140 | # use the following line instead. 141 | # *.svg binary 142 | *.svgz binary 143 | *.tif binary 144 | *.tiff binary 145 | *.wbmp binary 146 | *.webp binary 147 | 148 | # Audio 149 | *.kar binary 150 | *.m4a binary 151 | *.mid binary 152 | *.midi binary 153 | *.mp3 binary 154 | *.ogg binary 155 | *.ra binary 156 | 157 | # Video 158 | *.3gpp binary 159 | *.3gp binary 160 | *.as binary 161 | *.asf binary 162 | *.asx binary 163 | *.fla binary 164 | *.flv binary 165 | *.m4v binary 166 | *.mng binary 167 | *.mov binary 168 | *.mp4 binary 169 | *.mpeg binary 170 | *.mpg binary 171 | *.ogv binary 172 | *.swc binary 173 | *.swf binary 174 | *.webm binary 175 | 176 | # Archives 177 | *.7z binary 178 | *.gz binary 179 | *.jar binary 180 | *.rar binary 181 | *.tar binary 182 | *.zip binary 183 | 184 | # Fonts 185 | *.ttf binary 186 | *.eot binary 187 | *.otf binary 188 | *.woff binary 189 | *.woff2 binary 190 | 191 | # Executables 192 | *.exe binary 193 | *.pyc binary 194 | 195 | # RC files (like .babelrc or .eslintrc) 196 | *.*rc text 197 | 198 | # Ignore files (like .npmignore or .gitignore) 199 | *.*ignore text 200 | 201 | *.go -text diff=golang -------------------------------------------------------------------------------- /.github/data/results.md.proto: -------------------------------------------------------------------------------- 1 | # Results 2 | 3 | - orm-benchmark (with no flags) 4 | 5 | ``` 6 | @(benchmark-results) 7 | ``` -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/run_benchmarks_actions.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Prototype benchmark results 4 | proto=$(cat .github/data/results.md.proto) 5 | 6 | # Build benchmarker 7 | go build main.go 8 | 9 | # Apply output to template 10 | logs=$(./main -source="host=localhost user=postgres password=postgres dbname=test sslmode=disable" -debug=false) 11 | 12 | # Print final results & delete benchmarker 13 | rm main 14 | 15 | echo """# Results 16 | 17 | - orm-benchmark (with no flags) 18 | \`\`\` 19 | ${logs} 20 | \`\`\`""" > results.md -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Benchmark 3 | jobs: 4 | Build: 5 | strategy: 6 | matrix: 7 | platform: [ubuntu-latest] 8 | runs-on: ${{ matrix.platform }} 9 | services: 10 | postgres: 11 | image: 'postgres:latest' 12 | ports: 13 | - '5432:5432' 14 | env: 15 | POSTGRES_USER: postgres 16 | POSTGRES_PASSWORD: postgres 17 | POSTGRES_DB: test 18 | steps: 19 | - name: Fetch Repository 20 | uses: actions/checkout@v3 21 | - name: Build Benchmarker 22 | run: go build main.go 23 | - name: Run Benchmarker 24 | run: ./main -source="host=localhost user=postgres password=postgres dbname=test sslmode=disable" -orm=all -debug=false -------------------------------------------------------------------------------- /.github/workflows/codeql.yaml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | on: [push, pull_request] 3 | jobs: 4 | codeql: 5 | name: CodeQL Analysis 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Fetch Repository 9 | uses: actions/checkout@v2 10 | with: 11 | fetch-depth: 2 12 | - run: git checkout HEAD^2 13 | if: ${{ github.event_name == 'pull_request' }} 14 | - name: Initialize CodeQL 15 | uses: github/codeql-action/init@v1 16 | with: 17 | languages: go 18 | - name: Autobuild 19 | uses: github/codeql-action/autobuild@v1 20 | - name: Perform CodeQL Analysis 21 | uses: github/codeql-action/analyze@v1 -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yaml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: GoLint 3 | jobs: 4 | GoLint: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Fetch Repository 8 | uses: actions/checkout@v2 9 | - name: Check GoLint Errors 10 | uses: reviewdog/action-golangci-lint@v2 -------------------------------------------------------------------------------- /.github/workflows/results.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | - main 6 | 7 | name: Generate Results 8 | jobs: 9 | Results: 10 | strategy: 11 | matrix: 12 | platform: [ubuntu-latest] 13 | runs-on: ${{ matrix.platform }} 14 | services: 15 | postgres: 16 | image: 'postgres:latest' 17 | ports: 18 | - '5432:5432' 19 | env: 20 | POSTGRES_DB: test 21 | POSTGRES_USER: postgres 22 | POSTGRES_PASSWORD: postgres 23 | steps: 24 | - name: Fetch Repository 25 | uses: actions/checkout@v3 26 | - name: Generate Results 27 | run: | 28 | git config user.name 'github-actions[bot]' 29 | git config user.email 'github-actions[bot]@users.noreply.github.com' 30 | 31 | .github/run_benchmarks_actions.sh > results.md 32 | git add results.md 33 | git commit -am "Generate benchmark results automatically." 34 | git push -------------------------------------------------------------------------------- /.github/workflows/security.yaml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: GoSec 3 | jobs: 4 | GoSec: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Fetch Repository 8 | uses: actions/checkout@v2 9 | - name: Run GoSec Security Scanner 10 | uses: securego/gosec@master -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows thumbnail cache files 2 | Thumbs.db 3 | Thumbs.db:encryptable 4 | ehthumbs.db 5 | ehthumbs_vista.db 6 | 7 | # Dump file 8 | *.stackdump 9 | 10 | # Folder config file 11 | [Dd]esktop.ini 12 | 13 | # Recycle Bin used on file shares 14 | $RECYCLE.BIN/ 15 | 16 | # Windows Installer files 17 | *.cab 18 | *.msi 19 | *.msix 20 | *.msm 21 | *.msp 22 | 23 | # Windows shortcuts 24 | *.lnk 25 | 26 | # General 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Icon must end with two \r 32 | Icon 33 | 34 | 35 | # Thumbnails 36 | ._* 37 | 38 | # Files that might appear in the root of a volume 39 | .DocumentRevisions-V100 40 | .fseventsd 41 | .Spotlight-V100 42 | .TemporaryItems 43 | .Trashes 44 | .VolumeIcon.icns 45 | .com.apple.timemachine.donotpresent 46 | 47 | # Directories potentially created on remote AFP share 48 | .AppleDB 49 | .AppleDesktop 50 | Network Trash Folder 51 | Temporary Items 52 | .apdisk 53 | 54 | # CMake 55 | cmake-build-*/ 56 | 57 | # Mongo Explorer plugin 58 | .idea/**/mongoSettings.xml 59 | 60 | # File-based project format 61 | *.iws 62 | 63 | # IntelliJ 64 | out/ 65 | 66 | # mpeltonen/sbt-idea plugin 67 | .idea_modules/ 68 | 69 | # JIRA plugin 70 | atlassian-ide-plugin.xml 71 | 72 | # Cursive Clojure plugin 73 | .idea/replstate.xml 74 | 75 | # Crashlytics plugin (for Android Studio and IntelliJ) 76 | com_crashlytics_export_strings.xml 77 | crashlytics.properties 78 | crashlytics-build.properties 79 | fabric.properties 80 | 81 | # Editor-based Rest Client 82 | .idea/httpRequests 83 | 84 | # Android studio 3.1+ serialized cache file 85 | .idea/caches/build_file_checksums.ser 86 | 87 | *.patch 88 | *.diff 89 | 90 | # Binaries for programs and plugins 91 | *.exe 92 | *.exe~ 93 | *.dll 94 | *.so 95 | *.dylib 96 | 97 | # Test binary, built with `go test -c` 98 | *.test 99 | 100 | # Output of the go coverage tool, specifically when used with LiteIDE 101 | *.out 102 | 103 | # Dependency directories (remove the comment below to include it) 104 | # vendor/ 105 | 106 | .idea -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Define specific args for benchmark 2 | FROM golang:alpine 3 | 4 | # Define env vars 5 | ARG ORM 6 | ENV ORM=${ORM:-all} 7 | 8 | # Build 9 | WORKDIR /app 10 | COPY . . 11 | RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w" -o /bin/go-orm-benchmarks ./main.go 12 | 13 | # Run 14 | CMD /bin/go-orm-benchmarks -source="host=postgres user=postgres password=postgres dbname=test sslmode=disable" -orm=$ORM -debug=false -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 M. Efe Çetin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go ORM Benchmarks 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/efectn/go-orm-benchmarks.svg)](https://pkg.go.dev/github.com/efectn/go-orm-benchmarks) 4 | 5 | Advanced benchmarks for +10 Go ORMs. Originally forked from [orm-benchmark](https://github.com/frederikhors/orm-benchmark). 6 | 7 | ### ORMs 8 | 9 | All package run in no-cache mode. 10 | 11 | - [beego/orm](https://github.com/astaxie/beego/tree/master/orm) 12 | - [bun](https://github.com/uptrace/bun) 13 | - [gorm 2](https://github.com/go-gorm/gorm) 14 | - [pg](https://github.com/go-pg/pg) 15 | - [sqlc](https://github.com/kyleconroy/sqlc) 16 | - [xorm](https://github.com/xormplus/xorm) 17 | - [ent](https://github.com/ent/ent) 18 | - [upper](https://github.com/upper/db) 19 | - [gorp](https://github.com/go-gorp/gorp) 20 | - [godb](https://github.com/samonzeweb/godb) 21 | - [dbr](https://github.com/gocraft/dbr/) 22 | - [pop](https://github.com/gobuffalo/pop) 23 | - [rel](https://github.com/go-rel/rel) 24 | - [reform](https://github.com/go-reform/reform) 25 | - [sqlboiler](https://github.com/volatiletech/sqlboiler) 26 | - [sqlx](https://github.com/jmoiron/sqlx) 27 | - [pgx](https://github.com/jackc/pgx) 28 | - [zorm](https://gitee.com/chunanyong/zorm) 29 | - [gen](https://gorm.io/gen/index.html) 30 | - [jet](https://github.com/go-jet/jet) 31 | - [sq](https://github.com/bokwoon95/sq) 32 | 33 | See [`go.mod`](go.mod) for their latest versions. 34 | 35 | ### Run 36 | 37 | ```shell 38 | # install 39 | go install github.com/efectn/go-orm-benchmarks@latest 40 | # all 41 | go-orm-benchmarks -orm=all 42 | # portion 43 | go-orm-benchmarks -orm=gorm 44 | go-orm-benchmarks -orm=pg 45 | go-orm-benchmarks -orm=bun 46 | # ... and so on... 47 | ``` 48 | 49 | **_Note: Also, you can run `./run_benchmarks.sh` and you can get output like results.md format._** 50 | 51 | ### Results 52 | Look at [`results.md`](results.md) to see detailed benchmark results. 53 | 54 | **Note:** All results are automatically generated by [Github Actions](https://github.com/features/actions). Benchmark results may sometimes be wrong. 55 | 56 | ### License 57 | 58 | go-orm-benchmarks is licensed under the terms of the **MIT License** (see [LICENSE](LICENSE)). 59 | -------------------------------------------------------------------------------- /bench/beego.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "fmt" 5 | "github.com/astaxie/beego/orm" 6 | "github.com/efectn/go-orm-benchmarks/helper" 7 | "testing" 8 | ) 9 | 10 | type Beego struct { 11 | helper.ORMInterface 12 | conn orm.Ormer 13 | } 14 | 15 | func CreateBeego() helper.ORMInterface { 16 | return &Beego{} 17 | } 18 | 19 | func (beego *Beego) Name() string { 20 | return "beego" 21 | } 22 | 23 | func (beego *Beego) Init() error { 24 | err := orm.RegisterDataBase("default", "postgres", helper.OrmSource, helper.OrmMaxIdle, helper.OrmMaxConn) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | orm.RegisterModel(new(Model)) 30 | beego.conn = orm.NewOrm() 31 | 32 | return nil 33 | } 34 | 35 | func (beego *Beego) Close() error { 36 | return nil 37 | } 38 | 39 | func (beego *Beego) Insert(b *testing.B) { 40 | m := NewModel() 41 | 42 | b.ReportAllocs() 43 | b.ResetTimer() 44 | 45 | for i := 0; i < b.N; i++ { 46 | m.Id = 0 47 | 48 | _, err := beego.conn.Insert(m) 49 | if err != nil { 50 | helper.SetError(b, beego.Name(), "Insert", fmt.Sprintf("beego: insert: %v", err)) 51 | } 52 | } 53 | } 54 | 55 | func (beego *Beego) InsertMulti(b *testing.B) { 56 | ms := make([]*Model, 0, 100) 57 | for i := 0; i < 100; i++ { 58 | ms = append(ms, NewModel()) 59 | } 60 | 61 | b.ReportAllocs() 62 | b.ResetTimer() 63 | 64 | for i := 0; i < b.N; i++ { 65 | _, err := beego.conn.InsertMulti(100, ms) 66 | if err != nil { 67 | helper.SetError(b, beego.Name(), "InsertMulti", err.Error()) 68 | } 69 | } 70 | } 71 | 72 | func (beego *Beego) Update(b *testing.B) { 73 | m := NewModel() 74 | 75 | _, err := beego.conn.Insert(m) 76 | if err != nil { 77 | helper.SetError(b, beego.Name(), "Update", err.Error()) 78 | } 79 | 80 | b.ReportAllocs() 81 | b.ResetTimer() 82 | 83 | for i := 0; i < b.N; i++ { 84 | _, err := beego.conn.Update(m) 85 | if err != nil { 86 | helper.SetError(b, beego.Name(), "Update", err.Error()) 87 | } 88 | } 89 | } 90 | 91 | func (beego *Beego) Read(b *testing.B) { 92 | m := NewModel() 93 | 94 | _, err := beego.conn.Insert(m) 95 | if err != nil { 96 | helper.SetError(b, beego.Name(), "Read", err.Error()) 97 | } 98 | 99 | b.ReportAllocs() 100 | b.ResetTimer() 101 | 102 | for i := 0; i < b.N; i++ { 103 | err := beego.conn.Read(m) 104 | if err != nil { 105 | helper.SetError(b, beego.Name(), "Read", err.Error()) 106 | } 107 | } 108 | } 109 | 110 | func (beego *Beego) ReadSlice(b *testing.B) { 111 | m := NewModel() 112 | for i := 0; i < 100; i++ { 113 | m.Id = 0 114 | _, err := beego.conn.Insert(m) 115 | if err != nil { 116 | helper.SetError(b, beego.Name(), "ReadSlice", err.Error()) 117 | } 118 | } 119 | 120 | b.ReportAllocs() 121 | b.ResetTimer() 122 | 123 | for i := 0; i < b.N; i++ { 124 | var models []*Model 125 | _, err := beego.conn.QueryTable("models").Filter("id__gt", 0).Limit(100).All(&models) 126 | if err != nil { 127 | helper.SetError(b, beego.Name(), "ReadSlice", err.Error()) 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /bench/bun.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "database/sql" 5 | "github.com/efectn/go-orm-benchmarks/helper" 6 | "testing" 7 | 8 | bundb "github.com/uptrace/bun" 9 | "github.com/uptrace/bun/dialect/pgdialect" 10 | "github.com/uptrace/bun/driver/pgdriver" 11 | ) 12 | 13 | type Bun struct { 14 | helper.ORMInterface 15 | conn *bundb.DB 16 | } 17 | 18 | func CreateBun() helper.ORMInterface { 19 | return &Bun{} 20 | } 21 | 22 | func (bun *Bun) Name() string { 23 | return "bun" 24 | } 25 | 26 | func (bun *Bun) Init() error { 27 | sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(helper.ConvertSourceToDSN()))) 28 | sqldb.SetMaxOpenConns(helper.OrmMaxConn) 29 | sqldb.SetMaxIdleConns(helper.OrmMaxIdle) 30 | 31 | bun.conn = bundb.NewDB(sqldb, pgdialect.New()) 32 | return nil 33 | } 34 | 35 | func (bun *Bun) Close() error { 36 | return bun.conn.Close() 37 | } 38 | 39 | func (bun *Bun) Insert(b *testing.B) { 40 | m := NewModel() 41 | 42 | b.ReportAllocs() 43 | b.ResetTimer() 44 | 45 | for i := 0; i < b.N; i++ { 46 | m.Id = 0 47 | 48 | _, err := bun.conn.NewInsert().Model(m).Exec(ctx) 49 | if err != nil { 50 | helper.SetError(b, bun.Name(), "Insert", err.Error()) 51 | } 52 | } 53 | } 54 | 55 | func (bun *Bun) InsertMulti(b *testing.B) { 56 | ms := make([]*Model, 0, 100) 57 | for i := 0; i < 100; i++ { 58 | ms = append(ms, NewModel()) 59 | } 60 | 61 | b.ReportAllocs() 62 | b.ResetTimer() 63 | 64 | for i := 0; i < b.N; i++ { 65 | for _, m := range ms { 66 | m.Id = 0 67 | } 68 | 69 | _, err := bun.conn.NewInsert().Model(&ms).Exec(ctx) 70 | if err != nil { 71 | helper.SetError(b, bun.Name(), "InsertMulti", err.Error()) 72 | } 73 | } 74 | } 75 | 76 | func (bun *Bun) Update(b *testing.B) { 77 | m := NewModel() 78 | 79 | _, err := bun.conn.NewInsert().Model(m).Exec(ctx) 80 | if err != nil { 81 | helper.SetError(b, bun.Name(), "Update", err.Error()) 82 | } 83 | 84 | b.ReportAllocs() 85 | b.ResetTimer() 86 | 87 | for i := 0; i < b.N; i++ { 88 | _, err := bun.conn.NewUpdate().Model(m).WherePK().Exec(ctx) 89 | if err != nil { 90 | helper.SetError(b, bun.Name(), "Update", err.Error()) 91 | } 92 | } 93 | } 94 | 95 | func (bun *Bun) Read(b *testing.B) { 96 | m := NewModel() 97 | 98 | _, err := bun.conn.NewInsert().Model(m).Exec(ctx) 99 | if err != nil { 100 | helper.SetError(b, bun.Name(), "Read", err.Error()) 101 | } 102 | 103 | b.ReportAllocs() 104 | b.ResetTimer() 105 | 106 | for i := 0; i < b.N; i++ { 107 | err := bun.conn.NewSelect().Model(m).Scan(ctx) 108 | if err != nil { 109 | helper.SetError(b, bun.Name(), "Read", err.Error()) 110 | } 111 | } 112 | } 113 | 114 | func (bun *Bun) ReadSlice(b *testing.B) { 115 | m := NewModel() 116 | for i := 0; i < 100; i++ { 117 | m.Id = 0 118 | _, err := bun.conn.NewInsert().Model(m).Exec(ctx) 119 | if err != nil { 120 | helper.SetError(b, bun.Name(), "ReadSlice", err.Error()) 121 | } 122 | } 123 | 124 | b.ReportAllocs() 125 | b.ResetTimer() 126 | 127 | for i := 0; i < b.N; i++ { 128 | var models []*Model 129 | err := bun.conn.NewSelect(). 130 | Model(&models). 131 | Where("id > ?", 0). 132 | Limit(100). 133 | Scan(ctx) 134 | if err != nil { 135 | helper.SetError(b, bun.Name(), "ReadSlice", err.Error()) 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /bench/dbr.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/efectn/go-orm-benchmarks/helper" 7 | 8 | dbrware "github.com/gocraft/dbr/v2" 9 | _ "github.com/jackc/pgx/v5/stdlib" 10 | ) 11 | 12 | var columns = []string{"name", "title", "fax", "web", "age", "right", "counter"} 13 | 14 | type Dbr struct { 15 | helper.ORMInterface 16 | conn *dbrware.Session 17 | } 18 | 19 | func CreateDbr() helper.ORMInterface { 20 | return &Dbr{} 21 | } 22 | 23 | func (dbr *Dbr) Name() string { 24 | return "dbr" 25 | } 26 | 27 | func (dbr *Dbr) Init() error { 28 | conn, err := dbrware.Open("postgres", helper.OrmSource, nil) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | dbr.conn = conn.NewSession(nil) 34 | _, err = dbr.conn.Begin() 35 | if err != nil { 36 | return err 37 | } 38 | 39 | return nil 40 | } 41 | 42 | func (dbr *Dbr) Close() error { 43 | return dbr.conn.Close() 44 | } 45 | 46 | func (dbr *Dbr) Insert(b *testing.B) { 47 | m := NewModel2() 48 | 49 | b.ReportAllocs() 50 | b.ResetTimer() 51 | 52 | for i := 0; i < b.N; i++ { 53 | _, err := dbr.conn.InsertInto("models").Columns(columns...).Record(m).Exec() 54 | if err != nil { 55 | helper.SetError(b, dbr.Name(), "Insert", err.Error()) 56 | } 57 | } 58 | } 59 | 60 | func (dbr *Dbr) InsertMulti(b *testing.B) { 61 | helper.SetError(b, dbr.Name(), "InsertMulti", "bulk-insert is not supported") 62 | } 63 | 64 | func (dbr *Dbr) Update(b *testing.B) { 65 | m := NewModel2() 66 | 67 | _, err := dbr.conn.InsertInto("models").Columns(columns...).Record(m).Exec() 68 | if err != nil { 69 | helper.SetError(b, dbr.Name(), "Update", err.Error()) 70 | } 71 | 72 | b.ReportAllocs() 73 | b.ResetTimer() 74 | 75 | for i := 0; i < b.N; i++ { 76 | _, err := dbr.conn.Update("models").SetMap(map[string]any{ 77 | "name": m.Name, 78 | "title": m.Title, 79 | "fax": m.Fax, 80 | "web": m.Web, 81 | "age": m.Age, 82 | "right": m.Right, 83 | "counter": m.Counter, 84 | }).Exec() 85 | if err != nil { 86 | helper.SetError(b, dbr.Name(), "Update", err.Error()) 87 | } 88 | } 89 | } 90 | 91 | func (dbr *Dbr) Read(b *testing.B) { 92 | m := NewModel2() 93 | 94 | _, err := dbr.conn.InsertInto("models").Columns(columns...).Record(m).Exec() 95 | if err != nil { 96 | helper.SetError(b, dbr.Name(), "Read", err.Error()) 97 | } 98 | 99 | b.ReportAllocs() 100 | b.ResetTimer() 101 | 102 | for i := 0; i < b.N; i++ { 103 | _, err := dbr.conn.Select("*").From("models").Where("id = ?", m.ID).Load(m) 104 | if err != nil { 105 | helper.SetError(b, dbr.Name(), "Read", err.Error()) 106 | } 107 | } 108 | } 109 | 110 | func (dbr *Dbr) ReadSlice(b *testing.B) { 111 | m := NewModel2() 112 | for i := 0; i < 100; i++ { 113 | _, err := dbr.conn.InsertInto("models").Columns(columns...).Record(m).Exec() 114 | if err != nil { 115 | helper.SetError(b, dbr.Name(), "ReadSlice", err.Error()) 116 | } 117 | } 118 | 119 | b.ReportAllocs() 120 | b.ResetTimer() 121 | 122 | for i := 0; i < b.N; i++ { 123 | var ms []*Model2 124 | _, err := dbr.conn.Select("*").From("models").Where("id > 0").Limit(100).Load(&ms) 125 | if err != nil { 126 | helper.SetError(b, dbr.Name(), "ReadSlice", err.Error()) 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /bench/ent.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "database/sql" 5 | "testing" 6 | 7 | "github.com/efectn/go-orm-benchmarks/helper" 8 | 9 | "entgo.io/ent/dialect" 10 | entsql "entgo.io/ent/dialect/sql" 11 | entdb "github.com/efectn/go-orm-benchmarks/bench/ent" 12 | "github.com/efectn/go-orm-benchmarks/bench/ent/model" 13 | _ "github.com/jackc/pgx/v5/stdlib" 14 | ) 15 | 16 | type Ent struct { 17 | helper.ORMInterface 18 | conn *entdb.Client 19 | dbEnt *sql.DB 20 | } 21 | 22 | func CreateEnt() helper.ORMInterface { 23 | return &Ent{} 24 | } 25 | 26 | func (ent *Ent) Name() string { 27 | return "ent" 28 | } 29 | 30 | func (ent *Ent) Init() error { 31 | var err error 32 | ent.dbEnt, err = sql.Open("pgx", helper.OrmSource) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | // Create an ent.Driver from `dbEnt`. 38 | drv := entsql.OpenDB(dialect.Postgres, ent.dbEnt) 39 | 40 | // Assign to client 41 | ent.conn = entdb.NewClient(entdb.Driver(drv)) 42 | 43 | return nil 44 | } 45 | 46 | func (ent *Ent) Close() error { 47 | return ent.conn.Close() 48 | } 49 | 50 | func (ent *Ent) Insert(b *testing.B) { 51 | m := NewModelAlt() 52 | 53 | b.ReportAllocs() 54 | b.ResetTimer() 55 | 56 | for i := 0; i < b.N; i++ { 57 | _, err := ent.conn.Model.Create(). 58 | SetName(m.Name). 59 | SetTitle(m.Title). 60 | SetFax(m.Fax). 61 | SetWeb(m.Web). 62 | SetAge(m.Age). 63 | SetRight(m.Right). 64 | SetCounter(m.Counter). 65 | Save(ctx) 66 | if err != nil { 67 | helper.SetError(b, ent.Name(), "Insert", err.Error()) 68 | } 69 | } 70 | } 71 | 72 | func (ent *Ent) InsertMulti(b *testing.B) { 73 | ms := make([]Model, 0, 100) 74 | for i := 0; i < 100; i++ { 75 | ms = append(ms, NewModelAlt()) 76 | } 77 | 78 | b.ReportAllocs() 79 | b.ResetTimer() 80 | 81 | bulk := make([]*entdb.ModelCreate, len(ms)) 82 | for i, m := range ms { 83 | bulk[i] = ent.conn.Model.Create(). 84 | SetName(m.Name). 85 | SetTitle(m.Title). 86 | SetFax(m.Fax). 87 | SetWeb(m.Web). 88 | SetAge(m.Age). 89 | SetRight(m.Right). 90 | SetCounter(m.Counter) 91 | } 92 | 93 | for i := 0; i < b.N; i++ { 94 | _, err := ent.conn.Model.CreateBulk(bulk...).Save(ctx) 95 | if err != nil { 96 | helper.SetError(b, ent.Name(), "InsertMulti", err.Error()) 97 | } 98 | } 99 | } 100 | 101 | func (ent *Ent) Update(b *testing.B) { 102 | m := NewModelAlt() 103 | 104 | created, err := ent.conn.Model.Create(). 105 | SetName(m.Name). 106 | SetTitle(m.Title). 107 | SetFax(m.Fax). 108 | SetWeb(m.Web). 109 | SetAge(m.Age). 110 | SetRight(m.Right). 111 | SetCounter(m.Counter). 112 | Save(ctx) 113 | if err != nil { 114 | helper.SetError(b, ent.Name(), "Update", err.Error()) 115 | } 116 | m.Id = created.ID 117 | 118 | b.ReportAllocs() 119 | b.ResetTimer() 120 | 121 | for i := 0; i < b.N; i++ { 122 | _, err := ent.conn.Model.Update(). 123 | Where(model.IDEQ(m.Id)). 124 | SetName(m.Name). 125 | SetTitle(m.Title). 126 | SetFax(m.Fax). 127 | SetWeb(m.Web). 128 | SetAge(m.Age). 129 | SetRight(m.Right). 130 | SetCounter(m.Counter). 131 | Save(ctx) 132 | if err != nil { 133 | helper.SetError(b, ent.Name(), "Update", err.Error()) 134 | } 135 | } 136 | } 137 | 138 | func (ent *Ent) Read(b *testing.B) { 139 | m := NewModelAlt() 140 | 141 | created, err := ent.conn.Model.Create(). 142 | SetName(m.Name). 143 | SetTitle(m.Title). 144 | SetFax(m.Fax). 145 | SetWeb(m.Web). 146 | SetAge(m.Age). 147 | SetRight(m.Right). 148 | SetCounter(m.Counter). 149 | Save(ctx) 150 | if err != nil { 151 | helper.SetError(b, ent.Name(), "Read", err.Error()) 152 | } 153 | m.Id = created.ID 154 | 155 | b.ReportAllocs() 156 | b.ResetTimer() 157 | 158 | for i := 0; i < b.N; i++ { 159 | _, err := ent.conn.Model.Query().Where(model.IDEQ(m.Id)).First(ctx) 160 | if err != nil { 161 | helper.SetError(b, ent.Name(), "Read", err.Error()) 162 | } 163 | } 164 | } 165 | 166 | func (ent *Ent) ReadSlice(b *testing.B) { 167 | m := NewModelAlt() 168 | for i := 0; i < 100; i++ { 169 | _, err := ent.conn.Model.Create(). 170 | SetName(m.Name). 171 | SetTitle(m.Title). 172 | SetFax(m.Fax). 173 | SetWeb(m.Web). 174 | SetAge(m.Age). 175 | SetRight(m.Right). 176 | SetCounter(m.Counter). 177 | Save(ctx) 178 | if err != nil { 179 | helper.SetError(b, ent.Name(), "ReadSlice", err.Error()) 180 | } 181 | } 182 | 183 | b.ReportAllocs() 184 | b.ResetTimer() 185 | 186 | for i := 0; i < b.N; i++ { 187 | _, err := ent.conn.Model.Query().Where(model.IDGT(0)).Unique(false).Limit(100).All(ctx) 188 | if err != nil { 189 | helper.SetError(b, ent.Name(), "ReadSlice", err.Error()) 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /bench/ent/client.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "fmt" 9 | "log" 10 | 11 | "github.com/efectn/go-orm-benchmarks/bench/ent/migrate" 12 | 13 | "entgo.io/ent" 14 | "entgo.io/ent/dialect" 15 | "entgo.io/ent/dialect/sql" 16 | "github.com/efectn/go-orm-benchmarks/bench/ent/model" 17 | ) 18 | 19 | // Client is the client that holds all ent builders. 20 | type Client struct { 21 | config 22 | // Schema is the client for creating, migrating and dropping schema. 23 | Schema *migrate.Schema 24 | // Model is the client for interacting with the Model builders. 25 | Model *ModelClient 26 | } 27 | 28 | // NewClient creates a new client configured with the given options. 29 | func NewClient(opts ...Option) *Client { 30 | cfg := config{log: log.Println, hooks: &hooks{}, inters: &inters{}} 31 | cfg.options(opts...) 32 | client := &Client{config: cfg} 33 | client.init() 34 | return client 35 | } 36 | 37 | func (c *Client) init() { 38 | c.Schema = migrate.NewSchema(c.driver) 39 | c.Model = NewModelClient(c.config) 40 | } 41 | 42 | type ( 43 | // config is the configuration for the client and its builder. 44 | config struct { 45 | // driver used for executing database requests. 46 | driver dialect.Driver 47 | // debug enable a debug logging. 48 | debug bool 49 | // log used for logging on debug mode. 50 | log func(...any) 51 | // hooks to execute on mutations. 52 | hooks *hooks 53 | // interceptors to execute on queries. 54 | inters *inters 55 | } 56 | // Option function to configure the client. 57 | Option func(*config) 58 | ) 59 | 60 | // options applies the options on the config object. 61 | func (c *config) options(opts ...Option) { 62 | for _, opt := range opts { 63 | opt(c) 64 | } 65 | if c.debug { 66 | c.driver = dialect.Debug(c.driver, c.log) 67 | } 68 | } 69 | 70 | // Debug enables debug logging on the ent.Driver. 71 | func Debug() Option { 72 | return func(c *config) { 73 | c.debug = true 74 | } 75 | } 76 | 77 | // Log sets the logging function for debug mode. 78 | func Log(fn func(...any)) Option { 79 | return func(c *config) { 80 | c.log = fn 81 | } 82 | } 83 | 84 | // Driver configures the client driver. 85 | func Driver(driver dialect.Driver) Option { 86 | return func(c *config) { 87 | c.driver = driver 88 | } 89 | } 90 | 91 | // Open opens a database/sql.DB specified by the driver name and 92 | // the data source name, and returns a new client attached to it. 93 | // Optional parameters can be added for configuring the client. 94 | func Open(driverName, dataSourceName string, options ...Option) (*Client, error) { 95 | switch driverName { 96 | case dialect.MySQL, dialect.Postgres, dialect.SQLite: 97 | drv, err := sql.Open(driverName, dataSourceName) 98 | if err != nil { 99 | return nil, err 100 | } 101 | return NewClient(append(options, Driver(drv))...), nil 102 | default: 103 | return nil, fmt.Errorf("unsupported driver: %q", driverName) 104 | } 105 | } 106 | 107 | // Tx returns a new transactional client. The provided context 108 | // is used until the transaction is committed or rolled back. 109 | func (c *Client) Tx(ctx context.Context) (*Tx, error) { 110 | if _, ok := c.driver.(*txDriver); ok { 111 | return nil, errors.New("ent: cannot start a transaction within a transaction") 112 | } 113 | tx, err := newTx(ctx, c.driver) 114 | if err != nil { 115 | return nil, fmt.Errorf("ent: starting a transaction: %w", err) 116 | } 117 | cfg := c.config 118 | cfg.driver = tx 119 | return &Tx{ 120 | ctx: ctx, 121 | config: cfg, 122 | Model: NewModelClient(cfg), 123 | }, nil 124 | } 125 | 126 | // BeginTx returns a transactional client with specified options. 127 | func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { 128 | if _, ok := c.driver.(*txDriver); ok { 129 | return nil, errors.New("ent: cannot start a transaction within a transaction") 130 | } 131 | tx, err := c.driver.(interface { 132 | BeginTx(context.Context, *sql.TxOptions) (dialect.Tx, error) 133 | }).BeginTx(ctx, opts) 134 | if err != nil { 135 | return nil, fmt.Errorf("ent: starting a transaction: %w", err) 136 | } 137 | cfg := c.config 138 | cfg.driver = &txDriver{tx: tx, drv: c.driver} 139 | return &Tx{ 140 | ctx: ctx, 141 | config: cfg, 142 | Model: NewModelClient(cfg), 143 | }, nil 144 | } 145 | 146 | // Debug returns a new debug-client. It's used to get verbose logging on specific operations. 147 | // 148 | // client.Debug(). 149 | // Model. 150 | // Query(). 151 | // Count(ctx) 152 | func (c *Client) Debug() *Client { 153 | if c.debug { 154 | return c 155 | } 156 | cfg := c.config 157 | cfg.driver = dialect.Debug(c.driver, c.log) 158 | client := &Client{config: cfg} 159 | client.init() 160 | return client 161 | } 162 | 163 | // Close closes the database connection and prevents new queries from starting. 164 | func (c *Client) Close() error { 165 | return c.driver.Close() 166 | } 167 | 168 | // Use adds the mutation hooks to all the entity clients. 169 | // In order to add hooks to a specific client, call: `client.Node.Use(...)`. 170 | func (c *Client) Use(hooks ...Hook) { 171 | c.Model.Use(hooks...) 172 | } 173 | 174 | // Intercept adds the query interceptors to all the entity clients. 175 | // In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`. 176 | func (c *Client) Intercept(interceptors ...Interceptor) { 177 | c.Model.Intercept(interceptors...) 178 | } 179 | 180 | // Mutate implements the ent.Mutator interface. 181 | func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { 182 | switch m := m.(type) { 183 | case *ModelMutation: 184 | return c.Model.mutate(ctx, m) 185 | default: 186 | return nil, fmt.Errorf("ent: unknown mutation type %T", m) 187 | } 188 | } 189 | 190 | // ModelClient is a client for the Model schema. 191 | type ModelClient struct { 192 | config 193 | } 194 | 195 | // NewModelClient returns a client for the Model from the given config. 196 | func NewModelClient(c config) *ModelClient { 197 | return &ModelClient{config: c} 198 | } 199 | 200 | // Use adds a list of mutation hooks to the hooks stack. 201 | // A call to `Use(f, g, h)` equals to `model.Hooks(f(g(h())))`. 202 | func (c *ModelClient) Use(hooks ...Hook) { 203 | c.hooks.Model = append(c.hooks.Model, hooks...) 204 | } 205 | 206 | // Intercept adds a list of query interceptors to the interceptors stack. 207 | // A call to `Intercept(f, g, h)` equals to `model.Intercept(f(g(h())))`. 208 | func (c *ModelClient) Intercept(interceptors ...Interceptor) { 209 | c.inters.Model = append(c.inters.Model, interceptors...) 210 | } 211 | 212 | // Create returns a builder for creating a Model entity. 213 | func (c *ModelClient) Create() *ModelCreate { 214 | mutation := newModelMutation(c.config, OpCreate) 215 | return &ModelCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} 216 | } 217 | 218 | // CreateBulk returns a builder for creating a bulk of Model entities. 219 | func (c *ModelClient) CreateBulk(builders ...*ModelCreate) *ModelCreateBulk { 220 | return &ModelCreateBulk{config: c.config, builders: builders} 221 | } 222 | 223 | // Update returns an update builder for Model. 224 | func (c *ModelClient) Update() *ModelUpdate { 225 | mutation := newModelMutation(c.config, OpUpdate) 226 | return &ModelUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} 227 | } 228 | 229 | // UpdateOne returns an update builder for the given entity. 230 | func (c *ModelClient) UpdateOne(m *Model) *ModelUpdateOne { 231 | mutation := newModelMutation(c.config, OpUpdateOne, withModel(m)) 232 | return &ModelUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} 233 | } 234 | 235 | // UpdateOneID returns an update builder for the given id. 236 | func (c *ModelClient) UpdateOneID(id int) *ModelUpdateOne { 237 | mutation := newModelMutation(c.config, OpUpdateOne, withModelID(id)) 238 | return &ModelUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} 239 | } 240 | 241 | // Delete returns a delete builder for Model. 242 | func (c *ModelClient) Delete() *ModelDelete { 243 | mutation := newModelMutation(c.config, OpDelete) 244 | return &ModelDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} 245 | } 246 | 247 | // DeleteOne returns a builder for deleting the given entity. 248 | func (c *ModelClient) DeleteOne(m *Model) *ModelDeleteOne { 249 | return c.DeleteOneID(m.ID) 250 | } 251 | 252 | // DeleteOneID returns a builder for deleting the given entity by its id. 253 | func (c *ModelClient) DeleteOneID(id int) *ModelDeleteOne { 254 | builder := c.Delete().Where(model.ID(id)) 255 | builder.mutation.id = &id 256 | builder.mutation.op = OpDeleteOne 257 | return &ModelDeleteOne{builder} 258 | } 259 | 260 | // Query returns a query builder for Model. 261 | func (c *ModelClient) Query() *ModelQuery { 262 | return &ModelQuery{ 263 | config: c.config, 264 | ctx: &QueryContext{Type: TypeModel}, 265 | inters: c.Interceptors(), 266 | } 267 | } 268 | 269 | // Get returns a Model entity by its id. 270 | func (c *ModelClient) Get(ctx context.Context, id int) (*Model, error) { 271 | return c.Query().Where(model.ID(id)).Only(ctx) 272 | } 273 | 274 | // GetX is like Get, but panics if an error occurs. 275 | func (c *ModelClient) GetX(ctx context.Context, id int) *Model { 276 | obj, err := c.Get(ctx, id) 277 | if err != nil { 278 | panic(err) 279 | } 280 | return obj 281 | } 282 | 283 | // Hooks returns the client hooks. 284 | func (c *ModelClient) Hooks() []Hook { 285 | return c.hooks.Model 286 | } 287 | 288 | // Interceptors returns the client interceptors. 289 | func (c *ModelClient) Interceptors() []Interceptor { 290 | return c.inters.Model 291 | } 292 | 293 | func (c *ModelClient) mutate(ctx context.Context, m *ModelMutation) (Value, error) { 294 | switch m.Op() { 295 | case OpCreate: 296 | return (&ModelCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) 297 | case OpUpdate: 298 | return (&ModelUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) 299 | case OpUpdateOne: 300 | return (&ModelUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) 301 | case OpDelete, OpDeleteOne: 302 | return (&ModelDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) 303 | default: 304 | return nil, fmt.Errorf("ent: unknown Model mutation op: %q", m.Op()) 305 | } 306 | } 307 | 308 | // hooks and interceptors per client, for fast access. 309 | type ( 310 | hooks struct { 311 | Model []ent.Hook 312 | } 313 | inters struct { 314 | Model []ent.Interceptor 315 | } 316 | ) 317 | -------------------------------------------------------------------------------- /bench/ent/enttest/enttest.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package enttest 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/efectn/go-orm-benchmarks/bench/ent" 9 | // required by schema hooks. 10 | _ "github.com/efectn/go-orm-benchmarks/bench/ent/runtime" 11 | 12 | "entgo.io/ent/dialect/sql/schema" 13 | "github.com/efectn/go-orm-benchmarks/bench/ent/migrate" 14 | ) 15 | 16 | type ( 17 | // TestingT is the interface that is shared between 18 | // testing.T and testing.B and used by enttest. 19 | TestingT interface { 20 | FailNow() 21 | Error(...any) 22 | } 23 | 24 | // Option configures client creation. 25 | Option func(*options) 26 | 27 | options struct { 28 | opts []ent.Option 29 | migrateOpts []schema.MigrateOption 30 | } 31 | ) 32 | 33 | // WithOptions forwards options to client creation. 34 | func WithOptions(opts ...ent.Option) Option { 35 | return func(o *options) { 36 | o.opts = append(o.opts, opts...) 37 | } 38 | } 39 | 40 | // WithMigrateOptions forwards options to auto migration. 41 | func WithMigrateOptions(opts ...schema.MigrateOption) Option { 42 | return func(o *options) { 43 | o.migrateOpts = append(o.migrateOpts, opts...) 44 | } 45 | } 46 | 47 | func newOptions(opts []Option) *options { 48 | o := &options{} 49 | for _, opt := range opts { 50 | opt(o) 51 | } 52 | return o 53 | } 54 | 55 | // Open calls ent.Open and auto-run migration. 56 | func Open(t TestingT, driverName, dataSourceName string, opts ...Option) *ent.Client { 57 | o := newOptions(opts) 58 | c, err := ent.Open(driverName, dataSourceName, o.opts...) 59 | if err != nil { 60 | t.Error(err) 61 | t.FailNow() 62 | } 63 | migrateSchema(t, c, o) 64 | return c 65 | } 66 | 67 | // NewClient calls ent.NewClient and auto-run migration. 68 | func NewClient(t TestingT, opts ...Option) *ent.Client { 69 | o := newOptions(opts) 70 | c := ent.NewClient(o.opts...) 71 | migrateSchema(t, c, o) 72 | return c 73 | } 74 | func migrateSchema(t TestingT, c *ent.Client, o *options) { 75 | tables, err := schema.CopyTables(migrate.Tables) 76 | if err != nil { 77 | t.Error(err) 78 | t.FailNow() 79 | } 80 | if err := migrate.Create(context.Background(), c.Schema, tables, o.migrateOpts...); err != nil { 81 | t.Error(err) 82 | t.FailNow() 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /bench/ent/generate.go: -------------------------------------------------------------------------------- 1 | package ent 2 | 3 | //go:generate go run -mod=mod entgo.io/ent/cmd/ent generate ./schema 4 | -------------------------------------------------------------------------------- /bench/ent/hook/hook.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package hook 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | 9 | "github.com/efectn/go-orm-benchmarks/bench/ent" 10 | ) 11 | 12 | // The ModelFunc type is an adapter to allow the use of ordinary 13 | // function as Model mutator. 14 | type ModelFunc func(context.Context, *ent.ModelMutation) (ent.Value, error) 15 | 16 | // Mutate calls f(ctx, m). 17 | func (f ModelFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { 18 | if mv, ok := m.(*ent.ModelMutation); ok { 19 | return f(ctx, mv) 20 | } 21 | return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.ModelMutation", m) 22 | } 23 | 24 | // Condition is a hook condition function. 25 | type Condition func(context.Context, ent.Mutation) bool 26 | 27 | // And groups conditions with the AND operator. 28 | func And(first, second Condition, rest ...Condition) Condition { 29 | return func(ctx context.Context, m ent.Mutation) bool { 30 | if !first(ctx, m) || !second(ctx, m) { 31 | return false 32 | } 33 | for _, cond := range rest { 34 | if !cond(ctx, m) { 35 | return false 36 | } 37 | } 38 | return true 39 | } 40 | } 41 | 42 | // Or groups conditions with the OR operator. 43 | func Or(first, second Condition, rest ...Condition) Condition { 44 | return func(ctx context.Context, m ent.Mutation) bool { 45 | if first(ctx, m) || second(ctx, m) { 46 | return true 47 | } 48 | for _, cond := range rest { 49 | if cond(ctx, m) { 50 | return true 51 | } 52 | } 53 | return false 54 | } 55 | } 56 | 57 | // Not negates a given condition. 58 | func Not(cond Condition) Condition { 59 | return func(ctx context.Context, m ent.Mutation) bool { 60 | return !cond(ctx, m) 61 | } 62 | } 63 | 64 | // HasOp is a condition testing mutation operation. 65 | func HasOp(op ent.Op) Condition { 66 | return func(_ context.Context, m ent.Mutation) bool { 67 | return m.Op().Is(op) 68 | } 69 | } 70 | 71 | // HasAddedFields is a condition validating `.AddedField` on fields. 72 | func HasAddedFields(field string, fields ...string) Condition { 73 | return func(_ context.Context, m ent.Mutation) bool { 74 | if _, exists := m.AddedField(field); !exists { 75 | return false 76 | } 77 | for _, field := range fields { 78 | if _, exists := m.AddedField(field); !exists { 79 | return false 80 | } 81 | } 82 | return true 83 | } 84 | } 85 | 86 | // HasClearedFields is a condition validating `.FieldCleared` on fields. 87 | func HasClearedFields(field string, fields ...string) Condition { 88 | return func(_ context.Context, m ent.Mutation) bool { 89 | if exists := m.FieldCleared(field); !exists { 90 | return false 91 | } 92 | for _, field := range fields { 93 | if exists := m.FieldCleared(field); !exists { 94 | return false 95 | } 96 | } 97 | return true 98 | } 99 | } 100 | 101 | // HasFields is a condition validating `.Field` on fields. 102 | func HasFields(field string, fields ...string) Condition { 103 | return func(_ context.Context, m ent.Mutation) bool { 104 | if _, exists := m.Field(field); !exists { 105 | return false 106 | } 107 | for _, field := range fields { 108 | if _, exists := m.Field(field); !exists { 109 | return false 110 | } 111 | } 112 | return true 113 | } 114 | } 115 | 116 | // If executes the given hook under condition. 117 | // 118 | // hook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...))) 119 | func If(hk ent.Hook, cond Condition) ent.Hook { 120 | return func(next ent.Mutator) ent.Mutator { 121 | return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) { 122 | if cond(ctx, m) { 123 | return hk(next).Mutate(ctx, m) 124 | } 125 | return next.Mutate(ctx, m) 126 | }) 127 | } 128 | } 129 | 130 | // On executes the given hook only for the given operation. 131 | // 132 | // hook.On(Log, ent.Delete|ent.Create) 133 | func On(hk ent.Hook, op ent.Op) ent.Hook { 134 | return If(hk, HasOp(op)) 135 | } 136 | 137 | // Unless skips the given hook only for the given operation. 138 | // 139 | // hook.Unless(Log, ent.Update|ent.UpdateOne) 140 | func Unless(hk ent.Hook, op ent.Op) ent.Hook { 141 | return If(hk, Not(HasOp(op))) 142 | } 143 | 144 | // FixedError is a hook returning a fixed error. 145 | func FixedError(err error) ent.Hook { 146 | return func(ent.Mutator) ent.Mutator { 147 | return ent.MutateFunc(func(context.Context, ent.Mutation) (ent.Value, error) { 148 | return nil, err 149 | }) 150 | } 151 | } 152 | 153 | // Reject returns a hook that rejects all operations that match op. 154 | // 155 | // func (T) Hooks() []ent.Hook { 156 | // return []ent.Hook{ 157 | // Reject(ent.Delete|ent.Update), 158 | // } 159 | // } 160 | func Reject(op ent.Op) ent.Hook { 161 | hk := FixedError(fmt.Errorf("%s operation is not allowed", op)) 162 | return On(hk, op) 163 | } 164 | 165 | // Chain acts as a list of hooks and is effectively immutable. 166 | // Once created, it will always hold the same set of hooks in the same order. 167 | type Chain struct { 168 | hooks []ent.Hook 169 | } 170 | 171 | // NewChain creates a new chain of hooks. 172 | func NewChain(hooks ...ent.Hook) Chain { 173 | return Chain{append([]ent.Hook(nil), hooks...)} 174 | } 175 | 176 | // Hook chains the list of hooks and returns the final hook. 177 | func (c Chain) Hook() ent.Hook { 178 | return func(mutator ent.Mutator) ent.Mutator { 179 | for i := len(c.hooks) - 1; i >= 0; i-- { 180 | mutator = c.hooks[i](mutator) 181 | } 182 | return mutator 183 | } 184 | } 185 | 186 | // Append extends a chain, adding the specified hook 187 | // as the last ones in the mutation flow. 188 | func (c Chain) Append(hooks ...ent.Hook) Chain { 189 | newHooks := make([]ent.Hook, 0, len(c.hooks)+len(hooks)) 190 | newHooks = append(newHooks, c.hooks...) 191 | newHooks = append(newHooks, hooks...) 192 | return Chain{newHooks} 193 | } 194 | 195 | // Extend extends a chain, adding the specified chain 196 | // as the last ones in the mutation flow. 197 | func (c Chain) Extend(chain Chain) Chain { 198 | return c.Append(chain.hooks...) 199 | } 200 | -------------------------------------------------------------------------------- /bench/ent/migrate/migrate.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package migrate 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "io" 9 | 10 | "entgo.io/ent/dialect" 11 | "entgo.io/ent/dialect/sql/schema" 12 | ) 13 | 14 | var ( 15 | // WithGlobalUniqueID sets the universal ids options to the migration. 16 | // If this option is enabled, ent migration will allocate a 1<<32 range 17 | // for the ids of each entity (table). 18 | // Note that this option cannot be applied on tables that already exist. 19 | WithGlobalUniqueID = schema.WithGlobalUniqueID 20 | // WithDropColumn sets the drop column option to the migration. 21 | // If this option is enabled, ent migration will drop old columns 22 | // that were used for both fields and edges. This defaults to false. 23 | WithDropColumn = schema.WithDropColumn 24 | // WithDropIndex sets the drop index option to the migration. 25 | // If this option is enabled, ent migration will drop old indexes 26 | // that were defined in the schema. This defaults to false. 27 | // Note that unique constraints are defined using `UNIQUE INDEX`, 28 | // and therefore, it's recommended to enable this option to get more 29 | // flexibility in the schema changes. 30 | WithDropIndex = schema.WithDropIndex 31 | // WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true. 32 | WithForeignKeys = schema.WithForeignKeys 33 | ) 34 | 35 | // Schema is the API for creating, migrating and dropping a schema. 36 | type Schema struct { 37 | drv dialect.Driver 38 | } 39 | 40 | // NewSchema creates a new schema client. 41 | func NewSchema(drv dialect.Driver) *Schema { return &Schema{drv: drv} } 42 | 43 | // Create creates all schema resources. 44 | func (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error { 45 | return Create(ctx, s, Tables, opts...) 46 | } 47 | 48 | // Create creates all table resources using the given schema driver. 49 | func Create(ctx context.Context, s *Schema, tables []*schema.Table, opts ...schema.MigrateOption) error { 50 | migrate, err := schema.NewMigrate(s.drv, opts...) 51 | if err != nil { 52 | return fmt.Errorf("ent/migrate: %w", err) 53 | } 54 | return migrate.Create(ctx, tables...) 55 | } 56 | 57 | // WriteTo writes the schema changes to w instead of running them against the database. 58 | // 59 | // if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil { 60 | // log.Fatal(err) 61 | // } 62 | func (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error { 63 | return Create(ctx, &Schema{drv: &schema.WriteDriver{Writer: w, Driver: s.drv}}, Tables, opts...) 64 | } 65 | -------------------------------------------------------------------------------- /bench/ent/migrate/schema.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package migrate 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql/schema" 7 | "entgo.io/ent/schema/field" 8 | ) 9 | 10 | var ( 11 | // ModelsColumns holds the columns for the "models" table. 12 | ModelsColumns = []*schema.Column{ 13 | {Name: "id", Type: field.TypeInt, Increment: true}, 14 | {Name: "name", Type: field.TypeString}, 15 | {Name: "title", Type: field.TypeString}, 16 | {Name: "fax", Type: field.TypeString}, 17 | {Name: "web", Type: field.TypeString}, 18 | {Name: "age", Type: field.TypeInt}, 19 | {Name: "right", Type: field.TypeBool}, 20 | {Name: "counter", Type: field.TypeInt64}, 21 | } 22 | // ModelsTable holds the schema information for the "models" table. 23 | ModelsTable = &schema.Table{ 24 | Name: "models", 25 | Columns: ModelsColumns, 26 | PrimaryKey: []*schema.Column{ModelsColumns[0]}, 27 | } 28 | // Tables holds all the tables in the schema. 29 | Tables = []*schema.Table{ 30 | ModelsTable, 31 | } 32 | ) 33 | 34 | func init() { 35 | } 36 | -------------------------------------------------------------------------------- /bench/ent/model.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | 9 | "entgo.io/ent" 10 | "entgo.io/ent/dialect/sql" 11 | "github.com/efectn/go-orm-benchmarks/bench/ent/model" 12 | ) 13 | 14 | // Model is the model entity for the Model schema. 15 | type Model struct { 16 | config `json:"-"` 17 | // ID of the ent. 18 | ID int `json:"id,omitempty"` 19 | // Name holds the value of the "name" field. 20 | Name string `json:"name,omitempty"` 21 | // Title holds the value of the "title" field. 22 | Title string `json:"title,omitempty"` 23 | // Fax holds the value of the "fax" field. 24 | Fax string `json:"fax,omitempty"` 25 | // Web holds the value of the "web" field. 26 | Web string `json:"web,omitempty"` 27 | // Age holds the value of the "age" field. 28 | Age int `json:"age,omitempty"` 29 | // Right holds the value of the "right" field. 30 | Right bool `json:"right,omitempty"` 31 | // Counter holds the value of the "counter" field. 32 | Counter int64 `json:"counter,omitempty"` 33 | selectValues sql.SelectValues 34 | } 35 | 36 | // scanValues returns the types for scanning values from sql.Rows. 37 | func (*Model) scanValues(columns []string) ([]any, error) { 38 | values := make([]any, len(columns)) 39 | for i := range columns { 40 | switch columns[i] { 41 | case model.FieldRight: 42 | values[i] = new(sql.NullBool) 43 | case model.FieldID, model.FieldAge, model.FieldCounter: 44 | values[i] = new(sql.NullInt64) 45 | case model.FieldName, model.FieldTitle, model.FieldFax, model.FieldWeb: 46 | values[i] = new(sql.NullString) 47 | default: 48 | values[i] = new(sql.UnknownType) 49 | } 50 | } 51 | return values, nil 52 | } 53 | 54 | // assignValues assigns the values that were returned from sql.Rows (after scanning) 55 | // to the Model fields. 56 | func (m *Model) assignValues(columns []string, values []any) error { 57 | if m, n := len(values), len(columns); m < n { 58 | return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) 59 | } 60 | for i := range columns { 61 | switch columns[i] { 62 | case model.FieldID: 63 | value, ok := values[i].(*sql.NullInt64) 64 | if !ok { 65 | return fmt.Errorf("unexpected type %T for field id", value) 66 | } 67 | m.ID = int(value.Int64) 68 | case model.FieldName: 69 | if value, ok := values[i].(*sql.NullString); !ok { 70 | return fmt.Errorf("unexpected type %T for field name", values[i]) 71 | } else if value.Valid { 72 | m.Name = value.String 73 | } 74 | case model.FieldTitle: 75 | if value, ok := values[i].(*sql.NullString); !ok { 76 | return fmt.Errorf("unexpected type %T for field title", values[i]) 77 | } else if value.Valid { 78 | m.Title = value.String 79 | } 80 | case model.FieldFax: 81 | if value, ok := values[i].(*sql.NullString); !ok { 82 | return fmt.Errorf("unexpected type %T for field fax", values[i]) 83 | } else if value.Valid { 84 | m.Fax = value.String 85 | } 86 | case model.FieldWeb: 87 | if value, ok := values[i].(*sql.NullString); !ok { 88 | return fmt.Errorf("unexpected type %T for field web", values[i]) 89 | } else if value.Valid { 90 | m.Web = value.String 91 | } 92 | case model.FieldAge: 93 | if value, ok := values[i].(*sql.NullInt64); !ok { 94 | return fmt.Errorf("unexpected type %T for field age", values[i]) 95 | } else if value.Valid { 96 | m.Age = int(value.Int64) 97 | } 98 | case model.FieldRight: 99 | if value, ok := values[i].(*sql.NullBool); !ok { 100 | return fmt.Errorf("unexpected type %T for field right", values[i]) 101 | } else if value.Valid { 102 | m.Right = value.Bool 103 | } 104 | case model.FieldCounter: 105 | if value, ok := values[i].(*sql.NullInt64); !ok { 106 | return fmt.Errorf("unexpected type %T for field counter", values[i]) 107 | } else if value.Valid { 108 | m.Counter = value.Int64 109 | } 110 | default: 111 | m.selectValues.Set(columns[i], values[i]) 112 | } 113 | } 114 | return nil 115 | } 116 | 117 | // Value returns the ent.Value that was dynamically selected and assigned to the Model. 118 | // This includes values selected through modifiers, order, etc. 119 | func (m *Model) Value(name string) (ent.Value, error) { 120 | return m.selectValues.Get(name) 121 | } 122 | 123 | // Update returns a builder for updating this Model. 124 | // Note that you need to call Model.Unwrap() before calling this method if this Model 125 | // was returned from a transaction, and the transaction was committed or rolled back. 126 | func (m *Model) Update() *ModelUpdateOne { 127 | return NewModelClient(m.config).UpdateOne(m) 128 | } 129 | 130 | // Unwrap unwraps the Model entity that was returned from a transaction after it was closed, 131 | // so that all future queries will be executed through the driver which created the transaction. 132 | func (m *Model) Unwrap() *Model { 133 | _tx, ok := m.config.driver.(*txDriver) 134 | if !ok { 135 | panic("ent: Model is not a transactional entity") 136 | } 137 | m.config.driver = _tx.drv 138 | return m 139 | } 140 | 141 | // String implements the fmt.Stringer. 142 | func (m *Model) String() string { 143 | var builder strings.Builder 144 | builder.WriteString("Model(") 145 | builder.WriteString(fmt.Sprintf("id=%v, ", m.ID)) 146 | builder.WriteString("name=") 147 | builder.WriteString(m.Name) 148 | builder.WriteString(", ") 149 | builder.WriteString("title=") 150 | builder.WriteString(m.Title) 151 | builder.WriteString(", ") 152 | builder.WriteString("fax=") 153 | builder.WriteString(m.Fax) 154 | builder.WriteString(", ") 155 | builder.WriteString("web=") 156 | builder.WriteString(m.Web) 157 | builder.WriteString(", ") 158 | builder.WriteString("age=") 159 | builder.WriteString(fmt.Sprintf("%v", m.Age)) 160 | builder.WriteString(", ") 161 | builder.WriteString("right=") 162 | builder.WriteString(fmt.Sprintf("%v", m.Right)) 163 | builder.WriteString(", ") 164 | builder.WriteString("counter=") 165 | builder.WriteString(fmt.Sprintf("%v", m.Counter)) 166 | builder.WriteByte(')') 167 | return builder.String() 168 | } 169 | 170 | // Models is a parsable slice of Model. 171 | type Models []*Model 172 | -------------------------------------------------------------------------------- /bench/ent/model/model.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package model 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql" 7 | ) 8 | 9 | const ( 10 | // Label holds the string label denoting the model type in the database. 11 | Label = "model" 12 | // FieldID holds the string denoting the id field in the database. 13 | FieldID = "id" 14 | // FieldName holds the string denoting the name field in the database. 15 | FieldName = "name" 16 | // FieldTitle holds the string denoting the title field in the database. 17 | FieldTitle = "title" 18 | // FieldFax holds the string denoting the fax field in the database. 19 | FieldFax = "fax" 20 | // FieldWeb holds the string denoting the web field in the database. 21 | FieldWeb = "web" 22 | // FieldAge holds the string denoting the age field in the database. 23 | FieldAge = "age" 24 | // FieldRight holds the string denoting the right field in the database. 25 | FieldRight = "right" 26 | // FieldCounter holds the string denoting the counter field in the database. 27 | FieldCounter = "counter" 28 | // Table holds the table name of the model in the database. 29 | Table = "models" 30 | ) 31 | 32 | // Columns holds all SQL columns for model fields. 33 | var Columns = []string{ 34 | FieldID, 35 | FieldName, 36 | FieldTitle, 37 | FieldFax, 38 | FieldWeb, 39 | FieldAge, 40 | FieldRight, 41 | FieldCounter, 42 | } 43 | 44 | // ValidColumn reports if the column name is valid (part of the table columns). 45 | func ValidColumn(column string) bool { 46 | for i := range Columns { 47 | if column == Columns[i] { 48 | return true 49 | } 50 | } 51 | return false 52 | } 53 | 54 | // OrderOption defines the ordering options for the Model queries. 55 | type OrderOption func(*sql.Selector) 56 | 57 | // ByID orders the results by the id field. 58 | func ByID(opts ...sql.OrderTermOption) OrderOption { 59 | return sql.OrderByField(FieldID, opts...).ToFunc() 60 | } 61 | 62 | // ByName orders the results by the name field. 63 | func ByName(opts ...sql.OrderTermOption) OrderOption { 64 | return sql.OrderByField(FieldName, opts...).ToFunc() 65 | } 66 | 67 | // ByTitle orders the results by the title field. 68 | func ByTitle(opts ...sql.OrderTermOption) OrderOption { 69 | return sql.OrderByField(FieldTitle, opts...).ToFunc() 70 | } 71 | 72 | // ByFax orders the results by the fax field. 73 | func ByFax(opts ...sql.OrderTermOption) OrderOption { 74 | return sql.OrderByField(FieldFax, opts...).ToFunc() 75 | } 76 | 77 | // ByWeb orders the results by the web field. 78 | func ByWeb(opts ...sql.OrderTermOption) OrderOption { 79 | return sql.OrderByField(FieldWeb, opts...).ToFunc() 80 | } 81 | 82 | // ByAge orders the results by the age field. 83 | func ByAge(opts ...sql.OrderTermOption) OrderOption { 84 | return sql.OrderByField(FieldAge, opts...).ToFunc() 85 | } 86 | 87 | // ByRight orders the results by the right field. 88 | func ByRight(opts ...sql.OrderTermOption) OrderOption { 89 | return sql.OrderByField(FieldRight, opts...).ToFunc() 90 | } 91 | 92 | // ByCounter orders the results by the counter field. 93 | func ByCounter(opts ...sql.OrderTermOption) OrderOption { 94 | return sql.OrderByField(FieldCounter, opts...).ToFunc() 95 | } 96 | -------------------------------------------------------------------------------- /bench/ent/model_create.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "fmt" 9 | 10 | "entgo.io/ent/dialect/sql/sqlgraph" 11 | "entgo.io/ent/schema/field" 12 | "github.com/efectn/go-orm-benchmarks/bench/ent/model" 13 | ) 14 | 15 | // ModelCreate is the builder for creating a Model entity. 16 | type ModelCreate struct { 17 | config 18 | mutation *ModelMutation 19 | hooks []Hook 20 | } 21 | 22 | // SetName sets the "name" field. 23 | func (mc *ModelCreate) SetName(s string) *ModelCreate { 24 | mc.mutation.SetName(s) 25 | return mc 26 | } 27 | 28 | // SetTitle sets the "title" field. 29 | func (mc *ModelCreate) SetTitle(s string) *ModelCreate { 30 | mc.mutation.SetTitle(s) 31 | return mc 32 | } 33 | 34 | // SetFax sets the "fax" field. 35 | func (mc *ModelCreate) SetFax(s string) *ModelCreate { 36 | mc.mutation.SetFax(s) 37 | return mc 38 | } 39 | 40 | // SetWeb sets the "web" field. 41 | func (mc *ModelCreate) SetWeb(s string) *ModelCreate { 42 | mc.mutation.SetWeb(s) 43 | return mc 44 | } 45 | 46 | // SetAge sets the "age" field. 47 | func (mc *ModelCreate) SetAge(i int) *ModelCreate { 48 | mc.mutation.SetAge(i) 49 | return mc 50 | } 51 | 52 | // SetRight sets the "right" field. 53 | func (mc *ModelCreate) SetRight(b bool) *ModelCreate { 54 | mc.mutation.SetRight(b) 55 | return mc 56 | } 57 | 58 | // SetCounter sets the "counter" field. 59 | func (mc *ModelCreate) SetCounter(i int64) *ModelCreate { 60 | mc.mutation.SetCounter(i) 61 | return mc 62 | } 63 | 64 | // Mutation returns the ModelMutation object of the builder. 65 | func (mc *ModelCreate) Mutation() *ModelMutation { 66 | return mc.mutation 67 | } 68 | 69 | // Save creates the Model in the database. 70 | func (mc *ModelCreate) Save(ctx context.Context) (*Model, error) { 71 | return withHooks(ctx, mc.sqlSave, mc.mutation, mc.hooks) 72 | } 73 | 74 | // SaveX calls Save and panics if Save returns an error. 75 | func (mc *ModelCreate) SaveX(ctx context.Context) *Model { 76 | v, err := mc.Save(ctx) 77 | if err != nil { 78 | panic(err) 79 | } 80 | return v 81 | } 82 | 83 | // Exec executes the query. 84 | func (mc *ModelCreate) Exec(ctx context.Context) error { 85 | _, err := mc.Save(ctx) 86 | return err 87 | } 88 | 89 | // ExecX is like Exec, but panics if an error occurs. 90 | func (mc *ModelCreate) ExecX(ctx context.Context) { 91 | if err := mc.Exec(ctx); err != nil { 92 | panic(err) 93 | } 94 | } 95 | 96 | // check runs all checks and user-defined validators on the builder. 97 | func (mc *ModelCreate) check() error { 98 | if _, ok := mc.mutation.Name(); !ok { 99 | return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "Model.name"`)} 100 | } 101 | if _, ok := mc.mutation.Title(); !ok { 102 | return &ValidationError{Name: "title", err: errors.New(`ent: missing required field "Model.title"`)} 103 | } 104 | if _, ok := mc.mutation.Fax(); !ok { 105 | return &ValidationError{Name: "fax", err: errors.New(`ent: missing required field "Model.fax"`)} 106 | } 107 | if _, ok := mc.mutation.Web(); !ok { 108 | return &ValidationError{Name: "web", err: errors.New(`ent: missing required field "Model.web"`)} 109 | } 110 | if _, ok := mc.mutation.Age(); !ok { 111 | return &ValidationError{Name: "age", err: errors.New(`ent: missing required field "Model.age"`)} 112 | } 113 | if _, ok := mc.mutation.Right(); !ok { 114 | return &ValidationError{Name: "right", err: errors.New(`ent: missing required field "Model.right"`)} 115 | } 116 | if _, ok := mc.mutation.Counter(); !ok { 117 | return &ValidationError{Name: "counter", err: errors.New(`ent: missing required field "Model.counter"`)} 118 | } 119 | return nil 120 | } 121 | 122 | func (mc *ModelCreate) sqlSave(ctx context.Context) (*Model, error) { 123 | if err := mc.check(); err != nil { 124 | return nil, err 125 | } 126 | _node, _spec := mc.createSpec() 127 | if err := sqlgraph.CreateNode(ctx, mc.driver, _spec); err != nil { 128 | if sqlgraph.IsConstraintError(err) { 129 | err = &ConstraintError{msg: err.Error(), wrap: err} 130 | } 131 | return nil, err 132 | } 133 | id := _spec.ID.Value.(int64) 134 | _node.ID = int(id) 135 | mc.mutation.id = &_node.ID 136 | mc.mutation.done = true 137 | return _node, nil 138 | } 139 | 140 | func (mc *ModelCreate) createSpec() (*Model, *sqlgraph.CreateSpec) { 141 | var ( 142 | _node = &Model{config: mc.config} 143 | _spec = sqlgraph.NewCreateSpec(model.Table, sqlgraph.NewFieldSpec(model.FieldID, field.TypeInt)) 144 | ) 145 | if value, ok := mc.mutation.Name(); ok { 146 | _spec.SetField(model.FieldName, field.TypeString, value) 147 | _node.Name = value 148 | } 149 | if value, ok := mc.mutation.Title(); ok { 150 | _spec.SetField(model.FieldTitle, field.TypeString, value) 151 | _node.Title = value 152 | } 153 | if value, ok := mc.mutation.Fax(); ok { 154 | _spec.SetField(model.FieldFax, field.TypeString, value) 155 | _node.Fax = value 156 | } 157 | if value, ok := mc.mutation.Web(); ok { 158 | _spec.SetField(model.FieldWeb, field.TypeString, value) 159 | _node.Web = value 160 | } 161 | if value, ok := mc.mutation.Age(); ok { 162 | _spec.SetField(model.FieldAge, field.TypeInt, value) 163 | _node.Age = value 164 | } 165 | if value, ok := mc.mutation.Right(); ok { 166 | _spec.SetField(model.FieldRight, field.TypeBool, value) 167 | _node.Right = value 168 | } 169 | if value, ok := mc.mutation.Counter(); ok { 170 | _spec.SetField(model.FieldCounter, field.TypeInt64, value) 171 | _node.Counter = value 172 | } 173 | return _node, _spec 174 | } 175 | 176 | // ModelCreateBulk is the builder for creating many Model entities in bulk. 177 | type ModelCreateBulk struct { 178 | config 179 | builders []*ModelCreate 180 | } 181 | 182 | // Save creates the Model entities in the database. 183 | func (mcb *ModelCreateBulk) Save(ctx context.Context) ([]*Model, error) { 184 | specs := make([]*sqlgraph.CreateSpec, len(mcb.builders)) 185 | nodes := make([]*Model, len(mcb.builders)) 186 | mutators := make([]Mutator, len(mcb.builders)) 187 | for i := range mcb.builders { 188 | func(i int, root context.Context) { 189 | builder := mcb.builders[i] 190 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 191 | mutation, ok := m.(*ModelMutation) 192 | if !ok { 193 | return nil, fmt.Errorf("unexpected mutation type %T", m) 194 | } 195 | if err := builder.check(); err != nil { 196 | return nil, err 197 | } 198 | builder.mutation = mutation 199 | var err error 200 | nodes[i], specs[i] = builder.createSpec() 201 | if i < len(mutators)-1 { 202 | _, err = mutators[i+1].Mutate(root, mcb.builders[i+1].mutation) 203 | } else { 204 | spec := &sqlgraph.BatchCreateSpec{Nodes: specs} 205 | // Invoke the actual operation on the latest mutation in the chain. 206 | if err = sqlgraph.BatchCreate(ctx, mcb.driver, spec); err != nil { 207 | if sqlgraph.IsConstraintError(err) { 208 | err = &ConstraintError{msg: err.Error(), wrap: err} 209 | } 210 | } 211 | } 212 | if err != nil { 213 | return nil, err 214 | } 215 | mutation.id = &nodes[i].ID 216 | if specs[i].ID.Value != nil { 217 | id := specs[i].ID.Value.(int64) 218 | nodes[i].ID = int(id) 219 | } 220 | mutation.done = true 221 | return nodes[i], nil 222 | }) 223 | for i := len(builder.hooks) - 1; i >= 0; i-- { 224 | mut = builder.hooks[i](mut) 225 | } 226 | mutators[i] = mut 227 | }(i, ctx) 228 | } 229 | if len(mutators) > 0 { 230 | if _, err := mutators[0].Mutate(ctx, mcb.builders[0].mutation); err != nil { 231 | return nil, err 232 | } 233 | } 234 | return nodes, nil 235 | } 236 | 237 | // SaveX is like Save, but panics if an error occurs. 238 | func (mcb *ModelCreateBulk) SaveX(ctx context.Context) []*Model { 239 | v, err := mcb.Save(ctx) 240 | if err != nil { 241 | panic(err) 242 | } 243 | return v 244 | } 245 | 246 | // Exec executes the query. 247 | func (mcb *ModelCreateBulk) Exec(ctx context.Context) error { 248 | _, err := mcb.Save(ctx) 249 | return err 250 | } 251 | 252 | // ExecX is like Exec, but panics if an error occurs. 253 | func (mcb *ModelCreateBulk) ExecX(ctx context.Context) { 254 | if err := mcb.Exec(ctx); err != nil { 255 | panic(err) 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /bench/ent/model_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/efectn/go-orm-benchmarks/bench/ent/model" 12 | "github.com/efectn/go-orm-benchmarks/bench/ent/predicate" 13 | ) 14 | 15 | // ModelDelete is the builder for deleting a Model entity. 16 | type ModelDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *ModelMutation 20 | } 21 | 22 | // Where appends a list predicates to the ModelDelete builder. 23 | func (md *ModelDelete) Where(ps ...predicate.Model) *ModelDelete { 24 | md.mutation.Where(ps...) 25 | return md 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (md *ModelDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, md.sqlExec, md.mutation, md.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (md *ModelDelete) ExecX(ctx context.Context) int { 35 | n, err := md.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (md *ModelDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(model.Table, sqlgraph.NewFieldSpec(model.FieldID, field.TypeInt)) 44 | if ps := md.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, md.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | md.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // ModelDeleteOne is the builder for deleting a single Model entity. 60 | type ModelDeleteOne struct { 61 | md *ModelDelete 62 | } 63 | 64 | // Where appends a list predicates to the ModelDelete builder. 65 | func (mdo *ModelDeleteOne) Where(ps ...predicate.Model) *ModelDeleteOne { 66 | mdo.md.mutation.Where(ps...) 67 | return mdo 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (mdo *ModelDeleteOne) Exec(ctx context.Context) error { 72 | n, err := mdo.md.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{model.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (mdo *ModelDeleteOne) ExecX(ctx context.Context) { 85 | if err := mdo.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /bench/ent/predicate/predicate.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package predicate 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql" 7 | ) 8 | 9 | // Model is the predicate function for model builders. 10 | type Model func(*sql.Selector) 11 | -------------------------------------------------------------------------------- /bench/ent/runtime.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | // The init function reads all schema descriptors with runtime code 6 | // (default values, validators, hooks and policies) and stitches it 7 | // to their package variables. 8 | func init() { 9 | } 10 | -------------------------------------------------------------------------------- /bench/ent/runtime/runtime.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package runtime 4 | 5 | // The schema-stitching logic is generated in github.com/efectn/go-orm-benchmarks/bench/ent/runtime.go 6 | 7 | const ( 8 | Version = "v0.12.3" // Version of ent codegen. 9 | Sum = "h1:N5lO2EOrHpCH5HYfiMOCHYbo+oh5M8GjT0/cx5x6xkk=" // Sum of ent codegen. 10 | ) 11 | -------------------------------------------------------------------------------- /bench/ent/schema/model.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent" 5 | "entgo.io/ent/schema/field" 6 | ) 7 | 8 | // Model holds the schema definition for the Model entity. 9 | type Model struct { 10 | ent.Schema 11 | } 12 | 13 | // Fields of the Model. 14 | func (Model) Fields() []ent.Field { 15 | return []ent.Field{ 16 | //field.Int("id").Unique().StorageKey("oid").Primar 17 | field.String("name"), 18 | field.String("title"), 19 | field.String("fax"), 20 | field.String("web"), 21 | field.Int("age"), 22 | field.Bool("right"), 23 | field.Int64("counter"), 24 | } 25 | } 26 | 27 | // Edges of the Model. 28 | func (Model) Edges() []ent.Edge { 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /bench/ent/tx.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "sync" 8 | 9 | "entgo.io/ent/dialect" 10 | ) 11 | 12 | // Tx is a transactional client that is created by calling Client.Tx(). 13 | type Tx struct { 14 | config 15 | // Model is the client for interacting with the Model builders. 16 | Model *ModelClient 17 | 18 | // lazily loaded. 19 | client *Client 20 | clientOnce sync.Once 21 | // ctx lives for the life of the transaction. It is 22 | // the same context used by the underlying connection. 23 | ctx context.Context 24 | } 25 | 26 | type ( 27 | // Committer is the interface that wraps the Commit method. 28 | Committer interface { 29 | Commit(context.Context, *Tx) error 30 | } 31 | 32 | // The CommitFunc type is an adapter to allow the use of ordinary 33 | // function as a Committer. If f is a function with the appropriate 34 | // signature, CommitFunc(f) is a Committer that calls f. 35 | CommitFunc func(context.Context, *Tx) error 36 | 37 | // CommitHook defines the "commit middleware". A function that gets a Committer 38 | // and returns a Committer. For example: 39 | // 40 | // hook := func(next ent.Committer) ent.Committer { 41 | // return ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error { 42 | // // Do some stuff before. 43 | // if err := next.Commit(ctx, tx); err != nil { 44 | // return err 45 | // } 46 | // // Do some stuff after. 47 | // return nil 48 | // }) 49 | // } 50 | // 51 | CommitHook func(Committer) Committer 52 | ) 53 | 54 | // Commit calls f(ctx, m). 55 | func (f CommitFunc) Commit(ctx context.Context, tx *Tx) error { 56 | return f(ctx, tx) 57 | } 58 | 59 | // Commit commits the transaction. 60 | func (tx *Tx) Commit() error { 61 | txDriver := tx.config.driver.(*txDriver) 62 | var fn Committer = CommitFunc(func(context.Context, *Tx) error { 63 | return txDriver.tx.Commit() 64 | }) 65 | txDriver.mu.Lock() 66 | hooks := append([]CommitHook(nil), txDriver.onCommit...) 67 | txDriver.mu.Unlock() 68 | for i := len(hooks) - 1; i >= 0; i-- { 69 | fn = hooks[i](fn) 70 | } 71 | return fn.Commit(tx.ctx, tx) 72 | } 73 | 74 | // OnCommit adds a hook to call on commit. 75 | func (tx *Tx) OnCommit(f CommitHook) { 76 | txDriver := tx.config.driver.(*txDriver) 77 | txDriver.mu.Lock() 78 | txDriver.onCommit = append(txDriver.onCommit, f) 79 | txDriver.mu.Unlock() 80 | } 81 | 82 | type ( 83 | // Rollbacker is the interface that wraps the Rollback method. 84 | Rollbacker interface { 85 | Rollback(context.Context, *Tx) error 86 | } 87 | 88 | // The RollbackFunc type is an adapter to allow the use of ordinary 89 | // function as a Rollbacker. If f is a function with the appropriate 90 | // signature, RollbackFunc(f) is a Rollbacker that calls f. 91 | RollbackFunc func(context.Context, *Tx) error 92 | 93 | // RollbackHook defines the "rollback middleware". A function that gets a Rollbacker 94 | // and returns a Rollbacker. For example: 95 | // 96 | // hook := func(next ent.Rollbacker) ent.Rollbacker { 97 | // return ent.RollbackFunc(func(ctx context.Context, tx *ent.Tx) error { 98 | // // Do some stuff before. 99 | // if err := next.Rollback(ctx, tx); err != nil { 100 | // return err 101 | // } 102 | // // Do some stuff after. 103 | // return nil 104 | // }) 105 | // } 106 | // 107 | RollbackHook func(Rollbacker) Rollbacker 108 | ) 109 | 110 | // Rollback calls f(ctx, m). 111 | func (f RollbackFunc) Rollback(ctx context.Context, tx *Tx) error { 112 | return f(ctx, tx) 113 | } 114 | 115 | // Rollback rollbacks the transaction. 116 | func (tx *Tx) Rollback() error { 117 | txDriver := tx.config.driver.(*txDriver) 118 | var fn Rollbacker = RollbackFunc(func(context.Context, *Tx) error { 119 | return txDriver.tx.Rollback() 120 | }) 121 | txDriver.mu.Lock() 122 | hooks := append([]RollbackHook(nil), txDriver.onRollback...) 123 | txDriver.mu.Unlock() 124 | for i := len(hooks) - 1; i >= 0; i-- { 125 | fn = hooks[i](fn) 126 | } 127 | return fn.Rollback(tx.ctx, tx) 128 | } 129 | 130 | // OnRollback adds a hook to call on rollback. 131 | func (tx *Tx) OnRollback(f RollbackHook) { 132 | txDriver := tx.config.driver.(*txDriver) 133 | txDriver.mu.Lock() 134 | txDriver.onRollback = append(txDriver.onRollback, f) 135 | txDriver.mu.Unlock() 136 | } 137 | 138 | // Client returns a Client that binds to current transaction. 139 | func (tx *Tx) Client() *Client { 140 | tx.clientOnce.Do(func() { 141 | tx.client = &Client{config: tx.config} 142 | tx.client.init() 143 | }) 144 | return tx.client 145 | } 146 | 147 | func (tx *Tx) init() { 148 | tx.Model = NewModelClient(tx.config) 149 | } 150 | 151 | // txDriver wraps the given dialect.Tx with a nop dialect.Driver implementation. 152 | // The idea is to support transactions without adding any extra code to the builders. 153 | // When a builder calls to driver.Tx(), it gets the same dialect.Tx instance. 154 | // Commit and Rollback are nop for the internal builders and the user must call one 155 | // of them in order to commit or rollback the transaction. 156 | // 157 | // If a closed transaction is embedded in one of the generated entities, and the entity 158 | // applies a query, for example: Model.QueryXXX(), the query will be executed 159 | // through the driver which created this transaction. 160 | // 161 | // Note that txDriver is not goroutine safe. 162 | type txDriver struct { 163 | // the driver we started the transaction from. 164 | drv dialect.Driver 165 | // tx is the underlying transaction. 166 | tx dialect.Tx 167 | // completion hooks. 168 | mu sync.Mutex 169 | onCommit []CommitHook 170 | onRollback []RollbackHook 171 | } 172 | 173 | // newTx creates a new transactional driver. 174 | func newTx(ctx context.Context, drv dialect.Driver) (*txDriver, error) { 175 | tx, err := drv.Tx(ctx) 176 | if err != nil { 177 | return nil, err 178 | } 179 | return &txDriver{tx: tx, drv: drv}, nil 180 | } 181 | 182 | // Tx returns the transaction wrapper (txDriver) to avoid Commit or Rollback calls 183 | // from the internal builders. Should be called only by the internal builders. 184 | func (tx *txDriver) Tx(context.Context) (dialect.Tx, error) { return tx, nil } 185 | 186 | // Dialect returns the dialect of the driver we started the transaction from. 187 | func (tx *txDriver) Dialect() string { return tx.drv.Dialect() } 188 | 189 | // Close is a nop close. 190 | func (*txDriver) Close() error { return nil } 191 | 192 | // Commit is a nop commit for the internal builders. 193 | // User must call `Tx.Commit` in order to commit the transaction. 194 | func (*txDriver) Commit() error { return nil } 195 | 196 | // Rollback is a nop rollback for the internal builders. 197 | // User must call `Tx.Rollback` in order to rollback the transaction. 198 | func (*txDriver) Rollback() error { return nil } 199 | 200 | // Exec calls tx.Exec. 201 | func (tx *txDriver) Exec(ctx context.Context, query string, args, v any) error { 202 | return tx.tx.Exec(ctx, query, args, v) 203 | } 204 | 205 | // Query calls tx.Query. 206 | func (tx *txDriver) Query(ctx context.Context, query string, args, v any) error { 207 | return tx.tx.Query(ctx, query, args, v) 208 | } 209 | 210 | var _ dialect.Driver = (*txDriver)(nil) 211 | -------------------------------------------------------------------------------- /bench/gen.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "testing" 5 | 6 | "gorm.io/driver/postgres" 7 | "gorm.io/gorm" 8 | "gorm.io/gorm/logger" 9 | 10 | "github.com/efectn/go-orm-benchmarks/bench/gen/models" 11 | "github.com/efectn/go-orm-benchmarks/bench/gen/query" 12 | "github.com/efectn/go-orm-benchmarks/helper" 13 | ) 14 | 15 | type Gen struct { 16 | helper.ORMInterface 17 | conn *gorm.DB 18 | } 19 | 20 | func CreateGen() helper.ORMInterface { 21 | return &Gen{} 22 | } 23 | 24 | func (gen *Gen) Name() string { 25 | return "gen" 26 | } 27 | 28 | func (gen *Gen) Init() error { 29 | var err error 30 | gen.conn, err = gorm.Open(postgres.New(postgres.Config{ 31 | DSN: helper.OrmSource, 32 | PreferSimpleProtocol: true, // disables implicit prepared statement usage 33 | }), &gorm.Config{ 34 | SkipDefaultTransaction: true, 35 | PrepareStmt: false, 36 | Logger: logger.Default.LogMode(logger.Silent), 37 | }) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | return nil 43 | } 44 | 45 | func (gen *Gen) Close() error { 46 | db, _ := gen.conn.DB() 47 | 48 | return db.Close() 49 | } 50 | 51 | func (gen *Gen) Insert(b *testing.B) { 52 | m := models.NewModelAlt() 53 | 54 | b.ReportAllocs() 55 | b.ResetTimer() 56 | for i := 0; i < b.N; i++ { 57 | m.Id = 0 58 | if err := query.Use(gen.conn).Model.WithContext(ctx).Create(&m); err != nil { 59 | helper.SetError(b, gen.Name(), "Insert", err.Error()) 60 | } 61 | } 62 | } 63 | 64 | func (gen Gen) InsertMulti(b *testing.B) { 65 | ms := make([]*models.Model, 0, 100) 66 | for i := 0; i < 100; i++ { 67 | m := models.NewModelAlt() 68 | ms = append(ms, &m) 69 | } 70 | 71 | b.ReportAllocs() 72 | b.ResetTimer() 73 | 74 | for i := 0; i < b.N; i++ { 75 | ms := make([]*models.Model, 0, 100) 76 | for i := 0; i < 100; i++ { 77 | m := models.NewModelAlt() 78 | ms = append(ms, &m) 79 | } 80 | if err := query.Use(gen.conn).Model.WithContext(ctx).Create(ms...); err != nil { 81 | helper.SetError(b, gen.Name(), "InsertMulti", err.Error()) 82 | } 83 | } 84 | } 85 | 86 | func (gen *Gen) Update(b *testing.B) { 87 | m := models.NewModelAlt() 88 | if err := query.Use(gen.conn).Model.WithContext(ctx).Create(&m); err != nil { 89 | helper.SetError(b, gen.Name(), "Insert", err.Error()) 90 | } 91 | b.ReportAllocs() 92 | b.ResetTimer() 93 | 94 | for i := 0; i < b.N; i++ { 95 | _, err := query.Use(gen.conn).Model.WithContext(ctx). 96 | Where(query.Use(gen.conn).Model.Id.Eq(m.Id)). 97 | Updates(m) 98 | if err != nil { 99 | helper.SetError(b, gen.Name(), "Update", err.Error()) 100 | } 101 | } 102 | } 103 | 104 | func (gen *Gen) Read(b *testing.B) { 105 | m := models.NewModelAlt() 106 | if err := query.Use(gen.conn).Model.WithContext(ctx).Create(&m); err != nil { 107 | helper.SetError(b, gen.Name(), "Insert", err.Error()) 108 | } 109 | b.ReportAllocs() 110 | b.ResetTimer() 111 | 112 | for i := 0; i < b.N; i++ { 113 | _, err := query.Use(gen.conn).Model.WithContext(ctx). 114 | Where(query.Use(gen.conn).Model.Id.Eq(m.Id)). 115 | First() 116 | if err != nil { 117 | helper.SetError(b, gen.Name(), "Read", err.Error()) 118 | } 119 | } 120 | 121 | } 122 | 123 | func (gen *Gen) ReadSlice(b *testing.B) { 124 | m := models.NewModelAlt() 125 | for i := 0; i < 100; i++ { 126 | m.Id = 0 127 | if err := query.Use(gen.conn).Model.WithContext(ctx).Create(&m); err != nil { 128 | helper.SetError(b, gen.Name(), "ReadSlice", err.Error()) 129 | } 130 | } 131 | 132 | for i := 0; i < b.N; i++ { 133 | _, err := query.Use(gen.conn).Model.WithContext(ctx). 134 | Where(query.Use(gen.conn).Model.Id.Gt(0)). 135 | Limit(100). 136 | Find() 137 | if err != nil { 138 | helper.SetError(b, gen.Name(), "ReadSlice", err.Error()) 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /bench/gen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "gorm.io/gen" 5 | 6 | "github.com/efectn/go-orm-benchmarks/bench/gen/models" 7 | ) 8 | 9 | func main() { 10 | // Initialize the generator with configuration 11 | g := gen.NewGenerator(gen.Config{ 12 | OutPath: "./query", 13 | Mode: gen.WithDefaultQuery | gen.WithQueryInterface, 14 | FieldNullable: true, 15 | }) 16 | // 17 | // db, err := gorm.Open(postgres.New(postgres.Config{ 18 | // DSN: helper.OrmSource, 19 | // PreferSimpleProtocol: true, // disables implicit prepared statement usage 20 | // }), &gorm.Config{ 21 | // SkipDefaultTransaction: true, 22 | // PrepareStmt: false, 23 | // Logger: logger.Default.LogMode(logger.Silent), 24 | // }) 25 | // if err != nil { 26 | // panic(err) 27 | // } 28 | // 29 | // // Use the above `*gorm.DB` instance to initialize the generator, 30 | // // which is required to generate structs from db when using `GenerateModel/GenerateModelAs` 31 | // g.UseDB(db) 32 | 33 | // Generate default DAO interface for those specified structs 34 | g.ApplyBasic(models.Model{}) 35 | 36 | // Execute the generator 37 | g.Execute() 38 | } 39 | -------------------------------------------------------------------------------- /bench/gen/models/models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "context" 5 | 6 | "gitee.com/chunanyong/zorm" 7 | 8 | r "github.com/efectn/go-orm-benchmarks/bench/reform" 9 | models "github.com/efectn/go-orm-benchmarks/bench/sqlboiler" 10 | ) 11 | 12 | var ctx = context.Background() 13 | 14 | // Model for GORM, GORP, Beego, Bun, Pg, Raw, Sqlc, Ent 15 | type Model struct { 16 | Id int `orm:"auto" gorm:"primary_key" db:"id" bun:",pk,autoincrement"` 17 | Name string 18 | Title string 19 | Fax string 20 | Web string 21 | Age int 22 | Right bool 23 | Counter int64 24 | } 25 | 26 | func (m *Model) TableName() string { 27 | return "models" 28 | } 29 | 30 | func (m *Model) Table() string { 31 | return "models" 32 | } 33 | 34 | func NewModel() *Model { 35 | m := new(Model) 36 | m.Name = "Orm Benchmark" 37 | m.Title = "Just a Benchmark for fun" 38 | m.Fax = "99909990" 39 | m.Web = "http://blog.milkpod29.me" 40 | m.Age = 100 41 | m.Right = true 42 | m.Counter = 1000 43 | 44 | return m 45 | } 46 | 47 | func NewModelAlt() Model { 48 | m := Model{} 49 | m.Name = "Orm Benchmark" 50 | m.Title = "Just a Benchmark for fun" 51 | m.Fax = "99909990" 52 | m.Web = "http://blog.milkpod29.me" 53 | m.Age = 100 54 | m.Right = true 55 | m.Counter = 1000 56 | 57 | return m 58 | } 59 | 60 | // Model for Godb, Dbr 61 | type Model2 struct { 62 | ID int `db:"id,key,auto"` 63 | Name string `db:"name"` 64 | Title string `db:"title"` 65 | Fax string `db:"fax"` 66 | Web string `db:"web"` 67 | Age int `db:"age"` 68 | Right bool `db:"right"` 69 | Counter int64 `db:"counter"` 70 | } 71 | 72 | func (*Model2) TableName() string { 73 | return "models" 74 | } 75 | 76 | func NewModel2() *Model2 { 77 | m := new(Model2) 78 | m.Name = "Orm Benchmark" 79 | m.Title = "Just a Benchmark for fun" 80 | m.Fax = "99909990" 81 | m.Web = "http://blog.milkpod29.me" 82 | m.Age = 100 83 | m.Right = true 84 | m.Counter = 1000 85 | 86 | return m 87 | } 88 | 89 | // Model for Pop, Rel 90 | type Model3 struct { 91 | ID int `db:"id"` 92 | Name string `db:"name"` 93 | Title string `db:"title"` 94 | Fax string `db:"fax"` 95 | Web string `db:"web"` 96 | Age int `db:"age"` 97 | Right bool `db:"right"` 98 | Counter int64 `db:"counter"` 99 | } 100 | 101 | func (Model3) Table() string { 102 | return "models" 103 | } 104 | 105 | func (Model3) TableName() string { 106 | return "models" 107 | } 108 | 109 | func NewModel3() *Model3 { 110 | m := new(Model3) 111 | m.Name = "Orm Benchmark" 112 | m.Title = "Just a Benchmark for fun" 113 | m.Fax = "99909990" 114 | m.Web = "http://blog.milkpod29.me" 115 | m.Age = 100 116 | m.Right = true 117 | m.Counter = 1000 118 | 119 | return m 120 | } 121 | 122 | // Model for Upper 123 | type Model4 struct { 124 | ID int `db:"id,omitempty"` 125 | Name string `db:"name"` 126 | Title string `db:"title"` 127 | Fax string `db:"fax"` 128 | Web string `db:"web"` 129 | Age int `db:"age"` 130 | Right bool `db:"right"` 131 | Counter int64 `db:"counter"` 132 | } 133 | 134 | func NewModel4() *Model4 { 135 | m := new(Model4) 136 | m.Name = "Orm Benchmark" 137 | m.Title = "Just a Benchmark for fun" 138 | m.Fax = "99909990" 139 | m.Web = "http://blog.milkpod29.me" 140 | m.Age = 100 141 | m.Right = true 142 | m.Counter = 1000 143 | 144 | return m 145 | } 146 | 147 | // Model for XORM 148 | type Model5 struct { 149 | ID int `xorm:"pk autoincr 'id'"` 150 | Name string 151 | Title string 152 | Fax string 153 | Web string 154 | Age int 155 | Right bool 156 | Counter int64 157 | } 158 | 159 | func NewModel5() *Model5 { 160 | m := new(Model5) 161 | m.Name = "Orm Benchmark" 162 | m.Title = "Just a Benchmark for fun" 163 | m.Fax = "99909990" 164 | m.Web = "http://blog.milkpod29.me" 165 | m.Age = 100 166 | m.Right = true 167 | m.Counter = 1000 168 | 169 | return m 170 | } 171 | 172 | // Model for Sqlboiler 173 | func NewModel6() *models.Model { 174 | m := new(models.Model) 175 | m.Name = "Orm Benchmark" 176 | m.Title = "Just a Benchmark for fun" 177 | m.Fax = "99909990" 178 | m.Web = "http://blog.milkpod29.me" 179 | m.Age = 100 180 | m.Right = true 181 | m.Counter = 1000 182 | 183 | return m 184 | } 185 | 186 | // Model for zorm 187 | type Model7 struct { 188 | zorm.EntityStruct 189 | ID int `column:"id"` 190 | Name string `column:"name"` 191 | Title string `column:"title"` 192 | Fax string `column:"fax"` 193 | Web string `column:"web"` 194 | Age int `column:"age"` 195 | Right bool `column:"\"right\""` 196 | Counter int64 `column:"counter"` 197 | } 198 | 199 | func (entity *Model7) GetTableName() string { 200 | return "models" 201 | } 202 | 203 | func (entity *Model7) GetPKColumnName() string { 204 | return "id" 205 | } 206 | 207 | func NewModel7() *Model7 { 208 | m := new(Model7) 209 | m.Name = "Orm Benchmark" 210 | m.Title = "Just a Benchmark for fun" 211 | m.Fax = "99909990" 212 | m.Web = "http://blog.milkpod29.me" 213 | m.Age = 100 214 | m.Right = true 215 | m.Counter = 1000 216 | 217 | return m 218 | } 219 | 220 | func NewReformModel() *r.ReformModels { 221 | m := new(r.ReformModels) 222 | m.Name = "Orm Benchmark" 223 | m.Title = "Just a Benchmark for fun" 224 | m.Fax = "99909990" 225 | m.Web = "http://blog.milkpod29.me" 226 | m.Age = 100 227 | m.Right = true 228 | m.Counter = 1000 229 | 230 | return m 231 | } 232 | -------------------------------------------------------------------------------- /bench/gen/query/gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gorm.io/gen. DO NOT EDIT. 2 | // Code generated by gorm.io/gen. DO NOT EDIT. 3 | // Code generated by gorm.io/gen. DO NOT EDIT. 4 | 5 | package query 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | 11 | "gorm.io/gorm" 12 | 13 | "gorm.io/gen" 14 | 15 | "gorm.io/plugin/dbresolver" 16 | ) 17 | 18 | var ( 19 | Q = new(Query) 20 | Model *model 21 | ) 22 | 23 | func SetDefault(db *gorm.DB, opts ...gen.DOOption) { 24 | *Q = *Use(db, opts...) 25 | Model = &Q.Model 26 | } 27 | 28 | func Use(db *gorm.DB, opts ...gen.DOOption) *Query { 29 | return &Query{ 30 | db: db, 31 | Model: newModel(db, opts...), 32 | } 33 | } 34 | 35 | type Query struct { 36 | db *gorm.DB 37 | 38 | Model model 39 | } 40 | 41 | func (q *Query) Available() bool { return q.db != nil } 42 | 43 | func (q *Query) clone(db *gorm.DB) *Query { 44 | return &Query{ 45 | db: db, 46 | Model: q.Model.clone(db), 47 | } 48 | } 49 | 50 | func (q *Query) ReadDB() *Query { 51 | return q.ReplaceDB(q.db.Clauses(dbresolver.Read)) 52 | } 53 | 54 | func (q *Query) WriteDB() *Query { 55 | return q.ReplaceDB(q.db.Clauses(dbresolver.Write)) 56 | } 57 | 58 | func (q *Query) ReplaceDB(db *gorm.DB) *Query { 59 | return &Query{ 60 | db: db, 61 | Model: q.Model.replaceDB(db), 62 | } 63 | } 64 | 65 | type queryCtx struct { 66 | Model IModelDo 67 | } 68 | 69 | func (q *Query) WithContext(ctx context.Context) *queryCtx { 70 | return &queryCtx{ 71 | Model: q.Model.WithContext(ctx), 72 | } 73 | } 74 | 75 | func (q *Query) Transaction(fc func(tx *Query) error, opts ...*sql.TxOptions) error { 76 | return q.db.Transaction(func(tx *gorm.DB) error { return fc(q.clone(tx)) }, opts...) 77 | } 78 | 79 | func (q *Query) Begin(opts ...*sql.TxOptions) *QueryTx { 80 | tx := q.db.Begin(opts...) 81 | return &QueryTx{Query: q.clone(tx), Error: tx.Error} 82 | } 83 | 84 | type QueryTx struct { 85 | *Query 86 | Error error 87 | } 88 | 89 | func (q *QueryTx) Commit() error { 90 | return q.db.Commit().Error 91 | } 92 | 93 | func (q *QueryTx) Rollback() error { 94 | return q.db.Rollback().Error 95 | } 96 | 97 | func (q *QueryTx) SavePoint(name string) error { 98 | return q.db.SavePoint(name).Error 99 | } 100 | 101 | func (q *QueryTx) RollbackTo(name string) error { 102 | return q.db.RollbackTo(name).Error 103 | } 104 | -------------------------------------------------------------------------------- /bench/godb.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/efectn/go-orm-benchmarks/helper" 7 | _ "github.com/jackc/pgx/v5/stdlib" 8 | godbware "github.com/samonzeweb/godb" 9 | "github.com/samonzeweb/godb/adapters/postgresql" 10 | ) 11 | 12 | type Godb struct { 13 | helper.ORMInterface 14 | conn *godbware.DB 15 | } 16 | 17 | func CreateGodb() helper.ORMInterface { 18 | return &Godb{} 19 | } 20 | 21 | func (godb *Godb) Name() string { 22 | return "godb" 23 | } 24 | 25 | func (godb *Godb) Init() error { 26 | var err error 27 | godb.conn, err = godbware.Open(postgresql.Adapter, helper.OrmSource) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | return nil 33 | } 34 | 35 | func (godb *Godb) Close() error { 36 | return godb.conn.Close() 37 | } 38 | 39 | func (godb *Godb) Insert(b *testing.B) { 40 | m := NewModel2() 41 | 42 | b.ReportAllocs() 43 | b.ResetTimer() 44 | 45 | for i := 0; i < b.N; i++ { 46 | err := godb.conn.Insert(m).Do() 47 | if err != nil { 48 | helper.SetError(b, godb.Name(), "Insert", err.Error()) 49 | } 50 | } 51 | } 52 | 53 | func (godb *Godb) InsertMulti(b *testing.B) { 54 | ms := make([]*Model2, 0, 100) 55 | for i := 0; i < 100; i++ { 56 | ms = append(ms, NewModel2()) 57 | } 58 | 59 | b.ReportAllocs() 60 | b.ResetTimer() 61 | 62 | for i := 0; i < b.N; i++ { 63 | err := godb.conn.BulkInsert(&ms).Do() 64 | if err != nil { 65 | helper.SetError(b, godb.Name(), "InsertMulti", err.Error()) 66 | } 67 | } 68 | } 69 | 70 | func (godb *Godb) Update(b *testing.B) { 71 | m := NewModel2() 72 | 73 | err := godb.conn.Insert(m).Do() 74 | if err != nil { 75 | helper.SetError(b, godb.Name(), "Update", err.Error()) 76 | } 77 | 78 | b.ReportAllocs() 79 | b.ResetTimer() 80 | 81 | for i := 0; i < b.N; i++ { 82 | err := godb.conn.Update(m).Do() 83 | if err != nil { 84 | helper.SetError(b, godb.Name(), "Update", err.Error()) 85 | } 86 | } 87 | } 88 | 89 | func (godb *Godb) Read(b *testing.B) { 90 | m := NewModel2() 91 | 92 | err := godb.conn.Insert(m).Do() 93 | if err != nil { 94 | helper.SetError(b, godb.Name(), "Read", err.Error()) 95 | } 96 | 97 | b.ReportAllocs() 98 | b.ResetTimer() 99 | 100 | for i := 0; i < b.N; i++ { 101 | err := godb.conn.Select(m).Do() 102 | if err != nil { 103 | helper.SetError(b, godb.Name(), "Read", err.Error()) 104 | } 105 | } 106 | } 107 | 108 | func (godb *Godb) ReadSlice(b *testing.B) { 109 | m := NewModel2() 110 | for i := 0; i < 100; i++ { 111 | err := godb.conn.Insert(m).Do() 112 | if err != nil { 113 | helper.SetError(b, godb.Name(), "ReadSlice", err.Error()) 114 | } 115 | } 116 | 117 | b.ReportAllocs() 118 | b.ResetTimer() 119 | 120 | for i := 0; i < b.N; i++ { 121 | var ms []*Model2 122 | err := godb.conn.Select(&ms).Where("id > 0").Limit(100).Do() 123 | if err != nil { 124 | helper.SetError(b, godb.Name(), "ReadSlice", err.Error()) 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /bench/gorm.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "github.com/efectn/go-orm-benchmarks/helper" 5 | "gorm.io/driver/postgres" 6 | gormdb "gorm.io/gorm" 7 | "gorm.io/gorm/logger" 8 | "testing" 9 | ) 10 | 11 | type Gorm struct { 12 | helper.ORMInterface 13 | conn *gormdb.DB 14 | } 15 | 16 | func CreateGorm() helper.ORMInterface { 17 | return &Gorm{} 18 | } 19 | 20 | func (gorm *Gorm) Name() string { 21 | return "gorm" 22 | } 23 | 24 | func (gorm *Gorm) Init() error { 25 | var err error 26 | gorm.conn, err = gormdb.Open(postgres.New(postgres.Config{ 27 | DSN: helper.OrmSource, 28 | PreferSimpleProtocol: true, // disables implicit prepared statement usage 29 | }), &gormdb.Config{ 30 | SkipDefaultTransaction: true, 31 | PrepareStmt: false, 32 | Logger: logger.Default.LogMode(logger.Silent), 33 | }) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | return nil 39 | } 40 | 41 | func (gorm *Gorm) Close() error { 42 | db, _ := gorm.conn.DB() 43 | 44 | return db.Close() 45 | } 46 | 47 | func (gorm *Gorm) Insert(b *testing.B) { 48 | m := NewModel() 49 | 50 | b.ReportAllocs() 51 | b.ResetTimer() 52 | 53 | for i := 0; i < b.N; i++ { 54 | m.Id = 0 55 | err := gorm.conn.Create(m).Error 56 | if err != nil { 57 | helper.SetError(b, gorm.Name(), "Insert", err.Error()) 58 | } 59 | } 60 | } 61 | 62 | func (gorm *Gorm) InsertMulti(b *testing.B) { 63 | ms := make([]*Model, 0, 100) 64 | for i := 0; i < 100; i++ { 65 | ms = append(ms, NewModel()) 66 | } 67 | 68 | b.ReportAllocs() 69 | b.ResetTimer() 70 | 71 | for i := 0; i < b.N; i++ { 72 | for _, m := range ms { 73 | m.Id = 0 74 | } 75 | err := gorm.conn.Create(&ms).Error 76 | if err != nil { 77 | helper.SetError(b, gorm.Name(), "InsertMulti", err.Error()) 78 | } 79 | } 80 | } 81 | 82 | func (gorm *Gorm) Update(b *testing.B) { 83 | m := NewModel() 84 | 85 | err := gorm.conn.Create(m).Error 86 | if err != nil { 87 | helper.SetError(b, gorm.Name(), "Update", err.Error()) 88 | } 89 | 90 | b.ReportAllocs() 91 | b.ResetTimer() 92 | 93 | for i := 0; i < b.N; i++ { 94 | err := gorm.conn.Model(m).Updates(m).Error 95 | if err != nil { 96 | helper.SetError(b, gorm.Name(), "Update", err.Error()) 97 | } 98 | } 99 | } 100 | 101 | func (gorm *Gorm) Read(b *testing.B) { 102 | m := NewModel() 103 | 104 | err := gorm.conn.Create(m).Error 105 | if err != nil { 106 | helper.SetError(b, gorm.Name(), "Read", err.Error()) 107 | } 108 | 109 | b.ReportAllocs() 110 | b.ResetTimer() 111 | 112 | for i := 0; i < b.N; i++ { 113 | err := gorm.conn.Take(m).Error 114 | if err != nil { 115 | helper.SetError(b, gorm.Name(), "Read", err.Error()) 116 | } 117 | } 118 | } 119 | 120 | func (gorm *Gorm) ReadSlice(b *testing.B) { 121 | m := NewModel() 122 | for i := 0; i < 100; i++ { 123 | m.Id = 0 124 | err := gorm.conn.Create(m).Error 125 | if err != nil { 126 | helper.SetError(b, gorm.Name(), "ReadSlice", err.Error()) 127 | } 128 | } 129 | 130 | b.ReportAllocs() 131 | b.ResetTimer() 132 | 133 | for i := 0; i < b.N; i++ { 134 | var models []*Model 135 | err := gorm.conn.Where("id > ?", 0).Limit(100).Find(&models).Error 136 | if err != nil { 137 | helper.SetError(b, gorm.Name(), "ReadSlice", err.Error()) 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /bench/gorm_prep.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "github.com/efectn/go-orm-benchmarks/helper" 5 | "gorm.io/driver/postgres" 6 | gormdb "gorm.io/gorm" 7 | "gorm.io/gorm/logger" 8 | "testing" 9 | ) 10 | 11 | type GormPrep struct { 12 | helper.ORMInterface 13 | conn *gormdb.DB 14 | } 15 | 16 | func CreateGormPrep() helper.ORMInterface { 17 | return &GormPrep{} 18 | } 19 | 20 | func (gorm *GormPrep) Name() string { 21 | return "gorm_prep" 22 | } 23 | 24 | func (gorm *GormPrep) Init() error { 25 | var err error 26 | gorm.conn, err = gormdb.Open(postgres.New(postgres.Config{ 27 | DSN: helper.OrmSource, 28 | }), &gormdb.Config{ 29 | SkipDefaultTransaction: true, 30 | PrepareStmt: true, 31 | Logger: logger.Default.LogMode(logger.Silent), 32 | }) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | return nil 38 | } 39 | 40 | func (gorm *GormPrep) Close() error { 41 | db, _ := gorm.conn.DB() 42 | 43 | return db.Close() 44 | } 45 | 46 | func (gorm *GormPrep) Insert(b *testing.B) { 47 | m := NewModel() 48 | 49 | b.ReportAllocs() 50 | b.ResetTimer() 51 | 52 | for i := 0; i < b.N; i++ { 53 | m.Id = 0 54 | err := gorm.conn.Create(m).Error 55 | if err != nil { 56 | helper.SetError(b, gorm.Name(), "Insert", err.Error()) 57 | } 58 | } 59 | } 60 | 61 | func (gorm *GormPrep) InsertMulti(b *testing.B) { 62 | ms := make([]*Model, 0, 100) 63 | for i := 0; i < 100; i++ { 64 | ms = append(ms, NewModel()) 65 | } 66 | 67 | b.ReportAllocs() 68 | b.ResetTimer() 69 | 70 | for i := 0; i < b.N; i++ { 71 | for _, m := range ms { 72 | m.Id = 0 73 | } 74 | err := gorm.conn.Create(&ms).Error 75 | if err != nil { 76 | helper.SetError(b, gorm.Name(), "InsertMulti", err.Error()) 77 | } 78 | } 79 | } 80 | 81 | func (gorm *GormPrep) Update(b *testing.B) { 82 | m := NewModel() 83 | 84 | err := gorm.conn.Create(m).Error 85 | if err != nil { 86 | helper.SetError(b, gorm.Name(), "Update", err.Error()) 87 | } 88 | 89 | b.ReportAllocs() 90 | b.ResetTimer() 91 | 92 | for i := 0; i < b.N; i++ { 93 | err := gorm.conn.Model(m).Updates(m).Error 94 | if err != nil { 95 | helper.SetError(b, gorm.Name(), "Update", err.Error()) 96 | } 97 | } 98 | } 99 | 100 | func (gorm *GormPrep) Read(b *testing.B) { 101 | m := NewModel() 102 | 103 | err := gorm.conn.Create(m).Error 104 | if err != nil { 105 | helper.SetError(b, gorm.Name(), "Read", err.Error()) 106 | } 107 | 108 | b.ReportAllocs() 109 | b.ResetTimer() 110 | 111 | for i := 0; i < b.N; i++ { 112 | err := gorm.conn.Take(m).Error 113 | if err != nil { 114 | helper.SetError(b, gorm.Name(), "Read", err.Error()) 115 | } 116 | } 117 | } 118 | 119 | func (gorm *GormPrep) ReadSlice(b *testing.B) { 120 | m := NewModel() 121 | for i := 0; i < 100; i++ { 122 | m.Id = 0 123 | err := gorm.conn.Create(m).Error 124 | if err != nil { 125 | helper.SetError(b, gorm.Name(), "ReadSlice", err.Error()) 126 | } 127 | } 128 | 129 | b.ReportAllocs() 130 | b.ResetTimer() 131 | 132 | for i := 0; i < b.N; i++ { 133 | var models []*Model 134 | err := gorm.conn.Where("id > ?", 0).Limit(100).Find(&models).Error 135 | if err != nil { 136 | helper.SetError(b, gorm.Name(), "ReadSlice", err.Error()) 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /bench/gorp.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "database/sql" 5 | "testing" 6 | 7 | "github.com/efectn/go-orm-benchmarks/helper" 8 | 9 | _ "github.com/jackc/pgx/v5/stdlib" 10 | _ "github.com/lib/pq" 11 | gorpware "gopkg.in/gorp.v1" 12 | ) 13 | 14 | type Gorp struct { 15 | helper.ORMInterface 16 | conn *gorpware.DbMap 17 | } 18 | 19 | func CreateGorp() helper.ORMInterface { 20 | return &Gorp{} 21 | } 22 | 23 | func (gorp *Gorp) Name() string { 24 | return "gorp" 25 | } 26 | 27 | func (gorp *Gorp) Init() error { 28 | db, err := sql.Open("pgx", helper.OrmSource) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | gorp.conn = &gorpware.DbMap{Db: db, Dialect: gorpware.PostgresDialect{}} 34 | gorp.conn.AddTableWithName(Model{}, "models").SetKeys(true, "Id") 35 | 36 | return nil 37 | } 38 | 39 | func (gorp *Gorp) Close() error { 40 | return gorp.conn.Db.Close() 41 | } 42 | 43 | func (gorp *Gorp) Insert(b *testing.B) { 44 | m := NewModel() 45 | 46 | b.ReportAllocs() 47 | b.ResetTimer() 48 | 49 | for i := 0; i < b.N; i++ { 50 | err := gorp.conn.Insert(m) 51 | if err != nil { 52 | helper.SetError(b, gorp.Name(), "Insert", err.Error()) 53 | } 54 | } 55 | } 56 | 57 | func (gorp *Gorp) InsertMulti(b *testing.B) { 58 | helper.SetError(b, gorp.Name(), "InsertMulti", "bulk-insert is not supported") 59 | } 60 | 61 | func (gorp *Gorp) Update(b *testing.B) { 62 | m := NewModel() 63 | 64 | err := gorp.conn.Insert(m) 65 | if err != nil { 66 | helper.SetError(b, gorp.Name(), "Update", err.Error()) 67 | } 68 | 69 | b.ReportAllocs() 70 | b.ResetTimer() 71 | 72 | for i := 0; i < b.N; i++ { 73 | _, err := gorp.conn.Update(m) 74 | if err != nil { 75 | helper.SetError(b, gorp.Name(), "Update", err.Error()) 76 | } 77 | } 78 | } 79 | 80 | func (gorp *Gorp) Read(b *testing.B) { 81 | m := NewModel() 82 | 83 | err := gorp.conn.Insert(m) 84 | if err != nil { 85 | helper.SetError(b, gorp.Name(), "Read", err.Error()) 86 | } 87 | 88 | b.ReportAllocs() 89 | b.ResetTimer() 90 | 91 | for i := 0; i < b.N; i++ { 92 | err := gorp.conn.SelectOne(m, "SELECT * FROM models LIMIT 1") 93 | if err != nil { 94 | helper.SetError(b, gorp.Name(), "Read", err.Error()) 95 | } 96 | } 97 | } 98 | 99 | func (gorp *Gorp) ReadSlice(b *testing.B) { 100 | m := NewModel() 101 | for i := 0; i < 100; i++ { 102 | err := gorp.conn.Insert(m) 103 | if err != nil { 104 | helper.SetError(b, gorp.Name(), "ReadSlice", err.Error()) 105 | } 106 | } 107 | 108 | b.ReportAllocs() 109 | b.ResetTimer() 110 | 111 | for i := 0; i < b.N; i++ { 112 | var ms []*Model 113 | _, err := gorp.conn.Select(&ms, "select * from models where id > 0 LIMIT 100") 114 | if err != nil { 115 | helper.SetError(b, gorp.Name(), "ReadSlice", err.Error()) 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /bench/jet.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "database/sql" 5 | "github.com/efectn/go-orm-benchmarks/helper" 6 | 7 | "github.com/efectn/go-orm-benchmarks/bench/jet/test/public/model" 8 | . "github.com/efectn/go-orm-benchmarks/bench/jet/test/public/table" 9 | jetpg "github.com/go-jet/jet/v2/postgres" 10 | 11 | "testing" 12 | ) 13 | 14 | type Jet struct { 15 | helper.ORMInterface 16 | conn *sql.DB 17 | } 18 | 19 | func CreateJet() helper.ORMInterface { 20 | return &Jet{} 21 | } 22 | 23 | func (jet *Jet) Name() string { 24 | return "jet" 25 | } 26 | 27 | func (jet *Jet) Init() error { 28 | var err error 29 | jet.conn, err = sql.Open("pgx", helper.OrmSource) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | return nil 35 | } 36 | 37 | func (jet *Jet) Close() error { 38 | return jet.conn.Close() 39 | } 40 | 41 | func (jet *Jet) Insert(b *testing.B) { 42 | m := NewModelJet() 43 | 44 | b.ReportAllocs() 45 | b.ResetTimer() 46 | 47 | for i := 0; i < b.N; i++ { 48 | _, err := Models.INSERT(Models.MutableColumns).MODEL(m).Exec(jet.conn) 49 | if err != nil { 50 | helper.SetError(b, jet.Name(), "Insert", err.Error()) 51 | } 52 | } 53 | } 54 | 55 | func (jet *Jet) InsertMulti(b *testing.B) { 56 | ms := make([]*model.Models, 0, 100) 57 | for i := 0; i < 100; i++ { 58 | ms = append(ms, NewModelJet()) 59 | } 60 | 61 | b.ReportAllocs() 62 | b.ResetTimer() 63 | 64 | bulk := make([]*model.Models, len(ms)) 65 | for i, m := range ms { 66 | bulk[i] = m 67 | } 68 | 69 | for i := 0; i < b.N; i++ { 70 | _, err := Models.INSERT(Models.MutableColumns).MODELS(ms).Exec(jet.conn) 71 | if err != nil { 72 | helper.SetError(b, jet.Name(), "InsertMulti", err.Error()) 73 | } 74 | } 75 | } 76 | 77 | func (jet *Jet) Update(b *testing.B) { 78 | m, err := jet.create() 79 | if err != nil { 80 | helper.SetError(b, jet.Name(), "Update", err.Error()) 81 | } 82 | 83 | b.ReportAllocs() 84 | b.ResetTimer() 85 | 86 | for i := 0; i < b.N; i++ { 87 | _, err := Models.UPDATE(Models.MutableColumns).MODEL(m).WHERE(Models.ID.EQ(jetpg.Int32(m.ID))).Exec(jet.conn) 88 | if err != nil { 89 | helper.SetError(b, jet.Name(), "Update", err.Error()) 90 | } 91 | } 92 | } 93 | 94 | func (jet *Jet) Read(b *testing.B) { 95 | m, err := jet.create() 96 | if err != nil { 97 | helper.SetError(b, jet.Name(), "Read", err.Error()) 98 | } 99 | 100 | b.ReportAllocs() 101 | b.ResetTimer() 102 | 103 | for i := 0; i < b.N; i++ { 104 | var mout model.Models 105 | err := Models.SELECT(Models.AllColumns). 106 | WHERE(Models.ID.EQ(jetpg.Int32(m.ID))). 107 | Query(jet.conn, &mout) 108 | if err != nil { 109 | helper.SetError(b, jet.Name(), "Read", err.Error()) 110 | } 111 | } 112 | } 113 | 114 | func (jet *Jet) ReadSlice(b *testing.B) { 115 | for i := 0; i < 100; i++ { 116 | _, err := jet.create() 117 | if err != nil { 118 | helper.SetError(b, jet.Name(), "ReadSlice", err.Error()) 119 | } 120 | } 121 | 122 | b.ReportAllocs() 123 | b.ResetTimer() 124 | 125 | for i := 0; i < b.N; i++ { 126 | models := make([]model.Models, 100) 127 | err := Models.SELECT(Models.AllColumns).WHERE(Models.ID.GT(jetpg.Int32(0))).LIMIT(100).Query(jet.conn, &models) 128 | if err != nil { 129 | helper.SetError(b, jet.Name(), "ReadSlice", err.Error()) 130 | } 131 | } 132 | } 133 | 134 | func (jet *Jet) create() (*model.Models, error) { 135 | m := NewModelJet() 136 | 137 | var created []model.Models 138 | err := Models.INSERT(Models.MutableColumns).MODEL(m).RETURNING(Models.ID).Query(jet.conn, &created) 139 | if err != nil { 140 | return nil, err 141 | } 142 | m.ID = created[0].ID 143 | return m, nil 144 | } 145 | -------------------------------------------------------------------------------- /bench/jet/gen.go: -------------------------------------------------------------------------------- 1 | package jet 2 | 3 | //go:generate go run github.com/go-jet/jet/v2/cmd/jet -source=postgresql -user=postgres --password=postgres -dbname=test -host=localhost -port=5432 -path=./ 4 | -------------------------------------------------------------------------------- /bench/jet/test/public/model/models.go: -------------------------------------------------------------------------------- 1 | // 2 | // Code generated by go-jet DO NOT EDIT. 3 | // 4 | // WARNING: Changes to this file may cause incorrect behavior 5 | // and will be lost if the code is regenerated 6 | // 7 | 8 | package model 9 | 10 | type Models struct { 11 | ID int32 `sql:"primary_key"` 12 | Name string 13 | Title string 14 | Fax string 15 | Web string 16 | Age int32 17 | Right bool 18 | Counter int64 19 | } 20 | -------------------------------------------------------------------------------- /bench/jet/test/public/table/models.go: -------------------------------------------------------------------------------- 1 | // 2 | // Code generated by go-jet DO NOT EDIT. 3 | // 4 | // WARNING: Changes to this file may cause incorrect behavior 5 | // and will be lost if the code is regenerated 6 | // 7 | 8 | package table 9 | 10 | import ( 11 | "github.com/go-jet/jet/v2/postgres" 12 | ) 13 | 14 | var Models = newModelsTable("public", "models", "") 15 | 16 | type modelsTable struct { 17 | postgres.Table 18 | 19 | // Columns 20 | ID postgres.ColumnInteger 21 | Name postgres.ColumnString 22 | Title postgres.ColumnString 23 | Fax postgres.ColumnString 24 | Web postgres.ColumnString 25 | Age postgres.ColumnInteger 26 | Right postgres.ColumnBool 27 | Counter postgres.ColumnInteger 28 | 29 | AllColumns postgres.ColumnList 30 | MutableColumns postgres.ColumnList 31 | } 32 | 33 | type ModelsTable struct { 34 | modelsTable 35 | 36 | EXCLUDED modelsTable 37 | } 38 | 39 | // AS creates new ModelsTable with assigned alias 40 | func (a ModelsTable) AS(alias string) *ModelsTable { 41 | return newModelsTable(a.SchemaName(), a.TableName(), alias) 42 | } 43 | 44 | // Schema creates new ModelsTable with assigned schema name 45 | func (a ModelsTable) FromSchema(schemaName string) *ModelsTable { 46 | return newModelsTable(schemaName, a.TableName(), a.Alias()) 47 | } 48 | 49 | // WithPrefix creates new ModelsTable with assigned table prefix 50 | func (a ModelsTable) WithPrefix(prefix string) *ModelsTable { 51 | return newModelsTable(a.SchemaName(), prefix+a.TableName(), a.TableName()) 52 | } 53 | 54 | // WithSuffix creates new ModelsTable with assigned table suffix 55 | func (a ModelsTable) WithSuffix(suffix string) *ModelsTable { 56 | return newModelsTable(a.SchemaName(), a.TableName()+suffix, a.TableName()) 57 | } 58 | 59 | func newModelsTable(schemaName, tableName, alias string) *ModelsTable { 60 | return &ModelsTable{ 61 | modelsTable: newModelsTableImpl(schemaName, tableName, alias), 62 | EXCLUDED: newModelsTableImpl("", "excluded", ""), 63 | } 64 | } 65 | 66 | func newModelsTableImpl(schemaName, tableName, alias string) modelsTable { 67 | var ( 68 | IDColumn = postgres.IntegerColumn("id") 69 | NameColumn = postgres.StringColumn("name") 70 | TitleColumn = postgres.StringColumn("title") 71 | FaxColumn = postgres.StringColumn("fax") 72 | WebColumn = postgres.StringColumn("web") 73 | AgeColumn = postgres.IntegerColumn("age") 74 | RightColumn = postgres.BoolColumn("right") 75 | CounterColumn = postgres.IntegerColumn("counter") 76 | allColumns = postgres.ColumnList{IDColumn, NameColumn, TitleColumn, FaxColumn, WebColumn, AgeColumn, RightColumn, CounterColumn} 77 | mutableColumns = postgres.ColumnList{NameColumn, TitleColumn, FaxColumn, WebColumn, AgeColumn, RightColumn, CounterColumn} 78 | ) 79 | 80 | return modelsTable{ 81 | Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), 82 | 83 | //Columns 84 | ID: IDColumn, 85 | Name: NameColumn, 86 | Title: TitleColumn, 87 | Fax: FaxColumn, 88 | Web: WebColumn, 89 | Age: AgeColumn, 90 | Right: RightColumn, 91 | Counter: CounterColumn, 92 | 93 | AllColumns: allColumns, 94 | MutableColumns: mutableColumns, 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /bench/jet/test/public/table/table_use_schema.go: -------------------------------------------------------------------------------- 1 | // 2 | // Code generated by go-jet DO NOT EDIT. 3 | // 4 | // WARNING: Changes to this file may cause incorrect behavior 5 | // and will be lost if the code is regenerated 6 | // 7 | 8 | package table 9 | 10 | // UseSchema sets a new schema name for all generated table SQL builder types. It is recommended to invoke 11 | // this method only once at the beginning of the program. 12 | func UseSchema(schema string) { 13 | Models = Models.FromSchema(schema) 14 | } 15 | -------------------------------------------------------------------------------- /bench/jet/tool.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | 3 | package jet 4 | 5 | import _ "github.com/go-jet/jet/v2/cmd/jet" 6 | -------------------------------------------------------------------------------- /bench/models.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "context" 5 | "gitee.com/chunanyong/zorm" 6 | jetmodel "github.com/efectn/go-orm-benchmarks/bench/jet/test/public/model" 7 | r "github.com/efectn/go-orm-benchmarks/bench/reform" 8 | models "github.com/efectn/go-orm-benchmarks/bench/sqlboiler" 9 | ) 10 | 11 | var ctx = context.Background() 12 | 13 | // Model for GORM, GORP, Beego, Bun, Pg, Raw, Sqlc, Ent 14 | type Model struct { 15 | Id int `orm:"auto" gorm:"primary_key" db:"id" bun:",pk,autoincrement"` 16 | Name string 17 | Title string 18 | Fax string 19 | Web string 20 | Age int 21 | Right bool 22 | Counter int64 23 | } 24 | 25 | func (m *Model) TableName() string { 26 | return "models" 27 | } 28 | 29 | func (m *Model) Table() string { 30 | return "models" 31 | } 32 | 33 | func NewModel() *Model { 34 | m := new(Model) 35 | m.Name = "Orm Benchmark" 36 | m.Title = "Just a Benchmark for fun" 37 | m.Fax = "99909990" 38 | m.Web = "http://blog.milkpod29.me" 39 | m.Age = 100 40 | m.Right = true 41 | m.Counter = 1000 42 | 43 | return m 44 | } 45 | 46 | func NewModelAlt() Model { 47 | m := Model{} 48 | m.Name = "Orm Benchmark" 49 | m.Title = "Just a Benchmark for fun" 50 | m.Fax = "99909990" 51 | m.Web = "http://blog.milkpod29.me" 52 | m.Age = 100 53 | m.Right = true 54 | m.Counter = 1000 55 | 56 | return m 57 | } 58 | 59 | // Model for Godb, Dbr 60 | type Model2 struct { 61 | ID int `db:"id,key,auto"` 62 | Name string `db:"name"` 63 | Title string `db:"title"` 64 | Fax string `db:"fax"` 65 | Web string `db:"web"` 66 | Age int `db:"age"` 67 | Right bool `db:"right"` 68 | Counter int64 `db:"counter"` 69 | } 70 | 71 | func (*Model2) TableName() string { 72 | return "models" 73 | } 74 | 75 | func NewModel2() *Model2 { 76 | m := new(Model2) 77 | m.Name = "Orm Benchmark" 78 | m.Title = "Just a Benchmark for fun" 79 | m.Fax = "99909990" 80 | m.Web = "http://blog.milkpod29.me" 81 | m.Age = 100 82 | m.Right = true 83 | m.Counter = 1000 84 | 85 | return m 86 | } 87 | 88 | // Model for Pop, Rel 89 | type Model3 struct { 90 | ID int `db:"id"` 91 | Name string `db:"name"` 92 | Title string `db:"title"` 93 | Fax string `db:"fax"` 94 | Web string `db:"web"` 95 | Age int `db:"age"` 96 | Right bool `db:"right"` 97 | Counter int64 `db:"counter"` 98 | } 99 | 100 | func (Model3) Table() string { 101 | return "models" 102 | } 103 | 104 | func (Model3) TableName() string { 105 | return "models" 106 | } 107 | 108 | func NewModel3() *Model3 { 109 | m := new(Model3) 110 | m.Name = "Orm Benchmark" 111 | m.Title = "Just a Benchmark for fun" 112 | m.Fax = "99909990" 113 | m.Web = "http://blog.milkpod29.me" 114 | m.Age = 100 115 | m.Right = true 116 | m.Counter = 1000 117 | 118 | return m 119 | } 120 | 121 | // Model for Upper 122 | type Model4 struct { 123 | ID int `db:"id,omitempty"` 124 | Name string `db:"name"` 125 | Title string `db:"title"` 126 | Fax string `db:"fax"` 127 | Web string `db:"web"` 128 | Age int `db:"age"` 129 | Right bool `db:"right"` 130 | Counter int64 `db:"counter"` 131 | } 132 | 133 | func NewModel4() *Model4 { 134 | m := new(Model4) 135 | m.Name = "Orm Benchmark" 136 | m.Title = "Just a Benchmark for fun" 137 | m.Fax = "99909990" 138 | m.Web = "http://blog.milkpod29.me" 139 | m.Age = 100 140 | m.Right = true 141 | m.Counter = 1000 142 | 143 | return m 144 | } 145 | 146 | // Model for XORM 147 | type Model5 struct { 148 | ID int `xorm:"pk autoincr 'id'"` 149 | Name string 150 | Title string 151 | Fax string 152 | Web string 153 | Age int 154 | Right bool 155 | Counter int64 156 | } 157 | 158 | func NewModel5() *Model5 { 159 | m := new(Model5) 160 | m.Name = "Orm Benchmark" 161 | m.Title = "Just a Benchmark for fun" 162 | m.Fax = "99909990" 163 | m.Web = "http://blog.milkpod29.me" 164 | m.Age = 100 165 | m.Right = true 166 | m.Counter = 1000 167 | 168 | return m 169 | } 170 | 171 | // Model for Sqlboiler 172 | func NewModel6() *models.Model { 173 | m := new(models.Model) 174 | m.Name = "Orm Benchmark" 175 | m.Title = "Just a Benchmark for fun" 176 | m.Fax = "99909990" 177 | m.Web = "http://blog.milkpod29.me" 178 | m.Age = 100 179 | m.Right = true 180 | m.Counter = 1000 181 | 182 | return m 183 | } 184 | 185 | // Model for zorm 186 | type Model7 struct { 187 | zorm.EntityStruct 188 | ID int `column:"id"` 189 | Name string `column:"name"` 190 | Title string `column:"title"` 191 | Fax string `column:"fax"` 192 | Web string `column:"web"` 193 | Age int `column:"age"` 194 | Right bool `column:"\"right\""` 195 | Counter int64 `column:"counter"` 196 | } 197 | 198 | func (entity *Model7) GetTableName() string { 199 | return "models" 200 | } 201 | 202 | func (entity *Model7) GetPKColumnName() string { 203 | return "id" 204 | } 205 | 206 | func NewModel7() *Model7 { 207 | m := new(Model7) 208 | m.Name = "Orm Benchmark" 209 | m.Title = "Just a Benchmark for fun" 210 | m.Fax = "99909990" 211 | m.Web = "http://blog.milkpod29.me" 212 | m.Age = 100 213 | m.Right = true 214 | m.Counter = 1000 215 | 216 | return m 217 | } 218 | 219 | func NewReformModel() *r.ReformModels { 220 | m := new(r.ReformModels) 221 | m.Name = "Orm Benchmark" 222 | m.Title = "Just a Benchmark for fun" 223 | m.Fax = "99909990" 224 | m.Web = "http://blog.milkpod29.me" 225 | m.Age = 100 226 | m.Right = true 227 | m.Counter = 1000 228 | 229 | return m 230 | } 231 | 232 | func NewModelJet() *jetmodel.Models { 233 | m := new(jetmodel.Models) 234 | m.Name = "Orm Benchmark" 235 | m.Title = "Just a Benchmark for fun" 236 | m.Fax = "99909990" 237 | m.Web = "http://blog.milkpod29.me" 238 | m.Age = 100 239 | m.Right = true 240 | m.Counter = 1000 241 | 242 | return m 243 | } 244 | -------------------------------------------------------------------------------- /bench/pg.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "github.com/efectn/go-orm-benchmarks/helper" 5 | pgdb "github.com/go-pg/pg/v10" 6 | "testing" 7 | ) 8 | 9 | type Pg struct { 10 | helper.ORMInterface 11 | conn *pgdb.DB 12 | } 13 | 14 | func CreatePg() helper.ORMInterface { 15 | return &Pg{} 16 | } 17 | 18 | func (pg *Pg) Name() string { 19 | return "pg" 20 | } 21 | 22 | func (pg *Pg) Init() error { 23 | source := helper.SplitSource() 24 | pg.conn = pgdb.Connect(&pgdb.Options{ 25 | Addr: source["host"] + ":5432", 26 | User: source["user"], 27 | Password: source["password"], 28 | Database: source["dbname"], 29 | }) 30 | 31 | if err := pg.conn.Ping(ctx); err != nil { 32 | return err 33 | } 34 | 35 | return nil 36 | } 37 | 38 | func (pg *Pg) Close() error { 39 | return pg.conn.Close() 40 | } 41 | 42 | func (pg *Pg) Insert(b *testing.B) { 43 | m := NewModel() 44 | 45 | b.ReportAllocs() 46 | b.ResetTimer() 47 | 48 | for i := 0; i < b.N; i++ { 49 | m.Id = 0 50 | _, err := pg.conn.Model(m).Insert() 51 | if err != nil { 52 | helper.SetError(b, pg.Name(), "Insert", err.Error()) 53 | } 54 | } 55 | } 56 | 57 | func (pg *Pg) InsertMulti(b *testing.B) { 58 | ms := make([]*Model, 0, 100) 59 | for i := 0; i < 100; i++ { 60 | ms = append(ms, NewModel()) 61 | } 62 | 63 | b.ReportAllocs() 64 | b.ResetTimer() 65 | 66 | for i := 0; i < b.N; i++ { 67 | for _, m := range ms { 68 | m.Id = 0 69 | } 70 | 71 | _, err := pg.conn.Model(&ms).Insert() 72 | if err != nil { 73 | helper.SetError(b, pg.Name(), "InsertMulti", err.Error()) 74 | } 75 | } 76 | } 77 | 78 | func (pg *Pg) Update(b *testing.B) { 79 | m := NewModel() 80 | 81 | _, err := pg.conn.Model(m).Insert() 82 | if err != nil { 83 | helper.SetError(b, pg.Name(), "Update", err.Error()) 84 | } 85 | 86 | b.ReportAllocs() 87 | b.ResetTimer() 88 | 89 | for i := 0; i < b.N; i++ { 90 | _, err := pg.conn.Model(m).WherePK().Update() 91 | if err != nil { 92 | helper.SetError(b, pg.Name(), "Update", err.Error()) 93 | } 94 | } 95 | } 96 | 97 | func (pg *Pg) Read(b *testing.B) { 98 | m := NewModel() 99 | 100 | _, err := pg.conn.Model(m).Insert() 101 | if err != nil { 102 | helper.SetError(b, pg.Name(), "Read", err.Error()) 103 | } 104 | 105 | b.ReportAllocs() 106 | b.ResetTimer() 107 | 108 | for i := 0; i < b.N; i++ { 109 | err := pg.conn.Model(m).Limit(1).Select() 110 | if err != nil { 111 | helper.SetError(b, pg.Name(), "Read", err.Error()) 112 | } 113 | } 114 | } 115 | 116 | func (pg *Pg) ReadSlice(b *testing.B) { 117 | m := NewModel() 118 | for i := 0; i < 100; i++ { 119 | m.Id = 0 120 | _, err := pg.conn.Model(m).Insert() 121 | if err != nil { 122 | helper.SetError(b, pg.Name(), "ReadSlice", err.Error()) 123 | } 124 | } 125 | 126 | b.ReportAllocs() 127 | b.ResetTimer() 128 | 129 | for i := 0; i < b.N; i++ { 130 | var models []*Model 131 | err := pg.conn.Model(&models).Where("id > ?", 0).Limit(100).Select() 132 | if err != nil { 133 | helper.SetError(b, pg.Name(), "ReadSlice", err.Error()) 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /bench/pgx.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/efectn/go-orm-benchmarks/helper" 7 | pgxdb "github.com/jackc/pgx/v5" 8 | ) 9 | 10 | type Pgx struct { 11 | helper.ORMInterface 12 | conn *pgxdb.Conn 13 | } 14 | 15 | func CreatePgx() helper.ORMInterface { 16 | return &Pgx{} 17 | } 18 | 19 | func (pgx *Pgx) Name() string { 20 | return "pgx" 21 | } 22 | 23 | func (pgx *Pgx) Init() error { 24 | var err error 25 | pgx.conn, err = pgxdb.Connect(ctx, helper.OrmSource) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | return nil 31 | } 32 | 33 | func (pgx *Pgx) Close() error { 34 | return pgx.conn.Close(ctx) 35 | } 36 | 37 | func (pgx *Pgx) Insert(b *testing.B) { 38 | m := NewModel() 39 | 40 | b.ReportAllocs() 41 | b.ResetTimer() 42 | 43 | for i := 0; i < b.N; i++ { 44 | _, err := pgx.conn.Exec(ctx, sqlxInsertSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 45 | if err != nil { 46 | helper.SetError(b, pgx.Name(), "Insert", err.Error()) 47 | } 48 | } 49 | } 50 | 51 | func (pgx *Pgx) InsertMulti(b *testing.B) { 52 | var rows = make([][]interface{}, 0) 53 | 54 | m := NewModel() 55 | for i := 0; i < 100; i++ { 56 | rows = append(rows, []interface{}{m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter}) 57 | } 58 | 59 | b.ReportAllocs() 60 | b.ResetTimer() 61 | 62 | for i := 0; i < b.N; i++ { 63 | _, err := pgx.conn.CopyFrom(ctx, pgxdb.Identifier{"models"}, columns, pgxdb.CopyFromRows(rows)) 64 | if err != nil { 65 | helper.SetError(b, pgx.Name(), "InsertMulti", err.Error()) 66 | } 67 | } 68 | } 69 | 70 | func (pgx *Pgx) Update(b *testing.B) { 71 | m := NewModel() 72 | m.Id = 1 73 | 74 | _, err := pgx.conn.Exec(ctx, sqlxInsertSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 75 | if err != nil { 76 | helper.SetError(b, pgx.Name(), "Update", err.Error()) 77 | } 78 | 79 | b.ReportAllocs() 80 | b.ResetTimer() 81 | 82 | for i := 0; i < b.N; i++ { 83 | _, err := pgx.conn.Exec(ctx, sqlxUpdateSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter, m.Id) 84 | if err != nil { 85 | helper.SetError(b, pgx.Name(), "Update", err.Error()) 86 | } 87 | } 88 | } 89 | 90 | func (pgx *Pgx) Read(b *testing.B) { 91 | m := NewModel() 92 | m.Id = 1 93 | 94 | _, err := pgx.conn.Exec(ctx, sqlxInsertSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 95 | if err != nil { 96 | helper.SetError(b, pgx.Name(), "Read", err.Error()) 97 | } 98 | 99 | b.ReportAllocs() 100 | b.ResetTimer() 101 | 102 | for i := 0; i < b.N; i++ { 103 | var m Model 104 | err := pgx.conn.QueryRow(ctx, sqlxSelectSQL, 1).Scan( 105 | &m.Id, 106 | &m.Name, 107 | &m.Title, 108 | &m.Fax, 109 | &m.Web, 110 | &m.Age, 111 | &m.Right, 112 | &m.Counter, 113 | ) 114 | if err != nil { 115 | helper.SetError(b, pgx.Name(), "Read", err.Error()) 116 | } 117 | } 118 | } 119 | 120 | func (pgx *Pgx) ReadSlice(b *testing.B) { 121 | m := NewModel() 122 | for i := 0; i < 100; i++ { 123 | _, err := pgx.conn.Exec(ctx, sqlxInsertSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 124 | if err != nil { 125 | helper.SetError(b, pgx.Name(), "ReadSlice", err.Error()) 126 | } 127 | } 128 | 129 | b.ReportAllocs() 130 | b.ResetTimer() 131 | 132 | for i := 0; i < b.N; i++ { 133 | ms := make([]Model, 100) 134 | rows, err := pgx.conn.Query(ctx, sqlxSelectMultiSQL) 135 | if err != nil { 136 | helper.SetError(b, pgx.Name(), "ReadSlice", err.Error()) 137 | } 138 | 139 | for j := 0; rows.Next() && j < len(ms); j++ { 140 | err = rows.Scan( 141 | &ms[j].Id, 142 | &ms[j].Name, 143 | &ms[j].Title, 144 | &ms[j].Fax, 145 | &ms[j].Web, 146 | &ms[j].Age, 147 | &ms[j].Right, 148 | &ms[j].Counter, 149 | ) 150 | if err != nil { 151 | helper.SetError(b, pgx.Name(), "ReadSlice", err.Error()) 152 | } 153 | } 154 | err = rows.Err() 155 | if err != nil { 156 | helper.SetError(b, pgx.Name(), "ReadSlice", err.Error()) 157 | } 158 | 159 | rows.Close() 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /bench/pgx_pool.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/efectn/go-orm-benchmarks/helper" 7 | pgxdb "github.com/jackc/pgx/v5" 8 | "github.com/jackc/pgx/v5/pgxpool" 9 | ) 10 | 11 | type PgxPool struct { 12 | helper.ORMInterface 13 | conn *pgxpool.Pool 14 | } 15 | 16 | func CreatePgxPool() helper.ORMInterface { 17 | return &PgxPool{} 18 | } 19 | 20 | func (pgx *PgxPool) Name() string { 21 | return "pgx_pool" 22 | } 23 | 24 | func (pgx *PgxPool) Init() error { 25 | var err error 26 | pgx.conn, err = pgxpool.New(ctx, helper.OrmSource) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func (pgx *PgxPool) Close() error { 35 | pgx.conn.Close() 36 | 37 | return nil 38 | } 39 | 40 | func (pgx *PgxPool) Insert(b *testing.B) { 41 | m := NewModel() 42 | 43 | b.ReportAllocs() 44 | b.ResetTimer() 45 | 46 | for i := 0; i < b.N; i++ { 47 | _, err := pgx.conn.Exec(ctx, sqlxInsertSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 48 | if err != nil { 49 | helper.SetError(b, pgx.Name(), "Insert", err.Error()) 50 | } 51 | } 52 | } 53 | 54 | func (pgx *PgxPool) InsertMulti(b *testing.B) { 55 | var rows = make([][]interface{}, 0) 56 | 57 | m := NewModel() 58 | for i := 0; i < 100; i++ { 59 | rows = append(rows, []interface{}{m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter}) 60 | } 61 | 62 | b.ReportAllocs() 63 | b.ResetTimer() 64 | 65 | for i := 0; i < b.N; i++ { 66 | _, err := pgx.conn.CopyFrom(ctx, pgxdb.Identifier{"models"}, columns, pgxdb.CopyFromRows(rows)) 67 | if err != nil { 68 | helper.SetError(b, pgx.Name(), "InsertMulti", err.Error()) 69 | } 70 | } 71 | } 72 | 73 | func (pgx *PgxPool) Update(b *testing.B) { 74 | m := NewModel() 75 | m.Id = 1 76 | 77 | _, err := pgx.conn.Exec(ctx, sqlxInsertSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 78 | if err != nil { 79 | helper.SetError(b, pgx.Name(), "Update", err.Error()) 80 | } 81 | 82 | b.ReportAllocs() 83 | b.ResetTimer() 84 | 85 | for i := 0; i < b.N; i++ { 86 | _, err := pgx.conn.Exec(ctx, sqlxUpdateSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter, m.Id) 87 | if err != nil { 88 | helper.SetError(b, pgx.Name(), "Update", err.Error()) 89 | } 90 | } 91 | } 92 | 93 | func (pgx *PgxPool) Read(b *testing.B) { 94 | m := NewModel() 95 | m.Id = 1 96 | 97 | _, err := pgx.conn.Exec(ctx, sqlxInsertSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 98 | if err != nil { 99 | helper.SetError(b, pgx.Name(), "Read", err.Error()) 100 | } 101 | 102 | b.ReportAllocs() 103 | b.ResetTimer() 104 | 105 | for i := 0; i < b.N; i++ { 106 | var m Model 107 | err := pgx.conn.QueryRow(ctx, sqlxSelectSQL, 1).Scan( 108 | &m.Id, 109 | &m.Name, 110 | &m.Title, 111 | &m.Fax, 112 | &m.Web, 113 | &m.Age, 114 | &m.Right, 115 | &m.Counter, 116 | ) 117 | if err != nil { 118 | helper.SetError(b, pgx.Name(), "Read", err.Error()) 119 | } 120 | } 121 | } 122 | 123 | func (pgx *PgxPool) ReadSlice(b *testing.B) { 124 | m := NewModel() 125 | for i := 0; i < 100; i++ { 126 | _, err := pgx.conn.Exec(ctx, sqlxInsertSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 127 | if err != nil { 128 | helper.SetError(b, pgx.Name(), "ReadSlice", err.Error()) 129 | } 130 | } 131 | 132 | b.ReportAllocs() 133 | b.ResetTimer() 134 | 135 | for i := 0; i < b.N; i++ { 136 | ms := make([]Model, 100) 137 | rows, err := pgx.conn.Query(ctx, sqlxSelectMultiSQL) 138 | if err != nil { 139 | helper.SetError(b, pgx.Name(), "ReadSlice", err.Error()) 140 | } 141 | 142 | for j := 0; rows.Next() && j < len(ms); j++ { 143 | err = rows.Scan( 144 | &ms[j].Id, 145 | &ms[j].Name, 146 | &ms[j].Title, 147 | &ms[j].Fax, 148 | &ms[j].Web, 149 | &ms[j].Age, 150 | &ms[j].Right, 151 | &ms[j].Counter, 152 | ) 153 | if err != nil { 154 | helper.SetError(b, pgx.Name(), "ReadSlice", err.Error()) 155 | } 156 | } 157 | err = rows.Err() 158 | if err != nil { 159 | helper.SetError(b, pgx.Name(), "ReadSlice", err.Error()) 160 | } 161 | 162 | rows.Close() 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /bench/pop.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/efectn/go-orm-benchmarks/helper" 7 | 8 | popware "github.com/gobuffalo/pop/v6" 9 | _ "github.com/jackc/pgx/v5/stdlib" 10 | ) 11 | 12 | type Pop struct { 13 | helper.ORMInterface 14 | conn *popware.Connection 15 | } 16 | 17 | func CreatePop() helper.ORMInterface { 18 | return &Pop{} 19 | } 20 | 21 | func (pop *Pop) Name() string { 22 | return "pop" 23 | } 24 | 25 | func (pop *Pop) Init() error { 26 | var err error 27 | pop.conn, err = popware.NewConnection(&popware.ConnectionDetails{ 28 | URL: helper.ConvertSourceToDSN(), 29 | }) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | if err = pop.conn.Open(); err != nil { 35 | return err 36 | } 37 | 38 | return nil 39 | } 40 | 41 | func (pop *Pop) Close() error { 42 | return pop.conn.Close() 43 | } 44 | 45 | func (pop *Pop) Insert(b *testing.B) { 46 | m := NewModel3() 47 | 48 | b.ReportAllocs() 49 | b.ResetTimer() 50 | 51 | for i := 0; i < b.N; i++ { 52 | err := pop.conn.Create(m) 53 | if err != nil { 54 | helper.SetError(b, pop.Name(), "Insert", err.Error()) 55 | } 56 | } 57 | } 58 | 59 | func (pop *Pop) InsertMulti(b *testing.B) { 60 | helper.SetError(b, pop.Name(), "InsertMulti", "bulk-insert is not supported") 61 | } 62 | 63 | func (pop *Pop) Update(b *testing.B) { 64 | m := NewModel3() 65 | 66 | err := pop.conn.Create(m) 67 | if err != nil { 68 | helper.SetError(b, pop.Name(), "Update", err.Error()) 69 | } 70 | 71 | b.ReportAllocs() 72 | b.ResetTimer() 73 | 74 | for i := 0; i < b.N; i++ { 75 | err := pop.conn.Update(m) 76 | if err != nil { 77 | helper.SetError(b, pop.Name(), "Update", err.Error()) 78 | } 79 | } 80 | } 81 | 82 | func (pop *Pop) Read(b *testing.B) { 83 | m := NewModel3() 84 | 85 | err := pop.conn.Create(m) 86 | if err != nil { 87 | helper.SetError(b, pop.Name(), "Read", err.Error()) 88 | } 89 | 90 | b.ReportAllocs() 91 | b.ResetTimer() 92 | 93 | for i := 0; i < b.N; i++ { 94 | err := pop.conn.First(m) 95 | if err != nil { 96 | helper.SetError(b, pop.Name(), "Read", err.Error()) 97 | } 98 | } 99 | } 100 | 101 | func (pop *Pop) ReadSlice(b *testing.B) { 102 | m := NewModel3() 103 | for i := 0; i < 100; i++ { 104 | err := pop.conn.Create(m) 105 | if err != nil { 106 | helper.SetError(b, pop.Name(), "ReadSlice", err.Error()) 107 | } 108 | } 109 | 110 | b.ReportAllocs() 111 | b.ResetTimer() 112 | 113 | for i := 0; i < b.N; i++ { 114 | var ms []Model3 115 | err := pop.conn.Where("id > 0").Limit(100).All(&ms) 116 | if err != nil { 117 | helper.SetError(b, pop.Name(), "ReadSlice", err.Error()) 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /bench/raw.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "database/sql" 5 | "github.com/efectn/go-orm-benchmarks/helper" 6 | "strconv" 7 | "testing" 8 | ) 9 | 10 | const ( 11 | rawInsertBaseSQL = `INSERT INTO models (name, title, fax, web, age, "right", counter) VALUES ` 12 | rawInsertValuesSQL = `($1, $2, $3, $4, $5, $6, $7)` 13 | rawInsertSQL = rawInsertBaseSQL + rawInsertValuesSQL 14 | rawUpdateSQL = `UPDATE models SET name = $1, title = $2, fax = $3, web = $4, age = $5, "right" = $6, counter = $7 WHERE id = $8` 15 | rawSelectSQL = `SELECT id, name, title, fax, web, age, "right", counter FROM models WHERE id = $1` 16 | rawSelectMultiSQL = `SELECT id, name, title, fax, web, age, "right", counter FROM models WHERE id > 0 LIMIT 100` 17 | ) 18 | 19 | type Raw struct { 20 | helper.ORMInterface 21 | conn *sql.DB 22 | } 23 | 24 | func CreateRaw() helper.ORMInterface { 25 | return &Raw{} 26 | } 27 | 28 | func (raw *Raw) Name() string { 29 | return "raw" 30 | } 31 | 32 | func (raw *Raw) Init() error { 33 | var err error 34 | raw.conn, err = sql.Open("pgx", helper.OrmSource) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | return nil 40 | } 41 | 42 | func (raw *Raw) Close() error { 43 | return raw.conn.Close() 44 | } 45 | 46 | func (raw *Raw) Insert(b *testing.B) { 47 | m := NewModel() 48 | 49 | b.ReportAllocs() 50 | b.ResetTimer() 51 | 52 | for i := 0; i < b.N; i++ { 53 | // pq dose not support the LastInsertId method. 54 | _, err := raw.conn.Exec(rawInsertSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 55 | if err != nil { 56 | helper.SetError(b, raw.Name(), "Insert", err.Error()) 57 | } 58 | } 59 | } 60 | 61 | func (raw *Raw) InsertMulti(b *testing.B) { 62 | ms := make([]*Model, 0, 100) 63 | for i := 0; i < 100; i++ { 64 | ms = append(ms, NewModel()) 65 | } 66 | 67 | b.ReportAllocs() 68 | b.ResetTimer() 69 | 70 | var valuesSQL string 71 | counter := 1 72 | for i := 0; i < 100; i++ { 73 | hoge := "" 74 | for j := 0; j < 7; j++ { 75 | if j != 6 { 76 | hoge += "$" + strconv.Itoa(counter) + "," 77 | } else { 78 | hoge += "$" + strconv.Itoa(counter) 79 | } 80 | counter++ 81 | 82 | } 83 | if i != 99 { 84 | valuesSQL += "(" + hoge + ")," 85 | } else { 86 | valuesSQL += "(" + hoge + ")" 87 | } 88 | } 89 | 90 | for i := 0; i < b.N; i++ { 91 | nFields := 7 92 | query := rawInsertBaseSQL + valuesSQL 93 | args := make([]interface{}, len(ms)*nFields) 94 | for j := range ms { 95 | offset := j * nFields 96 | args[offset+0] = ms[j].Name 97 | args[offset+1] = ms[j].Title 98 | args[offset+2] = ms[j].Fax 99 | args[offset+3] = ms[j].Web 100 | args[offset+4] = ms[j].Age 101 | args[offset+5] = ms[j].Right 102 | args[offset+6] = ms[j].Counter 103 | } 104 | // pq dose not support the LastInsertId method. 105 | _, err := raw.conn.Exec(query, args...) 106 | if err != nil { 107 | helper.SetError(b, raw.Name(), "InsertMulti", err.Error()) 108 | } 109 | } 110 | } 111 | 112 | func (raw *Raw) Update(b *testing.B) { 113 | m := NewModel() 114 | 115 | _, err := raw.conn.Exec(rawInsertSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 116 | if err != nil { 117 | helper.SetError(b, raw.Name(), "Update", err.Error()) 118 | } 119 | 120 | b.ReportAllocs() 121 | b.ResetTimer() 122 | 123 | for i := 0; i < b.N; i++ { 124 | _, err := raw.conn.Exec(rawUpdateSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter, m.Id) 125 | if err != nil { 126 | helper.SetError(b, raw.Name(), "Update", err.Error()) 127 | } 128 | } 129 | } 130 | 131 | func (raw *Raw) Read(b *testing.B) { 132 | m := NewModel() 133 | 134 | _, err := raw.conn.Exec(rawInsertSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 135 | if err != nil { 136 | helper.SetError(b, raw.Name(), "Read", err.Error()) 137 | } 138 | 139 | b.ReportAllocs() 140 | b.ResetTimer() 141 | 142 | for i := 0; i < b.N; i++ { 143 | var mout Model 144 | err := raw.conn.QueryRow(rawSelectSQL, 1).Scan( 145 | //err := stmt.QueryRow(m.Id).Scan( 146 | &mout.Id, 147 | &mout.Name, 148 | &mout.Title, 149 | &mout.Fax, 150 | &mout.Web, 151 | &mout.Age, 152 | &mout.Right, 153 | &mout.Counter, 154 | ) 155 | if err != nil { 156 | helper.SetError(b, raw.Name(), "Read", err.Error()) 157 | } 158 | } 159 | } 160 | 161 | func (raw *Raw) ReadSlice(b *testing.B) { 162 | m := NewModel() 163 | for i := 0; i < 100; i++ { 164 | _, err := raw.conn.Exec(rawInsertSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 165 | if err != nil { 166 | helper.SetError(b, raw.Name(), "ReadSlice", err.Error()) 167 | } 168 | } 169 | 170 | b.ReportAllocs() 171 | b.ResetTimer() 172 | 173 | for i := 0; i < b.N; i++ { 174 | var j int 175 | models := make([]Model, 100) 176 | rows, err := raw.conn.Query(rawSelectMultiSQL) 177 | if err != nil { 178 | helper.SetError(b, raw.Name(), "ReadSlice", err.Error()) 179 | } 180 | 181 | for j = 0; rows.Next() && j < len(models); j++ { 182 | err = rows.Scan( 183 | &models[j].Id, 184 | &models[j].Name, 185 | &models[j].Title, 186 | &models[j].Fax, 187 | &models[j].Web, 188 | &models[j].Age, 189 | &models[j].Right, 190 | &models[j].Counter, 191 | ) 192 | if err != nil { 193 | helper.SetError(b, raw.Name(), "ReadSlice", err.Error()) 194 | } 195 | } 196 | err = rows.Err() 197 | if err != nil { 198 | helper.SetError(b, raw.Name(), "ReadSlice", err.Error()) 199 | } 200 | err = rows.Close() 201 | if err != nil { 202 | helper.SetError(b, raw.Name(), "ReadSlice", err.Error()) 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /bench/reform.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "database/sql" 5 | "testing" 6 | 7 | "github.com/efectn/go-orm-benchmarks/helper" 8 | 9 | r "github.com/efectn/go-orm-benchmarks/bench/reform" 10 | _ "github.com/jackc/pgx/v5/stdlib" 11 | "gopkg.in/reform.v1/dialects/postgresql" 12 | 13 | reformware "gopkg.in/reform.v1" 14 | ) 15 | 16 | type Reform struct { 17 | helper.ORMInterface 18 | conn *reformware.DB 19 | db *sql.DB 20 | } 21 | 22 | func CreateReform() helper.ORMInterface { 23 | return &Reform{} 24 | } 25 | 26 | func (reform *Reform) Name() string { 27 | return "reform" 28 | } 29 | 30 | func (reform *Reform) Init() error { 31 | var err error 32 | reform.db, err = sql.Open("pgx", helper.OrmSource) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | reform.conn = reformware.NewDB(reform.db, postgresql.Dialect, nil) 38 | 39 | return nil 40 | } 41 | 42 | func (reform *Reform) Close() error { 43 | return reform.db.Close() 44 | } 45 | 46 | func (reform *Reform) Insert(b *testing.B) { 47 | m := NewReformModel() 48 | 49 | b.ReportAllocs() 50 | b.ResetTimer() 51 | 52 | for i := 0; i < b.N; i++ { 53 | err := reform.conn.Save(m) 54 | if err != nil { 55 | helper.SetError(b, reform.Name(), "Insert", err.Error()) 56 | } 57 | } 58 | } 59 | 60 | func (reform *Reform) InsertMulti(b *testing.B) { 61 | ms := make([]reformware.Struct, 0, 100) 62 | for i := 0; i < 100; i++ { 63 | ms = append(ms, NewReformModel()) 64 | } 65 | 66 | b.ReportAllocs() 67 | b.ResetTimer() 68 | 69 | for i := 0; i < b.N; i++ { 70 | err := reform.conn.InsertMulti(ms...) 71 | if err != nil { 72 | helper.SetError(b, reform.Name(), "InsertMulti", err.Error()) 73 | } 74 | } 75 | } 76 | 77 | func (reform *Reform) Update(b *testing.B) { 78 | m := NewReformModel() 79 | 80 | err := reform.conn.Save(m) 81 | if err != nil { 82 | helper.SetError(b, reform.Name(), "Update", err.Error()) 83 | } 84 | 85 | b.ReportAllocs() 86 | b.ResetTimer() 87 | 88 | for i := 0; i < b.N; i++ { 89 | err := reform.conn.Update(m) 90 | if err != nil { 91 | helper.SetError(b, reform.Name(), "Update", err.Error()) 92 | } 93 | } 94 | } 95 | 96 | func (reform *Reform) Read(b *testing.B) { 97 | m := NewReformModel() 98 | 99 | err := reform.conn.Save(m) 100 | if err != nil { 101 | helper.SetError(b, reform.Name(), "Read", err.Error()) 102 | } 103 | 104 | b.ReportAllocs() 105 | b.ResetTimer() 106 | 107 | for i := 0; i < b.N; i++ { 108 | _, err := reform.conn.FindByPrimaryKeyFrom(r.ReformModelsTable, m.ID) 109 | if err != nil { 110 | helper.SetError(b, reform.Name(), "Read", err.Error()) 111 | } 112 | } 113 | } 114 | 115 | func (reform *Reform) ReadSlice(b *testing.B) { 116 | m := NewReformModel() 117 | for i := 0; i < 100; i++ { 118 | err := reform.conn.Save(m) 119 | if err != nil { 120 | helper.SetError(b, reform.Name(), "ReadSlice", err.Error()) 121 | } 122 | } 123 | 124 | b.ReportAllocs() 125 | b.ResetTimer() 126 | 127 | for i := 0; i < b.N; i++ { 128 | _, err := reform.conn.SelectAllFrom(r.ReformModelsTable, "WHERE id > 0 LIMIT 100") 129 | if err != nil { 130 | helper.SetError(b, reform.Name(), "ReadSlice", err.Error()) 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /bench/reform/reform_models.go: -------------------------------------------------------------------------------- 1 | package reform 2 | 3 | //go:generate reform 4 | 5 | // reform_models represents a row in models table. 6 | // 7 | //reform:models 8 | type ReformModels struct { 9 | ID int `reform:"id,pk"` 10 | Name string `reform:"name"` 11 | Title string `reform:"title"` 12 | Fax string `reform:"fax"` 13 | Web string `reform:"web"` 14 | Age int32 `reform:"age"` 15 | Right bool `reform:"right"` 16 | Counter int64 `reform:"counter"` 17 | } 18 | -------------------------------------------------------------------------------- /bench/reform/reform_models_reform.go: -------------------------------------------------------------------------------- 1 | // Code generated by gopkg.in/reform.v1. DO NOT EDIT. 2 | 3 | package reform 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | 9 | "gopkg.in/reform.v1" 10 | "gopkg.in/reform.v1/parse" 11 | ) 12 | 13 | type reformModelsTableType struct { 14 | s parse.StructInfo 15 | z []interface{} 16 | } 17 | 18 | // Schema returns a schema name in SQL database (""). 19 | func (v *reformModelsTableType) Schema() string { 20 | return v.s.SQLSchema 21 | } 22 | 23 | // Name returns a view or table name in SQL database ("models"). 24 | func (v *reformModelsTableType) Name() string { 25 | return v.s.SQLName 26 | } 27 | 28 | // Columns returns a new slice of column names for that view or table in SQL database. 29 | func (v *reformModelsTableType) Columns() []string { 30 | return []string{ 31 | "id", 32 | "name", 33 | "title", 34 | "fax", 35 | "web", 36 | "age", 37 | "right", 38 | "counter", 39 | } 40 | } 41 | 42 | // NewStruct makes a new struct for that view or table. 43 | func (v *reformModelsTableType) NewStruct() reform.Struct { 44 | return new(ReformModels) 45 | } 46 | 47 | // NewRecord makes a new record for that table. 48 | func (v *reformModelsTableType) NewRecord() reform.Record { 49 | return new(ReformModels) 50 | } 51 | 52 | // PKColumnIndex returns an index of primary key column for that table in SQL database. 53 | func (v *reformModelsTableType) PKColumnIndex() uint { 54 | return uint(v.s.PKFieldIndex) 55 | } 56 | 57 | // ReformModelsTable represents models view or table in SQL database. 58 | var ReformModelsTable = &reformModelsTableType{ 59 | s: parse.StructInfo{ 60 | Type: "ReformModels", 61 | SQLName: "models", 62 | Fields: []parse.FieldInfo{ 63 | {Name: "ID", Type: "int", Column: "id"}, 64 | {Name: "Name", Type: "string", Column: "name"}, 65 | {Name: "Title", Type: "string", Column: "title"}, 66 | {Name: "Fax", Type: "string", Column: "fax"}, 67 | {Name: "Web", Type: "string", Column: "web"}, 68 | {Name: "Age", Type: "int32", Column: "age"}, 69 | {Name: "Right", Type: "bool", Column: "right"}, 70 | {Name: "Counter", Type: "int64", Column: "counter"}, 71 | }, 72 | PKFieldIndex: 0, 73 | }, 74 | z: new(ReformModels).Values(), 75 | } 76 | 77 | // String returns a string representation of this struct or record. 78 | func (s ReformModels) String() string { 79 | res := make([]string, 8) 80 | res[0] = "ID: " + reform.Inspect(s.ID, true) 81 | res[1] = "Name: " + reform.Inspect(s.Name, true) 82 | res[2] = "Title: " + reform.Inspect(s.Title, true) 83 | res[3] = "Fax: " + reform.Inspect(s.Fax, true) 84 | res[4] = "Web: " + reform.Inspect(s.Web, true) 85 | res[5] = "Age: " + reform.Inspect(s.Age, true) 86 | res[6] = "Right: " + reform.Inspect(s.Right, true) 87 | res[7] = "Counter: " + reform.Inspect(s.Counter, true) 88 | return strings.Join(res, ", ") 89 | } 90 | 91 | // Values returns a slice of struct or record field values. 92 | // Returned interface{} values are never untyped nils. 93 | func (s *ReformModels) Values() []interface{} { 94 | return []interface{}{ 95 | s.ID, 96 | s.Name, 97 | s.Title, 98 | s.Fax, 99 | s.Web, 100 | s.Age, 101 | s.Right, 102 | s.Counter, 103 | } 104 | } 105 | 106 | // Pointers returns a slice of pointers to struct or record fields. 107 | // Returned interface{} values are never untyped nils. 108 | func (s *ReformModels) Pointers() []interface{} { 109 | return []interface{}{ 110 | &s.ID, 111 | &s.Name, 112 | &s.Title, 113 | &s.Fax, 114 | &s.Web, 115 | &s.Age, 116 | &s.Right, 117 | &s.Counter, 118 | } 119 | } 120 | 121 | // View returns View object for that struct. 122 | func (s *ReformModels) View() reform.View { 123 | return ReformModelsTable 124 | } 125 | 126 | // Table returns Table object for that record. 127 | func (s *ReformModels) Table() reform.Table { 128 | return ReformModelsTable 129 | } 130 | 131 | // PKValue returns a value of primary key for that record. 132 | // Returned interface{} value is never untyped nil. 133 | func (s *ReformModels) PKValue() interface{} { 134 | return s.ID 135 | } 136 | 137 | // PKPointer returns a pointer to primary key field for that record. 138 | // Returned interface{} value is never untyped nil. 139 | func (s *ReformModels) PKPointer() interface{} { 140 | return &s.ID 141 | } 142 | 143 | // HasPK returns true if record has non-zero primary key set, false otherwise. 144 | func (s *ReformModels) HasPK() bool { 145 | return s.ID != ReformModelsTable.z[ReformModelsTable.s.PKFieldIndex] 146 | } 147 | 148 | // SetPK sets record primary key, if possible. 149 | // 150 | // Deprecated: prefer direct field assignment where possible: s.ID = pk. 151 | func (s *ReformModels) SetPK(pk interface{}) { 152 | reform.SetPK(s, pk) 153 | } 154 | 155 | // check interfaces 156 | var ( 157 | _ reform.View = ReformModelsTable 158 | _ reform.Struct = (*ReformModels)(nil) 159 | _ reform.Table = ReformModelsTable 160 | _ reform.Record = (*ReformModels)(nil) 161 | _ fmt.Stringer = (*ReformModels)(nil) 162 | ) 163 | 164 | func init() { 165 | parse.AssertUpToDate(&ReformModelsTable.s, new(ReformModels)) 166 | } 167 | -------------------------------------------------------------------------------- /bench/rel.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "context" 5 | "github.com/efectn/go-orm-benchmarks/helper" 6 | "testing" 7 | 8 | "github.com/go-rel/postgres" 9 | relware "github.com/go-rel/rel" 10 | "github.com/go-rel/rel/where" 11 | ) 12 | 13 | type Rel struct { 14 | helper.ORMInterface 15 | conn relware.Repository 16 | db relware.Adapter 17 | } 18 | 19 | func CreateRel() helper.ORMInterface { 20 | return &Rel{} 21 | } 22 | 23 | func (rel *Rel) Name() string { 24 | return "rel" 25 | } 26 | 27 | func (rel *Rel) Init() error { 28 | var err error 29 | rel.db, err = postgres.Open(helper.OrmSource) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | rel.conn = relware.New(rel.db) 35 | if err := rel.conn.Ping(ctx); err != nil { 36 | return err 37 | } 38 | 39 | // Disable debug logging 40 | rel.conn.Instrumentation(func(ctx context.Context, op string, message string, args ...any) func(err error) { 41 | return func(err error) { 42 | if err != nil { 43 | panic(err) 44 | } 45 | } 46 | }) 47 | 48 | return nil 49 | } 50 | 51 | func (rel *Rel) Close() error { 52 | return rel.db.Close() 53 | } 54 | 55 | func (rel *Rel) Insert(b *testing.B) { 56 | m := NewModel3() 57 | 58 | b.ReportAllocs() 59 | b.ResetTimer() 60 | 61 | for i := 0; i < b.N; i++ { 62 | m.ID = 0 63 | err := rel.conn.Insert(ctx, m) 64 | if err != nil { 65 | helper.SetError(b, rel.Name(), "Insert", err.Error()) 66 | } 67 | } 68 | } 69 | 70 | func (rel *Rel) InsertMulti(b *testing.B) { 71 | ms := make([]*Model3, 0, 100) 72 | for i := 0; i < 100; i++ { 73 | ms = append(ms, NewModel3()) 74 | } 75 | 76 | b.ReportAllocs() 77 | b.ResetTimer() 78 | 79 | for i := 0; i < b.N; i++ { 80 | for _, m := range ms { 81 | m.ID = 0 82 | } 83 | err := rel.conn.InsertAll(ctx, &ms) 84 | if err != nil { 85 | helper.SetError(b, rel.Name(), "InsertMulti", err.Error()) 86 | } 87 | } 88 | } 89 | 90 | func (rel *Rel) Update(b *testing.B) { 91 | m := NewModel3() 92 | m.ID = 0 93 | err := rel.conn.Insert(ctx, m) 94 | if err != nil { 95 | helper.SetError(b, rel.Name(), "Update", err.Error()) 96 | } 97 | 98 | b.ReportAllocs() 99 | b.ResetTimer() 100 | 101 | for i := 0; i < b.N; i++ { 102 | err := rel.conn.Update(ctx, m) 103 | if err != nil { 104 | helper.SetError(b, rel.Name(), "Update", err.Error()) 105 | } 106 | } 107 | } 108 | 109 | func (rel *Rel) Read(b *testing.B) { 110 | m := NewModel3() 111 | m.ID = 0 112 | err := rel.conn.Insert(ctx, m) 113 | if err != nil { 114 | helper.SetError(b, rel.Name(), "Read", err.Error()) 115 | } 116 | 117 | b.ReportAllocs() 118 | b.ResetTimer() 119 | 120 | for i := 0; i < b.N; i++ { 121 | err := rel.conn.Find(ctx, m) 122 | if err != nil { 123 | helper.SetError(b, rel.Name(), "Read", err.Error()) 124 | } 125 | } 126 | } 127 | 128 | func (rel *Rel) ReadSlice(b *testing.B) { 129 | m := NewModel3() 130 | for i := 0; i < 100; i++ { 131 | m.ID = 0 132 | err := rel.conn.Insert(ctx, m) 133 | if err != nil { 134 | helper.SetError(b, rel.Name(), "ReadSlice", err.Error()) 135 | } 136 | } 137 | 138 | b.ReportAllocs() 139 | b.ResetTimer() 140 | 141 | for i := 0; i < b.N; i++ { 142 | var ms []Model3 143 | err := rel.conn.FindAll(ctx, &ms, where.Gt("id", 0), relware.Limit(100)) 144 | if err != nil { 145 | helper.SetError(b, rel.Name(), "ReadSlice", err.Error()) 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /bench/sq.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "database/sql" 5 | "testing" 6 | 7 | qm "github.com/bokwoon95/sq" 8 | "github.com/efectn/go-orm-benchmarks/bench/sq/db" 9 | "github.com/efectn/go-orm-benchmarks/helper" 10 | ) 11 | 12 | // sq supports both templated queries and a query builder. 13 | // This benchmark only uses the query builder. 14 | type Sq struct { 15 | helper.ORMInterface 16 | conn *sql.DB 17 | } 18 | 19 | func CreateSq() helper.ORMInterface { 20 | return &Sq{} 21 | } 22 | 23 | func (sq *Sq) Name() string { 24 | return "sq" 25 | } 26 | 27 | func (sq *Sq) Init() error { 28 | var err error 29 | sq.conn, err = sql.Open("pgx", helper.OrmSource) 30 | if err != nil { 31 | sq.conn = nil 32 | return err 33 | } 34 | return nil 35 | } 36 | 37 | func (sq *Sq) Close() error { 38 | return sq.conn.Close() 39 | } 40 | 41 | func (sq *Sq) Insert(b *testing.B) { 42 | m := NewModelAlt() 43 | 44 | b.ReportAllocs() 45 | b.ResetTimer() 46 | 47 | for i := 0; i < b.N; i++ { 48 | m.Id = 0 49 | err := sq.insertModel(&m) 50 | if err != nil { 51 | helper.SetError(b, sq.Name(), "Insert", err.Error()) 52 | } 53 | } 54 | } 55 | 56 | func (sq *Sq) InsertMulti(b *testing.B) { 57 | ms := make([]Model, 0, 100) 58 | for i := 0; i < 100; i++ { 59 | ms = append(ms, NewModelAlt()) 60 | } 61 | 62 | b.ReportAllocs() 63 | b.ResetTimer() 64 | 65 | for i := 0; i < b.N; i++ { 66 | tbl := qm.New[db.MODELS]("") 67 | query := qm.Postgres.InsertInto(tbl).Columns(tbl.NAME, tbl.TITLE, tbl.FAX, tbl.WEB, tbl.AGE, tbl.RIGHT, tbl.COUNTER) 68 | for _, m := range ms { 69 | query = query.Values(m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 70 | } 71 | _, err := qm.Exec(sq.conn, query) 72 | if err != nil { 73 | helper.SetError(b, sq.Name(), "InsertMulti", err.Error()) 74 | } 75 | } 76 | } 77 | 78 | func (sq *Sq) Update(b *testing.B) { 79 | m := NewModelAlt() 80 | 81 | err := sq.insertModel(&m) 82 | if err != nil { 83 | helper.SetError(b, sq.Name(), "Update", err.Error()) 84 | } 85 | 86 | b.ReportAllocs() 87 | b.ResetTimer() 88 | 89 | for i := 0; i < b.N; i++ { 90 | tbl := qm.New[db.MODELS]("") 91 | query := qm.Postgres.Update(tbl).Set( 92 | tbl.NAME.SetString(m.Name), 93 | tbl.TITLE.SetString(m.Title), 94 | tbl.FAX.SetString(m.Fax), 95 | tbl.WEB.SetString(m.Web), 96 | tbl.AGE.SetInt(m.Age), 97 | tbl.RIGHT.SetBool(m.Right), 98 | tbl.COUNTER.SetInt64(m.Counter), 99 | ).Where(tbl.ID.EqInt(m.Id)) 100 | _, err = qm.Exec(sq.conn, query) 101 | if err != nil { 102 | helper.SetError(b, sq.Name(), "Update", err.Error()) 103 | } 104 | } 105 | } 106 | 107 | func (sq *Sq) Read(b *testing.B) { 108 | m := NewModelAlt() 109 | 110 | err := sq.insertModel(&m) 111 | if err != nil { 112 | helper.SetError(b, sq.Name(), "Read", err.Error()) 113 | } 114 | 115 | b.ReportAllocs() 116 | b.ResetTimer() 117 | 118 | for i := 0; i < b.N; i++ { 119 | tbl := qm.New[db.MODELS]("") 120 | query := qm.Postgres.From(tbl).Where(tbl.ID.EqInt(m.Id)) 121 | _, err = qm.FetchOne(sq.conn, query, sq.modelRowMapper(tbl)) 122 | if err != nil { 123 | helper.SetError(b, sq.Name(), "Read", err.Error()) 124 | } 125 | } 126 | } 127 | 128 | func (sq *Sq) ReadSlice(b *testing.B) { 129 | m := NewModelAlt() 130 | 131 | for i := 0; i < 100; i++ { 132 | m.Id = 0 133 | err := sq.insertModel(&m) 134 | if err != nil { 135 | helper.SetError(b, sq.Name(), "ReadSlice", err.Error()) 136 | } 137 | } 138 | 139 | b.ReportAllocs() 140 | b.ResetTimer() 141 | 142 | for i := 0; i < b.N; i++ { 143 | tbl := qm.New[db.MODELS]("") 144 | query := qm.Postgres.From(tbl).Where(tbl.ID.GtInt(0)).Limit(100) 145 | _, err := qm.FetchAll(sq.conn, query, sq.modelRowMapper(tbl)) 146 | if err != nil { 147 | helper.SetError(b, sq.Name(), "ReadSlice", err.Error()) 148 | } 149 | } 150 | } 151 | 152 | func (sq *Sq) insertModel(m *Model) error { 153 | var err error 154 | tbl := qm.New[db.MODELS]("") 155 | query := qm.Postgres.InsertInto(tbl). 156 | Columns(tbl.NAME, tbl.TITLE, tbl.FAX, tbl.WEB, tbl.AGE, tbl.RIGHT, tbl.COUNTER). 157 | Values(m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 158 | m.Id, err = qm.FetchOne(sq.conn, query, func(r *qm.Row) int { 159 | return r.IntField(tbl.ID) 160 | }) 161 | return err 162 | } 163 | 164 | func (sq *Sq) modelRowMapper(tbl db.MODELS) func(*qm.Row) Model { 165 | return func(r *qm.Row) Model { 166 | return Model{ 167 | Id: r.IntField(tbl.ID), 168 | Name: r.StringField(tbl.NAME), 169 | Title: r.StringField(tbl.TITLE), 170 | Fax: r.StringField(tbl.FAX), 171 | Web: r.StringField(tbl.WEB), 172 | Age: r.IntField(tbl.AGE), 173 | Right: r.BoolField(tbl.RIGHT), 174 | Counter: r.Int64Field(tbl.COUNTER), 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /bench/sq/db/models.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/bokwoon95/sq" 5 | ) 6 | 7 | type MODELS struct { 8 | sq.TableStruct 9 | ID sq.NumberField 10 | NAME sq.StringField 11 | TITLE sq.StringField 12 | FAX sq.StringField 13 | WEB sq.StringField 14 | AGE sq.NumberField 15 | RIGHT sq.BooleanField 16 | COUNTER sq.NumberField 17 | } 18 | -------------------------------------------------------------------------------- /bench/sqlboiler.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "database/sql" 5 | "testing" 6 | 7 | "github.com/efectn/go-orm-benchmarks/helper" 8 | 9 | models "github.com/efectn/go-orm-benchmarks/bench/sqlboiler" 10 | _ "github.com/jackc/pgx/v5/stdlib" 11 | "github.com/volatiletech/sqlboiler/v4/boil" 12 | "github.com/volatiletech/sqlboiler/v4/queries/qm" 13 | ) 14 | 15 | type Sqlboiler struct { 16 | helper.ORMInterface 17 | conn *sql.DB 18 | } 19 | 20 | func CreateSqlboiler() helper.ORMInterface { 21 | return &Sqlboiler{} 22 | } 23 | 24 | func (sqlboiler *Sqlboiler) Name() string { 25 | return "sqlboiler" 26 | } 27 | 28 | func (sqlboiler *Sqlboiler) Init() error { 29 | var err error 30 | sqlboiler.conn, err = sql.Open("pgx", helper.OrmSource) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | boil.SetDB(sqlboiler.conn) 36 | 37 | return nil 38 | } 39 | 40 | func (sqlboiler *Sqlboiler) Close() error { 41 | return sqlboiler.conn.Close() 42 | } 43 | 44 | func (sqlboiler *Sqlboiler) Insert(b *testing.B) { 45 | m := NewModel6() 46 | 47 | b.ReportAllocs() 48 | b.ResetTimer() 49 | 50 | for i := 0; i < b.N; i++ { 51 | m.ID = 0 52 | err := m.Insert(ctx, sqlboiler.conn, boil.Infer()) 53 | if err != nil { 54 | helper.SetError(b, sqlboiler.Name(), "Insert", err.Error()) 55 | } 56 | } 57 | } 58 | 59 | func (sqlboiler *Sqlboiler) InsertMulti(b *testing.B) { 60 | helper.SetError(b, sqlboiler.Name(), "InsertMulti", "bulk-insert is not supported") 61 | } 62 | 63 | func (sqlboiler *Sqlboiler) Update(b *testing.B) { 64 | m := NewModel6() 65 | m.ID = 0 66 | err := m.Insert(ctx, sqlboiler.conn, boil.Infer()) 67 | if err != nil { 68 | helper.SetError(b, sqlboiler.Name(), "Update", err.Error()) 69 | } 70 | 71 | b.ReportAllocs() 72 | b.ResetTimer() 73 | 74 | for i := 0; i < b.N; i++ { 75 | _, err := m.Update(ctx, sqlboiler.conn, boil.Infer()) 76 | if err != nil { 77 | helper.SetError(b, sqlboiler.Name(), "Update", err.Error()) 78 | } 79 | } 80 | } 81 | 82 | func (sqlboiler *Sqlboiler) Read(b *testing.B) { 83 | m := NewModel6() 84 | m.ID = 0 85 | err := m.Insert(ctx, sqlboiler.conn, boil.Infer()) 86 | if err != nil { 87 | helper.SetError(b, sqlboiler.Name(), "Read", err.Error()) 88 | } 89 | 90 | b.ReportAllocs() 91 | b.ResetTimer() 92 | 93 | for i := 0; i < b.N; i++ { 94 | _, err := models.Models(qm.Where("id = 0")).Exec(sqlboiler.conn) 95 | if err != nil { 96 | helper.SetError(b, sqlboiler.Name(), "Read", err.Error()) 97 | } 98 | } 99 | } 100 | 101 | func (sqlboiler *Sqlboiler) ReadSlice(b *testing.B) { 102 | m := NewModel6() 103 | for i := 0; i < 100; i++ { 104 | m.ID = 0 105 | err := m.Insert(ctx, sqlboiler.conn, boil.Infer()) 106 | if err != nil { 107 | helper.SetError(b, sqlboiler.Name(), "ReadSlice", err.Error()) 108 | } 109 | } 110 | 111 | b.ReportAllocs() 112 | b.ResetTimer() 113 | 114 | for i := 0; i < b.N; i++ { 115 | _, err := models.Models(qm.Where("id > 0"), qm.Limit(100)).All(ctx, sqlboiler.conn) 116 | if err != nil { 117 | helper.SetError(b, sqlboiler.Name(), "ReadSlice", err.Error()) 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /bench/sqlboiler/boil_queries.go: -------------------------------------------------------------------------------- 1 | // Code generated by SQLBoiler 4.8.3 (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/v4/drivers" 8 | "github.com/volatiletech/sqlboiler/v4/queries" 9 | "github.com/volatiletech/sqlboiler/v4/queries/qm" 10 | ) 11 | 12 | var dialect = drivers.Dialect{ 13 | LQ: 0x22, 14 | RQ: 0x22, 15 | 16 | UseIndexPlaceholders: true, 17 | UseLastInsertID: false, 18 | UseSchema: false, 19 | UseDefaultKeyword: true, 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 | -------------------------------------------------------------------------------- /bench/sqlboiler/boil_table_names.go: -------------------------------------------------------------------------------- 1 | // Code generated by SQLBoiler 4.8.3 (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 | Model string 8 | Models string 9 | ModelsGorp string 10 | }{ 11 | Model: "model", 12 | Models: "models", 13 | ModelsGorp: "models_gorp", 14 | } 15 | -------------------------------------------------------------------------------- /bench/sqlboiler/boil_types.go: -------------------------------------------------------------------------------- 1 | // Code generated by SQLBoiler 4.8.3 (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/friendsofgo/errors" 10 | "github.com/volatiletech/sqlboiler/v4/boil" 11 | "github.com/volatiletech/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 | -------------------------------------------------------------------------------- /bench/sqlboiler/psql_upsert.go: -------------------------------------------------------------------------------- 1 | // Code generated by SQLBoiler 4.8.3 (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/v4/drivers" 11 | "github.com/volatiletech/strmangle" 12 | ) 13 | 14 | // buildUpsertQueryPostgres builds a SQL statement string using the upsertData provided. 15 | func buildUpsertQueryPostgres(dia drivers.Dialect, tableName string, updateOnConflict bool, ret, update, conflict, whitelist []string) string { 16 | conflict = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, conflict) 17 | whitelist = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, whitelist) 18 | ret = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, ret) 19 | 20 | buf := strmangle.GetBuffer() 21 | defer strmangle.PutBuffer(buf) 22 | 23 | columns := "DEFAULT VALUES" 24 | if len(whitelist) != 0 { 25 | columns = fmt.Sprintf("(%s) VALUES (%s)", 26 | strings.Join(whitelist, ", "), 27 | strmangle.Placeholders(dia.UseIndexPlaceholders, len(whitelist), 1, 1)) 28 | } 29 | 30 | fmt.Fprintf( 31 | buf, 32 | "INSERT INTO %s %s ON CONFLICT ", 33 | tableName, 34 | columns, 35 | ) 36 | 37 | if !updateOnConflict || len(update) == 0 { 38 | buf.WriteString("DO NOTHING") 39 | } else { 40 | buf.WriteByte('(') 41 | buf.WriteString(strings.Join(conflict, ", ")) 42 | buf.WriteString(") DO UPDATE SET ") 43 | 44 | for i, v := range update { 45 | if i != 0 { 46 | buf.WriteByte(',') 47 | } 48 | quoted := strmangle.IdentQuote(dia.LQ, dia.RQ, v) 49 | buf.WriteString(quoted) 50 | buf.WriteString(" = EXCLUDED.") 51 | buf.WriteString(quoted) 52 | } 53 | } 54 | 55 | if len(ret) != 0 { 56 | buf.WriteString(" RETURNING ") 57 | buf.WriteString(strings.Join(ret, ", ")) 58 | } 59 | 60 | return buf.String() 61 | } 62 | -------------------------------------------------------------------------------- /bench/sqlc.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/efectn/go-orm-benchmarks/helper" 7 | 8 | "github.com/efectn/go-orm-benchmarks/bench/sqlc/db" 9 | "github.com/jackc/pgx/v5" 10 | ) 11 | 12 | type Sqlc struct { 13 | helper.ORMInterface 14 | conn *db.Queries 15 | db *pgx.Conn 16 | } 17 | 18 | func CreateSqlc() helper.ORMInterface { 19 | return &Sqlc{} 20 | } 21 | 22 | func (sqlc *Sqlc) Name() string { 23 | return "sqlc" 24 | } 25 | 26 | func (sqlc *Sqlc) Init() error { 27 | var err error 28 | sqlc.db, err = pgx.Connect(ctx, helper.OrmSource) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | sqlc.conn = db.New(sqlc.db) 34 | 35 | return nil 36 | } 37 | 38 | func (sqlc *Sqlc) Close() error { 39 | return sqlc.db.Close(ctx) 40 | } 41 | 42 | func (sqlc *Sqlc) Insert(b *testing.B) { 43 | m := NewModel() 44 | 45 | args := db.CreateModelParams{ 46 | Name: m.Name, 47 | Title: m.Title, 48 | Fax: m.Fax, 49 | Web: m.Web, 50 | Age: int32(m.Age), 51 | Right: m.Right, 52 | Counter: m.Counter, 53 | } 54 | 55 | b.ReportAllocs() 56 | b.ResetTimer() 57 | 58 | for i := 0; i < b.N; i++ { 59 | m.Id = 0 60 | err := sqlc.conn.CreateModel(ctx, args) 61 | if err != nil { 62 | helper.SetError(b, sqlc.Name(), "Insert", err.Error()) 63 | } 64 | } 65 | } 66 | 67 | func (sqlc *Sqlc) InsertMulti(b *testing.B) { 68 | ms := make([]db.InsertMultiParams, 0, 100) 69 | m := NewModel() 70 | for i := 0; i < 100; i++ { 71 | ms = append(ms, db.InsertMultiParams{ 72 | Name: m.Name, 73 | Title: m.Title, 74 | Fax: m.Fax, 75 | Web: m.Web, 76 | Age: int32(m.Age), 77 | Right: m.Right, 78 | Counter: m.Counter, 79 | }) 80 | } 81 | 82 | b.ReportAllocs() 83 | b.ResetTimer() 84 | 85 | for i := 0; i < b.N; i++ { 86 | _, err := sqlc.conn.InsertMulti(ctx, ms) 87 | if err != nil { 88 | helper.SetError(b, sqlc.Name(), "InsertMulti", err.Error()) 89 | } 90 | } 91 | } 92 | 93 | func (sqlc *Sqlc) Update(b *testing.B) { 94 | m := NewModel() 95 | err := sqlc.conn.CreateModel(ctx, db.CreateModelParams{ 96 | Name: m.Name, 97 | Title: m.Title, 98 | Fax: m.Fax, 99 | Web: m.Web, 100 | Age: int32(m.Age), 101 | Right: m.Right, 102 | Counter: m.Counter, 103 | }) 104 | if err != nil { 105 | helper.SetError(b, sqlc.Name(), "Update", err.Error()) 106 | } 107 | 108 | args := db.UpdateModelParams{ 109 | Name: m.Name, 110 | Title: m.Title, 111 | Fax: m.Fax, 112 | Web: m.Web, 113 | Age: int32(m.Age), 114 | Right: m.Right, 115 | Counter: m.Counter, 116 | ID: int32(m.Id), 117 | } 118 | b.ReportAllocs() 119 | b.ResetTimer() 120 | 121 | for i := 0; i < b.N; i++ { 122 | err := sqlc.conn.UpdateModel(ctx, args) 123 | if err != nil { 124 | helper.SetError(b, sqlc.Name(), "Update", err.Error()) 125 | } 126 | } 127 | } 128 | 129 | func (sqlc *Sqlc) Read(b *testing.B) { 130 | m := NewModel() 131 | 132 | err := sqlc.conn.CreateModel(ctx, db.CreateModelParams{ 133 | Name: m.Name, 134 | Title: m.Title, 135 | Fax: m.Fax, 136 | Web: m.Web, 137 | Age: int32(m.Age), 138 | Right: m.Right, 139 | Counter: m.Counter, 140 | }) 141 | m.Id = 1 142 | if err != nil { 143 | helper.SetError(b, sqlc.Name(), "Read", err.Error()) 144 | } 145 | 146 | b.ReportAllocs() 147 | b.ResetTimer() 148 | 149 | for i := 0; i < b.N; i++ { 150 | _, err := sqlc.conn.GetModel(ctx, int32(m.Id)) 151 | if err != nil { 152 | helper.SetError(b, sqlc.Name(), "Read", err.Error()) 153 | } 154 | } 155 | } 156 | 157 | func (sqlc *Sqlc) ReadSlice(b *testing.B) { 158 | m := NewModel() 159 | 160 | for i := 0; i < 100; i++ { 161 | m.Id = 0 162 | 163 | err := sqlc.conn.CreateModel(ctx, db.CreateModelParams{ 164 | Name: m.Name, 165 | Title: m.Title, 166 | Fax: m.Fax, 167 | Web: m.Web, 168 | Age: int32(m.Age), 169 | Right: m.Right, 170 | Counter: m.Counter, 171 | }) 172 | if err != nil { 173 | helper.SetError(b, sqlc.Name(), "ReadSlice", err.Error()) 174 | } 175 | } 176 | 177 | b.ReportAllocs() 178 | b.ResetTimer() 179 | 180 | for i := 0; i < b.N; i++ { 181 | _, err := sqlc.conn.ListModels(ctx) 182 | if err != nil { 183 | helper.SetError(b, sqlc.Name(), "ReadSlice", err.Error()) 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /bench/sqlc/README.md: -------------------------------------------------------------------------------- 1 | ### How to generate on Windows (PowerShell): 2 | 3 | ```powershell 4 | docker run --rm -v ${PWD}:/src -w /src kjconroy/sqlc generate 5 | ``` 6 | -------------------------------------------------------------------------------- /bench/sqlc/db/copyfrom.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.27.0 4 | // source: copyfrom.go 5 | 6 | package db 7 | 8 | import ( 9 | "context" 10 | ) 11 | 12 | // iteratorForInsertMulti implements pgx.CopyFromSource. 13 | type iteratorForInsertMulti struct { 14 | rows []InsertMultiParams 15 | skippedFirstNextCall bool 16 | } 17 | 18 | func (r *iteratorForInsertMulti) Next() bool { 19 | if len(r.rows) == 0 { 20 | return false 21 | } 22 | if !r.skippedFirstNextCall { 23 | r.skippedFirstNextCall = true 24 | return true 25 | } 26 | r.rows = r.rows[1:] 27 | return len(r.rows) > 0 28 | } 29 | 30 | func (r iteratorForInsertMulti) Values() ([]interface{}, error) { 31 | return []interface{}{ 32 | r.rows[0].Name, 33 | r.rows[0].Title, 34 | r.rows[0].Fax, 35 | r.rows[0].Web, 36 | r.rows[0].Age, 37 | r.rows[0].Right, 38 | r.rows[0].Counter, 39 | }, nil 40 | } 41 | 42 | func (r iteratorForInsertMulti) Err() error { 43 | return nil 44 | } 45 | 46 | func (q *Queries) InsertMulti(ctx context.Context, arg []InsertMultiParams) (int64, error) { 47 | return q.db.CopyFrom(ctx, []string{"models"}, []string{"name", "title", "fax", "web", "age", "right", "counter"}, &iteratorForInsertMulti{rows: arg}) 48 | } 49 | -------------------------------------------------------------------------------- /bench/sqlc/db/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.27.0 4 | 5 | package db 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/jackc/pgx/v5" 11 | "github.com/jackc/pgx/v5/pgconn" 12 | ) 13 | 14 | type DBTX interface { 15 | Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) 16 | Query(context.Context, string, ...interface{}) (pgx.Rows, error) 17 | QueryRow(context.Context, string, ...interface{}) pgx.Row 18 | CopyFrom(ctx context.Context, tableName pgx.Identifier, columnNames []string, rowSrc pgx.CopyFromSource) (int64, error) 19 | } 20 | 21 | func New(db DBTX) *Queries { 22 | return &Queries{db: db} 23 | } 24 | 25 | type Queries struct { 26 | db DBTX 27 | } 28 | 29 | func (q *Queries) WithTx(tx pgx.Tx) *Queries { 30 | return &Queries{ 31 | db: tx, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /bench/sqlc/db/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.27.0 4 | 5 | package db 6 | 7 | type Model struct { 8 | ID int32 9 | Name string 10 | Title string 11 | Fax string 12 | Web string 13 | Age int32 14 | Right bool 15 | Counter int64 16 | } 17 | -------------------------------------------------------------------------------- /bench/sqlc/db/queries.sql.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.27.0 4 | // source: queries.sql 5 | 6 | package db 7 | 8 | import ( 9 | "context" 10 | ) 11 | 12 | const createModel = `-- name: CreateModel :exec 13 | INSERT INTO models (NAME, title, fax, web, age, "right", counter) 14 | VALUES ($1, $2, $3, $4, $5, $6, $7) 15 | ` 16 | 17 | type CreateModelParams struct { 18 | Name string 19 | Title string 20 | Fax string 21 | Web string 22 | Age int32 23 | Right bool 24 | Counter int64 25 | } 26 | 27 | func (q *Queries) CreateModel(ctx context.Context, arg CreateModelParams) error { 28 | _, err := q.db.Exec(ctx, createModel, 29 | arg.Name, 30 | arg.Title, 31 | arg.Fax, 32 | arg.Web, 33 | arg.Age, 34 | arg.Right, 35 | arg.Counter, 36 | ) 37 | return err 38 | } 39 | 40 | const getModel = `-- name: GetModel :one 41 | SELECT id, name, title, fax, web, age, "right", counter 42 | FROM models 43 | WHERE id = $1 44 | ` 45 | 46 | func (q *Queries) GetModel(ctx context.Context, id int32) (Model, error) { 47 | row := q.db.QueryRow(ctx, getModel, id) 48 | var i Model 49 | err := row.Scan( 50 | &i.ID, 51 | &i.Name, 52 | &i.Title, 53 | &i.Fax, 54 | &i.Web, 55 | &i.Age, 56 | &i.Right, 57 | &i.Counter, 58 | ) 59 | return i, err 60 | } 61 | 62 | type InsertMultiParams struct { 63 | Name string 64 | Title string 65 | Fax string 66 | Web string 67 | Age int32 68 | Right bool 69 | Counter int64 70 | } 71 | 72 | const listModels = `-- name: ListModels :many 73 | SELECT id, name, title, fax, web, age, "right", counter 74 | FROM models 75 | WHERE ID > 0 76 | LIMIT 100 77 | ` 78 | 79 | func (q *Queries) ListModels(ctx context.Context) ([]Model, error) { 80 | rows, err := q.db.Query(ctx, listModels) 81 | if err != nil { 82 | return nil, err 83 | } 84 | defer rows.Close() 85 | var items []Model 86 | for rows.Next() { 87 | var i Model 88 | if err := rows.Scan( 89 | &i.ID, 90 | &i.Name, 91 | &i.Title, 92 | &i.Fax, 93 | &i.Web, 94 | &i.Age, 95 | &i.Right, 96 | &i.Counter, 97 | ); err != nil { 98 | return nil, err 99 | } 100 | items = append(items, i) 101 | } 102 | if err := rows.Err(); err != nil { 103 | return nil, err 104 | } 105 | return items, nil 106 | } 107 | 108 | const updateModel = `-- name: UpdateModel :exec 109 | UPDATE models 110 | SET name = $1, 111 | title = $2, 112 | fax = $3, 113 | web = $4, 114 | age = $5, 115 | "right" = $6, 116 | counter = $7 117 | WHERE id = $8 118 | ` 119 | 120 | type UpdateModelParams struct { 121 | Name string 122 | Title string 123 | Fax string 124 | Web string 125 | Age int32 126 | Right bool 127 | Counter int64 128 | ID int32 129 | } 130 | 131 | func (q *Queries) UpdateModel(ctx context.Context, arg UpdateModelParams) error { 132 | _, err := q.db.Exec(ctx, updateModel, 133 | arg.Name, 134 | arg.Title, 135 | arg.Fax, 136 | arg.Web, 137 | arg.Age, 138 | arg.Right, 139 | arg.Counter, 140 | arg.ID, 141 | ) 142 | return err 143 | } 144 | -------------------------------------------------------------------------------- /bench/sqlc/queries.sql: -------------------------------------------------------------------------------- 1 | -- name: CreateModel :exec 2 | INSERT INTO models (NAME, title, fax, web, age, "right", counter) 3 | VALUES ($1, $2, $3, $4, $5, $6, $7); 4 | -- name: UpdateModel :exec 5 | UPDATE models 6 | SET name = $1, 7 | title = $2, 8 | fax = $3, 9 | web = $4, 10 | age = $5, 11 | "right" = $6, 12 | counter = $7 13 | WHERE id = $8; 14 | -- name: GetModel :one 15 | SELECT * 16 | FROM models 17 | WHERE id = $1; 18 | -- name: ListModels :many 19 | SELECT * 20 | FROM models 21 | WHERE ID > 0 22 | LIMIT 100; 23 | -- name: InsertMulti :copyfrom 24 | INSERT INTO models (name, title, fax, web, age, "right", counter) VALUES 25 | ($1, $2, $3, $4, $5, $6, $7); 26 | -------------------------------------------------------------------------------- /bench/sqlc/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE models ( 2 | ID SERIAL NOT NULL, 3 | NAME TEXT NOT NULL, 4 | title TEXT NOT NULL, 5 | fax TEXT NOT NULL, 6 | web TEXT NOT NULL, 7 | age INTEGER NOT NULL, 8 | "right" BOOLEAN NOT NULL, 9 | counter BIGINT NOT NULL, 10 | CONSTRAINT models_pkey PRIMARY KEY (ID) 11 | ) WITH (OIDS = FALSE); -------------------------------------------------------------------------------- /bench/sqlc/sqlc.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | sql: 3 | - engine: "postgresql" 4 | queries: "queries.sql" 5 | schema: "schema.sql" 6 | gen: 7 | go: 8 | package: "db" 9 | sql_package: "pgx/v5" 10 | out: "db" 11 | # emit_prepared_queries: true 12 | # emit_interface: false 13 | # emit_exact_table_names: false 14 | # emit_empty_slices: false 15 | # emit_json_tags: true 16 | # json_tags_case_style: "camel" 17 | -------------------------------------------------------------------------------- /bench/sqlx.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "github.com/efectn/go-orm-benchmarks/helper" 5 | sqlxdb "github.com/jmoiron/sqlx" 6 | _ "github.com/lib/pq" 7 | "testing" 8 | ) 9 | 10 | const ( 11 | sqlxInsertBaseSQL = `INSERT INTO models (name, title, fax, web, age, "right", counter) VALUES ` 12 | sqlxInsertValuesSQL = `($1, $2, $3, $4, $5, $6, $7)` 13 | sqlxInsertSQL = sqlxInsertBaseSQL + sqlxInsertValuesSQL 14 | sqlxInsertNamesSQL = `(:name, :title, :fax, :web, :age, :right, :counter)` 15 | sqlxInsertMultiSQL = sqlxInsertBaseSQL + sqlxInsertNamesSQL 16 | sqlxUpdateSQL = `UPDATE models SET name = $1, title = $2, fax = $3, web = $4, age = $5, "right" = $6, counter = $7 WHERE id = $8` 17 | sqlxSelectSQL = `SELECT * FROM models WHERE id = $1` 18 | sqlxSelectMultiSQL = `SELECT * FROM models WHERE id > 0 LIMIT 100` 19 | ) 20 | 21 | type Sqlx struct { 22 | helper.ORMInterface 23 | conn *sqlxdb.DB 24 | } 25 | 26 | func CreateSqlx() helper.ORMInterface { 27 | return &Sqlx{} 28 | } 29 | 30 | func (sqlx *Sqlx) Name() string { 31 | return "sqlx" 32 | } 33 | 34 | func (sqlx *Sqlx) Init() error { 35 | var err error 36 | sqlx.conn, err = sqlxdb.Connect("postgres", helper.OrmSource) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | return nil 42 | } 43 | 44 | func (sqlx *Sqlx) Close() error { 45 | return sqlx.conn.Close() 46 | } 47 | 48 | func (sqlx *Sqlx) Insert(b *testing.B) { 49 | m := NewModel() 50 | 51 | b.ReportAllocs() 52 | b.ResetTimer() 53 | 54 | for i := 0; i < b.N; i++ { 55 | _, err := sqlx.conn.Exec(sqlxInsertSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 56 | if err != nil { 57 | helper.SetError(b, sqlx.Name(), "Insert", err.Error()) 58 | } 59 | } 60 | } 61 | 62 | func (sqlx *Sqlx) InsertMulti(b *testing.B) { 63 | ms := make([]*Model, 0, 100) 64 | for i := 0; i < 100; i++ { 65 | ms = append(ms, NewModel()) 66 | } 67 | 68 | b.ReportAllocs() 69 | b.ResetTimer() 70 | 71 | for i := 0; i < b.N; i++ { 72 | _, err := sqlx.conn.NamedExec(sqlxInsertMultiSQL, ms) 73 | if err != nil { 74 | helper.SetError(b, sqlx.Name(), "InsertMulti", err.Error()) 75 | } 76 | } 77 | } 78 | 79 | func (sqlx *Sqlx) Update(b *testing.B) { 80 | m := NewModel() 81 | _, err := sqlx.conn.Exec(sqlxInsertSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 82 | if err != nil { 83 | helper.SetError(b, sqlx.Name(), "Update", err.Error()) 84 | } 85 | 86 | b.ReportAllocs() 87 | b.ResetTimer() 88 | 89 | for i := 0; i < b.N; i++ { 90 | _, err := sqlx.conn.Exec(sqlxUpdateSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter, m.Id) 91 | if err != nil { 92 | helper.SetError(b, sqlx.Name(), "Update", err.Error()) 93 | } 94 | } 95 | } 96 | 97 | func (sqlx *Sqlx) Read(b *testing.B) { 98 | m := NewModel() 99 | _, err := sqlx.conn.Exec(sqlxInsertSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 100 | if err != nil { 101 | helper.SetError(b, sqlx.Name(), "Read", err.Error()) 102 | } 103 | 104 | b.ReportAllocs() 105 | b.ResetTimer() 106 | 107 | for i := 0; i < b.N; i++ { 108 | var m []Model 109 | err := sqlx.conn.Select(&m, sqlxSelectSQL, 1) 110 | if err != nil { 111 | helper.SetError(b, sqlx.Name(), "Read", err.Error()) 112 | } 113 | } 114 | } 115 | 116 | func (sqlx *Sqlx) ReadSlice(b *testing.B) { 117 | m := NewModel() 118 | for i := 0; i < 100; i++ { 119 | _, err := sqlx.conn.Exec(sqlxInsertSQL, m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 120 | if err != nil { 121 | helper.SetError(b, sqlx.Name(), "ReadSlice", err.Error()) 122 | } 123 | } 124 | 125 | b.ReportAllocs() 126 | b.ResetTimer() 127 | 128 | for i := 0; i < b.N; i++ { 129 | ms := make([]Model, 100) 130 | err := sqlx.conn.Select(&ms, sqlxSelectMultiSQL) 131 | if err != nil { 132 | helper.SetError(b, sqlx.Name(), "ReadSlice", err.Error()) 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /bench/upper.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "github.com/efectn/go-orm-benchmarks/helper" 5 | db "github.com/upper/db/v4" 6 | "github.com/upper/db/v4/adapter/postgresql" 7 | "testing" 8 | ) 9 | 10 | type Upper struct { 11 | helper.ORMInterface 12 | conn db.Session 13 | } 14 | 15 | func CreateUpper() helper.ORMInterface { 16 | return &Upper{} 17 | } 18 | 19 | func (upper *Upper) Name() string { 20 | return "upper" 21 | } 22 | 23 | func (upper *Upper) Init() error { 24 | var err error 25 | source := helper.SplitSource() 26 | 27 | upper.conn, err = postgresql.Open(postgresql.ConnectionURL{ 28 | Host: source["host"] + ":5432", 29 | User: source["user"], 30 | Password: source["password"], 31 | Database: source["dbname"], 32 | }) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | // Disable logger 38 | db.LC().SetLogger(nil) 39 | 40 | if err := upper.conn.Ping(); err != nil { 41 | return err 42 | } 43 | 44 | return nil 45 | } 46 | 47 | func (upper *Upper) Close() error { 48 | return upper.conn.Close() 49 | } 50 | 51 | func (upper *Upper) Insert(b *testing.B) { 52 | m := NewModel4() 53 | 54 | b.ReportAllocs() 55 | b.ResetTimer() 56 | 57 | for i := 0; i < b.N; i++ { 58 | _, err := upper.conn.Collection("models").Insert(m) 59 | if err != nil { 60 | helper.SetError(b, upper.Name(), "Insert", err.Error()) 61 | } 62 | } 63 | } 64 | 65 | func (upper *Upper) InsertMulti(b *testing.B) { 66 | m := NewModel4() 67 | 68 | b.ReportAllocs() 69 | b.ResetTimer() 70 | 71 | for i := 0; i < b.N; i++ { 72 | batch := upper.conn.SQL().InsertInto("models").Columns("name", "title", "fax", "web", "age", "right", "counter").Batch(100) 73 | 74 | go func() { 75 | for i := 0; i < 100; i++ { 76 | batch.Values(m.Name, m.Title, m.Fax, m.Web, m.Age, m.Right, m.Counter) 77 | } 78 | batch.Done() 79 | }() 80 | 81 | if err := batch.Wait(); err != nil { 82 | helper.SetError(b, upper.Name(), "InsertMulti", err.Error()) 83 | } 84 | } 85 | } 86 | 87 | func (upper *Upper) Update(b *testing.B) { 88 | m := NewModel4() 89 | 90 | err := upper.conn.Collection("models").InsertReturning(m) 91 | if err != nil { 92 | helper.SetError(b, upper.Name(), "Update", err.Error()) 93 | } 94 | 95 | b.ReportAllocs() 96 | b.ResetTimer() 97 | 98 | for i := 0; i < b.N; i++ { 99 | err := upper.conn.Collection("models").UpdateReturning(m) 100 | if err != nil { 101 | helper.SetError(b, upper.Name(), "Update", err.Error()) 102 | } 103 | } 104 | } 105 | 106 | func (upper *Upper) Read(b *testing.B) { 107 | m := NewModel4() 108 | 109 | err := upper.conn.Collection("models").InsertReturning(m) 110 | if err != nil { 111 | helper.SetError(b, upper.Name(), "Read", err.Error()) 112 | } 113 | 114 | b.ReportAllocs() 115 | b.ResetTimer() 116 | 117 | for i := 0; i < b.N; i++ { 118 | err := upper.conn.SQL().SelectFrom("models").Where("id = ?", m.ID).One(m) 119 | if err != nil { 120 | helper.SetError(b, upper.Name(), "Read", err.Error()) 121 | } 122 | } 123 | } 124 | 125 | func (upper *Upper) ReadSlice(b *testing.B) { 126 | m := NewModel4() 127 | for i := 0; i < 100; i++ { 128 | m.ID = 0 129 | err := upper.conn.Collection("models").InsertReturning(m) 130 | if err != nil { 131 | helper.SetError(b, upper.Name(), "ReadSlice", err.Error()) 132 | } 133 | } 134 | 135 | b.ReportAllocs() 136 | b.ResetTimer() 137 | 138 | for i := 0; i < b.N; i++ { 139 | var ms []*Model4 140 | err := upper.conn.SQL().SelectFrom("models").Where("id > ?", m.ID).Limit(100).All(&ms) 141 | if err != nil { 142 | helper.SetError(b, upper.Name(), "ReadSlice", err.Error()) 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /bench/xorm.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "github.com/efectn/go-orm-benchmarks/helper" 5 | "testing" 6 | xormdb "xorm.io/xorm" 7 | ) 8 | 9 | type Xorm struct { 10 | helper.ORMInterface 11 | conn *xormdb.Session 12 | } 13 | 14 | func CreateXorm() helper.ORMInterface { 15 | return &Xorm{} 16 | } 17 | 18 | func (xorm *Xorm) Name() string { 19 | return "xorm" 20 | } 21 | 22 | func (xorm *Xorm) Init() error { 23 | engine, err := xormdb.NewEngine("postgres", helper.OrmSource) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | xorm.conn = engine.NewSession() 29 | 30 | return nil 31 | } 32 | 33 | func (xorm *Xorm) Close() error { 34 | return xorm.conn.Close() 35 | } 36 | 37 | func (xorm *Xorm) Insert(b *testing.B) { 38 | m := NewModel5() 39 | 40 | b.ReportAllocs() 41 | b.ResetTimer() 42 | 43 | for i := 0; i < b.N; i++ { 44 | m.ID = 0 45 | _, err := xorm.conn.Insert(m) 46 | if err != nil { 47 | helper.SetError(b, xorm.Name(), "Insert", err.Error()) 48 | } 49 | } 50 | } 51 | 52 | func (xorm *Xorm) InsertMulti(b *testing.B) { 53 | ms := make([]*Model5, 0, 100) 54 | for i := 0; i < 100; i++ { 55 | ms = append(ms, NewModel5()) 56 | } 57 | 58 | b.ReportAllocs() 59 | b.ResetTimer() 60 | 61 | for i := 0; i < b.N; i++ { 62 | _, err := xorm.conn.Insert(&ms) 63 | if err != nil { 64 | helper.SetError(b, xorm.Name(), "InsertMulti", err.Error()) 65 | } 66 | } 67 | } 68 | 69 | func (xorm *Xorm) Update(b *testing.B) { 70 | m := NewModel5() 71 | 72 | _, err := xorm.conn.Insert(m) 73 | if err != nil { 74 | helper.SetError(b, xorm.Name(), "Update", err.Error()) 75 | } 76 | 77 | b.ReportAllocs() 78 | b.ResetTimer() 79 | 80 | for i := 0; i < b.N; i++ { 81 | _, err := xorm.conn.ID(m.ID).Update(m) 82 | if err != nil { 83 | helper.SetError(b, xorm.Name(), "Update", err.Error()) 84 | } 85 | } 86 | } 87 | 88 | func (xorm *Xorm) Read(b *testing.B) { 89 | m := NewModel5() 90 | 91 | _, err := xorm.conn.Insert(m) 92 | if err != nil { 93 | helper.SetError(b, xorm.Name(), "Read", err.Error()) 94 | } 95 | 96 | b.ReportAllocs() 97 | b.ResetTimer() 98 | 99 | for i := 0; i < b.N; i++ { 100 | _, err := xorm.conn.ID(1).NoAutoCondition().Get(m) 101 | if err != nil { 102 | helper.SetError(b, xorm.Name(), "Read", err.Error()) 103 | } 104 | } 105 | } 106 | 107 | func (xorm *Xorm) ReadSlice(b *testing.B) { 108 | m := NewModel5() 109 | 110 | for i := 0; i < 100; i++ { 111 | m.ID = 0 112 | _, err := xorm.conn.Insert(m) 113 | if err != nil { 114 | helper.SetError(b, xorm.Name(), "ReadSlice", err.Error()) 115 | } 116 | } 117 | 118 | b.ReportAllocs() 119 | b.ResetTimer() 120 | 121 | for i := 0; i < b.N; i++ { 122 | var models []*Model5 123 | err := xorm.conn.Where("id > 0").Limit(100).Find(&models) 124 | if err != nil { 125 | helper.SetError(b, xorm.Name(), "ReadSlice", err.Error()) 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /bench/zorm.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "context" 5 | "github.com/efectn/go-orm-benchmarks/helper" 6 | "testing" 7 | 8 | zormdb "gitee.com/chunanyong/zorm" 9 | _ "github.com/lib/pq" 10 | ) 11 | 12 | var ( 13 | zormCtx = context.Background() 14 | readFinder = zormdb.NewFinder().Append("SELECT * FROM models WHERE id = 1") 15 | readSliceFinder = zormdb.NewFinder().Append("SELECT * FROM models WHERE id > 0") 16 | page = &zormdb.Page{PageNo: 1, PageSize: 100} 17 | ) 18 | 19 | type Zorm struct { 20 | helper.ORMInterface 21 | } 22 | 23 | func CreateZorm() helper.ORMInterface { 24 | return &Zorm{} 25 | } 26 | 27 | func (zorm *Zorm) Name() string { 28 | return "zorm" 29 | } 30 | 31 | func (zorm *Zorm) Init() error { 32 | readFinder.InjectionCheck = false 33 | readSliceFinder.InjectionCheck = false 34 | readSliceFinder.SelectTotalCount = false 35 | 36 | dbDaoConfig := zormdb.DataSourceConfig{ 37 | DSN: helper.OrmSource, 38 | DriverName: "postgres", 39 | Dialect: "postgresql", 40 | MaxOpenConns: helper.OrmMaxConn, 41 | MaxIdleConns: helper.OrmMaxIdle, 42 | SlowSQLMillis: -1, 43 | DisableTransaction: true, 44 | } 45 | _, err := zormdb.NewDBDao(&dbDaoConfig) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | return nil 51 | } 52 | 53 | func (zorm *Zorm) Close() error { 54 | return nil 55 | } 56 | 57 | func (zorm *Zorm) Insert(b *testing.B) { 58 | m := NewModel7() 59 | 60 | b.ReportAllocs() 61 | b.ResetTimer() 62 | 63 | for i := 0; i < b.N; i++ { 64 | m.ID = 0 65 | _, err := zormdb.Insert(zormCtx, m) 66 | if err != nil { 67 | helper.SetError(b, zorm.Name(), "Insert", err.Error()) 68 | } 69 | } 70 | } 71 | 72 | func (zorm *Zorm) InsertMulti(b *testing.B) { 73 | ms := make([]zormdb.IEntityStruct, 0, 100) 74 | for i := 0; i < 100; i++ { 75 | ms = append(ms, NewModel7()) 76 | } 77 | 78 | b.ReportAllocs() 79 | b.ResetTimer() 80 | 81 | for i := 0; i < b.N; i++ { 82 | for _, m := range ms { 83 | m7, _ := m.(*Model7) 84 | m7.ID = 0 85 | } 86 | _, err := zormdb.InsertSlice(zormCtx, ms) 87 | if err != nil { 88 | helper.SetError(b, zorm.Name(), "InsertMulti", err.Error()) 89 | } 90 | } 91 | } 92 | 93 | func (zorm *Zorm) Update(b *testing.B) { 94 | m := NewModel7() 95 | 96 | _, err := zormdb.Insert(zormCtx, m) 97 | if err != nil { 98 | helper.SetError(b, zorm.Name(), "Update", err.Error()) 99 | } 100 | 101 | b.ReportAllocs() 102 | b.ResetTimer() 103 | 104 | for i := 0; i < b.N; i++ { 105 | _, err := zormdb.Update(zormCtx, m) 106 | if err != nil { 107 | helper.SetError(b, zorm.Name(), "Update", err.Error()) 108 | } 109 | } 110 | } 111 | 112 | func (zorm *Zorm) Read(b *testing.B) { 113 | m := NewModel7() 114 | 115 | _, err := zormdb.Insert(zormCtx, m) 116 | if err != nil { 117 | helper.SetError(b, zorm.Name(), "Read", err.Error()) 118 | } 119 | 120 | b.ReportAllocs() 121 | b.ResetTimer() 122 | 123 | for i := 0; i < b.N; i++ { 124 | _, err := zormdb.QueryRow(zormCtx, readFinder, m) 125 | if err != nil { 126 | helper.SetError(b, zorm.Name(), "Read", err.Error()) 127 | } 128 | } 129 | } 130 | 131 | func (zorm *Zorm) ReadSlice(b *testing.B) { 132 | m := NewModel7() 133 | for i := 0; i < 100; i++ { 134 | m.ID = 0 135 | _, err := zormdb.Insert(zormCtx, m) 136 | if err != nil { 137 | helper.SetError(b, zorm.Name(), "ReadSlice", err.Error()) 138 | } 139 | } 140 | 141 | b.ReportAllocs() 142 | b.ResetTimer() 143 | 144 | for i := 0; i < b.N; i++ { 145 | var models []Model7 146 | err := zormdb.Query(zormCtx, readSliceFinder, &models, page) 147 | if err != nil { 148 | helper.SetError(b, zorm.Name(), "ReadSlice", err.Error()) 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | networks: 4 | default: 5 | driver: bridge 6 | 7 | services: 8 | postgres: 9 | image: 'postgres:14-alpine' 10 | networks: 11 | - default 12 | ports: 13 | - 5432:5432 14 | environment: 15 | - POSTGRES_USER=postgres 16 | - POSTGRES_PASSWORD=postgres 17 | - POSTGRES_DB=test 18 | 19 | benchmarker: 20 | build: 21 | context: . 22 | dockerfile: Dockerfile 23 | networks: 24 | - default 25 | depends_on: 26 | - postgres 27 | links: 28 | - postgres -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/efectn/go-orm-benchmarks 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | entgo.io/ent v0.14.1 7 | gitee.com/chunanyong/zorm v1.7.7 8 | github.com/astaxie/beego v1.12.3 9 | github.com/bokwoon95/sq v0.5.1 10 | github.com/friendsofgo/errors v0.9.2 11 | github.com/go-jet/jet/v2 v2.11.1 12 | github.com/go-pg/pg/v10 v10.13.0 13 | github.com/go-rel/postgres v0.12.0 14 | github.com/go-rel/rel v0.42.0 15 | github.com/gobuffalo/pop/v6 v6.1.1 16 | github.com/gocraft/dbr/v2 v2.7.6 17 | github.com/jackc/pgx/v5 v5.7.1 18 | github.com/jmoiron/sqlx v1.4.0 19 | github.com/lib/pq v1.10.9 20 | github.com/samonzeweb/godb v1.0.15 21 | github.com/upper/db/v4 v4.7.0 22 | github.com/uptrace/bun v1.2.3 23 | github.com/uptrace/bun/dialect/pgdialect v1.2.3 24 | github.com/uptrace/bun/driver/pgdriver v1.2.3 25 | github.com/volatiletech/sqlboiler/v4 v4.16.2 26 | github.com/volatiletech/strmangle v0.0.6 27 | gopkg.in/gorp.v1 v1.7.2 28 | gopkg.in/reform.v1 v1.5.1 29 | gorm.io/driver/postgres v1.5.9 30 | gorm.io/gen v0.3.26 31 | gorm.io/gorm v1.25.12 32 | gorm.io/plugin/dbresolver v1.5.3 33 | xorm.io/xorm v1.3.9 34 | ) 35 | 36 | require ( 37 | ariga.io/atlas v0.27.0 // indirect 38 | filippo.io/edwards25519 v1.1.0 // indirect 39 | github.com/Masterminds/semver/v3 v3.3.0 // indirect 40 | github.com/agext/levenshtein v1.2.3 // indirect 41 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 42 | github.com/aymerick/douceur v0.2.0 // indirect 43 | github.com/davecgh/go-spew v1.1.1 // indirect 44 | github.com/fatih/color v1.17.0 // indirect 45 | github.com/fatih/structs v1.1.0 // indirect 46 | github.com/go-openapi/inflect v0.21.0 // indirect 47 | github.com/go-pg/zerochecker v0.2.0 // indirect 48 | github.com/go-rel/sql v0.17.0 // indirect 49 | github.com/go-sql-driver/mysql v1.8.1 // indirect 50 | github.com/gobuffalo/envy v1.10.2 // indirect 51 | github.com/gobuffalo/fizz v1.14.4 // indirect 52 | github.com/gobuffalo/flect v1.0.2 // indirect 53 | github.com/gobuffalo/github_flavored_markdown v1.1.4 // indirect 54 | github.com/gobuffalo/helpers v0.6.7 // indirect 55 | github.com/gobuffalo/nulls v0.4.2 // indirect 56 | github.com/gobuffalo/plush/v4 v4.1.22 // indirect 57 | github.com/gobuffalo/tags/v3 v3.1.4 // indirect 58 | github.com/gobuffalo/validate/v3 v3.3.3 // indirect 59 | github.com/goccy/go-json v0.10.3 // indirect 60 | github.com/gofrs/uuid v4.4.0+incompatible // indirect 61 | github.com/golang/snappy v0.0.4 // indirect 62 | github.com/google/go-cmp v0.6.0 // indirect 63 | github.com/google/uuid v1.6.0 // indirect 64 | github.com/gorilla/css v1.0.1 // indirect 65 | github.com/hashicorp/golang-lru v1.0.2 // indirect 66 | github.com/hashicorp/hcl/v2 v2.22.0 // indirect 67 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect 68 | github.com/jackc/pgconn v1.14.3 // indirect 69 | github.com/jackc/pgio v1.0.0 // indirect 70 | github.com/jackc/pgpassfile v1.0.0 // indirect 71 | github.com/jackc/pgproto3/v2 v2.3.3 // indirect 72 | github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect 73 | github.com/jackc/pgtype v1.14.3 // indirect 74 | github.com/jackc/pgx/v4 v4.18.3 // indirect 75 | github.com/jackc/puddle/v2 v2.2.2 // indirect 76 | github.com/jinzhu/inflection v1.0.0 // indirect 77 | github.com/jinzhu/now v1.1.5 // indirect 78 | github.com/joho/godotenv v1.5.1 // indirect 79 | github.com/json-iterator/go v1.1.12 // indirect 80 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 81 | github.com/luna-duclos/instrumentedsql v1.1.3 // indirect 82 | github.com/mattn/go-colorable v0.1.13 // indirect 83 | github.com/mattn/go-isatty v0.0.20 // indirect 84 | github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect 85 | github.com/microcosm-cc/bluemonday v1.0.27 // indirect 86 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 87 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 88 | github.com/modern-go/reflect2 v1.0.2 // indirect 89 | github.com/pkg/errors v0.9.1 // indirect 90 | github.com/pmezard/go-difflib v1.0.0 // indirect 91 | github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect 92 | github.com/rogpeppe/go-internal v1.12.0 // indirect 93 | github.com/segmentio/fasthash v1.0.3 // indirect 94 | github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e // indirect 95 | github.com/sergi/go-diff v1.3.1 // indirect 96 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect 97 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect 98 | github.com/spf13/cast v1.7.0 // indirect 99 | github.com/stretchr/testify v1.9.0 // indirect 100 | github.com/syndtr/goleveldb v1.0.0 // indirect 101 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect 102 | github.com/vmihailenco/bufpool v0.1.11 // indirect 103 | github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 104 | github.com/vmihailenco/tagparser v0.1.2 // indirect 105 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 106 | github.com/volatiletech/inflect v0.0.1 // indirect 107 | github.com/zclconf/go-cty v1.15.0 // indirect 108 | golang.org/x/crypto v0.27.0 // indirect 109 | golang.org/x/mod v0.21.0 // indirect 110 | golang.org/x/net v0.29.0 // indirect 111 | golang.org/x/sync v0.8.0 // indirect 112 | golang.org/x/sys v0.25.0 // indirect 113 | golang.org/x/text v0.18.0 // indirect 114 | golang.org/x/tools v0.25.0 // indirect 115 | golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect 116 | gopkg.in/yaml.v2 v2.4.0 // indirect 117 | gopkg.in/yaml.v3 v3.0.1 // indirect 118 | gorm.io/datatypes v1.2.2 // indirect 119 | gorm.io/driver/mysql v1.5.7 // indirect 120 | gorm.io/hints v1.1.2 // indirect 121 | mellium.im/sasl v0.3.2 // indirect 122 | xorm.io/builder v0.3.13 // indirect 123 | ) 124 | -------------------------------------------------------------------------------- /helper/constants.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | var ( 4 | OrmMulti int 5 | OrmMaxIdle int 6 | OrmMaxConn int 7 | OrmSource string 8 | DebugMode bool 9 | ) 10 | -------------------------------------------------------------------------------- /helper/error.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | var Errors map[string]map[string]string 9 | 10 | var mu sync.Mutex 11 | 12 | func SetError(b *testing.B, orm, method, err string) { 13 | b.Helper() 14 | 15 | mu.Lock() 16 | Errors[orm][method] = err 17 | mu.Unlock() 18 | b.Fail() 19 | } 20 | 21 | func GetError(orm, method string) string { 22 | return Errors[orm][method] 23 | } 24 | -------------------------------------------------------------------------------- /helper/misc.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "reflect" 5 | "runtime" 6 | "strings" 7 | ) 8 | 9 | func getFuncName(function any) string { 10 | name := strings.Split(runtime.FuncForPC(reflect.ValueOf(function).Pointer()).Name(), ".") 11 | straightName := strings.Split(name[len(name)-1], "-")[0] 12 | 13 | return straightName 14 | } 15 | -------------------------------------------------------------------------------- /helper/sql.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | // Convert ORMSource to DSN (dburl) 10 | func ConvertSourceToDSN() string { 11 | template := "postgres://$(user):$(password)@$(host):5432/$(dbname)" 12 | 13 | // Parse one-by-one instead of using REGEX because of performance issues 14 | for _, option := range strings.Split(OrmSource, " ") { 15 | k := strings.Split(option, "=")[0] 16 | v := strings.Split(option, "=")[1] 17 | 18 | if strings.Contains(template, "$("+k+")") { 19 | template = strings.ReplaceAll(template, "$("+k+")", v) 20 | } else { 21 | template += "?" + option 22 | } 23 | } 24 | 25 | return template 26 | } 27 | 28 | func SplitSource() map[string]string { 29 | options := make(map[string]string) 30 | // Split one-by-one instead of using REGEX because of performance issues 31 | for _, option := range strings.Split(OrmSource, " ") { 32 | k := strings.Split(option, "=")[0] 33 | v := strings.Split(option, "=")[1] 34 | 35 | options[k] = v 36 | } 37 | 38 | return options 39 | } 40 | 41 | func CreateTables() error { 42 | queries := [][]string{ 43 | { 44 | `DROP TABLE IF EXISTS models;`, 45 | `CREATE TABLE models ( 46 | id SERIAL NOT NULL, 47 | name text NOT NULL, 48 | title text NOT NULL, 49 | fax text NOT NULL, 50 | web text NOT NULL, 51 | age integer NOT NULL, 52 | "right" boolean NOT NULL, 53 | counter bigint NOT NULL, 54 | CONSTRAINT models_pkey PRIMARY KEY (id) 55 | ) WITH (OIDS=FALSE);`, 56 | }, 57 | { 58 | `DROP TABLE IF EXISTS model5;`, 59 | `CREATE TABLE model5 ( 60 | id SERIAL NOT NULL, 61 | name text NOT NULL, 62 | title text NOT NULL, 63 | fax text NOT NULL, 64 | web text NOT NULL, 65 | age integer NOT NULL, 66 | "right" boolean NOT NULL, 67 | counter bigint NOT NULL 68 | ) WITH (OIDS=FALSE);`, 69 | }, 70 | } 71 | 72 | db, err := sql.Open("pgx", OrmSource) 73 | if err != nil { 74 | return fmt.Errorf("init_tables: %w", err) 75 | } 76 | 77 | defer func() { 78 | _ = db.Close() 79 | }() 80 | 81 | err = db.Ping() 82 | if err != nil { 83 | return fmt.Errorf("init_tables: %w", err) 84 | } 85 | 86 | for _, query := range queries { 87 | for _, line := range query { 88 | _, err = db.Exec(line) 89 | if err != nil { 90 | return fmt.Errorf("init_tables: %w", err) 91 | } 92 | } 93 | } 94 | 95 | return nil 96 | } 97 | -------------------------------------------------------------------------------- /helper/suite.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type ORMInterface interface { 8 | Name() string 9 | Init() error 10 | Close() error 11 | Insert(b *testing.B) 12 | InsertMulti(b *testing.B) 13 | Update(b *testing.B) 14 | Read(b *testing.B) 15 | ReadSlice(b *testing.B) 16 | } 17 | 18 | type BenchmarkResult struct { 19 | ORM string 20 | Results []Result 21 | } 22 | 23 | type Result struct { 24 | Name string 25 | Method string 26 | ErrorMsg string 27 | 28 | N int 29 | NsPerOp int64 30 | MemAllocs int64 31 | MemBytes int64 32 | } 33 | 34 | type BenchmarkReport []*Result 35 | 36 | func (s BenchmarkReport) Len() int { return len(s) } 37 | 38 | func (s BenchmarkReport) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 39 | 40 | func (s BenchmarkReport) Less(i, j int) bool { 41 | if s[i].ErrorMsg != "" { 42 | return false 43 | } 44 | if s[j].ErrorMsg != "" { 45 | return true 46 | } 47 | return s[i].NsPerOp < s[j].NsPerOp 48 | } 49 | 50 | func RunBenchmarks(orm ORMInterface, reports map[string]BenchmarkReport) (BenchmarkResult, error) { 51 | err := orm.Init() 52 | if err != nil { 53 | return BenchmarkResult{}, err 54 | } 55 | 56 | defer func(orm ORMInterface) { 57 | _ = orm.Close() 58 | }(orm) 59 | 60 | var result BenchmarkResult 61 | operations := []func(b *testing.B){orm.Insert, orm.InsertMulti, orm.Update, orm.Read, orm.ReadSlice} 62 | 63 | result.ORM = orm.Name() 64 | for _, operation := range operations { 65 | // Clean tables for each run 66 | err := CreateTables() 67 | if err != nil { 68 | panic(err) 69 | } 70 | 71 | br := testing.Benchmark(operation) 72 | method := getFuncName(operation) 73 | 74 | gotResult := &Result{ 75 | Name: orm.Name(), 76 | Method: method, 77 | ErrorMsg: GetError(orm.Name(), method), 78 | N: br.N, 79 | NsPerOp: br.NsPerOp(), 80 | MemAllocs: br.AllocsPerOp(), 81 | MemBytes: br.AllocedBytesPerOp(), 82 | } 83 | 84 | reports[method] = append(reports[method], gotResult) 85 | result.Results = append(result.Results, *gotResult) 86 | } 87 | 88 | return result, nil 89 | } 90 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "math/rand" 7 | "os" 8 | "runtime" 9 | "sort" 10 | "strings" 11 | "text/tabwriter" 12 | "time" 13 | 14 | "github.com/efectn/go-orm-benchmarks/helper" 15 | 16 | "github.com/efectn/go-orm-benchmarks/bench" 17 | 18 | _ "github.com/lib/pq" 19 | ) 20 | 21 | // VERSION constant 22 | const VERSION = "v1.0.2" 23 | 24 | var defaultBenchmarkNames = []string{ 25 | "beego", "bun", "dbr", "ent", 26 | "godb", "gorm", "gorm_prep", "gorp", 27 | "pg", "pgx", "pgx_pool", "pop", 28 | "raw", "reform", "rel", "sqlboiler", 29 | "sqlc", "sqlx", "upper", "xorm", 30 | "zorm", "gen", "jet", "sq", 31 | } 32 | 33 | type ListOpts []string 34 | 35 | func (opts *ListOpts) String() string { 36 | return fmt.Sprint(*opts) 37 | } 38 | 39 | func (opts *ListOpts) Set(value string) error { 40 | if value != "all" && !strings.Contains(" "+strings.Join(defaultBenchmarkNames, " ")+" ", " "+value+" ") { 41 | return fmt.Errorf("wrong run name %s", value) 42 | } 43 | 44 | *opts = append(*opts, value) 45 | return nil 46 | } 47 | 48 | func (opts ListOpts) Shuffle() { 49 | rd := rand.New(rand.NewSource(time.Now().UnixNano())) 50 | for i := 0; i < len(opts); i++ { 51 | a := rd.Intn(len(opts)) 52 | b := rd.Intn(len(opts)) 53 | opts[a], opts[b] = opts[b], opts[a] 54 | } 55 | } 56 | 57 | func main() { 58 | runtime.GOMAXPROCS(runtime.NumCPU()) 59 | 60 | var orms ListOpts 61 | var all bool 62 | 63 | flag.StringVar(&helper.OrmSource, "source", "host=localhost user=postgres password=postgres dbname=test sslmode=disable", "postgres dsn source") 64 | flag.Var(&orms, "orm", "orm name: all, "+strings.Join(defaultBenchmarkNames, ", ")) 65 | flag.IntVar(&helper.OrmMaxIdle, "max_idle", 200, "max idle conns") 66 | flag.IntVar(&helper.OrmMaxConn, "max_conn", 200, "max open conns") 67 | flag.BoolVar(&helper.DebugMode, "debug", false, "Enable debug mode (also prints not-sorted results of ORMs)") 68 | version := flag.Bool("version", false, "prints current roxy version") 69 | flag.Parse() 70 | 71 | // Print version 72 | if *version { 73 | fmt.Println(VERSION) 74 | os.Exit(0) 75 | } 76 | 77 | // Check it is all 78 | if len(orms) == 0 { 79 | all = true 80 | } else { 81 | for _, n := range orms { 82 | if n == "all" { 83 | all = true 84 | } 85 | } 86 | } 87 | 88 | if all { 89 | orms = defaultBenchmarkNames 90 | } 91 | orms.Shuffle() 92 | 93 | // Init error map 94 | helper.Errors = make(map[string]map[string]string, 0) 95 | for _, name := range defaultBenchmarkNames { 96 | helper.Errors[name] = make(map[string]string, 0) 97 | } 98 | 99 | runBenchmarks(orms) 100 | } 101 | 102 | func runBenchmarks(orms ListOpts) { 103 | // Run benchmarks 104 | benchmarks := map[string]helper.ORMInterface{ 105 | "beego": bench.CreateBeego(), 106 | "bun": bench.CreateBun(), 107 | "dbr": bench.CreateDbr(), 108 | "ent": bench.CreateEnt(), 109 | "godb": bench.CreateGodb(), 110 | "gorm": bench.CreateGorm(), 111 | "gorm_prep": bench.CreateGormPrep(), 112 | "gorp": bench.CreateGorp(), 113 | "pg": bench.CreatePg(), 114 | "pgx": bench.CreatePgx(), 115 | "pgx_pool": bench.CreatePgxPool(), 116 | "pop": bench.CreatePop(), 117 | "raw": bench.CreateRaw(), 118 | "reform": bench.CreateReform(), 119 | "rel": bench.CreateRel(), 120 | "sqlboiler": bench.CreateSqlboiler(), 121 | "sqlc": bench.CreateSqlc(), 122 | "sqlx": bench.CreateSqlx(), 123 | "upper": bench.CreateUpper(), 124 | "xorm": bench.CreateXorm(), 125 | "zorm": bench.CreateZorm(), 126 | "gen": bench.CreateGen(), 127 | "jet": bench.CreateJet(), 128 | "sq": bench.CreateSq(), 129 | } 130 | 131 | table := new(tabwriter.Writer) 132 | table.Init(os.Stdout, 0, 8, 2, '\t', tabwriter.AlignRight) 133 | 134 | reports := make(map[string]helper.BenchmarkReport, 0) 135 | i := 0 136 | for _, n := range orms { 137 | orm := benchmarks[n] 138 | if orm == nil { 139 | panic(fmt.Sprintf("Unknown ORM: %s", n)) 140 | } 141 | 142 | res, err := helper.RunBenchmarks(orm, reports) 143 | if err != nil { 144 | panic(fmt.Sprintf("An error occured while running the benchmarks: %v", err)) 145 | } 146 | 147 | if helper.DebugMode { 148 | if i != 0 { 149 | _, _ = fmt.Fprint(table, "\n") 150 | } 151 | _, _ = fmt.Fprintf(table, "%s Benchmark Results:\n", n) 152 | for _, result := range res.Results { 153 | if result.ErrorMsg == "" { 154 | _, _ = fmt.Fprintf(table, "%s:\t%d\t%d ns/op\t%d B/op\t%d allocs/op\n", result.Method, result.N, result.NsPerOp, result.MemBytes, result.MemAllocs) 155 | } else { 156 | _, _ = fmt.Fprintf(table, "%s:\t%s\n", result.Method, result.ErrorMsg) 157 | } 158 | } 159 | _ = table.Flush() 160 | } 161 | i++ 162 | } 163 | 164 | // Sort results 165 | for _, v := range reports { 166 | sort.Sort(v) 167 | } 168 | 169 | // Print final reports 170 | if helper.DebugMode { 171 | _, _ = fmt.Fprint(table, "\n") 172 | } 173 | _, _ = fmt.Fprintf(table, "Reports:\n\n") 174 | 175 | i = 1 176 | for method, report := range reports { 177 | _, _ = fmt.Fprintf(table, "%s\n", method) 178 | for _, result := range report { 179 | if result.ErrorMsg == "" { 180 | _, _ = fmt.Fprintf(table, "%s:\t%d\t%d ns/op\t%d B/op\t%d allocs/op\n", result.Name, result.N, result.NsPerOp, result.MemBytes, result.MemAllocs) 181 | } else { 182 | _, _ = fmt.Fprintf(table, "%s:\t%s\n", result.Name, result.ErrorMsg) 183 | } 184 | } 185 | 186 | if i != len(reports) { 187 | _, _ = fmt.Fprintf(table, "\n") 188 | } 189 | i++ 190 | } 191 | _ = table.Flush() 192 | } 193 | -------------------------------------------------------------------------------- /results.md: -------------------------------------------------------------------------------- 1 | # Results 2 | 3 | - orm-benchmark (with no flags) 4 | ``` 5 | Reports: 6 | 7 | Read 8 | sqlc: 8448 148618 ns/op 904 B/op 19 allocs/op 9 | pgx: 8600 148944 ns/op 776 B/op 18 allocs/op 10 | pgx_pool: 7509 151412 ns/op 963 B/op 19 allocs/op 11 | beego: 8461 153944 ns/op 2112 B/op 75 allocs/op 12 | raw: 8208 155169 ns/op 2093 B/op 50 allocs/op 13 | reform: 8284 164604 ns/op 3230 B/op 86 allocs/op 14 | gorp: 7862 166784 ns/op 3909 B/op 194 allocs/op 15 | pop: 8088 169084 ns/op 3206 B/op 66 allocs/op 16 | pg: 7572 172384 ns/op 872 B/op 20 allocs/op 17 | sq: 7442 175209 ns/op 11160 B/op 135 allocs/op 18 | bun: 7082 175719 ns/op 5845 B/op 39 allocs/op 19 | dbr: 7629 176276 ns/op 2184 B/op 36 allocs/op 20 | sqlboiler: 7356 176344 ns/op 957 B/op 14 allocs/op 21 | ent: 7432 177505 ns/op 5685 B/op 145 allocs/op 22 | rel: 7408 179028 ns/op 2336 B/op 47 allocs/op 23 | gorm_prep: 6666 179496 ns/op 4565 B/op 89 allocs/op 24 | zorm: 6795 193242 ns/op 3337 B/op 74 allocs/op 25 | jet: 6574 194693 ns/op 13067 B/op 273 allocs/op 26 | gorm: 5752 223097 ns/op 4983 B/op 102 allocs/op 27 | gen: 5293 241580 ns/op 10508 B/op 154 allocs/op 28 | sqlx: 4130 319489 ns/op 2008 B/op 43 allocs/op 29 | godb: 3882 336949 ns/op 4097 B/op 102 allocs/op 30 | xorm: 3566 350352 ns/op 5161 B/op 131 allocs/op 31 | upper: 3532 350440 ns/op 5087 B/op 110 allocs/op 32 | 33 | ReadSlice 34 | reform: 7446 159781 ns/op 4044 B/op 100 allocs/op 35 | pgx: 4573 247183 ns/op 30320 B/op 513 allocs/op 36 | pgx_pool: 4633 247645 ns/op 30380 B/op 513 allocs/op 37 | sqlc: 4596 259784 ns/op 54624 B/op 620 allocs/op 38 | raw: 3896 296835 ns/op 38374 B/op 1038 allocs/op 39 | pg: 3427 339762 ns/op 23002 B/op 629 allocs/op 40 | upper: 3492 349657 ns/op 4823 B/op 90 allocs/op 41 | ent: 3240 364374 ns/op 77398 B/op 2036 allocs/op 42 | sqlx: 3196 368843 ns/op 37512 B/op 1225 allocs/op 43 | gorp: 3386 370691 ns/op 57545 B/op 1494 allocs/op 44 | pop: 3070 381803 ns/op 77133 B/op 1306 allocs/op 45 | sq: 2968 399622 ns/op 152519 B/op 1829 allocs/op 46 | bun: 2877 414916 ns/op 34207 B/op 1124 allocs/op 47 | dbr: 2799 415880 ns/op 30944 B/op 1253 allocs/op 48 | beego: 2721 431007 ns/op 55351 B/op 3077 allocs/op 49 | sqlboiler: 2749 431614 ns/op 67006 B/op 2260 allocs/op 50 | gorm_prep: 2487 461743 ns/op 43532 B/op 2082 allocs/op 51 | gorm: 2293 515118 ns/op 44752 B/op 2196 allocs/op 52 | gen: 2046 540283 ns/op 50267 B/op 2247 allocs/op 53 | xorm: 2100 566538 ns/op 121237 B/op 4407 allocs/op 54 | zorm: 2068 572100 ns/op 169548 B/op 2959 allocs/op 55 | jet: 2029 585397 ns/op 192714 B/op 3642 allocs/op 56 | godb: 1839 637968 ns/op 75392 B/op 3084 allocs/op 57 | rel: 1785 666717 ns/op 149041 B/op 2553 allocs/op 58 | 59 | Insert 60 | raw: 4507 292461 ns/op 704 B/op 13 allocs/op 61 | pgx: 4158 294418 ns/op 280 B/op 8 allocs/op 62 | sqlc: 4150 298137 ns/op 280 B/op 8 allocs/op 63 | pgx_pool: 4075 298807 ns/op 298 B/op 8 allocs/op 64 | beego: 4190 300981 ns/op 2400 B/op 56 allocs/op 65 | sqlboiler: 3470 312346 ns/op 1591 B/op 34 allocs/op 66 | gorm_prep: 4120 319339 ns/op 5192 B/op 65 allocs/op 67 | jet: 4076 319977 ns/op 3584 B/op 105 allocs/op 68 | reform: 3774 320355 ns/op 1775 B/op 51 allocs/op 69 | sq: 4378 330198 ns/op 9777 B/op 100 allocs/op 70 | gorp: 3480 331171 ns/op 1798 B/op 41 allocs/op 71 | ent: 3637 332290 ns/op 4151 B/op 97 allocs/op 72 | bun: 3572 336491 ns/op 5029 B/op 13 allocs/op 73 | pg: 3733 343780 ns/op 1095 B/op 10 allocs/op 74 | dbr: 3303 355087 ns/op 2688 B/op 65 allocs/op 75 | gen: 2950 376749 ns/op 10041 B/op 131 allocs/op 76 | gorm: 3283 409477 ns/op 7209 B/op 105 allocs/op 77 | rel: 2821 445630 ns/op 2639 B/op 45 allocs/op 78 | sqlx: 2977 445687 ns/op 856 B/op 19 allocs/op 79 | upper: 2426 484638 ns/op 5913 B/op 125 allocs/op 80 | zorm: 2728 489784 ns/op 5145 B/op 104 allocs/op 81 | godb: 2509 498311 ns/op 4537 B/op 115 allocs/op 82 | xorm: 2488 506730 ns/op 3120 B/op 87 allocs/op 83 | pop: 1840 708639 ns/op 9421 B/op 234 allocs/op 84 | 85 | InsertMulti 86 | pgx: 1707 728816 ns/op 47532 B/op 38 allocs/op 87 | pgx_pool: 1640 733620 ns/op 47998 B/op 38 allocs/op 88 | sqlc: 1687 761902 ns/op 66433 B/op 639 allocs/op 89 | raw: 1402 851151 ns/op 187127 B/op 930 allocs/op 90 | sq: 1293 949722 ns/op 237251 B/op 1706 allocs/op 91 | beego: 1314 969644 ns/op 177652 B/op 2744 allocs/op 92 | gorm_prep: 1184 1047137 ns/op 254382 B/op 1890 allocs/op 93 | reform: 1184 1123778 ns/op 462209 B/op 2746 allocs/op 94 | ent: 1022 1161757 ns/op 396528 B/op 4597 allocs/op 95 | jet: 927 1358204 ns/op 338333 B/op 6493 allocs/op 96 | pg: 900 1382670 ns/op 4563 B/op 112 allocs/op 97 | sqlx: 867 1396858 ns/op 170457 B/op 1550 allocs/op 98 | bun: 865 1434560 ns/op 42580 B/op 219 allocs/op 99 | gorm: 844 1444648 ns/op 276182 B/op 5230 allocs/op 100 | gen: 826 1463510 ns/op 289370 B/op 5354 allocs/op 101 | zorm: 750 1598643 ns/op 212131 B/op 2808 allocs/op 102 | rel: 770 1625136 ns/op 312562 B/op 3265 allocs/op 103 | upper: 756 1643732 ns/op 328153 B/op 4204 allocs/op 104 | godb: 715 1753339 ns/op 260734 B/op 5894 allocs/op 105 | xorm: 717 1757731 ns/op 258931 B/op 5518 allocs/op 106 | gorp: bulk-insert is not supported 107 | sqlboiler: bulk-insert is not supported 108 | dbr: bulk-insert is not supported 109 | pop: bulk-insert is not supported 110 | 111 | Update 112 | raw: 8934 147057 ns/op 749 B/op 13 allocs/op 113 | sqlc: 8311 148037 ns/op 288 B/op 8 allocs/op 114 | pgx: 4188 301977 ns/op 288 B/op 8 allocs/op 115 | pgx_pool: 3891 304645 ns/op 305 B/op 8 allocs/op 116 | beego: 4108 306440 ns/op 1736 B/op 46 allocs/op 117 | sqlx: 3846 314410 ns/op 872 B/op 20 allocs/op 118 | sq: 3429 314900 ns/op 7417 B/op 90 allocs/op 119 | reform: 3711 316066 ns/op 1773 B/op 51 allocs/op 120 | sqlboiler: 4436 321791 ns/op 901 B/op 17 allocs/op 121 | gorm_prep: 4154 324145 ns/op 5072 B/op 56 allocs/op 122 | jet: 4131 326424 ns/op 4565 B/op 126 allocs/op 123 | gorp: 3295 327641 ns/op 1205 B/op 32 allocs/op 124 | pop: 3597 335513 ns/op 5857 B/op 184 allocs/op 125 | bun: 3705 336445 ns/op 4762 B/op 5 allocs/op 126 | ent: 3654 339315 ns/op 4725 B/op 98 allocs/op 127 | pg: 3789 353320 ns/op 768 B/op 9 allocs/op 128 | dbr: 3787 356261 ns/op 2651 B/op 57 allocs/op 129 | gorm: 2986 389390 ns/op 6832 B/op 99 allocs/op 130 | gen: 3373 403848 ns/op 13408 B/op 161 allocs/op 131 | rel: 2775 471517 ns/op 3048 B/op 45 allocs/op 132 | xorm: 2366 517855 ns/op 4305 B/op 145 allocs/op 133 | zorm: 2434 524936 ns/op 4449 B/op 84 allocs/op 134 | godb: 2534 532693 ns/op 5145 B/op 154 allocs/op 135 | upper: 1030 1156016 ns/op 16743 B/op 390 allocs/op 136 | ``` 137 | -------------------------------------------------------------------------------- /run_benchmarks.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Prototype benchmark results 4 | proto=$(cat .github/data/results.md.proto) 5 | 6 | # Debug Message 7 | echo "[go-orm-benchmarks] DBG: Benchmarks are running at the moment." 8 | 9 | # Build & Run Container 10 | docker-compose build --build-arg &>/dev/null 11 | docker-compose up --exit-code-from benchmarker 12 | 13 | # Apply output to template 14 | logs=$(docker logs go-orm-benchmarks-benchmarker-1 --tail 114) 15 | escaped=$(echo "${logs}" | sed '$!s@$@\\@g') 16 | 17 | proto=$(sed "s|@(benchmark-results)|${escaped}|g" <<< $proto) 18 | 19 | # Print final results 20 | echo "$proto" --------------------------------------------------------------------------------