diff --git a/go.mod b/go.mod index 43aa5429..ac35ddec 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,11 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/denisenkom/go-mssqldb v0.12.3 // indirect + github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.4.0 // indirect + golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9e7e28c4..f392342a 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,18 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= +github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= @@ -13,18 +23,40 @@ github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/database/database.go b/pkg/database/database.go index 76c8dfa3..79a6f7ea 100644 --- a/pkg/database/database.go +++ b/pkg/database/database.go @@ -15,6 +15,7 @@ var ( settings.DBTypePostgresql: "postgres", settings.DBTypeMySQL: "mysql", settings.DBTypeSQLite: "sqlite3", + settings.DBTypeSQLServer: "sqlserver", } ) @@ -91,6 +92,8 @@ func New(s *settings.Settings) Database { db = NewSQLite(s) case settings.DBTypeMySQL: db = NewMySQL(s) + case settings.DBTypeSQLServer: + db = NewSqlserver(s) case settings.DBTypePostgresql: fallthrough default: diff --git a/pkg/database/sqlserver.go b/pkg/database/sqlserver.go new file mode 100644 index 00000000..ca9e6dbd --- /dev/null +++ b/pkg/database/sqlserver.go @@ -0,0 +1,208 @@ +package database + +import ( + "fmt" + "strings" + + "github.com/fraenky8/tables-to-go/pkg/settings" + + // sqlserver database driver + _ "github.com/denisenkom/go-mssqldb" +) + +// Sqlserver implements the Database interface with help of GeneralDatabase. +type Sqlserver struct { + *GeneralDatabase + + defaultUserName string +} + +// NewSqlserver creates a new Sqlserver database. +func NewSqlserver(s *settings.Settings) *Sqlserver { + return &Sqlserver{ + GeneralDatabase: &GeneralDatabase{ + Settings: s, + driver: dbTypeToDriverMap[s.DbType], + }, + defaultUserName: "sa", + } +} + +// Connect connects to the database by the given data source name (dsn) of the +// concrete database. +func (ss *Sqlserver) Connect() error { + return ss.GeneralDatabase.Connect(ss.DSN()) +} + +// DSN creates the DSN String to connect to this database. +func (ss *Sqlserver) DSN() string { + user := ss.defaultUserName + if ss.Settings.User != "" { + user = ss.Settings.User + } + return fmt.Sprintf("server=%s;port=%s;user=%s;database=%s;password=%s", + ss.Settings.Host, ss.Settings.Port, user, ss.Settings.DbName, ss.Settings.Pswd) +} + +// GetTables gets all tables for a given schema by name. +func (ss *Sqlserver) GetTables() (tables []*Table, err error) { + + err = ss.Select(&tables, ` + SELECT + table_name + FROM + INFORMATION_SCHEMA.TABLES + WHERE + TABLE_SCHEMA = @p1 + ORDER BY TABLE_NAME + `, ss.Schema) + + if ss.Verbose { + if err != nil { + fmt.Println("> Error at GetTables()") + fmt.Printf("> schema: %q\r\n", ss.Schema) + } + } + + return tables, err +} + +// PrepareGetColumnsOfTableStmt prepares the statement for retrieving the +// columns of a specific table for a given database. +func (ss *Sqlserver) PrepareGetColumnsOfTableStmt() (err error) { + + ss.GetColumnsOfTableStmt, err = ss.Preparex(` + SELECT + ic.ordinal_position, + ic.column_name, + ic.data_type, + ic.column_default, + ic.is_nullable, + ic.character_maximum_length, + ic.numeric_precision, + itc.constraint_name, + itc.constraint_type + FROM information_schema.columns AS ic + LEFT JOIN information_schema.key_column_usage AS ikcu ON ic.table_name = ikcu.table_name + AND ic.table_schema = ikcu.table_schema + AND ic.column_name = ikcu.column_name + LEFT JOIN information_schema.table_constraints AS itc ON ic.table_name = itc.table_name + AND ic.table_schema = itc.table_schema + AND ikcu.constraint_name = itc.constraint_name + WHERE ic.table_name = @p1 + AND ic.table_schema = @p2 + ORDER BY ic.ordinal_position + `) + + return err +} + +// GetColumnsOfTable executes the statement for retrieving the columns of a +// specific table in a given schema. +func (ss *Sqlserver) GetColumnsOfTable(table *Table) (err error) { + + err = ss.GetColumnsOfTableStmt.Select(&table.Columns, table.Name, ss.Schema) + + if ss.Verbose { + if err != nil { + fmt.Printf("> Error at GetColumnsOfTable(%v)\r\n", table.Name) + fmt.Printf("> schema: %q\r\n", ss.Schema) + } + } + + return err +} + +// IsPrimaryKey checks if the column belongs to the primary key. +func (ss *Sqlserver) IsPrimaryKey(column Column) bool { + return strings.Contains(column.ConstraintType.String, "PRIMARY KEY") +} + +// IsAutoIncrement checks if the column is an auto_increment column. +func (ss *Sqlserver) IsAutoIncrement(column Column) bool { + return strings.Contains(column.DefaultValue.String, "IDENTITY") +} + +// GetStringDatatypes returns the string datatypes for the Sqlserver database. +// https://learn.microsoft.com/en-us/sql/t-sql/data-types/data-types-transact-sql?view=sql-server-ver16#character-strings +func (ss *Sqlserver) GetStringDatatypes() []string { + return []string{ + "char", + "varchar", + "nchar", + "nvarchar", + } +} + +// IsString returns true if colum is of type string for the Sqlserver database. +func (ss *Sqlserver) IsString(column Column) bool { + return isStringInSlice(column.DataType, ss.GetStringDatatypes()) +} + +// GetTextDatatypes returns the text datatypes for the Sqlserver database. +// https://learn.microsoft.com/en-us/sql/t-sql/data-types/data-types-transact-sql?view=sql-server-ver16#character-strings +func (ss *Sqlserver) GetTextDatatypes() []string { + return []string{ + "text", + "ntext", + "binary", + "varbinary", + "image", + } +} + +// IsText returns true if colum is of type text for the Sqlserver database. +func (ss *Sqlserver) IsText(column Column) bool { + return isStringInSlice(column.DataType, ss.GetTextDatatypes()) +} + +// GetIntegerDatatypes returns the integer datatypes for the Sqlserver database. +// https://learn.microsoft.com/en-us/sql/t-sql/data-types/data-types-transact-sql?view=sql-server-ver16#exact-numerics +func (ss *Sqlserver) GetIntegerDatatypes() []string { + return []string{ + "bigint", + "bit", + "smallint", + "int", + "tinyint", + } +} + +// IsInteger returns true if colum is of type integer for the Sqlserver database. +func (ss *Sqlserver) IsInteger(column Column) bool { + return isStringInSlice(column.DataType, ss.GetIntegerDatatypes()) +} + +// GetFloatDatatypes returns the float datatypes for the Sqlserver database. +// https://learn.microsoft.com/en-us/sql/t-sql/data-types/data-types-transact-sql?view=sql-server-ver16#exact-numerics +func (ss *Sqlserver) GetFloatDatatypes() []string { + return []string{ + "numeric", + "decimal", + "float", + "real", + } +} + +// IsFloat returns true if colum is of type float for the Sqlserver database. +func (ss *Sqlserver) IsFloat(column Column) bool { + return isStringInSlice(column.DataType, ss.GetFloatDatatypes()) +} + +// GetTemporalDatatypes returns the temporal datatypes for the Sqlserver database. +// https://learn.microsoft.com/en-us/sql/t-sql/data-types/data-types-transact-sql?view=sql-server-ver16#date-and-time +func (ss *Sqlserver) GetTemporalDatatypes() []string { + return []string{ + "date", + "datetimeoffset", + "datetime2", + "smalldatetime", + "datetime", + "time", + } +} + +// IsTemporal returns true if colum is of type temporal for the Sqlserver database. +func (ss *Sqlserver) IsTemporal(column Column) bool { + return isStringInSlice(column.DataType, ss.GetTemporalDatatypes()) +} diff --git a/pkg/settings/settings.go b/pkg/settings/settings.go index 1fc2bd52..ae29fe43 100644 --- a/pkg/settings/settings.go +++ b/pkg/settings/settings.go @@ -14,6 +14,7 @@ const ( DBTypePostgresql DBType = "pg" DBTypeMySQL DBType = "mysql" DBTypeSQLite DBType = "sqlite3" + DBTypeSQLServer DBType = "sqlserver" ) // Set sets the datatype for the custom type for the flag package. @@ -122,6 +123,7 @@ var ( DBTypePostgresql: true, DBTypeMySQL: true, DBTypeSQLite: true, + DBTypeSQLServer: true, } // supportedOutputFormats represents the supported output formats @@ -135,6 +137,7 @@ var ( DBTypePostgresql: "5432", DBTypeMySQL: "3306", DBTypeSQLite: "", + DBTypeSQLServer: "1433", } // supportedNullTypes represents the supported types of NULL types diff --git a/vendor/modules.txt b/vendor/modules.txt index 05afdacd..ffdd74f2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,9 +1,22 @@ # github.com/davecgh/go-spew v1.1.1 ## explicit github.com/davecgh/go-spew/spew +# github.com/denisenkom/go-mssqldb v0.12.3 +## explicit; go 1.13 +github.com/denisenkom/go-mssqldb +github.com/denisenkom/go-mssqldb/internal/cp +github.com/denisenkom/go-mssqldb/internal/decimal +github.com/denisenkom/go-mssqldb/internal/querytext +github.com/denisenkom/go-mssqldb/msdsn # github.com/go-sql-driver/mysql v1.6.0 ## explicit; go 1.10 github.com/go-sql-driver/mysql +# github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe +## explicit +github.com/golang-sql/civil +# github.com/golang-sql/sqlexp v0.1.0 +## explicit; go 1.16 +github.com/golang-sql/sqlexp # github.com/iancoleman/strcase v0.2.0 ## explicit; go 1.16 github.com/iancoleman/strcase @@ -29,6 +42,9 @@ github.com/stretchr/objx ## explicit; go 1.13 github.com/stretchr/testify/assert github.com/stretchr/testify/mock +# golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d +## explicit; go 1.17 +golang.org/x/crypto/md4 # golang.org/x/text v0.3.7 ## explicit; go 1.17 golang.org/x/text/cases