├── .gitignore ├── .idea ├── .gitignore ├── modules.xml ├── sqlc.iml └── vcs.xml ├── LICENSE ├── README.md ├── builder.go ├── context.go ├── db.go ├── example ├── db.go ├── models.go ├── query.sql └── query.sql.go ├── go.mod ├── go.sum ├── interface.go ├── sqlc.json ├── wrap.go └── wrap_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/go,macos,goland,visualstudiocode 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=go,macos,goland,visualstudiocode 4 | 5 | ### Go ### 6 | # If you prefer the allow list template instead of the deny list, see community template: 7 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 8 | # 9 | # Binaries for programs and plugins 10 | *.exe 11 | *.exe~ 12 | *.dll 13 | *.so 14 | *.dylib 15 | 16 | # Test binary, built with `go test -c` 17 | *.test 18 | 19 | # Output of the go coverage tool, specifically when used with LiteIDE 20 | *.out 21 | 22 | # Dependency directories (remove the comment below to include it) 23 | # vendor/ 24 | 25 | # Go workspace file 26 | go.work 27 | 28 | ### Go Patch ### 29 | /vendor/ 30 | /Godeps/ 31 | 32 | ### GoLand ### 33 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 34 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 35 | 36 | # User-specific stuff 37 | .idea/**/workspace.xml 38 | .idea/**/tasks.xml 39 | .idea/**/usage.statistics.xml 40 | .idea/**/dictionaries 41 | .idea/**/shelf 42 | 43 | # AWS User-specific 44 | .idea/**/aws.xml 45 | 46 | # Generated files 47 | .idea/**/contentModel.xml 48 | 49 | # Sensitive or high-churn files 50 | .idea/**/dataSources/ 51 | .idea/**/dataSources.ids 52 | .idea/**/dataSources.local.xml 53 | .idea/**/sqlDataSources.xml 54 | .idea/**/dynamic.xml 55 | .idea/**/uiDesigner.xml 56 | .idea/**/dbnavigator.xml 57 | 58 | # Gradle 59 | .idea/**/gradle.xml 60 | .idea/**/libraries 61 | 62 | # Gradle and Maven with auto-import 63 | # When using Gradle or Maven with auto-import, you should exclude module files, 64 | # since they will be recreated, and may cause churn. Uncomment if using 65 | # auto-import. 66 | # .idea/artifacts 67 | # .idea/compiler.xml 68 | # .idea/jarRepositories.xml 69 | # .idea/modules.xml 70 | # .idea/*.iml 71 | # .idea/modules 72 | # *.iml 73 | # *.ipr 74 | 75 | # CMake 76 | cmake-build-*/ 77 | 78 | # Mongo Explorer plugin 79 | .idea/**/mongoSettings.xml 80 | 81 | # File-based project format 82 | *.iws 83 | 84 | # IntelliJ 85 | out/ 86 | 87 | # mpeltonen/sbt-idea plugin 88 | .idea_modules/ 89 | 90 | # JIRA plugin 91 | atlassian-ide-plugin.xml 92 | 93 | # Cursive Clojure plugin 94 | .idea/replstate.xml 95 | 96 | # SonarLint plugin 97 | .idea/sonarlint/ 98 | 99 | # Crashlytics plugin (for Android Studio and IntelliJ) 100 | com_crashlytics_export_strings.xml 101 | crashlytics.properties 102 | crashlytics-build.properties 103 | fabric.properties 104 | 105 | # Editor-based Rest Client 106 | .idea/httpRequests 107 | 108 | # Android studio 3.1+ serialized cache file 109 | .idea/caches/build_file_checksums.ser 110 | 111 | ### GoLand Patch ### 112 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 113 | 114 | # *.iml 115 | # modules.xml 116 | # .idea/misc.xml 117 | # *.ipr 118 | 119 | # Sonarlint plugin 120 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 121 | .idea/**/sonarlint/ 122 | 123 | # SonarQube Plugin 124 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 125 | .idea/**/sonarIssues.xml 126 | 127 | # Markdown Navigator plugin 128 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 129 | .idea/**/markdown-navigator.xml 130 | .idea/**/markdown-navigator-enh.xml 131 | .idea/**/markdown-navigator/ 132 | 133 | # Cache file creation bug 134 | # See https://youtrack.jetbrains.com/issue/JBR-2257 135 | .idea/$CACHE_FILE$ 136 | 137 | # CodeStream plugin 138 | # https://plugins.jetbrains.com/plugin/12206-codestream 139 | .idea/codestream.xml 140 | 141 | ### macOS ### 142 | # General 143 | .DS_Store 144 | .AppleDouble 145 | .LSOverride 146 | 147 | # Icon must end with two \r 148 | Icon 149 | 150 | # Thumbnails 151 | ._* 152 | 153 | # Files that might appear in the root of a volume 154 | .DocumentRevisions-V100 155 | .fseventsd 156 | .Spotlight-V100 157 | .TemporaryItems 158 | .Trashes 159 | .VolumeIcon.icns 160 | .com.apple.timemachine.donotpresent 161 | 162 | # Directories potentially created on remote AFP share 163 | .AppleDB 164 | .AppleDesktop 165 | Network Trash Folder 166 | Temporary Items 167 | .apdisk 168 | 169 | ### macOS Patch ### 170 | # iCloud generated files 171 | *.icloud 172 | 173 | ### VisualStudioCode ### 174 | .vscode/* 175 | !.vscode/settings.json 176 | !.vscode/tasks.json 177 | !.vscode/launch.json 178 | !.vscode/extensions.json 179 | !.vscode/*.code-snippets 180 | 181 | # Local History for Visual Studio Code 182 | .history/ 183 | 184 | # Built Visual Studio Code Extensions 185 | *.vsix 186 | 187 | ### VisualStudioCode Patch ### 188 | # Ignore all local history of files 189 | .history 190 | .ionide 191 | 192 | # Support for Project snippet scope 193 | .vscode/*.code-snippets 194 | 195 | # Ignore code-workspaces 196 | *.code-workspace 197 | 198 | # End of https://www.toptal.com/developers/gitignore/api/go,macos,goland,visualstudiocode 199 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/sqlc.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 yiplee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sqlc: A simple dynamic query builder for [kyleconroy/sqlc](https://github.com/kyleconroy/sqlc) 2 | 3 | ## How to use 4 | 5 | ```go 6 | package sqlc_test 7 | 8 | import ( 9 | "context" 10 | "database/sql" 11 | "log" 12 | 13 | "github.com/yiplee/sqlc" 14 | "github.com/yiplee/sqlc/example" 15 | ) 16 | 17 | func Example_build() { 18 | ctx := context.Background() 19 | 20 | db, err := sql.Open("postgres", "dsn") 21 | if err != nil { 22 | panic(err) 23 | } 24 | defer db.Close() 25 | 26 | // Wrap the db in order to hook the query 27 | query := example.New(sqlc.Wrap(db)) 28 | 29 | /* 30 | the original query must be simple and not contain any WHERE/ORDER/LIMIT/OFFSET clause 31 | 32 | -- name: ListAuthors :many 33 | SELECT * FROM authors; 34 | */ 35 | 36 | // customize the builder 37 | authors, err := query.ListAuthors(sqlc.Build(ctx, func(b *sqlc.Builder) { 38 | b.Where("age > $1", 10) 39 | b.Where("name = $2", "foo") 40 | b.Order("age,name DESC") 41 | b.Limit(10) 42 | })) 43 | 44 | if err != nil { 45 | log.Fatalln("ListAuthors", err) 46 | } 47 | 48 | log.Printf("list %d authors", len(authors)) 49 | } 50 | ``` 51 | 52 | 53 | Not perfect, but enough for now. 54 | -------------------------------------------------------------------------------- /builder.go: -------------------------------------------------------------------------------- 1 | package sqlc 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | type ( 10 | Builder struct { 11 | filters []filter 12 | order string 13 | offset, limit int 14 | } 15 | 16 | filter struct { 17 | expression string 18 | args []interface{} 19 | } 20 | ) 21 | 22 | func (b *Builder) clone() *Builder { 23 | var cb Builder 24 | cb = *b 25 | return &cb 26 | } 27 | 28 | // Where set conditions of where in SELECT 29 | // Where("user = ?","tom") 30 | // Where("a = ? OR b = ?",1,2) 31 | // Where("foo = $1","bar") 32 | func (b *Builder) Where(query string, args ...interface{}) *Builder { 33 | b.filters = append(b.filters, filter{ 34 | expression: query, 35 | args: args, 36 | }) 37 | 38 | return b 39 | } 40 | 41 | // In is an equivalent of Where("column IN (?,?,?)", args...). 42 | // In("id", 1, 2, 3) 43 | func (b *Builder) In(column string, args ...interface{}) *Builder { 44 | placeholders := make([]string, len(args)) 45 | for i := range args { 46 | placeholders[i] = "?" 47 | } 48 | 49 | query := fmt.Sprintf("%s IN (%s)", column, strings.Join(placeholders, ",")) 50 | return b.Where(query, args...) 51 | } 52 | 53 | // Order sets columns of ORDER BY in SELECT. 54 | // Order("name, age DESC") 55 | func (b *Builder) Order(cols string) *Builder { 56 | b.order = cols 57 | return b 58 | } 59 | 60 | // Offset sets the offset in SELECT. 61 | func (b *Builder) Offset(x int) *Builder { 62 | b.offset = x 63 | return b 64 | } 65 | 66 | // Limit sets the limit in SELECT. 67 | func (b *Builder) Limit(x int) *Builder { 68 | b.limit = x 69 | return b 70 | } 71 | 72 | // Build returns compiled SELECT string and args. 73 | func (b *Builder) Build(query string, args ...interface{}) (string, []interface{}) { 74 | var sb strings.Builder 75 | 76 | sb.WriteString(query) 77 | sb.WriteByte('\n') 78 | 79 | // append where conditions 80 | for idx, filter := range b.filters { 81 | if idx == 0 { 82 | sb.WriteString("WHERE ") 83 | } else { 84 | sb.WriteString("AND ") 85 | } 86 | 87 | sb.WriteByte('(') 88 | sb.WriteString(filter.expression) 89 | sb.WriteByte(')') 90 | sb.WriteByte('\n') 91 | 92 | args = append(args, filter.args...) 93 | } 94 | 95 | if b.order != "" { 96 | sb.WriteString("ORDER BY ") 97 | sb.WriteString(b.order) 98 | sb.WriteByte('\n') 99 | } 100 | 101 | if b.limit > 0 { 102 | sb.WriteString("LIMIT ") 103 | sb.WriteString(strconv.Itoa(b.limit)) 104 | sb.WriteByte('\n') 105 | } 106 | 107 | if b.offset > 0 { 108 | sb.WriteString("OFFSET ") 109 | sb.WriteString(strconv.Itoa(b.offset)) 110 | sb.WriteByte('\n') 111 | } 112 | 113 | return sb.String(), args 114 | } 115 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | package sqlc 2 | 3 | import "context" 4 | 5 | type builderContextKey struct{} 6 | 7 | func WithBuilder(ctx context.Context, b *Builder) context.Context { 8 | return context.WithValue(ctx, builderContextKey{}, b) 9 | } 10 | 11 | func BuilderFrom(ctx context.Context) (*Builder, bool) { 12 | b, ok := ctx.Value(builderContextKey{}).(*Builder) 13 | return b, ok 14 | } 15 | 16 | func Build(ctx context.Context, f func(builder *Builder)) context.Context { 17 | b, ok := BuilderFrom(ctx) 18 | if !ok { 19 | b = &Builder{} 20 | } else { 21 | b = b.clone() 22 | } 23 | 24 | f(b) 25 | return WithBuilder(ctx, b) 26 | } 27 | -------------------------------------------------------------------------------- /db.go: -------------------------------------------------------------------------------- 1 | package sqlc 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | 7 | "github.com/yiplee/nap" 8 | ) 9 | 10 | var _ DBTX = (*DB)(nil) 11 | 12 | type DB struct { 13 | *nap.DB 14 | } 15 | 16 | func Connect(driverName, master string, slaves ...string) (*DB, error) { 17 | db, err := nap.Open(driverName, master, slaves...) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | if err := db.Ping(); err != nil { 23 | return nil, err 24 | } 25 | 26 | return &DB{db}, nil 27 | } 28 | 29 | // PrepareContext creates a prepared statement for later queries or executions. 30 | // 31 | // Read or update can not be determined by the query string currently, use master database. 32 | func (db *DB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) { 33 | return db.Master().PrepareContext(ctx, query) 34 | } 35 | -------------------------------------------------------------------------------- /example/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.13.0 4 | 5 | package example 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | ) 11 | 12 | type DBTX interface { 13 | ExecContext(context.Context, string, ...interface{}) (sql.Result, error) 14 | PrepareContext(context.Context, string) (*sql.Stmt, error) 15 | QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) 16 | QueryRowContext(context.Context, string, ...interface{}) *sql.Row 17 | } 18 | 19 | func New(db DBTX) *Queries { 20 | return &Queries{db: db} 21 | } 22 | 23 | type Queries struct { 24 | db DBTX 25 | } 26 | 27 | func (q *Queries) WithTx(tx *sql.Tx) *Queries { 28 | return &Queries{ 29 | db: tx, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.13.0 4 | 5 | package example 6 | 7 | import ( 8 | "database/sql" 9 | ) 10 | 11 | type Author struct { 12 | ID int64 13 | Name string 14 | Age int32 15 | Bio sql.NullString 16 | } 17 | -------------------------------------------------------------------------------- /example/query.sql: -------------------------------------------------------------------------------- 1 | -- Example queries for sqlc 2 | CREATE TABLE authors 3 | ( 4 | id BIGSERIAL PRIMARY KEY, 5 | name text NOT NULL, 6 | age integer NOT NULL, 7 | bio text 8 | ); 9 | 10 | -- name: GetAuthor :one 11 | SELECT * 12 | FROM authors 13 | WHERE id = $1 14 | LIMIT 1; 15 | 16 | -- name: ListAuthors :many 17 | SELECT * 18 | FROM authors; 19 | -------------------------------------------------------------------------------- /example/query.sql.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.13.0 4 | // source: query.sql 5 | 6 | package example 7 | 8 | import ( 9 | "context" 10 | ) 11 | 12 | const getAuthor = `-- name: GetAuthor :one 13 | SELECT id, name, age, bio 14 | FROM authors 15 | WHERE id = $1 16 | LIMIT 1 17 | ` 18 | 19 | func (q *Queries) GetAuthor(ctx context.Context, id int64) (Author, error) { 20 | row := q.db.QueryRowContext(ctx, getAuthor, id) 21 | var i Author 22 | err := row.Scan( 23 | &i.ID, 24 | &i.Name, 25 | &i.Age, 26 | &i.Bio, 27 | ) 28 | return i, err 29 | } 30 | 31 | const listAuthors = `-- name: ListAuthors :many 32 | SELECT id, name, age, bio 33 | FROM authors 34 | ` 35 | 36 | func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) { 37 | rows, err := q.db.QueryContext(ctx, listAuthors) 38 | if err != nil { 39 | return nil, err 40 | } 41 | defer rows.Close() 42 | var items []Author 43 | for rows.Next() { 44 | var i Author 45 | if err := rows.Scan( 46 | &i.ID, 47 | &i.Name, 48 | &i.Age, 49 | &i.Bio, 50 | ); err != nil { 51 | return nil, err 52 | } 53 | items = append(items, i) 54 | } 55 | if err := rows.Close(); err != nil { 56 | return nil, err 57 | } 58 | if err := rows.Err(); err != nil { 59 | return nil, err 60 | } 61 | return items, nil 62 | } 63 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/yiplee/sqlc 2 | 3 | go 1.13 4 | 5 | require github.com/yiplee/nap v1.0.1 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-sqlite3 v1.14.13 h1:1tj15ngiFfcZzii7yd82foL+ks+ouQcj8j/TPq3fk1I= 2 | github.com/mattn/go-sqlite3 v1.14.13/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 3 | github.com/yiplee/nap v1.0.1 h1:5p8KAIkYy+PIMGSk+ScF13Hh/OFkIEBHPuD14OFvStg= 4 | github.com/yiplee/nap v1.0.1/go.mod h1:7Zvro/en8ARhkqgv3vpj037yJSBRvGeNyj5Np5XUFgc= 5 | golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4= 6 | golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 7 | -------------------------------------------------------------------------------- /interface.go: -------------------------------------------------------------------------------- 1 | package sqlc 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | ) 7 | 8 | type DBTX interface { 9 | ExecContext(context.Context, string, ...interface{}) (sql.Result, error) 10 | PrepareContext(context.Context, string) (*sql.Stmt, error) 11 | QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) 12 | QueryRowContext(context.Context, string, ...interface{}) *sql.Row 13 | } 14 | -------------------------------------------------------------------------------- /sqlc.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "packages": [ 4 | { 5 | "path": "example", 6 | "engine": "postgresql", 7 | "schema": "example/query.sql", 8 | "queries": "example/query.sql" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /wrap.go: -------------------------------------------------------------------------------- 1 | package sqlc 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | ) 7 | 8 | var _ DBTX = (*wrappedDB)(nil) 9 | 10 | func Wrap(db DBTX) DBTX { 11 | return &wrappedDB{db} 12 | } 13 | 14 | type wrappedDB struct { 15 | DBTX 16 | } 17 | 18 | func (w wrappedDB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { 19 | if b, ok := BuilderFrom(ctx); ok { 20 | query, args = b.Build(query, args...) 21 | } 22 | 23 | return w.DBTX.ExecContext(ctx, query, args...) 24 | } 25 | 26 | func (w wrappedDB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { 27 | if b, ok := BuilderFrom(ctx); ok { 28 | query, args = b.Build(query, args...) 29 | } 30 | 31 | return w.DBTX.QueryContext(ctx, query, args...) 32 | } 33 | 34 | func (w wrappedDB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { 35 | if b, ok := BuilderFrom(ctx); ok { 36 | query, args = b.Build(query, args...) 37 | } 38 | 39 | return w.DBTX.QueryRowContext(ctx, query, args...) 40 | } 41 | -------------------------------------------------------------------------------- /wrap_test.go: -------------------------------------------------------------------------------- 1 | package sqlc_test 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "log" 7 | 8 | "github.com/yiplee/sqlc" 9 | "github.com/yiplee/sqlc/example" 10 | ) 11 | 12 | func Example_build() { 13 | ctx := context.Background() 14 | 15 | db, err := sql.Open("postgres", "dsn") 16 | if err != nil { 17 | panic(err) 18 | } 19 | defer db.Close() 20 | 21 | query := example.New(sqlc.Wrap(db)) 22 | authors, err := query.ListAuthors(sqlc.Build(ctx, func(b *sqlc.Builder) { 23 | b.Where("age > $1", 10) 24 | b.Where("name = $2", "foo") 25 | b.In("id", 1, 2, 3) 26 | b.Order("age,name DESC") 27 | b.Limit(10) 28 | })) 29 | 30 | if err != nil { 31 | log.Fatalln("ListAuthors", err) 32 | } 33 | 34 | log.Printf("list %d authors", len(authors)) 35 | } 36 | --------------------------------------------------------------------------------