Skip to content

Commit a50dc00

Browse files
ci: expand SQL Server test matrix to include 2017 and 2025
1 parent c16a19e commit a50dc00

File tree

5 files changed

+106
-27
lines changed

5 files changed

+106
-27
lines changed

.github/workflows/pr-validation.yml

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,47 @@ jobs:
1111
strategy:
1212
matrix:
1313
go: ['1.24']
14-
sqlImage: ['2019-latest','2022-latest']
14+
sqlImage: ['2017-latest','2019-latest','2022-latest','2025-latest']
1515
steps:
1616
- uses: actions/checkout@v2
1717
- name: Setup go
1818
uses: actions/setup-go@v2
1919
with:
2020
go-version: '${{ matrix.go }}'
21+
- name: Install sqlcmd
22+
run: |
23+
curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc
24+
curl -sSL https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list
25+
sudo apt-get update
26+
sudo ACCEPT_EULA=Y apt-get install -y mssql-tools18
27+
echo "/opt/mssql-tools18/bin" >> $GITHUB_PATH
2128
- name: Run tests against Linux SQL
29+
shell: bash
2230
run: |
2331
go version
2432
export SQLCMDPASSWORD=$(date +%s|sha256sum|base64|head -c 32)
2533
export SQLCMDUSER=sa
2634
export SQLUSER=sa
2735
export SQLPASSWORD=$SQLCMDPASSWORD
2836
export DATABASE=master
29-
export HOST=.
37+
export HOST=localhost
38+
# Build connection string - SQL 2017 and 2025 Docker images use self-signed certificates
39+
if [ "${{ matrix.sqlImage }}" = "2017-latest" ]; then
40+
export SQLSERVER_DSN="sqlserver://${SQLUSER}:${SQLPASSWORD}@localhost:1433?database=${DATABASE}&trustServerCertificate=true"
41+
# SQL 2017's self-signed certificate has a negative serial number that Go 1.23+ rejects by default.
42+
# This GODEBUG override is only for CI testing against SQL Server 2017 and MUST NOT be used in production.
43+
export GODEBUG=x509negativeserial=1
44+
elif [ "${{ matrix.sqlImage }}" = "2025-latest" ]; then
45+
# SQL 2025 Docker image also uses a self-signed certificate
46+
export SQLSERVER_DSN="sqlserver://${SQLUSER}:${SQLPASSWORD}@localhost:1433?database=${DATABASE}&trustServerCertificate=true"
47+
else
48+
export SQLSERVER_DSN="sqlserver://${SQLUSER}:${SQLPASSWORD}@localhost:1433?database=${DATABASE}"
49+
fi
3050
docker run -m 2GB -e ACCEPT_EULA=1 -d --name sqlserver -p:1433:1433 -e SA_PASSWORD=$SQLCMDPASSWORD mcr.microsoft.com/mssql/server:${{ matrix.sqlImage }}
31-
sleep 10
51+
# Wait for SQL Server to start - retry connection up to 30 seconds
52+
for i in {1..6}; do
53+
sleep 5
54+
sqlcmd -S localhost -U sa -P "$SQLCMDPASSWORD" -C -Q "SELECT 1" > /dev/null 2>&1 && break
55+
echo "Waiting for SQL Server to start... (attempt $i)"
56+
done || { echo "SQL Server did not start within expected time; aborting tests."; exit 1; }
3257
go test -v ./...

msdsn/conn_str.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@ func Parse(dsn string) (Config, error) {
647647
if !ok {
648648
epaString = os.Getenv("MSSQL_USE_EPA")
649649
}
650-
if epaString != "" {
650+
if epaString != "" {
651651
epaEnabled, err := strconv.ParseBool(epaString)
652652
if err != nil {
653653
return p, fmt.Errorf("invalid epa enabled value '%s': %v", epaString, err)
@@ -679,7 +679,8 @@ func (p Config) URL() *url.URL {
679679
}
680680
}
681681
if p.Port > 0 {
682-
host = fmt.Sprintf("%s:%d", host, p.Port)
682+
// Use net.JoinHostPort to properly handle IPv6 addresses (e.g., [::1]:1433)
683+
host = net.JoinHostPort(host, strconv.Itoa(int(p.Port)))
683684
}
684685
q.Add(DisableRetry, fmt.Sprintf("%t", p.DisableRetry))
685686
protocolParam, ok := p.Parameters[Protocol]

msdsn/conn_str_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,57 @@ func TestConnParseRoundTripFixed(t *testing.T) {
287287
}
288288
}
289289

290+
func TestURLWithIPv6Address(t *testing.T) {
291+
tests := []struct {
292+
name string
293+
host string
294+
port uint64
295+
expected string
296+
}{
297+
{
298+
name: "IPv6 loopback with port",
299+
host: "::1",
300+
port: 1433,
301+
expected: "[::1]:1433",
302+
},
303+
{
304+
name: "IPv6 full address with port",
305+
host: "2001:db8::1",
306+
port: 1433,
307+
expected: "[2001:db8::1]:1433",
308+
},
309+
{
310+
name: "IPv4 address with port",
311+
host: "192.168.1.1",
312+
port: 1433,
313+
expected: "192.168.1.1:1433",
314+
},
315+
{
316+
name: "hostname with port",
317+
host: "localhost",
318+
port: 1433,
319+
expected: "localhost:1433",
320+
},
321+
{
322+
name: "IPv6 without port",
323+
host: "::1",
324+
port: 0,
325+
expected: "::1",
326+
},
327+
}
328+
329+
for _, tt := range tests {
330+
t.Run(tt.name, func(t *testing.T) {
331+
cfg := Config{
332+
Host: tt.host,
333+
Port: tt.port,
334+
}
335+
u := cfg.URL()
336+
assert.Equal(t, tt.expected, u.Host, "URL().Host")
337+
})
338+
}
339+
}
340+
290341
func TestServerNameInTLSConfig(t *testing.T) {
291342
var tests = []struct {
292343
dsn string

queries_go19_test.go

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func TestOutputParam(t *testing.T) {
3939
AS
4040
SELECT @strparam = REPLICATE('a', 8000)
4141
RETURN 0`
42-
sqltextdrop := `drop procedure GetTask`
42+
sqltextdrop := `DROP PROCEDURE IF EXISTS GetTask`
4343
sqltextrun := `GetTask`
4444
_, _ = db.ExecContext(ctx, sqltextdrop)
4545
_, err = db.ExecContext(ctx, sqltextcreate)
@@ -74,7 +74,7 @@ BEGIN
7474
SELECT @intparam = 10
7575
END;
7676
`
77-
sqltextdrop := `DROP PROCEDURE spwithrows;`
77+
sqltextdrop := `DROP PROCEDURE IF EXISTS spwithrows;`
7878
sqltextrun := `spwithrows`
7979

8080
db.ExecContext(ctx, sqltextdrop)
@@ -146,7 +146,7 @@ BEGIN
146146
SELECT @bid = @aid, @cstr = 'OK', @datetime = '2010-01-01T00:00:00';
147147
END;
148148
`
149-
sqltextdrop := `DROP PROCEDURE abassign;`
149+
sqltextdrop := `DROP PROCEDURE IF EXISTS abassign;`
150150
sqltextrun := `abassign`
151151

152152
db.ExecContext(ctx, sqltextdrop)
@@ -386,7 +386,7 @@ BEGIN
386386
SET @sinout = 'long_long_value'
387387
END;
388388
`
389-
sqltextdrop := `DROP PROCEDURE vinout;`
389+
sqltextdrop := `DROP PROCEDURE IF EXISTS vinout;`
390390
sqltextrun := `vinout`
391391

392392
checkConnStr(t)
@@ -484,7 +484,7 @@ BEGIN
484484
SET @binout = CONVERT(VARBINARY(4000), 'long_long_value')
485485
END;
486486
`
487-
sqltextdrop := `DROP PROCEDURE vinout;`
487+
sqltextdrop := `DROP PROCEDURE IF EXISTS vinout;`
488488
sqltextrun := `vinout`
489489

490490
checkConnStr(t)
@@ -537,7 +537,7 @@ BEGIN
537537
SET @dinout = 29342.1234
538538
END;
539539
`
540-
sqltextdrop := `DROP PROCEDURE vinout;`
540+
sqltextdrop := `DROP PROCEDURE IF EXISTS vinout;`
541541
sqltextrun := `vinout`
542542

543543
checkConnStr(t)
@@ -645,7 +645,7 @@ BEGIN
645645
SET @minout = 29342.1234
646646
END;
647647
`
648-
sqltextdrop := `DROP PROCEDURE vinout;`
648+
sqltextdrop := `DROP PROCEDURE IF EXISTS vinout;`
649649
sqltextrun := `vinout`
650650

651651
checkConnStr(t)
@@ -758,7 +758,7 @@ BEGIN
758758
SELECT @dinout
759759
END;
760760
`
761-
sqltextdrop := `DROP PROCEDURE vinout;`
761+
sqltextdrop := `DROP PROCEDURE IF EXISTS vinout;`
762762
sqltextrun := `vinout`
763763

764764
checkConnStr(t)
@@ -962,7 +962,7 @@ BEGIN
962962
SELECT @minout
963963
END;
964964
`
965-
sqltextdrop := `DROP PROCEDURE vinout;`
965+
sqltextdrop := `DROP PROCEDURE IF EXISTS vinout;`
966966
sqltextrun := `vinout`
967967

968968
checkConnStr(t)
@@ -1249,7 +1249,7 @@ BEGIN
12491249
;
12501250
END;
12511251
`
1252-
sqltextdrop := `DROP PROCEDURE abinout;`
1252+
sqltextdrop := `DROP PROCEDURE IF EXISTS abinout;`
12531253
sqltextrun := `abinout`
12541254

12551255
checkConnStr(t)
@@ -1451,7 +1451,7 @@ func TestOutputParamWithRows(t *testing.T) {
14511451
SELECT 'Row 1'
14521452
END
14531453
`
1454-
sqltextdrop := `DROP PROCEDURE spwithoutputandrows;`
1454+
sqltextdrop := `DROP PROCEDURE IF EXISTS spwithoutputandrows;`
14551455
sqltextrun := `spwithoutputandrows`
14561456

14571457
checkConnStr(t)
@@ -1556,7 +1556,7 @@ func TestParamNoName(t *testing.T) {
15561556
AS BEGIN
15571557
SELECT @intCol, @nvarcharCol, @varcharCol, @decimalCol, @nullDecimalCol
15581558
END`
1559-
sqltextdrop := `DROP PROCEDURE spnoparamname`
1559+
sqltextdrop := `DROP PROCEDURE IF EXISTS spnoparamname`
15601560
sqltextrun := `spnoparamname`
15611561

15621562
db.ExecContext(ctx, sqltextdrop)
@@ -2233,9 +2233,7 @@ func TestCancelWithNoResults(t *testing.T) {
22332233
}
22342234
}
22352235

2236-
const DropSprocWithCursor = `IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TestSqlCmd]') AND type in (N'P', N'PC'))
2237-
DROP PROCEDURE [dbo].[TestSqlCmd]
2238-
`
2236+
const DropSprocWithCursor = `DROP PROCEDURE IF EXISTS [dbo].[TestSqlCmd]`
22392237

22402238
// This query generates half a dozen tokenDoneInProc tokens which fill the channel if the app isn't scanning Rowsq
22412239
const CreateSprocWithCursor = `

tvp_go19_db_test.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,15 @@ func TestTVPGoSQLTypesWithStandardType(t *testing.T) {
100100
SMoneyNull *Money[decimal.Decimal]
101101
}
102102

103-
sqltextdropsp := `DROP PROCEDURE spwithtvpGoSQLTypesWithStandardType;`
103+
sqltextdropsp := `DROP PROCEDURE IF EXISTS spwithtvpGoSQLTypesWithStandardType;`
104104

105105
_, err = db.ExecContext(ctx, sqltextcreatetable)
106106
if err != nil {
107107
t.Fatal(err)
108108
}
109109
defer db.ExecContext(ctx, sqltextdroptable)
110110

111+
db.ExecContext(ctx, sqltextdropsp) // Clean up from previous runs
111112
_, err = db.ExecContext(ctx, sqltextcreatesp)
112113
if err != nil {
113114
t.Fatal(err)
@@ -395,14 +396,15 @@ func TestTVPGoSQLTypes(t *testing.T) {
395396
PMoneyNull Money[decimal.NullDecimal]
396397
}
397398

398-
sqltextdropsp := `DROP PROCEDURE spwithtvpGoSQLTypes;`
399+
sqltextdropsp := `DROP PROCEDURE IF EXISTS spwithtvpGoSQLTypes;`
399400

400401
_, err = db.ExecContext(ctx, sqltextcreatetable)
401402
if err != nil {
402403
t.Fatal(err)
403404
}
404405
defer db.ExecContext(ctx, sqltextdroptable)
405406

407+
db.ExecContext(ctx, sqltextdropsp) // Clean up from previous runs
406408
_, err = db.ExecContext(ctx, sqltextcreatesp)
407409
if err != nil {
408410
t.Fatal(err)
@@ -634,14 +636,15 @@ func testTVP(t *testing.T, guidConversion bool) {
634636
PMoneyNull *Money[decimal.Decimal] `db:"p_moneyNull"`
635637
}
636638

637-
sqltextdropsp := `DROP PROCEDURE spwithtvp;`
639+
sqltextdropsp := `DROP PROCEDURE IF EXISTS spwithtvp;`
638640

639641
_, err = db.ExecContext(ctx, sqltextcreatetable)
640642
if err != nil {
641643
t.Fatal(err)
642644
}
643645
defer db.ExecContext(ctx, sqltextdroptable)
644646

647+
db.ExecContext(ctx, sqltextdropsp) // Clean up from previous runs
645648
_, err = db.ExecContext(ctx, sqltextcreatesp)
646649
if err != nil {
647650
t.Fatal(err)
@@ -936,7 +939,7 @@ func testTVP_WithTag(t *testing.T, guidConversion bool) {
936939
SELECT * FROM @param2;
937940
SELECT @param3;
938941
END;`
939-
sqltextdropsp := `DROP PROCEDURE spwithtvp;`
942+
sqltextdropsp := `DROP PROCEDURE IF EXISTS spwithtvp;`
940943

941944
type TvptableRowWithSkipTag struct {
942945
PBinary []byte `db:"p_binary"`
@@ -1267,7 +1270,7 @@ func TestTVPSchema(t *testing.T) {
12671270
END;
12681271
`
12691272

1270-
dropProcedure = `drop PROCEDURE ExecTVP`
1273+
dropProcedure = `DROP PROCEDURE IF EXISTS ExecTVP`
12711274

12721275
execTvp = `exec ExecTVP @param1;`
12731276
)
@@ -1458,14 +1461,15 @@ func TestTVPUnsigned(t *testing.T) {
14581461
PintNull *uint `db:"pIntNull"`
14591462
}
14601463

1461-
sqltextdropsp := `DROP PROCEDURE spwithtvpUnsigned;`
1464+
sqltextdropsp := `DROP PROCEDURE IF EXISTS spwithtvpUnsigned;`
14621465

14631466
_, err = db.ExecContext(ctx, sqltextcreatetable)
14641467
if err != nil {
14651468
t.Fatal(err)
14661469
}
14671470
defer db.ExecContext(ctx, sqltextdroptable)
14681471

1472+
db.ExecContext(ctx, sqltextdropsp) // Clean up from previous runs
14691473
_, err = db.ExecContext(ctx, sqltextcreatesp)
14701474
if err != nil {
14711475
t.Fatal(err)
@@ -1614,7 +1618,7 @@ func TestTVPIdentity(t *testing.T) {
16141618
END;
16151619
`
16161620

1617-
dropProcedure = `drop PROCEDURE ExecIdentityTVP`
1621+
dropProcedure = `DROP PROCEDURE IF EXISTS ExecIdentityTVP`
16181622

16191623
execTvp = `exec ExecIdentityTVP @param1;`
16201624
)

0 commit comments

Comments
 (0)