Skip to content

Commit

Permalink
Merge pull request #1038 from go-kivik/completeDelete
Browse files Browse the repository at this point in the history
Improve DestroyDB to remove all tables
  • Loading branch information
flimzy authored Jul 25, 2024
2 parents 6ae4cbb + bc88ef5 commit 6825b68
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 21 deletions.
8 changes: 8 additions & 0 deletions x/sqlite/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,11 @@ var viewSchema = []string{
)`,
`CREATE INDEX {{ .IndexMap }} ON {{ .Map }} (key)`,
}

var destroySchema = []string{
`DROP TABLE {{ .Design }}`,
`DROP TABLE {{ .AttachmentsBridge }}`,
`DROP TABLE {{ .Attachments }}`,
`DROP TABLE {{ .Docs }}`,
`DROP TABLE {{ .Revs }}`,
}
92 changes: 74 additions & 18 deletions x/sqlite/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"context"
"database/sql"
"errors"
"fmt"
"log"
"net/http"
"regexp"
Expand Down Expand Up @@ -123,9 +124,21 @@ func (c *client) DBExists(ctx context.Context, name string, _ driver.Options) (b

var validDBNameRE = regexp.MustCompile(`^[a-z][a-z0-9_$()+/-]*$`)

func validateDBName(name string) error {
switch name {
case "_users", "_replicator", "_global_changes":
return nil
default:
if !validDBNameRE.MatchString(name) {
return &internal.Error{Status: http.StatusBadRequest, Message: fmt.Sprintf("invalid database name: %s", name)}
}
}
return nil
}

func (c *client) CreateDB(ctx context.Context, name string, _ driver.Options) error {
if !validDBNameRE.MatchString(name) {
return &internal.Error{Status: http.StatusBadRequest, Message: "invalid database name"}
if err := validateDBName(name); err != nil {
return err
}
tx, err := c.db.BeginTx(ctx, nil)
if err != nil {
Expand All @@ -136,34 +149,77 @@ func (c *client) CreateDB(ctx context.Context, name string, _ driver.Options) er
d := c.newDB(name)
for _, query := range schema {
_, err := tx.ExecContext(ctx, d.query(query))
if err == nil {
continue
}
if errIsAlreadyExists(err) {
return &internal.Error{Status: http.StatusPreconditionFailed, Message: "database already exists"}
if err != nil {
if errIsAlreadyExists(err) {
return &internal.Error{Status: http.StatusPreconditionFailed, Message: "database already exists"}
}
return err
}
return err
}

return tx.Commit()
}

func (c *client) DestroyDB(ctx context.Context, name string, _ driver.Options) error {
if !validDBNameRE.MatchString(name) {
return &internal.Error{Status: http.StatusBadRequest, Message: "invalid database name"}
if err := validateDBName(name); err != nil {
return err
}
_, err := c.db.ExecContext(ctx, `DROP TABLE "`+name+`"`)
if err == nil {
return nil
tx, err := c.db.BeginTx(ctx, nil)
if err != nil {
return err
}
if errIsNoSuchTable(err) {
return &internal.Error{Status: http.StatusNotFound, Message: "database not found"}
defer tx.Rollback()

d := c.newDB(name)
rows, err := tx.QueryContext(ctx, d.query(`
SELECT
id,
rev,
rev_id,
func_name
FROM {{ .Design }}
WHERE func_type = 'map'
`))
if err != nil {
if errIsNoSuchTable(err) {
return &internal.Error{Status: http.StatusNotFound, Message: "database not found"}
}
return err
}

defer rows.Close()
for rows.Next() {
var (
id, view string
rev revision
)
if err := rows.Scan(&id, &rev.rev, &rev.id, &view); err != nil {
return err
}
_, err := tx.ExecContext(ctx, d.ddocQuery(id, view, rev.String(), `DROP TABLE {{ .Map }}`))
if err != nil {
return err
}
}
return err
if err := rows.Err(); err != nil {
return err
}

for _, query := range destroySchema {
_, err := tx.ExecContext(ctx, d.query(query))
if err != nil {
if errIsNoSuchTable(err) {
return &internal.Error{Status: http.StatusNotFound, Message: "database not found"}
}
return err
}
}
return tx.Commit()
}

func (c *client) DB(name string, _ driver.Options) (driver.DB, error) {
if !validDBNameRE.MatchString(name) {
return nil, &internal.Error{Status: http.StatusBadRequest, Message: "invalid database name"}
if err := validateDBName(name); err != nil {
return nil, err
}
return c.newDB(name), nil
}
7 changes: 4 additions & 3 deletions x/sqlite/sqlite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"testing"

"github.com/google/go-cmp/cmp"
"gitlab.com/flimzy/testy"

"github.com/go-kivik/kivik/v4"
"github.com/go-kivik/kivik/v4/driver"
Expand Down Expand Up @@ -127,9 +128,9 @@ func TestClientCreateDB(t *testing.T) {
if err == nil {
t.Fatal("err should not be nil")
}
const wantErr = "invalid database name"
if err.Error() != wantErr {
t.Fatalf("err should be %s", wantErr)
const wantErr = "invalid database name: Foo"
if !testy.ErrorMatches(wantErr, err) {
t.Fatalf("Unexpected error: %s", err)
}
const wantStatus = http.StatusBadRequest
if status := kivik.HTTPStatus(err); status != wantStatus {
Expand Down

0 comments on commit 6825b68

Please sign in to comment.