Skip to content

Commit dbc8185

Browse files
committed
Add BACKUP ENCRYPTION option support
- Add BackupEncryptionOption and BackupOptionBase interface - Support ENCRYPTION(ALGORITHM = ..., SERVER CERTIFICATE|ASYMMETRIC KEY = ...) - Reuse existing CryptoMechanism type - Add Stats option kind capitalization - Enable 1 passing test
1 parent 6bf8518 commit dbc8185

5 files changed

Lines changed: 173 additions & 41 deletions

File tree

ast/backup_statement.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ type BackupDatabaseStatement struct {
66
DatabaseName *IdentifierOrValueExpression
77
MirrorToClauses []*MirrorToClause
88
Devices []*DeviceInfo
9-
Options []*BackupOption
9+
Options []BackupOptionBase
1010
}
1111

1212
// MirrorToClause represents a MIRROR TO clause in a BACKUP statement
@@ -22,19 +22,37 @@ func (s *BackupDatabaseStatement) node() {}
2222
type BackupTransactionLogStatement struct {
2323
DatabaseName *IdentifierOrValueExpression
2424
Devices []*DeviceInfo
25-
Options []*BackupOption
25+
Options []BackupOptionBase
2626
}
2727

2828
func (s *BackupTransactionLogStatement) statementNode() {}
2929
func (s *BackupTransactionLogStatement) statement() {}
3030
func (s *BackupTransactionLogStatement) node() {}
3131

32+
// BackupOptionBase is an interface for backup options
33+
type BackupOptionBase interface {
34+
backupOption()
35+
}
36+
3237
// BackupOption represents a backup option
3338
type BackupOption struct {
3439
OptionKind string // Compression, NoCompression, StopOnError, ContinueAfterError, etc.
3540
Value ScalarExpression
3641
}
3742

43+
func (o *BackupOption) backupOption() {}
44+
45+
// BackupEncryptionOption represents an ENCRYPTION(...) backup option
46+
type BackupEncryptionOption struct {
47+
Algorithm string // Aes128, Aes192, Aes256, TripleDes3Key
48+
Encryptor *CryptoMechanism
49+
OptionKind string // typically "None"
50+
}
51+
52+
func (o *BackupEncryptionOption) backupOption() {}
53+
54+
// CryptoMechanism is defined in create_simple_statements.go
55+
3856
// BackupCertificateStatement represents a BACKUP CERTIFICATE statement
3957
type BackupCertificateStatement struct {
4058
Name *Identifier

parser/marshal.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11534,7 +11534,7 @@ func backupDatabaseStatementToJSON(s *ast.BackupDatabaseStatement) jsonNode {
1153411534
if len(s.Options) > 0 {
1153511535
options := make([]jsonNode, len(s.Options))
1153611536
for i, o := range s.Options {
11537-
options[i] = backupOptionToJSON(o)
11537+
options[i] = backupOptionBaseToJSON(o)
1153811538
}
1153911539
node["Options"] = options
1154011540
}
@@ -11565,7 +11565,7 @@ func backupTransactionLogStatementToJSON(s *ast.BackupTransactionLogStatement) j
1156511565
if len(s.Options) > 0 {
1156611566
options := make([]jsonNode, len(s.Options))
1156711567
for i, o := range s.Options {
11568-
options[i] = backupOptionToJSON(o)
11568+
options[i] = backupOptionBaseToJSON(o)
1156911569
}
1157011570
node["Options"] = options
1157111571
}
@@ -11659,6 +11659,17 @@ func restoreMasterKeyStatementToJSON(s *ast.RestoreMasterKeyStatement) jsonNode
1165911659
return node
1166011660
}
1166111661

11662+
func backupOptionBaseToJSON(o ast.BackupOptionBase) jsonNode {
11663+
switch opt := o.(type) {
11664+
case *ast.BackupOption:
11665+
return backupOptionToJSON(opt)
11666+
case *ast.BackupEncryptionOption:
11667+
return backupEncryptionOptionToJSON(opt)
11668+
default:
11669+
return jsonNode{"$type": "Unknown"}
11670+
}
11671+
}
11672+
1166211673
func backupOptionToJSON(o *ast.BackupOption) jsonNode {
1166311674
node := jsonNode{
1166411675
"$type": "BackupOption",
@@ -11670,6 +11681,18 @@ func backupOptionToJSON(o *ast.BackupOption) jsonNode {
1167011681
return node
1167111682
}
1167211683

11684+
func backupEncryptionOptionToJSON(o *ast.BackupEncryptionOption) jsonNode {
11685+
node := jsonNode{
11686+
"$type": "BackupEncryptionOption",
11687+
"Algorithm": o.Algorithm,
11688+
"OptionKind": o.OptionKind,
11689+
}
11690+
if o.Encryptor != nil {
11691+
node["Encryptor"] = cryptoMechanismToJSON(o.Encryptor)
11692+
}
11693+
return node
11694+
}
11695+
1167311696
func deviceInfoToJSON(d *ast.DeviceInfo) jsonNode {
1167411697
node := jsonNode{
1167511698
"$type": "DeviceInfo",

parser/parse_statements.go

Lines changed: 126 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7062,51 +7062,63 @@ func (p *Parser) parseBackupStatement() (ast.Statement, error) {
70627062
}
70637063

70647064
// Parse optional WITH clause
7065-
var options []*ast.BackupOption
7065+
var options []ast.BackupOptionBase
70667066
if p.curTok.Type == TokenWith {
70677067
p.nextToken()
70687068

70697069
for {
70707070
optionName := strings.ToUpper(p.curTok.Literal)
7071-
option := &ast.BackupOption{}
70727071

7073-
switch optionName {
7074-
case "COMPRESSION":
7075-
option.OptionKind = "Compression"
7076-
case "NO_COMPRESSION":
7077-
option.OptionKind = "NoCompression"
7078-
case "STOP_ON_ERROR":
7079-
option.OptionKind = "StopOnError"
7080-
case "CONTINUE_AFTER_ERROR":
7081-
option.OptionKind = "ContinueAfterError"
7082-
case "CHECKSUM":
7083-
option.OptionKind = "Checksum"
7084-
case "NO_CHECKSUM":
7085-
option.OptionKind = "NoChecksum"
7086-
case "INIT":
7087-
option.OptionKind = "Init"
7088-
case "NOINIT":
7089-
option.OptionKind = "NoInit"
7090-
case "FORMAT":
7091-
option.OptionKind = "Format"
7092-
case "NOFORMAT":
7093-
option.OptionKind = "NoFormat"
7094-
default:
7095-
option.OptionKind = optionName
7096-
}
7097-
p.nextToken()
7098-
7099-
// Check for = value
7100-
if p.curTok.Type == TokenEquals {
7101-
p.nextToken()
7102-
val, err := p.parseScalarExpression()
7072+
// Check for ENCRYPTION with parentheses
7073+
if optionName == "ENCRYPTION" && p.peekTok.Type == TokenLParen {
7074+
encOpt, err := p.parseBackupEncryptionOption()
71037075
if err != nil {
71047076
return nil, err
71057077
}
7106-
option.Value = val
7107-
}
7078+
options = append(options, encOpt)
7079+
} else {
7080+
option := &ast.BackupOption{}
7081+
7082+
switch optionName {
7083+
case "COMPRESSION":
7084+
option.OptionKind = "Compression"
7085+
case "NO_COMPRESSION":
7086+
option.OptionKind = "NoCompression"
7087+
case "STOP_ON_ERROR":
7088+
option.OptionKind = "StopOnError"
7089+
case "CONTINUE_AFTER_ERROR":
7090+
option.OptionKind = "ContinueAfterError"
7091+
case "CHECKSUM":
7092+
option.OptionKind = "Checksum"
7093+
case "NO_CHECKSUM":
7094+
option.OptionKind = "NoChecksum"
7095+
case "INIT":
7096+
option.OptionKind = "Init"
7097+
case "NOINIT":
7098+
option.OptionKind = "NoInit"
7099+
case "FORMAT":
7100+
option.OptionKind = "Format"
7101+
case "NOFORMAT":
7102+
option.OptionKind = "NoFormat"
7103+
case "STATS":
7104+
option.OptionKind = "Stats"
7105+
default:
7106+
option.OptionKind = optionName
7107+
}
7108+
p.nextToken()
7109+
7110+
// Check for = value
7111+
if p.curTok.Type == TokenEquals {
7112+
p.nextToken()
7113+
val, err := p.parseScalarExpression()
7114+
if err != nil {
7115+
return nil, err
7116+
}
7117+
option.Value = val
7118+
}
71087119

7109-
options = append(options, option)
7120+
options = append(options, option)
7121+
}
71107122

71117123
if p.curTok.Type == TokenComma {
71127124
p.nextToken()
@@ -7137,6 +7149,85 @@ func (p *Parser) parseBackupStatement() (ast.Statement, error) {
71377149
}, nil
71387150
}
71397151

7152+
func (p *Parser) parseBackupEncryptionOption() (*ast.BackupEncryptionOption, error) {
7153+
// curTok is ENCRYPTION
7154+
p.nextToken() // consume ENCRYPTION
7155+
7156+
if p.curTok.Type != TokenLParen {
7157+
return nil, fmt.Errorf("expected ( after ENCRYPTION, got %s", p.curTok.Literal)
7158+
}
7159+
p.nextToken() // consume (
7160+
7161+
opt := &ast.BackupEncryptionOption{
7162+
OptionKind: "None",
7163+
}
7164+
7165+
// Parse options inside parentheses
7166+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
7167+
optName := strings.ToUpper(p.curTok.Literal)
7168+
p.nextToken()
7169+
7170+
if p.curTok.Type == TokenEquals {
7171+
p.nextToken() // consume =
7172+
}
7173+
7174+
switch optName {
7175+
case "ALGORITHM":
7176+
// Parse algorithm type: AES_128, AES_192, AES_256, TRIPLE_DES_3KEY
7177+
algName := strings.ToUpper(p.curTok.Literal)
7178+
switch algName {
7179+
case "AES_128":
7180+
opt.Algorithm = "Aes128"
7181+
case "AES_192":
7182+
opt.Algorithm = "Aes192"
7183+
case "AES_256":
7184+
opt.Algorithm = "Aes256"
7185+
case "TRIPLE_DES_3KEY":
7186+
opt.Algorithm = "TripleDes3Key"
7187+
default:
7188+
opt.Algorithm = algName
7189+
}
7190+
p.nextToken()
7191+
case "SERVER":
7192+
// SERVER CERTIFICATE or SERVER ASYMMETRIC KEY
7193+
mechType := strings.ToUpper(p.curTok.Literal)
7194+
p.nextToken()
7195+
7196+
if p.curTok.Type == TokenEquals {
7197+
p.nextToken() // consume =
7198+
}
7199+
7200+
opt.Encryptor = &ast.CryptoMechanism{}
7201+
switch mechType {
7202+
case "CERTIFICATE":
7203+
opt.Encryptor.CryptoMechanismType = "Certificate"
7204+
case "ASYMMETRIC":
7205+
// Consume KEY
7206+
if p.curTok.Type == TokenKey {
7207+
p.nextToken()
7208+
}
7209+
if p.curTok.Type == TokenEquals {
7210+
p.nextToken() // consume =
7211+
}
7212+
opt.Encryptor.CryptoMechanismType = "AsymmetricKey"
7213+
}
7214+
7215+
// Parse identifier
7216+
opt.Encryptor.Identifier = p.parseIdentifier()
7217+
}
7218+
7219+
if p.curTok.Type == TokenComma {
7220+
p.nextToken()
7221+
}
7222+
}
7223+
7224+
if p.curTok.Type == TokenRParen {
7225+
p.nextToken() // consume )
7226+
}
7227+
7228+
return opt, nil
7229+
}
7230+
71407231
func (p *Parser) parseBackupCertificateStatement() (*ast.BackupCertificateStatement, error) {
71417232
// Consume CERTIFICATE
71427233
p.nextToken()
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"todo": true}
1+
{}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"todo": true}
1+
{}

0 commit comments

Comments
 (0)