├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── config.yml └── workflows │ ├── build.yml │ └── release.yml ├── .golangci.yml ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── base.go ├── base_test.go ├── bench_test.go ├── conv_test.go ├── db.go ├── db_test.go ├── doc.go ├── error.go ├── example_array_test.go ├── example_composite_test.go ├── example_custom_test.go ├── example_db_model_test.go ├── example_db_query_test.go ├── example_hstore_test.go ├── example_many2many_self_test.go ├── example_many2many_test.go ├── example_model_test.go ├── example_placeholders_test.go ├── example_soft_delete_test.go ├── example_test.go ├── export_test.go ├── extra ├── pgdebug │ ├── go.mod │ ├── go.sum │ └── pgdebug.go ├── pgotel │ ├── README.md │ ├── example │ │ ├── Makefile │ │ ├── README.md │ │ ├── docker-compose.yml │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ ├── go.mod │ ├── go.sum │ ├── pgotel.go │ └── pgotel_test.go └── pgsegment │ ├── go.mod │ ├── go.sum │ └── pgsegment.go ├── go.mod ├── go.sum ├── hook.go ├── hook_test.go ├── internal ├── context.go ├── error.go ├── internal.go ├── log.go ├── parser │ ├── parser.go │ └── streaming_parser.go ├── pool │ ├── bench_test.go │ ├── conn.go │ ├── export_test.go │ ├── main_test.go │ ├── pool.go │ ├── pool_single.go │ ├── pool_single_test.go │ ├── pool_sticky.go │ ├── pool_sticky_test.go │ ├── pool_test.go │ ├── reader.go │ ├── reader_buf.go │ ├── reader_bytes.go │ └── write_buffer.go ├── safe.go ├── strconv.go ├── underscore.go ├── underscore_test.go ├── unsafe.go └── util.go ├── listener.go ├── listener_test.go ├── loader_test.go ├── main_test.go ├── messages.go ├── options.go ├── options_test.go ├── orm ├── bench_test.go ├── composite.go ├── composite_create.go ├── composite_drop.go ├── composite_parser.go ├── count_estimate.go ├── delete.go ├── delete_test.go ├── field.go ├── format.go ├── format_test.go ├── hook.go ├── insert.go ├── insert_test.go ├── internal_test.go ├── join.go ├── join_test.go ├── main_test.go ├── model.go ├── model_discard.go ├── model_func.go ├── model_map.go ├── model_map_slice.go ├── model_scan.go ├── model_slice.go ├── model_table.go ├── model_table_m2m.go ├── model_table_many.go ├── model_table_slice.go ├── model_table_struct.go ├── msgpack.go ├── orm.go ├── query.go ├── query_test.go ├── relation.go ├── result.go ├── select.go ├── select_nil_test.go ├── select_test.go ├── table.go ├── table_create.go ├── table_create_test.go ├── table_drop.go ├── table_drop_test.go ├── table_params.go ├── table_test.go ├── tables.go ├── types.go ├── update.go ├── update_test.go ├── util.go └── util_test.go ├── pg.go ├── pgjson ├── json.go └── provider.go ├── pool_test.go ├── race_test.go ├── result.go ├── scripts ├── release.sh └── tag.sh ├── stmt.go ├── testdata ├── issue1079.go ├── issue1214.go ├── issue1664.go └── issue1726.go ├── tx.go ├── tx_test.go ├── types ├── append.go ├── append_ident.go ├── append_ident_test.go ├── append_jsonb.go ├── append_jsonb_test.go ├── append_test.go ├── append_value.go ├── array.go ├── array_append.go ├── array_parser.go ├── array_parser_test.go ├── array_scan.go ├── bench_test.go ├── column.go ├── doc.go ├── flags.go ├── hex.go ├── hstore.go ├── hstore_append.go ├── hstore_parser.go ├── hstore_parser_test.go ├── hstore_scan.go ├── in_op.go ├── in_op_test.go ├── null_time.go ├── scan.go ├── scan_value.go ├── time.go ├── time_test.go └── types.go └── version.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://uptrace.dev/sponsor'] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | Issue tracker is used for reporting bugs and discussing new features. Please use 10 | [Discord](https://discord.gg/rWtp5Aj) or [stackoverflow](https://stackoverflow.com) for supporting 11 | issues. 12 | 13 | 14 | 15 | ## Expected Behavior 16 | 17 | 18 | 19 | ## Current Behavior 20 | 21 | 22 | 23 | ## Possible Solution 24 | 25 | 26 | 27 | ## Steps to Reproduce 28 | 29 | 30 | 31 | 32 | 1. 33 | 2. 34 | 3. 35 | 4. 36 | 37 | ## Context (Environment) 38 | 39 | 40 | 41 | 42 | 43 | 44 | ## Detailed Description 45 | 46 | 47 | 48 | ## Possible Implementation 49 | 50 | 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Discussions 4 | url: https://github.com/go-pg/pg/discussions 5 | about: Ask a question via GitHub Discussions 6 | - name: Discord 7 | url: https://discord.gg/rWtp5Aj 8 | about: Ask a question via Discord 9 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [master, v10, v11] 6 | pull_request: 7 | branches: [master, v10, v11] 8 | 9 | jobs: 10 | build: 11 | name: build 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | go-version: [1.21.x, 1.22.x, 1.23.x] 16 | 17 | services: 18 | postgres: 19 | image: postgres:14 20 | env: 21 | POSTGRES_PASSWORD: postgres 22 | options: >- 23 | --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 24 | ports: 25 | - 5432:5432 26 | 27 | steps: 28 | - name: Set up ${{ matrix.go-version }} 29 | uses: actions/setup-go@v2 30 | with: 31 | go-version: ${{ matrix.go-version }} 32 | 33 | - name: Checkout code 34 | uses: actions/checkout@v2 35 | 36 | - name: Install hstore 37 | run: PGPASSWORD=postgres psql -U postgres -h localhost -c "CREATE EXTENSION hstore" 38 | 39 | - name: Test 40 | run: make test 41 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Releases 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: ncipollo/release-action@v1 14 | with: 15 | body: 16 | Please refer to [CHANGELOG.md](https://github.com/go-pg/pg/blob/v10/CHANGELOG.md) for 17 | details 18 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | concurrency: 8 3 | deadline: 5m 4 | tests: false 5 | linters: 6 | enable-all: true 7 | disable: 8 | - gochecknoglobals 9 | - gocognit 10 | - gomnd 11 | - wsl 12 | - funlen 13 | - godox 14 | - goerr113 15 | - exhaustive 16 | - nestif 17 | - gofumpt 18 | - goconst 19 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | semi: false 2 | singleQuote: true 3 | proseWrap: always 4 | printWidth: 100 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 github.com/go-pg/pg Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 18 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGE_DIRS := $(shell find . -mindepth 2 -type f -name 'go.mod' -exec dirname {} \; | sort) 2 | 3 | all: 4 | TZ= go test ./... 5 | TZ= go test ./... -short -race 6 | TZ= go test ./... -run=NONE -bench=. -benchmem 7 | env GOOS=linux GOARCH=386 go test ./... 8 | go vet 9 | golangci-lint run 10 | 11 | .PHONY: test 12 | test: 13 | TZ= PGSSLMODE=disable go test ./... -v -race 14 | 15 | tag: 16 | git tag $(VERSION) 17 | git tag extra/pgdebug/$(VERSION) 18 | git tag extra/pgotel/$(VERSION) 19 | git tag extra/pgsegment/$(VERSION) 20 | 21 | fmt: 22 | gofmt -w -s ./ 23 | goimports -w -local github.com/go-pg/pg ./ 24 | 25 | go_mod_tidy: 26 | go get -u && go mod tidy 27 | set -e; for dir in $(PACKAGE_DIRS); do \ 28 | echo "go mod tidy in $${dir}"; \ 29 | (cd "$${dir}" && \ 30 | go get -u && \ 31 | go mod tidy); \ 32 | done 33 | -------------------------------------------------------------------------------- /base_test.go: -------------------------------------------------------------------------------- 1 | package pg 2 | 3 | import ( 4 | "context" 5 | "encoding/binary" 6 | "net" 7 | "testing" 8 | "time" 9 | 10 | "github.com/go-pg/pg/v10/internal/pool" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | /* 15 | The test is for testing the case that sending a cancel request when the timeout from connection comes earlier than ctx.Done(). 16 | */ 17 | func Test_baseDB_withConn(t *testing.T) { 18 | b := mockBaseDB{} 19 | b.init() 20 | b.pool = &mockPooler{} 21 | ctx, _ := context.WithDeadline(context.TODO(), time.Now().Add(1000*time.Second)) // Make a deadline in context further than the timeout of connection. 22 | b.withConn(ctx, func(context.Context, *pool.Conn) error { 23 | // Immediately returns the error, so it is faster than the ctx.Done() returns. The error code here according to the function `isBadConn`. 24 | return &mockPGError{map[byte]string{byte('C'): "57014"}} 25 | }) 26 | // In the new change, a cancel request is sent to db and that connection is removed from the connection pool. 27 | // Check if the cancel request, its code int32(80877102), is sent. 28 | assert.Equal(t, int32(80877102), b.pool.(*mockPooler).mockConn.cancelCode) 29 | assert.True(t, b.pool.(*mockPooler).toRemove) 30 | } 31 | 32 | type mockBaseDB struct { 33 | baseDB 34 | } 35 | 36 | func (m *mockBaseDB) init() { 37 | m.opt = &Options{} 38 | } 39 | 40 | type mockPooler struct { 41 | conn *pool.Conn 42 | toRemove bool 43 | mockConn mockConn 44 | } 45 | 46 | func (m *mockPooler) NewConn(ctx context.Context) (*pool.Conn, error) { 47 | m.mockConn = mockConn{} 48 | m.conn = pool.NewConn(&m.mockConn, pool.NewConnPool(&pool.Options{})) 49 | m.conn.ProcessID = 123 50 | m.conn.SecretKey = 234 51 | return m.conn, nil 52 | } 53 | 54 | func (m *mockPooler) CloseConn(conn *pool.Conn) error { 55 | return nil 56 | } 57 | 58 | func (m *mockPooler) Get(ctx context.Context) (*pool.Conn, error) { 59 | return &pool.Conn{ProcessID: 123, SecretKey: 234, Inited: true}, nil 60 | } 61 | 62 | func (m *mockPooler) Put(ctx context.Context, conn *pool.Conn) { 63 | return 64 | } 65 | 66 | func (m *mockPooler) Remove(ctx context.Context, conn *pool.Conn, err error) { 67 | m.toRemove = true 68 | return 69 | } 70 | 71 | func (m *mockPooler) Len() int { 72 | return 1 73 | } 74 | 75 | func (m *mockPooler) IdleLen() int { 76 | return 1 77 | } 78 | 79 | func (m *mockPooler) Stats() *pool.Stats { 80 | return nil 81 | } 82 | 83 | func (m *mockPooler) Close() error { 84 | return nil 85 | } 86 | 87 | func (m *mockPooler) GetWriteBuffer() *pool.WriteBuffer { 88 | return pool.NewWriteBuffer(1024) 89 | } 90 | 91 | func (m *mockPooler) PutWriteBuffer(_ *pool.WriteBuffer) { 92 | } 93 | 94 | func (m *mockPooler) GetReaderContext() *pool.ReaderContext { 95 | return pool.NewReaderContext(1024) 96 | } 97 | 98 | func (m *mockPooler) PutReaderContext(_ *pool.ReaderContext) { 99 | } 100 | 101 | type mockPGError struct { 102 | M map[byte]string 103 | } 104 | 105 | func (m *mockPGError) Error() string { 106 | return "" 107 | } 108 | 109 | func (m *mockPGError) Field(field byte) string { 110 | return m.M[field] 111 | } 112 | 113 | func (m *mockPGError) IntegrityViolation() bool { 114 | return false 115 | } 116 | 117 | type mockConn struct { 118 | cancelCode int32 119 | } 120 | 121 | func (m *mockConn) Read(b []byte) (n int, err error) { 122 | return 0, nil 123 | } 124 | 125 | func (m *mockConn) Write(b []byte) (n int, err error) { 126 | m.cancelCode = int32(binary.BigEndian.Uint32(b[4:8])) 127 | return 0, nil 128 | } 129 | 130 | func (m *mockConn) Close() error { 131 | return nil 132 | } 133 | 134 | func (m *mockConn) LocalAddr() net.Addr { 135 | return nil 136 | } 137 | 138 | func (m *mockConn) RemoteAddr() net.Addr { 139 | return nil 140 | } 141 | 142 | func (m *mockConn) SetDeadline(t time.Time) error { 143 | return nil 144 | } 145 | 146 | func (m *mockConn) SetReadDeadline(t time.Time) error { 147 | return nil 148 | } 149 | 150 | func (m *mockConn) SetWriteDeadline(t time.Time) error { 151 | return nil 152 | } 153 | -------------------------------------------------------------------------------- /db.go: -------------------------------------------------------------------------------- 1 | package pg 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/go-pg/pg/v10/internal/pool" 9 | "github.com/go-pg/pg/v10/orm" 10 | ) 11 | 12 | // Connect connects to a database using provided options. 13 | // 14 | // The returned DB is safe for concurrent use by multiple goroutines 15 | // and maintains its own connection pool. 16 | func Connect(opt *Options) *DB { 17 | opt.init() 18 | return newDB( 19 | context.Background(), 20 | &baseDB{ 21 | opt: opt, 22 | pool: newConnPool(opt), 23 | fmter: orm.NewFormatter(), 24 | }, 25 | ) 26 | } 27 | 28 | func newDB(ctx context.Context, baseDB *baseDB) *DB { 29 | db := &DB{ 30 | baseDB: baseDB.clone(), 31 | ctx: ctx, 32 | } 33 | db.baseDB.db = db 34 | return db 35 | } 36 | 37 | // DB is a database handle representing a pool of zero or more 38 | // underlying connections. It's safe for concurrent use by multiple 39 | // goroutines. 40 | type DB struct { 41 | *baseDB 42 | ctx context.Context 43 | } 44 | 45 | var _ orm.DB = (*DB)(nil) 46 | 47 | func (db *DB) String() string { 48 | return fmt.Sprintf("DB", db.opt.Addr, db.fmter) 49 | } 50 | 51 | // Options returns read-only Options that were used to connect to the DB. 52 | func (db *DB) Options() *Options { 53 | return db.opt 54 | } 55 | 56 | // Context returns DB context. 57 | func (db *DB) Context() context.Context { 58 | return db.ctx 59 | } 60 | 61 | // WithContext returns a copy of the DB that uses the ctx. 62 | func (db *DB) WithContext(ctx context.Context) *DB { 63 | return newDB(ctx, db.baseDB) 64 | } 65 | 66 | // WithTimeout returns a copy of the DB that uses d as the read/write timeout. 67 | func (db *DB) WithTimeout(d time.Duration) *DB { 68 | return newDB(db.ctx, db.baseDB.WithTimeout(d)) 69 | } 70 | 71 | // WithParam returns a copy of the DB that replaces the param with the value 72 | // in queries. 73 | func (db *DB) WithParam(param string, value interface{}) *DB { 74 | return newDB(db.ctx, db.baseDB.WithParam(param, value)) 75 | } 76 | 77 | // Listen listens for notifications sent with NOTIFY command. 78 | func (db *DB) Listen(ctx context.Context, channels ...string) *Listener { 79 | ln := &Listener{ 80 | db: db, 81 | } 82 | ln.init() 83 | _ = ln.Listen(ctx, channels...) 84 | return ln 85 | } 86 | 87 | // Conn represents a single database connection rather than a pool of database 88 | // connections. Prefer running queries from DB unless there is a specific 89 | // need for a continuous single database connection. 90 | // 91 | // A Conn must call Close to return the connection to the database pool 92 | // and may do so concurrently with a running query. 93 | // 94 | // After a call to Close, all operations on the connection fail. 95 | type Conn struct { 96 | *baseDB 97 | ctx context.Context 98 | } 99 | 100 | var _ orm.DB = (*Conn)(nil) 101 | 102 | // Conn returns a single connection from the connection pool. 103 | // Queries run on the same Conn will be run in the same database session. 104 | // 105 | // Every Conn must be returned to the database pool after use by 106 | // calling Conn.Close. 107 | func (db *DB) Conn() *Conn { 108 | return newConn(db.ctx, db.baseDB.withPool(pool.NewStickyConnPool(db.pool))) 109 | } 110 | 111 | func newConn(ctx context.Context, baseDB *baseDB) *Conn { 112 | conn := &Conn{ 113 | baseDB: baseDB, 114 | ctx: ctx, 115 | } 116 | conn.baseDB.db = conn 117 | return conn 118 | } 119 | 120 | // Context returns DB context. 121 | func (db *Conn) Context() context.Context { 122 | if db.ctx != nil { 123 | return db.ctx 124 | } 125 | return context.Background() 126 | } 127 | 128 | // WithContext returns a copy of the DB that uses the ctx. 129 | func (db *Conn) WithContext(ctx context.Context) *Conn { 130 | return newConn(ctx, db.baseDB) 131 | } 132 | 133 | // WithTimeout returns a copy of the DB that uses d as the read/write timeout. 134 | func (db *Conn) WithTimeout(d time.Duration) *Conn { 135 | return newConn(db.ctx, db.baseDB.WithTimeout(d)) 136 | } 137 | 138 | // WithParam returns a copy of the DB that replaces the param with the value 139 | // in queries. 140 | func (db *Conn) WithParam(param string, value interface{}) *Conn { 141 | return newConn(db.ctx, db.baseDB.WithParam(param, value)) 142 | } 143 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | pg provides PostgreSQL client. 3 | */ 4 | package pg 5 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package pg 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/go-pg/pg/v10/internal" 7 | ) 8 | 9 | // ErrNoRows is returned by QueryOne and ExecOne when query returned zero rows 10 | // but at least one row is expected. 11 | var ErrNoRows = internal.ErrNoRows 12 | 13 | // ErrMultiRows is returned by QueryOne and ExecOne when query returned 14 | // multiple rows but exactly one row is expected. 15 | var ErrMultiRows = internal.ErrMultiRows 16 | 17 | // Error represents an error returned by PostgreSQL server 18 | // using PostgreSQL ErrorResponse protocol. 19 | // 20 | // https://www.postgresql.org/docs/10/static/protocol-message-formats.html 21 | type Error interface { 22 | error 23 | 24 | // Field returns a string value associated with an error field. 25 | // 26 | // https://www.postgresql.org/docs/10/static/protocol-error-fields.html 27 | Field(field byte) string 28 | 29 | // IntegrityViolation reports whether an error is a part of 30 | // Integrity Constraint Violation class of errors. 31 | // 32 | // https://www.postgresql.org/docs/10/static/errcodes-appendix.html 33 | IntegrityViolation() bool 34 | } 35 | 36 | var _ Error = (*internal.PGError)(nil) 37 | 38 | func isBadConn(err error, allowTimeout bool) (bool, string) { 39 | if err == nil { 40 | return false, "" 41 | } 42 | if _, ok := err.(internal.Error); ok { 43 | return false, "" 44 | } 45 | if pgErr, ok := err.(Error); ok { 46 | switch pgErr.Field('V') { 47 | case "FATAL", "PANIC": 48 | return true, "" 49 | } 50 | switch pgErr.Field('C') { 51 | case "25P02": // current transaction is aborted 52 | return true, "25P02" 53 | case "57014": // canceling statement due to user request 54 | return true, "57014" 55 | } 56 | return false, "" 57 | } 58 | if allowTimeout { 59 | if netErr, ok := err.(net.Error); ok && netErr.Timeout() { 60 | return !netErr.Temporary(), "" 61 | } 62 | } 63 | return true, "" 64 | } 65 | 66 | //------------------------------------------------------------------------------ 67 | 68 | type timeoutError interface { 69 | Timeout() bool 70 | } 71 | -------------------------------------------------------------------------------- /example_array_test.go: -------------------------------------------------------------------------------- 1 | package pg_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-pg/pg/v10" 7 | "github.com/go-pg/pg/v10/types" 8 | ) 9 | 10 | func ExampleDB_Model_postgresArrayStructTag() { 11 | type Item struct { 12 | Id int64 13 | Emails []string `pg:",array"` // marshalled as PostgreSQL array 14 | Numbers [][]int `pg:",array"` // marshalled as PostgreSQL array 15 | } 16 | 17 | _, err := pgdb.Exec(`CREATE TEMP TABLE items (id serial, emails text[], numbers int[][])`) 18 | panicIf(err) 19 | defer pgdb.Exec("DROP TABLE items") 20 | 21 | item1 := &Item{ 22 | Id: 1, 23 | Emails: []string{"one@example.com", "two@example.com"}, 24 | Numbers: [][]int{{1, 2}, {3, 4}}, 25 | } 26 | _, err = pgdb.Model(item1).Insert() 27 | panicIf(err) 28 | 29 | item := new(Item) 30 | err = pgdb.Model(item).Where("id = ?", 1).Select() 31 | panicIf(err) 32 | fmt.Println(item) 33 | // Output: &{1 [one@example.com two@example.com] [[1 2] [3 4]]} 34 | } 35 | 36 | func ExampleArray() { 37 | src := []string{"one@example.com", "two@example.com"} 38 | var dst []string 39 | _, err := pgdb.QueryOne(pg.Scan(pg.Array(&dst)), `SELECT ?`, pg.Array(src)) 40 | panicIf(err) 41 | fmt.Println(dst) 42 | // Output: [one@example.com two@example.com] 43 | } 44 | 45 | type MyArrayValueScanner struct { 46 | sum int 47 | } 48 | 49 | var _ types.ArrayValueScanner = (*MyArrayValueScanner)(nil) 50 | 51 | func (s *MyArrayValueScanner) BeforeScanArrayValue(rd types.Reader, n int) error { 52 | return nil 53 | } 54 | 55 | func (s *MyArrayValueScanner) ScanArrayValue(rd types.Reader, n int) error { 56 | num, err := types.ScanInt(rd, n) 57 | if err != nil { 58 | return err 59 | } 60 | s.sum += num 61 | return nil 62 | } 63 | 64 | func (s *MyArrayValueScanner) AfterScanArrayValue() error { 65 | return nil 66 | } 67 | 68 | func ExampleDB_arrayValueScanner() { 69 | var dst MyArrayValueScanner 70 | _, err := pgdb.QueryOne(pg.Scan(pg.Array(&dst)), 71 | `SELECT array_agg(id) from generate_series(0, 10) AS id`) 72 | panicIf(err) 73 | fmt.Println(dst.sum) 74 | // Output: 55 75 | } 76 | -------------------------------------------------------------------------------- /example_composite_test.go: -------------------------------------------------------------------------------- 1 | package pg_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-pg/pg/v10/orm" 7 | ) 8 | 9 | type InventoryItem struct { 10 | Name string 11 | SupplierID int 12 | Price float64 13 | } 14 | 15 | type OnHand struct { 16 | tableName struct{} `pg:"on_hand"` 17 | 18 | Item InventoryItem `pg:"composite:inventory_item"` 19 | Count int 20 | } 21 | 22 | func ExampleDB_Model_compositeType() { 23 | db := connect() 24 | defer db.Close() 25 | 26 | err := db.Model((*OnHand)(nil)).DropTable(&orm.DropTableOptions{ 27 | IfExists: true, 28 | Cascade: true, 29 | }) 30 | panicIf(err) 31 | 32 | err = db.Model((*InventoryItem)(nil)).DropComposite(&orm.DropCompositeOptions{ 33 | IfExists: true, 34 | }) 35 | panicIf(err) 36 | 37 | err = db.Model((*InventoryItem)(nil)).CreateComposite(nil) 38 | panicIf(err) 39 | 40 | err = db.Model((*OnHand)(nil)).CreateTable(nil) 41 | panicIf(err) 42 | 43 | _, err = db.Model(&OnHand{ 44 | Item: InventoryItem{ 45 | Name: "fuzzy dice", 46 | SupplierID: 42, 47 | Price: 1.99, 48 | }, 49 | Count: 1000, 50 | }).Insert() 51 | panicIf(err) 52 | 53 | onHand := new(OnHand) 54 | err = db.Model(onHand).Select() 55 | panicIf(err) 56 | 57 | fmt.Println(onHand.Item.Name, onHand.Item.Price, onHand.Count) 58 | // Output: fuzzy dice 1.99 1000 59 | } 60 | -------------------------------------------------------------------------------- /example_custom_test.go: -------------------------------------------------------------------------------- 1 | package pg_test 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/go-pg/pg/v10/orm" 8 | "github.com/go-pg/pg/v10/types" 9 | ) 10 | 11 | const pgTimeFormat = "15:04:05.999999999" 12 | 13 | type Time struct { 14 | time.Time 15 | } 16 | 17 | var _ types.ValueAppender = (*Time)(nil) 18 | 19 | func (tm Time) AppendValue(b []byte, flags int) ([]byte, error) { 20 | if flags == 1 { 21 | b = append(b, '\'') 22 | } 23 | b = tm.UTC().AppendFormat(b, pgTimeFormat) 24 | if flags == 1 { 25 | b = append(b, '\'') 26 | } 27 | return b, nil 28 | } 29 | 30 | var _ types.ValueScanner = (*Time)(nil) 31 | 32 | func (tm *Time) ScanValue(rd types.Reader, n int) error { 33 | if n <= 0 { 34 | tm.Time = time.Time{} 35 | return nil 36 | } 37 | 38 | tmp, err := rd.ReadFullTemp() 39 | if err != nil { 40 | return err 41 | } 42 | 43 | tm.Time, err = time.ParseInLocation(pgTimeFormat, string(tmp), time.UTC) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | return nil 49 | } 50 | 51 | type Event struct { 52 | Id int 53 | Time Time `pg:"type:time"` 54 | } 55 | 56 | func ExampleDB_Model_customType() { 57 | db := connect() 58 | defer db.Close() 59 | 60 | err := db.Model((*Event)(nil)).CreateTable(&orm.CreateTableOptions{ 61 | Temp: true, 62 | }) 63 | panicIf(err) 64 | 65 | _, err = db.Model(&Event{ 66 | Time: Time{time.Date(0, 0, 0, 12, 00, 00, 00, time.UTC)}, // noon 67 | }).Insert() 68 | panicIf(err) 69 | 70 | evt := new(Event) 71 | err = db.Model(evt).Select() 72 | panicIf(err) 73 | 74 | fmt.Println(evt.Time) 75 | // Output: 0000-01-01 12:00:00 +0000 UTC 76 | } 77 | -------------------------------------------------------------------------------- /example_db_model_test.go: -------------------------------------------------------------------------------- 1 | package pg_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-pg/pg/v10" 7 | "github.com/go-pg/pg/v10/orm" 8 | ) 9 | 10 | type User struct { 11 | Id int64 12 | Name string 13 | Emails []string 14 | } 15 | 16 | func (u User) String() string { 17 | return fmt.Sprintf("User<%d %s %v>", u.Id, u.Name, u.Emails) 18 | } 19 | 20 | type Story struct { 21 | Id int64 22 | Title string 23 | AuthorId int64 24 | Author *User `pg:"rel:has-one"` 25 | } 26 | 27 | func (s Story) String() string { 28 | return fmt.Sprintf("Story<%d %s %s>", s.Id, s.Title, s.Author) 29 | } 30 | 31 | func ExampleDB_Model() { 32 | db := pg.Connect(&pg.Options{ 33 | User: "postgres", 34 | Password: "postgres", 35 | }) 36 | defer db.Close() 37 | 38 | err := createSchema(db) 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | user1 := &User{ 44 | Name: "admin", 45 | Emails: []string{"admin1@admin", "admin2@admin"}, 46 | } 47 | _, err = db.Model(user1).Insert() 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | _, err = db.Model(&User{ 53 | Name: "root", 54 | Emails: []string{"root1@root", "root2@root"}, 55 | }).Insert() 56 | if err != nil { 57 | panic(err) 58 | } 59 | 60 | story1 := &Story{ 61 | Title: "Cool story", 62 | AuthorId: user1.Id, 63 | } 64 | _, err = db.Model(story1).Insert() 65 | if err != nil { 66 | panic(err) 67 | } 68 | 69 | // Select user by primary key. 70 | user := &User{Id: user1.Id} 71 | err = db.Model(user).WherePK().Select() 72 | if err != nil { 73 | panic(err) 74 | } 75 | 76 | // Select all users. 77 | var users []User 78 | err = db.Model(&users).Select() 79 | if err != nil { 80 | panic(err) 81 | } 82 | 83 | // Select story and associated author in one query. 84 | story := new(Story) 85 | err = db.Model(story). 86 | Relation("Author"). 87 | Where("story.id = ?", story1.Id). 88 | Select() 89 | if err != nil { 90 | panic(err) 91 | } 92 | 93 | fmt.Println(user) 94 | fmt.Println(users) 95 | fmt.Println(story) 96 | // Output: User<1 admin [admin1@admin admin2@admin]> 97 | // [User<1 admin [admin1@admin admin2@admin]> User<2 root [root1@root root2@root]>] 98 | // Story<1 Cool story User<1 admin [admin1@admin admin2@admin]>> 99 | } 100 | 101 | // createSchema creates database schema for User and Story models. 102 | func createSchema(db *pg.DB) error { 103 | models := []interface{}{ 104 | (*User)(nil), 105 | (*Story)(nil), 106 | } 107 | 108 | for _, model := range models { 109 | err := db.Model(model).CreateTable(&orm.CreateTableOptions{ 110 | Temp: true, 111 | }) 112 | if err != nil { 113 | return err 114 | } 115 | } 116 | return nil 117 | } 118 | -------------------------------------------------------------------------------- /example_db_query_test.go: -------------------------------------------------------------------------------- 1 | package pg_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-pg/pg/v10" 7 | ) 8 | 9 | func CreateUser(db *pg.DB, user *User) error { 10 | _, err := db.QueryOne(user, ` 11 | INSERT INTO users (name, emails) VALUES (?name, ?emails) 12 | RETURNING id 13 | `, user) 14 | return err 15 | } 16 | 17 | func GetUser(db *pg.DB, id int64) (*User, error) { 18 | var user User 19 | _, err := db.QueryOne(&user, `SELECT * FROM users WHERE id = ?`, id) 20 | return &user, err 21 | } 22 | 23 | func GetUsers(db *pg.DB) ([]User, error) { 24 | var users []User 25 | _, err := db.Query(&users, `SELECT * FROM users`) 26 | return users, err 27 | } 28 | 29 | func GetUsersByIds(db *pg.DB, ids []int64) ([]User, error) { 30 | var users []User 31 | _, err := db.Query(&users, `SELECT * FROM users WHERE id IN (?)`, pg.In(ids)) 32 | return users, err 33 | } 34 | 35 | func CreateStory(db *pg.DB, story *Story) error { 36 | _, err := db.QueryOne(story, ` 37 | INSERT INTO stories (title, author_id) VALUES (?title, ?author_id) 38 | RETURNING id 39 | `, story) 40 | return err 41 | } 42 | 43 | // GetStory returns story with associated author. 44 | func GetStory(db *pg.DB, id int64) (*Story, error) { 45 | var story Story 46 | _, err := db.QueryOne(&story, ` 47 | SELECT s.*, 48 | u.id AS author__id, u.name AS author__name, u.emails AS author__emails 49 | FROM stories AS s, users AS u 50 | WHERE s.id = ? AND u.id = s.author_id 51 | `, id) 52 | return &story, err 53 | } 54 | 55 | func ExampleDB_Query() { 56 | db := pg.Connect(&pg.Options{ 57 | User: "postgres", 58 | Password: "postgres", 59 | }) 60 | 61 | err := createSchema(db) 62 | panicIf(err) 63 | 64 | user1 := &User{ 65 | Name: "admin", 66 | Emails: []string{"admin1@admin", "admin2@admin"}, 67 | } 68 | err = CreateUser(db, user1) 69 | panicIf(err) 70 | 71 | err = CreateUser(db, &User{ 72 | Name: "root", 73 | Emails: []string{"root1@root", "root2@root"}, 74 | }) 75 | panicIf(err) 76 | 77 | story1 := &Story{ 78 | Title: "Cool story", 79 | AuthorId: user1.Id, 80 | } 81 | err = CreateStory(db, story1) 82 | panicIf(err) 83 | 84 | user, err := GetUser(db, user1.Id) 85 | panicIf(err) 86 | 87 | users, err := GetUsers(db) 88 | panicIf(err) 89 | 90 | story, err := GetStory(db, story1.Id) 91 | panicIf(err) 92 | 93 | fmt.Println(user) 94 | fmt.Println(users) 95 | fmt.Println(story) 96 | // Output: User<1 admin [admin1@admin admin2@admin]> 97 | // [User<1 admin [admin1@admin admin2@admin]> User<2 root [root1@root root2@root]>] 98 | // Story<1 Cool story User<1 admin [admin1@admin admin2@admin]>> 99 | } 100 | -------------------------------------------------------------------------------- /example_hstore_test.go: -------------------------------------------------------------------------------- 1 | package pg_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-pg/pg/v10" 7 | ) 8 | 9 | func ExampleDB_Model_hstoreStructTag() { 10 | type Item struct { 11 | Id int64 12 | Attrs map[string]string `pg:",hstore"` // marshalled as PostgreSQL hstore 13 | } 14 | 15 | _, err := pgdb.Exec(`CREATE TEMP TABLE items (id serial, attrs hstore)`) 16 | if err != nil { 17 | panic(err) 18 | } 19 | defer pgdb.Exec("DROP TABLE items") 20 | 21 | item1 := Item{ 22 | Id: 1, 23 | Attrs: map[string]string{"hello": "world"}, 24 | } 25 | _, err = pgdb.Model(&item1).Insert() 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | var item Item 31 | err = pgdb.Model(&item).Where("id = ?", 1).Select() 32 | if err != nil { 33 | panic(err) 34 | } 35 | fmt.Println(item) 36 | // Output: {1 map[hello:world]} 37 | } 38 | 39 | func ExampleHstore() { 40 | src := map[string]string{"hello": "world"} 41 | var dst map[string]string 42 | _, err := pgdb.QueryOne(pg.Scan(pg.Hstore(&dst)), `SELECT ?`, pg.Hstore(src)) 43 | if err != nil { 44 | panic(err) 45 | } 46 | fmt.Println(dst) 47 | // Output: map[hello:world] 48 | } 49 | -------------------------------------------------------------------------------- /example_many2many_self_test.go: -------------------------------------------------------------------------------- 1 | package pg_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-pg/pg/v10" 7 | "github.com/go-pg/pg/v10/orm" 8 | ) 9 | 10 | func init() { 11 | // Register many to many model so ORM can better recognize m2m relation. 12 | // This should be done before dependant models are used. 13 | orm.RegisterTable((*ElemToElem)(nil)) 14 | } 15 | 16 | type Elem struct { 17 | Id int 18 | Elems []Elem `pg:"many2many:elem_to_elems,join_fk:sub_id"` 19 | } 20 | 21 | type ElemToElem struct { 22 | ElemId int 23 | SubId int 24 | } 25 | 26 | func createManyToManySefTables(db *pg.DB) error { 27 | models := []interface{}{ 28 | (*Elem)(nil), 29 | (*ElemToElem)(nil), 30 | } 31 | for _, model := range models { 32 | err := db.Model(model).CreateTable(&orm.CreateTableOptions{ 33 | Temp: true, 34 | }) 35 | if err != nil { 36 | return err 37 | } 38 | } 39 | return nil 40 | } 41 | 42 | func ExampleDB_Model_manyToManySelf() { 43 | db := connect() 44 | defer db.Close() 45 | 46 | if err := createManyToManySefTables(db); err != nil { 47 | panic(err) 48 | } 49 | 50 | values := []interface{}{ 51 | &Elem{Id: 1}, 52 | &Elem{Id: 2}, 53 | &Elem{Id: 3}, 54 | &ElemToElem{ElemId: 1, SubId: 2}, 55 | &ElemToElem{ElemId: 1, SubId: 3}, 56 | } 57 | for _, v := range values { 58 | _, err := db.Model(v).Insert() 59 | if err != nil { 60 | panic(err) 61 | } 62 | } 63 | 64 | // Select elem and all subelems with following queries: 65 | // 66 | // SELECT "elem"."id" FROM "elems" AS "elem" ORDER BY "elem"."id" LIMIT 1 67 | // 68 | // SELECT elem_to_elems.*, "elem"."id" FROM "elems" AS "elem" 69 | // JOIN elem_to_elems AS elem_to_elems ON (elem_to_elems."elem_id") IN (1) 70 | // WHERE ("elem"."id" = elem_to_elems."sub_id") 71 | 72 | elem := new(Elem) 73 | err := db.Model(elem).Relation("Elems").First() 74 | if err != nil { 75 | panic(err) 76 | } 77 | fmt.Println("Elem", elem.Id) 78 | fmt.Println("Subelems", elem.Elems[0].Id, elem.Elems[1].Id) 79 | // Output: Elem 1 80 | // Subelems 2 3 81 | } 82 | -------------------------------------------------------------------------------- /example_many2many_test.go: -------------------------------------------------------------------------------- 1 | package pg_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-pg/pg/v10" 7 | "github.com/go-pg/pg/v10/orm" 8 | ) 9 | 10 | func init() { 11 | // Register many to many model so ORM can better recognize m2m relation. 12 | // This should be done before dependant models are used. 13 | orm.RegisterTable((*OrderToItem)(nil)) 14 | } 15 | 16 | type Order struct { 17 | Id int 18 | Items []Item `pg:"many2many:order_to_items"` 19 | } 20 | 21 | type Item struct { 22 | Id int 23 | } 24 | 25 | type OrderToItem struct { 26 | OrderId int 27 | ItemId int 28 | } 29 | 30 | func ExampleDB_Model_manyToMany() { 31 | db := connect() 32 | defer db.Close() 33 | 34 | if err := createManyToManyTables(db); err != nil { 35 | panic(err) 36 | } 37 | 38 | values := []interface{}{ 39 | &Item{Id: 1}, 40 | &Item{Id: 2}, 41 | &Order{Id: 1}, 42 | &OrderToItem{OrderId: 1, ItemId: 1}, 43 | &OrderToItem{OrderId: 1, ItemId: 2}, 44 | } 45 | for _, v := range values { 46 | _, err := db.Model(v).Insert() 47 | if err != nil { 48 | panic(err) 49 | } 50 | } 51 | 52 | // Select order and all items with following queries: 53 | // 54 | // SELECT "order"."id" FROM "orders" AS "order" ORDER BY "order"."id" LIMIT 1 55 | // 56 | // SELECT order_to_items.*, "item"."id" FROM "items" AS "item" 57 | // JOIN order_to_items AS order_to_items ON (order_to_items."order_id") IN (1) 58 | // WHERE ("item"."id" = order_to_items."item_id") 59 | 60 | order := new(Order) 61 | err := db.Model(order).Relation("Items").First() 62 | if err != nil { 63 | panic(err) 64 | } 65 | fmt.Println("Order", order.Id, "Items", order.Items[0].Id, order.Items[1].Id) 66 | 67 | // Select order and all items sorted by id with following queries: 68 | // 69 | // SELECT "order"."id" FROM "orders" AS "order" ORDER BY "order"."id" LIMIT 1 70 | // 71 | // SELECT order_to_items.*, "item"."id" FROM "items" AS "item" 72 | // JOIN order_to_items AS order_to_items ON (order_to_items."order_id") IN (1) 73 | // WHERE ("item"."id" = order_to_items."item_id") 74 | // ORDER BY item.id DESC 75 | 76 | order = new(Order) 77 | err = db.Model(order). 78 | Relation("Items", func(q *pg.Query) (*pg.Query, error) { 79 | q = q.OrderExpr("item.id DESC") 80 | return q, nil 81 | }). 82 | First() 83 | if err != nil { 84 | panic(err) 85 | } 86 | fmt.Println("Order", order.Id, "Items", order.Items[0].Id, order.Items[1].Id) 87 | 88 | // Output: Order 1 Items 1 2 89 | // Order 1 Items 2 1 90 | } 91 | 92 | func createManyToManyTables(db *pg.DB) error { 93 | models := []interface{}{ 94 | (*Order)(nil), 95 | (*Item)(nil), 96 | (*OrderToItem)(nil), 97 | } 98 | for _, model := range models { 99 | err := db.Model(model).CreateTable(&orm.CreateTableOptions{ 100 | Temp: true, 101 | }) 102 | if err != nil { 103 | return err 104 | } 105 | } 106 | return nil 107 | } 108 | -------------------------------------------------------------------------------- /example_placeholders_test.go: -------------------------------------------------------------------------------- 1 | package pg_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-pg/pg/v10" 7 | ) 8 | 9 | type Params struct { 10 | X int 11 | Y int 12 | } 13 | 14 | func (p *Params) Sum() int { 15 | return p.X + p.Y 16 | } 17 | 18 | // go-pg recognizes `?` in queries as placeholders and replaces them 19 | // with parameters when queries are executed. `?` can be escaped with backslash. 20 | // Parameters are escaped before replacing according to PostgreSQL rules. 21 | // Specifically: 22 | // - all parameters are properly quoted against SQL injections; 23 | // - null byte is removed; 24 | // - JSON/JSONB gets `\u0000` escaped as `\\u0000`. 25 | func Example_placeholders() { 26 | var num int 27 | 28 | // Simple params. 29 | _, err := pgdb.Query(pg.Scan(&num), "SELECT ?", 42) 30 | if err != nil { 31 | panic(err) 32 | } 33 | fmt.Println("simple:", num) 34 | 35 | // Indexed params. 36 | _, err = pgdb.Query(pg.Scan(&num), "SELECT ?0 + ?0", 1) 37 | if err != nil { 38 | panic(err) 39 | } 40 | fmt.Println("indexed:", num) 41 | 42 | // Named params. 43 | params := &Params{ 44 | X: 1, 45 | Y: 1, 46 | } 47 | _, err = pgdb.Query(pg.Scan(&num), "SELECT ?x + ?y + ?Sum", params) 48 | if err != nil { 49 | panic(err) 50 | } 51 | fmt.Println("named:", num) 52 | 53 | // Global params. 54 | _, err = pgdb.WithParam("z", 1).Query(pg.Scan(&num), "SELECT ?x + ?y + ?z", params) 55 | if err != nil { 56 | panic(err) 57 | } 58 | fmt.Println("global:", num) 59 | 60 | // Model params. 61 | var tableName, tableAlias, tableColumns, columns string 62 | _, err = pgdb.Model(&Params{}).Query( 63 | pg.Scan(&tableName, &tableAlias, &tableColumns, &columns), 64 | "SELECT '?TableName', '?TableAlias', '?TableColumns', '?Columns'", 65 | ) 66 | if err != nil { 67 | panic(err) 68 | } 69 | fmt.Println("table name:", tableName) 70 | fmt.Println("table alias:", tableAlias) 71 | fmt.Println("table columns:", tableColumns) 72 | fmt.Println("columns:", columns) 73 | 74 | // Output: simple: 42 75 | // indexed: 2 76 | // named: 4 77 | // global: 3 78 | // table name: "params" 79 | // table alias: "params" 80 | // table columns: "params"."x", "params"."y" 81 | // columns: "x", "y" 82 | } 83 | -------------------------------------------------------------------------------- /example_soft_delete_test.go: -------------------------------------------------------------------------------- 1 | package pg_test 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/go-pg/pg/v10/types" 8 | ) 9 | 10 | type CustomTime struct { 11 | Time time.Time 12 | } 13 | 14 | var _ types.ValueScanner = (*CustomTime)(nil) 15 | 16 | func (tm *CustomTime) ScanValue(rd types.Reader, n int) error { 17 | var err error 18 | tm.Time, err = types.ScanTime(rd, n) 19 | return err 20 | } 21 | 22 | var _ types.ValueAppender = (*CustomTime)(nil) 23 | 24 | func (tm *CustomTime) AppendValue(b []byte, flags int) ([]byte, error) { 25 | return types.AppendTime(b, tm.Time, flags), nil 26 | } 27 | 28 | type Video struct { 29 | Id int 30 | Name string 31 | DeletedAt CustomTime `pg:",soft_delete"` 32 | } 33 | 34 | func ExampleDB_Model_softDeleteCustom() { 35 | video1 := &Video{ 36 | Id: 1, 37 | } 38 | _, err := pgdb.Model(video1).Insert() 39 | panicIf(err) 40 | 41 | // Soft delete. 42 | _, err = pgdb.Model(video1).WherePK().Delete() 43 | panicIf(err) 44 | 45 | // Count visible videos. 46 | count, err := pgdb.Model((*Video)(nil)).Count() 47 | panicIf(err) 48 | fmt.Println("count", count) 49 | 50 | // Count soft deleted videos. 51 | deletedCount, err := pgdb.Model((*Video)(nil)).Deleted().Count() 52 | panicIf(err) 53 | fmt.Println("deleted count", deletedCount) 54 | 55 | // Actually delete the video. 56 | _, err = pgdb.Model(video1).WherePK().ForceDelete() 57 | panicIf(err) 58 | 59 | // Count soft deleted videos. 60 | deletedCount, err = pgdb.Model((*Video)(nil)).Deleted().Count() 61 | panicIf(err) 62 | fmt.Println("deleted count", deletedCount) 63 | 64 | // Output: count 0 65 | // deleted count 1 66 | // deleted count 0 67 | } 68 | -------------------------------------------------------------------------------- /export_test.go: -------------------------------------------------------------------------------- 1 | package pg 2 | 3 | import "github.com/go-pg/pg/v10/internal/pool" 4 | 5 | func (db *DB) Pool() pool.Pooler { 6 | return db.pool 7 | } 8 | 9 | func (ln *Listener) CurrentConn() *pool.Conn { 10 | return ln.cn 11 | } 12 | -------------------------------------------------------------------------------- /extra/pgdebug/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-pg/pg/extra/pgdebug/v10 2 | 3 | go 1.19 4 | 5 | replace github.com/go-pg/pg/v10 => ../.. 6 | 7 | require github.com/go-pg/pg/v10 v10.14.0 8 | 9 | require ( 10 | github.com/go-pg/zerochecker v0.2.0 // indirect 11 | github.com/jinzhu/inflection v1.0.0 // indirect 12 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect 13 | github.com/vmihailenco/bufpool v0.1.11 // indirect 14 | github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect 15 | github.com/vmihailenco/tagparser v0.1.2 // indirect 16 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 17 | golang.org/x/crypto v0.31.0 // indirect 18 | golang.org/x/sys v0.28.0 // indirect 19 | mellium.im/sasl v0.3.1 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /extra/pgdebug/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 4 | github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU= 5 | github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo= 6 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 7 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 8 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 9 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 10 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 11 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 12 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 13 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 14 | github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= 15 | github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= 16 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 17 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 18 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 19 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 20 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 21 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 22 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= 23 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= 24 | github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94= 25 | github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ= 26 | github.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2ellBfvnqc= 27 | github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= 28 | github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= 29 | github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= 30 | github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= 31 | github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= 32 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 33 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 34 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= 35 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 36 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 37 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 38 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 39 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 40 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 41 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 42 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 43 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 44 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 45 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 46 | gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= 47 | mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= 48 | mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= 49 | -------------------------------------------------------------------------------- /extra/pgdebug/pgdebug.go: -------------------------------------------------------------------------------- 1 | package pgdebug 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/go-pg/pg/v10" 8 | ) 9 | 10 | // DebugHook is a query hook that logs an error with a query if there are any. 11 | // It can be installed with: 12 | // 13 | // db.AddQueryHook(pgext.DebugHook{}) 14 | type DebugHook struct { 15 | // Verbose causes hook to print all queries (even those without an error). 16 | Verbose bool 17 | EmptyLine bool 18 | } 19 | 20 | func NewDebugHook() *DebugHook { 21 | return new(DebugHook) 22 | } 23 | 24 | var _ pg.QueryHook = (*DebugHook)(nil) 25 | 26 | func (h *DebugHook) BeforeQuery(ctx context.Context, evt *pg.QueryEvent) (context.Context, error) { 27 | q, err := evt.FormattedQuery() 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | if evt.Err != nil { 33 | fmt.Printf("%s executing a query:\n%s\n", evt.Err, q) 34 | } else if h.Verbose { 35 | if h.EmptyLine { 36 | fmt.Println() 37 | } 38 | fmt.Println(string(q)) 39 | } 40 | 41 | return ctx, nil 42 | } 43 | 44 | func (DebugHook) AfterQuery(context.Context, *pg.QueryEvent) error { 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /extra/pgotel/README.md: -------------------------------------------------------------------------------- 1 | # OpenTelemetry instrumentation for go-pg 2 | 3 | ## Installation 4 | 5 | ```bash 6 | go get github.com/go-pg/pg/extra/pgotel/v10 7 | ``` 8 | 9 | ## Usage 10 | 11 | Tracing is enabled by adding a query hook: 12 | 13 | ```go 14 | import ( 15 | "github.com/go-pg/pg/v10" 16 | "github.com/go-pg/pg/extra/pgotel/v10" 17 | ) 18 | 19 | db := pg.Connect(&pg.Options{...}) 20 | 21 | db.AddQueryHook(pgotel.NewTracingHook()) 22 | ``` 23 | 24 | See [documentation](https://pg.uptrace.dev/tracing/) for more details. 25 | -------------------------------------------------------------------------------- /extra/pgotel/example/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | docker-compose build 3 | up: build 4 | docker-compose up --detach postgresql-server 5 | down: 6 | docker-compose down 7 | logs: 8 | docker-compose logs postgresql-server 9 | -------------------------------------------------------------------------------- /extra/pgotel/example/README.md: -------------------------------------------------------------------------------- 1 | # OpenTelemetry instrumentation for go-pg 2 | 3 | To run this example you need a PostgreSQL server. You can start one with Docker: 4 | 5 | ```bash 6 | make up 7 | ``` 8 | 9 | Then run the example: 10 | 11 | ```bash 12 | go run main.go 13 | ``` 14 | -------------------------------------------------------------------------------- /extra/pgotel/example/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | postgresql-server: 4 | image: postgres:13 5 | environment: 6 | POSTGRES_USER: postgres 7 | POSTGRES_DB: example 8 | POSTGRES_HOST_AUTH_METHOD: trust 9 | ports: 10 | - 5432:5432 11 | -------------------------------------------------------------------------------- /extra/pgotel/example/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.19 4 | 5 | replace github.com/go-pg/pg/v10 => ../../.. 6 | 7 | replace github.com/go-pg/pg/extra/pgotel/v10 => ../ 8 | 9 | require ( 10 | github.com/go-pg/pg/extra/pgotel/v10 v10.14.0 11 | github.com/go-pg/pg/v10 v10.14.0 12 | go.opentelemetry.io/otel v1.0.0 13 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.0.0 14 | go.opentelemetry.io/otel/sdk v1.0.0 15 | ) 16 | 17 | require ( 18 | github.com/go-pg/zerochecker v0.2.0 // indirect 19 | github.com/jinzhu/inflection v1.0.0 // indirect 20 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect 21 | github.com/vmihailenco/bufpool v0.1.11 // indirect 22 | github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect 23 | github.com/vmihailenco/tagparser v0.1.2 // indirect 24 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 25 | go.opentelemetry.io/otel/trace v1.0.0 // indirect 26 | golang.org/x/crypto v0.31.0 // indirect 27 | golang.org/x/sys v0.28.0 // indirect 28 | mellium.im/sasl v0.3.1 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /extra/pgotel/example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "go.opentelemetry.io/otel" 7 | "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" 8 | sdktrace "go.opentelemetry.io/otel/sdk/trace" 9 | 10 | "github.com/go-pg/pg/extra/pgotel/v10" 11 | "github.com/go-pg/pg/v10" 12 | ) 13 | 14 | var tracer = otel.Tracer("app_or_package_name") 15 | 16 | func main() { 17 | exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) 18 | if err != nil { 19 | panic(err) 20 | } 21 | 22 | provider := sdktrace.NewTracerProvider() 23 | provider.RegisterSpanProcessor(sdktrace.NewSimpleSpanProcessor(exporter)) 24 | 25 | otel.SetTracerProvider(provider) 26 | 27 | db := pg.Connect(&pg.Options{ 28 | Addr: ":5432", 29 | User: "postgres", 30 | Password: "postgres", 31 | Database: "example", 32 | }) 33 | defer db.Close() 34 | 35 | db.AddQueryHook(pgotel.NewTracingHook()) 36 | 37 | ctx, span := tracer.Start(context.TODO(), "main") 38 | defer span.End() 39 | 40 | if err := db.Ping(ctx); err != nil { 41 | panic(err) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /extra/pgotel/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-pg/pg/extra/pgotel/v10 2 | 3 | go 1.19 4 | 5 | replace github.com/go-pg/pg/v10 => ../.. 6 | 7 | require ( 8 | github.com/go-pg/pg/v10 v10.14.0 9 | go.opentelemetry.io/otel v1.0.0 10 | go.opentelemetry.io/otel/trace v1.0.0 11 | ) 12 | 13 | require ( 14 | github.com/go-pg/zerochecker v0.2.0 // indirect 15 | github.com/jinzhu/inflection v1.0.0 // indirect 16 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect 17 | github.com/vmihailenco/bufpool v0.1.11 // indirect 18 | github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect 19 | github.com/vmihailenco/tagparser v0.1.2 // indirect 20 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 21 | golang.org/x/crypto v0.31.0 // indirect 22 | golang.org/x/sys v0.28.0 // indirect 23 | mellium.im/sasl v0.3.1 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /extra/pgotel/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 4 | github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU= 5 | github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo= 6 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 7 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 8 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 9 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 10 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 11 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 12 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 13 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 14 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 15 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 16 | github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= 17 | github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= 18 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 19 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 20 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 21 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 22 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 23 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 24 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 25 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= 26 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= 27 | github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94= 28 | github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ= 29 | github.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2ellBfvnqc= 30 | github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= 31 | github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= 32 | github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= 33 | github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= 34 | github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= 35 | go.opentelemetry.io/otel v1.0.0 h1:qTTn6x71GVBvoafHK/yaRUmFzI4LcONZD0/kXxl5PHI= 36 | go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg= 37 | go.opentelemetry.io/otel/trace v1.0.0 h1:TSBr8GTEtKevYMG/2d21M989r5WJYVimhTHBKVEZuh4= 38 | go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs= 39 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 40 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 41 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= 42 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 43 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 44 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 45 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 46 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 47 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 48 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 49 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 50 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 51 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 52 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 53 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 54 | gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= 55 | mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= 56 | mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= 57 | -------------------------------------------------------------------------------- /extra/pgotel/pgotel.go: -------------------------------------------------------------------------------- 1 | package pgotel 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "runtime" 7 | "strings" 8 | 9 | "go.opentelemetry.io/otel" 10 | "go.opentelemetry.io/otel/attribute" 11 | "go.opentelemetry.io/otel/codes" 12 | "go.opentelemetry.io/otel/trace" 13 | 14 | "github.com/go-pg/pg/v10" 15 | "github.com/go-pg/pg/v10/orm" 16 | ) 17 | 18 | var tracer = otel.Tracer("github.com/go-pg/pg") 19 | 20 | type queryOperation interface { 21 | Operation() orm.QueryOp 22 | } 23 | 24 | // TracingHook is a pg.QueryHook that adds OpenTelemetry instrumentation. 25 | type TracingHook struct{} 26 | 27 | func NewTracingHook() *TracingHook { 28 | return new(TracingHook) 29 | } 30 | 31 | var _ pg.QueryHook = (*TracingHook)(nil) 32 | 33 | func (h *TracingHook) BeforeQuery(ctx context.Context, _ *pg.QueryEvent) (context.Context, error) { 34 | if !trace.SpanFromContext(ctx).IsRecording() { 35 | return ctx, nil 36 | } 37 | 38 | ctx, _ = tracer.Start(ctx, "") 39 | return ctx, nil 40 | } 41 | 42 | func (h *TracingHook) AfterQuery(ctx context.Context, evt *pg.QueryEvent) error { 43 | const ( 44 | softQueryLimit = 5000 45 | hardQueryLimit = 10000 46 | ) 47 | 48 | span := trace.SpanFromContext(ctx) 49 | if !span.IsRecording() { 50 | return nil 51 | } 52 | defer span.End() 53 | 54 | var operation orm.QueryOp 55 | 56 | if v, ok := evt.Query.(queryOperation); ok { 57 | operation = v.Operation() 58 | } 59 | 60 | var query string 61 | 62 | if operation == orm.InsertOp { 63 | b, err := evt.UnformattedQuery() 64 | if err != nil { 65 | return err 66 | } 67 | query = string(b) 68 | } else { 69 | b, err := evt.FormattedQuery() 70 | if err != nil { 71 | return err 72 | } 73 | 74 | if len(b) > softQueryLimit { 75 | b, err = evt.UnformattedQuery() 76 | if err != nil { 77 | return err 78 | } 79 | } 80 | 81 | query = string(b) 82 | } 83 | 84 | if operation != "" { 85 | span.SetName(string(operation)) 86 | } else { 87 | name := query 88 | if idx := strings.IndexByte(name, ' '); idx > 0 { 89 | name = name[:idx] 90 | } 91 | if len(name) > 20 { 92 | name = name[:20] 93 | } 94 | span.SetName(strings.TrimSpace(name)) 95 | } 96 | 97 | if len(query) > hardQueryLimit { 98 | query = query[:hardQueryLimit] 99 | } 100 | 101 | fn, file, line := funcFileLine("github.com/go-pg/pg") 102 | 103 | attrs := make([]attribute.KeyValue, 0, 10) 104 | attrs = append(attrs, 105 | attribute.String("db.system", "postgresql"), 106 | attribute.String("db.statement", query), 107 | 108 | attribute.String("code.function", fn), 109 | attribute.String("code.filepath", file), 110 | attribute.Int("code.lineno", line), 111 | ) 112 | 113 | if db, ok := evt.DB.(*pg.DB); ok { 114 | opt := db.Options() 115 | attrs = append(attrs, 116 | attribute.String("db.connection_string", opt.Addr), 117 | attribute.String("db.user", opt.User), 118 | attribute.String("db.name", opt.Database), 119 | ) 120 | } 121 | 122 | if evt.Err != nil { 123 | switch evt.Err { 124 | case pg.ErrNoRows, pg.ErrMultiRows: 125 | default: 126 | span.RecordError(evt.Err) 127 | span.SetStatus(codes.Error, evt.Err.Error()) 128 | } 129 | } else if hasResults(evt) { 130 | numRow := evt.Result.RowsAffected() 131 | if numRow == 0 { 132 | numRow = evt.Result.RowsReturned() 133 | } 134 | attrs = append(attrs, attribute.Int("db.rows_affected", numRow)) 135 | } 136 | 137 | span.SetAttributes(attrs...) 138 | 139 | return nil 140 | } 141 | 142 | func hasResults(evt *pg.QueryEvent) bool { 143 | return evt.Result != nil && (reflect.ValueOf(evt.Result).Kind() != reflect.Ptr || !reflect.ValueOf(evt.Result).IsNil()) 144 | } 145 | 146 | func funcFileLine(pkg string) (string, string, int) { 147 | const depth = 16 148 | var pcs [depth]uintptr 149 | n := runtime.Callers(3, pcs[:]) 150 | ff := runtime.CallersFrames(pcs[:n]) 151 | 152 | var fn, file string 153 | var line int 154 | for { 155 | f, ok := ff.Next() 156 | if !ok { 157 | break 158 | } 159 | fn, file, line = f.Function, f.File, f.Line 160 | if !strings.Contains(fn, pkg) { 161 | break 162 | } 163 | } 164 | 165 | if ind := strings.LastIndexByte(fn, '/'); ind != -1 { 166 | fn = fn[ind+1:] 167 | } 168 | 169 | return fn, file, line 170 | } 171 | -------------------------------------------------------------------------------- /extra/pgotel/pgotel_test.go: -------------------------------------------------------------------------------- 1 | package pgotel 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/go-pg/pg/v10" 7 | "github.com/go-pg/pg/v10/orm" 8 | ) 9 | 10 | // mockResult is a mock implementation of the Result interface 11 | type mockResult struct { 12 | model orm.Model 13 | rowsAffected int 14 | rowsReturned int 15 | } 16 | 17 | func (m mockResult) Model() orm.Model { 18 | return m.model 19 | } 20 | 21 | func (m mockResult) RowsAffected() int { 22 | return m.rowsAffected 23 | } 24 | 25 | func (m mockResult) RowsReturned() int { 26 | return m.rowsReturned 27 | } 28 | 29 | func TestHasResults(t *testing.T) { 30 | // Define test cases 31 | testCases := []struct { 32 | name string 33 | event *pg.QueryEvent 34 | expected bool 35 | }{ 36 | { 37 | name: "Nil Result", 38 | event: &pg.QueryEvent{Result: nil}, 39 | expected: false, 40 | }, 41 | { 42 | name: "Nil Pointer Result", 43 | event: &pg.QueryEvent{Result: (*mockResult)(nil)}, 44 | expected: false, 45 | }, 46 | { 47 | name: "Non-Nil Result", 48 | event: &pg.QueryEvent{Result: mockResult{rowsAffected: 1, rowsReturned: 1}}, 49 | expected: true, 50 | }, 51 | } 52 | 53 | // Run test cases 54 | for _, tc := range testCases { 55 | t.Run(tc.name, func(t *testing.T) { 56 | result := hasResults(tc.event) 57 | if result != tc.expected { 58 | t.Errorf("hasResults(%v) = %v, want %v", tc.event, result, tc.expected) 59 | } 60 | }) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /extra/pgsegment/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-pg/pg/extra/pgsegment/v10 2 | 3 | go 1.19 4 | 5 | replace github.com/go-pg/pg/v10 => ../.. 6 | 7 | require ( 8 | github.com/go-pg/pg/v10 v10.14.0 9 | github.com/segmentio/encoding v0.2.21 10 | ) 11 | 12 | require ( 13 | github.com/klauspost/cpuid/v2 v2.0.9 // indirect 14 | github.com/segmentio/asm v1.0.1 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /extra/pgsegment/go.sum: -------------------------------------------------------------------------------- 1 | github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 2 | github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= 3 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 4 | github.com/segmentio/asm v1.0.1 h1:g9VK62hXylgXI4yJV+dLTu/1j7kTxG9bkUSYBxL9dpg= 5 | github.com/segmentio/asm v1.0.1/go.mod h1:4EUJGaKsB8ImLUwOGORVsNd9vTRDeh44JGsY4aKp5I4= 6 | github.com/segmentio/encoding v0.2.21 h1:hlRQz3Pv+/mBj+jqr46TVqqv6AuTwvP5aAxQ0Usd4gY= 7 | github.com/segmentio/encoding v0.2.21/go.mod h1:kF1db1oBuYxMvLR3RXrZJchRdBKrS+1J/hL63p5hekI= 8 | -------------------------------------------------------------------------------- /extra/pgsegment/pgsegment.go: -------------------------------------------------------------------------------- 1 | package pgsegment 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/segmentio/encoding/json" 7 | 8 | "github.com/go-pg/pg/v10/pgjson" 9 | ) 10 | 11 | var _ pgjson.Provider = (*JSONProvider)(nil) 12 | 13 | type JSONProvider struct{} 14 | 15 | func NewJSONProvider() JSONProvider { 16 | return JSONProvider{} 17 | } 18 | 19 | func (JSONProvider) Marshal(v interface{}) ([]byte, error) { 20 | return json.Marshal(v) 21 | } 22 | 23 | func (JSONProvider) Unmarshal(data []byte, v interface{}) error { 24 | return json.Unmarshal(data, v) 25 | } 26 | 27 | func (JSONProvider) NewEncoder(w io.Writer) pgjson.Encoder { 28 | return json.NewEncoder(w) 29 | } 30 | 31 | func (JSONProvider) NewDecoder(r io.Reader) pgjson.Decoder { 32 | return json.NewDecoder(r) 33 | } 34 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-pg/pg/v10 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/go-pg/zerochecker v0.2.0 7 | github.com/jinzhu/inflection v1.0.0 8 | github.com/onsi/ginkgo v1.14.2 9 | github.com/onsi/gomega v1.10.3 10 | github.com/stretchr/testify v1.7.0 11 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc 12 | github.com/vmihailenco/bufpool v0.1.11 13 | github.com/vmihailenco/msgpack/v5 v5.3.4 14 | github.com/vmihailenco/tagparser v0.1.2 15 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f 16 | mellium.im/sasl v0.3.1 17 | ) 18 | 19 | require ( 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/fsnotify/fsnotify v1.4.9 // indirect 22 | github.com/golang/protobuf v1.5.0 // indirect 23 | github.com/kr/text v0.1.0 // indirect 24 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 25 | github.com/nxadm/tail v1.4.4 // indirect 26 | github.com/pmezard/go-difflib v1.0.0 // indirect 27 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 28 | golang.org/x/crypto v0.31.0 // indirect 29 | golang.org/x/net v0.23.0 // indirect 30 | golang.org/x/sys v0.28.0 // indirect 31 | golang.org/x/text v0.21.0 // indirect 32 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect 33 | google.golang.org/protobuf v1.33.0 // indirect 34 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 35 | gopkg.in/yaml.v2 v2.3.0 // indirect 36 | gopkg.in/yaml.v3 v3.0.0 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /hook.go: -------------------------------------------------------------------------------- 1 | package pg 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/go-pg/pg/v10/orm" 9 | ) 10 | 11 | type ( 12 | BeforeScanHook = orm.BeforeScanHook 13 | AfterScanHook = orm.AfterScanHook 14 | AfterSelectHook = orm.AfterSelectHook 15 | BeforeInsertHook = orm.BeforeInsertHook 16 | AfterInsertHook = orm.AfterInsertHook 17 | BeforeUpdateHook = orm.BeforeUpdateHook 18 | AfterUpdateHook = orm.AfterUpdateHook 19 | BeforeDeleteHook = orm.BeforeDeleteHook 20 | AfterDeleteHook = orm.AfterDeleteHook 21 | ) 22 | 23 | //------------------------------------------------------------------------------ 24 | 25 | type dummyFormatter struct{} 26 | 27 | func (dummyFormatter) FormatQuery(b []byte, query string, params ...interface{}) []byte { 28 | return append(b, query...) 29 | } 30 | 31 | // QueryEvent ... 32 | type QueryEvent struct { 33 | StartTime time.Time 34 | DB orm.DB 35 | Model interface{} 36 | Query interface{} 37 | Params []interface{} 38 | fmtedQuery []byte 39 | Result Result 40 | Err error 41 | 42 | Stash map[interface{}]interface{} 43 | } 44 | 45 | // QueryHook ... 46 | type QueryHook interface { 47 | BeforeQuery(context.Context, *QueryEvent) (context.Context, error) 48 | AfterQuery(context.Context, *QueryEvent) error 49 | } 50 | 51 | // UnformattedQuery returns the unformatted query of a query event. 52 | // The query is only valid until the query Result is returned to the user. 53 | func (e *QueryEvent) UnformattedQuery() ([]byte, error) { 54 | return queryString(e.Query) 55 | } 56 | 57 | func queryString(query interface{}) ([]byte, error) { 58 | switch query := query.(type) { 59 | case orm.TemplateAppender: 60 | return query.AppendTemplate(nil) 61 | case string: 62 | return dummyFormatter{}.FormatQuery(nil, query), nil 63 | default: 64 | return nil, fmt.Errorf("pg: can't append %T", query) 65 | } 66 | } 67 | 68 | // FormattedQuery returns the formatted query of a query event. 69 | // The query is only valid until the query Result is returned to the user. 70 | func (e *QueryEvent) FormattedQuery() ([]byte, error) { 71 | return e.fmtedQuery, nil 72 | } 73 | 74 | // AddQueryHook adds a hook into query processing. 75 | func (db *baseDB) AddQueryHook(hook QueryHook) { 76 | db.queryHooks = append(db.queryHooks, hook) 77 | } 78 | 79 | func (db *baseDB) beforeQuery( 80 | ctx context.Context, 81 | ormDB orm.DB, 82 | model, query interface{}, 83 | params []interface{}, 84 | fmtedQuery []byte, 85 | ) (context.Context, *QueryEvent, error) { 86 | if len(db.queryHooks) == 0 { 87 | return ctx, nil, nil 88 | } 89 | 90 | event := &QueryEvent{ 91 | StartTime: time.Now(), 92 | DB: ormDB, 93 | Model: model, 94 | Query: query, 95 | Params: params, 96 | fmtedQuery: fmtedQuery, 97 | } 98 | 99 | for i, hook := range db.queryHooks { 100 | var err error 101 | ctx, err = hook.BeforeQuery(ctx, event) 102 | if err != nil { 103 | if err := db.afterQueryFromIndex(ctx, event, i); err != nil { 104 | return ctx, nil, err 105 | } 106 | return ctx, nil, err 107 | } 108 | } 109 | 110 | return ctx, event, nil 111 | } 112 | 113 | func (db *baseDB) afterQuery( 114 | ctx context.Context, 115 | event *QueryEvent, 116 | res Result, 117 | err error, 118 | ) error { 119 | if event == nil { 120 | return nil 121 | } 122 | 123 | event.Err = err 124 | event.Result = res 125 | return db.afterQueryFromIndex(ctx, event, len(db.queryHooks)-1) 126 | } 127 | 128 | func (db *baseDB) afterQueryFromIndex(ctx context.Context, event *QueryEvent, hookIndex int) error { 129 | for ; hookIndex >= 0; hookIndex-- { 130 | if err := db.queryHooks[hookIndex].AfterQuery(ctx, event); err != nil { 131 | return err 132 | } 133 | } 134 | return nil 135 | } 136 | 137 | func copyQueryHooks(s []QueryHook) []QueryHook { 138 | return s[:len(s):len(s)] 139 | } 140 | -------------------------------------------------------------------------------- /internal/context.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | type UndoneContext struct { 9 | context.Context 10 | } 11 | 12 | func UndoContext(ctx context.Context) UndoneContext { 13 | return UndoneContext{Context: ctx} 14 | } 15 | 16 | func (UndoneContext) Deadline() (deadline time.Time, ok bool) { 17 | return time.Time{}, false 18 | } 19 | 20 | func (UndoneContext) Done() <-chan struct{} { 21 | return nil 22 | } 23 | 24 | func (UndoneContext) Err() error { 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /internal/error.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | var ( 8 | ErrNoRows = Errorf("pg: no rows in result set") 9 | ErrMultiRows = Errorf("pg: multiple rows in result set") 10 | ) 11 | 12 | type Error struct { 13 | s string 14 | } 15 | 16 | func Errorf(s string, args ...interface{}) Error { 17 | return Error{s: fmt.Sprintf(s, args...)} 18 | } 19 | 20 | func (err Error) Error() string { 21 | return err.s 22 | } 23 | 24 | type PGError struct { 25 | m map[byte]string 26 | } 27 | 28 | func NewPGError(m map[byte]string) PGError { 29 | return PGError{ 30 | m: m, 31 | } 32 | } 33 | 34 | func (err PGError) Field(k byte) string { 35 | return err.m[k] 36 | } 37 | 38 | func (err PGError) IntegrityViolation() bool { 39 | switch err.Field('C') { 40 | case "23000", "23001", "23502", "23503", "23505", "23514", "23P01": 41 | return true 42 | default: 43 | return false 44 | } 45 | } 46 | 47 | func (err PGError) Error() string { 48 | return fmt.Sprintf("%s #%s %s", 49 | err.Field('S'), err.Field('C'), err.Field('M')) 50 | } 51 | 52 | func AssertOneRow(l int) error { 53 | switch { 54 | case l == 0: 55 | return ErrNoRows 56 | case l > 1: 57 | return ErrMultiRows 58 | default: 59 | return nil 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /internal/internal.go: -------------------------------------------------------------------------------- 1 | /* 2 | internal is a private internal package. 3 | */ 4 | package internal 5 | 6 | import ( 7 | "math/rand" 8 | "time" 9 | ) 10 | 11 | func RetryBackoff(retry int, minBackoff, maxBackoff time.Duration) time.Duration { 12 | if retry < 0 { 13 | panic("not reached") 14 | } 15 | if minBackoff == 0 { 16 | return 0 17 | } 18 | 19 | d := minBackoff << uint(retry) 20 | d = minBackoff + time.Duration(rand.Int63n(int64(d))) 21 | 22 | if d > maxBackoff || d < minBackoff { 23 | d = maxBackoff 24 | } 25 | 26 | return d 27 | } 28 | -------------------------------------------------------------------------------- /internal/log.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | ) 9 | 10 | var Warn = log.New(os.Stderr, "WARN: pg: ", log.LstdFlags) 11 | 12 | var Deprecated = log.New(os.Stderr, "DEPRECATED: pg: ", log.LstdFlags) 13 | 14 | type Logging interface { 15 | Printf(ctx context.Context, format string, v ...interface{}) 16 | } 17 | 18 | type logger struct { 19 | log *log.Logger 20 | } 21 | 22 | func (l *logger) Printf(ctx context.Context, format string, v ...interface{}) { 23 | _ = l.log.Output(2, fmt.Sprintf(format, v...)) 24 | } 25 | 26 | var Logger Logging = &logger{ 27 | log: log.New(os.Stderr, "pg: ", log.LstdFlags|log.Lshortfile), 28 | } 29 | -------------------------------------------------------------------------------- /internal/parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "bytes" 5 | "strconv" 6 | 7 | "github.com/go-pg/pg/v10/internal" 8 | ) 9 | 10 | type Parser struct { 11 | b []byte 12 | i int 13 | } 14 | 15 | func New(b []byte) *Parser { 16 | return &Parser{ 17 | b: b, 18 | } 19 | } 20 | 21 | func NewString(s string) *Parser { 22 | return New(internal.StringToBytes(s)) 23 | } 24 | 25 | func (p *Parser) Valid() bool { 26 | return p.i < len(p.b) 27 | } 28 | 29 | func (p *Parser) Bytes() []byte { 30 | return p.b[p.i:] 31 | } 32 | 33 | func (p *Parser) Read() byte { 34 | if p.Valid() { 35 | c := p.b[p.i] 36 | p.Advance() 37 | return c 38 | } 39 | return 0 40 | } 41 | 42 | func (p *Parser) Peek() byte { 43 | if p.Valid() { 44 | return p.b[p.i] 45 | } 46 | return 0 47 | } 48 | 49 | func (p *Parser) Advance() { 50 | p.i++ 51 | } 52 | 53 | func (p *Parser) Skip(skip byte) bool { 54 | if p.Peek() == skip { 55 | p.Advance() 56 | return true 57 | } 58 | return false 59 | } 60 | 61 | func (p *Parser) SkipBytes(skip []byte) bool { 62 | if len(skip) > len(p.b[p.i:]) { 63 | return false 64 | } 65 | if !bytes.Equal(p.b[p.i:p.i+len(skip)], skip) { 66 | return false 67 | } 68 | p.i += len(skip) 69 | return true 70 | } 71 | 72 | func (p *Parser) ReadSep(sep byte) ([]byte, bool) { 73 | ind := bytes.IndexByte(p.b[p.i:], sep) 74 | if ind == -1 { 75 | b := p.b[p.i:] 76 | p.i = len(p.b) 77 | return b, false 78 | } 79 | 80 | b := p.b[p.i : p.i+ind] 81 | p.i += ind + 1 82 | return b, true 83 | } 84 | 85 | func (p *Parser) ReadIdentifier() (string, bool) { 86 | if p.i < len(p.b) && p.b[p.i] == '(' { 87 | s := p.i + 1 88 | if ind := bytes.IndexByte(p.b[s:], ')'); ind != -1 { 89 | b := p.b[s : s+ind] 90 | p.i = s + ind + 1 91 | return internal.BytesToString(b), false 92 | } 93 | } 94 | 95 | ind := len(p.b) - p.i 96 | var alpha bool 97 | for i, c := range p.b[p.i:] { 98 | if isNum(c) { 99 | continue 100 | } 101 | if isAlpha(c) || (i > 0 && alpha && c == '_') { 102 | alpha = true 103 | continue 104 | } 105 | ind = i 106 | break 107 | } 108 | if ind == 0 { 109 | return "", false 110 | } 111 | b := p.b[p.i : p.i+ind] 112 | p.i += ind 113 | return internal.BytesToString(b), !alpha 114 | } 115 | 116 | func (p *Parser) ReadNumber() int { 117 | ind := len(p.b) - p.i 118 | for i, c := range p.b[p.i:] { 119 | if !isNum(c) { 120 | ind = i 121 | break 122 | } 123 | } 124 | if ind == 0 { 125 | return 0 126 | } 127 | n, err := strconv.Atoi(string(p.b[p.i : p.i+ind])) 128 | if err != nil { 129 | panic(err) 130 | } 131 | p.i += ind 132 | return n 133 | } 134 | 135 | func isNum(c byte) bool { 136 | return c >= '0' && c <= '9' 137 | } 138 | 139 | func isAlpha(c byte) bool { 140 | return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') 141 | } 142 | -------------------------------------------------------------------------------- /internal/parser/streaming_parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-pg/pg/v10/internal/pool" 7 | ) 8 | 9 | type StreamingParser struct { 10 | pool.Reader 11 | } 12 | 13 | func NewStreamingParser(rd pool.Reader) StreamingParser { 14 | return StreamingParser{ 15 | Reader: rd, 16 | } 17 | } 18 | 19 | func (p StreamingParser) SkipByte(skip byte) error { 20 | c, err := p.ReadByte() 21 | if err != nil { 22 | return err 23 | } 24 | if c == skip { 25 | return nil 26 | } 27 | _ = p.UnreadByte() 28 | return fmt.Errorf("got %q, wanted %q", c, skip) 29 | } 30 | 31 | func (p StreamingParser) ReadSubstring(b []byte) ([]byte, error) { 32 | c, err := p.ReadByte() 33 | if err != nil { 34 | return b, err 35 | } 36 | 37 | for { 38 | if c == '"' { 39 | return b, nil 40 | } 41 | 42 | next, err := p.ReadByte() 43 | if err != nil { 44 | return b, err 45 | } 46 | 47 | if c == '\\' { 48 | switch next { 49 | case '\\', '"': 50 | b = append(b, next) 51 | c, err = p.ReadByte() 52 | if err != nil { 53 | return nil, err 54 | } 55 | default: 56 | b = append(b, '\\') 57 | c = next 58 | } 59 | continue 60 | } 61 | 62 | b = append(b, c) 63 | c = next 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /internal/pool/bench_test.go: -------------------------------------------------------------------------------- 1 | package pool_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/go-pg/pg/v10/internal/pool" 9 | ) 10 | 11 | func benchmarkPoolGetPut(b *testing.B, poolSize int) { 12 | ctx := context.Background() 13 | connPool := pool.NewConnPool(&pool.Options{ 14 | Dialer: dummyDialer, 15 | PoolSize: poolSize, 16 | PoolTimeout: time.Second, 17 | IdleTimeout: time.Hour, 18 | IdleCheckFrequency: time.Hour, 19 | }) 20 | 21 | b.ResetTimer() 22 | 23 | b.RunParallel(func(pb *testing.PB) { 24 | for pb.Next() { 25 | cn, err := connPool.Get(ctx) 26 | if err != nil { 27 | b.Fatal(err) 28 | } 29 | connPool.Put(ctx, cn) 30 | } 31 | }) 32 | } 33 | 34 | func BenchmarkPoolGetPut10Conns(b *testing.B) { 35 | benchmarkPoolGetPut(b, 10) 36 | } 37 | 38 | func BenchmarkPoolGetPut100Conns(b *testing.B) { 39 | benchmarkPoolGetPut(b, 100) 40 | } 41 | 42 | func BenchmarkPoolGetPut1000Conns(b *testing.B) { 43 | benchmarkPoolGetPut(b, 1000) 44 | } 45 | 46 | func benchmarkPoolGetRemove(b *testing.B, poolSize int) { 47 | ctx := context.Background() 48 | connPool := pool.NewConnPool(&pool.Options{ 49 | Dialer: dummyDialer, 50 | PoolSize: poolSize, 51 | PoolTimeout: time.Second, 52 | IdleTimeout: time.Hour, 53 | IdleCheckFrequency: time.Hour, 54 | }) 55 | 56 | b.ResetTimer() 57 | 58 | b.RunParallel(func(pb *testing.PB) { 59 | for pb.Next() { 60 | cn, err := connPool.Get(ctx) 61 | if err != nil { 62 | b.Fatal(err) 63 | } 64 | connPool.Remove(ctx, cn, nil) 65 | } 66 | }) 67 | } 68 | 69 | func BenchmarkPoolGetRemove10Conns(b *testing.B) { 70 | benchmarkPoolGetRemove(b, 10) 71 | } 72 | 73 | func BenchmarkPoolGetRemove100Conns(b *testing.B) { 74 | benchmarkPoolGetRemove(b, 100) 75 | } 76 | 77 | func BenchmarkPoolGetRemove1000Conns(b *testing.B) { 78 | benchmarkPoolGetRemove(b, 1000) 79 | } 80 | 81 | var columns = [][]byte{ 82 | []byte("id"), 83 | []byte("business_phone"), 84 | []byte("display_name"), 85 | []byte("given_name"), 86 | []byte("job_title"), 87 | []byte("mail"), 88 | []byte("mobile_phone"), 89 | []byte("office_location"), 90 | []byte("preferred_language"), 91 | []byte("surname"), 92 | []byte("user_principal_name"), 93 | } 94 | 95 | func BenchmarkColumnAlloc(b *testing.B) { 96 | columnAlloc := pool.NewColumnAlloc() 97 | for i := 0; i < b.N; i++ { 98 | for i, column := range columns { 99 | columnAlloc.New(int16(i), column) 100 | } 101 | columnAlloc.Columns() 102 | columnAlloc.Reset() 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /internal/pool/conn.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "strconv" 7 | "sync/atomic" 8 | "time" 9 | ) 10 | 11 | var noDeadline = time.Time{} 12 | 13 | type Conn struct { 14 | netConn net.Conn 15 | rd *ReaderContext 16 | pool *ConnPool 17 | 18 | ProcessID int32 19 | SecretKey int32 20 | lastID int64 21 | 22 | createdAt time.Time 23 | usedAt uint32 // atomic 24 | pooled bool 25 | Inited bool 26 | } 27 | 28 | func NewConn(netConn net.Conn, pool *ConnPool) *Conn { 29 | cn := &Conn{ 30 | createdAt: time.Now(), 31 | pool: pool, 32 | } 33 | cn.SetNetConn(netConn) 34 | cn.SetUsedAt(time.Now()) 35 | return cn 36 | } 37 | 38 | func (cn *Conn) UsedAt() time.Time { 39 | unix := atomic.LoadUint32(&cn.usedAt) 40 | return time.Unix(int64(unix), 0) 41 | } 42 | 43 | func (cn *Conn) SetUsedAt(tm time.Time) { 44 | atomic.StoreUint32(&cn.usedAt, uint32(tm.Unix())) 45 | } 46 | 47 | func (cn *Conn) RemoteAddr() net.Addr { 48 | return cn.netConn.RemoteAddr() 49 | } 50 | 51 | func (cn *Conn) SetNetConn(netConn net.Conn) { 52 | cn.netConn = netConn 53 | if cn.rd != nil { 54 | cn.rd.Reset(netConn) 55 | } 56 | } 57 | 58 | func (cn *Conn) LockReader() { 59 | if cn.rd != nil { 60 | panic("not reached") 61 | } 62 | cn.rd = NewReaderContext(cn.pool.opt.ReadBufferInitialSize) 63 | cn.rd.Reset(cn.netConn) 64 | } 65 | 66 | func (cn *Conn) NetConn() net.Conn { 67 | return cn.netConn 68 | } 69 | 70 | func (cn *Conn) NextID() string { 71 | cn.lastID++ 72 | return strconv.FormatInt(cn.lastID, 10) 73 | } 74 | 75 | func (cn *Conn) WithReader( 76 | ctx context.Context, timeout time.Duration, fn func(rd *ReaderContext) error, 77 | ) error { 78 | if err := cn.netConn.SetReadDeadline(cn.deadline(ctx, timeout)); err != nil { 79 | return err 80 | } 81 | 82 | rd := cn.rd 83 | if rd == nil { 84 | rd = cn.pool.GetReaderContext() 85 | defer cn.pool.PutReaderContext(rd) 86 | 87 | rd.Reset(cn.netConn) 88 | } 89 | 90 | rd.bytesRead = 0 91 | 92 | if err := fn(rd); err != nil { 93 | return err 94 | } 95 | 96 | return nil 97 | } 98 | 99 | func (cn *Conn) WithWriter( 100 | ctx context.Context, timeout time.Duration, fn func(wb *WriteBuffer) error, 101 | ) error { 102 | wb := cn.pool.GetWriteBuffer() 103 | defer cn.pool.PutWriteBuffer(wb) 104 | 105 | if err := fn(wb); err != nil { 106 | return err 107 | } 108 | 109 | return cn.writeBuffer(ctx, timeout, wb) 110 | } 111 | 112 | func (cn *Conn) WriteBuffer(ctx context.Context, timeout time.Duration, wb *WriteBuffer) error { 113 | return cn.writeBuffer(ctx, timeout, wb) 114 | } 115 | 116 | func (cn *Conn) writeBuffer( 117 | ctx context.Context, 118 | timeout time.Duration, 119 | wb *WriteBuffer, 120 | ) error { 121 | if err := cn.netConn.SetWriteDeadline(cn.deadline(ctx, timeout)); err != nil { 122 | return err 123 | } 124 | if _, err := cn.netConn.Write(wb.Bytes); err != nil { 125 | return err 126 | } 127 | return nil 128 | } 129 | 130 | func (cn *Conn) Close() error { 131 | return cn.netConn.Close() 132 | } 133 | 134 | func (cn *Conn) deadline(ctx context.Context, timeout time.Duration) time.Time { 135 | tm := time.Now() 136 | cn.SetUsedAt(tm) 137 | 138 | if timeout > 0 { 139 | tm = tm.Add(timeout) 140 | } 141 | 142 | if ctx != nil { 143 | deadline, ok := ctx.Deadline() 144 | if ok { 145 | if timeout == 0 { 146 | return deadline 147 | } 148 | if deadline.Before(tm) { 149 | return deadline 150 | } 151 | return tm 152 | } 153 | } 154 | 155 | if timeout > 0 { 156 | return tm 157 | } 158 | 159 | return noDeadline 160 | } 161 | -------------------------------------------------------------------------------- /internal/pool/export_test.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import "time" 4 | 5 | func (cn *Conn) SetCreatedAt(tm time.Time) { 6 | cn.createdAt = tm 7 | } 8 | -------------------------------------------------------------------------------- /internal/pool/main_test.go: -------------------------------------------------------------------------------- 1 | package pool_test 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "sync" 7 | "testing" 8 | 9 | . "github.com/onsi/ginkgo" 10 | . "github.com/onsi/gomega" 11 | ) 12 | 13 | func TestGinkgo(t *testing.T) { 14 | RegisterFailHandler(Fail) 15 | RunSpecs(t, "pool") 16 | } 17 | 18 | func perform(n int, cbs ...func(int)) { 19 | var wg sync.WaitGroup 20 | for _, cb := range cbs { 21 | for i := 0; i < n; i++ { 22 | wg.Add(1) 23 | go func(cb func(int), i int) { 24 | defer GinkgoRecover() 25 | defer wg.Done() 26 | 27 | cb(i) 28 | }(cb, i) 29 | } 30 | } 31 | wg.Wait() 32 | } 33 | 34 | func dummyDialer(context.Context) (net.Conn, error) { 35 | return &net.TCPConn{}, nil 36 | } 37 | -------------------------------------------------------------------------------- /internal/pool/pool_single.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import "context" 4 | 5 | type SingleConnPool struct { 6 | pool Pooler 7 | cn *Conn 8 | stickyErr error 9 | } 10 | 11 | var _ Pooler = (*SingleConnPool)(nil) 12 | 13 | func NewSingleConnPool(pool Pooler, cn *Conn) *SingleConnPool { 14 | return &SingleConnPool{ 15 | pool: pool, 16 | cn: cn, 17 | } 18 | } 19 | 20 | func (p *SingleConnPool) NewConn(ctx context.Context) (*Conn, error) { 21 | return p.pool.NewConn(ctx) 22 | } 23 | 24 | func (p *SingleConnPool) CloseConn(cn *Conn) error { 25 | return p.pool.CloseConn(cn) 26 | } 27 | 28 | func (p *SingleConnPool) Get(ctx context.Context) (*Conn, error) { 29 | if p.stickyErr != nil { 30 | return nil, p.stickyErr 31 | } 32 | return p.cn, nil 33 | } 34 | 35 | func (p *SingleConnPool) Put(ctx context.Context, cn *Conn) {} 36 | 37 | func (p *SingleConnPool) Remove(ctx context.Context, cn *Conn, reason error) { 38 | p.cn = nil 39 | p.stickyErr = reason 40 | // If ctx is cancelled without a reason(error) value, 41 | // then the ctx.Error is used as the reason for why the p.cn is assigned nil. 42 | if reason == nil && ctx != nil { 43 | p.stickyErr = ctx.Err() 44 | } 45 | } 46 | 47 | func (p *SingleConnPool) Close() error { 48 | p.cn = nil 49 | p.stickyErr = ErrClosed 50 | return nil 51 | } 52 | 53 | func (p *SingleConnPool) Len() int { 54 | return 0 55 | } 56 | 57 | func (p *SingleConnPool) IdleLen() int { 58 | return 0 59 | } 60 | 61 | func (p *SingleConnPool) Stats() *Stats { 62 | return &Stats{} 63 | } 64 | 65 | func (p *SingleConnPool) GetWriteBuffer() *WriteBuffer { 66 | return p.pool.GetWriteBuffer() 67 | } 68 | 69 | func (p *SingleConnPool) PutWriteBuffer(wb *WriteBuffer) { 70 | p.pool.PutWriteBuffer(wb) 71 | } 72 | 73 | func (p *SingleConnPool) GetReaderContext() *ReaderContext { 74 | return p.pool.GetReaderContext() 75 | } 76 | 77 | func (p *SingleConnPool) PutReaderContext(rd *ReaderContext) { 78 | p.pool.PutReaderContext(rd) 79 | } 80 | -------------------------------------------------------------------------------- /internal/pool/pool_single_test.go: -------------------------------------------------------------------------------- 1 | package pool_test 2 | 3 | import ( 4 | "context" 5 | . "github.com/onsi/ginkgo" 6 | . "github.com/onsi/gomega" 7 | 8 | "github.com/go-pg/pg/v10/internal/pool" 9 | ) 10 | 11 | var _ = Describe("SingleConnPool", func() { 12 | It("remove a conn due to context is cancelled", func() { 13 | p := pool.NewSingleConnPool(nil, &pool.Conn{}) 14 | ctx, cancel := context.WithCancel(context.TODO()) 15 | cn, err := p.Get(nil) 16 | Expect(err).To(BeNil()) 17 | Expect(cn).ToNot(BeNil()) 18 | 19 | cancel() 20 | p.Remove(ctx, cn, nil) 21 | cn, err = p.Get(nil) 22 | Expect(cn).To(BeNil()) 23 | Expect(err).ToNot(BeNil()) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /internal/pool/pool_sticky_test.go: -------------------------------------------------------------------------------- 1 | package pool_test 2 | 3 | import ( 4 | "context" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | 9 | "github.com/go-pg/pg/v10/internal/pool" 10 | ) 11 | 12 | var _ = Describe("StickyConnPool", func() { 13 | var p *pool.StickyConnPool 14 | 15 | BeforeEach(func() { 16 | p = pool.NewStickyConnPool(nil) 17 | }) 18 | 19 | It("closes the pool", func() { 20 | err := p.Close() 21 | Expect(err).NotTo(HaveOccurred()) 22 | 23 | _, err = p.Get(context.Background()) 24 | Expect(err).To(Equal(pool.ErrClosed)) 25 | 26 | err = p.Close() 27 | Expect(err).To(Equal(pool.ErrClosed)) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /internal/pool/reader.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | type Reader interface { 4 | Buffered() int 5 | 6 | Bytes() []byte 7 | Read([]byte) (int, error) 8 | ReadByte() (byte, error) 9 | UnreadByte() error 10 | ReadSlice(byte) ([]byte, error) 11 | Discard(int) (int, error) 12 | 13 | // ReadBytes(fn func(byte) bool) ([]byte, error) 14 | // ReadN(int) ([]byte, error) 15 | ReadFull() ([]byte, error) 16 | ReadFullTemp() ([]byte, error) 17 | } 18 | 19 | type ColumnInfo struct { 20 | Index int16 21 | DataType int32 22 | Name string 23 | } 24 | 25 | type ColumnAlloc struct { 26 | columns []ColumnInfo 27 | } 28 | 29 | func NewColumnAlloc() *ColumnAlloc { 30 | return new(ColumnAlloc) 31 | } 32 | 33 | func (c *ColumnAlloc) Reset() { 34 | c.columns = c.columns[:0] 35 | } 36 | 37 | func (c *ColumnAlloc) New(index int16, name []byte) *ColumnInfo { 38 | c.columns = append(c.columns, ColumnInfo{ 39 | Index: index, 40 | Name: string(name), 41 | }) 42 | return &c.columns[len(c.columns)-1] 43 | } 44 | 45 | func (c *ColumnAlloc) Columns() []ColumnInfo { 46 | return c.columns 47 | } 48 | 49 | type ReaderContext struct { 50 | *BufReader 51 | ColumnAlloc *ColumnAlloc 52 | } 53 | 54 | func NewReaderContext(bufSize int) *ReaderContext { 55 | return &ReaderContext{ 56 | BufReader: NewBufReader(bufSize), 57 | ColumnAlloc: NewColumnAlloc(), 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /internal/pool/reader_bytes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pool 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "io" 11 | ) 12 | 13 | type BytesReader struct { 14 | s []byte 15 | i int 16 | } 17 | 18 | func NewBytesReader(b []byte) *BytesReader { 19 | return &BytesReader{ 20 | s: b, 21 | } 22 | } 23 | 24 | func (r *BytesReader) Reset(b []byte) { 25 | r.s = b 26 | r.i = 0 27 | } 28 | 29 | func (r *BytesReader) Buffered() int { 30 | return len(r.s) - r.i 31 | } 32 | 33 | func (r *BytesReader) Bytes() []byte { 34 | return r.s[r.i:] 35 | } 36 | 37 | func (r *BytesReader) Read(b []byte) (n int, err error) { 38 | if r.i >= len(r.s) { 39 | return 0, io.EOF 40 | } 41 | n = copy(b, r.s[r.i:]) 42 | r.i += n 43 | return 44 | } 45 | 46 | func (r *BytesReader) ReadByte() (byte, error) { 47 | if r.i >= len(r.s) { 48 | return 0, io.EOF 49 | } 50 | b := r.s[r.i] 51 | r.i++ 52 | return b, nil 53 | } 54 | 55 | func (r *BytesReader) UnreadByte() error { 56 | if r.i <= 0 { 57 | return errors.New("UnreadByte: at beginning of slice") 58 | } 59 | r.i-- 60 | return nil 61 | } 62 | 63 | func (r *BytesReader) ReadSlice(delim byte) ([]byte, error) { 64 | if i := bytes.IndexByte(r.s[r.i:], delim); i >= 0 { 65 | i++ 66 | line := r.s[r.i : r.i+i] 67 | r.i += i 68 | return line, nil 69 | } 70 | 71 | line := r.s[r.i:] 72 | r.i = len(r.s) 73 | return line, io.EOF 74 | } 75 | 76 | func (r *BytesReader) ReadBytes(fn func(byte) bool) ([]byte, error) { 77 | for i, c := range r.s[r.i:] { 78 | if !fn(c) { 79 | i++ 80 | line := r.s[r.i : r.i+i] 81 | r.i += i 82 | return line, nil 83 | } 84 | } 85 | 86 | line := r.s[r.i:] 87 | r.i = len(r.s) 88 | return line, io.EOF 89 | } 90 | 91 | func (r *BytesReader) Discard(n int) (int, error) { 92 | b, err := r.ReadN(n) 93 | return len(b), err 94 | } 95 | 96 | func (r *BytesReader) ReadN(n int) ([]byte, error) { 97 | nn := n 98 | if nn > len(r.s) { 99 | nn = len(r.s) 100 | } 101 | 102 | b := r.s[r.i : r.i+nn] 103 | r.i += nn 104 | if n > nn { 105 | return b, io.EOF 106 | } 107 | return b, nil 108 | } 109 | 110 | func (r *BytesReader) ReadFull() ([]byte, error) { 111 | b := make([]byte, len(r.s)-r.i) 112 | copy(b, r.s[r.i:]) 113 | r.i = len(r.s) 114 | return b, nil 115 | } 116 | 117 | func (r *BytesReader) ReadFullTemp() ([]byte, error) { 118 | b := r.s[r.i:] 119 | r.i = len(r.s) 120 | return b, nil 121 | } 122 | -------------------------------------------------------------------------------- /internal/pool/write_buffer.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | ) 7 | 8 | type WriteBuffer struct { 9 | Bytes []byte 10 | 11 | msgStart int 12 | paramStart int 13 | } 14 | 15 | func NewWriteBuffer(bufSize int) *WriteBuffer { 16 | return &WriteBuffer{ 17 | Bytes: make([]byte, 0, bufSize), 18 | } 19 | } 20 | 21 | func (buf *WriteBuffer) Reset() { 22 | buf.Bytes = buf.Bytes[:0] 23 | } 24 | 25 | func (buf *WriteBuffer) StartMessage(c byte) { 26 | if c == 0 { 27 | buf.msgStart = len(buf.Bytes) 28 | buf.Bytes = append(buf.Bytes, 0, 0, 0, 0) 29 | } else { 30 | buf.msgStart = len(buf.Bytes) + 1 31 | buf.Bytes = append(buf.Bytes, c, 0, 0, 0, 0) 32 | } 33 | } 34 | 35 | func (buf *WriteBuffer) FinishMessage() { 36 | binary.BigEndian.PutUint32( 37 | buf.Bytes[buf.msgStart:], uint32(len(buf.Bytes)-buf.msgStart)) 38 | } 39 | 40 | func (buf *WriteBuffer) Query() []byte { 41 | return buf.Bytes[buf.msgStart+4 : len(buf.Bytes)-1] 42 | } 43 | 44 | func (buf *WriteBuffer) StartParam() { 45 | buf.paramStart = len(buf.Bytes) 46 | buf.Bytes = append(buf.Bytes, 0, 0, 0, 0) 47 | } 48 | 49 | func (buf *WriteBuffer) FinishParam() { 50 | binary.BigEndian.PutUint32( 51 | buf.Bytes[buf.paramStart:], uint32(len(buf.Bytes)-buf.paramStart-4)) 52 | } 53 | 54 | var nullParamLength = int32(-1) 55 | 56 | func (buf *WriteBuffer) FinishNullParam() { 57 | binary.BigEndian.PutUint32( 58 | buf.Bytes[buf.paramStart:], uint32(nullParamLength)) 59 | } 60 | 61 | func (buf *WriteBuffer) Write(b []byte) (int, error) { 62 | buf.Bytes = append(buf.Bytes, b...) 63 | return len(b), nil 64 | } 65 | 66 | func (buf *WriteBuffer) WriteInt16(num int16) { 67 | buf.Bytes = append(buf.Bytes, 0, 0) 68 | binary.BigEndian.PutUint16(buf.Bytes[len(buf.Bytes)-2:], uint16(num)) 69 | } 70 | 71 | func (buf *WriteBuffer) WriteInt32(num int32) { 72 | buf.Bytes = append(buf.Bytes, 0, 0, 0, 0) 73 | binary.BigEndian.PutUint32(buf.Bytes[len(buf.Bytes)-4:], uint32(num)) 74 | } 75 | 76 | func (buf *WriteBuffer) WriteString(s string) { 77 | buf.Bytes = append(buf.Bytes, s...) 78 | buf.Bytes = append(buf.Bytes, 0) 79 | } 80 | 81 | func (buf *WriteBuffer) WriteBytes(b []byte) { 82 | buf.Bytes = append(buf.Bytes, b...) 83 | buf.Bytes = append(buf.Bytes, 0) 84 | } 85 | 86 | func (buf *WriteBuffer) WriteByte(c byte) error { 87 | buf.Bytes = append(buf.Bytes, c) 88 | return nil 89 | } 90 | 91 | func (buf *WriteBuffer) ReadFrom(r io.Reader) (int64, error) { 92 | n, err := r.Read(buf.Bytes[len(buf.Bytes):cap(buf.Bytes)]) 93 | buf.Bytes = buf.Bytes[:len(buf.Bytes)+n] 94 | return int64(n), err 95 | } 96 | -------------------------------------------------------------------------------- /internal/safe.go: -------------------------------------------------------------------------------- 1 | //go:build appengine 2 | // +build appengine 3 | 4 | package internal 5 | 6 | func BytesToString(b []byte) string { 7 | return string(b) 8 | } 9 | 10 | func StringToBytes(s string) []byte { 11 | return []byte(s) 12 | } 13 | -------------------------------------------------------------------------------- /internal/strconv.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "strconv" 4 | 5 | func Atoi(b []byte) (int, error) { 6 | return strconv.Atoi(BytesToString(b)) 7 | } 8 | 9 | func ParseInt(b []byte, base int, bitSize int) (int64, error) { 10 | return strconv.ParseInt(BytesToString(b), base, bitSize) 11 | } 12 | 13 | func ParseUint(b []byte, base int, bitSize int) (uint64, error) { 14 | return strconv.ParseUint(BytesToString(b), base, bitSize) 15 | } 16 | 17 | func ParseFloat(b []byte, bitSize int) (float64, error) { 18 | return strconv.ParseFloat(BytesToString(b), bitSize) 19 | } 20 | -------------------------------------------------------------------------------- /internal/underscore.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | func IsUpper(c byte) bool { 4 | return c >= 'A' && c <= 'Z' 5 | } 6 | 7 | func IsLower(c byte) bool { 8 | return c >= 'a' && c <= 'z' 9 | } 10 | 11 | func ToUpper(c byte) byte { 12 | return c - 32 13 | } 14 | 15 | func ToLower(c byte) byte { 16 | return c + 32 17 | } 18 | 19 | // Underscore converts "CamelCasedString" to "camel_cased_string". 20 | func Underscore(s string) string { 21 | r := make([]byte, 0, len(s)+5) 22 | for i := 0; i < len(s); i++ { 23 | c := s[i] 24 | if IsUpper(c) { 25 | if i > 0 && i+1 < len(s) && (IsLower(s[i-1]) || IsLower(s[i+1])) { 26 | r = append(r, '_', ToLower(c)) 27 | } else { 28 | r = append(r, ToLower(c)) 29 | } 30 | } else { 31 | r = append(r, c) 32 | } 33 | } 34 | return string(r) 35 | } 36 | 37 | func CamelCased(s string) string { 38 | r := make([]byte, 0, len(s)) 39 | upperNext := true 40 | for i := 0; i < len(s); i++ { 41 | c := s[i] 42 | if c == '_' { 43 | upperNext = true 44 | continue 45 | } 46 | if upperNext { 47 | if IsLower(c) { 48 | c = ToUpper(c) 49 | } 50 | upperNext = false 51 | } 52 | r = append(r, c) 53 | } 54 | return string(r) 55 | } 56 | 57 | func ToExported(s string) string { 58 | if len(s) == 0 { 59 | return s 60 | } 61 | if c := s[0]; IsLower(c) { 62 | b := []byte(s) 63 | b[0] = ToUpper(c) 64 | return string(b) 65 | } 66 | return s 67 | } 68 | 69 | func UpperString(s string) string { 70 | if isUpperString(s) { 71 | return s 72 | } 73 | 74 | b := make([]byte, len(s)) 75 | for i := range b { 76 | c := s[i] 77 | if IsLower(c) { 78 | c = ToUpper(c) 79 | } 80 | b[i] = c 81 | } 82 | return string(b) 83 | } 84 | 85 | func isUpperString(s string) bool { 86 | for i := 0; i < len(s); i++ { 87 | c := s[i] 88 | if IsLower(c) { 89 | return false 90 | } 91 | } 92 | return true 93 | } 94 | -------------------------------------------------------------------------------- /internal/underscore_test.go: -------------------------------------------------------------------------------- 1 | package internal_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/go-pg/pg/v10/internal" 7 | ) 8 | 9 | func TestUnderscore(t *testing.T) { 10 | tests := []struct { 11 | s, wanted string 12 | }{ 13 | {"Megacolumn", "megacolumn"}, 14 | {"MegaColumn", "mega_column"}, 15 | {"MegaColumn_Id", "mega_column__id"}, 16 | {"MegaColumn_id", "mega_column_id"}, 17 | } 18 | for _, v := range tests { 19 | if got := internal.Underscore(v.s); got != v.wanted { 20 | t.Errorf("got %q, wanted %q", got, v.wanted) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /internal/unsafe.go: -------------------------------------------------------------------------------- 1 | //go:build !appengine 2 | // +build !appengine 3 | 4 | package internal 5 | 6 | import ( 7 | "unsafe" 8 | ) 9 | 10 | // BytesToString converts byte slice to string. 11 | func BytesToString(b []byte) string { 12 | return *(*string)(unsafe.Pointer(&b)) 13 | } 14 | 15 | // StringToBytes converts string to byte slice. 16 | func StringToBytes(s string) []byte { 17 | return *(*[]byte)(unsafe.Pointer( 18 | &struct { 19 | string 20 | Cap int 21 | }{s, len(s)}, 22 | )) 23 | } 24 | -------------------------------------------------------------------------------- /internal/util.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "time" 7 | ) 8 | 9 | func Sleep(ctx context.Context, dur time.Duration) error { 10 | t := time.NewTimer(dur) 11 | defer t.Stop() 12 | 13 | select { 14 | case <-t.C: 15 | return nil 16 | case <-ctx.Done(): 17 | return ctx.Err() 18 | } 19 | } 20 | 21 | func MakeSliceNextElemFunc(v reflect.Value) func() reflect.Value { 22 | if v.Kind() == reflect.Array { 23 | var pos int 24 | return func() reflect.Value { 25 | v := v.Index(pos) 26 | pos++ 27 | return v 28 | } 29 | } 30 | 31 | elemType := v.Type().Elem() 32 | 33 | if elemType.Kind() == reflect.Ptr { 34 | elemType = elemType.Elem() 35 | return func() reflect.Value { 36 | if v.Len() < v.Cap() { 37 | v.Set(v.Slice(0, v.Len()+1)) 38 | elem := v.Index(v.Len() - 1) 39 | if elem.IsNil() { 40 | elem.Set(reflect.New(elemType)) 41 | } 42 | return elem.Elem() 43 | } 44 | 45 | elem := reflect.New(elemType) 46 | v.Set(reflect.Append(v, elem)) 47 | return elem.Elem() 48 | } 49 | } 50 | 51 | zero := reflect.Zero(elemType) 52 | return func() reflect.Value { 53 | if v.Len() < v.Cap() { 54 | v.Set(v.Slice(0, v.Len()+1)) 55 | return v.Index(v.Len() - 1) 56 | } 57 | 58 | v.Set(reflect.Append(v, zero)) 59 | return v.Index(v.Len() - 1) 60 | } 61 | } 62 | 63 | func Unwrap(err error) error { 64 | u, ok := err.(interface { 65 | Unwrap() error 66 | }) 67 | if !ok { 68 | return nil 69 | } 70 | return u.Unwrap() 71 | } 72 | -------------------------------------------------------------------------------- /loader_test.go: -------------------------------------------------------------------------------- 1 | package pg_test 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/go-pg/pg/v10" 7 | "github.com/go-pg/pg/v10/orm" 8 | "github.com/go-pg/pg/v10/types" 9 | 10 | . "gopkg.in/check.v1" 11 | ) 12 | 13 | type LoaderTest struct { 14 | db *pg.DB 15 | } 16 | 17 | var _ = Suite(&LoaderTest{}) 18 | 19 | func (t *LoaderTest) SetUpTest(c *C) { 20 | t.db = pg.Connect(pgOptions()) 21 | } 22 | 23 | func (t *LoaderTest) TearDownTest(c *C) { 24 | c.Assert(t.db.Close(), IsNil) 25 | } 26 | 27 | type numLoader struct { 28 | Num int 29 | } 30 | 31 | type embeddedLoader struct { 32 | *numLoader 33 | Num2 int 34 | } 35 | 36 | type multipleLoader struct { 37 | One struct { 38 | Num int 39 | } 40 | Num int 41 | } 42 | 43 | func (t *LoaderTest) TestQuery(c *C) { 44 | var dst numLoader 45 | _, err := t.db.Query(&dst, "SELECT 1 AS num") 46 | c.Assert(err, IsNil) 47 | c.Assert(dst.Num, Equals, 1) 48 | } 49 | 50 | func (t *LoaderTest) TestQueryNull(c *C) { 51 | var dst numLoader 52 | _, err := t.db.Query(&dst, "SELECT NULL AS num") 53 | c.Assert(err, IsNil) 54 | c.Assert(dst.Num, Equals, 0) 55 | } 56 | 57 | func (t *LoaderTest) TestQueryEmbeddedStruct(c *C) { 58 | src := &embeddedLoader{ 59 | numLoader: &numLoader{ 60 | Num: 1, 61 | }, 62 | Num2: 2, 63 | } 64 | dst := &embeddedLoader{ 65 | numLoader: &numLoader{}, 66 | } 67 | _, err := t.db.QueryOne(dst, "SELECT ?num AS num, ?num2 as num2", src) 68 | c.Assert(err, IsNil) 69 | c.Assert(dst, DeepEquals, src) 70 | } 71 | 72 | func (t *LoaderTest) TestQueryNestedStructs(c *C) { 73 | src := &multipleLoader{} 74 | src.One.Num = 1 75 | src.Num = 2 76 | dst := &multipleLoader{} 77 | _, err := t.db.QueryOne(dst, `SELECT ?one__num AS one__num, ?num as num`, src) 78 | c.Assert(err, IsNil) 79 | c.Assert(dst, DeepEquals, src) 80 | } 81 | 82 | func (t *LoaderTest) TestQueryStmt(c *C) { 83 | stmt, err := t.db.Prepare("SELECT 1 AS num") 84 | c.Assert(err, IsNil) 85 | defer stmt.Close() 86 | 87 | dst := &numLoader{} 88 | _, err = stmt.Query(dst) 89 | c.Assert(err, IsNil) 90 | c.Assert(dst.Num, Equals, 1) 91 | } 92 | 93 | func (t *LoaderTest) TestQueryInts(c *C) { 94 | var ids pg.Ints 95 | _, err := t.db.Query(&ids, "SELECT s.num AS num FROM generate_series(0, 10) AS s(num)") 96 | c.Assert(err, IsNil) 97 | c.Assert(ids, DeepEquals, pg.Ints{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) 98 | } 99 | 100 | func (t *LoaderTest) TestQueryInts2(c *C) { 101 | var ints pg.Ints 102 | _, err := t.db.Query(&ints, "SELECT * FROM generate_series(1, 1000000)") 103 | c.Assert(err, IsNil) 104 | c.Assert(ints, HasLen, 1000000) 105 | } 106 | 107 | func (t *LoaderTest) TestQueryStrings(c *C) { 108 | var strings pg.Strings 109 | _, err := t.db.Query(&strings, "SELECT 'hello'") 110 | c.Assert(err, IsNil) 111 | c.Assert(strings, DeepEquals, pg.Strings{"hello"}) 112 | } 113 | 114 | type errLoader struct { 115 | err error 116 | } 117 | 118 | var _ orm.HooklessModel = (*errLoader)(nil) 119 | 120 | func newErrLoader(err error) *errLoader { 121 | return &errLoader{ 122 | err: err, 123 | } 124 | } 125 | 126 | func (m *errLoader) Init() error { 127 | return nil 128 | } 129 | 130 | func (m *errLoader) NextColumnScanner() orm.ColumnScanner { 131 | return m 132 | } 133 | 134 | func (m *errLoader) AddColumnScanner(_ orm.ColumnScanner) error { 135 | return nil 136 | } 137 | 138 | func (m *errLoader) ScanColumn(types.ColumnInfo, types.Reader, int) error { 139 | return m.err 140 | } 141 | 142 | func (t *LoaderTest) TestLoaderError(c *C) { 143 | tx, err := t.db.Begin() 144 | c.Assert(err, IsNil) 145 | defer tx.Rollback() 146 | 147 | loader := newErrLoader(errors.New("my error")) 148 | _, err = tx.QueryOne(loader, "SELECT 1, 2") 149 | c.Assert(err, Not(IsNil)) 150 | c.Assert(err.Error(), Equals, "my error") 151 | 152 | // Verify that client is still functional. 153 | var n1, n2 int 154 | _, err = tx.QueryOne(pg.Scan(&n1, &n2), "SELECT 1, 2") 155 | c.Assert(err, IsNil) 156 | c.Assert(n1, Equals, 1) 157 | c.Assert(n2, Equals, 2) 158 | } 159 | -------------------------------------------------------------------------------- /orm/bench_test.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | var tableSink *Table 9 | 10 | func BenchmarkTablesGet(b *testing.B) { 11 | tables := newTables() 12 | typ := reflect.TypeOf(Query{}) 13 | 14 | b.RunParallel(func(pb *testing.PB) { 15 | for pb.Next() { 16 | tableSink = tables.Get(typ) 17 | } 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /orm/composite.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/go-pg/pg/v10/internal/pool" 8 | "github.com/go-pg/pg/v10/types" 9 | ) 10 | 11 | func compositeScanner(typ reflect.Type) types.ScannerFunc { 12 | if typ.Kind() == reflect.Ptr { 13 | typ = typ.Elem() 14 | } 15 | 16 | var table *Table 17 | return func(v reflect.Value, rd types.Reader, n int) error { 18 | if n == -1 { 19 | v.Set(reflect.Zero(v.Type())) 20 | return nil 21 | } 22 | 23 | if table == nil { 24 | table = GetTable(typ) 25 | } 26 | if v.Kind() == reflect.Ptr { 27 | if v.IsNil() { 28 | v.Set(reflect.New(v.Type().Elem())) 29 | } 30 | v = v.Elem() 31 | } 32 | 33 | p := newCompositeParser(rd) 34 | var elemReader *pool.BytesReader 35 | 36 | var firstErr error 37 | for i := 0; ; i++ { 38 | elem, err := p.NextElem() 39 | if err != nil { 40 | if err == errEndOfComposite { 41 | break 42 | } 43 | return err 44 | } 45 | 46 | if i >= len(table.Fields) { 47 | if firstErr == nil { 48 | firstErr = fmt.Errorf( 49 | "pg: %s has %d fields, but composite requires at least %d values", 50 | table, len(table.Fields), i) 51 | } 52 | continue 53 | } 54 | 55 | if elemReader == nil { 56 | elemReader = pool.NewBytesReader(elem) 57 | } else { 58 | elemReader.Reset(elem) 59 | } 60 | 61 | field := table.Fields[i] 62 | if elem == nil { 63 | err = field.ScanValue(v, elemReader, -1) 64 | } else { 65 | err = field.ScanValue(v, elemReader, len(elem)) 66 | } 67 | if err != nil && firstErr == nil { 68 | firstErr = err 69 | } 70 | } 71 | 72 | return firstErr 73 | } 74 | } 75 | 76 | func compositeAppender(typ reflect.Type) types.AppenderFunc { 77 | if typ.Kind() == reflect.Ptr { 78 | typ = typ.Elem() 79 | } 80 | 81 | var table *Table 82 | return func(b []byte, v reflect.Value, quote int) []byte { 83 | if table == nil { 84 | table = GetTable(typ) 85 | } 86 | if v.Kind() == reflect.Ptr { 87 | v = v.Elem() 88 | } 89 | 90 | b = append(b, "ROW("...) 91 | for i, f := range table.Fields { 92 | if i > 0 { 93 | b = append(b, ',') 94 | } 95 | b = f.AppendValue(b, v, quote) 96 | } 97 | b = append(b, ')') 98 | return b 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /orm/composite_create.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | type CreateCompositeOptions struct { 8 | Varchar int // replaces PostgreSQL data type `text` with `varchar(n)` 9 | } 10 | 11 | type CreateCompositeQuery struct { 12 | q *Query 13 | opt *CreateCompositeOptions 14 | } 15 | 16 | var ( 17 | _ QueryAppender = (*CreateCompositeQuery)(nil) 18 | _ QueryCommand = (*CreateCompositeQuery)(nil) 19 | ) 20 | 21 | func NewCreateCompositeQuery(q *Query, opt *CreateCompositeOptions) *CreateCompositeQuery { 22 | return &CreateCompositeQuery{ 23 | q: q, 24 | opt: opt, 25 | } 26 | } 27 | 28 | func (q *CreateCompositeQuery) String() string { 29 | b, err := q.AppendQuery(defaultFmter, nil) 30 | if err != nil { 31 | panic(err) 32 | } 33 | return string(b) 34 | } 35 | 36 | func (q *CreateCompositeQuery) Operation() QueryOp { 37 | return CreateCompositeOp 38 | } 39 | 40 | func (q *CreateCompositeQuery) Clone() QueryCommand { 41 | return &CreateCompositeQuery{ 42 | q: q.q.Clone(), 43 | opt: q.opt, 44 | } 45 | } 46 | 47 | func (q *CreateCompositeQuery) Query() *Query { 48 | return q.q 49 | } 50 | 51 | func (q *CreateCompositeQuery) AppendTemplate(b []byte) ([]byte, error) { 52 | return q.AppendQuery(dummyFormatter{}, b) 53 | } 54 | 55 | func (q *CreateCompositeQuery) AppendQuery(fmter QueryFormatter, b []byte) ([]byte, error) { 56 | b = appendComment(b, q.q.comment) 57 | if q.q.stickyErr != nil { 58 | return nil, q.q.stickyErr 59 | } 60 | if q.q.tableModel == nil { 61 | return nil, errModelNil 62 | } 63 | 64 | table := q.q.tableModel.Table() 65 | 66 | b = append(b, "CREATE TYPE "...) 67 | b = append(b, table.Alias...) 68 | b = append(b, " AS ("...) 69 | 70 | for i, field := range table.Fields { 71 | if i > 0 { 72 | b = append(b, ", "...) 73 | } 74 | 75 | b = append(b, field.Column...) 76 | b = append(b, " "...) 77 | if field.UserSQLType == "" && q.opt != nil && q.opt.Varchar > 0 && 78 | field.SQLType == "text" { 79 | b = append(b, "varchar("...) 80 | b = strconv.AppendInt(b, int64(q.opt.Varchar), 10) 81 | b = append(b, ")"...) 82 | } else { 83 | b = append(b, field.SQLType...) 84 | } 85 | } 86 | 87 | b = append(b, ")"...) 88 | 89 | return b, q.q.stickyErr 90 | } 91 | -------------------------------------------------------------------------------- /orm/composite_drop.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | type DropCompositeOptions struct { 4 | IfExists bool 5 | Cascade bool 6 | } 7 | 8 | type DropCompositeQuery struct { 9 | q *Query 10 | opt *DropCompositeOptions 11 | } 12 | 13 | var ( 14 | _ QueryAppender = (*DropCompositeQuery)(nil) 15 | _ QueryCommand = (*DropCompositeQuery)(nil) 16 | ) 17 | 18 | func NewDropCompositeQuery(q *Query, opt *DropCompositeOptions) *DropCompositeQuery { 19 | return &DropCompositeQuery{ 20 | q: q, 21 | opt: opt, 22 | } 23 | } 24 | 25 | func (q *DropCompositeQuery) String() string { 26 | b, err := q.AppendQuery(defaultFmter, nil) 27 | if err != nil { 28 | panic(err) 29 | } 30 | return string(b) 31 | } 32 | 33 | func (q *DropCompositeQuery) Operation() QueryOp { 34 | return DropCompositeOp 35 | } 36 | 37 | func (q *DropCompositeQuery) Clone() QueryCommand { 38 | return &DropCompositeQuery{ 39 | q: q.q.Clone(), 40 | opt: q.opt, 41 | } 42 | } 43 | 44 | func (q *DropCompositeQuery) Query() *Query { 45 | return q.q 46 | } 47 | 48 | func (q *DropCompositeQuery) AppendTemplate(b []byte) ([]byte, error) { 49 | return q.AppendQuery(dummyFormatter{}, b) 50 | } 51 | 52 | func (q *DropCompositeQuery) AppendQuery(fmter QueryFormatter, b []byte) ([]byte, error) { 53 | b = appendComment(b, q.q.comment) 54 | if q.q.stickyErr != nil { 55 | return nil, q.q.stickyErr 56 | } 57 | if q.q.tableModel == nil { 58 | return nil, errModelNil 59 | } 60 | 61 | b = append(b, "DROP TYPE "...) 62 | if q.opt != nil && q.opt.IfExists { 63 | b = append(b, "IF EXISTS "...) 64 | } 65 | b = append(b, q.q.tableModel.Table().Alias...) 66 | if q.opt != nil && q.opt.Cascade { 67 | b = append(b, " CASCADE"...) 68 | } 69 | 70 | return b, q.q.stickyErr 71 | } 72 | -------------------------------------------------------------------------------- /orm/composite_parser.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "io" 8 | 9 | "github.com/go-pg/pg/v10/internal/parser" 10 | "github.com/go-pg/pg/v10/types" 11 | ) 12 | 13 | var errEndOfComposite = errors.New("pg: end of composite") 14 | 15 | type compositeParser struct { 16 | p parser.StreamingParser 17 | 18 | stickyErr error 19 | } 20 | 21 | func newCompositeParserErr(err error) *compositeParser { 22 | return &compositeParser{ 23 | stickyErr: err, 24 | } 25 | } 26 | 27 | func newCompositeParser(rd types.Reader) *compositeParser { 28 | p := parser.NewStreamingParser(rd) 29 | err := p.SkipByte('(') 30 | if err != nil { 31 | return newCompositeParserErr(err) 32 | } 33 | return &compositeParser{ 34 | p: p, 35 | } 36 | } 37 | 38 | func (p *compositeParser) NextElem() ([]byte, error) { 39 | if p.stickyErr != nil { 40 | return nil, p.stickyErr 41 | } 42 | 43 | c, err := p.p.ReadByte() 44 | if err != nil { 45 | if err == io.EOF { 46 | return nil, errEndOfComposite 47 | } 48 | return nil, err 49 | } 50 | 51 | switch c { 52 | case '"': 53 | return p.readQuoted() 54 | case ',': 55 | return nil, nil 56 | case ')': 57 | return nil, errEndOfComposite 58 | default: 59 | _ = p.p.UnreadByte() 60 | } 61 | 62 | var b []byte 63 | for { 64 | tmp, err := p.p.ReadSlice(',') 65 | if err == nil { 66 | if b == nil { 67 | b = tmp 68 | } else { 69 | b = append(b, tmp...) 70 | } 71 | b = b[:len(b)-1] 72 | break 73 | } 74 | b = append(b, tmp...) 75 | if err == bufio.ErrBufferFull { 76 | continue 77 | } 78 | if err == io.EOF { 79 | if b[len(b)-1] == ')' { 80 | b = b[:len(b)-1] 81 | break 82 | } 83 | } 84 | return nil, err 85 | } 86 | 87 | if len(b) == 0 { // NULL 88 | return nil, nil 89 | } 90 | return b, nil 91 | } 92 | 93 | func (p *compositeParser) readQuoted() ([]byte, error) { 94 | var b []byte 95 | 96 | c, err := p.p.ReadByte() 97 | if err != nil { 98 | return nil, err 99 | } 100 | 101 | for { 102 | next, err := p.p.ReadByte() 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | if c == '\\' || c == '\'' { 108 | if next == c { 109 | b = append(b, c) 110 | c, err = p.p.ReadByte() 111 | if err != nil { 112 | return nil, err 113 | } 114 | } else { 115 | b = append(b, c) 116 | c = next 117 | } 118 | continue 119 | } 120 | 121 | if c == '"' { 122 | switch next { 123 | case '"': 124 | b = append(b, '"') 125 | c, err = p.p.ReadByte() 126 | if err != nil { 127 | return nil, err 128 | } 129 | case ',', ')': 130 | return b, nil 131 | default: 132 | return nil, fmt.Errorf("pg: got %q, wanted ',' or ')'", c) 133 | } 134 | continue 135 | } 136 | 137 | b = append(b, c) 138 | c = next 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /orm/count_estimate.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-pg/pg/v10/internal" 7 | ) 8 | 9 | // Placeholder that is replaced with count(*). 10 | const placeholder = `'_go_pg_placeholder'` 11 | 12 | // https://wiki.postgresql.org/wiki/Count_estimate 13 | //nolint 14 | var pgCountEstimateFunc = fmt.Sprintf(` 15 | CREATE OR REPLACE FUNCTION _go_pg_count_estimate_v2(query text, threshold int) 16 | RETURNS int AS $$ 17 | DECLARE 18 | rec record; 19 | nrows int; 20 | BEGIN 21 | FOR rec IN EXECUTE 'EXPLAIN ' || query LOOP 22 | nrows := substring(rec."QUERY PLAN" FROM ' rows=(\d+)'); 23 | EXIT WHEN nrows IS NOT NULL; 24 | END LOOP; 25 | 26 | -- Return the estimation if there are too many rows. 27 | IF nrows > threshold THEN 28 | RETURN nrows; 29 | END IF; 30 | 31 | -- Otherwise execute real count query. 32 | query := replace(query, 'SELECT '%s'', 'SELECT count(*)'); 33 | EXECUTE query INTO nrows; 34 | 35 | IF nrows IS NULL THEN 36 | nrows := 0; 37 | END IF; 38 | 39 | RETURN nrows; 40 | END; 41 | $$ LANGUAGE plpgsql; 42 | `, placeholder) 43 | 44 | // CountEstimate uses EXPLAIN to get estimated number of rows returned the query. 45 | // If that number is bigger than the threshold it returns the estimation. 46 | // Otherwise it executes another query using count aggregate function and 47 | // returns the result. 48 | // 49 | // Based on https://wiki.postgresql.org/wiki/Count_estimate 50 | func (q *Query) CountEstimate(threshold int) (int, error) { 51 | if q.stickyErr != nil { 52 | return 0, q.stickyErr 53 | } 54 | 55 | query, err := q.countSelectQuery(placeholder).AppendQuery(q.db.Formatter(), nil) 56 | if err != nil { 57 | return 0, err 58 | } 59 | 60 | for i := 0; i < 3; i++ { 61 | var count int 62 | _, err = q.db.QueryOneContext( 63 | q.ctx, 64 | Scan(&count), 65 | "SELECT _go_pg_count_estimate_v2(?, ?)", 66 | string(query), threshold, 67 | ) 68 | if err != nil { 69 | if pgerr, ok := err.(internal.PGError); ok && pgerr.Field('C') == "42883" { 70 | // undefined_function 71 | err = q.createCountEstimateFunc() 72 | if err != nil { 73 | pgerr, ok := err.(internal.PGError) 74 | if !ok || !pgerr.IntegrityViolation() { 75 | return 0, err 76 | } 77 | } 78 | continue 79 | } 80 | } 81 | return count, err 82 | } 83 | 84 | return 0, err 85 | } 86 | 87 | func (q *Query) createCountEstimateFunc() error { 88 | _, err := q.db.ExecContext(q.ctx, pgCountEstimateFunc) 89 | return err 90 | } 91 | -------------------------------------------------------------------------------- /orm/delete.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/go-pg/pg/v10/types" 7 | ) 8 | 9 | type DeleteQuery struct { 10 | q *Query 11 | placeholder bool 12 | } 13 | 14 | var ( 15 | _ QueryAppender = (*DeleteQuery)(nil) 16 | _ QueryCommand = (*DeleteQuery)(nil) 17 | ) 18 | 19 | func NewDeleteQuery(q *Query) *DeleteQuery { 20 | return &DeleteQuery{ 21 | q: q, 22 | } 23 | } 24 | 25 | func (q *DeleteQuery) String() string { 26 | b, err := q.AppendQuery(defaultFmter, nil) 27 | if err != nil { 28 | panic(err) 29 | } 30 | return string(b) 31 | } 32 | 33 | func (q *DeleteQuery) Operation() QueryOp { 34 | return DeleteOp 35 | } 36 | 37 | func (q *DeleteQuery) Clone() QueryCommand { 38 | return &DeleteQuery{ 39 | q: q.q.Clone(), 40 | placeholder: q.placeholder, 41 | } 42 | } 43 | 44 | func (q *DeleteQuery) Query() *Query { 45 | return q.q 46 | } 47 | 48 | func (q *DeleteQuery) AppendTemplate(b []byte) ([]byte, error) { 49 | cp := q.Clone().(*DeleteQuery) 50 | cp.placeholder = true 51 | return cp.AppendQuery(dummyFormatter{}, b) 52 | } 53 | 54 | func (q *DeleteQuery) AppendQuery(fmter QueryFormatter, b []byte) (_ []byte, err error) { 55 | b = appendComment(b, q.q.comment) 56 | if q.q.stickyErr != nil { 57 | return nil, q.q.stickyErr 58 | } 59 | 60 | if len(q.q.with) > 0 { 61 | b, err = q.q.appendWith(fmter, b) 62 | if err != nil { 63 | return nil, err 64 | } 65 | } 66 | 67 | b = append(b, "DELETE FROM "...) 68 | b, err = q.q.appendFirstTableWithAlias(fmter, b) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | if q.q.hasMultiTables() { 74 | b = append(b, " USING "...) 75 | b, err = q.q.appendOtherTables(fmter, b) 76 | if err != nil { 77 | return nil, err 78 | } 79 | } 80 | 81 | b = append(b, " WHERE "...) 82 | value := q.q.tableModel.Value() 83 | 84 | if q.q.isSliceModelWithData() { 85 | if len(q.q.where) > 0 { 86 | b, err = q.q.appendWhere(fmter, b) 87 | if err != nil { 88 | return nil, err 89 | } 90 | } else { 91 | table := q.q.tableModel.Table() 92 | err = table.checkPKs() 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | b = appendColumnAndSliceValue(fmter, b, value, table.Alias, table.PKs) 98 | } 99 | } else { 100 | b, err = q.q.mustAppendWhere(fmter, b) 101 | if err != nil { 102 | return nil, err 103 | } 104 | } 105 | 106 | if len(q.q.returning) > 0 { 107 | b, err = q.q.appendReturning(fmter, b) 108 | if err != nil { 109 | return nil, err 110 | } 111 | } 112 | 113 | return b, q.q.stickyErr 114 | } 115 | 116 | func appendColumnAndSliceValue( 117 | fmter QueryFormatter, b []byte, slice reflect.Value, alias types.Safe, fields []*Field, 118 | ) []byte { 119 | if len(fields) > 1 { 120 | b = append(b, '(') 121 | } 122 | b = appendColumns(b, alias, fields) 123 | if len(fields) > 1 { 124 | b = append(b, ')') 125 | } 126 | 127 | b = append(b, " IN ("...) 128 | 129 | isPlaceholder := isTemplateFormatter(fmter) 130 | sliceLen := slice.Len() 131 | for i := 0; i < sliceLen; i++ { 132 | if i > 0 { 133 | b = append(b, ", "...) 134 | } 135 | 136 | el := indirect(slice.Index(i)) 137 | 138 | if len(fields) > 1 { 139 | b = append(b, '(') 140 | } 141 | for i, f := range fields { 142 | if i > 0 { 143 | b = append(b, ", "...) 144 | } 145 | if isPlaceholder { 146 | b = append(b, '?') 147 | } else { 148 | b = f.AppendValue(b, el, 1) 149 | } 150 | } 151 | if len(fields) > 1 { 152 | b = append(b, ')') 153 | } 154 | } 155 | 156 | b = append(b, ')') 157 | 158 | return b 159 | } 160 | -------------------------------------------------------------------------------- /orm/delete_test.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | type DeleteTest struct{} 9 | 10 | var _ = Describe("Delete", func() { 11 | It("supports WITH", func() { 12 | q := NewQuery(nil, &DeleteTest{}). 13 | WrapWith("wrapper"). 14 | Model(&DeleteTest{}). 15 | Table("wrapper"). 16 | Where("delete_test.id = wrapper.id") 17 | 18 | s := deleteQueryString(q) 19 | Expect(s).To(Equal(`WITH "wrapper" AS (SELECT FROM "delete_tests" AS "delete_test") DELETE FROM "delete_tests" AS "delete_test" USING "wrapper" WHERE (delete_test.id = wrapper.id)`)) 20 | }) 21 | }) 22 | 23 | func deleteQueryString(q *Query) string { 24 | del := NewDeleteQuery(q) 25 | return queryString(del) 26 | } 27 | -------------------------------------------------------------------------------- /orm/field.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/go-pg/zerochecker" 8 | 9 | "github.com/go-pg/pg/v10/types" 10 | ) 11 | 12 | const ( 13 | PrimaryKeyFlag = uint8(1) << iota 14 | ForeignKeyFlag 15 | NotNullFlag 16 | UseZeroFlag 17 | UniqueFlag 18 | ArrayFlag 19 | ) 20 | 21 | type Field struct { 22 | Field reflect.StructField 23 | Type reflect.Type 24 | Index []int 25 | 26 | GoName string // struct field name, e.g. Id 27 | SQLName string // SQL name, .e.g. id 28 | Column types.Safe // escaped SQL name, e.g. "id" 29 | SQLType string 30 | UserSQLType string 31 | Default types.Safe 32 | OnDelete string 33 | OnUpdate string 34 | 35 | flags uint8 36 | 37 | append types.AppenderFunc 38 | scan types.ScannerFunc 39 | 40 | isZero zerochecker.Func 41 | } 42 | 43 | func indexEqual(ind1, ind2 []int) bool { 44 | if len(ind1) != len(ind2) { 45 | return false 46 | } 47 | for i, ind := range ind1 { 48 | if ind != ind2[i] { 49 | return false 50 | } 51 | } 52 | return true 53 | } 54 | 55 | func (f *Field) Clone() *Field { 56 | cp := *f 57 | cp.Index = cp.Index[:len(f.Index):len(f.Index)] 58 | return &cp 59 | } 60 | 61 | func (f *Field) setFlag(flag uint8) { 62 | f.flags |= flag 63 | } 64 | 65 | func (f *Field) hasFlag(flag uint8) bool { 66 | return f.flags&flag != 0 67 | } 68 | 69 | func (f *Field) Value(strct reflect.Value) reflect.Value { 70 | return fieldByIndexAlloc(strct, f.Index) 71 | } 72 | 73 | func (f *Field) HasZeroValue(strct reflect.Value) bool { 74 | return f.hasZeroValue(strct, f.Index) 75 | } 76 | 77 | func (f *Field) hasZeroValue(v reflect.Value, index []int) bool { 78 | for _, idx := range index { 79 | if v.Kind() == reflect.Ptr { 80 | if v.IsNil() { 81 | return true 82 | } 83 | v = v.Elem() 84 | } 85 | v = v.Field(idx) 86 | } 87 | return f.isZero(v) 88 | } 89 | 90 | func (f *Field) NullZero() bool { 91 | return !f.hasFlag(UseZeroFlag) 92 | } 93 | 94 | func (f *Field) AppendValue(b []byte, strct reflect.Value, quote int) []byte { 95 | fv, ok := fieldByIndex(strct, f.Index) 96 | if !ok { 97 | return types.AppendNull(b, quote) 98 | } 99 | 100 | if f.NullZero() && f.isZero(fv) { 101 | return types.AppendNull(b, quote) 102 | } 103 | if f.append == nil { 104 | panic(fmt.Errorf("pg: AppendValue(unsupported %s)", fv.Type())) 105 | } 106 | return f.append(b, fv, quote) 107 | } 108 | 109 | func (f *Field) ScanValue(strct reflect.Value, rd types.Reader, n int) error { 110 | if f.scan == nil { 111 | return fmt.Errorf("pg: ScanValue(unsupported %s)", f.Type) 112 | } 113 | 114 | var fv reflect.Value 115 | if n == -1 { 116 | var ok bool 117 | fv, ok = fieldByIndex(strct, f.Index) 118 | if !ok { 119 | return nil 120 | } 121 | } else { 122 | fv = fieldByIndexAlloc(strct, f.Index) 123 | } 124 | 125 | return f.scan(fv, rd, n) 126 | } 127 | 128 | type Method struct { 129 | Index int 130 | 131 | flags int8 132 | 133 | appender func([]byte, reflect.Value, int) []byte 134 | } 135 | 136 | func (m *Method) Has(flag int8) bool { 137 | return m.flags&flag != 0 138 | } 139 | 140 | func (m *Method) Value(strct reflect.Value) reflect.Value { 141 | return strct.Method(m.Index).Call(nil)[0] 142 | } 143 | 144 | func (m *Method) AppendValue(dst []byte, strct reflect.Value, quote int) []byte { 145 | mv := m.Value(strct) 146 | return m.appender(dst, mv, quote) 147 | } 148 | -------------------------------------------------------------------------------- /orm/internal_test.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | "testing" 7 | ) 8 | 9 | type TableInitRace struct { 10 | Id int 11 | Foo int 12 | 13 | HasSelf *TableInitRace 14 | HasSelfId int 15 | 16 | HasOne1 *TableInitRace1 17 | HasOne1Id int 18 | 19 | HasAnother1 *TableInitRace1 20 | HasAnother1Id int 21 | 22 | Bar int 23 | } 24 | 25 | type TableInitRace1 struct { 26 | Id int 27 | Foo int 28 | 29 | HasOne2 *TableInitRace2 30 | HasOne2Id int 31 | 32 | Bar int 33 | } 34 | 35 | type TableInitRace2 struct { 36 | Id int 37 | Foo int 38 | 39 | HasOne3 *TableInitRace3 40 | HasOne3Id int 41 | 42 | Bar int 43 | } 44 | 45 | type TableInitRace3 struct { 46 | Id int 47 | Foo int 48 | Bar int 49 | } 50 | 51 | func TestTableInitRace(t *testing.T) { 52 | const C = 16 53 | 54 | types := []reflect.Type{ 55 | reflect.TypeOf((*TableInitRace)(nil)).Elem(), 56 | reflect.TypeOf((*TableInitRace1)(nil)).Elem(), 57 | reflect.TypeOf((*TableInitRace2)(nil)).Elem(), 58 | reflect.TypeOf((*TableInitRace3)(nil)).Elem(), 59 | } 60 | 61 | var wg sync.WaitGroup 62 | for _, typ := range types { 63 | wg.Add(C) 64 | for i := 0; i < C; i++ { 65 | go func(typ reflect.Type, i int) { 66 | if i%2 == 0 { 67 | _ = _tables.get(typ, true) 68 | } else { 69 | _ = _tables.get(typ, false) 70 | } 71 | wg.Done() 72 | }(typ, i) 73 | } 74 | } 75 | wg.Wait() 76 | } 77 | 78 | type TableInlineRace struct { 79 | Id int 80 | Foo int 81 | 82 | R1 *TableInlineRace1 83 | R1Id int 84 | R10 *TableInlineRace1 85 | 86 | R2 *TableInlineRace2 87 | R2Id int 88 | R20 *TableInlineRace2 89 | 90 | R3 *TableInitRace3 91 | R3Id int 92 | R30 *TableInitRace3 93 | } 94 | 95 | type TableInlineRace1 struct { 96 | Id int 97 | Foo1 int 98 | 99 | R2 *TableInlineRace2 100 | R2Id int 101 | R20 *TableInlineRace2 102 | 103 | R3 *TableInitRace3 104 | R3Id int 105 | R30 *TableInitRace3 106 | } 107 | 108 | type TableInlineRace2 struct { 109 | Id int 110 | Foo2 int 111 | 112 | R3 *TableInlineRace3 113 | R3Id int 114 | R30 *TableInlineRace3 115 | } 116 | 117 | type TableInlineRace3 struct { 118 | Id int 119 | Foo2 int 120 | } 121 | 122 | func TestTableInlineRace(t *testing.T) { 123 | const C = 32 124 | 125 | types := []reflect.Type{ 126 | reflect.TypeOf((*TableInlineRace)(nil)).Elem(), 127 | reflect.TypeOf((*TableInlineRace1)(nil)).Elem(), 128 | reflect.TypeOf((*TableInlineRace2)(nil)).Elem(), 129 | reflect.TypeOf((*TableInlineRace3)(nil)).Elem(), 130 | } 131 | 132 | var wg sync.WaitGroup 133 | for _, typ := range types { 134 | wg.Add(C) 135 | for i := 0; i < C; i++ { 136 | go func(typ reflect.Type, i int) { 137 | _ = _tables.get(typ, false) 138 | wg.Done() 139 | }(typ, i) 140 | } 141 | } 142 | wg.Wait() 143 | } 144 | -------------------------------------------------------------------------------- /orm/join_test.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | type JoinTest struct { 9 | tableName struct{} `pg:"JoinTest,alias:JoinTest"` 10 | 11 | Id int 12 | 13 | HasOne *HasOne 14 | HasOneId int 15 | 16 | BelongsTo *BelongsTo 17 | } 18 | 19 | type HasOne struct { 20 | tableName struct{} `pg:"HasOne,alias:HasOne"` 21 | 22 | Id int 23 | 24 | HasOne *HasOne 25 | HasOneId int 26 | } 27 | 28 | type BelongsTo struct { 29 | tableName struct{} `pg:"BelongsTo,alias:BelongsTo"` 30 | 31 | Id int 32 | JoinTestId int 33 | } 34 | 35 | type HasOneNonPK struct { 36 | tableName struct{} `pg:"HasOneNonPK,alias:HasOneNonPK"` 37 | 38 | Id int 39 | 40 | CustomKey string `pg:"custom_key"` 41 | } 42 | 43 | type NonPKJoinTest struct { 44 | tableName struct{} `pg:"NonPKJoinTest,alias:NonPKJoinTest"` 45 | 46 | Id int 47 | 48 | CustomHasOneKey string `pg:"custom_has_one_key"` 49 | CustomHasOne *HasOneNonPK `pg:"rel:has-one,fk:custom_has_one_key,join_fk:custom_key"` 50 | } 51 | 52 | var _ = Describe("Join", func() { 53 | It("supports has one", func() { 54 | q := NewQuery(nil, &JoinTest{}).Relation("HasOne.HasOne", nil) 55 | 56 | s := selectQueryString(q) 57 | Expect(s).To(Equal(`SELECT "JoinTest"."id", "JoinTest"."has_one_id", "has_one"."id" AS "has_one__id", "has_one"."has_one_id" AS "has_one__has_one_id", "has_one__has_one"."id" AS "has_one__has_one__id", "has_one__has_one"."has_one_id" AS "has_one__has_one__has_one_id" FROM "JoinTest" AS "JoinTest" LEFT JOIN "HasOne" AS "has_one" ON "has_one"."id" = "JoinTest"."has_one_id" LEFT JOIN "HasOne" AS "has_one__has_one" ON "has_one__has_one"."id" = "has_one"."has_one_id"`)) 58 | }) 59 | 60 | It("supports join_fk for has one", func() { 61 | q := NewQuery(nil, &NonPKJoinTest{}).Relation("CustomHasOne", nil) 62 | 63 | s := selectQueryString(q) 64 | Expect(s).To(Equal(`SELECT "NonPKJoinTest"."id", "NonPKJoinTest"."custom_has_one_key", "custom_has_one"."id" AS "custom_has_one__id", "custom_has_one"."custom_key" AS "custom_has_one__custom_key" FROM "NonPKJoinTest" AS "NonPKJoinTest" LEFT JOIN "HasOneNonPK" AS "custom_has_one" ON "custom_has_one"."custom_key" = "NonPKJoinTest"."custom_has_one_key"`)) 65 | }) 66 | 67 | It("supports belongs to", func() { 68 | q := NewQuery(nil, &JoinTest{}).Relation("BelongsTo", nil) 69 | 70 | s := selectQueryString(q) 71 | Expect(s).To(Equal(`SELECT "JoinTest"."id", "JoinTest"."has_one_id", "belongs_to"."id" AS "belongs_to__id", "belongs_to"."join_test_id" AS "belongs_to__join_test_id" FROM "JoinTest" AS "JoinTest" LEFT JOIN "BelongsTo" AS "belongs_to" ON "belongs_to"."join_test_id" = "JoinTest"."id"`)) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /orm/main_test.go: -------------------------------------------------------------------------------- 1 | package orm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestGinkgo(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "orm") 13 | } 14 | -------------------------------------------------------------------------------- /orm/model.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | 9 | "github.com/go-pg/pg/v10/types" 10 | ) 11 | 12 | var errModelNil = errors.New("pg: Model(nil)") 13 | 14 | type useQueryOne interface { 15 | useQueryOne() bool 16 | } 17 | 18 | type HooklessModel interface { 19 | // Init is responsible to initialize/reset model state. 20 | // It is called only once no matter how many rows were returned. 21 | Init() error 22 | 23 | // NextColumnScanner returns a ColumnScanner that is used to scan columns 24 | // from the current row. It is called once for every row. 25 | NextColumnScanner() ColumnScanner 26 | 27 | // AddColumnScanner adds the ColumnScanner to the model. 28 | AddColumnScanner(ColumnScanner) error 29 | } 30 | 31 | type Model interface { 32 | HooklessModel 33 | 34 | AfterScanHook 35 | AfterSelectHook 36 | 37 | BeforeInsertHook 38 | AfterInsertHook 39 | 40 | BeforeUpdateHook 41 | AfterUpdateHook 42 | 43 | BeforeDeleteHook 44 | AfterDeleteHook 45 | } 46 | 47 | func NewModel(value interface{}) (Model, error) { 48 | return newModel(value, false) 49 | } 50 | 51 | func newScanModel(values []interface{}) (Model, error) { 52 | if len(values) > 1 { 53 | return Scan(values...), nil 54 | } 55 | return newModel(values[0], true) 56 | } 57 | 58 | func newModel(value interface{}, scan bool) (Model, error) { 59 | switch value := value.(type) { 60 | case Model: 61 | return value, nil 62 | case HooklessModel: 63 | return newModelWithHookStubs(value), nil 64 | case types.ValueScanner, sql.Scanner: 65 | if !scan { 66 | return nil, fmt.Errorf("pg: Model(unsupported %T)", value) 67 | } 68 | return Scan(value), nil 69 | } 70 | 71 | v := reflect.ValueOf(value) 72 | if !v.IsValid() { 73 | return nil, errModelNil 74 | } 75 | if v.Kind() != reflect.Ptr { 76 | return nil, fmt.Errorf("pg: Model(non-pointer %T)", value) 77 | } 78 | 79 | if v.IsNil() { 80 | typ := v.Type().Elem() 81 | if typ.Kind() == reflect.Struct { 82 | return newStructTableModel(GetTable(typ)), nil 83 | } 84 | return nil, errModelNil 85 | } 86 | 87 | v = v.Elem() 88 | 89 | if v.Kind() == reflect.Interface { 90 | if !v.IsNil() { 91 | v = v.Elem() 92 | if v.Kind() != reflect.Ptr { 93 | return nil, fmt.Errorf("pg: Model(non-pointer %s)", v.Type().String()) 94 | } 95 | } 96 | } 97 | 98 | switch v.Kind() { 99 | case reflect.Struct: 100 | if v.Type() != timeType { 101 | return newStructTableModelValue(v), nil 102 | } 103 | case reflect.Slice: 104 | elemType := sliceElemType(v) 105 | switch elemType.Kind() { 106 | case reflect.Struct: 107 | if elemType != timeType { 108 | return newSliceTableModel(v, elemType), nil 109 | } 110 | case reflect.Map: 111 | if err := validMap(elemType); err != nil { 112 | return nil, err 113 | } 114 | slicePtr := v.Addr().Interface().(*[]map[string]interface{}) 115 | return newMapSliceModel(slicePtr), nil 116 | } 117 | return newSliceModel(v, elemType), nil 118 | case reflect.Map: 119 | typ := v.Type() 120 | if err := validMap(typ); err != nil { 121 | return nil, err 122 | } 123 | mapPtr := v.Addr().Interface().(*map[string]interface{}) 124 | return newMapModel(mapPtr), nil 125 | } 126 | 127 | if !scan { 128 | return nil, fmt.Errorf("pg: Model(unsupported %T)", value) 129 | } 130 | return Scan(value), nil 131 | } 132 | 133 | type modelWithHookStubs struct { 134 | hookStubs 135 | HooklessModel 136 | } 137 | 138 | func newModelWithHookStubs(m HooklessModel) Model { 139 | return modelWithHookStubs{ 140 | HooklessModel: m, 141 | } 142 | } 143 | 144 | func validMap(typ reflect.Type) error { 145 | if typ.Key().Kind() != reflect.String || typ.Elem().Kind() != reflect.Interface { 146 | return fmt.Errorf("pg: Model(unsupported %s, expected *map[string]interface{})", 147 | typ.String()) 148 | } 149 | return nil 150 | } 151 | -------------------------------------------------------------------------------- /orm/model_discard.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "github.com/go-pg/pg/v10/types" 5 | ) 6 | 7 | type Discard struct { 8 | hookStubs 9 | } 10 | 11 | var _ Model = (*Discard)(nil) 12 | 13 | func (Discard) Init() error { 14 | return nil 15 | } 16 | 17 | func (m Discard) NextColumnScanner() ColumnScanner { 18 | return m 19 | } 20 | 21 | func (m Discard) AddColumnScanner(ColumnScanner) error { 22 | return nil 23 | } 24 | 25 | func (m Discard) ScanColumn(col types.ColumnInfo, rd types.Reader, n int) error { 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /orm/model_func.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | var errorType = reflect.TypeOf((*error)(nil)).Elem() 9 | 10 | type funcModel struct { 11 | Model 12 | fnv reflect.Value 13 | fnIn []reflect.Value 14 | } 15 | 16 | var _ Model = (*funcModel)(nil) 17 | 18 | func newFuncModel(fn interface{}) *funcModel { 19 | m := &funcModel{ 20 | fnv: reflect.ValueOf(fn), 21 | } 22 | 23 | fnt := m.fnv.Type() 24 | if fnt.Kind() != reflect.Func { 25 | panic(fmt.Errorf("ForEach expects a %s, got a %s", 26 | reflect.Func, fnt.Kind())) 27 | } 28 | 29 | if fnt.NumIn() < 1 { 30 | panic(fmt.Errorf("ForEach expects at least 1 arg, got %d", fnt.NumIn())) 31 | } 32 | 33 | if fnt.NumOut() != 1 { 34 | panic(fmt.Errorf("ForEach must return 1 error value, got %d", fnt.NumOut())) 35 | } 36 | if fnt.Out(0) != errorType { 37 | panic(fmt.Errorf("ForEach must return an error, got %T", fnt.Out(0))) 38 | } 39 | 40 | if fnt.NumIn() > 1 { 41 | initFuncModelScan(m, fnt) 42 | return m 43 | } 44 | 45 | t0 := fnt.In(0) 46 | var v0 reflect.Value 47 | if t0.Kind() == reflect.Ptr { 48 | t0 = t0.Elem() 49 | v0 = reflect.New(t0) 50 | } else { 51 | v0 = reflect.New(t0).Elem() 52 | } 53 | 54 | m.fnIn = []reflect.Value{v0} 55 | 56 | model, ok := v0.Interface().(Model) 57 | if ok { 58 | m.Model = model 59 | return m 60 | } 61 | 62 | if v0.Kind() == reflect.Ptr { 63 | v0 = v0.Elem() 64 | } 65 | if v0.Kind() != reflect.Struct { 66 | panic(fmt.Errorf("ForEach accepts a %s, got %s", 67 | reflect.Struct, v0.Kind())) 68 | } 69 | m.Model = newStructTableModelValue(v0) 70 | 71 | return m 72 | } 73 | 74 | func initFuncModelScan(m *funcModel, fnt reflect.Type) { 75 | m.fnIn = make([]reflect.Value, fnt.NumIn()) 76 | for i := 0; i < fnt.NumIn(); i++ { 77 | m.fnIn[i] = reflect.New(fnt.In(i)).Elem() 78 | } 79 | m.Model = scanReflectValues(m.fnIn) 80 | } 81 | 82 | func (m *funcModel) AddColumnScanner(_ ColumnScanner) error { 83 | out := m.fnv.Call(m.fnIn) 84 | errv := out[0] 85 | if !errv.IsNil() { 86 | return errv.Interface().(error) 87 | } 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /orm/model_map.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "github.com/go-pg/pg/v10/types" 5 | ) 6 | 7 | type mapModel struct { 8 | hookStubs 9 | ptr *map[string]interface{} 10 | m map[string]interface{} 11 | } 12 | 13 | var _ Model = (*mapModel)(nil) 14 | 15 | func newMapModel(ptr *map[string]interface{}) *mapModel { 16 | model := &mapModel{ 17 | ptr: ptr, 18 | } 19 | if ptr != nil { 20 | model.m = *ptr 21 | } 22 | return model 23 | } 24 | 25 | func (m *mapModel) Init() error { 26 | return nil 27 | } 28 | 29 | func (m *mapModel) NextColumnScanner() ColumnScanner { 30 | if m.m == nil { 31 | m.m = make(map[string]interface{}) 32 | *m.ptr = m.m 33 | } 34 | return m 35 | } 36 | 37 | func (m mapModel) AddColumnScanner(ColumnScanner) error { 38 | return nil 39 | } 40 | 41 | func (m *mapModel) ScanColumn(col types.ColumnInfo, rd types.Reader, n int) error { 42 | val, err := types.ReadColumnValue(col, rd, n) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | m.m[col.Name] = val 48 | return nil 49 | } 50 | 51 | func (mapModel) useQueryOne() bool { 52 | return true 53 | } 54 | -------------------------------------------------------------------------------- /orm/model_map_slice.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | type mapSliceModel struct { 4 | mapModel 5 | slice *[]map[string]interface{} 6 | } 7 | 8 | var _ Model = (*mapSliceModel)(nil) 9 | 10 | func newMapSliceModel(ptr *[]map[string]interface{}) *mapSliceModel { 11 | return &mapSliceModel{ 12 | slice: ptr, 13 | } 14 | } 15 | 16 | func (m *mapSliceModel) Init() error { 17 | slice := *m.slice 18 | if len(slice) > 0 { 19 | *m.slice = slice[:0] 20 | } 21 | return nil 22 | } 23 | 24 | func (m *mapSliceModel) NextColumnScanner() ColumnScanner { 25 | slice := *m.slice 26 | if len(slice) == cap(slice) { 27 | m.mapModel.m = make(map[string]interface{}) 28 | *m.slice = append(slice, m.mapModel.m) //nolint:gocritic 29 | return m 30 | } 31 | 32 | slice = slice[:len(slice)+1] 33 | el := slice[len(slice)-1] 34 | if el != nil { 35 | m.mapModel.m = el 36 | } else { 37 | el = make(map[string]interface{}) 38 | slice[len(slice)-1] = el 39 | m.mapModel.m = el 40 | } 41 | *m.slice = slice 42 | return m 43 | } 44 | 45 | func (mapSliceModel) useQueryOne() {} //nolint:unused 46 | -------------------------------------------------------------------------------- /orm/model_scan.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/go-pg/pg/v10/types" 8 | ) 9 | 10 | type scanValuesModel struct { 11 | Discard 12 | values []interface{} 13 | } 14 | 15 | var _ Model = scanValuesModel{} 16 | 17 | //nolint 18 | func Scan(values ...interface{}) scanValuesModel { 19 | return scanValuesModel{ 20 | values: values, 21 | } 22 | } 23 | 24 | func (scanValuesModel) useQueryOne() bool { 25 | return true 26 | } 27 | 28 | func (m scanValuesModel) NextColumnScanner() ColumnScanner { 29 | return m 30 | } 31 | 32 | func (m scanValuesModel) ScanColumn(col types.ColumnInfo, rd types.Reader, n int) error { 33 | if int(col.Index) >= len(m.values) { 34 | return fmt.Errorf("pg: no Scan var for column index=%d name=%q", 35 | col.Index, col.Name) 36 | } 37 | return types.Scan(m.values[col.Index], rd, n) 38 | } 39 | 40 | //------------------------------------------------------------------------------ 41 | 42 | type scanReflectValuesModel struct { 43 | Discard 44 | values []reflect.Value 45 | } 46 | 47 | var _ Model = scanReflectValuesModel{} 48 | 49 | func scanReflectValues(values []reflect.Value) scanReflectValuesModel { 50 | return scanReflectValuesModel{ 51 | values: values, 52 | } 53 | } 54 | 55 | func (scanReflectValuesModel) useQueryOne() bool { 56 | return true 57 | } 58 | 59 | func (m scanReflectValuesModel) NextColumnScanner() ColumnScanner { 60 | return m 61 | } 62 | 63 | func (m scanReflectValuesModel) ScanColumn(col types.ColumnInfo, rd types.Reader, n int) error { 64 | if int(col.Index) >= len(m.values) { 65 | return fmt.Errorf("pg: no Scan var for column index=%d name=%q", 66 | col.Index, col.Name) 67 | } 68 | return types.ScanValue(m.values[col.Index], rd, n) 69 | } 70 | -------------------------------------------------------------------------------- /orm/model_slice.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/go-pg/pg/v10/internal" 7 | "github.com/go-pg/pg/v10/types" 8 | ) 9 | 10 | type sliceModel struct { 11 | Discard 12 | slice reflect.Value 13 | nextElem func() reflect.Value 14 | scan func(reflect.Value, types.Reader, int) error 15 | } 16 | 17 | var _ Model = (*sliceModel)(nil) 18 | 19 | func newSliceModel(slice reflect.Value, elemType reflect.Type) *sliceModel { 20 | return &sliceModel{ 21 | slice: slice, 22 | scan: types.Scanner(elemType), 23 | } 24 | } 25 | 26 | func (m *sliceModel) Init() error { 27 | if m.slice.IsValid() && m.slice.Len() > 0 { 28 | m.slice.Set(m.slice.Slice(0, 0)) 29 | } 30 | return nil 31 | } 32 | 33 | func (m *sliceModel) NextColumnScanner() ColumnScanner { 34 | return m 35 | } 36 | 37 | func (m *sliceModel) ScanColumn(col types.ColumnInfo, rd types.Reader, n int) error { 38 | if m.nextElem == nil { 39 | m.nextElem = internal.MakeSliceNextElemFunc(m.slice) 40 | } 41 | v := m.nextElem() 42 | return m.scan(v, rd, n) 43 | } 44 | -------------------------------------------------------------------------------- /orm/model_table.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/go-pg/pg/v10/types" 8 | ) 9 | 10 | type TableModel interface { 11 | Model 12 | 13 | IsNil() bool 14 | Table() *Table 15 | Relation() *Relation 16 | AppendParam(QueryFormatter, []byte, string) ([]byte, bool) 17 | 18 | Join(string, func(*Query) (*Query, error)) *join 19 | GetJoin(string) *join 20 | GetJoins() []join 21 | AddJoin(join) *join 22 | 23 | Root() reflect.Value 24 | Index() []int 25 | ParentIndex() []int 26 | Mount(reflect.Value) 27 | Kind() reflect.Kind 28 | Value() reflect.Value 29 | 30 | setSoftDeleteField() error 31 | scanColumn(types.ColumnInfo, types.Reader, int) (bool, error) 32 | } 33 | 34 | func newTableModelIndex(typ reflect.Type, root reflect.Value, index []int, rel *Relation) (TableModel, error) { 35 | typ = typeByIndex(typ, index) 36 | 37 | if typ.Kind() == reflect.Struct { 38 | return &structTableModel{ 39 | table: GetTable(typ), 40 | rel: rel, 41 | 42 | root: root, 43 | index: index, 44 | }, nil 45 | } 46 | 47 | if typ.Kind() == reflect.Slice { 48 | structType := indirectType(typ.Elem()) 49 | if structType.Kind() == reflect.Struct { 50 | m := sliceTableModel{ 51 | structTableModel: structTableModel{ 52 | table: GetTable(structType), 53 | rel: rel, 54 | 55 | root: root, 56 | index: index, 57 | }, 58 | } 59 | m.init(typ) 60 | return &m, nil 61 | } 62 | } 63 | 64 | return nil, fmt.Errorf("pg: NewModel(%s)", typ) 65 | } 66 | -------------------------------------------------------------------------------- /orm/model_table_m2m.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/go-pg/pg/v10/internal/pool" 8 | "github.com/go-pg/pg/v10/types" 9 | ) 10 | 11 | type m2mModel struct { 12 | *sliceTableModel 13 | baseTable *Table 14 | rel *Relation 15 | 16 | buf []byte 17 | dstValues map[string][]reflect.Value 18 | columns map[string]string 19 | } 20 | 21 | var _ TableModel = (*m2mModel)(nil) 22 | 23 | func newM2MModel(j *join) *m2mModel { 24 | baseTable := j.BaseModel.Table() 25 | joinModel := j.JoinModel.(*sliceTableModel) 26 | dstValues := dstValues(joinModel, baseTable.PKs) 27 | if len(dstValues) == 0 { 28 | return nil 29 | } 30 | m := &m2mModel{ 31 | sliceTableModel: joinModel, 32 | baseTable: baseTable, 33 | rel: j.Rel, 34 | 35 | dstValues: dstValues, 36 | columns: make(map[string]string), 37 | } 38 | if !m.sliceOfPtr { 39 | m.strct = reflect.New(m.table.Type).Elem() 40 | } 41 | return m 42 | } 43 | 44 | func (m *m2mModel) NextColumnScanner() ColumnScanner { 45 | if m.sliceOfPtr { 46 | m.strct = reflect.New(m.table.Type).Elem() 47 | } else { 48 | m.strct.Set(m.table.zeroStruct) 49 | } 50 | m.structInited = false 51 | return m 52 | } 53 | 54 | func (m *m2mModel) AddColumnScanner(_ ColumnScanner) error { 55 | buf, err := m.modelIDMap(m.buf[:0]) 56 | if err != nil { 57 | return err 58 | } 59 | m.buf = buf 60 | 61 | dstValues, ok := m.dstValues[string(buf)] 62 | if !ok { 63 | return fmt.Errorf( 64 | "pg: relation=%q does not have base %s with id=%q (check join conditions)", 65 | m.rel.Field.GoName, m.baseTable, buf) 66 | } 67 | 68 | for _, v := range dstValues { 69 | if m.sliceOfPtr { 70 | v.Set(reflect.Append(v, m.strct.Addr())) 71 | } else { 72 | v.Set(reflect.Append(v, m.strct)) 73 | } 74 | } 75 | 76 | return nil 77 | } 78 | 79 | func (m *m2mModel) modelIDMap(b []byte) ([]byte, error) { 80 | for i, col := range m.rel.M2MBaseFKs { 81 | if i > 0 { 82 | b = append(b, ',') 83 | } 84 | if s, ok := m.columns[col]; ok { 85 | b = append(b, s...) 86 | } else { 87 | return nil, fmt.Errorf("pg: %s does not have column=%q", 88 | m.sliceTableModel, col) 89 | } 90 | } 91 | return b, nil 92 | } 93 | 94 | func (m *m2mModel) ScanColumn(col types.ColumnInfo, rd types.Reader, n int) error { 95 | if n > 0 { 96 | b, err := rd.ReadFullTemp() 97 | if err != nil { 98 | return err 99 | } 100 | 101 | m.columns[col.Name] = string(b) 102 | rd = pool.NewBytesReader(b) 103 | } else { 104 | m.columns[col.Name] = "" 105 | } 106 | 107 | if ok, err := m.sliceTableModel.scanColumn(col, rd, n); ok { 108 | return err 109 | } 110 | return nil 111 | } 112 | -------------------------------------------------------------------------------- /orm/model_table_many.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | type manyModel struct { 9 | *sliceTableModel 10 | baseTable *Table 11 | rel *Relation 12 | 13 | buf []byte 14 | dstValues map[string][]reflect.Value 15 | } 16 | 17 | var _ TableModel = (*manyModel)(nil) 18 | 19 | func newManyModel(j *join) *manyModel { 20 | baseTable := j.BaseModel.Table() 21 | joinModel := j.JoinModel.(*sliceTableModel) 22 | dstValues := dstValues(joinModel, j.Rel.BaseFKs) 23 | if len(dstValues) == 0 { 24 | return nil 25 | } 26 | m := manyModel{ 27 | sliceTableModel: joinModel, 28 | baseTable: baseTable, 29 | rel: j.Rel, 30 | 31 | dstValues: dstValues, 32 | } 33 | if !m.sliceOfPtr { 34 | m.strct = reflect.New(m.table.Type).Elem() 35 | } 36 | return &m 37 | } 38 | 39 | func (m *manyModel) NextColumnScanner() ColumnScanner { 40 | if m.sliceOfPtr { 41 | m.strct = reflect.New(m.table.Type).Elem() 42 | } else { 43 | m.strct.Set(m.table.zeroStruct) 44 | } 45 | m.structInited = false 46 | return m 47 | } 48 | 49 | func (m *manyModel) AddColumnScanner(model ColumnScanner) error { 50 | m.buf = modelID(m.buf[:0], m.strct, m.rel.JoinFKs) 51 | dstValues, ok := m.dstValues[string(m.buf)] 52 | if !ok { 53 | return fmt.Errorf( 54 | "pg: relation=%q does not have base %s with id=%q (check join conditions)", 55 | m.rel.Field.GoName, m.baseTable, m.buf) 56 | } 57 | 58 | for i, v := range dstValues { 59 | if !m.sliceOfPtr { 60 | v.Set(reflect.Append(v, m.strct)) 61 | continue 62 | } 63 | 64 | if i == 0 { 65 | v.Set(reflect.Append(v, m.strct.Addr())) 66 | continue 67 | } 68 | 69 | clone := reflect.New(m.strct.Type()).Elem() 70 | clone.Set(m.strct) 71 | v.Set(reflect.Append(v, clone.Addr())) 72 | } 73 | 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /orm/model_table_slice.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | 7 | "github.com/go-pg/pg/v10/internal" 8 | ) 9 | 10 | type sliceTableModel struct { 11 | structTableModel 12 | 13 | slice reflect.Value 14 | sliceLen int 15 | sliceOfPtr bool 16 | nextElem func() reflect.Value 17 | } 18 | 19 | var _ TableModel = (*sliceTableModel)(nil) 20 | 21 | func newSliceTableModel(slice reflect.Value, elemType reflect.Type) *sliceTableModel { 22 | m := &sliceTableModel{ 23 | structTableModel: structTableModel{ 24 | table: GetTable(elemType), 25 | root: slice, 26 | }, 27 | slice: slice, 28 | sliceLen: slice.Len(), 29 | nextElem: internal.MakeSliceNextElemFunc(slice), 30 | } 31 | m.init(slice.Type()) 32 | return m 33 | } 34 | 35 | func (m *sliceTableModel) init(sliceType reflect.Type) { 36 | switch sliceType.Elem().Kind() { 37 | case reflect.Ptr, reflect.Interface: 38 | m.sliceOfPtr = true 39 | } 40 | } 41 | 42 | //nolint 43 | func (*sliceTableModel) useQueryOne() {} 44 | 45 | func (m *sliceTableModel) IsNil() bool { 46 | return false 47 | } 48 | 49 | func (m *sliceTableModel) AppendParam(fmter QueryFormatter, b []byte, name string) ([]byte, bool) { 50 | if field, ok := m.table.FieldsMap[name]; ok { 51 | b = append(b, "_data."...) 52 | b = append(b, field.Column...) 53 | return b, true 54 | } 55 | return m.structTableModel.AppendParam(fmter, b, name) 56 | } 57 | 58 | func (m *sliceTableModel) Join(name string, apply func(*Query) (*Query, error)) *join { 59 | return m.join(m.Value(), name, apply) 60 | } 61 | 62 | func (m *sliceTableModel) Bind(bind reflect.Value) { 63 | m.slice = bind.Field(m.index[len(m.index)-1]) 64 | } 65 | 66 | func (m *sliceTableModel) Kind() reflect.Kind { 67 | return reflect.Slice 68 | } 69 | 70 | func (m *sliceTableModel) Value() reflect.Value { 71 | return m.slice 72 | } 73 | 74 | func (m *sliceTableModel) Init() error { 75 | if m.slice.IsValid() && m.slice.Len() > 0 { 76 | m.slice.Set(m.slice.Slice(0, 0)) 77 | } 78 | return nil 79 | } 80 | 81 | func (m *sliceTableModel) NextColumnScanner() ColumnScanner { 82 | m.strct = m.nextElem() 83 | m.structInited = false 84 | return m 85 | } 86 | 87 | func (m *sliceTableModel) AddColumnScanner(_ ColumnScanner) error { 88 | return nil 89 | } 90 | 91 | // Inherit these hooks from structTableModel. 92 | var ( 93 | _ BeforeScanHook = (*sliceTableModel)(nil) 94 | _ AfterScanHook = (*sliceTableModel)(nil) 95 | ) 96 | 97 | func (m *sliceTableModel) AfterSelect(ctx context.Context) error { 98 | if m.table.hasFlag(afterSelectHookFlag) { 99 | return callAfterSelectHookSlice(ctx, m.slice, m.sliceOfPtr) 100 | } 101 | return nil 102 | } 103 | 104 | func (m *sliceTableModel) BeforeInsert(ctx context.Context) (context.Context, error) { 105 | if m.table.hasFlag(beforeInsertHookFlag) { 106 | return callBeforeInsertHookSlice(ctx, m.slice, m.sliceOfPtr) 107 | } 108 | return ctx, nil 109 | } 110 | 111 | func (m *sliceTableModel) AfterInsert(ctx context.Context) error { 112 | if m.table.hasFlag(afterInsertHookFlag) { 113 | return callAfterInsertHookSlice(ctx, m.slice, m.sliceOfPtr) 114 | } 115 | return nil 116 | } 117 | 118 | func (m *sliceTableModel) BeforeUpdate(ctx context.Context) (context.Context, error) { 119 | if m.table.hasFlag(beforeUpdateHookFlag) && !m.IsNil() { 120 | return callBeforeUpdateHookSlice(ctx, m.slice, m.sliceOfPtr) 121 | } 122 | return ctx, nil 123 | } 124 | 125 | func (m *sliceTableModel) AfterUpdate(ctx context.Context) error { 126 | if m.table.hasFlag(afterUpdateHookFlag) { 127 | return callAfterUpdateHookSlice(ctx, m.slice, m.sliceOfPtr) 128 | } 129 | return nil 130 | } 131 | 132 | func (m *sliceTableModel) BeforeDelete(ctx context.Context) (context.Context, error) { 133 | if m.table.hasFlag(beforeDeleteHookFlag) && !m.IsNil() { 134 | return callBeforeDeleteHookSlice(ctx, m.slice, m.sliceOfPtr) 135 | } 136 | return ctx, nil 137 | } 138 | 139 | func (m *sliceTableModel) AfterDelete(ctx context.Context) error { 140 | if m.table.hasFlag(afterDeleteHookFlag) && !m.IsNil() { 141 | return callAfterDeleteHookSlice(ctx, m.slice, m.sliceOfPtr) 142 | } 143 | return nil 144 | } 145 | 146 | func (m *sliceTableModel) setSoftDeleteField() error { 147 | sliceLen := m.slice.Len() 148 | for i := 0; i < sliceLen; i++ { 149 | strct := indirect(m.slice.Index(i)) 150 | fv := m.table.SoftDeleteField.Value(strct) 151 | if err := m.table.SetSoftDeleteField(fv); err != nil { 152 | return err 153 | } 154 | } 155 | return nil 156 | } 157 | -------------------------------------------------------------------------------- /orm/msgpack.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/vmihailenco/msgpack/v5" 7 | 8 | "github.com/go-pg/pg/v10/types" 9 | ) 10 | 11 | func msgpackAppender(_ reflect.Type) types.AppenderFunc { 12 | return func(b []byte, v reflect.Value, flags int) []byte { 13 | hexEnc := types.NewHexEncoder(b, flags) 14 | 15 | enc := msgpack.GetEncoder() 16 | defer msgpack.PutEncoder(enc) 17 | 18 | enc.Reset(hexEnc) 19 | if err := enc.EncodeValue(v); err != nil { 20 | return types.AppendError(b, err) 21 | } 22 | 23 | if err := hexEnc.Close(); err != nil { 24 | return types.AppendError(b, err) 25 | } 26 | 27 | return hexEnc.Bytes() 28 | } 29 | } 30 | 31 | func msgpackScanner(_ reflect.Type) types.ScannerFunc { 32 | return func(v reflect.Value, rd types.Reader, n int) error { 33 | if n <= 0 { 34 | return nil 35 | } 36 | 37 | hexDec, err := types.NewHexDecoder(rd, n) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | dec := msgpack.GetDecoder() 43 | defer msgpack.PutDecoder(dec) 44 | 45 | dec.Reset(hexDec) 46 | if err := dec.DecodeValue(v); err != nil { 47 | return err 48 | } 49 | 50 | return nil 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /orm/orm.go: -------------------------------------------------------------------------------- 1 | /* 2 | The API in this package is not stable and may change without any notice. 3 | */ 4 | package orm 5 | 6 | import ( 7 | "context" 8 | "io" 9 | 10 | "github.com/go-pg/pg/v10/types" 11 | ) 12 | 13 | // ColumnScanner is used to scan column values. 14 | type ColumnScanner interface { 15 | // Scan assigns a column value from a row. 16 | // 17 | // An error should be returned if the value can not be stored 18 | // without loss of information. 19 | ScanColumn(col types.ColumnInfo, rd types.Reader, n int) error 20 | } 21 | 22 | type QueryAppender interface { 23 | AppendQuery(fmter QueryFormatter, b []byte) ([]byte, error) 24 | } 25 | 26 | type TemplateAppender interface { 27 | AppendTemplate(b []byte) ([]byte, error) 28 | } 29 | 30 | type QueryCommand interface { 31 | QueryAppender 32 | TemplateAppender 33 | String() string 34 | Operation() QueryOp 35 | Clone() QueryCommand 36 | Query() *Query 37 | } 38 | 39 | // DB is a common interface for pg.DB and pg.Tx types. 40 | type DB interface { 41 | Model(model ...interface{}) *Query 42 | ModelContext(c context.Context, model ...interface{}) *Query 43 | 44 | Exec(query interface{}, params ...interface{}) (Result, error) 45 | ExecContext(c context.Context, query interface{}, params ...interface{}) (Result, error) 46 | ExecOne(query interface{}, params ...interface{}) (Result, error) 47 | ExecOneContext(c context.Context, query interface{}, params ...interface{}) (Result, error) 48 | Query(model, query interface{}, params ...interface{}) (Result, error) 49 | QueryContext(c context.Context, model, query interface{}, params ...interface{}) (Result, error) 50 | QueryOne(model, query interface{}, params ...interface{}) (Result, error) 51 | QueryOneContext(c context.Context, model, query interface{}, params ...interface{}) (Result, error) 52 | 53 | CopyFrom(r io.Reader, query interface{}, params ...interface{}) (Result, error) 54 | CopyTo(w io.Writer, query interface{}, params ...interface{}) (Result, error) 55 | 56 | Context() context.Context 57 | Formatter() QueryFormatter 58 | } 59 | -------------------------------------------------------------------------------- /orm/query_test.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestQueryFormatQuery(t *testing.T) { 11 | type FormatModel struct { 12 | Foo string 13 | Bar string 14 | } 15 | 16 | q := NewQuery(nil, &FormatModel{"foo", "bar"}) 17 | 18 | params := &struct { 19 | Foo string 20 | }{ 21 | "not_foo", 22 | } 23 | fmter := NewFormatter().WithModel(q) 24 | b := fmter.FormatQuery(nil, "?foo ?TableName ?TableAlias ?TableColumns ?Columns", params) 25 | 26 | wanted := `'not_foo' "format_models" "format_model" "format_model"."foo", "format_model"."bar" "foo", "bar"` 27 | if string(b) != wanted { 28 | t.Fatalf("got `%s`, wanted `%s`", string(b), wanted) 29 | } 30 | } 31 | 32 | var _ = Describe("NewQuery", func() { 33 | It("works with nil db", func() { 34 | q := NewQuery(nil) 35 | 36 | b, err := q.AppendQuery(defaultFmter, nil) 37 | Expect(err).NotTo(HaveOccurred()) 38 | Expect(string(b)).To(Equal("SELECT *")) 39 | }) 40 | 41 | It("works with nil model", func() { 42 | type Model struct { 43 | Id int 44 | } 45 | q := NewQuery(nil, (*Model)(nil)) 46 | 47 | b, err := q.AppendQuery(defaultFmter, nil) 48 | Expect(err).NotTo(HaveOccurred()) 49 | Expect(string(b)).To(Equal(`SELECT "model"."id" FROM "models" AS "model"`)) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /orm/relation.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-pg/pg/v10/types" 7 | ) 8 | 9 | const ( 10 | InvalidRelation = iota 11 | HasOneRelation 12 | BelongsToRelation 13 | HasManyRelation 14 | Many2ManyRelation 15 | ) 16 | 17 | type Relation struct { 18 | Type int 19 | Field *Field 20 | JoinTable *Table 21 | BaseFKs []*Field 22 | JoinFKs []*Field 23 | Polymorphic *Field 24 | 25 | M2MTableName types.Safe 26 | M2MTableAlias types.Safe 27 | M2MBaseFKs []string 28 | M2MJoinFKs []string 29 | } 30 | 31 | func (r *Relation) String() string { 32 | return fmt.Sprintf("relation=%s", r.Field.GoName) 33 | } 34 | -------------------------------------------------------------------------------- /orm/result.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | // Result summarizes an executed SQL command. 4 | type Result interface { 5 | Model() Model 6 | 7 | // RowsAffected returns the number of rows affected by SELECT, INSERT, UPDATE, 8 | // or DELETE queries. It returns -1 if query can't possibly affect any rows, 9 | // e.g. in case of CREATE or SHOW queries. 10 | RowsAffected() int 11 | 12 | // RowsReturned returns the number of rows returned by the query. 13 | RowsReturned() int 14 | } 15 | -------------------------------------------------------------------------------- /orm/table_drop.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | type DropTableOptions struct { 4 | IfExists bool 5 | Cascade bool 6 | } 7 | 8 | type DropTableQuery struct { 9 | q *Query 10 | opt *DropTableOptions 11 | } 12 | 13 | var ( 14 | _ QueryAppender = (*DropTableQuery)(nil) 15 | _ QueryCommand = (*DropTableQuery)(nil) 16 | ) 17 | 18 | func NewDropTableQuery(q *Query, opt *DropTableOptions) *DropTableQuery { 19 | return &DropTableQuery{ 20 | q: q, 21 | opt: opt, 22 | } 23 | } 24 | 25 | func (q *DropTableQuery) String() string { 26 | b, err := q.AppendQuery(defaultFmter, nil) 27 | if err != nil { 28 | panic(err) 29 | } 30 | return string(b) 31 | } 32 | 33 | func (q *DropTableQuery) Operation() QueryOp { 34 | return DropTableOp 35 | } 36 | 37 | func (q *DropTableQuery) Clone() QueryCommand { 38 | return &DropTableQuery{ 39 | q: q.q.Clone(), 40 | opt: q.opt, 41 | } 42 | } 43 | 44 | func (q *DropTableQuery) Query() *Query { 45 | return q.q 46 | } 47 | 48 | func (q *DropTableQuery) AppendTemplate(b []byte) ([]byte, error) { 49 | return q.AppendQuery(dummyFormatter{}, b) 50 | } 51 | 52 | func (q *DropTableQuery) AppendQuery(fmter QueryFormatter, b []byte) (_ []byte, err error) { 53 | b = appendComment(b, q.q.comment) 54 | if q.q.stickyErr != nil { 55 | return nil, q.q.stickyErr 56 | } 57 | if q.q.tableModel == nil { 58 | return nil, errModelNil 59 | } 60 | 61 | b = append(b, "DROP TABLE "...) 62 | if q.opt != nil && q.opt.IfExists { 63 | b = append(b, "IF EXISTS "...) 64 | } 65 | b, err = q.q.appendFirstTable(fmter, b) 66 | if err != nil { 67 | return nil, err 68 | } 69 | if q.opt != nil && q.opt.Cascade { 70 | b = append(b, " CASCADE"...) 71 | } 72 | 73 | return b, q.q.stickyErr 74 | } 75 | -------------------------------------------------------------------------------- /orm/table_drop_test.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | type DropTableModel struct{} 9 | 10 | var _ = Describe("DropTable", func() { 11 | It("drops table", func() { 12 | q := NewQuery(nil, &DropTableModel{}) 13 | 14 | s := dropTableQueryString(q, nil) 15 | Expect(s).To(Equal(`DROP TABLE "drop_table_models"`)) 16 | }) 17 | 18 | It("drops table if exists", func() { 19 | q := NewQuery(nil, &DropTableModel{}) 20 | 21 | s := dropTableQueryString(q, &DropTableOptions{IfExists: true}) 22 | Expect(s).To(Equal(`DROP TABLE IF EXISTS "drop_table_models"`)) 23 | }) 24 | }) 25 | 26 | func dropTableQueryString(q *Query, opt *DropTableOptions) string { 27 | qq := NewDropTableQuery(q, opt) 28 | return queryString(qq) 29 | } 30 | -------------------------------------------------------------------------------- /orm/table_params.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import "reflect" 4 | 5 | type tableParams struct { 6 | table *Table 7 | strct reflect.Value 8 | } 9 | 10 | func newTableParams(strct interface{}) (*tableParams, bool) { 11 | v := reflect.ValueOf(strct) 12 | if !v.IsValid() { 13 | return nil, false 14 | } 15 | 16 | v = reflect.Indirect(v) 17 | if v.Kind() != reflect.Struct { 18 | return nil, false 19 | } 20 | 21 | return &tableParams{ 22 | table: GetTable(v.Type()), 23 | strct: v, 24 | }, true 25 | } 26 | 27 | func (m *tableParams) AppendParam(fmter QueryFormatter, b []byte, name string) ([]byte, bool) { 28 | return m.table.AppendParam(b, m.strct, name) 29 | } 30 | -------------------------------------------------------------------------------- /orm/tables.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sync" 7 | 8 | "github.com/go-pg/pg/v10/types" 9 | ) 10 | 11 | var _tables = newTables() 12 | 13 | type tableInProgress struct { 14 | table *Table 15 | 16 | init1Once sync.Once 17 | init2Once sync.Once 18 | } 19 | 20 | func newTableInProgress(table *Table) *tableInProgress { 21 | return &tableInProgress{ 22 | table: table, 23 | } 24 | } 25 | 26 | func (inp *tableInProgress) init1() bool { 27 | var inited bool 28 | inp.init1Once.Do(func() { 29 | inp.table.init1() 30 | inited = true 31 | }) 32 | return inited 33 | } 34 | 35 | func (inp *tableInProgress) init2() bool { 36 | var inited bool 37 | inp.init2Once.Do(func() { 38 | inp.table.init2() 39 | inited = true 40 | }) 41 | return inited 42 | } 43 | 44 | // GetTable returns a Table for a struct type. 45 | func GetTable(typ reflect.Type) *Table { 46 | return _tables.Get(typ) 47 | } 48 | 49 | // RegisterTable registers a struct as SQL table. 50 | // It is usually used to register intermediate table 51 | // in many to many relationship. 52 | func RegisterTable(strct interface{}) { 53 | _tables.Register(strct) 54 | } 55 | 56 | type tables struct { 57 | tables sync.Map 58 | 59 | mu sync.RWMutex 60 | inProgress map[reflect.Type]*tableInProgress 61 | } 62 | 63 | func newTables() *tables { 64 | return &tables{ 65 | inProgress: make(map[reflect.Type]*tableInProgress), 66 | } 67 | } 68 | 69 | func (t *tables) Register(strct interface{}) { 70 | typ := reflect.TypeOf(strct) 71 | if typ.Kind() == reflect.Ptr { 72 | typ = typ.Elem() 73 | } 74 | _ = t.Get(typ) 75 | } 76 | 77 | func (t *tables) get(typ reflect.Type, allowInProgress bool) *Table { 78 | if typ.Kind() != reflect.Struct { 79 | panic(fmt.Errorf("got %s, wanted %s", typ.Kind(), reflect.Struct)) 80 | } 81 | 82 | if v, ok := t.tables.Load(typ); ok { 83 | return v.(*Table) 84 | } 85 | 86 | t.mu.Lock() 87 | 88 | if v, ok := t.tables.Load(typ); ok { 89 | t.mu.Unlock() 90 | return v.(*Table) 91 | } 92 | 93 | var table *Table 94 | 95 | inProgress := t.inProgress[typ] 96 | if inProgress == nil { 97 | table = newTable(typ) 98 | inProgress = newTableInProgress(table) 99 | t.inProgress[typ] = inProgress 100 | } else { 101 | table = inProgress.table 102 | } 103 | 104 | t.mu.Unlock() 105 | 106 | inProgress.init1() 107 | if allowInProgress { 108 | return table 109 | } 110 | 111 | if inProgress.init2() { 112 | t.mu.Lock() 113 | delete(t.inProgress, typ) 114 | t.tables.Store(typ, table) 115 | t.mu.Unlock() 116 | } 117 | 118 | return table 119 | } 120 | 121 | func (t *tables) Get(typ reflect.Type) *Table { 122 | return t.get(typ, false) 123 | } 124 | 125 | func (t *tables) getByName(name types.Safe) *Table { 126 | var found *Table 127 | t.tables.Range(func(key, value interface{}) bool { 128 | t := value.(*Table) 129 | if t.SQLName == name { 130 | found = t 131 | return false 132 | } 133 | return true 134 | }) 135 | return found 136 | } 137 | -------------------------------------------------------------------------------- /orm/types.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | //nolint 4 | const ( 5 | // Date / Time 6 | pgTypeTimestamp = "timestamp" // Timestamp without a time zone 7 | pgTypeTimestampTz = "timestamptz" // Timestamp with a time zone 8 | pgTypeDate = "date" // Date 9 | pgTypeTime = "time" // Time without a time zone 10 | pgTypeTimeTz = "time with time zone" // Time with a time zone 11 | pgTypeInterval = "interval" // Time Interval 12 | 13 | // Network Addresses 14 | pgTypeInet = "inet" // IPv4 or IPv6 hosts and networks 15 | pgTypeCidr = "cidr" // IPv4 or IPv6 networks 16 | pgTypeMacaddr = "macaddr" // MAC addresses 17 | 18 | // Boolean 19 | pgTypeBoolean = "boolean" 20 | 21 | // Numeric Types 22 | 23 | // Floating Point Types 24 | pgTypeReal = "real" // 4 byte floating point (6 digit precision) 25 | pgTypeDoublePrecision = "double precision" // 8 byte floating point (15 digit precision) 26 | 27 | // Integer Types 28 | pgTypeSmallint = "smallint" // 2 byte integer 29 | pgTypeInteger = "integer" // 4 byte integer 30 | pgTypeBigint = "bigint" // 8 byte integer 31 | 32 | // Serial Types 33 | pgTypeSmallserial = "smallserial" // 2 byte autoincrementing integer 34 | pgTypeSerial = "serial" // 4 byte autoincrementing integer 35 | pgTypeBigserial = "bigserial" // 8 byte autoincrementing integer 36 | 37 | // Character Types 38 | pgTypeVarchar = "varchar" // variable length string with limit 39 | pgTypeChar = "char" // fixed length string (blank padded) 40 | pgTypeText = "text" // variable length string without limit 41 | 42 | // JSON Types 43 | pgTypeJSON = "json" // text representation of json data 44 | pgTypeJSONB = "jsonb" // binary representation of json data 45 | 46 | // Binary Data Types 47 | pgTypeBytea = "bytea" // binary string 48 | ) 49 | -------------------------------------------------------------------------------- /orm/util.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | 8 | "github.com/go-pg/pg/v10/types" 9 | ) 10 | 11 | func indirect(v reflect.Value) reflect.Value { 12 | switch v.Kind() { 13 | case reflect.Interface: 14 | return indirect(v.Elem()) 15 | case reflect.Ptr: 16 | return v.Elem() 17 | default: 18 | return v 19 | } 20 | } 21 | 22 | func indirectType(t reflect.Type) reflect.Type { 23 | if t.Kind() == reflect.Ptr { 24 | t = t.Elem() 25 | } 26 | return t 27 | } 28 | 29 | func sliceElemType(v reflect.Value) reflect.Type { 30 | elemType := v.Type().Elem() 31 | if elemType.Kind() == reflect.Interface && v.Len() > 0 { 32 | return indirect(v.Index(0).Elem()).Type() 33 | } 34 | return indirectType(elemType) 35 | } 36 | 37 | func typeByIndex(t reflect.Type, index []int) reflect.Type { 38 | for _, x := range index { 39 | switch t.Kind() { 40 | case reflect.Ptr: 41 | t = t.Elem() 42 | case reflect.Slice: 43 | t = indirectType(t.Elem()) 44 | } 45 | t = t.Field(x).Type 46 | } 47 | return indirectType(t) 48 | } 49 | 50 | func fieldByIndex(v reflect.Value, index []int) (_ reflect.Value, ok bool) { 51 | if len(index) == 1 { 52 | return v.Field(index[0]), true 53 | } 54 | 55 | for i, idx := range index { 56 | if i > 0 { 57 | if v.Kind() == reflect.Ptr { 58 | if v.IsNil() { 59 | return v, false 60 | } 61 | v = v.Elem() 62 | } 63 | } 64 | v = v.Field(idx) 65 | } 66 | return v, true 67 | } 68 | 69 | func fieldByIndexAlloc(v reflect.Value, index []int) reflect.Value { 70 | if len(index) == 1 { 71 | return v.Field(index[0]) 72 | } 73 | 74 | for i, idx := range index { 75 | if i > 0 { 76 | v = indirectNil(v) 77 | } 78 | v = v.Field(idx) 79 | } 80 | return v 81 | } 82 | 83 | func indirectNil(v reflect.Value) reflect.Value { 84 | if v.Kind() == reflect.Ptr { 85 | if v.IsNil() { 86 | v.Set(reflect.New(v.Type().Elem())) 87 | } 88 | v = v.Elem() 89 | } 90 | return v 91 | } 92 | 93 | func walk(v reflect.Value, index []int, fn func(reflect.Value)) { 94 | v = reflect.Indirect(v) 95 | switch v.Kind() { 96 | case reflect.Slice: 97 | sliceLen := v.Len() 98 | for i := 0; i < sliceLen; i++ { 99 | visitField(v.Index(i), index, fn) 100 | } 101 | default: 102 | visitField(v, index, fn) 103 | } 104 | } 105 | 106 | func visitField(v reflect.Value, index []int, fn func(reflect.Value)) { 107 | v = reflect.Indirect(v) 108 | if len(index) > 0 { 109 | v = v.Field(index[0]) 110 | if v.Kind() == reflect.Ptr && v.IsNil() { 111 | return 112 | } 113 | walk(v, index[1:], fn) 114 | } else { 115 | fn(v) 116 | } 117 | } 118 | 119 | func dstValues(model TableModel, fields []*Field) map[string][]reflect.Value { 120 | fieldIndex := model.Relation().Field.Index 121 | m := make(map[string][]reflect.Value) 122 | var id []byte 123 | walk(model.Root(), model.ParentIndex(), func(v reflect.Value) { 124 | id = modelID(id[:0], v, fields) 125 | m[string(id)] = append(m[string(id)], v.FieldByIndex(fieldIndex)) 126 | }) 127 | return m 128 | } 129 | 130 | func modelID(b []byte, v reflect.Value, fields []*Field) []byte { 131 | for i, f := range fields { 132 | if i > 0 { 133 | b = append(b, ',') 134 | } 135 | b = f.AppendValue(b, v, 0) 136 | } 137 | return b 138 | } 139 | 140 | func appendColumns(b []byte, table types.Safe, fields []*Field) []byte { 141 | for i, f := range fields { 142 | if i > 0 { 143 | b = append(b, ", "...) 144 | } 145 | 146 | if len(table) > 0 { 147 | b = append(b, table...) 148 | b = append(b, '.') 149 | } 150 | b = append(b, f.Column...) 151 | } 152 | return b 153 | } 154 | 155 | // appendComment adds comment in the header of the query into buffer 156 | func appendComment(b []byte, name string) []byte { 157 | if name == "" { 158 | return b 159 | } 160 | name = strings.ReplaceAll(name, `/*`, `/\*`) 161 | name = strings.ReplaceAll(name, `*/`, `*\/`) 162 | return append(b, fmt.Sprintf("/* %s */ ", name)...) 163 | } 164 | -------------------------------------------------------------------------------- /orm/util_test.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | var _ = Describe("Comment - escape comment symbols", func() { 9 | It("only open sequence", func() { 10 | var res []byte 11 | c := "/* comment" 12 | 13 | s := appendComment(res, c) 14 | Expect(s).To(Equal([]byte("/* /\\* comment */ "))) 15 | }) 16 | It("only close sequence", func() { 17 | var res []byte 18 | c := "comment */" 19 | 20 | s := appendComment(res, c) 21 | Expect(s).To(Equal([]byte("/* comment *\\/ */ "))) 22 | }) 23 | It("open and close sequences", func() { 24 | var res []byte 25 | c := "/* comment */" 26 | 27 | s := appendComment(res, c) 28 | Expect(s).To(Equal([]byte("/* /\\* comment *\\/ */ "))) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /pgjson/json.go: -------------------------------------------------------------------------------- 1 | package pgjson 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | ) 7 | 8 | var _ Provider = (*StdProvider)(nil) 9 | 10 | type StdProvider struct{} 11 | 12 | func (StdProvider) Marshal(v interface{}) ([]byte, error) { 13 | return json.Marshal(v) 14 | } 15 | 16 | func (StdProvider) Unmarshal(data []byte, v interface{}) error { 17 | return json.Unmarshal(data, v) 18 | } 19 | 20 | func (StdProvider) NewEncoder(w io.Writer) Encoder { 21 | return json.NewEncoder(w) 22 | } 23 | 24 | func (StdProvider) NewDecoder(r io.Reader) Decoder { 25 | return json.NewDecoder(r) 26 | } 27 | -------------------------------------------------------------------------------- /pgjson/provider.go: -------------------------------------------------------------------------------- 1 | package pgjson 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | var provider Provider = StdProvider{} 8 | 9 | func SetProvider(p Provider) { 10 | provider = p 11 | } 12 | 13 | type Provider interface { 14 | Marshal(v interface{}) ([]byte, error) 15 | Unmarshal(data []byte, v interface{}) error 16 | NewEncoder(w io.Writer) Encoder 17 | NewDecoder(r io.Reader) Decoder 18 | } 19 | 20 | type Decoder interface { 21 | Decode(v interface{}) error 22 | UseNumber() 23 | } 24 | 25 | type Encoder interface { 26 | Encode(v interface{}) error 27 | } 28 | 29 | func Marshal(v interface{}) ([]byte, error) { 30 | return provider.Marshal(v) 31 | } 32 | 33 | func Unmarshal(data []byte, v interface{}) error { 34 | return provider.Unmarshal(data, v) 35 | } 36 | 37 | func NewEncoder(w io.Writer) Encoder { 38 | return provider.NewEncoder(w) 39 | } 40 | 41 | func NewDecoder(r io.Reader) Decoder { 42 | return provider.NewDecoder(r) 43 | } 44 | -------------------------------------------------------------------------------- /pool_test.go: -------------------------------------------------------------------------------- 1 | package pg_test 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/go-pg/pg/v10" 7 | 8 | . "gopkg.in/check.v1" 9 | ) 10 | 11 | var _ = Suite(&PoolTest{}) 12 | 13 | type PoolTest struct { 14 | db *pg.DB 15 | } 16 | 17 | func (t *PoolTest) SetUpTest(c *C) { 18 | opt := pgOptions() 19 | opt.IdleTimeout = time.Second 20 | t.db = pg.Connect(opt) 21 | } 22 | 23 | func (t *PoolTest) TearDownTest(c *C) { 24 | _ = t.db.Close() 25 | } 26 | 27 | func (t *PoolTest) TestPoolReusesConnection(c *C) { 28 | for i := 0; i < 100; i++ { 29 | _, err := t.db.Exec("SELECT 'test_pool_reuses_connection'") 30 | c.Assert(err, IsNil) 31 | } 32 | 33 | c.Assert(t.db.Pool().Len(), Equals, 1) 34 | c.Assert(t.db.Pool().IdleLen(), Equals, 1) 35 | } 36 | 37 | func (t *PoolTest) TestPoolMaxSize(c *C) { 38 | N := 1000 39 | 40 | perform(N, func(int) { 41 | _, err := t.db.Exec("SELECT 'test_pool_max_size'") 42 | c.Assert(err, IsNil) 43 | }) 44 | 45 | c.Assert(t.db.Pool().Len(), Equals, 10) 46 | c.Assert(t.db.Pool().IdleLen(), Equals, 10) 47 | } 48 | 49 | func (t *PoolTest) TestCloseClosesAllConnections(c *C) { 50 | ln := t.db.Listen(ctx, "test_channel") 51 | 52 | wait := make(chan struct{}, 2) 53 | go func() { 54 | wait <- struct{}{} 55 | _, _, err := ln.Receive(ctx) 56 | c.Assert(err, ErrorMatches, `^(.*use of closed (file or )?network connection|EOF)$`) 57 | wait <- struct{}{} 58 | }() 59 | 60 | select { 61 | case <-wait: 62 | // ok 63 | case <-time.After(3 * time.Second): 64 | c.Fatal("timeout") 65 | } 66 | 67 | c.Assert(t.db.Close(), IsNil) 68 | 69 | select { 70 | case <-wait: 71 | // ok 72 | case <-time.After(3 * time.Second): 73 | c.Fatal("timeout") 74 | } 75 | 76 | c.Assert(t.db.Pool().Len(), Equals, 0) 77 | c.Assert(t.db.Pool().IdleLen(), Equals, 0) 78 | } 79 | 80 | func (t *PoolTest) TestClosedDB(c *C) { 81 | c.Assert(t.db.Close(), IsNil) 82 | 83 | c.Assert(t.db.Pool().Len(), Equals, 0) 84 | c.Assert(t.db.Pool().IdleLen(), Equals, 0) 85 | 86 | err := t.db.Close() 87 | c.Assert(err, Not(IsNil)) 88 | c.Assert(err.Error(), Equals, "pg: database is closed") 89 | 90 | _, err = t.db.Exec("SELECT 'test_closed_db'") 91 | c.Assert(err, Not(IsNil)) 92 | c.Assert(err.Error(), Equals, "pg: database is closed") 93 | } 94 | 95 | func (t *PoolTest) TestClosedListener(c *C) { 96 | ln := t.db.Listen(ctx, "test_channel") 97 | 98 | c.Assert(t.db.Pool().Len(), Equals, 1) 99 | c.Assert(t.db.Pool().IdleLen(), Equals, 0) 100 | 101 | c.Assert(ln.Close(), IsNil) 102 | 103 | c.Assert(t.db.Pool().Len(), Equals, 0) 104 | c.Assert(t.db.Pool().IdleLen(), Equals, 0) 105 | 106 | err := ln.Close() 107 | c.Assert(err, Not(IsNil)) 108 | c.Assert(err.Error(), Equals, "pg: listener is closed") 109 | 110 | _, _, err = ln.ReceiveTimeout(ctx, time.Second) 111 | c.Assert(err, Not(IsNil)) 112 | c.Assert(err.Error(), Equals, "pg: listener is closed") 113 | } 114 | 115 | func (t *PoolTest) TestClosedTx(c *C) { 116 | tx, err := t.db.Begin() 117 | c.Assert(err, IsNil) 118 | 119 | c.Assert(t.db.Pool().Len(), Equals, 1) 120 | c.Assert(t.db.Pool().IdleLen(), Equals, 0) 121 | 122 | c.Assert(tx.Rollback(), IsNil) 123 | 124 | c.Assert(t.db.Pool().Len(), Equals, 1) 125 | c.Assert(t.db.Pool().IdleLen(), Equals, 1) 126 | 127 | err = tx.Rollback() 128 | c.Assert(err, Not(IsNil)) 129 | c.Assert(err.Error(), Equals, "pg: transaction has already been committed or rolled back") 130 | 131 | _, err = tx.Exec("SELECT 'test_closed_tx'") 132 | c.Assert(err, Not(IsNil)) 133 | c.Assert(err.Error(), Equals, "pg: transaction has already been committed or rolled back") 134 | } 135 | 136 | func (t *PoolTest) TestClosedStmt(c *C) { 137 | stmt, err := t.db.Prepare("SELECT $1::int") 138 | c.Assert(err, IsNil) 139 | 140 | c.Assert(t.db.Pool().Len(), Equals, 1) 141 | c.Assert(t.db.Pool().IdleLen(), Equals, 0) 142 | 143 | c.Assert(stmt.Close(), IsNil) 144 | 145 | c.Assert(t.db.Pool().Len(), Equals, 1) 146 | c.Assert(t.db.Pool().IdleLen(), Equals, 1) 147 | 148 | err = stmt.Close() 149 | c.Assert(err, Not(IsNil)) 150 | c.Assert(err.Error(), Equals, "pg: statement is closed") 151 | 152 | _, err = stmt.Exec(1) 153 | c.Assert(err, Not(IsNil)) 154 | c.Assert(err.Error(), Equals, "pg: statement is closed") 155 | } 156 | -------------------------------------------------------------------------------- /result.go: -------------------------------------------------------------------------------- 1 | package pg 2 | 3 | import ( 4 | "bytes" 5 | "strconv" 6 | 7 | "github.com/go-pg/pg/v10/internal" 8 | "github.com/go-pg/pg/v10/orm" 9 | ) 10 | 11 | // Result summarizes an executed SQL command. 12 | type Result = orm.Result 13 | 14 | // A result summarizes an executed SQL command. 15 | type result struct { 16 | model orm.Model 17 | 18 | affected int 19 | returned int 20 | } 21 | 22 | var _ Result = (*result)(nil) 23 | 24 | //nolint 25 | func (res *result) parse(b []byte) error { 26 | res.affected = -1 27 | 28 | ind := bytes.LastIndexByte(b, ' ') 29 | if ind == -1 { 30 | return nil 31 | } 32 | 33 | s := internal.BytesToString(b[ind+1 : len(b)-1]) 34 | 35 | affected, err := strconv.Atoi(s) 36 | if err == nil { 37 | res.affected = affected 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func (res *result) Model() orm.Model { 44 | return res.model 45 | } 46 | 47 | func (res *result) RowsAffected() int { 48 | return res.affected 49 | } 50 | 51 | func (res *result) RowsReturned() int { 52 | return res.returned 53 | } 54 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Fixes a dumb issue where Mac's don't come with GNU sed out of the box. 6 | sed=$(which {gsed,sed} | head -n1) 7 | 8 | help() { 9 | cat <<- EOF 10 | Usage: TAG=tag $0 11 | 12 | Updates version in go.mod files and pushes a new brash to GitHub. 13 | 14 | VARIABLES: 15 | TAG git tag, for example, v1.0.0 16 | EOF 17 | exit 0 18 | } 19 | 20 | if [ -z "$TAG" ] 21 | then 22 | printf "TAG is required\n\n" 23 | help 24 | fi 25 | 26 | TAG_REGEX="^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(\\-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$" 27 | if ! [[ "${TAG}" =~ ${TAG_REGEX} ]]; then 28 | printf "TAG is not valid: ${TAG}\n\n" 29 | exit 1 30 | fi 31 | 32 | TAG_FOUND=`git tag --list ${TAG}` 33 | if [[ ${TAG_FOUND} = ${TAG} ]] ; then 34 | printf "tag ${TAG} already exists\n\n" 35 | exit 1 36 | fi 37 | 38 | if ! git diff --quiet 39 | then 40 | printf "working tree is not clean\n\n" 41 | git status 42 | exit 1 43 | fi 44 | 45 | git checkout v10 46 | 47 | PACKAGE_DIRS=$(find . -mindepth 2 -type f -name 'go.mod' -exec dirname {} \; \ 48 | | $sed 's/^\.\///' \ 49 | | sort) 50 | 51 | for dir in $PACKAGE_DIRS 52 | do 53 | $sed --in-place \ 54 | "s/go-pg\/pg\([^ ]*\) v.*/go-pg\/pg\1 ${TAG}/" "${dir}/go.mod" 55 | done 56 | 57 | for dir in $PACKAGE_DIRS 58 | do 59 | printf "${dir}: go get -d && go mod tidy\n" 60 | (cd ./${dir} && go get -d && go mod tidy) 61 | done 62 | 63 | $sed --in-place "s/\(return \)\"[^\"]*\"/\1\"${TAG#v}\"/" ./version.go 64 | 65 | git checkout -b release/${TAG} v10 66 | git add -u 67 | git commit -m "Release $TAG (release.sh)" 68 | git push origin release/${TAG} 69 | -------------------------------------------------------------------------------- /scripts/tag.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | help() { 6 | cat <<- EOF 7 | Usage: TAG=tag $0 8 | 9 | Creates git tags for public Go packages. 10 | 11 | VARIABLES: 12 | TAG git tag, for example, v1.0.0 13 | EOF 14 | exit 0 15 | } 16 | 17 | if [ -z "$TAG" ] 18 | then 19 | printf "TAG env var is required\n\n"; 20 | help 21 | fi 22 | 23 | if ! grep -Fq "\"${TAG#v}\"" version.go 24 | then 25 | printf "version.go does not contain ${TAG#v}\n" 26 | exit 1 27 | fi 28 | 29 | PACKAGE_DIRS=$(find . -mindepth 2 -type f -name 'go.mod' -exec dirname {} \; \ 30 | | grep -E -v "example|internal" \ 31 | | sed 's/^\.\///' \ 32 | | sort) 33 | 34 | git tag ${TAG} 35 | git push origin ${TAG} 36 | 37 | for dir in $PACKAGE_DIRS 38 | do 39 | printf "tagging ${dir}/${TAG}\n" 40 | git tag ${dir}/${TAG} 41 | git push origin ${dir}/${TAG} 42 | done 43 | -------------------------------------------------------------------------------- /testdata/issue1079.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/go-pg/pg/v10" 5 | "github.com/go-pg/pg/v10/orm" 6 | ) 7 | 8 | type MyType struct { 9 | MyInfo [3]bool `pg:",array"` 10 | } 11 | 12 | func createSchema(db *pg.DB) error { 13 | for _, model := range []interface{}{(*MyType)(nil)} { 14 | err := db.CreateTable(model, &orm.CreateTableOptions{ 15 | Temp: true, 16 | }) 17 | if err != nil { 18 | return err 19 | } 20 | } 21 | return nil 22 | } 23 | 24 | func main() { 25 | db := pg.Connect(&pg.Options{ 26 | User: "postgres", 27 | }) 28 | defer db.Close() 29 | 30 | err := createSchema(db) 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | thing := &MyType{ 36 | MyInfo: [3]bool{true, false, true}, 37 | } 38 | err = db.Insert(thing) 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | thing2 := new(MyType) 44 | err = db.Model(thing2).Select() 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | if thing2.MyInfo != thing.MyInfo { 50 | panic("not equal") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /testdata/issue1214.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/go-pg/pg/v10" 7 | ) 8 | 9 | type Test struct { 10 | ID string `json:"id" pg:",pk"` 11 | Data *Data `pg:"type:jsonb"` 12 | } 13 | 14 | type Data struct { 15 | Key string `json:"key"` 16 | Value string `json:"value"` 17 | DataID string `json:"data_id"` 18 | TestID string `json:"test_id"` 19 | } 20 | 21 | func main() { 22 | pgConfig := pg.Options{ 23 | Addr: "localhost:5432", 24 | User: "postgres", 25 | Password: "postgres", 26 | Database: "test", 27 | } 28 | 29 | db := pg.Connect(&pgConfig) 30 | defer db.Close() 31 | 32 | createSchema(db) 33 | 34 | t := Test{ 35 | Data: &Data{ 36 | Key: "akey", 37 | Value: "avalue", 38 | DataID: "1", 39 | TestID: "2", 40 | }, 41 | } 42 | 43 | if err := db.Insert(&t); err != nil { 44 | log.Fatal(err) 45 | } 46 | } 47 | 48 | func createSchema(db *pg.DB) { 49 | queries := []string{ 50 | `DROP TABLE IF EXISTS "tests";`, 51 | `CREATE TABLE "tests" ( 52 | "id" bigserial PRIMARY KEY, 53 | "data" jsonb 54 | ); 55 | `, 56 | } 57 | for _, q := range queries { 58 | _, err := db.Exec(q) 59 | if err != nil { 60 | log.Fatalln(err.Error()) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /testdata/issue1664.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/go-pg/pg/v10" 8 | ) 9 | 10 | // models 11 | 12 | type Apple struct { 13 | ID string `json:"id"` 14 | Balls []*Ball `json:"balls"` 15 | } 16 | 17 | type Ball struct { 18 | ID string `json:"id"` 19 | AppleID string `json:"-"` 20 | CatID string `json:"-"` 21 | Cat *Cat `json:"cat"` 22 | } 23 | 24 | type Cat struct { 25 | ID string `json:"id"` 26 | Dogs []*Dog `json:"dogs"` 27 | } 28 | 29 | type Dog struct { 30 | ID string `json:"id"` 31 | CatID string `json:"-"` 32 | Elephants []*Elephant `json:"elephants"` 33 | } 34 | 35 | type Elephant struct { 36 | ID string `json:"id"` 37 | DogID string `json:"-"` 38 | } 39 | 40 | // main 41 | 42 | func main() { 43 | db := pg.Connect(&pg.Options{ 44 | User: "postgres", 45 | }) 46 | defer db.Close() 47 | 48 | var n int 49 | _, err := db.QueryOne(pg.Scan(&n), "SELECT 1") 50 | panicIf(err) 51 | 52 | statements := []string{ 53 | `CREATE TEMP TABLE apples (id TEXT PRIMARY KEY)`, 54 | `CREATE TEMP TABLE cats (id TEXT PRIMARY KEY)`, 55 | `CREATE TEMP TABLE balls (id TEXT PRIMARY KEY, apple_id TEXT REFERENCES apples (id), cat_id TEXT REFERENCES cats (id))`, 56 | `CREATE TEMP TABLE dogs (id TEXT PRIMARY KEY, cat_id TEXT REFERENCES cats (id))`, 57 | `CREATE TEMP TABLE elephants (id TEXT PRIMARY KEY, dog_id TEXT REFERENCES dogs (id))`, 58 | 59 | `INSERT INTO apples VALUES ('apple_1')`, 60 | `INSERT INTO cats VALUES ('cat_1')`, 61 | `INSERT INTO balls VALUES ('ball_1', 'apple_1', 'cat_1'), ('ball_2', 'apple_1', 'cat_1')`, 62 | `INSERT INTO dogs VALUES ('dog_1', 'cat_1')`, 63 | `INSERT INTO elephants VALUES ('elephant_1', 'dog_1')`, 64 | } 65 | 66 | for _, s := range statements { 67 | _, err = db.Exec(s) 68 | panicIf(err) 69 | } 70 | 71 | a := &Apple{} 72 | 73 | err = db.Model(a).Relation("Balls.Cat.Dogs.Elephants").Select() 74 | panicIf(err) 75 | 76 | printA(a) 77 | } 78 | 79 | // helpers 80 | 81 | func panicIf(err error) { 82 | if err != nil { 83 | panic(err) 84 | } 85 | } 86 | 87 | func printA(a *Apple) { 88 | b, err := json.MarshalIndent(a, "", " ") 89 | panicIf(err) 90 | fmt.Println(string(b)) 91 | } 92 | -------------------------------------------------------------------------------- /testdata/issue1726.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | pg "github.com/go-pg/pg/v10" 5 | "github.com/go-pg/pg/v10/orm" 6 | "github.com/go-pg/pgext" 7 | ) 8 | 9 | func init() { 10 | orm.RegisterTable((*ProjectDocument)(nil)) 11 | } 12 | 13 | func main() { 14 | db := pg.Connect(&pg.Options{ 15 | User: "postgres", 16 | }) 17 | db.AddQueryHook(&pgext.DebugHook{Verbose: true}) 18 | 19 | // if _, err := db.Model(&Project{ 20 | // ID: "bsvt22v0cr8rnl249230", 21 | // CompanyID: "a9543f45-5ea3-4bcb-81c6-1cba6c278f6d", 22 | // }).Insert(); err != nil { 23 | // panic(err) 24 | // } 25 | 26 | // if _, err := db.Model(&Document{ 27 | // ID: "bsvsdsdad", 28 | // CompanyID: "a9543f45-5ea3-4bcb-81c6-1cba6c278f6d", 29 | // }).Insert(); err != nil { 30 | // panic(err) 31 | // } 32 | 33 | // if _, err := db.Model(&ProjectDocument{ 34 | // ProjectID: "bsvt22v0cr8rnl249230", 35 | // DocumentID: "bsvsdsdad", 36 | // CompanyID: "a9543f45-5ea3-4bcb-81c6-1cba6c278f6d", 37 | // }).Insert(); err != nil { 38 | // panic(err) 39 | // } 40 | 41 | qwe := &Project{ 42 | ID: "bsvt22v0cr8rnl249230", 43 | CompanyID: "a9543f45-5ea3-4bcb-81c6-1cba6c278f6d", 44 | } 45 | 46 | err := db.Model(qwe). 47 | WherePK(). 48 | Relation("Documents"). 49 | Select() 50 | if err != nil { 51 | panic(err) 52 | } 53 | } 54 | 55 | type Project struct { 56 | ID string `pg:",pk"` 57 | CompanyID string `pg:",pk"` 58 | Documents []Document `pg:",many2many:project_documents"` 59 | } 60 | 61 | type Document struct { 62 | ID string `pg:",pk"` 63 | CompanyID string `pg:",pk"` 64 | } 65 | 66 | type ProjectDocument struct { 67 | CompanyID string `pg:",pk"` 68 | ProjectID string `pg:",pk"` 69 | Project *Project 70 | DocumentID string `pg:",pk"` 71 | Document *Document 72 | } 73 | -------------------------------------------------------------------------------- /types/append.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "math" 5 | "reflect" 6 | "strconv" 7 | "time" 8 | "unicode/utf8" 9 | 10 | "github.com/tmthrgd/go-hex" 11 | ) 12 | 13 | func Append(b []byte, v interface{}, flags int) []byte { 14 | switch v := v.(type) { 15 | case nil: 16 | return AppendNull(b, flags) 17 | case bool: 18 | return appendBool(b, v) 19 | case int32: 20 | return strconv.AppendInt(b, int64(v), 10) 21 | case int64: 22 | return strconv.AppendInt(b, v, 10) 23 | case int: 24 | return strconv.AppendInt(b, int64(v), 10) 25 | case float32: 26 | return appendFloat(b, float64(v), flags, 32) 27 | case float64: 28 | return appendFloat(b, v, flags, 64) 29 | case string: 30 | return AppendString(b, v, flags) 31 | case time.Time: 32 | return AppendTime(b, v, flags) 33 | case []byte: 34 | return AppendBytes(b, v, flags) 35 | case ValueAppender: 36 | return appendAppender(b, v, flags) 37 | default: 38 | return appendValue(b, reflect.ValueOf(v), flags) 39 | } 40 | } 41 | 42 | func AppendError(b []byte, err error) []byte { 43 | b = append(b, "?!("...) 44 | b = append(b, err.Error()...) 45 | b = append(b, ')') 46 | return b 47 | } 48 | 49 | func AppendNull(b []byte, flags int) []byte { 50 | if hasFlag(flags, quoteFlag) { 51 | return append(b, "NULL"...) 52 | } 53 | return nil 54 | } 55 | 56 | func appendBool(dst []byte, v bool) []byte { 57 | if v { 58 | return append(dst, "TRUE"...) 59 | } 60 | return append(dst, "FALSE"...) 61 | } 62 | 63 | func appendFloat(dst []byte, v float64, flags int, bitSize int) []byte { 64 | if hasFlag(flags, arrayFlag) { 65 | return appendFloat2(dst, v, flags) 66 | } 67 | 68 | switch { 69 | case math.IsNaN(v): 70 | if hasFlag(flags, quoteFlag) { 71 | return append(dst, "'NaN'"...) 72 | } 73 | return append(dst, "NaN"...) 74 | case math.IsInf(v, 1): 75 | if hasFlag(flags, quoteFlag) { 76 | return append(dst, "'Infinity'"...) 77 | } 78 | return append(dst, "Infinity"...) 79 | case math.IsInf(v, -1): 80 | if hasFlag(flags, quoteFlag) { 81 | return append(dst, "'-Infinity'"...) 82 | } 83 | return append(dst, "-Infinity"...) 84 | default: 85 | return strconv.AppendFloat(dst, v, 'f', -1, bitSize) 86 | } 87 | } 88 | 89 | func appendFloat2(dst []byte, v float64, _ int) []byte { 90 | switch { 91 | case math.IsNaN(v): 92 | return append(dst, "NaN"...) 93 | case math.IsInf(v, 1): 94 | return append(dst, "Infinity"...) 95 | case math.IsInf(v, -1): 96 | return append(dst, "-Infinity"...) 97 | default: 98 | return strconv.AppendFloat(dst, v, 'f', -1, 64) 99 | } 100 | } 101 | 102 | func AppendString(b []byte, s string, flags int) []byte { 103 | if hasFlag(flags, arrayFlag) { 104 | return appendString2(b, s, flags) 105 | } 106 | 107 | if hasFlag(flags, quoteFlag) { 108 | b = append(b, '\'') 109 | for _, c := range s { 110 | if c == '\000' { 111 | continue 112 | } 113 | 114 | if c == '\'' { 115 | b = append(b, '\'', '\'') 116 | } else { 117 | b = appendRune(b, c) 118 | } 119 | } 120 | b = append(b, '\'') 121 | return b 122 | } 123 | 124 | for _, c := range s { 125 | if c != '\000' { 126 | b = appendRune(b, c) 127 | } 128 | } 129 | return b 130 | } 131 | 132 | func appendString2(b []byte, s string, flags int) []byte { 133 | b = append(b, '"') 134 | for _, c := range s { 135 | if c == '\000' { 136 | continue 137 | } 138 | 139 | switch c { 140 | case '\'': 141 | if hasFlag(flags, quoteFlag) { 142 | b = append(b, '\'') 143 | } 144 | b = append(b, '\'') 145 | case '"': 146 | b = append(b, '\\', '"') 147 | case '\\': 148 | b = append(b, '\\', '\\') 149 | default: 150 | b = appendRune(b, c) 151 | } 152 | } 153 | b = append(b, '"') 154 | return b 155 | } 156 | 157 | func appendRune(b []byte, r rune) []byte { 158 | if r < utf8.RuneSelf { 159 | return append(b, byte(r)) 160 | } 161 | l := len(b) 162 | if cap(b)-l < utf8.UTFMax { 163 | nb := make([]byte, l, 2*l+utf8.UTFMax) 164 | copy(nb, b) 165 | b = nb 166 | } 167 | n := utf8.EncodeRune(b[l:l+utf8.UTFMax], r) 168 | return b[:l+n] 169 | } 170 | 171 | func AppendBytes(b []byte, bytes []byte, flags int) []byte { 172 | if bytes == nil { 173 | return AppendNull(b, flags) 174 | } 175 | 176 | if hasFlag(flags, arrayFlag) { 177 | b = append(b, `"\`...) 178 | } else if hasFlag(flags, quoteFlag) { 179 | b = append(b, '\'') 180 | } 181 | 182 | b = append(b, `\x`...) 183 | 184 | s := len(b) 185 | b = append(b, make([]byte, hex.EncodedLen(len(bytes)))...) 186 | hex.Encode(b[s:], bytes) 187 | 188 | if hasFlag(flags, arrayFlag) { 189 | b = append(b, '"') 190 | } else if hasFlag(flags, quoteFlag) { 191 | b = append(b, '\'') 192 | } 193 | 194 | return b 195 | } 196 | 197 | func appendAppender(b []byte, v ValueAppender, flags int) []byte { 198 | bb, err := v.AppendValue(b, flags) 199 | if err != nil { 200 | return AppendError(b, err) 201 | } 202 | return bb 203 | } 204 | -------------------------------------------------------------------------------- /types/append_ident.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "github.com/go-pg/pg/v10/internal" 4 | 5 | func AppendIdent(b []byte, field string, flags int) []byte { 6 | return appendIdent(b, internal.StringToBytes(field), flags) 7 | } 8 | 9 | func AppendIdentBytes(b []byte, field []byte, flags int) []byte { 10 | return appendIdent(b, field, flags) 11 | } 12 | 13 | func appendIdent(b, src []byte, flags int) []byte { 14 | var quoted bool 15 | loop: 16 | for _, c := range src { 17 | switch c { 18 | case '*': 19 | if !quoted { 20 | b = append(b, '*') 21 | continue loop 22 | } 23 | case '.': 24 | if quoted && hasFlag(flags, quoteFlag) { 25 | b = append(b, '"') 26 | quoted = false 27 | } 28 | b = append(b, '.') 29 | continue loop 30 | } 31 | 32 | if !quoted && hasFlag(flags, quoteFlag) { 33 | b = append(b, '"') 34 | quoted = true 35 | } 36 | if c == '"' { 37 | b = append(b, '"', '"') 38 | } else { 39 | b = append(b, c) 40 | } 41 | } 42 | if quoted && hasFlag(flags, quoteFlag) { 43 | b = append(b, '"') 44 | } 45 | return b 46 | } 47 | -------------------------------------------------------------------------------- /types/append_ident_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/go-pg/pg/v10/types" 7 | ) 8 | 9 | var appendFieldTests = []struct { 10 | field string 11 | wanted string 12 | }{ 13 | {"", ""}, 14 | {"id", `"id"`}, 15 | {"table.id", `"table"."id"`}, 16 | 17 | {"*", "*"}, 18 | {"table.*", `"table".*`}, 19 | 20 | {"id AS pk", `"id AS pk"`}, 21 | {"table.id AS table__id", `"table"."id AS table__id"`}, 22 | 23 | {"?shard", `"?shard"`}, 24 | {"?shard.id", `"?shard"."id"`}, 25 | 26 | {`"`, `""""`}, 27 | {`'`, `"'"`}, 28 | } 29 | 30 | func TestAppendField(t *testing.T) { 31 | for _, test := range appendFieldTests { 32 | got := types.AppendIdent(nil, test.field, 1) 33 | if string(got) != test.wanted { 34 | t.Errorf("got %q, wanted %q (field=%q)", got, test.wanted, test.field) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /types/append_jsonb.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "github.com/go-pg/pg/v10/internal/parser" 4 | 5 | func AppendJSONB(b, jsonb []byte, flags int) []byte { 6 | if hasFlag(flags, arrayFlag) { 7 | b = append(b, '"') 8 | } else if hasFlag(flags, quoteFlag) { 9 | b = append(b, '\'') 10 | } 11 | 12 | p := parser.New(jsonb) 13 | for p.Valid() { 14 | c := p.Read() 15 | switch c { 16 | case '"': 17 | if hasFlag(flags, arrayFlag) { 18 | b = append(b, '\\') 19 | } 20 | b = append(b, '"') 21 | case '\'': 22 | if hasFlag(flags, quoteFlag) { 23 | b = append(b, '\'') 24 | } 25 | b = append(b, '\'') 26 | case '\000': 27 | continue 28 | case '\\': 29 | if p.SkipBytes([]byte("u0000")) { 30 | b = append(b, "\\\\u0000"...) 31 | } else { 32 | b = append(b, '\\') 33 | if p.Valid() { 34 | b = append(b, p.Read()) 35 | } 36 | } 37 | default: 38 | b = append(b, c) 39 | } 40 | } 41 | 42 | if hasFlag(flags, arrayFlag) { 43 | b = append(b, '"') 44 | } else if hasFlag(flags, quoteFlag) { 45 | b = append(b, '\'') 46 | } 47 | 48 | return b 49 | } 50 | -------------------------------------------------------------------------------- /types/append_jsonb_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/go-pg/pg/v10/pgjson" 8 | "github.com/go-pg/pg/v10/types" 9 | ) 10 | 11 | var jsonbTests = []struct { 12 | s, wanted string 13 | }{ 14 | {`\u0000`, `\\u0000`}, 15 | {`\\u0000`, `\\u0000`}, 16 | {`\\\u0000`, `\\\\u0000`}, 17 | {`foo \u0000 bar`, `foo \\u0000 bar`}, 18 | {`\u0001`, `\u0001`}, 19 | {`\\u0001`, `\\u0001`}, 20 | } 21 | 22 | func TestAppendJSONB(t *testing.T) { 23 | for _, test := range jsonbTests { 24 | got := types.AppendJSONB(nil, []byte(test.s), 0) 25 | if !bytes.Equal(got, []byte(test.wanted)) { 26 | t.Errorf("got %q, wanted %q", got, test.wanted) 27 | } 28 | } 29 | } 30 | 31 | func BenchmarkAppendJSONB(b *testing.B) { 32 | bytes, err := pgjson.Marshal(jsonbTests) 33 | if err != nil { 34 | b.Fatal(err) 35 | } 36 | buf := make([]byte, 1024) 37 | 38 | b.ResetTimer() 39 | 40 | for i := 0; i < b.N; i++ { 41 | _ = types.AppendJSONB(buf[:0], bytes, 1) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /types/append_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkAppendRuneNew(b *testing.B) { 8 | for i := 0; i < b.N; i++ { 9 | b.StopTimer() 10 | s := make([]byte, 0, 1024) 11 | b.StartTimer() 12 | 13 | for j := 0; j < 1000000; j++ { 14 | s = appendRune(s, '世') 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /types/array.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | type Array struct { 9 | v reflect.Value 10 | 11 | append AppenderFunc 12 | scan ScannerFunc 13 | } 14 | 15 | var ( 16 | _ ValueAppender = (*Array)(nil) 17 | _ ValueScanner = (*Array)(nil) 18 | ) 19 | 20 | func NewArray(vi interface{}) *Array { 21 | v := reflect.ValueOf(vi) 22 | if !v.IsValid() { 23 | panic(fmt.Errorf("pg: Array(nil)")) 24 | } 25 | 26 | return &Array{ 27 | v: v, 28 | 29 | append: ArrayAppender(v.Type()), 30 | scan: ArrayScanner(v.Type()), 31 | } 32 | } 33 | 34 | func (a *Array) AppendValue(b []byte, flags int) ([]byte, error) { 35 | if a.append == nil { 36 | panic(fmt.Errorf("pg: Array(unsupported %s)", a.v.Type())) 37 | } 38 | return a.append(b, a.v, flags), nil 39 | } 40 | 41 | func (a *Array) ScanValue(rd Reader, n int) error { 42 | if a.scan == nil { 43 | return fmt.Errorf("pg: Array(unsupported %s)", a.v.Type()) 44 | } 45 | 46 | if a.v.Kind() != reflect.Ptr { 47 | return fmt.Errorf("pg: Array(non-pointer %s)", a.v.Type()) 48 | } 49 | 50 | return a.scan(a.v.Elem(), rd, n) 51 | } 52 | 53 | func (a *Array) Value() interface{} { 54 | if a.v.IsValid() { 55 | return a.v.Interface() 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /types/array_parser.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "errors" 7 | "fmt" 8 | "io" 9 | 10 | "github.com/go-pg/pg/v10/internal/parser" 11 | ) 12 | 13 | var errEndOfArray = errors.New("pg: end of array") 14 | 15 | type arrayParser struct { 16 | p parser.StreamingParser 17 | 18 | stickyErr error 19 | buf []byte 20 | } 21 | 22 | func newArrayParserErr(err error) *arrayParser { 23 | return &arrayParser{ 24 | stickyErr: err, 25 | buf: make([]byte, 32), 26 | } 27 | } 28 | 29 | func newArrayParser(rd Reader) *arrayParser { 30 | p := parser.NewStreamingParser(rd) 31 | err := p.SkipByte('{') 32 | if err != nil { 33 | return newArrayParserErr(err) 34 | } 35 | return &arrayParser{ 36 | p: p, 37 | } 38 | } 39 | 40 | func (p *arrayParser) NextElem() ([]byte, error) { 41 | if p.stickyErr != nil { 42 | return nil, p.stickyErr 43 | } 44 | 45 | c, err := p.p.ReadByte() 46 | if err != nil { 47 | if err == io.EOF { 48 | return nil, errEndOfArray 49 | } 50 | return nil, err 51 | } 52 | 53 | switch c { 54 | case '"': 55 | b, err := p.p.ReadSubstring(p.buf[:0]) 56 | if err != nil { 57 | return nil, err 58 | } 59 | p.buf = b 60 | 61 | err = p.readCommaBrace() 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | return b, nil 67 | case '{': 68 | b, err := p.readSubArray(p.buf[:0]) 69 | if err != nil { 70 | return nil, err 71 | } 72 | p.buf = b 73 | 74 | err = p.readCommaBrace() 75 | if err != nil { 76 | return nil, err 77 | } 78 | 79 | return b, nil 80 | case '}': 81 | return nil, errEndOfArray 82 | default: 83 | err = p.p.UnreadByte() 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | b, err := p.readSimple(p.buf[:0]) 89 | if err != nil { 90 | return nil, err 91 | } 92 | p.buf = b 93 | 94 | if bytes.Equal(b, []byte("NULL")) { 95 | return nil, nil 96 | } 97 | return b, nil 98 | } 99 | } 100 | 101 | func (p *arrayParser) readSimple(b []byte) ([]byte, error) { 102 | for { 103 | tmp, err := p.p.ReadSlice(',') 104 | if err == nil { 105 | b = append(b, tmp...) 106 | b = b[:len(b)-1] 107 | break 108 | } 109 | b = append(b, tmp...) 110 | if err == bufio.ErrBufferFull { 111 | continue 112 | } 113 | if err == io.EOF { 114 | if b[len(b)-1] == '}' { 115 | b = b[:len(b)-1] 116 | break 117 | } 118 | } 119 | return nil, err 120 | } 121 | return b, nil 122 | } 123 | 124 | func (p *arrayParser) readSubArray(b []byte) ([]byte, error) { 125 | b = append(b, '{') 126 | for { 127 | c, err := p.p.ReadByte() 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | if c == '}' { 133 | b = append(b, '}') 134 | return b, nil 135 | } 136 | 137 | if c == '"' { 138 | b = append(b, '"') 139 | for { 140 | tmp, err := p.p.ReadSlice('"') 141 | b = append(b, tmp...) 142 | if err != nil { 143 | if err == bufio.ErrBufferFull { 144 | continue 145 | } 146 | return nil, err 147 | } 148 | if len(b) > 1 && b[len(b)-2] != '\\' { 149 | break 150 | } 151 | } 152 | continue 153 | } 154 | 155 | b = append(b, c) 156 | } 157 | } 158 | 159 | func (p *arrayParser) readCommaBrace() error { 160 | c, err := p.p.ReadByte() 161 | if err != nil { 162 | return err 163 | } 164 | switch c { 165 | case ',', '}': 166 | return nil 167 | default: 168 | return fmt.Errorf("pg: got %q, wanted ',' or '}'", c) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /types/array_parser_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/go-pg/pg/v10/internal/pool" 7 | ) 8 | 9 | var arrayTests = []struct { 10 | s string 11 | els []string 12 | }{ 13 | {`{}`, []string{}}, 14 | {`{""}`, []string{""}}, 15 | {`{"\\"}`, []string{`\`}}, 16 | {`{"''"}`, []string{"''"}}, 17 | {`{{"''\"{}"}}`, []string{`{"''\"{}"}`}}, 18 | {`{"''\"{}"}`, []string{`''"{}`}}, 19 | 20 | {"{1,2}", []string{"1", "2"}}, 21 | {"{1,NULL}", []string{"1", ""}}, 22 | {`{"1","2"}`, []string{"1", "2"}}, 23 | {`{"{1}","{2}"}`, []string{"{1}", "{2}"}}, 24 | 25 | {"{{1,2},{3}}", []string{"{1,2}", "{3}"}}, 26 | } 27 | 28 | func TestArrayParser(t *testing.T) { 29 | for testi, test := range arrayTests { 30 | p := newArrayParser(pool.NewBytesReader([]byte(test.s))) 31 | 32 | var got []string 33 | for { 34 | b, err := p.NextElem() 35 | if err != nil { 36 | if err == errEndOfArray { 37 | break 38 | } 39 | t.Fatal(err) 40 | } 41 | got = append(got, string(b)) 42 | } 43 | 44 | if len(got) != len(test.els) { 45 | t.Fatalf( 46 | "test #%d got %d elements, wanted %d (got=%#v wanted=%#v)", 47 | testi, len(got), len(test.els), got, test.els) 48 | } 49 | 50 | for i, el := range got { 51 | if el != test.els[i] { 52 | t.Fatalf( 53 | "test #%d el #%d does not match: %s != %s (got=%#v wanted=%#v)", 54 | testi, i, el, test.els[i], got, test.els) 55 | } 56 | } 57 | } 58 | } 59 | 60 | var array = `{foo,bar,"some relatively long string","foo\""}` 61 | 62 | func BenchmarkArrayParserArray(b *testing.B) { 63 | bb := []byte(array) 64 | rd := pool.NewBytesReader(bb) 65 | b.ResetTimer() 66 | 67 | for i := 0; i < b.N; i++ { 68 | rd.Reset(bb) 69 | p := newArrayParser(rd) 70 | for { 71 | _, err := p.NextElem() 72 | if err != nil { 73 | if err == errEndOfArray { 74 | break 75 | } 76 | b.Fatal(err) 77 | } 78 | } 79 | } 80 | } 81 | 82 | func BenchmarkArrayParserSubArray(b *testing.B) { 83 | var bb []byte 84 | bb = append(bb, '{') 85 | for i := 0; i < 100; i++ { 86 | if i > 0 { 87 | bb = append(bb, ',') 88 | } 89 | bb = append(bb, array...) 90 | } 91 | bb = append(bb, '}') 92 | 93 | rd := pool.NewBytesReader(bb) 94 | b.ResetTimer() 95 | 96 | for i := 0; i < b.N; i++ { 97 | rd.Reset(bb) 98 | p := newArrayParser(rd) 99 | for { 100 | _, err := p.NextElem() 101 | if err != nil { 102 | if err == errEndOfArray { 103 | break 104 | } 105 | b.Fatal(err) 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /types/bench_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkAppendArrayBytesValue(b *testing.B) { 9 | var bytes [64]byte 10 | v := reflect.ValueOf(bytes) 11 | 12 | for i := 0; i < b.N; i++ { 13 | _ = appendArrayBytesValue(nil, v, 0) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /types/column.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/go-pg/pg/v10/internal/pool" 7 | "github.com/go-pg/pg/v10/pgjson" 8 | ) 9 | 10 | const ( 11 | pgBool = 16 12 | 13 | pgInt2 = 21 14 | pgInt4 = 23 15 | pgInt8 = 20 16 | 17 | pgFloat4 = 700 18 | pgFloat8 = 701 19 | 20 | pgText = 25 21 | pgVarchar = 1043 22 | pgBytea = 17 23 | pgJSON = 114 24 | pgJSONB = 3802 25 | 26 | pgTimestamp = 1114 27 | pgTimestamptz = 1184 28 | 29 | // pgInt2Array = 1005 30 | pgInt32Array = 1007 31 | pgInt8Array = 1016 32 | pgFloat8Array = 1022 33 | pgStringArray = 1009 34 | 35 | pgUUID = 2950 36 | ) 37 | 38 | type ColumnInfo = pool.ColumnInfo 39 | 40 | type RawValue struct { 41 | Type int32 42 | Value string 43 | } 44 | 45 | func (v RawValue) AppendValue(b []byte, flags int) ([]byte, error) { 46 | return AppendString(b, v.Value, flags), nil 47 | } 48 | 49 | func (v RawValue) MarshalJSON() ([]byte, error) { 50 | return pgjson.Marshal(v.Value) 51 | } 52 | 53 | func ReadColumnValue(col ColumnInfo, rd Reader, n int) (interface{}, error) { 54 | switch col.DataType { 55 | case pgBool: 56 | return ScanBool(rd, n) 57 | 58 | case pgInt2: 59 | n, err := scanInt64(rd, n, 16) 60 | if err != nil { 61 | return nil, err 62 | } 63 | return int16(n), nil 64 | case pgInt4: 65 | n, err := scanInt64(rd, n, 32) 66 | if err != nil { 67 | return nil, err 68 | } 69 | return int32(n), nil 70 | case pgInt8: 71 | return ScanInt64(rd, n) 72 | 73 | case pgFloat4: 74 | return ScanFloat32(rd, n) 75 | case pgFloat8: 76 | return ScanFloat64(rd, n) 77 | 78 | case pgBytea: 79 | return ScanBytes(rd, n) 80 | case pgText, pgVarchar, pgUUID: 81 | return ScanString(rd, n) 82 | case pgJSON, pgJSONB: 83 | s, err := ScanString(rd, n) 84 | if err != nil { 85 | return nil, err 86 | } 87 | return json.RawMessage(s), nil 88 | 89 | case pgTimestamp: 90 | return ScanTime(rd, n) 91 | case pgTimestamptz: 92 | return ScanTime(rd, n) 93 | 94 | case pgInt32Array: 95 | return scanInt64Array(rd, n) 96 | case pgInt8Array: 97 | return scanInt64Array(rd, n) 98 | case pgFloat8Array: 99 | return scanFloat64Array(rd, n) 100 | case pgStringArray: 101 | return scanStringArray(rd, n) 102 | 103 | default: 104 | s, err := ScanString(rd, n) 105 | if err != nil { 106 | return nil, err 107 | } 108 | return RawValue{ 109 | Type: col.DataType, 110 | Value: s, 111 | }, nil 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /types/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | The API in this package is not stable and may change without any notice. 3 | */ 4 | package types 5 | -------------------------------------------------------------------------------- /types/flags.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "reflect" 4 | 5 | const ( 6 | quoteFlag = 1 << iota 7 | arrayFlag 8 | subArrayFlag 9 | ) 10 | 11 | func hasFlag(flags, flag int) bool { 12 | return flags&flag == flag 13 | } 14 | 15 | func shouldQuoteArray(flags int) bool { 16 | return hasFlag(flags, quoteFlag) && !hasFlag(flags, subArrayFlag) 17 | } 18 | 19 | func nilable(v reflect.Value) bool { 20 | switch v.Kind() { 21 | case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: 22 | return true 23 | } 24 | return false 25 | } 26 | -------------------------------------------------------------------------------- /types/hex.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "fmt" 7 | "io" 8 | 9 | fasthex "github.com/tmthrgd/go-hex" 10 | ) 11 | 12 | type HexEncoder struct { 13 | b []byte 14 | flags int 15 | written bool 16 | } 17 | 18 | func NewHexEncoder(b []byte, flags int) *HexEncoder { 19 | return &HexEncoder{ 20 | b: b, 21 | flags: flags, 22 | } 23 | } 24 | 25 | func (enc *HexEncoder) Bytes() []byte { 26 | return enc.b 27 | } 28 | 29 | func (enc *HexEncoder) Write(b []byte) (int, error) { 30 | if !enc.written { 31 | if hasFlag(enc.flags, arrayFlag) { 32 | enc.b = append(enc.b, `"\`...) 33 | } else if hasFlag(enc.flags, quoteFlag) { 34 | enc.b = append(enc.b, '\'') 35 | } 36 | enc.b = append(enc.b, `\x`...) 37 | enc.written = true 38 | } 39 | 40 | i := len(enc.b) 41 | enc.b = append(enc.b, make([]byte, fasthex.EncodedLen(len(b)))...) 42 | fasthex.Encode(enc.b[i:], b) 43 | 44 | return len(b), nil 45 | } 46 | 47 | func (enc *HexEncoder) Close() error { 48 | if enc.written { 49 | if hasFlag(enc.flags, arrayFlag) { 50 | enc.b = append(enc.b, '"') 51 | } else if hasFlag(enc.flags, quoteFlag) { 52 | enc.b = append(enc.b, '\'') 53 | } 54 | } else { 55 | enc.b = AppendNull(enc.b, enc.flags) 56 | } 57 | return nil 58 | } 59 | 60 | //------------------------------------------------------------------------------ 61 | 62 | func NewHexDecoder(rd Reader, n int) (io.Reader, error) { 63 | if n <= 0 { 64 | var rd bytes.Reader 65 | return &rd, nil 66 | } 67 | 68 | if c, err := rd.ReadByte(); err != nil { 69 | return nil, err 70 | } else if c != '\\' { 71 | return nil, fmt.Errorf("got %q, wanted %q", c, '\\') 72 | } 73 | 74 | if c, err := rd.ReadByte(); err != nil { 75 | return nil, err 76 | } else if c != 'x' { 77 | return nil, fmt.Errorf("got %q, wanted %q", c, 'x') 78 | } 79 | 80 | return hex.NewDecoder(rd), nil 81 | } 82 | -------------------------------------------------------------------------------- /types/hstore.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | type Hstore struct { 9 | v reflect.Value 10 | 11 | append AppenderFunc 12 | scan ScannerFunc 13 | } 14 | 15 | var ( 16 | _ ValueAppender = (*Hstore)(nil) 17 | _ ValueScanner = (*Hstore)(nil) 18 | ) 19 | 20 | func NewHstore(vi interface{}) *Hstore { 21 | v := reflect.ValueOf(vi) 22 | if !v.IsValid() { 23 | panic(fmt.Errorf("pg.Hstore(nil)")) 24 | } 25 | 26 | typ := v.Type() 27 | if typ.Kind() == reflect.Ptr { 28 | typ = typ.Elem() 29 | } 30 | if typ.Kind() != reflect.Map { 31 | panic(fmt.Errorf("pg.Hstore(unsupported %s)", typ)) 32 | } 33 | 34 | return &Hstore{ 35 | v: v, 36 | 37 | append: HstoreAppender(typ), 38 | scan: HstoreScanner(typ), 39 | } 40 | } 41 | 42 | func (h *Hstore) Value() interface{} { 43 | if h.v.IsValid() { 44 | return h.v.Interface() 45 | } 46 | return nil 47 | } 48 | 49 | func (h *Hstore) AppendValue(b []byte, flags int) ([]byte, error) { 50 | return h.append(b, h.v, flags), nil 51 | } 52 | 53 | func (h *Hstore) ScanValue(rd Reader, n int) error { 54 | if h.v.Kind() != reflect.Ptr { 55 | return fmt.Errorf("pg: Hstore(non-pointer %s)", h.v.Type()) 56 | } 57 | 58 | return h.scan(h.v.Elem(), rd, n) 59 | } 60 | -------------------------------------------------------------------------------- /types/hstore_append.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | var mapStringStringType = reflect.TypeOf(map[string]string(nil)) 9 | 10 | func HstoreAppender(typ reflect.Type) AppenderFunc { 11 | if typ.Key() == stringType && typ.Elem() == stringType { 12 | return appendMapStringStringValue 13 | } 14 | 15 | return func(b []byte, v reflect.Value, flags int) []byte { 16 | err := fmt.Errorf("pg.Hstore(unsupported %s)", v.Type()) 17 | return AppendError(b, err) 18 | } 19 | } 20 | 21 | func appendMapStringString(b []byte, m map[string]string, flags int) []byte { 22 | if m == nil { 23 | return AppendNull(b, flags) 24 | } 25 | 26 | if hasFlag(flags, quoteFlag) { 27 | b = append(b, '\'') 28 | } 29 | 30 | for key, value := range m { 31 | b = appendString2(b, key, flags) 32 | b = append(b, '=', '>') 33 | b = appendString2(b, value, flags) 34 | b = append(b, ',') 35 | } 36 | if len(m) > 0 { 37 | b = b[:len(b)-1] // Strip trailing comma. 38 | } 39 | 40 | if hasFlag(flags, quoteFlag) { 41 | b = append(b, '\'') 42 | } 43 | 44 | return b 45 | } 46 | 47 | func appendMapStringStringValue(b []byte, v reflect.Value, flags int) []byte { 48 | m := v.Convert(mapStringStringType).Interface().(map[string]string) 49 | return appendMapStringString(b, m, flags) 50 | } 51 | -------------------------------------------------------------------------------- /types/hstore_parser.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | 7 | "github.com/go-pg/pg/v10/internal/parser" 8 | ) 9 | 10 | var errEndOfHstore = errors.New("pg: end of hstore") 11 | 12 | type hstoreParser struct { 13 | p parser.StreamingParser 14 | } 15 | 16 | func newHstoreParser(rd Reader) *hstoreParser { 17 | return &hstoreParser{ 18 | p: parser.NewStreamingParser(rd), 19 | } 20 | } 21 | 22 | func (p *hstoreParser) NextKey() ([]byte, error) { 23 | err := p.p.SkipByte('"') 24 | if err != nil { 25 | if err == io.EOF { 26 | return nil, errEndOfHstore 27 | } 28 | return nil, err 29 | } 30 | 31 | key, err := p.p.ReadSubstring(nil) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | err = p.p.SkipByte('=') 37 | if err != nil { 38 | return nil, err 39 | } 40 | err = p.p.SkipByte('>') 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return key, nil 46 | } 47 | 48 | func (p *hstoreParser) NextValue() ([]byte, error) { 49 | err := p.p.SkipByte('"') 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | value, err := p.p.ReadSubstring(nil) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | err = p.p.SkipByte(',') 60 | if err == nil { 61 | _ = p.p.SkipByte(' ') 62 | } 63 | 64 | return value, nil 65 | } 66 | -------------------------------------------------------------------------------- /types/hstore_parser_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/go-pg/pg/v10/internal/pool" 7 | ) 8 | 9 | var hstoreTests = []struct { 10 | s string 11 | m map[string]string 12 | }{ 13 | {`""=>""`, map[string]string{"": ""}}, 14 | {`"k''k"=>"k''k"`, map[string]string{"k''k": "k''k"}}, 15 | {`"k\"k"=>"k\"k"`, map[string]string{`k"k`: `k"k`}}, 16 | {`"k\k"=>"k\k"`, map[string]string{`k\k`: `k\k`}}, 17 | 18 | {`"foo"=>"bar"`, map[string]string{"foo": "bar"}}, 19 | {`"foo"=>"bar","k"=>"v"`, map[string]string{"foo": "bar", "k": "v"}}, 20 | } 21 | 22 | func TestHstoreParser(t *testing.T) { 23 | for testi, test := range hstoreTests { 24 | got, err := scanMapStringString(pool.NewBytesReader([]byte(test.s)), 0) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | 29 | if len(got) != len(test.m) { 30 | t.Fatalf( 31 | "test #%d got %d elements, wanted %d (got=%#v wanted=%#v)", 32 | testi, len(got), len(test.m), got, test.m) 33 | } 34 | 35 | for k, v := range got { 36 | if v != test.m[k] { 37 | t.Fatalf( 38 | "#%d el %q does not match: %s != %s (got=%#v wanted=%#v)", 39 | testi, k, v, test.m[k], got, test.m) 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /types/hstore_scan.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func HstoreScanner(typ reflect.Type) ScannerFunc { 9 | if typ.Key() == stringType && typ.Elem() == stringType { 10 | return scanMapStringStringValue 11 | } 12 | return func(v reflect.Value, rd Reader, n int) error { 13 | return fmt.Errorf("pg.Hstore(unsupported %s)", v.Type()) 14 | } 15 | } 16 | 17 | func scanMapStringStringValue(v reflect.Value, rd Reader, n int) error { 18 | m, err := scanMapStringString(rd, n) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | v.Set(reflect.ValueOf(m)) 24 | return nil 25 | } 26 | 27 | func scanMapStringString(rd Reader, n int) (map[string]string, error) { 28 | if n == -1 { 29 | return nil, nil 30 | } 31 | 32 | p := newHstoreParser(rd) 33 | m := make(map[string]string) 34 | for { 35 | key, err := p.NextKey() 36 | if err != nil { 37 | if err == errEndOfHstore { 38 | break 39 | } 40 | return nil, err 41 | } 42 | 43 | value, err := p.NextValue() 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | m[string(key)] = string(value) 49 | } 50 | return m, nil 51 | } 52 | -------------------------------------------------------------------------------- /types/in_op.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | type inOp struct { 9 | slice reflect.Value 10 | stickyErr error 11 | } 12 | 13 | var _ ValueAppender = (*inOp)(nil) 14 | 15 | func InMulti(values ...interface{}) ValueAppender { 16 | return &inOp{ 17 | slice: reflect.ValueOf(values), 18 | } 19 | } 20 | 21 | func In(slice interface{}) ValueAppender { 22 | v := reflect.ValueOf(slice) 23 | if v.Kind() != reflect.Slice { 24 | return &inOp{ 25 | stickyErr: fmt.Errorf("pg: In(non-slice %T)", slice), 26 | } 27 | } 28 | 29 | return &inOp{ 30 | slice: v, 31 | } 32 | } 33 | 34 | func (in *inOp) AppendValue(b []byte, flags int) ([]byte, error) { 35 | if in.stickyErr != nil { 36 | return nil, in.stickyErr 37 | } 38 | return appendIn(b, in.slice, flags), nil 39 | } 40 | 41 | func appendIn(b []byte, slice reflect.Value, flags int) []byte { 42 | sliceLen := slice.Len() 43 | for i := 0; i < sliceLen; i++ { 44 | if i > 0 { 45 | b = append(b, ',') 46 | } 47 | 48 | elem := slice.Index(i) 49 | if elem.Kind() == reflect.Interface { 50 | elem = elem.Elem() 51 | } 52 | 53 | if elem.Kind() == reflect.Slice { 54 | b = append(b, '(') 55 | b = appendIn(b, elem, flags) 56 | b = append(b, ')') 57 | } else { 58 | b = appendValue(b, elem, flags) 59 | } 60 | } 61 | return b 62 | } 63 | -------------------------------------------------------------------------------- /types/in_op_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/go-pg/pg/v10/types" 9 | ) 10 | 11 | func TestInOp(t *testing.T) { 12 | _, err := types.In(&[]string{}).AppendValue(nil, 0) 13 | assert.EqualError(t, err, "pg: In(non-slice *[]string)") 14 | 15 | tests := []struct { 16 | app types.ValueAppender 17 | wanted string 18 | }{ 19 | {types.InMulti(), ""}, 20 | {types.InMulti(1), "1"}, 21 | {types.InMulti(1, 2, 3), "1,2,3"}, 22 | {types.InMulti([]int{1, 2, 3}), "(1,2,3)"}, 23 | {types.InMulti([]int{1, 2}, []int{3, 4}), "(1,2),(3,4)"}, 24 | {types.InMulti(types.NewArray([]int{1, 2}), types.NewArray([]int{3, 4})), "{1,2},{3,4}"}, 25 | } 26 | 27 | for _, test := range tests { 28 | b, err := test.app.AppendValue(nil, 0) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | if string(b) != test.wanted { 33 | t.Fatalf("%s != %s", b, test.wanted) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /types/null_time.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | "database/sql" 6 | "encoding/json" 7 | "time" 8 | ) 9 | 10 | var jsonNull = []byte("null") 11 | 12 | // NullTime is a time.Time wrapper that marshals zero time as JSON null and 13 | // PostgreSQL NULL. 14 | type NullTime struct { 15 | time.Time 16 | } 17 | 18 | var ( 19 | _ json.Marshaler = (*NullTime)(nil) 20 | _ json.Unmarshaler = (*NullTime)(nil) 21 | _ sql.Scanner = (*NullTime)(nil) 22 | _ ValueAppender = (*NullTime)(nil) 23 | ) 24 | 25 | func (tm NullTime) MarshalJSON() ([]byte, error) { 26 | if tm.IsZero() { 27 | return jsonNull, nil 28 | } 29 | return tm.Time.MarshalJSON() 30 | } 31 | 32 | func (tm *NullTime) UnmarshalJSON(b []byte) error { 33 | if bytes.Equal(b, jsonNull) { 34 | tm.Time = time.Time{} 35 | return nil 36 | } 37 | return tm.Time.UnmarshalJSON(b) 38 | } 39 | 40 | func (tm NullTime) AppendValue(b []byte, flags int) ([]byte, error) { 41 | if tm.IsZero() { 42 | return AppendNull(b, flags), nil 43 | } 44 | return AppendTime(b, tm.Time, flags), nil 45 | } 46 | 47 | func (tm *NullTime) Scan(b interface{}) error { 48 | if b == nil { 49 | tm.Time = time.Time{} 50 | return nil 51 | } 52 | newtm, err := ParseTime(b.([]byte)) 53 | if err != nil { 54 | return err 55 | } 56 | tm.Time = newtm 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /types/time.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/go-pg/pg/v10/internal" 7 | ) 8 | 9 | const ( 10 | dateFormat = "2006-01-02" 11 | timeFormat = "15:04:05.999999999" 12 | timestampFormat = "2006-01-02 15:04:05.999999999" 13 | timestamptzFormat = "2006-01-02 15:04:05.999999999-07:00:00" 14 | timestamptzFormat2 = "2006-01-02 15:04:05.999999999-07:00" 15 | timestamptzFormat3 = "2006-01-02 15:04:05.999999999-07" 16 | ) 17 | 18 | func ParseTime(b []byte) (time.Time, error) { 19 | s := internal.BytesToString(b) 20 | return ParseTimeString(s) 21 | } 22 | 23 | func ParseTimeString(s string) (time.Time, error) { 24 | switch l := len(s); { 25 | case l <= len(timeFormat): 26 | if s[2] == ':' { 27 | return time.ParseInLocation(timeFormat, s, time.UTC) 28 | } 29 | return time.ParseInLocation(dateFormat, s, time.UTC) 30 | default: 31 | if s[10] == 'T' { 32 | return time.Parse(time.RFC3339Nano, s) 33 | } 34 | if c := s[l-9]; c == '+' || c == '-' { 35 | return time.Parse(timestamptzFormat, s) 36 | } 37 | if c := s[l-6]; c == '+' || c == '-' { 38 | return time.Parse(timestamptzFormat2, s) 39 | } 40 | if c := s[l-3]; c == '+' || c == '-' { 41 | return time.Parse(timestamptzFormat3, s) 42 | } 43 | return time.ParseInLocation(timestampFormat, s, time.UTC) 44 | } 45 | } 46 | 47 | func AppendTime(b []byte, tm time.Time, flags int) []byte { 48 | if flags == 1 { 49 | b = append(b, '\'') 50 | } 51 | b = tm.UTC().AppendFormat(b, timestamptzFormat) 52 | if flags == 1 { 53 | b = append(b, '\'') 54 | } 55 | return b 56 | } 57 | -------------------------------------------------------------------------------- /types/time_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/go-pg/pg/v10/types" 8 | ) 9 | 10 | func TestParseTimeString(t *testing.T) { 11 | ss := []string{ 12 | "2006-01-02", 13 | "15:04:05.999999999", 14 | "15:04:05.999999", 15 | "15:04:05.999", 16 | "15:04:05", 17 | "2006-01-02 15:04:05.999999999", 18 | "2006-01-02 15:04:05.999999999-07:00:00", 19 | "2006-01-02 15:04:05.999999999-07:00", 20 | "2006-01-02 15:04:05.999999999-07", 21 | time.Now().Format(time.RFC3339), 22 | time.Now().In(time.FixedZone("", 3600)).Format(time.RFC3339), 23 | time.Now().UTC().Format(time.RFC3339), 24 | } 25 | for _, s := range ss { 26 | _, err := types.ParseTimeString(s) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | } 31 | } 32 | 33 | func BenchmarkParseTime(b *testing.B) { 34 | for i := 0; i < b.N; i++ { 35 | types.ParseTimeString("2001-02-03 04:05:06+07") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/go-pg/pg/v10/internal/pool" 5 | ) 6 | 7 | type Reader = pool.Reader 8 | 9 | type ValueScanner interface { 10 | ScanValue(rd Reader, n int) error 11 | } 12 | 13 | type ValueAppender interface { 14 | AppendValue(b []byte, flags int) ([]byte, error) 15 | } 16 | 17 | //------------------------------------------------------------------------------ 18 | 19 | // Safe represents a safe SQL query. 20 | type Safe string 21 | 22 | var _ ValueAppender = (*Safe)(nil) 23 | 24 | func (q Safe) AppendValue(b []byte, flags int) ([]byte, error) { 25 | return append(b, q...), nil 26 | } 27 | 28 | //------------------------------------------------------------------------------ 29 | 30 | // Ident represents a SQL identifier, e.g. table or column name. 31 | type Ident string 32 | 33 | var _ ValueAppender = (*Ident)(nil) 34 | 35 | func (f Ident) AppendValue(b []byte, flags int) ([]byte, error) { 36 | return AppendIdent(b, string(f), flags), nil 37 | } 38 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package pg 2 | 3 | // Version is the current release version. 4 | func Version() string { 5 | return "10.14.0" 6 | } 7 | --------------------------------------------------------------------------------