@@ -9131,6 +9131,7 @@ func (p *Parser) parseRestoreStatement() (ast.Statement, error) {
91319131 }
91329132
91339133 stmt := & ast.RestoreStatement {}
9134+ hasDatabaseName := true
91349135
91359136 // Parse restore kind (DATABASE, LOG, etc.)
91369137 switch strings .ToUpper (p .curTok .Literal ) {
@@ -9140,74 +9141,95 @@ func (p *Parser) parseRestoreStatement() (ast.Statement, error) {
91409141 case "LOG" :
91419142 stmt .Kind = "TransactionLog"
91429143 p .nextToken ()
9144+ case "FILELISTONLY" :
9145+ stmt .Kind = "FileListOnly"
9146+ p .nextToken ()
9147+ hasDatabaseName = false
9148+ case "VERIFYONLY" :
9149+ stmt .Kind = "VerifyOnly"
9150+ p .nextToken ()
9151+ hasDatabaseName = false
9152+ case "LABELONLY" :
9153+ stmt .Kind = "LabelOnly"
9154+ p .nextToken ()
9155+ hasDatabaseName = false
9156+ case "REWINDONLY" :
9157+ stmt .Kind = "RewindOnly"
9158+ p .nextToken ()
9159+ hasDatabaseName = false
9160+ case "HEADERONLY" :
9161+ stmt .Kind = "HeaderOnly"
9162+ p .nextToken ()
9163+ hasDatabaseName = false
91439164 default :
91449165 stmt .Kind = "Database"
91459166 }
91469167
9147- // Parse database name
9148- dbName := & ast.IdentifierOrValueExpression {}
9149- if p .curTok .Type == TokenIdent && strings .HasPrefix (p .curTok .Literal , "@" ) {
9150- // Variable reference
9151- varRef := & ast.VariableReference {Name : p .curTok .Literal }
9152- p .nextToken ()
9153- dbName .Value = varRef .Name
9154- dbName .ValueExpression = varRef
9155- } else {
9156- ident := p .parseIdentifier ()
9157- dbName .Value = ident .Value
9158- dbName .Identifier = ident
9159- }
9160- stmt .DatabaseName = dbName
9161-
9162- // Parse optional FILE = or FILEGROUP = before FROM
9163- for strings .ToUpper (p .curTok .Literal ) == "FILE" || strings .ToUpper (p .curTok .Literal ) == "FILEGROUP" {
9164- itemKind := "Files"
9165- if strings .ToUpper (p .curTok .Literal ) == "FILEGROUP" {
9166- itemKind = "FileGroups"
9167- }
9168- p .nextToken () // consume FILE/FILEGROUP
9169- if p .curTok .Type != TokenEquals {
9170- return nil , fmt .Errorf ("expected = after FILE/FILEGROUP, got %s" , p .curTok .Literal )
9171- }
9172- p .nextToken () // consume =
9173-
9174- fileInfo := & ast.BackupRestoreFileInfo {ItemKind : itemKind }
9175- // Parse the file name
9176- var item ast.ScalarExpression
9177- if p .curTok .Type == TokenString || p .curTok .Type == TokenNationalString {
9178- // Strip surrounding quotes
9179- val := p .curTok .Literal
9180- if len (val ) >= 2 && ((val [0 ] == '\'' && val [len (val )- 1 ] == '\'' ) || (val [0 ] == '"' && val [len (val )- 1 ] == '"' )) {
9181- val = val [1 : len (val )- 1 ]
9182- }
9183- item = & ast.StringLiteral {
9184- LiteralType : "String" ,
9185- Value : val ,
9186- IsNational : p .curTok .Type == TokenNationalString ,
9187- IsLargeObject : false ,
9188- }
9189- p .nextToken ()
9190- } else if p .curTok .Type == TokenIdent && strings .HasPrefix (p .curTok .Literal , "@" ) {
9191- item = & ast.VariableReference {Name : p .curTok .Literal }
9168+ // Parse database name (only for DATABASE and LOG kinds)
9169+ if hasDatabaseName {
9170+ dbName := & ast.IdentifierOrValueExpression {}
9171+ if p .curTok .Type == TokenIdent && strings .HasPrefix (p .curTok .Literal , "@" ) {
9172+ // Variable reference
9173+ varRef := & ast.VariableReference {Name : p .curTok .Literal }
91929174 p .nextToken ()
9175+ dbName .Value = varRef .Name
9176+ dbName .ValueExpression = varRef
91939177 } else {
91949178 ident := p .parseIdentifier ()
9195- item = & ast.ColumnReferenceExpression {
9196- ColumnType : "Regular" ,
9197- MultiPartIdentifier : & ast.MultiPartIdentifier {
9198- Identifiers : []* ast.Identifier {ident },
9199- Count : 1 ,
9200- },
9201- }
9179+ dbName .Value = ident .Value
9180+ dbName .Identifier = ident
92029181 }
9203- fileInfo .Items = append (fileInfo .Items , item )
9204- stmt .Files = append (stmt .Files , fileInfo )
9182+ stmt .DatabaseName = dbName
92059183
9206- if p .curTok .Type == TokenComma {
9207- p .nextToken ()
9184+ // Parse optional FILE = or FILEGROUP = before FROM
9185+ for strings .ToUpper (p .curTok .Literal ) == "FILE" || strings .ToUpper (p .curTok .Literal ) == "FILEGROUP" {
9186+ itemKind := "Files"
9187+ if strings .ToUpper (p .curTok .Literal ) == "FILEGROUP" {
9188+ itemKind = "FileGroups"
9189+ }
9190+ p .nextToken () // consume FILE/FILEGROUP
9191+ if p .curTok .Type != TokenEquals {
9192+ return nil , fmt .Errorf ("expected = after FILE/FILEGROUP, got %s" , p .curTok .Literal )
9193+ }
9194+ p .nextToken () // consume =
9195+
9196+ fileInfo := & ast.BackupRestoreFileInfo {ItemKind : itemKind }
9197+ // Parse the file name
9198+ var item ast.ScalarExpression
9199+ if p .curTok .Type == TokenString || p .curTok .Type == TokenNationalString {
9200+ // Strip surrounding quotes
9201+ val := p .curTok .Literal
9202+ if len (val ) >= 2 && ((val [0 ] == '\'' && val [len (val )- 1 ] == '\'' ) || (val [0 ] == '"' && val [len (val )- 1 ] == '"' )) {
9203+ val = val [1 : len (val )- 1 ]
9204+ }
9205+ item = & ast.StringLiteral {
9206+ LiteralType : "String" ,
9207+ Value : val ,
9208+ IsNational : p .curTok .Type == TokenNationalString ,
9209+ IsLargeObject : false ,
9210+ }
9211+ p .nextToken ()
9212+ } else if p .curTok .Type == TokenIdent && strings .HasPrefix (p .curTok .Literal , "@" ) {
9213+ item = & ast.VariableReference {Name : p .curTok .Literal }
9214+ p .nextToken ()
9215+ } else {
9216+ ident := p .parseIdentifier ()
9217+ item = & ast.ColumnReferenceExpression {
9218+ ColumnType : "Regular" ,
9219+ MultiPartIdentifier : & ast.MultiPartIdentifier {
9220+ Identifiers : []* ast.Identifier {ident },
9221+ Count : 1 ,
9222+ },
9223+ }
9224+ }
9225+ fileInfo .Items = append (fileInfo .Items , item )
9226+ stmt .Files = append (stmt .Files , fileInfo )
9227+
9228+ if p .curTok .Type == TokenComma {
9229+ p .nextToken ()
9230+ }
92089231 }
92099232 }
9210-
92119233 // Check for optional FROM clause
92129234 if strings .ToUpper (p .curTok .Literal ) != "FROM" {
92139235 // No FROM clause - check for WITH clause
@@ -9228,6 +9250,13 @@ func (p *Parser) parseRestoreStatement() (ast.Statement, error) {
92289250
92299251 // Check for device type
92309252 switch strings .ToUpper (p .curTok .Literal ) {
9253+ case "TAPE" :
9254+ device .DeviceType = "Tape"
9255+ p .nextToken ()
9256+ if p .curTok .Type != TokenEquals {
9257+ return nil , fmt .Errorf ("expected = after TAPE, got %s" , p .curTok .Literal )
9258+ }
9259+ p .nextToken ()
92319260 case "DISK" :
92329261 device .DeviceType = "Disk"
92339262 p .nextToken ()
@@ -9245,8 +9274,8 @@ func (p *Parser) parseRestoreStatement() (ast.Statement, error) {
92459274 }
92469275
92479276 // Parse device name
9248- if device .DeviceType == "Disk" || device .DeviceType == "URL" {
9249- // For DISK and URL , use PhysicalDevice with the string literal directly
9277+ if device .DeviceType == "Disk" || device .DeviceType == "URL" || device . DeviceType == "Tape" {
9278+ // For DISK, URL, and TAPE , use PhysicalDevice with the string literal directly
92509279 if p .curTok .Type == TokenString || p .curTok .Type == TokenNationalString {
92519280 // Strip the surrounding quotes from the literal
92529281 val := p .curTok .Literal
@@ -9358,6 +9387,28 @@ parseWithClause:
93589387 }
93599388 stmt .Options = append (stmt .Options , fsOpt )
93609389
9390+ case "MOVE" :
9391+ // MOVE 'logical_file_name' TO 'os_file_name'
9392+ opt := & ast.MoveRestoreOption {OptionKind : "Move" }
9393+ // Parse logical file name
9394+ expr , err := p .parseScalarExpression ()
9395+ if err != nil {
9396+ return nil , err
9397+ }
9398+ opt .LogicalFileName = expr
9399+ // Expect TO
9400+ if strings .ToUpper (p .curTok .Literal ) != "TO" {
9401+ return nil , fmt .Errorf ("expected TO after logical file name, got %s" , p .curTok .Literal )
9402+ }
9403+ p .nextToken ()
9404+ // Parse OS file name
9405+ osExpr , err := p .parseScalarExpression ()
9406+ if err != nil {
9407+ return nil , err
9408+ }
9409+ opt .OSFileName = osExpr
9410+ stmt .Options = append (stmt .Options , opt )
9411+
93619412 case "STOPATMARK" , "STOPBEFOREMARK" :
93629413 opt := & ast.StopRestoreOption {
93639414 OptionKind : "StopAt" ,
@@ -9399,21 +9450,73 @@ parseWithClause:
93999450 }
94009451 stmt .Options = append (stmt .Options , opt )
94019452
9402- case "KEEP_TEMPORAL_RETENTION" , "NOREWIND" , "NOUNLOAD" , "STATS" ,
9453+ case "FILE" , "MEDIANAME" , "MEDIAPASSWORD" , "PASSWORD" , "STOPAT" :
9454+ // Options that take a scalar expression value
9455+ optKind := optionName
9456+ switch optionName {
9457+ case "MEDIANAME" :
9458+ optKind = "MediaName"
9459+ case "MEDIAPASSWORD" :
9460+ optKind = "MediaPassword"
9461+ case "PASSWORD" :
9462+ optKind = "Password"
9463+ case "STOPAT" :
9464+ optKind = "StopAt"
9465+ case "FILE" :
9466+ optKind = "File"
9467+ }
9468+ opt := & ast.ScalarExpressionRestoreOption {OptionKind : optKind }
9469+ if p .curTok .Type == TokenEquals {
9470+ p .nextToken ()
9471+ expr , err := p .parseScalarExpression ()
9472+ if err != nil {
9473+ return nil , err
9474+ }
9475+ opt .Value = expr
9476+ }
9477+ stmt .Options = append (stmt .Options , opt )
9478+
9479+ case "STATS" :
9480+ // STATS can optionally have a value: STATS or STATS = 10
9481+ if p .curTok .Type == TokenEquals {
9482+ p .nextToken ()
9483+ expr , err := p .parseScalarExpression ()
9484+ if err != nil {
9485+ return nil , err
9486+ }
9487+ stmt .Options = append (stmt .Options , & ast.ScalarExpressionRestoreOption {
9488+ OptionKind : "Stats" ,
9489+ Value : expr ,
9490+ })
9491+ } else {
9492+ stmt .Options = append (stmt .Options , & ast.SimpleRestoreOption {OptionKind : "Stats" })
9493+ }
9494+
9495+ case "ENABLE_BROKER" , "ERROR_BROKER_CONVERSATIONS" , "NEW_BROKER" ,
9496+ "KEEP_REPLICATION" , "RESTRICTED_USER" ,
9497+ "KEEP_TEMPORAL_RETENTION" , "NOREWIND" , "NOUNLOAD" ,
94039498 "RECOVERY" , "NORECOVERY" , "REPLACE" , "RESTART" , "REWIND" ,
94049499 "UNLOAD" , "CHECKSUM" , "NO_CHECKSUM" , "STOP_ON_ERROR" ,
94059500 "CONTINUE_AFTER_ERROR" :
94069501 // Map option names to proper casing
94079502 optKind := optionName
94089503 switch optionName {
9504+ case "ENABLE_BROKER" :
9505+ optKind = "EnableBroker"
9506+ case "ERROR_BROKER_CONVERSATIONS" :
9507+ optKind = "ErrorBrokerConversations"
9508+ case "NEW_BROKER" :
9509+ optKind = "NewBroker"
9510+ case "KEEP_REPLICATION" :
9511+ optKind = "KeepReplication"
9512+ case "RESTRICTED_USER" :
9513+ optKind = "RestrictedUser"
94099514 case "KEEP_TEMPORAL_RETENTION" :
94109515 optKind = "KeepTemporalRetention"
94119516 case "NOREWIND" :
94129517 optKind = "NoRewind"
94139518 case "NOUNLOAD" :
94149519 optKind = "NoUnload"
9415- case "STATS" :
9416- optKind = "Stats"
94179520 case "RECOVERY" :
94189521 optKind = "Recovery"
94199522 case "NORECOVERY" :
@@ -12115,10 +12218,10 @@ func restoreOptionToJSON(o ast.RestoreOption) jsonNode {
1211512218 "OptionKind" : opt .OptionKind ,
1211612219 }
1211712220 if opt .LogicalFileName != nil {
12118- node ["LogicalFileName" ] = identifierOrValueExpressionToJSON (opt .LogicalFileName )
12221+ node ["LogicalFileName" ] = scalarExpressionToJSON (opt .LogicalFileName )
1211912222 }
1212012223 if opt .OSFileName != nil {
12121- node ["OSFileName" ] = identifierOrValueExpressionToJSON (opt .OSFileName )
12224+ node ["OSFileName" ] = scalarExpressionToJSON (opt .OSFileName )
1212212225 }
1212312226 return node
1212412227 default :
0 commit comments