Add database query in loop query

co-authored-by: Robert <robertbrignull@github.com>
co-authored-by: Sam Partington <sampart@github.com>
This commit is contained in:
Sauyon Lee
2021-08-10 02:23:17 -07:00
parent 1ffeb26a61
commit 02396dbd04
13 changed files with 1015 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
package main
import "gorm.io/gorm"
func getUsers(db *gorm.DB, names []string) []User {
res := make([]User, 0, len(names))
for _, name := range names {
var user User
db.Where("name = ?", name).First(&user)
res = append(res, user)
}
return res
}

View File

@@ -0,0 +1,29 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Database calls in loops are slower than running a single query and consume more resources. This
can lead to denial of service attacks if the loop bounds can be controlled by an attacker.</p>
</overview>
<recommendation>
<p>Ensure that where possible, database queries are not run in a loop, instead running a single query to get all relevant data.</p>
</recommendation>
<example>
<p>In the example below, users in a database are queried one by one in a loop:</p>
<sample src="DatabaseCallInLoop.go" />
<p>This is corrected by running a single query that selects all of the users at once:</p>
<sample src="DatabaseCallInLoopGood.go" />
</example>
<references>
</references>
</qhelp>

View File

@@ -0,0 +1,69 @@
/**
* @name Database call in loop
* @description Detects database operations within loops.
* Doing operations in series can be slow and lead to N+1 situations.
* @kind path-problem
* @problem.severity warning
* @precision high
* @id go/examples/database-call-in-loop
*/
import go
class DatabaseAccess extends DataFlow::MethodCallNode {
DatabaseAccess() {
exists(string name |
this.getTarget().hasQualifiedName(Gorm::packagePath(), "DB", name) and
// all terminating Gorm methods
name =
[
"Find", "Take", "Last", "Scan", "Row", "Rows", "ScanRows", "Pluck", "Count", "First",
"FirstOrInit", "FindOrCreate", "Update", "Updates", "UpdateColumn", "UpdateColumns",
"Save", "Create", "Delete", "Exec"
]
)
}
}
class CallGraphNode extends Locatable {
CallGraphNode() {
this instanceof LoopStmt
or
this instanceof CallExpr
or
this instanceof FuncDef
}
}
/**
* Holds if `pred` calls `succ`, i.e. is an edge in the call graph,
* This includes explicit edges from call -> callee, to produce better paths.
*/
predicate callGraphEdge(CallGraphNode pred, CallGraphNode succ) {
// Go from a loop to an enclosed expression.
pred.(LoopStmt).getBody().getAChild*() = succ.(CallExpr)
or
// Go from a call to the called function.
pred.(CallExpr) = succ.(FuncDef).getACall().asExpr()
or
// Go from a function to an enclosed loop.
pred.(FuncDef) = succ.(LoopStmt).getEnclosingFunction()
or
// Go from a function to an enclosed call.
pred.(FuncDef) = succ.(CallExpr).getEnclosingFunction()
}
query predicate edges(CallGraphNode pred, CallGraphNode succ) {
callGraphEdge(pred, succ) and
// Limit the range of edges to only those that are relevant.
// This helps to speed up the query by reducing the size of the outputted path information.
exists(LoopStmt loop, DatabaseAccess dbAccess |
// is between a loop and a db access
callGraphEdge*(loop, pred) and
callGraphEdge*(succ, dbAccess.asExpr())
)
}
from LoopStmt loop, DatabaseAccess dbAccess
where edges*(loop, dbAccess.asExpr())
select dbAccess, loop, dbAccess, "$@ is called in $@", dbAccess, dbAccess.toString(), loop, "a loop"

View File

@@ -0,0 +1,9 @@
package main
import "gorm.io/gorm"
func getUsersGood(db *gorm.DB, names []string) []User {
res := make([]User, 0, len(names))
db.Where("name IN ?", names).Find(&res)
return res
}

View File

@@ -0,0 +1,13 @@
edges
| DatabaseCallInLoop.go:7:2:11:2 | range statement | DatabaseCallInLoop.go:9:3:9:41 | call to First |
| test.go:10:1:12:1 | function declaration | test.go:11:2:11:13 | call to Take |
| test.go:14:1:16:1 | function declaration | test.go:15:2:15:13 | call to runQuery |
| test.go:15:2:15:13 | call to runQuery | test.go:10:1:12:1 | function declaration |
| test.go:20:2:22:2 | for statement | test.go:21:3:21:14 | call to runQuery |
| test.go:21:3:21:14 | call to runQuery | test.go:10:1:12:1 | function declaration |
| test.go:24:2:26:2 | for statement | test.go:25:3:25:17 | call to runRunQuery |
| test.go:25:3:25:17 | call to runRunQuery | test.go:14:1:16:1 | function declaration |
#select
| DatabaseCallInLoop.go:9:3:9:41 | call to First | DatabaseCallInLoop.go:7:2:11:2 | range statement | DatabaseCallInLoop.go:9:3:9:41 | call to First | $@ is called in $@ | DatabaseCallInLoop.go:9:3:9:41 | call to First | call to First | DatabaseCallInLoop.go:7:2:11:2 | range statement | a loop |
| test.go:11:2:11:13 | call to Take | test.go:20:2:22:2 | for statement | test.go:11:2:11:13 | call to Take | $@ is called in $@ | test.go:11:2:11:13 | call to Take | call to Take | test.go:20:2:22:2 | for statement | a loop |
| test.go:11:2:11:13 | call to Take | test.go:24:2:26:2 | for statement | test.go:11:2:11:13 | call to Take | $@ is called in $@ | test.go:11:2:11:13 | call to Take | call to Take | test.go:24:2:26:2 | for statement | a loop |

View File

@@ -0,0 +1,13 @@
package main
import "gorm.io/gorm"
func getUsers(db *gorm.DB, names []string) []User {
res := make([]User, 0, len(names))
for _, name := range names {
var user User
db.Where("name = ?", name).First(&user)
res = append(res, user)
}
return res
}

View File

@@ -0,0 +1 @@
experimental/CWE-400/DatabaseCallInLoop.ql

View File

@@ -0,0 +1,9 @@
package main
import "gorm.io/gorm"
func getUsersGood(db *gorm.DB, names []string) []User {
res := make([]User, 0, len(names))
db.Where("name IN ?", names).Find(&res)
return res
}

View File

@@ -0,0 +1,5 @@
module query-tests/databasecallinloop
go 1.16
require gorm.io/gorm v1.21.12

View File

@@ -0,0 +1,27 @@
package main
import "gorm.io/gorm"
type User struct {
Id int64
Name string
}
func runQuery(db *gorm.DB) {
db.Take(nil)
}
func runRunQuery(db *gorm.DB) {
runQuery(db)
}
func main() {
var db *gorm.DB
for i := 0; i < 10; i++ {
runQuery(db)
}
for i := 10; i > 0; i-- {
runRunQuery(db)
}
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013-NOW Jinzhu <wosmvp@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,803 @@
// Code generated by depstubber. DO NOT EDIT.
// This is a simple stub for gorm.io/gorm, strictly for use in testing.
// See the LICENSE file for information about the licensing of the original library.
// Source: gorm.io/gorm (exports: DB; functions: )
// Package gorm is a stub of gorm.io/gorm, generated by depstubber.
package gorm
import (
context "context"
sql "database/sql"
reflect "reflect"
strings "strings"
sync "sync"
time "time"
)
type Association struct {
DB *DB
Relationship interface{}
Error error
}
func (_ *Association) Append(_ ...interface{}) error {
return nil
}
func (_ *Association) Clear() error {
return nil
}
func (_ *Association) Count() int64 {
return 0
}
func (_ *Association) Delete(_ ...interface{}) error {
return nil
}
func (_ *Association) Find(_ interface{}, _ ...interface{}) error {
return nil
}
func (_ *Association) Replace(_ ...interface{}) error {
return nil
}
type ColumnType interface {
DatabaseTypeName() string
DecimalSize() (int64, int64, bool)
Length() (int64, bool)
Name() string
Nullable() (bool, bool)
}
type Config struct {
SkipDefaultTransaction bool
NamingStrategy interface{}
FullSaveAssociations bool
Logger interface{}
NowFunc func() time.Time
DryRun bool
PrepareStmt bool
DisableAutomaticPing bool
DisableForeignKeyConstraintWhenMigrating bool
DisableNestedTransaction bool
AllowGlobalUpdate bool
QueryFields bool
CreateBatchSize int
ClauseBuilders map[string]interface{}
ConnPool ConnPool
Dialector Dialector
Plugins map[string]Plugin
}
func (_ Config) BindVarTo(_ interface{}, _ *Statement, _ interface{}) {}
func (_ Config) DataTypeOf(_ interface{}) string {
return ""
}
func (_ Config) DefaultValueOf(_ interface{}) interface{} {
return nil
}
func (_ Config) Explain(_ string, _ ...interface{}) string {
return ""
}
func (_ Config) Initialize(_ *DB) error {
return nil
}
func (_ Config) Migrator(_ *DB) Migrator {
return nil
}
func (_ Config) Name() string {
return ""
}
func (_ Config) QuoteTo(_ interface{}, _ string) {}
func (_ *Config) AfterInitialize(_ *DB) error {
return nil
}
func (_ *Config) Apply(_ *Config) error {
return nil
}
type ConnPool interface {
ExecContext(_ context.Context, _ string, _ ...interface{}) (sql.Result, error)
PrepareContext(_ context.Context, _ string) (*sql.Stmt, error)
QueryContext(_ context.Context, _ string, _ ...interface{}) (*sql.Rows, error)
QueryRowContext(_ context.Context, _ string, _ ...interface{}) *sql.Row
}
type DB struct {
Config *Config
Error error
RowsAffected int64
Statement *Statement
}
func (_ DB) AfterInitialize(_ *DB) error {
return nil
}
func (_ DB) Apply(_ *Config) error {
return nil
}
func (_ DB) BindVarTo(_ interface{}, _ *Statement, _ interface{}) {}
func (_ DB) DataTypeOf(_ interface{}) string {
return ""
}
func (_ DB) DefaultValueOf(_ interface{}) interface{} {
return nil
}
func (_ DB) Explain(_ string, _ ...interface{}) string {
return ""
}
func (_ DB) Initialize(_ *DB) error {
return nil
}
func (_ DB) Name() string {
return ""
}
func (_ DB) QuoteTo(_ interface{}, _ string) {}
func (_ *DB) AddError(_ error) error {
return nil
}
func (_ *DB) Assign(_ ...interface{}) *DB {
return nil
}
func (_ *DB) Association(_ string) *Association {
return nil
}
func (_ *DB) Attrs(_ ...interface{}) *DB {
return nil
}
func (_ *DB) AutoMigrate(_ ...interface{}) error {
return nil
}
func (_ *DB) Begin(_ ...*sql.TxOptions) *DB {
return nil
}
func (_ *DB) Callback() interface{} {
return nil
}
func (_ *DB) Clauses(_ ...interface{}) *DB {
return nil
}
func (_ *DB) Commit() *DB {
return nil
}
func (_ *DB) Count(_ *int64) *DB {
return nil
}
func (_ *DB) Create(_ interface{}) *DB {
return nil
}
func (_ *DB) CreateInBatches(_ interface{}, _ int) *DB {
return nil
}
func (_ *DB) DB() (*sql.DB, error) {
return nil, nil
}
func (_ *DB) Debug() *DB {
return nil
}
func (_ *DB) Delete(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ *DB) Distinct(_ ...interface{}) *DB {
return nil
}
func (_ *DB) Exec(_ string, _ ...interface{}) *DB {
return nil
}
func (_ *DB) Find(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ *DB) FindInBatches(_ interface{}, _ int, _ func(*DB, int) error) *DB {
return nil
}
func (_ *DB) First(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ *DB) FirstOrCreate(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ *DB) FirstOrInit(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ *DB) Get(_ string) (interface{}, bool) {
return nil, false
}
func (_ *DB) Group(_ string) *DB {
return nil
}
func (_ *DB) Having(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ *DB) InstanceGet(_ string) (interface{}, bool) {
return nil, false
}
func (_ *DB) InstanceSet(_ string, _ interface{}) *DB {
return nil
}
func (_ *DB) Joins(_ string, _ ...interface{}) *DB {
return nil
}
func (_ *DB) Last(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ *DB) Limit(_ int) *DB {
return nil
}
func (_ *DB) Migrator() Migrator {
return nil
}
func (_ *DB) Model(_ interface{}) *DB {
return nil
}
func (_ *DB) Not(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ *DB) Offset(_ int) *DB {
return nil
}
func (_ *DB) Omit(_ ...string) *DB {
return nil
}
func (_ *DB) Or(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ *DB) Order(_ interface{}) *DB {
return nil
}
func (_ *DB) Pluck(_ string, _ interface{}) *DB {
return nil
}
func (_ *DB) Preload(_ string, _ ...interface{}) *DB {
return nil
}
func (_ *DB) Raw(_ string, _ ...interface{}) *DB {
return nil
}
func (_ *DB) Rollback() *DB {
return nil
}
func (_ *DB) RollbackTo(_ string) *DB {
return nil
}
func (_ *DB) Row() *sql.Row {
return nil
}
func (_ *DB) Rows() (*sql.Rows, error) {
return nil, nil
}
func (_ *DB) Save(_ interface{}) *DB {
return nil
}
func (_ *DB) SavePoint(_ string) *DB {
return nil
}
func (_ *DB) Scan(_ interface{}) *DB {
return nil
}
func (_ *DB) ScanRows(_ *sql.Rows, _ interface{}) error {
return nil
}
func (_ *DB) Scopes(_ ...func(*DB) *DB) *DB {
return nil
}
func (_ *DB) Select(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ *DB) Session(_ *Session) *DB {
return nil
}
func (_ *DB) Set(_ string, _ interface{}) *DB {
return nil
}
func (_ *DB) SetupJoinTable(_ interface{}, _ string, _ interface{}) error {
return nil
}
func (_ *DB) Table(_ string, _ ...interface{}) *DB {
return nil
}
func (_ *DB) Take(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ *DB) Transaction(_ func(*DB) error, _ ...*sql.TxOptions) error {
return nil
}
func (_ *DB) Unscoped() *DB {
return nil
}
func (_ *DB) Update(_ string, _ interface{}) *DB {
return nil
}
func (_ *DB) UpdateColumn(_ string, _ interface{}) *DB {
return nil
}
func (_ *DB) UpdateColumns(_ interface{}) *DB {
return nil
}
func (_ *DB) Updates(_ interface{}) *DB {
return nil
}
func (_ *DB) Use(_ Plugin) error {
return nil
}
func (_ *DB) Where(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ *DB) WithContext(_ context.Context) *DB {
return nil
}
type Dialector interface {
BindVarTo(_ interface{}, _ *Statement, _ interface{})
DataTypeOf(_ interface{}) string
DefaultValueOf(_ interface{}) interface{}
Explain(_ string, _ ...interface{}) string
Initialize(_ *DB) error
Migrator(_ *DB) Migrator
Name() string
QuoteTo(_ interface{}, _ string)
}
type Migrator interface {
AddColumn(_ interface{}, _ string) error
AlterColumn(_ interface{}, _ string) error
AutoMigrate(_ ...interface{}) error
ColumnTypes(_ interface{}) ([]ColumnType, error)
CreateConstraint(_ interface{}, _ string) error
CreateIndex(_ interface{}, _ string) error
CreateTable(_ ...interface{}) error
CreateView(_ string, _ ViewOption) error
CurrentDatabase() string
DropColumn(_ interface{}, _ string) error
DropConstraint(_ interface{}, _ string) error
DropIndex(_ interface{}, _ string) error
DropTable(_ ...interface{}) error
DropView(_ string) error
FullDataTypeOf(_ interface{}) interface{}
HasColumn(_ interface{}, _ string) bool
HasConstraint(_ interface{}, _ string) bool
HasIndex(_ interface{}, _ string) bool
HasTable(_ interface{}) bool
MigrateColumn(_ interface{}, _ interface{}, _ ColumnType) error
RenameColumn(_ interface{}, _ string, _ string) error
RenameIndex(_ interface{}, _ string, _ string) error
RenameTable(_ interface{}, _ interface{}) error
}
type Plugin interface {
Initialize(_ *DB) error
Name() string
}
type Session struct {
DryRun bool
PrepareStmt bool
NewDB bool
SkipHooks bool
SkipDefaultTransaction bool
DisableNestedTransaction bool
AllowGlobalUpdate bool
FullSaveAssociations bool
QueryFields bool
Context context.Context
Logger interface{}
NowFunc func() time.Time
CreateBatchSize int
}
type Statement struct {
DB *DB
TableExpr interface{}
Table string
Model interface{}
Unscoped bool
Dest interface{}
ReflectValue reflect.Value
Clauses map[string]interface{}
BuildClauses []string
Distinct bool
Selects []string
Omits []string
Joins []interface{}
Preloads map[string][]interface{}
Settings sync.Map
ConnPool ConnPool
Schema interface{}
Context context.Context
RaiseErrorOnNotFound bool
SkipHooks bool
SQL strings.Builder
Vars []interface{}
CurDestIndex int
}
func (_ Statement) AddError(_ error) error {
return nil
}
func (_ Statement) AfterInitialize(_ *DB) error {
return nil
}
func (_ Statement) Apply(_ *Config) error {
return nil
}
func (_ Statement) Assign(_ ...interface{}) *DB {
return nil
}
func (_ Statement) Association(_ string) *Association {
return nil
}
func (_ Statement) Attrs(_ ...interface{}) *DB {
return nil
}
func (_ Statement) AutoMigrate(_ ...interface{}) error {
return nil
}
func (_ Statement) Begin(_ ...*sql.TxOptions) *DB {
return nil
}
func (_ Statement) BindVarTo(_ interface{}, _ *Statement, _ interface{}) {}
func (_ Statement) Callback() interface{} {
return nil
}
func (_ Statement) Commit() *DB {
return nil
}
func (_ Statement) Count(_ *int64) *DB {
return nil
}
func (_ Statement) Create(_ interface{}) *DB {
return nil
}
func (_ Statement) CreateInBatches(_ interface{}, _ int) *DB {
return nil
}
func (_ Statement) DataTypeOf(_ interface{}) string {
return ""
}
func (_ Statement) Debug() *DB {
return nil
}
func (_ Statement) DefaultValueOf(_ interface{}) interface{} {
return nil
}
func (_ Statement) Delete(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ Statement) Exec(_ string, _ ...interface{}) *DB {
return nil
}
func (_ Statement) Explain(_ string, _ ...interface{}) string {
return ""
}
func (_ Statement) Find(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ Statement) FindInBatches(_ interface{}, _ int, _ func(*DB, int) error) *DB {
return nil
}
func (_ Statement) First(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ Statement) FirstOrCreate(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ Statement) FirstOrInit(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ Statement) Get(_ string) (interface{}, bool) {
return nil, false
}
func (_ Statement) Group(_ string) *DB {
return nil
}
func (_ Statement) Having(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ Statement) Initialize(_ *DB) error {
return nil
}
func (_ Statement) InstanceGet(_ string) (interface{}, bool) {
return nil, false
}
func (_ Statement) InstanceSet(_ string, _ interface{}) *DB {
return nil
}
func (_ Statement) Last(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ Statement) Limit(_ int) *DB {
return nil
}
func (_ Statement) Migrator() Migrator {
return nil
}
func (_ Statement) Name() string {
return ""
}
func (_ Statement) Not(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ Statement) Offset(_ int) *DB {
return nil
}
func (_ Statement) Omit(_ ...string) *DB {
return nil
}
func (_ Statement) Or(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ Statement) Order(_ interface{}) *DB {
return nil
}
func (_ Statement) Pluck(_ string, _ interface{}) *DB {
return nil
}
func (_ Statement) Preload(_ string, _ ...interface{}) *DB {
return nil
}
func (_ Statement) Raw(_ string, _ ...interface{}) *DB {
return nil
}
func (_ Statement) Rollback() *DB {
return nil
}
func (_ Statement) RollbackTo(_ string) *DB {
return nil
}
func (_ Statement) Row() *sql.Row {
return nil
}
func (_ Statement) Rows() (*sql.Rows, error) {
return nil, nil
}
func (_ Statement) Save(_ interface{}) *DB {
return nil
}
func (_ Statement) SavePoint(_ string) *DB {
return nil
}
func (_ Statement) Scan(_ interface{}) *DB {
return nil
}
func (_ Statement) ScanRows(_ *sql.Rows, _ interface{}) error {
return nil
}
func (_ Statement) Scopes(_ ...func(*DB) *DB) *DB {
return nil
}
func (_ Statement) Select(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ Statement) Session(_ *Session) *DB {
return nil
}
func (_ Statement) Set(_ string, _ interface{}) *DB {
return nil
}
func (_ Statement) SetupJoinTable(_ interface{}, _ string, _ interface{}) error {
return nil
}
func (_ Statement) Take(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ Statement) Transaction(_ func(*DB) error, _ ...*sql.TxOptions) error {
return nil
}
func (_ Statement) Update(_ string, _ interface{}) *DB {
return nil
}
func (_ Statement) UpdateColumn(_ string, _ interface{}) *DB {
return nil
}
func (_ Statement) UpdateColumns(_ interface{}) *DB {
return nil
}
func (_ Statement) Updates(_ interface{}) *DB {
return nil
}
func (_ Statement) Use(_ Plugin) error {
return nil
}
func (_ Statement) Where(_ interface{}, _ ...interface{}) *DB {
return nil
}
func (_ Statement) WithContext(_ context.Context) *DB {
return nil
}
func (_ *Statement) AddClause(_ interface{}) {}
func (_ *Statement) AddClauseIfNotExists(_ interface{}) {}
func (_ *Statement) AddVar(_ interface{}, _ ...interface{}) {}
func (_ *Statement) Build(_ ...string) {}
func (_ *Statement) BuildCondition(_ interface{}, _ ...interface{}) []interface{} {
return nil
}
func (_ *Statement) Changed(_ ...string) bool {
return false
}
func (_ *Statement) Parse(_ interface{}) error {
return nil
}
func (_ *Statement) Quote(_ interface{}) string {
return ""
}
func (_ *Statement) QuoteTo(_ interface{}, _ interface{}) {}
func (_ *Statement) SelectAndOmitColumns(_ bool, _ bool) (map[string]bool, bool) {
return nil, false
}
func (_ *Statement) SetColumn(_ string, _ interface{}, _ ...bool) {}
func (_ *Statement) WriteByte(_ byte) error {
return nil
}
func (_ *Statement) WriteQuoted(_ interface{}) {}
func (_ *Statement) WriteString(_ string) (int, error) {
return 0, nil
}
type ViewOption struct {
Replace bool
CheckOption string
Query *DB
}

View File

@@ -0,0 +1,3 @@
# gorm.io/gorm v1.21.12
## explicit
gorm.io/gorm