@@ -11,11 +11,12 @@ import (
1111 "github.com/microsoft/go-sqlcmd/internal/tools"
1212 "net/url"
1313 "os"
14- "path"
1514 "path/filepath"
1615 "runtime"
1716 "strings"
1817
18+ "github.com/microsoft/go-sqlcmd/pkg/mssqlcontainer"
19+
1920 "github.com/microsoft/go-sqlcmd/cmd/modern/root/open"
2021
2122 "github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig"
@@ -224,7 +225,7 @@ func (c *MssqlBase) AddFlags(
224225 String : & c .usingDatabaseUrl ,
225226 DefaultString : "" ,
226227 Name : "using" ,
227- Usage : "Download (into container) and attach database ( .bak) from URL" ,
228+ Usage : "Download and use database from .bak/.bacpac/.mdf/.7z URL" ,
228229 })
229230
230231 addFlag (cmdparser.FlagOptions {
@@ -514,18 +515,16 @@ func (c *MssqlBase) validateUsingUrlExists() {
514515 if u .Path == "" {
515516 output .FatalfWithHints (
516517 []string {
517- "--using URL must have a path to .bak or .bacpac file" ,
518+ "--using URL must have a path to .bak, .bacpac or .mdf (.7z) file" ,
518519 },
519520 "%q is not a valid URL for --using flag" , c .usingDatabaseUrl )
520521 }
521522
522- // At the moment we only support attaching .bak files, but we should
523- // support .bacpacs and .mdfs in the future
524523 _ , f := filepath .Split (u .Path )
525- if filepath .Ext (f ) != ".bak" && filepath .Ext (f ) != ".bacpac" {
524+ if filepath .Ext (f ) != ".bak" && filepath .Ext (f ) != ".bacpac" && filepath . Ext ( f ) != ".mdf" && filepath . Ext ( f ) != ".7z" {
526525 output .FatalfWithHints (
527526 []string {
528- "--using file URL must be a .bak or .bacpac file" ,
527+ "--using file URL must be a .bak, .bacpac, or .mdf (.7z) file" ,
529528 },
530529 "Invalid --using file type, extension %q is not supported" , filepath .Ext (f ))
531530 }
@@ -582,47 +581,22 @@ CHECK_POLICY=OFF`
582581 }
583582}
584583
585- func getDbNameAsIdentifier (dbName string ) string {
586- escapedDbNAme := strings .ReplaceAll (dbName , "'" , "''" )
587- return strings .ReplaceAll (escapedDbNAme , "]" , "]]" )
588- }
589-
590- func getDbNameAsNonIdentifier (dbName string ) string {
591- return strings .ReplaceAll (dbName , "]" , "]]" )
592- }
593-
594- // parseDbName returns the databaseName from --using arg
595- // It sets database name to the specified database name
596- // or in absence of it, it is set to the filename without
597- // extension.
598- func parseDbName (usingDbUrl string ) string {
599- u , _ := url .Parse (usingDbUrl )
600- dbToken := path .Base (u .Path )
601- if dbToken != "." && dbToken != "/" {
602- lastIdx := strings .LastIndex (dbToken , ".bak" )
603- if lastIdx == - 1 {
604- lastIdx = strings .LastIndex (dbToken , ".bacpac" )
605- }
606- if lastIdx != - 1 {
607- //Get file name without extension
608- fileName := dbToken [0 :lastIdx ]
609- lastIdx += 5
610- if lastIdx >= len (dbToken ) {
611- return fileName
612- }
613- //Return database name if it was specified
614- return dbToken [lastIdx :]
615- }
616- }
617- return ""
618- }
619-
620584func extractUrl (usingArg string ) string {
621585 urlEndIdx := strings .LastIndex (usingArg , ".bak" )
586+ if urlEndIdx == - 1 {
587+ urlEndIdx = strings .LastIndex (usingArg , ".mdf" )
588+ }
622589 if urlEndIdx != - 1 {
623590 return usingArg [0 :(urlEndIdx + 4 )]
624591 }
625592
593+ if urlEndIdx == - 1 {
594+ urlEndIdx = strings .LastIndex (usingArg , ".7z" )
595+ if urlEndIdx != - 1 {
596+ return usingArg [0 :(urlEndIdx + 3 )]
597+ }
598+ }
599+
626600 if urlEndIdx == - 1 {
627601 urlEndIdx = strings .LastIndex (usingArg , ".bacpac" )
628602 if urlEndIdx != - 1 {
@@ -639,111 +613,15 @@ func (c *MssqlBase) downloadAndRestoreDb(
639613 userName string ,
640614 password string ,
641615) {
642- output := c .Cmd .Output ()
643- databaseName := parseDbName (c .usingDatabaseUrl )
644- databaseUrl := extractUrl (c .usingDatabaseUrl )
645-
646- _ , file := filepath .Split (databaseUrl )
647-
648- // Download file from URL into container
649- output .Infof ("Downloading %s" , file )
650-
651- temporaryFolder := "/var/opt/mssql/backup"
652-
653- controller .DownloadFile (
616+ mssqlcontainer .DownloadAndRestoreDb (
617+ controller ,
654618 containerId ,
655- databaseUrl ,
656- temporaryFolder ,
657- )
658-
659- dbNameAsIdentifier := getDbNameAsIdentifier (databaseName )
660- dbNameAsNonIdentifier := getDbNameAsNonIdentifier (databaseName )
661-
662- u , err := url .Parse (databaseUrl )
663- c .CheckErr (err )
664-
665- _ , f := filepath .Split (u .Path )
666- if filepath .Ext (f ) == ".bak" {
667- // Restore database from file
668- output .Infof ("Restoring database %s" , databaseName )
669-
670- text := `SET NOCOUNT ON;
671-
672- -- Build a SQL Statement to restore any .bak file to the Linux filesystem
673- DECLARE @sql NVARCHAR(max)
674-
675- -- This table definition works since SQL Server 2017, therefore
676- -- works for all SQL Server containers (which started in 2017)
677- DECLARE @fileListTable TABLE (
678- [LogicalName] NVARCHAR(128),
679- [PhysicalName] NVARCHAR(260),
680- [Type] CHAR(1),
681- [FileGroupName] NVARCHAR(128),
682- [Size] NUMERIC(20,0),
683- [MaxSize] NUMERIC(20,0),
684- [FileID] BIGINT,
685- [CreateLSN] NUMERIC(25,0),
686- [DropLSN] NUMERIC(25,0),
687- [UniqueID] UNIQUEIDENTIFIER,
688- [ReadOnlyLSN] NUMERIC(25,0),
689- [ReadWriteLSN] NUMERIC(25,0),
690- [BackupSizeInBytes] BIGINT,
691- [SourceBlockSize] INT,
692- [FileGroupID] INT,
693- [LogGroupGUID] UNIQUEIDENTIFIER,
694- [DifferentialBaseLSN] NUMERIC(25,0),
695- [DifferentialBaseGUID] UNIQUEIDENTIFIER,
696- [IsReadOnly] BIT,
697- [IsPresent] BIT,
698- [TDEThumbprint] VARBINARY(32),
699- [SnapshotURL] NVARCHAR(360)
700- )
701-
702- INSERT INTO @fileListTable
703- EXEC('RESTORE FILELISTONLY FROM DISK = ''%s/%s''')
704- SET @sql = 'RESTORE DATABASE [%s] FROM DISK = ''%s/%s'' WITH '
705- SELECT @sql = @sql + char(13) + ' MOVE ''' + LogicalName + ''' TO ''/var/opt/mssql/' + LogicalName + '.' + RIGHT(PhysicalName,CHARINDEX('\',PhysicalName)) + ''','
706- FROM @fileListTable
707- WHERE IsPresent = 1
708- SET @sql = SUBSTRING(@sql, 1, LEN(@sql)-1)
709- EXEC(@sql)`
710-
711- c .query (fmt .Sprintf (text , temporaryFolder , file , dbNameAsIdentifier , temporaryFolder , file ))
712- } else if filepath .Ext (f ) == ".bacpac" {
713- // sqlpackage /a:import /SourceFile:DaasMM.bacpac
714- // /tu:"joey"
715- // /tp:"6%oO3L#X%d%8hAH5E*y6plD6#IV1$PEESS$ZAhC8uL#@Q@40e3"
716- // /tsn:"localhost,1433"
717- // /tdn:"db1"
718-
719- controller .DownloadFile (
720- containerId ,
721- "https://aka.ms/sqlpackage-linux" ,
722- "/tmp" ,
723- )
724-
725- controller .RunCmdInContainer (containerId , []string {"apt-get" , "update" })
726- controller .RunCmdInContainer (containerId , []string {"apt-get" , "install" , "-y" , "unzip" })
727- controller .RunCmdInContainer (containerId , []string {"unzip" , "/tmp/sqlpackage-linux" , "-d" , "/opt/sqlpackage" })
728- controller .RunCmdInContainer (containerId , []string {"chmod" , "+x" , "/opt/sqlpackage/sqlpackage" })
729- //controller.RunCmdInContainer(containerId, []string{"echo", `'export PATH="$PATH:/opt/sqlpackage"' >> ~/.bash_profile`})
730- //controller.RunCmdInContainer(containerId, []string{"source", "~/.bash_profile"})
731- controller .RunCmdInContainer (containerId , []string {
732- "/opt/sqlpackage/sqlpackage" ,
733- "/a:import" ,
734- "/SourceFile:" + temporaryFolder + "/" + file ,
735- "/tu:" + userName ,
736- "/tp:" + password ,
737- "/tsn:127.0.0.1,1433" ,
738- "/tdn:" + dbNameAsIdentifier ,
739- "/TargetTrustServerCertificate:true" })
740- }
741-
742- alterDefaultDb := fmt .Sprintf (
743- "ALTER LOGIN [%s] WITH DEFAULT_DATABASE = [%s]" ,
619+ c .usingDatabaseUrl ,
744620 userName ,
745- dbNameAsNonIdentifier )
746- c .query (alterDefaultDb )
621+ password ,
622+ c .query ,
623+ c .Cmd .Output (),
624+ )
747625}
748626
749627func (c * MssqlBase ) downloadImage (
0 commit comments