Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions localization/l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
Expand Up @@ -1519,6 +1519,7 @@
"DacFx service is not available. Publish and generate script operations cannot be performed.": "DacFx service is not available. Publish and generate script operations cannot be performed.",
"DacFx service is not available. Profile loaded without deployment options. Publish and generate script operations cannot be performed.": "DacFx service is not available. Profile loaded without deployment options. Publish and generate script operations cannot be performed.",
"Failed to list databases": "Failed to list databases",
"Failed to fetch Docker container tags: {0}": "Failed to fetch Docker container tags: {0}",
"Schema Compare": "Schema Compare",
"Options have changed. Recompare to see the comparison?": "Options have changed. Recompare to see the comparison?",
"Failed to generate script: '{0}'/{0} is the error message returned from the generate script operation": {
Expand Down
3 changes: 3 additions & 0 deletions localization/xliff/vscode-mssql.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1484,6 +1484,9 @@
<source xml:lang="en">Failed to establish connection with ID &quot;{0}&quot;. Please check connection details and network connectivity.</source>
<note>{0} is the connection ID</note>
</trans-unit>
<trans-unit id="++CODE++5da8436d7f85f325dbcb03e43c8b81f49b13fa6997b2e200ff9f5272cb577fc5">
<source xml:lang="en">Failed to fetch Docker container tags: {0}</source>
</trans-unit>
<trans-unit id="++CODE++fa3e091290b3ba78f8eaf354e98df990932f0b9bed9c453f61ac1a887867da16">
<source xml:lang="en">Failed to fetch user tokens.</source>
</trans-unit>
Expand Down
3 changes: 3 additions & 0 deletions src/constants/locConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1360,6 +1360,9 @@ export class PublishProject {
"DacFx service is not available. Profile loaded without deployment options. Publish and generate script operations cannot be performed.",
);
public static FailedToListDatabases = l10n.t("Failed to list databases");
public static FailedToFetchContainerTags = (errorMessage: string) => {
return l10n.t("Failed to fetch Docker container tags: {0}", errorMessage);
};
}

export class SchemaCompare {
Expand Down
66 changes: 0 additions & 66 deletions src/deployment/dockerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -995,72 +995,6 @@ export async function getAllSqlServerContainerTags(): Promise<string[]> {
}
}

/**
* Retrieves and filters SQL Server container tags based on project target version.
* Used by Publish Project dialog to provide granular tag selection.
*
* @param targetVersion - The SQL Server version (e.g., "160" for SQL Server 2022)
* @returns Sorted array of tags filtered by version and organized by year
*/
export async function getSqlServerContainerTagsForTargetVersion(
targetVersion?: string,
): Promise<FormItemOptions[]> {
try {
const allTags = await getAllSqlServerContainerTags();
if (!allTags || allTags.length === 0) {
return [];
}

// Extract year from tag (e.g., "2025-RC1-ubuntu-24.04" -> "2025")
const extractYear = (tag: string): string | undefined => {
const year = tag.slice(0, 4);
return /^\d{4}$/.test(year) ? year : undefined;
};

// Extract version number from tag (e.g., "2025-RC1-ubuntu-24.04" -> 2025)
const extractVersionNumber = (tag: string): number | undefined => {
const year = extractYear(tag);
return year ? parseInt(year, 10) : undefined;
};

// Determine minimum version based on target
let minVersion = 2017; // Default to SQL Server 2017
if (targetVersion) {
const versionNum = parseInt(targetVersion, 10);
if (versionNum >= 160)
minVersion = 2022; // SQL Server 2022
else if (versionNum >= 150)
minVersion = 2019; // SQL Server 2019
else if (versionNum >= 140) minVersion = 2017; // SQL Server 2017
}

// Filter and sort tags
const sortedTags = allTags
.filter((tag) => {
const version = extractVersionNumber(tag);
return version === undefined || version >= minVersion;
})
.sort((a, b) => (a.indexOf("latest") > 0 ? -1 : a.localeCompare(b)));

// Move "latest" to the very first position (as default selection)
const latestIndex = sortedTags.indexOf("latest");
if (latestIndex > 0) {
// Only move if it's not already at position 0
sortedTags.splice(latestIndex, 1);
sortedTags.unshift("latest");
}

// Convert to FormItemOptions format
return sortedTags.map((tag) => ({
value: tag,
displayName: tag,
}));
} catch (e) {
dockerLogger.appendLine(`Error filtering SQL Server container tags: ${getErrorMessage(e)}`);
return [];
}
}

/**
* Retrieves the SQL Server container versions from the Microsoft Container Registry.
* Returns a simplified year-based list (2025, 2022, 2019, 2017) for the deployment UI.
Expand Down
79 changes: 79 additions & 0 deletions src/publishProject/projectUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import * as constants from "../constants/constants";
import { SqlProjectsService } from "../services/sqlProjectsService";
import { promises as fs } from "fs";
import { DOMParser } from "@xmldom/xmldom";
import { getSqlServerContainerVersions, dockerLogger } from "../deployment/dockerUtils";
import { FormItemOptions } from "../sharedInterfaces/form";
import { getErrorMessage } from "../utils/utils";

/**
* Checks if preview features are enabled in VS Code settings for SQL Database Projects.
Expand All @@ -25,6 +28,7 @@ export function isPreviewFeaturesEnabled(): boolean {
/**
* Target platforms for a SQL project - these are user-facing display names shown in the VS Code UI.
* The corresponding internal version numbers used by DacFx are defined in targetPlatformToVersion map.
* Maps Dacfx Microsoft.Data.Tools.Schema.SchemaModel.SqlPlatformNames to vscode display names.
*/
export const enum SqlTargetPlatform {
sqlServer2012 = "SQL Server 2012",
Expand All @@ -33,6 +37,7 @@ export const enum SqlTargetPlatform {
sqlServer2017 = "SQL Server 2017",
sqlServer2019 = "SQL Server 2019",
sqlServer2022 = "SQL Server 2022",
sqlServer2025 = "SQL Server 2025",
sqlAzure = "Azure SQL Database",
sqlDW = "Azure Synapse SQL Pool",
sqlDwServerless = "Azure Synapse Serverless SQL Pool",
Expand All @@ -51,13 +56,28 @@ export const targetPlatformToVersion: Map<string, string> = new Map<string, stri
[SqlTargetPlatform.sqlServer2017, "140"],
[SqlTargetPlatform.sqlServer2019, "150"],
[SqlTargetPlatform.sqlServer2022, "160"],
[SqlTargetPlatform.sqlServer2025, "170"],
[SqlTargetPlatform.sqlAzure, "AzureV12"],
[SqlTargetPlatform.sqlDW, "Dw"],
[SqlTargetPlatform.sqlDwServerless, "Serverless"],
[SqlTargetPlatform.sqlDwUnified, "DwUnified"],
[SqlTargetPlatform.sqlDbFabric, "DbFabric"],
]);

/**
* Maps DSP version numbers to SQL Server release years.
* Add new versions here as they become available.
*/
const DSP_VERSION_TO_YEAR: Map<number, number> = new Map([
[170, 2025], // SQL Server 2025
[160, 2022], // SQL Server 2022
[150, 2019], // SQL Server 2019
[140, 2017], // SQL Server 2017
[130, 2016], // SQL Server 2016
[120, 2014], // SQL Server 2014
[110, 2012], // SQL Server 2012
]);

/**
* Get project properties from the tools service and extract the target platform version portion
* of the DatabaseSchemaProvider (e.g. 130, 150, 160, AzureV12, AzureDw, etc.).
Expand Down Expand Up @@ -143,6 +163,65 @@ export function validateSqlServerPortNumber(port: number): boolean {
return Number.isInteger(port) && port >= 1 && port <= constants.MAX_PORT_NUMBER;
}

/**
* Retrieves and filters SQL Server container tags based on project target version.
* Used by Publish Project dialog to provide granular tag selection.
*
* This follows the same filtering logic as ADS (Azure Data Studio):
* - If target version is known: filter to show versions >= target version year
* - If target version is unknown: fallback to max available version year (like ADS)
*
* @param targetVersion - The SQL Server version (e.g., "160" for SQL Server 2022)
* @returns Sorted array of tags filtered by version from deployment UI versions
*/
export async function getSqlServerContainerTagsForTargetVersion(
targetVersion?: string,
): Promise<FormItemOptions[]> {
try {
// Get the deployment UI versions first
const deploymentVersions = await getSqlServerContainerVersions();
if (!deploymentVersions || deploymentVersions.length === 0) {
return [];
}

const yearToOptionMap = new Map<number, FormItemOptions>();
for (const option of deploymentVersions) {
const year = parseInt(option.value);
if (!isNaN(year)) {
yearToOptionMap.set(year, option);
}
}

if (yearToOptionMap.size === 0) {
return deploymentVersions;
}

const availableYears = Array.from(yearToOptionMap.keys());
const maxYear = Math.max(...availableYears);

// Determine minimum year based on target version
let minYear: number = maxYear;
if (targetVersion) {
const versionNum = parseInt(targetVersion);
const mappedYear = DSP_VERSION_TO_YEAR.get(versionNum);
minYear = mappedYear ?? maxYear;
}

// Filter the image tags that are >= minYear
const filteredVersions: FormItemOptions[] = [];
for (const [year, option] of yearToOptionMap.entries()) {
if (year >= minYear) {
filteredVersions.push(option);
}
}

return filteredVersions;
} catch (e) {
dockerLogger.error(`Error filtering SQL Server container tags: ${getErrorMessage(e)}`);
return [];
}
}

/**
* Read SQLCMD variables from publish profile text
* @param profileText Publish profile XML text
Expand Down
19 changes: 9 additions & 10 deletions src/publishProject/publishProjectWebViewController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { parsePublishProfileXml, readProjectProperties } from "./projectUtils";
import { SqlProjectsService } from "../services/sqlProjectsService";
import { Deferred } from "../protocol";
import { TelemetryViews, TelemetryActions } from "../sharedInterfaces/telemetry";
import { getSqlServerContainerTagsForTargetVersion } from "../deployment/dockerUtils";
import { getSqlServerContainerTagsForTargetVersion } from "../publishProject/projectUtils";
import { hasAnyMissingRequiredValues, getErrorMessage } from "../utils/utils";

export class PublishProjectWebViewController extends FormWebviewController<
Expand Down Expand Up @@ -146,7 +146,7 @@ export class PublishProjectWebViewController extends FormWebviewController<
}
} catch (error) {
// Log error and send telemetry, but keep dialog resilient
console.error("Failed to read project properties:", error);
this.logger.error("Failed to read project properties:", error);
sendErrorEvent(
TelemetryViews.SqlProjects,
TelemetryActions.PublishProjectChanges,
Expand All @@ -169,16 +169,15 @@ export class PublishProjectWebViewController extends FormWebviewController<
try {
const tagOptions =
await getSqlServerContainerTagsForTargetVersion(projectTargetVersion);
if (tagOptions && tagOptions.length > 0) {
tagComponent.options = tagOptions;

// Set default to first option (most recent -latest) if not already set
if (!this.state.formState.containerImageTag && tagOptions[0]) {
this.state.formState.containerImageTag = tagOptions[0].value;
}
tagComponent.options = tagOptions;
if (!this.state.formState.containerImageTag && tagOptions.length > 0) {
this.state.formState.containerImageTag = tagOptions[0].value;
}
} catch (error) {
console.error("Failed to fetch Docker container tags:", error);
this.state.formMessage = {
message: Loc.FailedToFetchContainerTags(getErrorMessage(error)),
intent: "error",
};
}
}

Expand Down
21 changes: 21 additions & 0 deletions test/unit/publishProjectWebViewController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { validateSqlServerPassword } from "../../src/deployment/dockerUtils";
import { stubVscodeWrapper } from "./utils";
import { PublishTarget } from "../../src/sharedInterfaces/publishDialog";
import { SqlProjectsService } from "../../src/services/sqlProjectsService";
import * as dockerUtils from "../../src/deployment/dockerUtils";
import * as projectUtils from "../../src/publishProject/projectUtils";

suite("PublishProjectWebViewController Tests", () => {
let sandbox: sinon.SinonSandbox;
Expand Down Expand Up @@ -294,6 +296,25 @@ suite("PublishProjectWebViewController Tests", () => {
);
});

test("getSqlServerContainerTagsForTargetVersion filters versions correctly for SQL Server 2022", async () => {
// Mock deployment versions that would be returned from getSqlServerContainerVersions()
const mockDeploymentVersions = [
{ displayName: "SQL Server 2025 image (latest)", value: "2025-latest" },
{ displayName: "SQL Server 2022 image", value: "2022" },
{ displayName: "SQL Server 2019 image", value: "2019" },
{ displayName: "SQL Server 2017 image", value: "2017" },
];

sandbox.stub(dockerUtils, "getSqlServerContainerVersions").resolves(mockDeploymentVersions);

const result = await projectUtils.getSqlServerContainerTagsForTargetVersion("160");

// Should return only versions >= 2022 (2025 and 2022, filtered out 2019 and 2017)
expect(result).to.have.lengthOf(2);
expect(result[0].displayName).to.equal("SQL Server 2025 image (latest)");
expect(result[1].displayName).to.equal("SQL Server 2022 image");
});

//#region Publish Profile Section Tests
// Shared test data
const SAMPLE_PUBLISH_PROFILE_XML = `<?xml version="1.0" encoding="utf-8"?>
Expand Down