-
Notifications
You must be signed in to change notification settings - Fork 63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Round out --use scenario (add "use" sub-command, add local file, add .bacpac / .mdf & .ldf / compressed file support) #319
base: main
Are you sure you want to change the base?
Conversation
b239523
to
0e6223f
Compare
What about supporting non-public URLs (SAS etc)? Does it download the file then discover its type similar to how you'd have to examine a zip to know its content? |
cmd/modern/root/use.go
Outdated
|
||
endpoint, user := config.CurrentContext() | ||
|
||
c.sql = sql.New(sql.SqlOptions{UnitTesting: false}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
output.FatalfWithHintExamples([][]string{ | ||
{"Create new context with a sql container ", "sqlcmd create mssql"}, | ||
}, "Current context does not have a container") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what will this look like for non-container connections?
We can't claim this is production/enterprise ready until we support that.
I'd have expected most of the code from lines 76 to 96 to be outside the "if it's a container block"
Supporting a UNC or local file path to a bak file is a relatively straightforward restore operation
Restore from URL is also straightforward without copying the file locally if the server has a credential stored for the URL already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is only for container scenarios (I'll update the PR description). I am not sure if non-container scenarios are needed. (folks already have solutions for all of this). Containers are particularly tricky in this regard for our normal users (who are windows centric). This functionality abstracts most of the users during the early part of their journey from all these internals.
cmd/modern/root/use.go
Outdated
"Unable to download file to container") | ||
} | ||
|
||
useDatabase.CopyToContainer(id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added an issue for this:
cmd/modern/root/use.go
Outdated
useDatabase.BringOnline( | ||
c.sql.Query, | ||
user.BasicAuth.Username, | ||
secret.Decode(user.BasicAuth.Password, user.BasicAuth.PasswordEncryption), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we have a "credential" or "authentication" abstraction associated with the user to pass around instead of explicit user name and password? We're going to need to support all the auth types.
|
||
// Create and add some files to the archive. | ||
var buf bytes.Buffer | ||
tw := tar.NewWriter(&buf) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
internal/uri/type.go
Outdated
import "net/url" | ||
|
||
type Uri struct { | ||
uri string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
internal/uri/uri.go
Outdated
func (u Uri) IsLocal() bool { | ||
if len(u.Scheme()) > 2 { | ||
return false | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
couldn't this just be
return len(u.Scheme()) <= 2
internal/uri/uri.go
Outdated
|
||
// actualUrl returns the url without the query string | ||
func (u Uri) ActualUrl() string { | ||
terminator := strings.LastIndex(u.uri, ",") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
internal/uri/uri.go
Outdated
func (u Uri) Filename() string { | ||
filename := filepath.Base(u.ActualUrl()) | ||
if filename == "" { | ||
panic("filename is empty") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this reliance on file name in the user's URL seems very limiting, even at this phase of our development.
It's fine for the Uri to say "I don't know the file name" then let the consumer decide what to do. The consumer might say "ok let's download it and see what we get" or "fail if the user didn't tell us what mechanism to use".
Presumably if the mechanism is "restore" we can assume it's a "bak", correct?
internal/uri/uri.go
Outdated
return "" | ||
} | ||
|
||
func (u Uri) GetDbNameAsIdentifier() string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
internal/uri/uri_test.go
Outdated
} | ||
|
||
tests := []test{ | ||
{"https://example.com/testdb.bak,myDbName", "myDbName"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{"https://example.com/testdb.bak,myDbName" [](http://example.com/codeflow?start=0&length=44)
It's a bit odd to conflate "URL + a database name" as "Uri"
The fact that --use
flag and "use" command take this format as a parameter is specific to the semantic of those commands, correct?
I would expect these strings to be deserialized into something more meaningful like
type DatabaseFile struct {
Source *url.URL
DatabaseName string
}
internal/uri/uri_test.go
Outdated
{"https://example.com/test.foo", "test"}, | ||
{"https://example.com/test.foo,test", "test"}, | ||
{"https://example.com/test.7z,tql_name", "tsql_name"}, | ||
{"https://example.com/test.mdf,tsql_name?foo=bar", "tsql_name"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how does putting the query portion of the URL after the comma work? It doesn't look like valid input.
A URL with a query would be
https://example.com/test.mdf?version=10,mydatabase
return false | ||
} else { | ||
return true | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I propose we pick one of three styles to avoid superfluous "else" blocks
1
return i.extractor != nil
2
func (i *ingest) IsExtractionNeeded() needed bool {
i.extractor = extract.NewExtractor(i.uri.FileExtension(), i.controller)
needed = iExtractor != nil
}
3
func (i *ingest) IsExtractionNeeded() bool {
i.extractor = extract.NewExtractor(i.uri.FileExtension(), i.controller)
if i.extractor == nil {
return false
}
return true
}
My vote is 1 .
i.options.Filename = i.uri.Filename() | ||
|
||
if i.options.Filename == "" { | ||
panic("filename is empty") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
alterDefaultDb := fmt.Sprintf( | ||
"ALTER LOGIN [%s] WITH DEFAULT_DATABASE = [%s]", | ||
username, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
user names need to be bracket escaped as well.
Fundamentally, we need a package with the same helper functions SMO has for safe scripting. See https://github.com/microsoft/sqlmanagementobjects/blob/54e95cd095e232e43e711e65a83d55514ddc5e0e/src/Microsoft/SqlServer/Management/Smo/SqlSmoObject.cs#L329
internal/uri/factory.go
Outdated
@@ -0,0 +1,17 @@ | |||
package uri |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add the copyright header to your new files
func (e *sevenZip) IsInstalled(containerId string) bool { | ||
e.containerId = containerId | ||
|
||
return false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"wget", | ||
"-O", | ||
"/opt/7-zip/7-zip.tar", | ||
"https://7-zip.org/a/7z2201-linux-x64.tar.xz"}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
containerId string | ||
} | ||
|
||
func (e *sevenZip) Initialize(controller *container.Controller) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can we abstract away the differences between
"SQL running in a container", "SQL running on the local host", "SQL running in the cloud", and "SQL running on a remote host"?
Or would we have multiple extractor implementations and this one should be renamed "sevenZipInLinuxX64Container" and we'd have others like "windowsZipLocalHost" ?
I don't see how this model evolves to support the non-container scenarios.
Could you create a couple mermaid diagrams in the wiki? One would show the relationship between components like graph TD;
a -- label --> b
|
} | ||
|
||
func extractPaths(input string) []string { | ||
re := regexp.MustCompile(`Path\s*=\s*(\S+)`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
paths := extractPaths(stdout) | ||
|
||
fmt.Println(paths) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add some asserts
uri: uri, | ||
controller: controller, | ||
} | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove else
m.setFilePermissions(m.CopyToLocation() + "/" + options.Filename) | ||
if options.LdfFilename == "" { | ||
text += `CREATE DATABASE [%s] ON (FILENAME = '%s/%s') FOR ATTACH;` | ||
query(fmt.Sprintf( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it'd be a good test case to have some file names that use extended characters. Go stores strings as UTF8 but nvarchar is UTF16, while varchar is SQL-collation-dependent.
How do we guarantee the Go strings get encoded properly in the query?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe go-mssqldb does the right thing already for strings and we should use a parameterized query to let the driver do the conversion instead of manually inserting tokens in the query.
Password string | ||
|
||
Filename string | ||
LdfFilename string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
m.controller.DownloadFile( | ||
m.containerId, | ||
"https://aka.ms/sqlpackage-linux", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simplifying the getting started README for when this sqlcmd PR is pushed microsoft/go-sqlcmd#319
…o-sqlcmd into stuartpa/sqlcmd-urls
DRAFT: Not ready for code review yet.
This PR rounds out the "use" scenario for containers, to allow the casual user who doesn't know containers/linux well, to be able to get going quickly with already existing databases:
sqlcmd create mssql --use [<local file>|<remote file>]
The T/SQL database name by default becomes the --use file name (without extension). This can be overridden with a ",", e.g.
This will result in the restored database being named [adventure_works] instead of [AdventureWorksLT]
--use now supports .mdf/.bacpac and .bak files
sqlcmd create mssql --use [.mdf | .bacpac | *.bak]
--use now supports compressed file formats (this also allows a .mdf/.ldf file pair)
sqlcmd create mssql --use [.7z | .zip | .tar] # containing any of the above (inc. a .mdf/.ldf pair)
the new
sqlcmd use
subcommand allows all of the above, to add a database to an already existing containersqlcmd use [<local file>|<remote file>]
This PR also enables the open sub-command e.g.
sqlcmd open ads
to be leveraged directly insqlcmd create
, e.g.This enables a T/SQL repro (including database and T/SQL script file) to be shared in a single command line
Examples: