├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── dbhub.go ├── dbhub_test.go ├── examples ├── column_details │ └── main.go ├── delete_database │ └── main.go ├── diff_commits │ └── main.go ├── download_database │ └── main.go ├── list_branches │ └── main.go ├── list_commits │ └── main.go ├── list_databases │ └── main.go ├── list_indexes │ └── main.go ├── list_releases │ └── main.go ├── list_tables │ └── main.go ├── list_tags │ └── main.go ├── list_views │ └── main.go ├── metadata │ └── main.go ├── sql_query │ └── main.go ├── upload │ ├── example.db │ └── main.go └── webpage │ └── main.go ├── go.mod ├── go.sum ├── http.go ├── server_types.go └── types.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | 17 | - name: Install NodeJS 20 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 20 21 | 22 | # Build and start the DBHub.io server daemons 23 | - name: Checkout the DBHub.io source code 24 | uses: actions/checkout@v4 25 | with: 26 | repository: 'sqlitebrowser/dbhub.io' 27 | path: daemons 28 | 29 | - name: Build the DBHub.io daemons 30 | run: cd daemons; yarn docker:build 31 | 32 | - name: Update the daemon config file 33 | run: cd daemons; sed -i 's/bind_address = ":9444"/bind_address = "0.0.0.0:9444"/' docker/config.toml 34 | 35 | - name: Start the DBHub.io daemons 36 | run: cd daemons; docker run -itd --rm --name dbhub-build --net host dbhub-build:latest && sleep 5 37 | 38 | # Build and test the go-dbhub library 39 | - name: Checkout go-dbhub library source code 40 | uses: actions/checkout@v4 41 | with: 42 | path: main 43 | 44 | - name: Set up Go for go-dbhub library 45 | uses: actions/setup-go@v4 46 | with: 47 | go-version: '1.20' 48 | 49 | - name: Build the go-dbhub library 50 | run: cd main; go build -v 51 | 52 | - name: Test the go-dbhub library 53 | run: cd main; go test -v 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # IDE project files 15 | .idea 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 sqlitebrowser 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 | [![GoDoc](https://godoc.org/github.com/sqlitebrowser/go-dbhub?status.svg)](https://godoc.org/github.com/sqlitebrowser/go-dbhub) 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/sqlitebrowser/go-dbhub)](https://goreportcard.com/report/github.com/sqlitebrowser/go-dbhub) 3 | 4 | A Go library for accessing and using SQLite databases stored remotely on DBHub.io 5 | 6 | ### What works now 7 | 8 | * (Experimental) Upload, delete, and list your "Live" databases 9 | * (Experimental) Execute INSERT/UPDATE/DELETE statements on your "Live" databases 10 | * (Experimental) List the tables, views, indexes, and columns in your "Live" databases 11 | * Run read-only queries (eg SELECT statements) on databases, returning the results as JSON 12 | * Upload and download your databases 13 | * List the databases in your account 14 | * List the tables, views, and indexes present in a database 15 | * List the columns in a table, view or index, along with their details 16 | * List the branches, releases, tags, and commits for a database 17 | * Generate diffs between two databases, or database revisions 18 | * Download the database metadata (size, branches, commit list, etc.) 19 | * Retrieve the web page URL of a database 20 | 21 | ### Still to do 22 | 23 | * Have the backend server correctly use the incoming branch, release, and tag information 24 | * Tests for each function 25 | * Investigate what would be needed for this to work through the Go SQL API 26 | * Probably need to improve the Query approach, to at least support placeholders and argument parameters 27 | * Anything else people suggest and seems like a good idea :smile: 28 | 29 | ### Requirements 30 | 31 | * [Go](https://golang.org/dl/) version 1.17 or above 32 | * A DBHub.io API key 33 | * These can be generated in your [Settings](https://dbhub.io/pref) page, when logged in. 34 | 35 | ### Example code 36 | 37 | #### Create a new DBHub.io API object 38 | 39 | ``` 40 | db, err := dbhub.New("YOUR_API_KEY_HERE") 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | ``` 45 | 46 | #### Retrieve the list of tables in a remote database 47 | ``` 48 | // Run the `Tables()` function on the new API object 49 | tables, err := db.Tables("justinclift", "Join Testing.sqlite", dbhub.Identifier{Branch: "master"}) 50 | if err != nil { 51 | log.Fatal(err) 52 | } 53 | 54 | // Display the retrieved list of tables 55 | fmt.Println("Tables:") 56 | for _, j := range tables { 57 | fmt.Printf(" * %s\n", j) 58 | } 59 | ``` 60 | 61 | ##### Output 62 | ``` 63 | Tables: 64 | * table1 65 | * table2 66 | ``` 67 | 68 | #### Run a SQL query on a remote database 69 | ``` 70 | // Do we want to display BLOBs as base64? 71 | showBlobs := false 72 | 73 | // Run the query 74 | result, err := db.Query("justinclift", "Join Testing.sqlite", 75 | dbhub.Identifier{ Branch: "master" }, showBlobs, 76 | `SELECT table1.Name, table2.value 77 | FROM table1 JOIN table2 78 | USING (id) 79 | ORDER BY table1.id`) 80 | if err != nil { 81 | log.Fatal(err) 82 | } 83 | fmt.Printf("Query results (JSON):\n\t%v\n", result) 84 | fmt.Println() 85 | ``` 86 | 87 | ##### Output 88 | ``` 89 | Query results (JSON): 90 | {[{[Foo 5]} {[Bar 10]} {[Baz 15]} {[Blumph 12.5000]} {[Blargo 8]} {[Batty 3]}]} 91 | ``` 92 | 93 | #### Generate and display the difference between two commits of a remote database 94 | ``` 95 | // The databases we want to see differences for 96 | db1Owner := "justinclift" 97 | db1Name := "Join Testing.sqlite" 98 | db1Commit := dbhub.Identifier{ 99 | CommitID: "c82ba65add364427e9af3f540be8bf98e8cd6bdb825b07c334858e816c983db0" } 100 | db2Owner := "" 101 | db2Name := "" 102 | db2Commit := dbhub.Identifier{ 103 | CommitID: "adf78104254ece17ff40dab80ae800574fa5d429a4869792a64dcf2027cd9cd9" } 104 | 105 | // Create the diff 106 | diffs, err := db.Diff(db1Owner, db1Name, db1Commit, db2Owner, db2Name, db2Commit, 107 | dbhub.PreservePkMerge) 108 | if err != nil { 109 | log.Fatal(err) 110 | } 111 | 112 | // Display the diff 113 | fmt.Printf("SQL statements for turning the first commit into the second:\n") 114 | for _, i := range diffs.Diff { 115 | if i.Schema != nil { 116 | fmt.Printf("%s\n", i.Schema.Sql) 117 | } 118 | for _, j := range i.Data { 119 | fmt.Printf("%s\n", j.Sql) 120 | } 121 | } 122 | ``` 123 | 124 | ##### Output 125 | ``` 126 | SQL statements for turning the first commit into the second: 127 | CREATE VIEW joinedView AS 128 | SELECT table1.Name, table2.value 129 | FROM table1 JOIN table2 130 | USING (id) 131 | ORDER BY table1.id; 132 | ``` 133 | 134 | ### Further examples 135 | 136 | * [SQL Query](https://github.com/sqlitebrowser/go-dbhub/blob/master/examples/sql_query/main.go) - Run a SQL query, return the results as JSON 137 | * [List databases](https://github.com/sqlitebrowser/go-dbhub/blob/master/examples/list_databases/main.go) - List the databases present in your account 138 | * [List tables](https://github.com/sqlitebrowser/go-dbhub/blob/master/examples/list_tables/main.go) - List the tables present in a database 139 | * [List views](https://github.com/sqlitebrowser/go-dbhub/blob/master/examples/list_views/main.go) - List the views present in a database 140 | * [List indexes](https://github.com/sqlitebrowser/go-dbhub/blob/master/examples/list_indexes/main.go) - List the indexes present in a database 141 | * [Retrieve column details](https://github.com/sqlitebrowser/go-dbhub/blob/master/examples/column_details/main.go) - Retrieve the details of columns in a table 142 | * [List branches](https://github.com/sqlitebrowser/go-dbhub/blob/master/examples/list_branches/main.go) - List all branches of a database 143 | * [List releases](https://github.com/sqlitebrowser/go-dbhub/blob/master/examples/list_releases/main.go) - Display the releases for a database 144 | * [List tags](https://github.com/sqlitebrowser/go-dbhub/blob/master/examples/list_tags/main.go) - Display the tags for a database 145 | * [List commits](https://github.com/sqlitebrowser/go-dbhub/blob/master/examples/list_commits/main.go) - Display the commits for a database 146 | * [Generate diff between two revisions](https://github.com/sqlitebrowser/go-dbhub/blob/master/examples/diff_commits/main.go) - Figure out the differences between two databases or two versions of one database 147 | * [Upload database](https://github.com/sqlitebrowser/go-dbhub/blob/master/examples/upload/main.go) - Upload a new database file 148 | * [Download database](https://github.com/sqlitebrowser/go-dbhub/blob/master/examples/download_database/main.go) - Download the complete database file 149 | * [Delete database](https://github.com/sqlitebrowser/go-dbhub/blob/master/examples/delete_database/main.go) - Delete a database 150 | * [Retrieve metadata](https://github.com/sqlitebrowser/go-dbhub/blob/master/examples/metadata/main.go) - Download the database metadata (size, branches, commit list, etc) 151 | * [Web page](https://github.com/sqlitebrowser/go-dbhub/blob/master/examples/webpage/main.go) - Get the URL of the database file in the webUI. eg. for web browsers 152 | 153 | Please try it out, submits PRs to extend or fix things, and report any weirdness or bugs you encounter. :smile: 154 | -------------------------------------------------------------------------------- /dbhub.go: -------------------------------------------------------------------------------- 1 | package dbhub 2 | 3 | // A Go library for working with databases on DBHub.io 4 | 5 | import ( 6 | "encoding/base64" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "net/url" 11 | "time" 12 | ) 13 | 14 | const ( 15 | version = "0.1.0" 16 | ) 17 | 18 | // New creates a new DBHub.io connection object. It doesn't connect to DBHub.io to do this. Connection only occurs 19 | // when subsequent functions (eg Query()) are called. 20 | func New(key string) (Connection, error) { 21 | c := Connection{ 22 | APIKey: key, 23 | Server: "https://api.dbhub.io", 24 | VerifyServerCert: true, 25 | } 26 | return c, nil 27 | } 28 | 29 | // ChangeAPIKey updates the API key used for authenticating with DBHub.io. 30 | func (c *Connection) ChangeAPIKey(k string) { 31 | c.APIKey = k 32 | } 33 | 34 | // ChangeServer changes the address for communicating with DBHub.io. Useful for testing and development. 35 | func (c *Connection) ChangeServer(s string) { 36 | c.Server = s 37 | } 38 | 39 | // ChangeVerifyServerCert changes whether to verify the server provided https certificate. Useful for testing and development. 40 | func (c *Connection) ChangeVerifyServerCert(b bool) { 41 | c.VerifyServerCert = b 42 | } 43 | 44 | // Branches returns a list of all available branches of a database along with the name of the default branch 45 | func (c Connection) Branches(dbOwner, dbName string) (branches map[string]BranchEntry, defaultBranch string, err error) { 46 | // Prepare the API parameters 47 | data := c.PrepareVals(dbOwner, dbName, Identifier{}) 48 | 49 | // Fetch the list of branches and the default branch 50 | var response BranchListResponseContainer 51 | queryUrl := c.Server + "/v1/branches" 52 | err = sendRequestJSON(queryUrl, c.VerifyServerCert, data, &response) 53 | 54 | // Extract information for return values 55 | branches = response.Branches 56 | defaultBranch = response.DefaultBranch 57 | return 58 | } 59 | 60 | // Columns returns the column information for a given table or view 61 | func (c Connection) Columns(dbOwner, dbName string, ident Identifier, table string) (columns []APIJSONColumn, err error) { 62 | // Prepare the API parameters 63 | data := c.PrepareVals(dbOwner, dbName, ident) 64 | data.Set("table", table) 65 | 66 | // Fetch the list of columns 67 | queryUrl := c.Server + "/v1/columns" 68 | err = sendRequestJSON(queryUrl, c.VerifyServerCert, data, &columns) 69 | return 70 | } 71 | 72 | // Commits returns the details of all commits for a database 73 | func (c Connection) Commits(dbOwner, dbName string) (commits map[string]CommitEntry, err error) { 74 | // Prepare the API parameters 75 | data := c.PrepareVals(dbOwner, dbName, Identifier{}) 76 | 77 | // Fetch the commits 78 | queryUrl := c.Server + "/v1/commits" 79 | err = sendRequestJSON(queryUrl, c.VerifyServerCert, data, &commits) 80 | return 81 | } 82 | 83 | // Databases returns the list of standard databases in your account 84 | func (c Connection) Databases() (databases []string, err error) { 85 | // Prepare the API parameters 86 | data := url.Values{} 87 | data.Set("apikey", c.APIKey) 88 | 89 | // Fetch the list of databases 90 | queryUrl := c.Server + "/v1/databases" 91 | err = sendRequestJSON(queryUrl, c.VerifyServerCert, data, &databases) 92 | return 93 | } 94 | 95 | // DatabasesLive returns the list of Live databases in your account 96 | func (c Connection) DatabasesLive() (databases []string, err error) { 97 | // Prepare the API parameters 98 | data := url.Values{} 99 | data.Set("apikey", c.APIKey) 100 | data.Set("live", "true") 101 | 102 | // Fetch the list of databases 103 | queryUrl := c.Server + "/v1/databases" 104 | err = sendRequestJSON(queryUrl, c.VerifyServerCert, data, &databases) 105 | return 106 | } 107 | 108 | // Delete deletes a database in your account 109 | func (c Connection) Delete(dbName string) (err error) { 110 | // Prepare the API parameters 111 | data := c.PrepareVals("", dbName, Identifier{}) 112 | 113 | // Delete the database 114 | queryUrl := c.Server + "/v1/delete" 115 | err = sendRequestJSON(queryUrl, c.VerifyServerCert, data, nil) 116 | if err != nil && err.Error() == "no rows in result set" { // Feels like a dodgy workaround 117 | err = fmt.Errorf("Unknown database\n") 118 | } 119 | return 120 | } 121 | 122 | // Diff returns the differences between two commits of two databases, or if the details on the second database are left empty, 123 | // between two commits of the same database. You can also specify the merge strategy used for the generated SQL statements. 124 | func (c Connection) Diff(dbOwnerA, dbNameA string, identA Identifier, dbOwnerB, dbNameB string, identB Identifier, merge MergeStrategy) (diffs Diffs, err error) { 125 | // Prepare the API parameters 126 | data := url.Values{} 127 | data.Set("apikey", c.APIKey) 128 | data.Set("dbowner_a", dbOwnerA) 129 | data.Set("dbname_a", dbNameA) 130 | if identA.Branch != "" { 131 | data.Set("branch_a", identA.Branch) 132 | } 133 | if identA.CommitID != "" { 134 | data.Set("commit_a", identA.CommitID) 135 | } 136 | if identA.Release != "" { 137 | data.Set("release_a", identA.Release) 138 | } 139 | if identA.Tag != "" { 140 | data.Set("tag_a", identA.Tag) 141 | } 142 | data.Set("dbowner_b", dbOwnerB) 143 | data.Set("dbname_b", dbNameB) 144 | if identB.Branch != "" { 145 | data.Set("branch_b", identB.Branch) 146 | } 147 | if identB.CommitID != "" { 148 | data.Set("commit_b", identB.CommitID) 149 | } 150 | if identB.Release != "" { 151 | data.Set("release_b", identB.Release) 152 | } 153 | if identB.Tag != "" { 154 | data.Set("tag_b", identB.Tag) 155 | } 156 | if merge == PreservePkMerge { 157 | data.Set("merge", "preserve_pk") 158 | } else if merge == NewPkMerge { 159 | data.Set("merge", "new_pk") 160 | } else { 161 | data.Set("merge", "none") 162 | } 163 | 164 | // Fetch the diffs 165 | queryUrl := c.Server + "/v1/diff" 166 | err = sendRequestJSON(queryUrl, c.VerifyServerCert, data, &diffs) 167 | return 168 | } 169 | 170 | // Download returns the database file 171 | func (c Connection) Download(dbOwner, dbName string, ident Identifier) (db io.ReadCloser, err error) { 172 | // Prepare the API parameters 173 | data := c.PrepareVals(dbOwner, dbName, ident) 174 | 175 | // Fetch the database file 176 | queryUrl := c.Server + "/v1/download" 177 | db, err = sendRequest(queryUrl, c.VerifyServerCert, data) 178 | if err != nil { 179 | return 180 | } 181 | return 182 | } 183 | 184 | // Execute executes a SQL statement (INSERT, UPDATE, DELETE) on the chosen database. 185 | func (c Connection) Execute(dbOwner, dbName string, sql string) (rowsChanged int, err error) { 186 | // Prepare the API parameters 187 | data := c.PrepareVals(dbOwner, dbName, Identifier{}) 188 | data.Set("sql", base64.StdEncoding.EncodeToString([]byte(sql))) 189 | 190 | // Run the query on the remote database 191 | var execResponse ExecuteResponseContainer 192 | queryUrl := c.Server + "/v1/execute" 193 | err = sendRequestJSON(queryUrl, c.VerifyServerCert, data, &execResponse) 194 | if err != nil { 195 | return 196 | } 197 | rowsChanged = execResponse.RowsChanged 198 | return 199 | } 200 | 201 | // Indexes returns the list of indexes present in the database, along with the table they belong to 202 | func (c Connection) Indexes(dbOwner, dbName string, ident Identifier) (idx []APIJSONIndex, err error) { 203 | // Prepare the API parameters 204 | data := c.PrepareVals(dbOwner, dbName, ident) 205 | 206 | // Fetch the list of indexes 207 | queryUrl := c.Server + "/v1/indexes" 208 | err = sendRequestJSON(queryUrl, c.VerifyServerCert, data, &idx) 209 | return 210 | } 211 | 212 | // Metadata returns the metadata (branches, releases, tags, commits, etc) for the database 213 | func (c Connection) Metadata(dbOwner, dbName string) (meta MetadataResponseContainer, err error) { 214 | // Prepare the API parameters 215 | data := c.PrepareVals(dbOwner, dbName, Identifier{}) 216 | 217 | // Fetch the list of databases 218 | queryUrl := c.Server + "/v1/metadata" 219 | err = sendRequestJSON(queryUrl, c.VerifyServerCert, data, &meta) 220 | return 221 | } 222 | 223 | // PrepareVals creates an url.Values container holding the API key, database owner, name, and database identifier. The 224 | // url.Values container is then used for the requests to DBHub.io. 225 | func (c Connection) PrepareVals(dbOwner, dbName string, ident Identifier) (data url.Values) { 226 | // Prepare the API parameters 227 | data = url.Values{} 228 | if c.APIKey != "" { 229 | data.Set("apikey", c.APIKey) 230 | } 231 | if dbOwner != "" { 232 | data.Set("dbowner", dbOwner) 233 | } 234 | if dbName != "" { 235 | data.Set("dbname", dbName) 236 | } 237 | if ident.Branch != "" { 238 | data.Set("branch", ident.Branch) 239 | } 240 | if ident.CommitID != "" { 241 | data.Set("commit", ident.CommitID) 242 | } 243 | if ident.Release != "" { 244 | data.Set("release", ident.Release) 245 | } 246 | if ident.Tag != "" { 247 | data.Set("tag", ident.Tag) 248 | } 249 | return 250 | } 251 | 252 | // Query runs a SQL query (SELECT only) on the chosen database, returning the results. 253 | // The "blobBase64" boolean specifies whether BLOB data fields should be base64 encoded in the output, or just skipped 254 | // using an empty string as a placeholder. 255 | func (c Connection) Query(dbOwner, dbName string, ident Identifier, blobBase64 bool, sql string) (out Results, err error) { 256 | // Prepare the API parameters 257 | data := c.PrepareVals(dbOwner, dbName, ident) 258 | data.Set("sql", base64.StdEncoding.EncodeToString([]byte(sql))) 259 | 260 | // Run the query on the remote database 261 | var returnedData []DataRow 262 | queryUrl := c.Server + "/v1/query" 263 | err = sendRequestJSON(queryUrl, c.VerifyServerCert, data, &returnedData) 264 | if err != nil { 265 | return 266 | } 267 | 268 | // Loop through the results, converting it to a more concise output format 269 | for _, j := range returnedData { 270 | 271 | // Construct a single row 272 | var oneRow ResultRow 273 | for _, l := range j { 274 | switch l.Type { 275 | case Float, Integer, Text: 276 | // Float, integer, and text fields are added to the output 277 | oneRow.Fields = append(oneRow.Fields, fmt.Sprint(l.Value)) 278 | case Binary: 279 | // BLOB data is optionally Base64 encoded, or just skipped (using an empty string as placeholder) 280 | if blobBase64 { 281 | // Safety check. Make sure we've received a string 282 | if s, ok := l.Value.(string); ok { 283 | oneRow.Fields = append(oneRow.Fields, base64.StdEncoding.EncodeToString([]byte(s))) 284 | } else { 285 | oneRow.Fields = append(oneRow.Fields, fmt.Sprintf("unexpected data type '%T' for returned BLOB", l.Value)) 286 | } 287 | } else { 288 | oneRow.Fields = append(oneRow.Fields, "") 289 | } 290 | default: 291 | // All other value types are just output as an empty string (for now) 292 | oneRow.Fields = append(oneRow.Fields, "") 293 | } 294 | } 295 | 296 | // Add the row to the output list 297 | out.Rows = append(out.Rows, oneRow) 298 | } 299 | return 300 | } 301 | 302 | // Releases returns the details of all releases for a database 303 | func (c Connection) Releases(dbOwner, dbName string) (releases map[string]ReleaseEntry, err error) { 304 | // Prepare the API parameters 305 | data := c.PrepareVals(dbOwner, dbName, Identifier{}) 306 | 307 | // Fetch the releases 308 | queryUrl := c.Server + "/v1/releases" 309 | err = sendRequestJSON(queryUrl, c.VerifyServerCert, data, &releases) 310 | return 311 | } 312 | 313 | // Tables returns the list of tables in the database 314 | func (c Connection) Tables(dbOwner, dbName string, ident Identifier) (tbl []string, err error) { 315 | // Prepare the API parameters 316 | data := c.PrepareVals(dbOwner, dbName, ident) 317 | 318 | // Fetch the list of tables 319 | queryUrl := c.Server + "/v1/tables" 320 | err = sendRequestJSON(queryUrl, c.VerifyServerCert, data, &tbl) 321 | return 322 | } 323 | 324 | // Tags returns the details of all tags for a database 325 | func (c Connection) Tags(dbOwner, dbName string) (tags map[string]TagEntry, err error) { 326 | // Prepare the API parameters 327 | data := c.PrepareVals(dbOwner, dbName, Identifier{}) 328 | 329 | // Fetch the tags 330 | queryUrl := c.Server + "/v1/tags" 331 | err = sendRequestJSON(queryUrl, c.VerifyServerCert, data, &tags) 332 | return 333 | } 334 | 335 | // Views returns the list of views in the database 336 | func (c Connection) Views(dbOwner, dbName string, ident Identifier) (views []string, err error) { 337 | // Prepare the API parameters 338 | data := c.PrepareVals(dbOwner, dbName, ident) 339 | 340 | // Fetch the list of views 341 | queryUrl := c.Server + "/v1/views" 342 | err = sendRequestJSON(queryUrl, c.VerifyServerCert, data, &views) 343 | return 344 | } 345 | 346 | // Upload uploads a new standard database, or a new revision of a database 347 | func (c Connection) Upload(dbName string, info UploadInformation, dbBytes *[]byte) (err error) { 348 | // Prepare the API parameters 349 | data := c.PrepareVals("", dbName, info.Ident) 350 | data.Del("dbowner") // The upload function always stores the database in the account of the API key user 351 | if info.CommitMsg != "" { 352 | data.Set("commitmsg", info.CommitMsg) 353 | } 354 | if info.SourceURL != "" { 355 | data.Set("sourceurl", info.SourceURL) 356 | } 357 | if !info.LastModified.IsZero() { 358 | data.Set("lastmodified", info.LastModified.Format(time.RFC3339)) 359 | } 360 | if info.Licence != "" { 361 | data.Set("licence", info.Licence) 362 | } 363 | if info.Public != "" { 364 | data.Set("public", info.Public) 365 | } 366 | if info.Force { 367 | data.Set("force", "true") 368 | } 369 | if !info.CommitTimestamp.IsZero() { 370 | data.Set("committimestamp", info.CommitTimestamp.Format(time.RFC3339)) 371 | } 372 | if info.AuthorName != "" { 373 | data.Set("authorname", info.AuthorName) 374 | } 375 | if info.AuthorEmail != "" { 376 | data.Set("authoremail", info.AuthorEmail) 377 | } 378 | if info.CommitterName != "" { 379 | data.Set("committername", info.CommitterName) 380 | } 381 | if info.CommitterEmail != "" { 382 | data.Set("committeremail", info.CommitterEmail) 383 | } 384 | if info.OtherParents != "" { 385 | data.Set("otherparents", info.OtherParents) 386 | } 387 | if info.ShaSum != "" { 388 | data.Set("dbshasum", info.ShaSum) 389 | } 390 | 391 | // Upload the database 392 | var body io.ReadCloser 393 | queryUrl := c.Server + "/v1/upload" 394 | body, err = sendUpload(queryUrl, c.VerifyServerCert, &data, dbBytes) 395 | if body != nil { 396 | defer body.Close() 397 | } 398 | if err != nil { 399 | if body != nil { 400 | // If there's useful error info in the returned JSON, return that as the error message 401 | var z JSONError 402 | err = json.NewDecoder(body).Decode(&z) 403 | if err != nil { 404 | return 405 | } 406 | err = fmt.Errorf("%s", z.Msg) 407 | } 408 | } 409 | return 410 | } 411 | 412 | // UploadLive uploads a new Live database 413 | func (c Connection) UploadLive(dbName string, dbBytes *[]byte) (err error) { 414 | // Prepare the API parameters 415 | data := c.PrepareVals("", dbName, Identifier{}) 416 | data.Del("dbowner") // The upload function always stores the database in the account of the API key user 417 | data.Set("live", "true") 418 | 419 | // Upload the database 420 | var body io.ReadCloser 421 | queryUrl := c.Server + "/v1/upload" 422 | body, err = sendUpload(queryUrl, c.VerifyServerCert, &data, dbBytes) 423 | if body != nil { 424 | defer body.Close() 425 | } 426 | if err != nil { 427 | if body != nil { 428 | // If there's useful error info in the returned JSON, return that as the error message 429 | var z JSONError 430 | err = json.NewDecoder(body).Decode(&z) 431 | if err != nil { 432 | return 433 | } 434 | err = fmt.Errorf("%s", z.Msg) 435 | } 436 | } 437 | return 438 | } 439 | 440 | // Webpage returns the URL of the database file in the webUI. eg. for web browsers 441 | func (c Connection) Webpage(dbOwner, dbName string) (webPage WebpageResponseContainer, err error) { 442 | // Prepare the API parameters 443 | data := c.PrepareVals(dbOwner, dbName, Identifier{}) 444 | 445 | // Fetch the releases 446 | queryUrl := c.Server + "/v1/webpage" 447 | err = sendRequestJSON(queryUrl, c.VerifyServerCert, data, &webPage) 448 | return 449 | } 450 | -------------------------------------------------------------------------------- /dbhub_test.go: -------------------------------------------------------------------------------- 1 | package dbhub 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "io" 7 | "log" 8 | "math/rand" 9 | "net/http" 10 | "os" 11 | "path/filepath" 12 | "testing" 13 | "time" 14 | 15 | "github.com/docker/docker/pkg/fileutils" 16 | sqlite "github.com/gwenn/gosqlite" 17 | "github.com/sqlitebrowser/dbhub.io/common" 18 | "github.com/stretchr/testify/assert" 19 | ) 20 | 21 | // For now, the tests require the DBHub.io dev docker container be running on its standard 22 | // ports (that means the API server is listening on https://localhost:9444) 23 | 24 | func TestMain(m *testing.M) { 25 | log.Println("Seeding the database...") 26 | 27 | // Disable https cert validation for our tests 28 | insecureTLS := tls.Config{InsecureSkipVerify: true} 29 | insecureTransport := http.Transport{TLSClientConfig: &insecureTLS} 30 | client := http.Client{Transport: &insecureTransport} 31 | 32 | // Seed the database 33 | resp, err := client.Get("https://localhost:9443/x/test/seed") 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | if resp.StatusCode != 200 { 38 | log.Fatalf("Database seed request returned http code '%d'. Aborting tests.", resp.StatusCode) 39 | } 40 | log.Println("Database seeding completed ok.") 41 | 42 | // Run the tests 43 | log.Println("Running the tests...") 44 | m.Run() 45 | } 46 | 47 | // TestBranches verifies retrieving the branch and default branch information using the API 48 | func TestBranches(t *testing.T) { 49 | // Create the local test server connection 50 | conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw") 51 | 52 | // Retrieve branch details for the database 53 | branches, defaultBranch, err := conn.Branches("default", "Assembly Election 2017 with view.sqlite") 54 | if err != nil { 55 | t.Error(err) 56 | return 57 | } 58 | 59 | // Verify the returned branch information matches what we're expecting 60 | assert.Len(t, branches, 1) 61 | assert.Contains(t, branches, "main") 62 | assert.Equal(t, "main", defaultBranch) 63 | } 64 | 65 | // TestColumns verifies retrieving the list of column names for a database using the API 66 | func TestColumns(t *testing.T) { 67 | // Create the local test server connection 68 | conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw") 69 | 70 | // Retrieve the column info for a table or view in the remote database 71 | table := "Candidate_Names" 72 | columns, err := conn.Columns("default", "Assembly Election 2017 with view.sqlite", Identifier{Branch: "main"}, table) 73 | if err != nil { 74 | log.Fatal(err) 75 | } 76 | 77 | // Verify the returned column information matches what we're expecting 78 | assert.Len(t, columns, 2) 79 | assert.Contains(t, columns[0].Name, "Firstname") 80 | assert.Equal(t, "Surname", columns[1].Name) 81 | } 82 | 83 | // TestCommits verifies retrieving commit information for standard databases using the API 84 | func TestCommits(t *testing.T) { 85 | // Create the local test server connection 86 | conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw") 87 | 88 | // Retrieve the commit info for a remote database 89 | commits, err := conn.Commits("default", "Assembly Election 2017 with view.sqlite") 90 | if err != nil { 91 | log.Fatal(err) 92 | } 93 | 94 | // Verify the returned commit information matches what we're expecting 95 | assert.Len(t, commits, 1) 96 | 97 | // Abort early if the returned length of commits isn't what we're expecting 98 | if len(commits) != 1 { 99 | return 100 | } 101 | 102 | // Retrieve the the first commit id 103 | var ids []string 104 | for id := range commits { 105 | ids = append(ids, id) 106 | } 107 | firstID := ids[0] 108 | 109 | // Verify the commit information is what we're expecting 110 | assert.Equal(t, "default@docker-dev.dbhub.io", commits[firstID].AuthorEmail) 111 | assert.Equal(t, "Default system user", commits[firstID].AuthorName) 112 | assert.Equal(t, "Initial commit", commits[firstID].Message) 113 | assert.Equal(t, "Assembly Election 2017 with view.sqlite", commits[firstID].Tree.Entries[0].Name) 114 | assert.Equal(t, int64(73728), commits[firstID].Tree.Entries[0].Size) 115 | assert.Equal(t, "9cb18719bddb949043abc1ba089dd7c4845ab024ddbe4ad19e9334da4e5b8cdc", commits[firstID].Tree.Entries[0].Sha256) 116 | assert.Equal(t, "9348ddfd44da5a127c59141981954746a860ec8e03e0412cf3af7134af0f97e2", commits[firstID].Tree.Entries[0].LicenceSHA) 117 | } 118 | 119 | // TestDatabases verifies retrieving the list of standard databases using the API 120 | func TestDatabases(t *testing.T) { 121 | // Create the local test server connection 122 | conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw") 123 | 124 | // Retrieve the list of databases for the user 125 | databases, err := conn.Databases() 126 | if err != nil { 127 | t.Errorf("Connecting to the API server failed: %v", err) 128 | return 129 | } 130 | 131 | // If no databases were found, the test failed 132 | if len(databases) == 0 { 133 | t.Error("No databases found") 134 | return 135 | } 136 | 137 | // Verify the expected database names were returned, and only them 138 | assert.Contains(t, databases, "Assembly Election 2017 with view.sqlite") 139 | assert.Contains(t, databases, "Assembly Election 2017.sqlite") 140 | assert.Len(t, databases, 2) 141 | return 142 | } 143 | 144 | // TestDatabases verifies retrieving the list of live databases using the API 145 | func TestDatabasesLive(t *testing.T) { 146 | // Create the local test server connection 147 | conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw") 148 | 149 | // Retrieve the list of live databases for the user 150 | databases, err := conn.DatabasesLive() 151 | if err != nil { 152 | t.Error(err) 153 | return 154 | } 155 | 156 | // If no databases were found, the test failed 157 | if len(databases) == 0 { 158 | t.Error("No databases found") 159 | return 160 | } 161 | 162 | // Verify the expected database name was returned 163 | assert.Contains(t, databases, "Join Testing with index.sqlite") 164 | assert.Len(t, databases, 1) 165 | return 166 | } 167 | 168 | // TestDiff verifies the Diff API call 169 | func TestDiff(t *testing.T) { 170 | // Create the local test server connection 171 | conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw") 172 | 173 | // Read the example database file into memory 174 | dbFile := filepath.Join("examples", "upload", "example.db") 175 | z, err := os.ReadFile(dbFile) 176 | if err != nil { 177 | t.Error(err) 178 | return 179 | } 180 | 181 | // Upload the example database 182 | dbName := "uploadtest1.sqlite" 183 | err = conn.Upload(dbName, UploadInformation{}, &z) 184 | if err != nil { 185 | t.Error(err) 186 | return 187 | } 188 | t.Cleanup(func() { 189 | // Delete the uploaded database when the test exits 190 | err = conn.Delete(dbName) 191 | if err != nil { 192 | t.Error(err) 193 | return 194 | } 195 | }) 196 | 197 | // Copy the database file to a temp location so we can make some changes 198 | newFile := filepath.Join(t.TempDir(), "diff-"+randomString(8)+".sqlite") 199 | _, err = fileutils.CopyFile(dbFile, newFile) 200 | if err != nil { 201 | t.Error(err) 202 | return 203 | } 204 | 205 | // Make some changes to the copied database file 206 | sdb, err := sqlite.Open(newFile, sqlite.OpenReadWrite|sqlite.OpenFullMutex) 207 | if err != nil { 208 | t.Error() 209 | return 210 | } 211 | dbQuery := ` 212 | CREATE TABLE foo (first integer); 213 | INSERT INTO foo (first) values (10); 214 | INSERT INTO foo (first) values (20);` 215 | err = sdb.Exec(dbQuery) 216 | if err != nil { 217 | t.Error() 218 | sdb.Close() 219 | return 220 | } 221 | sdb.Close() 222 | 223 | // Retrieve the initial commit id for the database 224 | dbOwner := "default" 225 | commitMap, err := conn.Commits(dbOwner, dbName) 226 | if err != nil { 227 | t.Error(err) 228 | return 229 | } 230 | var commits []string 231 | for idx := range commitMap { 232 | commits = append(commits, idx) 233 | } 234 | firstCommit := commits[0] 235 | 236 | // Upload the copied file as a new commit 237 | z, err = os.ReadFile(newFile) 238 | if err != nil { 239 | t.Error(err) 240 | return 241 | } 242 | uploadCommit := Identifier{CommitID: firstCommit} 243 | err = conn.Upload(dbName, UploadInformation{Ident: uploadCommit}, &z) 244 | if err != nil { 245 | t.Error(err) 246 | return 247 | } 248 | 249 | // Retrieve the new commit id for the database 250 | commitMap, err = conn.Commits(dbOwner, dbName) 251 | if err != nil { 252 | t.Error(err) 253 | return 254 | } 255 | var secondCommit string 256 | for idx := range commitMap { 257 | if idx != firstCommit { 258 | secondCommit = idx 259 | } 260 | } 261 | 262 | // Do the diff using NewPkMerge 263 | commit1 := Identifier{CommitID: firstCommit} 264 | commit2 := Identifier{CommitID: secondCommit} 265 | diffs, err := conn.Diff(dbOwner, dbName, commit1, "", "", commit2, NewPkMerge) 266 | if err != nil { 267 | t.Error(err) 268 | return 269 | } 270 | 271 | // Verify the changes 272 | assert.Len(t, diffs.Diff, 1) 273 | assert.Equal(t, "foo", diffs.Diff[0].ObjectName) 274 | assert.Equal(t, "table", diffs.Diff[0].ObjectType) 275 | assert.Equal(t, DiffType("add"), diffs.Diff[0].Schema.ActionType) 276 | assert.Equal(t, "CREATE TABLE foo (first integer);", diffs.Diff[0].Schema.Sql) 277 | assert.Equal(t, "", diffs.Diff[0].Schema.Before) 278 | assert.Equal(t, "CREATE TABLE foo (first integer)", diffs.Diff[0].Schema.After) 279 | assert.Equal(t, `INSERT INTO "foo"("first") VALUES(10);`, diffs.Diff[0].Data[0].Sql) 280 | assert.Equal(t, `INSERT INTO "foo"("first") VALUES(20);`, diffs.Diff[0].Data[1].Sql) 281 | 282 | // Diff with PreservePkMerge 283 | diffs, err = conn.Diff(dbOwner, dbName, commit1, "", "", commit2, PreservePkMerge) 284 | if err != nil { 285 | t.Error(err) 286 | return 287 | } 288 | 289 | // Verify the changes 290 | assert.Len(t, diffs.Diff, 1) 291 | assert.Equal(t, "foo", diffs.Diff[0].ObjectName) 292 | assert.Equal(t, "table", diffs.Diff[0].ObjectType) 293 | assert.Equal(t, DiffType("add"), diffs.Diff[0].Schema.ActionType) 294 | assert.Equal(t, "CREATE TABLE foo (first integer);", diffs.Diff[0].Schema.Sql) 295 | assert.Equal(t, "", diffs.Diff[0].Schema.Before) 296 | assert.Equal(t, "CREATE TABLE foo (first integer)", diffs.Diff[0].Schema.After) 297 | assert.Equal(t, `INSERT INTO "foo"("first") VALUES(10);`, diffs.Diff[0].Data[0].Sql) 298 | assert.Equal(t, `INSERT INTO "foo"("first") VALUES(20);`, diffs.Diff[0].Data[1].Sql) 299 | 300 | // Diff with NoMerge 301 | diffs, err = conn.Diff(dbOwner, dbName, commit1, "", "", commit2, NoMerge) 302 | if err != nil { 303 | t.Error(err) 304 | return 305 | } 306 | 307 | // Verify the changes 308 | assert.Len(t, diffs.Diff, 1) 309 | assert.Equal(t, "foo", diffs.Diff[0].ObjectName) 310 | assert.Equal(t, "table", diffs.Diff[0].ObjectType) 311 | assert.Equal(t, DiffType("add"), diffs.Diff[0].Schema.ActionType) 312 | assert.Equal(t, "", diffs.Diff[0].Schema.Sql) 313 | assert.Equal(t, "", diffs.Diff[0].Schema.Before) 314 | assert.Equal(t, "CREATE TABLE foo (first integer)", diffs.Diff[0].Schema.After) 315 | assert.Equal(t, "", diffs.Diff[0].Data[0].Sql) 316 | assert.Equal(t, "", diffs.Diff[0].Data[1].Sql) 317 | } 318 | 319 | // TestExecute verifies the Execute API call 320 | func TestExecute(t *testing.T) { 321 | // Create the local test server connection 322 | conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw") 323 | 324 | // Execute a SQL statement 325 | dbQuery := `INSERT INTO table1 (id, Name) VALUES (7, "Stuff")` 326 | rowsChanged, err := conn.Execute("default", "Join Testing with index.sqlite", dbQuery) 327 | if err != nil { 328 | t.Error(err) 329 | return 330 | } 331 | 332 | // Verify the result 333 | assert.Equal(t, 1, rowsChanged) 334 | 335 | // Execute another SQL statement 336 | dbQuery = `UPDATE table1 SET Name = "New Stuff" WHERE id = 1 OR id = 7` 337 | rowsChanged, err = conn.Execute("default", "Join Testing with index.sqlite", dbQuery) 338 | if err != nil { 339 | t.Error(err) 340 | return 341 | } 342 | 343 | // Verify the result 344 | assert.Equal(t, 2, rowsChanged) 345 | } 346 | 347 | // TestIndexes verifies the Indexes API call 348 | func TestIndexes(t *testing.T) { 349 | // Create the local test server connection 350 | conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw") 351 | 352 | // Retrieve the index information for the database 353 | indexes, err := conn.Indexes("default", "Join Testing with index.sqlite", Identifier{}) 354 | if err != nil { 355 | t.Error(err) 356 | return 357 | } 358 | 359 | // Verify the index information 360 | assert.Len(t, indexes, 1) 361 | assert.Equal(t, "table1", indexes[0].Table) 362 | assert.Equal(t, "stuff", indexes[0].Name) 363 | assert.Empty(t, indexes[0].Columns[0].CID) 364 | assert.Equal(t, "id", indexes[0].Columns[0].Name) 365 | } 366 | 367 | // TestMetadata verifies the metadata API call 368 | func TestMetadata(t *testing.T) { 369 | // Create the local test server connection 370 | conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw") 371 | 372 | // Retrieve the metadata information for the database 373 | meta, err := conn.Metadata("default", "Assembly Election 2017 with view.sqlite") 374 | if err != nil { 375 | t.Error(err) 376 | return 377 | } 378 | 379 | // Get the commit id of the first commit 380 | var firstCommit string 381 | for _, c := range meta.Commits { 382 | firstCommit = c.ID 383 | } 384 | 385 | // Verify the metadata info 386 | assert.Equal(t, "https://docker-dev.dbhub.io:9443/default/Assembly Election 2017 with view.sqlite", meta.WebPage) 387 | assert.Equal(t, "main", meta.DefBranch) 388 | assert.Equal(t, "", meta.Branches["main"].Description) 389 | assert.Equal(t, 1, meta.Branches["main"].CommitCount) 390 | assert.Empty(t, meta.Releases) 391 | assert.Empty(t, meta.Tags) 392 | assert.Equal(t, firstCommit, meta.Commits[firstCommit].ID) 393 | assert.Equal(t, "Initial commit", meta.Commits[firstCommit].Message) 394 | assert.Equal(t, "Default system user", meta.Commits[firstCommit].AuthorName) 395 | assert.Equal(t, "default@docker-dev.dbhub.io", meta.Commits[firstCommit].AuthorEmail) 396 | assert.Equal(t, int64(73728), meta.Commits[firstCommit].Tree.Entries[0].Size) 397 | } 398 | 399 | // TestQuery verifies the Query API call 400 | func TestQuery(t *testing.T) { 401 | // Create the local test server connection 402 | conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw") 403 | 404 | // Query the database 405 | dbQuery := ` 406 | SELECT id, Name 407 | FROM table1 408 | ORDER BY Name DESC` 409 | result, err := conn.Query("default", "Join Testing with index.sqlite", Identifier{}, false, dbQuery) 410 | if err != nil { 411 | t.Error(err) 412 | return 413 | } 414 | 415 | // Verify the result 416 | assert.Len(t, result.Rows, 7) 417 | assert.Contains(t, result.Rows, ResultRow{Fields: []string{"2", "Bar"}}) 418 | assert.Contains(t, result.Rows, ResultRow{Fields: []string{"3", "Baz"}}) 419 | assert.Contains(t, result.Rows, ResultRow{Fields: []string{"4", "Blumph"}}) 420 | assert.Contains(t, result.Rows, ResultRow{Fields: []string{"5", "Blargo"}}) 421 | assert.Contains(t, result.Rows, ResultRow{Fields: []string{"6", "Batty"}}) 422 | } 423 | 424 | // TestReleases verifies the Releases API call 425 | func TestReleases(t *testing.T) { 426 | // Create the local test server connection 427 | conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw") 428 | 429 | // Retrieve the releases for a database 430 | releases, err := conn.Releases("default", "Assembly Election 2017.sqlite") 431 | if err != nil { 432 | t.Error(err) 433 | return 434 | } 435 | 436 | // Verify the retrieved information 437 | assert.Len(t, releases, 2) 438 | assert.Equal(t, "First release", releases["first"].Description) 439 | assert.Equal(t, "Example Releaser", releases["first"].ReleaserName) 440 | assert.Equal(t, "example@example.org", releases["first"].ReleaserEmail) 441 | assert.Equal(t, "Second release", releases["second"].Description) 442 | assert.Equal(t, "Example Releaser", releases["second"].ReleaserName) 443 | assert.Equal(t, "example@example.org", releases["second"].ReleaserEmail) 444 | } 445 | 446 | // TestTables verifies the Tables API call 447 | func TestTables(t *testing.T) { 448 | // Create the local test server connection 449 | conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw") 450 | 451 | // Retrieve table information for a database 452 | tbls, err := conn.Tables("default", "Assembly Election 2017.sqlite", Identifier{}) 453 | if err != nil { 454 | t.Error(err) 455 | return 456 | } 457 | 458 | // Verify the returned information 459 | assert.Len(t, tbls, 3) 460 | assert.Contains(t, tbls, "Candidate_Information") 461 | assert.Contains(t, tbls, "Constituency_Turnout_Information") 462 | assert.Contains(t, tbls, "Elected_Candidates") 463 | } 464 | 465 | // TestTags verifies the Tags API call 466 | func TestTags(t *testing.T) { 467 | // Create the local test server connection 468 | conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw") 469 | 470 | // Retrieve the tags for a database 471 | tags, err := conn.Tags("default", "Assembly Election 2017.sqlite") 472 | if err != nil { 473 | t.Error(err) 474 | return 475 | } 476 | 477 | // Verify the retrieved information 478 | assert.Len(t, tags, 2) 479 | assert.Equal(t, "First tag", tags["first"].Description) 480 | assert.Equal(t, "Example Tagger", tags["first"].TaggerName) 481 | assert.Equal(t, "example@example.org", tags["first"].TaggerEmail) 482 | assert.Equal(t, "Second tag", tags["second"].Description) 483 | assert.Equal(t, "Example Tagger", tags["second"].TaggerName) 484 | assert.Equal(t, "example@example.org", tags["second"].TaggerEmail) 485 | } 486 | 487 | // TestUpload verifies uploading a standard database via the API 488 | func TestUpload(t *testing.T) { 489 | // Create the local test server connection 490 | conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw") 491 | 492 | // Read the example database file into memory 493 | dbFile := filepath.Join("examples", "upload", "example.db") 494 | z, err := os.ReadFile(dbFile) 495 | if err != nil { 496 | t.Error(err) 497 | return 498 | } 499 | 500 | // Upload the example database 501 | dbName := "testupload.sqlite" 502 | err = conn.Upload(dbName, UploadInformation{}, &z) 503 | if err != nil { 504 | t.Error(err) 505 | return 506 | } 507 | 508 | // Verify the file contents. This is done by downloading the database and doing a byte comparison to ensure its 509 | // identical to the upload 510 | downloaded, err := conn.Download("default", dbName, Identifier{}) 511 | if err != nil { 512 | t.Error(err) 513 | return 514 | } 515 | t.Cleanup(func() { 516 | // Delete the uploaded file when the function exits 517 | err = conn.Delete(dbName) 518 | if err != nil { 519 | t.Error(err) 520 | return 521 | } 522 | }) 523 | data, err := io.ReadAll(downloaded) 524 | if err != nil { 525 | t.Error(err) 526 | return 527 | } 528 | result := bytes.Compare(z, data) 529 | if result != 0 { 530 | t.Errorf("Standard database upload succeeded, but failed verification when downloading it and comparing to the original") 531 | return 532 | } 533 | } 534 | 535 | // TestUploadLive verifies uploading a live database via the API 536 | func TestUploadLive(t *testing.T) { 537 | // Create the local test server connection 538 | conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw") 539 | 540 | // Read the example database file into memory 541 | dbA := filepath.Join("examples", "upload", "example.db") 542 | z, err := os.ReadFile(dbA) 543 | if err != nil { 544 | t.Error(err) 545 | return 546 | } 547 | 548 | // Upload the database 549 | dbName := "testuploadlive.sqlite" 550 | err = conn.UploadLive(dbName, &z) 551 | if err != nil { 552 | t.Error(err) 553 | return 554 | } 555 | 556 | // *** Verify the database *** 557 | 558 | // This is done by downloading and comparing (database diff) the database contents with the database file that was uploaded 559 | downloaded, err := conn.Download("default", dbName, Identifier{}) 560 | if err != nil { 561 | t.Error(err) 562 | return 563 | } 564 | t.Cleanup(func() { 565 | // Delete the uploaded database when the test exits 566 | err = conn.Delete(dbName) 567 | if err != nil { 568 | t.Error(err) 569 | return 570 | } 571 | }) 572 | data, err := io.ReadAll(downloaded) 573 | if err != nil { 574 | t.Error(err) 575 | return 576 | } 577 | dbB := filepath.Join(t.TempDir(), "diff-"+randomString(8)+".sqlite") 578 | err = os.WriteFile(dbB, data, 0750) 579 | if err != nil { 580 | t.Error(err) 581 | return 582 | } 583 | 584 | // Do the comparison 585 | diffs, err := common.DBDiff(dbA, dbB, common.NoMerge, false) 586 | if err != nil { 587 | t.Error(err) 588 | return 589 | } 590 | assert.Empty(t, diffs.Diff) 591 | } 592 | 593 | // TestViews verifies the Views API call 594 | func TestViews(t *testing.T) { 595 | // Create the local test server connection 596 | conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw") 597 | 598 | // Get the list of views in the database 599 | views, err := conn.Views("default", "Assembly Election 2017 with view.sqlite", Identifier{}) 600 | if err != nil { 601 | t.Error(err) 602 | return 603 | } 604 | assert.Len(t, views, 1) 605 | assert.Equal(t, "Candidate_Names", views[0]) 606 | } 607 | 608 | // TestWebpage verifies the Webpage API call 609 | func TestWebpage(t *testing.T) { 610 | // Create the local test server connection 611 | conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw") 612 | 613 | // Gather the data then test the result 614 | pageData, err := conn.Webpage("default", "Assembly Election 2017.sqlite") 615 | if err != nil { 616 | t.Error(err) 617 | return 618 | } 619 | assert.Equal(t, "https://docker-dev.dbhub.io:9443/default/Assembly Election 2017.sqlite", pageData.WebPage) 620 | } 621 | 622 | // randomString generates a random alphanumeric string of the desired length 623 | func randomString(length int) string { 624 | rand.Seed(time.Now().UnixNano()) 625 | const alphaNum = "abcdefghijklmnopqrstuvwxyz0123456789" 626 | randomString := make([]byte, length) 627 | for i := range randomString { 628 | randomString[i] = alphaNum[rand.Intn(len(alphaNum))] 629 | } 630 | return string(randomString) 631 | } 632 | 633 | // serverConnection is a utility function that sets up the API connection object to the test server, ready for use 634 | func serverConnection(apiKey string) Connection { 635 | // Create a new DBHub.io API object 636 | db, err := New(apiKey) 637 | if err != nil { 638 | log.Fatal(err) 639 | } 640 | db.ChangeServer("https://localhost:9444") 641 | db.ChangeVerifyServerCert(false) 642 | return db 643 | } 644 | -------------------------------------------------------------------------------- /examples/column_details/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/sqlitebrowser/go-dbhub" 8 | ) 9 | 10 | func main() { 11 | // Create a new DBHub.io API object 12 | db, err := dbhub.New("YOUR_API_KEY_HERE") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | // Retrieve the column info for a table or view in the remote database 18 | table := "table1" 19 | columns, err := db.Columns("justinclift", "Join Testing.sqlite", dbhub.Identifier{Branch: "master"}, table) 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | // Display the retrieved column details 25 | fmt.Printf("Columns on table or view '%s':\n", table) 26 | for _, j := range columns { 27 | fmt.Printf(" * '%v':\n", j.Name) 28 | fmt.Printf(" Cid: %v\n", j.Cid) 29 | fmt.Printf(" Data Type: %v\n", j.DataType) 30 | fmt.Printf(" Default Value: %v\n", j.DfltValue) 31 | fmt.Printf(" Not Null: %v\n", j.NotNull) 32 | fmt.Printf(" Primary Key: %v\n", j.Pk) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/delete_database/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/sqlitebrowser/go-dbhub" 8 | ) 9 | 10 | func main() { 11 | // Create a new DBHub.io API object 12 | db, err := dbhub.New("YOUR_API_KEY_HERE") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | // Delete a remote database 18 | dbName := "Join Testing.sqlite" 19 | err = db.Delete(dbName) 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | // Display a success message 25 | fmt.Printf("Database '%s' deleted\n", dbName) 26 | } 27 | -------------------------------------------------------------------------------- /examples/diff_commits/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/sqlitebrowser/go-dbhub" 8 | ) 9 | 10 | func main() { 11 | // Create a new DBHub.io API object 12 | db, err := dbhub.New("YOUR_API_KEY_HERE") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | // Retrieve the differences between two commits of the same database 18 | user := "justinclift" 19 | database := "DB4S download stats.sqlite" 20 | commit1 := dbhub.Identifier{CommitID: "34cbeebfc347a09406707f4220cd40f60778692523d2e7d227ccd92f4125c9ea"} 21 | commit2 := dbhub.Identifier{CommitID: "bc6a07955811d86db79e9b4f7fdc3cb2360d40da793066510d792588a8bf8de2"} 22 | mergeMode := dbhub.PreservePkMerge 23 | diffs, err := db.Diff(user, database, commit1, "", "", commit2, mergeMode) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | // Display the SQL statements needed to turn the first version of the database into the second. 29 | // This should produce a similar output to the sqldiff utility. 30 | fmt.Printf("SQL statements for turning the first version into the second:\n") 31 | for _, i := range diffs.Diff { // There is one item for each modified database object 32 | // Print schema changes to this object if there are any 33 | if i.Schema != nil { 34 | fmt.Printf("%s\n", i.Schema.Sql) 35 | } 36 | 37 | // Loop over all data changes in this object if there are any 38 | for _, j := range i.Data { 39 | fmt.Printf("%s\n", j.Sql) 40 | } 41 | } 42 | fmt.Println() 43 | } 44 | -------------------------------------------------------------------------------- /examples/download_database/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | 8 | "github.com/sqlitebrowser/go-dbhub" 9 | ) 10 | 11 | func main() { 12 | // Create a new DBHub.io API object 13 | db, err := dbhub.New("YOUR_API_KEY_HERE") 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | // Retrieve the remote database file 19 | dbName := "Join Testing.sqlite" 20 | dbStream, err := db.Download("justinclift", dbName, dbhub.Identifier{}) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | 25 | // Save the database file in the current directory 26 | buf, err := ioutil.ReadAll(dbStream) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | err = ioutil.WriteFile(dbName, buf, 0644) 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | fmt.Printf("Saved database file as '%s'\n", dbName) 35 | } 36 | -------------------------------------------------------------------------------- /examples/list_branches/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/sqlitebrowser/go-dbhub" 8 | ) 9 | 10 | func main() { 11 | // Create a new DBHub.io API object 12 | db, err := dbhub.New("YOUR_API_KEY_HERE") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | // Retrieve the list of branches of the remote database 18 | user := "justinclift" 19 | database := "Marine Litter Survey (Keep Northern Ireland Beautiful).sqlite" 20 | branches, defaultBranch, err := db.Branches(user, database) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | 25 | // Display the retrieved list of branches 26 | fmt.Println("Branches:") 27 | for branchName, branchDetails := range branches { 28 | var defaultBranchText string 29 | if branchName == defaultBranch { 30 | defaultBranchText = ", default branch" 31 | } 32 | fmt.Printf(" * %s (commits: %d%s)\n", branchName, branchDetails.CommitCount, defaultBranchText) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/list_commits/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/sqlitebrowser/go-dbhub" 9 | ) 10 | 11 | func main() { 12 | // Create a new DBHub.io API object 13 | db, err := dbhub.New("YOUR_API_KEY_HERE") 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | // Retrieve the commits for the remote database 19 | commits, err := db.Commits("justinclift", "Join Testing.sqlite") 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | // Display the commits 25 | fmt.Println("Commits:") 26 | for i, j := range commits { 27 | fmt.Printf(" * %s\n", i) 28 | if j.CommitterName != "" { 29 | fmt.Printf(" Committer Name: %v\n", j.CommitterName) 30 | } 31 | if j.CommitterEmail != "" { 32 | fmt.Printf(" Committer Email: %v\n", j.CommitterEmail) 33 | } 34 | fmt.Printf(" Timestamp: %v\n", j.Timestamp.Format(time.RFC1123)) 35 | fmt.Printf(" Author Name: %v\n", j.AuthorName) 36 | fmt.Printf(" Author Email: %v\n", j.AuthorEmail) 37 | if j.Message != "" { 38 | fmt.Printf(" Message: %v\n", j.Message) 39 | } 40 | if j.Parent == "" { 41 | fmt.Println(" Parent: NONE") 42 | } else { 43 | fmt.Printf(" Parent: %v\n", j.Parent) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/list_databases/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/sqlitebrowser/go-dbhub" 8 | ) 9 | 10 | func main() { 11 | // Create a new DBHub.io API object 12 | db, err := dbhub.New("YOUR_API_KEY_HERE") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | // Retrieve the databases in your account 18 | databases, err := db.Databases() 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | 23 | // Display the retrieved list of databases 24 | fmt.Println("Databases:") 25 | for _, j := range databases { 26 | fmt.Printf(" * %s\n", j) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/list_indexes/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/sqlitebrowser/go-dbhub" 8 | ) 9 | 10 | func main() { 11 | // Create a new DBHub.io API object 12 | db, err := dbhub.New("YOUR_API_KEY_HERE") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | // Retrieve the list of indexes in the remote database 18 | indexes, err := db.Indexes("justinclift", "Join Testing.sqlite", dbhub.Identifier{Branch: "master"}) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | 23 | // Display the retrieved list of indexes 24 | fmt.Println("Indexes:") 25 | for _, j := range indexes { 26 | fmt.Printf(" * '%s' on table '%s'\n", j.Name, j.Table) 27 | for _, l := range j.Columns { 28 | fmt.Printf(" Column name: %v\n", l.Name) 29 | fmt.Printf(" Column ID: %v\n", l.CID) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/list_releases/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/sqlitebrowser/go-dbhub" 9 | ) 10 | 11 | func main() { 12 | // Create a new DBHub.io API object 13 | db, err := dbhub.New("YOUR_API_KEY_HERE") 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | // Retrieve the release info for the remote database 19 | rels, err := db.Releases("justinclift", "Join Testing.sqlite") 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | // Display the release info 25 | fmt.Println("Releases:") 26 | for i, j := range rels { 27 | fmt.Printf(" * %s\n", i) 28 | fmt.Printf(" Commit: %v\n", j.Commit) 29 | fmt.Printf(" Date: %v\n", j.Date.Format(time.RFC1123)) 30 | fmt.Printf(" Size: %v bytes\n", j.Size) 31 | fmt.Printf(" Releaser Name: %v\n", j.ReleaserName) 32 | fmt.Printf(" Releaser Email: %v\n", j.ReleaserEmail) 33 | fmt.Printf(" Description: %v\n", j.Description) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/list_tables/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/sqlitebrowser/go-dbhub" 8 | ) 9 | 10 | func main() { 11 | // Create a new DBHub.io API object 12 | db, err := dbhub.New("YOUR_API_KEY_HERE") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | // Retrieve the list of tables in the remote database 18 | tables, err := db.Tables("justinclift", "Join Testing.sqlite", dbhub.Identifier{Branch: "master"}) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | 23 | // Display the retrieved list of tables 24 | fmt.Println("Tables:") 25 | for _, j := range tables { 26 | fmt.Printf(" * %s\n", j) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/list_tags/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/sqlitebrowser/go-dbhub" 9 | ) 10 | 11 | func main() { 12 | // Create a new DBHub.io API object 13 | db, err := dbhub.New("YOUR_API_KEY_HERE") 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | // Retrieve the tags for the remote database 19 | tags, err := db.Tags("justinclift", "Join Testing.sqlite") 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | // Display the tags 25 | fmt.Println("Tags:") 26 | for i, j := range tags { 27 | fmt.Printf(" * %s\n", i) 28 | fmt.Printf(" Commit: %v\n", j.Commit) 29 | fmt.Printf(" Date: %v\n", j.Date.Format(time.RFC1123)) 30 | fmt.Printf(" Tagger Name: %v\n", j.TaggerName) 31 | fmt.Printf(" Tagger Email: %v\n", j.TaggerEmail) 32 | fmt.Printf(" Description: %v\n", j.Description) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/list_views/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/sqlitebrowser/go-dbhub" 8 | ) 9 | 10 | func main() { 11 | // Create a new DBHub.io API object 12 | db, err := dbhub.New("YOUR_API_KEY_HERE") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | // Retrieve the list of views in the remote database 18 | views, err := db.Views("justinclift", "Join Testing.sqlite", dbhub.Identifier{Branch: "master"}) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | 23 | // Display the retrieved list of views 24 | fmt.Println("Views:") 25 | for _, j := range views { 26 | fmt.Printf(" * %s\n", j) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/metadata/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/sqlitebrowser/go-dbhub" 9 | ) 10 | 11 | func main() { 12 | // Create a new DBHub.io API object 13 | db, err := dbhub.New("YOUR_API_KEY_HERE") 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | // Retrieve the metadata for the remote database 19 | meta, err := db.Metadata("justinclift", "Join Testing.sqlite") 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | // * Display the retrieved metadata * 25 | 26 | // Display the database branches 27 | fmt.Println("Branches:") 28 | for i := range meta.Branches { 29 | fmt.Printf(" * %s\n", i) 30 | } 31 | fmt.Printf("Default branch: %s\n", meta.DefBranch) 32 | 33 | // Display the database releases 34 | if len(meta.Releases) != 0 { 35 | fmt.Println("Releases:") 36 | for i := range meta.Releases { 37 | fmt.Printf(" * %s\n", i) 38 | } 39 | } 40 | 41 | // Display the database tags 42 | if len(meta.Tags) != 0 { 43 | fmt.Println("Tags:") 44 | for i := range meta.Tags { 45 | fmt.Printf(" * %s\n", i) 46 | } 47 | } 48 | 49 | // Display the database commits 50 | fmt.Println("Commits:") 51 | for _, j := range meta.Commits { 52 | fmt.Printf(" * %s, %v\n", j.ID, j.Timestamp.Format(time.RFC1123)) 53 | } 54 | 55 | // Display the web page for the database 56 | fmt.Printf("Web page: %s\n", meta.WebPage) 57 | } 58 | -------------------------------------------------------------------------------- /examples/sql_query/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/sqlitebrowser/go-dbhub" 8 | ) 9 | 10 | func main() { 11 | // Create a new DBHub.io API object 12 | db, err := dbhub.New("YOUR_API_KEY_HERE") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | // Run a query on the remote database 18 | showBlobs := false 19 | r, err := db.Query("justinclift", "Join Testing.sqlite", dbhub.Identifier{Branch: "master"}, 20 | showBlobs, `SELECT table1.Name, table2.value 21 | FROM table1 JOIN table2 22 | USING (id) 23 | ORDER BY table1.id`) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | // Display the query result (without unmarshalling) 29 | fmt.Printf("Query results:\n\t%v\n", r) 30 | fmt.Println() 31 | } 32 | -------------------------------------------------------------------------------- /examples/upload/example.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sqlitebrowser/go-dbhub/34238e7a0ca677c5cd9e4bc7024bb69bee90ed32/examples/upload/example.db -------------------------------------------------------------------------------- /examples/upload/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | 8 | "github.com/sqlitebrowser/go-dbhub" 9 | ) 10 | 11 | func main() { 12 | // Create a new DBHub.io API object 13 | db, err := dbhub.New("YOUR_API_KEY_HERE") 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | // Read the database file into memory 19 | var myDB []byte 20 | myDB, err = ioutil.ReadFile("example.db") 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | 25 | // Prepare any information you want to include with the upload (eg a commit message, etc) 26 | info := dbhub.UploadInformation{ 27 | CommitMsg: "An example upload", 28 | } 29 | 30 | // Upload the database 31 | err = db.Upload("somedb.sqlite", info, &myDB) 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | 36 | // Display a success message 37 | fmt.Println("Database uploaded") 38 | } 39 | -------------------------------------------------------------------------------- /examples/webpage/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/sqlitebrowser/go-dbhub" 8 | ) 9 | 10 | func main() { 11 | // Create a new DBHub.io API object 12 | db, err := dbhub.New("YOUR_API_KEY_HERE") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | // Retrieve the metadata for the remote database 18 | wp, err := db.Webpage("justinclift", "Join Testing.sqlite") 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | 23 | // Display the web page for the database 24 | fmt.Printf("Web page: '%s'\n", wp.WebPage) 25 | } 26 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sqlitebrowser/go-dbhub 2 | 3 | go 1.18 4 | 5 | replace ( 6 | github.com/Sirupsen/logrus v1.0.5 => github.com/sirupsen/logrus v1.0.5 7 | github.com/Sirupsen/logrus v1.3.0 => github.com/sirupsen/logrus v1.3.0 8 | github.com/Sirupsen/logrus v1.4.0 => github.com/sirupsen/logrus v1.4.0 9 | github.com/Sirupsen/logrus v1.5.0 => github.com/sirupsen/logrus v1.5.0 10 | github.com/Sirupsen/logrus v1.6.0 => github.com/sirupsen/logrus v1.6.0 11 | ) 12 | 13 | require ( 14 | github.com/docker/docker v24.0.7+incompatible 15 | github.com/gwenn/gosqlite v0.0.0-20230220182433-af75c85b9faf 16 | github.com/sqlitebrowser/dbhub.io v0.2.1 17 | github.com/stretchr/testify v1.8.4 18 | ) 19 | 20 | require ( 21 | github.com/BurntSushi/toml v1.3.2 // indirect 22 | github.com/aquilax/truncate v1.0.0 // indirect 23 | github.com/aymerick/douceur v0.2.0 // indirect 24 | github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect 25 | github.com/containerd/containerd v1.7.11 // indirect 26 | github.com/containerd/log v0.1.0 // indirect 27 | github.com/davecgh/go-spew v1.1.1 // indirect 28 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 29 | github.com/go-ini/ini v1.67.0 // indirect 30 | github.com/go-playground/locales v0.14.1 // indirect 31 | github.com/go-playground/universal-translator v0.18.1 // indirect 32 | github.com/go-playground/validator/v10 v10.16.0 // indirect 33 | github.com/golang-migrate/migrate/v4 v4.17.0 // indirect 34 | github.com/gorilla/css v1.0.1 // indirect 35 | github.com/gwenn/yacr v0.0.0-20230220182143-2858410e8872 // indirect 36 | github.com/hashicorp/errwrap v1.1.0 // indirect 37 | github.com/hashicorp/go-multierror v1.1.1 // indirect 38 | github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect 39 | github.com/jackc/pgpassfile v1.0.0 // indirect 40 | github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect 41 | github.com/jackc/pgx/v5 v5.5.1 // indirect 42 | github.com/jackc/puddle/v2 v2.2.1 // indirect 43 | github.com/leodido/go-urn v1.2.4 // indirect 44 | github.com/microcosm-cc/bluemonday v1.0.16 // indirect 45 | github.com/minio/minio-go v6.0.14+incompatible // indirect 46 | github.com/mitchellh/go-homedir v1.1.0 // indirect 47 | github.com/pmezard/go-difflib v1.0.0 // indirect 48 | github.com/rabbitmq/amqp091-go v1.9.0 // indirect 49 | github.com/sergi/go-diff v1.3.1 // indirect 50 | github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 // indirect 51 | github.com/shurcooL/highlight_diff v0.0.0-20230708024848-22f825814995 // indirect 52 | github.com/shurcooL/highlight_go v0.0.0-20230708025100-33e05792540a // indirect 53 | github.com/shurcooL/octicon v0.0.0-20230705024016-66bff059edb8 // indirect 54 | github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect 55 | github.com/sirupsen/logrus v1.9.3 // indirect 56 | github.com/smtp2go-oss/smtp2go-go v1.0.2 // indirect 57 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect 58 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect 59 | github.com/sqlitebrowser/blackfriday v9.0.0+incompatible // indirect 60 | github.com/sqlitebrowser/github_flavored_markdown v0.0.0-20190120045821-b8cf8f054e47 // indirect 61 | go.uber.org/atomic v1.11.0 // indirect 62 | golang.org/x/crypto v0.18.0 // indirect 63 | golang.org/x/net v0.20.0 // indirect 64 | golang.org/x/sync v0.6.0 // indirect 65 | golang.org/x/sys v0.16.0 // indirect 66 | golang.org/x/text v0.14.0 // indirect 67 | gopkg.in/yaml.v3 v3.0.1 // indirect 68 | ) 69 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= 2 | github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= 3 | github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 4 | github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= 5 | github.com/aquilax/truncate v1.0.0 h1:UgIGS8U/aZ4JyOJ2h3xcF5cSQ06+gGBnjxH2RUHJe0U= 6 | github.com/aquilax/truncate v1.0.0/go.mod h1:BeMESIDMlvlS3bmg4BVvBbbZUNwWtS8uzYPAKXwwhLw= 7 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 8 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 9 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= 10 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= 11 | github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= 12 | github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= 13 | github.com/containerd/containerd v1.7.11 h1:lfGKw3eU35sjV0aG2eYZTiwFEY1pCzxdzicHP3SZILw= 14 | github.com/containerd/containerd v1.7.11/go.mod h1:5UluHxHTX2rdvYuZ5OJTC5m/KJNs0Zs9wVoJm9zf5ZE= 15 | github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 16 | github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 17 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 19 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/dhui/dktest v0.4.0 h1:z05UmuXZHO/bgj/ds2bGMBu8FI4WA+Ag/m3ghL+om7M= 21 | github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= 22 | github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= 23 | github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 24 | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= 25 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 26 | github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= 27 | github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= 28 | github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= 29 | github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 30 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 31 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 32 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 33 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 34 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 35 | github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= 36 | github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 37 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 38 | github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU= 39 | github.com/golang-migrate/migrate/v4 v4.17.0/go.mod h1:+Cp2mtLP4/aXDTKb9wmXYitdrNx2HGs45rbWAo6OsKM= 40 | github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= 41 | github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= 42 | github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= 43 | github.com/gwenn/gosqlite v0.0.0-20230220182433-af75c85b9faf h1:lmqo4Osg1zQAq3ZOa5NACDlkiLCk3zAVEoGytyzkKPo= 44 | github.com/gwenn/gosqlite v0.0.0-20230220182433-af75c85b9faf/go.mod h1:WBYs9HfQGOYDCz7rFwMk7aHkbTTB0cUkQe3pZQARvIg= 45 | github.com/gwenn/yacr v0.0.0-20200110180258-a66d8c42d0ff/go.mod h1:5SNcBGxZ5OaJAMJCSI/x3V7SGsvXqbwnwP/sHZLgYsw= 46 | github.com/gwenn/yacr v0.0.0-20230220182143-2858410e8872 h1:AVWCyogAAzN3k+VEp01cNceW9X/Gd7SODLfeVP0ZI0s= 47 | github.com/gwenn/yacr v0.0.0-20230220182143-2858410e8872/go.mod h1:Ps/gikIXcn2rRmeP0HQ9EvUYJrfrjAi51Wg8acsrkP0= 48 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 49 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 50 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 51 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 52 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 53 | github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= 54 | github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= 55 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 56 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 57 | github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= 58 | github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= 59 | github.com/jackc/pgx/v5 v5.5.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI= 60 | github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= 61 | github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= 62 | github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= 63 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 64 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 65 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 66 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 67 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 68 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 69 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 70 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 71 | github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= 72 | github.com/microcosm-cc/bluemonday v1.0.16 h1:kHmAq2t7WPWLjiGvzKa5o3HzSfahUKiOq7fAPUiMNIc= 73 | github.com/microcosm-cc/bluemonday v1.0.16/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= 74 | github.com/minio/minio-go v6.0.14+incompatible h1:fnV+GD28LeqdN6vT2XdGKW8Qe/IfjJDswNVuni6km9o= 75 | github.com/minio/minio-go v6.0.14+incompatible/go.mod h1:7guKYtitv8dktvNUGrhzmNlA5wrAABTQXCoesZdFQO8= 76 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 77 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 78 | github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= 79 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 80 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 81 | github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8= 82 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 83 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 84 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 85 | github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo= 86 | github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= 87 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 88 | github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk= 89 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 90 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 91 | github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470 h1:qb9IthCFBmROJ6YBS31BEMeSYjOscSiG+EO+JVNTz64= 92 | github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 h1:aSISeOcal5irEhJd1M+IrApc0PdcN7e7Aj4yuEnOrfQ= 93 | github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= 94 | github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 h1:llrF3Fs4018ePo4+G/HV/uQUqEI1HMDjCeOf2V6puPc= 95 | github.com/shurcooL/highlight_diff v0.0.0-20230708024848-22f825814995 h1:/6Fa0HAouqks/nlr3C3sv7KNDqutP3CM/MYz225uO28= 96 | github.com/shurcooL/highlight_diff v0.0.0-20230708024848-22f825814995/go.mod h1:eqklBUMsamqZbxXhhr6GafgswFTa5Aq12VQ0I2lnCR8= 97 | github.com/shurcooL/highlight_go v0.0.0-20230708025100-33e05792540a h1:aMmA4ghJXuzwIS/mEK+bf7U2WZECRxa3sPgR4QHj8Hw= 98 | github.com/shurcooL/highlight_go v0.0.0-20230708025100-33e05792540a/go.mod h1:kLtotffsKtKsCupV8wNnNwQQHBccB1Oy5VSg8P409Go= 99 | github.com/shurcooL/octicon v0.0.0-20230705024016-66bff059edb8 h1:W5meM/5DP0Igf+pS3Se363Y2DoDv9LUuZgQ24uG9LNY= 100 | github.com/shurcooL/octicon v0.0.0-20230705024016-66bff059edb8/go.mod h1:hWBWTvIJ918VxbNOk2hxQg1/5j1M9yQI1Kp8d9qrOq8= 101 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 102 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 103 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 104 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 105 | github.com/smtp2go-oss/smtp2go-go v1.0.2 h1:vXkqx9kyoQIuetyV3nm40b+OZevihhgb78X4vA/u2fs= 106 | github.com/smtp2go-oss/smtp2go-go v1.0.2/go.mod h1:lkv36awQXRBWAvnd517FFESKvne8465KCu90lPThcEY= 107 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E= 108 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= 109 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8= 110 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= 111 | github.com/sqlitebrowser/blackfriday v9.0.0+incompatible h1:ddH/UyzasooYgGIblVU4R8DdmBuJ7QXLvSqX/0chZv4= 112 | github.com/sqlitebrowser/blackfriday v9.0.0+incompatible/go.mod h1:/zga9sqpWzcewuI83AO5JZwe9+6F9GgPDdqqdNNEL/0= 113 | github.com/sqlitebrowser/dbhub.io v0.2.1 h1:dnNU4EH/KxSJhaaYWDr48Bo0f5c+a26bmf5YFVW//js= 114 | github.com/sqlitebrowser/dbhub.io v0.2.1/go.mod h1:1a4m1T8DgJx4j9eNcDx3LKnz5N+leelikyAimlsmgJU= 115 | github.com/sqlitebrowser/github_flavored_markdown v0.0.0-20190120045821-b8cf8f054e47 h1:s0+Ea95n1LrsKh6rtclU/9Qb2/5ofvnfnR7gDDiFTw8= 116 | github.com/sqlitebrowser/github_flavored_markdown v0.0.0-20190120045821-b8cf8f054e47/go.mod h1:8vPIKi5FslxCXEgfQxrFtWfdclGy6VWAc9NA1ZTYCJg= 117 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 118 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 119 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 120 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 121 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 122 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 123 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 124 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 125 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 126 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 127 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 128 | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 129 | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 130 | go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= 131 | go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= 132 | golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= 133 | golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= 134 | golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= 135 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 136 | golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= 137 | golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= 138 | golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= 139 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 140 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 141 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 142 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 143 | golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= 144 | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 145 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 146 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 147 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 148 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 149 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 150 | golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= 151 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 152 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 153 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 154 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 155 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 156 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 157 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 158 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 159 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 160 | -------------------------------------------------------------------------------- /http.go: -------------------------------------------------------------------------------- 1 | package dbhub 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "mime/multipart" 10 | "net/http" 11 | "net/url" 12 | "strings" 13 | ) 14 | 15 | // sendRequestJSON sends a request to DBHub.io, formatting the returned result as JSON 16 | func sendRequestJSON(queryUrl string, verifyServerCert bool, data url.Values, returnStructure interface{}) (err error) { 17 | // Send the request 18 | var body io.ReadCloser 19 | body, err = sendRequest(queryUrl, verifyServerCert, data) 20 | if body != nil { 21 | defer body.Close() 22 | } 23 | if err != nil { 24 | if body != nil { 25 | // If there's useful error info in the returned JSON, return that as the error message 26 | var z JSONError 27 | err = json.NewDecoder(body).Decode(&z) 28 | if err != nil { 29 | return 30 | } 31 | err = fmt.Errorf("%s", z.Msg) 32 | } 33 | return 34 | } 35 | 36 | // Unmarshall the JSON response into the structure provided by the caller 37 | if returnStructure != nil { 38 | err = json.NewDecoder(body).Decode(returnStructure) 39 | if err != nil { 40 | return 41 | } 42 | } 43 | return 44 | } 45 | 46 | // sendRequest sends a request to DBHub.io. It exists because http.PostForm() doesn't seem to have a way of changing 47 | // header values. 48 | func sendRequest(queryUrl string, verifyServerCert bool, data url.Values) (body io.ReadCloser, err error) { 49 | // Disable verification of the server https cert, if we've been told to 50 | var client http.Client 51 | if !verifyServerCert { 52 | tr := &http.Transport{ 53 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 54 | } 55 | client = http.Client{Transport: tr} 56 | } 57 | 58 | var req *http.Request 59 | var resp *http.Response 60 | req, err = http.NewRequest(http.MethodPost, queryUrl, strings.NewReader(data.Encode())) 61 | if err != nil { 62 | return 63 | } 64 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 65 | req.Header.Set("User-Agent", fmt.Sprintf("go-dbhub v%s", version)) 66 | resp, err = client.Do(req) 67 | if err != nil { 68 | return 69 | } 70 | 71 | // Return the response body, even if an error occurred. This lets us return useful error information provided as 72 | // JSON in the body of the message 73 | body = resp.Body 74 | 75 | // Basic error handling, based on the status code received from the server 76 | if resp.StatusCode != 200 { 77 | // The returned status code indicates something went wrong 78 | err = fmt.Errorf(resp.Status) 79 | return 80 | } 81 | return 82 | } 83 | 84 | // sendUpload uploads a database to DBHub.io. It exists because the DBHub.io upload end point requires multi-part data 85 | func sendUpload(queryUrl string, verifyServerCert bool, data *url.Values, dbBytes *[]byte) (body io.ReadCloser, err error) { 86 | // Prepare the database file byte stream 87 | var buf bytes.Buffer 88 | w := multipart.NewWriter(&buf) 89 | dbName := data.Get("dbname") 90 | var wri io.Writer 91 | if dbName != "" { 92 | wri, err = w.CreateFormFile("file", dbName) 93 | } else { 94 | wri, err = w.CreateFormFile("file", "database.db") 95 | } 96 | if err != nil { 97 | return 98 | } 99 | _, err = wri.Write(*dbBytes) 100 | if err != nil { 101 | return 102 | } 103 | 104 | // Add the headers 105 | for i, j := range *data { 106 | wri, err = w.CreateFormField(i) 107 | if err != nil { 108 | return 109 | } 110 | _, err = wri.Write([]byte(j[0])) 111 | if err != nil { 112 | return 113 | } 114 | } 115 | err = w.Close() 116 | if err != nil { 117 | return 118 | } 119 | 120 | // Disable verification of the server https cert, if we've been told to 121 | var client http.Client 122 | if !verifyServerCert { 123 | tr := &http.Transport{ 124 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 125 | } 126 | client = http.Client{Transport: tr} 127 | } 128 | 129 | // Prepare the request 130 | var req *http.Request 131 | var resp *http.Response 132 | req, err = http.NewRequest(http.MethodPost, queryUrl, &buf) 133 | if err != nil { 134 | return 135 | } 136 | req.Header.Set("User-Agent", fmt.Sprintf("go-dbhub v%s", version)) 137 | req.Header.Set("Content-Type", w.FormDataContentType()) 138 | 139 | // Upload the database 140 | resp, err = client.Do(req) 141 | if err != nil { 142 | return 143 | } 144 | 145 | // Return the response body, even if an error occurred. This lets us return useful error information provided as 146 | // JSON in the body of the message 147 | body = resp.Body 148 | 149 | // Basic error handling, based on the status code received from the server 150 | if resp.StatusCode != 201 { 151 | // The returned status code indicates something went wrong 152 | err = fmt.Errorf(resp.Status) 153 | return 154 | } 155 | return 156 | } 157 | -------------------------------------------------------------------------------- /server_types.go: -------------------------------------------------------------------------------- 1 | package dbhub 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type APIJSONColumn struct { 8 | Cid int `json:"column_id"` 9 | Name string `json:"name"` 10 | DataType string `json:"data_type"` 11 | NotNull bool `json:"not_null"` 12 | DfltValue string `json:"default_value"` 13 | Pk int `json:"primary_key"` 14 | } 15 | 16 | type APIJSONIndexColumn struct { 17 | CID int `json:"id"` 18 | Name string `json:"name"` 19 | } 20 | 21 | type APIJSONIndex struct { 22 | Name string `json:"name"` 23 | Table string `json:"table"` 24 | Columns []APIJSONIndexColumn `json:"columns"` 25 | } 26 | 27 | type BranchEntry struct { 28 | Commit string `json:"commit"` 29 | CommitCount int `json:"commit_count"` 30 | Description string `json:"description"` 31 | } 32 | 33 | type BranchListResponseContainer struct { 34 | Branches map[string]BranchEntry `json:"branches"` 35 | DefaultBranch string `json:"default_branch"` 36 | } 37 | 38 | type CommitEntry struct { 39 | AuthorEmail string `json:"author_email"` 40 | AuthorName string `json:"author_name"` 41 | CommitterEmail string `json:"committer_email"` 42 | CommitterName string `json:"committer_name"` 43 | ID string `json:"id"` 44 | Message string `json:"message"` 45 | OtherParents []string `json:"other_parents"` 46 | Parent string `json:"parent"` 47 | Timestamp time.Time `json:"timestamp"` 48 | Tree DBTree `json:"tree"` 49 | } 50 | 51 | type ValType int 52 | 53 | const ( 54 | Binary ValType = iota 55 | Image 56 | Null 57 | Text 58 | Integer 59 | Float 60 | ) 61 | 62 | type DataValue struct { 63 | Name string 64 | Type ValType 65 | Value interface{} 66 | } 67 | 68 | type DataRow []DataValue 69 | 70 | type DBTree struct { 71 | ID string `json:"id"` 72 | Entries []DBTreeEntry `json:"entries"` 73 | } 74 | 75 | type DBTreeEntryType string 76 | 77 | const ( 78 | TREE DBTreeEntryType = "tree" 79 | DATABASE = "db" 80 | LICENCE = "licence" 81 | ) 82 | 83 | type DBTreeEntry struct { 84 | EntryType DBTreeEntryType `json:"entry_type"` 85 | LastModified time.Time `json:"last_modified"` 86 | LicenceSHA string `json:"licence"` 87 | Name string `json:"name"` 88 | Sha256 string `json:"sha256"` 89 | Size int64 `json:"size"` 90 | } 91 | 92 | type ExecuteResponseContainer struct { 93 | RowsChanged int `json:"rows_changed"` 94 | Status string `json:"status"` 95 | } 96 | 97 | type MetadataResponseContainer struct { 98 | Branches map[string]BranchEntry `json:"branches"` 99 | Commits map[string]CommitEntry `json:"commits"` 100 | DefBranch string `json:"default_branch"` 101 | Releases map[string]ReleaseEntry `json:"releases"` 102 | Tags map[string]TagEntry `json:"tags"` 103 | WebPage string `json:"web_page"` 104 | } 105 | 106 | type ReleaseEntry struct { 107 | Commit string `json:"commit"` 108 | Date time.Time `json:"date"` 109 | Description string `json:"description"` 110 | ReleaserEmail string `json:"email"` 111 | ReleaserName string `json:"name"` 112 | Size int64 `json:"size"` 113 | } 114 | 115 | type TagEntry struct { 116 | Commit string `json:"commit"` 117 | Date time.Time `json:"date"` 118 | Description string `json:"description"` 119 | TaggerEmail string `json:"email"` 120 | TaggerName string `json:"name"` 121 | } 122 | 123 | type WebpageResponseContainer struct { 124 | WebPage string `json:"web_page"` 125 | } 126 | 127 | type DiffType string 128 | 129 | const ( 130 | ActionAdd DiffType = "add" 131 | ActionDelete DiffType = "delete" 132 | ActionModify DiffType = "modify" 133 | ) 134 | 135 | type SchemaDiff struct { 136 | ActionType DiffType `json:"action_type"` 137 | Sql string `json:"sql,omitempty"` 138 | Before string `json:"before"` 139 | After string `json:"after"` 140 | } 141 | 142 | type DataDiff struct { 143 | ActionType DiffType `json:"action_type"` 144 | Sql string `json:"sql,omitempty"` 145 | Pk []DataValue `json:"pk"` 146 | DataBefore []interface{} `json:"data_before,omitempty"` 147 | DataAfter []interface{} `json:"data_after,omitempty"` 148 | } 149 | 150 | type DiffObjectChangeset struct { 151 | ObjectName string `json:"object_name"` 152 | ObjectType string `json:"object_type"` 153 | Schema *SchemaDiff `json:"schema,omitempty"` 154 | Data []DataDiff `json:"data,omitempty"` 155 | } 156 | 157 | type Diffs struct { 158 | Diff []DiffObjectChangeset `json:"diff"` 159 | } 160 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package dbhub 2 | 3 | import "time" 4 | 5 | // Connection is a simple container holding the API key and address of the DBHub.io server 6 | type Connection struct { 7 | APIKey string `json:"api_key"` 8 | Server string `json:"server"` 9 | VerifyServerCert bool `json:"verify_certificate"` 10 | } 11 | 12 | // Identifier holds information used to identify a specific commit, tag, release, or the head of a specific branch 13 | type Identifier struct { 14 | Branch string `json:"branch"` 15 | CommitID string `json:"commit_id"` 16 | Release string `json:"release"` 17 | Tag string `json:"tag"` 18 | } 19 | 20 | // JSONError holds information about an error condition, in a useful JSON format 21 | type JSONError struct { 22 | Msg string `json:"error"` 23 | } 24 | 25 | // MergeStrategy specifies the type of SQL statements included in the diff results. 26 | // The SQL statements can be used for merging databases and depending on whether and 27 | // how you want to merge you should choose your merge strategy. 28 | type MergeStrategy int 29 | 30 | const ( 31 | // NoMerge removes any SQL statements for merging from the diff results 32 | NoMerge MergeStrategy = iota 33 | 34 | // PreservePkMerge produces SQL statements which preserve the values of the primary key columns. 35 | // Executing these statements on the first database produces a database similar to the second. 36 | PreservePkMerge 37 | 38 | // NewPkMerge produces SQL statements which generate new values for the primary key columns when 39 | // executed. This avoids a couple of possible conflicts and allows merging more distant databases. 40 | NewPkMerge 41 | ) 42 | 43 | // ResultRow is used for returning the results of a SQL query as a slice of strings 44 | type ResultRow struct { 45 | Fields []string 46 | } 47 | 48 | // Results is used for returning the results of a SQL query as a slice of strings 49 | type Results struct { 50 | Rows []ResultRow 51 | } 52 | 53 | // UploadInformation holds information used when uploading 54 | type UploadInformation struct { 55 | Ident Identifier `json:"identifier"` 56 | CommitMsg string `json:"commitmsg"` 57 | SourceURL string `json:"sourceurl"` 58 | LastModified time.Time `json:"lastmodified"` 59 | Licence string `json:"licence"` 60 | Public string `json:"public"` 61 | Force bool `json:"force"` 62 | CommitTimestamp time.Time `json:"committimestamp"` 63 | AuthorName string `json:"authorname"` 64 | AuthorEmail string `json:"authoremail"` 65 | CommitterName string `json:"committername"` 66 | CommitterEmail string `json:"committeremail"` 67 | OtherParents string `json:"otherparents"` 68 | ShaSum string `json:"dbshasum"` 69 | } 70 | --------------------------------------------------------------------------------