Skip to content

Commit 0642478

Browse files
authored
Merge pull request #935 from tursodatabase/aws-upload-v2
Turso CLI database upload flow for AWS
2 parents e6e91f8 + 0d3208f commit 0642478

6 files changed

Lines changed: 320 additions & 41 deletions

File tree

internal/cmd/db_create.go

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cmd
22

33
import (
44
"fmt"
5+
"strings"
56
"time"
67

78
"github.com/athoscouto/codename"
@@ -62,7 +63,7 @@ var createCmd = &cobra.Command{
6263
return err
6364
}
6465

65-
group, err := groupFromFlag(client)
66+
group, isDefault, err := groupFromFlag(client)
6667
if err != nil {
6768
return err
6869
}
@@ -72,7 +73,8 @@ var createCmd = &cobra.Command{
7273
return err
7374
}
7475

75-
seed, err := parseDBSeedFlags(client)
76+
isAWS := strings.HasPrefix(group.Primary, "aws-")
77+
seed, err := parseDBSeedFlags(client, isAWS)
7678
if err != nil {
7779
return err
7880
}
@@ -82,21 +84,26 @@ var createCmd = &cobra.Command{
8284
version = "canary"
8385
}
8486

85-
if err := ensureGroup(client, group, location, version); err != nil {
87+
groupName := group.Name
88+
if isDefault {
89+
groupName = "default"
90+
}
91+
92+
if err := ensureGroup(client, groupName, location, version); err != nil {
8693
return err
8794
}
8895

8996
start := time.Now()
90-
spinner := prompt.Spinner(fmt.Sprintf("Creating database %s in group %s...", internal.Emph(name), internal.Emph(group)))
97+
spinner := prompt.Spinner(fmt.Sprintf("Creating database %s in group %s...", internal.Emph(name), internal.Emph(groupName)))
9198
defer spinner.Stop()
9299

93-
if _, err = client.Databases.Create(name, location, "", "", group, schemaFlag, typeFlag == "schema", seed, sizeLimitFlag); err != nil {
100+
if _, err = client.Databases.Create(name, location, "", "", groupName, schemaFlag, typeFlag == "schema", seed, sizeLimitFlag, spinner); err != nil {
94101
return fmt.Errorf("could not create database %s: %w", name, err)
95102
}
96103

97104
spinner.Stop()
98105
elapsed := time.Since(start)
99-
fmt.Printf("Created database %s at group %s in %s.\n\n", internal.Emph(name), internal.Emph(group), elapsed.Round(time.Millisecond).String())
106+
fmt.Printf("Created database %s at group %s in %s.\n\n", internal.Emph(name), internal.Emph(groupName), elapsed.Round(time.Millisecond).String())
100107

101108
fmt.Printf("Start an interactive SQL shell with:\n\n")
102109
fmt.Printf(" %s\n\n", internal.Emph("turso db shell "+name))
@@ -131,26 +138,32 @@ func getDatabaseName(args []string) (string, error) {
131138
return codename.Generate(rng, 0), nil
132139
}
133140

134-
func groupFromFlag(client *turso.Client) (string, error) {
141+
// Returns (group, isDefault, error)
142+
func groupFromFlag(client *turso.Client) (turso.Group, bool, error) {
135143
groups, err := getGroups(client)
136144
if err != nil {
137-
return "", err
145+
return turso.Group{}, false, err
138146
}
139147

140148
if groupFlag != "" {
141149
if !groupExists(groups, groupFlag) {
142-
return "", fmt.Errorf("group %s does not exist", groupFlag)
150+
return turso.Group{}, false, fmt.Errorf("group %s does not exist", groupFlag)
151+
}
152+
for _, group := range groups {
153+
if group.Name == groupFlag {
154+
return group, false, nil
155+
}
143156
}
144-
return groupFlag, nil
157+
return turso.Group{}, false, fmt.Errorf("group %s does not exist", groupFlag)
145158
}
146159

147160
switch {
148161
case len(groups) == 0:
149-
return "default", nil
162+
return turso.Group{Name: "default"}, true, nil
150163
case len(groups) == 1:
151-
return groups[0].Name, nil
164+
return groups[0], true, nil
152165
default:
153-
return "", fmt.Errorf("you have more than one database group. Please specify one with %s", internal.Emph("--group"))
166+
return turso.Group{}, false, fmt.Errorf("you have more than one database group. Please specify one with %s", internal.Emph("--group"))
154167

155168
}
156169
}

internal/cmd/group_flag.go

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import (
55
"bytes"
66
"errors"
77
"fmt"
8+
"log"
89
"os"
910
"os/exec"
11+
"strings"
1012
"time"
1113

1214
"github.com/Clever/csvlint"
@@ -62,7 +64,7 @@ func parseTimestampFlag() (*time.Time, error) {
6264
return &timestamp, nil
6365
}
6466

65-
func parseDBSeedFlags(client *turso.Client) (*turso.DBSeed, error) {
67+
func parseDBSeedFlags(client *turso.Client, isAWS bool) (*turso.DBSeed, error) {
6668
if countFlags(fromDBFlag, fromDumpFlag, fromFileFlag, fromDumpURLFlag, fromCSVFlag) > 1 {
6769
return nil, fmt.Errorf("only one of --from prefixed flags can be used at a time")
6870
}
@@ -83,7 +85,7 @@ func parseDBSeedFlags(client *turso.Client) (*turso.DBSeed, error) {
8385
}
8486

8587
if fromFileFlag != "" {
86-
return handleDBFile(client, fromFileFlag)
88+
return handleDBFile(client, fromFileFlag, isAWS)
8789
}
8890

8991
if fromDumpFlag != "" {
@@ -187,14 +189,84 @@ func countFlags(flags ...string) (count int) {
187189
return
188190
}
189191

190-
func handleDBFile(client *turso.Client, file string) (*turso.DBSeed, error) {
192+
const MaxAWSDBSizeBytes = 1024 * 1024 * 1024 * 20 // 20 GB
193+
func sqliteFileIntegrityChecks(file string) error {
194+
if flags.Debug() {
195+
log.Printf("Running integrity checks on database file %s", file)
196+
}
197+
if flags.Debug() {
198+
log.Printf("Checking file size...")
199+
}
200+
fileInfo, err := os.Stat(file)
201+
if err != nil {
202+
return fmt.Errorf("failed to get file info: %w", err)
203+
}
204+
205+
if fileInfo.Size() > MaxAWSDBSizeBytes {
206+
return fmt.Errorf("database file size exceeds maximum allowed size of 20 GB")
207+
}
208+
209+
if flags.Debug() {
210+
log.Printf("Checking database settings...")
211+
}
212+
output, err := exec.Command("sqlite3", file, ".mode line",
213+
"select journal_mode as j, page_size as p, auto_vacuum as a, encoding as e from pragma_journal_mode, pragma_page_size, pragma_auto_vacuum, pragma_encoding;").CombinedOutput()
214+
if err != nil {
215+
return fmt.Errorf("failed to check database settings: %w", err)
216+
}
217+
218+
settings := string(output)
219+
if !strings.Contains(settings, "j = wal") {
220+
return fmt.Errorf("database is not in WAL mode. Set it with 'sqlite3 %s 'PRAGMA journal_mode = WAL'", file)
221+
}
222+
223+
if !strings.Contains(settings, "p = 4096") {
224+
return fmt.Errorf("database must use 4KB page size. you can set it with 'sqlite3 %s 'PRAGMA page_size = 4096; VACUUM;' Note that this is not possible to do if your database is already in WAL mode", file)
225+
}
226+
if !strings.Contains(settings, "a = 0") {
227+
return fmt.Errorf("database must have autovacuum disabled. you can set it with 'sqlite3 %s 'PRAGMA auto_vacuum = 0;'", file)
228+
}
229+
if !strings.Contains(settings, "e = UTF-8") {
230+
return fmt.Errorf("database must use UTF-8 encoding. you can set it with 'sqlite3 %s 'PRAGMA encoding = 'UTF-8' ", file)
231+
}
232+
233+
// run quick_check
234+
if flags.Debug() {
235+
log.Printf("Running integrity check...")
236+
}
237+
_, err = exec.Command("sqlite3", file, "pragma quick_check;").CombinedOutput()
238+
if err != nil {
239+
return fmt.Errorf("integrity check on database failed: %w", err)
240+
}
241+
242+
return nil
243+
}
244+
245+
func handleDBFileAWS(file string) (*turso.DBSeed, error) {
246+
if err := sqliteFileIntegrityChecks(file); err != nil {
247+
return nil, err
248+
}
249+
250+
seed := &turso.DBSeed{
251+
Type: "database_upload",
252+
Filepath: file,
253+
}
254+
255+
return seed, nil
256+
}
257+
258+
func handleDBFile(client *turso.Client, file string, isAWS bool) (*turso.DBSeed, error) {
191259
if err := checkFileExists(file); err != nil {
192260
return nil, err
193261
}
194262
if err := checkSQLiteAvailable(); err != nil {
195263
return nil, err
196264
}
197265

266+
if isAWS {
267+
return handleDBFileAWS(file)
268+
}
269+
198270
if err := checkSQLiteFile(file); err != nil {
199271
return nil, err
200272
}
@@ -292,7 +364,7 @@ func handleCSVFile(client *turso.Client, file, csvTableName string, separator ru
292364
return nil, err
293365
}
294366

295-
seed, err := handleDBFile(client, tempDB.Name())
367+
seed, err := handleDBFile(client, tempDB.Name(), false)
296368
if err != nil {
297369
return nil, err
298370
}

internal/prompt/spinner.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
tea "github.com/charmbracelet/bubbletea"
99
)
1010

11-
type spinner struct {
11+
type SpinnerT struct {
1212
spinner spn.Model
1313
prefix string
1414
suffix string
@@ -17,17 +17,17 @@ type spinner struct {
1717
done chan bool
1818
}
1919

20-
func newSpinner(prefix, suffix string) *spinner {
20+
func newSpinner(prefix, suffix string) *SpinnerT {
2121
s := spn.New()
2222
s.Spinner = spn.Dot
23-
return &spinner{spinner: s, prefix: prefix, suffix: suffix}
23+
return &SpinnerT{spinner: s, prefix: prefix, suffix: suffix}
2424
}
2525

26-
func (m *spinner) Init() tea.Cmd {
26+
func (m *SpinnerT) Init() tea.Cmd {
2727
return m.spinner.Tick
2828
}
2929

30-
func (m *spinner) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
30+
func (m *SpinnerT) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
3131
if m.quitting {
3232
return m, tea.Quit
3333
}
@@ -48,25 +48,25 @@ func (m *spinner) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
4848
}
4949
}
5050

51-
func (m *spinner) View() string {
51+
func (m *SpinnerT) View() string {
5252
if m.quitting {
5353
return ""
5454
}
5555
return fmt.Sprintf("%s%s %s", m.prefix, m.spinner.View(), m.suffix)
5656
}
5757

58-
func (m *spinner) Stop() {
58+
func (m *SpinnerT) Stop() {
5959
m.quitting = true
6060
if m.done != nil {
6161
<-m.done
6262
}
6363
}
6464

65-
func (m *spinner) Text(t string) {
65+
func (m *SpinnerT) Text(t string) {
6666
m.suffix = t
6767
}
6868

69-
func (m *spinner) Start() {
69+
func (m *SpinnerT) Start() {
7070
if !isInteractive {
7171
fmt.Println(m.View())
7272
return
@@ -85,12 +85,12 @@ func (m *spinner) Start() {
8585
}()
8686
}
8787

88-
func StoppedSpinner(text string) *spinner {
88+
func StoppedSpinner(text string) *SpinnerT {
8989
spinner := newSpinner("", text)
9090
return spinner
9191
}
9292

93-
func Spinner(text string) *spinner {
93+
func Spinner(text string) *SpinnerT {
9494
spinner := StoppedSpinner(text)
9595
spinner.Start()
9696
return spinner

0 commit comments

Comments
 (0)