Skip to content

Commit 03e6b0e

Browse files
committed
support for dumping triggers and functions and procedures
Signed-off-by: Avi Deitcher <[email protected]>
1 parent 6711682 commit 03e6b0e

File tree

7 files changed

+146
-53
lines changed

7 files changed

+146
-53
lines changed

cmd/dump.go

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ func dumpCmd(passedExecs execs, cmdConfig *cmdConfiguration) (*cobra.Command, er
111111
if !v.IsSet("compact") && dumpConfig != nil && dumpConfig.Compact != nil {
112112
compact = *dumpConfig.Compact
113113
}
114+
// should we dump triggers and functions?
115+
triggersAndFunctions := v.GetBool("triggers-and-functions")
116+
if !v.IsSet("triggers-and-functions") && dumpConfig != nil && dumpConfig.TriggersAndFunctions != nil {
117+
triggersAndFunctions = *dumpConfig.TriggersAndFunctions
118+
}
114119
maxAllowedPacket := v.GetInt("max-allowed-packet")
115120
if !v.IsSet("max-allowed-packet") && dumpConfig != nil && dumpConfig.MaxAllowedPacket != nil && *dumpConfig.MaxAllowedPacket != 0 {
116121
maxAllowedPacket = *dumpConfig.MaxAllowedPacket
@@ -231,20 +236,21 @@ func dumpCmd(passedExecs execs, cmdConfig *cmdConfiguration) (*cobra.Command, er
231236
defer dumpSpan.End()
232237
uid := uuid.New()
233238
dumpOpts := core.DumpOptions{
234-
Targets: targets,
235-
Safechars: safechars,
236-
DBNames: include,
237-
DBConn: cmdConfig.dbconn,
238-
Compressor: compressor,
239-
Encryptor: encryptor,
240-
Exclude: exclude,
241-
PreBackupScripts: preBackupScripts,
242-
PostBackupScripts: postBackupScripts,
243-
SuppressUseDatabase: noDatabaseName,
244-
Compact: compact,
245-
MaxAllowedPacket: maxAllowedPacket,
246-
Run: uid,
247-
FilenamePattern: filenamePattern,
239+
Targets: targets,
240+
Safechars: safechars,
241+
DBNames: include,
242+
DBConn: cmdConfig.dbconn,
243+
Compressor: compressor,
244+
Encryptor: encryptor,
245+
Exclude: exclude,
246+
PreBackupScripts: preBackupScripts,
247+
PostBackupScripts: postBackupScripts,
248+
SuppressUseDatabase: noDatabaseName,
249+
Compact: compact,
250+
TriggersAndFunctions: triggersAndFunctions,
251+
MaxAllowedPacket: maxAllowedPacket,
252+
Run: uid,
253+
FilenamePattern: filenamePattern,
248254
}
249255
_, err := executor.Dump(tracerCtx, dumpOpts)
250256
if err != nil {
@@ -318,6 +324,9 @@ S3: If it is a URL of the format s3://bucketname/path then it will connect via S
318324
// max-allowed-packet size
319325
flags.Int("max-allowed-packet", defaultMaxAllowedPacket, "Maximum size of the buffer for client/server communication, similar to mysqldump's max_allowed_packet. 0 means to use the default size.")
320326

327+
// whether to include triggers and functions
328+
flags.Bool("triggers-and-functions", false, "Whether to include triggers and functions in the dump.")
329+
321330
cmd.MarkFlagsMutuallyExclusive("once", "cron")
322331
cmd.MarkFlagsMutuallyExclusive("once", "begin")
323332
cmd.MarkFlagsMutuallyExclusive("once", "frequency")

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ require (
3131
)
3232

3333
require (
34-
github.com/databacker/api/go/api v0.0.0-20250418100420-12e1adda1303
34+
github.com/databacker/api/go/api v0.0.0-20250423104730-2789787a240e
3535
github.com/google/go-cmp v0.6.0
3636
go.opentelemetry.io/otel v1.31.0
3737
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ github.com/databacker/api/go/api v0.0.0-20250418091750-e67e3226ca5f h1:vuPsDEgli
8181
github.com/databacker/api/go/api v0.0.0-20250418091750-e67e3226ca5f/go.mod h1:bQhbl71Lk1ATni0H+u249hjoQ8ShAdVNcNjnw6z+SbE=
8282
github.com/databacker/api/go/api v0.0.0-20250418100420-12e1adda1303 h1:TVLyJzdvDvWIEs1/v6G0rQPpZeUsArQ7skzicjfCV8I=
8383
github.com/databacker/api/go/api v0.0.0-20250418100420-12e1adda1303/go.mod h1:bQhbl71Lk1ATni0H+u249hjoQ8ShAdVNcNjnw6z+SbE=
84+
github.com/databacker/api/go/api v0.0.0-20250423104730-2789787a240e h1:0ITg+YYAjyvM+rXirvZvx/PBLhMhHG+Nj5h+1flzWwY=
85+
github.com/databacker/api/go/api v0.0.0-20250423104730-2789787a240e/go.mod h1:bQhbl71Lk1ATni0H+u249hjoQ8ShAdVNcNjnw6z+SbE=
8486
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8587
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
8688
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

pkg/core/dump.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func (e *Executor) Dump(ctx context.Context, opts DumpOptions) (DumpResults, err
3636
compressor := opts.Compressor
3737
encryptor := opts.Encryptor
3838
compact := opts.Compact
39+
triggersAndFunctions := opts.TriggersAndFunctions
3940
suppressUseDatabase := opts.SuppressUseDatabase
4041
maxAllowedPacket := opts.MaxAllowedPacket
4142
filenamePattern := opts.FilenamePattern
@@ -104,9 +105,10 @@ func (e *Executor) Dump(ctx context.Context, opts DumpOptions) (DumpResults, err
104105
results.DumpStart = time.Now()
105106
dbDumpCtx, dbDumpSpan := tracer.Start(ctx, "database_dump")
106107
if err := database.Dump(dbDumpCtx, dbconn, database.DumpOpts{
107-
Compact: compact,
108-
SuppressUseDatabase: suppressUseDatabase,
109-
MaxAllowedPacket: maxAllowedPacket,
108+
Compact: compact,
109+
TriggersAndFunctions: triggersAndFunctions,
110+
SuppressUseDatabase: suppressUseDatabase,
111+
MaxAllowedPacket: maxAllowedPacket,
110112
}, dw); err != nil {
111113
dbDumpSpan.SetStatus(codes.Error, err.Error())
112114
dbDumpSpan.End()

pkg/core/dumpoptions.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,19 @@ import (
99
)
1010

1111
type DumpOptions struct {
12-
Targets []storage.Storage
13-
Safechars bool
14-
DBNames []string
15-
DBConn database.Connection
16-
Compressor compression.Compressor
17-
Encryptor encrypt.Encryptor
18-
Exclude []string
19-
PreBackupScripts string
20-
PostBackupScripts string
21-
Compact bool
22-
SuppressUseDatabase bool
23-
MaxAllowedPacket int
24-
Run uuid.UUID
25-
FilenamePattern string
12+
Targets []storage.Storage
13+
Safechars bool
14+
DBNames []string
15+
DBConn database.Connection
16+
Compressor compression.Compressor
17+
Encryptor encrypt.Encryptor
18+
Exclude []string
19+
PreBackupScripts string
20+
PostBackupScripts string
21+
Compact bool
22+
TriggersAndFunctions bool
23+
SuppressUseDatabase bool
24+
MaxAllowedPacket int
25+
Run uuid.UUID
26+
FilenamePattern string
2627
}

pkg/database/dump.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import (
99
)
1010

1111
type DumpOpts struct {
12-
Compact bool
13-
SuppressUseDatabase bool
14-
MaxAllowedPacket int
12+
Compact bool
13+
TriggersAndFunctions bool
14+
SuppressUseDatabase bool
15+
MaxAllowedPacket int
1516
}
1617

1718
func Dump(ctx context.Context, dbconn Connection, opts DumpOpts, writers []DumpWriter) error {
@@ -31,13 +32,14 @@ func Dump(ctx context.Context, dbconn Connection, opts DumpOpts, writers []DumpW
3132
defer func() { _ = db.Close() }()
3233
for _, schema := range writer.Schemas {
3334
dumper := &mysql.Data{
34-
Out: writer.Writer,
35-
Connection: db,
36-
Schema: schema,
37-
Host: dbconn.Host,
38-
Compact: opts.Compact,
39-
SuppressUseDatabase: opts.SuppressUseDatabase,
40-
MaxAllowedPacket: opts.MaxAllowedPacket,
35+
Out: writer.Writer,
36+
Connection: db,
37+
Schema: schema,
38+
Host: dbconn.Host,
39+
Compact: opts.Compact,
40+
TriggersAndFunctions: opts.TriggersAndFunctions,
41+
SuppressUseDatabase: opts.SuppressUseDatabase,
42+
MaxAllowedPacket: opts.MaxAllowedPacket,
4143
}
4244
if err := dumper.Dump(); err != nil {
4345
return fmt.Errorf("failed to dump database %s: %v", schema, err)

pkg/database/mysql/dump.go

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"context"
2020
"database/sql"
2121
"errors"
22+
"fmt"
2223
"io"
2324
"text/template"
2425
"time"
@@ -34,17 +35,18 @@ Data struct to configure dump behavior
3435
LockTables: Lock all tables for the duration of the dump
3536
*/
3637
type Data struct {
37-
Out io.Writer
38-
Connection *sql.DB
39-
IgnoreTables []string
40-
MaxAllowedPacket int
41-
LockTables bool
42-
Schema string
43-
Compact bool
44-
Host string
45-
SuppressUseDatabase bool
46-
Charset string
47-
Collation string
38+
Out io.Writer
39+
Connection *sql.DB
40+
IgnoreTables []string
41+
MaxAllowedPacket int
42+
LockTables bool
43+
Schema string
44+
Compact bool
45+
TriggersAndFunctions bool
46+
Host string
47+
SuppressUseDatabase bool
48+
Charset string
49+
Collation string
4850

4951
tx *sql.Tx
5052
headerTmpl *template.Template
@@ -186,6 +188,19 @@ func (data *Data) Dump() error {
186188
return err
187189
}
188190
}
191+
192+
// Dump triggers and functions
193+
if data.TriggersAndFunctions {
194+
if err := data.dumpTriggers(); err != nil {
195+
return err
196+
}
197+
198+
// Dump procedures and functions
199+
if err := data.dumpProceduresAndFunctions(); err != nil {
200+
return err
201+
}
202+
}
203+
189204
if data.err != nil {
190205
return data.err
191206
}
@@ -328,6 +343,68 @@ func (data *Data) isIgnoredTable(name string) bool {
328343
return false
329344
}
330345

346+
// dumpTriggers dump the triggers for the current schema
347+
func (data *Data) dumpTriggers() error {
348+
rows, err := data.tx.Query("SHOW TRIGGERS FROM `" + data.Schema + "`")
349+
if err != nil {
350+
return err
351+
}
352+
defer rows.Close()
353+
354+
for rows.Next() {
355+
var triggerName, event, timing, statement, sqlMode, definer, table, charset sql.NullString
356+
var created sql.NullTime
357+
if err := rows.Scan(&triggerName, &event, &timing, &statement, &sqlMode, &definer, &table, &created, &charset); err != nil {
358+
return err
359+
}
360+
if !triggerName.Valid || !statement.Valid {
361+
continue
362+
}
363+
_, err := data.Out.Write([]byte(fmt.Sprintf("CREATE TRIGGER `%s` %s %s ON `%s` FOR EACH ROW %s;\n",
364+
triggerName.String, timing.String, event.String, table.String, statement.String)))
365+
if err != nil {
366+
return err
367+
}
368+
}
369+
return nil
370+
}
371+
372+
// dumpProceduresAndFunctions dump the procedures and functions for the current schema
373+
func (data *Data) dumpProceduresAndFunctions() error {
374+
queries := []string{
375+
"SHOW PROCEDURE STATUS WHERE Db = '" + data.Schema + "'",
376+
"SHOW FUNCTION STATUS WHERE Db = '" + data.Schema + "'",
377+
}
378+
for _, query := range queries {
379+
rows, err := data.tx.Query(query)
380+
if err != nil {
381+
return err
382+
}
383+
defer rows.Close()
384+
385+
for rows.Next() {
386+
var name, typeDef, otherFields sql.NullString
387+
if err := rows.Scan(&name, &typeDef, &otherFields); err != nil {
388+
return err
389+
}
390+
if name.Valid && typeDef.Valid {
391+
createQuery := fmt.Sprintf("SHOW CREATE %s `%s`", typeDef.String, name.String)
392+
var createStmt sql.NullString
393+
if err := data.tx.QueryRow(createQuery).Scan(&createStmt); err != nil {
394+
return err
395+
}
396+
if createStmt.Valid {
397+
_, err := data.Out.Write([]byte(createStmt.String + "\n"))
398+
if err != nil {
399+
return err
400+
}
401+
}
402+
}
403+
}
404+
}
405+
return nil
406+
}
407+
331408
func (meta *metaData) updateMetadata(data *Data) (err error) {
332409
var serverVersion sql.NullString
333410
err = data.tx.QueryRow("SELECT version()").Scan(&serverVersion)

0 commit comments

Comments
 (0)